Skip to content

Commit 5a63c09

Browse files
committed
setup
1 parent 4e604b9 commit 5a63c09

6 files changed

Lines changed: 462 additions & 738 deletions

File tree

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
###############################################################################
2+
# composite action: deploy-env
3+
#
4+
# Runs the four post-build deployment steps for a single environment:
5+
# 1. Update image tag in the GitOps repo
6+
# 2. Upsert Kubernetes DB credentials secret
7+
# 3. Wait for ArgoCD to reach Healthy + Synced
8+
# 4. Smoke-test the live URL
9+
#
10+
# Callers: cd-eks-gitops.yml (dev / staging / production deploy jobs)
11+
#
12+
# Note: composite actions cannot consume `secrets` context directly.
13+
# All sensitive values must be passed as inputs by the caller.
14+
###############################################################################
15+
name: "Deploy Environment"
16+
description: "Update GitOps repo, sync K8s secrets, wait for ArgoCD, smoke test"
17+
18+
inputs:
19+
# ── Identity ─────────────────────────────────────────────────────────────
20+
gitops-env:
21+
description: "Environment folder in the gitops repo (dev / staging / production)"
22+
required: true
23+
image-tag:
24+
description: "Docker image tag to deploy"
25+
required: true
26+
gitops-repo:
27+
description: "Owner/name of the GitOps repository"
28+
required: true
29+
30+
# ── K8s / AWS ─────────────────────────────────────────────────────────────
31+
eks-cluster-name:
32+
description: "EKS cluster name"
33+
required: true
34+
namespace:
35+
description: "Kubernetes namespace that holds the DB secret"
36+
required: true
37+
aws-role-arn:
38+
description: "IAM role ARN to assume (OIDC)"
39+
required: true
40+
aws-region:
41+
description: "AWS region"
42+
required: true
43+
44+
# ── Secrets passed as inputs ──────────────────────────────────────────────
45+
gitops-deploy-token:
46+
description: "PAT with contents:write on the GitOps repo"
47+
required: true
48+
db-host:
49+
description: "RDS hostname"
50+
required: true
51+
db-port:
52+
description: "RDS port"
53+
required: true
54+
db-name:
55+
description: "Database name"
56+
required: true
57+
db-username:
58+
description: "Database user"
59+
required: true
60+
db-password:
61+
description: "Database password"
62+
required: true
63+
argocd-server:
64+
description: "ArgoCD server hostname (no scheme)"
65+
required: true
66+
argocd-token:
67+
description: "ArgoCD API token"
68+
required: true
69+
70+
# ── ArgoCD poll settings ──────────────────────────────────────────────────
71+
argocd-app-name:
72+
description: "ArgoCD Application name to poll"
73+
required: true
74+
argocd-max-retries:
75+
description: "Maximum polling attempts (each waits 30 s)"
76+
required: false
77+
default: "30"
78+
79+
# ── Smoke test ────────────────────────────────────────────────────────────
80+
smoke-test-enabled:
81+
description: "Set to 'true' to run the smoke test step"
82+
required: false
83+
default: "false"
84+
app-url:
85+
description: "Base URL for the smoke-test health check"
86+
required: false
87+
default: ""
88+
warmup-seconds:
89+
description: "Seconds to wait before hitting the health endpoint"
90+
required: false
91+
default: "15"
92+
deploy-sha:
93+
description: "Commit SHA that was deployed (for summary)"
94+
required: false
95+
default: ""
96+
deploy-version:
97+
description: "Semver tag that was deployed (for summary)"
98+
required: false
99+
default: ""
100+
101+
runs:
102+
using: "composite"
103+
steps:
104+
# ── 1. Update GitOps repo ───────────────────────────────────────────────
105+
- name: "Checkout GitOps repo"
106+
uses: actions/checkout@v4
107+
with:
108+
repository: ${{ inputs.gitops-repo }}
109+
token: ${{ inputs.gitops-deploy-token }}
110+
path: _gitops
111+
112+
- name: "Patch image tag (${{ inputs.gitops-env }} → ${{ inputs.image-tag }})"
113+
shell: bash
114+
run: |
115+
docker run --rm --user root -v "$PWD/_gitops:/gitops" mikefarah/yq:4 \
116+
e '.backend.image.tag = "${{ inputs.image-tag }}" | .frontend.image.tag = "${{ inputs.image-tag }}"' \
117+
-i /gitops/environments/${{ inputs.gitops-env }}/values.yaml
118+
119+
- name: "Commit & push GitOps change"
120+
shell: bash
121+
run: |
122+
cd _gitops
123+
git config user.name "github-actions[bot]"
124+
git config user.email "github-actions[bot]@users.noreply.github.com"
125+
git add environments/${{ inputs.gitops-env }}/values.yaml
126+
git diff --cached --quiet && echo "No changes to commit" && exit 0
127+
git commit -m "chore(deploy): ${{ inputs.gitops-env }} image tag ${{ inputs.image-tag }} [skip ci]"
128+
git push origin main
129+
130+
# ── 2. Authenticate with AWS ────────────────────────────────────────────
131+
- name: "Authenticate with AWS"
132+
uses: ./.github/actions/aws-auth
133+
with:
134+
role-arn: ${{ inputs.aws-role-arn }}
135+
aws-region: ${{ inputs.aws-region }}
136+
137+
- name: "Update kubeconfig"
138+
shell: bash
139+
run: |
140+
aws eks update-kubeconfig \
141+
--name ${{ inputs.eks-cluster-name }} \
142+
--region ${{ inputs.aws-region }}
143+
144+
# ── 3. Upsert K8s DB secret ─────────────────────────────────────────────
145+
- name: "Upsert DB credentials secret"
146+
shell: bash
147+
run: |
148+
kubectl create secret generic mypythonproject1-db-secret \
149+
--namespace ${{ inputs.namespace }} \
150+
--from-literal=host=${{ inputs.db-host }} \
151+
--from-literal=port=${{ inputs.db-port }} \
152+
--from-literal=dbname=${{ inputs.db-name }} \
153+
--from-literal=username=${{ inputs.db-username }} \
154+
--from-literal=password=${{ inputs.db-password }} \
155+
--dry-run=client -o yaml | kubectl apply -f -
156+
157+
# ── 4. Wait for ArgoCD Healthy + Synced ────────────────────────────────
158+
- name: "Wait for ArgoCD sync (${{ inputs.argocd-app-name }})"
159+
shell: bash
160+
env:
161+
ARGOCD_SERVER: ${{ inputs.argocd-server }}
162+
ARGOCD_TOKEN: ${{ inputs.argocd-token }}
163+
run: |
164+
APP="${{ inputs.argocd-app-name }}"
165+
MAX=${{ inputs.argocd-max-retries }}
166+
SLEEP=30
167+
168+
for i in $(seq 1 "$MAX"); do
169+
RESP=$(curl -sf \
170+
-H "Authorization: Bearer $ARGOCD_TOKEN" \
171+
"http://${ARGOCD_SERVER}/api/v1/applications/${APP}" || true)
172+
173+
HEALTH=$(echo "$RESP" | jq -r '.status.health.status // "Unknown"')
174+
SYNC=$(echo "$RESP" | jq -r '.status.sync.status // "Unknown"')
175+
echo "[$i/$MAX] health=$HEALTH sync=$SYNC"
176+
177+
if [[ "$HEALTH" == "Healthy" && "$SYNC" == "Synced" ]]; then
178+
echo "✅ ArgoCD: $APP is Healthy and Synced"
179+
exit 0
180+
fi
181+
182+
if [[ "$HEALTH" == "Degraded" ]]; then
183+
echo "❌ ArgoCD: $APP health is Degraded — failing fast"
184+
exit 1
185+
fi
186+
187+
sleep "$SLEEP"
188+
done
189+
190+
echo "❌ Timed out waiting for $APP to become Healthy+Synced"
191+
exit 1
192+
193+
# ── 5. Smoke test (optional) ────────────────────────────────────────────
194+
- name: "Health check"
195+
if: inputs.smoke-test-enabled == 'true'
196+
shell: bash
197+
env:
198+
APP_URL: ${{ inputs.app-url }}
199+
WARMUP_SECS: ${{ inputs.warmup-seconds }}
200+
run: bash .github/scripts/smoke-test.sh
201+
202+
- name: "Write deployment summary"
203+
if: inputs.smoke-test-enabled == 'true'
204+
shell: bash
205+
run: |
206+
{
207+
echo "## ${{ inputs.gitops-env }} Deployment"
208+
echo "| Field | Value |"
209+
echo "|---------|-------|"
210+
echo "| Env | \`${{ inputs.gitops-env }}\` |"
211+
VERSION="${{ inputs.deploy-version }}"
212+
SHA="${{ inputs.deploy-sha }}"
213+
echo "| Version | \`${VERSION:-N/A}\` |"
214+
echo "| Commit | \`${SHA:-$GITHUB_SHA}\` |"
215+
} >> "$GITHUB_STEP_SUMMARY"
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
###############################################################################
2+
# Reusable workflow — Build & push Docker images to ECR
3+
#
4+
# Called by cd-eks-gitops.yml for dev / staging / production.
5+
# The calling job sets `environment:` so AWS_ROLE_TO_ASSUME resolves to the
6+
# correct environment-scoped secret automatically via `secrets: inherit`.
7+
###############################################################################
8+
name: "Build & Push to ECR"
9+
10+
on:
11+
workflow_call:
12+
inputs:
13+
git-ref:
14+
description: "Git ref or full SHA to checkout"
15+
required: true
16+
type: string
17+
env-name:
18+
description: "Environment name for Docker layer cache scope (dev/staging/production)"
19+
required: true
20+
type: string
21+
tags:
22+
description: "docker/metadata-action tags spec (multiline)"
23+
required: true
24+
type: string
25+
build-args:
26+
description: "Docker build-args (multiline KEY=VALUE)"
27+
required: true
28+
type: string
29+
scan-severity:
30+
description: "Trivy severities that trigger a finding"
31+
required: false
32+
type: string
33+
default: "CRITICAL"
34+
scan-exit-code:
35+
description: "Exit code when a scan finding is detected (0=warn, 1=block)"
36+
required: false
37+
type: string
38+
default: "0"
39+
outputs:
40+
sha-short:
41+
description: "7-char commit SHA used for image tagging"
42+
value: ${{ jobs.build.outputs.sha-short }}
43+
secrets:
44+
AWS_ROLE_TO_ASSUME:
45+
required: true
46+
AWS_REGION:
47+
required: true
48+
49+
jobs:
50+
build:
51+
name: "(${{ matrix.service }})"
52+
runs-on: ubuntu-latest
53+
timeout-minutes: 20
54+
strategy:
55+
matrix:
56+
service: [backend, frontend]
57+
fail-fast: true
58+
permissions:
59+
contents: read
60+
id-token: write
61+
outputs:
62+
# Both matrix legs write the same SHA; last-write-wins is safe here.
63+
sha-short: ${{ steps.meta.outputs.sha-short }}
64+
steps:
65+
- uses: actions/checkout@v4
66+
with:
67+
ref: ${{ inputs.git-ref }}
68+
69+
- name: Compute short SHA
70+
id: meta
71+
run: echo "sha-short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
72+
73+
- name: Authenticate with AWS
74+
uses: ./.github/actions/aws-auth
75+
with:
76+
role-arn: ${{ secrets.AWS_ROLE_TO_ASSUME }}
77+
aws-region: ${{ secrets.AWS_REGION }}
78+
79+
- name: Log in to Amazon ECR
80+
id: ecr-login
81+
uses: aws-actions/amazon-ecr-login@v2
82+
83+
- name: Build, tag & push
84+
uses: ./.github/actions/docker-build
85+
with:
86+
context: ./${{ matrix.service }}
87+
dockerfile: ./${{ matrix.service }}/Dockerfile
88+
image-name: mypythonproject1/${{ matrix.service }}
89+
registry: ${{ steps.ecr-login.outputs.registry }}
90+
tags: ${{ inputs.tags }}
91+
build-args: ${{ inputs.build-args }}
92+
platforms: linux/amd64
93+
scan: "true"
94+
scan-severity: ${{ inputs.scan-severity }}
95+
scan-exit-code: ${{ inputs.scan-exit-code }}
96+
cache-scope: ${{ matrix.service }}-${{ inputs.env-name }}

0 commit comments

Comments
 (0)