Repository navigation
Expand file tree
/
Copy pathpackages.sh
More file actions
166 lines (141 loc) · 4.96 KB
/
Copy pathpackages.sh
File metadata and controls
166 lines (141 loc) · 4.96 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
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
#!/usr/bin/env bash
set -euo pipefail
: "${GITHUB_TOKEN:?GITHUB_TOKEN is required}"
readonly RETAINED_BUILDS=10
readonly PACKAGE="template-service"
readonly IMAGE="ghcr.io/monadiam/$PACKAGE"
readonly LOCAL_IMAGE="monadiam/template-service:ci"
readonly PACKAGE_API="/orgs/MonadIAM/packages/container/$PACKAGE"
readonly VERSION_PATTERN='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
readonly DIGEST_PATTERN='^sha256:[0-9a-f]{64}$'
readonly SHA_TAG_PATTERN='^sha-[0-9a-f]{40}$'
commit=$(git rev-parse HEAD)
sha_tag="sha-$commit"
versions='[]'
source_digest=''
published_tag=''
load_versions() {
local page response status body url
versions='[]'
for ((page = 1; ; page++)); do
url="https://api.github.com$PACKAGE_API/versions?per_page=100&page=$page"
response=$(curl --silent --show-error \
--connect-timeout 5 --max-time 30 \
--header "Authorization: Bearer $GITHUB_TOKEN" \
--header "Accept: application/vnd.github+json" \
--header "X-GitHub-Api-Version: 2022-11-28" \
--write-out $'\n%{http_code}' "$url")
status="${response##*$'\n'}"
body="${response%$'\n'*}"
if [[ "$status" == 200 ]]; then
versions=$(jq -c --argjson batch "$body" '. + $batch' <<< "$versions")
if [[ $(jq 'length' <<< "$body") -lt 100 ]]; then
break
fi
elif [[ "$status" == 404 && "$page" == 1 ]]; then
break
else
printf 'Failed to list package versions: GET %s; HTTP %s\n%s\n' \
"$url" "$status" "${body:0:4096}" >&2
exit 1
fi
done
source_digest=$(jq -r --arg tag "$sha_tag" '
.[] | select(.metadata.container.tags | index($tag)) | .name
' <<< "$versions")
}
publish_commit() {
local revision
if [[ -n "$source_digest" ]]; then
printf 'Reusing %s:%s\n' "$IMAGE" "$sha_tag"
else
revision=$(docker inspect "$LOCAL_IMAGE" \
--format '{{index .Config.Labels "org.opencontainers.image.revision"}}')
if [[ "$revision" == "$commit" ]]; then
docker tag "$LOCAL_IMAGE" "$IMAGE:$sha_tag"
docker push "$IMAGE:$sha_tag"
else
printf 'Image revision does not match %s\n' "$commit" >&2
exit 1
fi
fi
published_tag="$sha_tag"
}
publish_release() {
local version existing_digest
: "${GITHUB_REF_NAME:?GITHUB_REF_NAME is required}"
version=$(jq -er '.version' package.json)
if [[ "$version" =~ $VERSION_PATTERN &&
"$GITHUB_REF_NAME" == "v$version" &&
"$source_digest" =~ $DIGEST_PATTERN ]]; then
existing_digest=$(jq -r --arg tag "$version" '
.[] | select(.metadata.container.tags | index($tag)) | .name
' <<< "$versions")
if [[ -z "$existing_digest" ]]; then
docker buildx imagetools create --prefer-index=false \
--tag "$IMAGE:$version" "$IMAGE@$source_digest"
elif [[ "$existing_digest" == "$source_digest" ]]; then
printf 'Reusing release %s\n' "$version"
else
printf 'Release %s already points to another digest\n' "$version" >&2
exit 1
fi
else
printf 'Release requires a matching version tag and published SHA image\n' >&2
exit 1
fi
published_tag="$version"
}
cleanup() {
local candidates id
candidates=$(jq -r \
--arg pattern "$SHA_TAG_PATTERN" \
--arg current "$sha_tag" \
--argjson keep "$RETAINED_BUILDS" '
map(select(
.metadata.container.tags |
length > 0 and all(.[]; test($pattern))
))
| sort_by(.created_at) | reverse
| .[$keep:][]
| select(.metadata.container.tags | index($current) == null)
| .id
' <<< "$versions")
if [[ -n "$candidates" ]]; then
while IFS= read -r id; do
gh api --method DELETE "$PACKAGE_API/versions/$id"
printf 'Deleted intermediate image %s\n' "$id"
done <<< "$candidates"
fi
}
verify_publication() {
local digest
digest=$(docker buildx imagetools inspect "$IMAGE:$published_tag" \
--format '{{.Manifest.Digest}}')
if [[ "$digest" =~ $DIGEST_PATTERN &&
( -z "$source_digest" || "$digest" == "$source_digest" ) ]]; then
printf 'Image: %s:%s\nDigest: %s@%s\n' \
"$IMAGE" "$published_tag" "$IMAGE" "$digest" \
| tee -a "$GITHUB_STEP_SUMMARY"
else
printf 'Unexpected digest: %s\n' "$digest" >&2
exit 1
fi
}
case "${1:?Usage: packages.sh sha|release|cleanup}" in
sha|release)
: "${GITHUB_STEP_SUMMARY:?GITHUB_STEP_SUMMARY is required}"
load_versions
if [[ "$1" == "sha" ]]; then
publish_commit
else
publish_release
fi
verify_publication ;;
cleanup)
load_versions
cleanup ;;
*)
printf 'Unknown operation: %s\n' "$1" >&2
exit 1 ;;
esac