Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a --output=reg|json option to rdctl list-settings #5006

Merged
merged 17 commits into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/actions/spelling/allow.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ skopeo
ssh
ubuntu
workarounds
hklm
hkcu
qword
dword
90 changes: 90 additions & 0 deletions bats/tests/preferences/list-settings-output.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
load '../helpers/load'

RD_USE_IMAGE_ALLOW_LIST=true

@test 'factory reset' {
factory_reset
# bypass the defaults deployment file
mkdir -p "$(dirname "${PATH_CONFIG_FILE})")"
touch "$PATH_CONFIG_FILE"
}

@test 'start app' {
start_container_engine
wait_for_container_engine
}

@test 'report parameters for json' {
run rdctl list-settings '--output=json' '--reg-hive=fish'
assert_failure
assert_output --partial $'registry hive and profile can\'t be specified with json'
}

@test 'report unrecognized output-options' {
run rdctl list-settings '--output=pickle,ruff'
assert_failure
assert_output --partial $'invalid output format of "pickle,ruff"'
}

@test 'report unrecognized reg sub-options' {
run rdctl list-settings --output=reg --reg-hive=hklm --section=ruff
assert_failure
assert_output --partial "invalid registry section of 'ruff' specified"
}

@test 'generates registry output for hklm/defaults' {
run rdctl list-settings --output reg
assert_success
assert_output --partial '[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Rancher Desktop\defaults\application]'

run rdctl list-settings --output reg --reg-hive=hklm
assert_success
assert_output --partial '[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Rancher Desktop\defaults\application]'

run rdctl list-settings --output reg --reg-hive=HKLM --section=Defaults
assert_success
assert_output --partial '[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Rancher Desktop\defaults\application]'

run rdctl list-settings --output reg --section=DEFAULTS
assert_success
assert_output --partial '[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Rancher Desktop\defaults\application]'
}

@test 'generates registry output for hklm/locked' {
run rdctl list-settings --output reg --reg-hive=Hklm --section=Locked
assert_success
assert_output --partial '[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Rancher Desktop\locked\application]'
run rdctl list-settings --output reg --section=LOCKED
assert_success
assert_output --partial '[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Rancher Desktop\locked\application]'
}

@test 'generates registry output for hkcu/defaults' {
run rdctl list-settings --output reg --reg-hive=Hkcu
assert_success
assert_output --partial '[HKEY_CURRENT_USER\SOFTWARE\Policies\Rancher Desktop\defaults\application]'
run rdctl list-settings --output reg --reg-hive=hkcu --section=Defaults
assert_success
assert_output --partial '[HKEY_CURRENT_USER\SOFTWARE\Policies\Rancher Desktop\defaults\application]'
}

@test 'generates registry output for hkcu/locked' {
run rdctl list-settings --output reg --reg-hive=HKCU --section=locked
assert_success
assert_output --partial '[HKEY_CURRENT_USER\SOFTWARE\Policies\Rancher Desktop\locked\application]'
}

@test 'generates registry output' {
run rdctl list-settings --output reg
assert_success
# Just match a few of the lines near the start and the end of the output.
# The unit tests do more comprehensive output checking.
assert_output --partial '[HKEY_LOCAL_MACHINE\SOFTWARE\Policies]'
assert_output --partial '"pathManagementStrategy"="rcfiles"'
assert_output --partial '[HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Rancher Desktop\defaults\diagnostics]'
assert_output --partial '"showMuted"=dword:0'
}

@test 'needs a shutdown' {
rdctl shutdown
}
1 change: 0 additions & 1 deletion e2e/rdctl.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1406,7 +1406,6 @@ test.describe('Command server', () => {
}
});
});

