func()

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
}