Skip to content

Add Kubernetes deploy pipeline with Helm engine#15723

Open
mitchdenny wants to merge 42 commits intomainfrom
feature/k8s-deploy
Open

Add Kubernetes deploy pipeline with Helm engine#15723
mitchdenny wants to merge 42 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
/// <see cref="KubernetesEnvironmentExtensions.WithHelm(IResourceBuilder{KubernetesEnvironmentResource}, Action{HelmChartConfiguration})"/>.
/// Each method adds a corresponding annotation to the environment resource.
/// </remarks>
public sealed class HelmChartConfiguration
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Should this be HelmChartOptions ... what would be consistent with the Docker Compose integration.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yep — I renamed it to HelmChartOptions. That reads more naturally for the callback shape here and is more consistent with the options-style configuration pattern used in the Docker Compose integration. The public API is now WithHelm(Action<HelmChartOptions>), which better communicates that this object is just a fluent surface for setting Helm-specific options.

/// <returns>This <see cref="HelmChartConfiguration"/> for chaining.</returns>
public HelmChartConfiguration WithReleaseName(string releaseName)
{
ArgumentException.ThrowIfNullOrEmpty(releaseName);
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

What input validation do we need here? Is there a stricter subset of input we can filter on. Same for the other With* methods on this class that take a string.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I tightened the literal string overloads here so we fail early on obviously invalid input:

  • WithNamespace(string) now enforces a Kubernetes DNS-label-friendly subset: lowercase letters, digits, and hyphens, starting/ending with an alphanumeric character, with the Kubernetes length cap.
  • WithReleaseName(string) uses the same shape with a Helm-safe length budget.
  • WithChartVersion(string) now requires strict semantic versioning.

I intentionally left the parameter-based overloads flexible because those values are supplied at deploy time, so we can’t validate them statically at AppHost build time. I also added tests to cover both empty and invalid values.

/// <summary>
/// Provides extension methods for creating Aspire Dashboard resources in a Kubernetes environment.
/// </summary>
public static class KubernetesAspireDashboardResourceBuilderExtensions
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Review this for consistency with the Docker Compose implementatation.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I reviewed this against the Docker Compose implementation and aligned the Kubernetes version more closely. The WithHostPort / WithForwardedHeaders behavior and docs now follow the same pattern, and I updated CreateDashboard remarks/exception docs to match the Compose version’s intent while keeping the Kubernetes-specific detail that the dashboard is configured for unsecured cluster-internal access by default.

@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 on lines +67 to +82
/// <remarks>
/// Helm is the default deployment engine. Call this method to customize Helm-specific settings.
/// <example>
/// Configure Helm deployment with custom settings:
/// <code>
/// builder.AddKubernetesEnvironment("k8s")
/// .WithHelm(helm =>
/// {
/// helm.WithNamespace("my-namespace");
/// helm.WithReleaseName("my-release");
/// helm.WithChartVersion("1.0.0");
/// });
/// </code>
/// </example>
/// </remarks>
[AspireExport(Description = "Configures Helm chart deployment settings", RunSyncOnBackgroundThread = true)]
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

The XML doc for WithHelm nests an <example> element inside <remarks>. Our XML doc tooling generally expects <example> as a top-level tag (sibling to <remarks>), and nesting can cause the example to be dropped or rendered incorrectly in IntelliSense/docs. Consider moving the <example> block out of <remarks> (or converting it to plain text inside <remarks> if you prefer to keep a single section).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed — moved <example> out of <remarks> to be a top-level sibling tag so it renders correctly in IntelliSense and docs tooling.

Comment on lines +221 to +222
// Print summary steps must run after helm deploy
var printSummarySteps = context.GetSteps(deploymentTarget, "print-summary");
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

print-summary steps are created by HelmDeploymentEngine.CreateStepsAsync(...) as part of the environment’s step list, so they’re associated with the Kubernetes environment resource (not the per-resource deploymentTarget). As written, context.GetSteps(deploymentTarget, "print-summary") will likely return no steps, making this dependency wiring ineffective/misleading. Consider either querying context.GetSteps(this, "print-summary") here, or (if the intent is per-deploymentTarget ownership) adjusting step creation so the summary steps are attached to the deployment target resource instead.

Suggested change
// Print summary steps must run after helm deploy
var printSummarySteps = context.GetSteps(deploymentTarget, "print-summary");
// Print summary steps are created for the environment and must run after helm deploy
var printSummarySteps = context.GetSteps(this, "print-summary");

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — the print-summary steps are created by HelmDeploymentEngine.CreateStepsAsync which is invoked via the KubernetesDeploymentEngineAnnotation on the environment resource. Since step.Resource ??= resource in DistributedApplicationPipeline.cs assigns unowned steps to the annotation's resource, all these steps end up owned by the environment — not the deploymentTarget. Fixed to context.GetSteps(this, "print-summary") so the lookup matches the actual ownership.

@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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

🎬 CLI E2E Test Recordings — 64 recordings uploaded (commit 7f47bc1)

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
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
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
StopAllAppHostsFromAppHostDirectory ▶️ View Recording
StopAllAppHostsFromUnrelatedDirectory ▶️ View Recording
StopNonInteractiveMultipleAppHostsShowsError ▶️ View Recording
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording

📹 Recordings uploaded automatically from CI run #24182951189

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.


try
{
var endpoints = await GetServiceEndpointsAsync(computeResource.Name, @namespace, context.Logger, context.CancellationToken).ConfigureAwait(false);
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.

PrintResourceSummaryAsync looks up the service by computeResource.Name, but the manifest publishes it as resource.Name.ToServiceName() (for example, api-service). That makes the kubectl get service ... call miss the deployed service, so summaries never show external endpoints. Could this use k8sResource.Service.Metadata.Name (or computeResource.Name.ToServiceName()) instead?

{
return builder.WithEndpoint("http", e =>
{
e.Port = port;
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.

I think this API is currently a no-op for Kubernetes. WithHostPort only sets EndpointAnnotation.Port, but the Kubernetes publish path carries resolved.TargetPort into EndpointMappings, and ToService()/ToContainerV1() both emit that same value. So WithDashboard(d => d.WithHostPort(9999)) does not change the generated Service/Container ports, and the deploy summary still hard-codes 18888:18888. Either the publisher needs to flow ResolvedEndpoint.ExposedPort into the Service manifest, or this API/docs should be tightened so they do not promise host-port behavior that is not implemented.


// Default to the deployment environment name (aspire deploy -e <name>), not the resource name.
var hostEnvironment = context.Services.GetRequiredService<IHostEnvironment>();
return hostEnvironment.EnvironmentName.ToLowerInvariant();
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.

This uses the resolved deploy-time value without re-validating it. The literal overloads validate release names up front, but the default fallback here is IHostEnvironment.EnvironmentName.ToLowerInvariant(), and deployment environment names currently allow underscores. That means aspire deploy -e my_env can produce an invalid Helm release name here. The same pattern also affects parameter-backed namespace/version values later in deploy, so it would be good to apply the same validation after resolution before rewriting Chart.yaml and composing the Helm command.

Mitch Denny and others added 11 commits April 10, 2026 10:07
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
…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>
Mitch Denny and others added 29 commits April 10, 2026 10:07
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>
…ver, 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>
These tests should run in normal CI, not be quarantined.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Each test class becomes a separate CI job via SplitTestsOnCI=true.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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>
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>
…s 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>
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>
…ical

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>
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>
…tine 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>
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>
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>
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>
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 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>
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>
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>
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>
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>
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.

3 participants