forked from OurHike/OurHike
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdelete.sh
More file actions
138 lines (118 loc) · 4.92 KB
/
Copy pathdelete.sh
File metadata and controls
138 lines (118 loc) · 4.92 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
#!/usr/bin/env bash
#
# Deletes every Cloudflare Pages deployment belonging to one pull request's
# preview alias. See action.yml for why this is not something Cloudflare does
# on its own.
set -euo pipefail
: "${API_TOKEN:?}"
: "${ACCOUNT_ID:?}"
: "${PROJECT:?}"
# Not `:?` like the rest, because an empty alias is the most likely way this
# ever goes wrong - an upstream step that produced nothing - and it deserves
# the explanation below rather than bash's "parameter null or not set", which
# carries no ::error:: annotation and so does not surface in the run summary
# at all. The check below rejects it either way.
ALIAS="${ALIAS:-}"
# Overridable so the tests can point this at a local stand-in for the
# Cloudflare API. There is no other way to exercise a function whose entire
# job is issuing DELETEs - against the real API the only honest test would
# destroy something.
API_BASE="${API_BASE:-https://api.cloudflare.com/client/v4}"
# The alias is the only thing between "tidy up a closed pull request" and
# "delete a deployment somebody is using". A filter one character too loose
# would not look wrong in review and would not fail a test that only checks
# the happy path, so the shape is asserted before anything is even listed.
if [[ ! "$ALIAS" =~ ^pr-[0-9]+$ ]]; then
echo "::error::Refusing to delete anything: '$ALIAS' is not a pull request preview alias (expected pr-<number>)."
exit 1
fi
DEPLOYMENTS="$API_BASE/accounts/$ACCOUNT_ID/pages/projects/$PROJECT/deployments"
found=0
deleted=0
failed=0
report() {
if [ -n "${GITHUB_OUTPUT:-}" ]; then
{
echo "found=$found"
echo "deleted=$deleted"
echo "failed=$failed"
} >>"$GITHUB_OUTPUT"
fi
}
trap report EXIT
api() {
# Deliberately not --fail: a Cloudflare error arrives as a 200 carrying
# "success": false at least as often as it arrives as a 4xx, and the message
# inside the body is the only useful part of either.
curl -sS -H "Authorization: Bearer $API_TOKEN" -H "Content-Type: application/json" "$@"
}
# Collect every id first, and only then delete. Not page-by-page-deleting as
# it goes: removing items from a paginated collection while paging through it
# shifts everything after them back a place, so the next page begins past
# whatever moved into the slots just vacated and those are never looked at.
# What that produces is a cleanup that silently leaves some behind - which is
# the exact failure this action exists to prevent, arrived at by way of the
# fix for it.
ids=()
page=1
while :; do
if ! response="$(api "$DEPLOYMENTS?env=preview&per_page=100&page=$page")"; then
echo "::error::Could not reach the Cloudflare API to list deployments for '$PROJECT'."
exit 1
fi
if [ "$(jq -r '.success // false' <<<"$response")" != "true" ]; then
jq -r '.errors[]?.message // empty' <<<"$response" >&2 || true
echo "::error::Cloudflare declined to list deployments for '$PROJECT'. The token needs \"Cloudflare Pages: Edit\" on this account."
exit 1
fi
if [ "$(jq '.result | length' <<<"$response")" -eq 0 ]; then
break
fi
# Matched two ways because only one of them is guaranteed to be there. The
# branch recorded against the deployment is what `--branch` set and is the
# authoritative answer; the alias URLs are what the deployment actually
# serves from. Either alone identifies this pull request's deployments, and
# if Cloudflare ever stops populating one the other still finds them.
#
# Both are exact rather than prefix matches. `pr-28` must never select
# `pr-281`, and a `startswith` on the branch would do precisely that.
while IFS= read -r id; do
if [ -n "$id" ]; then
ids+=("$id")
fi
done < <(jq -r --arg alias "$ALIAS" '
.result[]
| select(
(.deployment_trigger.metadata.branch? == $alias)
or (any(.aliases[]?; startswith("https://" + $alias + ".")))
)
| .id
' <<<"$response")
page=$(( page + 1 ))
done
found=${#ids[@]}
if [ "$found" -eq 0 ]; then
echo "No deployments found for '$ALIAS'; nothing to remove."
exit 0
fi
echo "Found $found deployment(s) for '$ALIAS'."
for id in "${ids[@]}"; do
# force=true because the newest deployment for an alias is the one holding
# that alias, and it is the single most important one to remove - a cleanup
# that deleted every deployment except the one still serving the URL would
# have achieved nothing at all. What makes that safe is the filter above,
# not restraint here.
if response="$(api -X DELETE "$DEPLOYMENTS/$id?force=true")" \
&& [ "$(jq -r '.success // false' <<<"$response")" = "true" ]; then
deleted=$(( deleted + 1 ))
else
failed=$(( failed + 1 ))
echo "Could not delete deployment $id:" >&2
jq -r '.errors[]?.message // empty' <<<"${response:-{\}}" >&2 || true
fi
done
echo "Deleted $deleted of $found deployment(s) for '$ALIAS'."
if [ "$failed" -gt 0 ]; then
echo "::error::$failed of $found deployment(s) for '$ALIAS' could not be deleted and are still reachable."
exit 1
fi