Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
9 changes: 8 additions & 1 deletion cli/azd/cmd/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,14 @@ func (p *pipelineConfigAction) Run(ctx context.Context) (*actions.ActionResult,
Title: fmt.Sprintf("Configure your %s pipeline", pipelineProviderName),
})

pipelineResult, err := p.manager.Configure(ctx, p.projectConfig.Name)
// Pull provider specific parameters
providerParameters, err := p.provisioningManager.Parameters(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get parameters for provider %s: %w", pipelineProviderName, err)
}
p.manager.SetParameters(providerParameters)

pipelineResult, err := p.manager.Configure(ctx, p.projectConfig.Name, infra)
if err != nil {
return nil, err
}
Expand Down
8 changes: 6 additions & 2 deletions cli/azd/internal/scaffold/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,12 @@ var camelCaseRegex = regexp.MustCompile(`([a-z0-9])([A-Z])`)
// EnvFormat takes an input parameter like `fooParam` which is expected to be in camel case and returns it in
// upper snake case with env var template, like `${AZURE_FOO_PARAM}`.
func EnvFormat(src string) string {
snake := strings.ReplaceAll(strings.ToUpper(camelCaseRegex.ReplaceAllString(src, "${1}_${2}")), "-", "_")
return fmt.Sprintf("${AZURE_%s}", snake)
return fmt.Sprintf("${%s}", AzureSnakeCase(src))
}

func AzureSnakeCase(src string) string {
return fmt.Sprintf(
"AZURE_%s", strings.ReplaceAll(strings.ToUpper(camelCaseRegex.ReplaceAllString(src, "${1}_${2}")), "-", "_"))
}

