Skip to content

Commit 840c327

Browse files
committed
feat: add wait-for-services action and refactor service readiness checks in workflows
1 parent d45d852 commit 840c327

5 files changed

Lines changed: 98 additions & 52 deletions

File tree

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
name: "Wait for services to be ready"
2+
description: "Polls store-front and order-service health endpoints until they respond, with optional kubectl diagnostics on failure."
3+
4+
inputs:
5+
store-front-url:
6+
description: "Base URL of the store-front service (e.g., http://x.x.x.x)"
7+
required: true
8+
k8s-namespace:
9+
description: "Kubernetes namespace for kubectl diagnostics on failure. If empty, diagnostics are skipped."
10+
required: false
11+
default: ""
12+
max-attempts:
13+
description: "Maximum number of polling attempts per service"
14+
required: false
15+
default: "30"
16+
retry-interval:
17+
description: "Seconds to wait between polling attempts"
18+
required: false
19+
default: "10"
20+
stabilization-delay:
21+
description: "Seconds to wait after order-service responds before continuing"
22+
required: false
23+
default: "30"
24+
25+
runs:
26+
using: "composite"
27+
steps:
28+
- name: Wait for services to be ready
29+
shell: bash
30+
env:
31+
STORE_FRONT_URL: ${{ inputs.store-front-url }}
32+
K8S_NAMESPACE: ${{ inputs.k8s-namespace }}
33+
MAX_ATTEMPTS: ${{ inputs.max-attempts }}
34+
RETRY_INTERVAL: ${{ inputs.retry-interval }}
35+
STABILIZATION_DELAY: ${{ inputs.stabilization-delay }}
36+
run: |
37+
echo "Waiting for store-front at ${STORE_FRONT_URL}..."
38+
STORE_FRONT_READY=false
39+
for i in $(seq 1 "$MAX_ATTEMPTS"); do
40+
if curl -sf "${STORE_FRONT_URL}" > /dev/null 2>&1; then
41+
echo "Store front is responding"
42+
STORE_FRONT_READY=true
43+
break
44+
fi
45+
echo "Attempt ${i}/${MAX_ATTEMPTS}: Store front not ready, waiting ${RETRY_INTERVAL}s..."
46+
sleep "$RETRY_INTERVAL"
47+
done
48+
if [ "$STORE_FRONT_READY" != "true" ]; then
49+
echo "::error::Store front failed to respond after ${MAX_ATTEMPTS} attempts"
50+
exit 1
51+
fi
52+
53+
echo "Waiting for order-service to be ready via ${STORE_FRONT_URL}/api/orders/health..."
54+
ORDER_SERVICE_READY=false
55+
for i in $(seq 1 "$MAX_ATTEMPTS"); do
56+
HTTP_RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" "${STORE_FRONT_URL}/api/orders/health" 2>&1) || true
57+
if [ "$HTTP_RESPONSE" = "200" ]; then
58+
echo "Order service is responding (HTTP 200), waiting ${STABILIZATION_DELAY}s for all services to stabilize..."
59+
sleep "$STABILIZATION_DELAY"
60+
ORDER_SERVICE_READY=true
61+
break
62+
fi
63+
echo "Attempt ${i}/${MAX_ATTEMPTS}: Order service not ready (HTTP ${HTTP_RESPONSE}), waiting ${RETRY_INTERVAL}s..."
64+
sleep "$RETRY_INTERVAL"
65+
done
66+
if [ "$ORDER_SERVICE_READY" != "true" ]; then
67+
echo "::error::Order service failed to respond after ${MAX_ATTEMPTS} attempts"
68+
if [ -n "$K8S_NAMESPACE" ]; then
69+
echo "::group::Diagnostics - pod status"
70+
kubectl get pods -n "$K8S_NAMESPACE" -l app=order-service 2>&1 || true
71+
echo "--- order-service events ---"
72+
kubectl get events -n "$K8S_NAMESPACE" --field-selector involvedObject.kind=Pod --sort-by='.lastTimestamp' 2>&1 | grep order-service || true
73+
echo "--- order-service container status ---"
74+
kubectl get pods -n "$K8S_NAMESPACE" -l app=order-service -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{range .status.containerStatuses[*]} {.name}: ready={.ready}, restartCount={.restartCount}, state={.state}{"\n"}{end}{range .status.initContainerStatuses[*]} init/{.name}: ready={.ready}, state={.state}{"\n"}{end}{end}' 2>&1 || true
75+
echo ""
76+
echo "--- configmap keys (values redacted) ---"
77+
kubectl get configmap -n "$K8S_NAMESPACE" -l "kustomize.toolkit.fluxcd.io/name" -o jsonpath='{range .items[*]}ConfigMap: {.metadata.name}{"\n"}{range $k, $v := .data} {$k}=***{"\n"}{end}{end}' 2>&1 || true
78+
kubectl get configmap -n "$K8S_NAMESPACE" order-service -o jsonpath='{range $k, $v := .data}{$k}=***{"\n"}{end}' 2>&1 || true
79+
echo "::endgroup::"
80+
fi
81+
exit 1
82+
fi

