Skip to content
Open
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
148 changes: 148 additions & 0 deletions cli/azd/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"io"
"maps"
"os"
"path/filepath"
"runtime"
"slices"
Expand Down Expand Up @@ -169,6 +170,17 @@ $ azd config set defaults.location eastus`,
ActionResolver: newConfigListAlphaAction,
})

group.Add("options", &actions.ActionDescriptorOptions{
Command: &cobra.Command{
Short: "List all available configuration settings.",
Long: "List all possible configuration settings that can be set with azd, " +
"including descriptions and allowed values.",
},
ActionResolver: newConfigOptionsAction,
OutputFormats: []output.Format{output.JsonFormat, output.TableFormat},
DefaultFormat: output.TableFormat,
})

return group
}

Expand Down Expand Up @@ -510,3 +522,139 @@ func getCmdListAlphaHelpFooter(*cobra.Command) string {
),
})
}

// azd config options

type configOptionsAction struct {
console input.Console
formatter output.Formatter
writer io.Writer
configManager config.UserConfigManager
args []string
}

func newConfigOptionsAction(
console input.Console,
formatter output.Formatter,
writer io.Writer,
configManager config.UserConfigManager,
args []string) actions.Action {
return &configOptionsAction{
console: console,
formatter: formatter,
writer: writer,
configManager: configManager,
args: args,
}
}

func (a *configOptionsAction) Run(ctx context.Context) (*actions.ActionResult, error) {
options := config.GetAllConfigOptions()

// Load current config to show current values
currentConfig, err := a.configManager.Load()
if err != nil {
// Only ignore "file not found" errors; other errors should be logged
if !os.IsNotExist(err) {
// Log the error but continue with empty config to ensure the command still works
fmt.Fprintf(a.console.Handles().Stderr, "Warning: failed to load config: %v\n", err)
}
currentConfig = config.NewEmptyConfig()
}

if a.formatter.Kind() == output.JsonFormat {
err := a.formatter.Format(options, a.writer, nil)
if err != nil {
return nil, fmt.Errorf("failed formatting config options: %w", err)
}
return nil, nil
}

// Table format
type tableRow struct {
Key string
Description string
Type string
CurrentValue string
AllowedValues string
EnvVar string
Example string
}

var rows []tableRow
for _, option := range options {
allowedValues := ""
if len(option.AllowedValues) > 0 {
allowedValues = strings.Join(option.AllowedValues, ", ")
}

// Get current value from config
currentValue := ""
// Skip environment-only variables (those with keys starting with EnvOnlyPrefix)
if !strings.HasPrefix(option.Key, config.EnvOnlyPrefix) {
if val, ok := currentConfig.Get(option.Key); ok {
// Convert value to string representation
switch v := val.(type) {
case string:
currentValue = v
case map[string]any:
currentValue = "<object>"
case []any:
currentValue = "<array>"
default:
currentValue = fmt.Sprintf("%v", v)
}
}
}

rows = append(rows, tableRow{
Key: option.Key,
Description: option.Description,
Type: option.Type,
CurrentValue: currentValue,
AllowedValues: allowedValues,
EnvVar: option.EnvVar,
Example: option.Example,
})
}

columns := []output.Column{
{
Heading: "Key",
ValueTemplate: "{{.Key}}",
},
{
Heading: "Description",
ValueTemplate: "{{.Description}}",
},
{
Heading: "Type",
ValueTemplate: "{{.Type}}",
},
{
Heading: "Current Value",
ValueTemplate: "{{.CurrentValue}}",
},
{
Heading: "Allowed Values",
ValueTemplate: "{{.AllowedValues}}",
},
{
Heading: "Environment Variable",
ValueTemplate: "{{.EnvVar}}",
},
{
Heading: "Example",
ValueTemplate: "{{.Example}}",
},
}

err = a.formatter.Format(rows, a.writer, output.TableFormatterOptions{
Columns: columns,
})
if err != nil {
return nil, fmt.Errorf("failed formatting config options: %w", err)
}

return nil, nil
}
8 changes: 8 additions & 0 deletions cli/azd/cmd/testdata/TestFigSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,10 @@ const completionSpec: Fig.Spec = {
name: ['list-alpha'],
description: 'Display the list of available features in alpha stage.',
},
{
name: ['options'],
description: 'List all available configuration settings.',
},
{
name: ['reset'],
description: 'Resets configuration to default.',
Expand Down Expand Up @@ -1584,6 +1588,10 @@ const completionSpec: Fig.Spec = {
name: ['list-alpha'],
description: 'Display the list of available features in alpha stage.',
},
{
name: ['options'],
description: 'List all available configuration settings.',
},
{
name: ['reset'],
description: 'Resets configuration to default.',
Expand Down
16 changes: 16 additions & 0 deletions cli/azd/cmd/testdata/TestUsage-azd-config-options.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@

List all available configuration settings.

Usage
azd config options [flags]

Global Flags
-C, --cwd string : Sets the current working directory.
--debug : Enables debugging and diagnostics logging.
--docs : Opens the documentation for azd config options in your web browser.
-h, --help : Gets help for options.
--no-prompt : Accepts the default value instead of prompting, or it fails if there is no default.

Find a bug? Want to let us know how we're doing? Fill out this brief survey: https://aka.ms/azure-dev/hats.


1 change: 1 addition & 0 deletions cli/azd/cmd/testdata/TestUsage-azd-config.snap
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Usage
Available Commands
get : Gets a configuration.
list-alpha : Display the list of available features in alpha stage.
options : List all available configuration settings.
reset : Resets configuration to default.
set : Sets a configuration.
show : Show all the configuration values.
Expand Down
38 changes: 38 additions & 0 deletions cli/azd/pkg/config/config_options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package config

import (
"log"

"github.com/azure/azure-dev/cli/azd/resources"
"github.com/braydonk/yaml"
)

// EnvOnlyPrefix is the prefix used to mark environment-only configuration options
const EnvOnlyPrefix = "(env) "

// ConfigOption defines a configuration setting that can be set in azd config
type ConfigOption struct {
Key string `yaml:"key"`
Description string `yaml:"description"`
Type string `yaml:"type"`
AllowedValues []string `yaml:"allowedValues,omitempty"`
Example string `yaml:"example,omitempty"`
EnvVar string `yaml:"envVar,omitempty"`
}

var allConfigOptions []ConfigOption

func init() {
err := yaml.Unmarshal(resources.ConfigOptions, &allConfigOptions)
if err != nil {
log.Panicf("Can't unmarshal config options! %v", err)
}
}

// GetAllConfigOptions returns all available configuration options
func GetAllConfigOptions() []ConfigOption {
return allConfigOptions
}
69 changes: 69 additions & 0 deletions cli/azd/pkg/config/config_options_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package config

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestGetAllConfigOptions(t *testing.T) {
options := GetAllConfigOptions()

// Should have at least some options
require.NotEmpty(t, options)
require.Greater(t, len(options), 0)

// Check that we have the expected default options
foundDefaultsSubscription := false
foundDefaultsLocation := false
foundAlphaAll := false

for _, option := range options {
require.NotEmpty(t, option.Key, "Config option key should not be empty")
require.NotEmpty(t, option.Description, "Config option description should not be empty")
require.NotEmpty(t, option.Type, "Config option type should not be empty")

switch option.Key {
case "defaults.subscription":
foundDefaultsSubscription = true
require.Equal(t, "string", option.Type)
require.Contains(t, option.Description, "subscription")
case "defaults.location":
foundDefaultsLocation = true
require.Equal(t, "string", option.Type)
require.Contains(t, option.Description, "location")
case "alpha.all":
foundAlphaAll = true
require.Equal(t, "string", option.Type)
require.Contains(t, option.AllowedValues, "on")
require.Contains(t, option.AllowedValues, "off")
require.Equal(t, "AZD_ALPHA_ENABLE_ALL", option.EnvVar)
}
}

// Verify expected options are present
require.True(t, foundDefaultsSubscription, "defaults.subscription option should be present")
require.True(t, foundDefaultsLocation, "defaults.location option should be present")
require.True(t, foundAlphaAll, "alpha.all option should be present")
}

func TestConfigOptionStructure(t *testing.T) {
options := GetAllConfigOptions()

for _, option := range options {
// All options should have required fields
require.NotEmpty(t, option.Key)
require.NotEmpty(t, option.Description)
require.NotEmpty(t, option.Type)

// If AllowedValues is set, it should not be empty
if len(option.AllowedValues) > 0 {
for _, val := range option.AllowedValues {
require.NotEmpty(t, val, "Allowed value should not be empty")
}
}
}
}
Comment on lines +1 to +69
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The command tests only unit test the GetAllConfigOptions function but don't test the configOptionsAction itself. Consider adding an integration test that verifies the action's behavior, including JSON and table output formats, error handling, and current value display. This follows the pattern seen in auth_token_test.go where action implementations are tested directly.

Copilot uses AI. Check for mistakes.
64 changes: 64 additions & 0 deletions cli/azd/resources/config_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
- key: defaults.subscription
description: "Default Azure subscription ID to use for operations."
type: string
example: "00000000-0000-0000-0000-000000000000"
- key: defaults.location
description: "Default Azure location/region to use for deployments."
type: string
example: "eastus"
- key: alpha.all
description: "Enable or disable all alpha features at once."
type: string
allowedValues: ["on", "off"]
envVar: "AZD_ALPHA_ENABLE_ALL"
- key: alpha.aks.helm
description: "Enable Helm support for AKS deployments."
type: string
allowedValues: ["on", "off"]
envVar: "AZD_ALPHA_ENABLE_AKS_HELM"
- key: alpha.aks.kustomize
description: "Enable Kustomize support for AKS deployments."
type: string
allowedValues: ["on", "off"]
envVar: "AZD_ALPHA_ENABLE_AKS_KUSTOMIZE"
- key: alpha.aca.persistDomains
description: "Do not change custom domains when deploying Azure Container Apps."
type: string
allowedValues: ["on", "off"]
envVar: "AZD_ALPHA_ENABLE_ACA_PERSISTDOMAINS"
- key: alpha.azd.operations
description: "Extends provisioning providers with azd operations."
type: string
allowedValues: ["on", "off"]
envVar: "AZD_ALPHA_ENABLE_AZD_OPERATIONS"
- key: alpha.aca.persistIngressSessionAffinity
description: "Do not change Ingress Session Affinity when deploying Azure Container Apps."
type: string
allowedValues: ["on", "off"]
envVar: "AZD_ALPHA_ENABLE_ACA_PERSISTINGRESSSESSIONAFFINITY"
- key: alpha.deployment.stacks
description: "Enables Azure deployment stacks for ARM/Bicep based deployments."
type: string
allowedValues: ["on", "off"]
envVar: "AZD_ALPHA_ENABLE_DEPLOYMENT_STACKS"
- key: alpha.llm
description: "Enables the use of LLMs in the CLI."
type: string
allowedValues: ["on", "off"]
envVar: "AZD_ALPHA_ENABLE_LLM"
- key: alpha.language.custom
description: "Enables support for services to use custom language."
type: string
allowedValues: ["on", "off"]
envVar: "AZD_ALPHA_ENABLE_LANGUAGE_CUSTOM"
- key: template.sources
description: "Custom template sources for azd template list and azd init."
type: object
example: "template.sources.<name>.type"
- key: pipeline.config.applicationServiceManagementReference
description: "Application Service Management Reference for Azure pipeline configuration."
type: string
- key: (env) AZD_CONFIG_DIR
description: "Override the default configuration directory location."
type: envvar
Comment on lines +61 to +63
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The environment variable entry uses "(env) AZD_CONFIG_DIR" as the key, which is a special prefix pattern. This breaks the standard key convention used elsewhere in config settings. Consider separating environment-only variables into a different section of the YAML or using a dedicated field (like 'environmentOnly: true') to mark these entries, rather than encoding this information in the key itself.

Suggested change
- key: (env) AZD_CONFIG_DIR
description: "Override the default configuration directory location."
type: envvar
- key: env.AZD_CONFIG_DIR
description: "Override the default configuration directory location."
type: envvar
environmentOnly: true

Copilot uses AI. Check for mistakes.
example: "/path/to/config"
3 changes: 3 additions & 0 deletions cli/azd/resources/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ var TemplatesJson []byte
//go:embed alpha_features.yaml
var AlphaFeatures []byte

//go:embed config_options.yaml
var ConfigOptions []byte

//go:embed minimal/main.bicep
var MinimalBicep []byte

Expand Down
Loading