diff --git a/src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs b/src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs index 6ccc245d316..0f2ba86cfba 100644 --- a/src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs +++ b/src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs @@ -580,8 +580,8 @@ private static async Task ResolveExpressionAtDeployTimeAsync(ReferenceEx /// /// Resolves a for inclusion in a Kubernetes manifest /// produced by an ingress or gateway resource. When the expression wraps one or more - /// instances that have no value at publish time - /// (e.g., user-supplied parameters without defaults, or secrets), the expression is + /// instances that must remain deploy-time inputs + /// (for example, secrets or parameters without published defaults), the expression is /// rendered with Helm template placeholders (such as {{ .Values.parameters.ingress.ingressclass }}) /// and the parameters are captured for deploy-time resolution into a values override file. /// @@ -594,31 +594,33 @@ private static async Task ResolveExpressionAtDeployTimeAsync(ReferenceEx /// The cancellation token. private async Task ResolveExpressionAsync(ReferenceExpression expression, string owningResourceName, CancellationToken cancellationToken) { - try - { - return (await expression.GetValueAsync(cancellationToken).ConfigureAwait(false))!; - } - catch (MissingParameterValueException) + if (!expression.ValueProviders.OfType().Any(ShouldCaptureAsHelmValue)) { - // One or more parameters in the expression have no value at publish time - // (e.g., a parameter created via AddParameter("ingressclass") with no default, - // or a secret parameter). Substitute each unresolved parameter with a Helm - // template reference into values.yaml so the resulting manifest is a valid - // Helm template and the value can be supplied at deploy time. - var owningResourceKey = owningResourceName.ToHelmValuesSectionName(); - var args = new object[expression.ValueProviders.Count]; - - for (var i = 0; i < expression.ValueProviders.Count; i++) + try + { + return (await expression.GetValueAsync(cancellationToken).ConfigureAwait(false))!; + } + catch (MissingParameterValueException) { - args[i] = await ResolveValueProviderAsync( - expression.ValueProviders[i], - owningResourceName, - owningResourceKey, - cancellationToken).ConfigureAwait(false); + // Fall through to Helm-reference substitution below. } + } - return string.Format(System.Globalization.CultureInfo.InvariantCulture, expression.Format, args); + // Parameters without published defaults may still have runtime values, but publish must + // preserve them as deploy-time inputs instead of leaking those values into generated YAML. + var owningResourceKey = owningResourceName.ToHelmValuesSectionName(); + var args = new object[expression.ValueProviders.Count]; + + for (var i = 0; i < expression.ValueProviders.Count; i++) + { + args[i] = await ResolveValueProviderAsync( + expression.ValueProviders[i], + owningResourceName, + owningResourceKey, + cancellationToken).ConfigureAwait(false); } + + return string.Format(System.Globalization.CultureInfo.InvariantCulture, expression.Format, args); } private async Task ResolveValueProviderAsync( @@ -629,19 +631,16 @@ private async Task ResolveValueProviderAsync( { if (valueProvider is ParameterResource parameter) { - // Attempt to resolve this individual parameter first. The outer - // MissingParameterValueException from the whole-expression resolve attempt - // only tells us that *some* parameter in the expression was unresolved; - // others (e.g., those with `publishValueAsDefault: true`) may still have - // a value and should be inlined into the manifest rather than left as - // Helm placeholders. This keeps the published chart maximally self-contained. - try + if (!ShouldCaptureAsHelmValue(parameter)) { - return (await parameter.GetValueAsync(cancellationToken).ConfigureAwait(false)) ?? string.Empty; - } - catch (MissingParameterValueException) - { - // Fall through to Helm-reference substitution below. + try + { + return (await parameter.GetValueAsync(cancellationToken).ConfigureAwait(false)) ?? string.Empty; + } + catch (MissingParameterValueException) + { + // Fall through to Helm-reference substitution below. + } } // Capture the parameter so HelmDeploymentEngine writes its resolved value to @@ -673,6 +672,24 @@ private async Task ResolveValueProviderAsync( return (await valueProvider.GetValueAsync(cancellationToken).ConfigureAwait(false)) ?? string.Empty; } + private static bool ShouldCaptureAsHelmValue(ParameterResource parameter) + => parameter.Secret || parameter.Default is null; + + private async Task> ResolveHostnamesAsync( + IEnumerable hostnames, + string owningResourceName, + CancellationToken cancellationToken) + { + var resolvedHostnames = new List(); + + foreach (var hostname in hostnames) + { + resolvedHostnames.Add(await ResolveExpressionAsync(hostname, owningResourceName, cancellationToken).ConfigureAwait(false)); + } + + return resolvedHostnames; + } + private async Task ProcessIngressResources(DistributedApplicationModel model, Dictionary deploymentTargets, ILogger logger, CancellationToken cancellationToken) { var ingressResources = model.Resources @@ -735,17 +752,40 @@ private async Task ProcessIngressResources(DistributedApplicationModel model, Di ingress.Metadata.Annotations[key] = await ResolveExpressionAsync(value, ingressResource.Name, cancellationToken).ConfigureAwait(false); } - var pathsByHost = ingressResource.Paths.GroupBy(p => p.Host ?? string.Empty); + var resolvedHostnames = await ResolveHostnamesAsync( + ingressResource.Hostnames, + ingressResource.Name, + cancellationToken).ConfigureAwait(false); + var pathsByHost = new Dictionary>(); + + foreach (var path in ingressResource.Paths) + { + if (path.Host is { } explicitHost) + { + AddPathForHost(explicitHost, path); + } + else if (resolvedHostnames.Count == 0) + { + AddPathForHost(string.Empty, path); + } + else + { + foreach (var hostname in resolvedHostnames) + { + AddPathForHost(hostname, path); + } + } + } - foreach (var hostGroup in pathsByHost) + foreach (var (host, paths) in pathsByHost) { var rule = new IngressRuleV1(); - if (!string.IsNullOrEmpty(hostGroup.Key)) + if (!string.IsNullOrEmpty(host)) { - rule.Host = hostGroup.Key; + rule.Host = host; } - foreach (var pathRule in hostGroup) + foreach (var pathRule in paths) { var backend = ResolveIngressBackend(pathRule.Endpoint, deploymentTargets, ingressResource.Name, logger); if (backend is null) @@ -783,16 +823,14 @@ private async Task ProcessIngressResources(DistributedApplicationModel model, Di SecretName = await ResolveExpressionAsync(tls.SecretName, ingressResource.Name, cancellationToken).ConfigureAwait(false), }; - foreach (var host in ingressResource.Hostnames) - { - tlsEntry.Hosts.Add(await ResolveExpressionAsync(host, ingressResource.Name, cancellationToken).ConfigureAwait(false)); - } + tlsEntry.Hosts.AddRange(resolvedHostnames); ingress.Spec.Tls.Add(tlsEntry); } - // Auto-generate rules for TLS hosts that don't have explicit routes. - if (ingress.Spec.DefaultBackend is not null) + // A default backend remains catch-all even when hostnames are configured. Only synthesize + // host rules for TLS because some ingress controllers require each TLS host to have a rule. + if (ingress.Spec.DefaultBackend is not null && ingress.Spec.Tls.Count > 0) { var hostsWithRules = new HashSet( ingress.Spec.Rules @@ -800,41 +838,37 @@ private async Task ProcessIngressResources(DistributedApplicationModel model, Di .Select(r => r.Host!), StringComparer.OrdinalIgnoreCase); - foreach (var tls in ingressResource.TlsConfigs) + foreach (var resolvedHost in resolvedHostnames) { - foreach (var host in ingressResource.Hostnames) + if (hostsWithRules.Add(resolvedHost)) { - var resolvedHost = await ResolveExpressionAsync(host, ingressResource.Name, cancellationToken).ConfigureAwait(false); - if (!hostsWithRules.Contains(resolvedHost)) + ingress.Spec.Rules.Add(new IngressRuleV1 { - ingress.Spec.Rules.Add(new IngressRuleV1 + Host = resolvedHost, + Http = new HttpIngressRuleValueV1 { - Host = resolvedHost, - Http = new HttpIngressRuleValueV1 + Paths = { - Paths = + new HttpIngressPathV1 { - new HttpIngressPathV1 + Path = "/", + PathType = IngressPathType.Prefix.ToKubernetesString(), + Backend = new IngressBackendV1 { - Path = "/", - PathType = IngressPathType.Prefix.ToKubernetesString(), - Backend = new IngressBackendV1 + Service = new IngressServiceBackendV1 { - Service = new IngressServiceBackendV1 + Name = ingress.Spec.DefaultBackend.Service.Name, + Port = new ServiceBackendPortV1 { - Name = ingress.Spec.DefaultBackend.Service.Name, - Port = new ServiceBackendPortV1 - { - Name = ingress.Spec.DefaultBackend.Service.Port.Name, - Number = ingress.Spec.DefaultBackend.Service.Port.Number - } + Name = ingress.Spec.DefaultBackend.Service.Port.Name, + Number = ingress.Spec.DefaultBackend.Service.Port.Number } } } } } - }); - } + } + }); } } } @@ -846,6 +880,17 @@ private async Task ProcessIngressResources(DistributedApplicationModel model, Di } return ingress; + + void AddPathForHost(string host, IngressPathConfig path) + { + if (!pathsByHost.TryGetValue(host, out var paths)) + { + paths = []; + pathsByHost.Add(host, paths); + } + + paths.Add(path); + } } private static IngressBackendV1? ResolveIngressBackend( @@ -1000,6 +1045,10 @@ private async Task BuildGatewayObjects( { Metadata = { Name = gatewayName } }; + var resolvedHostnames = await ResolveHostnamesAsync( + gatewayResource.Hostnames, + gatewayResource.Name, + cancellationToken).ConfigureAwait(false); gateway.Spec.GatewayClassName = await ResolveExpressionAsync(gatewayResource.GatewayClassName, gatewayResource.Name, cancellationToken).ConfigureAwait(false); @@ -1024,7 +1073,7 @@ private async Task BuildGatewayObjects( { var resolvedSecretName = await ResolveExpressionAsync(tls.SecretName, gatewayResource.Name, cancellationToken).ConfigureAwait(false); - if (gatewayResource.Hostnames.Count == 0) + if (resolvedHostnames.Count == 0) { // No hostnames specified — create an HTTPS listener without a hostname restriction. // The hostname will be discovered from the Gateway's assigned address after deployment @@ -1050,13 +1099,11 @@ private async Task BuildGatewayObjects( } else { - foreach (var host in gatewayResource.Hostnames) + foreach (var resolvedHost in resolvedHostnames) { var listenerName = tlsListenerIndex == 0 ? "https" : $"https-{tlsListenerIndex}"; tlsListenerIndex++; - var resolvedHost = await ResolveExpressionAsync(host, gatewayResource.Name, cancellationToken).ConfigureAwait(false); - gateway.Spec.Listeners.Add(new GatewayListenerV1 { Name = listenerName, @@ -1098,6 +1145,10 @@ private async Task BuildGatewayObjects( { httpRoute.Spec.Hostnames.Add(hostGroup.Key); } + else + { + httpRoute.Spec.Hostnames.AddRange(resolvedHostnames); + } foreach (var route in hostGroup) { diff --git a/src/Aspire.Hosting.Kubernetes/KubernetesGatewayExtensions.cs b/src/Aspire.Hosting.Kubernetes/KubernetesGatewayExtensions.cs index 4e1853361e4..2e42fded9a2 100644 --- a/src/Aspire.Hosting.Kubernetes/KubernetesGatewayExtensions.cs +++ b/src/Aspire.Hosting.Kubernetes/KubernetesGatewayExtensions.cs @@ -89,9 +89,10 @@ public static IResourceBuilder WithGatewayClass( } /// - /// Adds a path-based routing rule to the gateway. The rule matches all hosts and routes - /// traffic matching the specified path to the given endpoint's backing Kubernetes service. - /// This generates an HTTPRoute resource attached to the Gateway. + /// Adds a path-based routing rule to the gateway. The rule matches each hostname configured + /// with , or all + /// hosts when no hostname is configured, and routes matching traffic to the endpoint's backing + /// Kubernetes service. This generates an HTTPRoute resource attached to the Gateway. /// /// The gateway resource builder. /// The URL path to match (e.g., "/" or "/api"). Must start with /. @@ -165,8 +166,9 @@ public static IResourceBuilder WithRoute( /// /// Adds a hostname that this gateway's routes match. Multiple hostnames can be added by calling - /// this method repeatedly. Hostnames are used as hostnames in generated HTTPRoute - /// resources and as HTTPS listener hostnames when TLS is configured. + /// this method repeatedly. Routes without an explicit host apply to each configured hostname. + /// Hostnames are used as hostnames in generated HTTPRoute resources and as HTTPS + /// listener hostnames when TLS is configured. /// /// The gateway resource builder. /// The hostname to match (e.g., "api.example.com"). diff --git a/src/Aspire.Hosting.Kubernetes/KubernetesIngressExtensions.cs b/src/Aspire.Hosting.Kubernetes/KubernetesIngressExtensions.cs index 092ad05c59e..648127e1662 100644 --- a/src/Aspire.Hosting.Kubernetes/KubernetesIngressExtensions.cs +++ b/src/Aspire.Hosting.Kubernetes/KubernetesIngressExtensions.cs @@ -99,8 +99,9 @@ public static IResourceBuilder WithIngressClass( } /// - /// Adds a path-based rule to the ingress. The rule matches all hosts and forwards - /// traffic matching the specified path to the given endpoint's backing Kubernetes service. + /// Adds a path-based rule to the ingress. The rule matches each hostname configured with + /// , or all hosts + /// when no hostname is configured, and forwards matching traffic to the endpoint's backing Kubernetes service. /// /// The ingress resource builder. /// The URL path to match (e.g., "/" or "/api"). Must start with /. @@ -185,7 +186,8 @@ public static IResourceBuilder WithPath( /// /// Adds a hostname that this ingress matches. Multiple hostnames can be added by calling - /// this method repeatedly. If no hostnames are configured, the ingress matches all hosts. + /// this method repeatedly. Path rules without an explicit host apply to each configured + /// hostname. If no hostnames are configured, those rules match all hosts. /// /// The ingress resource builder. /// The hostname to match (e.g., "api.example.com"). @@ -300,6 +302,10 @@ public static IResourceBuilder WithTls( /// The endpoint reference identifying the default backend service and port. /// A reference to the for chaining. /// The resource builder. + /// + /// Kubernetes default backends remain catch-all even when hostnames are configured. Use host-specific + /// path rules when traffic must be restricted by hostname. + /// [AspireExport] public static IResourceBuilder WithDefaultBackend( this IResourceBuilder builder, diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/KubernetesGatewayTests.cs b/tests/Aspire.Hosting.Kubernetes.Tests/KubernetesGatewayTests.cs index ff0121b418c..441c5a4b2a8 100644 --- a/tests/Aspire.Hosting.Kubernetes.Tests/KubernetesGatewayTests.cs +++ b/tests/Aspire.Hosting.Kubernetes.Tests/KubernetesGatewayTests.cs @@ -81,6 +81,63 @@ public async Task AddGateway_WithHostRoute_GeneratesHostnameInHttpRoute() Assert.Contains("HTTPRoute", content); } + [Fact] + public async Task AddGateway_WithRuntimeOnlyHostnameParameter_DefersValue() + { + using var workspace = TemporaryWorkspace.Create(outputHelper); + var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, workspace.Path); + + var hostname = builder.AddParameter("hostname", "localhost"); + var k8s = builder.AddKubernetesEnvironment("env"); + var gateway = k8s.AddGateway("public") + .WithGatewayClass("nginx") + .WithHostname(hostname) + .WithTls(); + + var api = builder.AddContainer("myapi", "nginx") + .WithHttpEndpoint(targetPort: 8080) + .WithExternalHttpEndpoints(); + + gateway.WithRoute("/api", api.GetEndpoint("http")); + + using var app = builder.Build(); + app.Run(); + + var gatewayPath = Path.Combine(workspace.Path, "templates", "public", "public.yaml"); + var routePath = Path.Combine(workspace.Path, "templates", "public", "route.yaml"); + var valuesPath = Path.Combine(workspace.Path, "values.yaml"); + + await Verify(File.ReadAllText(gatewayPath), "yaml") + .AppendContentAsFile(File.ReadAllText(routePath), "yaml") + .AppendContentAsFile(File.ReadAllText(valuesPath), "yaml"); + } + + [Fact] + public async Task AddGateway_WithHostname_AppliesToHostlessRoute() + { + using var workspace = TemporaryWorkspace.Create(outputHelper); + var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, workspace.Path); + + var k8s = builder.AddKubernetesEnvironment("env"); + var gateway = k8s.AddGateway("public") + .WithGatewayClass("nginx") + .WithHostname("api.example.com") + .WithHostname("www.example.com"); + + var api = builder.AddContainer("myapi", "nginx") + .WithHttpEndpoint(targetPort: 8080) + .WithExternalHttpEndpoints(); + + gateway.WithRoute("/api", api.GetEndpoint("http")); + + using var app = builder.Build(); + app.Run(); + + var routePath = Path.Combine(workspace.Path, "templates", "public", "route.yaml"); + + await Verify(File.ReadAllText(routePath), "yaml"); + } + [Fact] public async Task AddGateway_WithTls_GeneratesHttpsListener() { diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/KubernetesIngressTests.cs b/tests/Aspire.Hosting.Kubernetes.Tests/KubernetesIngressTests.cs index 2edaa3381d5..bb68b72b671 100644 --- a/tests/Aspire.Hosting.Kubernetes.Tests/KubernetesIngressTests.cs +++ b/tests/Aspire.Hosting.Kubernetes.Tests/KubernetesIngressTests.cs @@ -153,6 +153,83 @@ public async Task AddIngress_WithIngressClassParameter_WithDefaultValue_Resolves Assert.DoesNotContain("{{ .Values", content); } + [Fact] + public async Task AddIngress_WithRuntimeOnlyHostnameParameter_DefersValue() + { + using var workspace = TemporaryWorkspace.Create(outputHelper); + var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, workspace.Path); + + var hostname = builder.AddParameter("hostname", "localhost"); + var k8s = builder.AddKubernetesEnvironment("env"); + var ingress = k8s.AddIngress("public") + .WithHostname(hostname) + .WithTls(); + + var api = builder.AddContainer("myapi", "nginx") + .WithHttpEndpoint(targetPort: 8080) + .WithExternalHttpEndpoints(); + + ingress.WithPath("/api", api.GetEndpoint("http")); + + using var app = builder.Build(); + app.Run(); + + var ingressPath = Path.Combine(workspace.Path, "templates", "public", "public.yaml"); + var valuesPath = Path.Combine(workspace.Path, "values.yaml"); + + await Verify(File.ReadAllText(ingressPath), "yaml") + .AppendContentAsFile(File.ReadAllText(valuesPath), "yaml"); + } + + [Fact] + public async Task AddIngress_WithHostname_AppliesToHostlessPath() + { + using var workspace = TemporaryWorkspace.Create(outputHelper); + var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, workspace.Path); + + var k8s = builder.AddKubernetesEnvironment("env"); + var ingress = k8s.AddIngress("public") + .WithHostname("api.example.com") + .WithHostname("www.example.com"); + + var api = builder.AddContainer("myapi", "nginx") + .WithHttpEndpoint(targetPort: 8080) + .WithExternalHttpEndpoints(); + + ingress.WithPath("/api", api.GetEndpoint("http")); + + using var app = builder.Build(); + app.Run(); + + var ingressPath = Path.Combine(workspace.Path, "templates", "public", "public.yaml"); + + await Verify(File.ReadAllText(ingressPath), "yaml"); + } + + [Fact] + public async Task AddIngress_HostnameWithDefaultBackendWithoutTls_DoesNotGenerateHostRule() + { + using var workspace = TemporaryWorkspace.Create(outputHelper); + var builder = TestDistributedApplicationBuilder.Create(DistributedApplicationOperation.Publish, workspace.Path); + + var k8s = builder.AddKubernetesEnvironment("env"); + var ingress = k8s.AddIngress("public") + .WithHostname("api.example.com"); + + var api = builder.AddContainer("myapi", "nginx") + .WithHttpEndpoint(targetPort: 8080) + .WithExternalHttpEndpoints(); + + ingress.WithDefaultBackend(api.GetEndpoint("http")); + + using var app = builder.Build(); + app.Run(); + + var ingressPath = Path.Combine(workspace.Path, "templates", "public", "public.yaml"); + + await Verify(File.ReadAllText(ingressPath), "yaml"); + } + [Fact] public async Task AddIngress_WithTls_GeneratesTlsSection() { diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesGatewayTests.AddGateway_WithHostname_AppliesToHostlessRoute.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesGatewayTests.AddGateway_WithHostname_AppliesToHostlessRoute.verified.yaml new file mode 100644 index 00000000000..aa9c11e64b3 --- /dev/null +++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesGatewayTests.AddGateway_WithHostname_AppliesToHostlessRoute.verified.yaml @@ -0,0 +1,19 @@ +--- +apiVersion: "gateway.networking.k8s.io/v1" +kind: "HTTPRoute" +metadata: + name: "public-route" +spec: + parentRefs: + - name: "public" + hostnames: + - "api.example.com" + - "www.example.com" + rules: + - matches: + - path: + type: "PathPrefix" + value: "/api" + backendRefs: + - name: "myapi-service" + port: 8080 diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesGatewayTests.AddGateway_WithRuntimeOnlyHostnameParameter_DefersValue#00.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesGatewayTests.AddGateway_WithRuntimeOnlyHostnameParameter_DefersValue#00.verified.yaml new file mode 100644 index 00000000000..5d0e5c4d745 --- /dev/null +++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesGatewayTests.AddGateway_WithRuntimeOnlyHostnameParameter_DefersValue#00.verified.yaml @@ -0,0 +1,25 @@ +--- +apiVersion: "gateway.networking.k8s.io/v1" +kind: "Gateway" +metadata: + name: "public" +spec: + gatewayClassName: "nginx" + listeners: + - name: "http" + protocol: "HTTP" + port: 80 + allowedRoutes: + namespaces: + from: "Same" + - name: "https" + protocol: "HTTPS" + port: 443 + hostname: "{{ .Values.parameters.public.hostname }}" + tls: + mode: "Terminate" + certificateRefs: + - name: "public-tls" + allowedRoutes: + namespaces: + from: "Same" diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesGatewayTests.AddGateway_WithRuntimeOnlyHostnameParameter_DefersValue#01.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesGatewayTests.AddGateway_WithRuntimeOnlyHostnameParameter_DefersValue#01.verified.yaml new file mode 100644 index 00000000000..d307f845ecf --- /dev/null +++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesGatewayTests.AddGateway_WithRuntimeOnlyHostnameParameter_DefersValue#01.verified.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: "gateway.networking.k8s.io/v1" +kind: "HTTPRoute" +metadata: + name: "public-route" +spec: + parentRefs: + - name: "public" + hostnames: + - "{{ .Values.parameters.public.hostname }}" + rules: + - matches: + - path: + type: "PathPrefix" + value: "/api" + backendRefs: + - name: "myapi-service" + port: 8080 diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesGatewayTests.AddGateway_WithRuntimeOnlyHostnameParameter_DefersValue#02.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesGatewayTests.AddGateway_WithRuntimeOnlyHostnameParameter_DefersValue#02.verified.yaml new file mode 100644 index 00000000000..6d621975b1f --- /dev/null +++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesGatewayTests.AddGateway_WithRuntimeOnlyHostnameParameter_DefersValue#02.verified.yaml @@ -0,0 +1,5 @@ +parameters: + public: + hostname: "" +secrets: {} +config: {} diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesIngressTests.AddIngress_HostnameWithDefaultBackendWithoutTls_DoesNotGenerateHostRule.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesIngressTests.AddIngress_HostnameWithDefaultBackendWithoutTls_DoesNotGenerateHostRule.verified.yaml new file mode 100644 index 00000000000..c240fa25066 --- /dev/null +++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesIngressTests.AddIngress_HostnameWithDefaultBackendWithoutTls_DoesNotGenerateHostRule.verified.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: "networking.k8s.io/v1" +kind: "Ingress" +metadata: + name: "public" +spec: + defaultBackend: + service: + name: "myapi-service" + port: + name: "http" diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesIngressTests.AddIngress_WithHostname_AppliesToHostlessPath.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesIngressTests.AddIngress_WithHostname_AppliesToHostlessPath.verified.yaml new file mode 100644 index 00000000000..bf20dde98aa --- /dev/null +++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesIngressTests.AddIngress_WithHostname_AppliesToHostlessPath.verified.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: "networking.k8s.io/v1" +kind: "Ingress" +metadata: + name: "public" +spec: + rules: + - http: + paths: + - backend: + service: + name: "myapi-service" + port: + name: "http" + pathType: "Prefix" + path: "/api" + host: "api.example.com" + - http: + paths: + - backend: + service: + name: "myapi-service" + port: + name: "http" + pathType: "Prefix" + path: "/api" + host: "www.example.com" diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesIngressTests.AddIngress_WithRuntimeOnlyHostnameParameter_DefersValue#00.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesIngressTests.AddIngress_WithRuntimeOnlyHostnameParameter_DefersValue#00.verified.yaml new file mode 100644 index 00000000000..5c38ab91bf0 --- /dev/null +++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesIngressTests.AddIngress_WithRuntimeOnlyHostnameParameter_DefersValue#00.verified.yaml @@ -0,0 +1,21 @@ +--- +apiVersion: "networking.k8s.io/v1" +kind: "Ingress" +metadata: + name: "public" +spec: + rules: + - http: + paths: + - backend: + service: + name: "myapi-service" + port: + name: "http" + pathType: "Prefix" + path: "/api" + host: "{{ .Values.parameters.public.hostname }}" + tls: + - secretName: "public-tls" + hosts: + - "{{ .Values.parameters.public.hostname }}" diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesIngressTests.AddIngress_WithRuntimeOnlyHostnameParameter_DefersValue#01.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesIngressTests.AddIngress_WithRuntimeOnlyHostnameParameter_DefersValue#01.verified.yaml new file mode 100644 index 00000000000..6d621975b1f --- /dev/null +++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesIngressTests.AddIngress_WithRuntimeOnlyHostnameParameter_DefersValue#01.verified.yaml @@ -0,0 +1,5 @@ +parameters: + public: + hostname: "" +secrets: {} +config: {}