From 138d4690640334d0eff13f3790ea80fcdc933c20 Mon Sep 17 00:00:00 2001 From: Victor Vazquez Date: Sat, 1 Feb 2025 01:50:16 +0000 Subject: [PATCH 1/7] support scope in bicep types --- cli/azd/pkg/apphost/generate.go | 86 ++++++++++++------- cli/azd/pkg/apphost/generate_types.go | 1 + cli/azd/pkg/apphost/manifest.go | 3 + .../TestAspireBicepGeneration-main.bicep.snap | 3 +- ...eBicepGeneration-main.parameters.json.snap | 3 + .../pkg/apphost/testdata/aspire-bicep.json | 11 ++- .../resources/apphost/templates/main.bicept | 4 + 7 files changed, 79 insertions(+), 32 deletions(-) diff --git a/cli/azd/pkg/apphost/generate.go b/cli/azd/pkg/apphost/generate.go index e7e06c9dfb2..d34a71e7099 100644 --- a/cli/azd/pkg/apphost/generate.go +++ b/cli/azd/pkg/apphost/generate.go @@ -827,7 +827,15 @@ func (b *infraGenerator) addBicep(name string, comp *Resource) error { stringParams["location"] = "location" } - b.bicepContext.BicepModules[name] = genBicepModules{Path: *comp.Path, Params: stringParams} + bicepScope := comp.Scope + if bicepScope != nil { + paramValue, _, err := injectValueForBicepParameter(name, "scope", *bicepScope) + if err != nil { + return err + } + bicepScope = ¶mValue + } + b.bicepContext.BicepModules[name] = genBicepModules{Path: *comp.Path, Params: stringParams, Scope: bicepScope} return nil } @@ -1284,38 +1292,22 @@ func (b *infraGenerator) Compile() error { for moduleName, module := range b.bicepContext.BicepModules { for paramName, paramValue := range module.Params { - // bicep uses ' instead of " for strings, so we need to replace all " with ' - singleQuoted := strings.ReplaceAll(paramValue, "\"", "'") - - var evaluationError error - evaluatedString := singleQuotedStringRegex.ReplaceAllStringFunc(singleQuoted, func(s string) string { - evaluatedString, err := EvalString(s, func(s string) (string, error) { - return b.evalBindingRef(s, inputEmitTypeBicep) - }) - if err != nil { - evaluationError = fmt.Errorf("evaluating bicep module %s parameter %s: %w", moduleName, paramName, err) - } - return evaluatedString - }) - if evaluationError != nil { - return evaluationError + value, err := b.resolveBicepReference(paramValue) + if err != nil { + return fmt.Errorf( + "resolving bicep module %s, param: %s, reference %s: %w", moduleName, paramName, paramValue, err) } - - // quick check to know if evaluatedString is only holding one only reference. If so, we don't need to use - // the form of '%{ref}' and we can directly use ref alone. - if isComplexExp, val := isComplexExpression(evaluatedString); !isComplexExp { - module.Params[paramName] = val - continue + module.Params[paramName] = value + } + if module.Scope != nil { + scope, err := b.resolveBicepReference(*module.Scope) + if err != nil { + return fmt.Errorf("resolving bicep module %s scope %s: %w", moduleName, *module.Scope, err) } - - // Property names that are valid identifiers should be declared without quotation marks and accessed - // using dot notation. - evaluatedString = propertyNameRegex.ReplaceAllString(evaluatedString, "${1}:") - - // restore double {{ }} to single { } for bicep output - // we used double only during the evaluation to scape single brackets - module.Params[paramName] = strings.ReplaceAll(strings.ReplaceAll(evaluatedString, "'{{", "${"), "}}'", "}") + scope = fmt.Sprintf("resourceGroup(%s)", scope) + module.Scope = &scope } + b.bicepContext.BicepModules[moduleName] = module } for _, kv := range b.bicepContext.KeyVaults { @@ -1328,6 +1320,40 @@ func (b *infraGenerator) Compile() error { return nil } +// resolve a reference for bicep types. The reference can be from a parameter of for the scope +func (b *infraGenerator) resolveBicepReference(ref string) (string, error) { + // bicep uses ' instead of " for strings, so we need to replace all " with ' + singleQuoted := strings.ReplaceAll(ref, "\"", "'") + + var evaluationError error + evaluatedString := singleQuotedStringRegex.ReplaceAllStringFunc(singleQuoted, func(s string) string { + evaluatedString, err := EvalString(s, func(s string) (string, error) { + return b.evalBindingRef(s, inputEmitTypeBicep) + }) + if err != nil { + evaluationError = err + } + return evaluatedString + }) + if evaluationError != nil { + return "", evaluationError + } + + // quick check to know if evaluatedString is only holding one only reference. If so, we don't need to use + // the form of '%{ref}' and we can directly use ref alone. + if isComplexExp, val := isComplexExpression(evaluatedString); !isComplexExp { + return val, nil + } + + // Property names that are valid identifiers should be declared without quotation marks and accessed + // using dot notation. + evaluatedString = propertyNameRegex.ReplaceAllString(evaluatedString, "${1}:") + + // restore double {{ }} to single { } for bicep output + // we used double only during the evaluation to scape single brackets + return strings.ReplaceAll(strings.ReplaceAll(evaluatedString, "'{{", "${"), "}}'", "}"), nil +} + // isComplexExpression checks if the evaluatedString is in the form of '{{ expr }}' or if it is a complex expression like // 'foo {{ expr }} bar {{ expr2 }}' and returns true if it is a complex expression. // When the expression is not complex, it returns false and the evaluatedString without the special characters. diff --git a/cli/azd/pkg/apphost/generate_types.go b/cli/azd/pkg/apphost/generate_types.go index 6f49f6a862c..ce7efe394ac 100644 --- a/cli/azd/pkg/apphost/generate_types.go +++ b/cli/azd/pkg/apphost/generate_types.go @@ -128,6 +128,7 @@ type genOutputParameter struct { type genBicepModules struct { Path string Params map[string]string + Scope *string } type genBicepTemplateContext struct { diff --git a/cli/azd/pkg/apphost/manifest.go b/cli/azd/pkg/apphost/manifest.go index f7007e0f77b..d6f23192798 100644 --- a/cli/azd/pkg/apphost/manifest.go +++ b/cli/azd/pkg/apphost/manifest.go @@ -100,6 +100,9 @@ type Resource struct { // project.v1 and container.v1 uses deployment when the AppHost owns the ACA bicep definitions. Deployment *DeploymentMetadata `json:"deployment,omitempty"` + + // Reference is present on bicep modules to control the scope of the module. + Scope *string `json:"scope,omitempty"` } type DeploymentMetadata struct { diff --git a/cli/azd/pkg/apphost/testdata/TestAspireBicepGeneration-main.bicep.snap b/cli/azd/pkg/apphost/testdata/TestAspireBicepGeneration-main.bicep.snap index 75b66a11166..5e7b1bf054d 100644 --- a/cli/azd/pkg/apphost/testdata/TestAspireBicepGeneration-main.bicep.snap +++ b/cli/azd/pkg/apphost/testdata/TestAspireBicepGeneration-main.bicep.snap @@ -16,6 +16,7 @@ param administrator_login string @secure() param administratorLoginPassword string param parameter string +param rg_scope string var tags = { 'azd-env-name': environmentName @@ -85,7 +86,7 @@ module sql 'sql/aspire.hosting.azure.bicep.sql.bicep' = { } module test 'test/test.bicep' = { name: 'test' - scope: rg + scope: resourceGroup(rg_scope) params: { host: 'frontend.internal.${resources.outputs.AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN}' location: location diff --git a/cli/azd/pkg/apphost/testdata/TestAspireBicepGeneration-main.parameters.json.snap b/cli/azd/pkg/apphost/testdata/TestAspireBicepGeneration-main.parameters.json.snap index ac0fdf332ab..ead0ea0b8c7 100644 --- a/cli/azd/pkg/apphost/testdata/TestAspireBicepGeneration-main.parameters.json.snap +++ b/cli/azd/pkg/apphost/testdata/TestAspireBicepGeneration-main.parameters.json.snap @@ -14,6 +14,9 @@ "parameter": { "value": "${AZURE_PARAMETER}" }, + "rg_scope": { + "value": "${AZURE_RG_SCOPE}" + }, "environmentName": { "value": "${AZURE_ENV_NAME}" }, diff --git a/cli/azd/pkg/apphost/testdata/aspire-bicep.json b/cli/azd/pkg/apphost/testdata/aspire-bicep.json index 9096222670b..6ca06272a3b 100644 --- a/cli/azd/pkg/apphost/testdata/aspire-bicep.json +++ b/cli/azd/pkg/apphost/testdata/aspire-bicep.json @@ -8,6 +8,14 @@ } } }, + "rg-scope": { + "type": "parameter.v0", + "value": "{rg-scope.inputs.value}", + "inputs": { + "value": { + } + } + }, "test": { "type": "azure.bicep.v0", "path": "test.bicep", @@ -19,7 +27,8 @@ "one", "two" ] - } + }, + "scope": "{rg-scope.value}" }, "administrator-login": { "type": "parameter.v0", diff --git a/cli/azd/resources/apphost/templates/main.bicept b/cli/azd/resources/apphost/templates/main.bicept index a4c77e4ce77..f50539d2033 100644 --- a/cli/azd/resources/apphost/templates/main.bicept +++ b/cli/azd/resources/apphost/templates/main.bicept @@ -54,7 +54,11 @@ module resources 'resources.bicep' = { {{ range $name, $module := .BicepModules }} module {{bicepParameterName $name}} '{{ fixBackSlash $module.Path }}' = { name: '{{$name}}' +{{- if $module.Scope }} + scope: {{$module.Scope}} +{{- else }} scope: rg +{{- end}} params: { {{- range $param, $value := $module.Params}} {{$param}}: {{$value}} From 3e35a58b45352b83617e1902868ee9934f1eac0c Mon Sep 17 00:00:00 2001 From: Victor Vazquez Date: Sat, 1 Feb 2025 05:45:35 +0000 Subject: [PATCH 2/7] make scope an object --- cli/azd/pkg/apphost/generate.go | 21 ++++++++++++------- cli/azd/pkg/apphost/generate_types.go | 2 +- cli/azd/pkg/apphost/manifest.go | 9 ++++++-- .../pkg/apphost/testdata/aspire-bicep.json | 4 +++- .../resources/apphost/templates/main.bicept | 4 ---- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/cli/azd/pkg/apphost/generate.go b/cli/azd/pkg/apphost/generate.go index d34a71e7099..c24af70b82a 100644 --- a/cli/azd/pkg/apphost/generate.go +++ b/cli/azd/pkg/apphost/generate.go @@ -827,13 +827,16 @@ func (b *infraGenerator) addBicep(name string, comp *Resource) error { stringParams["location"] = "location" } - bicepScope := comp.Scope - if bicepScope != nil { - paramValue, _, err := injectValueForBicepParameter(name, "scope", *bicepScope) + bicepScope := defaultBicepModuleScope + if comp.Scope != nil { + if comp.Scope.ResourceGroup == nil { + return fmt.Errorf("bicep resource %s has a scope without a resource group", name) + } + paramValue, _, err := injectValueForBicepParameter(name, "scope", *comp.Scope.ResourceGroup) if err != nil { return err } - bicepScope = ¶mValue + bicepScope = paramValue } b.bicepContext.BicepModules[name] = genBicepModules{Path: *comp.Path, Params: stringParams, Scope: bicepScope} return nil @@ -854,6 +857,8 @@ const ( knownInjectedValueLogAnalytics string = "resources.outputs.AZURE_LOG_ANALYTICS_WORKSPACE_ID" knownInjectedValueContainerEnvName string = "resources.outputs.AZURE_CONTAINER_APPS_ENVIRONMENT_NAME" knownInjectedValueContainerEnvId string = "resources.outputs.AZURE_CONTAINER_APPS_ENVIRONMENT_ID" + + defaultBicepModuleScope string = "rg" ) // injectValueForBicepParameter checks for aspire-manifest and azd conventions rules for auto injecting values for @@ -1299,13 +1304,13 @@ func (b *infraGenerator) Compile() error { } module.Params[paramName] = value } - if module.Scope != nil { - scope, err := b.resolveBicepReference(*module.Scope) + if module.Scope != defaultBicepModuleScope { + scope, err := b.resolveBicepReference(module.Scope) if err != nil { - return fmt.Errorf("resolving bicep module %s scope %s: %w", moduleName, *module.Scope, err) + return fmt.Errorf("resolving bicep module %s scope %s: %w", moduleName, module.Scope, err) } scope = fmt.Sprintf("resourceGroup(%s)", scope) - module.Scope = &scope + module.Scope = scope } b.bicepContext.BicepModules[moduleName] = module } diff --git a/cli/azd/pkg/apphost/generate_types.go b/cli/azd/pkg/apphost/generate_types.go index ce7efe394ac..d9d40718862 100644 --- a/cli/azd/pkg/apphost/generate_types.go +++ b/cli/azd/pkg/apphost/generate_types.go @@ -128,7 +128,7 @@ type genOutputParameter struct { type genBicepModules struct { Path string Params map[string]string - Scope *string + Scope string } type genBicepTemplateContext struct { diff --git a/cli/azd/pkg/apphost/manifest.go b/cli/azd/pkg/apphost/manifest.go index d6f23192798..82109c4dc38 100644 --- a/cli/azd/pkg/apphost/manifest.go +++ b/cli/azd/pkg/apphost/manifest.go @@ -101,8 +101,13 @@ type Resource struct { // project.v1 and container.v1 uses deployment when the AppHost owns the ACA bicep definitions. Deployment *DeploymentMetadata `json:"deployment,omitempty"` - // Reference is present on bicep modules to control the scope of the module. - Scope *string `json:"scope,omitempty"` + // Present on bicep modules to control the scope of the module. + Scope *BicepModuleScope `json:"scope,omitempty"` +} + +// BicepModuleScope is the scope of a bicep module. +type BicepModuleScope struct { + ResourceGroup *string `json:"resourceGroup,omitempty"` } type DeploymentMetadata struct { diff --git a/cli/azd/pkg/apphost/testdata/aspire-bicep.json b/cli/azd/pkg/apphost/testdata/aspire-bicep.json index 6ca06272a3b..0fac0cb7658 100644 --- a/cli/azd/pkg/apphost/testdata/aspire-bicep.json +++ b/cli/azd/pkg/apphost/testdata/aspire-bicep.json @@ -28,7 +28,9 @@ "two" ] }, - "scope": "{rg-scope.value}" + "scope": { + "resourceGroup": "{rg-scope.value}" + } }, "administrator-login": { "type": "parameter.v0", diff --git a/cli/azd/resources/apphost/templates/main.bicept b/cli/azd/resources/apphost/templates/main.bicept index f50539d2033..5fce7d3f1c7 100644 --- a/cli/azd/resources/apphost/templates/main.bicept +++ b/cli/azd/resources/apphost/templates/main.bicept @@ -54,11 +54,7 @@ module resources 'resources.bicep' = { {{ range $name, $module := .BicepModules }} module {{bicepParameterName $name}} '{{ fixBackSlash $module.Path }}' = { name: '{{$name}}' -{{- if $module.Scope }} scope: {{$module.Scope}} -{{- else }} - scope: rg -{{- end}} params: { {{- range $param, $value := $module.Params}} {{$param}}: {{$value}} From 950a7165091f609d2d2578b8ee0bbf70ff1ce100 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Sun, 2 Feb 2025 18:21:53 -0800 Subject: [PATCH 3/7] Fix type checks in manifest parsing and bicep generation --- cli/azd/pkg/apphost/generate.go | 4 ++-- cli/azd/pkg/apphost/manifest.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cli/azd/pkg/apphost/generate.go b/cli/azd/pkg/apphost/generate.go index c24af70b82a..57a42188fde 100644 --- a/cli/azd/pkg/apphost/generate.go +++ b/cli/azd/pkg/apphost/generate.go @@ -677,7 +677,7 @@ func (b *infraGenerator) LoadManifest(m *Manifest) error { if err := b.addInputParameter(name, comp); err != nil { return fmt.Errorf("adding bicep parameter from resource %s (%s): %w", name, comp.Type, err) } - case "azure.bicep.v0": + case "azure.bicep.v0", "azure.bicep.v1": if err := b.addBicep(name, comp); err != nil { return fmt.Errorf("adding bicep resource %s: %w", name, err) } @@ -1592,7 +1592,7 @@ func (b infraGenerator) evalBindingRef(v string, emitType inputEmitType) (string fmt.Errorf("malformed binding expression, expected "+ "bindings..[scheme|protocol|transport|external|host|targetPort|port|url] but was: %s", v) } - case targetType == "azure.bicep.v0": + case targetType == "azure.bicep.v0" || targetType == "azure.bicep.v1": if !strings.HasPrefix(prop, "outputs.") && !strings.HasPrefix(prop, "secretOutputs") { return "", fmt.Errorf("unsupported property referenced in binding expression: %s for %s", prop, targetType) } diff --git a/cli/azd/pkg/apphost/manifest.go b/cli/azd/pkg/apphost/manifest.go index 82109c4dc38..bfc4c5d9be8 100644 --- a/cli/azd/pkg/apphost/manifest.go +++ b/cli/azd/pkg/apphost/manifest.go @@ -250,7 +250,7 @@ func ManifestFromAppHost( for resourceName, res := range manifest.Resources { if res.Path != nil { - if res.Type == "azure.bicep.v0" { + if res.Type == "azure.bicep.v0" || res.Type == "azure.bicep.v1" { e := manifest.BicepFiles.MkdirAll(resourceName, osutil.PermissionDirectory) if e != nil { return nil, e @@ -279,9 +279,9 @@ func ManifestFromAppHost( } if res.Deployment != nil { - if res.Deployment.Type != "azure.bicep.v0" { + if res.Deployment.Type != "azure.bicep.v0" && res.Deployment.Type != "azure.bicep.v1" { return nil, fmt.Errorf( - "unexpected deployment type %q. Supported types: [azure.bicep.v0]", res.Deployment.Type) + "unexpected deployment type %q. Supported types: [azure.bicep.v0, azure.bicep.v1]", res.Deployment.Type) } // use a folder with the name of the resource e := manifest.BicepFiles.MkdirAll(resourceName, osutil.PermissionDirectory) From d1c2983d8c048e8aa208329057e795b40dadf5b0 Mon Sep 17 00:00:00 2001 From: Victor Vazquez Date: Mon, 3 Feb 2025 04:10:25 +0000 Subject: [PATCH 4/7] add bicep.v1 to test --- cli/azd/pkg/apphost/testdata/aspire-bicep.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/azd/pkg/apphost/testdata/aspire-bicep.json b/cli/azd/pkg/apphost/testdata/aspire-bicep.json index 0fac0cb7658..9d137090fc2 100644 --- a/cli/azd/pkg/apphost/testdata/aspire-bicep.json +++ b/cli/azd/pkg/apphost/testdata/aspire-bicep.json @@ -17,7 +17,7 @@ } }, "test": { - "type": "azure.bicep.v0", + "type": "azure.bicep.v1", "path": "test.bicep", "params": { "test": "{parameter.value}", From f0f83ce6f183528313ee79c4a1bc55f8459853bb Mon Sep 17 00:00:00 2001 From: Victor Vazquez Date: Mon, 3 Feb 2025 23:57:37 +0000 Subject: [PATCH 5/7] enhance resource group selection for prompt input --- cli/azd/pkg/apphost/generate.go | 19 ++++++++++-- cli/azd/pkg/apphost/manifest.go | 3 ++ .../TestAspireBicepGeneration-main.bicep.snap | 5 ++++ cli/azd/pkg/azure/arm_template.go | 1 + .../provisioning/bicep/bicep_provider.go | 2 +- .../pkg/infra/provisioning/bicep/prompt.go | 12 ++++++++ cli/azd/pkg/prompt/prompter.go | 29 +++++++++++++++---- 7 files changed, 62 insertions(+), 9 deletions(-) diff --git a/cli/azd/pkg/apphost/generate.go b/cli/azd/pkg/apphost/generate.go index 57a42188fde..5fad0082bce 100644 --- a/cli/azd/pkg/apphost/generate.go +++ b/cli/azd/pkg/apphost/generate.go @@ -346,6 +346,10 @@ func BicepTemplate(name string, manifest *Manifest, options AppHostOptions) (*me // 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. } + if parameter.scope != nil { + metadataType = azure.AzdMetadataTypeResourceGroup + parameterMetadata = "{}" + } input := genInput{Name: key, Secret: parameter.Secret, Type: parameter.Type, Value: parameterDefaultValue} parameters = append(parameters, autoGenInput{ genInput: input, @@ -1151,6 +1155,7 @@ func (b *infraGenerator) addDaprStateStoreComponent(name string) { var singleQuotedStringRegex = regexp.MustCompile(`'[^']*'`) var propertyNameRegex = regexp.MustCompile(`'([^']*)':`) var jsonSimpleKeyRegex = regexp.MustCompile(`"([a-zA-Z0-9]*)":`) +var resourceValueOnlyRefRegex = regexp.MustCompile(`^"{([a-zA-Z0-9\-]+)\.[vV]alue}"$`) type ingressDetails struct { // aca ingress definition @@ -1305,11 +1310,21 @@ func (b *infraGenerator) Compile() error { module.Params[paramName] = value } if module.Scope != defaultBicepModuleScope { + rgScope := "resourceGroup" + if matches := resourceValueOnlyRefRegex.FindStringSubmatch(module.Scope); len(matches) == 2 { + manifestResourceName := matches[1] + // Check if the scope is an input + input, hasInput := b.bicepContext.InputParameters[manifestResourceName] + if hasInput { + input.scope = &rgScope + b.bicepContext.InputParameters[manifestResourceName] = input + } + } scope, err := b.resolveBicepReference(module.Scope) if err != nil { return fmt.Errorf("resolving bicep module %s scope %s: %w", moduleName, module.Scope, err) } - scope = fmt.Sprintf("resourceGroup(%s)", scope) + scope = fmt.Sprintf("%s(%s)", rgScope, scope) module.Scope = scope } b.bicepContext.BicepModules[moduleName] = module @@ -1325,7 +1340,7 @@ func (b *infraGenerator) Compile() error { return nil } -// resolve a reference for bicep types. The reference can be from a parameter of for the scope +// resolve a reference for bicep types. The reference can be from a parameter or from the scope func (b *infraGenerator) resolveBicepReference(ref string) (string, error) { // bicep uses ' instead of " for strings, so we need to replace all " with ' singleQuoted := strings.ReplaceAll(ref, "\"", "'") diff --git a/cli/azd/pkg/apphost/manifest.go b/cli/azd/pkg/apphost/manifest.go index bfc4c5d9be8..b93ddb84ec9 100644 --- a/cli/azd/pkg/apphost/manifest.go +++ b/cli/azd/pkg/apphost/manifest.go @@ -190,6 +190,9 @@ type Input struct { Type string `json:"type"` Secret bool `json:"secret"` Default *InputDefault `json:"default,omitempty"` + // When the input is used to set a bicep module scope, the scope is set here. + // This allows generation to add azdMetadata to the bicep parameter. + scope *string } type InputDefaultGenerate struct { diff --git a/cli/azd/pkg/apphost/testdata/TestAspireBicepGeneration-main.bicep.snap b/cli/azd/pkg/apphost/testdata/TestAspireBicepGeneration-main.bicep.snap index 5e7b1bf054d..b4c15a95f0a 100644 --- a/cli/azd/pkg/apphost/testdata/TestAspireBicepGeneration-main.bicep.snap +++ b/cli/azd/pkg/apphost/testdata/TestAspireBicepGeneration-main.bicep.snap @@ -16,6 +16,11 @@ param administrator_login string @secure() param administratorLoginPassword string param parameter string +@metadata({azd: { + type: 'resourceGroup' + config: {} + } +}) param rg_scope string var tags = { diff --git a/cli/azd/pkg/azure/arm_template.go b/cli/azd/pkg/azure/arm_template.go index ca180b2a643..42fb7fa908a 100644 --- a/cli/azd/pkg/azure/arm_template.go +++ b/cli/azd/pkg/azure/arm_template.go @@ -138,6 +138,7 @@ const AzdMetadataTypeLocation AzdMetadataType = "location" const AzdMetadataTypeGenerate AzdMetadataType = "generate" const AzdMetadataTypeGenerateOrManual AzdMetadataType = "generateOrManual" const AzdMetadataTypeNeedForDeploy AzdMetadataType = "needForDeploy" +const AzdMetadataTypeResourceGroup AzdMetadataType = "resourceGroup" type AzdMetadata struct { Type *AzdMetadataType `json:"type,omitempty"` diff --git a/cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go b/cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go index bdad2c8f672..7338325601b 100644 --- a/cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go +++ b/cli/azd/pkg/infra/provisioning/bicep/bicep_provider.go @@ -161,7 +161,7 @@ func (p *BicepProvider) EnsureEnv(ctx context.Context) error { if scope == azure.DeploymentScopeResourceGroup { if p.env.Getenv(environment.ResourceGroupEnvVarName) == "" { - rgName, err := p.prompters.PromptResourceGroup(ctx) + rgName, err := p.prompters.PromptResourceGroup(ctx, prompt.PromptResourceOptions{}) if err != nil { return err } diff --git a/cli/azd/pkg/infra/provisioning/bicep/prompt.go b/cli/azd/pkg/infra/provisioning/bicep/prompt.go index 1b7f23c7661..7f07b83a84a 100644 --- a/cli/azd/pkg/infra/provisioning/bicep/prompt.go +++ b/cli/azd/pkg/infra/provisioning/bicep/prompt.go @@ -16,6 +16,7 @@ import ( "github.com/azure/azure-dev/cli/azd/pkg/input" "github.com/azure/azure-dev/cli/azd/pkg/output" "github.com/azure/azure-dev/cli/azd/pkg/password" + "github.com/azure/azure-dev/cli/azd/pkg/prompt" "github.com/azure/azure-dev/cli/azd/pkg/infra/provisioning" ) @@ -111,6 +112,17 @@ func (p *BicepProvider) promptForParameter( return nil, err } value = location + } else if paramType == provisioning.ParameterTypeString && + azdMetadata.Type != nil && + *azdMetadata.Type == azure.AzdMetadataTypeResourceGroup { + + rgName, err := p.prompters.PromptResourceGroup(ctx, prompt.PromptResourceOptions{ + DisableCreateNew: true, + }) + if err != nil { + return nil, err + } + value = rgName } else if paramType == provisioning.ParameterTypeString && azdMetadata.Type != nil && *azdMetadata.Type == azure.AzdMetadataTypeGenerateOrManual { diff --git a/cli/azd/pkg/prompt/prompter.go b/cli/azd/pkg/prompt/prompter.go index f78d554eba9..1e6236c0e8f 100644 --- a/cli/azd/pkg/prompt/prompter.go +++ b/cli/azd/pkg/prompt/prompter.go @@ -29,7 +29,7 @@ type LocationFilterPredicate func(loc account.Location) bool type Prompter interface { PromptSubscription(ctx context.Context, msg string) (subscriptionId string, err error) PromptLocation(ctx context.Context, subId string, msg string, filter LocationFilterPredicate) (string, error) - PromptResourceGroup(ctx context.Context) (string, error) + PromptResourceGroup(ctx context.Context, options PromptResourceOptions) (string, error) PromptResourceGroupFrom( ctx context.Context, subscriptionId string, location string, options PromptResourceGroupFromOptions) (string, error) } @@ -116,7 +116,11 @@ func (p *DefaultPrompter) PromptLocation( return loc, nil } -func (p *DefaultPrompter) PromptResourceGroup(ctx context.Context) (string, error) { +type PromptResourceOptions struct { + DisableCreateNew bool +} + +func (p *DefaultPrompter) PromptResourceGroup(ctx context.Context, options PromptResourceOptions) (string, error) { return p.PromptResourceGroupFrom( ctx, p.env.GetSubscriptionId(), @@ -125,7 +129,8 @@ func (p *DefaultPrompter) PromptResourceGroup(ctx context.Context) (string, erro Tags: map[string]string{ azure.TagKeyAzdEnvName: p.env.Name(), }, - DefaultName: fmt.Sprintf("rg-%s", p.env.Name()), + DefaultName: fmt.Sprintf("rg-%s", p.env.Name()), + DisableCreateNew: options.DisableCreateNew, }) } @@ -134,6 +139,7 @@ type PromptResourceGroupFromOptions struct { DefaultName string NewResourceGroupHelp string PickResourceGroupHelp string + DisableCreateNew bool } func (p *DefaultPrompter) PromptResourceGroupFrom( @@ -148,10 +154,17 @@ func (p *DefaultPrompter) PromptResourceGroupFrom( return strings.Compare(a.Name, b.Name) }) - choices := make([]string, len(groups)+1) - choices[0] = "Create a new resource group" + canCreateNeResourceGroup := !options.DisableCreateNew + + choices := make([]string, len(groups)) + canCreateOverride := 0 + if canCreateNeResourceGroup { + choices = make([]string, len(groups)+1) + choices[0] = "Create a new resource group" + canCreateOverride = 1 + } for idx, group := range groups { - choices[idx+1] = fmt.Sprintf("%d. %s", idx+1, group.Name) + choices[idx+canCreateOverride] = fmt.Sprintf("%d. %s", idx+1, group.Name) } choice, err := p.console.Select(ctx, input.ConsoleOptions{ @@ -163,6 +176,10 @@ func (p *DefaultPrompter) PromptResourceGroupFrom( return "", fmt.Errorf("selecting resource group: %w", err) } + if !canCreateNeResourceGroup { + return groups[choice].Name, nil + } + if choice > 0 { return groups[choice-1].Name, nil } From 62cfeefa11f3d12c98a47274ec240a4880ffadb0 Mon Sep 17 00:00:00 2001 From: Victor Vazquez Date: Tue, 4 Feb 2025 00:06:15 +0000 Subject: [PATCH 6/7] better prompt --- cli/azd/.vscode/cspell.yaml | 3 +++ cli/azd/pkg/infra/provisioning/bicep/prompt.go | 2 ++ 2 files changed, 5 insertions(+) diff --git a/cli/azd/.vscode/cspell.yaml b/cli/azd/.vscode/cspell.yaml index e4891ef19be..39c3852e7a3 100644 --- a/cli/azd/.vscode/cspell.yaml +++ b/cli/azd/.vscode/cspell.yaml @@ -141,6 +141,9 @@ overrides: words: - GHSA - gjrc + - filename: pkg/apphost/generate.go + words: + - alue ignorePaths: - "**/*_test.go" - "**/mock*.go" diff --git a/cli/azd/pkg/infra/provisioning/bicep/prompt.go b/cli/azd/pkg/infra/provisioning/bicep/prompt.go index 7f07b83a84a..0a20aec0d13 100644 --- a/cli/azd/pkg/infra/provisioning/bicep/prompt.go +++ b/cli/azd/pkg/infra/provisioning/bicep/prompt.go @@ -116,6 +116,8 @@ func (p *BicepProvider) promptForParameter( azdMetadata.Type != nil && *azdMetadata.Type == azure.AzdMetadataTypeResourceGroup { + p.console.Message(ctx, fmt.Sprintf( + "Parameter %s requires an %s resource group.", output.WithUnderline(key), output.WithBold("existing"))) rgName, err := p.prompters.PromptResourceGroup(ctx, prompt.PromptResourceOptions{ DisableCreateNew: true, }) From 14ab5e1939e5fe07a0dad594dc616a5b8a1c0ba7 Mon Sep 17 00:00:00 2001 From: Victor Vazquez Date: Tue, 4 Feb 2025 00:14:01 +0000 Subject: [PATCH 7/7] lint --- cli/azd/pkg/infra/provisioning/bicep/prompt.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/azd/pkg/infra/provisioning/bicep/prompt.go b/cli/azd/pkg/infra/provisioning/bicep/prompt.go index 0a20aec0d13..25099ee3382 100644 --- a/cli/azd/pkg/infra/provisioning/bicep/prompt.go +++ b/cli/azd/pkg/infra/provisioning/bicep/prompt.go @@ -117,7 +117,7 @@ func (p *BicepProvider) promptForParameter( *azdMetadata.Type == azure.AzdMetadataTypeResourceGroup { p.console.Message(ctx, fmt.Sprintf( - "Parameter %s requires an %s resource group.", output.WithUnderline(key), output.WithBold("existing"))) + "Parameter %s requires an %s resource group.", output.WithUnderline("%s", key), output.WithBold("existing"))) rgName, err := p.prompters.PromptResourceGroup(ctx, prompt.PromptResourceOptions{ DisableCreateNew: true, })