-
Notifications
You must be signed in to change notification settings - Fork 14
489 lines (450 loc) · 20.5 KB
/
reusable.yaml
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
name: Reusable Uffizzi Preview Workflow
env:
LOGGER_KEY: "aHR0cHM6Ly80NGI3Zjk5MTFiODU0NWI5YTMzMDY4NzRhY2ZjYjJjOUBvMzI0MzExLmluZ2VzdC5zZW50cnkuaW8vNDUwNDM2MTIxMjI0ODA2NA=="
on:
workflow_call:
inputs:
compose-file-cache-key:
description: "GHA Cache Key for Docker Compose file ready to deploy"
required: true
type: string
compose-file-cache-path:
description: "GHA Cache Path for Docker Compose file ready to deploy"
required: true
type: string
username:
description: "Uffizzi username for login, usually an email address"
required: false
type: string
server:
description: "Uffizzi server URL"
default: https://app.uffizzi.com
required: false
type: string
project:
description: "Uffizzi project name"
default: default
required: false
type: string
pr-number:
description: "GitHub Pull Request Number"
default: ''
required: false
type: string
git-ref:
description: "Branch or other git reference to checkout"
default: ''
required: false
type: string
healthcheck-url-path:
description: "URL Path to the Uffizzi Preview URL where healthcheck would be performed. The URL Path has to start with a '/'."
default: ''
required: false
type: string
description:
description: "Text string added to comment on pull request issue"
default: 'What is Uffizzi? [Learn more](https://www.uffizzi.com)!'
required: false
type: string
environment:
description: 'Custom environment for the deployed preview'
default: 'uffizzi'
required: false
type: string
secrets:
access-token:
description: 'Github pipeline token'
required: false
password:
description: "Uffizzi password for login"
required: false
url-username:
description: "Username for authenticating to each Environment"
required: false
url-password:
description: "Password for authentication to each Environment"
required: false
personal-access-token:
description: "GitHub personal access token with access to container registry"
required: false
dockerhub-username:
description: 'DockerHub username'
required: false
dockerhub-password:
description: 'DockerHub password'
required: false
acr-username:
description: 'Azure username'
required: false
acr-password:
description: 'Azure password'
required: false
acr-registry-url:
description: 'Azure registry url'
required: false
aws-access-key-id:
description: 'Amazon Web Services access key id'
required: false
aws-secret-access-key:
description: 'Amazon Web Services secret access key'
required: false
aws-registry-url:
description: 'Amazon Web Services registry url'
required: false
gcloud-service-key:
description: 'Google Cloud service key'
required: false
docker-registry-username:
description: 'Custom docker registry username'
required: false
docker-registry-password:
description: 'Custom docker registry password'
required: false
docker-registry-url:
description: 'Custom docker registry url'
required: false
outputs:
url:
description: "URL to Uffizzi Preview Environment"
value: ${{ jobs.uffizzi-preview.outputs.url }}
id:
description: "Uffizzi Preview Deployment ID"
value: ${{ jobs.uffizzi-preview.outputs.id }}
containers_uri:
description: 'URL to Uffizzi Deployment Details'
value: ${{ jobs.uffizzi-preview.outputs.containers_uri }}
expiration_interval:
description: "Uffizzi Preview Expiration Interval in Seconds"
value: ${{ jobs.uffizzi-preview.outputs.expiration_interval }}
expiration:
description: "Uffizzi Preview Expiration Time String"
value: ${{ jobs.uffizzi-preview.outputs.expiration }}
expiration_timestamp:
description: "Uffizzi Preview Expiration UNIX Timestamp"
value: ${{ jobs.uffizzi-preview.outputs.expiration_timestamp }}
permissions:
contents: read
pull-requests: write
id-token: write
jobs:
uffizzi-preview:
name: "Create, Update, or Delete Preview on Uffizzi"
runs-on: ubuntu-22.04
environment:
name: ${{ inputs.environment }}
url: "${{ steps.outputs.outputs.url }}"
outputs:
url: ${{ steps.outputs.outputs.url }}
id: ${{ steps.outputs.outputs.id }}
containers_uri: ${{ steps.outputs.outputs.containers_uri }}
expiration_interval: ${{ steps.outputs.outputs.expiration_interval }}
expiration: ${{ steps.outputs.outputs.expiration }}
expiration_timestamp: ${{ steps.outputs.outputs.expiration_timestamp }}
steps:
- name: DEBUG - Dump GitHub context and environment info
if: ${{ runner.debug }}
uses: crazy-max/ghaction-dump-context@v1
- name: Determine Pull Request Number
id: pr
run: |
export INPUT_PR=${{ inputs.pr-number }}
export CONTEXT_PR=${{ github.event.number }}
export OUTPUT_PR=${INPUT_PR:-$CONTEXT_PR}
echo "PR_NUMBER=$OUTPUT_PR" >> $GITHUB_ENV
- name: Predict Deployment URL
id: url
# Replace dots in the repo name with the plus sign
run: |
REPO=$(echo ${{ github.repository }} | sed 's/\./+/g')
echo "EXPECTED_URL=${{ inputs.server }}/github.com/$REPO/pull/$PR_NUMBER" >> $GITHUB_ENV
- name: Obtain an a job token and an OIDC token request url from GHA
uses: actions/github-script@v6
id: ci-job-token
with:
debug: true
script: |
const token = process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN']
const runtimeUrl = process.env['ACTIONS_ID_TOKEN_REQUEST_URL']
core.setOutput('request-token', token.trim())
core.setOutput('request-token-url', runtimeUrl.trim())
# Look for an existing Preview Deployment.
- name: Find Deployment for this Pull Request
id: find-deployment
uses: docker://uffizzi/cli:v2
with:
entrypoint: bash
args: -c "/root/docker-entrypoint.sh && echo DEPLOYMENT_ID=$(/usr/local/bundle/bin/uffizzi preview list --filter \"github.repository=${{ github.repository }} github.event.number=$PR_NUMBER\") >> $GITHUB_ENV"
env:
UFFIZZI_PASSWORD: ${{ secrets.password }}
UFFIZZI_SERVER: ${{ inputs.server }}
UFFIZZI_USER: ${{ inputs.username }}
UFFIZZI_PROJECT: ${{ inputs.project }}
REQUEST_TOKEN: ${{ steps.ci-job-token.outputs.request-token }}
REQUEST_TOKEN_URL: ${{ steps.ci-job-token.outputs.request-token-url }}
ACCESS_TOKEN: ${{ secrets.access-token }}
LOGGER_KEY: ${{ env.LOGGER_KEY }}
CI_WORKFLOW: "true"
- name: Find comment for deployment URL
uses: peter-evans/find-comment@v2
id: find-comment
with:
issue-number: ${{ env.PR_NUMBER }}
comment-author: 'github-actions[bot]'
body-includes: '${{ env.EXPECTED_URL }}'
direction: last
- name: Determine Operation
uses: actions/github-script@v6
with:
compose-file-cache-key: ${{ inputs.compose-file-cache-key }}
script: |
let action = 'none'; // default
const { PR_NUMBER, DEPLOYMENT_ID } = process.env;
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: PR_NUMBER
});
if (core.isDebug()) {
console.log(pr);
}
// Retain legacy behavior if we're passed this input empty.
if (core.getInput('compose-file-cache-key') == '') {
if (DEPLOYMENT_ID == '') {
core.warning(`No compose file passed and no deployment found. No operation.`);
action = 'none';
} else {
console.log(`No compose file passed and ${DEPLOYMENT_ID} found. Deleting existing deployment.`);
action = 'delete';
}
core.exportVariable('UFFIZZI_ACTION', action);
// core.setOutput('action', action);
return;
}
// `state` is either `open` or `closed`.
if (pr.data.state == 'open') {
if (DEPLOYMENT_ID == '') {
console.log(`PR ${PR_NUMBER} is open but no deployment found. Creating new deployment.`);
action = 'create';
} else {
console.log(`PR ${PR_NUMBER} is open and ${DEPLOYMENT_ID} found. Updating existing deployment.`);
action = 'update';
}
} else if (pr.data.state == 'closed') {
if (DEPLOYMENT_ID == '') {
core.warning(`PR ${PR_NUMBER} is closed but no deployment found. No operation.`);
action = 'none';
} else {
console.log(`PR ${PR_NUMBER} is closed and ${DEPLOYMENT_ID} found. Deleting existing deployment.`);
action = 'delete';
}
} else {
core.setFailed(`Fatal exception: PR ${PR_NUMBER} is neither \`open\` nor \`closed\` but \`${pr.data.state}\`.`);
}
core.exportVariable('UFFIZZI_ACTION', action);
// core.setOutput('action', action);
return;
- name: Checkout commit
if: ${{ contains(fromJSON('["create", "update"]'), env.UFFIZZI_ACTION) }}
uses: actions/checkout@v3
with:
ref: ${{ inputs.git-ref || github.event.pull_request.head.sha }}
- name: Fetch cached Compose File
id: cache
if: ${{ contains(fromJSON('["create", "update"]'), env.UFFIZZI_ACTION) }}
uses: actions/cache@v3
with:
path: ${{ inputs.compose-file-cache-path }}
key: ${{ inputs.compose-file-cache-key }}
- name: Create or Update Comment with Deployment Notification
id: notification
uses: peter-evans/create-or-update-comment@v2
if: ${{ contains(fromJSON('["create", "update"]'), env.UFFIZZI_ACTION) }}
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ env.PR_NUMBER }}
body: |
## Uffizzi Ephemeral Environment Deploying
:cloud: ${{ env.EXPECTED_URL }}
:gear: Updating now by workflow run [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).
${{ inputs.description }}
edit-mode: replace
- name: Deploy New Preview
id: create-preview
if: ${{ env.UFFIZZI_ACTION == 'create' }}
uses: UffizziCloud/preview-action@v3
with:
compose-file: ${{ inputs.compose-file-cache-path }}
username: ${{ inputs.username }}
server: ${{ inputs.server }}
project: ${{ inputs.project }}
password: ${{ secrets.password }}
access-token: ${{ secrets.access-token }}
ghcr-username: ${{ github.actor }}
ghcr-access-token: ${{ secrets.personal-access-token }}
github-event-number: ${{ env.PR_NUMBER }}
github-ref: ${{ github.ref }}
github-repository: ${{ github.repository }}
request-token: ${{ steps.ci-job-token.outputs.request-token }}
request-token-url: ${{ steps.ci-job-token.outputs.request-token-url }}
dockerhub-username: ${{ secrets.dockerhub-username }}
dockerhub-password: ${{ secrets.dockerhub-password }}
acr-username: ${{ secrets.acr-username }}
acr-password: ${{ secrets.acr-password }}
acr-registry-url: ${{ secrets.acr-registry-url }}
aws-access-key-id: ${{ secrets.aws-access-key-id }}
aws-secret-access-key: ${{ secrets.aws-secret-access-key }}
aws-registry-url: ${{ secrets.aws-registry-url }}
gcloud-service-key: ${{ secrets.gcloud-service-key }}
docker-registry-username: ${{ secrets.docker-registry-username }}
docker-registry-password: ${{ secrets.docker-registry-password }}
docker-registry-url: ${{ secrets.docker-registry-url }}
logger-key: ${{ env.LOGGER_KEY }}
- name: Update Existing Preview
uses: UffizziCloud/update-preview-action@v2
id: update-preview
if: ${{ env.UFFIZZI_ACTION == 'update' }}
with:
preview-id: ${{ env.DEPLOYMENT_ID }}
compose-file: ${{ inputs.compose-file-cache-path }}
username: ${{ inputs.username }}
server: ${{ inputs.server }}
project: ${{ inputs.project }}
password: ${{ secrets.password }}
ghcr-username: ${{ github.actor }}
ghcr-access-token: ${{ secrets.personal-access-token }}
github-event-number: ${{ env.PR_NUMBER }}
github-ref: ${{ github.ref }}
github-repository: ${{ github.repository }}
request-token: ${{ steps.ci-job-token.outputs.request-token }}
request-token-url: ${{ steps.ci-job-token.outputs.request-token-url }}
dockerhub-username: ${{ secrets.dockerhub-username }}
dockerhub-password: ${{ secrets.dockerhub-password }}
acr-username: ${{ secrets.acr-username }}
acr-password: ${{ secrets.acr-password }}
acr-registry-url: ${{ secrets.acr-registry-url }}
aws-access-key-id: ${{ secrets.aws-access-key-id }}
aws-secret-access-key: ${{ secrets.aws-secret-access-key }}
aws-registry-url: ${{ secrets.aws-registry-url }}
gcloud-service-key: ${{ secrets.gcloud-service-key }}
docker-registry-username: ${{ secrets.docker-registry-username }}
docker-registry-password: ${{ secrets.docker-registry-password }}
docker-registry-url: ${{ secrets.docker-registry-url }}
logger-key: ${{ env.LOGGER_KEY }}
- name: Job output consolidation
id: outputs
if: ${{ contains(fromJSON('["create", "update"]'), env.UFFIZZI_ACTION) }}
run: |
export CREATE_URL=${{ steps.create-preview.outputs.url }}
export CREATE_ID=${{ steps.create-preview.outputs.id }}
export CREATE_CONTAINERS=${{ steps.create-preview.outputs.containers_uri }}
export UPDATE_URL=${{ steps.update-preview.outputs.url }}
export UPDATE_ID=${{ steps.update-preview.outputs.id }}
export UPDATE_CONTAINERS=${{ steps.update-preview.outputs.containers_uri }}
export OUTPUT_URL=${CREATE_URL:-$UPDATE_URL}
export OUTPUT_ID=${CREATE_ID:-$UPDATE_ID}
export OUTPUT_CONTAINERS=${CREATE_CONTAINERS:-$UPDATE_CONTAINERS}
echo "Deployment ID $OUTPUT_ID at $OUTPUT_URL"
echo "OUTPUT_ID=$OUTPUT_ID" >> $GITHUB_ENV
echo "DEPLOYMENT_ID=$OUTPUT_ID" >> $GITHUB_ENV
echo "OUTPUT_URL=$OUTPUT_URL" >> $GITHUB_ENV
echo "OUTPUT_CONTAINERS=$OUTPUT_CONTAINERS" >> $GITHUB_ENV
export EXPIRATION_HOURS=$(grep --perl-regexp --only-matching '^[ \t]*(?!#)delete_preview_after: \K\d+' ${{ inputs.compose-file-cache-path }})
if [ -z "$EXPIRATION_HOURS" ]
then
echo "No preview expiration parameter found."
else
export EXPIRATION_INTERVAL=$(( $EXPIRATION_HOURS * 3600 ))
export EXPIRATION_TIMESTAMP=$(date +'%s')
export EXPIRATION=$(date --utc --date=@$(($EXPIRATION_TIMESTAMP + $EXPIRATION_INTERVAL)))
echo "Expiring in $EXPIRATION_INTERVAL seconds at $EXPIRATION."
echo "EXPIRATION_INTERVAL=$EXPIRATION_INTERVAL" >> $GITHUB_ENV
echo "EXPIRATION=:alarm_clock: This Preview will be destroyed in $EXPIRATION_HOURS hours at: $EXPIRATION" >> $GITHUB_ENV
fi
# Expose the step output variables
echo "url=$OUTPUT_URL" >> $GITHUB_OUTPUT
echo "id=$OUTPUT_ID" >> $GITHUB_OUTPUT
echo "containers_uri=$OUTPUT_CONTAINERS" >> $GITHUB_OUTPUT
echo "expiration_interval=$EXPIRATION_INTERVAL" >> $GITHUB_OUTPUT
echo "expiration=$EXPIRATION" >> $GITHUB_OUTPUT
echo "expiration_timestamp=$EXPIRATION_TIMESTAMP" >> $GITHUB_OUTPUT
- name: Check if URL Authentication is in use
# GHA limitation workaround https://github.com/actions/runner/issues/520#issuecomment-700579336
id: url-auth-for-create
shell: bash
run: if [ -z "${{ secrets.url-password }}" ]; then echo "No URL authentication."; else echo "URL_AUTHENTICATION=true" >> $GITHUB_ENV ; fi
- name: Confirm Successful Preview Deployment
if: ${{ contains(fromJSON('["create", "update"]'), env.UFFIZZI_ACTION) && env.URL_AUTHENTICATION == '' }}
uses: docker://curlimages/curl:7.87.0
with:
args: --retry 12 --retry-all-errors --retry-delay 0 --fail --location ${{ env.OUTPUT_URL }}${{ inputs.healthcheck-url-path }}
- name: Confirm Successful Preview Deployment with Authentication
if: ${{ contains(fromJSON('["create", "update"]'), env.UFFIZZI_ACTION) && env.URL_AUTHENTICATION == 'true' }}
uses: docker://curlimages/curl:7.87.0
with:
args: --basic --user ${{ secrets.url-username }}:${{ secrets.url-password }} --retry 12 --retry-all-errors --retry-delay 0 --fail --location ${{ env.OUTPUT_URL }}${{ inputs.healthcheck-url-path }}
- name: Create or Update Comment with Deployment URL
uses: peter-evans/create-or-update-comment@v2
if: ${{ contains(fromJSON('["create", "update"]'), env.UFFIZZI_ACTION) }}
with:
comment-id: ${{ steps.notification.outputs.comment-id }}
issue-number: ${{ env.PR_NUMBER }}
body: |
## Uffizzi Ephemeral Environment `${{ env.OUTPUT_ID }}`
:cloud: ${{ env.EXPECTED_URL }}
:page_facing_up: [View Application Logs etc.](${{ env.OUTPUT_CONTAINERS }})
${{ env.EXPIRATION }}
${{ inputs.description }}
edit-mode: replace
- name: Check for PR Closed During Deployment
uses: actions/github-script@v6
if: ${{ contains(fromJSON('["create", "update"]'), env.UFFIZZI_ACTION) }}
with:
script: |
let action = process.env.UFFIZZI_ACTION;
const { PR_NUMBER, DEPLOYMENT_ID } = process.env;
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: PR_NUMBER
});
if (core.isDebug()) {
console.log(pr);
}
// `state` is either `open` or `closed`.
if (pr.data.state == 'open') {
console.log(`PR ${PR_NUMBER} remains open.`);
} else if (pr.data.state == 'closed') {
// If the PR was closed while the deployment was happening, we want to delete what we just deployed.
if (DEPLOYMENT_ID == '') {
core.warning(`PR ${PR_NUMBER} is closed but no deployment found. No operation.`);
action = 'none';
} else {
console.log(`PR ${PR_NUMBER} is closed and ${DEPLOYMENT_ID} found. Deleting existing deployment.`);
action = 'delete';
}
} else {
core.setFailed(`Fatal exception: PR ${PR_NUMBER} is neither \`open\` nor \`closed\` but \`${pr.data.state}\`.`);
}
core.exportVariable('UFFIZZI_ACTION', action);
return;
- name: Delete Preview from Uffizzi
if: ${{ env.UFFIZZI_ACTION == 'delete' }}
uses: UffizziCloud/delete-preview-action@v2
with:
id: ${{ env.DEPLOYMENT_ID }}
username: ${{ inputs.username }}
server: ${{ inputs.server }}
project: ${{ inputs.project }}
password: ${{ secrets.password }}
request-token: ${{ steps.ci-job-token.outputs.request-token }}
request-token-url: ${{ steps.ci-job-token.outputs.request-token-url }}
logger-key: ${{ env.LOGGER_KEY }}
- name: Update Comment with Deletion
uses: peter-evans/create-or-update-comment@v2
if: ${{ env.UFFIZZI_ACTION == 'delete' }}
with:
comment-id: ${{ steps.find-comment.outputs.comment-id }}
issue-number: ${{ env.PR_NUMBER }}
body: |
Uffizzi Preview `${{ env.DEPLOYMENT_ID }}` was deleted.
edit-mode: replace