in cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go [1930:2160]
func (p *BicepProvider) ensureParameters(
ctx context.Context,
template azure.ArmTemplate,
) (azure.ArmParameters, error) {
//snapshot the AZURE_LOCATIOn in azd env if it is set in System env
locationSystemEnv, hasLocation := os.LookupEnv(environment.LocationEnvVarName)
_, hasAzdLocation := p.env.Dotenv()[environment.LocationEnvVarName]
if hasLocation && !hasAzdLocation && locationSystemEnv != "" {
p.env.SetLocation(locationSystemEnv)
if err := p.envManager.Save(ctx, p.env); err != nil {
return nil, fmt.Errorf("saving location to .env: %w", err)
}
}
// using loadParameters to resolve the parameters file (usually main.parameters.json)
// parameters with a mapping to env vars are resolved.
// Parameters mapped to env vars that are not set in the environment are removed from the parameters file
parametersResult, err := p.loadParameters(ctx)
if err != nil {
return nil, fmt.Errorf("resolving bicep parameters file: %w", err)
}
parameters := parametersResult.parameters
locationParameters := parametersResult.locationParams
if len(template.Parameters) == 0 {
return azure.ArmParameters{}, nil
}
configuredParameters := make(azure.ArmParameters, len(template.Parameters))
sortedKeys := slices.Sorted(maps.Keys(template.Parameters))
configModified := false
var parameterPrompts []struct {
key string
param azure.ArmTemplateParameterDefinition
}
// make all parameters mapped to AZURE_LOCATION env var to be location parameters
for _, key := range sortedKeys {
param := template.Parameters[key]
if slices.Contains(locationParameters, key) {
azdMetadata, hasAzdMetadata := param.AzdMetadata()
if !hasAzdMetadata {
azdMetadata = azure.AzdMetadata{
Type: to.Ptr(azure.AzdMetadataTypeLocation),
}
}
if azdMetadata.Type == nil {
azdMetadata.Type = to.Ptr(azure.AzdMetadataTypeLocation)
}
if azdMetadata.Type != nil && *azdMetadata.Type != azure.AzdMetadataTypeLocation {
return nil, fmt.Errorf(
"parameter %s is mapped to AZURE_LOCATION but has a different azd metadata type: %s."+
"Parameters mapped to AZURE_LOCATION can only be typed as location",
key,
*azdMetadata.Type)
}
mdBytes, err := json.Marshal(azdMetadata)
if err != nil {
return nil, fmt.Errorf("marshalling azd metadata: %w", err)
}
if param.Metadata == nil {
param.Metadata = map[string]json.RawMessage{"azd": mdBytes}
} else {
param.Metadata["azd"] = mdBytes
}
template.Parameters[key] = param
}
}
for _, key := range sortedKeys {
param := template.Parameters[key]
parameterType := p.mapBicepTypeToInterfaceType(param.Type)
azdMetadata, hasMetadata := param.AzdMetadata()
// If a value is explicitly configured via a parameters file, use it.
// unless the parameter value inference is nil/empty
if v, has := parameters[key]; has {
// Directly pass through Key Vault references without prompting.
if v.KeyVaultReference != nil {
configuredParameters[key] = azure.ArmParameter{
KeyVaultReference: v.KeyVaultReference,
}
continue
}
paramValue := armParameterFileValue(parameterType, v.Value, param.DefaultValue)
if paramValue != nil {
if stringValue, isString := paramValue.(string); isString && param.Secure() {
// For secure parameters using a string value, azd checks if the string is an Azure Key Vault Secret
// and if yes, it fetches the secret value from the Key Vault.
if keyvault.IsAzureKeyVaultSecret(stringValue) {
paramValue, err = p.keyvaultService.SecretFromAkvs(ctx, stringValue)
if err != nil {
return nil, err
}
}
}
needForDeployParameter := hasMetadata &&
azdMetadata.Type != nil &&
*azdMetadata.Type == azure.AzdMetadataTypeNeedForDeploy
if needForDeployParameter && paramValue == "" && param.DefaultValue != nil {
// Parameters with needForDeploy metadata don't support overriding with empty values when a default
// value is present. If the value is empty, we'll use the default value instead.
defValue, castOk := param.DefaultValue.(string)
if castOk {
paramValue = defValue
}
}
configuredParameters[key] = azure.ArmParameter{
Value: paramValue,
}
if needForDeployParameter {
mustSetParamAsConfig(key, paramValue, p.env.Config, param.Secure())
configModified = true
}
continue
}
}
// If this parameter has a default, then there is no need for us to configure it.
if param.DefaultValue != nil {
continue
}
if param.Nullable != nil && *param.Nullable {
// If the parameter is nullable, we can skip prompting for it.
continue
}
// This required parameter was not in parameters file - see if we stored a value in config from an earlier
// prompt and if so use it.
configKey := fmt.Sprintf("infra.parameters.%s", key)
if v, has := p.env.Config.Get(configKey); has {
if isValueAssignableToParameterType(parameterType, v) {
configuredParameters[key] = azure.ArmParameter{
Value: v,
}
continue
} else {
// The saved value is no longer valid (perhaps the user edited their template to change the type of a)
// parameter and then re-ran `azd provision`. Forget the saved value (if we can) and prompt for a new one.
_ = p.env.Config.Unset("infra.parameters.%s")
}
}
// If the parameter is tagged with {type: "generate"}, skip prompting.
// We generate it once, then save to config for next attempts.`.
if hasMetadata && parameterType == provisioning.ParameterTypeString && azdMetadata.Type != nil &&
*azdMetadata.Type == azure.AzdMetadataTypeGenerate {
// - generate once
genValue, err := autoGenerate(key, azdMetadata)
if err != nil {
return nil, err
}
configuredParameters[key] = azure.ArmParameter{
Value: genValue,
}
mustSetParamAsConfig(key, genValue, p.env.Config, param.Secure())
configModified = true
continue
}
// No saved value for this required parameter, we'll need to prompt for it.
parameterPrompts = append(parameterPrompts, struct {
key string
param azure.ArmTemplateParameterDefinition
}{key: key, param: param})
}
if len(parameterPrompts) > 0 {
if p.console.SupportsPromptDialog() {
dialog := input.PromptDialog{
Title: "Configure required deployment parameters",
Description: "The following parameters are required for deployment. " +
"Provide values for each parameter. They will be saved for future deployments.",
}
for _, prompt := range parameterPrompts {
dialog.Prompts = append(dialog.Prompts, p.promptDialogItemForParameter(prompt.key, prompt.param))
}
values, err := p.console.PromptDialog(ctx, dialog)
if err != nil {
return nil, fmt.Errorf("prompting for values: %w", err)
}
for _, prompt := range parameterPrompts {
key := prompt.key
value := values[prompt.key]
mustSetParamAsConfig(key, value, p.env.Config, prompt.param.Secure())
configModified = true
configuredParameters[key] = azure.ArmParameter{
Value: value,
}
}
} else {
for _, prompt := range parameterPrompts {
key := prompt.key
// Otherwise, prompt for the value.
value, err := p.promptForParameter(ctx, key, prompt.param, locationParameters)
if err != nil {
return nil, fmt.Errorf("prompting for value: %w", err)
}
if key != "location" {
// location param is special.
// It is not persisted in config, it is set in the .env directly
mustSetParamAsConfig(key, value, p.env.Config, prompt.param.Secure())
}
configModified = true
configuredParameters[key] = azure.ArmParameter{
Value: value,
}
}
}
}
if configModified {
if err := p.envManager.Save(ctx, p.env); err != nil {
return nil, fmt.Errorf("saving prompt values: %w", err)
}
}
return configuredParameters, nil
}