diff --git a/.github/workflows/pr-ghcr-image-comment-opened.yml b/.github/workflows/pr-ghcr-image-comment-opened.yml new file mode 100644 index 00000000..99d628e3 --- /dev/null +++ b/.github/workflows/pr-ghcr-image-comment-opened.yml @@ -0,0 +1,80 @@ +# SPDX-FileCopyrightText: 2026 Catalan Lover +# +# SPDX-License-Identifier: Apache-2.0 + +name: "PR - GHCR Image Link (Opened)" + +on: + pull_request_target: + types: [opened, synchronize, reopened] + +permissions: + issues: write + +jobs: + comment-pr: + runs-on: ubuntu-latest + steps: + - name: Comment on the pull request + uses: actions/github-script@v8 + 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, + }); + } diff --git a/.github/workflows/pr-ghcr-image-comment.yml b/.github/workflows/pr-ghcr-image-comment.yml new file mode 100644 index 00000000..58b3d2e6 --- /dev/null +++ b/.github/workflows/pr-ghcr-image-comment.yml @@ -0,0 +1,104 @@ +# 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, + }); + } + }