-
-
Notifications
You must be signed in to change notification settings - Fork 485
151 lines (141 loc) · 6.17 KB
/
preview.yml
File metadata and controls
151 lines (141 loc) · 6.17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
name: Preview
on:
# pull_request_target gives write access to GHCR even for PRs from forks.
# This is safe because:
# 1. We explicitly checkout the PR's head commit (no base branch code execution)
# 2. We ONLY build a Docker image (isolated container, no workflow scripts from PR)
# 3. The github-script step only uses safe PR metadata (number, SHA) — no PR-supplied
# text (title, body, commit messages) is interpolated, so there is no injection risk
# 4. Build happens in isolated Docker container with well-defined Dockerfile
pull_request_target:
jobs:
docker:
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
pull-requests: write
steps:
- name: Free Disk Space
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1
with:
large-packages: false
docker-images: false
swap-storage: false
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
# For pull_request_target, we need to explicitly fetch the PR ref from forks
# since the PR's commit SHA is not reachable in the base repository.
# This is safe because no PR code is executed in workflow context.
# Only Docker build uses the PR code (isolated in container).
ref: refs/pull/${{ github.event.pull_request.number }}/head
- name: Git describe
id: ghd
uses: proudust/gh-describe@v2
- name: Login to GHCR
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
with:
version: latest
- name: Docker meta
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
with:
images: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}
tags: |
type=ref,event=pr
type=sha,format=long
- name: Build and push PR image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6
with:
context: .
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
RELEASE_VERSION=${{ steps.ghd.outputs.describe }}
- name: Comment on PR
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
DOCKER_META_TAGS: ${{ steps.meta.outputs.tags }}
with:
script: |
const prNumber = context.payload.pull_request.number;
const base = 'preview.vikunja.dev';
const image = `ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}`;
const marker = '<!-- vikunja-preview-comment -->';
// Extract the SHA tag from docker meta output (the actual tag pushed to GHCR)
const metaTags = process.env.DOCKER_META_TAGS.split('\n').map(t => t.trim()).filter(Boolean);
const shaImageRef = metaTags.find(t => t.includes(':sha-'));
const shaTag = shaImageRef ? shaImageRef.split(':').pop() : null;
const shortSha = shaTag ? shaTag.replace('sha-', '').substring(0, 7) : context.payload.pull_request.head.sha.substring(0, 7);
const prTag = `pr-${prNumber}`;
const newShaRow = shaTag
? `| https://${shaTag}.${base} | \`${image}:${shaTag}\` | \`${shortSha}\` |`
: '';
// Collect previous SHA rows from existing comment
let previousShaRows = [];
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
const existing = comments.find(c => c.body.includes(marker));
if (existing) {
previousShaRows = existing.body
.split('\n')
.filter(l => l.includes(`sha-`) && l.includes(`.${base}`));
}
// Remove duplicate if this SHA was already recorded
if (shaTag) {
previousShaRows = previousShaRows.filter(r => !r.includes(shaTag));
}
const allShaRows = [newShaRow, ...previousShaRows].filter(Boolean).join('\n');
const body = [
marker,
`### Preview Deployment`,
``,
`Preview deployments for this PR are available at:`,
``,
`| URL | Tag | Commit |`,
`| --- | --- | --- |`,
`| https://${prTag}.${base} | \`${image}:${prTag}\` | latest |`,
allShaRows,
``,
`The preview environment will start automatically on first visit. Subsequent pushes to this PR will update the \`${prTag}\` image — the preview picks up the new version on restart. The per-commit URLs point to a specific version and will not change.`,
``,
`<details>`,
`<summary>Run locally with Docker</summary>`,
``,
'```bash',
`docker pull ${image}:${prTag}`,
`docker run -p 3456:3456 ${image}:${prTag}`,
'```',
`</details>`,
``,
`_Last updated for commit ${shortSha}_`,
].join('\n');
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body,
});
}