Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
150 changes: 150 additions & 0 deletions .github/workflows/cd.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
name: CD

on:
workflow_run:
workflows: ['CI']
types: [completed]
branches: [main]

concurrency:
group: cd-main
cancel-in-progress: false

permissions:
contents: write

Check warning on line 14 in .github/workflows/cd.yaml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Move this write permission from workflow level to job level.

See more on https://sonarcloud.io/project/issues?id=mck21_devsecops-platform&issues=AZ6oLnMdSLbzeDoaZU2l&open=AZ6oLnMdSLbzeDoaZU2l&pullRequest=18
id-token: write

Check warning on line 15 in .github/workflows/cd.yaml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Move this write permission from workflow level to job level.

See more on https://sonarcloud.io/project/issues?id=mck21_devsecops-platform&issues=AZ6oLnMdSLbzeDoaZU2m&open=AZ6oLnMdSLbzeDoaZU2m&pullRequest=18

env:
NAMESPACE: staging
OVERLAY_PATH: k8s/overlays/staging

jobs:
gate:
name: Gate on CI success
runs-on: ubuntu-latest
if: github.event.workflow_run.conclusion == 'success'
outputs:
sha_tag: sha-${{ github.event.workflow_run.head_sha }}
steps:
- name: Validate CI conclusion
run: echo "Deploying ${{ github.event.workflow_run.head_sha }}"

gitops-bump:
name: GitOps image bump
needs: gate
runs-on: ubuntu-latest
outputs:
idle_color: ${{ steps.bump.outputs.idle_color }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

Check failure on line 39 in .github/workflows/cd.yaml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make sure that no untrusted code is executed from a fork.

See more on https://sonarcloud.io/project/issues?id=mck21_devsecops-platform&issues=AZ6oLnMdSLbzeDoaZU2i&open=AZ6oLnMdSLbzeDoaZU2i&pullRequest=18
with:
ref: main
token: ${{ secrets.CD_BOT_TOKEN || github.token }}

- name: Bump idle color image tag
id: bump
run: |
OUTPUT="$(./scripts/gitops-bump.sh "${{ needs.gate.outputs.sha_tag }}")"
eval "$OUTPUT"
echo "idle_color=${idle_color}" >> "$GITHUB_OUTPUT"

- name: Commit and push tag bump
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add "${{ env.OVERLAY_PATH }}/kustomization.yaml"
git commit -m "chore(cd): bump staging ${{ steps.bump.outputs.idle_color }} to ${{ needs.gate.outputs.sha_tag }} [skip ci]"
git push

deploy:
name: Sync, health check, traffic switch
needs: [gate, gitops-bump]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

Check failure on line 64 in .github/workflows/cd.yaml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make sure that no untrusted code is executed from a fork.

See more on https://sonarcloud.io/project/issues?id=mck21_devsecops-platform&issues=AZ6oLnMdSLbzeDoaZU2j&open=AZ6oLnMdSLbzeDoaZU2j&pullRequest=18
with:
ref: main
token: ${{ secrets.CD_BOT_TOKEN || github.token }}

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/${{ vars.PROJECT_NAME }}-staging-cicd-role
aws-region: ${{ vars.AWS_REGION }}

- name: Configure kubectl
run: |
aws eks update-kubeconfig \
--name "${{ vars.EKS_CLUSTER_NAME }}" \
--region "${{ vars.AWS_REGION }}"

- name: Wait for ArgoCD sync and rollout
run: ./scripts/blue-green-health.sh "${{ needs.gitops-bump.outputs.idle_color }}" "${{ env.NAMESPACE }}"

- name: Switch traffic to new color
run: |
./scripts/blue-green-switch.sh "${{ needs.gitops-bump.outputs.idle_color }}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add k8s/blue-green/virtualservice.yaml k8s/overlays/staging/active-color.yaml
git commit -m "chore(cd): switch staging traffic to ${{ needs.gitops-bump.outputs.idle_color }} [skip ci]"
git push

- name: Public health check
if: ${{ vars.STAGING_HEALTH_URL != '' }}
run: |
for i in $(seq 1 30); do
if curl -fsS "${{ vars.STAGING_HEALTH_URL }}"; then
echo "Public health check passed"
exit 0
fi
sleep 10
done
echo "Public health check failed" >&2
exit 1

rollback:
name: Rollback on failure
needs: [gate, gitops-bump, deploy]
if: failure() && needs.gate.result == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

Check failure on line 112 in .github/workflows/cd.yaml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make sure that no untrusted code is executed from a fork.

See more on https://sonarcloud.io/project/issues?id=mck21_devsecops-platform&issues=AZ6oLnMdSLbzeDoaZU2k&open=AZ6oLnMdSLbzeDoaZU2k&pullRequest=18
with:
ref: main
fetch-depth: 0
token: ${{ secrets.CD_BOT_TOKEN || github.token }}

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/${{ vars.PROJECT_NAME }}-staging-cicd-role
aws-region: ${{ vars.AWS_REGION }}

- name: Configure kubectl
run: |
aws eks update-kubeconfig \
--name "${{ vars.EKS_CLUSTER_NAME }}" \
--region "${{ vars.AWS_REGION }}"

- name: Revert traffic and active color
run: |
ACTIVE="$(grep 'ACTIVE_COLOR:' k8s/overlays/staging/active-color.yaml | awk '{print $2}')"
if [[ "$ACTIVE" == "${{ needs.gitops-bump.outputs.idle_color }}" ]]; then
./scripts/rollback.sh
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add k8s/blue-green/virtualservice.yaml k8s/overlays/staging/active-color.yaml
git diff --cached --quiet || git commit -m "chore(cd): rollback staging traffic [skip ci]"
git push || true
fi

- name: Revert image tag bump commit
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
BUMP_SHA="$(git log -1 --grep 'bump staging' --format=%H || true)"
if [[ -n "$BUMP_SHA" ]]; then
git revert --no-edit "$BUMP_SHA" || true
git push || true
fi
160 changes: 160 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
name: CI

on:
push:
branches: ['**']
pull_request:

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

env:
# Prisma 7 loads prisma.config.ts at postinstall; no .env file in CI
DATABASE_URL: postgresql://ci:ci@localhost:5432/ci

jobs:
lint:
name: Lint
runs-on: ubuntu-latest
if: github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]')
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14

- uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3

- name: Install yamllint
run: pip install yamllint

- name: Install backend dependencies
working-directory: app/backend
run: bun install --frozen-lockfile

- name: ESLint
working-directory: app/backend
run: bun run lint:ci

- name: yamllint
run: |
yamllint -c .yamllint k8s/
yamllint -c .yamllint .github/

- name: Terraform fmt
run: terraform fmt -check -recursive infrastructure/

test:
name: Unit Tests
runs-on: ubuntu-latest
if: github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]')
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14

- name: Cache Bun dependencies
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.bun/install/cache
key: bun-${{ hashFiles('app/backend/bun.lock') }}
restore-keys: |
bun-

- name: Install backend dependencies
working-directory: app/backend
run: bun install --frozen-lockfile

- name: Jest with coverage
working-directory: app/backend
run: bun run test:ci

- name: Upload coverage artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: coverage
path: app/backend/coverage/
retention-days: 7

# SonarCloud analysis runs via the SonarCloud GitHub App (check: "SonarCloud Code Analysis").
# A manual scanner job conflicts when Automatic Analysis is enabled on the free plan.

build-and-push:
name: Build, Scan & Push
needs: [lint, test]
runs-on: ubuntu-latest
if: github.event_name != 'push' || !contains(github.event.head_commit.message, '[skip ci]')
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3

- name: Build image
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: app/backend
push: false
load: true
tags: backend:ci
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Trivy image scan
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
with:
image-ref: backend:ci
format: table
severity: CRITICAL
ignore-unfixed: true
exit-code: '1'

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4
with:
role-to-assume: arn:aws:iam::${{ vars.AWS_ACCOUNT_ID }}:role/${{ vars.PROJECT_NAME }}-staging-cicd-role
aws-region: ${{ vars.AWS_REGION }}

- name: Login to Amazon ECR
uses: aws-actions/amazon-ecr-login@fa648b43de3d4d023bcb3f89ed6940096949c419 # v2
id: ecr-login

- name: Set image tags
id: tags
run: |
REGISTRY="${{ vars.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com"
STAGING="${REGISTRY}/${{ vars.PROJECT_NAME }}-staging-backend"
echo "staging=${STAGING}" >> "$GITHUB_OUTPUT"
echo "sha_tag=sha-${{ github.sha }}" >> "$GITHUB_OUTPUT"

