diff --git a/cli/azd/cmd/infra_synth.go b/cli/azd/cmd/infra_synth.go index 9df815e6cb6..a079fcaa8b0 100644 --- a/cli/azd/cmd/infra_synth.go +++ b/cli/azd/cmd/infra_synth.go @@ -79,6 +79,7 @@ func newInfraSynthAction( } var infraSynthFeature = alpha.MustFeatureKey("infraSynth") +var bicepForContainerAppFeature = alpha.MustFeatureKey("aspire.useBicepForContainerApps") func (a *infraSynthAction) Run(ctx context.Context) (*actions.ActionResult, error) { if !a.alphaManager.IsEnabled(infraSynthFeature) { @@ -93,7 +94,9 @@ func (a *infraSynthAction) Run(ctx context.Context) (*actions.ActionResult, erro spinnerMessage := "Synthesizing infrastructure" a.console.ShowSpinner(ctx, spinnerMessage, input.Step) - synthFS, err := a.importManager.SynthAllInfrastructure(ctx, a.projectConfig) + synthFS, err := a.importManager.SynthAllInfrastructure(ctx, a.projectConfig, &project.SynthOptions{ + UseBicepForContainerApps: a.alphaManager.IsEnabled(bicepForContainerAppFeature), + }) if err != nil { a.console.StopSpinner(ctx, spinnerMessage, input.StepFailed) return nil, err diff --git a/cli/azd/pkg/project/dotnet_importer.go b/cli/azd/pkg/project/dotnet_importer.go index bf812f5aa1d..6a804354e29 100644 --- a/cli/azd/pkg/project/dotnet_importer.go +++ b/cli/azd/pkg/project/dotnet_importer.go @@ -443,7 +443,7 @@ func evaluateSingleExpressionMatch( } func (ai *DotNetImporter) SynthAllInfrastructure( - ctx context.Context, p *ProjectConfig, svcConfig *ServiceConfig, + ctx context.Context, p *ProjectConfig, svcConfig *ServiceConfig, useBicepForContainerApps bool, ) (fs.FS, error) { manifest, err := ai.ReadManifest(ctx, svcConfig) if err != nil { @@ -511,17 +511,6 @@ func (ai *DotNetImporter) SynthAllInfrastructure( // manifest is written to a file name "containerApp.tmpl.yaml" in the same directory as the project that produces the // container we will deploy. writeManifestForResource := func(name string) error { - containerAppManifest, err := apphost.ContainerAppManifestTemplateForProject( - manifest, name, apphost.AppHostOptions{}) - if err != nil { - return fmt.Errorf("generating containerApp.tmpl.yaml for resource %s: %w", name, err) - } - - bicepManifest, err := apphost.BicepModuleForProject(manifest, name, apphost.AppHostOptions{}) - if err != nil { - return fmt.Errorf("generating bicep module for resource %s: %w", name, err) - } - normalPath, err := filepath.EvalSymlinks(svcConfig.Path()) if err != nil { return err @@ -532,19 +521,42 @@ func (ai *DotNetImporter) SynthAllInfrastructure( return err } - manifestPath := filepath.Join(filepath.Dir(projectRelPath), "infra", fmt.Sprintf("%s.tmpl.yaml", name)) - bicepPath := filepath.Join(filepath.Dir(projectRelPath), "infra", fmt.Sprintf("%s.bicep", name)) + if useBicepForContainerApps { + bicepManifest, err := apphost.BicepModuleForProject(manifest, name, apphost.AppHostOptions{}) + if err != nil { + return fmt.Errorf("generating bicep module for resource %s: %w", name, err) + } - if err := generatedFS.MkdirAll(filepath.Dir(manifestPath), osutil.PermissionDirectoryOwnerOnly); err != nil { - return err - } + bicepPath := filepath.Join(filepath.Dir(projectRelPath), "infra", fmt.Sprintf("%s.bicep", name)) - err = generatedFS.WriteFile(manifestPath, []byte(containerAppManifest), osutil.PermissionFileOwnerOnly) - if err != nil { - return err + if err := generatedFS.MkdirAll(filepath.Dir(bicepPath), osutil.PermissionDirectoryOwnerOnly); err != nil { + return err + } + + err = generatedFS.WriteFile(bicepPath, []byte(bicepManifest), osutil.PermissionFileOwnerOnly) + if err != nil { + return err + } + } else { + containerAppManifest, err := apphost.ContainerAppManifestTemplateForProject( + manifest, name, apphost.AppHostOptions{}) + if err != nil { + return fmt.Errorf("generating containerApp.tmpl.yaml for resource %s: %w", name, err) + } + + manifestPath := filepath.Join(filepath.Dir(projectRelPath), "infra", fmt.Sprintf("%s.tmpl.yaml", name)) + + if err := generatedFS.MkdirAll(filepath.Dir(manifestPath), osutil.PermissionDirectoryOwnerOnly); err != nil { + return err + } + + err = generatedFS.WriteFile(manifestPath, []byte(containerAppManifest), osutil.PermissionFileOwnerOnly) + if err != nil { + return err + } } - return generatedFS.WriteFile(bicepPath, []byte(bicepManifest), osutil.PermissionFileOwnerOnly) + return nil } for name := range apphost.ProjectPaths(manifest) { diff --git a/cli/azd/pkg/project/importer.go b/cli/azd/pkg/project/importer.go index 3f5ec6bc5b4..33d9ad8dc06 100644 --- a/cli/azd/pkg/project/importer.go +++ b/cli/azd/pkg/project/importer.go @@ -20,6 +20,10 @@ type ImportManager struct { dotNetImporter *DotNetImporter } +type SynthOptions struct { + UseBicepForContainerApps bool +} + func NewImportManager(dotNetImporter *DotNetImporter) *ImportManager { return &ImportManager{ dotNetImporter: dotNetImporter, @@ -180,14 +184,20 @@ func pathHasModule(path, module string) (bool, error) { } -func (im *ImportManager) SynthAllInfrastructure(ctx context.Context, projectConfig *ProjectConfig) (fs.FS, error) { +func (im *ImportManager) SynthAllInfrastructure( + ctx context.Context, projectConfig *ProjectConfig, options *SynthOptions, +) (fs.FS, error) { + if options == nil { + options = &SynthOptions{} + } + for _, svcConfig := range projectConfig.Services { if svcConfig.Language == ServiceLanguageDotNet { if len(projectConfig.Services) != 1 { return nil, errNoMultipleServicesWithAppHost } - return im.dotNetImporter.SynthAllInfrastructure(ctx, projectConfig, svcConfig) + return im.dotNetImporter.SynthAllInfrastructure(ctx, projectConfig, svcConfig, options.UseBicepForContainerApps) } } diff --git a/cli/azd/pkg/project/service_target_dotnet_containerapp.go b/cli/azd/pkg/project/service_target_dotnet_containerapp.go index f538fed1e4d..754241fae1b 100644 --- a/cli/azd/pkg/project/service_target_dotnet_containerapp.go +++ b/cli/azd/pkg/project/service_target_dotnet_containerapp.go @@ -7,6 +7,7 @@ import ( "context" "encoding/json" "fmt" + "io" "log" "net/url" "os" @@ -31,6 +32,8 @@ import ( "github.com/azure/azure-dev/cli/azd/pkg/tools/dotnet" ) +var bicepForContainerAppFeature = alpha.MustFeatureKey("aspire.useBicepForContainerApps") + type dotnetContainerAppTarget struct { env *environment.Environment containerHelper *ContainerHelper @@ -174,8 +177,10 @@ func (at *dotnetContainerAppTarget) Deploy( progress.SetProgress(NewServiceProgress("Updating container app")) + useBicepForContainerApps := at.alphaFeatureManager.IsEnabled(bicepForContainerAppFeature) + var manifest string - var bicepTemplate *azure.RawArmTemplate + var armTemplate *azure.RawArmTemplate appHostRoot := serviceConfig.DotNetContainerApp.AppHostPath if f, err := os.Stat(appHostRoot); err == nil && !f.IsDir() { @@ -187,14 +192,14 @@ func (at *dotnetContainerAppTarget) Deploy( bicepPath := filepath.Join(appHostRoot, "infra", fmt.Sprintf("%s.bicep", serviceConfig.DotNetContainerApp.ProjectName)) - if _, err := os.Stat(bicepPath); err == nil { + if _, err := os.Stat(bicepPath); err == nil && useBicepForContainerApps { log.Printf("using container app manifest from %s", bicepPath) res, err := at.bicepCli.Build(ctx, bicepPath) if err != nil { return nil, fmt.Errorf("building container app bicep: %w", err) } - bicepTemplate = to.Ptr(azure.RawArmTemplate(res.Compiled)) - } else if _, err := os.Stat(manifestPath); err == nil { + armTemplate = to.Ptr(azure.RawArmTemplate(res.Compiled)) + } else if _, err := os.Stat(manifestPath); err == nil && !useBicepForContainerApps { log.Printf("using container app manifest from %s", manifestPath) contents, err := os.ReadFile(manifestPath) @@ -202,7 +207,7 @@ func (at *dotnetContainerAppTarget) Deploy( return nil, fmt.Errorf("reading container app manifest: %w", err) } manifest = string(contents) - } else { + } else if !useBicepForContainerApps { log.Printf( "generating container app manifest from %s for project %s", serviceConfig.DotNetContainerApp.AppHostPath, @@ -217,6 +222,45 @@ func (at *dotnetContainerAppTarget) Deploy( return nil, fmt.Errorf("generating container app manifest: %w", err) } manifest = generatedManifest + } else { + log.Printf( + "generating container app bicep from %s for project %s", + serviceConfig.DotNetContainerApp.AppHostPath, + serviceConfig.DotNetContainerApp.ProjectName) + + generatedBicep, err := apphost.BicepModuleForProject( + serviceConfig.DotNetContainerApp.Manifest, + serviceConfig.DotNetContainerApp.ProjectName, + apphost.AppHostOptions{}, + ) + if err != nil { + return nil, fmt.Errorf("generating container app manifest: %w", err) + } + + compiled, err := func() (azure.RawArmTemplate, error) { + f, err := os.CreateTemp("", fmt.Sprintf("azd-bicep-%s.bicep", serviceConfig.DotNetContainerApp.ProjectName)) + if err != nil { + return azure.RawArmTemplate{}, fmt.Errorf("creating temporary file: %w", err) + } + defer func() { + _ = f.Close() + _ = os.Remove(f.Name()) + }() + _, err = io.Copy(f, strings.NewReader(generatedBicep)) + if err != nil { + return azure.RawArmTemplate{}, fmt.Errorf("writing bicep file: %w", err) + } + + res, err := at.bicepCli.Build(ctx, f.Name()) + if err != nil { + return azure.RawArmTemplate{}, fmt.Errorf("building container app bicep: %w", err) + } + return azure.RawArmTemplate(res.Compiled), nil + }() + if err != nil { + return nil, err + } + armTemplate = &compiled } fns := &containerAppTemplateManifestFuncs{ @@ -255,9 +299,9 @@ func (at *dotnetContainerAppTarget) Deploy( inputs = make(map[string]any) } - if bicepTemplate != nil { + if armTemplate != nil { var parsed *azure.ArmTemplate - if err := json.Unmarshal(*bicepTemplate, &parsed); err != nil { + if err := json.Unmarshal(*armTemplate, &parsed); err != nil { return nil, fmt.Errorf("parsing arm template: %w", err) } @@ -313,7 +357,7 @@ func (at *dotnetContainerAppTarget) Deploy( at.env.GetSubscriptionId(), targetResource.ResourceGroupName(), at.deploymentService.GenerateDeploymentName(serviceConfig.Name), - *bicepTemplate, + *armTemplate, params, nil) if err != nil { diff --git a/cli/azd/resources/alpha_features.yaml b/cli/azd/resources/alpha_features.yaml index fa12515db7f..7950d254813 100644 --- a/cli/azd/resources/alpha_features.yaml +++ b/cli/azd/resources/alpha_features.yaml @@ -14,3 +14,5 @@ description: "Do not change Ingress Session Affinity when deploying Azure Container Apps." - id: deployment.stacks description: "Enables Azure deployment stacks for ARM/Bicep based deployments." +- id: aspire.useBicepForContainerApps + description: "Use bicep for container app deployments instead of yaml." diff --git a/cli/azd/test/functional/testdata/snaps/aspire-full/AspireAzdTests.AppHost/infra/apiservice.bicep b/cli/azd/test/functional/testdata/snaps/aspire-full/AspireAzdTests.AppHost/infra/apiservice.bicep deleted file mode 100644 index daed1f235e9..00000000000 --- a/cli/azd/test/functional/testdata/snaps/aspire-full/AspireAzdTests.AppHost/infra/apiservice.bicep +++ /dev/null @@ -1,98 +0,0 @@ -@description('') -param location string = resourceGroup().location - -@metadata({azd: { defaultValueExpr: '{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}' } }) -param principalId string - -@metadata({azd : { defaultValueExpr: '{{ .Env.MANAGED_IDENTITY_CLIENT_ID }}' } }) -param principalClientId string - -@metadata({azd: { defaultValueExpr: '{{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}' } }) -param environmentId string - -@metadata({azd: { defaultValueExpr: '{{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}' } }) -param containerRegistryEndpoint string - -@metadata({azd: { defaultValueExpr: '{{ .Image }}' } }) -param image string - -@metadata({azd: { defaultValueExpr: '{{ targetPortOrDefault 8080 }}' } }) -param targetPort int -@metadata({azd: { defaultValueExpr: '{{ targetPortOrDefault 0 }}' } }) -param apiservice_bindings_http_targetPort string - -resource app 'Microsoft.App/containerApps@2024-02-02-preview' = { - name: 'apiservice' - location: location - identity: { - type: 'UserAssigned' - userAssignedIdentities: { - '${principalId}': {} - } - } - properties: { - environmentId: environmentId - configuration: { - activeRevisionsMode: 'Single' - runtime: { - dotnet: { - autoConfigureDataProtection: true - } - } - ingress: { - external: false - targetPort: targetPort - transport: 'http' - allowInsecure: true - } - registries: [ - { - server: containerRegistryEndpoint - identity: principalId - } - ] - } - template: { - containers: [ - { - image: image - name: 'apiservice' - env: [ - { - name: 'AZURE_CLIENT_ID' - value: principalClientId - } - { - name: 'ASPNETCORE_FORWARDEDHEADERS_ENABLED' - value: 'true' - } - { - name: 'HTTP_PORTS' - value: '${apiservice_bindings_http_targetPort}' - } - { - name: 'OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES' - value: 'true' - } - { - name: 'OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES' - value: 'true' - } - { - name: 'OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY' - value: 'in_memory' - } - ] - } - ] - scale: { - minReplicas: 1 - } - } - } - tags: { - 'azd-service-name': 'apiservice' - 'aspire-resource-name': 'apiservice' - } -} - diff --git a/cli/azd/test/functional/testdata/snaps/aspire-full/AspireAzdTests.AppHost/infra/pubsub.bicep b/cli/azd/test/functional/testdata/snaps/aspire-full/AspireAzdTests.AppHost/infra/pubsub.bicep deleted file mode 100644 index e9a29ea3007..00000000000 --- a/cli/azd/test/functional/testdata/snaps/aspire-full/AspireAzdTests.AppHost/infra/pubsub.bicep +++ /dev/null @@ -1,76 +0,0 @@ -@description('') -param location string = resourceGroup().location - -@metadata({azd: { defaultValueExpr: '{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}' } }) -param principalId string - -@metadata({azd : { defaultValueExpr: '{{ .Env.MANAGED_IDENTITY_CLIENT_ID }}' } }) -param principalClientId string - -@metadata({azd: { defaultValueExpr: '{{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}' } }) -param environmentId string - -@metadata({azd: { defaultValueExpr: '{{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}' } }) -param containerRegistryEndpoint string - -@metadata({azd: { defaultValueExpr: '{{ .Image }}' } }) -param image string - -@metadata({azd: { defaultValueExpr: '6379' } }) -param targetPort int - -resource app 'Microsoft.App/containerApps@2024-02-02-preview' = { - name: 'pubsub' - location: location - identity: { - type: 'UserAssigned' - userAssignedIdentities: { - '${principalId}': {} - } - } - properties: { - environmentId: environmentId - configuration: { - activeRevisionsMode: 'Single' - runtime: { - dotnet: { - autoConfigureDataProtection: true - } - } - ingress: { - external: false - targetPort: targetPort - transport: 'tcp' - allowInsecure: false - } - registries: [ - { - server: containerRegistryEndpoint - identity: principalId - } - ] - } - template: { - containers: [ - { - image: image - name: 'pubsub' - env: [ - { - name: 'AZURE_CLIENT_ID' - value: principalClientId - } - ] - } - ] - scale: { - minReplicas: 1 - } - } - } - tags: { - 'azd-service-name': 'pubsub' - 'aspire-resource-name': 'pubsub' - } -} - diff --git a/cli/azd/test/functional/testdata/snaps/aspire-full/AspireAzdTests.AppHost/infra/webfrontend.bicep b/cli/azd/test/functional/testdata/snaps/aspire-full/AspireAzdTests.AppHost/infra/webfrontend.bicep deleted file mode 100644 index 77d208e2eb1..00000000000 --- a/cli/azd/test/functional/testdata/snaps/aspire-full/AspireAzdTests.AppHost/infra/webfrontend.bicep +++ /dev/null @@ -1,140 +0,0 @@ -@description('') -param location string = resourceGroup().location - -@metadata({azd: { defaultValueExpr: '{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}' } }) -param principalId string - -@metadata({azd : { defaultValueExpr: '{{ .Env.MANAGED_IDENTITY_CLIENT_ID }}' } }) -param principalClientId string - -@metadata({azd: { defaultValueExpr: '{{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}' } }) -param environmentId string - -@metadata({azd: { defaultValueExpr: '{{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}' } }) -param containerRegistryEndpoint string - -@metadata({azd: { defaultValueExpr: '{{ .Image }}' } }) -param image string - -@metadata({azd: { defaultValueExpr: '{{ targetPortOrDefault 8080 }}' } }) -param targetPort int -@metadata({azd: { defaultValueExpr: 'http://apiservice.internal.{{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN }}' } }) -param apiservice_bindings_http_url string -@metadata({azd: { defaultValueExpr: 'https://apiservice.internal.{{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN }}' } }) -param apiservice_bindings_https_url string -@metadata({azd: { defaultValueExpr: '{{ secretOutput {{ .Env.SERVICE_BINDING_KVF2EDECB5_ENDPOINT }}secrets/connectionString }}' } }) -param cosmos_connectionString string -@metadata({azd: { defaultValueExpr: '{{ .Env.STORAGE_BLOBENDPOINT }}' } }) -param markdown_connectionString string -@metadata({azd: { defaultValueExpr: '{{ .Env.STORAGE_QUEUEENDPOINT }}' } }) -param messages_connectionString string -@metadata({azd: { defaultValueExpr: 'pubsub:6379' } }) -param pubsub_connectionString string -@metadata({azd: { defaultValueExpr: '{{ .Env.STORAGE_TABLEENDPOINT }}' } }) -param requestlog_connectionString string -@metadata({azd: { defaultValueExpr: '{{ targetPortOrDefault 0 }}' } }) -param webfrontend_bindings_http_targetPort string - -resource app 'Microsoft.App/containerApps@2024-02-02-preview' = { - name: 'webfrontend' - location: location - identity: { - type: 'UserAssigned' - userAssignedIdentities: { - '${principalId}': {} - } - } - properties: { - environmentId: environmentId - configuration: { - activeRevisionsMode: 'Single' - runtime: { - dotnet: { - autoConfigureDataProtection: true - } - } - ingress: { - external: true - targetPort: targetPort - transport: 'http' - allowInsecure: false - } - registries: [ - { - server: containerRegistryEndpoint - identity: principalId - } - ] - } - template: { - containers: [ - { - image: image - name: 'webfrontend' - env: [ - { - name: 'AZURE_CLIENT_ID' - value: principalClientId - } - { - name: 'ASPNETCORE_FORWARDEDHEADERS_ENABLED' - value: 'true' - } - { - name: 'ConnectionStrings__cosmos' - value: '${cosmos_connectionString}' - } - { - name: 'ConnectionStrings__markdown' - value: '${markdown_connectionString}' - } - { - name: 'ConnectionStrings__messages' - value: '${messages_connectionString}' - } - { - name: 'ConnectionStrings__pubsub' - value: '${pubsub_connectionString}' - } - { - name: 'ConnectionStrings__requestlog' - value: '${requestlog_connectionString}' - } - { - name: 'HTTP_PORTS' - value: '${webfrontend_bindings_http_targetPort}' - } - { - name: 'OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES' - value: 'true' - } - { - name: 'OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES' - value: 'true' - } - { - name: 'OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY' - value: 'in_memory' - } - { - name: 'services__apiservice__http__0' - value: '${apiservice_bindings_http_url}' - } - { - name: 'services__apiservice__https__0' - value: '${apiservice_bindings_https_url}' - } - ] - } - ] - scale: { - minReplicas: 1 - } - } - } - tags: { - 'azd-service-name': 'webfrontend' - 'aspire-resource-name': 'webfrontend' - } -} - diff --git a/cli/azd/test/functional/testdata/snaps/aspire-full/AspireAzdTests.AppHost/infra/worker.bicep b/cli/azd/test/functional/testdata/snaps/aspire-full/AspireAzdTests.AppHost/infra/worker.bicep deleted file mode 100644 index 4ee0512ddd9..00000000000 --- a/cli/azd/test/functional/testdata/snaps/aspire-full/AspireAzdTests.AppHost/infra/worker.bicep +++ /dev/null @@ -1,82 +0,0 @@ -@description('') -param location string = resourceGroup().location - -@metadata({azd: { defaultValueExpr: '{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}' } }) -param principalId string - -@metadata({azd : { defaultValueExpr: '{{ .Env.MANAGED_IDENTITY_CLIENT_ID }}' } }) -param principalClientId string - -@metadata({azd: { defaultValueExpr: '{{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}' } }) -param environmentId string - -@metadata({azd: { defaultValueExpr: '{{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}' } }) -param containerRegistryEndpoint string - -@metadata({azd: { defaultValueExpr: '{{ .Image }}' } }) -param image string - -@metadata({azd: { defaultValueExpr: '' } }) -param targetPort int - -resource app 'Microsoft.App/containerApps@2024-02-02-preview' = { - name: 'worker' - location: location - identity: { - type: 'UserAssigned' - userAssignedIdentities: { - '${principalId}': {} - } - } - properties: { - environmentId: environmentId - configuration: { - activeRevisionsMode: 'Single' - runtime: { - dotnet: { - autoConfigureDataProtection: true - } - } - registries: [ - { - server: containerRegistryEndpoint - identity: principalId - } - ] - } - template: { - containers: [ - { - image: image - name: 'worker' - env: [ - { - name: 'AZURE_CLIENT_ID' - value: principalClientId - } - { - name: 'OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES' - value: 'true' - } - { - name: 'OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES' - value: 'true' - } - { - name: 'OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY' - value: 'in_memory' - } - ] - } - ] - scale: { - minReplicas: 1 - } - } - } - tags: { - 'azd-service-name': 'worker' - 'aspire-resource-name': 'worker' - } -} -