Skip to content

Fix Kubernetes hostname publishing and routing - #19430

Open
Mitch Denny (mitchdenny) wants to merge 1 commit into
mainfrom
mitchdenny-fix-kubernetes-ingress-hostname
Open

Fix Kubernetes hostname publishing and routing#19430
Mitch Denny (mitchdenny) wants to merge 1 commit into
mainfrom
mitchdenny-fix-kubernetes-ingress-hostname

Conversation

@mitchdenny

@mitchdenny Mitch Denny (mitchdenny) commented Aug 17, 2026

Copy link
Copy Markdown
Member

Description

Kubernetes publishing currently hardcodes runtime-only hostname parameter values and leaves hostless Ingress paths and Gateway routes as catch-all rules. This means WithHostname(...) does not provide the documented routing scope and publishValueAsDefault: false is not honored for these resources.

This change:

  • preserves secrets and parameters without published defaults as owner-scoped Helm values;
  • applies configured hostnames to hostless Ingress paths and Gateway HTTPRoute resources;
  • keeps explicit per-route hostnames authoritative;
  • preserves Kubernetes default backends as catch-all, including the existing TLS compatibility rule generation; and
  • documents the hostname inheritance and default-backend behavior.

User-facing usage

C# AppHost:

var hostname = builder.AddParameter("hostname", "localhost");
var k8s = builder.AddKubernetesEnvironment("k8s");
var ingress = k8s.AddIngress("public")
    .WithHostname(hostname)
    .WithTls();

ingress.WithPath("/api", api.GetEndpoint("http"));

The generated Ingress now scopes the route and TLS configuration with a deploy-time value:

spec:
  rules:
    - host: "{{ .Values.parameters.public.hostname }}"
  tls:
    - hosts:
        - "{{ .Values.parameters.public.hostname }}"

TypeScript AppHost:

const k8s = await builder.addKubernetesEnvironment("k8s");
const ingress = await k8s.addIngress("public");
await ingress.withHostname("api.example.com");
await ingress.withIngressPath("/api", api.getEndpoint("http"));

Validation

  • Aspire.Hosting.Kubernetes.Tests: 287 passed
  • AzureKubernetesIngressTests: 8 passed
  • Deployment E2E Tests: 42 of 42 deployment jobs passed

Fixes #17755

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No

Preserve runtime-only parameters as Helm values and apply configured hostnames to hostless Ingress paths and Gateway routes. Keep default backends catch-all outside the existing TLS compatibility behavior.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: f5d87339-2d36-4690-8422-801dfac7e135
Copilot AI balanced review requested due to automatic review settings August 17, 2026 09:14
@mitchdenny

Copy link
Copy Markdown
Member Author

/deployment-test

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 19430

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 19430"

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes #17755 by preserving deploy-time hostname parameters and applying configured hostnames to Kubernetes routing resources.

Changes:

  • Emits owner-scoped Helm values for deferred parameters and secrets.
  • Applies hostnames to hostless Ingress and Gateway routes while preserving explicit hosts and default backends.
  • Adds documentation and snapshot tests.
Show a summary per file
File Description
src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs Implements deferred values and hostname inheritance.
src/Aspire.Hosting.Kubernetes/KubernetesIngressExtensions.cs Documents Ingress routing behavior.
src/Aspire.Hosting.Kubernetes/KubernetesGatewayExtensions.cs Documents Gateway routing behavior.
tests/Aspire.Hosting.Kubernetes.Tests/KubernetesIngressTests.cs Adds Ingress regression tests.
tests/Aspire.Hosting.Kubernetes.Tests/KubernetesGatewayTests.cs Adds Gateway regression tests.
...Ingress...RuntimeOnlyHostnameParameter...#00.verified.yaml Verifies deferred Ingress hostname output.
...Ingress...RuntimeOnlyHostnameParameter...#01.verified.yaml Verifies Ingress Helm values.
...Ingress...Hostname_AppliesToHostlessPath.verified.yaml Verifies inherited Ingress hosts.
...Ingress...DefaultBackendWithoutTls...verified.yaml Verifies catch-all default backend.
...Gateway...RuntimeOnlyHostnameParameter...#00.verified.yaml Verifies deferred Gateway listener.
...Gateway...RuntimeOnlyHostnameParameter...#01.verified.yaml Verifies deferred HTTPRoute hostname.
...Gateway...RuntimeOnlyHostnameParameter...#02.verified.yaml Verifies Gateway Helm values.
...Gateway...Hostname_AppliesToHostlessRoute.verified.yaml Verifies inherited HTTPRoute hosts.