func HasACA(services []ServiceSpec) bool {
Expand Down
7 changes: 3 additions & 4 deletions cli/azd/pkg/apphost/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ func init() {
"bicepParameterName": func(src string) string {
return strings.ReplaceAll(src, "-", "_")
},
"removeDot": scaffold.RemoveDotAndDash,
"envFormat": scaffold.EnvFormat,
"removeDot": scaffold.RemoveDotAndDash,
"envFormat": scaffold.EnvFormat,
"azureSnakeCase": scaffold.AzureSnakeCase,
"bicepParameterValue": func(value *string) string {
if value == nil {
return ""
Expand Down Expand Up @@ -337,8 +338,6 @@ func BicepTemplate(name string, manifest *Manifest, options AppHostOptions) (*me
// if not nil, like empty string or any other string, it is used as `= '<value>'`
if parameter.Default.Value != nil {
parameterDefaultValue = parameter.Default.Value
metadataType = azure.AzdMetadataTypeNeedForDeploy
parameterMetadata = "{}"
} else if parameter.Default.Generate != nil { // Note: .Value and .Generate are mutually exclusive
pMetadata, err := inputMetadata(*parameter.Default.Generate)
if err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,7 @@ param noVolume_pas_sw_ord string
})
@secure()
param noVolume_password string
@metadata({azd: {
type: 'needForDeploy'
config: {}
}
})
param param_with_empty_value string = ''
@metadata({azd: {
type: 'needForDeploy'
config: {}
}
})
param param_with_value string = 'default value for param'

var tags = {
Expand Down Expand Up @@ -100,4 +90,6 @@ output SERVICE_NOVOLUME_FILE_SHARE_BM0_NAME string = resources.outputs.SERVICE_N
output SERVICE_NOVOLUME_VOLUME_BM1_NAME string = resources.outputs.SERVICE_NOVOLUME_VOLUME_BM1_NAME
output SERVICE_NOVOLUME_FILE_SHARE_BM1_NAME string = resources.outputs.SERVICE_NOVOLUME_FILE_SHARE_BM1_NAME
output AZURE_VOLUMES_STORAGE_ACCOUNT string = resources.outputs.AZURE_VOLUMES_STORAGE_ACCOUNT
output AZURE_PARAM_WITH_EMPTY_VALUE string = param-with-empty-value
output AZURE_PARAM_WITH_VALUE string = param-with-value

1 change: 0 additions & 1 deletion cli/azd/pkg/azure/arm_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ type AzdMetadataType string
const AzdMetadataTypeLocation AzdMetadataType = "location"
const AzdMetadataTypeGenerate AzdMetadataType = "generate"
const AzdMetadataTypeGenerateOrManual AzdMetadataType = "generateOrManual"
const AzdMetadataTypeNeedForDeploy AzdMetadataType = "needForDeploy"
const AzdMetadataTypeResourceGroup AzdMetadataType = "resourceGroup"

type AzdMetadata struct {
Expand Down
5 changes: 5 additions & 0 deletions cli/azd/pkg/devcenter/provision_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -531,3 +531,8 @@ func hasInfraTemplates(path string) bool {

return len(entries) > 0
}

func (p *ProvisionProvider) Parameters(ctx context.Context) ([]provisioning.Parameter, error) {
// not supported (no-op)
return nil, nil
}
9 changes: 9 additions & 0 deletions cli/azd/pkg/environment/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ type Environment struct {
Config config.Config
}

// AzdInitialEnvironmentConfigName is part of a strategy to re-construct AZD environment in CI/CD from an initial state.
// This strategy was introduced for templates which takes input parameters. Parameters are saved to azd's environment
// configuration (.azure/env-name/config.json). This file is not committed to source control, so the saved values can't
// be used during CI/CD. AZD uses AZD_INITIAL_ENVIRONMENT_CONFIG to smuggle all saved parameters into a CI/CD secret and
// use it to create the environment configuration file (the first time AZD runs and creates a new environment).
//
// While AZD_INITIAL_ENVIRONMENT_CONFIG is still supported for backwards compatibility, it is deprecated.
// The currently strategy is to create individual variables or secrets for the CI/CD pipeline depending on the parameter
// configuration.
const AzdInitialEnvironmentConfigName = "AZD_INITIAL_ENVIRONMENT_CONFIG"

// New returns a new environment with the specified name.
Expand Down
75 changes: 59 additions & 16 deletions cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -1524,6 +1524,13 @@ func (p *BicepProvider) createOutputParameters(
type loadParametersResult struct {
parameters map[string]azure.ArmParameter
locationParams []string
// envMapping is a map of parameter name to environment variable names
// holds information about which parameters are mapped to which env vars for
// cases like "param": "${env:AZURE_FOO}-${env:AZURE_BAR}", envMapping will
// contain {"param": ["AZURE_FOO", "AZURE_BAR"]}
// This information is useful for setting a CI/CD automatically. Each env var
// will be set to the value of the parameter as variable or secret.
envMapping map[string][]string
}

// loadParameters reads the parameters file template for environment/module specified by Options,
Expand Down Expand Up @@ -1561,13 +1568,15 @@ func (p *BicepProvider) loadParameters(ctx context.Context) (loadParametersResul

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

// 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 {
mappedEnvVars := []string{}
paramBytes, err := json.Marshal(param)
if err != nil {
return loadParametersResult{}, fmt.Errorf("error decoding deployment parameter %s: %w", paramName, err)
Expand All @@ -1581,6 +1590,9 @@ func (p *BicepProvider) loadParameters(ctx context.Context) (loadParametersResul
if name == environment.LocationEnvVarName {
parametersMappedToAzureLocation = append(parametersMappedToAzureLocation, paramName)
}
// principalId and locations are intentionally excluded from the mapped env vars as
// they are global env vars
mappedEnvVars = append(mappedEnvVars, name)
if _, isDefined := p.env.LookupEnv(name); !isDefined {
hasUnsetEnvVar = true
}
Expand All @@ -1589,6 +1601,7 @@ func (p *BicepProvider) loadParameters(ctx context.Context) (loadParametersResul
if err != nil {
return loadParametersResult{}, fmt.Errorf("substituting environment variables for %s: %w", paramName, err)
}
envMapping[paramName] = mappedEnvVars
// 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) {
Expand Down Expand Up @@ -1634,6 +1647,7 @@ func (p *BicepProvider) loadParameters(ctx context.Context) (loadParametersResul
return loadParametersResult{
parameters: resolvedParams,
locationParams: parametersMappedToAzureLocation,
envMapping: envMapping,
}, nil
}

Expand Down Expand Up @@ -2003,7 +2017,6 @@ func (p *BicepProvider) ensureParameters(
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 {
Expand All @@ -2027,24 +2040,9 @@ func (p *BicepProvider) ensureParameters(
}
}

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
}
}
Expand Down Expand Up @@ -2293,3 +2291,48 @@ func NewBicepProvider(
azureClient: azureClient,
}
}

func (p *BicepProvider) Parameters(ctx context.Context) ([]provisioning.Parameter, error) {
modulePath := p.modulePath()
compileResult, err := p.compileBicep(ctx, modulePath)
if err != nil {
return nil, fmt.Errorf("creating template: %w", err)
}
// templateParameters are the parameters defined in the bicep template. We know when a parameter is secured,
// its type and its default value from this definition.
templateParameters := compileResult.Template.Parameters

// parametersInfo contains the env vars mappings (from a parameters file). bicepparam is not supported yet.
parametersInfo, err := p.loadParameters(ctx)
if err != nil {
return nil, fmt.Errorf("loading parameters: %w", err)
}

// resolved parameters contains the final value for the parameters after evaluating. The final value can be
// from env var, from default value or from user input (prompt).
resolvedParams, err := p.ensureParameters(ctx, compileResult.Template)
if err != nil {
return nil, fmt.Errorf("resolving parameters: %w", err)
}

provisionParameters := []provisioning.Parameter{}
for key, param := range templateParameters {
if _, usingParam := resolvedParams[key]; !usingParam {
// No resolved param for this parameter definition.
continue
}
_, isPrompt := p.env.Config.Get(fmt.Sprintf("infra.parameters.%s", key))
provisionParameters = append(provisionParameters, provisioning.Parameter{
Name: key,
Secret: param.Secure(),
Value: resolvedParams[key].Value,
EnvVarMapping: parametersInfo.envMapping[key],
// No env var mapping and param is persisted in env config infra.parameters means local prompt only
// If user set an env var mapping after a local prompt, the env var overrides the value persisted in config
// which turns local prompt false
LocalPrompt: isPrompt && len(parametersInfo.envMapping[key]) == 0,
})
}

return provisionParameters, nil
}
8 changes: 8 additions & 0 deletions cli/azd/pkg/infra/provisioning/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ func (m *Manager) Initialize(ctx context.Context, projectPath string, options Op
return m.provider.Initialize(ctx, projectPath, options)
}

// Parameters gets the list of parameters and its value which will be used to provision the infrastructure.
func (m *Manager) Parameters(ctx context.Context) ([]Parameter, error) {
if m.provider == nil {
panic("called parameters() with provider not initialized. Make sure to call manager.Initialize() first.")
}
return m.provider.Parameters(ctx)
}

// Gets the latest deployment details for the specified scope
func (m *Manager) State(ctx context.Context, options *StateOptions) (*StateResult, error) {
result, err := m.provider.State(ctx, options)
Expand Down
10 changes: 10 additions & 0 deletions cli/azd/pkg/infra/provisioning/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ type StateResult struct {
State *State
}

type Parameter struct {
Name string
Secret bool
Value any
EnvVarMapping []string
// true when the parameter value was set by the user from the command line (prompt)
LocalPrompt bool
}

type Provider interface {
Name() string
Initialize(ctx context.Context, projectPath string, options Options) error
Expand All @@ -59,4 +68,5 @@ type Provider interface {
Preview(ctx context.Context) (*DeployPreviewResult, error)
Destroy(ctx context.Context, options DestroyOptions) (*DestroyResult, error)
EnsureEnv(ctx context.Context) error
Parameters(ctx context.Context) ([]Parameter, error)
}
Original file line number Diff line number Diff line change
Expand Up @@ -765,3 +765,8 @@ type terraformChildModule struct {
Resources []terraformResource `json:"resources"`
ChildModules []terraformChildModule `json:"child_modules"`
}

func (t *TerraformProvider) Parameters(ctx context.Context) ([]provisioning.Parameter, error) {
// not supported (no-op)
return nil, nil
}
5 changes: 5 additions & 0 deletions cli/azd/pkg/infra/provisioning/test/test_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ func (p *TestProvider) Destroy(
return &destroyResult, nil
}

func (p *TestProvider) Parameters(ctx context.Context) ([]provisioning.Parameter, error) {
// not supported (no-op)
return nil, nil
}

func NewTestProvider(
envManager environment.Manager,
env *environment.Environment,
Expand Down
Loading
Loading