diff --git a/.github/workflows/aws-cdk.yml b/.github/workflows/aws-cdk.yml index 321440d..2bfbc4f 100644 --- a/.github/workflows/aws-cdk.yml +++ b/.github/workflows/aws-cdk.yml @@ -41,6 +41,11 @@ on: type: boolean required: false default: false + skip-validation: + description: "Skip post-deployment validation" + type: boolean + required: false + default: false # Advanced Configuration context-values: @@ -93,23 +98,29 @@ on: outputs: stack-outputs: description: "CloudFormation stack outputs as JSON" - value: ${{ jobs.deploy.outputs.stack-outputs }} + value: ${{ jobs.cdk.outputs.stack-outputs }} deployment-status: description: "Deployment status (success/failed)" - value: ${{ jobs.deploy.outputs.deployment-status }} + value: ${{ jobs.cdk.outputs.deployment-status }} jobs: - # Setup Node.js environment with caching - setup-node: - name: 🚀 Setup Node.js Environment + prepare: + name: 🔍 Prepare CDK Deployment runs-on: ubuntu-latest + environment: ${{ inputs.github-environment }} outputs: node-version: ${{ steps.node-version.outputs.version }} package-manager: ${{ steps.detect-package-manager.outputs.manager }} - cache-key: ${{ steps.cache-config.outputs.key }} + context-args: ${{ steps.context-config.outputs.args }} + stack-name: ${{ steps.resolve-stack-name.outputs.stack-name }} + sanitised-cdk-stack-name: ${{ steps.sanitise.outputs.sanitised-cdk-stack-name }} + cdk-bootstrap-cmd: ${{ steps.parse-cdk-config.outputs.bootstrap-cmd }} + cdk-synth-cmd: ${{ steps.parse-cdk-config.outputs.synth-cmd }} + cdk-diff-cmd: ${{ steps.parse-cdk-config.outputs.diff-cmd }} + cdk-deploy-cmd: ${{ steps.parse-cdk-config.outputs.deploy-cmd }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: lfs: ${{ inputs.lfs }} @@ -145,11 +156,11 @@ jobs: fi - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: ${{ steps.node-version.outputs.version }} - - name: Enable Corepack for Yarn Berry + - name: Enable Corepack if: steps.detect-package-manager.outputs.manager == 'yarn-berry' run: | echo "🔧 Enabling Corepack for Yarn Berry..." @@ -161,42 +172,44 @@ jobs: corepack prepare yarn@stable --activate fi - # Initialize yarn and ensure cache directory exists for post-job cache step + # Initialise yarn and ensure cache directory exists for post-job cache step yarn --version CACHE_DIR=$(yarn config get cacheFolder) mkdir -p "$CACHE_DIR" echo "✅ Corepack enabled and cache directory created: $CACHE_DIR" - name: Configure dependency cache - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: ${{ steps.node-version.outputs.version }} cache: ${{ steps.detect-package-manager.outputs.manager == 'yarn-berry' && 'yarn' || (steps.detect-package-manager.outputs.manager == 'yarn-classic' && 'yarn' || steps.detect-package-manager.outputs.manager) }} - - name: Configure cache key - id: cache-config + - name: Install safe-chain + run: curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci + + - name: Install dependencies run: | - CACHE_KEY="node-${{ steps.node-version.outputs.version }}-${{ steps.detect-package-manager.outputs.manager }}-${{ hashFiles('**/package-lock.json', '**/yarn.lock', '**/pnpm-lock.yaml') }}" - echo "key=$CACHE_KEY" >> $GITHUB_OUTPUT + echo "📦 Installing dependencies with ${{ steps.detect-package-manager.outputs.manager }}..." - # Validate inputs and prepare deployment configuration - prepare: - name: 🔍 Prepare CDK Deployment - runs-on: ubuntu-latest - environment: ${{ inputs.github-environment }} - outputs: - context-args: ${{ steps.context-config.outputs.args }} - stack-name: ${{ steps.resolve-stack-name.outputs.stack-name }} - sanitised-cdk-stack-name: ${{ steps.sanitise.outputs.sanitised-cdk-stack-name }} - cdk-bootstrap-cmd: ${{ steps.parse-cdk-config.outputs.bootstrap-cmd }} - cdk-synth-cmd: ${{ steps.parse-cdk-config.outputs.synth-cmd }} - cdk-diff-cmd: ${{ steps.parse-cdk-config.outputs.diff-cmd }} - cdk-deploy-cmd: ${{ steps.parse-cdk-config.outputs.deploy-cmd }} - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - lfs: ${{ inputs.lfs }} + verbose="" + if [ "${{ inputs.debug }}" = "true" ]; then + verbose="--verbose" + fi + + case "${{ steps.detect-package-manager.outputs.manager }}" in + "npm") + npm ci $verbose + ;; + "yarn-classic") + yarn install --frozen-lockfile $verbose + ;; + "yarn-berry") + yarn install --immutable $verbose + ;; + "pnpm") + pnpm install --frozen-lockfile $verbose + ;; + esac - name: Set CDK commands id: parse-cdk-config @@ -316,26 +329,40 @@ jobs: sanitised_cdk_stack_name=$(echo "${{ steps.resolve-stack-name.outputs.stack-name }}" | tr -cd '[:alnum:]-_') echo "sanitised-cdk-stack-name=$sanitised_cdk_stack_name" >> $GITHUB_OUTPUT - # Bootstrap CDK environment if required - bootstrap: - name: 🥾 CDK Bootstrap + - name: Package node_modules for artifact + run: | + echo "📦 Packaging node_modules..." + tar -czf node_modules.tar.gz node_modules/ + echo "✅ node_modules packaged ($(du -h node_modules.tar.gz | cut -f1))" + + - name: Upload node_modules artifact + uses: actions/upload-artifact@v6 + with: + name: node_modules-${{ steps.sanitise.outputs.sanitised-cdk-stack-name }} + path: node_modules.tar.gz + retention-days: 1 + + cdk: + name: ☁️ CDK Operations runs-on: ubuntu-latest - needs: [setup-node, prepare] - if: inputs.bootstrap == true + needs: [prepare] environment: ${{ inputs.github-environment }} + outputs: + stack-outputs: ${{ steps.deploy.outputs.stack-outputs }} + deployment-status: ${{ steps.deploy.outputs.status }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: lfs: ${{ inputs.lfs }} - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: ${{ needs.setup-node.outputs.node-version }} + node-version: ${{ needs.prepare.outputs.node-version }} - name: Enable Corepack - if: needs.setup-node.outputs.package-manager == 'yarn-berry' + if: needs.prepare.outputs.package-manager == 'yarn-berry' run: | echo "🔧 Enabling Corepack for Yarn Berry..." corepack enable @@ -347,14 +374,17 @@ jobs: fi echo "✅ Corepack enabled" - - name: Configure dependency cache - uses: actions/setup-node@v4 + - name: Download node_modules artifact + uses: actions/download-artifact@v5 with: - node-version: ${{ needs.setup-node.outputs.node-version }} - cache: ${{ needs.setup-node.outputs.package-manager == 'yarn-berry' && 'yarn' || (needs.setup-node.outputs.package-manager == 'yarn-classic' && 'yarn' || needs.setup-node.outputs.package-manager) }} + name: node_modules-${{ needs.prepare.outputs.sanitised-cdk-stack-name }} - - name: Install safe-chain - run: curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci + - name: Extract node_modules + run: | + echo "📦 Extracting node_modules..." + tar -xzf node_modules.tar.gz + rm node_modules.tar.gz + echo "✅ node_modules extracted" - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 @@ -364,31 +394,8 @@ jobs: aws-region: ${{ inputs.aws-region }} role-to-assume: ${{ secrets.CFN_EXECUTION_ROLE }} - - name: Install dependencies - run: | - echo "📦 Installing dependencies with ${{ needs.setup-node.outputs.package-manager }}..." - - verbose="" - if [ "${{ inputs.debug }}" = "true" ]; then - verbose="--verbose" - fi - - case "${{ needs.setup-node.outputs.package-manager }}" in - "npm") - npm ci $verbose - ;; - "yarn-classic") - yarn install --frozen-lockfile $verbose - ;; - "yarn-berry") - yarn install --immutable $verbose - ;; - "pnpm") - pnpm install --frozen-lockfile $verbose - ;; - esac - - name: Bootstrap CDK environment + if: inputs.bootstrap == true run: | echo "🥾 Bootstrapping CDK environment..." @@ -417,84 +424,8 @@ jobs: echo "✅ CDK environment bootstrapped successfully" - # Synthesize CDK application - synth: - name: 🔨 CDK Synthesis - runs-on: ubuntu-latest - needs: [setup-node, prepare, bootstrap] - if: | - !cancelled() && - needs.setup-node.result == 'success' && - needs.prepare.result == 'success' && - (needs.bootstrap.result == 'success' || needs.bootstrap.result == 'skipped') && - inputs.synth == true - environment: ${{ inputs.github-environment }} - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - lfs: ${{ inputs.lfs }} - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ needs.setup-node.outputs.node-version }} - - - name: Enable Corepack - if: needs.setup-node.outputs.package-manager == 'yarn-berry' - run: | - echo "🔧 Enabling Corepack for Yarn Berry..." - corepack enable - - # If no packageManager field, default to stable Berry - if ! grep -q '"packageManager"' package.json; then - echo "📦 No packageManager found, preparing stable Yarn..." - corepack prepare yarn@stable --activate - fi - echo "✅ Corepack enabled" - - - name: Configure dependency cache - uses: actions/setup-node@v4 - with: - node-version: ${{ needs.setup-node.outputs.node-version }} - cache: ${{ needs.setup-node.outputs.package-manager == 'yarn-berry' && 'yarn' || (needs.setup-node.outputs.package-manager == 'yarn-classic' && 'yarn' || needs.setup-node.outputs.package-manager) }} - - - name: Install safe-chain - run: curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-access-key-id: ${{ vars.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ inputs.aws-region }} - role-to-assume: ${{ secrets.CFN_EXECUTION_ROLE }} - - - name: Install dependencies - run: | - echo "📦 Installing dependencies with ${{ needs.setup-node.outputs.package-manager }}..." - - verbose="" - if [ "${{ inputs.debug }}" = "true" ]; then - verbose="--verbose" - fi - - case "${{ needs.setup-node.outputs.package-manager }}" in - "npm") - npm ci $verbose - ;; - "yarn-classic") - yarn install --frozen-lockfile $verbose - ;; - "yarn-berry") - yarn install --immutable $verbose - ;; - "pnpm") - pnpm install --frozen-lockfile $verbose - ;; - esac - - name: Synthesize CDK application + if: inputs.synth == true run: | echo "🔨 Synthesizing CDK application..." @@ -513,98 +444,18 @@ jobs: echo "✅ CDK synthesis completed successfully" - name: Upload synthesis artifacts - uses: actions/upload-artifact@v4 + if: inputs.synth == true + uses: actions/upload-artifact@v6 with: name: cdk-synthesis-${{ needs.prepare.outputs.sanitised-cdk-stack-name }} path: cdk.out/ - retention-days: 30 - - # Generate deployment diff - diff: - name: 📊 CDK Diff - runs-on: ubuntu-latest - needs: [setup-node, prepare, synth] - if: | - !cancelled() && - needs.setup-node.result == 'success' && - needs.prepare.result == 'success' && - (needs.synth.result == 'success' || needs.synth.result == 'skipped') && - inputs.diff == true - environment: ${{ inputs.github-environment }} - outputs: - has-changes: ${{ steps.diff-analysis.outputs.has-changes }} - diff-summary: ${{ steps.diff-analysis.outputs.summary }} - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - lfs: ${{ inputs.lfs }} - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ needs.setup-node.outputs.node-version }} - - - name: Enable Corepack - if: needs.setup-node.outputs.package-manager == 'yarn-berry' - run: | - echo "🔧 Enabling Corepack for Yarn Berry..." - corepack enable - - # If no packageManager field, default to stable Berry - if ! grep -q '"packageManager"' package.json; then - echo "📦 No packageManager found, preparing stable Yarn..." - corepack prepare yarn@stable --activate - fi - echo "✅ Corepack enabled" - - - name: Configure dependency cache - uses: actions/setup-node@v4 - with: - node-version: ${{ needs.setup-node.outputs.node-version }} - cache: ${{ needs.setup-node.outputs.package-manager == 'yarn-berry' && 'yarn' || (needs.setup-node.outputs.package-manager == 'yarn-classic' && 'yarn' || needs.setup-node.outputs.package-manager) }} - - - name: Install safe-chain - run: curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-access-key-id: ${{ vars.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ inputs.aws-region }} - role-to-assume: ${{ secrets.CFN_EXECUTION_ROLE }} - - - name: Download synthesis artifacts - if: needs.synth.result == 'success' - uses: actions/download-artifact@v4 - with: - name: cdk-synthesis-${{ needs.prepare.outputs.sanitised-cdk-stack-name }} - path: cdk.out/ - - - name: Install dependencies - run: | - echo "📦 Installing dependencies with ${{ needs.setup-node.outputs.package-manager }}..." - - case "${{ needs.setup-node.outputs.package-manager }}" in - "npm") - npm ci - ;; - "yarn-classic") - yarn install --frozen-lockfile - ;; - "yarn-berry") - yarn install --immutable - ;; - "pnpm") - pnpm install --frozen-lockfile - ;; - esac + retention-days: 7 - name: Generate deployment diff + if: inputs.diff == true id: diff-analysis run: | - echo "📊 Analyzing deployment changes..." + echo "📊 Analysing deployment changes..." DIFF_CMD="${{ needs.prepare.outputs.cdk-diff-cmd }}" @@ -616,7 +467,7 @@ jobs: # Save diff to file for analysis echo "$diff_output" > deployment-diff.txt - # Analyze diff for changes + # Analyse diff for changes if echo "$diff_output" | grep -q "There were no differences"; then echo "has-changes=false" >> $GITHUB_OUTPUT echo "summary=No infrastructure changes detected" >> $GITHUB_OUTPUT @@ -636,97 +487,16 @@ jobs: fi - name: Upload diff analysis - uses: actions/upload-artifact@v4 + if: inputs.diff == true + uses: actions/upload-artifact@v6 with: name: deployment-diff-${{ needs.prepare.outputs.sanitised-cdk-stack-name }} path: deployment-diff.txt - retention-days: 30 - - # Deploy CDK stack - deploy: - name: "🚀 CDK Deploy" - runs-on: ubuntu-latest - needs: [setup-node, prepare, synth, diff] - if: | - !cancelled() && - needs.setup-node.result == 'success' && - needs.prepare.result == 'success' && - (needs.synth.result == 'success' || needs.synth.result == 'skipped') && - (needs.diff.result == 'success' || needs.diff.result == 'skipped') && - inputs.deploy == true - environment: ${{ inputs.github-environment }} - outputs: - stack-outputs: ${{ steps.deployment.outputs.stack-outputs }} - deployment-status: ${{ steps.deployment.outputs.status }} - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - lfs: ${{ inputs.lfs }} - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ needs.setup-node.outputs.node-version }} - - - name: Enable Corepack - if: needs.setup-node.outputs.package-manager == 'yarn-berry' - run: | - echo "🔧 Enabling Corepack for Yarn Berry..." - corepack enable - - # If no packageManager field, default to stable Berry - if ! grep -q '"packageManager"' package.json; then - echo "📦 No packageManager found, preparing stable Yarn..." - corepack prepare yarn@stable --activate - fi - echo "✅ Corepack enabled" - - - name: Configure dependency cache - uses: actions/setup-node@v4 - with: - node-version: ${{ needs.setup-node.outputs.node-version }} - cache: ${{ needs.setup-node.outputs.package-manager == 'yarn-berry' && 'yarn' || (needs.setup-node.outputs.package-manager == 'yarn-classic' && 'yarn' || needs.setup-node.outputs.package-manager) }} - - - name: Install safe-chain - run: curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-access-key-id: ${{ vars.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ inputs.aws-region }} - role-to-assume: ${{ secrets.CFN_EXECUTION_ROLE }} - - - name: Download synthesis artifacts - if: needs.synth.result == 'success' - uses: actions/download-artifact@v4 - with: - name: cdk-synthesis-${{ needs.prepare.outputs.sanitised-cdk-stack-name }} - path: cdk.out/ - - - name: Install dependencies - run: | - echo "📦 Installing dependencies with ${{ needs.setup-node.outputs.package-manager }}..." - - case "${{ needs.setup-node.outputs.package-manager }}" in - "npm") - npm ci - ;; - "yarn-classic") - yarn install --frozen-lockfile - ;; - "yarn-berry") - yarn install --immutable - ;; - "pnpm") - pnpm install --frozen-lockfile - ;; - esac + retention-days: 7 - name: Execute CDK deployment - id: deployment + if: inputs.deploy == true + id: deploy run: | verbose="" if [ "${{ inputs.debug }}" = "true" ]; then @@ -757,81 +527,15 @@ jobs: echo "✅ Stack deployed successfully" - name: Upload deployment artifacts - if: steps.deployment.outputs.status == 'success' - uses: actions/upload-artifact@v4 + if: inputs.deploy == true && steps.deploy.outputs.status == 'success' + uses: actions/upload-artifact@v6 with: name: deployment-outputs-${{ needs.prepare.outputs.sanitised-cdk-stack-name }} path: stack-outputs.json - retention-days: 30 - - # Post-deployment validation and drift detection - validate: - name: 🔍 Post-Deployment Validation - runs-on: ubuntu-latest - needs: [setup-node, prepare, deploy] - if: needs.deploy.result == 'success' - environment: ${{ inputs.github-environment }} - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - lfs: ${{ inputs.lfs }} - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: ${{ needs.setup-node.outputs.node-version }} - - - name: Enable Corepack - if: needs.setup-node.outputs.package-manager == 'yarn-berry' - run: | - echo "🔧 Enabling Corepack for Yarn Berry..." - corepack enable - - # If no packageManager field, default to stable Berry - if ! grep -q '"packageManager"' package.json; then - echo "📦 No packageManager found, preparing stable Yarn..." - corepack prepare yarn@stable --activate - fi - echo "✅ Corepack enabled" - - - name: Configure dependency cache - uses: actions/setup-node@v4 - with: - node-version: ${{ needs.setup-node.outputs.node-version }} - cache: ${{ needs.setup-node.outputs.package-manager == 'yarn-berry' && 'yarn' || (needs.setup-node.outputs.package-manager == 'yarn-classic' && 'yarn' || needs.setup-node.outputs.package-manager) }} - - - name: Install safe-chain - run: curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh -s -- --ci - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-access-key-id: ${{ vars.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ inputs.aws-region }} - role-to-assume: ${{ secrets.CFN_EXECUTION_ROLE }} - - - name: Install dependencies - run: | - echo "📦 Installing dependencies with ${{ needs.setup-node.outputs.package-manager }}..." - - case "${{ needs.setup-node.outputs.package-manager }}" in - "npm") - npm ci - ;; - "yarn-classic") - yarn install --frozen-lockfile - ;; - "yarn-berry") - yarn install --immutable - ;; - "pnpm") - pnpm install --frozen-lockfile - ;; - esac + retention-days: 7 - name: Validate stack deployment + if: inputs.deploy == true && steps.deploy.outputs.status == 'success' && inputs.skip-validation != true run: | echo "🔍 Validating deployed stack..." @@ -851,6 +555,7 @@ jobs: fi - name: Check for stack drift + if: inputs.deploy == true && steps.deploy.outputs.status == 'success' && inputs.skip-validation != true run: | echo "🔍 Checking for infrastructure drift..." @@ -887,16 +592,16 @@ jobs: esac - name: Display deployment summary + if: inputs.deploy == true && steps.deploy.outputs.status == 'success' run: | echo "📋 Deployment Summary" echo "====================" echo "Stack Name: ${{ needs.prepare.outputs.stack-name }}" echo "Environment: ${{ inputs.github-environment }}" echo "Region: ${{ inputs.aws-region }}" - echo "Status: ${{ needs.deploy.outputs.deployment-status }}" - echo "Node Version: ${{ needs.setup-node.outputs.node-version }}" - echo "Package Manager: ${{ needs.setup-node.outputs.package-manager }}" + echo "Status: ${{ steps.deploy.outputs.status }}" + echo "Node Version: ${{ needs.prepare.outputs.node-version }}" + echo "Package Manager: ${{ needs.prepare.outputs.package-manager }}" echo "" echo "🎉 Deployment completed successfully!" -