diff --git a/.github/workflows/update-azure-vm-sizes.yml b/.github/workflows/update-azure-vm-sizes.yml
new file mode 100644
index 00000000000..a3a163c90a7
--- /dev/null
+++ b/.github/workflows/update-azure-vm-sizes.yml
@@ -0,0 +1,48 @@
+name: Update Azure VM Sizes
+
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '0 6 1 * *' # Monthly on the 1st at 06:00 UTC
+
+permissions:
+ contents: write
+ pull-requests: write
+
+jobs:
+ generate-and-pr:
+ runs-on: ubuntu-latest
+ if: ${{ github.repository_owner == 'microsoft' }}
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+
+ - name: Azure Login
+ uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
+ with:
+ creds: ${{ secrets.AZURE_CREDENTIALS }}
+
+ - name: Generate updated Azure VM size descriptors
+ working-directory: src/Aspire.Hosting.Azure.Kubernetes/tools
+ run: |
+ set -e
+ "$GITHUB_WORKSPACE/dotnet.sh" run GenVmSizes.cs
+
+ - name: Generate GitHub App Token
+ id: app-token
+ uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6
+ with:
+ app-id: ${{ secrets.ASPIRE_BOT_APP_ID }}
+ private-key: ${{ secrets.ASPIRE_BOT_PRIVATE_KEY }}
+
+ - name: Create or update pull request
+ uses: ./.github/actions/create-pull-request
+ with:
+ token: ${{ steps.app-token.outputs.token }}
+ branch: update-azure-vm-sizes
+ base: main
+ commit-message: "[Automated] Update Azure VM Sizes"
+ labels: |
+ area-integrations
+ area-engineering-systems
+ title: "[Automated] Update Azure VM Sizes"
+ body: "Auto-generated update of Azure VM size descriptors (AksNodeVmSizes.Generated.cs)."
diff --git a/Aspire.slnx b/Aspire.slnx
index 687b4d4c339..4cb8040f922 100644
--- a/Aspire.slnx
+++ b/Aspire.slnx
@@ -93,6 +93,7 @@
+
@@ -472,6 +473,7 @@
+
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 89f63e5f2c2..8dc3cabab9f 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -45,6 +45,7 @@
+
diff --git a/docs/specs/aks-support.md b/docs/specs/aks-support.md
new file mode 100644
index 00000000000..edc42f6f515
--- /dev/null
+++ b/docs/specs/aks-support.md
@@ -0,0 +1,620 @@
+# AKS Support in Aspire — Implementation Spec
+
+## Problem Statement
+
+Aspire's `Aspire.Hosting.Kubernetes` package currently supports end-to-end deployment to any conformant Kubernetes cluster (including AKS) via Helm charts. However, the support is **generic Kubernetes** — it has no awareness of Azure-specific capabilities. Users who want to deploy to AKS must manually provision the cluster, configure workload identity, set up monitoring, and manage networking outside of Aspire.
+
+The goal is to create a first-class AKS experience in Aspire that supports:
+- **Provisioning the AKS cluster itself** via Azure.Provisioning
+- **Workload identity** (Azure AD federated credentials for pods)
+- **Azure Monitor integration** (Container Insights, Log Analytics, managed Prometheus/Grafana)
+- **VNet integration** (subnet delegation, private clusters)
+- **Network perimeter support** (NSP, private endpoints for backing Azure services)
+
+## Current State Analysis
+
+### Kubernetes Publishing (Aspire.Hosting.Kubernetes)
+- **Helm-chart based** deployment model with 5-step pipeline: Publish → Prepare → Deploy → Summary → Uninstall
+- `KubernetesEnvironmentResource` is the root compute environment
+- `KubernetesResource` wraps each Aspire resource into Deployment/Service/ConfigMap/Secret YAML
+- `HelmDeploymentEngine` executes `helm upgrade --install`
+- No Azure awareness — works with any kubeconfig-accessible cluster
+- Identity support: ❌ None
+- Networking: Basic K8s Service/Ingress only
+- Monitoring: OTLP to Aspire Dashboard only
+
+### Azure Provisioning Patterns (established)
+- `AzureProvisioningResource` base class → generates Bicep via `Azure.Provisioning` SDK
+- `AzureResourceInfrastructure` builder → `CreateExistingOrNewProvisionableResource()` factory
+- `BicepOutputReference` for cross-resource wiring
+- `AppIdentityAnnotation` + `IAppIdentityResource` for managed identity attachment
+- Role assignments via `AddRoleAssignments()` / `IAddRoleAssignmentsContext`
+
+### Azure Container Apps (reference pattern)
+- `AzureContainerAppEnvironmentResource` : `AzureProvisioningResource`, `IAzureComputeEnvironmentResource`
+- Implements `IAzureContainerRegistry`, `IAzureDelegatedSubnetResource`
+- Auto-creates Container Registry, Log Analytics, managed identity
+- Subscribes to `BeforeStartEvent` → creates ContainerApp per compute resource → adds `DeploymentTargetAnnotation`
+
+### Azure Networking (established)
+- VNet, Subnet, NSG, NAT Gateway, Private DNS Zone, Private Endpoint, Public IP resources
+- `IAzurePrivateEndpointTarget` interface (implemented by Storage, SQL, etc.)
+- `IAzureNspAssociationTarget` for network security perimeters
+- `DelegatedSubnetAnnotation` for service delegation
+- `PrivateEndpointTargetAnnotation` to deny public access
+
+### Azure Identity (established)
+- `AzureUserAssignedIdentityResource` with Id, ClientId, PrincipalId outputs
+- `AppIdentityAnnotation` attaches identity to compute resources
+- Container Apps sets `AZURE_CLIENT_ID` + `AZURE_TOKEN_CREDENTIALS=ManagedIdentityCredential`
+- **No workload identity or federated credential support** exists today
+
+### Azure Monitoring (established)
+- `AzureLogAnalyticsWorkspaceResource` via `Azure.Provisioning.OperationalInsights`
+- `AzureApplicationInsightsResource` via `Azure.Provisioning.ApplicationInsights`
+- Container Apps links Log Analytics workspace to environment
+
+## Proposed Architecture
+
+### New Package: `Aspire.Hosting.Azure.Kubernetes`
+
+This package provides a unified `AddAzureKubernetesEnvironment()` entry point that internally invokes `AddKubernetesEnvironment()` (from the generic K8s package) and layers on AKS-specific Azure provisioning. This mirrors the established pattern of `AddAzureContainerAppEnvironment()` which internally sets up the Container Apps infrastructure.
+
+```text
+Aspire.Hosting.Azure.Kubernetes
+├── depends on: Aspire.Hosting.Kubernetes
+├── depends on: Aspire.Hosting.Azure
+├── depends on: Azure.Provisioning.Kubernetes (v1.0.0-beta.3)
+├── depends on: Azure.Provisioning.Roles (for federated credentials)
+├── depends on: Azure.Provisioning.Network (for VNet integration)
+└── depends on: Azure.Provisioning.OperationalInsights (for monitoring)
+```
+
+### Design Principle: Unified Environment Resource
+
+Just as `AddAzureContainerAppEnvironment("aca")` creates a single resource that is both the Azure provisioning target AND the compute environment, `AddAzureKubernetesEnvironment("aks")` creates a single `AzureKubernetesEnvironmentResource` that:
+1. Extends `AzureProvisioningResource` (generates Bicep for AKS cluster + supporting resources)
+2. Implements `IAzureComputeEnvironmentResource` (serves as the compute target)
+3. Internally creates and manages a `KubernetesEnvironmentResource` for Helm-based deployment
+4. Registers the `KubernetesInfrastructure` eventing subscriber (same as `AddKubernetesEnvironment`)
+
+### Integration Points
+
+```text
+┌─────────────────────────────────────────────────────────────┐
+│ User's AppHost │
+│ │
+│ var aks = builder.AddAzureKubernetesEnvironment("aks") │
+│ .WithDelegatedSubnet(subnet) │
+│ .WithAzureLogAnalyticsWorkspace(logAnalytics) │
+│ .WithWorkloadIdentity() │
+│ .WithVersion("1.30") │
+│ .WithHelm(...) ← from K8s environment │
+│ .WithDashboard(); ← from K8s environment │
+│ │
+│ var db = builder.AddAzureSqlServer("sql") │
+│ .WithPrivateEndpoint(subnet); ← existing pattern │
+│ │
+│ builder.AddProject() │
+│ .WithReference(db) │
+│ .WithAzureWorkloadIdentity(identity); ← new │
+└─────────────────────────────────────────────────────────────┘
+```
+
+## Detailed Design
+
+### 1. Unified AKS Environment Resource
+
+**New resource**: `AzureKubernetesEnvironmentResource`
+
+This is the single entry point — analogous to `AzureContainerAppEnvironmentResource`. It extends `AzureProvisioningResource` to generate Bicep for the AKS cluster and supporting infrastructure, while also serving as the compute environment by internally delegating to `KubernetesEnvironmentResource` for Helm-based deployment.
+
+```csharp
+public class AzureKubernetesEnvironmentResource(
+ string name,
+ Action configureInfrastructure)
+ : AzureProvisioningResource(name, configureInfrastructure),
+ IAzureComputeEnvironmentResource,
+ IAzureContainerRegistry, // For ACR integration
+ IAzureDelegatedSubnetResource, // For VNet integration
+ IAzureNspAssociationTarget // For NSP association
+{
+ // The underlying generic K8s environment (created internally)
+ internal KubernetesEnvironmentResource KubernetesEnvironment { get; set; } = default!;
+
+ // AKS cluster outputs
+ public BicepOutputReference Id => new("id", this);
+ public BicepOutputReference ClusterFqdn => new("clusterFqdn", this);
+ public BicepOutputReference OidcIssuerUrl => new("oidcIssuerUrl", this);
+ public BicepOutputReference KubeletIdentityObjectId => new("kubeletIdentityObjectId", this);
+ public BicepOutputReference NodeResourceGroup => new("nodeResourceGroup", this);
+ public BicepOutputReference NameOutputReference => new("name", this);
+
+ // ACR outputs (like AzureContainerAppEnvironmentResource)
+ internal BicepOutputReference ContainerRegistryName => new("AZURE_CONTAINER_REGISTRY_NAME", this);
+ internal BicepOutputReference ContainerRegistryUrl => new("AZURE_CONTAINER_REGISTRY_ENDPOINT", this);
+ internal BicepOutputReference ContainerRegistryManagedIdentityId
+ => new("AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID", this);
+
+ // Service delegation
+ string IAzureDelegatedSubnetResource.DelegatedSubnetServiceName
+ => "Microsoft.ContainerService/managedClusters";
+
+ // Configuration
+ internal string? KubernetesVersion { get; set; }
+ internal AksSkuTier SkuTier { get; set; } = AksSkuTier.Free;
+ internal bool OidcIssuerEnabled { get; set; } = true;
+ internal bool WorkloadIdentityEnabled { get; set; } = true;
+ internal AzureContainerRegistryResource? DefaultContainerRegistry { get; set; }
+ internal AzureLogAnalyticsWorkspaceResource? LogAnalyticsWorkspace { get; set; }
+
+ // Node pool configuration
+ internal List NodePools { get; } = [
+ new AksNodePoolConfig("system", "Standard_D4s_v5", 1, 3, AksNodePoolMode.System)
+ ];
+
+ // Networking
+ internal AksNetworkProfile? NetworkProfile { get; set; }
+ internal AzureSubnetResource? SubnetResource { get; set; }
+ internal bool IsPrivateCluster { get; set; }
+}
+```
+
+**Entry point extension** (mirrors `AddAzureContainerAppEnvironment`):
+```csharp
+public static class AzureKubernetesEnvironmentExtensions
+{
+ public static IResourceBuilder AddAzureKubernetesEnvironment(
+ this IDistributedApplicationBuilder builder,
+ [ResourceName] string name)
+ {
+ // 1. Set up Azure provisioning infrastructure
+ builder.AddAzureProvisioning();
+ builder.Services.Configure(
+ o => o.SupportsTargetedRoleAssignments = true);
+
+ // 2. Register the AKS-specific infrastructure eventing subscriber
+ builder.Services.TryAddEventingSubscriber();
+
+ // 3. Also register the generic K8s infrastructure (for Helm chart generation)
+ builder.AddKubernetesInfrastructureCore();
+
+ // 4. Create the unified environment resource
+ var resource = new AzureKubernetesEnvironmentResource(name, ConfigureAksInfrastructure);
+
+ // 5. Create the inner KubernetesEnvironmentResource (for Helm deployment)
+ resource.KubernetesEnvironment = new KubernetesEnvironmentResource($"{name}-k8s")
+ {
+ HelmChartName = builder.Environment.ApplicationName.ToHelmChartName(),
+ Dashboard = builder.CreateDashboard($"{name}-dashboard")
+ };
+
+ // 6. Auto-create ACR (like Container Apps does)
+ var acr = CreateDefaultContainerRegistry(builder, name);
+ resource.DefaultContainerRegistry = acr;
+
+ return builder.AddResource(resource);
+ }
+
+ // Configuration extensions
+ public static IResourceBuilder WithVersion(
+ this IResourceBuilder builder, string version);
+ public static IResourceBuilder WithSkuTier(
+ this IResourceBuilder builder, AksSkuTier tier);
+ public static IResourceBuilder WithNodePool(
+ this IResourceBuilder builder,
+ string name, string vmSize, int minCount, int maxCount,
+ AksNodePoolMode mode = AksNodePoolMode.User);
+
+ // Networking (matches existing pattern: WithDelegatedSubnet)
+ public static IResourceBuilder WithDelegatedSubnet(
+ this IResourceBuilder builder,
+ IResourceBuilder subnet);
+ public static IResourceBuilder AsPrivateCluster(
+ this IResourceBuilder builder);
+
+ // Identity
+ public static IResourceBuilder WithWorkloadIdentity(
+ this IResourceBuilder builder);
+
+ // Monitoring (matches existing pattern: WithAzureLogAnalyticsWorkspace)
+ public static IResourceBuilder WithAzureLogAnalyticsWorkspace(
+ this IResourceBuilder builder,
+ IResourceBuilder workspaceBuilder);
+ public static IResourceBuilder WithContainerInsights(
+ this IResourceBuilder builder,
+ IResourceBuilder? logAnalytics = null);
+
+ // Container Registry
+ public static IResourceBuilder WithContainerRegistry(
+ this IResourceBuilder builder,
+ IResourceBuilder registry);
+
+ // Helm configuration (delegates to inner KubernetesEnvironmentResource)
+ public static IResourceBuilder WithHelm(
+ this IResourceBuilder builder,
+ Action configure);
+ public static IResourceBuilder WithDashboard(
+ this IResourceBuilder builder);
+}
+```
+
+**`AzureKubernetesInfrastructure`** (eventing subscriber, mirrors `AzureContainerAppsInfrastructure`):
+```csharp
+internal sealed class AzureKubernetesInfrastructure(
+ ILogger logger,
+ DistributedApplicationExecutionContext executionContext)
+ : IDistributedApplicationEventingSubscriber
+{
+ private async Task OnBeforeStartAsync(BeforeStartEvent @event, CancellationToken ct)
+ {
+ var aksEnvironments = @event.Model.Resources
+ .OfType().ToArray();
+
+ foreach (var environment in aksEnvironments)
+ {
+ foreach (var r in @event.Model.GetComputeResources())
+ {
+ var computeEnv = r.GetComputeEnvironment();
+ if (computeEnv is not null && computeEnv != environment)
+ continue;
+
+ // 1. Process workload identity annotations
+ // → Generate federated credentials in Bicep
+ // → Add ServiceAccount + labels to Helm chart
+
+ // 2. Create KubernetesResource via inner environment
+ // (delegates to existing KubernetesInfrastructure)
+
+ // 3. Add DeploymentTargetAnnotation
+ r.Annotations.Add(new DeploymentTargetAnnotation(environment)
+ {
+ ContainerRegistry = environment,
+ ComputeEnvironment = environment
+ });
+ }
+ }
+ }
+}
+```
+
+**Bicep output**: The `ConfigureAksInfrastructure` callback uses `Azure.Provisioning.Kubernetes` to produce:
+- `ManagedCluster` with system-assigned or user-assigned identity for the control plane
+- OIDC issuer enabled (required for workload identity)
+- Workload identity enabled on the cluster
+- Azure CNI or Kubenet network profile (based on VNet configuration)
+- Container Insights add-on profile (if monitoring configured)
+- Node pools with autoscaler configuration
+- ACR pull role assignment for kubelet identity
+- Container Registry (auto-created or explicit)
+
+### 2. Workload Identity Support
+
+Workload identity enables pods to authenticate to Azure services using federated credentials without storing secrets. This is implemented by honoring the shared `AppIdentityAnnotation` from `Aspire.Hosting.Azure` — the same mechanism used by ACA and AppService.
+
+**How it works**:
+1. `AzureResourcePreparer` auto-creates a per-resource managed identity when a compute resource references Azure services (e.g., `WithReference(blobStorage)`)
+2. It adds `AppIdentityAnnotation` with the identity to the resource
+3. Users can override with `WithAzureUserAssignedIdentity(myIdentity)` to supply their own identity
+4. `AzureKubernetesInfrastructure` detects `AppIdentityAnnotation` and generates:
+ - A K8s `ServiceAccount` with `azure.workload.identity/client-id` annotation
+ - `serviceAccountName` on the pod spec
+ - `azure.workload.identity/use: "true"` pod label
+ - Federated identity credential in AKS Bicep module
+
+**User API** (same as ACA):
+```csharp
+// Automatic — identity auto-created when referencing Azure resources
+builder.AddProject()
+ .WithComputeEnvironment(aks)
+ .WithReference(blobStorage); // gets identity + workload identity + role assignments
+
+// Explicit — bring your own identity
+var identity = builder.AddAzureUserAssignedIdentity("api-identity");
+builder.AddProject()
+ .WithComputeEnvironment(aks)
+ .WithAzureUserAssignedIdentity(identity);
+```
+
+**Generated Bicep** (federated credential):
+```bicep
+resource federatedCredential 'Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials@2023-01-31' = {
+ parent: identity
+ name: '${resourceName}-fedcred'
+ properties: {
+ issuer: aksCluster.properties.oidcIssuerProfile.issuerURL
+ subject: 'system:serviceaccount:${namespace}:${resourceName}-sa'
+ audiences: ['api://AzureADTokenExchange']
+ }
+}
+```
+
+**Generated Helm chart** (ServiceAccount + pod template):
+```yaml
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: apiservice-sa
+ annotations:
+ azure.workload.identity/client-id: {{ .Values.parameters.apiservice.identityClientId }}
+ labels:
+ azure.workload.identity/use: "true"
+---
+# In the Deployment pod template:
+spec:
+ serviceAccountName: apiservice-sa
+ template:
+ metadata:
+ labels:
+ azure.workload.identity/use: "true"
+```
+
+### 3. Monitoring Integration
+
+**Goal**: When monitoring is enabled on the AKS environment, provision:
+- Container Insights (via AKS addon profile) with Log Analytics workspace
+- Azure Monitor metrics profile (managed Prometheus)
+- Optional: Azure Managed Grafana dashboard
+- Optional: Application Insights for application-level telemetry
+
+**Design** (matches `WithAzureLogAnalyticsWorkspace` pattern from Container Apps):
+```csharp
+// Option 1: Explicit workspace (matches Container Apps naming exactly)
+var aks = builder.AddAzureKubernetesEnvironment("aks")
+ .WithAzureLogAnalyticsWorkspace(logAnalytics);
+
+// Option 2: Enable Container Insights (auto-creates workspace if not provided)
+var aks = builder.AddAzureKubernetesEnvironment("aks")
+ .WithContainerInsights();
+
+// Option 3: Both — explicit workspace + Container Insights addon
+var aks = builder.AddAzureKubernetesEnvironment("aks")
+ .WithContainerInsights(logAnalytics);
+```
+
+**Bicep additions**:
+- `addonProfiles.omsagent.enabled = true` with Log Analytics workspace ID
+- `azureMonitorProfile.metrics.enabled = true` for managed Prometheus
+- Data collection rule for container insights
+- Optional: `AzureMonitorWorkspaceResource` for managed Prometheus
+
+**OTLP integration**: The existing Kubernetes publishing already injects `OTEL_EXPORTER_OTLP_ENDPOINT`. For AKS, we can optionally route OTLP to Application Insights via the connection string environment variable.
+
+### 4. VNet Integration
+
+AKS needs a subnet for its nodes. Unlike Container Apps, AKS does **not** use subnet delegation — it uses plain (non-delegated) subnets. The API is `WithSubnet()` (not `WithDelegatedSubnet()`).
+
+**Design**:
+```csharp
+var vnet = builder.AddAzureVirtualNetwork("vnet", "10.0.0.0/16");
+var defaultSubnet = vnet.AddSubnet("default", "10.0.0.0/22");
+var gpuSubnet = vnet.AddSubnet("gpu-subnet", "10.0.4.0/24");
+
+// Environment-level subnet (applies to all pools by default)
+var aks = builder.AddAzureKubernetesEnvironment("aks")
+ .WithSubnet(defaultSubnet);
+
+// Per-pool subnet override
+var gpuPool = aks.AddNodePool("gpu", AksNodeVmSizes.GpuAccelerated.StandardNC6sV3, 0, 5)
+ .WithSubnet(gpuSubnet);
+```
+
+**Bicep**: Environment-level subnet → `subnetId` parameter. Per-pool subnets → `subnetId_{poolName}` parameters. Each agent pool profile uses its own subnet if set, else the environment default.
+
+**Network profile**: Azure CNI is auto-configured when any subnet is set.
+
+**Private cluster support**:
+```csharp
+public static IResourceBuilder AsPrivateCluster(
+ this IResourceBuilder builder)
+{
+ // Enable private cluster (API server behind private endpoint)
+ // Requires a delegated subnet to be configured
+ // Sets apiServerAccessProfile.enablePrivateCluster = true
+}
+```
+
+### 5. Network Perimeter Support
+
+AKS backing Azure services (SQL, Storage, Key Vault) should be accessible via private endpoints within the cluster's VNet.
+
+**This largely uses existing infrastructure**:
+```csharp
+// User code in AppHost
+var vnet = builder.AddAzureVirtualNetwork("vnet");
+var aksSubnet = vnet.AddSubnet("aks-subnet", "10.0.0.0/22");
+var peSubnet = vnet.AddSubnet("pe-subnet", "10.0.4.0/24");
+
+var aks = builder.AddAzureKubernetesService("aks")
+ .WithVirtualNetwork(aksSubnet);
+
+var sql = builder.AddAzureSqlServer("sql")
+ .WithPrivateEndpoint(peSubnet); // existing pattern
+
+// The SQL private endpoint is in the same VNet as AKS
+// DNS resolution via Private DNS Zone (existing pattern) enables pod → SQL connectivity
+```
+
+**New consideration**: When AKS is configured with a VNet and backing services have private endpoints, the AKS infrastructure should verify or configure:
+- Private DNS Zone links to the AKS VNet (so pods can resolve private endpoint DNS)
+- This may need a new extension or automatic wiring
+
+### 6. Deployment Pipeline Integration
+
+Since `AzureKubernetesEnvironmentResource` unifies both Azure provisioning and K8s deployment, the pipeline is a superset of both:
+
+```text
+[Azure Provisioning Phase] [Kubernetes Deployment Phase]
+1. Generate Bicep (AKS + ACR + 4. Publish Helm chart
+ identity + fedcreds) 5. Get kubeconfig from AKS (az aks get-credentials)
+2. Deploy Bicep via azd 6. Push images to ACR
+3. Capture outputs (OIDC URL, 7. Prepare Helm values (resolve AKS outputs)
+ ACR endpoint, etc.) 8. helm upgrade --install
+ 9. Print summary
+ 10. (Optional) Uninstall
+```
+
+The Azure provisioning happens first (via `AzureEnvironmentResource` / `AzureProvisioner`), then the Kubernetes Helm deployment pipeline steps execute against the provisioned cluster. The kubeconfig step bridges the two phases — it uses the AKS cluster name from Bicep outputs to call `az aks get-credentials`.
+
+This is implemented by adding AKS-specific `DeploymentEngineStepsFactory` entries to the inner `KubernetesEnvironmentResource`:
+```csharp
+// In AddAzureKubernetesEnvironment, after AKS provisioning completes:
+resource.KubernetesEnvironment.AddDeploymentEngineStep(
+ "get-kubeconfig",
+ async (context, ct) =>
+ {
+ // Use AKS outputs to fetch kubeconfig
+ var clusterName = await resource.NameOutputReference.GetValueAsync(ct);
+ var resourceGroup = await resource.ResourceGroupOutput.GetValueAsync(ct);
+ // az aks get-credentials --resource-group {rg} --name {name}
+ });
+```
+
+### 7. Container Registry Integration
+
+AKS needs a container registry for application images. Options:
+1. **Auto-create ACR** when AKS is added (like Container Apps does)
+2. **Bring your own ACR** via `.WithContainerRegistry()`
+3. **Use existing ACR** via `AsExisting()` pattern
+
+```csharp
+// Auto-create (default)
+var aks = builder.AddAzureKubernetesService("aks");
+// → auto-creates ACR, attaches AcrPull role to kubelet identity
+
+// Explicit
+var acr = builder.AddAzureContainerRegistry("acr");
+var aks = builder.AddAzureKubernetesService("aks")
+ .WithContainerRegistry(acr);
+```
+
+**Role assignment**: The AKS kubelet managed identity needs `AcrPull` role on the registry.
+
+## Open Questions
+
+1. **`Azure.Provisioning.Kubernetes` readiness**: The package is at v1.0.0-beta.3. We need to verify it has the types we need (`ManagedCluster`, `AgentPool`, `OidcIssuerProfile`, `WorkloadIdentity` flags, etc.) and assess stability risk.
+
+2. **Existing cluster support**: Should we support `AsExisting()` for AKS (reference a pre-provisioned cluster)?
+ - **Recommendation**: Yes, this is a common scenario. Use the established `ExistingAzureResourceAnnotation` pattern.
+
+3. **Managed Grafana**: Should `WithMonitoring()` also provision Azure Managed Grafana?
+ - Could be a separate `.WithGrafana()` extension to keep it opt-in.
+
+4. **Ingress controller**: Should Aspire configure an ingress controller (NGINX, Traefik, or Application Gateway Ingress Controller)?
+ - Application Gateway Ingress Controller (AGIC) would be the Azure-native choice.
+ - Could be opt-in via `.WithApplicationGatewayIngress()`.
+
+5. **DNS integration**: Should external endpoints auto-configure Azure DNS zones?
+ - Probably out of scope for v1.
+
+6. **Deployment mode**: For publish, should AKS support work with `aspire publish` only, or also `aspire run` (local dev with AKS)?
+ - Recommendation: `aspire publish` first. Local dev uses the generic K8s environment with local/kind clusters.
+
+7. **Multi-cluster**: Should we support multiple AKS environments in one AppHost?
+ - The `KubernetesEnvironmentResource` model already supports this conceptually.
+
+8. **Helm config delegation**: How cleanly can `WithHelm()` / `WithDashboard()` be forwarded from `AzureKubernetesEnvironmentResource` to the inner `KubernetesEnvironmentResource`? Should the inner resource be exposed or kept fully internal?
+
+## Implementation Status
+
+### ✅ Implemented
+
+#### Phase 1: Unified AKS Environment (Foundation)
+- ✅ `Aspire.Hosting.Azure.Kubernetes` package created
+- ✅ `AzureKubernetesEnvironmentResource` — extends `AzureProvisioningResource`, implements `IAzureComputeEnvironmentResource`, `IAzureNspAssociationTarget`
+- ✅ `AddAzureKubernetesEnvironment()` entry point — calls `AddKubernetesEnvironment()` internally
+- ✅ `AzureKubernetesInfrastructure` eventing subscriber
+- ✅ Hand-crafted Bicep generation (not Azure.Provisioning SDK — `Azure.Provisioning.ContainerService` not in internal feeds)
+- ✅ ACR auto-creation + AcrPull role assignment in Bicep
+- ✅ Kubeconfig retrieval via `az aks get-credentials` to isolated temp file
+- ✅ Multi-environment support (scoped Helm chart names, per-env kubeconfig)
+- ✅ `WithContainerRegistry()`
+- ❌ ~~`WithVersion()`, `WithSkuTier()`, `AsPrivateCluster()`~~ — **Removed** in initial sweep; use `ConfigureInfrastructure()` instead
+- ✅ Push step dependency wiring for container image builds
+
+#### Phase 2: Workload Identity
+- ✅ Honors `AppIdentityAnnotation` from `Aspire.Hosting.Azure` (same mechanism as ACA/AppService)
+- ✅ Auto-identity via `AzureResourcePreparer` when resources reference Azure services
+- ✅ Override with `WithAzureUserAssignedIdentity(identity)` (standard API)
+- ✅ ServiceAccount YAML generation with `azure.workload.identity/client-id` annotation
+- ✅ Pod label `azure.workload.identity/use: "true"` on pod template
+- ✅ `serviceAccountName` set on pod spec
+- ✅ Federated identity credential Bicep generation per workload
+- ✅ Identity `clientId` wired as deferred Helm value (resolved at deploy time)
+- ✅ `ServiceAccountV1` resource added to `Aspire.Hosting.Kubernetes`
+- ❌ ~~`AksWorkloadIdentityAnnotation`~~ — **Removed** (redundant with `AppIdentityAnnotation`)
+- ❌ ~~`WithAzureWorkloadIdentity()`~~ — **Removed** (standard `WithAzureUserAssignedIdentity` works)
+
+#### Phase 3: Networking
+- ✅ `WithSubnet()` (NOT `WithDelegatedSubnet` — AKS doesn't support subnet delegation)
+- ✅ Per-node-pool subnet support via `WithSubnet()` on `AksNodePoolResource`
+- ✅ Azure CNI network profile auto-configured when subnet is set
+- ✅ `AsPrivateCluster()` for private API server
+- ❌ AKS does NOT implement `IAzureDelegatedSubnetResource` (intentionally — AKS uses plain subnets)
+
+#### Node Pools (not in original spec)
+- ✅ Base `KubernetesNodePoolResource` in `Aspire.Hosting.Kubernetes` (cloud-agnostic)
+- ✅ `AksNodePoolResource` extends base with VM size, scaling, mode config
+- ✅ `AddNodePool()` on both K8s and AKS environments
+- ✅ `WithNodePool()` schedules workloads via `nodeSelector` on pod spec
+- ✅ `AksNodeVmSizes` constants class (GeneralPurpose, ComputeOptimized, MemoryOptimized, GpuAccelerated, StorageOptimized, Burstable, Arm)
+- ✅ `GenVmSizes.cs` tool + `update-azure-vm-sizes.yml` monthly workflow
+- ✅ Default "workload" user pool auto-created if none configured
+
+#### IValueProvider Resolution (not in original spec)
+- ✅ Azure resource connection strings and endpoints resolved at deploy time
+- ✅ Composite expressions (e.g., `Endpoint={storage.outputs.blobEndpoint};ContainerName=photos`) handled
+- ✅ Phase 4 in HelmDeploymentEngine for generic `IValueProvider` resolution
+
+### 🔲 Not Yet Implemented
+
+#### Monitoring (Phase 4) — Bicep not emitted
+- 🔲 `WithContainerInsights()` and `WithAzureLogAnalyticsWorkspace()` **exist as APIs** but the Bicep generation does NOT emit:
+ - Container Insights addon profile (`addonProfiles.omsagent`)
+ - Azure Monitor metrics profile (managed Prometheus)
+ - Data collection rules
+ - Application Insights OTLP integration
+
+#### Helm/Dashboard delegation
+- 🔲 `WithHelm()` and `WithDashboard()` are not exposed on `AzureKubernetesEnvironmentResource`
+ - They work on the inner `KubernetesEnvironmentResource` but users can't access them from the AKS builder
+
+#### AsExisting() support
+- 🔲 `AsExisting()` for referencing pre-provisioned AKS clusters
+
+#### Private DNS Zone auto-linking
+- 🔲 When backing services have private endpoints in same VNet as AKS, Private DNS Zones should be auto-linked
+
+#### IAzureContainerRegistry interface
+- 🔲 AKS resource does not implement `IAzureContainerRegistry` (ACR outputs not exposed via standard interface)
+
+#### Ingress controller
+- 🔲 Application Gateway Ingress Controller (AGIC) or other ingress support
+
+#### Managed Prometheus/Grafana
+- 🔲 Azure Monitor workspace for managed Prometheus
+- 🔲 Azure Managed Grafana provisioning
+
+## Key Design Changes from Original Spec
+
+1. **Bicep generation**: Uses hand-crafted `StringBuilder` via `GetBicepTemplateString()` override, NOT `Azure.Provisioning.ContainerService` SDK (package not available in internal NuGet feeds)
+2. **Workload identity**: Uses shared `AppIdentityAnnotation` from `Aspire.Hosting.Azure`, not AKS-specific annotation. Same mechanism as ACA/AppService.
+3. **Subnet integration**: `WithSubnet()` not `WithDelegatedSubnet()` — AKS uses plain subnets, not delegated ones
+4. **Node pools**: First-class resources with `AddNodePool()` returning `IResourceBuilder`, `WithNodePool()` for scheduling, per-pool subnets, `AksNodeVmSizes` constants
+5. **Multi-environment**: Full support for multiple AKS environments with scoped chart names and isolated kubeconfigs
+
+## Dependencies / Prerequisites
+
+- ~~`Azure.Provisioning.Kubernetes`~~ — Not used (hand-crafted Bicep instead)
+- `Azure.Provisioning.ContainerRegistry` (for ACR resource type reference)
+- `Azure.Provisioning.OperationalInsights` (for Log Analytics workspace type)
+- `Aspire.Hosting.Kubernetes` (the generic K8s package)
+- `Aspire.Hosting.Azure` (for `AppIdentityAnnotation`, `AzureProvisioningResource`, etc.)
+- `Aspire.Hosting.Azure.Network` (for subnet, VNet, NSP types)
+- `Aspire.Hosting.Azure.ContainerRegistry` (for ACR auto-creation)
+
+## Testing
+
+- 31 AKS unit tests passing (extensions + infrastructure)
+- 88 K8s base tests passing
+- Manual E2E validation against live Azure clusters
diff --git a/src/Aspire.Hosting.Azure.Kubernetes/AksNetworkProfile.cs b/src/Aspire.Hosting.Azure.Kubernetes/AksNetworkProfile.cs
new file mode 100644
index 00000000000..ca707af77ed
--- /dev/null
+++ b/src/Aspire.Hosting.Azure.Kubernetes/AksNetworkProfile.cs
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Aspire.Hosting.Azure.Kubernetes;
+
+///
+/// Network profile configuration for an AKS cluster.
+///
+internal sealed class AksNetworkProfile
+{
+ ///
+ /// Gets or sets the network plugin. Defaults to "azure" for Azure CNI.
+ ///
+ public string NetworkPlugin { get; set; } = "azure";
+
+ ///
+ /// Gets or sets the network policy. Defaults to "calico".
+ ///
+ public string? NetworkPolicy { get; set; } = "calico";
+
+ ///
+ /// Gets or sets the service CIDR.
+ ///
+ public string ServiceCidr { get; set; } = "10.0.4.0/22";
+
+ ///
+ /// Gets or sets the DNS service IP address.
+ ///
+ public string DnsServiceIP { get; set; } = "10.0.4.10";
+}
diff --git a/src/Aspire.Hosting.Azure.Kubernetes/AksNodePoolConfig.cs b/src/Aspire.Hosting.Azure.Kubernetes/AksNodePoolConfig.cs
new file mode 100644
index 00000000000..5fc3016f1f8
--- /dev/null
+++ b/src/Aspire.Hosting.Azure.Kubernetes/AksNodePoolConfig.cs
@@ -0,0 +1,35 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Aspire.Hosting.Azure.Kubernetes;
+
+///
+/// Configuration for an AKS node pool.
+///
+/// The name of the node pool.
+/// The VM size for nodes in the pool.
+/// The minimum number of nodes.
+/// The maximum number of nodes.
+/// The mode of the node pool.
+public sealed record AksNodePoolConfig(
+ string Name,
+ string VmSize,
+ int MinCount,
+ int MaxCount,
+ AksNodePoolMode Mode);
+
+///
+/// Specifies the mode of an AKS node pool.
+///
+public enum AksNodePoolMode
+{
+ ///
+ /// System node pool for hosting system pods.
+ ///
+ System,
+
+ ///
+ /// User node pool for hosting application workloads.
+ ///
+ User
+}
diff --git a/src/Aspire.Hosting.Azure.Kubernetes/AksNodePoolResource.cs b/src/Aspire.Hosting.Azure.Kubernetes/AksNodePoolResource.cs
new file mode 100644
index 00000000000..e6fb6a17999
--- /dev/null
+++ b/src/Aspire.Hosting.Azure.Kubernetes/AksNodePoolResource.cs
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Aspire.Hosting.Kubernetes;
+
+namespace Aspire.Hosting.Azure.Kubernetes;
+
+///
+/// Represents an AKS node pool with Azure-specific configuration such as VM size and autoscaling.
+/// Extends the base with provisioning configuration
+/// that is used to generate Azure Bicep for the AKS agent pool profile.
+///
+/// The name of the node pool resource.
+/// The Azure-specific node pool configuration.
+/// The parent AKS environment resource.
+public class AksNodePoolResource(
+ string name,
+ AksNodePoolConfig config,
+ AzureKubernetesEnvironmentResource parent) : KubernetesNodePoolResource(name, parent.KubernetesEnvironment)
+{
+ ///
+ /// Gets the parent AKS environment resource.
+ ///
+ public AzureKubernetesEnvironmentResource AksParent { get; } = parent ?? throw new ArgumentNullException(nameof(parent));
+
+ ///
+ /// Gets the Azure-specific node pool configuration.
+ ///
+ public AksNodePoolConfig Config { get; } = config ?? throw new ArgumentNullException(nameof(config));
+}
diff --git a/src/Aspire.Hosting.Azure.Kubernetes/AksNodeVmSizes.Generated.cs b/src/Aspire.Hosting.Azure.Kubernetes/AksNodeVmSizes.Generated.cs
new file mode 100644
index 00000000000..40b5524311c
--- /dev/null
+++ b/src/Aspire.Hosting.Azure.Kubernetes/AksNodeVmSizes.Generated.cs
@@ -0,0 +1,274 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+//
+// This file is generated by the GenVmSizes tool. Do not edit manually.
+
+namespace Aspire.Hosting.Azure.Kubernetes;
+
+///
+/// Provides well-known Azure VM size constants for use with AKS node pools.
+///
+///
+/// This class is auto-generated. To update, run the GenVmSizes tool:
+/// dotnet run --project src/Aspire.Hosting.Azure.Kubernetes/tools GenVmSizes.cs
+///
+public static partial class AksNodeVmSizes
+{
+ ///
+ /// General purpose VM sizes optimized for balanced CPU-to-memory ratio.
+ ///
+ public static class GeneralPurpose
+ {
+ ///
+ /// Standard_D2s_v5 — 2 vCPUs, 8 GB RAM, Premium SSD.
+ ///
+ public const string StandardD2sV5 = "Standard_D2s_v5";
+
+ ///
+ /// Standard_D4s_v5 — 4 vCPUs, 16 GB RAM, Premium SSD.
+ ///
+ public const string StandardD4sV5 = "Standard_D4s_v5";
+
+ ///
+ /// Standard_D8s_v5 — 8 vCPUs, 32 GB RAM, Premium SSD.
+ ///
+ public const string StandardD8sV5 = "Standard_D8s_v5";
+
+ ///
+ /// Standard_D16s_v5 — 16 vCPUs, 64 GB RAM, Premium SSD.
+ ///
+ public const string StandardD16sV5 = "Standard_D16s_v5";
+
+ ///
+ /// Standard_D32s_v5 — 32 vCPUs, 128 GB RAM, Premium SSD.
+ ///
+ public const string StandardD32sV5 = "Standard_D32s_v5";
+
+ ///
+ /// Standard_D2s_v6 — 2 vCPUs, 8 GB RAM, Premium SSD.
+ ///
+ public const string StandardD2sV6 = "Standard_D2s_v6";
+
+ ///
+ /// Standard_D4s_v6 — 4 vCPUs, 16 GB RAM, Premium SSD.
+ ///
+ public const string StandardD4sV6 = "Standard_D4s_v6";
+
+ ///
+ /// Standard_D8s_v6 — 8 vCPUs, 32 GB RAM, Premium SSD.
+ ///
+ public const string StandardD8sV6 = "Standard_D8s_v6";
+
+ ///
+ /// Standard_D2as_v5 — 2 vCPUs, 8 GB RAM, Premium SSD, AMD processor.
+ ///
+ public const string StandardD2asV5 = "Standard_D2as_v5";
+
+ ///
+ /// Standard_D4as_v5 — 4 vCPUs, 16 GB RAM, Premium SSD, AMD processor.
+ ///
+ public const string StandardD4asV5 = "Standard_D4as_v5";
+
+ ///
+ /// Standard_D8as_v5 — 8 vCPUs, 32 GB RAM, Premium SSD, AMD processor.
+ ///
+ public const string StandardD8asV5 = "Standard_D8as_v5";
+ }
+
+ ///
+ /// Compute optimized VM sizes with high CPU-to-memory ratio.
+ ///
+ public static class ComputeOptimized
+ {
+ ///
+ /// Standard_F2s_v2 — 2 vCPUs, 4 GB RAM, Premium SSD.
+ ///
+ public const string StandardF2sV2 = "Standard_F2s_v2";
+
+ ///
+ /// Standard_F4s_v2 — 4 vCPUs, 8 GB RAM, Premium SSD.
+ ///
+ public const string StandardF4sV2 = "Standard_F4s_v2";
+
+ ///
+ /// Standard_F8s_v2 — 8 vCPUs, 16 GB RAM, Premium SSD.
+ ///
+ public const string StandardF8sV2 = "Standard_F8s_v2";
+
+ ///
+ /// Standard_F16s_v2 — 16 vCPUs, 32 GB RAM, Premium SSD.
+ ///
+ public const string StandardF16sV2 = "Standard_F16s_v2";
+ }
+
+ ///
+ /// Memory optimized VM sizes with high memory-to-CPU ratio.
+ ///
+ public static class MemoryOptimized
+ {
+ ///
+ /// Standard_E2s_v5 — 2 vCPUs, 16 GB RAM, Premium SSD.
+ ///
+ public const string StandardE2sV5 = "Standard_E2s_v5";
+
+ ///
+ /// Standard_E4s_v5 — 4 vCPUs, 32 GB RAM, Premium SSD.
+ ///
+ public const string StandardE4sV5 = "Standard_E4s_v5";
+
+ ///
+ /// Standard_E8s_v5 — 8 vCPUs, 64 GB RAM, Premium SSD.
+ ///
+ public const string StandardE8sV5 = "Standard_E8s_v5";
+
+ ///
+ /// Standard_E16s_v5 — 16 vCPUs, 128 GB RAM, Premium SSD.
+ ///
+ public const string StandardE16sV5 = "Standard_E16s_v5";
+
+ ///
+ /// Standard_E2as_v5 — 2 vCPUs, 16 GB RAM, Premium SSD, AMD processor.
+ ///
+ public const string StandardE2asV5 = "Standard_E2as_v5";
+
+ ///
+ /// Standard_E4as_v5 — 4 vCPUs, 32 GB RAM, Premium SSD, AMD processor.
+ ///
+ public const string StandardE4asV5 = "Standard_E4as_v5";
+ }
+
+ ///
+ /// GPU-enabled VM sizes for compute-intensive and ML workloads.
+ ///
+ public static class GpuAccelerated
+ {
+ ///
+ /// Standard_NC6s_v3 — 6 vCPUs, 112 GB RAM, 1 GPU (NVIDIA V100).
+ ///
+ public const string StandardNC6sV3 = "Standard_NC6s_v3";
+
+ ///
+ /// Standard_NC12s_v3 — 12 vCPUs, 224 GB RAM, 2 GPUs (NVIDIA V100).
+ ///
+ public const string StandardNC12sV3 = "Standard_NC12s_v3";
+
+ ///
+ /// Standard_NC24s_v3 — 24 vCPUs, 448 GB RAM, 4 GPUs (NVIDIA V100).
+ ///
+ public const string StandardNC24sV3 = "Standard_NC24s_v3";
+
+ ///
+ /// Standard_NC4as_T4_v3 — 4 vCPUs, 28 GB RAM, 1 GPU (NVIDIA T4).
+ ///
+ public const string StandardNC4asT4V3 = "Standard_NC4as_T4_v3";
+
+ ///
+ /// Standard_NC8as_T4_v3 — 8 vCPUs, 56 GB RAM, 1 GPU (NVIDIA T4).
+ ///
+ public const string StandardNC8asT4V3 = "Standard_NC8as_T4_v3";
+
+ ///
+ /// Standard_NC16as_T4_v3 — 16 vCPUs, 110 GB RAM, 1 GPU (NVIDIA T4).
+ ///
+ public const string StandardNC16asT4V3 = "Standard_NC16as_T4_v3";
+
+ ///
+ /// Standard_NC24ads_A100_v4 — 24 vCPUs, 220 GB RAM, 1 GPU (NVIDIA A100 80GB).
+ ///
+ public const string StandardNC24adsA100V4 = "Standard_NC24ads_A100_v4";
+
+ ///
+ /// Standard_NC48ads_A100_v4 — 48 vCPUs, 440 GB RAM, 2 GPUs (NVIDIA A100 80GB).
+ ///
+ public const string StandardNC48adsA100V4 = "Standard_NC48ads_A100_v4";
+ }
+
+ ///
+ /// Storage optimized VM sizes with high disk throughput and I/O.
+ ///
+ public static class StorageOptimized
+ {
+ ///
+ /// Standard_L8s_v3 — 8 vCPUs, 64 GB RAM, Premium SSD, high local NVMe storage.
+ ///
+ public const string StandardL8sV3 = "Standard_L8s_v3";
+
+ ///
+ /// Standard_L16s_v3 — 16 vCPUs, 128 GB RAM, Premium SSD, high local NVMe storage.
+ ///
+ public const string StandardL16sV3 = "Standard_L16s_v3";
+
+ ///
+ /// Standard_L32s_v3 — 32 vCPUs, 256 GB RAM, Premium SSD, high local NVMe storage.
+ ///
+ public const string StandardL32sV3 = "Standard_L32s_v3";
+ }
+
+ ///
+ /// Burstable VM sizes for cost-effective workloads with variable CPU usage.
+ ///
+ public static class Burstable
+ {
+ ///
+ /// Standard_B2s — 2 vCPUs, 4 GB RAM.
+ ///
+ public const string StandardB2s = "Standard_B2s";
+
+ ///
+ /// Standard_B4ms — 4 vCPUs, 16 GB RAM.
+ ///
+ public const string StandardB4ms = "Standard_B4ms";
+
+ ///
+ /// Standard_B8ms — 8 vCPUs, 32 GB RAM.
+ ///
+ public const string StandardB8ms = "Standard_B8ms";
+
+ ///
+ /// Standard_B2s_v2 — 2 vCPUs, 8 GB RAM.
+ ///
+ public const string StandardB2sV2 = "Standard_B2s_v2";
+
+ ///
+ /// Standard_B4s_v2 — 4 vCPUs, 16 GB RAM.
+ ///
+ public const string StandardB4sV2 = "Standard_B4s_v2";
+ }
+
+ ///
+ /// Arm-based VM sizes with high energy efficiency and price-performance.
+ ///
+ public static class Arm
+ {
+ ///
+ /// Standard_D2pds_v5 — 2 vCPUs, 8 GB RAM, Ampere Altra Arm processor.
+ ///
+ public const string StandardD2pdsV5 = "Standard_D2pds_v5";
+
+ ///
+ /// Standard_D4pds_v5 — 4 vCPUs, 16 GB RAM, Ampere Altra Arm processor.
+ ///
+ public const string StandardD4pdsV5 = "Standard_D4pds_v5";
+
+ ///
+ /// Standard_D8pds_v5 — 8 vCPUs, 32 GB RAM, Ampere Altra Arm processor.
+ ///
+ public const string StandardD8pdsV5 = "Standard_D8pds_v5";
+
+ ///
+ /// Standard_D16pds_v5 — 16 vCPUs, 64 GB RAM, Ampere Altra Arm processor.
+ ///
+ public const string StandardD16pdsV5 = "Standard_D16pds_v5";
+
+ ///
+ /// Standard_E2pds_v5 — 2 vCPUs, 16 GB RAM, Ampere Altra Arm processor.
+ ///
+ public const string StandardE2pdsV5 = "Standard_E2pds_v5";
+
+ ///
+ /// Standard_E4pds_v5 — 4 vCPUs, 32 GB RAM, Ampere Altra Arm processor.
+ ///
+ public const string StandardE4pdsV5 = "Standard_E4pds_v5";
+ }
+}
diff --git a/src/Aspire.Hosting.Azure.Kubernetes/AksSkuTier.cs b/src/Aspire.Hosting.Azure.Kubernetes/AksSkuTier.cs
new file mode 100644
index 00000000000..f2f10865ac4
--- /dev/null
+++ b/src/Aspire.Hosting.Azure.Kubernetes/AksSkuTier.cs
@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Aspire.Hosting.Azure.Kubernetes;
+
+///
+/// Specifies the SKU tier for an AKS cluster.
+///
+public enum AksSkuTier
+{
+ ///
+ /// Free tier with no SLA.
+ ///
+ Free,
+
+ ///
+ /// Standard tier with financially backed SLA.
+ ///
+ Standard,
+
+ ///
+ /// Premium tier with mission-critical features.
+ ///
+ Premium
+}
diff --git a/src/Aspire.Hosting.Azure.Kubernetes/AksSubnetAnnotation.cs b/src/Aspire.Hosting.Azure.Kubernetes/AksSubnetAnnotation.cs
new file mode 100644
index 00000000000..9bdf97dbee3
--- /dev/null
+++ b/src/Aspire.Hosting.Azure.Kubernetes/AksSubnetAnnotation.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Aspire.Hosting.ApplicationModel;
+
+namespace Aspire.Hosting.Azure.Kubernetes;
+
+///
+/// Annotation that stores a subnet ID reference for AKS VNet integration.
+/// Unlike DelegatedSubnetAnnotation, this does NOT add a service delegation
+/// to the subnet — AKS uses plain (non-delegated) subnets for node pools.
+///
+internal sealed class AksSubnetAnnotation(BicepOutputReference subnetId) : IResourceAnnotation
+{
+ ///
+ /// Gets the subnet ID output reference.
+ ///
+ public BicepOutputReference SubnetId { get; } = subnetId;
+}
diff --git a/src/Aspire.Hosting.Azure.Kubernetes/Aspire.Hosting.Azure.Kubernetes.csproj b/src/Aspire.Hosting.Azure.Kubernetes/Aspire.Hosting.Azure.Kubernetes.csproj
new file mode 100644
index 00000000000..e361ac7edb2
--- /dev/null
+++ b/src/Aspire.Hosting.Azure.Kubernetes/Aspire.Hosting.Azure.Kubernetes.csproj
@@ -0,0 +1,38 @@
+
+
+
+ $(DefaultTargetFramework)
+ true
+ true
+ true
+ false
+ aspire integration hosting azure kubernetes aks
+ Azure Kubernetes Service (AKS) resource types for Aspire.
+ $(SharedDir)Azure_256x.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Aspire.Hosting.Azure.Kubernetes/AzureKubernetesEnvironmentExtensions.cs b/src/Aspire.Hosting.Azure.Kubernetes/AzureKubernetesEnvironmentExtensions.cs
new file mode 100644
index 00000000000..2d82b90e387
--- /dev/null
+++ b/src/Aspire.Hosting.Azure.Kubernetes/AzureKubernetesEnvironmentExtensions.cs
@@ -0,0 +1,601 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable ASPIREPIPELINES001 // Pipeline step types used for push/deploy dependency wiring
+#pragma warning disable ASPIREAZURE001 // AzureEnvironmentResource.ProvisionInfrastructureStepName for pipeline ordering
+#pragma warning disable ASPIREAZURE003 // AzureSubnetResource used in WithSubnet extensions
+
+using Aspire.Hosting.ApplicationModel;
+using Aspire.Hosting.Azure;
+using Aspire.Hosting.Azure.Kubernetes;
+using Aspire.Hosting.Kubernetes;
+using Aspire.Hosting.Kubernetes.Extensions;
+using Aspire.Hosting.Lifecycle;
+using Aspire.Hosting.Pipelines;
+using Azure.Provisioning;
+using Azure.Provisioning.Authorization;
+using Azure.Provisioning.ContainerRegistry;
+using Azure.Provisioning.ContainerService;
+using Azure.Provisioning.Expressions;
+using Azure.Provisioning.Resources;
+using Azure.Provisioning.Roles;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Aspire.Hosting;
+
+///
+/// Provides extension methods for adding Azure Kubernetes Service (AKS) environments to the application model.
+///
+public static class AzureKubernetesEnvironmentExtensions
+{
+ ///
+ /// Adds an Azure Kubernetes Service (AKS) environment to the distributed application.
+ /// This provisions an AKS cluster and configures it as a Kubernetes compute environment.
+ ///
+ /// The .
+ /// The name of the AKS environment resource.
+ /// A reference to the .
+ ///
+ /// This method internally creates a Kubernetes environment for Helm-based deployment
+ /// and provisions an AKS cluster via Azure Bicep. It combines the functionality of
+ /// AddKubernetesEnvironment with Azure-specific provisioning.
+ ///
+ ///
+ ///
+ /// var aks = builder.AddAzureKubernetesEnvironment("aks");
+ ///
+ ///
+ [AspireExport(Description = "Adds an Azure Kubernetes Service environment resource")]
+ public static IResourceBuilder AddAzureKubernetesEnvironment(
+ this IDistributedApplicationBuilder builder,
+ [ResourceName] string name)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentException.ThrowIfNullOrEmpty(name);
+
+ // Set up Azure provisioning infrastructure
+ builder.AddAzureProvisioning();
+ builder.Services.Configure(
+ o => o.SupportsTargetedRoleAssignments = true);
+
+ // Register the AKS-specific infrastructure eventing subscriber
+ builder.Services.TryAddEventingSubscriber();
+
+ // Create the inner KubernetesEnvironmentResource via the public API.
+ // This registers KubernetesInfrastructure, creates the resource with
+ // Helm chart name/dashboard, adds it to the model, and sets up the
+ // default Helm deployment engine.
+ var k8sEnvBuilder = builder.AddKubernetesEnvironment($"{name}-k8s");
+
+ // Scope the Helm chart name to this AKS environment to avoid
+ // conflicts when multiple environments deploy to the same cluster
+ // or when re-deploying with different environment names.
+ k8sEnvBuilder.Resource.HelmChartName = $"{builder.Environment.ApplicationName}-{name}".ToHelmChartName();
+
+ // Create the unified AKS environment resource
+ var resource = new AzureKubernetesEnvironmentResource(name, ConfigureAksInfrastructure);
+ resource.KubernetesEnvironment = k8sEnvBuilder.Resource;
+
+ // Set the parent so KubernetesInfrastructure matches resources that use
+ // WithComputeEnvironment(aksEnv) — the inner K8s env checks both itself
+ // and its parent when filtering compute resources.
+ k8sEnvBuilder.Resource.OwningComputeEnvironment = resource;
+
+ if (builder.ExecutionContext.IsRunMode)
+ {
+ return builder.CreateResourceBuilder(resource);
+ }
+
+ // Auto-create a default Azure Container Registry for image push/pull.
+ // Wire it to the inner K8s environment immediately so KubernetesInfrastructure
+ // can discover it during BeforeStartEvent (both subscribers run during the same
+ // event, so we can't rely on annotation ordering during the event).
+ var defaultRegistry = builder.AddAzureContainerRegistry($"{name}-acr");
+ resource.DefaultContainerRegistry = defaultRegistry.Resource;
+ k8sEnvBuilder.WithAnnotation(new ContainerRegistryReferenceAnnotation(defaultRegistry.Resource));
+
+ // Wire ACR name as a parameter on the AKS resource so the Bicep module
+ // can create an AcrPull role assignment for the kubelet identity.
+ // The publishing context will wire this as a parameter in main.bicep.
+ resource.Parameters["acrName"] = defaultRegistry.Resource.NameOutputReference;
+
+ // Ensure push steps wait for ALL Azure provisioning to complete. Push steps
+ // call registry.Endpoint.GetValueAsync() which awaits the BicepOutputReference
+ // for loginServer — if the ACR hasn't been provisioned yet, this blocks.
+ //
+ // NOTE: The standard push step dependency wiring (pushSteps.DependsOn(buildSteps)
+ // and pushSteps.DependsOn(push-prereq)) from ProjectResource's PipelineConfigurationAnnotation
+ // may not resolve correctly when using Kubernetes compute environments, because
+ // context.GetSteps(resource, tag) may return empty if the resource reference doesn't
+ // match. We explicitly wire the dependencies here as a workaround.
+ k8sEnvBuilder.WithAnnotation(new PipelineConfigurationAnnotation(context =>
+ {
+ var pushSteps = context.Steps
+ .Where(s => s.Tags.Contains(WellKnownPipelineTags.PushContainerImage))
+ .ToList();
+
+ foreach (var pushStep in pushSteps)
+ {
+ // Ensure push waits for Azure provisioning (ACR endpoint resolution)
+ pushStep.DependsOn(AzureEnvironmentResource.ProvisionInfrastructureStepName);
+
+ // Ensure push waits for push-prereq (ACR login)
+ pushStep.DependsOn(WellKnownPipelineSteps.PushPrereq);
+
+ // Ensure push waits for its corresponding build step
+ var resourceName = pushStep.Resource?.Name;
+ if (resourceName is not null)
+ {
+ pushStep.DependsOn($"build-{resourceName}");
+ }
+ }
+ }));
+
+ return builder.AddResource(resource);
+ }
+
+ ///
+ /// Adds a node pool to the AKS cluster.
+ ///
+ /// The AKS environment resource builder.
+ /// The name of the node pool.
+ /// The VM size for nodes. Defaults to Standard_D2s_v5 if not specified.
+ /// The minimum node count for autoscaling. Defaults to 1.
+ /// The maximum node count for autoscaling. Defaults to 3.
+ /// A reference to the for the new node pool.
+ ///
+ /// The returned node pool resource can be passed to
+ /// on compute resources to schedule workloads on this pool.
+ ///
+ ///
+ ///
+ /// var aks = builder.AddAzureKubernetesEnvironment("aks");
+ ///
+ /// // With defaults (Standard_D2s_v5, 1-3 nodes)
+ /// var pool = aks.AddNodePool("workload");
+ ///
+ /// // With explicit VM size and scaling
+ /// var gpuPool = aks.AddNodePool("gpu", "Standard_NC6s_v3", 0, 5);
+ ///
+ ///
+ [AspireExport(Description = "Adds a node pool to the AKS cluster")]
+ public static IResourceBuilder AddNodePool(
+ this IResourceBuilder builder,
+ [ResourceName] string name,
+ string vmSize = "Standard_D2s_v5",
+ int minCount = 1,
+ int maxCount = 3)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentException.ThrowIfNullOrEmpty(name);
+ ArgumentException.ThrowIfNullOrEmpty(vmSize);
+ ArgumentOutOfRangeException.ThrowIfNegative(minCount);
+ ArgumentOutOfRangeException.ThrowIfNegative(maxCount);
+ ArgumentOutOfRangeException.ThrowIfGreaterThan(minCount, maxCount);
+
+ var config = new AksNodePoolConfig(name, vmSize, minCount, maxCount, AksNodePoolMode.User);
+ builder.Resource.NodePools.Add(config);
+
+ var nodePool = new AksNodePoolResource(name, config, builder.Resource);
+
+ if (builder.ApplicationBuilder.ExecutionContext.IsRunMode)
+ {
+ return builder.ApplicationBuilder.CreateResourceBuilder(nodePool);
+ }
+
+ return builder.ApplicationBuilder.AddResource(nodePool)
+ .ExcludeFromManifest();
+ }
+
+ ///
+ /// Configures the AKS cluster to use a VNet subnet for node pool networking.
+ /// Unlike , this does NOT
+ /// add a service delegation to the subnet — AKS uses plain (non-delegated) subnets.
+ ///
+ /// The AKS environment resource builder.
+ /// The subnet to use for AKS node pools.
+ /// A reference to the for chaining.
+ ///
+ ///
+ /// var vnet = builder.AddAzureVirtualNetwork("vnet", "10.0.0.0/16");
+ /// var subnet = vnet.AddSubnet("aks-subnet", "10.0.0.0/22");
+ /// var aks = builder.AddAzureKubernetesEnvironment("aks")
+ /// .WithSubnet(subnet);
+ ///
+ ///
+ [AspireExport(Description = "Configures the AKS cluster to use a VNet subnet")]
+ public static IResourceBuilder WithSubnet(
+ this IResourceBuilder builder,
+ IResourceBuilder subnet)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(subnet);
+
+ builder.WithAnnotation(new AksSubnetAnnotation(subnet.Resource.Id), ResourceAnnotationMutationBehavior.Replace);
+ return builder;
+ }
+
+ ///
+ /// Configures a specific AKS node pool to use its own VNet subnet.
+ /// When applied, this node pool's subnet overrides the environment-level subnet
+ /// set via .
+ ///
+ /// The node pool resource builder.
+ /// The subnet to use for this node pool.
+ /// A reference to the for chaining.
+ ///
+ ///
+ /// var vnet = builder.AddAzureVirtualNetwork("vnet", "10.0.0.0/16");
+ /// var defaultSubnet = vnet.AddSubnet("default", "10.0.0.0/22");
+ /// var gpuSubnet = vnet.AddSubnet("gpu-subnet", "10.0.4.0/24");
+ ///
+ /// var aks = builder.AddAzureKubernetesEnvironment("aks")
+ /// .WithSubnet(defaultSubnet);
+ ///
+ /// var gpuPool = aks.AddNodePool("gpu", AksNodeVmSizes.GpuAccelerated.StandardNC6sV3, 0, 5)
+ /// .WithSubnet(gpuSubnet);
+ ///
+ ///
+ [AspireExport("withAksNodePoolSubnet", Description = "Configures an AKS node pool to use a specific VNet subnet")]
+ public static IResourceBuilder WithSubnet(
+ this IResourceBuilder builder,
+ IResourceBuilder subnet)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(subnet);
+
+ // Store the subnet on the node pool annotation for Bicep resolution
+ builder.WithAnnotation(new AksSubnetAnnotation(subnet.Resource.Id));
+
+ // Also register in the parent AKS environment's per-pool subnet dictionary
+ // so Bicep generation can emit the correct parameter per pool.
+ builder.Resource.AksParent.NodePoolSubnets[builder.Resource.Name] = subnet.Resource.Id;
+
+ return builder;
+ }
+
+ ///
+ /// Configures the AKS environment to use a specific Azure Container Registry for image storage.
+ /// When set, this replaces the auto-created default container registry.
+ ///
+ /// The AKS environment resource builder.
+ /// The Azure Container Registry resource builder.
+ /// A reference to the for chaining.
+ ///
+ /// If not called, a default Azure Container Registry is automatically created.
+ /// The registry endpoint is flowed to the inner Kubernetes environment so that
+ /// Helm deployments can push and pull images.
+ ///
+ [AspireExport(Description = "Configures the AKS environment to use a specific container registry")]
+ public static IResourceBuilder WithContainerRegistry(
+ this IResourceBuilder builder,
+ IResourceBuilder registry)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(registry);
+
+ // Remove the default registry from the model if one was auto-created
+ if (builder.Resource.DefaultContainerRegistry is not null)
+ {
+ builder.ApplicationBuilder.Resources.Remove(builder.Resource.DefaultContainerRegistry);
+ builder.Resource.DefaultContainerRegistry = null;
+ }
+
+ // Set the explicit registry via annotation on both the AKS environment
+ // and the inner K8s environment (so KubernetesInfrastructure finds it)
+ builder.WithAnnotation(new ContainerRegistryReferenceAnnotation(registry.Resource));
+
+ // Remove any stale container registry annotations from the inner K8s environment
+ // before adding the new one (the default ACR annotation was added during
+ // AddAzureKubernetesEnvironment and now references a removed resource).
+ var staleAnnotations = builder.Resource.KubernetesEnvironment.Annotations
+ .OfType().ToList();
+ foreach (var old in staleAnnotations)
+ {
+ builder.Resource.KubernetesEnvironment.Annotations.Remove(old);
+ }
+
+ builder.Resource.KubernetesEnvironment.Annotations.Add(
+ new ContainerRegistryReferenceAnnotation(registry.Resource));
+
+ // Update the acrName parameter to reference the explicit registry's output
+ // (replaces the default ACR reference set during AddAzureKubernetesEnvironment)
+ builder.Resource.Parameters["acrName"] = registry.Resource.NameOutputReference;
+
+ return builder;
+ }
+
+ ///
+ /// Enables or disables workload identity on the AKS environment, allowing pods to authenticate
+ /// to Azure services using federated credentials.
+ ///
+ /// The resource builder.
+ /// true to enable workload identity (the default); false to disable it.
+ /// A reference to the for chaining.
+ ///
+ /// This ensures the AKS cluster is configured with OIDC issuer and workload identity enabled.
+ /// Workload identity is automatically wired when compute resources have an ,
+ /// which is added by WithAzureUserAssignedIdentity or auto-created by AzureResourcePreparer.
+ ///
+ [AspireExport(Description = "Enables workload identity on the AKS cluster")]
+ public static IResourceBuilder WithWorkloadIdentity(
+ this IResourceBuilder builder,
+ bool enabled = true)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+
+ builder.Resource.OidcIssuerEnabled = enabled;
+ builder.Resource.WorkloadIdentityEnabled = enabled;
+ return builder;
+ }
+
+ private static void ConfigureAksInfrastructure(AzureResourceInfrastructure infrastructure)
+ {
+ var aksResource = (AzureKubernetesEnvironmentResource)infrastructure.AspireResource;
+
+ var skuTier = aksResource.SkuTier switch
+ {
+ AksSkuTier.Free => ManagedClusterSkuTier.Free,
+ AksSkuTier.Standard => ManagedClusterSkuTier.Standard,
+ AksSkuTier.Premium => ManagedClusterSkuTier.Premium,
+ _ => ManagedClusterSkuTier.Free
+ };
+
+ // Create the AKS managed cluster
+ var aks = new ContainerServiceManagedCluster(aksResource.GetBicepIdentifier(),
+ ContainerServiceManagedCluster.ResourceVersions.V2025_03_01)
+ {
+ ClusterIdentity = new ManagedClusterIdentity
+ {
+ ResourceIdentityType = ManagedServiceIdentityType.SystemAssigned
+ },
+ Sku = new ManagedClusterSku
+ {
+ Name = ManagedClusterSkuName.Base,
+ Tier = skuTier
+ },
+ DnsPrefix = $"{aksResource.Name}-dns",
+ Tags = { { "aspire-resource-name", aksResource.Name } }
+ };
+
+ if (aksResource.KubernetesVersion is not null)
+ {
+ aks.KubernetesVersion = aksResource.KubernetesVersion;
+ }
+
+ // Agent pool profiles
+ var hasDefaultSubnet = aksResource.TryGetLastAnnotation(out var subnetAnnotation);
+ ProvisioningParameter? defaultSubnetParam = null;
+
+ if (hasDefaultSubnet)
+ {
+ defaultSubnetParam = new ProvisioningParameter("subnetId", typeof(string));
+ infrastructure.Add(defaultSubnetParam);
+ aksResource.Parameters["subnetId"] = subnetAnnotation!.SubnetId;
+ }
+
+ // Per-pool subnet parameters
+ var poolSubnetParams = new Dictionary();
+ foreach (var (poolName, poolSubnetRef) in aksResource.NodePoolSubnets)
+ {
+ var paramName = $"subnetId_{poolName}";
+ var param = new ProvisioningParameter(paramName, typeof(string));
+ infrastructure.Add(param);
+ poolSubnetParams[poolName] = param;
+ aksResource.Parameters[paramName] = poolSubnetRef;
+ }
+
+ foreach (var pool in aksResource.NodePools)
+ {
+ var mode = pool.Mode switch
+ {
+ AksNodePoolMode.System => AgentPoolMode.System,
+ AksNodePoolMode.User => AgentPoolMode.User,
+ _ => AgentPoolMode.User
+ };
+
+ var agentPool = new ManagedClusterAgentPoolProfile
+ {
+ Name = pool.Name,
+ VmSize = pool.VmSize,
+ MinCount = pool.MinCount,
+ MaxCount = pool.MaxCount,
+ Count = pool.MinCount,
+ EnableAutoScaling = true,
+ Mode = mode,
+ OSType = ContainerServiceOSType.Linux,
+ };
+
+ // Per-pool subnet override, else environment default
+ if (poolSubnetParams.TryGetValue(pool.Name, out var poolSubnetParam))
+ {
+ agentPool.VnetSubnetId = poolSubnetParam;
+ }
+ else if (defaultSubnetParam is not null)
+ {
+ agentPool.VnetSubnetId = defaultSubnetParam;
+ }
+
+ aks.AgentPoolProfiles.Add(agentPool);
+ }
+
+ // OIDC issuer
+ if (aksResource.OidcIssuerEnabled)
+ {
+ aks.OidcIssuerProfile = new ManagedClusterOidcIssuerProfile
+ {
+ IsEnabled = true
+ };
+ }
+
+ // Workload identity
+ if (aksResource.WorkloadIdentityEnabled)
+ {
+ aks.SecurityProfile = new ManagedClusterSecurityProfile
+ {
+ IsWorkloadIdentityEnabled = true
+ };
+ }
+
+ // Private cluster
+ if (aksResource.IsPrivateCluster)
+ {
+ aks.ApiServerAccessProfile = new ManagedClusterApiServerAccessProfile
+ {
+ EnablePrivateCluster = true
+ };
+ }
+
+ // Network profile
+ var hasSubnetConfig = hasDefaultSubnet || aksResource.NodePoolSubnets.Count > 0;
+ if (aksResource.NetworkProfile is not null)
+ {
+ aks.NetworkProfile = new ContainerServiceNetworkProfile
+ {
+ NetworkPlugin = aksResource.NetworkProfile.NetworkPlugin switch
+ {
+ "azure" => ContainerServiceNetworkPlugin.Azure,
+ "kubenet" => ContainerServiceNetworkPlugin.Kubenet,
+ _ => ContainerServiceNetworkPlugin.Azure
+ },
+ ServiceCidr = aksResource.NetworkProfile.ServiceCidr,
+ DnsServiceIP = aksResource.NetworkProfile.DnsServiceIP
+ };
+ if (aksResource.NetworkProfile.NetworkPolicy is not null)
+ {
+ aks.NetworkProfile.NetworkPolicy = aksResource.NetworkProfile.NetworkPolicy switch
+ {
+ "calico" => ContainerServiceNetworkPolicy.Calico,
+ "azure" => ContainerServiceNetworkPolicy.Azure,
+ _ => ContainerServiceNetworkPolicy.Calico
+ };
+ }
+ }
+ else if (hasSubnetConfig)
+ {
+ aks.NetworkProfile = new ContainerServiceNetworkProfile
+ {
+ NetworkPlugin = ContainerServiceNetworkPlugin.Azure,
+ };
+ }
+
+ infrastructure.Add(aks);
+
+ // ACR pull role assignment for kubelet identity
+ if (aksResource.DefaultContainerRegistry is not null || aksResource.TryGetLastAnnotation(out _))
+ {
+ var acrNameParam = new ProvisioningParameter("acrName", typeof(string));
+ infrastructure.Add(acrNameParam);
+
+ var acr = ContainerRegistryService.FromExisting("acr");
+ acr.Name = acrNameParam;
+ infrastructure.Add(acr);
+
+ // AcrPull role: 7f951dda-4ed3-4680-a7ca-43fe172d538d
+ var acrPullRoleId = BicepFunction.GetSubscriptionResourceId(
+ "Microsoft.Authorization/roleDefinitions",
+ "7f951dda-4ed3-4680-a7ca-43fe172d538d");
+
+ // Access kubelet identity objectId via property path
+ var kubeletObjectId = new MemberExpression(
+ new MemberExpression(
+ new MemberExpression(
+ new MemberExpression(
+ new IdentifierExpression(aks.BicepIdentifier),
+ "properties"),
+ "identityProfile"),
+ "kubeletidentity"),
+ "objectId");
+
+ var roleAssignment = new RoleAssignment("acrPullRole")
+ {
+ Name = BicepFunction.CreateGuid(acr.Id, aks.Id, acrPullRoleId),
+ Scope = new IdentifierExpression(acr.BicepIdentifier),
+ RoleDefinitionId = acrPullRoleId,
+ PrincipalId = kubeletObjectId,
+ PrincipalType = RoleManagementPrincipalType.ServicePrincipal
+ };
+ infrastructure.Add(roleAssignment);
+ }
+
+ // Outputs
+ infrastructure.Add(new ProvisioningOutput("id", typeof(string)) { Value = aks.Id });
+ infrastructure.Add(new ProvisioningOutput("name", typeof(string)) { Value = aks.Name });
+
+ // OIDC issuer URL and kubelet identity require property path expressions
+ var aksId = new IdentifierExpression(aks.BicepIdentifier);
+ infrastructure.Add(new ProvisioningOutput("clusterFqdn", typeof(string))
+ {
+ Value = new MemberExpression(new MemberExpression(aksId, "properties"), "fqdn")
+ });
+ // OIDC issuer URL and kubelet identity outputs are only valid when the
+ // corresponding features are enabled on the cluster.
+ if (aksResource.OidcIssuerEnabled)
+ {
+ infrastructure.Add(new ProvisioningOutput("oidcIssuerUrl", typeof(string))
+ {
+ Value = new MemberExpression(
+ new MemberExpression(new MemberExpression(aksId, "properties"), "oidcIssuerProfile"),
+ "issuerURL")
+ });
+ }
+
+ infrastructure.Add(new ProvisioningOutput("kubeletIdentityObjectId", typeof(string))
+ {
+ Value = new MemberExpression(
+ new MemberExpression(
+ new MemberExpression(new MemberExpression(aksId, "properties"), "identityProfile"),
+ "kubeletidentity"),
+ "objectId")
+ });
+ infrastructure.Add(new ProvisioningOutput("nodeResourceGroup", typeof(string))
+ {
+ Value = new MemberExpression(new MemberExpression(aksId, "properties"), "nodeResourceGroup")
+ });
+
+ // Federated identity credentials for workload identity
+ // Resolve the K8s namespace for the service account subject.
+ // If not explicitly configured, defaults to "default".
+ var k8sNamespace = "default";
+ if (aksResource.KubernetesEnvironment.TryGetLastAnnotation(out var nsAnnotation))
+ {
+ // Use the namespace expression's format string as the literal value.
+ // Dynamic (parameter-based) namespaces are not supported for federated
+ // credentials since Azure AD needs a fixed subject at provision time.
+ var nsFormat = nsAnnotation.Namespace.Format;
+ if (!string.IsNullOrEmpty(nsFormat) && !nsFormat.Contains('{'))
+ {
+ k8sNamespace = nsFormat;
+ }
+ }
+
+ foreach (var (resourceName, identityResource) in aksResource.WorkloadIdentities)
+ {
+ var saName = $"{resourceName}-sa";
+ var sanitizedName = Infrastructure.NormalizeBicepIdentifier(resourceName);
+ var identityParamName = $"identityName_{sanitizedName}";
+
+ var identityNameParam = new ProvisioningParameter(identityParamName, typeof(string));
+ infrastructure.Add(identityNameParam);
+ aksResource.Parameters[identityParamName] = identityResource.PrincipalName;
+
+ var existingIdentity = UserAssignedIdentity.FromExisting($"identity_{sanitizedName}");
+ existingIdentity.Name = identityNameParam;
+ infrastructure.Add(existingIdentity);
+
+ var fedCred = new FederatedIdentityCredential($"fedcred_{sanitizedName}")
+ {
+ Parent = existingIdentity,
+ Name = $"{resourceName}-fedcred",
+ IssuerUri = new MemberExpression(
+ new MemberExpression(
+ new MemberExpression(new IdentifierExpression(aks.BicepIdentifier), "properties"),
+ "oidcIssuerProfile"),
+ "issuerURL"),
+ Subject = $"system:serviceaccount:{k8sNamespace}:{saName}",
+ Audiences = { "api://AzureADTokenExchange" }
+ };
+ infrastructure.Add(fedCred);
+ }
+ }
+}
diff --git a/src/Aspire.Hosting.Azure.Kubernetes/AzureKubernetesEnvironmentResource.cs b/src/Aspire.Hosting.Azure.Kubernetes/AzureKubernetesEnvironmentResource.cs
new file mode 100644
index 00000000000..0cc80dd8fd5
--- /dev/null
+++ b/src/Aspire.Hosting.Azure.Kubernetes/AzureKubernetesEnvironmentResource.cs
@@ -0,0 +1,123 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable ASPIREAZURE003 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+
+using Aspire.Hosting.Azure.Kubernetes;
+using Aspire.Hosting.Kubernetes;
+
+namespace Aspire.Hosting.Azure;
+
+///
+/// Represents an Azure Kubernetes Service (AKS) environment resource that provisions
+/// an AKS cluster and serves as a compute environment for Kubernetes workloads.
+///
+/// The name of the resource.
+/// Callback to configure the Azure infrastructure.
+public class AzureKubernetesEnvironmentResource(
+ string name,
+ Action configureInfrastructure)
+ : AzureProvisioningResource(name, configureInfrastructure),
+ IAzureComputeEnvironmentResource,
+ IAzureNspAssociationTarget
+{
+
+ ///
+ /// Gets the underlying Kubernetes environment resource used for Helm-based deployment.
+ ///
+ internal KubernetesEnvironmentResource KubernetesEnvironment { get; set; } = default!;
+
+ ///
+ /// Gets the resource ID of the AKS cluster.
+ ///
+ public BicepOutputReference Id => new("id", this);
+
+ ///
+ /// Gets the fully qualified domain name of the AKS cluster.
+ ///
+ public BicepOutputReference ClusterFqdn => new("clusterFqdn", this);
+
+ ///
+ /// Gets the OIDC issuer URL for the AKS cluster, used for workload identity federation.
+ ///
+ public BicepOutputReference OidcIssuerUrl => new("oidcIssuerUrl", this);
+
+ ///
+ /// Gets the object ID of the kubelet managed identity.
+ ///
+ public BicepOutputReference KubeletIdentityObjectId => new("kubeletIdentityObjectId", this);
+
+ ///
+ /// Gets the name of the node resource group.
+ ///
+ public BicepOutputReference NodeResourceGroup => new("nodeResourceGroup", this);
+
+ ///
+ /// Gets the name output reference for the AKS cluster.
+ ///
+ public BicepOutputReference NameOutputReference => new("name", this);
+
+ ///
+ /// Gets or sets the Kubernetes version for the AKS cluster.
+ ///
+ internal string? KubernetesVersion { get; set; }
+
+ ///
+ /// Gets or sets the SKU tier for the AKS cluster.
+ ///
+ internal AksSkuTier SkuTier { get; set; } = AksSkuTier.Free;
+
+ ///
+ /// Gets or sets whether OIDC issuer is enabled on the cluster.
+ ///
+ internal bool OidcIssuerEnabled { get; set; } = true;
+
+ ///
+ /// Gets or sets whether workload identity is enabled on the cluster.
+ ///
+ internal bool WorkloadIdentityEnabled { get; set; } = true;
+
+ ///
+ /// Gets or sets the Log Analytics workspace resource for monitoring.
+ ///
+ internal AzureLogAnalyticsWorkspaceResource? LogAnalyticsWorkspace { get; set; }
+
+ ///
+ /// Gets or sets whether Container Insights is enabled.
+ ///
+ internal bool ContainerInsightsEnabled { get; set; }
+
+ ///
+ /// Gets the node pool configurations.
+ ///
+ internal List NodePools { get; } =
+ [
+ new AksNodePoolConfig("system", "Standard_D2s_v5", 1, 3, AksNodePoolMode.System)
+ ];
+
+ ///
+ /// Gets the per-node-pool subnet overrides. Key is the pool name.
+ ///
+ internal Dictionary NodePoolSubnets { get; } = [];
+
+ ///
+ /// Gets the workload identity mappings. Key is the resource name, value is the identity resource.
+ /// Used to generate federated identity credentials in Bicep.
+ ///
+ internal Dictionary WorkloadIdentities { get; } = [];
+
+ ///
+ /// Gets or sets the network profile for the AKS cluster.
+ ///
+ internal AksNetworkProfile? NetworkProfile { get; set; }
+
+ ///
+ /// Gets or sets whether the cluster should be private.
+ ///
+ internal bool IsPrivateCluster { get; set; }
+
+ ///
+ /// Gets or sets the default container registry auto-created for this AKS environment.
+ ///
+ internal AzureContainerRegistryResource? DefaultContainerRegistry { get; set; }
+}
diff --git a/src/Aspire.Hosting.Azure.Kubernetes/AzureKubernetesInfrastructure.cs b/src/Aspire.Hosting.Azure.Kubernetes/AzureKubernetesInfrastructure.cs
new file mode 100644
index 00000000000..7b4016b209e
--- /dev/null
+++ b/src/Aspire.Hosting.Azure.Kubernetes/AzureKubernetesInfrastructure.cs
@@ -0,0 +1,442 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable ASPIREPIPELINES001 // Pipeline step types used for push/deploy dependency wiring
+#pragma warning disable ASPIREAZURE001 // AzureEnvironmentResource.ProvisionInfrastructureStepName for pipeline ordering
+#pragma warning disable ASPIREFILESYSTEM001 // IFileSystemService/TempDirectory are experimental
+
+using System.Text;
+using System.Text.RegularExpressions;
+using Aspire.Hosting.ApplicationModel;
+using Aspire.Hosting.Dcp.Process;
+using Aspire.Hosting.Eventing;
+using Aspire.Hosting.Kubernetes;
+using Aspire.Hosting.Kubernetes.Resources;
+using Aspire.Hosting.Lifecycle;
+using Aspire.Hosting.Pipelines;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Aspire.Hosting.Azure.Kubernetes;
+
+///
+/// Infrastructure eventing subscriber that processes compute resources
+/// targeting an AKS environment.
+///
+internal sealed partial class AzureKubernetesInfrastructure(
+ ILogger logger)
+ : IDistributedApplicationEventingSubscriber
+{
+ ///
+ public Task SubscribeAsync(IDistributedApplicationEventing eventing, DistributedApplicationExecutionContext executionContext, CancellationToken cancellationToken)
+ {
+ if (!executionContext.IsRunMode)
+ {
+ eventing.Subscribe(OnBeforeStartAsync);
+ }
+
+ return Task.CompletedTask;
+ }
+
+ private Task OnBeforeStartAsync(BeforeStartEvent @event, CancellationToken cancellationToken = default)
+ {
+ var aksEnvironments = @event.Model.Resources
+ .OfType()
+ .ToArray();
+
+ if (aksEnvironments.Length == 0)
+ {
+ return Task.CompletedTask;
+ }
+
+ foreach (var environment in aksEnvironments)
+ {
+ logger.LogInformation("Processing AKS environment '{Name}'", environment.Name);
+
+ // Add a pipeline step to fetch AKS credentials into an isolated kubeconfig
+ // file. This runs after AKS is provisioned and before the Helm deploy.
+ AddGetCredentialsStep(environment);
+
+ // Ensure a default user node pool exists for workload scheduling.
+ // The system pool should only run system pods; application workloads
+ // need a user pool.
+ var defaultUserPool = EnsureDefaultUserNodePool(environment, @event.Model);
+
+ foreach (var r in @event.Model.GetComputeResources())
+ {
+ var resourceComputeEnvironment = r.GetComputeEnvironment();
+
+ // Check if this resource targets THIS AKS environment
+ if (resourceComputeEnvironment is not null && resourceComputeEnvironment != environment)
+ {
+ continue;
+ }
+
+ // If the resource has no explicit node pool affinity, assign it
+ // to the default user pool.
+ if (!r.TryGetLastAnnotation(out _) && defaultUserPool is not null)
+ {
+ r.Annotations.Add(new KubernetesNodePoolAnnotation(defaultUserPool));
+ }
+
+ // Wire workload identity: if the resource has an AppIdentityAnnotation
+ // (auto-created by AzureResourcePreparer or explicit via WithAzureUserAssignedIdentity),
+ // generate a ServiceAccount and wire the pod spec.
+ if (r.TryGetLastAnnotation(out var appIdentity))
+ {
+ // Ensure OIDC + workload identity are enabled on the cluster
+ environment.OidcIssuerEnabled = true;
+ environment.WorkloadIdentityEnabled = true;
+
+ var saName = $"{r.Name}-sa";
+ var identityClientId = appIdentity.IdentityResource.ClientId;
+
+ // Use KubernetesServiceCustomizationAnnotation to inject SA + pod spec changes
+ // during Helm chart generation.
+ r.Annotations.Add(new KubernetesServiceCustomizationAnnotation(kubeResource =>
+ {
+ // Create ServiceAccount with workload identity annotations
+ var serviceAccount = new ServiceAccountV1();
+ serviceAccount.Metadata.Name = saName;
+ serviceAccount.Metadata.Annotations["azure.workload.identity/client-id"] =
+ $"{{{{ .Values.parameters.{r.Name}.identityClientId }}}}";
+ serviceAccount.Metadata.Labels["azure.workload.identity/use"] = "true";
+ kubeResource.AdditionalResources.Add(serviceAccount);
+
+ // Add a placeholder parameter for the identity clientId
+ // so it appears in values.yaml under parameters..identityClientId.
+ // The actual value is resolved at deploy time via CapturedHelmValueProviders.
+ kubeResource.Parameters["identityClientId"] = new KubernetesResource.HelmValue(
+ $"{{{{ .Values.parameters.{r.Name}.identityClientId }}}}",
+ string.Empty);
+
+ // Set serviceAccountName on pod spec and add workload identity label
+ if (kubeResource.Workload?.PodTemplate is { } podTemplate)
+ {
+ if (podTemplate.Spec is { } podSpec)
+ {
+ podSpec.ServiceAccountName = saName;
+ }
+
+ // The workload identity webhook requires this label on the POD
+ // to inject AZURE_CLIENT_ID, token volume mounts, etc.
+ podTemplate.Metadata.Labels["azure.workload.identity/use"] = "true";
+ }
+ }));
+
+ // Wire the identity clientId as a deferred Helm value so it gets
+ // resolved from the Bicep output at deploy time. The SA annotation
+ // references {{ .Values.parameters..identityClientId }}.
+ if (identityClientId is IValueProvider clientIdProvider)
+ {
+ environment.KubernetesEnvironment.CapturedHelmValueProviders.Add(
+ new KubernetesEnvironmentResource.CapturedHelmValueProvider(
+ "parameters",
+ r.Name,
+ "identityClientId",
+ clientIdProvider));
+ }
+
+ // Store the identity reference for federated credential Bicep generation
+ environment.WorkloadIdentities[r.Name] = appIdentity.IdentityResource;
+ }
+ }
+ }
+
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// Ensures the AKS environment has at least one user node pool. If none exists,
+ /// creates a default "workload" user pool and adds it to the app model.
+ ///
+ private static AksNodePoolResource? EnsureDefaultUserNodePool(
+ AzureKubernetesEnvironmentResource environment,
+ DistributedApplicationModel appModel)
+ {
+ var hasUserPool = environment.NodePools.Any(p => p.Mode is AksNodePoolMode.User);
+
+ if (hasUserPool)
+ {
+ // Return the first user pool. Search the app model for the existing
+ // AksNodePoolResource so we use the same object identity as AddNodePool created.
+ var firstUserConfig = environment.NodePools.First(p => p.Mode is AksNodePoolMode.User);
+ return FindNodePoolResource(appModel, environment, firstUserConfig.Name);
+ }
+
+ // No user pool configured — create a default one and add it to the app model.
+ var defaultConfig = new AksNodePoolConfig("workload", "Standard_D2s_v5", 1, 3, AksNodePoolMode.User);
+ environment.NodePools.Add(defaultConfig);
+
+ var defaultPool = new AksNodePoolResource("workload", defaultConfig, environment);
+ defaultPool.Annotations.Add(ManifestPublishingCallbackAnnotation.Ignore);
+ appModel.Resources.Add(defaultPool);
+ return defaultPool;
+ }
+
+ ///
+ /// Finds an existing AksNodePoolResource in the app model by name,
+ /// or creates one if not found (for pools added via config but not via AddNodePool).
+ ///
+ private static AksNodePoolResource FindNodePoolResource(
+ DistributedApplicationModel appModel,
+ AzureKubernetesEnvironmentResource environment,
+ string poolName)
+ {
+ // Search the app model for an existing pool resource with matching name and parent
+ var existing = appModel.Resources
+ .OfType()
+ .FirstOrDefault(p => p.Name == poolName && p.AksParent == environment);
+
+ if (existing is not null)
+ {
+ return existing;
+ }
+
+ // Pool was added via NodePools config but not via AddNodePool — create the resource
+ var config = environment.NodePools.First(p => p.Name == poolName);
+ var pool = new AksNodePoolResource(poolName, config, environment);
+ pool.Annotations.Add(ManifestPublishingCallbackAnnotation.Ignore);
+ appModel.Resources.Add(pool);
+ return pool;
+ }
+
+ ///
+ /// Adds a pipeline step to the inner KubernetesEnvironmentResource that fetches
+ /// AKS cluster credentials into an isolated kubeconfig file after the AKS cluster
+ /// is provisioned via Bicep.
+ ///
+ private static void AddGetCredentialsStep(AzureKubernetesEnvironmentResource environment)
+ {
+ var k8sEnv = environment.KubernetesEnvironment;
+
+ k8sEnv.Annotations.Add(new PipelineStepAnnotation((_) =>
+ {
+ var step = new PipelineStep
+ {
+ Name = $"aks-get-credentials-{environment.Name}",
+ Description = $"Fetches AKS credentials for {environment.Name}",
+ Action = ctx => GetAksCredentialsAsync(ctx, environment)
+ };
+
+ // Run after ALL Azure infrastructure is provisioned (including the AKS cluster).
+ // This depends on the aggregation step that gates on all individual provision-* steps.
+ step.DependsOn(AzureEnvironmentResource.ProvisionInfrastructureStepName);
+
+ // Must complete before Helm prepare step
+ step.RequiredBy($"prepare-{k8sEnv.Name}");
+
+ return new[] { step };
+ }));
+ }
+
+ ///
+ /// Fetches AKS credentials into an isolated kubeconfig file using az aks get-credentials,
+ /// then sets the KubeConfigPath on the inner KubernetesEnvironmentResource so that
+ /// subsequent Helm and kubectl commands target the AKS cluster.
+ ///
+ private static async Task GetAksCredentialsAsync(
+ PipelineStepContext context,
+ AzureKubernetesEnvironmentResource environment)
+ {
+ var getCredsTask = await context.ReportingStep.CreateTaskAsync(
+ $"Fetching AKS credentials for {environment.Name}",
+ context.CancellationToken).ConfigureAwait(false);
+
+ await using (getCredsTask.ConfigureAwait(false))
+ {
+ try
+ {
+ // Get the actual provisioned cluster name from the Bicep output.
+ // The Azure.Provisioning SDK may add a unique suffix to the name
+ // (e.g., take('aks-${uniqueString(resourceGroup().id)}', 63)).
+ var clusterName = await environment.NameOutputReference.GetValueAsync(context.CancellationToken).ConfigureAwait(false)
+ ?? environment.Name;
+
+ var azPath = FindAzCli();
+
+ // Defense-in-depth: validate that values used as CLI arguments
+ // contain only expected characters (alphanumeric, hyphens, underscores, dots).
+ ValidateAzureResourceName(clusterName, "cluster name");
+
+ var resourceGroup = await GetResourceGroupAsync(azPath, clusterName, context)
+ .ConfigureAwait(false);
+
+ ValidateAzureResourceName(resourceGroup, "resource group");
+
+ // Fetch kubeconfig content to stdout using --file - to avoid az CLI
+ // writing credentials with potentially permissive file permissions.
+ // We then write the content ourselves to a temp file with controlled access.
+ var fileSystemService = context.Services.GetRequiredService();
+ var kubeConfigDir = fileSystemService.TempDirectory.CreateTempSubdirectory("aspire-aks");
+ var kubeConfigPath = Path.Combine(kubeConfigDir.Path, "kubeconfig");
+
+ context.Logger.LogInformation(
+ "Fetching AKS credentials: cluster={ClusterName}, resourceGroup={ResourceGroup}",
+ clusterName, resourceGroup);
+
+ var result = await RunAzCommandAsync(
+ azPath,
+ $"aks get-credentials --resource-group \"{resourceGroup}\" --name \"{clusterName}\" --file -",
+ context.Logger).ConfigureAwait(false);
+
+ if (result.ExitCode != 0)
+ {
+ throw new InvalidOperationException(
+ $"az aks get-credentials failed (exit code {result.ExitCode}): {result.StandardError}");
+ }
+
+ // Write kubeconfig content to a temp file we control.
+ // The IFileSystemService temp directory is auto-cleaned on dispose.
+ await File.WriteAllTextAsync(kubeConfigPath, result.StandardOutput, context.CancellationToken).ConfigureAwait(false);
+
+ // On Unix, restrict file permissions to owner-only (0600)
+ if (!OperatingSystem.IsWindows())
+ {
+ File.SetUnixFileMode(kubeConfigPath, UnixFileMode.UserRead | UnixFileMode.UserWrite);
+ }
+
+ // Set the kubeconfig path on the inner K8s environment so
+ // Helm and kubectl commands use --kubeconfig to target this cluster
+ environment.KubernetesEnvironment.KubeConfigPath = kubeConfigPath;
+
+ context.Logger.LogInformation(
+ "AKS credentials written to {KubeConfigPath}", kubeConfigPath);
+
+ // Add AKS connection info to the pipeline summary
+ context.Summary.Add(
+ "☸ AKS Cluster",
+ new MarkdownString($"**{clusterName}** in resource group **{resourceGroup}**"));
+
+ context.Summary.Add(
+ "🔑 Connect to cluster",
+ new MarkdownString($"`az aks get-credentials --resource-group {resourceGroup} --name {clusterName}`"));
+
+ await getCredsTask.SucceedAsync(
+ $"AKS credentials fetched for cluster {clusterName}",
+ context.CancellationToken).ConfigureAwait(false);
+ }
+ catch (Exception ex) when (ex is not OperationCanceledException)
+ {
+ await getCredsTask.FailAsync(
+ $"Failed to fetch AKS credentials: {ex.Message}",
+ context.CancellationToken).ConfigureAwait(false);
+ throw;
+ }
+ }
+ }
+
+ private static string FindAzCli()
+ {
+ var azPath = PathLookupHelper.FindFullPathFromPath("az");
+ if (azPath is null)
+ {
+ throw new InvalidOperationException(
+ "Azure CLI (az) not found. Install it from https://learn.microsoft.com/cli/azure/install-azure-cli");
+ }
+ return azPath;
+ }
+
+ ///
+ /// Gets the resource group, trying deployment state first, falling back to az CLI query.
+ /// On first deploy, the deployment state may not be loaded into IConfiguration yet
+ /// because it's written during the pipeline run (after create-provisioning-context).
+ ///
+ private static async Task GetResourceGroupAsync(
+ string azPath,
+ string clusterName,
+ PipelineStepContext context)
+ {
+ // Try deployment state first (works on re-deploys)
+ var configuration = context.Services.GetRequiredService();
+ var resourceGroup = configuration["Azure:ResourceGroup"];
+
+ if (!string.IsNullOrEmpty(resourceGroup))
+ {
+ return resourceGroup;
+ }
+
+ // Fallback for first deploy: query Azure directly
+ context.Logger.LogDebug(
+ "Resource group not in deployment state, querying Azure for cluster '{ClusterName}'",
+ clusterName);
+
+ var result = await RunAzCommandAsync(
+ azPath,
+ $"resource list --resource-type Microsoft.ContainerService/managedClusters --name \"{clusterName}\" --query [0].resourceGroup -o tsv",
+ context.Logger).ConfigureAwait(false);
+
+ if (result.ExitCode != 0)
+ {
+ throw new InvalidOperationException(
+ $"az resource list failed (exit code {result.ExitCode}): {result.StandardError}");
+ }
+
+ resourceGroup = result.StandardOutput.Trim().ReplaceLineEndings("").Trim();
+
+ if (string.IsNullOrEmpty(resourceGroup))
+ {
+ throw new InvalidOperationException(
+ $"Could not resolve resource group for AKS cluster '{clusterName}'. " +
+ "Ensure Azure provisioning has completed.");
+ }
+
+ return resourceGroup;
+ }
+
+ ///
+ /// Runs an az CLI command using the shared ProcessSpec/ProcessUtil infrastructure.
+ /// Returns the captured stdout, stderr, and exit code.
+ ///
+ private static async Task RunAzCommandAsync(
+ string azPath,
+ string arguments,
+ ILogger logger)
+ {
+ var stdout = new StringBuilder();
+ var stderr = new StringBuilder();
+
+ var spec = new ProcessSpec(azPath)
+ {
+ Arguments = arguments,
+ OnOutputData = data => stdout.AppendLine(data),
+ OnErrorData = data => stderr.AppendLine(data),
+ ThrowOnNonZeroReturnCode = false
+ };
+
+ logger.LogDebug("Running: {AzPath} {Arguments}", azPath, arguments);
+
+ var (task, disposable) = ProcessUtil.Run(spec);
+
+ try
+ {
+ var result = await task.ConfigureAwait(false);
+ return new AzCommandResult(result.ExitCode, stdout.ToString(), stderr.ToString());
+ }
+ finally
+ {
+ await disposable.DisposeAsync().ConfigureAwait(false);
+ }
+ }
+
+ private sealed record AzCommandResult(int ExitCode, string StandardOutput, string StandardError);
+
+ ///
+ /// Validates that an Azure resource name contains only expected characters.
+ /// Azure resource names and resource group names allow alphanumeric, hyphens,
+ /// underscores, parentheses, and dots.
+ ///
+ private static void ValidateAzureResourceName(string value, string parameterDescription)
+ {
+ if (!AzureResourceNamePattern().IsMatch(value))
+ {
+ throw new InvalidOperationException(
+ $"The {parameterDescription} '{value}' contains unexpected characters. " +
+ $"Expected only alphanumeric characters, hyphens, underscores, parentheses, and dots.");
+ }
+ }
+
+ [GeneratedRegex(@"^[a-zA-Z0-9\-_\.\(\)]+$")]
+ private static partial Regex AzureResourceNamePattern();
+}
diff --git a/src/Aspire.Hosting.Azure.Kubernetes/README.md b/src/Aspire.Hosting.Azure.Kubernetes/README.md
new file mode 100644
index 00000000000..c31a66af4d0
--- /dev/null
+++ b/src/Aspire.Hosting.Azure.Kubernetes/README.md
@@ -0,0 +1,36 @@
+# Aspire.Hosting.Azure.Kubernetes library
+
+Provides extension methods and resource definitions for an Aspire AppHost to configure an Azure Kubernetes Service (AKS) environment.
+
+## Getting started
+
+### Prerequisites
+
+- An Azure subscription - [create one for free](https://azure.microsoft.com/free/)
+
+### Install the package
+
+In your AppHost project, install the Aspire Azure Kubernetes Hosting library with [NuGet](https://www.nuget.org):
+
+```dotnetcli
+dotnet add package Aspire.Hosting.Azure.Kubernetes
+```
+
+## Usage example
+
+Then, in the _AppHost.cs_ file of `AppHost`, add an AKS environment and deploy services to it:
+
+```csharp
+var aks = builder.AddAzureKubernetesEnvironment("aks");
+
+var myService = builder.AddProject()
+ .WithComputeEnvironment(aks);
+```
+
+## Additional documentation
+
+* https://learn.microsoft.com/azure/aks/
+
+## Feedback & contributing
+
+https://github.com/microsoft/aspire
diff --git a/src/Aspire.Hosting.Azure.Kubernetes/tools/GenVmSizes.cs b/src/Aspire.Hosting.Azure.Kubernetes/tools/GenVmSizes.cs
new file mode 100644
index 00000000000..fb45d20a0c3
--- /dev/null
+++ b/src/Aspire.Hosting.Azure.Kubernetes/tools/GenVmSizes.cs
@@ -0,0 +1,344 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#:property PublishAot=false
+
+using System.Diagnostics;
+using System.Globalization;
+using System.Text;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Text.RegularExpressions;
+
+// Fetch VM sizes from Azure REST API using the 'az' CLI
+var subscriptionId = Environment.GetEnvironmentVariable("AZURE_SUBSCRIPTION_ID");
+if (string.IsNullOrWhiteSpace(subscriptionId))
+{
+ // Try to get default subscription from az CLI
+ subscriptionId = await RunAzCommand("account show --query id -o tsv").ConfigureAwait(false);
+ subscriptionId = subscriptionId?.Trim();
+}
+
+if (string.IsNullOrWhiteSpace(subscriptionId))
+{
+ Console.Error.WriteLine("Error: No Azure subscription found. Set AZURE_SUBSCRIPTION_ID or run 'az login'.");
+ return 1;
+}
+
+Console.WriteLine($"Using subscription: {subscriptionId}");
+
+// Query all US regions for VM SKUs to build a comprehensive unified list.
+// Different regions offer different VM sizes, so querying multiple regions
+// ensures we capture the full set available across the US.
+string[] usRegions = [
+ "eastus", "eastus2", "centralus", "northcentralus", "southcentralus",
+ "westus", "westus2", "westus3", "westcentralus"
+];
+
+var allSkus = new List();
+foreach (var region in usRegions)
+{
+ Console.WriteLine($"Querying VM SKUs for {region}...");
+ var url = $"https://management.azure.com/subscriptions/{subscriptionId}/providers/Microsoft.Compute/skus?api-version=2021-07-01&$filter=location eq '{region}'";
+ var json = await RunAzCommand($"rest --method get --url \"{url}\"").ConfigureAwait(false);
+
+ if (string.IsNullOrWhiteSpace(json))
+ {
+ Console.Error.WriteLine($"Warning: Failed to fetch VM SKUs for {region}, skipping.");
+ continue;
+ }
+
+ var skuResponse = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
+ if (skuResponse?.Value is not null)
+ {
+ allSkus.AddRange(skuResponse.Value);
+ Console.WriteLine($" Found {skuResponse.Value.Count} SKUs in {region}");
+ }
+}
+
+if (allSkus.Count == 0)
+{
+ Console.Error.WriteLine("Error: Failed to fetch VM SKUs from any US region.");
+ return 1;
+}
+
+// Filter to virtualMachines, deduplicate by name (keep first occurrence for capability data)
+var vmSkus = allSkus
+ .Where(s => s.ResourceType == "virtualMachines" && !string.IsNullOrEmpty(s.Name))
+ .GroupBy(s => s.Name)
+ .Select(g => g.First())
+ .Select(s => new VmSizeInfo
+ {
+ Name = s.Name!,
+ Family = s.Family ?? "Other",
+ VCpus = s.GetCapabilityValue("vCPUs"),
+ MemoryGB = s.GetCapabilityValue("MemoryGB"),
+ MaxDataDiskCount = s.GetCapabilityValue("MaxDataDiskCount"),
+ PremiumIO = s.GetCapabilityBool("PremiumIO"),
+ AcceleratedNetworking = s.GetCapabilityBool("AcceleratedNetworkingEnabled"),
+ GpuCount = s.GetCapabilityValue("GPUs"),
+ })
+ .DistinctBy(s => s.Name)
+ .OrderBy(s => s.Family, StringComparer.OrdinalIgnoreCase)
+ .ThenBy(s => s.Name, StringComparer.OrdinalIgnoreCase)
+ .ToList();
+
+Console.WriteLine($"Found {vmSkus.Count} VM sizes");
+
+var code = VmSizeClassGenerator.GenerateCode("Aspire.Hosting.Azure.Kubernetes", vmSkus);
+File.WriteAllText(Path.Combine("..", "AksNodeVmSizes.Generated.cs"), code);
+Console.WriteLine($"Generated AksNodeVmSizes.Generated.cs with {vmSkus.Count} VM sizes");
+
+return 0;
+
+static async Task RunAzCommand(string arguments)
+{
+ var psi = new ProcessStartInfo
+ {
+ FileName = "az",
+ Arguments = arguments,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ };
+
+ using var process = Process.Start(psi);
+ if (process is null)
+ {
+ return null;
+ }
+
+ // Read stdout and stderr concurrently to avoid deadlock when
+ // the process fills the stderr pipe buffer.
+ var stdoutTask = process.StandardOutput.ReadToEndAsync();
+ var stderrTask = process.StandardError.ReadToEndAsync();
+
+ await process.WaitForExitAsync().ConfigureAwait(false);
+
+ var output = await stdoutTask.ConfigureAwait(false);
+ var stderr = await stderrTask.ConfigureAwait(false);
+
+ if (process.ExitCode != 0 && !string.IsNullOrWhiteSpace(stderr))
+ {
+ Console.Error.WriteLine($"az {arguments}: {stderr.Trim()}");
+ }
+
+ return process.ExitCode == 0 ? output : null;
+}
+
+public sealed class SkuResponse
+{
+ [JsonPropertyName("value")]
+ public List? Value { get; set; }
+}
+
+public sealed class ResourceSku
+{
+ [JsonPropertyName("resourceType")]
+ public string? ResourceType { get; set; }
+
+ [JsonPropertyName("name")]
+ public string? Name { get; set; }
+
+ [JsonPropertyName("tier")]
+ public string? Tier { get; set; }
+
+ [JsonPropertyName("size")]
+ public string? Size { get; set; }
+
+ [JsonPropertyName("family")]
+ public string? Family { get; set; }
+
+ [JsonPropertyName("capabilities")]
+ public List? Capabilities { get; set; }
+
+ public string? GetCapabilityValue(string name)
+ {
+ return Capabilities?.FirstOrDefault(c => c.Name == name)?.Value;
+ }
+
+ public bool GetCapabilityBool(string name)
+ {
+ var value = GetCapabilityValue(name);
+ return string.Equals(value, "True", StringComparison.OrdinalIgnoreCase);
+ }
+}
+
+public sealed class SkuCapability
+{
+ [JsonPropertyName("name")]
+ public string? Name { get; set; }
+
+ [JsonPropertyName("value")]
+ public string? Value { get; set; }
+}
+
+public sealed class VmSizeInfo
+{
+ public string Name { get; set; } = "";
+ public string Family { get; set; } = "";
+ public string? VCpus { get; set; }
+ public string? MemoryGB { get; set; }
+ public string? MaxDataDiskCount { get; set; }
+ public bool PremiumIO { get; set; }
+ public bool AcceleratedNetworking { get; set; }
+ public string? GpuCount { get; set; }
+}
+
+internal static partial class VmSizeClassGenerator
+{
+ public static string GenerateCode(string ns, List sizes)
+ {
+ var sb = new StringBuilder();
+ sb.AppendLine("// Licensed to the .NET Foundation under one or more agreements.");
+ sb.AppendLine("// The .NET Foundation licenses this file to you under the MIT license.");
+ sb.AppendLine();
+ sb.AppendLine("// ");
+ sb.AppendLine("// This file is generated by the GenVmSizes tool. Do not edit manually.");
+ sb.AppendLine();
+ sb.AppendLine(CultureInfo.InvariantCulture, $"namespace {ns};");
+ sb.AppendLine();
+ sb.AppendLine("/// ");
+ sb.AppendLine("/// Provides well-known Azure VM size constants for use with AKS node pools.");
+ sb.AppendLine("/// ");
+ sb.AppendLine("/// ");
+ sb.AppendLine("/// This class is auto-generated from Azure Resource SKUs across all US regions.");
+ sb.AppendLine("/// To update, run the GenVmSizes tool:");
+ sb.AppendLine("/// dotnet run --project src/Aspire.Hosting.Azure.Kubernetes/tools GenVmSizes.cs");
+ sb.AppendLine("/// VM size availability varies by region. This list is a union of sizes available");
+ sb.AppendLine("/// across eastus, eastus2, centralus, northcentralus, southcentralus, westus, westus2,");
+ sb.AppendLine("/// westus3, and westcentralus. Not all sizes may be available in every region.");
+ sb.AppendLine("/// ");
+ sb.AppendLine("public static partial class AksNodeVmSizes");
+ sb.AppendLine("{");
+
+ var groups = sizes.GroupBy(s => s.Family)
+ .OrderBy(g => g.Key, StringComparer.OrdinalIgnoreCase);
+
+ var firstClass = true;
+ foreach (var group in groups)
+ {
+ if (!firstClass)
+ {
+ sb.AppendLine();
+ }
+ firstClass = false;
+
+ var className = FamilyToClassName(group.Key);
+
+ sb.AppendLine(" /// ");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" /// VM sizes in the {EscapeXml(group.Key)} family.");
+ sb.AppendLine(" /// ");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" public static class {className}");
+ sb.AppendLine(" {");
+
+ var firstField = true;
+ foreach (var size in group.OrderBy(s => s.Name, StringComparer.OrdinalIgnoreCase))
+ {
+ if (!firstField)
+ {
+ sb.AppendLine();
+ }
+ firstField = false;
+
+ var fieldName = VmSizeToFieldName(size.Name);
+ var description = BuildDescription(size);
+
+ sb.AppendLine(" /// ");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" /// {EscapeXml(description)}");
+ sb.AppendLine(" /// ");
+ sb.AppendLine(CultureInfo.InvariantCulture, $" public const string {fieldName} = \"{size.Name}\";");
+ }
+
+ sb.AppendLine(" }");
+ }
+
+ sb.AppendLine("}");
+ return sb.ToString();
+ }
+
+ private static string BuildDescription(VmSizeInfo size)
+ {
+ var parts = new List { size.Name };
+ if (size.VCpus is not null)
+ {
+ parts.Add($"{size.VCpus} vCPUs");
+ }
+ if (size.MemoryGB is not null)
+ {
+ parts.Add($"{size.MemoryGB} GB RAM");
+ }
+ if (size.GpuCount is not null && size.GpuCount != "0")
+ {
+ parts.Add($"{size.GpuCount} GPU(s)");
+ }
+ if (size.PremiumIO)
+ {
+ parts.Add("Premium SSD");
+ }
+ return string.Join(" — ", parts);
+ }
+
+ private static string FamilyToClassName(string family)
+ {
+ // Convert family names like "standardDSv2Family" to "StandardDSv2"
+ var name = family.Replace("Family", "", StringComparison.OrdinalIgnoreCase)
+ .Replace("_", "");
+
+ if (name.Length > 0)
+ {
+ name = char.ToUpperInvariant(name[0]) + name[1..];
+ }
+
+ // Clean non-identifier chars
+ return CleanIdentifier(name);
+ }
+
+ private static string VmSizeToFieldName(string vmSize)
+ {
+ // "Standard_D4s_v5" → "StandardD4sV5"
+ var parts = vmSize.Split('_');
+ var sb = new StringBuilder();
+ foreach (var part in parts)
+ {
+ if (part.Length > 0)
+ {
+ sb.Append(char.ToUpperInvariant(part[0]));
+ if (part.Length > 1)
+ {
+ sb.Append(part[1..]);
+ }
+ }
+ }
+ return CleanIdentifier(sb.ToString());
+ }
+
+ private static string CleanIdentifier(string name)
+ {
+ var sb = new StringBuilder();
+ foreach (var c in name)
+ {
+ if (char.IsLetterOrDigit(c))
+ {
+ sb.Append(c);
+ }
+ }
+
+ var result = sb.ToString();
+
+ // Ensure doesn't start with a digit
+ if (result.Length > 0 && char.IsDigit(result[0]))
+ {
+ result = "_" + result;
+ }
+
+ return result;
+ }
+
+ private static string EscapeXml(string s) =>
+ s.Replace("&", "&")
+ .Replace("<", "<")
+ .Replace(">", ">")
+ .Replace("\"", """)
+ .Replace("'", "'");
+}
diff --git a/src/Aspire.Hosting.Kubernetes/Aspire.Hosting.Kubernetes.csproj b/src/Aspire.Hosting.Kubernetes/Aspire.Hosting.Kubernetes.csproj
index de2d534e056..04e87db475a 100644
--- a/src/Aspire.Hosting.Kubernetes/Aspire.Hosting.Kubernetes.csproj
+++ b/src/Aspire.Hosting.Kubernetes/Aspire.Hosting.Kubernetes.csproj
@@ -19,6 +19,8 @@
+
+
diff --git a/src/Aspire.Hosting.Kubernetes/Deployment/HelmDeploymentEngine.cs b/src/Aspire.Hosting.Kubernetes/Deployment/HelmDeploymentEngine.cs
index ed04194be43..cf8c380151f 100644
--- a/src/Aspire.Hosting.Kubernetes/Deployment/HelmDeploymentEngine.cs
+++ b/src/Aspire.Hosting.Kubernetes/Deployment/HelmDeploymentEngine.cs
@@ -120,6 +120,27 @@ internal static Task> CreateStepsAsync(
var model = factoryContext.PipelineContext.Model;
var steps = new List();
+ // Step 0: Check prerequisites — verify Helm CLI is available
+ var checkPrereqStep = new PipelineStep
+ {
+ Name = $"check-helm-prereqs-{environment.Name}",
+ Description = $"Verifies Helm CLI is available for {environment.Name}.",
+ Action = ctx =>
+ {
+ var helmPath = PathLookupHelper.FindFullPathFromPath("helm");
+ if (helmPath is null)
+ {
+ throw new InvalidOperationException(
+ "Helm CLI not found. Install it from https://helm.sh/docs/intro/install/ " +
+ "and ensure it is available on your PATH.");
+ }
+
+ ctx.Logger.LogDebug("Helm CLI found at: {HelmPath}", helmPath);
+ return Task.CompletedTask;
+ }
+ };
+ steps.Add(checkPrereqStep);
+
// Step 1: Prepare - resolve values.yaml with actual image references and parameter values
var prepareStep = new PipelineStep
{
@@ -129,6 +150,7 @@ internal static Task> CreateStepsAsync(
};
prepareStep.DependsOn(WellKnownPipelineSteps.Publish);
prepareStep.DependsOn(WellKnownPipelineSteps.Build);
+ prepareStep.DependsOn($"check-helm-prereqs-{environment.Name}");
steps.Add(prepareStep);
// Step 2: Helm deploy - run helm upgrade --install
@@ -180,7 +202,7 @@ await ctx.ReportingStep.CompleteAsync(
// Use saved state for the confirmation message (more accurate than recomputing)
var @namespace = savedNamespace ?? "default";
await ConfirmDestroyAsync(ctx, $"Uninstall Helm release '{savedReleaseName}' from namespace '{@namespace}'? This action cannot be undone.").ConfigureAwait(false);
- await HelmUninstallAsync(ctx, savedReleaseName, @namespace).ConfigureAwait(false);
+ await HelmUninstallAsync(ctx, environment, savedReleaseName, @namespace).ConfigureAwait(false);
ctx.Summary.Add("🗑️ Helm Release", savedReleaseName);
ctx.Summary.Add("☸️ Namespace", @namespace);
@@ -283,7 +305,8 @@ internal static async Task ResolveAndWriteDeployValuesAsync(
{
if (environment.CapturedHelmValues.Count == 0
&& environment.CapturedHelmCrossReferences.Count == 0
- && environment.CapturedHelmImageReferences.Count == 0)
+ && environment.CapturedHelmImageReferences.Count == 0
+ && environment.CapturedHelmValueProviders.Count == 0)
{
return;
}
@@ -329,6 +352,20 @@ internal static async Task ResolveAndWriteDeployValuesAsync(
}
}
+ // Phase 4: Resolve generic IValueProvider references.
+ // During publish, values backed by IValueProvider (e.g., Bicep output references,
+ // connection strings) are written as empty placeholders. At deploy time, we call
+ // GetValueAsync() to resolve the actual values from external sources.
+ // This is cloud-provider agnostic — any IValueProvider implementation works.
+ foreach (var valueProviderRef in environment.CapturedHelmValueProviders)
+ {
+ var resolvedValue = await valueProviderRef.ValueProvider.GetValueAsync(cancellationToken).ConfigureAwait(false);
+ if (resolvedValue is not null)
+ {
+ SetOverrideValue(overrideValues, valueProviderRef.Section, valueProviderRef.ResourceKey, valueProviderRef.ValueKey, resolvedValue);
+ }
+ }
+
if (overrideValues.Count > 0)
{
var serializer = new YamlDotNet.Serialization.SerializerBuilder()
@@ -424,6 +461,11 @@ private static async Task HelmDeployAsync(PipelineStepContext context, Kubernete
arguments.Append(" --create-namespace");
arguments.Append(" --wait");
+ if (environment.KubeConfigPath is not null)
+ {
+ arguments.Append(CultureInfo.InvariantCulture, $" --kubeconfig \"{environment.KubeConfigPath}\"");
+ }
+
if (File.Exists(valuesFilePath))
{
arguments.Append(CultureInfo.InvariantCulture, $" -f \"{valuesFilePath}\"");
@@ -501,7 +543,7 @@ internal static async Task PrintResourceSummaryAsync(
try
{
- var endpoints = await GetServiceEndpointsAsync(computeResource.Name.ToServiceName(), @namespace, context.Logger, context.CancellationToken).ConfigureAwait(false);
+ var endpoints = await GetServiceEndpointsAsync(computeResource.Name.ToServiceName(), @namespace, environment.KubeConfigPath, context.Logger, context.CancellationToken).ConfigureAwait(false);
if (endpoints.Count > 0)
{
@@ -534,6 +576,11 @@ private static async Task PrintDeploymentInstructionsAsync(
context.Summary.Add(
"📊 Dashboard",
new MarkdownString($"`kubectl port-forward -n {@namespace} svc/{dashboardServiceName} 18888:18888` then open [http://localhost:18888](http://localhost:18888)"));
+
+ var dashboardDeploymentName = environment.Dashboard.Resource.Name.ToKubernetesResourceName();
+ context.Summary.Add(
+ "🔑 Dashboard login",
+ new MarkdownString($"`kubectl logs -n {@namespace} -l app.kubernetes.io/component={dashboardDeploymentName} --tail=50` to retrieve the login token"));
}
// Helm status and resource inspection
@@ -555,10 +602,10 @@ private static async Task HelmUninstallAsync(PipelineStepContext context, Kubern
{
var @namespace = await ResolveNamespaceAsync(context, environment).ConfigureAwait(false);
var releaseName = await ResolveReleaseNameAsync(context, environment).ConfigureAwait(false);
- await HelmUninstallAsync(context, releaseName, @namespace).ConfigureAwait(false);
+ await HelmUninstallAsync(context, environment, releaseName, @namespace).ConfigureAwait(false);
}
- private static async Task HelmUninstallAsync(PipelineStepContext context, string releaseName, string @namespace)
+ private static async Task HelmUninstallAsync(PipelineStepContext context, KubernetesEnvironmentResource environment, string releaseName, string @namespace)
{
var uninstallTask = await context.ReportingStep.CreateTaskAsync(
new MarkdownString($"Uninstalling Helm release **{releaseName}** from namespace **{@namespace}**"),
@@ -571,6 +618,11 @@ private static async Task HelmUninstallAsync(PipelineStepContext context, string
var helmRunner = context.Services.GetRequiredService();
var arguments = $"uninstall {releaseName} --namespace {@namespace}";
+ if (environment.KubeConfigPath is not null)
+ {
+ arguments += $" --kubeconfig \"{environment.KubeConfigPath}\"";
+ }
+
context.Logger.LogDebug("Running helm {Arguments}", arguments);
var exitCode = await helmRunner.RunAsync(
@@ -640,12 +692,18 @@ private static async Task ConfirmDestroyAsync(PipelineStepContext context, strin
private static async Task> GetServiceEndpointsAsync(
string serviceName,
string @namespace,
+ string? kubeConfigPath,
ILogger logger,
CancellationToken cancellationToken)
{
var endpoints = new List();
var arguments = $"get service {serviceName} --namespace {@namespace} -o json";
+
+ if (kubeConfigPath is not null)
+ {
+ arguments += $" --kubeconfig \"{kubeConfigPath}\"";
+ }
var stdoutBuilder = new StringBuilder();
var spec = new ProcessSpec("kubectl")
diff --git a/src/Aspire.Hosting.Kubernetes/KubernetesAspireDashboardResourceBuilderExtensions.cs b/src/Aspire.Hosting.Kubernetes/KubernetesAspireDashboardResourceBuilderExtensions.cs
index e45d8f9997e..32c1bebee18 100644
--- a/src/Aspire.Hosting.Kubernetes/KubernetesAspireDashboardResourceBuilderExtensions.cs
+++ b/src/Aspire.Hosting.Kubernetes/KubernetesAspireDashboardResourceBuilderExtensions.cs
@@ -40,9 +40,7 @@ internal static IResourceBuilder CreateDashbo
// Expose the HTTP endpoint so ingress or explicit host port mapping can route browser traffic to the dashboard.
.WithEndpoint("http", e => e.IsExternal = true)
.WithHttpEndpoint(name: "otlp-grpc", targetPort: 18889)
- .WithHttpEndpoint(name: "otlp-http", targetPort: 18890)
- .WithEnvironment("DASHBOARD__FRONTEND__AUTHMODE", "Unsecured")
- .WithEnvironment("DASHBOARD__OTLP__AUTHMODE", "Unsecured");
+ .WithHttpEndpoint(name: "otlp-http", targetPort: 18890);
}
///
diff --git a/src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentExtensions.cs b/src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentExtensions.cs
index f12456f6473..8122a274cbc 100644
--- a/src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentExtensions.cs
+++ b/src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentExtensions.cs
@@ -161,7 +161,80 @@ public static IResourceBuilder WithDashboard(this
return builder;
}
- private static void EnsureDefaultHelmEngine(IResourceBuilder builder)
+ ///
+ /// Adds a named node pool to the Kubernetes environment.
+ ///
+ /// The Kubernetes environment resource builder.
+ /// The name of the node pool. This value is used as the nodeSelector value.
+ /// A reference to the for the new node pool.
+ ///
+ /// For vanilla Kubernetes, this creates a named reference to an existing node pool.
+ /// For managed Kubernetes services (e.g., AKS), the cloud-specific AddNodePool overload
+ /// provisions the pool with additional configuration such as VM size and autoscaling.
+ /// Use to schedule workloads on the returned node pool.
+ ///
+ ///
+ ///
+ /// var k8s = builder.AddKubernetesEnvironment("k8s");
+ /// var gpuPool = k8s.AddNodePool("gpu");
+ ///
+ /// builder.AddProject<MyApi>()
+ /// .WithComputeEnvironment(k8s)
+ /// .WithNodePool(gpuPool);
+ ///
+ ///
+ [AspireExport(Description = "Adds a named node pool to a Kubernetes environment")]
+ public static IResourceBuilder AddNodePool(
+ this IResourceBuilder builder,
+ [ResourceName] string name)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentException.ThrowIfNullOrEmpty(name);
+
+ var nodePool = new KubernetesNodePoolResource(name, builder.Resource);
+
+ if (builder.ApplicationBuilder.ExecutionContext.IsRunMode)
+ {
+ return builder.ApplicationBuilder.CreateResourceBuilder(nodePool);
+ }
+
+ return builder.ApplicationBuilder.AddResource(nodePool)
+ .ExcludeFromManifest();
+ }
+
+ ///
+ /// Schedules a compute resource's workload on the specified Kubernetes node pool.
+ /// This translates to a Kubernetes nodeSelector in the pod specification
+ /// targeting the named node pool.
+ ///
+ /// The type of the compute resource.
+ /// The resource builder.
+ /// The node pool to schedule the workload on.
+ /// A reference to the for chaining.
+ ///
+ ///
+ /// var k8s = builder.AddKubernetesEnvironment("k8s");
+ /// var gpuPool = k8s.AddNodePool("gpu");
+ ///
+ /// builder.AddProject<MyApi>()
+ /// .WithComputeEnvironment(k8s)
+ /// .WithNodePool(gpuPool);
+ ///
+ ///
+ [AspireExport("withKubernetesNodePool", Description = "Schedules a workload on a specific Kubernetes node pool")]
+ public static IResourceBuilder WithNodePool(
+ this IResourceBuilder builder,
+ IResourceBuilder nodePool)
+ where T : IResource
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(nodePool);
+
+ builder.WithAnnotation(new KubernetesNodePoolAnnotation(nodePool.Resource));
+ return builder;
+ }
+
+ internal static void EnsureDefaultHelmEngine(IResourceBuilder builder)
{
builder.Resource.DeploymentEngineStepsFactory ??= HelmDeploymentEngine.CreateStepsAsync;
}
diff --git a/src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs b/src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs
index 68c03a816e0..22c91845353 100644
--- a/src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs
+++ b/src/Aspire.Hosting.Kubernetes/KubernetesEnvironmentResource.cs
@@ -92,6 +92,29 @@ public sealed class KubernetesEnvironmentResource : Resource, IComputeEnvironmen
///
public string DefaultServiceType { get; set; } = "ClusterIP";
+ ///
+ /// Gets or sets the path to an explicit kubeconfig file for Helm and kubectl commands.
+ /// When set, all Helm and kubectl commands will use --kubeconfig to target
+ /// this file instead of the default ~/.kube/config.
+ ///
+ ///
+ /// This is used by Azure Kubernetes Service (AKS) integration to isolate credentials
+ /// fetched via az aks get-credentials from the user's default kubectl context.
+ ///
+ public string? KubeConfigPath { get; set; }
+
+ ///
+ /// Gets or sets the parent compute environment resource that owns this Kubernetes environment.
+ /// When set, resources with WithComputeEnvironment targeting the parent will also
+ /// be processed by this Kubernetes environment.
+ ///
+ ///
+ /// This is used by Azure Kubernetes Service (AKS) integration where the user calls
+ /// WithComputeEnvironment(aksEnv) but the inner KubernetesEnvironmentResource
+ /// needs to process the resource.
+ ///
+ public IComputeEnvironmentResource? OwningComputeEnvironment { get; set; }
+
internal IPortAllocator PortAllocator { get; } = new PortAllocator();
///
@@ -131,6 +154,19 @@ internal sealed record CapturedHelmCrossReference(string Section, string Resourc
///
internal sealed record CapturedHelmImageReference(string Section, string ResourceKey, string ValueKey, IResource Resource);
+ ///
+ /// Represents a captured value provider reference that needs deploy-time resolution.
+ /// This handles any implementation (e.g., Bicep output references,
+ /// connection strings) that can't be resolved at publish time.
+ ///
+ internal sealed record CapturedHelmValueProvider(string Section, string ResourceKey, string ValueKey, IValueProvider ValueProvider);
+
+ ///
+ /// Captured value provider references populated during publish, consumed during deploy
+ /// to resolve values from external sources (e.g., Azure Bicep outputs).
+ ///
+ internal List CapturedHelmValueProviders { get; } = [];
+
///
/// Gets or sets the delegate that creates deployment pipeline steps for the configured engine.
///
@@ -173,7 +209,8 @@ public KubernetesEnvironmentResource(string name) : base(name)
foreach (var computeResource in resources)
{
- var deploymentTarget = computeResource.GetDeploymentTargetAnnotation(environment)?.DeploymentTarget;
+ var targetEnv = (IComputeEnvironmentResource?)environment.OwningComputeEnvironment ?? environment;
+ var deploymentTarget = computeResource.GetDeploymentTargetAnnotation(targetEnv)?.DeploymentTarget;
if (deploymentTarget is not null &&
deploymentTarget.TryGetAnnotationsOfType(out var annotations))
{
@@ -209,7 +246,8 @@ public KubernetesEnvironmentResource(string name) : base(name)
foreach (var computeResource in resources)
{
- var deploymentTarget = computeResource.GetDeploymentTargetAnnotation(this)?.DeploymentTarget;
+ var targetEnv = (IComputeEnvironmentResource?)OwningComputeEnvironment ?? this;
+ var deploymentTarget = computeResource.GetDeploymentTargetAnnotation(targetEnv)?.DeploymentTarget;
if (deploymentTarget is null)
{
continue;
diff --git a/src/Aspire.Hosting.Kubernetes/KubernetesInfrastructure.cs b/src/Aspire.Hosting.Kubernetes/KubernetesInfrastructure.cs
index 46c1003398c..5eb6ad88a6a 100644
--- a/src/Aspire.Hosting.Kubernetes/KubernetesInfrastructure.cs
+++ b/src/Aspire.Hosting.Kubernetes/KubernetesInfrastructure.cs
@@ -52,9 +52,13 @@ private async Task OnBeforeStartAsync(BeforeStartEvent @event, CancellationToken
foreach (var r in @event.Model.GetComputeResources())
{
- // Skip resources that are explicitly targeted to a different compute environment
+ // Skip resources that are explicitly targeted to a different compute environment.
+ // Also match if the resource targets a parent compute environment (e.g., AKS)
+ // that owns this Kubernetes environment.
var resourceComputeEnvironment = r.GetComputeEnvironment();
- if (resourceComputeEnvironment is not null && resourceComputeEnvironment != environment)
+ if (resourceComputeEnvironment is not null &&
+ resourceComputeEnvironment != environment &&
+ resourceComputeEnvironment != environment.OwningComputeEnvironment)
{
continue;
}
@@ -69,10 +73,14 @@ private async Task OnBeforeStartAsync(BeforeStartEvent @event, CancellationToken
var serviceResource = await environmentContext.CreateKubernetesResourceAsync(r, executionContext, cancellationToken).ConfigureAwait(false);
serviceResource.AddPrintSummaryStep();
- // Add deployment target annotation to the resource
+ // Add deployment target annotation to the resource.
+ // Use the resource's actual compute environment (which may be a parent
+ // like AzureKubernetesEnvironmentResource) so that GetDeploymentTargetAnnotation
+ // can match it correctly during publish.
+ var computeEnvForAnnotation = resourceComputeEnvironment ?? (IComputeEnvironmentResource)environment;
r.Annotations.Add(new DeploymentTargetAnnotation(serviceResource)
{
- ComputeEnvironment = environment,
+ ComputeEnvironment = computeEnvForAnnotation,
ContainerRegistry = containerRegistry
});
}
diff --git a/src/Aspire.Hosting.Kubernetes/KubernetesNodePoolAnnotation.cs b/src/Aspire.Hosting.Kubernetes/KubernetesNodePoolAnnotation.cs
new file mode 100644
index 00000000000..df2f82b78b9
--- /dev/null
+++ b/src/Aspire.Hosting.Kubernetes/KubernetesNodePoolAnnotation.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Aspire.Hosting.ApplicationModel;
+
+namespace Aspire.Hosting.Kubernetes;
+
+///
+/// Annotation that associates a compute resource with a specific Kubernetes node pool.
+/// When present, the Kubernetes deployment will include a nodeSelector targeting
+/// the specified node pool.
+///
+internal sealed class KubernetesNodePoolAnnotation(KubernetesNodePoolResource nodePool) : IResourceAnnotation
+{
+ ///
+ /// Gets the node pool to schedule the workload on.
+ ///
+ public KubernetesNodePoolResource NodePool { get; } = nodePool;
+}
diff --git a/src/Aspire.Hosting.Kubernetes/KubernetesNodePoolResource.cs b/src/Aspire.Hosting.Kubernetes/KubernetesNodePoolResource.cs
new file mode 100644
index 00000000000..94c1b41fad8
--- /dev/null
+++ b/src/Aspire.Hosting.Kubernetes/KubernetesNodePoolResource.cs
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Aspire.Hosting.ApplicationModel;
+
+namespace Aspire.Hosting.Kubernetes;
+
+///
+/// Represents a Kubernetes node pool as a child resource of a .
+/// Node pools can be referenced by compute resources to schedule workloads on specific node pools
+/// using .
+///
+/// The name of the node pool resource.
+/// The parent Kubernetes environment resource.
+[AspireExport]
+public class KubernetesNodePoolResource(
+ string name,
+ KubernetesEnvironmentResource environment) : Resource(name), IResourceWithParent
+{
+ ///
+ /// Gets the parent Kubernetes environment resource.
+ ///
+ public KubernetesEnvironmentResource Parent { get; } = environment ?? throw new ArgumentNullException(nameof(environment));
+
+ ///
+ /// Gets the label key used to identify the node pool in the Kubernetes cluster.
+ /// Defaults to agentpool which is the standard label used by AKS and many managed Kubernetes services.
+ ///
+ public string NodeSelectorLabelKey { get; init; } = "agentpool";
+}
diff --git a/src/Aspire.Hosting.Kubernetes/KubernetesPublishingContext.cs b/src/Aspire.Hosting.Kubernetes/KubernetesPublishingContext.cs
index f29e9f65f0e..91ef401acd3 100644
--- a/src/Aspire.Hosting.Kubernetes/KubernetesPublishingContext.cs
+++ b/src/Aspire.Hosting.Kubernetes/KubernetesPublishingContext.cs
@@ -75,7 +75,9 @@ private async Task WriteKubernetesOutputAsync(DistributedApplicationModel model,
foreach (var resource in resources)
{
- if (resource.GetDeploymentTargetAnnotation(environment)?.DeploymentTarget is KubernetesResource serviceResource)
+ // Check for deployment target matching this environment or its parent (e.g., AKS)
+ var targetEnv = (IComputeEnvironmentResource?)environment.OwningComputeEnvironment ?? environment;
+ if (resource.GetDeploymentTargetAnnotation(targetEnv)?.DeploymentTarget is KubernetesResource serviceResource)
{
// Materialize Dockerfile factory if present
if (serviceResource.TargetResource.TryGetLastAnnotation(out var dockerfileBuildAnnotation) &&
@@ -103,6 +105,12 @@ private async Task WriteKubernetesOutputAsync(DistributedApplicationModel model,
}
}
+ // Apply node pool nodeSelector if the resource has a node pool annotation
+ if (serviceResource.TargetResource.TryGetLastAnnotation(out var nodePoolAnnotation))
+ {
+ ApplyNodePoolSelector(serviceResource, nodePoolAnnotation.NodePool);
+ }
+
await WriteKubernetesTemplatesForResource(resource, serviceResource.GetTemplatedResources()).ConfigureAwait(false);
await AppendResourceContextToHelmValuesAsync(resource, serviceResource).ConfigureAwait(false);
}
@@ -188,6 +196,21 @@ private async Task AddValuesToHelmSectionAsync(
else
{
value = helmExpressionWithValue.Value;
+
+ // If the value has an IValueProvider source, capture it for deploy-time
+ // resolution. Write an empty placeholder now and resolve at deploy time.
+ // This handles Bicep output references, connection strings, and any other
+ // deferred value source without requiring Azure-specific knowledge.
+ if (helmExpressionWithValue.ValueProviderSource is { } valueProvider)
+ {
+ value = string.Empty;
+ environment?.CapturedHelmValueProviders.Add(
+ new KubernetesEnvironmentResource.CapturedHelmValueProvider(
+ helmKey,
+ resource.Name.ToHelmValuesSectionName(),
+ valuesKey,
+ valueProvider));
+ }
}
paramValues[valuesKey] = value ?? string.Empty;
@@ -273,4 +296,15 @@ private async Task WriteKubernetesHelmChartAsync(KubernetesEnvironmentResource e
Directory.CreateDirectory(OutputPath);
await File.WriteAllTextAsync(outputFile, chartYaml, cancellationToken).ConfigureAwait(false);
}
+
+ private static void ApplyNodePoolSelector(KubernetesResource serviceResource, KubernetesNodePoolResource nodePool)
+ {
+ var podSpec = serviceResource.Workload?.PodTemplate?.Spec;
+ if (podSpec is null)
+ {
+ return;
+ }
+
+ podSpec.NodeSelector[nodePool.NodeSelectorLabelKey] = nodePool.Name;
+ }
}
diff --git a/src/Aspire.Hosting.Kubernetes/KubernetesResource.cs b/src/Aspire.Hosting.Kubernetes/KubernetesResource.cs
index 3015e3438e3..04e9d21348d 100644
--- a/src/Aspire.Hosting.Kubernetes/KubernetesResource.cs
+++ b/src/Aspire.Hosting.Kubernetes/KubernetesResource.cs
@@ -365,6 +365,18 @@ private async Task ProcessEnvironmentAsync(KubernetesEnvironmentContext environm
foreach (var environmentVariable in context.EnvironmentVariables)
{
var key = environmentVariable.Key;
+
+ // Check if the value contains deferred providers (e.g., Bicep outputs)
+ // that can only be resolved after infrastructure provisioning.
+ // If so, create a deferred HelmValue with the env var key name.
+ if (IsUnresolvedAtPublishTime(environmentVariable.Value) &&
+ environmentVariable.Value is IValueProvider deferredVp)
+ {
+ var deferredHelmValue = CreateDeferredHelmValue(key, deferredVp);
+ ProcessEnvironmentHelmExpression(deferredHelmValue, key);
+ continue;
+ }
+
var value = await ProcessValueAsync(environmentContext, executionContext, environmentVariable.Value).ConfigureAwait(false);
switch (value)
@@ -660,6 +672,8 @@ private static HelmValue ResolveUnknownValue(IManifestExpressionProvider paramet
{
var formattedName = parameter.ValueExpression.Replace(HelmExtensions.StartDelimiter, string.Empty)
.Replace(HelmExtensions.EndDelimiter, string.Empty)
+ .Replace("{", string.Empty)
+ .Replace("}", string.Empty)
.Replace(".", "_")
.ToHelmValuesSectionName();
@@ -667,7 +681,52 @@ private static HelmValue ResolveUnknownValue(IManifestExpressionProvider paramet
formattedName.ToHelmSecretExpression(resource.Name) :
formattedName.ToHelmConfigExpression(resource.Name);
- return new(helmExpression, parameter.ValueExpression);
+ var helmValue = new HelmValue(helmExpression, parameter.ValueExpression)
+ {
+ // If the expression provider also implements IValueProvider, attach it
+ // for deploy-time resolution. This handles Bicep output references,
+ // connection strings, and any other deferred value source.
+ ValueProviderSource = parameter as IValueProvider
+ };
+
+ return helmValue;
+ }
+
+ ///
+ /// Checks if a value contains sub-expressions that cannot be resolved
+ /// at publish time and need deploy-time resolution via IValueProvider.
+ /// Recursively checks ReferenceExpression value providers.
+ ///
+ private static bool IsUnresolvedAtPublishTime(object value)
+ {
+ return value switch
+ {
+ string => false,
+ EndpointReference => false,
+ EndpointReferenceExpression => false,
+ ParameterResource => false,
+ ConnectionStringReference cs => IsUnresolvedAtPublishTime(cs.Resource.ConnectionStringExpression),
+ IResourceWithConnectionString csrs => IsUnresolvedAtPublishTime(csrs.ConnectionStringExpression),
+ ReferenceExpression expr => expr.ValueProviders.Any(IsUnresolvedAtPublishTime),
+ // Any other IManifestExpressionProvider that also implements IValueProvider
+ // is a deferred source (e.g., BicepOutputReference)
+ IManifestExpressionProvider when value is IValueProvider => true,
+ _ => false
+ };
+ }
+
+ ///
+ /// Creates a HelmValue that defers resolution to deploy time via IValueProvider.
+ ///
+ /// The environment variable or config key name to use as the Helm values path.
+ /// The value provider for deploy-time resolution.
+ private HelmValue CreateDeferredHelmValue(string key, IValueProvider valueProvider)
+ {
+ var helmExpression = key.ToHelmConfigExpression(TargetResource.Name);
+ return new HelmValue(helmExpression, string.Empty)
+ {
+ ValueProviderSource = valueProvider
+ };
}
private static string GetKubernetesProtocolName(ProtocolType type)
@@ -743,6 +802,14 @@ public HelmValue(string expression, ParameterResource parameterSource)
///
public IResource? ImageResource { get; init; }
+ ///
+ /// Gets the value provider for deferred resolution at deploy time.
+ /// When set, the value is resolved via
+ /// during the prepare step, replacing the placeholder in values.yaml.
+ /// This handles any value provider (e.g., Bicep output references, connection strings).
+ ///
+ public IValueProvider? ValueProviderSource { get; init; }
+
///
/// Gets the key to use when writing this value to the Helm values.yaml file.
/// When set, this overrides the dictionary key to ensure the values.yaml key matches
diff --git a/src/Aspire.Hosting.Kubernetes/Resources/ServiceAccountV1.cs b/src/Aspire.Hosting.Kubernetes/Resources/ServiceAccountV1.cs
new file mode 100644
index 00000000000..891262ab49a
--- /dev/null
+++ b/src/Aspire.Hosting.Kubernetes/Resources/ServiceAccountV1.cs
@@ -0,0 +1,14 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using YamlDotNet.Serialization;
+
+namespace Aspire.Hosting.Kubernetes.Resources;
+
+///
+/// Represents a Kubernetes ServiceAccount resource.
+///
+[YamlSerializable]
+public sealed class ServiceAccountV1() : BaseKubernetesResource("v1", "ServiceAccount")
+{
+}
diff --git a/tests/Aspire.Hosting.Azure.Kubernetes.Tests/Aspire.Hosting.Azure.Kubernetes.Tests.csproj b/tests/Aspire.Hosting.Azure.Kubernetes.Tests/Aspire.Hosting.Azure.Kubernetes.Tests.csproj
new file mode 100644
index 00000000000..eb9779ffcbd
--- /dev/null
+++ b/tests/Aspire.Hosting.Azure.Kubernetes.Tests/Aspire.Hosting.Azure.Kubernetes.Tests.csproj
@@ -0,0 +1,22 @@
+
+
+
+ $(DefaultTargetFramework)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Aspire.Hosting.Azure.Kubernetes.Tests/AzureKubernetesEnvironmentExtensionsTests.cs b/tests/Aspire.Hosting.Azure.Kubernetes.Tests/AzureKubernetesEnvironmentExtensionsTests.cs
new file mode 100644
index 00000000000..e06ae52d338
--- /dev/null
+++ b/tests/Aspire.Hosting.Azure.Kubernetes.Tests/AzureKubernetesEnvironmentExtensionsTests.cs
@@ -0,0 +1,303 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable ASPIREAZURE003 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+#pragma warning disable ASPIRECOMPUTE003 // Type is for evaluation purposes only
+
+using System.Runtime.CompilerServices;
+using Aspire.Hosting.ApplicationModel;
+using Aspire.Hosting.Azure.Kubernetes;
+using Aspire.Hosting.Kubernetes;
+using Aspire.Hosting.Utils;
+
+namespace Aspire.Hosting.Azure.Tests;
+
+public class AzureKubernetesEnvironmentExtensionsTests
+{
+ [Fact]
+ public async Task AddAzureKubernetesEnvironment_BasicConfiguration()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create();
+
+ var aks = builder.AddAzureKubernetesEnvironment("aks");
+
+ Assert.Equal("aks", aks.Resource.Name);
+ Assert.Equal("{aks.outputs.id}", aks.Resource.Id.ValueExpression);
+ Assert.Equal("{aks.outputs.clusterFqdn}", aks.Resource.ClusterFqdn.ValueExpression);
+ Assert.Equal("{aks.outputs.oidcIssuerUrl}", aks.Resource.OidcIssuerUrl.ValueExpression);
+
+ var manifest = await AzureManifestUtils.GetManifestWithBicep(aks.Resource);
+ await Verify(manifest.BicepText, extension: "bicep");
+ }
+
+ [Fact]
+ public void AddNodePool_ReturnsNodePoolResource()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create();
+
+ var aks = builder.AddAzureKubernetesEnvironment("aks");
+ var gpuPool = aks.AddNodePool("gpu", "Standard_NC6s_v3", 0, 5);
+
+ // Default system pool + added user pool
+ Assert.Equal(2, aks.Resource.NodePools.Count);
+
+ Assert.Equal("gpu", gpuPool.Resource.Name);
+ Assert.Equal("gpu", gpuPool.Resource.Config.Name);
+ Assert.Equal("Standard_NC6s_v3", gpuPool.Resource.Config.VmSize);
+ Assert.Equal(0, gpuPool.Resource.Config.MinCount);
+ Assert.Equal(5, gpuPool.Resource.Config.MaxCount);
+ Assert.Equal(AksNodePoolMode.User, gpuPool.Resource.Config.Mode);
+ Assert.Same(aks.Resource, gpuPool.Resource.AksParent);
+ }
+
+ [Fact]
+ public void AddAzureKubernetesEnvironment_DefaultNodePool()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create();
+
+ var aks = builder.AddAzureKubernetesEnvironment("aks");
+
+ Assert.Single(aks.Resource.NodePools);
+ var defaultPool = aks.Resource.NodePools[0];
+ Assert.Equal("system", defaultPool.Name);
+ Assert.Equal("Standard_D2s_v5", defaultPool.VmSize);
+ Assert.Equal(1, defaultPool.MinCount);
+ Assert.Equal(3, defaultPool.MaxCount);
+ Assert.Equal(AksNodePoolMode.System, defaultPool.Mode);
+ }
+
+ [Fact]
+ public void AddAzureKubernetesEnvironment_DefaultConfiguration()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create();
+
+ var aks = builder.AddAzureKubernetesEnvironment("aks");
+
+ Assert.True(aks.Resource.OidcIssuerEnabled);
+ Assert.True(aks.Resource.WorkloadIdentityEnabled);
+ Assert.Equal(AksSkuTier.Free, aks.Resource.SkuTier);
+ Assert.Null(aks.Resource.KubernetesVersion);
+ Assert.False(aks.Resource.IsPrivateCluster);
+ Assert.False(aks.Resource.ContainerInsightsEnabled);
+ Assert.Null(aks.Resource.LogAnalyticsWorkspace);
+ }
+
+ [Fact]
+ public void AddAzureKubernetesEnvironment_HasInternalKubernetesEnvironment()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create();
+
+ var aks = builder.AddAzureKubernetesEnvironment("aks");
+
+ Assert.NotNull(aks.Resource.KubernetesEnvironment);
+ Assert.Equal("aks-k8s", aks.Resource.KubernetesEnvironment.Name);
+ }
+
+ [Fact]
+ public void AddAzureKubernetesEnvironment_ThrowsOnNullBuilder()
+ {
+ IDistributedApplicationBuilder builder = null!;
+
+ Assert.Throws(() =>
+ builder.AddAzureKubernetesEnvironment("aks"));
+ }
+
+ [Fact]
+ public void AddAzureKubernetesEnvironment_ThrowsOnEmptyName()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create();
+
+ Assert.Throws(() =>
+ builder.AddAzureKubernetesEnvironment(""));
+ }
+
+ [Fact]
+ public void WithWorkloadIdentity_EnablesOidcAndWorkloadIdentity()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create();
+
+ var aks = builder.AddAzureKubernetesEnvironment("aks")
+ .WithWorkloadIdentity();
+
+ Assert.True(aks.Resource.OidcIssuerEnabled);
+ Assert.True(aks.Resource.WorkloadIdentityEnabled);
+ }
+
+ [Fact]
+ public void WithAzureUserAssignedIdentity_WorksWithAks()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create();
+
+ var aks = builder.AddAzureKubernetesEnvironment("aks");
+ var identity = builder.AddAzureUserAssignedIdentity("myIdentity");
+
+ var project = builder.AddContainer("myapi", "myimage")
+ .WithAzureUserAssignedIdentity(identity);
+
+ Assert.True(project.Resource.TryGetLastAnnotation(out var appIdentity));
+ Assert.Same(identity.Resource, appIdentity.IdentityResource);
+ }
+
+ [Fact]
+ public void AzureKubernetesEnvironment_ImplementsIAzureComputeEnvironmentResource()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create();
+ var aks = builder.AddAzureKubernetesEnvironment("aks");
+ Assert.IsAssignableFrom(aks.Resource);
+ }
+
+ [Fact]
+ public void AzureKubernetesEnvironment_ImplementsIAzureNspAssociationTarget()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create();
+ var aks = builder.AddAzureKubernetesEnvironment("aks");
+ Assert.IsAssignableFrom(aks.Resource);
+ }
+
+ [Fact]
+ public void AsExisting_WorksOnAksResource()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create();
+
+ var nameParam = builder.AddParameter("aks-name");
+ var rgParam = builder.AddParameter("aks-rg");
+
+ var aks = builder.AddAzureKubernetesEnvironment("aks")
+ .AsExisting(nameParam, rgParam);
+
+ Assert.NotNull(aks);
+ }
+
+ [Fact]
+ public void WithSubnet_OnNodePool_StoresPerPoolSubnet()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(
+ DistributedApplicationOperation.Publish);
+
+ var vnet = builder.AddAzureVirtualNetwork("vnet", "10.0.0.0/16");
+ var defaultSubnet = vnet.AddSubnet("default-subnet", "10.0.0.0/22");
+ var gpuSubnet = vnet.AddSubnet("gpu-subnet", "10.0.4.0/24");
+
+ var aks = builder.AddAzureKubernetesEnvironment("aks")
+ .WithSubnet(defaultSubnet);
+
+ var gpuPool = aks.AddNodePool("gpu", "Standard_NC6s_v3", 0, 5)
+ .WithSubnet(gpuSubnet);
+
+ // Environment-level subnet should be set via annotation
+ Assert.True(aks.Resource.TryGetLastAnnotation(out _));
+
+ // Per-pool subnet should be stored in NodePoolSubnets dictionary
+ Assert.Single(aks.Resource.NodePoolSubnets);
+ Assert.True(aks.Resource.NodePoolSubnets.ContainsKey("gpu"));
+
+ // Node pool should also have its own subnet annotation
+ Assert.True(gpuPool.Resource.TryGetLastAnnotation(out _));
+ }
+
+ [Fact]
+ public void WithSubnet_OnNodePool_WithoutEnvironmentSubnet()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(
+ DistributedApplicationOperation.Publish);
+
+ var vnet = builder.AddAzureVirtualNetwork("vnet", "10.0.0.0/16");
+ var gpuSubnet = vnet.AddSubnet("gpu-subnet", "10.0.4.0/24");
+
+ var aks = builder.AddAzureKubernetesEnvironment("aks");
+
+ // Only set subnet on the pool, not the environment
+ var gpuPool = aks.AddNodePool("gpu", "Standard_NC6s_v3", 0, 5)
+ .WithSubnet(gpuSubnet);
+
+ // No environment-level subnet
+ Assert.False(aks.Resource.TryGetLastAnnotation(out _));
+
+ // Per-pool subnet should still work
+ Assert.Single(aks.Resource.NodePoolSubnets);
+ Assert.True(aks.Resource.NodePoolSubnets.ContainsKey("gpu"));
+ }
+
+ [Fact]
+ public void WithNodePool_AddsAnnotation()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create();
+
+ var aks = builder.AddAzureKubernetesEnvironment("aks");
+ var gpuPool = aks.AddNodePool("gpu", "Standard_NC6s_v3", 0, 5);
+
+ var container = builder.AddContainer("myapi", "myimage")
+ .WithNodePool(gpuPool);
+
+ Assert.True(container.Resource.TryGetLastAnnotation(out var affinity));
+ Assert.Same(gpuPool.Resource, affinity.NodePool);
+ Assert.Equal("gpu", affinity.NodePool.Name);
+ }
+
+ [Fact]
+ public void AddNodePool_MultiplePoolsSupported()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create();
+
+ var aks = builder.AddAzureKubernetesEnvironment("aks");
+ var pool1 = aks.AddNodePool("cpu", "Standard_D2s_v5", 1, 10);
+ var pool2 = aks.AddNodePool("gpu", "Standard_NC6s_v3", 0, 5);
+
+ // Default system pool + 2 user pools
+ Assert.Equal(3, aks.Resource.NodePools.Count);
+ Assert.Equal("cpu", pool1.Resource.Name);
+ Assert.Equal("gpu", pool2.Resource.Name);
+ }
+
+ [Fact]
+ public void AddAzureKubernetesEnvironment_AutoCreatesDefaultRegistry()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(
+ DistributedApplicationOperation.Publish);
+
+ var aks = builder.AddAzureKubernetesEnvironment("aks");
+
+ Assert.NotNull(aks.Resource.DefaultContainerRegistry);
+ Assert.Equal("aks-acr", aks.Resource.DefaultContainerRegistry.Name);
+ }
+
+ [Fact]
+ public void WithContainerRegistry_ReplacesDefault()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(
+ DistributedApplicationOperation.Publish);
+
+ var aks = builder.AddAzureKubernetesEnvironment("aks");
+ var explicitAcr = builder.AddAzureContainerRegistry("my-acr");
+
+ aks.WithContainerRegistry(explicitAcr);
+
+ // Default registry should be removed
+ Assert.Null(aks.Resource.DefaultContainerRegistry);
+
+ // Explicit registry should be set via annotation
+ Assert.True(aks.Resource.TryGetLastAnnotation(out var annotation));
+ Assert.Same(explicitAcr.Resource, annotation.Registry);
+ }
+
+ [Fact]
+ public async Task ContainerRegistry_FlowsToInnerKubernetesEnvironment()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(
+ DistributedApplicationOperation.Publish);
+
+ var aks = builder.AddAzureKubernetesEnvironment("aks");
+ var container = builder.AddContainer("myapi", "myimage");
+
+ await using var app = builder.Build();
+ await ExecuteBeforeStartHooksAsync(app, default);
+
+ // The inner K8s environment should have the registry annotation
+ Assert.True(aks.Resource.KubernetesEnvironment
+ .TryGetLastAnnotation(out var annotation));
+ Assert.Same(aks.Resource.DefaultContainerRegistry, annotation.Registry);
+ }
+
+ [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "ExecuteBeforeStartHooksAsync")]
+ private static extern Task ExecuteBeforeStartHooksAsync(DistributedApplication app, CancellationToken cancellationToken);
+}
diff --git a/tests/Aspire.Hosting.Azure.Kubernetes.Tests/AzureKubernetesInfrastructureTests.cs b/tests/Aspire.Hosting.Azure.Kubernetes.Tests/AzureKubernetesInfrastructureTests.cs
new file mode 100644
index 00000000000..bf999fe977e
--- /dev/null
+++ b/tests/Aspire.Hosting.Azure.Kubernetes.Tests/AzureKubernetesInfrastructureTests.cs
@@ -0,0 +1,156 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#pragma warning disable ASPIREAZURE003
+
+using System.Runtime.CompilerServices;
+using Aspire.Hosting.ApplicationModel;
+using Aspire.Hosting.Azure.Kubernetes;
+using Aspire.Hosting.Kubernetes;
+using Aspire.Hosting.Utils;
+
+namespace Aspire.Hosting.Azure.Tests;
+
+public class AzureKubernetesInfrastructureTests
+{
+ [Fact]
+ public async Task NoUserPool_CreatesDefaultWorkloadPool()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(
+ DistributedApplicationOperation.Publish);
+
+ var aks = builder.AddAzureKubernetesEnvironment("aks");
+
+ // No AddNodePool call — only the default system pool exists
+ Assert.Single(aks.Resource.NodePools);
+ Assert.Equal(AksNodePoolMode.System, aks.Resource.NodePools[0].Mode);
+
+ var container = builder.AddContainer("myapi", "myimage");
+
+ await using var app = builder.Build();
+ await ExecuteBeforeStartHooksAsync(app, default);
+
+ // Infrastructure should have added a default "workload" user pool
+ Assert.Equal(2, aks.Resource.NodePools.Count);
+ var workloadPool = aks.Resource.NodePools.First(p => p.Mode is AksNodePoolMode.User);
+ Assert.Equal("workload", workloadPool.Name);
+
+ // Compute resource should have been auto-assigned to the workload pool
+ Assert.True(container.Resource.TryGetLastAnnotation(out var affinity));
+ Assert.Equal("workload", affinity.NodePool.Name);
+ }
+
+ [Fact]
+ public async Task ExplicitUserPool_NoDefaultCreated()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(
+ DistributedApplicationOperation.Publish);
+
+ var aks = builder.AddAzureKubernetesEnvironment("aks");
+ var gpuPool = aks.AddNodePool("gpu", "Standard_NC6s_v3", 0, 5);
+
+ var container = builder.AddContainer("myapi", "myimage");
+
+ await using var app = builder.Build();
+ await ExecuteBeforeStartHooksAsync(app, default);
+
+ // Should NOT create a default pool since one already exists
+ Assert.Equal(2, aks.Resource.NodePools.Count); // system + gpu
+ Assert.DoesNotContain(aks.Resource.NodePools, p => p.Name == "workload");
+
+ // Unaffinitized compute resource should get assigned to the first user pool
+ Assert.True(container.Resource.TryGetLastAnnotation(out var affinity));
+ Assert.Equal("gpu", affinity.NodePool.Name);
+ }
+
+ [Fact]
+ public async Task ExplicitAffinity_NotOverridden()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(
+ DistributedApplicationOperation.Publish);
+
+ var aks = builder.AddAzureKubernetesEnvironment("aks");
+ var gpuPool = aks.AddNodePool("gpu", "Standard_NC6s_v3", 0, 5);
+ var cpuPool = aks.AddNodePool("cpu", "Standard_D4s_v5", 1, 10);
+
+ var container = builder.AddContainer("myapi", "myimage")
+ .WithNodePool(cpuPool);
+
+ await using var app = builder.Build();
+ await ExecuteBeforeStartHooksAsync(app, default);
+
+ // Explicit affinity should be preserved, not overridden
+ Assert.True(container.Resource.TryGetLastAnnotation(out var affinity));
+ Assert.Equal("cpu", affinity.NodePool.Name);
+ }
+
+ [Fact]
+ public async Task ComputeResource_GetsDeploymentTargetFromKubernetesInfrastructure()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(
+ DistributedApplicationOperation.Publish);
+
+ var aks = builder.AddAzureKubernetesEnvironment("aks");
+ var container = builder.AddContainer("myapi", "myimage");
+
+ await using var app = builder.Build();
+ await ExecuteBeforeStartHooksAsync(app, default);
+
+ // DeploymentTargetAnnotation comes from KubernetesInfrastructure (via the inner
+ // KubernetesEnvironmentResource), not from AzureKubernetesInfrastructure.
+ Assert.True(container.Resource.TryGetLastAnnotation(out var target));
+ Assert.NotNull(target.DeploymentTarget);
+
+ // The compute environment should be the inner K8s environment
+ Assert.Same(aks.Resource.KubernetesEnvironment, target.ComputeEnvironment);
+
+ // CRITICAL: ContainerRegistry must be set on the DeploymentTargetAnnotation
+ // so that push steps can resolve the registry endpoint
+ Assert.NotNull(target.ContainerRegistry);
+ Assert.IsType(target.ContainerRegistry);
+ }
+
+ [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "ExecuteBeforeStartHooksAsync")]
+ private static extern Task ExecuteBeforeStartHooksAsync(DistributedApplication app, CancellationToken cancellationToken);
+
+ [Fact]
+ public async Task MultiEnv_ResourcesMatchCorrectEnvironment()
+ {
+ using var builder = TestDistributedApplicationBuilder.Create(
+ DistributedApplicationOperation.Publish);
+
+ var registry = builder.AddAzureContainerRegistry("registry");
+ var enva = builder.AddAzureKubernetesEnvironment("enva")
+ .WithContainerRegistry(registry);
+ var envb = builder.AddAzureKubernetesEnvironment("envb")
+ .WithContainerRegistry(registry);
+
+ var cache = builder.AddContainer("cache", "redis")
+ .WithComputeEnvironment(enva);
+ var api = builder.AddContainer("api", "myapi")
+ .WithComputeEnvironment(enva);
+ var other = builder.AddContainer("other", "myother")
+ .WithComputeEnvironment(envb);
+
+ // OwningComputeEnvironment should be set
+ Assert.Same(enva.Resource, enva.Resource.KubernetesEnvironment.OwningComputeEnvironment);
+ Assert.Same(envb.Resource, envb.Resource.KubernetesEnvironment.OwningComputeEnvironment);
+
+ await using var app = builder.Build();
+ await ExecuteBeforeStartHooksAsync(app, default);
+
+ // cache and api should get DeploymentTargetAnnotation targeting enva
+ Assert.True(cache.Resource.TryGetLastAnnotation(out var cacheTarget),
+ "cache should have DeploymentTargetAnnotation");
+ Assert.Same(enva.Resource, cacheTarget.ComputeEnvironment);
+
+ Assert.True(api.Resource.TryGetLastAnnotation(out var apiTarget),
+ "api should have DeploymentTargetAnnotation");
+ Assert.Same(enva.Resource, apiTarget.ComputeEnvironment);
+
+ // other should get DeploymentTargetAnnotation targeting envb
+ Assert.True(other.Resource.TryGetLastAnnotation(out var otherTarget),
+ "other should have DeploymentTargetAnnotation");
+ Assert.Same(envb.Resource, otherTarget.ComputeEnvironment);
+ }
+}
diff --git a/tests/Aspire.Hosting.Azure.Kubernetes.Tests/Snapshots/AzureKubernetesEnvironmentExtensionsTests.AddAzureKubernetesEnvironment_BasicConfiguration.verified.bicep b/tests/Aspire.Hosting.Azure.Kubernetes.Tests/Snapshots/AzureKubernetesEnvironmentExtensionsTests.AddAzureKubernetesEnvironment_BasicConfiguration.verified.bicep
new file mode 100644
index 00000000000..d48ffe64157
--- /dev/null
+++ b/tests/Aspire.Hosting.Azure.Kubernetes.Tests/Snapshots/AzureKubernetesEnvironmentExtensionsTests.AddAzureKubernetesEnvironment_BasicConfiguration.verified.bicep
@@ -0,0 +1,52 @@
+@description('The location for the resource(s) to be deployed.')
+param location string = resourceGroup().location
+
+resource aks 'Microsoft.ContainerService/managedClusters@2025-03-01' = {
+ name: take('aks-${uniqueString(resourceGroup().id)}', 63)
+ location: location
+ properties: {
+ agentPoolProfiles: [
+ {
+ name: 'system'
+ count: 1
+ vmSize: 'Standard_D2s_v5'
+ osType: 'Linux'
+ maxCount: 3
+ minCount: 1
+ enableAutoScaling: true
+ mode: 'System'
+ }
+ ]
+ dnsPrefix: 'aks-dns'
+ oidcIssuerProfile: {
+ enabled: true
+ }
+ securityProfile: {
+ workloadIdentity: {
+ enabled: true
+ }
+ }
+ }
+ identity: {
+ type: 'SystemAssigned'
+ }
+ sku: {
+ name: 'Base'
+ tier: 'Free'
+ }
+ tags: {
+ 'aspire-resource-name': 'aks'
+ }
+}
+
+output id string = aks.id
+
+output name string = aks.name
+
+output clusterFqdn string = aks.properties.fqdn
+
+output oidcIssuerUrl string = aks.properties.oidcIssuerProfile.issuerURL
+
+output kubeletIdentityObjectId string = aks.properties.identityProfile.kubeletidentity.objectId
+
+output nodeResourceGroup string = aks.properties.nodeResourceGroup
\ No newline at end of file
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/KubernetesPublisherTests.cs b/tests/Aspire.Hosting.Kubernetes.Tests/KubernetesPublisherTests.cs
index 8796df0d9dd..c0d70f56d7d 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/KubernetesPublisherTests.cs
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/KubernetesPublisherTests.cs
@@ -50,7 +50,6 @@ public async Task PublishAsync_GeneratesValidHelmChart()
"values.yaml",
"templates/env-dashboard/deployment.yaml",
"templates/env-dashboard/service.yaml",
- "templates/env-dashboard/config.yaml",
"templates/project1/deployment.yaml",
"templates/project1/config.yaml",
"templates/myapp/deployment.yaml",
@@ -150,7 +149,6 @@ public async Task PublishAsync_CustomWorkloadAndResourceType()
"values.yaml",
"templates/env-dashboard/deployment.yaml",
"templates/env-dashboard/service.yaml",
- "templates/env-dashboard/config.yaml",
"templates/myapp/rollout.yaml",
"templates/myapp/service.yaml",
"templates/myapp/config.yaml",
@@ -208,7 +206,6 @@ public async Task PublishAsync_HandlesSpecialResourceName()
"values.yaml",
"templates/env-dashboard/deployment.yaml",
"templates/env-dashboard/service.yaml",
- "templates/env-dashboard/config.yaml",
"templates/SpeciaL-ApP/deployment.yaml",
"templates/SpeciaL-ApP/config.yaml",
"templates/SpeciaL-ApP/secrets.yaml"
@@ -392,7 +389,6 @@ public async Task KubernetesWithProjectResources()
"values.yaml",
"templates/env-dashboard/deployment.yaml",
"templates/env-dashboard/service.yaml",
- "templates/env-dashboard/config.yaml",
"templates/project1/deployment.yaml",
"templates/project1/service.yaml",
"templates/project1/config.yaml",
@@ -441,7 +437,6 @@ public async Task KubernetesMapsPortsForBaitAndSwitchResources()
"values.yaml",
"templates/env-dashboard/deployment.yaml",
"templates/env-dashboard/service.yaml",
- "templates/env-dashboard/config.yaml",
"templates/api/deployment.yaml",
"templates/api/service.yaml",
"templates/api/config.yaml",
@@ -502,7 +497,6 @@ public async Task PublishAsync_HandlesConditionalReferenceExpression()
"values.yaml",
"templates/env-dashboard/deployment.yaml",
"templates/env-dashboard/service.yaml",
- "templates/env-dashboard/config.yaml",
"templates/myapp/deployment.yaml",
"templates/myapp/config.yaml",
};
@@ -559,7 +553,6 @@ public async Task PublishAsync_HandlesConditionalReferenceExpressionWithParamete
"values.yaml",
"templates/env-dashboard/deployment.yaml",
"templates/env-dashboard/service.yaml",
- "templates/env-dashboard/config.yaml",
"templates/myapp/deployment.yaml",
"templates/myapp/config.yaml",
};
@@ -619,7 +612,6 @@ public async Task PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax()
"values.yaml",
"templates/env-dashboard/deployment.yaml",
"templates/env-dashboard/service.yaml",
- "templates/env-dashboard/config.yaml",
"templates/myapp/deployment.yaml",
"templates/myapp/config.yaml",
};
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env1/templates/env1-dashboard/config.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env1/templates/env1-dashboard/config.verified.yaml
deleted file mode 100644
index a1981018b93..00000000000
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env1/templates/env1-dashboard/config.verified.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
----
-apiVersion: "v1"
-kind: "ConfigMap"
-metadata:
- name: "env1-dashboard-config"
- labels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "env1-dashboard"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- DASHBOARD__FRONTEND__AUTHMODE: "{{ .Values.config.env1_dashboard.DASHBOARD__FRONTEND__AUTHMODE }}"
- DASHBOARD__OTLP__AUTHMODE: "{{ .Values.config.env1_dashboard.DASHBOARD__OTLP__AUTHMODE }}"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env1/templates/env1-dashboard/deployment.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env1/templates/env1-dashboard/deployment.verified.yaml
index e5141dd85b2..2a3688a724e 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env1/templates/env1-dashboard/deployment.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env1/templates/env1-dashboard/deployment.verified.yaml
@@ -18,9 +18,6 @@ spec:
containers:
- image: "mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest"
name: "env1-dashboard"
- envFrom:
- - configMapRef:
- name: "env1-dashboard-config"
ports:
- name: "http"
protocol: "TCP"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env1/values.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env1/values.verified.yaml
index a5010ec71a9..c7772f5474f 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env1/values.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env1/values.verified.yaml
@@ -4,9 +4,6 @@
ServiceA_image: "ServiceA:latest"
secrets: {}
config:
- env1_dashboard:
- DASHBOARD__FRONTEND__AUTHMODE: "Unsecured"
- DASHBOARD__OTLP__AUTHMODE: "Unsecured"
ServiceA:
OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory"
ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env2/templates/env2-dashboard/config.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env2/templates/env2-dashboard/config.verified.yaml
deleted file mode 100644
index 8accea03b34..00000000000
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env2/templates/env2-dashboard/config.verified.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
----
-apiVersion: "v1"
-kind: "ConfigMap"
-metadata:
- name: "env2-dashboard-config"
- labels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "env2-dashboard"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- DASHBOARD__FRONTEND__AUTHMODE: "{{ .Values.config.env2_dashboard.DASHBOARD__FRONTEND__AUTHMODE }}"
- DASHBOARD__OTLP__AUTHMODE: "{{ .Values.config.env2_dashboard.DASHBOARD__OTLP__AUTHMODE }}"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env2/templates/env2-dashboard/deployment.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env2/templates/env2-dashboard/deployment.verified.yaml
index 0b5088a5f0a..9772c86683a 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env2/templates/env2-dashboard/deployment.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env2/templates/env2-dashboard/deployment.verified.yaml
@@ -18,9 +18,6 @@ spec:
containers:
- image: "mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest"
name: "env2-dashboard"
- envFrom:
- - configMapRef:
- name: "env2-dashboard-config"
ports:
- name: "http"
protocol: "TCP"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env2/values.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env2/values.verified.yaml
index 438b0054b87..49bcc853f4c 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env2/values.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.MultipleKubernetesEnvironmentsSupported/env2/values.verified.yaml
@@ -4,9 +4,6 @@
ServiceB_image: "ServiceB:latest"
secrets: {}
config:
- env2_dashboard:
- DASHBOARD__FRONTEND__AUTHMODE: "Unsecured"
- DASHBOARD__OTLP__AUTHMODE: "Unsecured"
ServiceB:
OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory"
ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.PublishingKubernetesEnvironmentPublishesFile#01.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.PublishingKubernetesEnvironmentPublishesFile#01.verified.yaml
index 8e4b8515505..6e65f5d657a 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.PublishingKubernetesEnvironmentPublishesFile#01.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesEnvironmentResourceTests.PublishingKubernetesEnvironmentPublishesFile#01.verified.yaml
@@ -1,6 +1,3 @@
parameters: {}
secrets: {}
-config:
- env_dashboard:
- DASHBOARD__FRONTEND__AUTHMODE: "Unsecured"
- DASHBOARD__OTLP__AUTHMODE: "Unsecured"
+config: {}
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#01.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#01.verified.yaml
index a270c5f7097..9bb8e2495d4 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#01.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#01.verified.yaml
@@ -1,11 +1,8 @@
-parameters:
+parameters:
api:
api_image: "api:latest"
secrets: {}
config:
- env_dashboard:
- DASHBOARD__FRONTEND__AUTHMODE: "Unsecured"
- DASHBOARD__OTLP__AUTHMODE: "Unsecured"
api:
PORT: "8000"
gateway:
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#02.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#02.verified.yaml
index d9837afd3a3..4cce26b3384 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#02.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#02.verified.yaml
@@ -18,9 +18,6 @@ spec:
containers:
- image: "mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest"
name: "env-dashboard"
- envFrom:
- - configMapRef:
- name: "env-dashboard-config"
ports:
- name: "http"
protocol: "TCP"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#04.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#04.verified.yaml
index 28f355f91c5..c02bdb29653 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#04.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#04.verified.yaml
@@ -1,12 +1,40 @@
---
-apiVersion: "v1"
-kind: "ConfigMap"
+apiVersion: "apps/v1"
+kind: "Deployment"
metadata:
- name: "env-dashboard-config"
+ name: "api-deployment"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "env-dashboard"
+ app.kubernetes.io/component: "api"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- DASHBOARD__FRONTEND__AUTHMODE: "{{ .Values.config.env_dashboard.DASHBOARD__FRONTEND__AUTHMODE }}"
- DASHBOARD__OTLP__AUTHMODE: "{{ .Values.config.env_dashboard.DASHBOARD__OTLP__AUTHMODE }}"
+spec:
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "api"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ spec:
+ containers:
+ - image: "{{ .Values.parameters.api.api_image }}"
+ name: "api"
+ envFrom:
+ - configMapRef:
+ name: "api-config"
+ ports:
+ - name: "http"
+ protocol: "TCP"
+ containerPort: 8000
+ imagePullPolicy: "IfNotPresent"
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "api"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ replicas: 1
+ revisionHistoryLimit: 3
+ strategy:
+ rollingUpdate:
+ maxSurge: 1
+ maxUnavailable: 1
+ type: "RollingUpdate"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#05.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#05.verified.yaml
index c02bdb29653..e66ef8c2ce4 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#05.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#05.verified.yaml
@@ -1,40 +1,20 @@
---
-apiVersion: "apps/v1"
-kind: "Deployment"
+apiVersion: "v1"
+kind: "Service"
metadata:
- name: "api-deployment"
+ name: "api-service"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
app.kubernetes.io/component: "api"
app.kubernetes.io/instance: "{{ .Release.Name }}"
spec:
- template:
- metadata:
- labels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "api"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- spec:
- containers:
- - image: "{{ .Values.parameters.api.api_image }}"
- name: "api"
- envFrom:
- - configMapRef:
- name: "api-config"
- ports:
- - name: "http"
- protocol: "TCP"
- containerPort: 8000
- imagePullPolicy: "IfNotPresent"
+ type: "ClusterIP"
selector:
- matchLabels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "api"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- replicas: 1
- revisionHistoryLimit: 3
- strategy:
- rollingUpdate:
- maxSurge: 1
- maxUnavailable: 1
- type: "RollingUpdate"
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "api"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ ports:
+ - name: "http"
+ protocol: "TCP"
+ port: 8000
+ targetPort: 8000
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#06.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#06.verified.yaml
index e66ef8c2ce4..d588ff078e6 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#06.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#06.verified.yaml
@@ -1,20 +1,11 @@
---
apiVersion: "v1"
-kind: "Service"
+kind: "ConfigMap"
metadata:
- name: "api-service"
+ name: "api-config"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
app.kubernetes.io/component: "api"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-spec:
- type: "ClusterIP"
- selector:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "api"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- ports:
- - name: "http"
- protocol: "TCP"
- port: 8000
- targetPort: 8000
+data:
+ PORT: "{{ .Values.config.api.PORT }}"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#07.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#07.verified.yaml
index d588ff078e6..18e18bb62d7 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#07.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#07.verified.yaml
@@ -1,11 +1,40 @@
---
-apiVersion: "v1"
-kind: "ConfigMap"
+apiVersion: "apps/v1"
+kind: "Deployment"
metadata:
- name: "api-config"
+ name: "gateway-deployment"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "api"
+ app.kubernetes.io/component: "gateway"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- PORT: "{{ .Values.config.api.PORT }}"
+spec:
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "gateway"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ spec:
+ containers:
+ - image: "nginx:latest"
+ name: "gateway"
+ envFrom:
+ - configMapRef:
+ name: "gateway-config"
+ ports:
+ - name: "http"
+ protocol: "TCP"
+ containerPort: 8080
+ imagePullPolicy: "IfNotPresent"
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "gateway"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ replicas: 1
+ revisionHistoryLimit: 3
+ strategy:
+ rollingUpdate:
+ maxSurge: 1
+ maxUnavailable: 1
+ type: "RollingUpdate"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#08.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#08.verified.yaml
index 18e18bb62d7..9f4aaee87c2 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#08.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#08.verified.yaml
@@ -1,40 +1,12 @@
---
-apiVersion: "apps/v1"
-kind: "Deployment"
+apiVersion: "v1"
+kind: "ConfigMap"
metadata:
- name: "gateway-deployment"
+ name: "gateway-config"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
app.kubernetes.io/component: "gateway"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-spec:
- template:
- metadata:
- labels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "gateway"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- spec:
- containers:
- - image: "nginx:latest"
- name: "gateway"
- envFrom:
- - configMapRef:
- name: "gateway-config"
- ports:
- - name: "http"
- protocol: "TCP"
- containerPort: 8080
- imagePullPolicy: "IfNotPresent"
- selector:
- matchLabels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "gateway"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- replicas: 1
- revisionHistoryLimit: 3
- strategy:
- rollingUpdate:
- maxSurge: 1
- maxUnavailable: 1
- type: "RollingUpdate"
+data:
+ API_HTTP: "{{ .Values.config.gateway.API_HTTP }}"
+ services__api__http__0: "{{ .Values.config.gateway.services__api__http__0 }}"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#09.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#09.verified.yaml
deleted file mode 100644
index 9f4aaee87c2..00000000000
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesMapsPortsForBaitAndSwitchResources#09.verified.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
----
-apiVersion: "v1"
-kind: "ConfigMap"
-metadata:
- name: "gateway-config"
- labels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "gateway"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- API_HTTP: "{{ .Values.config.gateway.API_HTTP }}"
- services__api__http__0: "{{ .Values.config.gateway.services__api__http__0 }}"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#01.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#01.verified.yaml
index b44334bcf03..ffa13c7773a 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#01.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#01.verified.yaml
@@ -1,12 +1,9 @@
-parameters:
+parameters:
project1:
port_http: 8080
project1_image: "project1:latest"
secrets: {}
config:
- env_dashboard:
- DASHBOARD__FRONTEND__AUTHMODE: "Unsecured"
- DASHBOARD__OTLP__AUTHMODE: "Unsecured"
project1:
OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory"
ASPNETCORE_FORWARDEDHEADERS_ENABLED: "true"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#02.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#02.verified.yaml
index d9837afd3a3..4cce26b3384 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#02.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#02.verified.yaml
@@ -18,9 +18,6 @@ spec:
containers:
- image: "mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest"
name: "env-dashboard"
- envFrom:
- - configMapRef:
- name: "env-dashboard-config"
ports:
- name: "http"
protocol: "TCP"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#04.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#04.verified.yaml
index 28f355f91c5..fe6845b975f 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#04.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#04.verified.yaml
@@ -1,12 +1,52 @@
---
-apiVersion: "v1"
-kind: "ConfigMap"
+apiVersion: "apps/v1"
+kind: "Deployment"
metadata:
- name: "env-dashboard-config"
+ name: "project1-deployment"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "env-dashboard"
+ app.kubernetes.io/component: "project1"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- DASHBOARD__FRONTEND__AUTHMODE: "{{ .Values.config.env_dashboard.DASHBOARD__FRONTEND__AUTHMODE }}"
- DASHBOARD__OTLP__AUTHMODE: "{{ .Values.config.env_dashboard.DASHBOARD__OTLP__AUTHMODE }}"
+spec:
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "project1"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ spec:
+ containers:
+ - image: "{{ .Values.parameters.project1.project1_image }}"
+ name: "project1"
+ envFrom:
+ - configMapRef:
+ name: "project1-config"
+ ports:
+ - name: "http"
+ protocol: "TCP"
+ containerPort: {{ .Values.parameters.project1.port_http | int }}
+ - name: "custom1"
+ protocol: "TCP"
+ containerPort: 8000
+ - name: "custom2"
+ protocol: "TCP"
+ containerPort: 8001
+ - name: "custom3"
+ protocol: "TCP"
+ containerPort: 7002
+ - name: "custom4"
+ protocol: "TCP"
+ containerPort: 7004
+ imagePullPolicy: "IfNotPresent"
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "project1"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ replicas: 1
+ revisionHistoryLimit: 3
+ strategy:
+ rollingUpdate:
+ maxSurge: 1
+ maxUnavailable: 1
+ type: "RollingUpdate"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#05.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#05.verified.yaml
index fe6845b975f..a70a914e74f 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#05.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#05.verified.yaml
@@ -1,52 +1,36 @@
---
-apiVersion: "apps/v1"
-kind: "Deployment"
+apiVersion: "v1"
+kind: "Service"
metadata:
- name: "project1-deployment"
+ name: "project1-service"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
app.kubernetes.io/component: "project1"
app.kubernetes.io/instance: "{{ .Release.Name }}"
spec:
- template:
- metadata:
- labels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "project1"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- spec:
- containers:
- - image: "{{ .Values.parameters.project1.project1_image }}"
- name: "project1"
- envFrom:
- - configMapRef:
- name: "project1-config"
- ports:
- - name: "http"
- protocol: "TCP"
- containerPort: {{ .Values.parameters.project1.port_http | int }}
- - name: "custom1"
- protocol: "TCP"
- containerPort: 8000
- - name: "custom2"
- protocol: "TCP"
- containerPort: 8001
- - name: "custom3"
- protocol: "TCP"
- containerPort: 7002
- - name: "custom4"
- protocol: "TCP"
- containerPort: 7004
- imagePullPolicy: "IfNotPresent"
+ type: "ClusterIP"
selector:
- matchLabels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "project1"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- replicas: 1
- revisionHistoryLimit: 3
- strategy:
- rollingUpdate:
- maxSurge: 1
- maxUnavailable: 1
- type: "RollingUpdate"
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "project1"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ ports:
+ - name: "http"
+ protocol: "TCP"
+ port: {{ .Values.parameters.project1.port_http | int }}
+ targetPort: {{ .Values.parameters.project1.port_http | int }}
+ - name: "custom1"
+ protocol: "TCP"
+ port: 8000
+ targetPort: 8000
+ - name: "custom2"
+ protocol: "TCP"
+ port: 7001
+ targetPort: 8001
+ - name: "custom3"
+ protocol: "TCP"
+ port: 7002
+ targetPort: 7002
+ - name: "custom4"
+ protocol: "TCP"
+ port: 7003
+ targetPort: 7004
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#06.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#06.verified.yaml
index a70a914e74f..eb04bc499a5 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#06.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#06.verified.yaml
@@ -1,36 +1,16 @@
---
apiVersion: "v1"
-kind: "Service"
+kind: "ConfigMap"
metadata:
- name: "project1-service"
+ name: "project1-config"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
app.kubernetes.io/component: "project1"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-spec:
- type: "ClusterIP"
- selector:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "project1"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- ports:
- - name: "http"
- protocol: "TCP"
- port: {{ .Values.parameters.project1.port_http | int }}
- targetPort: {{ .Values.parameters.project1.port_http | int }}
- - name: "custom1"
- protocol: "TCP"
- port: 8000
- targetPort: 8000
- - name: "custom2"
- protocol: "TCP"
- port: 7001
- targetPort: 8001
- - name: "custom3"
- protocol: "TCP"
- port: 7002
- targetPort: 7002
- - name: "custom4"
- protocol: "TCP"
- port: 7003
- targetPort: 7004
+data:
+ OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "{{ .Values.config.project1.OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY }}"
+ ASPNETCORE_FORWARDEDHEADERS_ENABLED: "{{ .Values.config.project1.ASPNETCORE_FORWARDEDHEADERS_ENABLED }}"
+ HTTP_PORTS: "{{ .Values.parameters.project1.port_http }};8000;8001;7002;7004"
+ OTEL_EXPORTER_OTLP_ENDPOINT: "{{ .Values.config.project1.OTEL_EXPORTER_OTLP_ENDPOINT }}"
+ OTEL_EXPORTER_OTLP_PROTOCOL: "{{ .Values.config.project1.OTEL_EXPORTER_OTLP_PROTOCOL }}"
+ OTEL_SERVICE_NAME: "{{ .Values.config.project1.OTEL_SERVICE_NAME }}"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#07.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#07.verified.yaml
index eb04bc499a5..90177b02f82 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#07.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#07.verified.yaml
@@ -1,16 +1,36 @@
---
-apiVersion: "v1"
-kind: "ConfigMap"
+apiVersion: "apps/v1"
+kind: "Deployment"
metadata:
- name: "project1-config"
+ name: "api-deployment"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "project1"
+ app.kubernetes.io/component: "api"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "{{ .Values.config.project1.OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY }}"
- ASPNETCORE_FORWARDEDHEADERS_ENABLED: "{{ .Values.config.project1.ASPNETCORE_FORWARDEDHEADERS_ENABLED }}"
- HTTP_PORTS: "{{ .Values.parameters.project1.port_http }};8000;8001;7002;7004"
- OTEL_EXPORTER_OTLP_ENDPOINT: "{{ .Values.config.project1.OTEL_EXPORTER_OTLP_ENDPOINT }}"
- OTEL_EXPORTER_OTLP_PROTOCOL: "{{ .Values.config.project1.OTEL_EXPORTER_OTLP_PROTOCOL }}"
- OTEL_SERVICE_NAME: "{{ .Values.config.project1.OTEL_SERVICE_NAME }}"
+spec:
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "api"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ spec:
+ containers:
+ - image: "reg:api"
+ name: "api"
+ envFrom:
+ - configMapRef:
+ name: "api-config"
+ imagePullPolicy: "IfNotPresent"
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "api"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ replicas: 1
+ revisionHistoryLimit: 3
+ strategy:
+ rollingUpdate:
+ maxSurge: 1
+ maxUnavailable: 1
+ type: "RollingUpdate"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#08.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#08.verified.yaml
index 90177b02f82..711b0061a78 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#08.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#08.verified.yaml
@@ -1,36 +1,21 @@
---
-apiVersion: "apps/v1"
-kind: "Deployment"
+apiVersion: "v1"
+kind: "ConfigMap"
metadata:
- name: "api-deployment"
+ name: "api-config"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
app.kubernetes.io/component: "api"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-spec:
- template:
- metadata:
- labels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "api"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- spec:
- containers:
- - image: "reg:api"
- name: "api"
- envFrom:
- - configMapRef:
- name: "api-config"
- imagePullPolicy: "IfNotPresent"
- selector:
- matchLabels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "api"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- replicas: 1
- revisionHistoryLimit: 3
- strategy:
- rollingUpdate:
- maxSurge: 1
- maxUnavailable: 1
- type: "RollingUpdate"
+data:
+ PROJECT1_HTTP: "http://project1-service:{{ .Values.parameters.project1.port_http }}"
+ services__project1__http__0: "http://project1-service:{{ .Values.parameters.project1.port_http }}"
+ PROJECT1_HTTPS: "https://project1-service:{{ .Values.parameters.project1.port_http }}"
+ PROJECT1_CUSTOM1: "{{ .Values.config.api.PROJECT1_CUSTOM1 }}"
+ services__project1__custom1__0: "{{ .Values.config.api.services__project1__custom1__0 }}"
+ PROJECT1_CUSTOM2: "{{ .Values.config.api.PROJECT1_CUSTOM2 }}"
+ services__project1__custom2__0: "{{ .Values.config.api.services__project1__custom2__0 }}"
+ PROJECT1_CUSTOM3: "{{ .Values.config.api.PROJECT1_CUSTOM3 }}"
+ services__project1__custom3__0: "{{ .Values.config.api.services__project1__custom3__0 }}"
+ PROJECT1_CUSTOM4: "{{ .Values.config.api.PROJECT1_CUSTOM4 }}"
+ services__project1__custom4__0: "{{ .Values.config.api.services__project1__custom4__0 }}"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#09.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#09.verified.yaml
deleted file mode 100644
index 711b0061a78..00000000000
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.KubernetesWithProjectResources#09.verified.yaml
+++ /dev/null
@@ -1,21 +0,0 @@
----
-apiVersion: "v1"
-kind: "ConfigMap"
-metadata:
- name: "api-config"
- labels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "api"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- PROJECT1_HTTP: "http://project1-service:{{ .Values.parameters.project1.port_http }}"
- services__project1__http__0: "http://project1-service:{{ .Values.parameters.project1.port_http }}"
- PROJECT1_HTTPS: "https://project1-service:{{ .Values.parameters.project1.port_http }}"
- PROJECT1_CUSTOM1: "{{ .Values.config.api.PROJECT1_CUSTOM1 }}"
- services__project1__custom1__0: "{{ .Values.config.api.services__project1__custom1__0 }}"
- PROJECT1_CUSTOM2: "{{ .Values.config.api.PROJECT1_CUSTOM2 }}"
- services__project1__custom2__0: "{{ .Values.config.api.services__project1__custom2__0 }}"
- PROJECT1_CUSTOM3: "{{ .Values.config.api.PROJECT1_CUSTOM3 }}"
- services__project1__custom3__0: "{{ .Values.config.api.services__project1__custom3__0 }}"
- PROJECT1_CUSTOM4: "{{ .Values.config.api.PROJECT1_CUSTOM4 }}"
- services__project1__custom4__0: "{{ .Values.config.api.services__project1__custom4__0 }}"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#01.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#01.verified.yaml
index 3b07241ffa0..b4c19ac3dce 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#01.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#01.verified.yaml
@@ -1,11 +1,8 @@
-parameters:
+parameters:
myapp:
enable_tls: "True"
secrets: {}
config:
- env_dashboard:
- DASHBOARD__FRONTEND__AUTHMODE: "Unsecured"
- DASHBOARD__OTLP__AUTHMODE: "Unsecured"
myapp:
TLS_SUFFIX: ""
tls_suffix: ",ssl=true"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#02.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#02.verified.yaml
index d9837afd3a3..4cce26b3384 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#02.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#02.verified.yaml
@@ -18,9 +18,6 @@ spec:
containers:
- image: "mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest"
name: "env-dashboard"
- envFrom:
- - configMapRef:
- name: "env-dashboard-config"
ports:
- name: "http"
protocol: "TCP"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#04.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#04.verified.yaml
index 28f355f91c5..37de00c4947 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#04.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#04.verified.yaml
@@ -1,12 +1,36 @@
---
-apiVersion: "v1"
-kind: "ConfigMap"
+apiVersion: "apps/v1"
+kind: "Deployment"
metadata:
- name: "env-dashboard-config"
+ name: "myapp-deployment"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "env-dashboard"
+ app.kubernetes.io/component: "myapp"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- DASHBOARD__FRONTEND__AUTHMODE: "{{ .Values.config.env_dashboard.DASHBOARD__FRONTEND__AUTHMODE }}"
- DASHBOARD__OTLP__AUTHMODE: "{{ .Values.config.env_dashboard.DASHBOARD__OTLP__AUTHMODE }}"
+spec:
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "myapp"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ spec:
+ containers:
+ - image: "mcr.microsoft.com/dotnet/aspnet:8.0"
+ name: "myapp"
+ envFrom:
+ - configMapRef:
+ name: "myapp-config"
+ imagePullPolicy: "IfNotPresent"
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "myapp"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ replicas: 1
+ revisionHistoryLimit: 3
+ strategy:
+ rollingUpdate:
+ maxSurge: 1
+ maxUnavailable: 1
+ type: "RollingUpdate"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#05.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#05.verified.yaml
index 37de00c4947..2823fb9bc2a 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#05.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#05.verified.yaml
@@ -1,36 +1,11 @@
---
-apiVersion: "apps/v1"
-kind: "Deployment"
+apiVersion: "v1"
+kind: "ConfigMap"
metadata:
- name: "myapp-deployment"
+ name: "myapp-config"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
app.kubernetes.io/component: "myapp"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-spec:
- template:
- metadata:
- labels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "myapp"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- spec:
- containers:
- - image: "mcr.microsoft.com/dotnet/aspnet:8.0"
- name: "myapp"
- envFrom:
- - configMapRef:
- name: "myapp-config"
- imagePullPolicy: "IfNotPresent"
- selector:
- matchLabels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "myapp"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- replicas: 1
- revisionHistoryLimit: 3
- strategy:
- rollingUpdate:
- maxSurge: 1
- maxUnavailable: 1
- type: "RollingUpdate"
+data:
+ TLS_SUFFIX: {{ if eq (.Values.parameters.myapp.enable_tls | lower) "true" }}{{ .Values.config.myapp.tls_suffix }}{{ else }},ssl=false{{ end }}
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#06.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#06.verified.yaml
deleted file mode 100644
index 2823fb9bc2a..00000000000
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ConditionalWithParameterBranch_UsesIfElseSyntax#06.verified.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
----
-apiVersion: "v1"
-kind: "ConfigMap"
-metadata:
- name: "myapp-config"
- labels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "myapp"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- TLS_SUFFIX: {{ if eq (.Values.parameters.myapp.enable_tls | lower) "true" }}{{ .Values.config.myapp.tls_suffix }}{{ else }},ssl=false{{ end }}
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#01.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#01.verified.yaml
index ddb2c0262be..86e85a25ddc 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#01.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#01.verified.yaml
@@ -1,11 +1,8 @@
-parameters:
+parameters:
project1:
project1_image: "project1:latest"
secrets: {}
config:
- env_dashboard:
- DASHBOARD__FRONTEND__AUTHMODE: "Unsecured"
- DASHBOARD__OTLP__AUTHMODE: "Unsecured"
myapp:
ASPNETCORE_ENVIRONMENT: "Development"
project1:
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#02.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#02.verified.yaml
index d9837afd3a3..4cce26b3384 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#02.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#02.verified.yaml
@@ -18,9 +18,6 @@ spec:
containers:
- image: "mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest"
name: "env-dashboard"
- envFrom:
- - configMapRef:
- name: "env-dashboard-config"
ports:
- name: "http"
protocol: "TCP"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#04.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#04.verified.yaml
index 28f355f91c5..a3a11c691ed 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#04.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#04.verified.yaml
@@ -1,12 +1,34 @@
---
-apiVersion: "v1"
-kind: "ConfigMap"
+apiVersion: "argoproj.io/v1alpha1"
+kind: "Rollout"
metadata:
- name: "env-dashboard-config"
+ name: "myapp-rollout"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "env-dashboard"
+ app.kubernetes.io/component: "myapp"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- DASHBOARD__FRONTEND__AUTHMODE: "{{ .Values.config.env_dashboard.DASHBOARD__FRONTEND__AUTHMODE }}"
- DASHBOARD__OTLP__AUTHMODE: "{{ .Values.config.env_dashboard.DASHBOARD__OTLP__AUTHMODE }}"
+spec:
+ replicas: 1
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "myapp"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ spec:
+ containers:
+ - image: "mcr.microsoft.com/dotnet/aspnet:8.0"
+ name: "myapp"
+ envFrom:
+ - configMapRef:
+ name: "myapp-config"
+ ports:
+ - name: "http"
+ protocol: "TCP"
+ containerPort: 8080
+ imagePullPolicy: "IfNotPresent"
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "myapp"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#05.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#05.verified.yaml
index a3a11c691ed..278bf076e42 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#05.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#05.verified.yaml
@@ -1,34 +1,20 @@
---
-apiVersion: "argoproj.io/v1alpha1"
-kind: "Rollout"
+apiVersion: "v1"
+kind: "Service"
metadata:
- name: "myapp-rollout"
+ name: "myapp-service"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
app.kubernetes.io/component: "myapp"
app.kubernetes.io/instance: "{{ .Release.Name }}"
spec:
- replicas: 1
- template:
- metadata:
- labels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "myapp"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- spec:
- containers:
- - image: "mcr.microsoft.com/dotnet/aspnet:8.0"
- name: "myapp"
- envFrom:
- - configMapRef:
- name: "myapp-config"
- ports:
- - name: "http"
- protocol: "TCP"
- containerPort: 8080
- imagePullPolicy: "IfNotPresent"
+ type: "ClusterIP"
selector:
- matchLabels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "myapp"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "myapp"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ ports:
+ - name: "http"
+ protocol: "TCP"
+ port: 8080
+ targetPort: 8080
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#06.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#06.verified.yaml
index 278bf076e42..6b54f9178a9 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#06.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#06.verified.yaml
@@ -1,20 +1,11 @@
---
apiVersion: "v1"
-kind: "Service"
+kind: "ConfigMap"
metadata:
- name: "myapp-service"
+ name: "myapp-config"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
app.kubernetes.io/component: "myapp"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-spec:
- type: "ClusterIP"
- selector:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "myapp"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- ports:
- - name: "http"
- protocol: "TCP"
- port: 8080
- targetPort: 8080
+data:
+ ASPNETCORE_ENVIRONMENT: "{{ .Values.config.myapp.ASPNETCORE_ENVIRONMENT }}"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#07.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#07.verified.yaml
index 6b54f9178a9..92ff24ba964 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#07.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#07.verified.yaml
@@ -1,11 +1,15 @@
---
-apiVersion: "v1"
-kind: "ConfigMap"
+apiVersion: "keda.sh/v1alpha1"
+kind: "ScaledObject"
metadata:
- name: "myapp-config"
+ name: "myapp-scaler"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
app.kubernetes.io/component: "myapp"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- ASPNETCORE_ENVIRONMENT: "{{ .Values.config.myapp.ASPNETCORE_ENVIRONMENT }}"
+spec:
+ scaleTargetRef:
+ name: "myapp-rollout"
+ kind: "Rollout"
+ minReplicaCount: 1
+ maxReplicaCount: 3
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#08.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#08.verified.yaml
deleted file mode 100644
index 92ff24ba964..00000000000
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_CustomWorkloadAndResourceType#08.verified.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
----
-apiVersion: "keda.sh/v1alpha1"
-kind: "ScaledObject"
-metadata:
- name: "myapp-scaler"
- labels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "myapp"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
-spec:
- scaleTargetRef:
- name: "myapp-rollout"
- kind: "Rollout"
- minReplicaCount: 1
- maxReplicaCount: 3
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#01.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#01.verified.yaml
index f5589575e23..a82a10634a2 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#01.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#01.verified.yaml
@@ -1,4 +1,4 @@
-parameters:
+parameters:
project1:
project1_image: "project1:latest"
secrets:
@@ -7,9 +7,6 @@ secrets:
param3: ""
ConnectionStrings__cs: ""
config:
- env_dashboard:
- DASHBOARD__FRONTEND__AUTHMODE: "Unsecured"
- DASHBOARD__OTLP__AUTHMODE: "Unsecured"
myapp:
ASPNETCORE_ENVIRONMENT: "Development"
param0: ""
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#02.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#02.verified.yaml
index d9837afd3a3..4cce26b3384 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#02.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#02.verified.yaml
@@ -18,9 +18,6 @@ spec:
containers:
- image: "mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest"
name: "env-dashboard"
- envFrom:
- - configMapRef:
- name: "env-dashboard-config"
ports:
- name: "http"
protocol: "TCP"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#04.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#04.verified.yaml
index 28f355f91c5..8a8452a6954 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#04.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#04.verified.yaml
@@ -1,12 +1,36 @@
---
-apiVersion: "v1"
-kind: "ConfigMap"
+apiVersion: "apps/v1"
+kind: "Deployment"
metadata:
- name: "env-dashboard-config"
+ name: "project1-deployment"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "env-dashboard"
+ app.kubernetes.io/component: "project1"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- DASHBOARD__FRONTEND__AUTHMODE: "{{ .Values.config.env_dashboard.DASHBOARD__FRONTEND__AUTHMODE }}"
- DASHBOARD__OTLP__AUTHMODE: "{{ .Values.config.env_dashboard.DASHBOARD__OTLP__AUTHMODE }}"
+spec:
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "project1"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ spec:
+ containers:
+ - image: "{{ .Values.parameters.project1.project1_image }}"
+ name: "project1"
+ envFrom:
+ - configMapRef:
+ name: "project1-config"
+ imagePullPolicy: "IfNotPresent"
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "project1"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ replicas: 1
+ revisionHistoryLimit: 3
+ strategy:
+ rollingUpdate:
+ maxSurge: 1
+ maxUnavailable: 1
+ type: "RollingUpdate"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#05.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#05.verified.yaml
index 8a8452a6954..38439563476 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#05.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#05.verified.yaml
@@ -1,36 +1,16 @@
---
-apiVersion: "apps/v1"
-kind: "Deployment"
+apiVersion: "v1"
+kind: "ConfigMap"
metadata:
- name: "project1-deployment"
+ name: "project1-config"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
app.kubernetes.io/component: "project1"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-spec:
- template:
- metadata:
- labels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "project1"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- spec:
- containers:
- - image: "{{ .Values.parameters.project1.project1_image }}"
- name: "project1"
- envFrom:
- - configMapRef:
- name: "project1-config"
- imagePullPolicy: "IfNotPresent"
- selector:
- matchLabels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "project1"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- replicas: 1
- revisionHistoryLimit: 3
- strategy:
- rollingUpdate:
- maxSurge: 1
- maxUnavailable: 1
- type: "RollingUpdate"
+data:
+ OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "{{ .Values.config.project1.OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY }}"
+ MYAPP_HTTP: "{{ .Values.config.project1.MYAPP_HTTP }}"
+ services__myapp__http__0: "{{ .Values.config.project1.services__myapp__http__0 }}"
+ OTEL_EXPORTER_OTLP_ENDPOINT: "{{ .Values.config.project1.OTEL_EXPORTER_OTLP_ENDPOINT }}"
+ OTEL_EXPORTER_OTLP_PROTOCOL: "{{ .Values.config.project1.OTEL_EXPORTER_OTLP_PROTOCOL }}"
+ OTEL_SERVICE_NAME: "{{ .Values.config.project1.OTEL_SERVICE_NAME }}"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#06.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#06.verified.yaml
index 38439563476..19a7fd1c156 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#06.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#06.verified.yaml
@@ -1,16 +1,51 @@
---
-apiVersion: "v1"
-kind: "ConfigMap"
+apiVersion: "apps/v1"
+kind: "Deployment"
metadata:
- name: "project1-config"
+ name: "myapp-deployment"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "project1"
+ app.kubernetes.io/component: "myapp"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "{{ .Values.config.project1.OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY }}"
- MYAPP_HTTP: "{{ .Values.config.project1.MYAPP_HTTP }}"
- services__myapp__http__0: "{{ .Values.config.project1.services__myapp__http__0 }}"
- OTEL_EXPORTER_OTLP_ENDPOINT: "{{ .Values.config.project1.OTEL_EXPORTER_OTLP_ENDPOINT }}"
- OTEL_EXPORTER_OTLP_PROTOCOL: "{{ .Values.config.project1.OTEL_EXPORTER_OTLP_PROTOCOL }}"
- OTEL_SERVICE_NAME: "{{ .Values.config.project1.OTEL_SERVICE_NAME }}"
+spec:
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "myapp"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ spec:
+ containers:
+ - image: "mcr.microsoft.com/dotnet/aspnet:8.0"
+ name: "myapp"
+ envFrom:
+ - configMapRef:
+ name: "myapp-config"
+ - secretRef:
+ name: "myapp-secrets"
+ args:
+ - "--cs"
+ - "Url={{ .Values.config.myapp.param0 }}, Secret={{ .Values.secrets.myapp.param1 }}"
+ ports:
+ - name: "http"
+ protocol: "TCP"
+ containerPort: 8080
+ volumeMounts:
+ - name: "logs"
+ mountPath: "/logs"
+ imagePullPolicy: "IfNotPresent"
+ volumes:
+ - name: "logs"
+ emptyDir: {}
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "myapp"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ replicas: 1
+ revisionHistoryLimit: 3
+ strategy:
+ rollingUpdate:
+ maxSurge: 1
+ maxUnavailable: 1
+ type: "RollingUpdate"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#07.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#07.verified.yaml
index 19a7fd1c156..278bf076e42 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#07.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#07.verified.yaml
@@ -1,51 +1,20 @@
---
-apiVersion: "apps/v1"
-kind: "Deployment"
+apiVersion: "v1"
+kind: "Service"
metadata:
- name: "myapp-deployment"
+ name: "myapp-service"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
app.kubernetes.io/component: "myapp"
app.kubernetes.io/instance: "{{ .Release.Name }}"
spec:
- template:
- metadata:
- labels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "myapp"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- spec:
- containers:
- - image: "mcr.microsoft.com/dotnet/aspnet:8.0"
- name: "myapp"
- envFrom:
- - configMapRef:
- name: "myapp-config"
- - secretRef:
- name: "myapp-secrets"
- args:
- - "--cs"
- - "Url={{ .Values.config.myapp.param0 }}, Secret={{ .Values.secrets.myapp.param1 }}"
- ports:
- - name: "http"
- protocol: "TCP"
- containerPort: 8080
- volumeMounts:
- - name: "logs"
- mountPath: "/logs"
- imagePullPolicy: "IfNotPresent"
- volumes:
- - name: "logs"
- emptyDir: {}
+ type: "ClusterIP"
selector:
- matchLabels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "myapp"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- replicas: 1
- revisionHistoryLimit: 3
- strategy:
- rollingUpdate:
- maxSurge: 1
- maxUnavailable: 1
- type: "RollingUpdate"
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "myapp"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ ports:
+ - name: "http"
+ protocol: "TCP"
+ port: 8080
+ targetPort: 8080
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#08.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#08.verified.yaml
index 278bf076e42..67869876d40 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#08.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#08.verified.yaml
@@ -1,20 +1,13 @@
---
apiVersion: "v1"
-kind: "Service"
+kind: "ConfigMap"
metadata:
- name: "myapp-service"
+ name: "myapp-config"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
app.kubernetes.io/component: "myapp"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-spec:
- type: "ClusterIP"
- selector:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "myapp"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- ports:
- - name: "http"
- protocol: "TCP"
- port: 8080
- targetPort: 8080
+data:
+ ASPNETCORE_ENVIRONMENT: "{{ .Values.config.myapp.ASPNETCORE_ENVIRONMENT }}"
+ param0: "{{ .Values.config.myapp.param0 }}"
+ param2: "{{ .Values.config.myapp.param2 }}"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#09.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#09.verified.yaml
index 67869876d40..5ce56f6c429 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#09.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#09.verified.yaml
@@ -1,13 +1,14 @@
---
apiVersion: "v1"
-kind: "ConfigMap"
+kind: "Secret"
metadata:
- name: "myapp-config"
+ name: "myapp-secrets"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
app.kubernetes.io/component: "myapp"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- ASPNETCORE_ENVIRONMENT: "{{ .Values.config.myapp.ASPNETCORE_ENVIRONMENT }}"
- param0: "{{ .Values.config.myapp.param0 }}"
- param2: "{{ .Values.config.myapp.param2 }}"
+stringData:
+ param1: "{{ .Values.secrets.myapp.param1 }}"
+ param3: "{{ .Values.secrets.myapp.param3 }}"
+ ConnectionStrings__cs: "Url={{ .Values.config.myapp.param0 }}, Secret={{ .Values.secrets.myapp.param1 }}"
+type: "Opaque"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#10.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#10.verified.yaml
deleted file mode 100644
index 5ce56f6c429..00000000000
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_GeneratesValidHelmChart#10.verified.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
----
-apiVersion: "v1"
-kind: "Secret"
-metadata:
- name: "myapp-secrets"
- labels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "myapp"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
-stringData:
- param1: "{{ .Values.secrets.myapp.param1 }}"
- param3: "{{ .Values.secrets.myapp.param3 }}"
- ConnectionStrings__cs: "Url={{ .Values.config.myapp.param0 }}, Secret={{ .Values.secrets.myapp.param1 }}"
-type: "Opaque"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#01.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#01.verified.yaml
index ef457401ab5..b16121e9216 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#01.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#01.verified.yaml
@@ -1,9 +1,6 @@
-parameters: {}
+parameters: {}
secrets: {}
config:
- env_dashboard:
- DASHBOARD__FRONTEND__AUTHMODE: "Unsecured"
- DASHBOARD__OTLP__AUTHMODE: "Unsecured"
myapp:
TLS_SUFFIX: ",ssl=true"
TLS_SUFFIX_FALSE: ",ssl=false"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#02.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#02.verified.yaml
index d9837afd3a3..4cce26b3384 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#02.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#02.verified.yaml
@@ -18,9 +18,6 @@ spec:
containers:
- image: "mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest"
name: "env-dashboard"
- envFrom:
- - configMapRef:
- name: "env-dashboard-config"
ports:
- name: "http"
protocol: "TCP"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#04.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#04.verified.yaml
index 28f355f91c5..37de00c4947 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#04.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#04.verified.yaml
@@ -1,12 +1,36 @@
---
-apiVersion: "v1"
-kind: "ConfigMap"
+apiVersion: "apps/v1"
+kind: "Deployment"
metadata:
- name: "env-dashboard-config"
+ name: "myapp-deployment"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "env-dashboard"
+ app.kubernetes.io/component: "myapp"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- DASHBOARD__FRONTEND__AUTHMODE: "{{ .Values.config.env_dashboard.DASHBOARD__FRONTEND__AUTHMODE }}"
- DASHBOARD__OTLP__AUTHMODE: "{{ .Values.config.env_dashboard.DASHBOARD__OTLP__AUTHMODE }}"
+spec:
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "myapp"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ spec:
+ containers:
+ - image: "mcr.microsoft.com/dotnet/aspnet:8.0"
+ name: "myapp"
+ envFrom:
+ - configMapRef:
+ name: "myapp-config"
+ imagePullPolicy: "IfNotPresent"
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "myapp"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ replicas: 1
+ revisionHistoryLimit: 3
+ strategy:
+ rollingUpdate:
+ maxSurge: 1
+ maxUnavailable: 1
+ type: "RollingUpdate"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#05.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#05.verified.yaml
index 37de00c4947..89a6179e7e0 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#05.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#05.verified.yaml
@@ -1,36 +1,12 @@
---
-apiVersion: "apps/v1"
-kind: "Deployment"
+apiVersion: "v1"
+kind: "ConfigMap"
metadata:
- name: "myapp-deployment"
+ name: "myapp-config"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
app.kubernetes.io/component: "myapp"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-spec:
- template:
- metadata:
- labels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "myapp"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- spec:
- containers:
- - image: "mcr.microsoft.com/dotnet/aspnet:8.0"
- name: "myapp"
- envFrom:
- - configMapRef:
- name: "myapp-config"
- imagePullPolicy: "IfNotPresent"
- selector:
- matchLabels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "myapp"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- replicas: 1
- revisionHistoryLimit: 3
- strategy:
- rollingUpdate:
- maxSurge: 1
- maxUnavailable: 1
- type: "RollingUpdate"
+data:
+ TLS_SUFFIX: "{{ .Values.config.myapp.TLS_SUFFIX }}"
+ TLS_SUFFIX_FALSE: "{{ .Values.config.myapp.TLS_SUFFIX_FALSE }}"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#06.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#06.verified.yaml
deleted file mode 100644
index 89a6179e7e0..00000000000
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpression#06.verified.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
----
-apiVersion: "v1"
-kind: "ConfigMap"
-metadata:
- name: "myapp-config"
- labels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "myapp"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- TLS_SUFFIX: "{{ .Values.config.myapp.TLS_SUFFIX }}"
- TLS_SUFFIX_FALSE: "{{ .Values.config.myapp.TLS_SUFFIX_FALSE }}"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#01.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#01.verified.yaml
index 84363f49b59..b5ff02d1618 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#01.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#01.verified.yaml
@@ -1,10 +1,7 @@
-parameters:
+parameters:
myapp:
enable_tls: "True"
secrets: {}
config:
- env_dashboard:
- DASHBOARD__FRONTEND__AUTHMODE: "Unsecured"
- DASHBOARD__OTLP__AUTHMODE: "Unsecured"
myapp:
TLS_SUFFIX: ""
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#02.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#02.verified.yaml
index d9837afd3a3..4cce26b3384 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#02.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#02.verified.yaml
@@ -18,9 +18,6 @@ spec:
containers:
- image: "mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest"
name: "env-dashboard"
- envFrom:
- - configMapRef:
- name: "env-dashboard-config"
ports:
- name: "http"
protocol: "TCP"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#04.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#04.verified.yaml
index 28f355f91c5..37de00c4947 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#04.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#04.verified.yaml
@@ -1,12 +1,36 @@
---
-apiVersion: "v1"
-kind: "ConfigMap"
+apiVersion: "apps/v1"
+kind: "Deployment"
metadata:
- name: "env-dashboard-config"
+ name: "myapp-deployment"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "env-dashboard"
+ app.kubernetes.io/component: "myapp"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- DASHBOARD__FRONTEND__AUTHMODE: "{{ .Values.config.env_dashboard.DASHBOARD__FRONTEND__AUTHMODE }}"
- DASHBOARD__OTLP__AUTHMODE: "{{ .Values.config.env_dashboard.DASHBOARD__OTLP__AUTHMODE }}"
+spec:
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "myapp"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ spec:
+ containers:
+ - image: "mcr.microsoft.com/dotnet/aspnet:8.0"
+ name: "myapp"
+ envFrom:
+ - configMapRef:
+ name: "myapp-config"
+ imagePullPolicy: "IfNotPresent"
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: "aspire-hosting-tests"
+ app.kubernetes.io/component: "myapp"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ replicas: 1
+ revisionHistoryLimit: 3
+ strategy:
+ rollingUpdate:
+ maxSurge: 1
+ maxUnavailable: 1
+ type: "RollingUpdate"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#05.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#05.verified.yaml
index 37de00c4947..303a7d529b0 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#05.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#05.verified.yaml
@@ -1,36 +1,11 @@
---
-apiVersion: "apps/v1"
-kind: "Deployment"
+apiVersion: "v1"
+kind: "ConfigMap"
metadata:
- name: "myapp-deployment"
+ name: "myapp-config"
labels:
app.kubernetes.io/name: "aspire-hosting-tests"
app.kubernetes.io/component: "myapp"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-spec:
- template:
- metadata:
- labels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "myapp"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- spec:
- containers:
- - image: "mcr.microsoft.com/dotnet/aspnet:8.0"
- name: "myapp"
- envFrom:
- - configMapRef:
- name: "myapp-config"
- imagePullPolicy: "IfNotPresent"
- selector:
- matchLabels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "myapp"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- replicas: 1
- revisionHistoryLimit: 3
- strategy:
- rollingUpdate:
- maxSurge: 1
- maxUnavailable: 1
- type: "RollingUpdate"
+data:
+ TLS_SUFFIX: {{ if eq (.Values.parameters.myapp.enable_tls | lower) "true" }},ssl=true{{ else }},ssl=false{{ end }}
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#06.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#06.verified.yaml
deleted file mode 100644
index 303a7d529b0..00000000000
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesConditionalReferenceExpressionWithParameterCondition#06.verified.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
----
-apiVersion: "v1"
-kind: "ConfigMap"
-metadata:
- name: "myapp-config"
- labels:
- app.kubernetes.io/name: "aspire-hosting-tests"
- app.kubernetes.io/component: "myapp"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- TLS_SUFFIX: {{ if eq (.Values.parameters.myapp.enable_tls | lower) "true" }},ssl=true{{ else }},ssl=false{{ end }}
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#01.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#01.verified.yaml
index fc4f8122cf4..efa2cce635d 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#01.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#01.verified.yaml
@@ -1,4 +1,4 @@
-parameters:
+parameters:
SpeciaL_ApP:
SpeciaL_ApP_image: "SpeciaL-ApP:latest"
secrets:
@@ -6,9 +6,6 @@ secrets:
param3: ""
ConnectionStrings__api_cs: ""
config:
- env_dashboard:
- DASHBOARD__FRONTEND__AUTHMODE: "Unsecured"
- DASHBOARD__OTLP__AUTHMODE: "Unsecured"
SpeciaL_ApP:
OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "in_memory"
ConnectionStrings__api_cs2: "host.local:80"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#02.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#02.verified.yaml
index b694dc9be3b..5d450bc4a03 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#02.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#02.verified.yaml
@@ -18,9 +18,6 @@ spec:
containers:
- image: "mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest"
name: "env-dashboard"
- envFrom:
- - configMapRef:
- name: "env-dashboard-config"
ports:
- name: "http"
protocol: "TCP"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#04.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#04.verified.yaml
index 7b124741f11..544f449f306 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#04.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#04.verified.yaml
@@ -1,12 +1,38 @@
---
-apiVersion: "v1"
-kind: "ConfigMap"
+apiVersion: "apps/v1"
+kind: "Deployment"
metadata:
- name: "env-dashboard-config"
+ name: "special-app-deployment"
labels:
app.kubernetes.io/name: "my-chart"
- app.kubernetes.io/component: "env-dashboard"
+ app.kubernetes.io/component: "SpeciaL-ApP"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- DASHBOARD__FRONTEND__AUTHMODE: "{{ .Values.config.env_dashboard.DASHBOARD__FRONTEND__AUTHMODE }}"
- DASHBOARD__OTLP__AUTHMODE: "{{ .Values.config.env_dashboard.DASHBOARD__OTLP__AUTHMODE }}"
+spec:
+ template:
+ metadata:
+ labels:
+ app.kubernetes.io/name: "my-chart"
+ app.kubernetes.io/component: "SpeciaL-ApP"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ spec:
+ containers:
+ - image: "{{ .Values.parameters.SpeciaL_ApP.SpeciaL_ApP_image }}"
+ name: "SpeciaL-ApP"
+ envFrom:
+ - configMapRef:
+ name: "special-app-config"
+ - secretRef:
+ name: "special-app-secrets"
+ imagePullPolicy: "IfNotPresent"
+ selector:
+ matchLabels:
+ app.kubernetes.io/name: "my-chart"
+ app.kubernetes.io/component: "SpeciaL-ApP"
+ app.kubernetes.io/instance: "{{ .Release.Name }}"
+ replicas: 1
+ revisionHistoryLimit: 3
+ strategy:
+ rollingUpdate:
+ maxSurge: 1
+ maxUnavailable: 1
+ type: "RollingUpdate"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#05.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#05.verified.yaml
index 544f449f306..696e2ba23db 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#05.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#05.verified.yaml
@@ -1,38 +1,15 @@
---
-apiVersion: "apps/v1"
-kind: "Deployment"
+apiVersion: "v1"
+kind: "ConfigMap"
metadata:
- name: "special-app-deployment"
+ name: "special-app-config"
labels:
app.kubernetes.io/name: "my-chart"
app.kubernetes.io/component: "SpeciaL-ApP"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-spec:
- template:
- metadata:
- labels:
- app.kubernetes.io/name: "my-chart"
- app.kubernetes.io/component: "SpeciaL-ApP"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- spec:
- containers:
- - image: "{{ .Values.parameters.SpeciaL_ApP.SpeciaL_ApP_image }}"
- name: "SpeciaL-ApP"
- envFrom:
- - configMapRef:
- name: "special-app-config"
- - secretRef:
- name: "special-app-secrets"
- imagePullPolicy: "IfNotPresent"
- selector:
- matchLabels:
- app.kubernetes.io/name: "my-chart"
- app.kubernetes.io/component: "SpeciaL-ApP"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
- replicas: 1
- revisionHistoryLimit: 3
- strategy:
- rollingUpdate:
- maxSurge: 1
- maxUnavailable: 1
- type: "RollingUpdate"
+data:
+ OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "{{ .Values.config.SpeciaL_ApP.OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY }}"
+ ConnectionStrings__api-cs2: "{{ .Values.config.SpeciaL_ApP.ConnectionStrings__api_cs2 }}"
+ OTEL_EXPORTER_OTLP_ENDPOINT: "{{ .Values.config.SpeciaL_ApP.OTEL_EXPORTER_OTLP_ENDPOINT }}"
+ OTEL_EXPORTER_OTLP_PROTOCOL: "{{ .Values.config.SpeciaL_ApP.OTEL_EXPORTER_OTLP_PROTOCOL }}"
+ OTEL_SERVICE_NAME: "{{ .Values.config.SpeciaL_ApP.OTEL_SERVICE_NAME }}"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#06.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#06.verified.yaml
index 696e2ba23db..cd69a3b814c 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#06.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#06.verified.yaml
@@ -1,15 +1,13 @@
---
apiVersion: "v1"
-kind: "ConfigMap"
+kind: "Secret"
metadata:
- name: "special-app-config"
+ name: "special-app-secrets"
labels:
app.kubernetes.io/name: "my-chart"
app.kubernetes.io/component: "SpeciaL-ApP"
app.kubernetes.io/instance: "{{ .Release.Name }}"
-data:
- OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY: "{{ .Values.config.SpeciaL_ApP.OTEL_DOTNET_EXPERIMENTAL_OTLP_RETRY }}"
- ConnectionStrings__api-cs2: "{{ .Values.config.SpeciaL_ApP.ConnectionStrings__api_cs2 }}"
- OTEL_EXPORTER_OTLP_ENDPOINT: "{{ .Values.config.SpeciaL_ApP.OTEL_EXPORTER_OTLP_ENDPOINT }}"
- OTEL_EXPORTER_OTLP_PROTOCOL: "{{ .Values.config.SpeciaL_ApP.OTEL_EXPORTER_OTLP_PROTOCOL }}"
- OTEL_SERVICE_NAME: "{{ .Values.config.SpeciaL_ApP.OTEL_SERVICE_NAME }}"
+stringData:
+ param3: "{{ .Values.secrets.SpeciaL_ApP.param3 }}"
+ ConnectionStrings__api-cs: "Url={{ .Values.config.SpeciaL_ApP.param0 }}, Secret={{ .Values.secrets.SpeciaL_ApP.param1 }}"
+type: "Opaque"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#07.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#07.verified.yaml
deleted file mode 100644
index cd69a3b814c..00000000000
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_HandlesSpecialResourceName#07.verified.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
----
-apiVersion: "v1"
-kind: "Secret"
-metadata:
- name: "special-app-secrets"
- labels:
- app.kubernetes.io/name: "my-chart"
- app.kubernetes.io/component: "SpeciaL-ApP"
- app.kubernetes.io/instance: "{{ .Release.Name }}"
-stringData:
- param3: "{{ .Values.secrets.SpeciaL_ApP.param3 }}"
- ConnectionStrings__api-cs: "Url={{ .Values.config.SpeciaL_ApP.param0 }}, Secret={{ .Values.secrets.SpeciaL_ApP.param1 }}"
-type: "Opaque"
diff --git a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ResourceWithProbes#00.verified.yaml b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ResourceWithProbes#00.verified.yaml
index d9837afd3a3..4cce26b3384 100644
--- a/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ResourceWithProbes#00.verified.yaml
+++ b/tests/Aspire.Hosting.Kubernetes.Tests/Snapshots/KubernetesPublisherTests.PublishAsync_ResourceWithProbes#00.verified.yaml
@@ -18,9 +18,6 @@ spec:
containers:
- image: "mcr.microsoft.com/dotnet/nightly/aspire-dashboard:latest"
name: "env-dashboard"
- envFrom:
- - configMapRef:
- name: "env-dashboard-config"
ports:
- name: "http"
protocol: "TCP"