Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
43cbdd1
Add AKS starter deployment E2E test (Phase 1)
Feb 5, 2026
6ef79de
Fix AKS test: register required resource providers
Feb 5, 2026
7b754c1
Fix AKS test: use Standard_B2s_v2 VM size
Feb 5, 2026
886b180
Fix AKS test: use Standard_D2s_v3 VM size
Feb 5, 2026
4318b51
Add Phase 2 & 3: Aspire project creation, Helm chart generation, and …
Feb 5, 2026
d38acb6
Fix Kubernetes deployment: Add container build/push step
Feb 5, 2026
f46b53e
Fix duplicate Service ports in Kubernetes publisher
Feb 5, 2026
f175bca
Add explicit AKS-ACR attachment verification step
Feb 5, 2026
3ab65aa
Fix AKS image pull: correct Helm value paths and add ACR check
Feb 6, 2026
da42bb7
Fix duplicate Service/container ports: compare underlying values not …
Feb 6, 2026
1057b42
Re-enable AppService deployment tests
Feb 6, 2026
45adcbb
Add endpoint verification via kubectl port-forward to AKS test
Feb 6, 2026
c2aa4d7
Wait for pods to be ready before port-forward verification
Feb 6, 2026
41dd194
Use retry loop for health endpoint verification and log HTTP status c…
Feb 6, 2026
d1d6551
Use real app endpoints: /weatherforecast and / instead of /health
Feb 6, 2026
1934af2
Improve comments explaining duplicate port dedup rationale
Feb 6, 2026
a9bbfb9
Refactor cleanup to async pattern matching other deployment tests
Feb 6, 2026
f3aed68
Fix duplicate K8s ports: skip DefaultHttpsEndpoint in ProcessEndpoints
Feb 6, 2026
5fa81a7
Add AKS + Redis E2E deployment test
Feb 6, 2026
7d3be48
Fix ACR name collision between parallel AKS tests
Feb 6, 2026
dd62bbf
Fix Redis Helm deployment: provide missing cross-resource secret value
Feb 6, 2026
ae6eb39
Move ACR login before AKS creation to avoid OIDC token expiration
Feb 6, 2026
867064e
Merge main and resolve conflicts: keep early ACR login
Feb 6, 2026
079b0e2
Add pod diagnostics for Redis test: accept kubectl wait failure grace…
Feb 6, 2026
86683ed
Wait only for project resource pods, skip Redis (K8s publisher bug #1…
Feb 6, 2026
84b8a77
Remove redundant weather page grep check (Blazor SSR streaming issue)
Feb 6, 2026
1cf2c77
Add --max-time 10 to /weather curl (Blazor SSR streaming keeps connec…
Feb 6, 2026
fe2ce27
Fix /weather curl: capture status code in variable to handle SSR stre…
Feb 6, 2026
77a1c33
Fix K8s publisher: set ExecutionContext on CommandLineArgsCallbackCon…
Feb 6, 2026
bde5baf
Avoid leaking Redis password in test logs
Feb 6, 2026
eed9def
Replace kubectl exec redis-cli with pod status check to avoid contain…
Feb 6, 2026
f79ff20
Set REDIS_PASSWORD in helm install to prevent redis-server --requirep…
Feb 6, 2026
af91e2e
Fix Helm secret path: use parameter name (cache_password) not env var…
Feb 6, 2026
2ca23b5
Use dynamically generated GUID for Redis password instead of hardcode…
Feb 7, 2026
3e1ae7e
Pass same Redis password to webfrontend so it can authenticate to Red…
Feb 7, 2026
9bf4885
Merge remote-tracking branch 'origin/main' into aks-e2e-deployment
Feb 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/Aspire.Hosting.Kubernetes/KubernetesResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,10 @@ private async Task ProcessArgumentsAsync(KubernetesEnvironmentContext environmen
{
if (resource.TryGetAnnotationsOfType<CommandLineArgsCallbackAnnotation>(out var commandLineArgsCallbackAnnotations))
{
var context = new CommandLineArgsCallbackContext([], resource, cancellationToken: cancellationToken);
var context = new CommandLineArgsCallbackContext([], resource, cancellationToken: cancellationToken)
{
ExecutionContext = executionContext
};

foreach (var c in commandLineArgsCallbackAnnotations)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,15 @@ private async Task DeployStarterTemplateToAksCore(CancellationToken cancellation
.Enter()
.WaitForSuccessPrompt(counter, TimeSpan.FromMinutes(3));

// Step 4b: Login to ACR immediately (before AKS creation which takes 10-15 min).
// The OIDC federated token expires after ~5 minutes, so we must authenticate with
// ACR while it's still fresh. Docker credentials persist in ~/.docker/config.json.
output.WriteLine("Step 4b: Logging into Azure Container Registry (early, before token expires)...");
sequenceBuilder
.Type($"az acr login --name {acrName}")
.Enter()
.WaitForSuccessPrompt(counter, TimeSpan.FromSeconds(60));

// Step 5: Create AKS cluster with ACR attached
// Using minimal configuration: 1 node, Standard_D2s_v3 (widely available with quota)
output.WriteLine("Step 5: Creating AKS cluster (this may take 10-15 minutes)...");
Expand Down Expand Up @@ -274,12 +283,8 @@ private async Task DeployStarterTemplateToAksCore(CancellationToken cancellation
.Enter()
.WaitForSuccessPrompt(counter);

// Step 16: Login to ACR for Docker push
output.WriteLine("Step 16: Logging into Azure Container Registry...");
sequenceBuilder
.Type($"az acr login --name {acrName}")
.Enter()
.WaitForSuccessPrompt(counter, TimeSpan.FromSeconds(60));
// Step 16: ACR login was already done in Step 4b (before AKS creation).
// Docker credentials persist in ~/.docker/config.json.

// Step 17: Build and push container images to ACR
// The starter template creates webfrontend and apiservice projects
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ private async Task DeployStarterTemplateWithRedisToAksCore(CancellationToken can
// Generate unique names for Azure resources
var resourceGroupName = DeploymentE2ETestHelpers.GenerateResourceGroupName("aksredis");
var clusterName = $"aks-{DeploymentE2ETestHelpers.GetRunId()}-{DeploymentE2ETestHelpers.GetRunAttempt()}";
var redisPassword = Guid.NewGuid().ToString("N");
// ACR names must be alphanumeric only, 5-50 chars, globally unique
var acrName = $"acrr{DeploymentE2ETestHelpers.GetRunId()}{DeploymentE2ETestHelpers.GetRunAttempt()}".ToLowerInvariant();
acrName = new string(acrName.Where(char.IsLetterOrDigit).Take(50).ToArray());
Expand Down Expand Up @@ -139,6 +140,15 @@ private async Task DeployStarterTemplateWithRedisToAksCore(CancellationToken can
.Enter()
.WaitForSuccessPrompt(counter, TimeSpan.FromMinutes(3));

// Step 4b: Login to ACR immediately (before AKS creation which takes 10-15 min).
// The OIDC federated token expires after ~5 minutes, so we must authenticate with
// ACR while it's still fresh. Docker credentials persist in ~/.docker/config.json.
output.WriteLine("Step 4b: Logging into Azure Container Registry (early, before token expires)...");
sequenceBuilder
.Type($"az acr login --name {acrName}")
.Enter()
.WaitForSuccessPrompt(counter, TimeSpan.FromSeconds(60));

// Step 5: Create AKS cluster with ACR attached
output.WriteLine("Step 5: Creating AKS cluster (this may take 10-15 minutes)...");
sequenceBuilder
Expand Down Expand Up @@ -271,12 +281,8 @@ private async Task DeployStarterTemplateWithRedisToAksCore(CancellationToken can
.Enter()
.WaitForSuccessPrompt(counter);

// Step 16: Login to ACR for Docker push
output.WriteLine("Step 16: Logging into Azure Container Registry...");
sequenceBuilder
.Type($"az acr login --name {acrName}")
.Enter()
.WaitForSuccessPrompt(counter, TimeSpan.FromSeconds(60));
// Step 16: ACR login was already done in Step 4b (before AKS creation).
// Docker credentials persist in ~/.docker/config.json.

// Step 17: Build and push container images to ACR
// Only project resources need to be built — Redis uses a public container image
Expand Down Expand Up @@ -331,25 +337,38 @@ private async Task DeployStarterTemplateWithRedisToAksCore(CancellationToken can

// Step 21: Deploy Helm chart to AKS with ACR image overrides
// Only project resources need image overrides — Redis uses the public image from the chart
// Note: secrets.webfrontend.cache_password is a workaround for a K8s publisher bug where
// cross-resource secret references create Helm value paths under the consuming resource
// instead of referencing the owning resource's secret path (secrets.cache.REDIS_PASSWORD).
// Note: Two K8s publisher Helm value bugs require workarounds:
// 1. secrets.cache.cache_password: The Helm template expression uses the parameter name
// (cache_password from "cache-password") but values.yaml uses the env var key (REDIS_PASSWORD).
// We must set the parameter name path for the password to reach the K8s Secret.
// 2. secrets.webfrontend.cache_password: Cross-resource secret references create Helm value
// paths under the consuming resource instead of the owning resource (issue #14370).
output.WriteLine("Step 21: Deploying Helm chart to AKS...");
sequenceBuilder
.Type($"helm install aksredis ../charts --namespace default --wait --timeout 10m " +
$"--set parameters.webfrontend.webfrontend_image={acrName}.azurecr.io/webfrontend:latest " +
$"--set parameters.apiservice.apiservice_image={acrName}.azurecr.io/apiservice:latest " +
$"--set secrets.webfrontend.cache_password=\"\"")
$"--set secrets.cache.cache_password={redisPassword} " +
$"--set secrets.webfrontend.cache_password={redisPassword}")
.Enter()
.WaitForSuccessPrompt(counter, TimeSpan.FromMinutes(12));

// Step 22: Wait for all pods to be ready (including Redis)
output.WriteLine("Step 22: Waiting for pods to be ready...");
// Step 22: Wait for all pods to be ready (including Redis cache)
output.WriteLine("Step 22: Waiting for all pods to be ready...");
sequenceBuilder
.Type("kubectl wait --for=condition=ready pod --all -n default --timeout=120s")
.Type("kubectl wait --for=condition=ready pod -l app.kubernetes.io/component=apiservice --timeout=120s -n default && " +
"kubectl wait --for=condition=ready pod -l app.kubernetes.io/component=webfrontend --timeout=120s -n default && " +
"kubectl wait --for=condition=ready pod -l app.kubernetes.io/component=cache --timeout=120s -n default")
.Enter()
.WaitForSuccessPrompt(counter, TimeSpan.FromMinutes(3));

// Step 22b: Verify Redis container is running and stable (no restarts)
output.WriteLine("Step 22b: Verifying Redis container is stable...");
sequenceBuilder
.Type("kubectl get pod cache-statefulset-0 -o jsonpath='{.status.containerStatuses[0].ready} restarts:{.status.containerStatuses[0].restartCount}'")
.Enter()
.WaitForSuccessPrompt(counter, TimeSpan.FromSeconds(30));

// Step 23: Verify all pods are running
output.WriteLine("Step 23: Verifying pods are running...");
sequenceBuilder
Expand Down Expand Up @@ -392,29 +411,23 @@ private async Task DeployStarterTemplateWithRedisToAksCore(CancellationToken can
.WaitForSuccessPrompt(counter, TimeSpan.FromSeconds(60));

// Step 28: Verify webfrontend /weather page (exercises webfrontend → apiservice → Redis pipeline)
// The /weather page is server-side rendered and fetches data from the apiservice.
// Redis output caching is used, so this validates the full Redis integration.
// The /weather page uses Blazor SSR streaming rendering which keeps the HTTP connection open.
// We use -m 5 (max-time) to avoid curl hanging, and capture the status code in a variable
// because --max-time causes curl to exit non-zero (code 28) even on HTTP 200.
output.WriteLine("Step 28: Verifying webfrontend /weather page (exercises Redis cache)...");
sequenceBuilder
.Type("for i in $(seq 1 10); do sleep 3 && curl -sf http://localhost:18081/weather -o /dev/null -w '%{http_code}' && echo ' OK' && break; done")
.Type("for i in $(seq 1 10); do sleep 3; S=$(curl -so /dev/null -w '%{http_code}' -m 5 http://localhost:18081/weather); [ \"$S\" = \"200\" ] && echo \"$S OK\" && break; done")
.Enter()
.WaitForSuccessPrompt(counter, TimeSpan.FromSeconds(60));

// Step 29: Verify /weather page actually returns weather data
output.WriteLine("Step 29: Verifying weather page content...");
sequenceBuilder
.Type("curl -sf http://localhost:18081/weather | grep -q 'Weather' && echo 'Weather page content verified'")
.Enter()
.WaitForSuccessPrompt(counter, TimeSpan.FromSeconds(30));
.WaitForSuccessPrompt(counter, TimeSpan.FromSeconds(120));

// Step 30: Clean up port-forwards
output.WriteLine("Step 30: Cleaning up port-forwards...");
// Step 29: Clean up port-forwards
output.WriteLine("Step 29: Cleaning up port-forwards...");
sequenceBuilder
.Type("kill %1 %2 2>/dev/null; true")
.Enter()
.WaitForSuccessPrompt(counter, TimeSpan.FromSeconds(10));

// Step 31: Exit terminal
// Step 30: Exit terminal
sequenceBuilder
.Type("exit")
.Enter();
Expand Down
Loading