Upload PR Plugin to Cloudflare #255
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: Upload PR Plugin to Cloudflare | |
| on: | |
| workflow_run: | |
| workflows: ["CI - Main (API)"] | |
| types: | |
| - completed | |
| permissions: | |
| actions: read | |
| contents: read | |
| pull-requests: write | |
| concurrency: | |
| group: pr-plugin-upload-${{ github.event.workflow_run.pull_requests[0].number || github.event.workflow_run.id }} | |
| cancel-in-progress: true | |
| jobs: | |
| upload-pr-plugin: | |
| if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' }} | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| shell: bash | |
| env: | |
| SHELLOPTS: errexit:pipefail | |
| steps: | |
| - name: Resolve workflow run context | |
| id: context | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b | |
| with: | |
| script: | | |
| const workflowRun = context.payload.workflow_run; | |
| const runId = workflowRun.id; | |
| const headSha = workflowRun.head_sha; | |
| // Same-repo PRs populate this directly. | |
| let prNumber = workflowRun.pull_requests?.[0]?.number; | |
| // Fork PRs leave pull_requests empty. Match the run's head commit | |
| // against open PRs; pr.head.sha is the fork commit, so this works | |
| // across repositories where the commits API cannot. | |
| if (!prNumber) { | |
| const pulls = await github.paginate(github.rest.pulls.list, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: 'open', | |
| per_page: 100, | |
| }); | |
| prNumber = pulls.find((pr) => pr.head.sha === headSha)?.number; | |
| } | |
| // Last resort: only finds same-repo commits. | |
| if (!prNumber) { | |
| const prs = await github.rest.repos.listPullRequestsAssociatedWithCommit({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| commit_sha: headSha, | |
| }); | |
| prNumber = prs.data[0]?.number; | |
| } | |
| if (!prNumber) { | |
| core.setFailed(`Unable to resolve a pull request for workflow run ${runId}`); | |
| return; | |
| } | |
| core.setOutput('run_id', runId); | |
| core.setOutput('pr_number', prNumber); | |
| - name: Prepare artifact directory | |
| run: | | |
| set -Eeuo pipefail | |
| IFS=$'\n\t' | |
| mkdir -p "${{ runner.temp }}/pr-plugin" | |
| - name: Download plugin artifact | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b | |
| env: | |
| RUN_ID: ${{ steps.context.outputs.run_id }} | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const runId = Number(process.env.RUN_ID); | |
| const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| run_id: runId, | |
| }); | |
| const artifact = artifacts.data.artifacts.find((candidate) => | |
| candidate.name.startsWith(`unraid-plugin-${runId}-`) | |
| ); | |
| if (!artifact) { | |
| core.setFailed(`No unraid-plugin artifact found for workflow run ${runId}`); | |
| return; | |
| } | |
| const download = await github.rest.actions.downloadArtifact({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| artifact_id: artifact.id, | |
| archive_format: 'zip', | |
| }); | |
| const zipPath = path.join(process.env.RUNNER_TEMP, 'pr-plugin', 'artifact.zip'); | |
| fs.writeFileSync(zipPath, Buffer.from(download.data)); | |
| core.info(`Downloaded ${artifact.name}`); | |
| - name: Extract and validate plugin files | |
| id: files | |
| run: | | |
| set -Eeuo pipefail | |
| IFS=$'\n\t' | |
| ZIP_PATH="${{ runner.temp }}/pr-plugin/artifact.zip" | |
| UNPACKED_DIR="${{ runner.temp }}/pr-plugin/unpacked" | |
| DEPLOY_DIR="${{ runner.temp }}/pr-plugin/deploy" | |
| mkdir -p "$UNPACKED_DIR" "$DEPLOY_DIR" | |
| unzip -l "$ZIP_PATH" | awk ' | |
| NR <= 3 || /^-/ || /^Archive:/ {next} | |
| /files$/ {exit} | |
| { | |
| filename = $NF | |
| if (filename ~ /^\// || filename ~ /\.\.\//) { | |
| print "Invalid artifact path: " filename > "/dev/stderr" | |
| exit 1 | |
| } | |
| } | |
| ' | |
| unzip -o "$ZIP_PATH" -d "$UNPACKED_DIR" | |
| while IFS= read -r file; do | |
| filename=$(basename "$file") | |
| case "$filename" in | |
| *.plg|*.txz) ;; | |
| *) | |
| echo "Unexpected artifact file: $file" | |
| exit 1 | |
| ;; | |
| esac | |
| if [ -e "$DEPLOY_DIR/$filename" ]; then | |
| echo "Duplicate artifact filename: $filename" | |
| exit 1 | |
| fi | |
| cp "$file" "$DEPLOY_DIR/$filename" | |
| done < <(find "$UNPACKED_DIR" -type f) | |
| PLG_COUNT=$(find "$DEPLOY_DIR" -maxdepth 1 -name "*.plg" -type f | wc -l | tr -d ' ') | |
| TXZ_COUNT=$(find "$DEPLOY_DIR" -maxdepth 1 -name "*.txz" -type f | wc -l | tr -d ' ') | |
| if [ "$PLG_COUNT" -ne 1 ]; then | |
| echo "Expected exactly one .plg file, found $PLG_COUNT" | |
| exit 1 | |
| fi | |
| if [ "$TXZ_COUNT" -lt 1 ]; then | |
| echo "Expected at least one .txz file, found $TXZ_COUNT" | |
| exit 1 | |
| fi | |
| echo "deploy_dir=$DEPLOY_DIR" >> "$GITHUB_OUTPUT" | |
| ls -la "$DEPLOY_DIR" | |
| - name: Upload plugin to Cloudflare | |
| env: | |
| AWS_ACCESS_KEY_ID: ${{ secrets.CF_ACCESS_KEY_ID }} | |
| AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_SECRET_ACCESS_KEY }} | |
| AWS_DEFAULT_REGION: auto | |
| AWS_EC2_METADATA_DISABLED: true | |
| AWS_SHARED_CREDENTIALS_FILE: /dev/null | |
| AWS_CONFIG_FILE: /dev/null | |
| CF_BUCKET_PREVIEW: ${{ secrets.CF_BUCKET_PREVIEW }} | |
| CF_ENDPOINT: ${{ secrets.CF_ENDPOINT }} | |
| DEPLOY_DIR: ${{ steps.files.outputs.deploy_dir }} | |
| PR_NUMBER: ${{ steps.context.outputs.pr_number }} | |
| run: | | |
| set -Eeuo pipefail | |
| IFS=$'\n\t' | |
| : "${CF_BUCKET_PREVIEW:?CF_BUCKET_PREVIEW secret is required}" | |
| : "${CF_ENDPOINT:?CF_ENDPOINT secret is required}" | |
| : "${DEPLOY_DIR:?DEPLOY_DIR is required}" | |
| : "${PR_NUMBER:?PR_NUMBER is required}" | |
| aws s3 sync "$DEPLOY_DIR/" "s3://${CF_BUCKET_PREVIEW}/unraid-api/tag/PR${PR_NUMBER}" \ | |
| --endpoint-url "$CF_ENDPOINT" \ | |
| --checksum-algorithm CRC32 \ | |
| --no-guess-mime-type \ | |
| --content-encoding none \ | |
| --acl public-read | |
| - name: Comment URL | |
| uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 | |
| with: | |
| number: ${{ steps.context.outputs.pr_number }} | |
| header: prlink | |
| message: | | |
| This plugin has been deployed to Cloudflare R2 and is available for testing. | |
| Download it at this URL: | |
| ``` | |
| https://preview.dl.unraid.net/unraid-api/tag/PR${{ steps.context.outputs.pr_number }}/dynamix.unraid.net.plg | |
| ``` |