Skip to content
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
33 changes: 29 additions & 4 deletions cli/azd/cmd/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package cmd

import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -1500,10 +1501,18 @@ func newEnvConfigSetCmd() *cobra.Command {
return &cobra.Command{
Use: "set <path> <value>",
Short: "Sets a configuration value in the environment.",
Long: "Sets a configuration value in the environment's config.json file.",
Args: cobra.ExactArgs(2),
Long: `Sets a configuration value in the environment's config.json file.

Values are automatically parsed as JSON types when possible. Booleans (true/false),
numbers (42, 3.14), arrays ([...]), and objects ({...}) are stored with their native
JSON types. Plain text values are stored as strings. To force a JSON-typed value to be
stored as a string, wrap it in JSON quotes (e.g. '"true"' or '"8080"').`,
Args: cobra.ExactArgs(2),
Example: `$ azd env config set myapp.endpoint https://example.com
$ azd env config set myapp.debug true`,
$ azd env config set myapp.debug true
$ azd env config set myapp.count 42
$ azd env config set infra.parameters.tags '{"env":"dev"}'
$ azd env config set myapp.port '"8080"'`,
}
}

Expand Down Expand Up @@ -1567,7 +1576,7 @@ func (a *envConfigSetAction) Run(ctx context.Context) (*actions.ActionResult, er
path := a.args[0]
value := a.args[1]

err = env.Config.Set(path, value)
err = env.Config.Set(path, parseConfigValue(value))
if err != nil {
return nil, fmt.Errorf("failed setting configuration value '%s' to '%s'. %w", path, value, err)
}
Expand All @@ -1579,6 +1588,22 @@ func (a *envConfigSetAction) Run(ctx context.Context) (*actions.ActionResult, er
return nil, nil
}

// parseConfigValue attempts to parse a string value as a JSON type (bool, number, array, object).
// If parsing fails or the result is null, the original string is returned.
// JSON-quoted strings (e.g. `"true"`) are returned as their unquoted value,
// allowing users to force string type for values that would otherwise parse as bool/number.
func parseConfigValue(s string) any {
var parsed any
if err := json.Unmarshal([]byte(s), &parsed); err != nil {
return s
}
// null should remain as the string "null", not a nil value
if parsed == nil {
return s
}
return parsed
}

// azd env config unset <path>

func newEnvConfigUnsetCmd() *cobra.Command {
Expand Down
112 changes: 109 additions & 3 deletions cli/azd/cmd/env_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ func TestEnvConfigSet(t *testing.T) {
expectedConfig: map[string]any{
"app": map[string]any{
"endpoint": "https://example.com",
"port": "8080",
"port": float64(8080),
},
},
expectError: false,
Expand All @@ -246,6 +246,112 @@ func TestEnvConfigSet(t *testing.T) {
},
expectError: false,
},
{
name: "SetBoolValueTrue",
initialConfig: map[string]any{},
path: "infra.parameters.testBool",
value: "true",
expectedConfig: map[string]any{
"infra": map[string]any{
"parameters": map[string]any{
"testBool": true,
},
},
},
expectError: false,
},
{
name: "SetBoolValueFalse",
initialConfig: map[string]any{},
path: "infra.parameters.testBool",
value: "false",
expectedConfig: map[string]any{
"infra": map[string]any{
"parameters": map[string]any{
"testBool": false,
},
},
},
expectError: false,
},
{
name: "SetNumberValue",
initialConfig: map[string]any{},
path: "infra.parameters.count",
value: "42",
expectedConfig: map[string]any{
"infra": map[string]any{
"parameters": map[string]any{
"count": float64(42),
},
},
},
expectError: false,
},
{
name: "SetArrayValue",
initialConfig: map[string]any{},
path: "infra.parameters.testStrings",
value: `["one", "two", "three"]`,
expectedConfig: map[string]any{
"infra": map[string]any{
"parameters": map[string]any{
"testStrings": []any{"one", "two", "three"},
},
},
},
expectError: false,
},
{
name: "SetObjectValue",
initialConfig: map[string]any{},
path: "infra.parameters.tags",
value: `{"env":"dev","team":"platform"}`,
expectedConfig: map[string]any{
"infra": map[string]any{
"parameters": map[string]any{
"tags": map[string]any{"env": "dev", "team": "platform"},
},
},
},
expectError: false,
},
{
name: "SetPlainStringValue",
initialConfig: map[string]any{},
path: "app.name",
value: "my-app",
expectedConfig: map[string]any{
"app": map[string]any{
"name": "my-app",
},
},
expectError: false,
},
{
name: "SetNullAsString",
initialConfig: map[string]any{},
path: "app.value",
value: "null",
expectedConfig: map[string]any{
"app": map[string]any{
"value": "null",
},
},
expectError: false,
},
{
name: "SetQuotedJsonForcesStringType",
initialConfig: map[string]any{},
path: "app.flag",
value: `"true"`,
expectedConfig: map[string]any{
"app": map[string]any{
"flag": "true",
},
},
expectError: false,
},
Comment thread
vhvb1989 marked this conversation as resolved.
}

for _, tt := range tests {
Expand Down Expand Up @@ -552,7 +658,7 @@ func TestEnvConfigMultipleOperations(t *testing.T) {
err = json.Unmarshal(buf.Bytes(), &result)
require.NoError(t, err)
require.Equal(t, "https://example.com", result["endpoint"])
require.Equal(t, "8080", result["port"])
require.Equal(t, float64(8080), result["port"])

// Unset one value
unsetFlags := &envConfigUnsetFlags{}
Expand Down Expand Up @@ -584,5 +690,5 @@ func TestEnvConfigMultipleOperations(t *testing.T) {
var result2 map[string]any
err = json.Unmarshal(buf.Bytes(), &result2)
require.NoError(t, err)
require.Equal(t, map[string]any{"port": "8080"}, result2)
require.Equal(t, map[string]any{"port": float64(8080)}, result2)
}
Loading