func()

in cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go [1531:1638]


func (p *BicepProvider) loadParameters(ctx context.Context) (loadParametersResult, error) {
	parametersFilename := fmt.Sprintf("%s.parameters.json", p.options.Module)
	parametersRoot := p.options.Path

	if !filepath.IsAbs(parametersRoot) {
		parametersRoot = filepath.Join(p.projectPath, parametersRoot)
	}

	paramFilePath := filepath.Join(parametersRoot, parametersFilename)
	parametersBytes, err := os.ReadFile(paramFilePath)
	// if the file does not exist, we return an empty parameters map
	// This makes AZD to support deploying bicep modules without parameters file, assuming AZD prompts for all required
	// parameters.
	if os.IsNotExist(err) {
		log.Printf("parameters file %s does not exist, using empty parameters", paramFilePath)
		return loadParametersResult{}, nil
	}
	if err != nil {
		return loadParametersResult{}, fmt.Errorf("reading parameters.json: %w", err)
	}

	principalId, err := p.curPrincipal.CurrentPrincipalId(ctx)
	if err != nil {
		return loadParametersResult{}, fmt.Errorf("fetching current principal id: %w", err)
	}

	var decodedParamsFile azure.ArmParameterFile
	if err := json.Unmarshal(parametersBytes, &decodedParamsFile); err != nil {
		return loadParametersResult{}, fmt.Errorf("error unmarshalling Bicep template parameters: %w", err)
	}

	parametersMappedToAzureLocation := []string{}
	resolvedParams := map[string]azure.ArmParameter{}

	// resolving each parameter to keep track of the name during the resolution.
	// We used to resolve all the file before, supporting env var substitution at any part of the file.
	// We want to support substitution only for the parameter value.
	// We also need to identify which parameters are mapped to AZURE_LOCATION (if any).
	// We also want to exclude parameters mapped to env vars which env var is not set (instead of using empty string).
	for paramName, param := range decodedParamsFile.Parameters {
		paramBytes, err := json.Marshal(param)
		if err != nil {
			return loadParametersResult{}, fmt.Errorf("error decoding deployment parameter %s: %w", paramName, err)
		}
		var hasUnsetEnvVar bool
		// envsubst.Eval handles env var substitution and default values like ${VAR=default}
		replaced, err := envsubst.Eval(string(paramBytes), func(name string) string {
			if name == environment.PrincipalIdEnvVarName {
				return principalId
			}
			if name == environment.LocationEnvVarName {
				parametersMappedToAzureLocation = append(parametersMappedToAzureLocation, paramName)
			}
			if _, isDefined := p.env.LookupEnv(name); !isDefined {
				hasUnsetEnvVar = true
			}
			return p.env.Getenv(name)
		})
		if err != nil {
			return loadParametersResult{}, fmt.Errorf("substituting environment variables for %s: %w", paramName, err)
		}
		// resolve `secretOrRandomPassword` -> this is a way to ask AZD to generate a password for the user and
		// store it in a Key Vault. But if the Key Vault and secret exists, AZD just takes the secret from there.
		if cmdsubst.ContainsCommandInvocation(replaced, cmdsubst.SecretOrRandomPasswordCommandName) {
			cmdExecutor := cmdsubst.NewSecretOrRandomPasswordExecutor(p.keyvaultService, p.env.GetSubscriptionId())
			replaced, err = cmdsubst.Eval(ctx, replaced, cmdExecutor)
			if err != nil {
				return loadParametersResult{}, fmt.Errorf("substituting command output inside parameter file: %w", err)
			}
		}

		var resolvedParam azure.ArmParameter
		if err := json.Unmarshal([]byte(replaced), &resolvedParam); err != nil {
			return loadParametersResult{}, fmt.Errorf("error unmarshalling Bicep template parameters: %w", err)
		}
		if resolvedParam.Value == nil && resolvedParam.KeyVaultReference == nil {
			// ignore parameters that are not set
			continue
		}
		if resolvedParam.Value != nil && resolvedParam.KeyVaultReference != nil {
			return loadParametersResult{}, fmt.Errorf(
				"parameter %s has both a value and a keyvault reference: %w", paramName, err)
		}
		if resolvedParam.KeyVaultReference != nil {
			// parameter defined using a key vault reference. AZD does not validate the key vault reference
			// if there is an issue with it, the deployment will fail.
			resolvedParams[paramName] = resolvedParam
			continue
		}
		stringValue, isString := resolvedParam.Value.(string)
		if !isString {
			continue
		}

		// After previous checks, we know resolvedParam.Value is not nil
		if stringValue == "" && hasUnsetEnvVar {
			// parameter is empty and has an unset env var
			continue
		}
		// all other cases here represent a valid resolved parameter
		resolvedParams[paramName] = resolvedParam
	}

	return loadParametersResult{
		parameters:     resolvedParams,
		locationParams: parametersMappedToAzureLocation,
	}, nil
}