Skip to content

Add Kubernetes deploy pipeline with Helm engine#15723

Merged
davidfowl merged 54 commits intomainfrom
feature/k8s-deploy
Apr 11, 2026
Merged

Add Kubernetes deploy pipeline with Helm engine#15723
davidfowl merged 54 commits intomainfrom
feature/k8s-deploy

Conversation

@mitchdenny
Copy link
Copy Markdown
Member

Description

Implements end-to-end aspire deploy support for Kubernetes environments using an annotation-driven deployment engine architecture with Helm as the default engine.

What's new

Annotation-driven deployment engine:

  • KubernetesDeploymentEngineAnnotation — pluggable callback for creating deployment pipeline steps
  • Config annotations (KubernetesNamespaceAnnotation, HelmReleaseNameAnnotation, HelmChartVersionAnnotation) using ReferenceExpression for both literal and parameter-backed values

Helm deployment engine:

  • HelmDeploymentEngine creates 4 pipeline steps:
    • prepare-{name} — resolves chart values and updates Chart.yaml version
    • helm-deploy-{name} — runs helm upgrade --install with namespace/release config
    • print-{resource}-summary — retrieves service endpoints via kubectl
    • helm-uninstall-{name} — teardown step

Builder API:

builder.AddKubernetesEnvironment("k8s")
    .WithHelm(helm =>
    {
        helm.WithNamespace("production");
        helm.WithReleaseName("my-app");
        helm.WithChartVersion("1.0.0");
    });

Container registry wiring:

  • KubernetesInfrastructure now resolves container registries for image push support

Pipeline step ordering

Build → Push → Prepare → Helm Deploy → Print Summary

Design decisions

  • Uses helm upgrade --install for idempotent deployments
  • Helm is the default engine (added automatically by AddKubernetesEnvironment)
  • WithAnnotation(..., Replace) prevents annotation stacking on repeated WithHelm calls
  • Architecture supports future engines (kubectl apply, Kustomize) via the KubernetesDeploymentEngineAnnotation extension point

Testing

  • 22 new unit tests in KubernetesDeployTests.cs
  • All 48 Kubernetes tests passing (26 existing + 22 new)
  • Tests cover: annotations, configuration, pipeline step wiring, diagnostics mode, and container registry integration

Microsoft Reviewers: Open in CodeFlow

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 31, 2026

🚀 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 -- 15723

Or

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

@github-actions
Copy link
Copy Markdown
Contributor

Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

@mitchdenny mitchdenny force-pushed the feature/k8s-deploy branch from 0005428 to f979f8a Compare April 9, 2026 02:21
Comment thread src/Aspire.Hosting.Kubernetes/Extensions/ResourceExtensions.cs Outdated
Comment thread src/Aspire.Hosting.Kubernetes/HelmChartConfiguration.cs Outdated
Comment thread src/Aspire.Hosting.Kubernetes/HelmChartConfiguration.cs Outdated
@mitchdenny mitchdenny marked this pull request as ready for review April 9, 2026 04:52
Copilot AI review requested due to automatic review settings April 9, 2026 04:52
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds end-to-end aspire deploy support for Kubernetes by introducing a pluggable deployment-engine pipeline (defaulting to Helm), wiring container registry + OTLP/dashboard behavior into Kubernetes publishing, and expanding test coverage (unit snapshot tests + new CLI E2E tests).

Changes:

  • Introduces a Helm-based Kubernetes deployment engine that adds prepare/deploy/summary/uninstall pipeline steps and supports deploy-time resolution of parameters, secrets, cross-references, and container image registry prefixes.
  • Adds Kubernetes environment configuration APIs/annotations (namespace, release name, chart version), plus default dashboard generation and OTLP wiring.
  • Expands tests substantially: new Helm expression string-output coverage, updated Kubernetes publisher snapshots, and multiple new KinD+Helm CLI E2E tests.

Reviewed changes

