Skip to content
Merged
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
72 changes: 7 additions & 65 deletions cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ func (p *BicepProvider) State(ctx context.Context, options *provisioning.StateOp
}
}

state.Outputs = p.createOutputParameters(
state.Outputs = provisioning.OutputParametersFromArmOutputs(
outputs,
azapi.CreateDeploymentOutput(deployment.Outputs),
)
Expand Down Expand Up @@ -591,7 +591,7 @@ func (p *BicepProvider) Deploy(ctx context.Context) (*provisioning.DeployResult,
if !p.ignoreDeploymentState && parametersHashErr == nil {
deploymentState, err := p.deploymentState(ctx, planned, deployment, currentParamsHash)
if err == nil {
result.Outputs = p.createOutputParameters(
result.Outputs = provisioning.OutputParametersFromArmOutputs(
planned.Template.Outputs,
azapi.CreateDeploymentOutput(deploymentState.Outputs),
)
Expand Down Expand Up @@ -678,7 +678,7 @@ func (p *BicepProvider) Deploy(ctx context.Context) (*provisioning.DeployResult,
return nil, err
}

result.Outputs = p.createOutputParameters(
result.Outputs = provisioning.OutputParametersFromArmOutputs(
planned.Template.Outputs,
azapi.CreateDeploymentOutput(deployResult.Outputs),
)
Expand Down Expand Up @@ -925,7 +925,7 @@ func (p *BicepProvider) Destroy(
}

destroyResult := &provisioning.DestroyResult{
InvalidatedEnvKeys: slices.Collect(maps.Keys(p.createOutputParameters(
InvalidatedEnvKeys: slices.Collect(maps.Keys(provisioning.OutputParametersFromArmOutputs(
compileResult.Template.Outputs,
azapi.CreateDeploymentOutput(mostRecentDeployment.Outputs),
))),
Expand Down Expand Up @@ -1494,64 +1494,6 @@ func (p *BicepProvider) purgeAPIManagement(
return nil
}

func (p *BicepProvider) mapBicepTypeToInterfaceType(s string) provisioning.ParameterType {
switch s {
case "String", "string", "secureString", "securestring":
return provisioning.ParameterTypeString
case "Bool", "bool":
return provisioning.ParameterTypeBoolean
case "Int", "int":
return provisioning.ParameterTypeNumber
case "Object", "object", "secureObject", "secureobject":
return provisioning.ParameterTypeObject
case "Array", "array":
return provisioning.ParameterTypeArray
default:
panic(fmt.Sprintf("unexpected bicep type: '%s'", s))
}
}

// Creates a normalized view of the azure output parameters and resolves inconsistencies in the output parameter name
// casings.
func (p *BicepProvider) createOutputParameters(
templateOutputs azure.ArmTemplateOutputs,
azureOutputParams map[string]azapi.AzCliDeploymentOutput,
) map[string]provisioning.OutputParameter {
canonicalOutputCasings := make(map[string]string, len(templateOutputs))

for key := range templateOutputs {
canonicalOutputCasings[strings.ToLower(key)] = key
}

outputParams := make(map[string]provisioning.OutputParameter, len(azureOutputParams))

for key, azureParam := range azureOutputParams {
if azureParam.Secured() {
// Secured output can't be retrieved, so we skip it.
// https://learn.microsoft.com/azure/azure-resource-manager/bicep/outputs?tabs=azure-powershell#secure-outputs
log.Println("Skipping secured output parameter:", key)
continue
}
var paramName string
canonicalCasing, found := canonicalOutputCasings[strings.ToLower(key)]
if found {
paramName = canonicalCasing
} else {
// To support BYOI (bring your own infrastructure) scenarios we will default to UPPER when canonical casing
// is not found in the parameters file to workaround strange azure behavior with OUTPUT values that look
// like `azurE_RESOURCE_GROUP`
paramName = strings.ToUpper(key)
}

outputParams[paramName] = provisioning.OutputParameter{
Type: p.mapBicepTypeToInterfaceType(azureParam.Type),
Value: azureParam.Value,
}
}

return outputParams
}

type loadParametersResult struct {
parameters map[string]azure.ArmParameter
locationParams []string
Expand Down Expand Up @@ -1852,14 +1794,14 @@ func (p *BicepProvider) convertToDeployment(bicepTemplate azure.ArmTemplate) pro

for key, param := range bicepTemplate.Parameters {
parameters[key] = provisioning.InputParameter{
Type: string(p.mapBicepTypeToInterfaceType(param.Type)),
Type: string(provisioning.ParameterTypeFromArmType(param.Type)),
DefaultValue: param.DefaultValue,
}
}

for key, param := range bicepTemplate.Outputs {
outputs[key] = provisioning.OutputParameter{
Type: p.mapBicepTypeToInterfaceType(param.Type),
Type: provisioning.ParameterTypeFromArmType(param.Type),
Value: param.Value,
}
}
Expand Down Expand Up @@ -2022,7 +1964,7 @@ func (p *BicepProvider) ensureParameters(

for _, key := range sortedKeys {
param := template.Parameters[key]
parameterType := p.mapBicepTypeToInterfaceType(param.Type)
parameterType := provisioning.ParameterTypeFromArmType(param.Type)
azdMetadata, hasMetadata := param.AzdMetadata()

// If a value is explicitly configured via a parameters file, use it.
Expand Down
6 changes: 3 additions & 3 deletions cli/azd/pkg/infra/provisioning/bicep/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func (p *BicepProvider) promptDialogItemForParameter(
param azure.ArmTemplateParameterDefinition,
) input.PromptDialogItem {
help, _ := param.Description()
paramType := p.mapBicepTypeToInterfaceType(param.Type)
paramType := provisioning.ParameterTypeFromArmType(param.Type)

var dialogItem input.PromptDialogItem
dialogItem.ID = key
Expand Down Expand Up @@ -239,7 +239,7 @@ func (p *BicepProvider) promptForParameter(
msg := fmt.Sprintf("Enter a value for the '%s' infrastructure %s:", key, securedParam)
help, _ := param.Description()
azdMetadata, _ := param.AzdMetadata()
paramType := p.mapBicepTypeToInterfaceType(param.Type)
paramType := provisioning.ParameterTypeFromArmType(param.Type)

var value any

Expand Down Expand Up @@ -448,7 +448,7 @@ func (p *BicepProvider) promptForParameter(
}
value = userValue
default:
panic(fmt.Sprintf("unknown parameter type: %s", p.mapBicepTypeToInterfaceType(param.Type)))
panic(fmt.Sprintf("unknown parameter type: %s", provisioning.ParameterTypeFromArmType(param.Type)))
}
}

Expand Down
71 changes: 71 additions & 0 deletions cli/azd/pkg/infra/provisioning/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@
package provisioning

import (
"fmt"
"log"
"maps"
"slices"
"strings"

"github.com/azure/azure-dev/cli/azd/pkg/azapi"
"github.com/azure/azure-dev/cli/azd/pkg/azure"
)

type Deployment struct {
Expand All @@ -23,6 +29,26 @@ const (
ParameterTypeArray ParameterType = "array"
)

// ParameterTypeFromArmType maps an ARM parameter type to a provisioning.ParameterType.
//
// Panics if the provided armType is not recognized.
func ParameterTypeFromArmType(armType string) ParameterType {
switch armType {
case "String", "string", "secureString", "securestring":
return ParameterTypeString
case "Bool", "bool":
return ParameterTypeBoolean
case "Int", "int":
return ParameterTypeNumber
case "Object", "object", "secureObject", "secureobject":
return ParameterTypeObject
case "Array", "array":
return ParameterTypeArray
default:
panic(fmt.Sprintf("unexpected arm type: '%s'", armType))
}
}

type InputParameter struct {
Type string
DefaultValue interface{}
Expand All @@ -34,6 +60,51 @@ type OutputParameter struct {
Value interface{}
}

// OutputParametersFromArmOutputs converts the outputs from an ARM deployment to a map of provisioning.OutputParameter.
//
// The casing of the output parameter names will match the casing in the provided templateOutputs. If an output is
// present in azureOutputParams but not in templateOutputs, the output name will be upper-cased to work around
// inconsistent casing behavior in Azure (e.g. `azurE_RESOURCE_GROUP`).
//
// Secured outputs are skipped and not included in the result.
func OutputParametersFromArmOutputs(
templateOutputs azure.ArmTemplateOutputs,
azureOutputParams map[string]azapi.AzCliDeploymentOutput) map[string]OutputParameter {
canonicalOutputCasings := make(map[string]string, len(templateOutputs))

for key := range templateOutputs {
canonicalOutputCasings[strings.ToLower(key)] = key
}

outputParams := make(map[string]OutputParameter, len(azureOutputParams))

for key, azureParam := range azureOutputParams {
if azureParam.Secured() {
// Secured output can't be retrieved, so we skip it.
// https://learn.microsoft.com/azure/azure-resource-manager/bicep/outputs?tabs=azure-powershell#secure-outputs
log.Println("Skipping secured output parameter:", key)
continue
}
var paramName string
canonicalCasing, found := canonicalOutputCasings[strings.ToLower(key)]
if found {
paramName = canonicalCasing
} else {
// To support BYOI (bring your own infrastructure) scenarios we will default to UPPER when canonical casing
// is not found in the parameters file to workaround strange azure behavior with OUTPUT values that look
// like `azurE_RESOURCE_GROUP`
paramName = strings.ToUpper(key)
}

outputParams[paramName] = OutputParameter{
Type: ParameterTypeFromArmType(azureParam.Type),
Value: azureParam.Value,
}
}

return outputParams
}

// State represents the "current state" of the infrastructure, which is the result of the most recent deployment. For ARM
// this corresponds to information from the most recent deployment object. For Terraform, it's information from the state
// file.
Expand Down
2 changes: 2 additions & 0 deletions cli/azd/pkg/project/service_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ type ServiceConfig struct {
K8s AksOptions `yaml:"k8s,omitempty"`
// The optional Azure Spring Apps options
Spring SpringOptions `yaml:"spring,omitempty"`
// Infrastructure module path relative to the root infra folder
Module string `yaml:"module,omitempty"`
// The infrastructure provisioning configuration
Infra provisioning.Options `yaml:"infra,omitempty"`
// Hook configuration for service
Expand Down
2 changes: 1 addition & 1 deletion cli/azd/pkg/project/service_target.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ func (st ServiceTargetKind) IgnoreFile() string {
// As an example, ContainerAppTarget is able to provision the container app as part of deployment,
// and thus returns true.
func (st ServiceTargetKind) SupportsDelayedProvisioning() bool {
return st == AksTarget
return st == AksTarget || st == ContainerAppTarget
}

func checkResourceType(resource *environment.TargetResource, expectedResourceType azapi.AzureResourceType) error {
Expand Down
Loading
Loading