From e18ead3afc0250a64c6b18e4108ebcc87cad7900 Mon Sep 17 00:00:00 2001 From: Victor Vazquez Date: Mon, 7 Oct 2024 17:31:22 -0700 Subject: [PATCH] [Aspire] Add support for default.value for parameter.v0 (#4371) * Adding support for parameter.v0 default value * support mapping to env vars during deploy --- cli/azd/pkg/apphost/generate.go | 36 +++++++++++++++---- cli/azd/pkg/apphost/manifest.go | 1 + ...tAspireContainerGeneration-main.bicep.snap | 2 ++ ...tainerGeneration-main.parameters.json.snap | 6 ++++ ...pireContainerGeneration-mysqlabstract.snap | 4 +++ .../apphost/testdata/aspire-container.json | 30 +++++++++++++++- .../service_target_dotnet_containerapp.go | 27 ++++++++++++-- .../resources/apphost/templates/main.bicept | 2 +- 8 files changed, 98 insertions(+), 10 deletions(-) diff --git a/cli/azd/pkg/apphost/generate.go b/cli/azd/pkg/apphost/generate.go index ff3914882ff..4d1bececeb8 100644 --- a/cli/azd/pkg/apphost/generate.go +++ b/cli/azd/pkg/apphost/generate.go @@ -88,6 +88,12 @@ func init() { }, "removeDot": scaffold.RemoveDotAndDash, "envFormat": scaffold.EnvFormat, + "bicepParameterValue": func(value *string) string { + if value == nil { + return "" + } + return fmt.Sprintf(" = '%s'", *value) + }, }, ). ParseFS(resources.AppHostTemplates, "apphost/templates/*") @@ -298,6 +304,7 @@ func BicepTemplate(name string, manifest *Manifest, options AppHostOptions) (*me Name string Secret bool Type string + Value *string } type autoGenInput struct { genInput @@ -317,14 +324,22 @@ func BicepTemplate(name string, manifest *Manifest, options AppHostOptions) (*me for _, key := range genParametersKeys { parameter := generator.bicepContext.InputParameters[key] parameterMetadata := "" - if parameter.Default != nil && parameter.Default.Generate != nil { - pMetadata, err := inputMetadata(*parameter.Default.Generate) - if err != nil { - return nil, fmt.Errorf("generating input metadata for %s: %w", key, err) + var parameterDefaultValue *string + if parameter.Default != nil { + // main.bicep template handles *string for default.Value. If the value is nil, it will be ignored. + // if not nil, like empty string or any other string, it is used as `= ''` + parameterDefaultValue = parameter.Default.Value + if parameter.Default.Generate != nil { + pMetadata, err := inputMetadata(*parameter.Default.Generate) + if err != nil { + return nil, fmt.Errorf("generating input metadata for %s: %w", key, err) + } + parameterMetadata = pMetadata } - parameterMetadata = pMetadata + // Note: azd is not checking or validating that Default.Generate and Default.Value are not both set. + // The AppHost prevents this from happening by not allowing both to be set at the same time. } - input := genInput{Name: key, Secret: parameter.Secret, Type: parameter.Type} + input := genInput{Name: key, Secret: parameter.Secret, Type: parameter.Type, Value: parameterDefaultValue} parameters = append(parameters, autoGenInput{ genInput: input, MetadataConfig: parameterMetadata, @@ -1565,6 +1580,15 @@ func (b infraGenerator) evalBindingRef(v string, emitType inputEmitType) (string case inputEmitTypeBicep: return fmt.Sprintf("{{%s}}", replaceDash), nil case inputEmitTypeYaml: + if param.Default != nil && param.Default.Value != nil { + if param.Secret { + return "", fmt.Errorf("default value for secured parameter %s is not supported", resource) + } + inputType = "parameterWithDefault" + // parameter with default value will either use the default value or the value passed in the environment + return fmt.Sprintf(`{{ %s "%s" "%s"}}`, inputType, replaceDash, *param.Default.Value), nil + } + // parameter without default value return fmt.Sprintf(`{{ %s "%s" }}`, inputType, replaceDash), nil default: panic(fmt.Sprintf("unexpected parameter %s", string(emitType))) diff --git a/cli/azd/pkg/apphost/manifest.go b/cli/azd/pkg/apphost/manifest.go index b09cbe0f768..10235b9a3e0 100644 --- a/cli/azd/pkg/apphost/manifest.go +++ b/cli/azd/pkg/apphost/manifest.go @@ -195,6 +195,7 @@ type InputDefaultGenerate struct { type InputDefault struct { Generate *InputDefaultGenerate `json:"generate,omitempty"` + Value *string `json:"value,omitempty"` } // ManifestFromAppHost returns the Manifest from the given app host. diff --git a/cli/azd/pkg/apphost/testdata/TestAspireContainerGeneration-main.bicep.snap b/cli/azd/pkg/apphost/testdata/TestAspireContainerGeneration-main.bicep.snap index 1d0a89c4e2d..fe70e6755de 100644 --- a/cli/azd/pkg/apphost/testdata/TestAspireContainerGeneration-main.bicep.snap +++ b/cli/azd/pkg/apphost/testdata/TestAspireContainerGeneration-main.bicep.snap @@ -54,6 +54,8 @@ param noVolume_pas_sw_ord string }) @secure() param noVolume_password string +param param_with_empty_value string = '' +param param_with_value string = 'default value for param' var tags = { 'azd-env-name': environmentName diff --git a/cli/azd/pkg/apphost/testdata/TestAspireContainerGeneration-main.parameters.json.snap b/cli/azd/pkg/apphost/testdata/TestAspireContainerGeneration-main.parameters.json.snap index 150faf77e89..e364c1d0d9f 100644 --- a/cli/azd/pkg/apphost/testdata/TestAspireContainerGeneration-main.parameters.json.snap +++ b/cli/azd/pkg/apphost/testdata/TestAspireContainerGeneration-main.parameters.json.snap @@ -23,6 +23,12 @@ "noVolume_password": { "value": "${AZURE_NO_VOLUME_PASSWORD}" }, + "param_with_empty_value": { + "value": "${AZURE_PARAM_WITH_EMPTY_VALUE}" + }, + "param_with_value": { + "value": "${AZURE_PARAM_WITH_VALUE}" + }, "environmentName": { "value": "${AZURE_ENV_NAME}" }, diff --git a/cli/azd/pkg/apphost/testdata/TestAspireContainerGeneration-mysqlabstract.snap b/cli/azd/pkg/apphost/testdata/TestAspireContainerGeneration-mysqlabstract.snap index 2bab7d4678e..b254f2b8869 100644 --- a/cli/azd/pkg/apphost/testdata/TestAspireContainerGeneration-mysqlabstract.snap +++ b/cli/azd/pkg/apphost/testdata/TestAspireContainerGeneration-mysqlabstract.snap @@ -39,6 +39,10 @@ properties: env: - name: AZURE_CLIENT_ID value: {{ .Env.MANAGED_IDENTITY_CLIENT_ID }} + - name: EMPTY_VALUE + value: '{{ parameterWithDefault "param_with_empty_value" ""}}' + - name: WITH_VALUE + value: '{{ parameterWithDefault "param_with_value" "default value for param"}}' - name: MYSQL_ROOT_PASSWORD secretRef: mysql-root-password - name: SpecialChar diff --git a/cli/azd/pkg/apphost/testdata/aspire-container.json b/cli/azd/pkg/apphost/testdata/aspire-container.json index 94a53349725..97c706ce2b5 100644 --- a/cli/azd/pkg/apphost/testdata/aspire-container.json +++ b/cli/azd/pkg/apphost/testdata/aspire-container.json @@ -16,6 +16,32 @@ } } }, + "param-with-value": { + "type": "parameter.v0", + "value": "{param-with-value.inputs.value}", + "inputs": { + "value": { + "type": "string", + "secret": false, + "default":{ + "value": "default value for param" + } + } + } + }, + "param-with-empty-value": { + "type": "parameter.v0", + "value": "{param-with-empty-value.inputs.value}", + "inputs": { + "value": { + "type": "string", + "secret": false, + "default":{ + "value": "" + } + } + } + }, "mysqlabstract-pas-sw-ord": { "type": "parameter.v0", "value": "{mysqlabstract-pas-sw-ord.inputs.value}", @@ -38,7 +64,9 @@ "image": "mysql:latest", "env": { "MYSQL_ROOT_PASSWORD": "{mysqlabstract-password.value}", - "SpecialChar": "{mysqlabstract-pas-sw-ord.value}" + "SpecialChar": "{mysqlabstract-pas-sw-ord.value}", + "WITH_VALUE": "{param-with-value.value}", + "EMPTY_VALUE": "{param-with-empty-value.value}" }, "volumes": [ { diff --git a/cli/azd/pkg/project/service_target_dotnet_containerapp.go b/cli/azd/pkg/project/service_target_dotnet_containerapp.go index 02e7c7f575b..e057508d241 100644 --- a/cli/azd/pkg/project/service_target_dotnet_containerapp.go +++ b/cli/azd/pkg/project/service_target_dotnet_containerapp.go @@ -15,6 +15,7 @@ import ( "strings" "text/template" + "github.com/azure/azure-dev/cli/azd/internal/scaffold" "github.com/azure/azure-dev/cli/azd/pkg/alpha" "github.com/azure/azure-dev/cli/azd/pkg/apphost" "github.com/azure/azure-dev/cli/azd/pkg/async" @@ -252,8 +253,9 @@ func (at *dotnetContainerAppTarget) Deploy( } funcMap := template.FuncMap{ - "urlHost": fns.UrlHost, - "parameter": fns.Parameter, + "urlHost": fns.UrlHost, + "parameter": fns.Parameter, + "parameterWithDefault": fns.ParameterWithDefault, // securedParameter gets a parameter the same way as parameter, but supporting the securedParameter // allows to update the logic of pulling secret parameters in the future, if azd changes the way it // stores the parameter value. @@ -503,7 +505,16 @@ func (_ *containerAppTemplateManifestFuncs) UrlHost(s string) (string, error) { const infraParametersKey = "infra.parameters." +// Parameter resolves the name of a parameter defined in the ACA yaml definition. The parameter can be mapped to a system +// environment variable or persisted in the azd environment configuration. func (fns *containerAppTemplateManifestFuncs) Parameter(name string) (string, error) { + envVarMapping := scaffold.EnvFormat(name) + // map only to system environment variables. Not adding support for mapping to azd environment by design (b/c + // parameters could be secured) + if val, found := os.LookupEnv(envVarMapping); found { + return val, nil + } + key := infraParametersKey + name val, found := fns.env.Config.Get(key) if !found { @@ -516,6 +527,18 @@ func (fns *containerAppTemplateManifestFuncs) Parameter(name string) (string, er return valString, nil } +// ParameterWithDefault resolves the name of a parameter defined in the ACA yaml definition. +// The parameter can be mapped to a system environment variable or be default to a value directly. +func (fns *containerAppTemplateManifestFuncs) ParameterWithDefault(name string, defaultValue string) (string, error) { + envVarMapping := scaffold.EnvFormat(name) + // map only to system environment variables. Not adding support for mapping to azd environment by design (b/c + // parameters could be secured) + if val, found := os.LookupEnv(envVarMapping); found { + return val, nil + } + return defaultValue, nil +} + // kvSecret gets the value of the secret with the given name from the KeyVault with the given host name. If the secret is // not found, an error is returned. func (fns *containerAppTemplateManifestFuncs) kvSecret(kvHost, secretName string) (string, error) { diff --git a/cli/azd/resources/apphost/templates/main.bicept b/cli/azd/resources/apphost/templates/main.bicept index c93fe50d2f7..28a8b6835cc 100644 --- a/cli/azd/resources/apphost/templates/main.bicept +++ b/cli/azd/resources/apphost/templates/main.bicept @@ -24,7 +24,7 @@ param principalId string = '' {{- if $parameter.Secret }} @secure() {{- end}} -param {{bicepParameterName $parameter.Name}} {{$parameter.Type}} +param {{bicepParameterName $parameter.Name}} {{$parameter.Type}}{{bicepParameterValue $parameter.Value}} {{- end}} var tags = {