Copilot reviewed 110 out of 110 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/Shared/Docker/Dockerfile.e2e Disables stabilized package versions for pack/bundle during E2E image builds.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ResourceWithProbes#00.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ResourceWithProbes#01.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ResourceWithProbes#02.verified.yaml Adds/updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#00.verified.yaml Updates expected generated Helm chart snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#01.verified.yaml Updates expected generated values snapshot (incl. dashboard + OTLP).
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#02.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#03.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#04.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#05.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#06.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#07.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#01.verified.yaml Updates expected generated values snapshot (dashboard config + TLS placeholders).
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#02.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#03.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#04.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#05.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#06.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#01.verified.yaml Updates expected generated values snapshot (dashboard config).
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#02.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#03.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#04.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#05.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#06.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#01.verified.yaml Updates expected generated values snapshot (dashboard config + OTLP + secrets placeholders).
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#02.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#03.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#04.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#05.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#06.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#07.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#08.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#09.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#10.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#00.verified.yaml Updates expected generated Helm chart snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#01.verified.yaml Updates expected generated values snapshot (dashboard config + OTLP).
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#02.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#03.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#04.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#05.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#06.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#07.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#08.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#01.verified.yaml Updates expected generated values snapshot (dashboard config + TLS placeholders).
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#02.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#03.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#04.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#05.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#06.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#01.verified.yaml Updates expected generated values snapshot (dashboard config + OTLP + placeholders).
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#02.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#03.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#04.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#05.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#06.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#07.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#08.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#09.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#00.verified.yaml Updates expected generated Helm chart snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#01.verified.yaml Updates expected generated values snapshot (dashboard config).
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#02.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#03.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#04.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#05.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#06.verified.yaml Updates expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#07.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#08.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#09.verified.yaml Adds expected generated Kubernetes YAML snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.PublishingKubernetesEnvironmentPublishesFile#01.verified.yaml Updates expected environment values snapshot to include dashboard config.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env1/values.verified.yaml Updates expected multi-env values snapshot (dashboard config + OTLP).
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env1/templates/ServiceA/config.verified.yaml Updates expected generated config template snapshot (string-safe numeric output + OTLP).
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env1/templates/env1-dashboard/config.verified.yaml Adds expected dashboard config template snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env1/templates/env1-dashboard/deployment.verified.yaml Adds expected dashboard deployment template snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env1/templates/env1-dashboard/service.verified.yaml Adds expected dashboard service template snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env2/values.verified.yaml Updates expected multi-env values snapshot (dashboard config + OTLP).
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env2/templates/ServiceB/config.verified.yaml Updates expected generated config template snapshot (string-safe numeric output + OTLP).
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env2/templates/env2-dashboard/config.verified.yaml Adds expected dashboard config template snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env2/templates/env2-dashboard/deployment.verified.yaml Adds expected dashboard deployment template snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env2/templates/env2-dashboard/service.verified.yaml Adds expected dashboard service template snapshot.
tests/Aspire.Hosting.Kubernetes.Tests/KubernetesPublisherTests.cs Updates publisher tests to include dashboard templates in expected files.
tests/Aspire.Hosting.Kubernetes.Tests/HelmExtensionsTests.cs Adds coverage for EnsureStringOutput / toString behavior.
tests/Aspire.Hosting.Kubernetes.Tests/Aspire.Hosting.Kubernetes.Tests.csproj Adds shared test helper compile include.
tests/Aspire.Cli.EndToEnd.Tests/KubernetesDeployBasicApiServiceTests.cs New KinD+Helm E2E test for basic Kubernetes deploy.
tests/Aspire.Cli.EndToEnd.Tests/KubernetesDeployWithRedisTests.cs New KinD+Helm E2E test validating Redis-backed app deploy.
tests/Aspire.Cli.EndToEnd.Tests/KubernetesDeployWithValkeyTests.cs New KinD+Helm E2E test validating Valkey-backed app deploy.
tests/Aspire.Cli.EndToEnd.Tests/KubernetesDeployWithGarnetTests.cs New KinD+Helm E2E test validating Garnet-backed app deploy.
tests/Aspire.Cli.EndToEnd.Tests/KubernetesDeployWithRabbitMQTests.cs New KinD+Helm E2E test validating RabbitMQ-backed app deploy.
tests/Aspire.Cli.EndToEnd.Tests/KubernetesDeployWithPostgresTests.cs New KinD+Helm E2E test validating PostgreSQL-backed app deploy.
tests/Aspire.Cli.EndToEnd.Tests/KubernetesDeployWithMySqlTests.cs New KinD+Helm E2E test validating MySQL-backed app deploy.
tests/Aspire.Cli.EndToEnd.Tests/KubernetesDeployWithSqlServerTests.cs New KinD+Helm E2E test validating SQL Server-backed app deploy.
tests/Aspire.Cli.EndToEnd.Tests/KubernetesDeployWithMongoDBTests.cs New KinD+Helm E2E test validating MongoDB-backed app deploy.
tests/Aspire.Cli.EndToEnd.Tests/KubernetesDeployWithNatsTests.cs New (quarantined) KinD+Helm E2E test validating NATS-backed app deploy.
tests/Aspire.Cli.EndToEnd.Tests/Helpers/KubernetesDeployTestHelpers.cs Adds shared terminal automation helpers for KinD+Helm based Kubernetes deploy E2E tests.
src/Aspire.Hosting.Kubernetes/Resources/RollingUpdateStatefulSetStrategyV1.cs Makes maxUnavailable nullable for StatefulSet rolling update strategy YAML.
src/Aspire.Hosting.Kubernetes/KubernetesResource.cs Improves Helm parameter handling (deferred resolution, values key mapping, image registry resolution) and removes https service discovery env vars.
src/Aspire.Hosting.Kubernetes/KubernetesPublishingContext.cs Includes dashboard in publish output and captures deploy-time resolution mappings/cross-references/image refs.
src/Aspire.Hosting.Kubernetes/KubernetesInfrastructure.cs Wires dashboard deployment target + container registry resolution + OTLP environment injection for Kubernetes environments.
src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs Adds dashboard support and deploy pipeline wiring (engine expansion + dependencies).
src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentExtensions.cs Adds default Helm engine, .WithHelm(...), and dashboard configuration APIs.
src/Aspire.Hosting.Kubernetes/KubernetesAspireDashboardResourceBuilderExtensions.cs Adds dashboard builder helpers and customization (host port, forwarded headers).
src/Aspire.Hosting.Kubernetes/KubernetesAspireDashboardResource.cs Adds a Kubernetes-specific dashboard resource type with endpoint references.
src/Aspire.Hosting.Kubernetes/HelmChartOptions.cs Adds fluent configuration options for namespace/release/chart version with validation.
src/Aspire.Hosting.Kubernetes/Extensions/ResourceExtensions.cs Ensures Helm numeric conversions render as strings via toString to keep ConfigMap/Secret values string-typed.
src/Aspire.Hosting.Kubernetes/Extensions/HelmExtensions.cs Adds EnsureStringOutput helper and exposes regex used for numeric conversions.
src/Aspire.Hosting.Kubernetes/Deployment/HelmDeploymentEngine.cs Introduces Helm deployment pipeline steps including prepare/deploy/summary/uninstall and deploy-time values override generation.
src/Aspire.Hosting.Kubernetes/Annotations/KubernetesNamespaceAnnotation.cs Adds annotation for namespace configuration via ReferenceExpression.
src/Aspire.Hosting.Kubernetes/Annotations/KubernetesDeploymentEngineAnnotation.cs Adds annotation-based extension point for pluggable deployment engines.
src/Aspire.Hosting.Kubernetes/Annotations/HelmReleaseNameAnnotation.cs Adds annotation for Helm release name via ReferenceExpression.
src/Aspire.Hosting.Kubernetes/Annotations/HelmChartVersionAnnotation.cs Adds annotation for Helm chart version via ReferenceExpression.

Comment thread src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentExtensions.cs
Comment thread src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

Copy link
Copy Markdown
Member

@IEvangelist IEvangelist left a comment

Choose a reason for hiding this comment

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

Left 3 inline comments covering the service summary lookup, Kubernetes dashboard host-port behavior, and deploy-time Helm validation.

