Skip to content

Commit 31eddd5

Browse files
committed
WIP: deploy previews for admin app
1 parent 90037e3 commit 31eddd5

1 file changed

Lines changed: 246 additions & 0 deletions

File tree

.github/workflows/previews.yml

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
name: Previews
2+
3+
on:
4+
pull_request:
5+
types: [opened, reopened, synchronize, closed]
6+
7+
env:
8+
TOKEN: ${{ secrets.DIGITAL_OCEAN_PROXY_TOKEN }}
9+
URL: ${{ vars.DIGITAL_OCEAN_PROXY_URL }}
10+
APP_NAME: pdc-base-fields-admin-preview-${{ github.event.pull_request.number }}
11+
PROJECT_ID: ${{ secrets.DIGITAL_OCEAN_PROJECT_ID }}
12+
BRANCH: ${{ github.event.pull_request.head.ref }}
13+
14+
jobs:
15+
post-apps:
16+
runs-on: ubuntu-latest
17+
permissions:
18+
pull-requests: write
19+
if: |
20+
github.actor != 'dependabot[bot]' &&
21+
!cancelled() &&
22+
github.event.pull_request.head.repo.full_name == github.repository &&
23+
contains(fromJSON('["opened", "reopened", "synchronize"]'), github.event.action)
24+
needs: delete-app
25+
concurrency:
26+
group: preview-admin-app-${{ github.event.pull_request.number }}
27+
cancel-in-progress: false
28+
steps:
29+
- name: Create or recreate a preview admin app for this PR
30+
id: create-app-get-url
31+
run: |
32+
set -eo pipefail
33+
app_spec=$(cat <<EOF
34+
project_id: ${{ env.PROJECT_ID }}
35+
spec:
36+
alerts:
37+
- rule: DEPLOYMENT_FAILED
38+
- rule: DOMAIN_FAILED
39+
- rule: DEPLOYMENT_LIVE
40+
features:
41+
- buildpack-stack=ubuntu-22
42+
ingress:
43+
rules:
44+
- component:
45+
name: base-fields-admin
46+
match:
47+
path:
48+
prefix: /
49+
name: ${{ env.APP_NAME }}
50+
region: nyc
51+
static_sites:
52+
- catchall_document: index.html
53+
environment_slug: node-js
54+
envs:
55+
- key: VITE_API_URL
56+
scope: BUILD_TIME
57+
value: https://api.sandbox.philanthropydatacommons.org
58+
- key: VITE_KEYCLOAK_AUTHORITY
59+
scope: BUILD_TIME
60+
value: https://auth.philanthropydatacommons.org/
61+
- key: VITE_KEYCLOAK_REALM
62+
scope: BUILD_TIME
63+
value: pdc
64+
- key: VITE_KEYCLOAK_CLIENT_ID
65+
scope: BUILD_TIME
66+
value: pdc-base-fields-admin-previews
67+
- key: NODE_ENV
68+
scope: BUILD_TIME
69+
value: production
70+
github:
71+
branch: ${{ env.BRANCH }}
72+
deploy_on_push: false
73+
repo: PhilanthropyDataCommons/front-end
74+
name: base-fields-admin
75+
output_dir: apps/admin-interface/dist
76+
source_dir: /
77+
EOF
78+
)
79+
# Find existing ID if it exists
80+
app_id=$(curl --fail-with-body -X GET \
81+
-H "Accept: application/json" \
82+
-H "Authorization: Bearer ${TOKEN}" \
83+
--url "${URL}/v2/apps" |
84+
jq -r -c '.apps[] | select (.spec.name == "${{ env.APP_NAME }}") | .id')
85+
http_method=""
86+
http_url=""
87+
if [ "${app_id}" == "" ] || [ "${app_id}" == "null" ]; then
88+
# New app needed, POST a new spec. This is for PR open and reopen events.
89+
echo "New app needed"
90+
http_method=POST
91+
http_url="${URL}/v2/apps"
92+
else
93+
# Existing app, PUT a new spec. This should be a rare case, where the
94+
# DELETE was not correctly called on resynchronize before this.
95+
echo "Existing app ${app_id} found."
96+
http_method=PUT
97+
http_url="${URL}/v2/apps/${app_id}"
98+
# The PUT app spec forbids the `project_id` element so filter it out.
99+
app_spec=$(echo "${app_spec}" | grep -v "project_id:")
100+
fi
101+
102+
if ! app_response=$(curl --fail-with-body -X "${http_method}" \
103+
-H "Accept: application/json" \
104+
-H "Content-Type: application/yaml" \
105+
-H "Authorization: Bearer ${TOKEN}" \
106+
--url "${http_url}" \
107+
-d "${app_spec}")
108+
then
109+
echo "App could not be created. Response:"
110+
echo "${app_response}"
111+
exit 150
112+
fi
113+
114+
echo "App creation response body:"
115+
echo "${app_response}"
116+
117+
app_id=$(echo "${app_response}" | jq -r -c '.app.id')
118+
pending_deployment_id=$(echo "${app_response}" | jq -r -c '.app.pending_deployment.id')
119+
live_url=$(echo "${app_response}" | jq -r -c '.app.live_url')
120+
121+
if [ "${app_id}" == "" ] || [ "${app_id}" == "null" ]; then
122+
echo "App ID could not be found"
123+
exit 151
124+
fi
125+
126+
echo "New app named ${{ env.APP_NAME }} created successfully."
127+
echo "app_id: ${app_id}"
128+
echo "live_url: ${live_url}"
129+
echo "pending_deployment_id: ${pending_deployment_id}"
130+
131+
echo "Getting the deployment momentarily..."
132+
133+
for i in {1..12}; do
134+
sleep 10
135+
echo "Poll attempt ${i}"
136+
if ! deployment_response=$(curl --fail-with-body -X GET \
137+
-H "Accept: application/json" \
138+
-H "Authorization: Bearer ${TOKEN}" \
139+
--url "${URL}/v2/apps/${app_id}/deployments/${pending_deployment_id}")
140+
then
141+
echo "Failed to get a deployment response from DO."
142+
exit 152
143+
fi
144+
phase=$(echo "${deployment_response}" | jq -r -c '.deployment.phase')
145+
echo "Deployment ${phase}"
146+
if [ "${phase}" == "ACTIVE" ]; then
147+
# You might expect to get the live URL from response, no.
148+
break
149+
elif [ "${phase}" == "FAILED" ]; then
150+
exit 153
151+
fi
152+
done
153+
154+
if [ "${phase}" != "ACTIVE" ]; then
155+
echo "Deployment did not succeed in a reasonable amount of time."
156+
echo "Attempting to get DEPLOY logs, if possible. The task fails regardless."
157+
if logs_list_response=$(curl --fail-with-body -X GET \
158+
-H "Accept: application/json" \
159+
-H "Authorization: Bearer ${TOKEN}" \
160+
--url "${URL}/v2/apps/${app_id}/deployments/${pending_deployment_id}/components/service/logs?type=DEPLOY")
161+
then
162+
# Docs are ambiguous, experimentation shows `historic_urls` to be a key.
163+
# Sometimes `url` and `live_url` are here, matching.
164+
# This is best effort, so far with little success.
165+
echo "What URLs are available? Response:"
166+
echo "${logs_list_response}"
167+
if logs_url=$(echo "${logs_list_response}" | jq -r -c '.url')
168+
then
169+
if logs_response=$(curl --fail-with-body -X GET \
170+
--url "${logs_url}")
171+
then
172+
echo "Deploy log file found:"
173+
else
174+
echo "Failed to get a log file. Response:"
175+
fi
176+
echo "${logs_response}"
177+
fi
178+
else
179+
echo "Failed to get the list of DEPLOY logs. Response:"
180+
echo "${logs_list_response}"
181+
fi
182+
echo "Attempting to cancel the deployment. The task fails regardless."
183+
curl --fail-with-body -X POST \
184+
-H "Accept: application/json" \
185+
-H "Authorization: Bearer ${TOKEN}" \
186+
--url "${URL}/v2/apps/${app_id}/deployments/${pending_deployment_id}/cancel"
187+
exit 154
188+
fi
189+
190+
# Now that the app is live, we should be able to get the URL.
191+
live_url=$(curl --fail-with-body -X GET \
192+
-H "Accept: application/json" \
193+
-H "Authorization: Bearer ${TOKEN}" \
194+
--url "${URL}/v2/apps/${app_id}" |
195+
jq -r -c '.app.live_url')
196+
if [ "${live_url}" != "" ] && [ "${live_url}" != "null" ]; then
197+
# Save live_url for the next step.
198+
echo "live_url=${live_url}" >> "${GITHUB_OUTPUT}"
199+
else
200+
echo "The deployment of a deploy preview succeeded but couldn't find the URL."
201+
exit 155
202+
fi
203+
- name: Add deploy preview URL to the PR comments
204+
uses: thollander/actions-comment-pull-request@v3
205+
with:
206+
message: |
207+
🔎 A preview deployment of this pull request is available at ${{ steps.create-app-get-url.outputs.live_url }} assuming this PR is still open. 🔍
208+
comment-tag: deploy-preview-url
209+
210+
delete-app:
211+
runs-on: ubuntu-latest
212+
if: |
213+
github.actor != 'dependabot[bot]' &&
214+
github.event.pull_request.head.repo.full_name == github.repository &&
215+
contains(fromJSON('["closed", "synchronize"]'), github.event.action)
216+
concurrency:
217+
group: preview-admin-app-${{ github.event.pull_request.number }}
218+
cancel-in-progress: false
219+
steps:
220+
- name: Delete a preview app for this PR if it exists
221+
run: |
222+
set -eo pipefail
223+
# Get the list of applications (there is no filter by name call)
224+
if ! app_response=$(curl --fail-with-body -X GET \
225+
-H "Accept: application/json" \
226+
-H "Authorization: Bearer ${TOKEN}" \
227+
--url "${URL}/v2/apps")
228+
then
229+
echo "App list could not be found. Response:"
230+
echo "${app_response}"
231+
# Failure to GET a list of apps should fail the task.
232+
exit 153
233+
fi
234+
app_id=$(echo "${app_response}" |
235+
jq -r -c '.apps[] | select (.spec.name == "${{ env.APP_NAME }}") | .id')
236+
if [ "${app_id}" != "" ] && [ "${app_id}" != "null" ]; then
237+
# Found the app, delete it. Failure on DELETE should fail the task.
238+
curl --fail-with-body -X DELETE \
239+
-H "Accept: application/json" \
240+
-H "Authorization: Bearer ${TOKEN}" \
241+
--url "${URL}/v2/apps/${app_id}"
242+
else
243+
# Failure to find the app in a successfully-gotten list should not
244+
# fail the task. And we cannot print the list because secrets show.
245+
echo "WARNING: ${{ env.APP_NAME }} was not found in the list."
246+
fi

0 commit comments

Comments
 (0)