diff --git a/.github/workflows/aws-cdk.yml b/.github/workflows/aws-cdk.yml index 66aff00..015fae6 100644 --- a/.github/workflows/aws-cdk.yml +++ b/.github/workflows/aws-cdk.yml @@ -109,22 +109,17 @@ on: value: ${{ jobs.cdk.outputs.deployment-status }} jobs: - prepare: - name: 🔍 Prepare CDK Deployment + cdk: + name: â˜ī¸ CDK Operations runs-on: ${{ inputs.runs-on }} + permissions: + id-token: write + contents: read + pull-requests: write environment: ${{ inputs.github-environment }} outputs: - node-version: ${{ steps.node-version.outputs.version }} - package-manager: ${{ steps.detect-package-manager.outputs.manager }} - 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 }} - auth-mode: ${{ steps.validate-inputs.outputs.auth-mode }} - role-session-name: ${{ steps.resolve-session-name.outputs.role-session-name }} + deployment-status: ${{ steps.deploy.outputs.status }} + stack-outputs: ${{ steps.deploy.outputs.stack-outputs }} steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0 @@ -160,7 +155,7 @@ jobs: echo "✅ Detected pnpm" else echo "manager=npm" >> $GITHUB_OUTPUT - echo "✅ Detected npm" + echo "✅ Assuming npm" fi - name: Setup Node.js @@ -190,9 +185,11 @@ jobs: uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e #v6.4.0 with: node-version: ${{ steps.node-version.outputs.version }} - # yamllint disable rule:line-length - 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) }} - # yamllint enable rule:line-length + cache: |- + ${{ case( + startsWith(steps.detect-package-manager.outputs.manager, 'yarn-'), 'yarn', + steps.detect-package-manager.outputs.manager + ) }} - name: Install safe-chain run: | @@ -201,35 +198,30 @@ jobs: - name: Install dependencies env: + FLAG_VERBOSE: ${{ case(inputs.debug == true, '--verbose', '') }} PACKAGE_MANAGER: ${{ steps.detect-package-manager.outputs.manager }} - DEBUG_MODE: ${{ inputs.debug }} run: | echo "đŸ“Ļ Installing dependencies with $PACKAGE_MANAGER..." - verbose="" - if [ "$DEBUG_MODE" = "true" ]; then - verbose="--verbose" - fi - case "$PACKAGE_MANAGER" in "npm") - npm ci $verbose + npm ci $FLAG_VERBOSE ;; "yarn-classic") - yarn install --frozen-lockfile $verbose + yarn install --frozen-lockfile $FLAG_VERBOSE ;; "yarn-berry") - yarn install --immutable $verbose + yarn install --immutable $FLAG_VERBOSE ;; "pnpm") - pnpm install --frozen-lockfile $verbose + pnpm install --frozen-lockfile $FLAG_VERBOSE ;; esac - name: Export extra environment variables env: - EXTRA_VARS: ${{ vars.EXTRA_VARS }} EXTRA_SECRETS: ${{ secrets.EXTRA_SECRETS }} + EXTRA_VARS: ${{ vars.EXTRA_VARS }} run: | if [ -n "$EXTRA_VARS" ]; then while IFS= read -r line; do @@ -242,62 +234,21 @@ jobs: done <<< "$EXTRA_SECRETS" fi - - name: Set CDK commands - id: parse-cdk-config - env: - BOOTSTRAP_CMD: ${{ inputs.bootstrap-command }} - SYNTH_CMD: ${{ inputs.synth-command }} - DIFF_CMD: ${{ inputs.diff-command }} - DEPLOY_CMD: ${{ inputs.deploy-command }} - run: | - echo "✅ CDK commands:" - echo " bootstrap: $BOOTSTRAP_CMD" - echo " synth: $SYNTH_CMD" - echo " diff: $DIFF_CMD" - echo " deploy: $DEPLOY_CMD" - - echo "bootstrap-cmd=$BOOTSTRAP_CMD" >> $GITHUB_OUTPUT - echo "synth-cmd=$SYNTH_CMD" >> $GITHUB_OUTPUT - echo "diff-cmd=$DIFF_CMD" >> $GITHUB_OUTPUT - echo "deploy-cmd=$DEPLOY_CMD" >> $GITHUB_OUTPUT - - - name: Resolve stack name - id: resolve-stack-name - env: - INPUT_STACK_NAME: ${{ inputs.stack-name }} - VAR_STACK_NAME: ${{ vars.STACK_NAME }} - run: | - # Input takes priority over variable - if [ -n "$INPUT_STACK_NAME" ]; then - STACK_NAME="$INPUT_STACK_NAME" - else - STACK_NAME="$VAR_STACK_NAME" - fi - echo "stack-name=$STACK_NAME" >> $GITHUB_OUTPUT - - name: Validate required inputs id: validate-inputs env: - INPUT_ENVIRONMENT: ${{ inputs.github-environment }} - INPUT_STACK_NAME: ${{ steps.resolve-stack-name.outputs.stack-name }} - VAR_AWS_ACCESS_KEY_ID: ${{ vars.AWS_ACCESS_KEY_ID }} - SECRET_AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - VAR_AWS_ROLE_ARN: ${{ vars.AWS_ROLE_ARN }} - ENVIRONMENT_TARGET: ${{ inputs.environment-target }} CONTEXT_VALUES: ${{ inputs.context-values }} - INPUT_SYNTH: ${{ inputs.synth }} - INPUT_DIFF: ${{ inputs.diff }} + ENVIRONMENT_TARGET: ${{ inputs.environment-target }} + ENVIRONMENT: ${{ inputs.github-environment || 'Repository' }} INPUT_DEPLOY: ${{ inputs.deploy }} + INPUT_DIFF: ${{ inputs.diff }} + INPUT_SYNTH: ${{ inputs.synth }} + SECRET_AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + STACK_NAME: ${{ inputs.stack-name || vars.STACK_NAME }} + VAR_AWS_ACCESS_KEY_ID: ${{ vars.AWS_ACCESS_KEY_ID }} + VAR_AWS_ROLE_ARN: ${{ vars.AWS_ROLE_ARN }} run: | echo "🔍 Validating deployment configuration..." - - ENVIRONMENT="$INPUT_ENVIRONMENT" - STACK_NAME="$INPUT_STACK_NAME" - - if [ -z "$ENVIRONMENT" ]; then - ENVIRONMENT="Repository" - fi - echo "â„šī¸ Using variables from $ENVIRONMENT environment" if [ -z "$STACK_NAME" ]; then @@ -368,9 +319,9 @@ jobs: id: resolve-session-name if: steps.validate-inputs.outputs.auth-mode == 'oidc' env: - ROLE_SESSION_NAME: ${{ inputs.role-session-name }} COMMIT_SHA: ${{ github.sha }} REPOSITORY_NAME: ${{ github.event.repository.name }} + ROLE_SESSION_NAME: ${{ inputs.role-session-name }} RUN_NUMBER: ${{ github.run_number }} run: | SESSION_NAME="$ROLE_SESSION_NAME" @@ -385,8 +336,8 @@ jobs: - name: Configure CDK context id: context-config env: - ENVIRONMENT_TARGET: ${{ inputs.environment-target }} CONTEXT_VALUES: ${{ inputs.context-values }} + ENVIRONMENT_TARGET: ${{ inputs.environment-target }} run: | echo "âš™ī¸ Configuring CDK context..." @@ -395,11 +346,8 @@ jobs: # Add environment-specific context context_args="$context_args --context environment=$ENVIRONMENT_TARGET" - # Add custom context values (using process substitution to avoid subshell) if [ "$CONTEXT_VALUES" != "{}" ]; then - while read -r ctx; do - context_args="$context_args $ctx" - done < <(echo "$CONTEXT_VALUES" | jq -r 'to_entries[] | "--context \(.key)=\(.value)"') + context_args="$context_args$(jq -j 'to_entries[] | " --context \(.key)=\(.value)"' <<< "$CONTEXT_VALUES")" fi echo "args=$context_args" >> $GITHUB_OUTPUT @@ -408,91 +356,13 @@ jobs: - name: Sanitise stack name id: sanitise env: - STACK_NAME: ${{ steps.resolve-stack-name.outputs.stack-name }} + STACK_NAME: ${{ inputs.stack-name || vars.STACK_NAME }} run: | sanitised_cdk_stack_name=$(echo "$STACK_NAME" | tr -cd '[:alnum:]-_') echo "sanitised-cdk-stack-name=$sanitised_cdk_stack_name" >> $GITHUB_OUTPUT - - 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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1 - 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: ${{ inputs.runs-on }} - needs: [prepare] - permissions: - id-token: write - contents: read - pull-requests: write - environment: ${{ inputs.github-environment }} - outputs: - stack-outputs: ${{ steps.deploy.outputs.stack-outputs }} - deployment-status: ${{ steps.deploy.outputs.status }} - steps: - - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0 - with: - lfs: ${{ inputs.lfs }} - persist-credentials: false - - - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e #v6.4.0 - with: - node-version: ${{ needs.prepare.outputs.node-version }} - - - name: Enable Corepack - if: needs.prepare.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: Download node_modules artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c #v8.0.1 - with: - name: node_modules-${{ needs.prepare.outputs.sanitised-cdk-stack-name }} - - - 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: Export extra environment variables - env: - EXTRA_VARS: ${{ vars.EXTRA_VARS }} - EXTRA_SECRETS: ${{ secrets.EXTRA_SECRETS }} - run: | - if [ -n "$EXTRA_VARS" ]; then - while IFS= read -r line; do - [ -n "$line" ] && echo "$line" | tr -d '\r' >> "$GITHUB_ENV" - done <<< "$EXTRA_VARS" - fi - if [ -n "$EXTRA_SECRETS" ]; then - while IFS= read -r line; do - [ -n "$line" ] && echo "$line" | tr -d '\r' >> "$GITHUB_ENV" - done <<< "$EXTRA_SECRETS" - fi - - name: Configure AWS credentials (Static) - if: needs.prepare.outputs.auth-mode == 'static' + if: steps.validate-inputs.outputs.auth-mode == 'static' uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 #v6.1.0 with: aws-access-key-id: ${{ vars.AWS_ACCESS_KEY_ID }} @@ -501,46 +371,38 @@ jobs: role-to-assume: ${{ vars.CFN_EXECUTION_ROLE || secrets.CFN_EXECUTION_ROLE }} - name: Configure AWS credentials (OIDC) - if: needs.prepare.outputs.auth-mode == 'oidc' + if: steps.validate-inputs.outputs.auth-mode == 'oidc' uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 #v6.1.0 with: role-to-assume: ${{ vars.AWS_ROLE_ARN }} - role-session-name: ${{ needs.prepare.outputs.role-session-name }} + role-session-name: ${{ steps.resolve-session-name.outputs.role-session-name }} aws-region: ${{ inputs.aws-region }} - name: Bootstrap CDK environment if: inputs.bootstrap == true env: - DEBUG_MODE: ${{ inputs.debug }} - BOOTSTRAP_CMD: ${{ needs.prepare.outputs.cdk-bootstrap-cmd }} - CFN_EXECUTION_ROLE: ${{ secrets.CFN_EXECUTION_ROLE }} AWS_REGION: ${{ inputs.aws-region }} + BOOTSTRAP_CMD: ${{ inputs.bootstrap-command }} + CFN_EXECUTION_ROLE: ${{ secrets.CFN_EXECUTION_ROLE }} EXTRA_ARGUMENTS: ${{ inputs.extra-arguments }} - CDK_BOOTSTRAP_CMD: ${{ needs.prepare.outputs.cdk-bootstrap-cmd }} + FLAG_CFN_EXECUTION_POLICY: |- + ${{ case( + secrets.CFN_EXECUTION_ROLE != '', + format('--cloudformation-execution-policies {0}', secrets.CFN_EXECUTION_ROLE), + '' + ) }} + FLAG_VERBOSE: ${{ case(inputs.debug == true, '--verbose', '') }} run: | echo "đŸĨž Bootstrapping CDK environment..." - verbose="" - if [ "$DEBUG_MODE" = "true" ]; then - verbose="--verbose" - fi - - BOOTSTRAP_CMD="${CDK_BOOTSTRAP_CMD}" - # Check if using custom command from config or default if [ "$BOOTSTRAP_CMD" = "npx cdk bootstrap" ]; then - # Default command - add AWS-specific arguments - role_args="" - if [ -n "$CFN_EXECUTION_ROLE" ]; then - role_args="--cloudformation-execution-policies $CFN_EXECUTION_ROLE" - fi - $BOOTSTRAP_CMD \ - aws://$(aws sts get-caller-identity --query Account --output text)/${AWS_REGION} \ - $role_args \ - $verbose + "aws://$(aws sts get-caller-identity --query Account --output text)/$AWS_REGION" \ + $FLAG_CFN_EXECUTION_POLICY \ + $FLAG_VERBOSE else - $BOOTSTRAP_CMD $EXTRA_ARGUMENTS $verbose + $BOOTSTRAP_CMD $EXTRA_ARGUMENTS $FLAG_VERBOSE fi echo "✅ CDK environment bootstrapped successfully" @@ -548,23 +410,18 @@ jobs: - name: Synthesize CDK application if: inputs.synth == true env: - DEBUG_MODE: ${{ inputs.debug }} - SYNTH_CMD: ${{ needs.prepare.outputs.cdk-synth-cmd }} - STACK_NAME: ${{ needs.prepare.outputs.stack-name }} - CONTEXT_ARGS: ${{ needs.prepare.outputs.context-args }} + CONTEXT_ARGS: ${{ steps.context-config.outputs.args }} EXTRA_ARGUMENTS: ${{ inputs.extra-arguments }} + FLAG_VERBOSE: ${{ case(inputs.debug == true, '--verbose', '') }} + STACK_NAME: ${{ inputs.stack-name || vars.STACK_NAME }} + SYNTH_CMD: ${{ inputs.synth-command }} run: | echo "🔨 Synthesizing CDK application..." - verbose="" - if [ "$DEBUG_MODE" = "true" ]; then - verbose="--verbose" - fi - $SYNTH_CMD $STACK_NAME \ $CONTEXT_ARGS \ $EXTRA_ARGUMENTS \ - $verbose + $FLAG_VERBOSE echo "✅ CDK synthesis completed successfully" @@ -572,7 +429,7 @@ jobs: if: inputs.synth == true uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1 with: - name: cdk-synthesis-${{ needs.prepare.outputs.sanitised-cdk-stack-name }} + name: cdk-synthesis-${{ steps.sanitise.outputs.sanitised-cdk-stack-name }} path: cdk.out/ retention-days: 7 @@ -580,10 +437,10 @@ jobs: if: inputs.diff == true id: diff-analysis env: - DIFF_CMD: ${{ needs.prepare.outputs.cdk-diff-cmd }} - STACK_NAME: ${{ needs.prepare.outputs.stack-name }} - CONTEXT_ARGS: ${{ needs.prepare.outputs.context-args }} + CONTEXT_ARGS: ${{ steps.context-config.outputs.args }} + DIFF_CMD: ${{ inputs.diff-command }} EXTRA_ARGUMENTS: ${{ inputs.extra-arguments }} + STACK_NAME: ${{ inputs.stack-name || vars.STACK_NAME }} run: | echo "📊 Analysing deployment changes..." @@ -618,7 +475,7 @@ jobs: if: inputs.diff == true uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1 with: - name: deployment-diff-${{ needs.prepare.outputs.sanitised-cdk-stack-name }} + name: deployment-diff-${{ steps.sanitise.outputs.sanitised-cdk-stack-name }} path: deployment-diff.txt retention-days: 7 @@ -626,9 +483,9 @@ jobs: if: inputs.diff == true && github.event_name == 'pull_request' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #v9.0.0 env: - STACK_NAME: ${{ needs.prepare.outputs.stack-name }} ENVIRONMENT: ${{ inputs.github-environment }} HAS_CHANGES: ${{ steps.diff-analysis.outputs.has-changes }} + STACK_NAME: ${{ inputs.stack-name || vars.STACK_NAME }} with: script: | const fs = require('fs'); @@ -724,17 +581,12 @@ jobs: if: inputs.deploy == true id: deploy env: - DEBUG_MODE: ${{ inputs.debug }} - DEPLOY_CMD: ${{ needs.prepare.outputs.cdk-deploy-cmd }} - STACK_NAME: ${{ needs.prepare.outputs.stack-name }} - CONTEXT_ARGS: ${{ needs.prepare.outputs.context-args }} + CONTEXT_ARGS: ${{ steps.context-config.outputs.args }} + DEPLOY_CMD: ${{ inputs.deploy-command }} EXTRA_ARGUMENTS: ${{ inputs.extra-arguments }} + FLAG_VERBOSE: ${{ case(inputs.debug == true, '--verbose', '') }} + STACK_NAME: ${{ inputs.stack-name || vars.STACK_NAME }} run: | - verbose="" - if [ "$DEBUG_MODE" = "true" ]; then - verbose="--verbose" - fi - echo "🚀 Deploying CDK stack: $STACK_NAME" $DEPLOY_CMD $STACK_NAME \ @@ -742,7 +594,7 @@ jobs: $EXTRA_ARGUMENTS \ --require-approval never \ --outputs-file stack-outputs.json \ - $verbose + $FLAG_VERBOSE # Extract stack outputs if [ -f "stack-outputs.json" ]; then @@ -760,19 +612,19 @@ jobs: if: inputs.deploy == true && steps.deploy.outputs.status == 'success' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1 with: - name: deployment-outputs-${{ needs.prepare.outputs.sanitised-cdk-stack-name }} + name: deployment-outputs-${{ steps.sanitise.outputs.sanitised-cdk-stack-name }} path: stack-outputs.json retention-days: 7 - name: Display deployment summary if: inputs.deploy == true && steps.deploy.outputs.status == 'success' env: - STACK_NAME: ${{ needs.prepare.outputs.stack-name }} - ENVIRONMENT: ${{ inputs.github-environment }} AWS_REGION: ${{ inputs.aws-region }} DEPLOY_STATUS: ${{ steps.deploy.outputs.status }} - NODE_VERSION: ${{ needs.prepare.outputs.node-version }} - PACKAGE_MANAGER: ${{ needs.prepare.outputs.package-manager }} + ENVIRONMENT: ${{ inputs.github-environment }} + NODE_VERSION: ${{ steps.node-version.outputs.version }} + PACKAGE_MANAGER: ${{ steps.detect-package-manager.outputs.manager }} + STACK_NAME: ${{ inputs.stack-name || vars.STACK_NAME }} run: | echo "📋 Deployment Summary" echo "===================="