From af84a938095204e641e8359cd647b38e95fb01cb Mon Sep 17 00:00:00 2001 From: Wallace Breza Date: Mon, 23 Oct 2023 14:45:03 -0700 Subject: [PATCH] Allows prompting for for bicep parameters that reference empty or not set environment variables (#2896) Fixes #2895 & #1910 When main.parameters.json references an empty or not set value expression azd will prompt the user for a value unless the parameter also defines a default value. --- .../provisioning/bicep/bicep_provider.go | 36 ++++++++-- .../provisioning/bicep/bicep_provider_test.go | 71 +++++++++++++++++++ 2 files changed, 102 insertions(+), 5 deletions(-) diff --git a/cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go b/cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go index 05d9c79683..da64e30394 100644 --- a/cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go +++ b/cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go @@ -13,6 +13,7 @@ import ( "math" "os" "path/filepath" + "reflect" "slices" "strconv" "strings" @@ -1861,11 +1862,15 @@ func (p *BicepProvider) ensureParameters( param := template.Parameters[key] // 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 { - configuredParameters[key] = azure.ArmParameterValue{ - Value: armParameterFileValue(p.mapBicepTypeToInterfaceType(param.Type), v.Value), + paramValue := armParameterFileValue(p.mapBicepTypeToInterfaceType(param.Type), v.Value, param.DefaultValue) + if paramValue != nil { + configuredParameters[key] = azure.ArmParameterValue{ + Value: paramValue, + } + continue } - continue } // If this parameter has a default, then there is no need for us to configure it. @@ -1930,7 +1935,12 @@ func (p *BicepProvider) ensureParameters( } // Convert the ARM parameters file value into a value suitable for deployment -func armParameterFileValue(paramType ParameterType, value any) any { +func armParameterFileValue(paramType ParameterType, value any, defaultValue any) any { + // Quick return if the value being converted is not a string + if value == nil || reflect.TypeOf(value).Kind() != reflect.String { + return value + } + // Relax the handling of bool and number types to accept convertible strings switch paramType { case ParameterTypeBoolean: @@ -1945,9 +1955,25 @@ func armParameterFileValue(paramType ParameterType, value any) any { return intVal } } + case ParameterTypeString: + // Use Cases + // 1. Non-empty input value, return input value (no prompt) + // 2. Empty input value and no default - return nil (prompt user) + // 3. Empty input value and non-empty default - return empty input string (no prompt) + paramVal, paramValid := value.(string) + if paramValid && paramVal != "" { + return paramVal + } + + defaultVal, hasDefault := defaultValue.(string) + if hasDefault && paramValid && paramVal != defaultVal { + return paramVal + } + default: + return value } - return value + return nil } func isValueAssignableToParameterType(paramType ParameterType, value any) bool { diff --git a/cli/azd/pkg/infra/provisioning/bicep/bicep_provider_test.go b/cli/azd/pkg/infra/provisioning/bicep/bicep_provider_test.go index d88c8d5439..1cd2a84f94 100644 --- a/cli/azd/pkg/infra/provisioning/bicep/bicep_provider_test.go +++ b/cli/azd/pkg/infra/provisioning/bicep/bicep_provider_test.go @@ -1022,6 +1022,77 @@ func TestUserDefinedTypes(t *testing.T) { }, customOutput.Metadata) } +func Test_armParameterFileValue(t *testing.T) { + t.Run("NilValue", func(t *testing.T) { + actual := armParameterFileValue(ParameterTypeString, nil, nil) + require.Nil(t, actual) + }) + + t.Run("StringWithValue", func(t *testing.T) { + expected := "value" + actual := armParameterFileValue(ParameterTypeString, expected, nil) + require.Equal(t, expected, actual) + }) + + t.Run("EmptyString", func(t *testing.T) { + input := "" + actual := armParameterFileValue(ParameterTypeString, input, nil) + require.Nil(t, actual) + }) + + t.Run("EmptyStringWithNonEmptyDefault", func(t *testing.T) { + expected := "" + actual := armParameterFileValue(ParameterTypeString, expected, "not-empty") + require.Equal(t, expected, actual) + }) + + t.Run("EmptyStringWithEmptyDefault", func(t *testing.T) { + input := "" + actual := armParameterFileValue(ParameterTypeString, input, "") + require.Nil(t, actual) + }) + + t.Run("ValidBool", func(t *testing.T) { + expected := true + actual := armParameterFileValue(ParameterTypeBoolean, expected, nil) + require.Equal(t, expected, actual) + }) + + t.Run("ActualBool", func(t *testing.T) { + expected := true + actual := armParameterFileValue(ParameterTypeBoolean, "true", nil) + require.Equal(t, expected, actual) + }) + + t.Run("InvalidBool", func(t *testing.T) { + actual := armParameterFileValue(ParameterTypeBoolean, "NotABool", nil) + require.Nil(t, actual) + }) + + t.Run("ValidInt", func(t *testing.T) { + var expected int64 = 42 + actual := armParameterFileValue(ParameterTypeNumber, "42", nil) + require.Equal(t, expected, actual) + }) + + t.Run("ActualInt", func(t *testing.T) { + var expected int64 = 42 + actual := armParameterFileValue(ParameterTypeNumber, expected, nil) + require.Equal(t, expected, actual) + }) + + t.Run("InvalidInt", func(t *testing.T) { + actual := armParameterFileValue(ParameterTypeNumber, "NotAnInt", nil) + require.Nil(t, actual) + }) + + t.Run("Array", func(t *testing.T) { + expected := []string{"a", "b", "c"} + actual := armParameterFileValue(ParameterTypeArray, expected, nil) + require.Equal(t, expected, actual) + }) +} + const userDefinedParamsSample = `{ "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", "languageVersion": "2.0",