.github/workflows/test-e2e-pr.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
steps:
2121
- name: Check if user has permission
2222
id: check
23-
uses: actions/github-script@v7
23+
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
2424
with:
2525
script: |
2626
const { data: collaborator } = await github.rest.repos.getCollaboratorPermissionLevel({
@@ -57,7 +57,7 @@ jobs:
5757
runs-on: ubuntu-latest
5858
steps:
5959
- name: Add status comment - Starting
60-
uses: actions/github-script@v7
60+
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
6161
with:
6262
script: |
6363
github.rest.issues.createComment({
@@ -89,7 +89,7 @@ jobs:
8989
steps:
9090
- name: Add status comment - Success
9191
if: needs.test-e2e.result == 'success'
92-
uses: actions/github-script@v7
92+
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
9393
with:
9494
script: |
9595
const startTime = new Date('${{ github.event.comment.created_at }}');
@@ -113,7 +113,7 @@ jobs:
113113
114114
- name: Add status comment - Failure
115115
if: needs.test-e2e.result == 'failure'
116-
uses: actions/github-script@v7
116+
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
117117
with:
118118
script: |
119119
github.rest.issues.createComment({

.github/workflows/test-e2e-reusable.yaml

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ jobs:
7575

7676
- name: Set helm chart location
7777
if: inputs.helm-chart-owner != ''
78-
run: sed -i "s/azure-samples.github.io/${HELM_CHART_OWNER}.github.io/" azure.yaml
78+
run: sed -i "s|azure-samples\.github\.io|${HELM_CHART_OWNER}.github.io|" azure.yaml
7979
env:
8080
HELM_CHART_OWNER: ${{ inputs.helm-chart-owner }}
8181

@@ -127,34 +127,17 @@ jobs:
127127
storeFrontIp=$(kubectl get service store-front -n $AZURE_AKS_NAMESPACE -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
128128
done
129129
echo "STORE_FRONT_URL=http://${storeFrontIp}" >> "$GITHUB_OUTPUT"
130+
echo "AZURE_AKS_NAMESPACE=${AZURE_AKS_NAMESPACE}" >> "$GITHUB_OUTPUT"
130131
131132
- name: Install Playwright dependencies
132133
run: npm ci
133134
working-directory: tests
134135

135136
- name: Wait for services to be ready
136-
run: |
137-
STORE_FRONT_URL="${{ steps.get_urls.outputs.STORE_FRONT_URL }}"
138-
echo "Waiting for store-front at ${STORE_FRONT_URL}..."
139-
for i in $(seq 1 30); do
140-
if curl -sf "${STORE_FRONT_URL}" > /dev/null 2>&1; then
141-
echo "Store front is responding"
142-
break
143-
fi
144-
echo "Attempt ${i}/30: Store front not ready, waiting 10s..."
145-
sleep 10
146-
done
147-
148-
echo "Waiting for order-service to be ready via ${STORE_FRONT_URL}/api/orders/health..."
149-
for i in $(seq 1 30); do
150-
if curl -sf "${STORE_FRONT_URL}/api/orders/health" > /dev/null 2>&1; then
151-
echo "Order service is responding, waiting 30s for all services to stabilize..."
152-
sleep 30
153-
break
154-
fi
155-
echo "Attempt ${i}/30: Order service not ready, waiting 10s..."
156-
sleep 10
157-
done
137+
uses: ./.github/actions/wait-for-services
138+
with:
139+
store-front-url: ${{ steps.get_urls.outputs.STORE_FRONT_URL }}
140+
k8s-namespace: ${{ steps.get_urls.outputs.AZURE_AKS_NAMESPACE }}
158141

159142
- name: Run Playwright tests
160143
run: npx playwright test --config=playwright.service.config.ts --workers=20
@@ -166,7 +149,7 @@ jobs:
166149
CI: true
167150

168151
- name: Upload Playwright report
169-
uses: actions/upload-artifact@v6
152+
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
170153
if: always()
171154
with:
172155
name: playwright-report

.github/workflows/test-playwright.yaml

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -30,28 +30,9 @@ jobs:
3030
working-directory: tests
3131

3232
- name: Wait for services to be ready
33-
run: |
34-
STORE_FRONT_URL="${{ inputs.storeFrontUrl }}"
35-
echo "Waiting for store-front at ${STORE_FRONT_URL}..."
36-
for i in $(seq 1 30); do
37-
if curl -sf "${STORE_FRONT_URL}" > /dev/null 2>&1; then
38-
echo "Store front is responding"
39-
break
40-
fi
41-
echo "Attempt ${i}/30: Store front not ready, waiting 10s..."
42-
sleep 10
43-
done
44-
45-
echo "Waiting for order-service to be ready via ${STORE_FRONT_URL}/api/orders/health..."
46-
for i in $(seq 1 30); do
47-
if curl -sf "${STORE_FRONT_URL}/api/orders/health" > /dev/null 2>&1; then
48-
echo "Order service is responding, waiting 30s for all services to stabilize..."
49-
sleep 30
50-
break
51-
fi
52-
echo "Attempt ${i}/30: Order service not ready, waiting 10s..."
53-
sleep 10
54-
done
33+
uses: ./.github/actions/wait-for-services
34+
with:
35+
store-front-url: ${{ inputs.storeFrontUrl }}
5536

5637
- name: Run Playwright tests
5738
run: npx playwright test --config=playwright.service.config.ts --workers=20
@@ -60,5 +41,5 @@ jobs:
6041
PLAYWRIGHT_SERVICE_URL: ${{ secrets.PLAYWRIGHT_SERVICE_URL }}
6142
SERVICE_STORE_ADMIN_ENDPOINT_URL: ${{ inputs.storeAdminUrl }}
6243
SERVICE_STORE_FRONT_ENDPOINT_URL: ${{ inputs.storeFrontUrl }}
63-
COMPANY_NAME: 'Contoso'
44+
COMPANY_NAME: "Contoso"
6445
CI: true

src/order-service/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Use an official Node.js runtime as a parent image
2-
FROM node:18.20.5-alpine AS builder
2+
FROM node:24.14-alpine AS builder
33

44
# Set the working directory to /app
55
WORKDIR /app

0 commit comments

Comments
 (0)