diff options
author | Mike Crute <mike@crute.us> | 2020-01-09 19:20:14 +0000 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2020-01-09 19:23:51 +0000 |
commit | 4c4e6d896989dee98b7c9672332b8701ebbe41a6 (patch) | |
tree | 90af8f46803c29b9485879bcefb50b5618f3c527 /bin | |
parent | 1fe8647502560320171b3ff19c34945d23a29c75 (diff) | |
download | dotfiles-4c4e6d896989dee98b7c9672332b8701ebbe41a6.tar.bz2 dotfiles-4c4e6d896989dee98b7c9672332b8701ebbe41a6.tar.xz dotfiles-4c4e6d896989dee98b7c9672332b8701ebbe41a6.zip |
Add registry remove script
Diffstat (limited to 'bin')
-rwxr-xr-x | bin/remove_image_from_registry.sh | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/bin/remove_image_from_registry.sh b/bin/remove_image_from_registry.sh new file mode 100755 index 0000000..d6e9fb1 --- /dev/null +++ b/bin/remove_image_from_registry.sh | |||
@@ -0,0 +1,315 @@ | |||
1 | #!/bin/bash | ||
2 | |||
3 | IMAGE_ARG="" | ||
4 | HOST="" | ||
5 | IMAGE="" | ||
6 | URI="" | ||
7 | TAG="" | ||
8 | USERNAME="" | ||
9 | CREDENTIALS_STRING="" | ||
10 | INSECURE="" | ||
11 | RAW_URL="" | ||
12 | RAW_HTTP_METHOD="" | ||
13 | RAW_HTTP_HEADER="" | ||
14 | TAG_ONLY=false | ||
15 | |||
16 | function printUsage | ||
17 | { | ||
18 | local RESULT=$1 | ||
19 | if [ "$RESULT" == "" ]; then | ||
20 | RESULT=0 | ||
21 | fi | ||
22 | cat << EOF | ||
23 | |||
24 | Usage: | ||
25 | $ ./remove_image_from_registry.sh [OPTIONS] [IMAGE] | ||
26 | |||
27 | IMAGE | ||
28 | Image name has the format registryhost:port/repository/imagename:version | ||
29 | For instance : mydockerregistry:5000/myrepo/zoombie:latest | ||
30 | Note that the version tag ("latest" in this example) is mandatory. | ||
31 | Please note that this script will delete the image from the repository, not only the tag; if the | ||
32 | image you are deleting have multiple tags ( for instance "1.0" and "latest" ), both tags will be | ||
33 | removed from the registry. | ||
34 | Docker registry does not support deleting only a tag ATM, ref https://github.com/docker/distribution/issues/2317 | ||
35 | The option "--tag-only" tries to circumvent this by restoring other tags which also disappear from | ||
36 | the registry during the delete. However, this is not entirely safe: | ||
37 | - Do not delete multiple images concurrently. | ||
38 | - Do not run registry garbage collector while deleting images (this you should never do anyway....). | ||
39 | - Do not create new local tags for the image during the delete operation. | ||
40 | |||
41 | REQUIREMENTS | ||
42 | The registry must run a v2 registry and have token based authentication enabled. | ||
43 | Deletion must be enabled on the registry server (REGISTRY_STORAGE_DELETE_ENABLED=true). | ||
44 | |||
45 | NOTE | ||
46 | The blobs are actually not deleted from the registry server automatically after running this script. | ||
47 | In order to do that you must manually (for the time being) run the registry garbage collector. | ||
48 | See https://docs.docker.com/registry/garbage-collection/ for more info about this. | ||
49 | |||
50 | OPTIONS | ||
51 | -h, --help | ||
52 | Print help | ||
53 | --insecure | ||
54 | Connect to a registry which has a self-signed SSL certificate | ||
55 | -p | ||
56 | Prompt for password | ||
57 | -u <username> | ||
58 | Use the given username when authenticating with the registry | ||
59 | --raw <url> <http-method> [http-header] | ||
60 | Send custom request to the registry. When using this argument, do not use the [IMAGE] argument too. | ||
61 | Example: | ||
62 | ./remove_image_from_registry.sh \\ | ||
63 | -u admin \\ | ||
64 | --insecure \\ | ||
65 | --raw \\ | ||
66 | mydockerregistry:5000/v2/imagename/manifests/latest \\ | ||
67 | GET \\ | ||
68 | "Accept: application/vnd.docker.distribution.manifest.v2+json" | ||
69 | --tag-only | ||
70 | After deleting the image, try to recover all other tags which also pointed to the image | ||
71 | |||
72 | |||
73 | Password may also be set using the environment variable REGISTRY_PASSWORD | ||
74 | $ export REGISTRY_PASSWORD=sesame | ||
75 | |||
76 | EOF | ||
77 | exit $RESULT; | ||
78 | } | ||
79 | |||
80 | function validateImageName | ||
81 | { | ||
82 | local IMAGE_NAME | ||
83 | IMAGE_NAME="$1" | ||
84 | if [[ "$IMAGE_NAME" == https://* ]]; then | ||
85 | echo "Image name or raw URL should not start with https://" | ||
86 | exit 1 | ||
87 | fi | ||
88 | if [[ "$IMAGE_NAME" == http://* ]]; then | ||
89 | echo "Image name or raw URL should not start with http://" | ||
90 | echo "Anyway, registry must use SSL in order to make token based auth work" | ||
91 | exit 1 | ||
92 | fi | ||
93 | } | ||
94 | |||
95 | function parseArguments | ||
96 | { | ||
97 | while (( "$#" )); do | ||
98 | if [ "$1" = "-u" ]; then | ||
99 | shift | ||
100 | USERNAME=$1 | ||
101 | elif [ "$1" = "-p" ]; then | ||
102 | echo -n "Password: " | ||
103 | read -s REGISTRY_PASSWORD | ||
104 | echo | ||
105 | elif [ "$1" = "--insecure" ]; then | ||
106 | INSECURE=" --insecure" | ||
107 | elif [ "$1" = "--help" ]; then | ||
108 | printUsage | ||
109 | elif [ "$1" = "-h" ]; then | ||
110 | printUsage | ||
111 | elif [ "$1" = "--raw" ]; then | ||
112 | shift | ||
113 | RAW_URL="$1" | ||
114 | validateImageName "$1" | ||
115 | shift | ||
116 | RAW_HTTP_METHOD="$1" | ||
117 | shift | ||
118 | RAW_HTTP_HEADER="$1" | ||
119 | elif [ "$1" = "--tag-only" ]; then | ||
120 | TAG_ONLY=true | ||
121 | else | ||
122 | # If first param is a dash, we have an invalid argumwent | ||
123 | if [ ${1:0:1} == "-" ]; then | ||
124 | echo "Error: Unknown parameter : $1" | ||
125 | exit 1 | ||
126 | fi | ||
127 | if [ "$IMAGE_ARG" != "" ]; then | ||
128 | echo "Error: You may only provide IMAGE name once" | ||
129 | exit 1 | ||
130 | fi | ||
131 | validateImageName "$1" | ||
132 | IMAGE_ARG="$1" | ||
133 | HOST=`echo $IMAGE_ARG|cut -f 1 -d "/"` | ||
134 | IMAGE=`echo $IMAGE_ARG|cut -f 2- -d "/"|cut -f 1 -d ":"` | ||
135 | TAG=`echo $IMAGE_ARG|cut -f 2- -d "/"|cut -f 2 -d ":"` | ||
136 | fi | ||
137 | shift | ||
138 | done | ||
139 | |||
140 | if [ "$IMAGE_ARG" = "" ] && [ "$RAW_URL" = "" ]; then | ||
141 | echo "Error: You need to provide image name" | ||
142 | printUsage 1 | ||
143 | fi | ||
144 | |||
145 | if [ "$USERNAME" != "" ]; then | ||
146 | CREDENTIALS_STRING=" --user ${USERNAME}:${REGISTRY_PASSWORD}" | ||
147 | fi | ||
148 | } | ||
149 | |||
150 | # $1 is URL | ||
151 | # $2 is HTTP METHOD (default GET) | ||
152 | # $2 is additional header ( optional ) | ||
153 | function sendRegistryRequest | ||
154 | { | ||
155 | local URL | ||
156 | local WWW_AUTH_HEADER | ||
157 | local TOKEN | ||
158 | local TOKEN_RESP | ||
159 | local REALM | ||
160 | local SERVICE | ||
161 | local SCOPE | ||
162 | local CUSTOM_HEADER | ||
163 | local HTTP_METHOD | ||
164 | local CURL_HTTP_METHOD_OPTION | ||
165 | local CURL_ARG | ||
166 | local RESULT | ||
167 | |||
168 | URL="$1" | ||
169 | |||
170 | CURL_HTTP_METHOD_OPTION="-X" | ||
171 | if [ "$2" != "" ]; then | ||
172 | HTTP_METHOD="$2" | ||
173 | else | ||
174 | HTTP_METHOD="GET" | ||
175 | fi | ||
176 | |||
177 | # If HTTP_METHOD == "HEAD", we'll need to use -I option instead | ||
178 | if [ $HTTP_METHOD = "HEAD" ]; then | ||
179 | CURL_HTTP_METHOD_OPTION="-I" | ||
180 | HTTP_METHOD="" | ||
181 | fi | ||
182 | |||
183 | if [ "$3" != "" ]; then | ||
184 | CUSTOM_HEADER="$3" | ||
185 | else | ||
186 | CUSTOM_HEADER="" | ||
187 | fi | ||
188 | WWW_AUTH_HEADER=`curl -sS -i $INSECURE $CURL_HTTP_METHOD_OPTION $HTTP_METHOD -H "Content-Type: application/json" ${URL} |grep Www-Authenticate|sed 's|.*realm="\(.*\)",service="\(.*\)",scope="\(.*\)".*|\1,\2,\3|'` | ||
189 | |||
190 | REALM=`echo $WWW_AUTH_HEADER|cut -f 1 -d ","` | ||
191 | SERVICE=`echo $WWW_AUTH_HEADER|cut -f 2 -d ","` | ||
192 | SCOPE=`echo $WWW_AUTH_HEADER|cut -f 3 -d ","` | ||
193 | |||
194 | TOKEN=`curl -f -sS $INSECURE -G --data-urlencode "service=${SERVICE}" --data-urlencode "scope=${SCOPE}" "${REALM}" -K- <<< $CREDENTIALS_STRING|jq .token|cut -f 2 -d "\""` | ||
195 | RESULT=$? | ||
196 | if [ $RESULT -ne 0 ] || [ "$TOKEN" == "" ]; then | ||
197 | # Run command again (without -f arg) and output message to std err | ||
198 | >&2 echo Auth server responded: | ||
199 | >&2 curl -sS $INSECURE -G --data-urlencode "service=${SERVICE}" --data-urlencode "scope=${SCOPE}" "${REALM}" -K- <<< $CREDENTIALS_STRING | ||
200 | if [ $RESULT -eq 0 ]; then | ||
201 | RESULT=42 | ||
202 | fi | ||
203 | exit $RESULT | ||
204 | fi | ||
205 | |||
206 | # We only use -f parameter if we are doing a ordinary delete request | ||
207 | # If we are doing raw request, we output both request and response ( including headers ) | ||
208 | if [ "$RAW_URL" = "" ]; then | ||
209 | CURL_ARG="-f " | ||
210 | else | ||
211 | CURL_ARG="-v " | ||
212 | fi | ||
213 | if [ "$CUSTOM_HEADER" == "" ]; then | ||
214 | curl $CURL_ARG -sS $INSECURE $CURL_HTTP_METHOD_OPTION $HTTP_METHOD -H "Authorization: Bearer $TOKEN" "${URL}" | ||
215 | RESULT=$? | ||
216 | if [ $RESULT -ne 0 ]; then | ||
217 | # Run command again (without -f arg) and output message to std err | ||
218 | >&2 curl -sS $INSECURE $CURL_HTTP_METHOD_OPTION $HTTP_METHOD -H "Authorization: Bearer $TOKEN" "${URL}" | ||
219 | exit $RESULT | ||
220 | fi | ||
221 | else | ||
222 | curl $CURL_ARG -i -sS $INSECURE $CURL_HTTP_METHOD_OPTION $HTTP_METHOD -H "$CUSTOM_HEADER" -H "Authorization: Bearer $TOKEN" "${URL}" | ||
223 | RESULT=$? | ||
224 | if [ $RESULT -ne 0 ]; then | ||
225 | # Run command again (without -f arg) and output message to std err | ||
226 | >&2 curl -i -sS $INSECURE $CURL_HTTP_METHOD_OPTION $HTTP_METHOD -H "$CUSTOM_HEADER" -H "Authorization: Bearer $TOKEN" "${URL}" | ||
227 | exit $RESULT | ||
228 | fi | ||
229 | fi | ||
230 | } | ||
231 | |||
232 | function getTags | ||
233 | { | ||
234 | TAGS=`sendRegistryRequest https://${HOST}/v2/${IMAGE}/tags/list GET |jq --compact-output .tags` | ||
235 | RESULT=$? | ||
236 | if [ "$TAGS" == "" ] || [ $RESULT -ne 0 ]; then | ||
237 | exit $RESULT | ||
238 | fi | ||
239 | # imagenames and tags cannot contain special characters or space. So let's just remove that JSON syntax | ||
240 | TAGS=${TAGS//\"} | ||
241 | TAGS=${TAGS//\[} | ||
242 | TAGS=${TAGS//\]} | ||
243 | TAGS=${TAGS//,/ } | ||
244 | } | ||
245 | |||
246 | # $1 is the tag you want to take backup of | ||
247 | # $2 is tag name used for backup (backup tag) | ||
248 | function backupLocalImage | ||
249 | { | ||
250 | # If the tag we are going to delete exists locally we need to take a backup of that | ||
251 | # then download the image from registry ( remote and local image may not match even though they have the same name ) | ||
252 | if [ `docker images --format "{{.Repository}}:{{.Tag}}" ${HOST}/${IMAGE}:$1| wc -l` -eq 1 ]; then | ||
253 | BACKUP_TAKEN="true" | ||
254 | docker tag ${HOST}/${IMAGE}:$1 ${HOST}/${IMAGE}:$2 | ||
255 | docker rmi ${HOST}/${IMAGE}:$1 | ||
256 | fi | ||
257 | } | ||
258 | |||
259 | # $1 is the tag you want to restore to | ||
260 | # $2 is the tag name used when taking the backup (backup tag) | ||
261 | function restoreBackup | ||
262 | { | ||
263 | if [ `docker images --format "{{.Repository}}:{{.Tag}}" ${HOST}/${IMAGE}:$2| wc -l` -eq 1 ]; then | ||
264 | # docker rmi ${HOST}/${IMAGE}:$1 | ||
265 | docker tag ${HOST}/${IMAGE}:$2 ${HOST}/${IMAGE}:$1 | ||
266 | docker rmi ${HOST}/${IMAGE}:$2 | ||
267 | fi | ||
268 | } | ||
269 | |||
270 | parseArguments "$@" | ||
271 | |||
272 | if [ "$RAW_URL" = "" ]; then | ||
273 | if [ "$TAG_ONLY" = "true" ]; then | ||
274 | getTags | ||
275 | backupLocalImage $TAG remove_image_from_registry1 | ||
276 | docker pull ${HOST}/${IMAGE}:${TAG} | ||
277 | fi | ||
278 | SHA_REQ=`sendRegistryRequest https://${HOST}/v2/${IMAGE}/manifests/${TAG} GET "Accept: application/vnd.docker.distribution.manifest.v2+json"` | ||
279 | RESULT=$? | ||
280 | if [ "$SHA_REQ" == "" ] || [ $RESULT -ne 0 ]; then | ||
281 | docker rmi ${HOST}/${IMAGE}:${TAG} | ||
282 | restoreBackup $TAG remove_image_from_registry1 | ||
283 | exit $RESULT | ||
284 | fi | ||
285 | |||
286 | SHA=$(echo "$SHA_REQ"|grep "Docker-Content-Digest:"|cut -f 2- -d ":"|tr -d '[:space:]') | ||
287 | sendRegistryRequest https://${HOST}/v2/${IMAGE}/manifests/${SHA} DELETE | ||
288 | if [ "$TAG_ONLY" = "true" ]; then | ||
289 | OLDTAGS="$TAGS" | ||
290 | getTags | ||
291 | TAGSPIPE="|${TAGS// /|}|" | ||
292 | |||
293 | for i in $OLDTAGS; do | ||
294 | # Clearly, we expect TAG to be gone | ||
295 | # We don't want to restore that one | ||
296 | if [ "$i" = "$TAG" ]; then | ||
297 | continue | ||
298 | fi | ||
299 | |||
300 | if [[ $TAGSPIPE != *"|$i|"* ]]; then | ||
301 | echo This tag needs to be restored : ${HOST}/${IMAGE}:${i} | ||
302 | backupLocalImage $i remove_image_from_registry2 | ||
303 | docker tag ${HOST}/${IMAGE}:${TAG} ${HOST}/${IMAGE}:${i} | ||
304 | docker push ${HOST}/${IMAGE}:${i} | ||
305 | docker rmi ${HOST}/${IMAGE}:${i} | ||
306 | restoreBackup $i remove_image_from_registry2 | ||
307 | fi | ||
308 | done | ||
309 | docker rmi ${HOST}/${IMAGE}:${TAG} | ||
310 | restoreBackup $TAG remove_image_from_registry1 | ||
311 | fi | ||
312 | else | ||
313 | sendRegistryRequest "https://${RAW_URL}" "${RAW_HTTP_METHOD}" "${RAW_HTTP_HEADER}" | ||
314 | fi | ||
315 | |||