-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Expand file tree
/
Copy pathci-cd-main-branch-docker-images.yml
More file actions
270 lines (249 loc) · 13.5 KB
/
Copy pathci-cd-main-branch-docker-images.yml
File metadata and controls
270 lines (249 loc) · 13.5 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
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
name: CI-CD build and deploy docker images based on the last commit in the target branch
run-name: "Commit id ${{ github.sha }}: CI-CD build and deploy docker images based on the commit id in the ${{ inputs.checkout_ref == '' && github.ref_name || inputs.checkout_ref }} branch"
env:
APPLICATION: "erigon"
APP_REPO: "erigontech/erigon"
DOCKERHUB_REPOSITORY: "erigontech/erigon"
BUILDER_IMAGE: "golang:1.26-trixie"
LABEL_DESCRIPTION: "[docker image built on the last commit id from the main branch] Erigon is an implementation of Ethereum (execution layer with embeddable consensus layer), on the efficiency frontier. Archive Node by default."
on:
push:
branches:
- 'main'
- 'docker_pectra'
paths-ignore:
- '.github/**'
workflow_dispatch:
inputs:
checkout_ref:
required: false
type: string
default: ''
description: 'The branch to checkout and build artifacts from (in case of a manual run). Important: the Docker image tag is generated automatically from the branch name by removing everything before the last slash. For example, for the branch "feature/user/my-cool-change", the tag will be "my-cool-change". Default is "".'
permissions:
contents: read
jobs:
Build:
name: Build and publish docker image
# runs-on: ubuntu-latest
runs-on: [devops-01-self-hosted]
environment: dockerhub-publish
timeout-minutes: 45
outputs:
docker_build_tag: ${{ steps.built_tag_export.outputs.docker_build_tag }}
steps:
- name: Cleanup workspace
run: |
rm -drf $(pwd)/*
- name: Fast checkout git repository, git ref ${{ inputs.checkout_ref == '' && github.ref_name || inputs.checkout_ref }}
uses: actions/checkout@v7
with:
repository: ${{ env.APP_REPO }}
fetch-depth: 1
ref: ${{ inputs.checkout_ref == '' && github.ref || inputs.checkout_ref }}
path: 'erigon'
persist-credentials: false
- name: Define variables
id: def_docker_vars
## Idea is:
## latest image: erigontech/erigon:${tag_name}${latest_suffix}
## commit id image: erigontech/erigon:${tag_name}-${short_commit_id}
env:
BRANCH_REF: ${{ inputs.checkout_ref == '' && github.ref_name || inputs.checkout_ref }}
run: |
branch_name="$BRANCH_REF"
case "$branch_name" in
"main" )
export tag_name='main';
export keep_images=100;
export latest_suffix='-latest'
export binaries="erigon integration rpcdaemon"
;;
"docker_pectra" )
export tag_name='docker_pectra';
export keep_images=5;
export latest_suffix='';
export binaries="erigon caplin downloader evm hack integration rpcdaemon rpctest sentinel sentry state txpool"
;;
* )
# use last string after last slash '/' by default if branch contains slash:
export tag_name=$(echo "$branch_name" | sed -e 's/.*\///g');
export keep_images=0;
export latest_suffix=''
export binaries="erigon"
;;
esac
echo "tag_name=${tag_name}" >> $GITHUB_OUTPUT
echo "keep_images=${keep_images}" >> $GITHUB_OUTPUT
echo "latest_suffix=${latest_suffix}" >> $GITHUB_OUTPUT
echo "binaries=${binaries}" >> $GITHUB_OUTPUT
echo "Debug ${tag_name} ${keep_images} ${latest_suffix} ${binaries}"
- name: Get commit id
id: getCommitId
run: |
cd erigon
echo "id=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
echo "short_commit_id=$(git rev-parse --short=7 HEAD)" >> $GITHUB_OUTPUT
cd ..
- name: Login to Docker Hub
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 ## v4.0.0
with:
username: ${{ secrets.DOCKERHUB_PUSH_USERNAME }}
password: ${{ secrets.DOCKERHUB_PUSH_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v4.2.0
- name: Set up Docker Buildx
id: docker-buildx
uses: docker/setup-buildx-action@v4
- name: Build and push multi-platform docker image based on the commit id ${{ steps.getCommitId.outputs.short_commit_id }} in the ${{ inputs.checkout_ref == '' && github.ref_name || inputs.checkout_ref }} branch
id: built_tag_export
env:
BUILD_VERSION: "${{ steps.def_docker_vars.outputs.tag_name }}-${{ steps.getCommitId.outputs.short_commit_id }}"
BUILD_VERSION_LATEST: "${{ steps.def_docker_vars.outputs.tag_name }}${{ steps.def_docker_vars.outputs.latest_suffix }}"
DOCKER_PUBLISH_CONDITION: ${{ steps.def_docker_vars.outputs.keep_images > 0 && format('--tag {0}:{1}-{2} ', env.DOCKERHUB_REPOSITORY, steps.def_docker_vars.outputs.tag_name, steps.getCommitId.outputs.short_commit_id) || '' }}
DOCKER_URL: ${{ env.DOCKERHUB_REPOSITORY }}
DOCKERFILE_PATH: Dockerfile
BINARIES: ${{ steps.def_docker_vars.outputs.binaries }}
IMAGE_REF: ${{ inputs.checkout_ref == '' && github.ref || inputs.checkout_ref }}
COMMIT_ID: ${{ steps.getCommitId.outputs.id }}
SHORT_COMMIT_ID: ${{ steps.getCommitId.outputs.short_commit_id }}
REPO_OWNER: ${{ github.repository_owner }}
run: |
echo "docker_build_tag=$BUILD_VERSION" >> $GITHUB_OUTPUT
cd erigon
docker buildx build \
--file "$DOCKERFILE_PATH" \
--build-arg BINARIES="$BINARIES" \
--build-arg BUILDER_IMAGE="$BUILDER_IMAGE" \
--attest type=provenance,mode=max \
--no-cache \
--sbom=true \
${DOCKER_PUBLISH_CONDITION} \
--tag "$DOCKER_URL:$BUILD_VERSION_LATEST" \
--label org.opencontainers.image.created=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
--label org.opencontainers.image.authors="https://github.com/erigontech/erigon/graphs/contributors" \
--label org.opencontainers.image.url="https://github.com/erigontech/erigon/blob/$IMAGE_REF/Dockerfile" \
--label org.opencontainers.image.documentation="https://docs.erigon.tech/" \
--label org.opencontainers.image.source="https://github.com/erigontech/erigon" \
--label org.opencontainers.image.version="$COMMIT_ID" \
--label org.opencontainers.image.revision="$COMMIT_ID" \
--label org.opencontainers.image.vcs-ref-short="$SHORT_COMMIT_ID" \
--label org.opencontainers.image.vendor="$REPO_OWNER" \
--label org.opencontainers.image.description="$LABEL_DESCRIPTION" \
--push \
--platform linux/amd64,linux/arm64 .
echo "Docker build and push done"
- name: export and print docker build tag, cleanup old docker images
env:
BUILD_VERSION: "${{ steps.def_docker_vars.outputs.tag_name }}-${{ steps.getCommitId.outputs.short_commit_id }}"
BUILD_VERSION_LATEST: "${{ steps.def_docker_vars.outputs.tag_name }}${{ steps.def_docker_vars.outputs.latest_suffix }}"
BUILD_VERSION_CONDITION: ${{ steps.def_docker_vars.outputs.keep_images > 0 && format('{0}:{1}-{2} ', env.DOCKERHUB_REPOSITORY, steps.def_docker_vars.outputs.tag_name, steps.getCommitId.outputs.short_commit_id) || '' }}
DOCKER_URL: ${{ env.DOCKERHUB_REPOSITORY }}
TAG_KEY: ${{ steps.def_docker_vars.outputs.tag_name }}
KEEP_IMAGES: ${{ steps.def_docker_vars.outputs.keep_images }}
DH_USERNAME: ${{ secrets.DOCKERHUB_PUSH_USERNAME }}
DH_TOKEN: ${{ secrets.DOCKERHUB_PUSH_TOKEN }}
run: |
HTTP_RESP=$(curl -s -o ./resp1 -w "%{http_code}" \
-X POST -H "Content-Type: application/json" \
-d '{
"identifier": "'"$DH_USERNAME"'",
"secret": "'"$DH_TOKEN"'"
}' \
https://hub.docker.com/v2/auth/token/)
if [ "$HTTP_RESP" -ne "200" ]; then
echo ERROR: HTTP response $HTTP_RESP from https://hub.docker.com/v2/auth/token/
fi
DOCKER_JWT=$(jq -r .access_token ./resp1)
rm -f ./resp1
# Registry delete-scoped token: the Hub JWT untags an image, but reclaiming
# storage requires deleting the manifests by digest via the registry API,
# which needs a token issued with 'delete' scope (PAT must allow deletion).
REG_TOKEN=$(curl -s -u "$DH_USERNAME:$DH_TOKEN" \
"https://auth.docker.io/token?service=registry.docker.io&scope=repository:${DOCKERHUB_REPOSITORY}:pull,delete" \
| jq -r .token)
echo The following docker images have been published:
echo "$DOCKERHUB_REPOSITORY:$BUILD_VERSION_LATEST"
echo "$BUILD_VERSION_CONDITION (empty, if keep_images is 0)"
echo
echo "Cleanup old docker images matching pattern tag ~= $TAG_KEY-XXXXXXX (where XXXXXXX is a short commit id)"
echo "Only last $KEEP_IMAGES images will be kept."
curl_cmd="curl -s -H \"Authorization: Bearer ${DOCKER_JWT}\" "
dockerhub_url='https://hub.docker.com/v2/namespaces/erigontech/repositories/erigon'
## getting all pages in a loop from dockerhub and grepping required tag from the list of tags:
my_list () {
# First page:
next_page="$dockerhub_url/tags?page=1&page_size=100"
while [ "$next_page" != "null" ]
do
# Print tags and push dates for tags matching "$TAG_KEY-":
$curl_cmd $next_page | jq -r '.results|.[]|.name + " " + .tag_last_pushed' | grep "$TAG_KEY-" || true
next_page=`$curl_cmd $next_page | jq '.next' | sed -e 's/^\"//' -e 's/\"$//'`
done
}
# A tag is just a pointer; a manifest still consumes storage until deleted by
# digest. A 403 means either the manifest is still referenced by another kept
# tag/image, or the token lacks the 'delete' scope.
delete_manifest () {
digest="$1"
if [ -z "$digest" ] || [ "$digest" = "null" ]; then
return 0
fi
code=$(curl --write-out %{http_code} --output curl-output.log \
-s -X DELETE \
-H "Authorization: Bearer ${REG_TOKEN}" \
https://registry-1.docker.io/v2/${DOCKERHUB_REPOSITORY}/manifests/${digest})
case "$code" in
200|202) echo -n "manifest ${digest} deleted. " ;;
403) echo -n "manifest ${digest} not deleted (403: still referenced or missing delete permission). " ;;
*) echo "ERROR: failed to delete manifest ${digest} (HTTP $code): $(cat curl-output.log)." ;;
esac
}
echo "DEBUG: full list of images:"
my_list
echo "DEBUG: end of the list."
# Sort newest-first by push time (ISO-8601 sorts chronologically) so tail keeps
# the newest KEEP_IMAGES; the Hub tags API order is not contractually guaranteed.
my_list | sort -k2,2 -r | tail -n +"$((KEEP_IMAGES + 1))" | while read line; do
echo -n "Removing docker image/published - $line "
current_image=$(echo $line | sed -e "s/^\(${TAG_KEY}-.\{7\}\) .*/\1/")
# Collect the manifest digests behind this tag before untagging it.
meta=$(curl -s -H "Authorization: Bearer ${DOCKER_JWT}" "$dockerhub_url/tags/${current_image}")
index_digest=$(echo "$meta" | jq -r '.digest // empty')
image_digests=$(echo "$meta" | jq -r '.images[]?.digest // empty')
output_code=$(curl --write-out %{http_code} --output curl-output.log \
-s -X DELETE -H "Accept: application/json" \
-H "Authorization: Bearer ${DOCKER_JWT}" \
https://hub.docker.com/v2/repositories/erigontech/erigon/tags/${current_image} )
if [ $output_code -ne 204 ]; then
echo "ERROR: failed to remove docker image erigon:${current_image}"
echo "ERROR: API response: $(cat curl-output.log)."
continue
fi
echo -n " - tag removed. "
# Delete the index first so its per-platform manifests become unreferenced
# and deletable, then delete those to actually reclaim storage.
delete_manifest "$index_digest"
for d in $image_digests; do
delete_manifest "$d"
done
echo "Done."
done
- name: Notify Discord on failure
# Alert only on the automated main-branch builds; manual dispatch runs
# are watched by whoever triggered them. No-op when the webhook is unset.
if: ${{ failure() && github.event_name == 'push' }}
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
BRANCH: ${{ github.ref_name }}
run: |
if [ -z "${DISCORD_WEBHOOK:-}" ]; then
echo "DISCORD_WEBHOOK not set — skipping Discord notification."
exit 0
fi
msg=":x: **CI-CD docker image build failed** on branch \`${BRANCH}\`\nRun: ${RUN_URL}"
payload=$(jq -nc --arg c "$msg" '{content: $c}')
curl -sS -f -H "Content-Type: application/json" -d "$payload" "$DISCORD_WEBHOOK" \
&& echo "Discord notified." \
|| echo "::warning::Discord notification failed (webhook unreachable or invalid)."