name: Munge PR on: pull_request_target: permissions: contents: read pull-requests: write defaults: run: shell: 'bash -Eeuo pipefail -x {0}' env: # https://github.com/docker-library/bashbrew/issues/10 GIT_LFS_SKIP_SMUDGE: 1 # limit to one run at a time per pull request # no cancel-in-progress so that the running diff comment can finish before the next since the old diff could be helpful for review concurrency: # this should use "github.ref", but: # "For pull requests events except pull_request_target, it is refs/pull//merge. pull_request_target events have the ref from the base branch." 🙃 # https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#github-context #group: ${{ github.workflow }}-${{ github.ref }} # https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request_target group: ${{ github.workflow }}-${{ github.event.pull_request.number }} cancel-in-progress: false jobs: gather: name: Gather Metadata runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: # ideally this would be "github.event.pull_request.merge_commit_sha" but according to https://docs.github.com/en/free-pro-team@latest/rest/reference/pulls#get-a-pull-request if "mergeable" is null (meaning there's a background job in-progress to check mergeability), that value is undefined... ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 - id: gather name: Affected Images run: | (set +x; echo "::stop-commands::$(echo -n ${{ github.token }} | sha256sum | head -c 64)") git fetch --quiet https://github.com/docker-library/official-images.git master externalPins="$(git diff --no-renames --name-only FETCH_HEAD...HEAD -- '.external-pins/*/**')" externalPinTags="$( if [ -n "$externalPins" ]; then # doing backflips to run "tag.sh" from master instead of from the PR git show FETCH_HEAD:.external-pins/tag.sh > ~/master-external-pins-tag.sh chmod +x ~/master-external-pins-tag.sh ~/master-external-pins-tag.sh $externalPins fi )" images="$(git diff --no-renames --name-only FETCH_HEAD...HEAD -- library/)" if [ -n "$images" ]; then new="$(git diff --no-renames --name-only --diff-filter=A FETCH_HEAD...HEAD -- $images)" deleted="$(git diff --no-renames --name-only --diff-filter=D FETCH_HEAD...HEAD -- $images)" else new= deleted= fi export images new deleted externalPins externalPinTags images="$(jq -cn ' (env.images | rtrimstr("\n") | split("\n")) as $images | (env.new | rtrimstr("\n") | split("\n")) as $new | (env.deleted | rtrimstr("\n") | split("\n")) as $deleted | (env.externalPins | rtrimstr("\n") | split("\n")) as $externalPins | (env.externalPinTags | rtrimstr("\n") | split("\n")) as $externalPinTags | { images: $images, count: ($images | length), new: $new, deleted: $deleted, externalPins: $externalPins, externalPinTags: $externalPinTags, externalPinsCount: ($externalPins | length), } | .imagesAndExternalPinsCount = (.count + .externalPinsCount) # man, I *really* do not love GitHub Actions expressions... ')" jq . <<<"$images" set +x echo "::$(echo -n ${{ github.token }} | sha256sum | head -c 64)::" echo "images=$images" >> "$GITHUB_OUTPUT" outputs: images: '${{ steps.gather.outputs.images }}' apply-labels: name: Apply Labels runs-on: ubuntu-latest needs: gather if: fromJSON(needs.gather.outputs.images).imagesAndExternalPinsCount > 0 steps: - name: Apply Labels uses: actions/github-script@v7 env: IMAGES: ${{ needs.gather.outputs.images }} with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const data = JSON.parse(process.env.IMAGES); var labels = [ ...data.images, // "library/debian", ... ...new Set(data.externalPinTags.map(x => 'external/' + x.replace(/:.+$/, ''))), // "external/mcr.microsoft.com/windows/servercore", ... ]; if (data.new.length > 0) { labels.push('new-image'); } console.log(labels); await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, labels: labels, }); diff: name: Diff Comment runs-on: ubuntu-latest needs: gather if: fromJSON(needs.gather.outputs.images).imagesAndExternalPinsCount > 0 steps: - uses: actions/checkout@v4 with: # again, this would ideally be "github.event.pull_request.merge_commit_sha" but we might not have that yet when this runs, so we compromise by checkout out the latest code from the target branch (so we get the latest "diff-pr.sh" script to run) ref: ${{ github.event.pull_request.base.ref }} fetch-depth: 0 - uses: ./.github/workflows/.bashbrew with: build: 'docker' - name: Prepare Environment run: | # this avoids running repo-local scripts (to avoid CVE-2020-15228 via the scripts being updated to write nasty things to $GITHUB_ENV) docker build --tag oisupport/bashbrew:diff-pr . - name: Gather Maintainers env: IMAGES: ${{ needs.gather.outputs.images }} run: | files="$(jq <<<"$IMAGES" -r '.images | map(@sh) | join(" ")')" eval "set -- $files" for f; do if [ -s "$f" ]; then docker run --rm --read-only --tmpfs /tmp oisupport/bashbrew:diff-pr \ bashbrew cat --format ' - `{{ $.RepoName }}`:{{ range .Manifest.Global.Maintainers }} @{{ .Handle }}{{ end }}' "$f" fi done | tee "$GITHUB_WORKSPACE/oi-pr.maint" - name: Generate Diff env: GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} run: | docker run --rm --read-only --tmpfs /tmp oisupport/bashbrew:diff-pr ./diff-pr.sh "$GITHUB_PR_NUMBER" | tee "$GITHUB_WORKSPACE/oi-pr.diff" - name: Comment uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const commentText = 'Diff for ' + context.payload.pull_request.head.sha + ':'; const fs = require('fs'); const diff = fs.readFileSync(process.env.GITHUB_WORKSPACE + '/oi-pr.diff').toString().trimEnd(); var body = "
\n" + commentText + "\n\n```diff\n" + diff + "\n```\n\n
"; const maint = fs.readFileSync(process.env.GITHUB_WORKSPACE + '/oi-pr.maint').toString().trimEnd(); if (maint.length > 0) { body += "\n\nRelevant Maintainers:\n\n" + maint; } fs.writeFileSync(process.env.GITHUB_STEP_SUMMARY, body); // https://docs.github.com/en/graphql/reference/mutations#minimizecomment const minimizeql = ` mutation($comment: ID!) { minimizeComment(input: { classifier: OUTDATED, clientMutationId: "doi-munge-pr", subjectId: $comment }) { clientMutationId minimizedComment { isMinimized minimizedReason } } } `; // https://docs.github.com/en/graphql/reference/mutations#unminimizecomment const unminimizeql = ` mutation($comment: ID!) { unminimizeComment(input: { clientMutationId: "doi-munge-pr", subjectId: $comment }) { clientMutationId unminimizedComment { isMinimized minimizedReason } } } `; needNewComment = true; console.log('Reviewing existing comments...'); for await (const { data: comments } of github.paginate.iterator( github.rest.issues.listComments, { owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, } )) { for (const comment of comments) { if (comment.user.login === 'github-actions[bot]') { if (needNewComment && comment.body.includes(commentText)) { needNewComment = false; console.log('Unhiding comment: ' + comment.id + ' (' + comment.node_id + ')'); const result = await github.graphql(unminimizeql, { comment: comment.node_id }); console.log('- result: ' + JSON.stringify(result)); } else { console.log('Hiding comment: ' + comment.id + ' (' + comment.node_id + ')'); const result = await github.graphql(minimizeql, { comment: comment.node_id }); console.log('- result: ' + JSON.stringify(result)); } } } } if (needNewComment) { console.log('Creating new comment...'); await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, body: body, }); }