Push PR Image #8
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: Push PR Image | |
| # Runs after "Build PR" completes. Because workflow_run executes in the context | |
| # of the base repository, it has the secrets/permissions needed to push to GHCR | |
| # even when the PR comes from a fork. Downloads the image artifacts built by the | |
| # PR run, pushes per-arch images, and stitches them into a multi-arch manifest | |
| # tagged `pr-<number>` so people can install/test a PR with a single pull. | |
| on: | |
| workflow_run: | |
| workflows: ["Build PR"] | |
| types: [completed] | |
| permissions: | |
| packages: write | |
| pull-requests: write | |
| jobs: | |
| push: | |
| if: | | |
| github.event.workflow_run.conclusion == 'success' && | |
| github.event.workflow_run.event == 'pull_request' | |
| runs-on: ubuntu-24.04 | |
| strategy: | |
| matrix: | |
| arch: [amd64, arm64] | |
| steps: | |
| - name: Prepare | |
| env: | |
| REPOSITORY: '${{ github.repository }}' | |
| run: echo "REPOSITORY_LC=${REPOSITORY,,}" >> ${GITHUB_ENV} | |
| - name: Get PR number | |
| id: pr | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| PR_INFO=$(gh api "repos/${{ github.repository }}/pulls?state=all" \ | |
| --jq ".[] | select(.head.sha == \"${{ github.event.workflow_run.head_sha }}\") | {number: .number, state: .state}" \ | |
| | head -1) | |
| PR_NUMBER=$(echo "$PR_INFO" | jq -r '.number // empty') | |
| PR_STATE=$(echo "$PR_INFO" | jq -r '.state // empty') | |
| if [[ ! "$PR_NUMBER" =~ ^[0-9]+$ ]]; then | |
| echo "::error::Could not resolve PR number for SHA ${{ github.event.workflow_run.head_sha }} (got: '${PR_NUMBER}')" >&2 | |
| exit 1 | |
| fi | |
| if [[ "$PR_STATE" != "open" ]]; then | |
| echo "::notice::PR #${PR_NUMBER} is already ${PR_STATE}, skipping image push." | |
| echo "skip=true" >> $GITHUB_OUTPUT | |
| fi | |
| echo "number=${PR_NUMBER}" >> $GITHUB_OUTPUT | |
| - name: Download image tar | |
| if: steps.pr.outputs.skip != 'true' | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: image-${{ matrix.arch }} | |
| run-id: ${{ github.event.workflow_run.id }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Login to GHCR | |
| if: steps.pr.outputs.skip != 'true' | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.repository_owner }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Load and push | |
| if: steps.pr.outputs.skip != 'true' | |
| run: | | |
| docker load < image.tar | |
| TAG="ghcr.io/${{ env.REPOSITORY_LC }}:pr-${{ steps.pr.outputs.number }}-${{ matrix.arch }}" | |
| docker tag "openmower-app-pr:latest" "$TAG" | |
| docker push "$TAG" | |
| merge: | |
| needs: push | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Prepare | |
| env: | |
| REPOSITORY: '${{ github.repository }}' | |
| run: echo "REPOSITORY_LC=${REPOSITORY,,}" >> ${GITHUB_ENV} | |
| - name: Get PR number | |
| id: pr | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| PR_INFO=$(gh api "repos/${{ github.repository }}/pulls?state=all" \ | |
| --jq ".[] | select(.head.sha == \"${{ github.event.workflow_run.head_sha }}\") | {number: .number, state: .state}" \ | |
| | head -1) | |
| PR_NUMBER=$(echo "$PR_INFO" | jq -r '.number // empty') | |
| PR_STATE=$(echo "$PR_INFO" | jq -r '.state // empty') | |
| if [[ ! "$PR_NUMBER" =~ ^[0-9]+$ ]]; then | |
| echo "::error::Could not resolve PR number for SHA ${{ github.event.workflow_run.head_sha }} (got: '${PR_NUMBER}')" >&2 | |
| exit 1 | |
| fi | |
| if [[ "$PR_STATE" != "open" ]]; then | |
| echo "::notice::PR #${PR_NUMBER} is already ${PR_STATE}, skipping manifest creation." | |
| echo "skip=true" >> $GITHUB_OUTPUT | |
| fi | |
| echo "number=${PR_NUMBER}" >> $GITHUB_OUTPUT | |
| - name: Set up Docker Buildx | |
| if: steps.pr.outputs.skip != 'true' | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to GHCR | |
| if: steps.pr.outputs.skip != 'true' | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.repository_owner }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Create manifest | |
| if: steps.pr.outputs.skip != 'true' | |
| run: | | |
| REPO="ghcr.io/${{ env.REPOSITORY_LC }}" | |
| PR="${{ steps.pr.outputs.number }}" | |
| docker buildx imagetools create \ | |
| -t "${REPO}:pr-${PR}" \ | |
| "${REPO}:pr-${PR}-amd64" \ | |
| "${REPO}:pr-${PR}-arm64" | |
| comment: | |
| needs: merge | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - name: Prepare | |
| env: | |
| REPOSITORY: '${{ github.repository }}' | |
| run: echo "REPOSITORY_LC=${REPOSITORY,,}" >> ${GITHUB_ENV} | |
| - name: Get PR number | |
| id: pr | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| PR_INFO=$(gh api "repos/${{ github.repository }}/pulls?state=all" \ | |
| --jq ".[] | select(.head.sha == \"${{ github.event.workflow_run.head_sha }}\") | {number: .number, state: .state}" \ | |
| | head -1) | |
| PR_NUMBER=$(echo "$PR_INFO" | jq -r '.number // empty') | |
| PR_STATE=$(echo "$PR_INFO" | jq -r '.state // empty') | |
| if [[ ! "$PR_NUMBER" =~ ^[0-9]+$ ]]; then | |
| echo "::error::Could not resolve PR number for SHA ${{ github.event.workflow_run.head_sha }} (got: '${PR_NUMBER}')" >&2 | |
| exit 1 | |
| fi | |
| if [[ "$PR_STATE" != "open" ]]; then | |
| echo "::notice::PR #${PR_NUMBER} is already ${PR_STATE}, skipping comment." | |
| echo "skip=true" >> $GITHUB_OUTPUT | |
| fi | |
| echo "number=${PR_NUMBER}" >> $GITHUB_OUTPUT | |
| - name: Post / update install instructions | |
| if: steps.pr.outputs.skip != 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PR: ${{ steps.pr.outputs.number }} | |
| IMAGE: ghcr.io/${{ env.REPOSITORY_LC }}:pr-${{ steps.pr.outputs.number }} | |
| run: | | |
| MARKER="<!-- pr-test-image -->" | |
| BODY=$(cat <<EOF | |
| ${MARKER} | |
| ## 🧪 Test this PR | |
| A multi-arch image (amd64 + arm64) for this PR has been pushed to: | |
| \`\`\` | |
| ${IMAGE} | |
| \`\`\` | |
| ### OpenMowerOS v2 | |
| 1. Go to \`http://<mower-ip>:5001/compose/openmower\` in your browser. | |
| 2. Switch to edit mode. | |
| 3. In the \`compose.yaml\` editor, add the following above the \`# Dockge-specific extras shown in the UI\` line (mind the 2-space indentation, like the other containers): | |
| \`\`\`yaml | |
| app: | |
| image: ${IMAGE} | |
| container_name: app | |
| ports: | |
| - 3000:3000 | |
| restart: unless-stopped | |
| \`\`\` | |
| 4. Add the URL under \`x-dockge\`: | |
| \`\`\`yaml | |
| x-dockge: | |
| urls: | |
| - http://\${HOSTNAME}:8080 | |
| - http://\${HOSTNAME}:3000 | |
| \`\`\` | |
| 5. Click \`Deploy\`, then open \`http://<mower-ip>:3000\`. | |
| ### Plain Docker | |
| \`\`\`bash | |
| docker run --rm -p 3000:3000 ${IMAGE} | |
| \`\`\` | |
| > This comment updates automatically on every new push to the PR. | |
| EOF | |
| ) | |
| # Sticky comment: update existing one (by marker) instead of spamming. | |
| COMMENT_ID=$(gh api "repos/${{ github.repository }}/issues/${PR}/comments" \ | |
| --jq ".[] | select(.body | startswith(\"${MARKER}\")) | .id" | head -1) | |
| if [[ -n "$COMMENT_ID" ]]; then | |
| gh api --method PATCH "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" \ | |
| -f body="$BODY" >/dev/null | |
| echo "Updated comment ${COMMENT_ID}" | |
| else | |
| gh api --method POST "repos/${{ github.repository }}/issues/${PR}/comments" \ | |
| -f body="$BODY" >/dev/null | |
| echo "Created new comment" | |
| fi |