Merge "Fix packagecloud_push.sh upload failure for vpp-ext-deps"
[ci-management.git] / docker / scripts / update_dockerhub_prod_tags.sh
1 #! /bin/bash
2
3 # Copyright (c) 2020 Cisco and/or its affiliates.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 set -euo pipefail
17 shopt -s extglob
18
19 # Log all output to stdout & stderr to a log file
20 logname="/tmp/$(basename $0).$(date +%Y_%m_%d_%H%M%S).log"
21 echo -e "\n*** Logging output to $logname ***\n"
22 exec > >(tee -a $logname) 2>&1
23
24 export CIMAN_DOCKER_SCRIPTS=${CIMAN_DOCKER_SCRIPTS:-"$(dirname $BASH_SOURCE)"}
25 . $CIMAN_DOCKER_SCRIPTS/lib_common.sh
26
27 # Global variables
28 long_bar="################################################################"
29 short_bar="-----"
30 image_not_found=""
31 image_user=""
32 image_repo=""
33 image_version=""
34 image_arch=""
35 image_name_prod=""
36 image_name_prev=""
37 image_name_new=""
38 image_realname=""
39 image_realname_prod=""
40 image_realname_prev=""
41 image_tags=""
42 image_tags_prod=""
43 image_tags_prev=""
44 image_tags_new=""
45 docker_id_prod=""
46 docker_id_prev=""
47 docker_id_new=""
48 digest_prod=""
49 digest_prev=""
50 digest_new=""
51 restore_cmd=""
52
53 usage() {
54     local script="$(basename $0)"
55     echo
56     echo "Usage: $script r[evert]  <prod image>"
57     echo "       $script p[romote] <new image> [<new image>]"
58     echo "       $script i[nspect] <prod image>"
59     echo
60     echo "  revert: swaps 'prod-<arch>' and 'prod-prev-<arch>' images"
61     echo "          <prod image>: e.g. fdiotools/builder-ubuntu1804:prod-x86_64"
62     echo
63     echo " promote: moves 'prod-<arch>' image to 'prod-prev-<arch>' tag and"
64     echo "          tags <new image> with 'prod-<arch>'"
65     echo "          <new image>: e.g. fdiotools/builder-ubuntu1804:2020_09_23_151655-x86_64"
66     echo " inspect: prints out all tags for prod-<arch> and prod-prev-<arch>"
67     echo
68     exit 1
69 }
70
71 echo_restore_cmd() {
72     echo -e "\n$long_bar\n"
73     echo "To restore tags to original state, issue the following command:"
74     echo -e "\n$restore_cmd\n\n$long_bar\n"
75 }
76
77 push_to_dockerhub() {
78     echo_restore_cmd
79     for image in "$@" ; do
80         set +e
81         echo "Pushing '$image' to docker hub..."
82         if ! docker push $image ; then
83             echo "ERROR: 'docker push $image' failed!"
84             exit 1
85         fi
86     done
87 }
88
89 parse_image_name() {
90     image_user="$(echo $1 | cut -d'/' -f1)"
91     image_repo="$(echo $1 | cut -d'/' -f2 | cut -d':' -f1)"
92     local tag="$(echo $1 | cut -d':' -f2)"
93     image_version="$(echo $tag | cut -d'-' -f1)"
94     image_arch="$(echo $tag | sed -e s/$image_version-//)"
95     image_name_new="${image_user}/${image_repo}:${image_version}-${image_arch}"
96     if [ "$1" != "$image_name_new" ] ; then
97         echo "ERROR: Image name parsing failed: $1 != '$image_name_new'"
98         usage
99     fi
100     if [[ "$image_version" =~ "prod" ]] ; then
101         image_name_new=""
102     fi
103     image_name_prod="${image_user}/${image_repo}:prod-${image_arch}"
104     image_name_prev="${image_user}/${image_repo}:prod-prev-${image_arch}"
105 }
106
107 format_image_tags() {
108     # Note: 'grep $image_arch' & grep -v 'prod-curr' is required due to a
109     #       bug in docker hub which returns old tags which were deleted via
110     #       the webUI, but are still retrieved by 'docker pull -a'
111     image_tags="$(docker images | grep $1 | grep $image_arch | grep -v prod-curr | sort -r | mawk '{print $1":"$2}' | tr '\n' ' ')"
112     image_realname="$(docker images | grep $1 | grep $image_arch | sort -r | grep -v prod | mawk '{print $1":"$2}')"
113 }
114
115 get_image_id_tags() {
116     for image in "$image_name_new" "$image_name_prod" "$image_name_prev" ; do
117         if [ -z "$image" ] ; then
118             continue
119         fi
120         # ensure image exists
121         set +e
122         local image_found="$(docker images | mawk '{print $1":"$2}' | grep $image)"
123         set -e
124         if [ -z "$image_found" ] ; then
125             if [ "$image" = "$image_name_prev" ] ; then
126                 if [ "$action" = "revert" ] ; then
127                     echo "ERROR: Image '$image' not found!"
128                     echo "Unable to revert production image '$image_name_prod'!"
129                     usage
130                 else
131                     continue
132                 fi
133             else
134                 echo "ERROR: Image '$image' not found!"
135                 usage
136             fi
137         fi
138         set +e
139         local id="$(docker image inspect $image | mawk -F':' '/Id/{print $3}')"
140         local digest="$(docker image inspect $image | grep -A1 RepoDigests | grep -v RepoDigests | mawk -F':' '{print $2}')"
141         local retval="$?"
142         set -e
143         if [ "$retval" -ne "0" ] ; then
144             echo "ERROR: Docker ID not found for '$image'!"
145             usage
146         fi
147         if [ "$image" = "$image_name_prod" ] ; then
148             docker_id_prod="${id::12}"
149             digest_prod="${digest::12}"
150             format_image_tags "$docker_id_prod"
151             image_tags_prod="$image_tags"
152             if [ -z "$image_realname_prod" ] ; then
153                 image_realname_prod="$image_realname"
154             fi
155         elif [ "$image" = "$image_name_prev" ] ; then
156             docker_id_prev="${id::12}"
157             digest_prev="${digest::12}"
158             format_image_tags "$docker_id_prev"
159             image_tags_prev="$image_tags"
160             if [ -z "$image_realname_prev" ] ; then
161                 image_realname_prev="$image_realname"
162             fi
163         else
164             docker_id_new="${id::12}"
165             digest_new="${digest::12}"
166             format_image_tags "$docker_id_new" "NEW"
167             image_tags_new="$image_tags"
168         fi
169     done
170     if [ -z "$restore_cmd" ] ; then
171         restore_cmd="sudo $0 p $image_realname_prev $image_realname_prod"
172     fi
173 }
174
175 get_all_tags_from_dockerhub() {
176     local dh_repo="$image_user/$image_repo"
177     echo -e "Pulling all tags from docker hub repo '$dh_repo':\n$long_bar"
178     if ! docker pull -a "$dh_repo" ; then
179         echo "ERROR: Repository '$dh_repo' not found on docker hub!"
180         usage
181     fi
182     echo "$long_bar"
183 }
184
185 verify_image_name() {
186     image_not_found=""
187     # Invalid user
188     if [ "$image_user" != "fdiotools" ] ; then
189         image_not_found="true"
190         echo "ERROR: invalid user '$image_user' in '$image_name_new'!"
191     fi
192     # Invalid version
193     if [ -z "$image_not_found" ] \
194            && [ "$image_version" != "prod" ] \
195            && ! [[ "$image_version" =~ \
196            ^[0-9]{4}_[0-1][0-9]_[0-3][0-9]_[0-2][0-9][0-5][0-9][0-5][0-9]$ ]]
197     then
198         image_not_found="true"
199         echo "ERROR: invalid version '$image_version' in '$image_name_new'!"
200     fi
201     # Invalid arch
202     if [ -z "$image_not_found" ] \
203            && ! [[ "$EXECUTOR_ARCHS" =~ .*"$image_arch".* ]] ; then
204         image_not_found="true"
205         echo "ERROR: invalid arch '$image_arch' in '$image_name_new'!"
206     fi
207     if [ -n "$image_not_found" ] ; then
208         echo "ERROR: Invalid image '$image_name_new'!"
209         usage
210     fi
211 }
212
213 docker_tag_image() {
214     echo ">>> docker tag $1 $2"
215     set +e
216     docker tag $1 $2
217     local retval="$?"
218     set -e
219     if [ "$retval" -ne "0" ] ; then
220         echo "WARNING: 'docker tag $1 $2' failed!"
221     fi
222 }
223
224 docker_rmi_tag() {
225     set +e
226     echo ">>> docker rmi $1"
227     docker rmi $1
228     local retval="$?"
229     set -e
230     if [ "$retval" -ne "0" ] ; then
231         echo "WARNING: 'docker rmi $1' failed!"
232     fi
233 }
234
235 print_image_list() {
236     if [ -z "$2" ] ; then
237         echo "$1 Image Not Found"
238         return
239     fi
240     echo "$1 (Id $2, Digest $3):"
241     for image in $4 ; do
242         echo -e "\t$image"
243     done
244 }
245
246 inspect_images() {
247     echo -e "\n${1}Production Docker Images:"
248     echo "$short_bar"
249     if [ -n "$image_tags_new" ] ; then
250         print_image_list "NEW" "$docker_id_new" "$digest_new" "$image_tags_new"
251         echo
252     fi
253     print_image_list "prod-$image_arch" "$docker_id_prod" "$digest_prod" \
254                      "$image_tags_prod"
255     echo
256     print_image_list "prod-prev-$image_arch" "$docker_id_prev" "$digest_prev" \
257                      "$image_tags_prev"
258     echo -e "$short_bar\n"
259 }
260
261 revert_prod_image() {
262     inspect_images "EXISTING "
263     docker_tag_image $docker_id_prod $image_name_prev
264     docker_tag_image $docker_id_prev $image_name_prod
265     get_image_id_tags
266     inspect_images "REVERTED "
267
268     local yn=""
269     while true; do
270         read -p "Push Reverted tags to '$image_user/$image_repo' (yes/no)? " yn
271         case ${yn:0:1} in
272             y|Y )
273                 break ;;
274             n|N )
275                 echo -e "\nABORTING REVERT!\n"
276                 docker_tag_image $docker_id_prev $image_name_prod
277                 docker_tag_image $docker_id_prod $image_name_prev
278                 get_image_id_tags
279                 inspect_images "RESTORED LOCAL "
280                 exit 1 ;;
281             * )
282                 echo "Please answer yes or no." ;;
283         esac
284     done
285     echo
286     push_to_dockerhub $image_name_prev $image_name_prod
287     inspect_images ""
288     echo_restore_cmd
289 }
290
291 promote_new_image() {
292     inspect_images "EXISTING "
293     docker_tag_image $docker_id_prod $image_name_prev
294     docker_tag_image $docker_id_new $image_name_prod
295     get_image_id_tags
296     inspect_images "PROMOTED "
297
298     local yn=""
299     while true; do
300         read -p "Push promoted tags to '$image_user/$image_repo' (yes/no)? " yn
301         case ${yn:0:1} in
302             y|Y )
303                 break ;;
304             n|N )
305                 echo -e "\nABORTING PROMOTION!\n"
306                 docker_tag_image $docker_id_prev $image_name_prod
307                 local restore_both="$(echo $restore_cmd | mawk '{print $5}')"
308                 if [[ -n "$restore_both" ]] ; then
309                     docker_tag_image $image_realname_prev $image_name_prev
310                 else
311                     docker_rmi_tag $image_name_prev
312                     image_name_prev=""
313                     docker_id_prev=""
314                 fi
315                 get_image_id_tags
316                 inspect_images "RESTORED "
317                 exit 1 ;;
318             * )
319                 echo "Please answer yes or no." ;;
320         esac
321     done
322     echo
323     push_to_dockerhub $image_name_new $image_name_prev $image_name_prod
324     inspect_images ""
325     echo_restore_cmd
326 }
327
328 must_be_run_as_root
329
330 # Validate arguments
331 num_args="$#"
332 if [ "$num_args" -lt "1" ] ; then
333     usage
334 fi
335 action=""
336 case "$1" in
337     r?(evert))
338         action="revert"
339         if [ "$num_args" -ne "2" ] ; then
340             echo "ERROR: Invalid number of arguments: $#"
341             usage
342         fi ;;
343     p?(romote))
344         if [ "$num_args" -eq "2" ] || [ "$num_args" -eq "3" ] ; then
345             action="promote"
346         else
347             echo "ERROR: Invalid number of arguments: $#"
348             usage
349         fi ;;
350     i?(nspect))
351         action="inspect"
352         if [ "$num_args" -ne "2" ] ; then
353             echo "ERROR: Invalid number of arguments: $#"
354             usage
355         fi ;;
356     *)
357         echo "ERROR: Invalid option '$1'!"
358         usage ;;
359 esac
360 shift
361 docker login >& /dev/null
362
363 # Update local tags
364 tags_to_push=""
365 for image in "$@" ; do
366     parse_image_name "$image"
367     verify_image_name "$image"
368     get_all_tags_from_dockerhub
369     get_image_id_tags
370     if [ "$action" = "promote" ] ; then
371         if [ -n "$image_name_new" ] ; then
372             promote_new_image
373         else
374             echo "ERROR: No new image specified to promote!"
375             usage
376         fi
377     elif [ "$action" = "revert" ] ; then
378         if [ "$image_version" = "prod" ] ; then
379             revert_prod_image
380         else
381             echo "ERROR: Non-production image '$image' specified!"
382             usage
383         fi
384     else
385         if [ "$image_version" = "prod" ] ; then
386             inspect_images ""
387         else
388             echo "ERROR: Non-production image '$image' specified!"
389             usage
390         fi
391     fi
392 done