- name: Push to staging ECR
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: app/backend
push: true
tags: |
${{ steps.tags.outputs.staging }}:${{ steps.tags.outputs.sha_tag }}
${{ steps.tags.outputs.staging }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Push to production ECR
if: github.ref == 'refs/heads/main' && vars.ENABLE_PROD_ECR == 'true'
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: app/backend
push: true
tags: |
${{ vars.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com/${{ vars.PROJECT_NAME }}-production-backend:${{ steps.tags.outputs.sha_tag }}
${{ vars.AWS_ACCOUNT_ID }}.dkr.ecr.${{ vars.AWS_REGION }}.amazonaws.com/${{ vars.PROJECT_NAME }}-production-backend:latest
cache-from: type=gha
cache-to: type=gha,mode=max
27 changes: 15 additions & 12 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Entry point for AI agents working on this repository. Read this file first, then
| Dev cluster | Minikube (local) |
| Staging / Production | EKS on AWS |

**Current phase:** Phase 3 complete. **Next:** Phase 4CI Pipeline. See [STATUS.md](STATUS.md).
**Current phase:** Phase 5 implemented (staging-first CD). **Next:** Validate CD on EKS staging, then Phase 6Security Hardening. See [STATUS.md](STATUS.md).

---

Expand All @@ -33,7 +33,8 @@ devsecops-platform/
├── app/frontend/ # Empty — reserved for future UI
├── infrastructure/ # Terraform modules + staging/production envs
├── k8s/ # Kustomize manifests (base + overlays + blue-green + argocd)
├── .github/workflows/ # Empty — Phase 4
├── .github/workflows/ # ci.yaml, cd.yaml, security.yaml, terraform.yaml
├── scripts/ # blue/green CD automation (Phase 5)
├── docs/ # Placeholder — full docs in Phase 9
├── images/phase-N/ # Screenshot evidence per phase
├── docker-compose.yml # Local dev stack (postgres + redis + backend)
Expand Down Expand Up @@ -117,14 +118,12 @@ Summary:

| Deferred to | Do not implement yet |
|-------------|---------------------|
| Phase 4 | GitHub Actions CI, ECR push, SonarCloud gate |
| Phase 5 | ArgoCD auto-sync, CD image-tag updates, blue/green traffic switch scripts |
| Phase 5 | Live EKS deploy of the application |
| Phase 6 | Kyverno policies, NetworkPolicy, External Secrets, manifest `securityContext` |
| Phase 7 | Grafana dashboards, Loki, custom Prometheus ServiceMonitors |
| Phase 9 | Full `docs/`, ADRs, architecture diagrams |
| End of PLAN | Production CD auto-sync — see [docs/cd-production-promotion.md](docs/cd-production-promotion.md) |

**Scaffolding exists but is not wired:** `k8s/argocd/application-*.yaml`, `k8s/blue-green/`, `k8s/overlays/staging|production/` — do not connect to live CD until Phase 5.
**Phase 5 (staging-first):** CD auto-deploys to **staging EKS only**. Dev is manual. Run EKS bootstrap once: [k8s/argocd/install-notes.md](k8s/argocd/install-notes.md). Set GitHub vars `EKS_CLUSTER_NAME`, `STAGING_HEALTH_URL`.

---

Expand All @@ -144,6 +143,10 @@ Summary:
| Terraform staging/prod | `infrastructure/environments/staging/`, `production/` |
| Phase roadmap | `PLAN.md` |
| Phase progress | `STATUS.md` |
| CI / CD workflows | `.github/workflows/ci.yaml`, `.github/workflows/cd.yaml` |
| CD scripts | `scripts/gitops-bump.sh`, `scripts/blue-green-switch.sh`, `scripts/rollback.sh` |
| SonarCloud config | `sonar-project.properties` |
| GitHub OIDC (Terraform) | `infrastructure/modules/github-oidc/` |

---

Expand All @@ -157,12 +160,12 @@ Summary:

---

## Tech Stack Note for CI (Phase 4)
## Tech Stack Note CI (Phase 4, done)

The application is **NestJS / TypeScript / Jest / Bun**, not Python. When implementing Phase 4 CI:
The application is **NestJS / TypeScript / Jest / Bun**, not Python:

- Lint: ESLint (not ruff)
- Tests: Jest with coverage (not pytest)
- SonarCloud: TypeScript sources under `app/backend/src/`
- Lint: ESLint via `bun run lint:ci` (not ruff)
- Tests: Jest with coverage via `bun run test:ci` (not pytest)
- SonarCloud: TypeScript sources under `app/backend/src/`; quality gate via GitHub App check on free plan

Some older references in PLAN.md Phase 4 may still mention Python — treat NestJS/TypeScript as the source of truth.
Some older references in [PLAN.md](PLAN.md) may still mention Python — treat NestJS/TypeScript as the source of truth.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ The application is a **Feature Flag Service** — manage feature toggles per env
| 1 | AWS Infrastructure (Terraform) | Done |
| 2 | Kubernetes base (Minikube + Istio) | Done |
| 3 | Feature Flag Service + K8s manifests | **Done** |
| 4 | CI Pipeline (GitHub Actions, SonarCloud) | Next |
| 5–9 | CD, Security, Monitoring, DR, Docs | Planned |
| 4 | CI Pipeline (GitHub Actions, SonarCloud, ECR) | **Done** |
| 5 | CD Pipeline & GitOps (ArgoCD, staging-first) | **Implemented** — validate on EKS |
| 6–9 | Security, Monitoring, DR, Docs | Planned |

Full tracker: [STATUS.md](STATUS.md) · Full roadmap: [PLAN.md](PLAN.md)

Expand Down
Loading
Loading