diff --git a/.github/workflows/layers_partitions.yml b/.github/workflows/layers_partitions.yml index 3e15bcf4ea..a9e1b851ab 100644 --- a/.github/workflows/layers_partitions.yml +++ b/.github/workflows/layers_partitions.yml @@ -1,8 +1,8 @@ # Partitioned Layer Publish # --- -# This workflow publishes a specific layer version in an AWS account based on the environment input. +# This workflow publishes a specific layer version in an AWS account based on the partition input. # -# We pull each the version of the layer and store them as artifacts, the we upload them to each of the Partitioned AWS accounts. +# We pull the version of the layer and store them as artifacts, then we upload them to each of the Partitioned AWS accounts. # # A number of safety checks are performed to ensure safety. # @@ -12,7 +12,7 @@ # 3. [Copy & Verify] deploy the layer to all regions in the target partition and validate layer deployment by comparing SHA256, description, and version numbers # # === Manual activities === -# 1. After the `make-release` workflow finishes and the PR for the documentation update gets created, trigger this workflow manually via `workflow_dispatch` with environment, version, and partition inputs for each Gamma and Prod environment in the China and GovCloud partitions +# 1. After the `make-release` workflow finishes and the PR for the documentation update gets created, trigger this workflow manually via `workflow_dispatch` with partition inputs for each China and GovCloud partitions # 2. Monitor deployment progress and verify successful layer publication across all target regions # 3. Once this workflow is completed, the PR for the documentation update can me merged # @@ -27,36 +27,15 @@ on: workflow_dispatch: inputs: - environment: - description: Deployment environment - type: choice - options: - - Gamma - - Prod - required: true - version: - description: Layer version to duplicate - type: string - required: true partition: description: Partition to deploy to type: choice options: - China - GovCloud - workflow_call: - inputs: - environment: - description: Deployment environment - type: string - required: true - version: - description: Layer version to duplicate - type: string - required: true name: Layer Deployment (Partitions) -run-name: Layer Deployment (${{ inputs.partition }}) - ${{ inputs.environment }} / Version - ${{ inputs.version }} +run-name: Layer Deployment (${{ inputs.partition }}) permissions: contents: read @@ -98,9 +77,15 @@ jobs: role-to-assume: ${{ secrets.AWS_IAM_ROLE }} aws-region: us-east-1 mask-aws-account-id: true + - name: Get Latest Layer Version + id: get_latest_layer_version + run: | + set -euo pipefail + LAYER_VERSION=$(aws lambda list-layer-versions --layer-name AWSLambdaPowertoolsTypeScriptV2 --query 'LayerVersions[0].Version' --output text --region us-east-1) + echo "LAYER_VERSION=$LAYER_VERSION" >> $GITHUB_OUTPUT - name: Grab Zip env: - VERSION: ${{ inputs.version }} + VERSION: ${{ steps.get_latest_layer_version.outputs.LAYER_VERSION }} run: | set -euo pipefail aws --region us-east-1 lambda get-layer-version-by-arn --arn "arn:aws:lambda:us-east-1:094274105915:layer:AWSLambdaPowertoolsTypeScriptV2:${VERSION}" --query 'Content.Location' | xargs curl -L -o AWSLambdaPowertoolsTypeScriptV2.zip @@ -119,96 +104,27 @@ jobs: path: AWSLambdaPowertoolsTypeScriptV2.json retention-days: 1 if-no-files-found: error - # This job deploys the layer to all regions in the target partition using a matrix strategy. It performs integrity checks, publishes the layer, sets public permissions, and validates deployment. - copy: - name: Copy - needs: - - setup - - download - runs-on: ubuntu-latest - permissions: - id-token: write - contents: read - # Environment should interperlate as "GovCloud Prod" or "China Beta" - environment: ${{ inputs.partition }} ${{ inputs.environment }} - strategy: - matrix: - region: ${{ fromJson(needs.setup.outputs.regions) }} - steps: - - name: Download Zip - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 - with: - name: AWSLambdaPowertoolsTypeScriptV2.zip - - name: Download Metadata - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 - with: - name: AWSLambdaPowertoolsTypeScriptV2.json - - name: Verify Layer Signature - run: | - SHA=$(jq -r '.Content.CodeSha256' 'AWSLambdaPowertoolsTypeScriptV2.json') - test "$(openssl dgst -sha256 -binary AWSLambdaPowertoolsTypeScriptV2.zip | openssl enc -base64)" == "$SHA" && echo "SHA OK: ${SHA}" || exit 1 - - id: transform - run: | - echo 'CONVERTED_REGION=${{ matrix.region }}' | tr 'a-z\-' 'A-Z_' >> "$GITHUB_OUTPUT" - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1 - with: - # Dynamic secret access is safe here - secrets are scoped per environment - role-to-assume: ${{ secrets[format('IAM_ROLE_{0}', steps.transform.outputs.CONVERTED_REGION)] }} - aws-region: ${{ matrix.region}} - mask-aws-account-id: true - audience: ${{ needs.setup.outputs.aud }} - - name: Create Layer - id: create-layer - run: | - set -euo pipefail - cat AWSLambdaPowertoolsTypeScriptV2.json | jq '{"LayerName": "AWSLambdaPowertoolsTypeScriptV2", "Description": .Description, "CompatibleRuntimes": .CompatibleRuntimes, "LicenseInfo": .LicenseInfo}' > input.json - - LAYER_VERSION=$(aws --region "${{ matrix.region }}" lambda publish-layer-version \ - --zip-file fileb://./AWSLambdaPowertoolsTypeScriptV2.zip \ - --cli-input-json file://./input.json \ - --query 'Version' \ - --output text) - - echo "LAYER_VERSION=$LAYER_VERSION" >> "$GITHUB_OUTPUT" - - aws --region "${{ matrix.region }}" lambda add-layer-version-permission \ - --layer-name 'AWSLambdaPowertoolsTypeScriptV2' \ - --statement-id 'PublicLayer' \ - --action lambda:GetLayerVersion \ - --principal '*' \ - --version-number "$LAYER_VERSION" - # This step retrieves the newly deployed layer metadata and compares it against the original source layer: - # 1. SHA256 hash verification - ensures the layer content is identical to the source - # 2. Description validation - confirms the version number in the description matches the source - # 3. Layer Version number verification - validates that the layer version numbers match between source and target - # 4. Tabular comparison output - displays side-by-side comparison of key layer properties - - name: Verify Layer - env: - LAYER_VERSION: ${{ steps.create-layer.outputs.LAYER_VERSION }} - ENVIRONMENT: ${{ inputs.environment }} - run: | - set -euo pipefail - export layer_output="AWSLambdaPowertoolsTypeScriptV2-${{ matrix.region }}.json" - # Dynamic secret access is safe here - secrets are scoped per environment - aws --region "${{ matrix.region }}" lambda get-layer-version-by-arn --arn "arn:${{ needs.setup.outputs.partition }}:lambda:${{ matrix.region }}:${{ secrets[format('AWS_ACCOUNT_{0}', steps.transform.outputs.CONVERTED_REGION)] }}:layer:AWSLambdaPowertoolsTypeScriptV2:${LAYER_VERSION}" > "$layer_output" - REMOTE_SHA=$(jq -r '.Content.CodeSha256' $layer_output) - LOCAL_SHA=$(jq -r '.Content.CodeSha256' AWSLambdaPowertoolsTypeScriptV2.json) - test "$REMOTE_SHA" == "$LOCAL_SHA" && echo "SHA OK: ${LOCAL_SHA}" || exit 1 - REMOTE_DESCRIPTION=$(jq -r '.Description' $layer_output) - LOCAL_DESCRIPTION=$(jq -r '.Description' AWSLambdaPowertoolsTypeScriptV2.json) - test "$REMOTE_DESCRIPTION" == "$LOCAL_DESCRIPTION" && echo "Version number OK: ${LOCAL_DESCRIPTION}" || exit 1 - if [ "$ENVIRONMENT" == "Prod" ]; then - REMOTE_LAYER_VERSION=$(jq -r '.LayerVersionArn' $layer_output | sed 's/.*://') - LOCAL_LAYER_VERSION=$(jq -r '.LayerVersionArn' AWSLambdaPowertoolsTypeScriptV2.json | sed 's/.*://') - test "$REMOTE_LAYER_VERSION" == "$LOCAL_LAYER_VERSION" && echo "Layer Version number OK: ${LOCAL_LAYER_VERSION}" || exit 1 - fi - jq -s -r '["Layer Arn", "Runtimes", "Version", "Description", "SHA256"], ([.[0], .[1]] | .[] | [.LayerArn, (.CompatibleRuntimes | join("/")), .Version, .Description, .Content.CodeSha256]) |@tsv' AWSLambdaPowertoolsTypeScriptV2.json $layer_output | column -t -s $'\t' - - - name: Store Metadata - ${{ matrix.region }} - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 - with: - name: AWSLambdaPowertoolsTypeScriptV2-${{ matrix.region }}.json - path: AWSLambdaPowertoolsTypeScriptV2-${{ matrix.region }}.json - retention-days: 1 - if-no-files-found: error \ No newline at end of file + # Copies the Layer to the Gamma Environment in the selected partition + deploy-gamma: + name: Deploy Gamma Layer + needs: [setup, download] + uses: ./.github/workflows/layers_partitions_deploy.yml + with: + environment: Gamma + partition: ${{ inputs.partition }} + arn_partition: ${{ needs.setup.outputs.partition }} + regions: ${{ needs.setup.outputs.regions }} + aud: ${{ needs.setup.outputs.aud }} + secrets: inherit + # Copies the Layer to the Prod Environment in the selected partition + deploy-prod: + name: Deploy Prod Layer + needs: [setup, download, deploy-gamma] + uses: ./.github/workflows/layers_partitions_deploy.yml + with: + environment: Prod + partition: ${{ inputs.partition }} + arn_partition: ${{ needs.setup.outputs.partition }} + regions: ${{ needs.setup.outputs.regions }} + aud: ${{ needs.setup.outputs.aud }} + secrets: inherit diff --git a/.github/workflows/layers_partitions_deploy.yml b/.github/workflows/layers_partitions_deploy.yml new file mode 100644 index 0000000000..8e9b0486e5 --- /dev/null +++ b/.github/workflows/layers_partitions_deploy.yml @@ -0,0 +1,121 @@ +name: Copy Layer + +on: + workflow_call: + inputs: + environment: + required: true + type: string + partition: + required: true + type: string + arn_partition: + required: true + type: string + regions: + required: true + type: string + aud: + required: true + type: string + +jobs: + # This job deploys the layer to all regions in the target partition using a matrix strategy. It performs integrity checks, publishes the layer, sets public permissions, and validates deployment. + deploy: + name: Deploy ${{ inputs.partition }} - ${{ inputs.environment }} + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + environment: ${{ inputs.partition }} ${{ inputs.environment }} + strategy: + matrix: + region: ${{ fromJson(inputs.regions) }} + steps: + - name: Download Zip + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + with: + name: AWSLambdaPowertoolsTypeScriptV2.zip + + - name: Download Metadata + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + with: + name: AWSLambdaPowertoolsTypeScriptV2.json + + - name: Verify Layer Signature + run: | + SHA=$(jq -r '.Content.CodeSha256' 'AWSLambdaPowertoolsTypeScriptV2.json') + test "$(openssl dgst -sha256 -binary AWSLambdaPowertoolsTypeScriptV2.zip | openssl enc -base64)" == "$SHA" && echo "SHA OK: ${SHA}" || exit 1 + + - id: transform + run: | + echo 'CONVERTED_REGION=${{ matrix.region }}' | tr 'a-z\-' 'A-Z_' >> "$GITHUB_OUTPUT" + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1 + with: + role-to-assume: ${{ secrets[format('IAM_ROLE_{0}', steps.transform.outputs.CONVERTED_REGION)] }} + aws-region: ${{ matrix.region }} + mask-aws-account-id: true + audience: ${{ inputs.aud }} + + - name: Create Layer + id: create-layer + run: | + set -euo pipefail + cat AWSLambdaPowertoolsTypeScriptV2.json | jq '{"LayerName": "AWSLambdaPowertoolsTypeScriptV2", "Description": .Description, "CompatibleRuntimes": .CompatibleRuntimes, "LicenseInfo": .LicenseInfo}' > input.json + + LAYER_VERSION=$(aws --region "${{ matrix.region }}" lambda publish-layer-version \ + --zip-file fileb://./AWSLambdaPowertoolsTypeScriptV2.zip \ + --cli-input-json file://./input.json \ + --query 'Version' \ + --output text) + + echo "LAYER_VERSION=$LAYER_VERSION" >> "$GITHUB_OUTPUT" + + aws --region "${{ matrix.region }}" lambda add-layer-version-permission \ + --layer-name 'AWSLambdaPowertoolsTypeScriptV2' \ + --statement-id 'PublicLayer' \ + --action lambda:GetLayerVersion \ + --principal '*' \ + --version-number "$LAYER_VERSION" + + # This step retrieves the newly deployed layer metadata and compares it against the original source layer: + # 1. SHA256 hash verification - ensures the layer content is identical to the source + # 2. Description validation - confirms the version number in the description matches the source + # 3. Layer Version number verification - validates that the layer version numbers match between source and target + # 4. Tabular comparison output - displays side-by-side comparison of key layer properties + - name: Verify Layer + env: + LAYER_VERSION: ${{ steps.create-layer.outputs.LAYER_VERSION }} + ENVIRONMENT: ${{ inputs.environment }} + PARTITION: ${{ inputs.arn_partition }} + run: | + set -euo pipefail + export layer_output="AWSLambdaPowertoolsTypeScriptV2-${{ matrix.region }}.json" + # Dynamic secret access is safe here - secrets are scoped per environment + aws --region "${{ matrix.region }}" lambda get-layer-version-by-arn --arn "arn:${PARTITION}:lambda:${{ matrix.region }}:${{ secrets[format('AWS_ACCOUNT_{0}', steps.transform.outputs.CONVERTED_REGION)] }}:layer:AWSLambdaPowertoolsTypeScriptV2:${LAYER_VERSION}" > "$layer_output" + + REMOTE_SHA=$(jq -r '.Content.CodeSha256' "$layer_output") + LOCAL_SHA=$(jq -r '.Content.CodeSha256' AWSLambdaPowertoolsTypeScriptV2.json) + test "$REMOTE_SHA" == "$LOCAL_SHA" && echo "SHA OK: ${LOCAL_SHA}" || exit 1 + + REMOTE_DESCRIPTION=$(jq -r '.Description' "$layer_output") + LOCAL_DESCRIPTION=$(jq -r '.Description' AWSLambdaPowertoolsTypeScriptV2.json) + test "$REMOTE_DESCRIPTION" == "$LOCAL_DESCRIPTION" && echo "Description OK: ${LOCAL_DESCRIPTION}" || exit 1 + + if [ "$ENVIRONMENT" == "Prod" ]; then + REMOTE_LAYER_VERSION=$(jq -r '.LayerVersionArn' "$layer_output" | sed 's/.*://') + LOCAL_LAYER_VERSION=$(jq -r '.LayerVersionArn' AWSLambdaPowertoolsTypeScriptV2.json | sed 's/.*://') + test "$REMOTE_LAYER_VERSION" == "$LOCAL_LAYER_VERSION" && echo "Layer Version number OK: ${LOCAL_LAYER_VERSION}" || exit 1 + fi + + jq -s -r '["Layer Arn", "Runtimes", "Version", "Description", "SHA256"], ([.[0], .[1]] | .[] | [.LayerArn, (.CompatibleRuntimes | join("/")), .Version, .Description, .Content.CodeSha256]) |@tsv' AWSLambdaPowertoolsTypeScriptV2.json "$layer_output" | column -t -s $'\t' + + - name: Store Metadata - ${{ matrix.region }} + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: AWSLambdaPowertoolsTypeScriptV2-${{ matrix.region }}.json + path: AWSLambdaPowertoolsTypeScriptV2-${{ matrix.region }}.json + retention-days: 1 + if-no-files-found: error