diff --git a/Aspire.sln b/Aspire.sln index ad79e3c7f1..2f417f04c8 100644 --- a/Aspire.sln +++ b/Aspire.sln @@ -307,6 +307,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProxylessEndToEnd.ApiServic EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ProxylessEndToEnd.AppHost", "playground\ProxylessEndToEnd\ProxylessEndToEnd.AppHost\ProxylessEndToEnd.AppHost.csproj", "{0244203D-7491-4414-9C88-10BFED9C5B2D}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cdk", "cdk", "{C3F48531-87D9-4E52-90AC-715A3E55751A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CdkSample.AppHost", "playground\cdk\CdkSample.AppHost\CdkSample.AppHost.csproj", "{A357411A-5909-4A49-9519-12A935F84395}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CdkSample.ApiService", "playground\cdk\CdkSample.ApiService\CdkSample.ApiService.csproj", "{4601F5A2-E445-41B2-9C1F-2CE016642E62}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CustomResources", "CustomResources", "{867A00A7-AF8E-4396-9583-982FBB31762C}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomResources.AppHost", "playground\CustomResources\CustomResources.AppHost\CustomResources.AppHost.csproj", "{4231B6F1-1110-4992-A727-8F1176A47440}" @@ -789,6 +794,14 @@ Global {0244203D-7491-4414-9C88-10BFED9C5B2D}.Debug|Any CPU.Build.0 = Debug|Any CPU {0244203D-7491-4414-9C88-10BFED9C5B2D}.Release|Any CPU.ActiveCfg = Release|Any CPU {0244203D-7491-4414-9C88-10BFED9C5B2D}.Release|Any CPU.Build.0 = Release|Any CPU + {A357411A-5909-4A49-9519-12A935F84395}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A357411A-5909-4A49-9519-12A935F84395}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A357411A-5909-4A49-9519-12A935F84395}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A357411A-5909-4A49-9519-12A935F84395}.Release|Any CPU.Build.0 = Release|Any CPU + {4601F5A2-E445-41B2-9C1F-2CE016642E62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4601F5A2-E445-41B2-9C1F-2CE016642E62}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4601F5A2-E445-41B2-9C1F-2CE016642E62}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4601F5A2-E445-41B2-9C1F-2CE016642E62}.Release|Any CPU.Build.0 = Release|Any CPU {4231B6F1-1110-4992-A727-8F1176A47440}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4231B6F1-1110-4992-A727-8F1176A47440}.Debug|Any CPU.Build.0 = Debug|Any CPU {4231B6F1-1110-4992-A727-8F1176A47440}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -955,6 +968,9 @@ Global {6723830C-7E39-4709-836B-17E5EE426751} = {A4F7DD00-59FE-4754-B0CF-3CC8C0856E17} {5345A33F-B845-4F2D-A934-F9D73327B1CE} = {A4F7DD00-59FE-4754-B0CF-3CC8C0856E17} {0244203D-7491-4414-9C88-10BFED9C5B2D} = {9C30FFD6-2262-45E7-B010-24B30E0433C2} + {C3F48531-87D9-4E52-90AC-715A3E55751A} = {D173887B-AF42-4576-B9C1-96B9E9B3D9C0} + {A357411A-5909-4A49-9519-12A935F84395} = {C3F48531-87D9-4E52-90AC-715A3E55751A} + {4601F5A2-E445-41B2-9C1F-2CE016642E62} = {C3F48531-87D9-4E52-90AC-715A3E55751A} {867A00A7-AF8E-4396-9583-982FBB31762C} = {D173887B-AF42-4576-B9C1-96B9E9B3D9C0} {4231B6F1-1110-4992-A727-8F1176A47440} = {867A00A7-AF8E-4396-9583-982FBB31762C} EndGlobalSection diff --git a/Directory.Packages.props b/Directory.Packages.props index 91e2474a54..72df8e855f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -19,9 +19,11 @@ + - - + + + diff --git a/NuGet.config b/NuGet.config index e8f26f0e63..960384ec28 100644 --- a/NuGet.config +++ b/NuGet.config @@ -17,9 +17,15 @@ + - + + + + + + diff --git a/playground/cdk/CdkSample.ApiService/CdkSample.ApiService.csproj b/playground/cdk/CdkSample.ApiService/CdkSample.ApiService.csproj new file mode 100644 index 0000000000..88b35ed33a --- /dev/null +++ b/playground/cdk/CdkSample.ApiService/CdkSample.ApiService.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/playground/cdk/CdkSample.ApiService/CdkSample.ApiService.http b/playground/cdk/CdkSample.ApiService/CdkSample.ApiService.http new file mode 100644 index 0000000000..59fcd09ca5 --- /dev/null +++ b/playground/cdk/CdkSample.ApiService/CdkSample.ApiService.http @@ -0,0 +1,6 @@ +@CosmosEndToEnd.ApiService_HostAddress = http://localhost:5193 + +GET {{SqlServerEndToEnd.ApiService_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/playground/cdk/CdkSample.ApiService/Program.cs b/playground/cdk/CdkSample.ApiService/Program.cs new file mode 100644 index 0000000000..643f6ce15e --- /dev/null +++ b/playground/cdk/CdkSample.ApiService/Program.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); + +var app = builder.Build(); + +app.MapGet("/", (IConfiguration config) => +{ + return $"TABLE_URI is: {config["TABLE_URI"]}"; +}); + +app.Run(); diff --git a/playground/cdk/CdkSample.ApiService/Properties/launchSettings.json b/playground/cdk/CdkSample.ApiService/Properties/launchSettings.json new file mode 100644 index 0000000000..f7bf310e7a --- /dev/null +++ b/playground/cdk/CdkSample.ApiService/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5180", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/playground/cdk/CdkSample.ApiService/appsettings.Development.json b/playground/cdk/CdkSample.ApiService/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/playground/cdk/CdkSample.ApiService/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/playground/cdk/CdkSample.ApiService/appsettings.json b/playground/cdk/CdkSample.ApiService/appsettings.json new file mode 100644 index 0000000000..10f68b8c8b --- /dev/null +++ b/playground/cdk/CdkSample.ApiService/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/playground/cdk/CdkSample.AppHost/CdkSample.AppHost.csproj b/playground/cdk/CdkSample.AppHost/CdkSample.AppHost.csproj new file mode 100644 index 0000000000..40ab6d9383 --- /dev/null +++ b/playground/cdk/CdkSample.AppHost/CdkSample.AppHost.csproj @@ -0,0 +1,23 @@ + + + + Exe + net8.0 + enable + enable + true + 44b9bf37-1892-4852-8b5f-153e3ac5d24c + + + + + + + + + + + + + + diff --git a/playground/cdk/CdkSample.AppHost/Directory.Build.props b/playground/cdk/CdkSample.AppHost/Directory.Build.props new file mode 100644 index 0000000000..b9b39c05e8 --- /dev/null +++ b/playground/cdk/CdkSample.AppHost/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/playground/cdk/CdkSample.AppHost/Directory.Build.targets b/playground/cdk/CdkSample.AppHost/Directory.Build.targets new file mode 100644 index 0000000000..b7ba77268f --- /dev/null +++ b/playground/cdk/CdkSample.AppHost/Directory.Build.targets @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/playground/cdk/CdkSample.AppHost/Program.cs b/playground/cdk/CdkSample.AppHost/Program.cs new file mode 100644 index 0000000000..27354cf9fc --- /dev/null +++ b/playground/cdk/CdkSample.AppHost/Program.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Azure.Provisioning.Storage; +using Azure.ResourceManager.Storage.Models; + +var builder = DistributedApplication.CreateBuilder(args); +builder.AddAzureProvisioning(); + +var sku = builder.AddParameter("storagesku"); + +var construct1 = builder.AddAzureConstruct("construct1", (construct) => +{ + var account = construct.AddStorageAccount( + name: "bob", + kind: StorageKind.BlobStorage, + sku: StorageSkuName.StandardLrs + ); + account.AssignParameter(a => a.Sku.Name, construct.AddParameter(sku)); + + account.AddOutput(data => data.PrimaryEndpoints.TableUri, "tableUri", isSecure: true); +}); + +builder.AddProject("api") + .WithEnvironment("TABLE_URI", construct1.GetOutput("tableUri")); + +// This project is only added in playground projects to support development/debugging +// of the dashboard. It is not required in end developer code. Comment out this code +// to test end developer dashboard launch experience. Refer to Directory.Build.props +// for the path to the dashboard binary (defaults to the Aspire.Dashboard bin output +// in the artifacts dir). +builder.AddProject(KnownResourceNames.AspireDashboard); + +builder.Build().Run(); diff --git a/playground/cdk/CdkSample.AppHost/Properties/launchSettings.json b/playground/cdk/CdkSample.AppHost/Properties/launchSettings.json new file mode 100644 index 0000000000..32514f6378 --- /dev/null +++ b/playground/cdk/CdkSample.AppHost/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15888", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16175", + "DOTNET_ASPIRE_SHOW_DASHBOARD_RESOURCES": "true" + } + }, + "generate-manifest": { + "commandName": "Project", + "launchBrowser": true, + "dotnetRunMessages": true, + "commandLineArgs": "--publisher manifest --output-path aspire-manifest.json", + "applicationUrl": "http://localhost:15888", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:16175" + } + } + } +} diff --git a/playground/cdk/CdkSample.AppHost/appsettings.Development.json b/playground/cdk/CdkSample.AppHost/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/playground/cdk/CdkSample.AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/playground/cdk/CdkSample.AppHost/appsettings.json b/playground/cdk/CdkSample.AppHost/appsettings.json new file mode 100644 index 0000000000..a40f92dadf --- /dev/null +++ b/playground/cdk/CdkSample.AppHost/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + }, + "Parameters": { + "val": "value from config" + } +} diff --git a/playground/cdk/CdkSample.AppHost/aspire-manifest.json b/playground/cdk/CdkSample.AppHost/aspire-manifest.json new file mode 100644 index 0000000000..556354092c --- /dev/null +++ b/playground/cdk/CdkSample.AppHost/aspire-manifest.json @@ -0,0 +1,41 @@ +{ + "resources": { + "storagesku": { + "type": "parameter.v0", + "value": "{storagesku.inputs.value}", + "inputs": { + "value": { + "type": "string" + } + } + }, + "construct1": { + "type": "azure.bicep.v0", + "path": "construct1.module.bicep", + "params": { + "storagesku": "{storagesku.value}" + } + }, + "api": { + "type": "project.v0", + "path": "../CdkSample.ApiService/CdkSample.ApiService.csproj", + "env": { + "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES": "true", + "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true", + "TABLE_URI": "{construct1.outputs.tableUri}" + }, + "bindings": { + "http": { + "scheme": "http", + "protocol": "tcp", + "transport": "http" + }, + "https": { + "scheme": "https", + "protocol": "tcp", + "transport": "http" + } + } + } + } +} \ No newline at end of file diff --git a/playground/cdk/CdkSample.AppHost/construct1.module.bicep b/playground/cdk/CdkSample.AppHost/construct1.module.bicep new file mode 100644 index 0000000000..d1892b40ed --- /dev/null +++ b/playground/cdk/CdkSample.AppHost/construct1.module.bicep @@ -0,0 +1,21 @@ +targetScope = 'resourceGroup' + +@description('') +param location string = resourceGroup().location + +@description('') +param storagesku string + + +resource storageAccount_unUi1Obb4 'Microsoft.Storage/storageAccounts@2022-09-01' = { + name: toLower(take(concat('bob', uniqueString(resourceGroup().id)), 24)) + location: location + sku: { + name: storagesku + } + kind: 'StorageV2' + properties: { + } +} + +output tableUri string = storageAccount_unUi1Obb4.properties.primaryEndpoints.table diff --git a/src/Aspire.Hosting.Azure.Provisioning/Provisioners/BicepProvisioner.cs b/src/Aspire.Hosting.Azure.Provisioning/Provisioners/BicepProvisioner.cs index 0373bd52ba..604b65c0a6 100644 --- a/src/Aspire.Hosting.Azure.Provisioning/Provisioners/BicepProvisioner.cs +++ b/src/Aspire.Hosting.Azure.Provisioning/Provisioners/BicepProvisioner.cs @@ -240,8 +240,11 @@ private static void PopulateWellKnownParameters(AzureBicepResource resource, Pro resource.Parameters.Remove(AzureBicepResource.KnownParameters.LogAnalyticsWorkspaceId); } - // Always specify the location - resource.Parameters[AzureBicepResource.KnownParameters.Location] = context.Location.Name; + if (resource.Parameters.TryGetValue(AzureBicepResource.KnownParameters.Location, out var location) && location is null) + { + // Always specify the location + resource.Parameters[AzureBicepResource.KnownParameters.Location] = context.Location.Name; + } } private static async Task ExecuteCommand(ProcessSpec processSpec) diff --git a/src/Aspire.Hosting.Azure/Aspire.Hosting.Azure.csproj b/src/Aspire.Hosting.Azure/Aspire.Hosting.Azure.csproj index a38080183b..898ddf5116 100644 --- a/src/Aspire.Hosting.Azure/Aspire.Hosting.Azure.csproj +++ b/src/Aspire.Hosting.Azure/Aspire.Hosting.Azure.csproj @@ -18,6 +18,7 @@ + diff --git a/src/Aspire.Hosting.Azure/AzureBicepResource.cs b/src/Aspire.Hosting.Azure/AzureBicepResource.cs index ecde2fda91..1217f773da 100644 --- a/src/Aspire.Hosting.Azure/AzureBicepResource.cs +++ b/src/Aspire.Hosting.Azure/AzureBicepResource.cs @@ -22,7 +22,7 @@ public class AzureBicepResource(string name, string? templateFile = null, string { internal string? TemplateFile { get; } = templateFile; - internal string? TemplateString { get; } = templateString; + internal string? TemplateString { get; set; } = templateString; internal string? TemplateResourceName { get; } = templateResourceName; @@ -48,7 +48,7 @@ public class AzureBicepResource(string name, string? templateFile = null, string /// A boolean that determines if the file should be deleted on disposal of the . /// A that represents the bicep file. /// - public BicepTemplateFile GetBicepTemplateFile(string? directory = null, bool deleteTemporaryFileOnDispose = true) + public virtual BicepTemplateFile GetBicepTemplateFile(string? directory = null, bool deleteTemporaryFileOnDispose = true) { // Throw if multiple template sources are specified if (TemplateFile is not null && (TemplateString is not null || TemplateResourceName is not null)) diff --git a/src/Aspire.Hosting.Azure/AzureConstructResource.cs b/src/Aspire.Hosting.Azure/AzureConstructResource.cs new file mode 100644 index 0000000000..44f1a3a092 --- /dev/null +++ b/src/Aspire.Hosting.Azure/AzureConstructResource.cs @@ -0,0 +1,140 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Hosting.ApplicationModel; +using Aspire.Hosting.Azure; +using Azure.Provisioning; + +namespace Aspire.Hosting; + +/// +/// An Aspire resource that supports use of Azure Provisioning APIs to create Azure resources. +/// +/// +/// +public class AzureConstructResource(string name, Action configureConstruct) : AzureBicepResource(name, templateFile: $"{name}.module.bicep") +{ + /// + /// Callback for configuring construct. + /// + public Action ConfigureConstruct { get; } = configureConstruct; + + /// + public override BicepTemplateFile GetBicepTemplateFile(string? directory = null, bool deleteTemporaryFileOnDispose = true) + { + var configuration = new Configuration() + { + UsePromptMode = true + }; + + var resourceModuleConstruct = new ResourceModuleConstruct(this, configuration); + + foreach (var aspireParameter in this.Parameters) + { + var constructParameter = new Parameter(aspireParameter.Key); + resourceModuleConstruct.AddParameter(constructParameter); + } + + ConfigureConstruct(resourceModuleConstruct); + + var generationPath = Directory.CreateTempSubdirectory("aspire").FullName; + resourceModuleConstruct.Build(generationPath); + + var moduleSourcePath = Path.Combine(generationPath, "main.bicep"); + var moduleDestinationPath = Path.Combine(directory ?? generationPath, $"{Name}.module.bicep"); + File.Copy(moduleSourcePath, moduleDestinationPath!, true); + + return new BicepTemplateFile(moduleDestinationPath, directory is null); + } +} + +/// +/// Extensions for working with and related types. +/// +public static class AzureConstructResourceExtensions +{ + /// + /// Adds an Azure construct resource to the application model. + /// + /// The distributed application builder. + /// The name of the resource being added. + /// A callback used to configure the construct resource. + /// + public static IResourceBuilder AddAzureConstruct(this IDistributedApplicationBuilder builder, string name, Action configureConstruct) + { + var resource = new AzureConstructResource(name, configureConstruct); + return builder.AddResource(resource) + .WithManifestPublishingCallback(resource.WriteToManifest); + } + + /// + /// Adds a parameter to the Azure construct resource based on an Aspire parameter. + /// + /// The Azure construct resource. + /// The Aspire parameter resource builder. + /// + public static Parameter AddParameter(this ResourceModuleConstruct resourceModuleConstruct, IResourceBuilder parameterResourceBuilder) + { + return resourceModuleConstruct.AddParameter(parameterResourceBuilder.Resource.Name, parameterResourceBuilder); + } + + /// + /// Adds a parameter to the Azure construct resource based on an Aspire parameter. + /// + /// The Azure construct resource. + /// The name to be used for the Azure construct parameter. + /// The Aspire parameter resource builder. + /// + public static Parameter AddParameter(this ResourceModuleConstruct resourceModuleConstruct, string name, IResourceBuilder parameterResourceBuilder) + { + // Ensure the parameter is added to the Aspire resource. + resourceModuleConstruct.Resource.Parameters.Add(name, parameterResourceBuilder); + + var parameter = new Parameter(name, isSecure: parameterResourceBuilder.Resource.Secret); + resourceModuleConstruct.AddParameter(parameter); + return parameter; + } + + /// + /// Adds a parameter to the Azure construct resource based on an + /// + /// The Azure construct resource. + /// The Aspire Bicep output reference. + /// + public static Parameter AddParameter(this ResourceModuleConstruct resourceModuleConstruct, BicepOutputReference outputReference) + { + return resourceModuleConstruct.AddParameter(outputReference.Name, outputReference); + } + + /// + /// Adds a parameter to the Azure construct resource based on an + /// + /// The Azure construct resource. + /// The name to be used for the Azure construct parameter. + /// The Aspire Bicep output reference. + /// + public static Parameter AddParameter(this ResourceModuleConstruct resourceModuleConstruct, string name, BicepOutputReference outputReference) + { + resourceModuleConstruct.Resource.Parameters.Add(name, outputReference); + + var parameter = new Parameter(name); + resourceModuleConstruct.AddParameter(parameter); + return parameter; + } +} + +/// +/// An Azure Provisioning construct which represents the root Bicep module that is generated for an Azure construct resource. +/// +public class ResourceModuleConstruct : Infrastructure +{ + internal ResourceModuleConstruct(AzureConstructResource resource, Configuration configuration) : base(constructScope: ConstructScope.ResourceGroup, tenantId: Guid.Empty, subscriptionId: Guid.Empty, envName: "temp", configuration: configuration) + { + Resource = resource; + } + + /// + /// The Azure cosntruct resource that this resource module construct represents. + /// + public AzureConstructResource Resource { get; } +} diff --git a/tests/Aspire.Hosting.Tests/Azure/AzureBicepResourceTests.cs b/tests/Aspire.Hosting.Tests/Azure/AzureBicepResourceTests.cs index 70de9877fb..c178fe75b4 100644 --- a/tests/Aspire.Hosting.Tests/Azure/AzureBicepResourceTests.cs +++ b/tests/Aspire.Hosting.Tests/Azure/AzureBicepResourceTests.cs @@ -5,6 +5,8 @@ using System.Text.Json.Nodes; using Aspire.Hosting.Azure; using Aspire.Hosting.Utils; +using Azure.Provisioning.Storage; +using Azure.ResourceManager.Storage.Models; using Xunit; namespace Aspire.Hosting.Tests.Azure; @@ -184,6 +186,54 @@ public async Task WithReferenceAppInsightsSetsEnvironmentVariable() Assert.Equal("myinstrumentationkey", config["APPLICATIONINSIGHTS_CONNECTION_STRING"]); } + [Fact] + public async Task AddAzureConstructGenertesCorrectManifestEntry() + { + var builder = DistributedApplication.CreateBuilder(); + var construct1 = builder.AddAzureConstruct("construct1", (construct) => + { + var storage = construct.AddStorageAccount( + kind: StorageKind.StorageV2, + sku: StorageSkuName.StandardLrs + ); + storage.AddOutput(sa => sa.Name, "storageAccountName"); + }); + + var manifest = await ManifestUtils.GetManifest(construct1.Resource); + Assert.Equal("azure.bicep.v0", manifest["type"]?.ToString()); + Assert.Equal("construct1.module.bicep", manifest["path"]?.ToString()); + } + + // TODO: This test to be reenabled once we figure out what is going on in CDK + // around parameters being injected twice. + //[Fact] + //public void AddParameterOnResourceModuleConstructPopulatesParametersEverywhere() + //{ + // var builder = DistributedApplication.CreateBuilder(); + // builder.Configuration["Parameters:skuName"] = "Standard_ZRS"; + + // var skuName = builder.AddParameter("skuName"); + + // ResourceModuleConstruct? moduleConstruct = null; + // var construct1 = builder.AddAzureConstruct("construct1", (construct) => + // { + // var storage = construct.AddStorageAccount( + // kind: StorageKind.StorageV2, + // sku: StorageSkuName.StandardLrs + // ); + // storage.AssignParameter(sa => sa.Sku.Name, construct.AddParameter(skuName)); + // moduleConstruct = construct; + // }); + + // var manifest = ManifestUtils.GetManifest(construct1.Resource); + + // Assert.NotNull(moduleConstruct); + // var constructParameters = moduleConstruct.GetParameters(false).ToDictionary(p => p.Name); + // Assert.True(constructParameters.ContainsKey("skuName")); + // Assert.Equal(skuName.Resource, construct1.Resource.Parameters["skuName"]); + // Assert.Equal("{skuName.value}", manifest["params"]?["skuName"]?.ToString()); + //} + [Fact] public async Task PublishAsRedisPublishesRedisAsAzureRedis() { diff --git a/tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs b/tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs index a5bfd9799a..eb085b0d4a 100644 --- a/tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs +++ b/tests/Aspire.Hosting.Tests/DistributedApplicationTests.cs @@ -469,7 +469,7 @@ public async Task KubernetesHasResourceNameForContainersAndExes() } } - await foreach(var resource in s.WatchAsync(cancellationToken: token)) + await foreach (var resource in s.WatchAsync(cancellationToken: token)) { Assert.True(resource.Item2.Metadata.Annotations.TryGetValue(Executable.ResourceNameAnnotation, out var value)); if (expectedExeResources.Contains(value))