Preview FastGPT Image — Push #2222
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: Preview FastGPT Image — Push | |
| on: | |
| workflow_run: | |
| workflows: ['Preview FastGPT Image — Build'] | |
| types: [completed] | |
| # Only one push at a time | |
| concurrency: | |
| group: 'preview-fastgpt-push' | |
| cancel-in-progress: false | |
| permissions: | |
| contents: read | |
| jobs: | |
| check_pr_author: | |
| runs-on: ubuntu-24.04 | |
| # Only inspect the upstream PR when the build succeeded. | |
| if: ${{ github.event.workflow_run.conclusion == 'success' }} | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| actions: read | |
| outputs: | |
| trusted: ${{ steps.trust.outputs.trusted }} | |
| should_publish: ${{ steps.trust.outputs.should_publish }} | |
| number: ${{ steps.trust.outputs.number }} | |
| sha: ${{ steps.trust.outputs.sha }} | |
| matrix: ${{ steps.trust.outputs.matrix }} | |
| steps: | |
| - name: Check trusted PR author | |
| id: trust | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const allowedAssociations = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']); | |
| const emptyMatrix = JSON.stringify({ | |
| include: [{ image: 'noop', artifact_name: 'noop', image_name: 'noop' }] | |
| }); | |
| const workflowRun = context.payload.workflow_run; | |
| let prNumber = workflowRun.pull_requests?.[0]?.number; | |
| core.setOutput('trusted', 'false'); | |
| core.setOutput('should_publish', 'false'); | |
| core.setOutput('matrix', emptyMatrix); | |
| if (!prNumber) { | |
| const headOwner = workflowRun.head_repository?.owner?.login; | |
| const headBranch = workflowRun.head_branch; | |
| if (headOwner && headBranch) { | |
| core.info(`workflow_run payload did not include a PR number; looking up PR by ${headOwner}:${headBranch}.`); | |
| const { data: pullRequests } = await github.rest.pulls.list({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: 'open', | |
| head: `${headOwner}:${headBranch}` | |
| }); | |
| const matchedPullRequest = pullRequests.find((pullRequest) => pullRequest.head.sha === workflowRun.head_sha) ?? pullRequests[0]; | |
| prNumber = matchedPullRequest?.number; | |
| } | |
| } | |
| if (!prNumber) { | |
| core.warning('No pull request was found on the workflow_run payload. Skipping privileged FastGPT preview publish.'); | |
| return; | |
| } | |
| const { data: pullRequest } = await github.rest.pulls.get({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: prNumber | |
| }); | |
| const trusted = allowedAssociations.has(pullRequest.author_association); | |
| core.setOutput('trusted', trusted ? 'true' : 'false'); | |
| core.setOutput('number', String(pullRequest.number)); | |
| core.setOutput('sha', pullRequest.head.sha); | |
| if (!trusted) { | |
| core.warning(`PR #${pullRequest.number} author association is ${pullRequest.author_association}; skipping privileged FastGPT preview publish.`); | |
| return; | |
| } | |
| const artifactConfigs = [ | |
| { image: 'fastgpt', artifact_name: 'preview-fastgpt-image', image_name: 'fastgpt' }, | |
| { image: 'code-sandbox', artifact_name: 'preview-code-sandbox-image', image_name: 'fastgpt-code-sandbox' }, | |
| { image: 'mcp_server', artifact_name: 'preview-mcp_server-image', image_name: 'fastgpt-mcp-server' } | |
| ]; | |
| const artifacts = await github.paginate(github.rest.actions.listWorkflowRunArtifacts, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: workflowRun.id, | |
| per_page: 100 | |
| }); | |
| const artifactNames = new Set(artifacts.map((artifact) => artifact.name)); | |
| const images = artifactConfigs.filter((artifactConfig) => | |
| artifactNames.has(artifactConfig.artifact_name) | |
| ); | |
| const matrix = images.length > 0 ? { include: images } : JSON.parse(emptyMatrix); | |
| core.setOutput('should_publish', images.length > 0 ? 'true' : 'false'); | |
| core.setOutput('matrix', JSON.stringify(matrix)); | |
| core.info(`PR #${pullRequest.number} author association is ${pullRequest.author_association}; preview images to publish: ${images.map((image) => image.image).join(', ') || 'none'}.`); | |
| push: | |
| needs: check_pr_author | |
| runs-on: ubuntu-24.04 | |
| # Only trusted repository members/collaborators can promote PR-built artifacts. | |
| if: ${{ needs.check_pr_author.outputs.trusted == 'true' && needs.check_pr_author.outputs.should_publish == 'true' }} | |
| permissions: | |
| contents: read | |
| packages: write | |
| attestations: write | |
| id-token: write | |
| pull-requests: write | |
| issues: write | |
| actions: read | |
| strategy: | |
| matrix: ${{ fromJSON(needs.check_pr_author.outputs.matrix) }} | |
| fail-fast: false | |
| steps: | |
| - name: Download build artifact | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: ${{ matrix.artifact_name }} | |
| path: /tmp | |
| run-id: ${{ github.event.workflow_run.id }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Load Docker image | |
| run: docker load --input /tmp/${{ matrix.image_name }}-image.tar | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to GitHub Container Registry | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.repository_owner }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Tag and push Docker image | |
| run: | | |
| SHA="${{ needs.check_pr_author.outputs.sha }}" | |
| docker tag ${{ matrix.image_name }}-pr:${SHA} \ | |
| ghcr.io/${{ github.repository_owner }}/fastgpt-pr:${{ matrix.image }}_${SHA} | |
| docker push ghcr.io/${{ github.repository_owner }}/fastgpt-pr:${{ matrix.image }}_${SHA} | |
| - name: Format preview timestamp | |
| id: preview_time | |
| run: | | |
| echo "value=$(TZ='Asia/Shanghai' date '+%Y-%m-%d %H:%M:%S (UTC+8)')" >> "$GITHUB_OUTPUT" | |
| - name: Add PR comment on success | |
| if: success() && needs.check_pr_author.outputs.number != '' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const prNumber = parseInt('${{ needs.check_pr_author.outputs.number }}'); | |
| const marker = '<!-- fastgpt-preview-${{ matrix.image }} -->'; | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| }); | |
| const existingComment = comments.find(comment => | |
| comment.body.includes(marker) | |
| ); | |
| const commentBody = `${marker} | |
| ✅ **Build Successful** - Preview ${{ matrix.image }} Image for this PR: | |
| \`\`\` | |
| ghcr.io/${{ github.repository_owner }}/fastgpt-pr:${{ matrix.image }}_${{ needs.check_pr_author.outputs.sha }} | |
| \`\`\` | |
| 🕒 Time: ${{ steps.preview_time.outputs.value }} | |
| `; | |
| if (existingComment) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existingComment.id, | |
| body: commentBody | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: prNumber, | |
| body: commentBody | |
| }); | |
| } |