Review details

Suppressed comments (1)

src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs:1150

  • The inherited HTTPRoute.spec.hostnames behavior is validated only as generated YAML. Add or extend a deployment end-to-end test to deploy a Gateway with WithHostname(...) and verify matching and nonmatching host requests, so listener/route attachment and Helm substitution are exercised together.
            else
            {
                httpRoute.Spec.Hostnames.AddRange(resolvedHostnames);
  • Files reviewed: 13/13 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Deployment tests starting on PR #19430...

This will deploy to real Azure infrastructure. Results will be posted here when complete.

View workflow run

@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:25 Inactive
@github-actions

Copy link
Copy Markdown
Contributor

Tests selector (audit mode)

The full test matrix and all jobs still run in audit mode. The tests and jobs below are what selective CI would run under enforcement.

3 / 101 test projects · 3 jobs, from 13 changed files.

Selected test projects (3 / 101)

Aspire.Hosting.Azure.Kubernetes.Tests, Aspire.Hosting.Docker.Tests, Aspire.Hosting.Kubernetes.Tests

Selected jobs (3)

deployment-e2e, extension-e2e, typescript-api-compat


How these were chosen — grouped by what changed

🔧 src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs (changed source)
1 directly: Aspire.Hosting.Kubernetes.Tests
2 via the project graph: Aspire.Hosting.Azure.Kubernetes.Tests (2 hops), Aspire.Hosting.Docker.Tests

🔧 src/Aspire.Hosting.Kubernetes/KubernetesGatewayExtensions.cs (changed source)
1 directly: Aspire.Hosting.Kubernetes.Tests

🔧 src/Aspire.Hosting.Kubernetes/KubernetesIngressExtensions.cs (changed source)
1 directly: Aspire.Hosting.Kubernetes.Tests

🧪 tests/Aspire.Hosting.Kubernetes.Tests/KubernetesGatewayTests.cs (changed test)
1 directly: Aspire.Hosting.Kubernetes.Tests

🧪 tests/Aspire.Hosting.Kubernetes.Tests/KubernetesIngressTests.cs (changed test)
1 directly: Aspire.Hosting.Kubernetes.Tests

🧪 tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesGatewayTests.AddGateway_WithHostname_AppliesToHostlessRoute.verified.yaml (changed test)
1 directly: Aspire.Hosting.Kubernetes.Tests

🧪 tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesGatewayTests.AddGateway_WithRuntimeOnlyHostnameParameter_DefersValue#00.verified.yaml (changed test)
1 directly: Aspire.Hosting.Kubernetes.Tests

🧪 tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesGatewayTests.AddGateway_WithRuntimeOnlyHostnameParameter_DefersValue#01.verified.yaml (changed test)
1 directly: Aspire.Hosting.Kubernetes.Tests

🧪 tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesGatewayTests.AddGateway_WithRuntimeOnlyHostnameParameter_DefersValue#02.verified.yaml (changed test)
1 directly: Aspire.Hosting.Kubernetes.Tests

🧪 tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesIngressTests.AddIngress_HostnameWithDefaultBackendWithoutTls_DoesNotGenerateHostRule.verified.yaml (changed test)
1 directly: Aspire.Hosting.Kubernetes.Tests

🧪 tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesIngressTests.AddIngress_WithHostname_AppliesToHostlessPath.verified.yaml (changed test)
1 directly: Aspire.Hosting.Kubernetes.Tests

🧪 tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesIngressTests.AddIngress_WithRuntimeOnlyHostnameParameter_DefersValue#00.verified.yaml (changed test)
1 directly: Aspire.Hosting.Kubernetes.Tests

🧪 tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesIngressTests.AddIngress_WithRuntimeOnlyHostnameParameter_DefersValue#01.verified.yaml (changed test)
1 directly: Aspire.Hosting.Kubernetes.Tests

Job reasons

Job Triggered by
deployment-e2e affected project Aspire.Hosting.Azure.Kubernetes
extension-e2e src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs, src/Aspire.Hosting.Kubernetes/KubernetesGatewayExtensions.cs, src/Aspire.Hosting.Kubernetes/KubernetesIngressExtensions.cs
• affected project Aspire.Hosting.Kubernetes
typescript-api-compat affected project Aspire.Hosting.Kubernetes

Selection computed for commit cb51702.

@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions
github-actions Bot temporarily deployed to deployment-testing August 17, 2026 09:33 Inactive
@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@mitchdenny

Copy link
Copy Markdown
Member Author

PR Testing Report

PR Information

Artifact Version Verification

  • Expected commit: cb51702806f38e200da794f8ad3a453e1a5cf5cf
  • Installed CLI: 13.6.0-pr.19430.gcb517028
  • Status: PASS - the installed PR artifact contains the expected short commit SHA.

Changes Analyzed

The PR changes Kubernetes publish-time parameter resolution and generated Ingress/Gateway routing behavior:

  • Runtime-only and secret parameters remain deployment-time Helm values instead of being evaluated and embedded during publish.
  • Resource-level Ingress hostnames apply to paths created with the hostless WithPath overload.
  • Resource-level Gateway hostnames apply to routes created with the hostless WithRoute overload.
  • Explicit route hostnames retain precedence.
  • A default Ingress backend remains catch-all when TLS is not configured.

Files Changed

  • src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs
  • src/Aspire.Hosting.Kubernetes/KubernetesGatewayExtensions.cs
  • src/Aspire.Hosting.Kubernetes/KubernetesIngressExtensions.cs
  • tests/Aspire.Hosting.Kubernetes.Tests/KubernetesGatewayTests.cs
  • tests/Aspire.Hosting.Kubernetes.Tests/KubernetesIngressTests.cs
  • Eight Kubernetes Verify snapshots

Change Categories

  • CLI implementation changes
  • Hosting integration changes
  • Dashboard changes
  • Client/component changes
  • Template changes
  • VS Code extension changes
  • Test changes
  • CI infrastructure changes

Test Scenarios Executed

Scenario 1: Runtime-only Ingress hostname parameter

Objective: Verify a runtime-only parameter is emitted as a Helm input and is used by both the Ingress rule and TLS host.

Coverage type: Happy path / original issue reproduction

Status: PASS

Steps:

  1. Created a fresh aspire-empty C# AppHost from the PR hive.
  2. Added Aspire.Hosting.Kubernetes version 13.6.0-pr.19430.gcb517028.
  3. Added an external nginx endpoint, an Ingress path, TLS, and WithHostname bound to AddParameter("hostname", "localhost").
  4. Published Kubernetes artifacts.
  5. Asserted that the rule and TLS host use {{ .Values.parameters.public.hostname }}, values.yaml contains parameters.public.hostname: "", and the literal runtime value localhost is absent.

Evidence:

  • scenario-ingress-parameter/apphost.cs
  • scenario-ingress-parameter-publish.log
  • scenario-ingress-parameter-assertions.txt
  • scenario-ingress-parameter/out/templates/public/public.yaml
  • scenario-ingress-parameter/out/values.yaml

Scenario 2: Inherited Ingress hosts and explicit-host precedence

Objective: Verify hostless paths are expanded across resource-level hosts while an explicit route host remains authoritative.

Coverage type: Happy path / precedence boundary

Status: PASS

Steps:

  1. Created a fresh AppHost with two external nginx endpoints.
  2. Added Ingress hostnames api.example.com and www.example.com.
  3. Added hostless path /shared and explicit route admin.example.com/admin.
  4. Published Kubernetes artifacts.
  5. Asserted exactly three host rules: two inherited /shared rules and one explicit /admin rule.

Evidence:

  • scenario-ingress-routing/apphost.cs
  • scenario-ingress-routing-publish.log
  • scenario-ingress-routing-assertions.txt
  • scenario-ingress-routing/out/templates/public/public.yaml

Scenario 3: Runtime-only Gateway hostname parameter

Objective: Verify a deferred hostname is shared by the Gateway HTTPS listener and generated HTTPRoute.

Coverage type: Happy path / analogous Gateway behavior

Status: PASS

Steps:

  1. Created a fresh AppHost from the PR hive.
  2. Added a Gateway with TLS and WithHostname bound to a runtime-only parameter.
  3. Added a hostless route to an external nginx endpoint.
  4. Published Kubernetes artifacts.
  5. Asserted that the HTTPS listener and HTTPRoute.spec.hostnames use {{ .Values.parameters.public.hostname }}, values.yaml contains the empty deployment input, and localhost is absent.

Evidence:

  • scenario-gateway-parameter/apphost.cs
  • scenario-gateway-parameter-publish.log
  • scenario-gateway-parameter-assertions.txt
  • scenario-gateway-parameter/out/templates/public/public.yaml
  • scenario-gateway-parameter/out/templates/public/route.yaml
  • scenario-gateway-parameter/out/values.yaml

Scenario 4: Default backend without TLS

Objective: Verify a configured hostname does not incorrectly host-restrict an Ingress default backend.

Coverage type: Boundary

Status: PASS

Steps:

  1. Created a fresh AppHost with an external nginx endpoint.
  2. Configured an Ingress with WithHostname("api.example.com") and WithDefaultBackend, without TLS.
  3. Published Kubernetes artifacts.
  4. Asserted that spec.defaultBackend exists and no rules, host, or api.example.com value was synthesized.

Evidence:

  • scenario-default-backend/apphost.cs
  • scenario-default-backend-publish.log
  • scenario-default-backend-assertions.txt
  • scenario-default-backend/out/templates/public/public.yaml

Scenario 5: Non-external routed endpoint

Objective: Verify publish fails safely when an Ingress routes to an endpoint that was not explicitly marked external.

Coverage type: Unhappy path

Status: PASS

Expected outcome: Non-zero publish exit with an actionable validation message.

Observed outcome: Publish failed and identified resource api, Ingress public, and .WithExternalHttpEndpoints() as the remedy.

Evidence:

  • scenario-nonexternal-endpoint/apphost.cs
  • scenario-nonexternal-endpoint-publish.log
  • scenario-nonexternal-endpoint-assertions.txt

Additional Validation

  • Aspire.Hosting.Kubernetes.Tests: 287 passed.
  • Focused Azure Kubernetes Ingress tests: 8 passed.
  • Deployment E2E run 32014982185 ran at the same head commit and completed successfully: 46 jobs passed, 1 skipped, 0 failed.

Summary

Scenario Status Notes
Runtime-only Ingress hostname parameter PASS Helm input appears in rule, TLS, and values.yaml; runtime value absent
Inherited Ingress hosts and explicit precedence PASS Two inherited rules plus one explicit rule
Runtime-only Gateway hostname parameter PASS Listener and HTTPRoute use the same deferred Helm input
Default backend without TLS PASS Remains catch-all; no host rule synthesized
Non-external routed endpoint PASS Publish fails with actionable guidance
Full deployment E2E PASS 46 passed, 1 skipped, 0 failed

Overall Result

PASS - PR #19430 is verified at cb51702806f38e200da794f8ad3a453e1a5cf5cf.

The original Ingress failure no longer reproduces, the equivalent Gateway path behaves consistently, precedence and default-backend boundaries are preserved, invalid endpoint exposure fails safely, and the full deployment E2E workflow remains green.

@mitchdenny
Mitch Denny (mitchdenny) marked this pull request as ready for review August 19, 2026 03:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Kubernetes ingress WithHostname with parameter is hardcoding the hostname and doesn't add the hostname to the rules

2 participants