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
name: Reap previews
on:
schedule:
- cron: "17 8 * * *"
workflow_dispatch:
inputs:
dry_run:
description: Report what would be destroyed without destroying it
type: boolean
default: false
stale_days:
description: Also reap previews for open PRs untouched this many days (0 disables)
type: string
default: "7"
permissions: {}
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false
jobs:
reap:
name: Reap previews
runs-on: ubuntu-latest
permissions:
pull-requests: read # read PR state to decide which previews are finished
environment: preview-reaper # holds FLY_API_TOKEN access, as demo/pr-* do
steps:
- uses: superfly/flyctl-actions/setup-flyctl@ed8efb33836e8b2096c7fd3ba1c8afe303ebbff1 # 1.6
- name: Destroy previews for finished pull requests
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
FLY_ORG: personal
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
OWNER: ${{ github.repository_owner }}
DRY_RUN: ${{ inputs.dry_run }}
STALE_DAYS: ${{ inputs.stale_days || '7' }}
run: |
set -euo pipefail
# Preview apps are named by superfly/fly-pr-review-apps as
# pr-<number>-<owner>-<repo>, with underscores turned into hyphens.
repo_name="${REPO#"${OWNER}/"}"
suffix="-$(printf '%s' "${OWNER}-${repo_name}" | tr '_' '-')"
# Prove the token can read this repo before trusting any 404 below to
# mean "that pull request is gone" rather than "the token is broken".
gh api "repos/${REPO}" --jq .full_name > /dev/null
cutoff=""
if [ "${STALE_DAYS}" != "0" ]; then
cutoff=$(date -u -d "${STALE_DAYS} days ago" +%s)
fi
destroyed=0
kept=0
{
echo "| Preview | Pull request | Outcome |"
echo "| --- | --- | --- |"
} >> "${GITHUB_STEP_SUMMARY}"
apps=$(flyctl apps list --org "${FLY_ORG}" --json | jq -r '.[] | (.Name // .name)')
while IFS= read -r app; do
[ -n "${app}" ] || continue
case "${app}" in
pr-*"${suffix}") ;;
*) continue ;;
esac
pr="${app#pr-}"
pr="${pr%"${suffix}"}"
case "${pr}" in
"" | *[!0-9]*) continue ;;
esac
if body=$(gh api "repos/${REPO}/pulls/${pr}" 2> "${RUNNER_TEMP}/gh-err"); then
state=$(printf '%s' "${body}" | jq -r .state)
merged=$(printf '%s' "${body}" | jq -r '.merged_at != null')
updated=$(printf '%s' "${body}" | jq -r .updated_at)
elif grep -q 'HTTP 404' "${RUNNER_TEMP}/gh-err"; then
state="missing"
merged="false"
updated=""
else
echo "::warning::Could not read PR #${pr}; leaving ${app} alone."
cat "${RUNNER_TEMP}/gh-err"
kept=$((kept + 1))
echo "| ${app} | #${pr} | kept (lookup failed) |" >> "${GITHUB_STEP_SUMMARY}"
continue
fi
reason=""
case "${state}" in
closed)
if [ "${merged}" = "true" ]; then
reason="PR merged"
else
reason="PR closed"
fi
;;
missing)
reason="PR no longer exists"
;;
open)
if [ -n "${cutoff}" ] && [ "$(date -u -d "${updated}" +%s)" -lt "${cutoff}" ]; then
reason="PR open but untouched since ${updated%T*}"
fi
;;
esac
if [ -z "${reason}" ]; then
kept=$((kept + 1))
echo "| ${app} | #${pr} | kept (${state}) |" >> "${GITHUB_STEP_SUMMARY}"
continue
fi
# Same guard the deploy action uses: never destroy an app whose name
# does not carry the PR number we looked up.
case "${app}" in
*"${pr}"*) ;;
*)
echo "::error::Refusing to destroy ${app}: name does not contain PR #${pr}."
exit 1
;;
esac
if [ "${DRY_RUN}" = "true" ]; then
echo "Would destroy ${app} (${reason})."
echo "| ${app} | #${pr} | would destroy (${reason}) |" >> "${GITHUB_STEP_SUMMARY}"
continue
fi
echo "Destroying ${app} (${reason})."
flyctl apps destroy "${app}" --yes
destroyed=$((destroyed + 1))
echo "| ${app} | #${pr} | destroyed (${reason}) |" >> "${GITHUB_STEP_SUMMARY}"
done <<EOF
${apps}
EOF
echo "" >> "${GITHUB_STEP_SUMMARY}"
echo "Destroyed ${destroyed}, kept ${kept}." >> "${GITHUB_STEP_SUMMARY}"