cli/azd/cmd/config.go (418 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. package cmd import ( "context" "fmt" "io" "maps" "path/filepath" "runtime" "slices" "strings" "github.com/MakeNowJust/heredoc/v2" "github.com/azure/azure-dev/cli/azd/cmd/actions" "github.com/azure/azure-dev/cli/azd/internal" "github.com/azure/azure-dev/cli/azd/pkg/alpha" "github.com/azure/azure-dev/cli/azd/pkg/config" "github.com/azure/azure-dev/cli/azd/pkg/input" "github.com/azure/azure-dev/cli/azd/pkg/output" "github.com/azure/azure-dev/cli/azd/pkg/output/ux" "github.com/spf13/cobra" ) var userConfigPath string // Setup account command category func configActions(root *actions.ActionDescriptor, rootOptions *internal.GlobalCommandOptions) *actions.ActionDescriptor { userConfigDir, err := config.GetUserConfigDir() if rootOptions.GenerateStaticHelp { userConfigPath = heredoc.Doc(`the configuration path. The default value of the config directory is: * ` + output.WithBackticks(`$HOME/.azd`) + ` on Linux and macOS * ` + output.WithBackticks(`%USERPROFILE%\.azd`) + ` on Windows The configuration directory can be overridden by specifying a path in the AZD_CONFIG_DIR environment variable`) } else if err != nil { userConfigPath = output.WithBackticks(filepath.Join("$AZURE_CONFIG_DIR", "config.json")) } else { userConfigPath = output.WithBackticks(filepath.Join(userConfigDir, "config.json")) } var defaultConfigPath string if runtime.GOOS == "windows" { defaultConfigPath = filepath.Join("%USERPROFILE%", ".azd") } else { defaultConfigPath = filepath.Join("$HOME", ".azd") } var helpConfigPaths string if rootOptions.GenerateStaticHelp { //nolint:lll helpConfigPaths = heredoc.Doc(` Available since ` + output.WithBackticks("azure-dev-cli_0.4.0-beta.1") + `. The easiest way to configure ` + output.WithBackticks("azd") + ` for the first time is to run [` + output.WithBackticks("azd init") + `](#azd-init). The subscription and location you select will be stored in the ` + output.WithBackticks("config.json") + ` file located in the config directory. To configure ` + output.WithBackticks("azd") + ` anytime afterwards, you'll use [` + output.WithBackticks("azd config set") + `](#azd-config-set). The default value of the config directory is: * $HOME/.azd on Linux and macOS * %USERPROFILE%\.azd on Windows `) } else { helpConfigPaths = heredoc.Docf(` The easiest way to initially configure azd is to run %s. The subscription and location you select will be stored at %s. The default configuration path is %s.`, output.WithBackticks("azd init"), userConfigPath, output.WithBackticks(defaultConfigPath)) } longDescription := heredoc.Docf(` Manage the Azure Developer CLI user configuration, which includes your default Azure subscription and location. %s The configuration directory can be overridden by specifying a path in the AZD_CONFIG_DIR environment variable.`, helpConfigPaths) group := root.Add("config", &actions.ActionDescriptorOptions{ Command: &cobra.Command{ Use: "config", Short: "Manage azd configurations (ex: default Azure subscription, location).", Long: longDescription, }, HelpOptions: actions.ActionHelpOptions{ Description: getCmdConfigHelpDescription, Footer: getCmdConfigHelpFooter, }, GroupingOptions: actions.CommandGroupOptions{ RootLevelHelp: actions.CmdGroupConfig, }, }) group.Add("show", &actions.ActionDescriptorOptions{ Command: &cobra.Command{ Short: "Show all the configuration values.", Long: `Show all configuration values in ` + userConfigPath + `.`, }, ActionResolver: newConfigShowAction, OutputFormats: []output.Format{output.JsonFormat}, DefaultFormat: output.JsonFormat, }) group.Add("list", &actions.ActionDescriptorOptions{ Command: &cobra.Command{ Short: "List all the configuration values. (Deprecated. Use azd config show)", Hidden: true, }, ActionResolver: newConfigListAction, OutputFormats: []output.Format{output.JsonFormat}, DefaultFormat: output.JsonFormat, }) group.Add("get", &actions.ActionDescriptorOptions{ Command: &cobra.Command{ Use: "get <path>", Short: "Gets a configuration.", Long: `Gets a configuration in ` + userConfigPath + `.`, Args: cobra.ExactArgs(1), }, ActionResolver: newConfigGetAction, OutputFormats: []output.Format{output.JsonFormat}, DefaultFormat: output.JsonFormat, }) group.Add("set", &actions.ActionDescriptorOptions{ Command: &cobra.Command{ Use: "set <path> <value>", Short: "Sets a configuration.", Long: `Sets a configuration in ` + userConfigPath + `.`, Args: cobra.ExactArgs(2), Example: `$ azd config set defaults.subscription <yourSubscriptionID> $ azd config set defaults.location eastus`, }, ActionResolver: newConfigSetAction, }) group.Add("unset", &actions.ActionDescriptorOptions{ Command: &cobra.Command{ Use: "unset <path>", Short: "Unsets a configuration.", Long: `Removes a configuration in ` + userConfigPath + `.`, Example: `$ azd config unset defaults.location`, Args: cobra.ExactArgs(1), }, ActionResolver: newConfigUnsetAction, }) group.Add("reset", &actions.ActionDescriptorOptions{ Command: &cobra.Command{ Short: "Resets configuration to default.", Long: `Resets all configuration in ` + userConfigPath + ` to the default.`, }, ActionResolver: newConfigResetAction, FlagsResolver: newConfigResetFlags, }) group.Add("list-alpha", &actions.ActionDescriptorOptions{ Command: &cobra.Command{ Short: "Display the list of available features in alpha stage.", }, HelpOptions: actions.ActionHelpOptions{ Footer: getCmdListAlphaHelpFooter, }, ActionResolver: newConfigListAlphaAction, }) return group } // azd config show type configShowAction struct { configManager config.UserConfigManager formatter output.Formatter writer io.Writer } func newConfigShowAction( configManager config.UserConfigManager, formatter output.Formatter, writer io.Writer, ) actions.Action { return &configShowAction{ configManager: configManager, formatter: formatter, writer: writer, } } // Executes the `azd config show` action func (a *configShowAction) Run(ctx context.Context) (*actions.ActionResult, error) { azdConfig, err := a.configManager.Load() if err != nil { return nil, err } values := azdConfig.Raw() if a.formatter.Kind() == output.JsonFormat { err := a.formatter.Format(values, a.writer, nil) if err != nil { return nil, fmt.Errorf("failing formatting config values: %w", err) } } return nil, nil } // azd config list - Deprecated type configListAction struct { configShow *configShowAction console input.Console } func newConfigListAction( console input.Console, configShow *configShowAction, ) actions.Action { return &configListAction{ configShow: configShow, console: console, } } func (a *configListAction) Run(ctx context.Context) (*actions.ActionResult, error) { fmt.Fprintln( a.console.Handles().Stderr, output.WithWarningFormat( "WARNING: `azd config list` is deprecated and will be removed in a future release.")) fmt.Fprintln( a.console.Handles().Stderr, "Next time use `azd config show`") return a.configShow.Run(ctx) } // azd config get <path> type configGetAction struct { configManager config.UserConfigManager formatter output.Formatter writer io.Writer args []string } func newConfigGetAction( configManager config.UserConfigManager, formatter output.Formatter, writer io.Writer, args []string, ) actions.Action { return &configGetAction{ configManager: configManager, formatter: formatter, writer: writer, args: args, } } // Executes the `azd config get <path>` action func (a *configGetAction) Run(ctx context.Context) (*actions.ActionResult, error) { azdConfig, err := a.configManager.Load() if err != nil { return nil, err } key := a.args[0] value, ok := azdConfig.Get(key) if !ok { return nil, fmt.Errorf("no value stored at path '%s'", key) } if a.formatter.Kind() == output.JsonFormat { err := a.formatter.Format(value, a.writer, nil) if err != nil { return nil, fmt.Errorf("failing formatting config values: %w", err) } } return nil, nil } // azd config set <path> <value> type configSetAction struct { configManager config.UserConfigManager args []string } func newConfigSetAction(configManager config.UserConfigManager, args []string) actions.Action { return &configSetAction{ configManager: configManager, args: args, } } // Executes the `azd config set <path> <value>` action func (a *configSetAction) Run(ctx context.Context) (*actions.ActionResult, error) { azdConfig, err := a.configManager.Load() if err != nil { return nil, err } path := a.args[0] value := a.args[1] err = azdConfig.Set(path, value) if err != nil { return nil, fmt.Errorf("failed setting configuration value '%s' to '%s'. %w", path, value, err) } return nil, a.configManager.Save(azdConfig) } // azd config unset <path> type configUnsetAction struct { configManager config.UserConfigManager args []string } func newConfigUnsetAction(configManager config.UserConfigManager, args []string) actions.Action { return &configUnsetAction{ configManager: configManager, args: args, } } // Executes the `azd config unset <path>` action func (a *configUnsetAction) Run(ctx context.Context) (*actions.ActionResult, error) { azdConfig, err := a.configManager.Load() if err != nil { return nil, err } path := a.args[0] err = azdConfig.Unset(path) if err != nil { return nil, fmt.Errorf("failed removing configuration with path '%s'. %w", path, err) } return nil, a.configManager.Save(azdConfig) } // azd config reset type configResetActionFlags struct { force bool } func newConfigResetFlags(cmd *cobra.Command) *configResetActionFlags { flags := &configResetActionFlags{} cmd.Flags().BoolVarP(&flags.force, "force", "f", false, "Force reset without confirmation.") return flags } type configResetAction struct { console input.Console configManager config.UserConfigManager flags *configResetActionFlags args []string } func newConfigResetAction( console input.Console, configManager config.UserConfigManager, flags *configResetActionFlags, args []string, ) actions.Action { return &configResetAction{ console: console, configManager: configManager, flags: flags, args: args, } } // Executes the `azd config reset` action func (a *configResetAction) Run(ctx context.Context) (*actions.ActionResult, error) { a.console.MessageUxItem(ctx, &ux.MessageTitle{ Title: "Reset configuration (azd config reset)", }) spinnerMessage := "Resetting azd configuration" a.console.ShowSpinner(ctx, spinnerMessage, input.Step) if !a.flags.force { // nolint:lll warningMessage := "WARNING: Resetting azd configuration will remove all stored values including defaults, feature flags and custom template sources.\n\n" a.console.Message(ctx, output.WithWarningFormat(warningMessage)) confirm, err := a.console.Confirm(ctx, input.ConsoleOptions{ Message: "Continue with reset?", DefaultValue: false, }) if !confirm || err != nil { a.console.StopSpinner(ctx, spinnerMessage, input.StepSkipped) if err != nil { return nil, fmt.Errorf("user cancelled reset confirmation, %w", err) } return nil, nil } } err := a.configManager.Save(config.NewEmptyConfig()) a.console.StopSpinner(ctx, spinnerMessage, input.GetStepResultFormat(err)) if err != nil { return nil, err } return &actions.ActionResult{ Message: &actions.ResultMessage{ Header: "Configuration reset", }, }, nil } func getCmdConfigHelpDescription(*cobra.Command) string { return generateCmdHelpDescription( "Manage the Azure Developer CLI user configuration.", []string{ formatHelpNote(fmt.Sprintf("The default configuration path is: %s.", output.WithLinkFormat("%HOME/.azd"), )), formatHelpNote(fmt.Sprintf("The configuration directory can be overridden by specifying a path"+ " in the %s environment variable.", output.WithBold("AZD_CONFIG_DIR"), )), formatHelpNote(fmt.Sprintf( "The default values for azd prompts like subscription and location are stored with the key: %s.", output.WithLinkFormat("defaults"), )), }) } func getCmdConfigHelpFooter(c *cobra.Command) string { return generateCmdHelpSamplesBlock(map[string]string{ "Set the default Azure subscription.": fmt.Sprintf("%s %s", output.WithHighLightFormat("azd config set defaults.subscription"), output.WithWarningFormat("<yourSubscriptionID>")), "Set the default Azure deployment location.": fmt.Sprintf("%s %s", output.WithHighLightFormat("azd config set defaults.location"), output.WithWarningFormat("<location>")), }) } type configListAlphaAction struct { alphaFeaturesManager *alpha.FeatureManager console input.Console args []string } func (a *configListAlphaAction) Run(ctx context.Context) (*actions.ActionResult, error) { features, err := a.alphaFeaturesManager.ListFeatures() if err != nil { return nil, err } featureKeys := slices.Sorted(maps.Keys(features)) var alphaOutput []string for _, alphaFeatureKey := range featureKeys { alphaFeature := features[alphaFeatureKey] alphaOutput = append(alphaOutput, strings.Join( []string{ fmt.Sprintf("Name: %s", alphaFeature.Id), fmt.Sprintf("Description: %s", alphaFeature.Description), fmt.Sprintf("Status: %s", alphaFeature.Status), }, "\n", )) } a.console.Message(ctx, strings.Join(alphaOutput, "\n\n")) // No UX output return nil, nil } func newConfigListAlphaAction( alphaFeaturesManager *alpha.FeatureManager, console input.Console, args []string) actions.Action { return &configListAlphaAction{ alphaFeaturesManager: alphaFeaturesManager, console: console, args: args, } } func getCmdListAlphaHelpFooter(*cobra.Command) string { return generateCmdHelpSamplesBlock(map[string]string{ "Displays a list of all available features in the alpha stage": output.WithHighLightFormat( "azd config list-alpha", ), "Turn on a specific alpha feature": output.WithHighLightFormat( "azd config set alpha.<feature-name> on", ), "Turn off a specific alpha feature": output.WithHighLightFormat( "azd config set alpha.<feature-name> off", ), "Turn on all alpha features": output.WithHighLightFormat( "azd config set alpha.all on", ), "Turn off all alpha features": output.WithHighLightFormat( "azd config set alpha.all off", ), }) }