1
- # This action will trigger when a PR is commentted containing /review -pr by a member of the org .
1
+ # This action will trigger when a PR is commented on with `/deploy -pr` or when the workflow is manually triggered .
2
2
name : Deploy PR
3
3
on :
4
4
issue_comment :
8
8
pr_number :
9
9
description : " PR number"
10
10
required : true
11
+ skip_build :
12
+ description : " Skip build job"
13
+ required : false
14
+ default : false
11
15
12
16
env :
13
17
REPO : ghcr.io/coder/coder-preview
@@ -25,29 +29,30 @@ jobs:
25
29
pr_commented :
26
30
if : (github.event_name == 'issue_comment' && contains(github.event.comment.body, '/deploy-pr') && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'COLLABORATOR' || github.event.comment.author_association == 'OWNER')) || github.event_name == 'workflow_dispatch'
27
31
outputs :
28
- PR_NUMBER : ${{ steps.pr_number .outputs.PR_NUMBER }}
29
- PR_TITLE : ${{ steps.pr_number .outputs.PR_TITLE }}
30
- PR_URL : ${{ steps.pr_number .outputs.PR_URL }}
31
- COMMENT_ID : ${{ steps.comment_id .outputs.comment-id }}
32
+ PR_NUMBER : ${{ steps.pr_info .outputs.PR_NUMBER }}
33
+ PR_TITLE : ${{ steps.pr_info .outputs.PR_TITLE }}
34
+ PR_URL : ${{ steps.pr_info .outputs.PR_URL }}
35
+ PR_BRANCH : ${{ steps.pr_info .outputs.PR_BRANCH }}
32
36
CODER_BASE_IMAGE_TAG : ${{ steps.set_tags.outputs.CODER_BASE_IMAGE_TAG }}
33
37
CODER_IMAGE_TAG : ${{ steps.set_tags.outputs.CODER_IMAGE_TAG }}
34
38
35
39
runs-on : " ubuntu-latest"
36
40
steps :
37
- - name : Get PR number and title
38
- id : pr_number
41
+ - name : Get PR number, title, and branch name
42
+ id : pr_info
39
43
run : |
40
44
set -euxo pipefail
41
45
if [[ ${{ github.event_name }} == "workflow_dispatch" ]]; then
42
46
PR_NUMBER=${{ github.event.inputs.pr_number }}
43
- PR_TITLE=$(curl -sSL -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/coder/coder/pulls/$PR_NUMBER" | jq -r '.title')
44
47
else
45
48
PR_NUMBER=${{ github.event.issue.number }}
46
- PR_TITLE='${{ github.event.issue.title }}'
47
49
fi
50
+ PR_TITLE=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/coder/coder/pulls/$PR_NUMBER | jq -r '.title')
51
+ PR_BRANCH=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/coder/coder/pulls/$PR_NUMBER | jq -r '.head.ref')
48
52
echo "PR_URL=https://github.com/coder/coder/pull/$PR_NUMBER" >> $GITHUB_OUTPUT
49
53
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_OUTPUT
50
54
echo "PR_TITLE=$PR_TITLE" >> $GITHUB_OUTPUT
55
+ echo "PR_BRANCH=$PR_BRANCH" >> $GITHUB_OUTPUT
51
56
52
57
- name : Set required tags
53
58
id : set_tags
@@ -56,39 +61,36 @@ jobs:
56
61
echo "CODER_BASE_IMAGE_TAG=$CODER_BASE_IMAGE_TAG" >> $GITHUB_OUTPUT
57
62
echo "CODER_IMAGE_TAG=$CODER_IMAGE_TAG" >> $GITHUB_OUTPUT
58
63
env :
59
- CODER_BASE_IMAGE_TAG : ghcr.io/coder/coder-preview-base:pr${{ steps.pr_number.outputs.PR_NUMBER }}
60
- CODER_IMAGE_TAG : ghcr.io/coder/coder-preview:pr${{ steps.pr_number.outputs.PR_NUMBER }}
61
-
62
- - name : Find Comment
63
- uses : peter-evans/find-comment@v2
64
- id : fc
65
- with :
66
- issue-number : ${{ steps.pr_number.outputs.PR_NUMBER }}
67
- comment-author : " github-actions[bot]"
68
- body-includes : This deployment will be deleted when the PR is closed
64
+ CODER_BASE_IMAGE_TAG : ghcr.io/coder/coder-preview-base:pr${{ steps.pr_info.outputs.PR_NUMBER }}
65
+ CODER_IMAGE_TAG : ghcr.io/coder/coder-preview:pr${{ steps.pr_info.outputs.PR_NUMBER }}
69
66
70
67
- name : Comment on PR
71
68
id : comment_id
69
+ if : github.event_name == 'issue_comment'
72
70
uses : peter-evans/create-or-update-comment@v3
73
71
with :
74
- comment-id : ${{ steps.fc.outputs.comment-id }}
75
- issue-number : ${{ steps.pr_number.outputs.PR_NUMBER }}
76
- edit-mode : replace
72
+ issue-number : ${{ steps.pr_info.outputs.PR_NUMBER }}
77
73
body : |
78
- :rocket: Deploying PR ${{ steps.pr_number .outputs.PR_NUMBER }} ...
74
+ :rocket: Deploying PR ${{ steps.pr_info .outputs.PR_NUMBER }} ...
79
75
:warning: This deployment will be deleted when the PR is closed.
76
+ reactions : " +1"
80
77
81
78
build :
82
79
needs : pr_commented
80
+ # Skips the build job if the workflow was triggered by a workflow_dispatch event and the skip_build input is set to true
81
+ # or if the workflow was triggered by an issue_comment event and the comment body contains --skip-build
82
+ if : (github.event_name == 'workflow_dispatch' && github.event.inputs.skip_build == 'false') || (github.event_name == 'issue_comment' && contains(github.event.comment.body, '--skip-build') != true)
83
83
runs-on : ${{ github.repository_owner == 'coder' && 'buildjet-8vcpu-ubuntu-2204' || 'ubuntu-latest' }}
84
84
env :
85
85
DOCKER_CLI_EXPERIMENTAL : " enabled"
86
- CODER_IMAGE_TAG : ${{ needs.pr_commented.outputs.coder_image_tag }}
87
- PR_NUMBER : ${{ needs.pr_commented.outputs.pr_number }}
86
+ CODER_IMAGE_TAG : ${{ needs.pr_commented.outputs.CODER_IMAGE_TAG }}
87
+ PR_NUMBER : ${{ needs.pr_commented.outputs.PR_NUMBER }}
88
+ PR_BRANCH : ${{ needs.pr_commented.outputs.PR_BRANCH }}
88
89
steps :
89
90
- name : Checkout
90
91
uses : actions/checkout@v3
91
92
with :
93
+ ref : ${{ env.PR_BRANCH }}
92
94
fetch-depth : 0
93
95
94
96
- name : Setup Node
@@ -125,22 +127,38 @@ jobs:
125
127
126
128
deploy :
127
129
needs : [build, pr_commented]
128
- if : needs.build.result == 'success'
130
+ # Run deploy job only if build job was successful or skipped
131
+ if : always() && (needs.build.result == 'success' || needs.build.result == 'skipped') && needs.pr_commented.result == 'success'
129
132
runs-on : " ubuntu-latest"
130
133
env :
131
134
CODER_IMAGE_TAG : ${{ needs.pr_commented.outputs.CODER_IMAGE_TAG }}
132
135
PR_NUMBER : ${{ needs.pr_commented.outputs.PR_NUMBER }}
133
136
PR_TITLE : ${{ needs.pr_commented.outputs.PR_TITLE }}
134
137
PR_URL : ${{ needs.pr_commented.outputs.PR_URL }}
138
+ PR_BRANCH : ${{ needs.pr_commented.outputs.PR_BRANCH }}
139
+ PR_DEPLOYMENT_ACCESS_URL : " https://pr${{ needs.pr_commented.outputs.PR_NUMBER }}.${{ secrets.PR_DEPLOYMENTS_DOMAIN }}"
135
140
steps :
141
+ - name : Check if image exists
142
+ run : |
143
+ set -euxo pipefail
144
+ foundTag=$(curl -fsSL https://github.com/coder/coder/pkgs/container/coder-preview | grep -o ${{ env.CODER_IMAGE_TAG }} | head -n 1)
145
+ if [ -z "$foundTag" ]; then
146
+ echo "Image not found"
147
+ echo "${{ env.CODER_IMAGE_TAG }} not found in ghcr.io/coder/coder-preview"
148
+ echo "Please remove --skip-build from the comment or ./scripts/deploy-pr.sh"
149
+ exit 1
150
+ fi
151
+
136
152
- name : Checkout
137
153
uses : actions/checkout@v3
154
+ with :
155
+ ref : ${{ env.PR_BRANCH }}
138
156
139
157
- name : Set up kubeconfig
140
158
run : |
141
159
set -euxo pipefail
142
160
mkdir -p ~/.kube
143
- echo "${{ secrets.DELIVERYBOT_KUBECONFIG }}" > ~/.kube/config
161
+ echo "${{ secrets.PR_DEPLOYMENTS_KUBECONFIG }}" > ~/.kube/config
144
162
export KUBECONFIG=~/.kube/config
145
163
146
164
- name : Create PR namespace
@@ -150,53 +168,172 @@ jobs:
150
168
kubectl delete namespace "pr${{ env.PR_NUMBER }}" || true
151
169
kubectl create namespace "pr${{ env.PR_NUMBER }}"
152
170
171
+ - name : Setup ingress
172
+ run : |
173
+ cat <<EOF > ingress.yaml
174
+ apiVersion: networking.k8s.io/v1
175
+ kind: Ingress
176
+ metadata:
177
+ name: pr${{ env.PR_NUMBER }}
178
+ namespace: pr${{ env.PR_NUMBER }}
179
+ annotations:
180
+ cert-manager.io/cluster-issuer: letsencrypt
181
+ spec:
182
+ tls:
183
+ - hosts:
184
+ - "${{ secrets.PR_DEPLOYMENTS_DOMAIN }}"
185
+ - "*.${{ secrets.PR_DEPLOYMENTS_DOMAIN }}"
186
+ secretName: pr${{ env.PR_NUMBER }}-tls
187
+ rules:
188
+ - host: "pr${{ env.PR_NUMBER }}.${{ secrets.PR_DEPLOYMENTS_DOMAIN }}"
189
+ http:
190
+ paths:
191
+ - pathType: Prefix
192
+ path: "/"
193
+ backend:
194
+ service:
195
+ name: coder
196
+ port:
197
+ number: 80
198
+ EOF
199
+ kubectl apply -f ingress.yaml
200
+
201
+ - name : Create values.yaml
202
+ run : |
203
+ cat <<EOF > pr-deploy-values.yaml
204
+ coder:
205
+ image:
206
+ repo: ${{ env.REPO }}
207
+ tag: pr${{ env.PR_NUMBER }}
208
+ pullPolicy: Always
209
+ service:
210
+ type: ClusterIP
211
+ env:
212
+ - name: "CODER_ACCESS_URL"
213
+ value: "https://pr${{ env.PR_NUMBER }}.${{ secrets.PR_DEPLOYMENTS_DOMAIN }}"
214
+ - name: "CODER_WILDCARD_ACCESS_URL"
215
+ value: "*--pr${{ env.PR_NUMBER }}.${{ secrets.PR_DEPLOYMENTS_DOMAIN }}"
216
+ - name: "CODER_EXPERIMENTS"
217
+ value: "*"
218
+ - name: "CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS"
219
+ value: "true"
220
+ - name: "CODER_OAUTH2_GITHUB_CLIENT_ID"
221
+ value: "${{ secrets.PR_DEPLOYMENTS_GITHUB_OAUTH_CLIENT_ID }}"
222
+ - name: "CODER_OAUTH2_GITHUB_CLIENT_SECRET"
223
+ value: "${{ secrets.PR_DEPLOYMENTS_GITHUB_OAUTH_CLIENT_SECRET }}"
224
+ - name: "CODER_OAUTH2_GITHUB_ALLOWED_ORGS"
225
+ value: "coder"
226
+ EOF
227
+
153
228
- name : Install Helm chart
154
229
run : |
155
- helm upgrade --install pr${{ env.PR_NUMBER }} ./helm \
230
+ helm upgrade --install " pr${{ env.PR_NUMBER }}" ./helm \
156
231
--namespace "pr${{ env.PR_NUMBER }}" \
157
- --set coder.image.repo=${{ env.REPO }} \
158
- --set coder.image.tag=pr${{ env.PR_NUMBER }} \
159
- --set coder.service.type=ClusterIP \
160
- --set coder.serviceAccount.enableDeployments=true \
161
- --set coder.env[0].name=CODER_ACCESS_URL \
162
- --set coder.env[0].value="" \
232
+ --values ./pr-deploy-values.yaml \
163
233
--force
164
234
165
- - name : Get deployment URL
166
- id : deployment_url
167
- run : |
168
- set -euo pipefail
169
- kubectl rollout status deployment/coder --namespace "pr${{ env.PR_NUMBER }}"
170
- POD_NAME=$(kubectl get pods -n "pr${{ env.PR_NUMBER }}" | awk 'NR==2{print $1}')
171
- CODER_ACCESS_URL=$(kubectl logs $POD_NAME -n "pr${{ env.PR_NUMBER }}" | grep "Web UI:" | awk -F ':' '{print $2":"$3}' | awk '{$1=$1};1')
172
- echo "::add-mask::$CODER_ACCESS_URL"
173
- echo "CODER_ACCESS_URL=$CODER_ACCESS_URL" >> $GITHUB_OUTPUT
174
-
175
235
- name : Install coder-logstream-kube
176
236
run : |
177
237
helm repo add coder-logstream-kube https://helm.coder.com/logstream-kube
178
- helm install coder-logstream-kube coder-logstream-kube/coder-logstream-kube \
238
+ helm upgrade -- install coder-logstream-kube coder-logstream-kube/coder-logstream-kube \
179
239
--namespace "pr${{ env.PR_NUMBER }}" \
180
- --set url="${{ steps.deployment_url.outputs.CODER_ACCESS_URL }}"
240
+ --set url="https://pr${{ env.PR_NUMBER }}.${{ secrets.PR_DEPLOYMENTS_DOMAIN }}"
241
+
242
+ - name : Get Coder binary
243
+ run : |
244
+ set -euxo pipefail
245
+
246
+ DEST="${HOME}/coder"
247
+ URL="${{ env.PR_DEPLOYMENT_ACCESS_URL }}/bin/coder-linux-amd64"
248
+
249
+ mkdir -p "$(dirname ${DEST})"
250
+
251
+ COUNT=0
252
+ until $(curl --output /dev/null --silent --head --fail "$URL"); do
253
+ printf '.'
254
+ sleep 5
255
+ COUNT=$((COUNT+1))
256
+ if [ $COUNT -ge 60 ]; then
257
+ echo "Timed out waiting for URL to be available"
258
+ exit 1
259
+ fi
260
+ done
261
+
262
+ curl -fsSL "$URL" -o "${DEST}"
263
+ chmod +x "${DEST}"
264
+ "${DEST}" version
265
+
266
+ - name : Create first user, template and workspace
267
+ id : setup_deployment
268
+ run : |
269
+ set -euxo pipefail
270
+
271
+ # Create first user
272
+
273
+ # create a masked random password 12 characters long
274
+ password=$(openssl rand -base64 16 | tr -d "=+/" | cut -c1-12)
275
+
276
+ # add mask so that the password is not printed to the logs
277
+ echo "::add-mask::$password"
278
+ echo "password=$password" >> $GITHUB_OUTPUT
279
+
280
+ /home/runner/coder login \
281
+ --first-user-username pr${{ env.PR_NUMBER }} \
282
+ --first-user-email ${{ env.PR_NUMBER }}@coder.com \
283
+ --first-user-password $password \
284
+ --first-user-trial \
285
+ --use-token-as-session \
286
+ ${{ env.PR_DEPLOYMENT_ACCESS_URL }}
287
+
288
+ # Create template
289
+ /home/runner/coder templates init --id kubernetes && cd ./kubernetes/ && /home/runner/coder templates create -y --variable namespace=pr${{ env.PR_NUMBER }}
290
+
291
+ # Create workspace
292
+ cat <<EOF > workspace.yaml
293
+ cpu: "2"
294
+ memory: "4"
295
+ home_disk_size: "2"
296
+ EOF
297
+
298
+ /home/runner/coder create --template="kubernetes" pr${{ env.PR_NUMBER }} --rich-parameter-file ./workspace.yaml -y
299
+ /home/runner/coder stop pr${{ env.PR_NUMBER }} -y
181
300
182
301
- name : Send Slack notification
183
302
run : |
184
303
curl -s -o /dev/null -X POST -H 'Content-type: application/json' \
185
- -d '{
186
- "pr_number": "'"${{ env.PR_NUMBER }}"'",
187
- "pr_url": "'"${{ env.PR_URL }}"'",
188
- "pr_title": "'"${{ env.PR_TITLE }}"'",
189
- "pr_access_url": "'"${{ steps.deployment_url.outputs.CODER_ACCESS_URL }}"'" }' ${{ secrets.PR_DEPLOYMENTS_SLACK_WEBHOOK }}
304
+ -d \
305
+ '{
306
+ "pr_number": "'"${{ env.PR_NUMBER }}"'",
307
+ "pr_url": "'"${{ env.PR_URL }}"'",
308
+ "pr_title": "'"${{ env.PR_TITLE }}"'",
309
+ "pr_access_url": "'"${{ env.PR_DEPLOYMENT_ACCESS_URL }}"'",
310
+ "pr_username": "'"pr${{ env.PR_NUMBER }}"'",
311
+ "pr_email": "'"${{ env.PR_NUMBER }}@coder.com"'",
312
+ "pr_password": "'"${{ steps.setup_deployment.outputs.password }}"'",
313
+ "pr_actor": "'"${{ github.actor }}"'"
314
+ }' \
315
+ ${{ secrets.PR_DEPLOYMENTS_SLACK_WEBHOOK }}
190
316
echo "Slack notification sent"
191
317
318
+ - name : Find Comment
319
+ uses : peter-evans/find-comment@v2
320
+ if : github.event_name == 'issue_comment'
321
+ id : fc
322
+ with :
323
+ issue-number : ${{ env.PR_NUMBER }}
324
+ comment-author : " github-actions[bot]"
325
+ body-includes : This deployment will be deleted when the PR is closed
326
+ direction : last
327
+
192
328
- name : Comment on PR
193
329
uses : peter-evans/create-or-update-comment@v3
330
+ if : github.event_name == 'issue_comment'
194
331
with :
195
332
issue-number : ${{ env.PR_NUMBER }}
196
333
edit-mode : replace
197
- comment-id : ${{ needs.pr_commented .outputs.COMMENT_ID }}
334
+ comment-id : ${{ steps.fc .outputs.comment-id }}
198
335
body : |
199
336
:heavy_check_mark: Deployed PR ${{ env.PR_NUMBER }} successfully.
200
- :rocket: Access the deployment link [here](https://codercom.slack.com/archives/C05DNE982E8 ).
337
+ :rocket: Access the deployment link [here](${{ env.PR_DEPLOYMENT_ACCESS_URL }} ).
201
338
:warning: This deployment will be deleted when the PR is closed.
202
- reactions : " +1 "
339
+ reactions : rocket
0 commit comments