test('should verify nerdctl can talk to containerd', async() => {
const { stdout } = await rdctl(['list-settings']);
const settings: Settings = JSON.parse(stdout);
Expand Down
13 changes: 8 additions & 5 deletions scripts/assets/options.go.templ
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ import (
)

/**
* The two types `serverSettingsForJSON` and `serverSettings` both reflect the settings type in the
* The two types `ServerSettingsForJSON` and `serverSettings` both reflect the settings type in the
* backend (as defined in `config/settings.ts`), but have different uses.
*
* As its name implies, the `serverSettingsForJSON` is used to generate a backend for the `rdctl set`
* As its name implies, the `ServerSettingsForJSON` is used to generate a backend for the `rdctl set`
* subcommand. Only fields that are explicitly changed by the user should be inserted into the JSON
* payload, so we use pointers that are by default nil. This way we don't inadvertently change backend
* settings to their default values.
Expand All @@ -50,7 +50,7 @@ import (
* See how the two structs are used in the `UpdateFieldsForJSON` function.
*/

type serverSettingsForJSON struct {
type ServerSettingsForJSON struct {
<%- linesForJSON %>
}

Expand Down Expand Up @@ -114,6 +114,9 @@ func qualifiedPlatformName() string {

func UpdateCommonStartAndSetCommands(cmd *cobra.Command) {
<%_ for (const flag of commandFlags) {
if (flag.flagType === 'Array') {
continue;
}
const kebabPropertyName = kebabCase(flag.propertyName); _%>
cmd.Flags().<%- flag.flagType %>Var(&specifiedSettings.<%- flag.capitalizedName %>, "<%- kebabPropertyName %>", <%- flag.defaultValue %>, "<%- flag.usageNote %>")
<%_ if (flag.aliasFor || flag.notAvailable) { _%>
Expand All @@ -122,8 +125,8 @@ func UpdateCommonStartAndSetCommands(cmd *cobra.Command) {
<%_ } _%>
}

func UpdateFieldsForJSON(flags *pflag.FlagSet) (*serverSettingsForJSON, error) {
var specifiedSettingsForJSON serverSettingsForJSON
func UpdateFieldsForJSON(flags *pflag.FlagSet) (*ServerSettingsForJSON, error) {
var specifiedSettingsForJSON ServerSettingsForJSON
changedSomething := false
<%_ for (const flag of commandFlags) {
const kebabPropertyName = kebabCase(flag.propertyName); _%>
Expand Down
37 changes: 27 additions & 10 deletions scripts/generateCliCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ interface commandFlagType {

type yamlObject = any;

type goTypeName = 'string' | 'bool' | 'int';
type goCmdFlagTypeName = 'String' | 'Bool' | 'Int';
type typeValue = goTypeName | settingsTreeType;
type goTypeName = 'string' | 'bool' | 'int' | 'array';
type goCmdFlagTypeName = 'String' | 'Bool' | 'Int' | 'Array';
type typeValue = goTypeName | settingsTreeType | 'hash';
type settingsTypeObject = { type: typeValue };
type settingsTreeType = Record<string, settingsTypeObject>;

Expand Down Expand Up @@ -202,10 +202,16 @@ class Generator {
} else {
const onlyLineParts = [indent, capitalize(propertyName), ' '];

if (includeJSONTag) {
onlyLineParts.push('*');
if (typeWrapper.type === 'array') {
onlyLineParts.push('[]string');
} else if (typeWrapper.type === 'hash') {
onlyLineParts.push('map[string]interface{}');
} else {
if (includeJSONTag) {
onlyLineParts.push('*');
}
onlyLineParts.push(typeWrapper.type);
}
onlyLineParts.push(typeWrapper.type);
if (includeJSONTag) {
onlyLineParts.push(`\`json:"${ propertyName },omitempty"\``);
}
Expand All @@ -226,6 +232,8 @@ class Generator {
return `, strconv.Itoa(specifiedSettings.${ capitalizedName })`;
case 'String':
return `, specifiedSettings.${ capitalizedName }`;
case 'Array':
return '';
}
}

Expand Down Expand Up @@ -287,14 +295,23 @@ class Generator {
case 'integer':
return this.walkPropertyInteger(propertyName, preference, notAvailable, settingsTree);
case 'array':
return this.walkPropertyArray(propertyName);
// not yet available
return this.walkPropertyArray(propertyName, preference, settingsTree);
default:
throw new Error(`walkProperty: unexpected preference.type: '${ preference.type }'`);
}
}

protected walkPropertyArray(propertyName: string): void {
console.log(`Not generating a CLI entry for property ${ propertyName }: arrays not supported.`);
protected walkPropertyArray(
propertyName: string,
preference: yamlObject,
settingsTree: settingsTreeType,
): void {
this.updateLeaf(propertyName, capitalizeParts(propertyName),
'array', 'Array', 'nil',
preference,
true,
settingsTree);
}

protected walkPropertyBoolean(
Expand Down Expand Up @@ -329,7 +346,7 @@ class Generator {
notAvailable: boolean,
settingsTree: settingsTreeType): void {
if (preference.additionalProperties) {
console.log(`Skipping ${ propertyName }: not settable from the command-line.`);
settingsTree[lastName(propertyName)] = { type: 'hash' };

return;
}
Expand Down
84 changes: 80 additions & 4 deletions src/go/rdctl/cmd/listSettings.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,37 @@ package cmd

import (
"fmt"
"github.com/rancher-sandbox/rancher-desktop/src/go/rdctl/pkg/reg"
"github.com/spf13/cobra"
"strings"
)

var outputSettingsFlags struct {
Format string
RegistryHive string
RegistryProfileType string
}

const jsonFormat = "json"
const regFormat = "reg"
const defaultsRegistrySection = "defaults"
const lockedRegistrySection = "locked"

// listSettingsCmd represents the listSettings command
var listSettingsCmd = &cobra.Command{
Use: "list-settings",
Short: "Lists the current settings.",
Long: `Lists the current settings in JSON format.`,
Long: `Lists the current settings in JSON or Windows registry-file format.
The default output format is JSON.

To convert the current settings into a registry file, run the following command:

rdctl list-commands --output reg --reg-hive=X --profile=Y

where X is either "hkcu" or "hklm", depending on whether you want to update HKEY_LOCAL_MACHINE
or HKEY_CURRENT_USER respectively (default: "hklm"),
and Y is either "defaults" or "locked", depending on which deployment profile you want to populate (default: "defaults").
`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := cobra.NoArgs(cmd, args); err != nil {
return err
Expand All @@ -35,15 +58,68 @@ var listSettingsCmd = &cobra.Command{
if err != nil {
return err
}
fmt.Println(string(result))
fmt.Println(result)
return nil
},
}

// the reg file format is directly usable only on Windows,
// but it can be created on any platform for purposes of testing or development

func init() {
rootCmd.AddCommand(listSettingsCmd)
listSettingsCmd.Flags().StringVarP(&outputSettingsFlags.Format, "output", "", jsonFormat, fmt.Sprintf("output format: %s|%s", jsonFormat, regFormat))
listSettingsCmd.Flags().StringVarP(&outputSettingsFlags.RegistryHive, "reg-hive", "", "", fmt.Sprintf(`registry hive: %s|%s (default "%s")`, reg.HklmRegistryHive, reg.HkcuRegistryHive, reg.HklmRegistryHive))
listSettingsCmd.Flags().StringVarP(&outputSettingsFlags.RegistryProfileType, "section", "", "", fmt.Sprintf(`registry section: %s|%s (default "%s")`, defaultsRegistrySection, lockedRegistrySection, defaultsRegistrySection))
}

func getListSettings() ([]byte, error) {
return processRequestForUtility(doRequest("GET", versionCommand("", "settings")))
func validateOutputFormatFlags() error {
if outputSettingsFlags.Format != jsonFormat && outputSettingsFlags.Format != regFormat {
return fmt.Errorf(`invalid output format of "%s"`, outputSettingsFlags.Format)
}
if outputSettingsFlags.Format == jsonFormat {
if outputSettingsFlags.RegistryHive != "" || outputSettingsFlags.RegistryProfileType != "" {
return fmt.Errorf("registry hive and profile can't be specified with json")
}
return nil
}
switch strings.ToLower(outputSettingsFlags.RegistryHive) {
case reg.HklmRegistryHive, reg.HkcuRegistryHive:
outputSettingsFlags.RegistryHive = strings.ToLower(outputSettingsFlags.RegistryHive)
case "":
outputSettingsFlags.RegistryHive = reg.HklmRegistryHive
default:
return fmt.Errorf("invalid registry hive of '%s' specified", outputSettingsFlags.RegistryHive)
}
switch strings.ToLower(outputSettingsFlags.RegistryProfileType) {
case defaultsRegistrySection, lockedRegistrySection:
outputSettingsFlags.RegistryProfileType = strings.ToLower(outputSettingsFlags.RegistryProfileType)
case "":
outputSettingsFlags.RegistryProfileType = defaultsRegistrySection
default:
return fmt.Errorf("invalid registry section of '%s' specified", outputSettingsFlags.RegistryProfileType)
}
return nil
}

func getListSettings() (string, error) {
err := validateOutputFormatFlags()
if err != nil {
return "", err
}
output, err := processRequestForUtility(doRequest("GET", versionCommand("", "settings")))
if err != nil {
return "", err
} else if outputSettingsFlags.Format == jsonFormat {
return string(output), nil
} else if outputSettingsFlags.Format == regFormat {
lines, err := reg.JsonToReg(outputSettingsFlags.RegistryHive, outputSettingsFlags.RegistryProfileType, string(output))
if err != nil {
return "", err
}
return strings.Join(lines, "\n"), nil
} else {
// This shouldn't happen
return "", fmt.Errorf("internal error: unexpected output format of %s", outputSettingsFlags.Format)
}
}
Loading
Loading