From f077589c517d84a2377d36d14c5d62df4f447b43 Mon Sep 17 00:00:00 2001 From: Catalan Lover Date: Sun, 17 May 2026 12:54:55 +0200 Subject: [PATCH] Refactor Image comment workflow to use shared script pattern. Also refactors away dead crud. --- .github/scripts/pr-ghcr-image-comment.js | 80 ++++++++++++++ .../pr-ghcr-image-comment-opened.yml | 66 +---------- .github/workflows/pr-ghcr-image-comment.yml | 104 ------------------ 3 files changed, 86 insertions(+), 164 deletions(-) create mode 100644 .github/scripts/pr-ghcr-image-comment.js delete mode 100644 .github/workflows/pr-ghcr-image-comment.yml diff --git a/.github/scripts/pr-ghcr-image-comment.js b/.github/scripts/pr-ghcr-image-comment.js new file mode 100644 index 00000000..d550edea --- /dev/null +++ b/.github/scripts/pr-ghcr-image-comment.js @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2026 Catalan Lover +// +// SPDX-License-Identifier: Apache-2.0 + +/** + * @param {Object} args + * @param {import('@actions/github').getOctokit} args.github + * @param {import('@actions/github').context} args.context + * @param {import('@actions/core')} args.core + */ +module.exports = async ({ github, context, core }) => { + const pullRequest = context.payload.pull_request; + if (!pullRequest) { + core.notice("No pull_request payload found; skipping."); + return; + } + + const marker = ""; + const repo = context.repo.repo; + const ownerContext = context.repo.owner; + const prNumber = pullRequest.number; + + // prOwner is a bit confusingly named it could be argued. Its Head Branch Owner effectively. + // It is used to determine the GHCR namespace for the PR build. + // In most cases this will be the same as the repo owner, + // but in fork scenarios it will be different and we want to ensure the GHCR namespace is correct, + // to keep this workflow functional for its intended purpose of providing image refs for PRs independently of origin. + const prOwner = pullRequest.head?.repo?.owner?.login || ownerContext; + const branch = pullRequest.head?.ref || ""; + const sha = pullRequest.head?.sha || context.sha; + const shortSha = sha.substring(0, 7); + const normalizedBranch = branch.replace(/\//g, "-"); + + const imageNamespace = `ghcr.io/${prOwner}/${repo}`; + const branchTag = normalizedBranch || shortSha; + const shaTag = `sha-${shortSha}`; + + const body = [ + `GHCR image refs for this PR:`, + "", + `- Namespace: \`${imageNamespace}\``, + `- Branch tag: \`${branchTag}\``, + `- SHA tag: \`${shaTag}\``, + `- Pull: \`docker pull ${imageNamespace}:${branchTag}\``, + "", + `This comment is generated from PR metadata only. It does not checkout or run PR code. It also does not reflect whether the image has actually been built yet.`, + marker, + ].join("\n"); + + // Update or create comments + const comments = await github.rest.issues.listComments({ + owner: ownerContext, + repo, + issue_number: prNumber, + per_page: 100, + }); + + const existingComment = comments.data.find( + (comment) => + comment.user?.type === "Bot" && + typeof comment.body === "string" && + comment.body.includes(marker) + ); + + if (existingComment) { + await github.rest.issues.updateComment({ + owner: ownerContext, + repo, + comment_id: existingComment.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: ownerContext, + repo, + issue_number: prNumber, + body, + }); + } +}; diff --git a/.github/workflows/pr-ghcr-image-comment-opened.yml b/.github/workflows/pr-ghcr-image-comment-opened.yml index 99d628e3..7c5ecd16 100644 --- a/.github/workflows/pr-ghcr-image-comment-opened.yml +++ b/.github/workflows/pr-ghcr-image-comment-opened.yml @@ -15,66 +15,12 @@ jobs: comment-pr: runs-on: ubuntu-latest steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + - name: Comment on the pull request - uses: actions/github-script@v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 with: script: | - const marker = ''; - const pullRequest = context.payload.pull_request; - - if (!pullRequest) { - core.notice('No pull_request payload found; skipping.'); - return; - } - - const owner = pullRequest.head?.repo?.owner?.login || context.repo.owner; - const repo = context.repo.repo; - const branch = pullRequest.head?.ref || ''; - const sha = pullRequest.head?.sha || context.sha; - const shortSha = sha.substring(0, 7); - const normalizedBranch = branch.replace(/\//g, '-'); - - const imageNamespace = `ghcr.io/${owner}/${repo}`; - const branchTag = normalizedBranch || shortSha; - const shaTag = `sha-${shortSha}`; - - const body = [ - `GHCR image refs for this PR:`, - '', - `- Namespace: \`${imageNamespace}\``, - `- Branch tag: \`${branchTag}\``, - `- SHA tag: \`${shaTag}\``, - `- Pull: \`docker pull ${imageNamespace}:${branchTag}\``, - '', - `This comment is generated from PR metadata only. It does not checkout or run PR code.`, - marker, - ].join('\n'); - - const comments = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo, - issue_number: pullRequest.number, - per_page: 100, - }); - - const existingComment = comments.data.find(comment => - comment.user?.type === 'Bot' && - typeof comment.body === 'string' && - comment.body.includes(marker) - ); - - if (existingComment) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo, - comment_id: existingComment.id, - body, - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo, - issue_number: pullRequest.number, - body, - }); - } + const script = require('./.github/scripts/pr-ghcr-image-comment.js'); + await script({ github, context, core }); diff --git a/.github/workflows/pr-ghcr-image-comment.yml b/.github/workflows/pr-ghcr-image-comment.yml deleted file mode 100644 index 58b3d2e6..00000000 --- a/.github/workflows/pr-ghcr-image-comment.yml +++ /dev/null @@ -1,104 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Catalan Lover -# -# SPDX-License-Identifier: Apache-2.0 -# Reusable workflow: comment on PRs with published GHCR image links - -name: "PR — GHCR Image Link" - -on: - workflow_call: - inputs: - branch: - description: "Branch name for the published image" - required: true - type: string - image_tags: - description: "Newline-delimited list of image tags from metadata-action" - required: true - type: string - image_owner: - description: "Lowercased image owner for ghcr.io" - required: true - type: string - -permissions: - issues: write - pull-requests: write - contents: read - -jobs: - comment-prs: - runs-on: ubuntu-latest - steps: - - name: Comment on matching pull requests - uses: actions/github-script@v8 - env: - IMAGE_TAGS: ${{ inputs.image_tags }} - IMAGE_OWNER: ${{ inputs.image_owner }} - BRANCH: ${{ inputs.branch }} - with: - script: | - const branch = process.env.BRANCH; - const repo = context.repo.repo; - const owner = process.env.IMAGE_OWNER; - const tagLines = (process.env.IMAGE_TAGS || '').split('\n').map(l => l.trim()).filter(Boolean); - - if (!branch) { - core.notice('No branch input; skipping PR comment.'); - return; - } - - const normalizedBranch = branch.replace(/\//g, '-'); - const shaShort = context.sha.substring(0, 7); - const candidates = [ - `ghcr.io/${owner}/${repo}:${branch}`, - `ghcr.io/${owner}/${repo}:${normalizedBranch}`, - `ghcr.io/${owner}/${repo}:sha-${shaShort}`, - ]; - - const imageRef = candidates.find(c => tagLines.includes(c)) || tagLines[0]; - if (!imageRef) { - core.notice('No image tags found from metadata-action; skipping PR comment.'); - return; - } - - const marker = ''; - const body = `Published GHCR image for branch \`${branch}\`:\n\n- Image: ${imageRef}\n- Pull: \`docker pull ${imageRef}\`\n\n` + marker; - - const prs = await github.rest.pulls.list({ - owner: context.repo.owner, - repo, - state: 'open', - head: `${context.repo.owner}:${branch}`, - per_page: 100, - }); - - if (!prs.data.length) { - core.notice('No open PRs found for branch; skipping comment.'); - return; - } - - for (const pr of prs.data) { - const comments = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo, - issue_number: pr.number, - per_page: 100, - }); - const botComment = comments.data.find(c => c.user && c.user.type === 'Bot' && c.body && c.body.includes(marker)); - if (botComment) { - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo, - comment_id: botComment.id, - body, - }); - } else { - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo, - issue_number: pr.number, - body, - }); - } - }