diff --git a/playground/AspireWithJavaScript/AspireJavaScript.Angular/default.conf.template b/playground/AspireWithJavaScript/AspireJavaScript.Angular/default.conf.template index 35142e67868..18408d72742 100644 --- a/playground/AspireWithJavaScript/AspireJavaScript.Angular/default.conf.template +++ b/playground/AspireWithJavaScript/AspireJavaScript.Angular/default.conf.template @@ -11,7 +11,7 @@ server { } location /api/ { - proxy_pass ${services__weatherapi__https__0}; + proxy_pass ${WEATHERAPI_HTTPS}; proxy_http_version 1.1; proxy_ssl_server_name on; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/playground/AspireWithJavaScript/AspireJavaScript.Angular/proxy.conf.js b/playground/AspireWithJavaScript/AspireJavaScript.Angular/proxy.conf.js index 97e64f6ffdc..f047c0c4efb 100644 --- a/playground/AspireWithJavaScript/AspireJavaScript.Angular/proxy.conf.js +++ b/playground/AspireWithJavaScript/AspireJavaScript.Angular/proxy.conf.js @@ -1,8 +1,7 @@ module.exports = { "/api": { target: - process.env["services__weatherapi__https__0"] || - process.env["services__weatherapi__http__0"], + process.env["WEATHERAPI_HTTPS"] || process.env["WEATHERAPI_HTTP"], secure: process.env["NODE_ENV"] !== "development", pathRewrite: { "^/api": "", diff --git a/playground/AspireWithJavaScript/AspireJavaScript.React/default.conf.template b/playground/AspireWithJavaScript/AspireJavaScript.React/default.conf.template index 35142e67868..18408d72742 100644 --- a/playground/AspireWithJavaScript/AspireJavaScript.React/default.conf.template +++ b/playground/AspireWithJavaScript/AspireJavaScript.React/default.conf.template @@ -11,7 +11,7 @@ server { } location /api/ { - proxy_pass ${services__weatherapi__https__0}; + proxy_pass ${WEATHERAPI_HTTPS}; proxy_http_version 1.1; proxy_ssl_server_name on; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/playground/AspireWithJavaScript/AspireJavaScript.React/webpack.config.js b/playground/AspireWithJavaScript/AspireJavaScript.React/webpack.config.js index 31b7b6e4a08..82c1a1a58e8 100644 --- a/playground/AspireWithJavaScript/AspireJavaScript.React/webpack.config.js +++ b/playground/AspireWithJavaScript/AspireJavaScript.React/webpack.config.js @@ -10,8 +10,7 @@ module.exports = (env) => { { context: ["/api"], target: - process.env.services__weatherapi__https__0 || - process.env.services__weatherapi__http__0, + process.env.WEATHERAPI_HTTPS || process.env.WEATHERAPI_HTTP, pathRewrite: { "^/api": "" }, secure: false, }, diff --git a/playground/AspireWithJavaScript/AspireJavaScript.Vite/default.conf.template b/playground/AspireWithJavaScript/AspireJavaScript.Vite/default.conf.template index 2ef52b5d036..5689b97e3ac 100644 --- a/playground/AspireWithJavaScript/AspireJavaScript.Vite/default.conf.template +++ b/playground/AspireWithJavaScript/AspireJavaScript.Vite/default.conf.template @@ -11,7 +11,7 @@ server { } location /api/ { - proxy_pass ${services__weatherapi__https__0}; + proxy_pass ${WEATHERAPI_HTTPS}; proxy_http_version 1.1; proxy_ssl_server_name on; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/playground/AspireWithJavaScript/AspireJavaScript.Vite/vite.config.ts b/playground/AspireWithJavaScript/AspireJavaScript.Vite/vite.config.ts index ab3ffd8141f..7bfe8f7b5b6 100644 --- a/playground/AspireWithJavaScript/AspireJavaScript.Vite/vite.config.ts +++ b/playground/AspireWithJavaScript/AspireJavaScript.Vite/vite.config.ts @@ -11,8 +11,7 @@ export default defineConfig(({ mode }) => { port: parseInt(env.VITE_PORT), proxy: { '/api': { - target: process.env.services__weatherapi__https__0 || - process.env.services__weatherapi__http__0, + target: process.env.WEATHERAPI_HTTPS || process.env.WEATHERAPI_HTTP, changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ''), secure: false, diff --git a/playground/AspireWithJavaScript/AspireJavaScript.Vue/default.conf.template b/playground/AspireWithJavaScript/AspireJavaScript.Vue/default.conf.template index 35142e67868..18408d72742 100644 --- a/playground/AspireWithJavaScript/AspireJavaScript.Vue/default.conf.template +++ b/playground/AspireWithJavaScript/AspireJavaScript.Vue/default.conf.template @@ -11,7 +11,7 @@ server { } location /api/ { - proxy_pass ${services__weatherapi__https__0}; + proxy_pass ${WEATHERAPI_HTTPS}; proxy_http_version 1.1; proxy_ssl_server_name on; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; diff --git a/playground/AspireWithJavaScript/AspireJavaScript.Vue/vite.config.ts b/playground/AspireWithJavaScript/AspireJavaScript.Vue/vite.config.ts index 0da890eb12d..dfd67f0c9db 100644 --- a/playground/AspireWithJavaScript/AspireJavaScript.Vue/vite.config.ts +++ b/playground/AspireWithJavaScript/AspireJavaScript.Vue/vite.config.ts @@ -18,7 +18,7 @@ export default defineConfig({ port: parseInt(process.env.PORT ?? "5173"), proxy: { '/api': { - target: process.env.services__weatherapi__https__0 || process.env.services__weatherapi__http__0, + target: process.env.WEATHERAPI_HTTPS || process.env.WEATHERAPI_HTTP, changeOrigin: true, rewrite: path => path.replace(/^\/api/, ''), secure: false diff --git a/playground/AspireWithJavaScript/README.md b/playground/AspireWithJavaScript/README.md index 123b7032c6f..e95a67efe3b 100644 --- a/playground/AspireWithJavaScript/README.md +++ b/playground/AspireWithJavaScript/README.md @@ -18,6 +18,8 @@ The app consists of four services: ### Experiencing the app +Before starting the app host run `npm install` in each javascript folder. + Once the app is running, the .NET Aspire dashboard will launch in your browser: ![.NET Aspire dashboard](./images/aspire-dashboard.png) diff --git a/playground/AspireWithNode/NodeFrontend/app.js b/playground/AspireWithNode/NodeFrontend/app.js index 8657911558a..7aec5ce9886 100644 --- a/playground/AspireWithNode/NodeFrontend/app.js +++ b/playground/AspireWithNode/NodeFrontend/app.js @@ -15,7 +15,7 @@ const config = { certFile: process.env['HTTPS_CERT_FILE'] ?? '', certKeyFile: process.env['HTTPS_CERT_KEY_FILE'] ?? '', cacheUrl: process.env['CACHE_URI'] ?? '', - apiServer: process.env['services__weatherapi__https__0'] ?? process.env['services__weatherapi__http__0'] + apiServer: process.env['WEATHERAPI_HTTPS'] ?? process.env['WEATHERAPI_HTTP'] }; console.log(`config: ${JSON.stringify(config)}`); diff --git a/src/Aspire.Hosting.DevTunnels/DevTunnelResourceBuilderExtensions.cs b/src/Aspire.Hosting.DevTunnels/DevTunnelResourceBuilderExtensions.cs index 2f3ba067238..68926dbb568 100644 --- a/src/Aspire.Hosting.DevTunnels/DevTunnelResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting.DevTunnels/DevTunnelResourceBuilderExtensions.cs @@ -374,7 +374,7 @@ public static EndpointReference GetEndpoint(this IResourceBuilder p.TargetEndpoint.Resource == targetEndpointReference.Resource + .FirstOrDefault(p => p.TargetEndpoint.Resource == targetEndpointReference.Resource && StringComparers.EndpointAnnotationName.Equals(p.TargetEndpoint.EndpointName, targetEndpointReference.EndpointName)); if (portResource is null) @@ -394,8 +394,9 @@ private static EndpointReference CreateEndpointReferenceWithError(DevTunnelResou } /// - /// Injects service discovery information as environment variables from the dev tunnel resource into the destination resource, using the tunneled resource's name as the service name. - /// Each endpoint defined on the target resource will be injected using the format "services__{sourceResourceName}__{endpointName}__{endpointIndex}={uriString}". + /// Injects service discovery and endpoint information as environment variables from the dev tunnel resource into the destination resource, using the tunneled resource's name as the service name. + /// Each endpoint defined on the target resource will be injected using the format defined by the on the destination resource, i.e. + /// either "services__{sourceResourceName}__{endpointName}__{endpointIndex}={uriString}" for .NET service discovery, or "{RESOURCE_ENDPOINT}={uri}" for endpoint injection. /// /// /// Referencing a dev tunnel will delay the start of the resource until the referenced dev tunnel's endpoint is allocated. @@ -422,12 +423,25 @@ public static IResourceBuilder WithReference(this IResourc .WithReferenceRelationship(tunnelResource) .WithEnvironment(context => { + // Determine what to inject based on the annotation on the destination resource + var injectionAnnotation = context.Resource.TryGetLastAnnotation(out var annotation) ? annotation : null; + var flags = injectionAnnotation?.Flags ?? ReferenceEnvironmentInjectionFlags.All; + // Add environment variables for each tunnel port that references an endpoint on the target resource foreach (var port in tunnelResource.Resource.Ports.Where(p => p.TargetEndpoint.Resource == targetResource.Resource)) { var serviceName = targetResource.Resource.Name; var endpointName = port.TargetEndpoint.EndpointName; - context.EnvironmentVariables[$"services__{serviceName}__{endpointName}__0"] = port.TunnelEndpoint; + + if (flags.HasFlag(ReferenceEnvironmentInjectionFlags.ServiceDiscovery)) + { + context.EnvironmentVariables[$"services__{serviceName}__{endpointName}__0"] = port.TunnelEndpoint; + } + + if (flags.HasFlag(ReferenceEnvironmentInjectionFlags.Endpoints)) + { + context.EnvironmentVariables[$"{serviceName.ToUpperInvariant()}_{endpointName.ToUpperInvariant()}"] = port.TunnelEndpoint; + } } }); diff --git a/src/Aspire.Hosting.Qdrant/QdrantBuilderExtensions.cs b/src/Aspire.Hosting.Qdrant/QdrantBuilderExtensions.cs index 927ec404879..295ba8ac2e3 100644 --- a/src/Aspire.Hosting.Qdrant/QdrantBuilderExtensions.cs +++ b/src/Aspire.Hosting.Qdrant/QdrantBuilderExtensions.cs @@ -161,7 +161,7 @@ public static IResourceBuilder WithReference(this IR var connectionStringName = resource.ConnectionStringEnvironmentVariable ?? $"ConnectionStrings__{connectionName}"; // Determine what to inject based on the annotation on the destination resource - var injectionAnnotation = builder.Resource.Annotations.OfType().LastOrDefault(); + var injectionAnnotation = builder.Resource.TryGetLastAnnotation(out var annotation) ? annotation : null; var flags = injectionAnnotation?.Flags ?? ReferenceEnvironmentInjectionFlags.All; if (flags.HasFlag(ReferenceEnvironmentInjectionFlags.ConnectionString)) diff --git a/src/Aspire.Hosting/ApplicationModel/ReferenceEnvironmentInjectionAnnotation.cs b/src/Aspire.Hosting/ApplicationModel/ReferenceEnvironmentInjectionAnnotation.cs index ae4bd44b249..46d359771f0 100644 --- a/src/Aspire.Hosting/ApplicationModel/ReferenceEnvironmentInjectionAnnotation.cs +++ b/src/Aspire.Hosting/ApplicationModel/ReferenceEnvironmentInjectionAnnotation.cs @@ -4,7 +4,7 @@ namespace Aspire.Hosting.ApplicationModel; /// -/// Annotation that specifies which connection information should be injected into environment variables +/// Annotation that specifies which connection and endpoint information should be injected into environment variables /// when a resource is referenced using WithReference(). /// public sealed class ReferenceEnvironmentInjectionAnnotation : IResourceAnnotation diff --git a/src/Aspire.Hosting/ApplicationModel/ReferenceEnvironmentInjectionFlags.cs b/src/Aspire.Hosting/ApplicationModel/ReferenceEnvironmentInjectionFlags.cs index 890c60ed1b0..d25d11b21ec 100644 --- a/src/Aspire.Hosting/ApplicationModel/ReferenceEnvironmentInjectionFlags.cs +++ b/src/Aspire.Hosting/ApplicationModel/ReferenceEnvironmentInjectionFlags.cs @@ -4,7 +4,7 @@ namespace Aspire.Hosting.ApplicationModel; /// -/// Specifies which connection information should be injected into environment variables when WithReference() is invoked. +/// Specifies which connection or endpoint information should be injected into environment variables when WithReference() is invoked. /// [Flags] public enum ReferenceEnvironmentInjectionFlags @@ -25,7 +25,17 @@ public enum ReferenceEnvironmentInjectionFlags ConnectionProperties = 1 << 1, /// - /// Both connection string and connection properties will be injected as environment variables. + /// Each endpoint defined on the resource will be injected using the format "services__{resourceName}__{endpointName}__{endpointIndex}". /// - All = ConnectionString | ConnectionProperties + ServiceDiscovery = 1 << 2, + + /// + /// Each endpoint defined on the resource will be injected using the format "{RESOURCENAME}_{ENDPOINTNAME}". + /// + Endpoints = 1 << 3, + + /// + /// Connection string, connection properties and service endpoints will be injected as environment variables. + /// + All = ConnectionString | ConnectionProperties | ServiceDiscovery | Endpoints } diff --git a/src/Aspire.Hosting/ResourceBuilderExtensions.cs b/src/Aspire.Hosting/ResourceBuilderExtensions.cs index 62be4407397..2c4164850d3 100644 --- a/src/Aspire.Hosting/ResourceBuilderExtensions.cs +++ b/src/Aspire.Hosting/ResourceBuilderExtensions.cs @@ -404,14 +404,25 @@ public static IResourceBuilder WithConnectionStringRedirection(this IResou return builder.WithAnnotation(new ConnectionStringRedirectAnnotation(resource), ResourceAnnotationMutationBehavior.Replace); } - private static Action CreateEndpointReferenceEnvironmentPopulationCallback(EndpointReferenceAnnotation endpointReferencesAnnotation) + private static Action CreateEndpointReferenceEnvironmentPopulationCallback(EndpointReferenceAnnotation endpointReferencesAnnotation, string? specificEndpointName = null, string? name = null) { return (context) => { var annotation = endpointReferencesAnnotation; - var serviceName = annotation.Resource.Name; + var serviceName = name ?? annotation.Resource.Name; + + // Determine what to inject based on the annotation on the destination resource + context.Resource.TryGetLastAnnotation(out var injectionAnnotation); + var flags = injectionAnnotation?.Flags ?? ReferenceEnvironmentInjectionFlags.All; + foreach (var endpoint in annotation.Resource.GetEndpoints()) { + if (specificEndpointName != null && !string.Equals(endpoint.EndpointName, specificEndpointName, StringComparison.OrdinalIgnoreCase)) + { + // Skip this endpoint since it's not the one we want to reference. + continue; + } + var endpointName = endpoint.EndpointName; if (!annotation.UseAllEndpoints && !annotation.EndpointNames.Contains(endpointName)) { @@ -420,19 +431,29 @@ private static Action CreateEndpointReferenceEnviron } // Add the endpoint, rewriting localhost to the container host if necessary. - context.EnvironmentVariables[$"services__{serviceName}__{endpointName}__0"] = endpoint; + + if (flags.HasFlag(ReferenceEnvironmentInjectionFlags.Endpoints)) + { + var serviceKey = name is null ? serviceName.ToUpperInvariant() : name; + context.EnvironmentVariables[$"{serviceKey}_{endpointName.ToUpperInvariant()}"] = endpoint; + } + + if (flags.HasFlag(ReferenceEnvironmentInjectionFlags.ServiceDiscovery)) + { + context.EnvironmentVariables[$"services__{serviceName}__{endpointName}__0"] = endpoint; + } } }; } /// - /// Configures how connection information is injected into environment variables when the resource references other resources. + /// Configures how information is injected into environment variables when the resource references other resources. /// /// The destination resource. /// The resource to configure. - /// The injection flags determining which connection information is emitted. + /// The injection flags determining which reference information is emitted. /// The . - public static IResourceBuilder WithConnectionProperties(this IResourceBuilder builder, ReferenceEnvironmentInjectionFlags flags) + public static IResourceBuilder WithReferenceEnvironment(this IResourceBuilder builder, ReferenceEnvironmentInjectionFlags flags) where TDestination : IResourceWithEnvironment { ArgumentNullException.ThrowIfNull(builder); @@ -471,7 +492,7 @@ public static IResourceBuilder WithReference(this IR builder.WithReferenceRelationship(resource); // Determine what to inject based on the annotation on the destination resource - var injectionAnnotation = builder.Resource.Annotations.OfType().LastOrDefault(); + builder.Resource.TryGetLastAnnotation(out var injectionAnnotation); var flags = injectionAnnotation?.Flags ?? ReferenceEnvironmentInjectionFlags.All; return builder.WithEnvironment(context => @@ -527,8 +548,9 @@ private static void SplatConnectionProperties(IResourceWithConnectionString reso } /// - /// Injects service discovery information as environment variables from the project resource into the destination resource, using the source resource's name as the service name. - /// Each endpoint defined on the project resource will be injected using the format "services__{sourceResourceName}__{endpointName}__{endpointIndex}={uriString}". + /// Injects service discovery and endpoint information as environment variables from the project resource into the destination resource, using the source resource's name as the service name. + /// Each endpoint defined on the project resource will be injected using the format defined by the on the destination resource, i.e. + /// either "services__{sourceResourceName}__{endpointName}__{endpointIndex}={uriString}" for .NET service discovery, or "{RESOURCE_ENDPOINT}={uri}" for endpoint injection. /// /// The destination resource. /// The resource where the service discovery information will be injected. @@ -545,8 +567,29 @@ public static IResourceBuilder WithReference(this IR } /// - /// Injects service discovery information as environment variables from the uri into the destination resource, using the name as the service name. - /// The uri will be injected using the format "services__{name}__default__0={uri}." + /// Injects service discovery and endpoint information as environment variables from the project resource into the destination resource, using the source resource's name as the service name. + /// Each endpoint defined on the project resource will be injected using the format defined by the on the destination resource, i.e. + /// either "services__{name}__{endpointName}__{endpointIndex}={uriString}" for .NET service discovery, or "{name}_{ENDPOINT}={uri}" for endpoint injection. + /// + /// The destination resource. + /// The resource where the service discovery information will be injected. + /// The resource from which to extract service discovery information. + /// The name of the resource for the environment variable. + /// The . + public static IResourceBuilder WithReference(this IResourceBuilder builder, IResourceBuilder source, string name) + where TDestination : IResourceWithEnvironment + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(source); + + ApplyEndpoints(builder, source.Resource, endpointName: null, name); + return builder; + } + + /// + /// Injects service discovery and endpoint information as environment variables from the uri into the destination resource, using the name as the service name. + /// The uri will be injected using the format defined by the on the destination resource, i.e. + /// either "services__{name}__default__0={uri}" for .NET service discovery, or "{name}={uri}" for endpoint injection. /// /// /// The resource where the service discovery information will be injected. @@ -570,7 +613,21 @@ public static IResourceBuilder WithReference(this IR throw new InvalidOperationException("The uri absolute path must be \"/\"."); } - return builder.WithEnvironment($"services__{name}__default__0", uri.ToString()); + // Determine what to inject based on the annotation on the destination resource + builder.Resource.TryGetLastAnnotation(out var injectionAnnotation); + var flags = injectionAnnotation?.Flags ?? ReferenceEnvironmentInjectionFlags.All; + + if (flags.HasFlag(ReferenceEnvironmentInjectionFlags.ServiceDiscovery)) + { + builder.WithEnvironment($"services__{name}__default__0", uri.ToString()); + } + + if (flags.HasFlag(ReferenceEnvironmentInjectionFlags.Endpoints)) + { + builder.WithEnvironment($"{name}", uri.ToString()); + } + + return builder; } /// @@ -589,30 +646,56 @@ public static IResourceBuilder WithReference(this IR builder.WithReferenceRelationship(externalService.Resource); + // Determine what to inject based on the annotation on the destination resource + builder.Resource.TryGetLastAnnotation(out var injectionAnnotation); + var flags = injectionAnnotation?.Flags ?? ReferenceEnvironmentInjectionFlags.All; + if (externalService.Resource.Uri is { } uri) { - var envVarName = $"services__{externalService.Resource.Name}__{uri.Scheme}__0"; - builder.WithEnvironment(envVarName, uri.ToString()); + if (flags.HasFlag(ReferenceEnvironmentInjectionFlags.Endpoints)) + { + var envVarName = $"{externalService.Resource.Name.ToUpperInvariant()}"; + builder.WithEnvironment(envVarName, uri.ToString()); + } + + if (flags.HasFlag(ReferenceEnvironmentInjectionFlags.ServiceDiscovery)) + { + var envVarName = $"services__{externalService.Resource.Name}__{uri.Scheme}__0"; + builder.WithEnvironment(envVarName, uri.ToString()); + } } else if (externalService.Resource.UrlParameter is not null) { builder.WithEnvironment(async context => { - string envVarName; + string discoveryEnvVarName; + string endpointEnvVarName; + if (context.ExecutionContext.IsPublishMode) { // In publish mode we can't read the parameter value to get the scheme so use 'default' - envVarName = $"services__{externalService.Resource.Name}__default__0"; + discoveryEnvVarName = $"services__{externalService.Resource.Name}__default__0"; + endpointEnvVarName = externalService.Resource.Name.ToUpperInvariant(); } else if (ExternalServiceResource.UrlIsValidForExternalService(await externalService.Resource.UrlParameter.GetValueAsync(context.CancellationToken).ConfigureAwait(false), out var uri, out var message)) { - envVarName = $"services__{externalService.Resource.Name}__{uri.Scheme}__0"; + discoveryEnvVarName = $"services__{externalService.Resource.Name}__{uri.Scheme}__0"; + endpointEnvVarName = $"{externalService.Resource.Name.ToUpperInvariant()}_{uri.Scheme.ToUpperInvariant()}"; } else { throw new DistributedApplicationException($"The URL parameter '{externalService.Resource.UrlParameter.Name}' for the external service '{externalService.Resource.Name}' is invalid: {message}"); } - context.EnvironmentVariables[envVarName] = externalService.Resource.UrlParameter; + + if (flags.HasFlag(ReferenceEnvironmentInjectionFlags.ServiceDiscovery)) + { + context.EnvironmentVariables[discoveryEnvVarName] = externalService.Resource.UrlParameter; + } + + if (flags.HasFlag(ReferenceEnvironmentInjectionFlags.Endpoints)) + { + context.EnvironmentVariables[endpointEnvVarName] = externalService.Resource.UrlParameter; + } }); } @@ -620,8 +703,9 @@ public static IResourceBuilder WithReference(this IR } /// - /// Injects service discovery information from the specified endpoint into the project resource using the source resource's name as the service name. - /// Each endpoint will be injected using the format "services__{sourceResourceName}__{endpointName}__{endpointIndex}={uriString}". + /// Injects service discovery and endpoint information from the specified endpoint into the project resource using the source resource's name as the service name. + /// Each endpoint uri will be injected using the format defined by the on the destination resource, i.e. + /// either "services__{name}__{endpointName}__{endpointIndex}={uriString}" for .NET service discovery, or "{NAME}_{ENDPOINT}={uri}" for endpoint injection. /// /// The destination resource. /// The resource where the service discovery information will be injected. @@ -637,7 +721,7 @@ public static IResourceBuilder WithReference(this IR return builder; } - private static void ApplyEndpoints(this IResourceBuilder builder, IResourceWithEndpoints resourceWithEndpoints, string? endpointName = null) + private static void ApplyEndpoints(this IResourceBuilder builder, IResourceWithEndpoints resourceWithEndpoints, string? endpointName = null, string? name = null) where T : IResourceWithEnvironment { // When adding an endpoint we get to see whether there is an EndpointReferenceAnnotation @@ -654,7 +738,7 @@ private static void ApplyEndpoints(this IResourceBuilder builder, IResourc endpointReferenceAnnotation = new EndpointReferenceAnnotation(resourceWithEndpoints); builder.WithAnnotation(endpointReferenceAnnotation); - var callback = CreateEndpointReferenceEnvironmentPopulationCallback(endpointReferenceAnnotation); + var callback = CreateEndpointReferenceEnvironmentPopulationCallback(endpointReferenceAnnotation, null, name); builder.WithEnvironment(callback); } diff --git a/src/Aspire.ProjectTemplates/templates/aspire-py-starter/13.0/frontend/vite.config.ts b/src/Aspire.ProjectTemplates/templates/aspire-py-starter/13.0/frontend/vite.config.ts index eca9419bb1d..4e4f3c02c01 100644 --- a/src/Aspire.ProjectTemplates/templates/aspire-py-starter/13.0/frontend/vite.config.ts +++ b/src/Aspire.ProjectTemplates/templates/aspire-py-starter/13.0/frontend/vite.config.ts @@ -8,7 +8,7 @@ export default defineConfig({ proxy: { // Proxy API calls to the backend service and strip 'api' prefix '/api': { - target: process.env.services__apiservice__https__0 || process.env.services__apiservice__http__0, + target: process.env.APISERVICE_HTTPS || process.env.APISERVICE_HTTP, changeOrigin: true } } diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceToEnvironmentWithoutDashboard.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceToEnvironmentWithoutDashboard.verified.bicep index 074638a4ba6..cd3fa23d268 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceToEnvironmentWithoutDashboard.verified.bicep +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceToEnvironmentWithoutDashboard.verified.bicep @@ -55,6 +55,10 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { name: 'HTTP_PORTS' value: project2_containerport } + { + name: 'PROJECT1_HTTP' + value: 'http://${take('${toLower('project1')}-${uniqueString(resourceGroup().id)}', 60)}.azurewebsites.net' + } { name: 'services__project1__http__0' value: 'http://${take('${toLower('project1')}-${uniqueString(resourceGroup().id)}', 60)}.azurewebsites.net' diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithArgs.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithArgs.verified.bicep index c6f6d2a6441..d1f15b66633 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithArgs.verified.bicep +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithArgs.verified.bicep @@ -65,6 +65,10 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { name: 'HTTP_PORTS' value: project2_containerport } + { + name: 'PROJECT1_HTTP' + value: 'http://${take('${toLower('project1')}-${uniqueString(resourceGroup().id)}', 60)}.azurewebsites.net' + } { name: 'services__project1__http__0' value: 'http://${take('${toLower('project1')}-${uniqueString(resourceGroup().id)}', 60)}.azurewebsites.net' @@ -116,4 +120,4 @@ resource project2_ra 'Microsoft.Authorization/roleAssignments@2022-04-01' = { principalType: 'ServicePrincipal' } scope: webapp -} \ No newline at end of file +} diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithTargetPort.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithTargetPort.verified.bicep index df0ed746c57..a9498d73f8c 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithTargetPort.verified.bicep +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.AddAppServiceWithTargetPort.verified.bicep @@ -64,10 +64,18 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { name: 'HTTP_PORTS' value: '9000' } + { + name: 'PROJECT1_HTTPS' + value: 'https://${take('${toLower('project1')}-${uniqueString(resourceGroup().id)}', 60)}.azurewebsites.net' + } { name: 'services__project1__https__0' value: 'https://${take('${toLower('project1')}-${uniqueString(resourceGroup().id)}', 60)}.azurewebsites.net' } + { + name: 'PROJECT1_HTTP' + value: 'http://${take('${toLower('project1')}-${uniqueString(resourceGroup().id)}', 60)}.azurewebsites.net' + } { name: 'services__project1__http__0' value: 'http://${take('${toLower('project1')}-${uniqueString(resourceGroup().id)}', 60)}.azurewebsites.net' diff --git a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.EndpointReferencesAreResolvedAcrossProjects.verified.bicep b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.EndpointReferencesAreResolvedAcrossProjects.verified.bicep index bd0059330d7..1e0f30193d9 100644 --- a/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.EndpointReferencesAreResolvedAcrossProjects.verified.bicep +++ b/tests/Aspire.Hosting.Azure.Tests/Snapshots/AzureAppServiceTests.EndpointReferencesAreResolvedAcrossProjects.verified.bicep @@ -61,6 +61,10 @@ resource webapp 'Microsoft.Web/sites@2024-11-01' = { name: 'HTTP_PORTS' value: project2_containerport } + { + name: 'PROJECT1_HTTP' + value: 'http://${take('${toLower('project1')}-${uniqueString(resourceGroup().id)}', 60)}.azurewebsites.net' + } { name: 'services__project1__http__0' value: 'http://${take('${toLower('project1')}-${uniqueString(resourceGroup().id)}', 60)}.azurewebsites.net' diff --git a/tests/Aspire.Hosting.DevTunnels.Tests/DevTunnelResourceBuilderExtensionsTests.cs b/tests/Aspire.Hosting.DevTunnels.Tests/DevTunnelResourceBuilderExtensionsTests.cs index 2ce476ab447..c311be1074b 100644 --- a/tests/Aspire.Hosting.DevTunnels.Tests/DevTunnelResourceBuilderExtensionsTests.cs +++ b/tests/Aspire.Hosting.DevTunnels.Tests/DevTunnelResourceBuilderExtensionsTests.cs @@ -27,11 +27,8 @@ public async Task WithReference_InjectsServiceDiscoveryEnvironmentVariablesWhenR var values = await consumer.Resource.GetEnvironmentVariableValuesAsync(); - var expectedKey = $"services__target__https__0"; - Assert.Contains(expectedKey, values.Keys); - - var expectedValue = "https://test123.devtunnels.ms:443"; - Assert.Equal(expectedValue, values[expectedKey]); + Assert.Equal("https://test123.devtunnels.ms:443", values["services__target__https__0"]); + Assert.Equal("https://test123.devtunnels.ms:443", values["TARGET_HTTPS"]); } [Fact] diff --git a/tests/Aspire.Hosting.Docker.Tests/Snapshots/DockerComposePublisherTests.DockerComposeWithProjectResources.verified.yaml b/tests/Aspire.Hosting.Docker.Tests/Snapshots/DockerComposePublisherTests.DockerComposeWithProjectResources.verified.yaml index 69c98197aae..50e77caf0e0 100644 --- a/tests/Aspire.Hosting.Docker.Tests/Snapshots/DockerComposePublisherTests.DockerComposeWithProjectResources.verified.yaml +++ b/tests/Aspire.Hosting.Docker.Tests/Snapshots/DockerComposePublisherTests.DockerComposeWithProjectResources.verified.yaml @@ -25,7 +25,9 @@ api: image: "reg:api" environment: + PROJECT1_HTTP: "http://project1:${PROJECT1_PORT}" services__project1__http__0: "http://project1:${PROJECT1_PORT}" + PROJECT1_HTTPS: "https://project1:${PROJECT1_PORT}" networks: - "aspire" networks: diff --git a/tests/Aspire.Hosting.Docker.Tests/Snapshots/DockerComposePublisherTests.PublishAsync_GeneratesValidDockerComposeFile.verified.yaml b/tests/Aspire.Hosting.Docker.Tests/Snapshots/DockerComposePublisherTests.PublishAsync_GeneratesValidDockerComposeFile.verified.yaml index f0b8516bb07..12cb38a4de2 100644 --- a/tests/Aspire.Hosting.Docker.Tests/Snapshots/DockerComposePublisherTests.PublishAsync_GeneratesValidDockerComposeFile.verified.yaml +++ b/tests/Aspire.Hosting.Docker.Tests/Snapshots/DockerComposePublisherTests.PublishAsync_GeneratesValidDockerComposeFile.verified.yaml @@ -68,6 +68,7 @@ OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" + MYAPP_HTTP: "http://myapp:8001" services__myapp__http__0: "http://myapp:8001" OTEL_EXPORTER_OTLP_ENDPOINT: "http://docker-compose-dashboard:18889" OTEL_EXPORTER_OTLP_PROTOCOL: "grpc" diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#01.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#01.verified.yaml index 1bc531b310c..dc84c7dd82e 100644 --- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#01.verified.yaml +++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#01.verified.yaml @@ -9,4 +9,5 @@ config: OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" + MYAPP_HTTP: "http://myapp-service:8080" services__myapp__http__0: "http://myapp-service:8080" diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#01.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#01.verified.yaml index 9a33ea8ddb2..4fae7e7fbf8 100644 --- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#01.verified.yaml +++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#01.verified.yaml @@ -14,4 +14,5 @@ config: OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "true" OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "true" OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory" + MYAPP_HTTP: "http://myapp-service:8080" services__myapp__http__0: "http://myapp-service:8080" diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#03.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#03.verified.yaml index 87cf962641c..db030ed23cb 100644 --- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#03.verified.yaml +++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#03.verified.yaml @@ -11,4 +11,5 @@ data: OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES: "{{ .Values.config.project1.OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES }}" OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES: "{{ .Values.config.project1.OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES }}" OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "{{ .Values.config.project1.OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY }}" + MYAPP_HTTP: "{{ .Values.config.project1.MYAPP_HTTP }}" services__myapp__http__0: "{{ .Values.config.project1.services__myapp__http__0 }}" diff --git a/tests/Aspire.Hosting.Tests/ExternalServiceTests.cs b/tests/Aspire.Hosting.Tests/ExternalServiceTests.cs index d121b8f8aa0..0accb9ff941 100644 --- a/tests/Aspire.Hosting.Tests/ExternalServiceTests.cs +++ b/tests/Aspire.Hosting.Tests/ExternalServiceTests.cs @@ -112,6 +112,7 @@ public async Task ExternalServiceWithHttpsCanBeReferenced() // Check that service discovery information was injected with https scheme Assert.Contains(config, kvp => kvp.Key == "services__nuget__https__0" && kvp.Value == "https://nuget.org/"); + Assert.Contains(config, kvp => kvp.Key == "NUGET" && kvp.Value == "https://nuget.org/"); } [Fact] @@ -128,6 +129,7 @@ public async Task ExternalServiceWithHttpCanBeReferenced() // Check that service discovery information was injected with http scheme Assert.Contains(config, kvp => kvp.Key == "services__nuget__http__0" && kvp.Value == "http://nuget.org/"); + Assert.Contains(config, kvp => kvp.Key == "NUGET" && kvp.Value == "http://nuget.org/"); } [Fact] @@ -145,10 +147,8 @@ public async Task ExternalServiceWithParameterCanBeReferencedInRunMode() var config = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(project.Resource, DistributedApplicationOperation.Run, TestServiceProvider.Instance).DefaultTimeout(); // Check that service discovery information was injected with the correct scheme from parameter value - Assert.Contains(config, kvp => kvp.Key == "services__nuget__https__0"); - // The value should be the URL value from the parameter - var urlValue = config["services__nuget__https__0"]; - Assert.Equal("https://nuget.org/", urlValue); + Assert.Contains(config, kvp => kvp.Key == "services__nuget__https__0" && kvp.Value == "https://nuget.org/"); + Assert.Contains(config, kvp => kvp.Key == "NUGET_HTTPS" && kvp.Value == "https://nuget.org/"); } [Fact] @@ -170,6 +170,7 @@ public async Task ExternalServiceWithParameterCanBeReferencedInPublishMode() Assert.Contains(config, kvp => kvp.Key == "services__nuget__default__0"); var urlValue = config["services__nuget__default__0"]; Assert.Equal(urlParam.Resource.ValueExpression, urlValue); + Assert.Contains(config, kvp => kvp.Key == "NUGET" && kvp.Value == urlValue); } [Fact] diff --git a/tests/Aspire.Hosting.Tests/Snapshots/ExternalServiceTests.ExternalServiceWithParameterPublishManifest.verified.json b/tests/Aspire.Hosting.Tests/Snapshots/ExternalServiceTests.ExternalServiceWithParameterPublishManifest.verified.json index 73910c77b68..fe2e27b1d99 100644 --- a/tests/Aspire.Hosting.Tests/Snapshots/ExternalServiceTests.ExternalServiceWithParameterPublishManifest.verified.json +++ b/tests/Aspire.Hosting.Tests/Snapshots/ExternalServiceTests.ExternalServiceWithParameterPublishManifest.verified.json @@ -6,6 +6,7 @@ "OTEL_DOTNET_EXPERIMENTAL_OTLP_EMIT_EVENT_LOG_ATTRIBUTES": "true", "OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY": "in_memory", "services__external__default__0": "{external-url.value}", + "EXTERNAL": "{external-url.value}", "EXTERNAL_SERVICE": "{external-url.value}" } } \ No newline at end of file diff --git a/tests/Aspire.Hosting.Tests/WithReferenceTests.cs b/tests/Aspire.Hosting.Tests/WithReferenceTests.cs index 3af25523a8b..97e4c759abc 100644 --- a/tests/Aspire.Hosting.Tests/WithReferenceTests.cs +++ b/tests/Aspire.Hosting.Tests/WithReferenceTests.cs @@ -28,16 +28,67 @@ public async Task ResourceWithSingleEndpointProducesSimplifiedEnvironmentVariabl var config = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(projectB.Resource, DistributedApplicationOperation.Run, TestServiceProvider.Instance).DefaultTimeout(); Assert.Equal("https://localhost:2000", config["services__projecta__mybinding__0"]); + Assert.Equal("https://localhost:2000", config["PROJECTA_MYBINDING"]); Assert.True(projectB.Resource.TryGetAnnotationsOfType(out var relationships)); - Assert.Collection(relationships, - r => - { - Assert.Equal("Reference", r.Type); - Assert.Same(projectA.Resource, r.Resource); - }); + var r = Assert.Single(relationships); + Assert.Equal("Reference", r.Type); + Assert.Same(projectA.Resource, r.Resource); } + [Theory] + [InlineData(ReferenceEnvironmentInjectionFlags.All)] + [InlineData(ReferenceEnvironmentInjectionFlags.ConnectionProperties)] + [InlineData(ReferenceEnvironmentInjectionFlags.ConnectionString)] + [InlineData(ReferenceEnvironmentInjectionFlags.ServiceDiscovery)] + [InlineData(ReferenceEnvironmentInjectionFlags.Endpoints)] + [InlineData(ReferenceEnvironmentInjectionFlags.None)] + public async Task ResourceWithEndpointRespectsCustomEnvironmentVariableNaming(ReferenceEnvironmentInjectionFlags flags) + { + using var builder = TestDistributedApplicationBuilder.Create(); + + // Create a binding and its matching annotation (simulating DCP behavior) + var projectA = builder.AddProject("projecta") + .WithHttpsEndpoint(1000, 2000, "mybinding") + .WithEndpoint("mybinding", e => e.AllocatedEndpoint = new AllocatedEndpoint(e, "localhost", 2000)); + + // Get the service provider. + var projectB = builder.AddProject("b") + .WithReference(projectA, "custom") + .WithReferenceEnvironment(flags); + + // Call environment variable callbacks. + var config = await EnvironmentVariableEvaluator.GetEnvironmentVariablesAsync(projectB.Resource, DistributedApplicationOperation.Run, TestServiceProvider.Instance).DefaultTimeout(); + + switch (flags) + { + case ReferenceEnvironmentInjectionFlags.All: + Assert.Equal("https://localhost:2000", config["services__custom__mybinding__0"]); + Assert.Equal("https://localhost:2000", config["custom_MYBINDING"]); + break; + case ReferenceEnvironmentInjectionFlags.ConnectionProperties: + Assert.False(config.ContainsKey("custom_MYBINDING")); + Assert.False(config.ContainsKey("services__custom__mybinding__0")); + break; + case ReferenceEnvironmentInjectionFlags.ConnectionString: + Assert.False(config.ContainsKey("custom_MYBINDING")); + Assert.False(config.ContainsKey("services__custom__mybinding__0")); + break; + case ReferenceEnvironmentInjectionFlags.ServiceDiscovery: + Assert.False(config.ContainsKey("custom_MYBINDING")); + Assert.True(config.ContainsKey("services__custom__mybinding__0")); + break; + case ReferenceEnvironmentInjectionFlags.Endpoints: + Assert.True(config.ContainsKey("custom_MYBINDING")); + Assert.False(config.ContainsKey("services__custom__mybinding__0")); + break; + case ReferenceEnvironmentInjectionFlags.None: + Assert.False(config.ContainsKey("custom_MYBINDING")); + Assert.False(config.ContainsKey("services__custom__mybinding__0")); + break; + } + } + [Fact] public async Task ResourceWithConflictingEndpointsProducesFullyScopedEnvironmentVariables() { @@ -60,6 +111,9 @@ public async Task ResourceWithConflictingEndpointsProducesFullyScopedEnvironment Assert.Equal("https://localhost:2000", config["services__projecta__mybinding__0"]); Assert.Equal("https://localhost:3000", config["services__projecta__myconflictingbinding__0"]); + + Assert.Equal("https://localhost:2000", config["PROJECTA_MYBINDING"]); + Assert.Equal("https://localhost:3000", config["PROJECTA_MYCONFLICTINGBINDING"]); } [Fact] @@ -85,6 +139,9 @@ public async Task ResourceWithNonConflictingEndpointsProducesAllVariantsOfEnviro Assert.Equal("https://localhost:2000", config["services__projecta__mybinding__0"]); Assert.Equal("http://localhost:3000", config["services__projecta__mynonconflictingbinding__0"]); + + Assert.Equal("https://localhost:2000", config["PROJECTA_MYBINDING"]); + Assert.Equal("http://localhost:3000", config["PROJECTA_MYNONCONFLICTINGBINDING"]); } [Fact] @@ -109,13 +166,13 @@ public async Task ResourceWithConflictingEndpointsProducesAllEnvironmentVariable Assert.Equal("https://localhost:2000", config["services__projecta__mybinding__0"]); Assert.Equal("https://localhost:3000", config["services__projecta__mybinding2__0"]); + Assert.Equal("https://localhost:2000", config["PROJECTA_MYBINDING"]); + Assert.Equal("https://localhost:3000", config["PROJECTA_MYBINDING2"]); + Assert.True(projectB.Resource.TryGetAnnotationsOfType(out var relationships)); - Assert.Collection(relationships, - r => - { - Assert.Equal("Reference", r.Type); - Assert.Same(projectA.Resource, r.Resource); - }); + var r = Assert.Single(relationships); + Assert.Equal("Reference", r.Type); + Assert.Same(projectA.Resource, r.Resource); } [Fact] @@ -138,13 +195,13 @@ public async Task ResourceWithEndpointsProducesAllEnvironmentVariables() Assert.Equal("https://localhost:2000", config["services__projecta__mybinding__0"]); Assert.Equal("http://localhost:3000", config["services__projecta__mybinding2__0"]); + Assert.Equal("https://localhost:2000", config["PROJECTA_MYBINDING"]); + Assert.Equal("http://localhost:3000", config["PROJECTA_MYBINDING2"]); + Assert.True(projectB.Resource.TryGetAnnotationsOfType(out var relationships)); - Assert.Collection(relationships, - r => - { - Assert.Equal("Reference", r.Type); - Assert.Same(projectA.Resource, r.Resource); - }); + var r = Assert.Single(relationships); + Assert.Equal("Reference", r.Type); + Assert.Same(projectA.Resource, r.Resource); } [Fact] @@ -299,13 +356,9 @@ public async Task ConnectionStringResourceWithExpressionConnectionString(bool us Assert.Equal("Endpoint=http://localhost:3452;Key=secretKey", config["ConnectionStrings__cs"]); Assert.True(projectB.Resource.TryGetAnnotationsOfType(out var relationships)); - Assert.Collection(relationships, - r => - { - Assert.Equal("Reference", r.Type); - Assert.Same(resource.Resource, r.Resource); - }); - + var r = Assert.Single(relationships); + Assert.Equal("Reference", r.Type); + Assert.Same(resource.Resource, r.Resource); Assert.True(resource.Resource.TryGetAnnotationsOfType(out var csRelationships)); Assert.Collection(csRelationships, r => @@ -407,6 +460,7 @@ public async Task WithReferenceHttpProduceEnvironmentVariables() var servicesKeysCount = config.Keys.Count(k => k.StartsWith("services__")); Assert.Equal(1, servicesKeysCount); Assert.Contains(config, kvp => kvp.Key == "services__petstore__default__0" && kvp.Value == "https://petstore.swagger.io/"); + Assert.Contains(config, kvp => kvp.Key == "petstore" && kvp.Value == "https://petstore.swagger.io/"); } [Fact] @@ -549,7 +603,7 @@ public async Task ResourceWithConnectionPropertiesExtensionRespectsFlags() // Create a container and explicitly configure it to emit only connection properties var container = builder.AddContainer("mycontainer", "myimage") - .WithConnectionProperties(ReferenceEnvironmentInjectionFlags.ConnectionProperties) + .WithReferenceEnvironment(ReferenceEnvironmentInjectionFlags.ConnectionProperties) .WithReference(resource); // Call environment variable callbacks. @@ -578,7 +632,7 @@ public async Task ResourceWithConnectionPropertiesExtensionOverridesDefault() // ProjectResource defaults to ReferenceEnvironmentInjectionFlags.All // Here we configure it to only inject ConnectionString (not ConnectionProperties) var projectB = builder.AddProject("projectb") - .WithConnectionProperties(ReferenceEnvironmentInjectionFlags.ConnectionString) + .WithReferenceEnvironment(ReferenceEnvironmentInjectionFlags.ConnectionString) .WithReference(resource); // Call environment variable callbacks. diff --git a/tests/Aspire.Hosting.Yarp.Tests/Snapshots/YarpConfigGeneratorTests.GenerateEnvVariablesConfigurationDockerCompose.verified.env b/tests/Aspire.Hosting.Yarp.Tests/Snapshots/YarpConfigGeneratorTests.GenerateEnvVariablesConfigurationDockerCompose.verified.env index b2d9c09b49e..4c70393fcf8 100644 --- a/tests/Aspire.Hosting.Yarp.Tests/Snapshots/YarpConfigGeneratorTests.GenerateEnvVariablesConfigurationDockerCompose.verified.env +++ b/tests/Aspire.Hosting.Yarp.Tests/Snapshots/YarpConfigGeneratorTests.GenerateEnvVariablesConfigurationDockerCompose.verified.env @@ -28,7 +28,7 @@ - "dotnet" environment: ASPNETCORE_ENVIRONMENT: "Production" - services__backend__http__0: "http://backend:8080" + BACKEND_HTTP: "http://backend:8080" REVERSEPROXY__ROUTES__route0__MATCH__PATH: "/{**catchall}" REVERSEPROXY__ROUTES__route0__CLUSTERID: "cluster_frontend" REVERSEPROXY__ROUTES__route0__TRANSFORMS__0__RequestHeader: "X-Custom-Forwarded" @@ -39,6 +39,8 @@ REVERSEPROXY__CLUSTERS__cluster_backend__METADATA__custom-metadata: "some-value" REVERSEPROXY__CLUSTERS__cluster_backend__DESTINATIONS__destination1__ADDRESS: "http://_http.backend" REVERSEPROXY__CLUSTERS__cluster_frontend__DESTINATIONS__destination1__ADDRESS: "http://_http.frontend" + services__backend__http__0: "http://backend:8080" + FRONTEND_HTTP: "http://frontend:8080" services__frontend__http__0: "http://frontend:8080" OTEL_EXPORTER_OTLP_ENDPOINT: "http://docker-compose-dashboard:18889" OTEL_EXPORTER_OTLP_PROTOCOL: "grpc"