diff --git a/.github/scripts/comment_on_large_pr.js b/.github/scripts/comment_on_large_pr.js deleted file mode 100644 index c17199faf76..00000000000 --- a/.github/scripts/comment_on_large_pr.js +++ /dev/null @@ -1,73 +0,0 @@ -const { - PR_NUMBER, - PR_ACTION, - PR_AUTHOR, - IGNORE_AUTHORS, -} = require("./constants") - - -/** - * Notify PR author to split XXL PR in smaller chunks - * - * @param {object} core - core functions instance from @actions/core - * @param {object} gh_client - Pre-authenticated REST client (Octokit) - * @param {string} owner - GitHub Organization - * @param {string} repository - GitHub repository - */ -const notifyAuthor = async ({ - core, - gh_client, - owner, - repository, -}) => { - core.info(`Commenting on PR ${PR_NUMBER}`) - - let msg = `### ⚠️Large PR detected⚠️ - -Please consider breaking into smaller PRs to avoid significant review delays. Ignore if this PR has naturally grown to this size after reviews. - `; - - try { - await gh_client.rest.issues.createComment({ - owner: owner, - repo: repository, - body: msg, - issue_number: PR_NUMBER, - }); - } catch (error) { - core.setFailed("Failed to notify PR author to split large PR"); - console.error(err); - } -} - -module.exports = async ({github, context, core}) => { - if (IGNORE_AUTHORS.includes(PR_AUTHOR)) { - return core.notice("Author in IGNORE_AUTHORS list; skipping...") - } - - if (PR_ACTION != "labeled") { - return core.notice("Only run on PRs labeling actions; skipping") - } - - - /** @type {string[]} */ - const { data: labels } = await github.rest.issues.listLabelsOnIssue({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: PR_NUMBER, - }) - - // Schema: https://docs.github.com/en/rest/issues/labels#list-labels-for-an-issue - for (const label of labels) { - core.info(`Label: ${label}`) - if (label.name == "size/XXL") { - await notifyAuthor({ - core: core, - gh_client: github, - owner: context.repo.owner, - repository: context.repo.repo, - }) - break; - } - } -} diff --git a/.github/scripts/download_pr_artifact.js b/.github/scripts/download_pr_artifact.js deleted file mode 100644 index 274467c1f1c..00000000000 --- a/.github/scripts/download_pr_artifact.js +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = async ({github, context, core}) => { - const fs = require('fs'); - - const workflowRunId = process.env.WORKFLOW_ID; - core.info(`Listing artifacts for workflow run ${workflowRunId}`); - - const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: workflowRunId, - }); - - const matchArtifact = artifacts.data.artifacts.filter(artifact => artifact.name == "pr")[0]; - - core.info(`Downloading artifacts for workflow run ${workflowRunId}`); - const artifact = await github.rest.actions.downloadArtifact({ - owner: context.repo.owner, - repo: context.repo.repo, - artifact_id: matchArtifact.id, - archive_format: 'zip', - }); - - core.info("Saving artifact found", artifact); - - fs.writeFileSync('pr.zip', Buffer.from(artifact.data)); -} diff --git a/.github/scripts/enforce_acknowledgment.js b/.github/scripts/enforce_acknowledgment.js deleted file mode 100644 index 3e3be636ede..00000000000 --- a/.github/scripts/enforce_acknowledgment.js +++ /dev/null @@ -1,40 +0,0 @@ -const { -PR_ACTION, -PR_AUTHOR, -PR_BODY, -PR_NUMBER, -IGNORE_AUTHORS, -LABEL_BLOCK, -LABEL_BLOCK_REASON -} = require("./constants") - -module.exports = async ({github, context, core}) => { - if (IGNORE_AUTHORS.includes(PR_AUTHOR)) { - return core.notice("Author in IGNORE_AUTHORS list; skipping...") - } - - if (PR_ACTION != "opened") { - return core.notice("Only newly open PRs are labelled to avoid spam; skipping") - } - - const RELATED_ISSUE_REGEX = /Issue number:[^\d\r\n]+(?\d+)/; - const isMatch = RELATED_ISSUE_REGEX.exec(PR_BODY); - if (isMatch == null) { - core.info(`No related issue found, maybe the author didn't use the template but there is one.`) - - let msg = "No related issues found. Please ensure there is an open issue related to this change to avoid significant delays or closure."; - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - body: msg, - issue_number: PR_NUMBER, - }); - - return await github.rest.issues.addLabels({ - issue_number: PR_NUMBER, - owner: context.repo.owner, - repo: context.repo.repo, - labels: [LABEL_BLOCK, LABEL_BLOCK_REASON] - }) - } -} diff --git a/.github/scripts/label_missing_acknowledgement_section.js b/.github/scripts/label_missing_acknowledgement_section.js deleted file mode 100644 index 12b85241d1d..00000000000 --- a/.github/scripts/label_missing_acknowledgement_section.js +++ /dev/null @@ -1,41 +0,0 @@ -const { - PR_ACTION, - PR_AUTHOR, - PR_BODY, - PR_NUMBER, - IGNORE_AUTHORS, - LABEL_BLOCK, - LABEL_BLOCK_MISSING_LICENSE_AGREEMENT -} = require("./constants") - -module.exports = async ({github, context, core}) => { - if (IGNORE_AUTHORS.includes(PR_AUTHOR)) { - return core.notice("Author in IGNORE_AUTHORS list; skipping...") - } - - if (PR_ACTION != "opened") { - return core.notice("Only newly open PRs are labelled to avoid spam; skipping") - } - - const RELATED_ACK_SECTION_REGEX = /By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice./; - - const isMatch = RELATED_ACK_SECTION_REGEX.exec(PR_BODY); - if (isMatch == null) { - core.info(`No acknowledgement section found, maybe the author didn't use the template but there is one.`) - - let msg = "No acknowledgement section found. Please make sure you used the template to open a PR and didn't remove the acknowledgment section. Check the template here: https://github.com/aws-powertools/powertools-lambda-python/blob/develop/.github/PULL_REQUEST_TEMPLATE.md#acknowledgment"; - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - body: msg, - issue_number: PR_NUMBER, - }); - - return await github.rest.issues.addLabels({ - issue_number: PR_NUMBER, - owner: context.repo.owner, - repo: context.repo.repo, - labels: [LABEL_BLOCK, LABEL_BLOCK_MISSING_LICENSE_AGREEMENT] - }) - } -} diff --git a/.github/scripts/label_missing_related_issue.js b/.github/scripts/label_missing_related_issue.js deleted file mode 100644 index 705e414c47f..00000000000 --- a/.github/scripts/label_missing_related_issue.js +++ /dev/null @@ -1,40 +0,0 @@ -const { - PR_ACTION, - PR_AUTHOR, - PR_BODY, - PR_NUMBER, - IGNORE_AUTHORS, - LABEL_BLOCK, - LABEL_BLOCK_REASON -} = require("./constants") - -module.exports = async ({github, context, core}) => { - if (IGNORE_AUTHORS.includes(PR_AUTHOR)) { - return core.notice("Author in IGNORE_AUTHORS list; skipping...") - } - - if (PR_ACTION != "opened") { - return core.notice("Only newly open PRs are labelled to avoid spam; skipping") - } - - const RELATED_ISSUE_REGEX = /Issue number:[^\d\r\n]+(?\d+)/; - const isMatch = RELATED_ISSUE_REGEX.exec(PR_BODY); - if (isMatch == null) { - core.info(`No related issue found, maybe the author didn't use the template but there is one.`) - - let msg = "No related issues found. Please ensure there is an open issue related to this change to avoid significant delays or closure."; - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - body: msg, - issue_number: PR_NUMBER, - }); - - return await github.rest.issues.addLabels({ - issue_number: PR_NUMBER, - owner: context.repo.owner, - repo: context.repo.repo, - labels: [LABEL_BLOCK, LABEL_BLOCK_REASON] - }) - } -} diff --git a/.github/scripts/label_pr_based_on_title.js b/.github/scripts/label_pr_based_on_title.js deleted file mode 100644 index 02f77f448b8..00000000000 --- a/.github/scripts/label_pr_based_on_title.js +++ /dev/null @@ -1,62 +0,0 @@ -const { PR_NUMBER, PR_TITLE, PR_LABELS } = require("./constants") - -module.exports = async ({github, context, core}) => { - const FEAT_REGEX = /feat(\((.+)\))?(:.+)/ - const BUG_REGEX = /(fix|bug)(\((.+)\))?(:.+)/ - const DOCS_REGEX = /(docs|doc)(\((.+)\))?(:.+)/ - const CHORE_REGEX = /(chore)(\((.+)\))?(:.+)/ - const DEPRECATED_REGEX = /(deprecated)(\((.+)\))?(:.+)/ - const REFACTOR_REGEX = /(refactor)(\((.+)\))?(:.+)/ - - const labels = { - "feature": FEAT_REGEX, - "bug": BUG_REGEX, - "documentation": DOCS_REGEX, - "internal": CHORE_REGEX, - "enhancement": REFACTOR_REGEX, - "deprecated": DEPRECATED_REGEX, - } - - // get PR labels from env - const prLabels = PR_LABELS.replaceAll("\"", "").split(","); - const labelKeys = Object.keys(labels); - - let miss = 0; - try { - for (const label in labels) { - const matcher = new RegExp(labels[label]) - const matches = matcher.exec(PR_TITLE) - if (matches != null) { - core.info(`Auto-labeling PR ${PR_NUMBER} with ${label}`) - - for (const prLabel of prLabels) { - if (labelKeys.includes(prLabel) && prLabel !== label) { - core.info(`PR previously tagged with: ${prLabel}, removing.`); - await github.rest.issues.removeLabel({ - issue_number: PR_NUMBER, - owner: context.repo.owner, - repo: context.repo.repo, - name: prLabel - }) - } - } - - await github.rest.issues.addLabels({ - issue_number: PR_NUMBER, - owner: context.repo.owner, - repo: context.repo.repo, - labels: [label] - }) - - return; - } else { - core.debug(`'${PR_TITLE}' didn't match '${label}' semantic.`) - miss += 1 - } - } - } finally { - if (miss == Object.keys(labels).length) { - core.notice(`PR ${PR_NUMBER} title '${PR_TITLE}' doesn't follow semantic titles; skipping...`) - } - } -} diff --git a/.github/scripts/label_related_issue.js b/.github/scripts/label_related_issue.js deleted file mode 100644 index 790aac1ced5..00000000000 --- a/.github/scripts/label_related_issue.js +++ /dev/null @@ -1,53 +0,0 @@ -const { - PR_AUTHOR, - PR_BODY, - PR_NUMBER, - IGNORE_AUTHORS, - LABEL_PENDING_RELEASE, - HANDLE_MAINTAINERS_TEAM, - PR_IS_MERGED, -} = require("./constants") - -module.exports = async ({github, context, core}) => { - if (IGNORE_AUTHORS.includes(PR_AUTHOR)) { - return core.notice("Author in IGNORE_AUTHORS list; skipping...") - } - - if (PR_IS_MERGED == "false") { - return core.notice("Only merged PRs to avoid spam; skipping") - } - - const RELATED_ISSUE_REGEX = /Issue number:[^\d\r\n]+(?\d+)/; - - const isMatch = RELATED_ISSUE_REGEX.exec(PR_BODY); - - try { - if (!isMatch) { - core.setFailed(`Unable to find related issue for PR number ${PR_NUMBER}.\n\n Body details: ${PR_BODY}`); - return await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - body: `${HANDLE_MAINTAINERS_TEAM} No related issues found. Please ensure '${LABEL_PENDING_RELEASE}' label is applied before releasing.`, - issue_number: PR_NUMBER, - }); - } - } catch (error) { - core.setFailed(`Unable to create comment on PR number ${PR_NUMBER}.\n\n Error details: ${error}`); - throw new Error(error); - } - - const { groups: {issue} } = isMatch - - try { - core.info(`Auto-labeling related issue ${issue} for release`) - return await github.rest.issues.addLabels({ - issue_number: issue, - owner: context.repo.owner, - repo: context.repo.repo, - labels: [LABEL_PENDING_RELEASE] - }) - } catch (error) { - core.setFailed(`Is this issue number (${issue}) valid? Perhaps a discussion?`); - throw new Error(error); - } -} diff --git a/.github/scripts/save_pr_details.js b/.github/scripts/save_pr_details.js deleted file mode 100644 index ba2de975b3c..00000000000 --- a/.github/scripts/save_pr_details.js +++ /dev/null @@ -1,23 +0,0 @@ -module.exports = async ({github, context, core}) => { - const fs = require('fs'); - const filename = "pr.txt"; - - const labelsData = await github.rest.issues.listLabelsOnIssue({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: (context.payload.issue || context.payload.pull_request || context.payload).number, - }); - - const labels = labelsData.data.map((label) => { - return label['name']; - }); - - try { - fs.writeFileSync(`./${filename}`, JSON.stringify({...context.payload, ...{labels:labels.join(",")}})); - - return `PR successfully saved ${filename}` - } catch (err) { - core.setFailed("Failed to save PR details"); - console.error(err); - } -} diff --git a/.github/workflows/dispatch_analytics.yml b/.github/workflows/dispatch_analytics.yml deleted file mode 100644 index 028eda1700e..00000000000 --- a/.github/workflows/dispatch_analytics.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Dispatch analytics - -# PROCESS -# -# 1. Trade GitHub JWT token with AWS credentials for the analytics account -# 2. Invoke a Lambda function dispatcher synchronously with the read-only scoped JWT token -# 3. The dispatcher function will call GitHub APIs to read data from the last hour and aggregate for operational analytics - -# USAGE -# -# NOTE: meant to use as a scheduled task only (or manually for debugging purposes). - -on: - workflow_dispatch: - - schedule: - - cron: "0 * * * *" - -permissions: - contents: read - - -jobs: - dispatch_token: - if: github.repository == 'aws-powertools/powertools-lambda-python' - concurrency: - group: analytics - runs-on: ubuntu-latest - environment: analytics - permissions: - id-token: write - actions: read - checks: read - contents: read # previously we needed `write` to use GH_TOKEN in our dispatcher (Lambda) - deployments: read - issues: read - discussions: read - packages: read - pages: read - pull-requests: read - repository-projects: read - security-events: read - statuses: read - steps: - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v4.3.0 - with: - aws-region: eu-central-1 - role-to-assume: ${{ secrets.AWS_LAYERS_ROLE_ARN }} - mask-aws-account-id: true - - - name: Invoke Lambda function - run: | - payload=$(echo -n '{"githubToken": "${{ secrets.GITHUB_TOKEN }}"}' | base64) - response=$(aws lambda invoke \ - --function-name "${{ secrets.AWS_ANALYTICS_DISPATCHER_ARN }}" \ - --payload "$payload" \ - response.json \ - --query 'FunctionError' \ - --output text) - - cat response.json ; echo # add newline at the end - - if [ "$response" != "None" ]; then - echo "Error invoking lambda function: $response. Aborting." - exit 1 - fi diff --git a/.github/workflows/label_pr_on_title.yml b/.github/workflows/label_pr_on_title.yml deleted file mode 100644 index 37fb1793b8c..00000000000 --- a/.github/workflows/label_pr_on_title.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Label PR based on title - -# PROCESS -# -# 1. Fetch PR details previously saved from untrusted location -# 2. Parse details for safety -# 3. Label PR based on semantic title (e.g., area, change type) - -# USAGE -# -# NOTE: meant to be used with ./.github/workflows/record_pr.yml -# -# Security Note: -# -# This workflow depends on "Record PR" workflow that runs in an untrusted location (forks) instead of `pull_request_target`. -# This enforces zero trust where "Record PR" workflow always runs on fork with zero permissions on GH_TOKEN. -# When "Record PR" completes, this workflow runs in our repository with the appropriate permissions and sanitize inputs. -# -# Coupled with "Approve GitHub Action to run on forks", we have confidence no privilege can be escalated, -# since any malicious change would need to be approved, and upon social engineering, it'll have zero permissions. - - -on: - workflow_run: - workflows: ["Record PR details"] - types: - - completed - -permissions: - contents: read - -jobs: - get_pr_details: - permissions: - actions: read # download PR artifact - contents: read # checkout code - # Guardrails to only ever run if PR recording workflow was indeed - # run in a PR event and ran successfully - if: ${{ github.event.workflow_run.conclusion == 'success' }} - uses: ./.github/workflows/reusable_export_pr_details.yml - with: - record_pr_workflow_id: ${{ github.event.workflow_run.id }} - workflow_origin: ${{ github.event.repository.full_name }} - secrets: - token: ${{ secrets.GITHUB_TOKEN }} - label_pr: - needs: get_pr_details - runs-on: ubuntu-latest - permissions: - pull-requests: write # label respective PR - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: "Label PR based on title" - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - env: - PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }} - PR_TITLE: ${{ needs.get_pr_details.outputs.prTitle }} - PR_LABELS: ${{ needs.get_pr_details.outputs.prLabels }} - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - # This safely runs in our base repo, not on fork - # thus allowing us to provide a write access token to label based on PR title - # and label PR based on semantic title accordingly - script: | - const script = require('.github/scripts/label_pr_based_on_title.js') - await script({github, context, core}) diff --git a/.github/workflows/on_label_added.yml b/.github/workflows/on_label_added.yml deleted file mode 100644 index 5e981138929..00000000000 --- a/.github/workflows/on_label_added.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: On Label added - -# PROCESS -# -# 1. Fetch PR details previously saved from untrusted location -# 2. Parse details for safety -# 3. Comment on PR labels `size/XXL` and suggest splitting into smaller PRs if possible - -# USAGE -# -# NOTE: meant to be used with ./.github/workflows/record_pr.yml -# -# Security Note: -# -# This workflow depends on "Record PR" workflow that runs in an untrusted location (forks) instead of `pull_request_target`. -# This enforces zero trust where "Record PR" workflow always runs on fork with zero permissions on GH_TOKEN. -# When "Record PR" completes, this workflow runs in our repository with the appropriate permissions and sanitize inputs. -# -# Coupled with "Approve GitHub Action to run on forks", we have confidence no privilege can be escalated, -# since any malicious change would need to be approved, and upon social engineering, it'll have zero permissions. - -on: - workflow_run: - workflows: ["Record PR details"] - types: - - completed - -permissions: - contents: read - -jobs: - get_pr_details: - permissions: - actions: read # download PR artifact - contents: read # checkout code - if: ${{ github.event.workflow_run.conclusion == 'success' }} - uses: ./.github/workflows/reusable_export_pr_details.yml - with: - record_pr_workflow_id: ${{ github.event.workflow_run.id }} - workflow_origin: ${{ github.event.repository.full_name }} - secrets: - token: ${{ secrets.GITHUB_TOKEN }} - - split_large_pr: - needs: get_pr_details - runs-on: ubuntu-latest - permissions: - pull-requests: write # comment on PR - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - # Maintenance: Persist state per PR as an artifact to avoid spam on label add - - name: "Suggest split large Pull Request" - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - env: - PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }} - PR_ACTION: ${{ needs.get_pr_details.outputs.prAction }} - PR_AUTHOR: ${{ needs.get_pr_details.outputs.prAuthor }} - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const script = require('.github/scripts/comment_on_large_pr.js'); - await script({github, context, core}); diff --git a/.github/workflows/on_merged_pr.yml b/.github/workflows/on_merged_pr.yml deleted file mode 100644 index 980c5be553f..00000000000 --- a/.github/workflows/on_merged_pr.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: On PR merge - -# PROCESS -# -# 1. Fetch PR details previously saved from untrusted location -# 2. Parse details for safety -# 3. Add `pending-release` label for related issue -# 4. Make a comment in PR if related issue is invalid or can't be labeled - -# USAGE -# -# NOTE: meant to be used with ./.github/workflows/record_pr.yml -# -# Security Note: -# -# This workflow depends on "Record PR" workflow that runs in an untrusted location (forks) instead of `pull_request_target`. -# This enforces zero trust where "Record PR" workflow always runs on fork with zero permissions on GH_TOKEN. -# When "Record PR" completes, this workflow runs in our repository with the appropriate permissions and sanitize inputs. -# -# Coupled with "Approve GitHub Action to run on forks", we have confidence no privilege can be escalated, -# since any malicious change would need to be approved, and upon social engineering, it'll have zero permissions. - -on: - workflow_run: - workflows: ["Record PR details"] - types: - - completed - -permissions: - contents: read - -jobs: - get_pr_details: - permissions: - actions: read # download PR artifact - contents: read # checkout code - if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' - uses: ./.github/workflows/reusable_export_pr_details.yml - with: - record_pr_workflow_id: ${{ github.event.workflow_run.id }} - workflow_origin: ${{ github.event.repository.full_name }} - secrets: - token: ${{ secrets.GITHUB_TOKEN }} - release_label_on_merge: - needs: get_pr_details - runs-on: ubuntu-latest - permissions: - pull-requests: write # make a comment in PR if unable to find related issue - issues: write # label issue with pending-release - if: needs.get_pr_details.outputs.prIsMerged == 'true' - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: "Label PR related issue for release" - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - env: - PR_NUMBER: ${{ needs.get_pr_details.outputs.prNumber }} - PR_BODY: ${{ needs.get_pr_details.outputs.prBody }} - PR_IS_MERGED: ${{ needs.get_pr_details.outputs.prIsMerged }} - PR_AUTHOR: ${{ needs.get_pr_details.outputs.prAuthor }} - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const script = require('.github/scripts/label_related_issue.js') - await script({github, context, core}) diff --git a/.github/workflows/on_opened_pr.yml b/.github/workflows/on_opened_pr.yml deleted file mode 100644 index ac5c7a7477d..00000000000 --- a/.github/workflows/on_opened_pr.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: On new PR - -# PROCESS -# -# 1. Fetch PR details previously saved from untrusted location -# 2. Parse details for safety -# 3. Confirm there is a related issue for newly opened PR -# 4. Verify if PR template is used and legal acknowledgement hasn't been removed - -# USAGE -# -# NOTE: meant to be used with ./.github/workflows/record_pr.yml -# -# Security Note: -# -# This workflow depends on "Record PR" workflow that runs in an untrusted location (forks) instead of `pull_request_target`. -# This enforces zero trust where "Record PR" workflow always runs on fork with zero permissions on GH_TOKEN. -# When "Record PR" completes, this workflow runs in our repository with the appropriate permissions and sanitize inputs. -# -# Coupled with "Approve GitHub Action to run on forks", we have confidence no privilege can be escalated, -# since any malicious change would need to be approved, and upon social engineering, it'll have zero permissions. - -on: - workflow_run: - workflows: ["Record PR details"] - types: - - completed - -permissions: - contents: read - -jobs: - get_pr_details: - permissions: - actions: read # download PR artifact - contents: read # checkout code - if: ${{ github.event.workflow_run.conclusion == 'success' }} - uses: ./.github/workflows/reusable_export_pr_details.yml - with: - record_pr_workflow_id: ${{ github.event.workflow_run.id }} - workflow_origin: ${{ github.event.repository.full_name }} - secrets: - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/on_pr_updates.yml b/.github/workflows/on_pr_updates.yml index d087d355d50..b81bfe228e5 100644 --- a/.github/workflows/on_pr_updates.yml +++ b/.github/workflows/on_pr_updates.yml @@ -13,8 +13,7 @@ name: PR requirements # NOTES # -# PR requirements are checked async in on_opened_pr.yml and enforced here synchronously -# due to limitations in GH API. +# PR requirements are enforced synchronously here. on: pull_request: diff --git a/.github/workflows/record_pr.yml b/.github/workflows/record_pr.yml deleted file mode 100644 index 7fc23fe8d4b..00000000000 --- a/.github/workflows/record_pr.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Record PR details - -# PROCESS -# -# 1. Runs in fork location upon PR creation or changes -# 2. Saves GitHub Pull Request Webhook payload -# 3. Uploads as a temporary GitHub Action Artifact with shortest retention - -# USAGE -# -# see .github/workflows/on_merged_pr.yml and related for full example. -# -# on: -# workflow_run: -# workflows: ["Record PR details"] -# types: -# - completed -# -# Security Note: -# -# For security, this is intended to be a 2-step process: (1) collect PR, (2) act on PR. -# Do not ever use `pull_request_target` to "simplify", as it sends a write-token to the fork. Our linter should catch it. -# -# The first step runs in untrusted location (fork), therefore we limit permissions to only check out code. -# -# The second step will be workflows that want to act on a given PR, this time with intended permissions, and -# it runs on its base location (this repo!). -# -# This enforces zero trust where this workflow always runs on fork with zero permissions on GH_TOKEN. -# When this workflow completes, X workflows run in our repository with the appropriate permissions and sanitize inputs. -# -# Coupled with "Approve GitHub Action to run on forks", we have confidence no privilege can be escalated, -# since any malicious change would need to be approved, and upon social engineering, it'll have zero permissions. - - -on: - pull_request: - types: [opened, edited, closed, labeled] - -permissions: - contents: read - -jobs: - record_pr: - runs-on: ubuntu-latest - permissions: - contents: read # NOTE: treat as untrusted location - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: "Extract PR details" - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - with: - script: | - const script = require('.github/scripts/save_pr_details.js') - await script({github, context, core}) - - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 - with: - name: pr - path: pr.txt - retention-days: 1 diff --git a/.github/workflows/reusable_export_pr_details.yml b/.github/workflows/reusable_export_pr_details.yml deleted file mode 100644 index f404fec5244..00000000000 --- a/.github/workflows/reusable_export_pr_details.yml +++ /dev/null @@ -1,115 +0,0 @@ -name: Export previously recorded PR - -# PROCESS -# -# 1. Fetch PR details previously saved from untrusted location -# 2. Parse details for safety -# 3. Export only what's needed for automation, e.g., PR number, title, body, author, action, whether is merged - -# USAGE -# -# see .github/workflows/on_merged_pr.yml and related for full example. -# -# NOTE: meant to be used with workflows that react to a given PR state (labeling, new, merged, etc.) -# done separately to isolate security practices and make it reusable. - - -on: - workflow_call: - inputs: - record_pr_workflow_id: - description: "Record PR workflow execution ID to download PR details" - required: true - type: number - workflow_origin: # see https://github.com/aws-powertools/powertools-lambda-python/issues/1349 - description: "Repository full name for runner integrity" - required: true - type: string - secrets: - token: - description: "GitHub Actions temporary and scoped token" - required: true - # Map the workflow outputs to job outputs - outputs: - prNumber: - description: "PR Number" - value: ${{ jobs.export_pr_details.outputs.prNumber }} - prTitle: - description: "PR Title" - value: ${{ jobs.export_pr_details.outputs.prTitle }} - prBody: - description: "PR Body as string" - value: ${{ jobs.export_pr_details.outputs.prBody }} - prAuthor: - description: "PR author username" - value: ${{ jobs.export_pr_details.outputs.prAuthor }} - prAction: - description: "PR event action" - value: ${{ jobs.export_pr_details.outputs.prAction }} - prIsMerged: - description: "Whether PR is merged" - value: ${{ jobs.export_pr_details.outputs.prIsMerged }} - prLabels: - description: "PR Labels" - value: ${{ jobs.export_pr_details.outputs.prLabels }} - -permissions: - contents: read - -jobs: - export_pr_details: - permissions: - actions: read # download PR artifact - # see https://github.com/aws-powertools/powertools-lambda-python/issues/1349 - if: inputs.workflow_origin == 'aws-powertools/powertools-lambda-python' - runs-on: ubuntu-latest - env: - FILENAME: pr.txt - # Map the job outputs to step outputs - outputs: - prNumber: ${{ steps.prNumber.outputs.prNumber }} - prTitle: ${{ steps.prTitle.outputs.prTitle }} - prBody: ${{ steps.prBody.outputs.prBody }} - prAuthor: ${{ steps.prAuthor.outputs.prAuthor }} - prAction: ${{ steps.prAction.outputs.prAction }} - prIsMerged: ${{ steps.prIsMerged.outputs.prIsMerged }} - prLabels: ${{ steps.prLabels.outputs.prLabels }} - steps: - - name: Checkout repository # in case caller workflow doesn't checkout thus failing with file not found - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: "Download previously saved PR" - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 - env: - WORKFLOW_ID: ${{ inputs.record_pr_workflow_id }} - # For security, we only download artifacts tied to the successful PR recording workflow - with: - github-token: ${{ secrets.token }} - script: | - const script = require('.github/scripts/download_pr_artifact.js') - await script({github, context, core}) - # NodeJS standard library doesn't provide ZIP capabilities; use system `unzip` command instead - - name: "Unzip PR artifact" - run: unzip pr.zip - # NOTE: We need separate steps for each mapped output and respective IDs - # otherwise the parent caller won't see them regardless on how outputs are set. - - name: "Export Pull Request Number" - id: prNumber - run: echo prNumber="$(jq -c '.number' "${FILENAME}")" >> "$GITHUB_OUTPUT" - - name: "Export Pull Request Title" - id: prTitle - run: echo prTitle="$(jq -c '.pull_request.title' "${FILENAME}")" >> "$GITHUB_OUTPUT" - - name: "Export Pull Request Body" - id: prBody - run: echo prBody="$(jq -c '.pull_request.body' "${FILENAME}")" >> "$GITHUB_OUTPUT" - - name: "Export Pull Request Author" - id: prAuthor - run: echo prAuthor="$(jq -c '.pull_request.user.login' "${FILENAME}")" >> "$GITHUB_OUTPUT" - - name: "Export Pull Request Action" - id: prAction - run: echo prAction="$(jq -c '.action' "${FILENAME}")" >> "$GITHUB_OUTPUT" - - name: "Export Pull Request Merged status" - id: prIsMerged - run: echo prIsMerged="$(jq -c '.pull_request.merged' "${FILENAME}")" >> "$GITHUB_OUTPUT" - - name: "Export Pull Request labels" - id: prLabels - run: echo prLabels="$(jq -c '.labels' "${FILENAME}")" >> "$GITHUB_OUTPUT"