Skip to content

Support bool default in AzdMetadata and use Confirmation for boolean parameters #5384

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
2 changes: 1 addition & 1 deletion cli/azd/pkg/azure/arm_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ type AzdMetadata struct {
Type *AzdMetadataType `json:"type,omitempty"`
AutoGenerateConfig *AutoGenInput `json:"config,omitempty"`
DefaultValueExpr *string `json:"defaultValueExpr,omitempty"`
Default *string `json:"default,omitempty"`
Default any `json:"default,omitempty"`
UsageName usageName `json:"usageName,omitempty"`
}

Expand Down
4 changes: 3 additions & 1 deletion cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,9 @@ func defaultPromptValue(locationParam azure.ArmTemplateParameterDefinition) *str
azdMetadata.Type != nil && *azdMetadata.Type == azure.AzdMetadataTypeLocation &&
azdMetadata.Default != nil {
// Metadata using location type and a default location. This is the highest priority.
return azdMetadata.Default
defaultStr := fmt.Sprintf("%v", azdMetadata.Default)
tmp := defaultStr
return &tmp
Comment on lines +194 to +196
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
defaultStr := fmt.Sprintf("%v", azdMetadata.Default)
tmp := defaultStr
return &tmp
defaultStr := fmt.Sprintf("%v", azdMetadata.Default)
return &defaultStr

}

if locationParam.AllowedValues != nil {
Expand Down
34 changes: 25 additions & 9 deletions cli/azd/pkg/infra/provisioning/bicep/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,11 +354,12 @@ func (p *BicepProvider) promptForParameter(
defaultOption := options[0]
// user can override the default value with azd metadata
if azdMetadata.Default != nil {
if !slices.Contains(options, *azdMetadata.Default) {
defaultValStr := fmt.Sprintf("%v", azdMetadata.Default)
if !slices.Contains(options, defaultValStr) {
return nil, fmt.Errorf(
"default value '%s' is not in the allowed values for parameter '%s'", *azdMetadata.Default, key)
"default value '%s' is not in the allowed values for parameter '%s'", defaultValStr, key)
}
defaultOption = *azdMetadata.Default
defaultOption = defaultValStr
}

choice, err := p.console.Select(ctx, input.ConsoleOptions{
Expand All @@ -372,23 +373,38 @@ func (p *BicepProvider) promptForParameter(
}
value = (*param.AllowedValues)[choice]
} else {
var defaultValueForPrompt *string
var defaultValueForPrompt any
if azdMetadata.Default != nil {
defaultValueForPrompt = azdMetadata.Default
}
switch paramType {
case provisioning.ParameterTypeBoolean:
options := []string{"False", "True"}
choice, err := p.console.Select(ctx, input.ConsoleOptions{
defaultBool := true
if azdMetadata.Default != nil {
switch v := azdMetadata.Default.(type) {
case bool:
defaultBool = v
case string:
defaultBool = strings.EqualFold(v, "true")
default:
strVal := fmt.Sprintf("%v", v)
defaultBool = strings.EqualFold(strVal, "true")
}
}
msg := fmt.Sprintf("Would you like to set the '%s' infrastructure %s to %t?", key, securedParam, defaultBool)
confirmValue, err := p.console.Confirm(ctx, input.ConsoleOptions{
Message: msg,
Help: help,
Options: options,
DefaultValue: defaultValueForPrompt,
DefaultValue: true,
})
Comment on lines +382 to 399
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like the new prompt change could get a bit confusing, especially if the Bicep param has a default of false because of the double negative.

Bicep:

@metadata({azd: {
  default: false
}})
param param1 bool

New prompt: if the user inputs 'n', then the value becomes true in this case:

image

Old prompt, a bit more explicit:
image

@SophCarp would love to get your thoughts here as well 🙂

Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer the way of selection (y/n) in the new prompt, but it does feel confusing due to double negative. Could we ask if users would like to set 'paraml' to true (y/N)?

Copy link
Collaborator

@SophCarp SophCarp Jun 20, 2025

Choose a reason for hiding this comment

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

hmmm. By default, param1 would be false. y/n isn't the same as true/false, but rather "confirm/change". "default" should be included in the question, because otherwise a customer might be confused why they're being asked a double negative question.

Maybe :

? 'param1's default value is false. Keep default value? y/n

or:

? Infrastructure parameter 'param1' has a default value of 'false'. Keep default value?
> Keep (param1=false)
Change (param1=true) 

or:

? Confirm infrastructure parameter 'param1' = false:
> Yes (param1=false)
   No (param1=true)

or:

? Confirm infrastructure parameter 'param1' = false: y/n

Is there more direct wording than "infrastructure parameter"? This is different from "environment variable", right?

if err != nil {
return nil, err
}
value = (options[choice] == "True")
if confirmValue {
value = defaultBool
} else {
value = !defaultBool
}
case provisioning.ParameterTypeNumber:
userValue, err := promptWithValidation(ctx, p.console, input.ConsoleOptions{
Message: msg,
Expand Down
19 changes: 8 additions & 11 deletions cli/azd/pkg/infra/provisioning/bicep/prompt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ func TestPromptForParameter(t *testing.T) {
{"emptyString", "string", "", ""},
{"int", "int", "1", 1},
{"intNegative", "int", "-1", -1},
{"boolTrue", "bool", 0, false},
{"boolFalse", "bool", 1, true},
{"boolTrue", "bool", true, true},
{"boolFalse", "bool", false, false},
{"arrayParam", "array", `["hello", "world"]`, []any{"hello", "world"}},
{"objectParam", "object", `{"hello": "world"}`, map[string]any{"hello": "world"}},
{"secureObject", "secureObject", `{"hello": "world"}`, map[string]any{"hello": "world"}},
Expand All @@ -52,20 +52,17 @@ func TestPromptForParameter(t *testing.T) {

p := createBicepProvider(t, mockContext)

if _, ok := tc.provided.(int); ok {
mockContext.Console.WhenSelect(func(options input.ConsoleOptions) bool {
return strings.Contains(options.Message, "for the 'testParam' infrastructure parameter")
switch tc.paramType {
case "bool":
mockContext.Console.WhenConfirm(func(options input.ConsoleOptions) bool {
return strings.Contains(options.Message, "testParam")
}).Respond(tc.provided)
} else {
default:
mockContext.Console.WhenPrompt(func(options input.ConsoleOptions) bool {
return strings.Contains(options.Message, "for the 'testParam' infrastructure parameter")
return strings.Contains(options.Message, "testParam")
}).Respond(tc.provided)
}

mockContext.Console.WhenPrompt(func(options input.ConsoleOptions) bool {
return true
}).Respond(tc.provided)

value, err := p.promptForParameter(*mockContext.Context, "testParam", azure.ArmTemplateParameterDefinition{
Type: tc.paramType,
}, nil)
Expand Down
Loading