Skip to content

Commit

Permalink
Merge pull request #5006 from rancher-sandbox/4562-add-reg-output-option
Browse files Browse the repository at this point in the history
Add a --output=reg|json option to `rdctl list-settings`
  • Loading branch information
ericpromislow authored Jul 5, 2023
2 parents ba5ba77 + 07445dd commit dd3e089
Show file tree
Hide file tree
Showing 8 changed files with 674 additions and 20 deletions.
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

0 comments on commit dd3e089

Please sign in to comment.