Podman PR Push #8119
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Podman PR Push | |
| on: | |
| workflow_run: | |
| workflows: | |
| - 'PR Build Image (Hermetic)' | |
| types: | |
| - completed | |
| jobs: | |
| podman-push: | |
| name: Push Podman Image to Registry | |
| runs-on: ubuntu-latest | |
| if: ${{ github.event_name == 'workflow_run' }} | |
| permissions: | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Get PR number from workflow run | |
| id: get-pr-info | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR_TARGET_REPO: ${{ github.repository }} | |
| # If the PR is from a fork, prefix it with `<owner-login>:`, otherwise only the PR branch name is relevant: | |
| PR_BRANCH: |- | |
| ${{ | |
| (github.event.workflow_run.head_repository.owner.login != github.event.workflow_run.repository.owner.login) | |
| && format('{0}:{1}', github.event.workflow_run.head_repository.owner.login, github.event.workflow_run.head_branch) | |
| || github.event.workflow_run.head_branch | |
| }} | |
| FULL_SHA: ${{ github.event.workflow_run.head_sha }} | |
| run: | | |
| SHORT_SHA=$(echo "${{ env.FULL_SHA }}" | cut -c1-8) | |
| # Need to use gh cli instead of `events.workflow_run.pull_requests` because the latter doesn't work for PRs from forks | |
| # Refer to https://github.com/orgs/community/discussions/25220 | |
| PR_NUMBER=$(gh pr view --repo "${PR_TARGET_REPO}" "${PR_BRANCH}" --json number --jq '.number') | |
| if [ -n "$PR_NUMBER" ]; then | |
| echo "Found PR number: $PR_NUMBER" | |
| echo "PR branch: $PR_BRANCH" | |
| echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT | |
| echo "short-sha=$SHORT_SHA" >> $GITHUB_OUTPUT | |
| else | |
| echo "::error::Failed to determine PR number for branch: $PR_BRANCH" | |
| echo "## Failed to determine PR number" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "Could not find a PR for branch: \`$PR_BRANCH\`" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "No PR comment could be posted for this workflow run." >> $GITHUB_STEP_SUMMARY | |
| exit 1 | |
| fi | |
| - name: Comment build failure | |
| if: ${{ github.event.workflow_run.conclusion != 'success' }} | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 | |
| env: | |
| PR_NUMBER: ${{ steps.get-pr-info.outputs.pr_number }} | |
| BUILD_WORKFLOW_URL: ${{ github.event.workflow_run.html_url }} | |
| BUILD_CONCLUSION: ${{ github.event.workflow_run.conclusion }} | |
| with: | |
| script: | | |
| const prNumber = process.env.PR_NUMBER; | |
| const buildUrl = process.env.BUILD_WORKFLOW_URL; | |
| const conclusion = process.env.BUILD_CONCLUSION; | |
| const body = `The container image [build workflow](${buildUrl}) finished with status: \`${conclusion}\`.\n\n` | |
| github.rest.issues.createComment({ | |
| issue_number: parseInt(prNumber), | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: body | |
| }) | |
| - name: Determine artifact name | |
| if: ${{ github.event.workflow_run.conclusion == 'success' }} | |
| id: get-artifact-name | |
| env: | |
| PR_NUMBER: ${{ steps.get-pr-info.outputs.pr_number }} | |
| WORKFLOW_HEAD_SHA: ${{ github.event.workflow_run.head_sha }} | |
| run: | | |
| # For workflow_run, extract from the event context | |
| SHORT_SHA=$(echo "${{ env.WORKFLOW_HEAD_SHA }}" | cut -c1-8) | |
| echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT | |
| ARTIFACT_NAME="podman-image-${{ env.PR_NUMBER }}-${SHORT_SHA}" | |
| SKIP_ARTIFACT_NAME="pr-${{ env.PR_NUMBER }}-${SHORT_SHA}-isSkipped" | |
| echo "Using artifact name: $ARTIFACT_NAME" | |
| echo "Using skip artifact name: $SKIP_ARTIFACT_NAME" | |
| echo "artifact_name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT | |
| echo "skip_artifact_name=$SKIP_ARTIFACT_NAME" >> $GITHUB_OUTPUT | |
| - name: Download Skip Status Artifact | |
| if: ${{ github.event.workflow_run.conclusion == 'success' }} | |
| id: download-skip-status | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| env: | |
| SKIP_ARTIFACT_NAME: ${{ steps.get-artifact-name.outputs.skip_artifact_name }} | |
| with: | |
| name: ${{ env.SKIP_ARTIFACT_NAME }} | |
| path: ./rhdh-skip-artifacts | |
| run-id: ${{ github.event.workflow_run.id || github.run_id }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| continue-on-error: true | |
| - name: Check Skip Status | |
| if: ${{ github.event.workflow_run.conclusion == 'success' }} | |
| id: check-skip | |
| run: | | |
| if [ -f "./rhdh-skip-artifacts/isSkipped.txt" ]; then | |
| IS_SKIPPED_RAW=$(cat ./rhdh-skip-artifacts/isSkipped.txt) | |
| # Sanitize: Allow only 'true' or 'false', default to false if unknown to skip artifact download in case of injection | |
| if [[ "$IS_SKIPPED_RAW" == "false" ]]; then | |
| IS_SKIPPED="false" | |
| else | |
| IS_SKIPPED="true" | |
| fi | |
| echo "Found skip status: $IS_SKIPPED_RAW" | |
| echo "Sanitized skip status: $IS_SKIPPED" | |
| echo "is_skipped=$IS_SKIPPED" >> $GITHUB_OUTPUT | |
| else | |
| echo "Skip status artifact not found, skipping push" | |
| echo "is_skipped=true" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Download Image Artifacts | |
| if: ${{ github.event.workflow_run.conclusion == 'success' && steps.check-skip.outputs.is_skipped != 'true' }} | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| env: | |
| ARTIFACT_NAME: ${{ steps.get-artifact-name.outputs.artifact_name }} | |
| with: | |
| name: ${{ env.ARTIFACT_NAME }} | |
| path: ./rhdh-podman-artifacts | |
| run-id: ${{ github.event.workflow_run.id || github.run_id }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Load and prepare image | |
| if: ${{ github.event.workflow_run.conclusion == 'success' && steps.check-skip.outputs.is_skipped != 'true' }} | |
| id: prepare | |
| run: | | |
| # Check if artifacts exist | |
| if [ ! -f "./rhdh-podman-artifacts/image.tar" ]; then | |
| echo "Error: image.tar not found in artifacts" | |
| echo "This may make sense if the build was skipped" | |
| exit 1 | |
| fi | |
| # Load the image from tar file (contains all tags) | |
| podman load -i ./rhdh-podman-artifacts/image.tar | |
| # Read metadata | |
| TAGS_LIST=$(cat ./rhdh-podman-artifacts/tags.txt) | |
| echo "Loaded images:" | |
| podman images | |
| echo "Full tags from metadata:" | |
| printf '%s\n' "$TAGS_LIST" | |
| # SECURITY: Use a random delimiter to prevent output injection from artifact poisoning | |
| DELIMITER=$(openssl rand -hex 16) | |
| echo "tags<<$DELIMITER" >> $GITHUB_OUTPUT | |
| printf '%s\n' "$TAGS_LIST" >> $GITHUB_OUTPUT | |
| echo "$DELIMITER" >> $GITHUB_OUTPUT | |
| - name: Push Images | |
| if: ${{ github.event.workflow_run.conclusion == 'success' && steps.check-skip.outputs.is_skipped != 'true' }} | |
| uses: redhat-actions/push-to-registry@5ed88d269cf581ea9ef6dd6806d01562096bee9c # v2.8 | |
| env: | |
| PUSHED_TAGS: ${{ steps.prepare.outputs.tags }} | |
| with: | |
| tags: ${{ env.PUSHED_TAGS }} | |
| username: ${{ secrets.QUAY_USERNAME }} | |
| password: ${{ secrets.QUAY_TOKEN }} | |
| - name: Comment skip status | |
| if: ${{ github.event.workflow_run.conclusion == 'success' && steps.check-skip.outputs.is_skipped == 'true' }} | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 | |
| env: | |
| PR_NUMBER: ${{ steps.get-pr-info.outputs.pr_number }} | |
| BUILD_WORKFLOW_URL: ${{ github.event.workflow_run.html_url }} | |
| PUBLISH_WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| with: | |
| script: | | |
| const prNumber = process.env.PR_NUMBER; | |
| const buildUrl = process.env.BUILD_WORKFLOW_URL; | |
| const publishUrl = process.env.PUBLISH_WORKFLOW_URL; | |
| const body = `The container image [build](${buildUrl}) and [publish](${publishUrl}) workflows were skipped (either due to [skip-build] tag or no relevant changes with existing image).\n`; | |
| github.rest.issues.createComment({ | |
| issue_number: parseInt(prNumber), | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: body | |
| }) | |
| - name: Comment the image pull link | |
| if: ${{ github.event.workflow_run.conclusion == 'success' && steps.check-skip.outputs.is_skipped != 'true' }} | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 | |
| env: | |
| PUSHED_TAGS: ${{ steps.prepare.outputs.tags }} | |
| PR_NUMBER: ${{ steps.get-pr-info.outputs.pr_number }} | |
| BUILD_WORKFLOW_URL: ${{ github.event.workflow_run.html_url }} | |
| PUBLISH_WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| with: | |
| script: | | |
| const prNumber = process.env.PR_NUMBER; | |
| const pushedTags = process.env.PUSHED_TAGS; | |
| const buildUrl = process.env.BUILD_WORKFLOW_URL; | |
| const publishUrl = process.env.PUBLISH_WORKFLOW_URL; | |
| // This shouldn't happen since Push Images would've failed, but just in case a mistake is made in the workflow logic or logic changes | |
| const noTagsError = (reason) => { | |
| const body = `The image was [built](${buildUrl}) and [published](${publishUrl}) but no tags were found: ${reason}.\n`; | |
| github.rest.issues.createComment({ | |
| issue_number: parseInt(prNumber), | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: body | |
| }); | |
| core.setFailed(reason); | |
| }; | |
| if (!pushedTags) { | |
| noTagsError('No pushed tags output found'); | |
| return; | |
| } | |
| const tags = pushedTags.trim().split('\n').filter(tag => tag.trim()); | |
| if (tags.length === 0) { | |
| noTagsError('Pushed tags output was empty'); | |
| return; | |
| } | |
| console.log(`Found ${tags.length} tags:`, tags); | |
| const tagLinks = tags.map(fullTag => { | |
| return `* [\`${fullTag}\`](https://${fullTag})`; | |
| }).join('\n'); | |
| const body = `Image was [built](${buildUrl}) and [published](${publishUrl}) successfully. It is available at:\n\n${tagLinks}\n`; | |
| console.log(`Creating comment for PR ${prNumber} with body:\n ${body}`); | |
| github.rest.issues.createComment({ | |
| issue_number: parseInt(prNumber), | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: body | |
| }) | |
| - name: Comment publish failure | |
| if: ${{ failure() && github.event.workflow_run.conclusion == 'success' }} | |
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 | |
| env: | |
| PR_NUMBER: ${{ steps.get-pr-info.outputs.pr_number }} | |
| BUILD_WORKFLOW_URL: ${{ github.event.workflow_run.html_url }} | |
| PUBLISH_WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| with: | |
| script: | | |
| const prNumber = process.env.PR_NUMBER; | |
| const buildUrl = process.env.BUILD_WORKFLOW_URL; | |
| const publishUrl = process.env.PUBLISH_WORKFLOW_URL; | |
| const body = `The container image was [built](${buildUrl}) successfully but failed to [publish](${publishUrl}) to the registry.\n`; | |
| github.rest.issues.createComment({ | |
| issue_number: parseInt(prNumber), | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: body | |
| }) | |