in cli/azd/pkg/infra/provisioning/bicep/prompt.go [228:418]
func (p *BicepProvider) promptForParameter(
ctx context.Context,
key string,
param azure.ArmTemplateParameterDefinition,
mappedToAzureLocationParams []string,
) (any, error) {
securedParam := "parameter"
isSecuredParam := param.Secure()
if isSecuredParam {
securedParam = "secured parameter"
}
msg := fmt.Sprintf("Enter a value for the '%s' infrastructure %s:", key, securedParam)
help, _ := param.Description()
azdMetadata, _ := param.AzdMetadata()
paramType := p.mapBicepTypeToInterfaceType(param.Type)
var value any
if paramType == provisioning.ParameterTypeString &&
azdMetadata.Type != nil && *azdMetadata.Type == azure.AzdMetadataTypeLocation {
// when more than one parameter is mapped to AZURE_LOCATION and AZURE_LOCATION is not set in the environment,
// AZD will prompt just once and immediately set the value in the .env for the next parameter to re-use the value
paramIsMappedToAzureLocation := slices.Contains(mappedToAzureLocationParams, key)
valueFromEnv, valueDefinedInEnv := p.env.LookupEnv(environment.LocationEnvVarName)
if paramIsMappedToAzureLocation && valueDefinedInEnv {
return valueFromEnv, nil
}
// location can be combined with allowedValues and with usageName metadata
// allowedValues == nil => all locations are allowed
// allowedValues != nil => only the locations in the allowedValues are allowed
// usageName != nil => the usageName is validated for quota for each allowed location (this is for Ai models),
// reducing the allowed locations to only those that have quota available
// usageName == nil => No quota validation is done
var allowedLocations []string
if param.AllowedValues != nil {
allowedLocations = make([]string, len(*param.AllowedValues))
for i, option := range *param.AllowedValues {
allowedLocations[i] = option.(string)
}
}
if len(azdMetadata.UsageName) > 0 {
withQuotaLocations, err := p.locationsWithQuotaFor(
ctx, p.env.GetSubscriptionId(), allowedLocations, azdMetadata.UsageName)
if err != nil {
return nil, fmt.Errorf("getting locations with quota: %w", err)
}
allowedLocations = withQuotaLocations
}
location, err := p.prompters.PromptLocation(
ctx, p.env.GetSubscriptionId(), msg, func(loc account.Location) bool {
return locationParameterFilterImpl(allowedLocations, loc)
}, defaultPromptValue(param))
if err != nil {
return nil, err
}
if paramIsMappedToAzureLocation && !valueDefinedInEnv {
// set the location in the environment variable
p.env.SetLocation(location)
if err := p.envManager.Save(ctx, p.env); err != nil {
return nil, fmt.Errorf("setting location in environment variable: %w", err)
}
}
value = location
} else if paramType == provisioning.ParameterTypeString &&
azdMetadata.Type != nil &&
*azdMetadata.Type == azure.AzdMetadataTypeResourceGroup {
p.console.Message(ctx, fmt.Sprintf(
"Parameter %s requires an %s resource group.", output.WithUnderline("%s", key), output.WithBold("existing")))
rgName, err := p.prompters.PromptResourceGroup(ctx, prompt.PromptResourceOptions{
DisableCreateNew: true,
})
if err != nil {
return nil, err
}
value = rgName
} else if paramType == provisioning.ParameterTypeString &&
azdMetadata.Type != nil &&
*azdMetadata.Type == azure.AzdMetadataTypeGenerateOrManual {
var manualUserInput bool
defaultOption := "Auto generate"
options := []string{defaultOption, "Manual input"}
choice, err := p.console.Select(ctx, input.ConsoleOptions{
Message: fmt.Sprintf(
"Parameter %s can be either autogenerated or you can enter its value. What would you like to do?", key),
Options: options,
DefaultValue: defaultOption,
})
if err != nil {
return nil, err
}
manualUserInput = options[choice] != defaultOption
if manualUserInput {
resultValue, err := promptWithValidation(ctx, p.console, input.ConsoleOptions{
Message: msg,
Help: help,
IsPassword: isSecuredParam,
}, convertString, validateLengthRange(key, param.MinLength, param.MaxLength))
if err != nil {
return nil, err
}
value = resultValue
} else {
genValue, err := autoGenerate(key, azdMetadata)
if err != nil {
return nil, err
}
value = genValue
}
} else if param.AllowedValues != nil {
options := make([]string, 0, len(*param.AllowedValues))
for _, option := range *param.AllowedValues {
options = append(options, fmt.Sprintf("%v", option))
}
if len(options) == 0 {
return nil, fmt.Errorf("parameter '%s' has no allowed values defined", key)
}
choice, err := p.console.Select(ctx, input.ConsoleOptions{
Message: msg,
Help: help,
Options: options,
})
if err != nil {
return nil, err
}
value = (*param.AllowedValues)[choice]
} else {
switch paramType {
case provisioning.ParameterTypeBoolean:
options := []string{"False", "True"}
choice, err := p.console.Select(ctx, input.ConsoleOptions{
Message: msg,
Help: help,
Options: options,
})
if err != nil {
return nil, err
}
value = (options[choice] == "True")
case provisioning.ParameterTypeNumber:
userValue, err := promptWithValidation(ctx, p.console, input.ConsoleOptions{
Message: msg,
Help: help,
}, convertInt, validateValueRange(key, param.MinValue, param.MaxValue))
if err != nil {
return nil, err
}
value = userValue
case provisioning.ParameterTypeString:
userValue, err := promptWithValidation(ctx, p.console, input.ConsoleOptions{
Message: msg,
Help: help,
IsPassword: isSecuredParam,
}, convertString, validateLengthRange(key, param.MinLength, param.MaxLength))
if err != nil {
return nil, err
}
value = userValue
case provisioning.ParameterTypeArray:
userValue, err := promptWithValidation(ctx, p.console, input.ConsoleOptions{
Message: msg,
Help: help,
}, convertJson[[]any], validateJsonArray)
if err != nil {
return nil, err
}
value = userValue
case provisioning.ParameterTypeObject:
userValue, err := promptWithValidation(ctx, p.console, input.ConsoleOptions{
Message: msg,
Help: help,
}, convertJson[map[string]any], validateJsonObject)
if err != nil {
return nil, err
}
value = userValue
default:
panic(fmt.Sprintf("unknown parameter type: %s", p.mapBicepTypeToInterfaceType(param.Type)))
}
}
return value, nil
}