diff --git a/playground/bicep/BicepSample.AppHost/Program.cs b/playground/bicep/BicepSample.AppHost/Program.cs index f2aae2b42e..ccf8b21921 100644 --- a/playground/bicep/BicepSample.AppHost/Program.cs +++ b/playground/bicep/BicepSample.AppHost/Program.cs @@ -44,7 +44,11 @@ var cosmosDb = builder.AddAzureCosmosDB("cosmos") .AddDatabase("db3"); -var appInsights = builder.AddAzureApplicationInsights("ai"); +var logAnalytics = builder.AddAzureLogAnalyticsWorkspace("lawkspc"); +var appInsights = builder.AddAzureApplicationInsights("ai", logAnalytics); + +// To verify that AZD will populate the LAW parameter. +builder.AddAzureApplicationInsights("aiwithoutlaw"); // Redis takes forever to spin up... var redis = builder.AddRedis("redis") diff --git a/playground/bicep/BicepSample.AppHost/aiwithoutlaw.module.bicep b/playground/bicep/BicepSample.AppHost/aiwithoutlaw.module.bicep new file mode 100644 index 0000000000..049db095d4 --- /dev/null +++ b/playground/bicep/BicepSample.AppHost/aiwithoutlaw.module.bicep @@ -0,0 +1,29 @@ +targetScope = 'resourceGroup' + +@description('') +param location string = resourceGroup().location + +@description('') +param applicationType string = 'web' + +@description('') +param kind string = 'web' + +@description('') +param logAnalyticsWorkspaceId string + + +resource applicationInsightsComponent_YlZN71uia 'Microsoft.Insights/components@2020-02-02' = { + name: toLower(take(concat('aiwithoutlaw', uniqueString(resourceGroup().id)), 24)) + location: location + tags: { + 'aspire-resource-name': 'aiwithoutlaw' + } + kind: kind + properties: { + Application_Type: applicationType + WorkspaceResourceId: logAnalyticsWorkspaceId + } +} + +output appInsightsConnectionString string = applicationInsightsComponent_YlZN71uia.properties.ConnectionString diff --git a/playground/bicep/BicepSample.AppHost/aspire-manifest.json b/playground/bicep/BicepSample.AppHost/aspire-manifest.json index 3e9afe47ef..3736a1611b 100644 --- a/playground/bicep/BicepSample.AppHost/aspire-manifest.json +++ b/playground/bicep/BicepSample.AppHost/aspire-manifest.json @@ -23,7 +23,7 @@ }, "test0": { "type": "azure.bicep.v0", - "path": "../../../../../../Users/davifowl/AppData/Local/Temp/tmppj1k21.tmp.bicep" + "path": "test0.bicep" }, "kv3": { "type": "azure.bicep.v0", @@ -118,10 +118,25 @@ "keyVaultName": "" } }, + "lawkspc": { + "type": "azure.bicep.v0", + "path": "lawkspc.module.bicep" + }, "ai": { "type": "azure.bicep.v0", "connectionString": "{ai.outputs.appInsightsConnectionString}", - "path": "ai.module.bicep" + "path": "ai.module.bicep", + "params": { + "logAnalyticsWorkspaceId": "{lawkspc.outputs.logAnalyticsWorkspaceId}" + } + }, + "aiwithoutlaw": { + "type": "azure.bicep.v0", + "connectionString": "{aiwithoutlaw.outputs.appInsightsConnectionString}", + "path": "aiwithoutlaw.module.bicep", + "params": { + "logAnalyticsWorkspaceId": "" + } }, "redis": { "type": "azure.bicep.v0", diff --git a/playground/bicep/BicepSample.AppHost/lawkspc.module.bicep b/playground/bicep/BicepSample.AppHost/lawkspc.module.bicep new file mode 100644 index 0000000000..ec2bea6b78 --- /dev/null +++ b/playground/bicep/BicepSample.AppHost/lawkspc.module.bicep @@ -0,0 +1,20 @@ +targetScope = 'resourceGroup' + +@description('') +param location string = resourceGroup().location + + +resource operationalInsightsWorkspace_cxL77xv9Y 'Microsoft.OperationalInsights/workspaces@2022-10-01' = { + name: toLower(take(concat('lawkspc', uniqueString(resourceGroup().id)), 24)) + location: location + tags: { + 'aspire-resource-name': 'lawkspc' + } + properties: { + sku: { + name: 'PerGB2018' + } + } +} + +output logAnalyticsWorkspaceId string = operationalInsightsWorkspace_cxL77xv9Y.id diff --git a/playground/bicep/BicepSample.AppHost/test0.bicep b/playground/bicep/BicepSample.AppHost/test0.bicep new file mode 100644 index 0000000000..4d513a6cd0 --- /dev/null +++ b/playground/bicep/BicepSample.AppHost/test0.bicep @@ -0,0 +1,2 @@ +param location string = '' +output val0 string = location \ No newline at end of file diff --git a/src/Aspire.Hosting.Azure.ApplicationInsights/Aspire.Hosting.Azure.ApplicationInsights.csproj b/src/Aspire.Hosting.Azure.ApplicationInsights/Aspire.Hosting.Azure.ApplicationInsights.csproj index af645d7ba6..512f1dd125 100644 --- a/src/Aspire.Hosting.Azure.ApplicationInsights/Aspire.Hosting.Azure.ApplicationInsights.csproj +++ b/src/Aspire.Hosting.Azure.ApplicationInsights/Aspire.Hosting.Azure.ApplicationInsights.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Aspire.Hosting.Azure.ApplicationInsights/AzureApplicationInsightsExtensions.cs b/src/Aspire.Hosting.Azure.ApplicationInsights/AzureApplicationInsightsExtensions.cs index 290b00d37e..60125641be 100644 --- a/src/Aspire.Hosting.Azure.ApplicationInsights/AzureApplicationInsightsExtensions.cs +++ b/src/Aspire.Hosting.Azure.ApplicationInsights/AzureApplicationInsightsExtensions.cs @@ -23,7 +23,21 @@ public static class AzureApplicationInsightsExtensions public static IResourceBuilder AddAzureApplicationInsights(this IDistributedApplicationBuilder builder, string name) { #pragma warning disable ASPIRE0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. - return builder.AddAzureApplicationInsights(name, (_, _, _) => { }); + return builder.AddAzureApplicationInsights(name, null, null); +#pragma warning restore ASPIRE0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + } + + /// + /// Adds an Azure Application Insights resource to the application model. + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// A resource builder for the log analytics workspace. + /// A reference to the . + public static IResourceBuilder AddAzureApplicationInsights(this IDistributedApplicationBuilder builder, string name, IResourceBuilder? logAnalyticsWorkspace) + { +#pragma warning disable ASPIRE0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. + return builder.AddAzureApplicationInsights(name, logAnalyticsWorkspace, null); #pragma warning restore ASPIRE0001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed. } @@ -36,6 +50,20 @@ public static IResourceBuilder AddAzureApplica /// [Experimental("ASPIRE0001", UrlFormat = "https://aka.ms/dotnet/aspire/diagnostics#{0}")] public static IResourceBuilder AddAzureApplicationInsights(this IDistributedApplicationBuilder builder, string name, Action, ResourceModuleConstruct, ApplicationInsightsComponent>? configureResource) + { + return builder.AddAzureApplicationInsights(name, null, configureResource); + } + + /// + /// Adds an Azure Application Insights resource to the application model. + /// + /// The builder for the distributed application. + /// The name of the resource. + /// A resource builder for the log analytics workspace. + /// Optional callback to configure the Application Insights resource. + /// + [Experimental("ASPIRE0001", UrlFormat = "https://aka.ms/dotnet/aspire/diagnostics#{0}")] + public static IResourceBuilder AddAzureApplicationInsights(this IDistributedApplicationBuilder builder, string name, IResourceBuilder? logAnalyticsWorkspace, Action, ResourceModuleConstruct, ApplicationInsightsComponent>? configureResource) { builder.AddAzureProvisioning(); @@ -45,7 +73,19 @@ public static IResourceBuilder AddAzureApplica appInsights.Properties.Tags["aspire-resource-name"] = construct.Resource.Name; appInsights.AssignProperty(p => p.ApplicationType, new Parameter("applicationType", defaultValue: "web")); appInsights.AssignProperty(p => p.Kind, new Parameter("kind", defaultValue: "web")); - appInsights.AssignProperty(p => p.WorkspaceResourceId, new Parameter(AzureBicepResource.KnownParameters.LogAnalyticsWorkspaceId)); + + if (logAnalyticsWorkspace != null) + { + appInsights.AssignProperty(p => p.WorkspaceResourceId, logAnalyticsWorkspace.Resource.WorkspaceId, AzureBicepResource.KnownParameters.LogAnalyticsWorkspaceId); + } + else + { + // If the user does not supply a log analytics workspace of their own we still create a parameter on the Aspire + // side and the CDK side so that AZD can fill the value in with the one it generates. + construct.Resource.Parameters.Add(AzureBicepResource.KnownParameters.LogAnalyticsWorkspaceId, ""); + appInsights.AssignProperty(p => p.WorkspaceResourceId, new Parameter(AzureBicepResource.KnownParameters.LogAnalyticsWorkspaceId)); + + } appInsights.AddOutput("appInsightsConnectionString", p => p.ConnectionString); @@ -56,6 +96,7 @@ public static IResourceBuilder AddAzureApplica configureResource(resourceBuilder, construct, appInsights); } }; + var resource = new AzureApplicationInsightsResource(name, configureConstruct); return builder.AddResource(resource) diff --git a/src/Aspire.Hosting.Azure/AzureBicepResource.cs b/src/Aspire.Hosting.Azure/AzureBicepResource.cs index e3d41d3fcf..63d7fe89d9 100644 --- a/src/Aspire.Hosting.Azure/AzureBicepResource.cs +++ b/src/Aspire.Hosting.Azure/AzureBicepResource.cs @@ -74,7 +74,7 @@ public virtual BicepTemplateFile GetBicepTemplateFile(string? directory = null, isTempFile = directory is null; path = TempDirectory is null - ? Path.GetTempFileName() + ".bicep" + ? Path.Combine(directory ?? Directory.CreateTempSubdirectory("aspire").FullName, $"{Name.ToLowerInvariant()}.bicep") : Path.Combine(TempDirectory, $"{Name.ToLowerInvariant()}.bicep"); if (TemplateResourceName is null) diff --git a/tests/Aspire.Hosting.Tests/Azure/AzureBicepResourceTests.cs b/tests/Aspire.Hosting.Tests/Azure/AzureBicepResourceTests.cs index 77b1e60589..451be873fb 100644 --- a/tests/Aspire.Hosting.Tests/Azure/AzureBicepResourceTests.cs +++ b/tests/Aspire.Hosting.Tests/Azure/AzureBicepResourceTests.cs @@ -344,7 +344,7 @@ param principalType string } [Fact] - public async Task AddApplicationInsights() + public async Task AddApplicationInsightsWithoutExplicitLawGetsDefaultLawParameter() { using var builder = TestDistributedApplicationBuilder.Create(); @@ -363,7 +363,74 @@ public async Task AddApplicationInsights() { "type": "azure.bicep.v0", "connectionString": "{appInsights.outputs.appInsightsConnectionString}", - "path": "appInsights.module.bicep" + "path": "appInsights.module.bicep", + "params": { + "logAnalyticsWorkspaceId": "" + } + } + """; + Assert.Equal(expectedManifest, appInsightsManifest.ManifestNode.ToString()); + + var expectedBicep = """ + targetScope = 'resourceGroup' + + @description('') + param location string = resourceGroup().location + + @description('') + param applicationType string = 'web' + + @description('') + param kind string = 'web' + + @description('') + param logAnalyticsWorkspaceId string + + + resource applicationInsightsComponent_fo9MneV12 'Microsoft.Insights/components@2020-02-02' = { + name: toLower(take(concat('appInsights', uniqueString(resourceGroup().id)), 24)) + location: location + tags: { + 'aspire-resource-name': 'appInsights' + } + kind: kind + properties: { + Application_Type: applicationType + WorkspaceResourceId: logAnalyticsWorkspaceId + } + } + + output appInsightsConnectionString string = applicationInsightsComponent_fo9MneV12.properties.ConnectionString + + """; + Assert.Equal(expectedBicep, appInsightsManifest.BicepText); + } + + [Fact] + public async Task AddApplicationInsightsWithExplicitLawArgumentDoesntGetDefaultParameter() + { + using var builder = TestDistributedApplicationBuilder.Create(); + + var law = builder.AddAzureLogAnalyticsWorkspace("mylaw"); + var appInsights = builder.AddAzureApplicationInsights("appInsights", law); + + appInsights.Resource.Outputs["appInsightsConnectionString"] = "myinstrumentationkey"; + + var connectionStringResource = (IResourceWithConnectionString)appInsights.Resource; + + Assert.Equal("appInsights", appInsights.Resource.Name); + Assert.Equal("myinstrumentationkey", await connectionStringResource.GetConnectionStringAsync()); + Assert.Equal("{appInsights.outputs.appInsightsConnectionString}", appInsights.Resource.ConnectionStringExpression.ValueExpression); + + var appInsightsManifest = await ManifestUtils.GetManifestWithBicep(appInsights.Resource); + var expectedManifest = """ + { + "type": "azure.bicep.v0", + "connectionString": "{appInsights.outputs.appInsightsConnectionString}", + "path": "appInsights.module.bicep", + "params": { + "logAnalyticsWorkspaceId": "{mylaw.outputs.logAnalyticsWorkspaceId}" + } } """; Assert.Equal(expectedManifest, appInsightsManifest.ManifestNode.ToString());