Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
62 changes: 56 additions & 6 deletions cli/azd/cmd/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,19 +177,69 @@ func (p *pipelineConfigAction) Run(ctx context.Context) (*actions.ActionResult,
layers := infra.Options.GetLayers()
allParameters := []provisioning.Parameter{}

for _, layer := range layers {
inputParameters := func(layer provisioning.Options) ([]provisioning.Parameter, error) {
err = p.provisioningManager.Initialize(ctx, p.projectConfig.Path, layer)
if err != nil {
return nil, err
return nil, fmt.Errorf(
"layer %q: failed to initialize infra provider %s: %w",
layer.Name,
layer.Provider,
err,
)
}

// Pull provider specific parameters
providerParameters, err := p.provisioningManager.Parameters(ctx)
parameters, err := p.provisioningManager.Parameters(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get parameters for provider %s: %w", pipelineProviderName, err)
return nil, fmt.Errorf(
"layer %q: failed to get parameters for infra provider %s: %w",
layer.Name,
layer.Provider,
err,
)
}

allParameters = append(allParameters, providerParameters...)
return parameters, nil
}

if len(layers) <= 1 {
for _, layer := range layers {
parameters, err := inputParameters(layer)
if err != nil {
return nil, err
}

allParameters = append(allParameters, parameters...)
}
} else {
// virtualEnv contains all accumulated outputs from previous layers.
virtualEnv := map[string]string{}

for _, layer := range layers {
layer.VirtualEnv = virtualEnv

parameters, err := inputParameters(layer)
if err != nil {
return nil, fmt.Errorf("layer '%s': %w", layer.Name, err)
}

allParameters = append(allParameters, parameters...)

outputs, err := p.provisioningManager.PlannedOutputs(ctx)
if err != nil {
return nil, fmt.Errorf(
"layer %q: failed to get planned outputs for infra provider %s: %w",
layer.Name,
layer.Provider,
err,
)
}

// Save current outputs
for _, output := range outputs {
// save a dummy value that is easily looked at
virtualEnv[output.Name] = fmt.Sprintf("%s--%s", layer.Name, output.Name)
}
}
}

p.manager.SetParameters(allParameters)
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 @@ -536,3 +536,8 @@ func (p *ProvisionProvider) Parameters(ctx context.Context) ([]provisioning.Para
// not supported (no-op)
return nil, nil
}

func (p *ProvisionProvider) PlannedOutputs(ctx context.Context) ([]provisioning.PlannedOutput, error) {
// not supported (no-op)
return nil, nil
}
68 changes: 63 additions & 5 deletions cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -1726,11 +1726,18 @@ type loadParametersResult struct {
// 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

// parameters that should be considered virtual, i.e. their values are only
// known at runtime.
//
// These parameters should not be prompted and treated as they have values.
virtualMapping map[string]struct{}
}

// envSubstResult contains the results of environment variable substitution
type envSubstResult struct {
hasUnsetEnvVar bool
hasVirtualEnvVar bool
mappedEnvVars []string
parametersMappedToAzureLocation []string
}
Expand All @@ -1743,6 +1750,7 @@ func evalParamEnvSubst(
principalType string,
paramName string,
env *environment.Environment,
virtualEnv map[string]string,
) (string, envSubstResult, error) {
result := envSubstResult{}

Expand All @@ -1756,13 +1764,22 @@ func evalParamEnvSubst(
if name == environment.LocationEnvVarName {
result.parametersMappedToAzureLocation = append(result.parametersMappedToAzureLocation, paramName)
}
// principalId and locations are intentionally excluded from the mapped env vars as
// they are global env vars
// principalId and principalType are intentionally excluded from the mapped env vars
// as they are global env vars.
result.mappedEnvVars = append(result.mappedEnvVars, name)
if _, isDefined := env.LookupEnv(name); !isDefined {

if virtualEnv != nil {
if value, has := virtualEnv[name]; has {
result.hasVirtualEnvVar = true
return value
}
}

value, isDefined := env.LookupEnv(name)
if !isDefined {
result.hasUnsetEnvVar = true
}
return env.Getenv(name)
return value
})
return replaced, result, err
}
Expand Down Expand Up @@ -1816,6 +1833,7 @@ func (p *BicepProvider) loadParameters(ctx context.Context, template *azure.ArmT
parametersMappedToAzureLocation := []string{}
resolvedParams := map[string]azure.ArmParameter{}
envMapping := map[string][]string{}
virtualMapping := map[string]struct{}{}

// 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.
Expand Down Expand Up @@ -1843,6 +1861,7 @@ func (p *BicepProvider) loadParameters(ctx context.Context, template *azure.ArmT
string(principalType),
paramName,
p.env,
p.options.VirtualEnv,
)
if err != nil {
return loadParametersResult{}, err
Expand All @@ -1852,6 +1871,12 @@ func (p *BicepProvider) loadParameters(ctx context.Context, template *azure.ArmT
parametersMappedToAzureLocation = append(
parametersMappedToAzureLocation, substResult.parametersMappedToAzureLocation...)

if substResult.hasVirtualEnvVar {
// Record the parameter as virtual, skip further processing
virtualMapping[paramName] = struct{}{}
continue
}

// Omit unset parameters
if replaced == "" && substResult.hasUnsetEnvVar {
continue
Expand Down Expand Up @@ -1897,6 +1922,7 @@ func (p *BicepProvider) loadParameters(ctx context.Context, template *azure.ArmT
string(principalType),
paramName,
p.env,
p.options.VirtualEnv,
)
if err != nil {
return loadParametersResult{}, fmt.Errorf("substituting environment variables for %s: %w", paramName, err)
Expand All @@ -1906,12 +1932,17 @@ func (p *BicepProvider) loadParameters(ctx context.Context, template *azure.ArmT
parametersMappedToAzureLocation = append(
parametersMappedToAzureLocation, substResult.parametersMappedToAzureLocation...)

if substResult.hasVirtualEnvVar {
// Record the parameter as virtual, skip further processing
virtualMapping[paramName] = struct{}{}
continue
}

// resolve command substitutions like `secretOrRandomPassword`
replaced, err = p.evalCommandSubstitution(ctx, replaced)
if err != nil {
return loadParametersResult{}, 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)
Expand Down Expand Up @@ -1948,6 +1979,7 @@ func (p *BicepProvider) loadParameters(ctx context.Context, template *azure.ArmT
parameters: resolvedParams,
locationParams: parametersMappedToAzureLocation,
envMapping: envMapping,
virtualMapping: virtualMapping,
}, nil
}

Expand Down Expand Up @@ -2403,6 +2435,7 @@ func (p *BicepProvider) ensureParameters(
}
parameters := parametersResult.parameters
locationParameters := parametersResult.locationParams
virtualParameters := parametersResult.virtualMapping

if len(template.Parameters) == 0 {
return azure.ArmParameters{}, nil
Expand Down Expand Up @@ -2456,6 +2489,11 @@ func (p *BicepProvider) ensureParameters(
parameterType := provisioning.ParameterTypeFromArmType(param.Type)
azdMetadata, hasMetadata := param.AzdMetadata()

// If a value is marked virtual, we skip configuration entirely
if _, has := virtualParameters[key]; has {
continue
}

// If a value is explicitly configured via a parameters file, use it.
if v, has := parameters[key]; has {
// Directly pass through Key Vault references without prompting.
Expand Down Expand Up @@ -2791,3 +2829,23 @@ func (p *BicepProvider) Parameters(ctx context.Context) ([]provisioning.Paramete

return provisionParameters, nil
}

func (p *BicepProvider) PlannedOutputs(ctx context.Context) ([]provisioning.PlannedOutput, error) {
compileResult, err := p.compileBicep(ctx)
if err != nil {
return nil, fmt.Errorf("creating template: %w", err)
}

var outputs []provisioning.PlannedOutput
for key, output := range compileResult.Template.Outputs {
if azure.IsSecuredARMType(output.Type) {
continue
}

outputs = append(outputs, provisioning.PlannedOutput{
Name: key,
})
}

return outputs, nil
}
Loading
Loading