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
166 changes: 166 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,20 @@ $ 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.",
},
HelpOptions: actions.ActionHelpOptions{
Footer: getCmdConfigOptionsHelpFooter,
},
ActionResolver: newConfigOptionsAction,
OutputFormats: []output.Format{output.JsonFormat, output.TableFormat},
DefaultFormat: output.TableFormat,
})

return group
}

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

func getCmdConfigOptionsHelpFooter(*cobra.Command) string {
return generateCmdHelpSamplesBlock(map[string]string{
"List all available configuration settings in table format": output.WithHighLightFormat(
"azd config options",
),
"List all available configuration settings in JSON format": output.WithHighLightFormat(
"azd config options -o json",
),
"View a setting and set it": output.WithHighLightFormat(
"azd config options") + "\n " + output.WithHighLightFormat(
"azd config set <key> <value>",
),
})
}

// 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{
Copy link
Contributor

Choose a reason for hiding this comment

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

Real hard to read when wrapped:

Image

Copy link
Collaborator

@SophCarp SophCarp Jan 7, 2026

Choose a reason for hiding this comment

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

I was about to leave the same comment. If all of these columns are important, I would recommend making the display a JSON format by default, rather than a table.

If we want a table, I think the columns should just be "Variable", "Current Value", "Allowed Values". Add a link to the docs for the description and examples, or add a tag --verbose for full content.

Copy link
Member

Choose a reason for hiding this comment

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

JSON is not very readable, linking to docs is meh experience. User could just expand their window :-)

Copy link
Contributor

@JeffreyCA JeffreyCA Jan 9, 2026

Choose a reason for hiding this comment

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

The GUIDs in the Current Value and Example columns (defaults.subscription key) are making those columns extra wide and hard to read when wrapped. On my typical VS Code setup (left sidebar, window fully maximized), the output still wraps:

image

I feel that Current Value would make most sense to omit, since users can use azd config get? We don't include the current value in azd config options --output json either. Otherwise, JSON feels more readable to me.

Copy link
Contributor

Choose a reason for hiding this comment

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

The way we format azd config list-alpha feels more readable to me as well:

$ azd config list-alpha
Name: aca.persistDomains
Description: Do not change custom domains when deploying Azure Container Apps.
Status: Off

Name: aca.persistIngressSessionAffinity
Description: Do not change Ingress Session Affinity when deploying Azure Container Apps.
Status: Off

Name: aks.helm
Description: Enable Helm support for AKS deployments.
Status: Off

Name: aks.kustomize
Description: Enable Kustomize support for AKS deployments.
Status: Off

Name: azd.operations
Description: Extends provisioning providers with azd operations.
Status: Off

Name: deployment.stacks
Description: Enables Azure deployment stacks for ARM/Bicep based deployments.
Status: On

Name: language.custom
Description: Enables support for services to use custom language.
Status: Off

Name: llm
Description: Enables the use of LLMs in the CLI.
Status: Off

{
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
25 changes: 25 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,25 @@

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.

Examples
List all available configuration settings in JSON format
azd config options -o json

List all available configuration settings in table format
azd config options

View a setting and set it
azd config options
azd config set <key> <value>


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.
Loading
Loading