Also, the KubernetesDeploymentEngineAnnotation feels pretty pipeline-internal for a public extension point, and WithHostPort (I know it's an existing API naming convention) but is a Docker-Compose-ish name for something that really needs clearer Kubernetes semantics.

Comment thread src/Aspire.Hosting.Kubernetes/Deployment/HelmDeploymentEngine.cs Outdated
Comment thread src/Aspire.Hosting.Kubernetes/Deployment/HelmDeploymentEngine.cs Outdated
@github-actions
Copy link
Copy Markdown
Contributor

Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

Comment thread src/Aspire.Hosting.Azure.Network/CompatibilitySuppressions.xml Outdated
Comment thread src/Aspire.Hosting.Kubernetes/Annotations/KubernetesDeploymentEngineAnnotation.cs Outdated
@github-actions
Copy link
Copy Markdown
Contributor

Re-running the failed jobs in the CI workflow for this pull request because 1 job was identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

@davidfowl
Copy link
Copy Markdown
Contributor

TypeScript AppHost support for Helm APIs

Tested the K8s deploy pipeline locally - works great! One gap I noticed:

The new Helm deployment APIs (WithHelm, WithNamespace, WithReleaseName, WithChartVersion) don't have TypeScript equivalents yet. The TS K8s API only exposes withProperties for chart-level settings, but the Helm deploy engine annotations aren't wired into the TypeScript code generation.

Would be great to add these as a follow-up so TypeScript AppHosts can also use the Helm deploy pipeline:

const kubernetes = await builder.addKubernetesEnvironment('env');
// These don't exist yet:
await kubernetes.withHelm(async (helm) => {
    await helm.withNamespace(helmNamespace);
    await helm.withReleaseName('my-release');
    await helm.withChartVersion('1.0.0');
});
await kubernetes.withContainerRegistry(registry);

@davidfowl
Copy link
Copy Markdown
Contributor

Testing Summary

Tested locally on Docker Desktop Kubernetes (2-node cluster, Helm 4.1.4).

What works well

  • Pipeline visualization is excellent -- step timeline with durations and dependency ordering is very clear
  • Error handling for missing Helm gives a clean, actionable message: 'helm' was not found. Please install 'helm' and ensure it is available on your PATH
  • Mixed workload deploy works great -- deployed .NET projects + a Node.js Express app (via AddJavaScriptApp) side by side, all 22/22 steps passed
  • Parameter persistence -- values saved to deployment state so re-deploys don't re-prompt
  • Post-deploy instructions are really helpful (dashboard port-forward, helm status, kubectl get all, uninstall)
  • Idempotent deploys via helm upgrade --install work as expected
  • Container build parallelism -- nodeapp, webfrontend, apiservice all build/push concurrently

Gap: TypeScript AppHost can't deploy to K8s

Tried creating a TypeScript AppHost with addKubernetesEnvironment + withContainerRegistry. The deploy pipeline runs but produces only 2 no-op steps (pipeline-execution + deploy). No build/push/helm steps are created.

The root cause: withHelm() is what registers the HelmDeploymentEngine which creates the pipeline steps. Since withHelm() has no TypeScript equivalent, TS AppHosts can't drive K8s deploys.

This is the main follow-up item -- exposing the Helm deployment engine APIs in the TypeScript codegen.

@davidfowl
Copy link
Copy Markdown
Contributor

Follow-up: WithHelm TypeScript codegen

WithHelm has [AspireExport]\ (line 82 of KubernetesEnvironmentExtensions.cs) but it doesn't appear in the generated TypeScript modules. The likely reason: \HelmChartOptions\ (the callback parameter type) is not marked with [AspireExport], so the codegen can't generate the TS equivalent for the \Action\ parameter.

Adding [AspireExport]\ to \HelmChartOptions\ and its methods (\WithNamespace, \WithReleaseName, \WithChartVersion) should unblock TS codegen.

@davidfowl
Copy link
Copy Markdown
Contributor

Port Mapping Verification

Deployed with multiple endpoint configurations and verified all port mappings are correct in the live K8s cluster:

Config Svc port Svc targetPort Container port
default http (null/null) 8080 (Helm param) 8080 8080
port:9001, target:null 9001 8001 (auto) 8001
port:null, target:9002 9002 9002 9002
port:9003, target:9004 9003 9004 9004
null/null (2nd) 8002 (auto) 8002 8002
env:PORT (nodeapp) 8000 8000 8000

Env var expressions also correct -- HTTP_PORTS lists all target ports, service references use Helm expressions.

Minor issue: endpoint name length validation

K8s port names must be <= 15 chars (IANA service name format). Names like 'custom-port-only' (16 chars) cause \helm upgrade --install\ to fail with:

\spec.template.spec.containers[0].ports[1].name: Invalid value: must be no more than 15 characters\

Consider validating endpoint names against this K8s constraint at publish time for a better error experience.

mitchdenny and others added 16 commits April 11, 2026 19:58
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The tests now run inside the Dockerfile.e2e container where the
CLI is pre-built from source (x.y.z-dev), instead of installing
from the PR shell script at runtime.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Docker container from Dockerfile.e2e does not include kubectl.
The bare runner had it pre-installed, but inside the container it
was missing, causing 'kubectl: command not found' at the ConfigMap
apply step.

Download kubectl alongside kind and helm in InstallKindAndHelmAsync
so the helper is self-contained regardless of environment.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When running inside a Docker container with socket forwarding,
KinD's kubeconfig uses 127.0.0.1:<port> (host loopback) which
is unreachable from inside the container. After cluster creation,
detect the Docker environment (/.dockerenv), join the 'kind'
network, and switch to the internal kubeconfig which uses Docker
DNS names instead of localhost.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two fixes for KubernetesDeploy E2E tests running in Docker containers:

1. Pre-install kind, helm, and kubectl in Dockerfile.e2e so they don't
   need to be downloaded at test runtime (~2-3 min saved per test).
   InstallKindAndHelmAsync now skips downloads when tools are on PATH.

2. After kind create cluster, detect Docker environment (/.dockerenv)
   and switch to KinD's internal kubeconfig + join the 'kind' network
   so kubectl can reach the API server via Docker DNS instead of the
   host's 127.0.0.1 loopback (unreachable from inside a container).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Docker container approach (CreateDockerTestTerminal) doesn't
work for K8s deploy tests because aspire deploy uses docker buildx
with a docker-container driver, which can't access the test
container's filesystem for build context. Revert to CreateTestTerminal
which runs directly on the GitHub Actions runner.

Keep the kubectl install improvement in InstallKindAndHelmAsync
(skips download if already on PATH via command -v).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix XML doc: move <example> out of <remarks> to top-level sibling
- Fix GetSteps lookup: print-summary steps are owned by the
  environment resource, not the deployment target
- Add post-deploy instructions step with dashboard port-forward
  command, helm status/get-all, and helm uninstall commands
- Revert KubernetesDeploy tests to bare-terminal (Docker container
  approach incompatible with docker buildx image builds)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The release name now defaults to IHostEnvironment.EnvironmentName
(from aspire deploy -e <name>) instead of the resource name from
AddKubernetesEnvironment(). This matches user expectations since
the -e flag is the primary way to differentiate deployments.

Also refactored namespace/release resolution into shared helpers
to eliminate duplication across HelmDeploy, PrintInstructions,
and HelmUninstall methods.

Improved summary presentation: split instructions into separate
concise key-value entries matching the established pattern used
by ACA and Docker Compose (emoji keys, inline markdown values).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix service name mismatch in PrintResourceSummaryAsync: use
  ToServiceName() so kubectl queries match the deployed service name
- Throw on helm deploy failure so the pipeline step fails and
  dependent print-summary steps do not run
- Use MatchEvaluator in Regex.Replace for Chart.yaml version to
  prevent regex backreference corruption from parameter-backed values
- Validate resolved release name and namespace at deploy time using
  DNS label rules, catching invalid environment names early
- Add ServicePort to EndpointMapping so WithHostPort flows the
  exposed port into the Kubernetes Service manifest Port field
  while keeping TargetPort as the container port
- Add missing ArgumentNullException.ThrowIfNull in WithHostPort

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
WithHostPort implied Docker-style host port mapping, which doesn't
apply in Kubernetes where Services are cluster-internal by default.

- Rename WithHostPort → WithServicePort with docs explaining it
  sets the Kubernetes Service port (useful behind ingress)
- Add WithOtlpServicePort(grpcPort, httpPort) for customizing the
  OTLP endpoint Service ports (e.g. standard 4317/4318)
- Re-add ServicePort plumbing in EndpointMapping so the APIs
  flow ExposedPort into the Service manifest Port field while
  keeping TargetPort as the container port

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address Eric's review feedback: eliminate the custom annotation type
and align with how Docker Compose handles deployment engines.

- Delete KubernetesDeploymentEngineAnnotation — replaced by a
  DeploymentEngineStepsFactory delegate property on
  KubernetesEnvironmentResource
- Move per-resource print-summary steps to KubernetesResource
  deployment targets via PipelineStepAnnotation (matching the
  DockerComposeServiceResource pattern)
- Merge the two PipelineStepAnnotations on the environment resource
  into a single one that handles publish + engine + deployment target
  step expansion (matching DockerComposeEnvironmentResource pattern)
- WithHelm() and EnsureDefaultHelmEngine() now set the delegate
  property instead of adding/replacing an annotation

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

GetSteps was looking for print-summary steps on the environment
resource (this), but they now live on the deployment target
(KubernetesResource). Updated to match the Docker Compose pattern
which uses context.GetSteps(deploymentTarget, 'print-summary').

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add [AspireExport(ExposeMethods = true)] to HelmChartOptions so
  WithNamespace, WithReleaseName, WithChartVersion are visible from
  TypeScript callbacks in withHelm()
- Add ASPIREEXPORT012 analyzer rule that warns when callback parameter
  types (Action<T>) in [AspireExport] methods lack [AspireExport] on
  the context type, scoped to same-assembly types only
- Add [AspireExport(ExposeProperties = true)] to ResourceUrlAnnotation
  (pre-existing gap caught by the new analyzer rule)
- Add KubernetesDeployTypeScriptTests E2E test that creates a project
  from the Express/React starter template, adds Aspire.Hosting.Kubernetes,
  modifies apphost.ts with addKubernetesEnvironment + withHelm callback,
  and deploys to a KinD cluster

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mitchdenny and others added 5 commits April 11, 2026 20:41
- ASPIREEXPORT012: Skip callback context types with no public
  instance methods/properties (fixes false positive on empty
  TestContext in analyzer unit tests)
- ASPIREEXPORT012: Scope to same-assembly types only (fixes
  false positives on external ContainerApp/ContainerAppJob types)
- Replace Semver NuGet package with GeneratedRegex for chart
  version validation (package reference was lost during rebase)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Revert [AspireExport] on ResourceUrlAnnotation (caused snapshot
  mismatches across all codegen tests — out of scope for this PR)
- ASPIREEXPORT012: Also skip types with [AspireDto] (they're already
  exported for serialization, not callback contexts)
- Use shared SmolSemVer (SemVersion.TryParse) instead of regex for
  chart version validation — the type is already available from
  Aspire.Hosting via project reference

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

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The test was running inside a Docker container, but KinD creates
clusters on the host via the mounted Docker socket. This caused
kubectl to fail connecting to the API server (localhost inside the
container != localhost on the host). Switched to CreateTestTerminal()
matching the pattern used by all other K8s deploy E2E tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Parameters must be created with addParameter() before being passed
into the withHelm callback. Also use separate await for
addKubernetesEnvironment and withHelm since the fluent chain
crosses async boundaries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

🎬 CLI E2E Test Recordings — 68 recordings uploaded (commit c6897c7)

View recordings
Test Recording
AddPackageInteractiveWhileAppHostRunningDetached ▶️ View Recording
AddPackageWhileAppHostRunningDetached ▶️ View Recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View Recording
AgentInitCommand_DefaultSelection_InstallsSkillOnly ▶️ View Recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View Recording
AllPublishMethodsBuildDockerImages ▶️ View Recording
AspireAddPackageVersionToDirectoryPackagesProps ▶️ View Recording
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps ▶️ View Recording
Banner_DisplayedOnFirstRun ▶️ View Recording
Banner_DisplayedWithExplicitFlag ▶️ View Recording
Banner_NotDisplayedWithNoLogoFlag ▶️ View Recording
CertificatesClean_RemovesCertificates ▶️ View Recording
CertificatesTrust_WithNoCert_CreatesAndTrustsCertificate ▶️ View Recording
CertificatesTrust_WithUntrustedCert_TrustsCertificate ▶️ View Recording
ConfigSetGet_CreatesNestedJsonFormat ▶️ View Recording
CreateAndRunAspireStarterProject ▶️ View Recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View Recording
CreateAndRunEmptyAppHostProject ▶️ View Recording
CreateAndRunJavaEmptyAppHostProject ▶️ View Recording
CreateAndRunJsReactProject ▶️ View Recording
CreateAndRunPythonReactProject ▶️ View Recording
CreateAndRunTypeScriptEmptyAppHostProject ▶️ View Recording
CreateAndRunTypeScriptStarterProject ▶️ View Recording
CreateJavaAppHostWithViteApp ▶️ View Recording
CreateStartAndStopAspireProject ▶️ View Recording
CreateTypeScriptAppHostWithViteApp ▶️ View Recording
DashboardRunWithOtelTracesReturnsNoTraces ▶️ View Recording
DeployK8sBasicApiService ▶️ View Recording
DeployK8sWithGarnet ▶️ View Recording
DeployK8sWithMongoDB ▶️ View Recording
DeployK8sWithMySql ▶️ View Recording
DeployK8sWithPostgres ▶️ View Recording
DeployK8sWithRabbitMQ ▶️ View Recording
DeployK8sWithRedis ▶️ View Recording
DeployK8sWithSqlServer ▶️ View Recording
DeployK8sWithValkey ▶️ View Recording
DeployTypeScriptAppToKubernetes ▶️ View Recording
DescribeCommandResolvesReplicaNames ▶️ View Recording
DescribeCommandShowsRunningResources ▶️ View Recording
DetachFormatJsonProducesValidJson ▶️ View Recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View Recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View Recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View Recording
GlobalMigration_HandlesCommentsAndTrailingCommas ▶️ View Recording
GlobalMigration_HandlesMalformedLegacyJson ▶️ View Recording
GlobalMigration_PreservesAllValueTypes ▶️ View Recording
GlobalMigration_SkipsWhenNewConfigExists ▶️ View Recording
GlobalSettings_MigratedFromLegacyFormat ▶️ View Recording
InitTypeScriptAppHost_AugmentsExistingViteRepoAtRoot ▶️ View Recording
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View Recording
LegacySettingsMigration_AdjustsRelativeAppHostPath ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View Recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View Recording
RestoreGeneratesSdkFiles ▶️ View Recording
RestoreSupportsConfigOnlyHelperPackageAndCrossPackageTypes ▶️ View Recording
RunFromParentDirectory_UsesExistingConfigNearAppHost ▶️ View Recording
SecretCrudOnDotNetAppHost ▶️ View Recording
SecretCrudOnTypeScriptAppHost ▶️ View Recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View Recording
StartAndWaitForTypeScriptSqlServerAppHostWithNativeAssets ▶️ View Recording
StopAllAppHostsFromAppHostDirectory ▶️ View Recording
StopAllAppHostsFromUnrelatedDirectory ▶️ View Recording
StopNonInteractiveMultipleAppHostsShowsError ▶️ View Recording
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording
UnAwaitedChainsCompileWithAutoResolvePromises ▶️ View Recording

📹 Recordings uploaded automatically from CI run #24281920579

/// Substitutes Helm value expressions (e.g., <c>{{ .Values.secrets.cache.password }}</c>) in a template
/// string with resolved values from the lookup dictionary.
/// </summary>
internal static string ResolveHelmExpressions(string template, Dictionary<string, string> resolvedLookup)
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.

Why do we do this resolution and not helm itself?

Copy link
Copy Markdown
Contributor

@davidfowl davidfowl left a comment

Choose a reason for hiding this comment

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

Small nits for follow ups. HUGE steps forward now lets polish and clean up!

@davidfowl davidfowl merged commit 0d361ca into main Apr 11, 2026
552 of 555 checks passed
Copy link
Copy Markdown
Member

@JamesNK JamesNK left a comment

Choose a reason for hiding this comment

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

Post-merge review: 5 issues found (1 correctness, 1 potential bug, 1 reproducibility, 1 maintainability, 1 observability).

var dashboardServiceName = environment.Dashboard.Resource.Name.ToKubernetesResourceName() + "-service";
context.Summary.Add(
"📊 Dashboard",
new MarkdownString($"`kubectl port-forward -n {@namespace} svc/{dashboardServiceName} 18888:18888` then open [http://localhost:18888](http://localhost:18888)"));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The port-forward instruction hard-codes 18888:18888, but the Service port is customizable via WithServicePort(). If someone calls .WithDashboard(d => d.WithServicePort(80)), the generated kubectl port-forward instruction will reference the wrong Service port. Consider resolving the actual service port from the dashboard endpoint configuration.

{
await uninstallTask.CompleteAsync(
new MarkdownString($"Helm release **{releaseName}** uninstalled from namespace **{@namespace}**"),
CompletionState.Completed,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When helm uninstall exits with a non-zero code, this calls FailAsync() but does not throw — so the pipeline step's Action delegate returns normally and the pipeline treats the step as successful. Compare with HelmDeployAsync (line ~437) which throws InvalidOperationException on failure. The catch block at line 571 also rethrows, creating an inconsistency: exceptions from helm uninstall propagate but non-zero exit codes don't.

If this is intentional best-effort teardown, a comment clarifying that would help. If not, add a throw after the FailAsync call.

var resource = new KubernetesAspireDashboardResource(name);

return builder.CreateResourceBuilder(resource)
.WithImage("mcr.microsoft.com/dotnet/nightly/aspire-dashboard")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

.WithImage("mcr.microsoft.com/dotnet/nightly/aspire-dashboard") has no tag, which means deploys pull whatever latest points to from the nightly feed. This makes Kubernetes deployments non-reproducible and ties production Helm charts to a nightly image. A pinned tag matching the Aspire SDK version would be more appropriate for generated Helm charts.

}

[GeneratedRegex("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$")]
private static partial Regex DnsLabelPattern();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The DnsLabelPattern() generated regex is duplicated here and in HelmDeploymentEngine.cs (line 108). If one is updated and the other isn't, build-time validation (in HelmChartOptions) and deploy-time validation (in HelmDeploymentEngine) would diverge. Consider extracting to a shared helper.

if (resolvedValue is null)
{
continue;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If captured.Parameter.GetValueAsync() returns null, the entry is silently skipped with no warning. A required parameter that has no value at deploy time will produce an incomplete override file, leading to cryptic Helm deployment failures (e.g., empty secrets). Consider logging a warning here so the root cause is easier to diagnose.

@joperezr joperezr added this to the 13.3 milestone Apr 14, 2026
radical pushed a commit that referenced this pull request Apr 14, 2026
* Add Kubernetes deploy pipeline with Helm engine

Implement end-to-end aspire deploy support for Kubernetes environments
using an annotation-driven deployment engine architecture with Helm as
the default engine.

New features:
- KubernetesDeploymentEngineAnnotation for pluggable deployment engines
- HelmDeploymentEngine with pipeline steps: prepare, helm-deploy,
  print-summary, and helm-uninstall
- HelmChartConfiguration builder with dual overloads (string + parameter)
  for namespace, release name, and chart version
- WithHelm() extension method on KubernetesEnvironmentResource
- Container registry wiring in KubernetesInfrastructure
- Pipeline step dependency wiring (build → push → prepare → deploy → summary)

Design decisions:
- Uses helm upgrade --install for idempotent deployments
- Config annotations use ReferenceExpression for both literal and
  parameter-backed values (enabling deploy-time prompting)
- Helm is the default engine added by AddKubernetesEnvironment()
- WithAnnotation Replace behavior prevents annotation stacking

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

* Fix: remove unused using directive in KubernetesDeployTests

Remove 'using Aspire.TestUtilities' which is unnecessary since
TestTempDirectory is in the global namespace. This was causing
IDE0005 build errors in CI where warnings are treated as errors.

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

* Fix values.yaml secret/parameter population during deploy

During aspire publish, secrets and parameters without defaults are written
as empty placeholders in values.yaml (correct for distributable charts).
During aspire deploy, these need actual resolved values.

Changes:
- KubernetesResource.AllocateParameter: Always store ParameterResource
  reference (previously dropped for secrets and params without defaults)
- KubernetesPublishingContext: Accept environment resource, capture
  secret/unresolved parameter mappings to CapturedHelmValues during publish
  while preserving empty placeholders in values.yaml
- KubernetesEnvironmentResource: Add CapturedHelmValues list to store
  parameter-to-values.yaml mappings between publish and deploy steps
- HelmDeploymentEngine.PrepareAsync: Resolve captured values via
  GetValueAsync() and write values-deploy.yaml override file
- HelmDeploymentEngine.HelmDeployAsync: Pass -f values-deploy.yaml after
  -f values.yaml so resolved secrets take precedence via Helm merge

This mirrors Docker Compose's CapturedEnvironmentVariables pattern:
capture parameter references during publish, resolve during deploy.

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

* Fix cross-resource secret references and rename deploy values file

Cross-resource secret references (e.g., server referencing cache's
password) were entirely skipped from values.yaml because their Value
string contained Helm expressions ({{ .Values.secrets.cache.password }}).
This caused 'nil pointer' errors in Helm templates because the
secrets.server section was completely missing from values.yaml.

Changes:
- AddValuesToHelmSectionAsync: entries with ValueContainsHelmExpression
  now write empty placeholders instead of being skipped, and capture
  the template string as CapturedHelmCrossReference for deploy-time
  resolution
- KubernetesEnvironmentResource: add CapturedHelmCrossReference record
  and CapturedHelmCrossReferences list
- HelmDeploymentEngine: two-phase resolution at deploy time:
  1) resolve direct ParameterResource values
  2) substitute Helm expressions in cross-reference templates with
     resolved values from phase 1
- Rename override file from values-deploy.yaml to values.{envName}.yaml
  to mirror Docker Compose's .env.{envName} naming convention
- Add ResolveHelmExpressions() with regex-based substitution
- 9 new tests: cross-reference resolution, expression substitution,
  file naming, unresolved expression preservation
- Updated 7 snapshots reflecting new empty placeholder entries

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

* Resolve container image references with registry prefix at deploy time

During publish, Kubernetes image parameters are written as 'server:latest'
to values.yaml. At deploy time, we now resolve the full registry-prefixed
image name (e.g., 'myregistry.azurecr.io/myrepo/server:latest') using the
same ContainerImageReference pattern as Docker Compose.

Changes:
- Added ImageResource property to HelmValue for tracking the source resource
- GetContainerImageName sets ImageResource on project/Dockerfile resources
- KubernetesPublishingContext captures CapturedHelmImageReferences during publish
- HelmDeploymentEngine Phase 3 resolves image references via ContainerImageReference
- Added CapturedHelmImageReference record to KubernetesEnvironmentResource
- 5 new tests covering image capture, resolution, and registry prefix

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

* Fix values.yaml key mismatch for parameter-backed secrets

When a container resource has an environment variable (e.g., REDIS_PASSWORD)
backed by a ParameterResource (e.g., cache-password), the Helm template
expression references the parameter name (cache_password) but values.yaml
was using the env var name (REDIS_PASSWORD). This caused nil pointer errors
at deploy time because Helm couldn't find the expected key.

Added HelmValue.ValuesKey property that preserves the parameter's formatted
name from AllocateParameter. AddValuesToHelmSectionAsync now uses ValuesKey
when present, ensuring values.yaml keys match the Helm expression paths.

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

* Add cross-resource secret resolution tests

Verify that the ValuesKey fix correctly resolves the key mismatch
between parameter names and environment variable names. Tests cover:
- values.yaml keys match Helm template expression paths
- Phase 1 + Phase 2 override file resolution produces fully resolved values
- Full E2E publish-then-resolve flow with shared secret parameter

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

* Fix ReferenceExpression {0} passthrough losing HelmValue metadata

When Redis provides its password via WithReference, it wraps the
ParameterResource in a ReferenceExpression($"{PasswordParameter}").
The {0} passthrough optimization in ProcessValueAsync was calling
.ToString() on the inner result, converting the HelmValue (with
ValuesKey="cache_password") to a plain string. This lost the
ValuesKey, causing the values.yaml key to fall back to the env var
name (CACHE_PASSWORD) instead of the parameter name (cache_password),
which didn't match the Helm template reference.

Fix: preserve the inner object when at the top level (embedded=false),
only convert to string when embedded in a larger format string. This
also correctly preserves integer type information for port parameters,
which now render as {{ .Values.parameters.X.port_http | int }} instead
of being quoted.

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

* Fix ConfigMap integer values: use toString instead of int pipe

Kubernetes ConfigMap data values must be strings. When the {0}
passthrough fix preserves HelmValue type information, port parameters
render with '| int' pipe which produces a number in the ConfigMap
template. K8s rejects this with 'cannot unmarshal number into Go
struct field ConfigMap.data of type string'.

Fix: in ToConfigMap, replace '| int' (and other numeric pipes) with
'| toString' so the value stays a string in the ConfigMap context.
The '| int' pipe is still used correctly in Service/Deployment specs
where numeric values are expected.

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

* Add Aspire Dashboard support to Kubernetes environment

- Create KubernetesAspireDashboardResource with HTTP (18888), OTLP gRPC (18889), and OTLP HTTP (18890) endpoints
- Create KubernetesAspireDashboardResourceBuilderExtensions with CreateDashboard, WithHostPort, and WithForwardedHeaders
- Add DashboardEnabled property and Dashboard resource to KubernetesEnvironmentResource (enabled by default)
- Add WithDashboard(bool) and WithDashboard(Action<>) extension methods to KubernetesEnvironmentExtensions
- Wire ConfigureOtlp in KubernetesInfrastructure: sets OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_PROTOCOL, and OTEL_SERVICE_NAME on resources with OtlpExporterAnnotation
- Dashboard deployed in unsecured auth mode (DASHBOARD__FRONTEND__AUTHMODE=Unsecured, DASHBOARD__OTLP__AUTHMODE=Unsecured)
- Include dashboard resource in pipeline steps and configuration for build/deploy coordination
- Add KnownOtelConfigNames shared file to K8s project
- Add 6 new tests covering dashboard creation, OTLP configuration, dashboard disable, endpoint verification
- Update 13 snapshot files to include OTLP env vars in generated K8s manifests
- All 72 tests pass

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

* Fix CS0436 build error: remove redundant KnownOtelConfigNames shared file

KnownOtelConfigNames is already accessible from Aspire.Hosting via
InternalsVisibleTo. Including the shared file copy caused a type conflict
that was promoted to error in Release builds (TreatWarningsAsErrors).

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

* Remove HTTPS service discovery variables in Kubernetes publishing

Containers in Kubernetes don't have TLS certificates - TLS termination
is handled externally by ingress controllers or service mesh. This mirrors
the Docker Compose behavior where RemoveHttpsServiceDiscoveryVariables
filters out services__*__https__* entries after environment callbacks run.

Without this fix, the Blazor webfrontend tries to connect to apiservice
via https://apiservice-service:8080 but apiservice only listens on HTTP,
causing connection failures.

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

* Include dashboard resource in Kubernetes publish output

The KubernetesPublishingContext only iterated model.Resources, which
doesn't include the dashboard resource. This meant OTLP env vars
pointed to env-dashboard-service but no dashboard deployment/service
was generated. Mirror the Docker Compose pattern: prepend the dashboard
resource to the iteration when DashboardEnabled is true.

Updated all publisher test expectedFiles arrays to include dashboard
templates and regenerated snapshots.

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

* Add Kubernetes deploy E2E tests (baseline, Redis, Postgres)

Add E2E CLI tests that exercise 'aspire deploy' to KinD clusters:
- KubernetesDeployTestHelpers: shared helpers for KinD setup, project
  scaffolding, interactive deploy, and deployment verification
- DeployBasicApiService: baseline test with a plain API endpoint
- DeployWithRedis: Redis SET+GET verification via /test-deployment
- DeployWithPostgres: PostgreSQL SELECT 1 verification

Tests use Hex1b terminal automation to answer parameter prompts
interactively (registryendpoint, namespace, chartversion) and verify
deployments via port-forwarded /test-deployment endpoints.

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

* Add remaining K8s deploy E2E tests (RabbitMQ, MongoDB, MySQL, SQL Server, Garnet, Valkey, NATS)

Complete the K8s deploy E2E test suite with 7 additional resource tests:
- DeployWithRabbitMQ: queue declare+delete via RabbitMQ.Client
- DeployWithMongoDB: insert+find document via MongoDB.Driver
- DeployWithMySql: SELECT 1 via MySqlConnector
- DeployWithSqlServer: SELECT 1 via Microsoft.Data.SqlClient
- DeployWithGarnet: SET+GET via StackExchange.Redis (Redis-compatible)
- DeployWithValkey: SET+GET via StackExchange.Redis (Redis-compatible)
- DeployWithNats: connection state check via NATS.Client.Core

Total: 10 E2E tests covering the full aspire deploy → KinD workflow
with real /test-deployment endpoints performing actual operations.

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

* Remove quarantine from K8s deploy E2E tests

These tests should run in normal CI, not be quarantined.

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

* Fix KinD cluster creation for containerd v2

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

* Split K8s deploy tests into one class per file for CI parallelization

Each test class becomes a separate CI job via SplitTestsOnCI=true.

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

* Use aspire new + aspire add for project scaffolding in E2E tests

Hardcoded SDK version 10.0.0-dev doesn't match the PR build's CLI.
Switch to aspire new (empty apphost) + aspire add for hosting packages
+ dotnet new web for ApiService, so SDK version is automatically correct.

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

* Use Starter template instead of EmptyAppHost for K8s deploy E2E tests

The EmptyAppHost template doesn't create a solution file, ApiService project, or
project references. Switch to the Starter template (no Redis) which provides the
full project structure (AppHost + ApiService + ServiceDefaults + solution) out of
the box. This eliminates the need for dotnet new web, dotnet sln add, and
dotnet add reference commands — we just aspire add hosting packages, add client
NuGet packages, and inject our custom code into the existing source files.

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

* Add pragma to suppress ASPIRECOMPUTE003 experimental diagnostic in K8s deploy tests

AddContainerRegistry is marked [Experimental('ASPIRECOMPUTE003')] and
TreatWarningsAsErrors promotes it to a build error. Add #pragma warning disable
at the top of the generated AppHost.cs code in all 10 test files.

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

* Keep ASPIRE_PLAYGROUND set so deploy prompts for parameters

Unsetting ASPIRE_PLAYGROUND caused aspire deploy to run non-interactively,
skipping parameter prompts entirely. The pipeline then failed because
parameters like chartversion, namespace, and registryendpoint had no values.
Leave ASPIRE_PLAYGROUND set so the interactive prompts appear.

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

* Fix parameter prompt order: match code declaration order not alphabetical

The deploy command prompts for parameters in the order they are declared
in AppHost.cs (registryendpoint, namespace, chartversion), not alphabetical.
The tests were waiting for chartversion first which never appeared, causing
a timeout. Reorder all 10 test files to match the actual prompt order.

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

* Add --prerelease flag to dotnet add package for client packages

In CI, the PR NuGet feed only has prerelease versions of Aspire client
packages (e.g. 13.3.0-pr.15723.*). Without --prerelease, dotnet add
package fails with NU1103 because it only looks for stable versions.

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

* Fix StatefulSet maxUnavailable: 0 bug, fix RabbitMQ async API, quarantine NATS

- Make MaxUnavailable nullable (int?) in RollingUpdateStatefulSetStrategyV1
  so it's omitted from YAML when not set (Kubernetes rejects 0)
- Fix RabbitMQ test: CreateChannel -> CreateChannelAsync (7.x API change)
- Quarantine NATS K8s deploy test: NATS hosting passes ParameterResource
  as container args which K8s publishing can't resolve (#15789)

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

* Increase verification retries for database containers

Database containers (Postgres, MySQL, SQL Server) need 60-120s to fully
initialize in CI. Increase retry loop from 10x5s to 30x5s (150s total)
and add HTTP status code to curl output for diagnostics.

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

* Add pod logs and env diagnostics to verification step

Show server pod logs and connection-related environment variables
before attempting port-forward + curl. This will help diagnose
why Postgres/MySQL/SQL Server return HTTP 500.

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

* Use server-level references for Postgres/MySQL/SQL Server K8s tests

AddDatabase() databases are not created in K8s deploy mode because
the ResourceReadyEvent CREATE DATABASE callbacks only fire during
Aspire orchestration, not in deployed Helm charts (#15795).

Work around by referencing the server directly (uses default database)
instead of AddDatabase(). This still validates the full deploy pipeline.

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

* Fix MySQL SELECT 1 type comparison (long vs int)

MySQL's ExecuteScalar returns a long, not int, for SELECT 1.
Use Convert.ToInt32 for all database tests to handle any numeric type.

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

* Rename K8s test methods to DeployK8s* and add stabilization override

Rename all Kubernetes deploy E2E test methods from DeployXYZ to
DeployK8sXYZ to make them obviously Kubernetes-related in CI.

Add /p:StabilizePackageVersion=false to Dockerfile.e2e Stage 1
build to ensure consistent -dev suffixed packages regardless of
whether the branch is stabilized for release.

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

* Fix ASPIREEXPORT011: remove redundant export IDs

The convention-derived names match the explicit IDs, so remove
the redundant first argument from AspireExport attributes.

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

* Add retry logic for KinD/Helm downloads to handle transient CDN failures

The SQL Server test failed because GitHub CDN returned HTML instead of the
KinD binary. Added a retry loop (3 attempts with ELF validation) for both
KinD and Helm downloads.

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

* Add version assertions to K8s deploy E2E tests

Assert CLI version has prerelease suffix (aspire --version) and
template version has prerelease suffix (Using project templates version:)
in every K8s deploy test. Runs in both CI and local environments.

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

* Address Kubernetes review feedback

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

* Switch KubernetesDeploy E2E tests to Docker container terminal

The tests now run inside the Dockerfile.e2e container where the
CLI is pre-built from source (x.y.z-dev), instead of installing
from the PR shell script at runtime.

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

* Add kubectl install to KubernetesDeployTestHelpers

The Docker container from Dockerfile.e2e does not include kubectl.
The bare runner had it pre-installed, but inside the container it
was missing, causing 'kubectl: command not found' at the ConfigMap
apply step.

Download kubectl alongside kind and helm in InstallKindAndHelmAsync
so the helper is self-contained regardless of environment.

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

* Fix kubectl networking inside Docker container

When running inside a Docker container with socket forwarding,
KinD's kubeconfig uses 127.0.0.1:<port> (host loopback) which
is unreachable from inside the container. After cluster creation,
detect the Docker environment (/.dockerenv), join the 'kind'
network, and switch to the internal kubeconfig which uses Docker
DNS names instead of localhost.

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

* Pre-install K8s tools in Dockerfile.e2e and fix API server networking

Two fixes for KubernetesDeploy E2E tests running in Docker containers:

1. Pre-install kind, helm, and kubectl in Dockerfile.e2e so they don't
   need to be downloaded at test runtime (~2-3 min saved per test).
   InstallKindAndHelmAsync now skips downloads when tools are on PATH.

2. After kind create cluster, detect Docker environment (/.dockerenv)
   and switch to KinD's internal kubeconfig + join the 'kind' network
   so kubectl can reach the API server via Docker DNS instead of the
   host's 127.0.0.1 loopback (unreachable from inside a container).

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

* Revert KubernetesDeploy tests to bare-terminal approach

The Docker container approach (CreateDockerTestTerminal) doesn't
work for K8s deploy tests because aspire deploy uses docker buildx
with a docker-container driver, which can't access the test
container's filesystem for build context. Revert to CreateTestTerminal
which runs directly on the GitHub Actions runner.

Keep the kubectl install improvement in InstallKindAndHelmAsync
(skips download if already on PATH via command -v).

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

* Trigger CI rebuild

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

* Address review feedback and add deployment instructions

- Fix XML doc: move <example> out of <remarks> to top-level sibling
- Fix GetSteps lookup: print-summary steps are owned by the
  environment resource, not the deployment target
- Add post-deploy instructions step with dashboard port-forward
  command, helm status/get-all, and helm uninstall commands
- Revert KubernetesDeploy tests to bare-terminal (Docker container
  approach incompatible with docker buildx image builds)

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

* Default Helm release name to deployment environment name

The release name now defaults to IHostEnvironment.EnvironmentName
(from aspire deploy -e <name>) instead of the resource name from
AddKubernetesEnvironment(). This matches user expectations since
the -e flag is the primary way to differentiate deployments.

Also refactored namespace/release resolution into shared helpers
to eliminate duplication across HelmDeploy, PrintInstructions,
and HelmUninstall methods.

Improved summary presentation: split instructions into separate
concise key-value entries matching the established pattern used
by ACA and Docker Compose (emoji keys, inline markdown values).

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

* Address review feedback: fix deploy issues and wire WithHostPort

- Fix service name mismatch in PrintResourceSummaryAsync: use
  ToServiceName() so kubectl queries match the deployed service name
- Throw on helm deploy failure so the pipeline step fails and
  dependent print-summary steps do not run
- Use MatchEvaluator in Regex.Replace for Chart.yaml version to
  prevent regex backreference corruption from parameter-backed values
- Validate resolved release name and namespace at deploy time using
  DNS label rules, catching invalid environment names early
- Add ServicePort to EndpointMapping so WithHostPort flows the
  exposed port into the Kubernetes Service manifest Port field
  while keeping TargetPort as the container port
- Add missing ArgumentNullException.ThrowIfNull in WithHostPort

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

* Trigger CI rebuild

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

* Rename WithHostPort to WithServicePort and add WithOtlpServicePort

WithHostPort implied Docker-style host port mapping, which doesn't
apply in Kubernetes where Services are cluster-internal by default.

- Rename WithHostPort → WithServicePort with docs explaining it
  sets the Kubernetes Service port (useful behind ingress)
- Add WithOtlpServicePort(grpcPort, httpPort) for customizing the
  OTLP endpoint Service ports (e.g. standard 4317/4318)
- Re-add ServicePort plumbing in EndpointMapping so the APIs
  flow ExposedPort into the Service manifest Port field while
  keeping TargetPort as the container port

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

* Remove compat suppression file (fix is now in main)

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

* Replace KubernetesDeploymentEngineAnnotation with delegate property

Address Eric's review feedback: eliminate the custom annotation type
and align with how Docker Compose handles deployment engines.

- Delete KubernetesDeploymentEngineAnnotation — replaced by a
  DeploymentEngineStepsFactory delegate property on
  KubernetesEnvironmentResource
- Move per-resource print-summary steps to KubernetesResource
  deployment targets via PipelineStepAnnotation (matching the
  DockerComposeServiceResource pattern)
- Merge the two PipelineStepAnnotations on the environment resource
  into a single one that handles publish + engine + deployment target
  step expansion (matching DockerComposeEnvironmentResource pattern)
- WithHelm() and EnsureDefaultHelmEngine() now set the delegate
  property instead of adding/replacing an annotation

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

* Fix print-summary step dependency wiring after move to deployment targets

GetSteps was looking for print-summary steps on the environment
resource (this), but they now live on the deployment target
(KubernetesResource). Updated to match the Docker Compose pattern
which uses context.GetSteps(deploymentTarget, 'print-summary').

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

* Add TypeScript AppHost support for K8s deploy and enhance ATS analyzer

- Add [AspireExport(ExposeMethods = true)] to HelmChartOptions so
  WithNamespace, WithReleaseName, WithChartVersion are visible from
  TypeScript callbacks in withHelm()
- Add ASPIREEXPORT012 analyzer rule that warns when callback parameter
  types (Action<T>) in [AspireExport] methods lack [AspireExport] on
  the context type, scoped to same-assembly types only
- Add [AspireExport(ExposeProperties = true)] to ResourceUrlAnnotation
  (pre-existing gap caught by the new analyzer rule)
- Add KubernetesDeployTypeScriptTests E2E test that creates a project
  from the Express/React starter template, adds Aspire.Hosting.Kubernetes,
  modifies apphost.ts with addKubernetesEnvironment + withHelm callback,
  and deploys to a KinD cluster

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

* Fix analyzer and build issues from CI failures

- ASPIREEXPORT012: Skip callback context types with no public
  instance methods/properties (fixes false positive on empty
  TestContext in analyzer unit tests)
- ASPIREEXPORT012: Scope to same-assembly types only (fixes
  false positives on external ContainerApp/ContainerAppJob types)
- Replace Semver NuGet package with GeneratedRegex for chart
  version validation (package reference was lost during rebase)

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

* Fix CI codegen failures and use SmolSemVer for chart validation

- Revert [AspireExport] on ResourceUrlAnnotation (caused snapshot
  mismatches across all codegen tests — out of scope for this PR)
- ASPIREEXPORT012: Also skip types with [AspireDto] (they're already
  exported for serialization, not callback contexts)
- Use shared SmolSemVer (SemVersion.TryParse) instead of regex for
  chart version validation — the type is already available from
  Aspire.Hosting via project reference

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

* Restore CompatibilitySuppressions.xml from main (needed for package validation)

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

* Fix TypeScript K8s deploy test: use bare terminal instead of Docker

The test was running inside a Docker container, but KinD creates
clusters on the host via the mounted Docker socket. This caused
kubectl to fail connecting to the API server (localhost inside the
container != localhost on the host). Switched to CreateTestTerminal()
matching the pattern used by all other K8s deploy E2E tests.

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

* Fix TypeScript K8s test: register parameters before withHelm callback

Parameters must be created with addParameter() before being passed
into the withHelm callback. Also use separate await for
addKubernetesEnvironment and withHelm since the fluent chain
crosses async boundaries.

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

---------

Co-authored-by: Mitch Denny <mitch@mitchdeny.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants