/
_build_images.sh
1091 lines (994 loc) · 48.3 KB
/
_build_images.sh
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
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env bash
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# For remote installation of airflow (from GitHub or PyPI) when building the image, you need to
# pass build flags depending on the version and method of the installation (for example to
# get proper requirement constraint files)
function build_images::add_build_args_for_remote_install() {
# entrypoint is used as AIRFLOW_SOURCES_FROM/TO in order to avoid costly copying of all sources of
# Airflow - those are not needed for remote install at all. Entrypoint is later overwritten by
EXTRA_DOCKER_PROD_BUILD_FLAGS+=(
"--build-arg" "AIRFLOW_SOURCES_FROM=empty"
"--build-arg" "AIRFLOW_SOURCES_TO=/empty"
)
if [[ ${CI} == "true" ]]; then
EXTRA_DOCKER_PROD_BUILD_FLAGS+=(
"--build-arg" "PIP_PROGRESS_BAR=off"
)
fi
if [[ -n "${AIRFLOW_CONSTRAINTS_REFERENCE}" ]]; then
EXTRA_DOCKER_PROD_BUILD_FLAGS+=(
"--build-arg" "AIRFLOW_CONSTRAINTS_REFERENCE=${AIRFLOW_CONSTRAINTS_REFERENCE}"
)
else
if [[ ${AIRFLOW_VERSION} =~ v?2.* ]]; then
EXTRA_DOCKER_PROD_BUILD_FLAGS+=(
# For specified minor version of 2.0 or v2 branch use specific reference constraints
"--build-arg" "AIRFLOW_CONSTRAINTS_REFERENCE=constraints-${AIRFLOW_VERSION}"
)
else
# For all other we just get the default constraint branch coming from the _initialization.sh
EXTRA_DOCKER_PROD_BUILD_FLAGS+=(
"--build-arg" "AIRFLOW_CONSTRAINTS_REFERENCE=${DEFAULT_CONSTRAINTS_BRANCH}"
)
fi
fi
if [[ -n "${AIRFLOW_CONSTRAINTS_LOCATION}" ]]; then
EXTRA_DOCKER_PROD_BUILD_FLAGS+=(
"--build-arg" "AIRFLOW_CONSTRAINTS_LOCATION=${AIRFLOW_CONSTRAINTS_LOCATION}"
)
fi
# Depending on the version built, we choose the right branch for preloading the packages from
# For v2-*-test we choose v2-*-test
# all other builds when you choose a specific version (1.0, 2.0, 2.1. series) should choose stable branch
# to preload. For all other builds we use the default branch defined in _initialization.sh
# TODO: Generalize me
if [[ ${AIRFLOW_VERSION} == 'v2-0-test' ]]; then
AIRFLOW_BRANCH_FOR_PYPI_PRELOADING="v2-0-test"
elif [[ ${AIRFLOW_VERSION} == 'v2-1-test' ]]; then
AIRFLOW_BRANCH_FOR_PYPI_PRELOADING="v2-1-test"
elif [[ ${AIRFLOW_VERSION} =~ v?2\.0* ]]; then
AIRFLOW_BRANCH_FOR_PYPI_PRELOADING="v2-0-stable"
elif [[ ${AIRFLOW_VERSION} =~ v?2\.1* ]]; then
AIRFLOW_BRANCH_FOR_PYPI_PRELOADING="v2-1-stable"
else
AIRFLOW_BRANCH_FOR_PYPI_PRELOADING=${DEFAULT_BRANCH}
fi
}
# Retrieves version of airflow stored in the production image (used to display the actual
# Version we use if it was build from PyPI or GitHub
function build_images::get_airflow_version_from_production_image() {
docker run --entrypoint /bin/bash "${AIRFLOW_PROD_IMAGE}" -c 'echo "${AIRFLOW_VERSION}"'
}
# Removes the "Forced answer" (yes/no/quit) given previously, unless you specifically want to remember it.
#
# This is the default behaviour of all rebuild scripts to ask independently whether you want to
# rebuild the image or not. Sometimes however we want to remember answer previously given. For
# example if you answered "no" to rebuild the image, the assumption is that you do not
# want to rebuild image also for other rebuilds in the same pre-commit execution.
#
# All the pre-commit checks therefore have `export REMEMBER_LAST_ANSWER="true"` set
# So that in case they are run in a sequence of commits they will not rebuild. Similarly if your most
# recent answer was "no" and you run `pre-commit run mypy` (for example) it will also reuse the
# "no" answer given previously. This happens until you run any of the breeze commands or run all
# pre-commits `pre-commit run` - then the "LAST_FORCE_ANSWER_FILE" will be removed and you will
# be asked again.
function build_images::forget_last_answer() {
if [[ ${REMEMBER_LAST_ANSWER:="false"} != "true" ]]; then
verbosity::print_info
verbosity::print_info "Forgetting last answer from ${LAST_FORCE_ANSWER_FILE}:"
verbosity::print_info
rm -f "${LAST_FORCE_ANSWER_FILE}"
else
if [[ -f "${LAST_FORCE_ANSWER_FILE}" ]]; then
verbosity::print_info
verbosity::print_info "Still remember last answer from ${LAST_FORCE_ANSWER_FILE}:"
verbosity::print_info "$(cat "${LAST_FORCE_ANSWER_FILE}")"
verbosity::print_info
fi
fi
}
function build_images::confirm_via_terminal() {
echo >"${DETECTED_TERMINAL}"
echo >"${DETECTED_TERMINAL}"
echo "${COLOR_YELLOW}WARNING:Make sure that you rebased to latest upstream before rebuilding!${COLOR_RESET}" >"${DETECTED_TERMINAL}"
echo >"${DETECTED_TERMINAL}"
# Make sure to use output of tty rather than stdin/stdout when available - this way confirm
# will works also in case of pre-commits (git does not pass stdin/stdout to pre-commit hooks)
# shellcheck disable=SC2094
"${AIRFLOW_SOURCES}/confirm" "${ACTION} image ${THE_IMAGE_TYPE}-python${PYTHON_MAJOR_MINOR_VERSION}" \
<"${DETECTED_TERMINAL}" >"${DETECTED_TERMINAL}"
RES=$?
}
# Confirms if the image should be rebuild and interactively checks it with the user.
# In case iit needs to be rebuild. It only ask the user if it determines that the rebuild
# is needed and that the rebuild is not already forced. It asks the user using available terminals
# So that the script works also from within pre-commit run via git hooks - where stdin is not
# available - it tries to find usable terminal and ask the user via this terminal.
function build_images::confirm_image_rebuild() {
ACTION="rebuild"
if [[ ${FORCE_PULL_IMAGES:=} == "true" ]]; then
ACTION="pull and rebuild"
fi
if [[ -f "${LAST_FORCE_ANSWER_FILE}" ]]; then
# set variable from last answered response given in the same pre-commit run - so that it can be
# answered in the first pre-commit check (build) and then used in another (pylint/mypy/flake8 etc).
# shellcheck disable=SC1090
source "${LAST_FORCE_ANSWER_FILE}"
fi
set +e
local RES
if [[ ${CI:="false"} == "true" ]]; then
verbosity::print_info
verbosity::print_info "CI environment - forcing rebuild for image ${THE_IMAGE_TYPE}."
verbosity::print_info
RES="0"
elif [[ -n "${FORCE_ANSWER_TO_QUESTIONS=}" ]]; then
verbosity::print_info
verbosity::print_info "Forcing answer '${FORCE_ANSWER_TO_QUESTIONS}'"
verbosity::print_info
case "${FORCE_ANSWER_TO_QUESTIONS}" in
[yY][eE][sS] | [yY])
RES="0"
;;
[qQ][uU][iI][tT] | [qQ])
RES="2"
;;
*)
RES="1"
;;
esac
elif [[ -t 0 ]]; then
echo
echo
echo "${COLOR_YELLOW}WARNING:Make sure that you rebased to latest upstream before rebuilding!${COLOR_RESET}"
echo
# Check if this script is run interactively with stdin open and terminal attached
"${AIRFLOW_SOURCES}/confirm" "${ACTION} image ${THE_IMAGE_TYPE}-python${PYTHON_MAJOR_MINOR_VERSION}"
RES=$?
elif [[ ${DETECTED_TERMINAL:=$(tty)} != "not a tty" ]]; then
export DETECTED_TERMINAL
build_images::confirm_via_terminal
elif [[ -c /dev/tty ]]; then
export DETECTED_TERMINAL=/dev/tty
build_images::confirm_via_terminal
else
verbosity::print_info
verbosity::print_info "No terminal, no stdin - quitting"
verbosity::print_info
# No terminal, no stdin, no force answer - quitting!
RES="2"
fi
set -e
if [[ ${RES} == "1" ]]; then
verbosity::print_info
verbosity::print_info "Skipping rebuilding the image ${THE_IMAGE_TYPE}-python${PYTHON_MAJOR_MINOR_VERSION}"
verbosity::print_info
export SKIP_REBUILD="true"
# Force "no" also to subsequent questions so that if you answer it once, you are not asked
# For all other pre-commits and you will continue using the images you already have
export FORCE_ANSWER_TO_QUESTIONS="no"
echo 'export FORCE_ANSWER_TO_QUESTIONS="no"' >"${LAST_FORCE_ANSWER_FILE}"
elif [[ ${RES} == "2" ]]; then
echo
echo "${COLOR_RED}ERROR: The ${THE_IMAGE_TYPE} needs to be rebuilt - it is outdated. ${COLOR_RESET}"
echo """
Make sure you build the images bu running
./breeze --python ${PYTHON_MAJOR_MINOR_VERSION} build-image
If you run it via pre-commit as individual hook, you can run 'pre-commit run build'.
"""
exit 1
else
# Force "yes" also to subsequent questions
export FORCE_ANSWER_TO_QUESTIONS="yes"
fi
}
function build_images::check_for_docker_context_files() {
local num_docker_context_files
local docker_context_files_dir="${AIRFLOW_SOURCES}/docker-context-files/"
num_docker_context_files=$(find "${docker_context_files_dir}" -type f | grep -c -v "README.md" || true)
if [[ ${num_docker_context_files} == "0" ]]; then
if [[ ${INSTALL_FROM_DOCKER_CONTEXT_FILES} != "false" ]]; then
echo
echo "${COLOR_YELLOW}ERROR! You want to install packages from docker-context-files${COLOR_RESET}"
echo "${COLOR_YELLOW} but there are no packages to install in this folder.${COLOR_RESET}"
echo
exit 1
fi
else
if [[ ${INSTALL_FROM_DOCKER_CONTEXT_FILES} == "false" ]]; then
echo
echo "${COLOR_YELLOW}ERROR! There are some extra files in docker-context-files except README.md${COLOR_RESET}"
echo "${COLOR_YELLOW} And you did not choose --install-from-docker-context-files flag${COLOR_RESET}"
echo "${COLOR_YELLOW} This might result in unnecessary cache invalidation and long build times${COLOR_RESET}"
echo "${COLOR_YELLOW} Exiting now - please restart the command with --cleanup-docker-context-files switch${COLOR_RESET}"
echo
exit 2
fi
fi
}
# Builds local image manifest
# It contains only one .json file - result of docker inspect - describing the image
# We cannot use docker registry APIs as they are available only with authorisation
# But this image can be pulled without authentication
function build_images::build_ci_image_manifest() {
docker_v build \
--tag="${AIRFLOW_CI_LOCAL_MANIFEST_IMAGE}" \
-f- . <<EOF
FROM scratch
COPY "manifests/local-build-cache-hash" /build-cache-hash
CMD ""
EOF
}
#
# Retrieves information about build cache hash random file from the local image
#
function build_images::get_local_build_cache_hash() {
set +e
# Remove the container just in case
docker_v rm --force "local-airflow-ci-container" 2>/dev/null >/dev/null
if ! docker_v inspect "${AIRFLOW_CI_IMAGE}" 2>/dev/null >/dev/null; then
verbosity::print_info
verbosity::print_info "Local airflow CI image not available"
verbosity::print_info
LOCAL_MANIFEST_IMAGE_UNAVAILABLE="true"
export LOCAL_MANIFEST_IMAGE_UNAVAILABLE
touch "${LOCAL_IMAGE_BUILD_CACHE_HASH_FILE}"
return
fi
docker_v create --name "local-airflow-ci-container" "${AIRFLOW_CI_IMAGE}" 2>/dev/null
docker_v cp "local-airflow-ci-container:/build-cache-hash" \
"${LOCAL_IMAGE_BUILD_CACHE_HASH_FILE}" 2>/dev/null ||
touch "${LOCAL_IMAGE_BUILD_CACHE_HASH_FILE}"
set -e
verbosity::print_info
verbosity::print_info "Local build cache hash: '$(cat "${LOCAL_IMAGE_BUILD_CACHE_HASH_FILE}")'"
verbosity::print_info
}
# Retrieves information about the build cache hash random file from the remote image.
# We actually use manifest image for that, which is a really, really small image to pull!
# The problem is that inspecting information about remote image cannot be done easily with existing APIs
# of Dockerhub because they require additional authentication even for public images.
# Therefore instead we are downloading a specially prepared manifest image
# which is built together with the main image and pushed with it. This special manifest image is prepared
# during building of the main image and contains single file which is randomly built during the docker
# build in the right place in the image (right after installing all dependencies of Apache Airflow
# for the first time). When this random file gets regenerated it means that either base image has
# changed or some of the earlier layers was modified - which means that it is usually faster to pull
# that image first and then rebuild it - because this will likely be faster
function build_images::get_remote_image_build_cache_hash() {
set +e
# Pull remote manifest image
if ! docker_v pull "${AIRFLOW_CI_REMOTE_MANIFEST_IMAGE}" 2>/dev/null >/dev/null; then
verbosity::print_info
verbosity::print_info "Remote docker registry unreachable"
verbosity::print_info
REMOTE_DOCKER_REGISTRY_UNREACHABLE="true"
export REMOTE_DOCKER_REGISTRY_UNREACHABLE
touch "${REMOTE_IMAGE_BUILD_CACHE_HASH_FILE}"
return
fi
set -e
rm -f "${REMOTE_IMAGE_CONTAINER_ID_FILE}"
# Create container dump out of the manifest image without actually running it
docker_v create --cidfile "${REMOTE_IMAGE_CONTAINER_ID_FILE}" "${AIRFLOW_CI_REMOTE_MANIFEST_IMAGE}"
# Extract manifest and store it in local file
docker_v cp "$(cat "${REMOTE_IMAGE_CONTAINER_ID_FILE}"):/build-cache-hash" \
"${REMOTE_IMAGE_BUILD_CACHE_HASH_FILE}"
docker_v rm --force "$(cat "${REMOTE_IMAGE_CONTAINER_ID_FILE}")"
rm -f "${REMOTE_IMAGE_CONTAINER_ID_FILE}"
verbosity::print_info
verbosity::print_info "Remote build cache hash: '$(cat "${REMOTE_IMAGE_BUILD_CACHE_HASH_FILE}")'"
verbosity::print_info
}
# Compares layers from both remote and local image and set FORCE_PULL_IMAGES to true in case
# More than the last NN layers are different.
function build_images::compare_local_and_remote_build_cache_hash() {
set +e
local remote_hash
remote_hash=$(cat "${REMOTE_IMAGE_BUILD_CACHE_HASH_FILE}")
local local_hash
local_hash=$(cat "${LOCAL_IMAGE_BUILD_CACHE_HASH_FILE}")
if [[ ${remote_hash} != "${local_hash}" || -z ${local_hash} ]] \
; then
echo
echo
echo "Your image and the dockerhub have different or missing build cache hashes."
echo "Local hash: '${local_hash}'. Remote hash: '${remote_hash}'."
echo
echo "Forcing pulling the images. It will be faster than rebuilding usually."
echo "You can avoid it by setting SKIP_CHECK_REMOTE_IMAGE to true"
echo
export FORCE_PULL_IMAGES="true"
else
echo
echo "No need to pull the image. Yours and remote cache hashes are the same!"
echo
fi
set -e
}
# Prints summary of the build parameters
function build_images::print_build_info() {
verbosity::print_info
verbosity::print_info "Airflow ${AIRFLOW_VERSION} Python: ${PYTHON_MAJOR_MINOR_VERSION}. Image description: ${IMAGE_DESCRIPTION}"
verbosity::print_info
}
function build_images::get_docker_image_names() {
# python image version to use
export PYTHON_BASE_IMAGE_VERSION=${PYTHON_BASE_IMAGE_VERSION:=${PYTHON_MAJOR_MINOR_VERSION}}
# Python base image to use
export PYTHON_BASE_IMAGE="python:${PYTHON_BASE_IMAGE_VERSION}-slim-buster"
# CI image base tag
export AIRFLOW_CI_BASE_TAG="${BRANCH_NAME}-python${PYTHON_MAJOR_MINOR_VERSION}-ci"
# CI image to build
export AIRFLOW_CI_IMAGE="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:${AIRFLOW_CI_BASE_TAG}"
# Default CI image
export AIRFLOW_PYTHON_BASE_IMAGE="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:python${PYTHON_MAJOR_MINOR_VERSION}-${BRANCH_NAME}"
# CI image to build
export AIRFLOW_CI_IMAGE="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:${AIRFLOW_CI_BASE_TAG}"
# Base production image tag - used to build kubernetes tag as well
if [[ -z "${FORCE_AIRFLOW_PROD_BASE_TAG=}" ]]; then
export AIRFLOW_PROD_BASE_TAG="${BRANCH_NAME}-python${PYTHON_MAJOR_MINOR_VERSION}"
else
export AIRFLOW_PROD_BASE_TAG="${FORCE_AIRFLOW_PROD_BASE_TAG}"
fi
# PROD image to build
export AIRFLOW_PROD_IMAGE="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:${AIRFLOW_PROD_BASE_TAG}"
# PROD build segment
export AIRFLOW_PROD_BUILD_IMAGE="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:${AIRFLOW_PROD_BASE_TAG}-build"
# PROD Kubernetes image to build
export AIRFLOW_PROD_IMAGE_KUBERNETES="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:${AIRFLOW_PROD_BASE_TAG}-kubernetes"
# PROD default image
export AIRFLOW_PROD_IMAGE_DEFAULT="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:${BRANCH_NAME}"
# File that is touched when the CI image is built for the first time locally
export BUILT_CI_IMAGE_FLAG_FILE="${BUILD_CACHE_DIR}/${BRANCH_NAME}/.built_${PYTHON_MAJOR_MINOR_VERSION}"
local image_name
image_name="${GITHUB_REGISTRY}/$(get_github_container_registry_image_prefix)"
local image_separator
if [[ ${GITHUB_REGISTRY} == "ghcr.io" ]]; then
image_separator="-"
elif [[ ${GITHUB_REGISTRY} == "docker.pkg.github.com" ]]; then
image_separator="/"
else
echo
echo "${COLOR_RED}ERROR: Bad value of '${GITHUB_REGISTRY}'. Should be either 'ghcr.io' or 'docker.pkg.github.com'!${COLOR_RESET}"
echo
exit 1
fi
# Example:
# docker.pkg.github.com/apache/airflow/main-python3.6-v2
# ghcr.io/apache/airflow-v2-1-test-python-v2:3.6-slim-buster
# ghcr.io/apache/airflow-python-v2:3.6-slim-buster-<COMMIT_SHA>
export GITHUB_REGISTRY_AIRFLOW_PROD_IMAGE="${image_name}${image_separator}${AIRFLOW_PROD_BASE_TAG}${GITHUB_REGISTRY_IMAGE_SUFFIX}"
# Example:
# docker.pkg.github.com/apache/airflow/main-python3.6-build-v2
# ghcr.io/apache/airflow-main-python3.6-build-v2
export GITHUB_REGISTRY_AIRFLOW_PROD_BUILD_IMAGE="${image_name}${image_separator}${AIRFLOW_PROD_BASE_TAG}-build${GITHUB_REGISTRY_IMAGE_SUFFIX}"
# Example:
# docker.pkg.github.com/apache/airflow/python-v2:3.6-slim-buster
# ghcr.io/apache/airflow-python-v2:3.6-slim-buster
# ghcr.io/apache/airflow-python-v2:3.6-slim-buster-<COMMIT_SHA>
export GITHUB_REGISTRY_PYTHON_BASE_IMAGE="${image_name}${image_separator}python${GITHUB_REGISTRY_IMAGE_SUFFIX}:${PYTHON_BASE_IMAGE_VERSION}-slim-buster"
# Example:
# docker.pkg.github.com/apache/airflow/main-python3.8-ci-v2
export GITHUB_REGISTRY_AIRFLOW_CI_IMAGE="${image_name}${image_separator}${AIRFLOW_CI_BASE_TAG}${GITHUB_REGISTRY_IMAGE_SUFFIX}"
}
# If GitHub Registry is used, login to the registry using GITHUB_USERNAME and
# GITHUB_TOKEN. In case Personal Access token is not set, skip logging in
# Also enable experimental features of docker (we need `docker manifest` command)
function build_images::configure_docker_registry() {
if [[ ${USE_GITHUB_REGISTRY} == "true" ]]; then
local token="${GITHUB_TOKEN}"
if [[ -z "${token}" ]] ; then
verbosity::print_info
verbosity::print_info "Skip logging in to GitHub Registry. No Token available!"
verbosity::print_info
fi
if [[ -n "${token}" ]]; then
echo "${token}" | docker_v login \
--username "${GITHUB_USERNAME:-apache}" \
--password-stdin \
"${GITHUB_REGISTRY}"
else
verbosity::print_info "Skip Login to GitHub Registry ${GITHUB_REGISTRY} as token is missing"
fi
local new_config
new_config=$(jq '.experimental = "enabled"' "${HOME}/.docker/config.json")
echo "${new_config}" > "${HOME}/.docker/config.json"
fi
}
# Prepares all variables needed by the CI build. Depending on the configuration used (python version
# DockerHub user etc. the variables are set so that other functions can use those variables.
function build_images::prepare_ci_build() {
export AIRFLOW_CI_LOCAL_MANIFEST_IMAGE="local/${DOCKERHUB_REPO}:${AIRFLOW_CI_BASE_TAG}-manifest"
export AIRFLOW_CI_REMOTE_MANIFEST_IMAGE="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:${AIRFLOW_CI_BASE_TAG}-manifest"
export THE_IMAGE_TYPE="CI"
export IMAGE_DESCRIPTION="Airflow CI"
# Those constants depend on the type of image run so they are only made constants here
export AIRFLOW_EXTRAS="${AIRFLOW_EXTRAS:="${DEFAULT_CI_EXTRAS}"}"
readonly AIRFLOW_EXTRAS
export AIRFLOW_IMAGE="${AIRFLOW_CI_IMAGE}"
readonly AIRFLOW_IMAGE
build_images::configure_docker_registry
sanity_checks::go_to_airflow_sources
permissions::fix_group_permissions
}
# Only rebuilds CI image if needed. It checks if the docker image build is needed
# because any of the important source files (from scripts/ci/libraries/_initialization.sh) has
# changed or in any of the edge cases (docker image removed, .build cache removed etc.
# In case rebuild is needed, it determines (by comparing layers in local and remote image)
# Whether pull is needed before rebuild.
function build_images::rebuild_ci_image_if_needed() {
verbosity::print_info
verbosity::print_info "Checking if pull or just build for ${THE_IMAGE_TYPE} is needed."
verbosity::print_info
if [[ -f "${BUILT_CI_IMAGE_FLAG_FILE}" ]]; then
verbosity::print_info
verbosity::print_info "${THE_IMAGE_TYPE} image already built locally."
verbosity::print_info
else
verbosity::print_info
verbosity::print_info "${THE_IMAGE_TYPE} image not built locally: pulling and building"
verbosity::print_info
export FORCE_PULL_IMAGES="true"
export FORCE_BUILD_IMAGES="true"
fi
if [[ ${CHECK_IMAGE_FOR_REBUILD} == "false" ]]; then
verbosity::print_info
verbosity::print_info "Skip checking for rebuilds of the CI image but checking if it needs to be pulled"
verbosity::print_info
push_pull_remove_images::pull_ci_images_if_needed
return
fi
local needs_docker_build="false"
md5sum::check_if_docker_build_is_needed
build_images::get_local_build_cache_hash
if [[ ${needs_docker_build} == "true" ]]; then
if [[ ${SKIP_CHECK_REMOTE_IMAGE:=} != "true" && ${DOCKER_CACHE} == "pulled" ]]; then
# Check if remote image is different enough to force pull
# This is an optimisation pull vs. build time. When there
# are enough changes (specifically after setup.py changes) it is faster to pull
# and build the image rather than just build it
echo
echo "Checking if the remote image needs to be pulled"
echo
build_images::get_remote_image_build_cache_hash
if [[ ${REMOTE_DOCKER_REGISTRY_UNREACHABLE:=} != "true" && ${LOCAL_MANIFEST_IMAGE_UNAVAILABLE:=} != "true" ]]; then
build_images::compare_local_and_remote_build_cache_hash
else
FORCE_PULL_IMAGES="true"
fi
fi
SKIP_REBUILD="false"
if [[ ${CI:=} != "true" && "${FORCE_BUILD:=}" != "true" ]]; then
build_images::confirm_image_rebuild
fi
if [[ ${SKIP_REBUILD} != "true" ]]; then
local system
system=$(uname -s)
if [[ ${system} != "Darwin" ]]; then
local root_files_count
root_files_count=$(find "airflow" "tests" -user root | wc -l | xargs)
if [[ ${root_files_count} != "0" ]]; then
./scripts/ci/tools/ci_fix_ownership.sh || true
fi
fi
verbosity::print_info
verbosity::print_info "Build start: ${THE_IMAGE_TYPE} image."
verbosity::print_info
build_images::build_ci_image
build_images::get_local_build_cache_hash
md5sum::update_all_md5
build_images::build_ci_image_manifest
verbosity::print_info
verbosity::print_info "Build completed: ${THE_IMAGE_TYPE} image."
verbosity::print_info
fi
else
verbosity::print_info
verbosity::print_info "No need to build - none of the important files changed: ${FILES_FOR_REBUILD_CHECK[*]}"
verbosity::print_info
fi
}
function build_images::rebuild_ci_image_if_needed_with_group() {
start_end::group_start "Check if CI image build is needed"
build_images::rebuild_ci_image_if_needed
start_end::group_end
}
# Interactive version of confirming the ci image that is used in pre-commits
# it displays additional information - what the user should do in order to bring the local images
# back to state that pre-commit will be happy with
function build_images::rebuild_ci_image_if_needed_and_confirmed() {
local needs_docker_build="false"
THE_IMAGE_TYPE="CI"
md5sum::check_if_docker_build_is_needed
if [[ ${needs_docker_build} == "true" ]]; then
verbosity::print_info
verbosity::print_info "Docker image build is needed!"
verbosity::print_info
else
verbosity::print_info
verbosity::print_info "Docker image build is not needed!"
verbosity::print_info
fi
if [[ "${needs_docker_build}" == "true" ]]; then
echo
echo "Some of your images need to be rebuild because important files (like package list) has changed."
echo
echo "You have those options:"
echo " * Rebuild the images now by answering 'y' (this might take some time!)"
echo " * Skip rebuilding the images and hope changes are not big (you will be asked again)"
echo " * Quit and manually rebuild the images using one of the following commands"
echo " * ./breeze build-image"
echo " * ./breeze build-image --force-pull-images"
echo
echo " The first command works incrementally from your last local build."
echo " The second command you use if you want to completely refresh your images from dockerhub."
echo
SKIP_REBUILD="false"
build_images::confirm_image_rebuild
if [[ ${SKIP_REBUILD} != "true" ]]; then
build_images::rebuild_ci_image_if_needed
fi
fi
}
# Retrieves GitHub Container Registry image prefix from repository name
# GitHub Container Registry stores all images at the organization level, they are just
# linked to the repository via docker label - however we assume a convention where we will
# add repository name to organisation separated by '-' and convert everything to lowercase
# this is because in order for it to work for internal PR for users or other organisation's
# repositories, the other organisations and repositories can be uppercase
# container registry image name has to be lowercase
function get_github_container_registry_image_prefix() {
echo "${GITHUB_REPOSITORY}" | tr '[:upper:]' '[:lower:]'
}
# Builds CI image - depending on the caching strategy (pulled, local, disabled) it
# passes the necessary docker build flags via DOCKER_CACHE_CI_DIRECTIVE array
# it also passes the right Build args depending on the configuration of the build
# selected by Breeze flags or environment variables.
function build_images::build_ci_image() {
local spin_pid
build_images::print_build_info
if [[ -n ${DETECTED_TERMINAL=} ]]; then
echo -n "Preparing ${AIRFLOW_CI_IMAGE}.
" >"${DETECTED_TERMINAL}"
spinner::spin "${OUTPUT_LOG}" &
spin_pid=$!
# shellcheck disable=SC2064,SC2016
traps::add_trap '$(kill '${spin_pid}' || true)' EXIT HUP INT TERM
fi
push_pull_remove_images::pull_ci_images_if_needed
if [[ "${DOCKER_CACHE}" == "disabled" ]]; then
export DOCKER_CACHE_CI_DIRECTIVE=("--no-cache")
elif [[ "${DOCKER_CACHE}" == "local" ]]; then
export DOCKER_CACHE_CI_DIRECTIVE=()
elif [[ "${DOCKER_CACHE}" == "pulled" ]]; then
export DOCKER_CACHE_CI_DIRECTIVE=(
"--cache-from" "${AIRFLOW_CI_IMAGE}"
)
else
echo
echo "${COLOR_RED}ERROR: The ${DOCKER_CACHE} cache is unknown! ${COLOR_RESET}"
echo
exit 1
fi
EXTRA_DOCKER_CI_BUILD_FLAGS=(
)
if [[ ${CI} == "true" ]]; then
EXTRA_DOCKER_PROD_BUILD_FLAGS+=(
"--build-arg" "PIP_PROGRESS_BAR=off"
)
fi
if [[ -n "${AIRFLOW_CONSTRAINTS_LOCATION}" ]]; then
EXTRA_DOCKER_CI_BUILD_FLAGS+=(
"--build-arg" "AIRFLOW_CONSTRAINTS_LOCATION=${AIRFLOW_CONSTRAINTS_LOCATION}"
)
fi
if [[ -n ${spin_pid=} ]]; then
kill -HUP "${spin_pid}" || true
wait "${spin_pid}" || true
echo >"${DETECTED_TERMINAL}"
fi
if [[ -n ${DETECTED_TERMINAL=} ]]; then
echo -n "Preparing ${AIRFLOW_CI_IMAGE}.
" >"${DETECTED_TERMINAL}"
spinner::spin "${OUTPUT_LOG}" &
spin_pid=$!
# shellcheck disable=SC2064,SC2016
traps::add_trap '$(kill '${spin_pid}' || true)' EXIT HUP INT TERM
fi
if [[ -n ${DETECTED_TERMINAL=} ]]; then
echo -n "
Docker building ${AIRFLOW_CI_IMAGE}.
" >"${DETECTED_TERMINAL}"
fi
set +u
local additional_dev_args=()
if [[ -n "${DEV_APT_DEPS}" ]]; then
additional_dev_args+=("--build-arg" "DEV_APT_DEPS=\"${DEV_APT_DEPS}\"")
fi
if [[ -n "${DEV_APT_COMMAND}" ]]; then
additional_dev_args+=("--build-arg" "DEV_APT_COMMAND=\"${DEV_APT_COMMAND}\"")
fi
local additional_runtime_args=()
if [[ -n "${RUNTIME_APT_DEPS}" ]]; then
additional_runtime_args+=("--build-arg" "RUNTIME_APT_DEPS=\"${RUNTIME_APT_DEPS}\"")
fi
if [[ -n "${RUNTIME_APT_COMMAND}" ]]; then
additional_runtime_args+=("--build-arg" "RUNTIME_APT_COMMAND=\"${RUNTIME_APT_COMMAND}\"")
fi
docker_v build \
"${EXTRA_DOCKER_CI_BUILD_FLAGS[@]}" \
--build-arg PYTHON_BASE_IMAGE="${AIRFLOW_PYTHON_BASE_IMAGE}" \
--build-arg AIRFLOW_VERSION="${AIRFLOW_VERSION}" \
--build-arg AIRFLOW_BRANCH="${BRANCH_NAME}" \
--build-arg AIRFLOW_EXTRAS="${AIRFLOW_EXTRAS}" \
--build-arg AIRFLOW_PRE_CACHED_PIP_PACKAGES="${AIRFLOW_PRE_CACHED_PIP_PACKAGES}" \
--build-arg ADDITIONAL_AIRFLOW_EXTRAS="${ADDITIONAL_AIRFLOW_EXTRAS}" \
--build-arg ADDITIONAL_PYTHON_DEPS="${ADDITIONAL_PYTHON_DEPS}" \
--build-arg ADDITIONAL_DEV_APT_COMMAND="${ADDITIONAL_DEV_APT_COMMAND}" \
--build-arg ADDITIONAL_DEV_APT_DEPS="${ADDITIONAL_DEV_APT_DEPS}" \
--build-arg ADDITIONAL_DEV_APT_ENV="${ADDITIONAL_DEV_APT_ENV}" \
--build-arg ADDITIONAL_RUNTIME_APT_COMMAND="${ADDITIONAL_RUNTIME_APT_COMMAND}" \
--build-arg ADDITIONAL_RUNTIME_APT_DEPS="${ADDITIONAL_RUNTIME_APT_DEPS}" \
--build-arg ADDITIONAL_RUNTIME_APT_ENV="${ADDITIONAL_RUNTIME_APT_ENV}" \
--build-arg UPGRADE_TO_NEWER_DEPENDENCIES="${UPGRADE_TO_NEWER_DEPENDENCIES}" \
--build-arg CONTINUE_ON_PIP_CHECK_FAILURE="${CONTINUE_ON_PIP_CHECK_FAILURE}" \
--build-arg CONSTRAINTS_GITHUB_REPOSITORY="${CONSTRAINTS_GITHUB_REPOSITORY}" \
--build-arg AIRFLOW_CONSTRAINTS_REFERENCE="${DEFAULT_CONSTRAINTS_BRANCH}" \
--build-arg AIRFLOW_CONSTRAINTS="${AIRFLOW_CONSTRAINTS}" \
--build-arg AIRFLOW_IMAGE_REPOSITORY="https://github.com/${GITHUB_REPOSITORY}" \
--build-arg AIRFLOW_IMAGE_DATE_CREATED="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
--build-arg BUILD_ID="${CI_BUILD_ID}" \
--build-arg COMMIT_SHA="${COMMIT_SHA}" \
"${additional_dev_args[@]}" \
"${additional_runtime_args[@]}" \
"${DOCKER_CACHE_CI_DIRECTIVE[@]}" \
-t "${AIRFLOW_CI_IMAGE}" \
--target "main" \
. -f Dockerfile.ci
set -u
if [[ -n "${DEFAULT_CI_IMAGE=}" ]]; then
echo "Tagging additionally image ${AIRFLOW_CI_IMAGE} with ${DEFAULT_CI_IMAGE}"
docker_v tag "${AIRFLOW_CI_IMAGE}" "${DEFAULT_CI_IMAGE}"
fi
if [[ -n "${IMAGE_TAG=}" ]]; then
echo "Tagging additionally image ${AIRFLOW_CI_IMAGE} with ${IMAGE_TAG}"
docker_v tag "${AIRFLOW_CI_IMAGE}" "${IMAGE_TAG}"
fi
if [[ -n ${spin_pid=} ]]; then
kill -HUP "${spin_pid}" || true
wait "${spin_pid}" || true
echo >"${DETECTED_TERMINAL}"
fi
}
# Prepares all variables needed by the CI build. Depending on the configuration used (python version
# DockerHub user etc. the variables are set so that other functions can use those variables.
function build_images::prepare_prod_build() {
if [[ -n "${INSTALL_AIRFLOW_REFERENCE=}" ]]; then
# When --install-airflow-reference is used then the image is build from GitHub tag
EXTRA_DOCKER_PROD_BUILD_FLAGS=(
"--build-arg" "AIRFLOW_INSTALLATION_METHOD=https://github.com/apache/airflow/archive/${INSTALL_AIRFLOW_REFERENCE}.tar.gz#egg=apache-airflow"
)
export AIRFLOW_VERSION="${INSTALL_AIRFLOW_REFERENCE}"
build_images::add_build_args_for_remote_install
elif [[ -n "${INSTALL_AIRFLOW_VERSION=}" ]]; then
# When --install-airflow-version is used then the image is build using released PIP package
# For PROD image only numeric versions are allowed
if [[ ! ${INSTALL_AIRFLOW_VERSION} =~ ^[0-9\.]*$ ]]; then
echo
echo "${COLOR_RED}ERROR: Bad value for install-airflow-version: '${INSTALL_AIRFLOW_VERSION}'. Only numerical versions allowed for PROD image here'!${COLOR_RESET}"
echo
exit 1
fi
EXTRA_DOCKER_PROD_BUILD_FLAGS=(
"--build-arg" "AIRFLOW_INSTALLATION_METHOD=apache-airflow"
"--build-arg" "AIRFLOW_VERSION_SPECIFICATION===${INSTALL_AIRFLOW_VERSION}"
"--build-arg" "AIRFLOW_VERSION=${INSTALL_AIRFLOW_VERSION}"
)
export AIRFLOW_VERSION="${INSTALL_AIRFLOW_VERSION}"
export INSTALL_PROVIDERS_FROM_SOURCES="false"
build_images::add_build_args_for_remote_install
else
# When no airflow version/reference is specified, production image is built either from the
# local sources (in Breeze) or from PyPI (in the ci_scripts)
# Default values for the variables are set in breeze (breeze defaults) and _initialization.sh (CI ones)
EXTRA_DOCKER_PROD_BUILD_FLAGS=(
"--build-arg" "AIRFLOW_SOURCES_FROM=${AIRFLOW_SOURCES_FROM}"
"--build-arg" "AIRFLOW_SOURCES_TO=${AIRFLOW_SOURCES_TO}"
"--build-arg" "AIRFLOW_INSTALLATION_METHOD=${AIRFLOW_INSTALLATION_METHOD}"
"--build-arg" "AIRFLOW_CONSTRAINTS_REFERENCE=${DEFAULT_CONSTRAINTS_BRANCH}"
)
fi
if [[ "${DEFAULT_PYTHON_MAJOR_MINOR_VERSION}" == "${PYTHON_MAJOR_MINOR_VERSION}" ]]; then
export DEFAULT_CI_IMAGE="${AIRFLOW_PROD_IMAGE_DEFAULT}"
else
export DEFAULT_CI_IMAGE=""
fi
export THE_IMAGE_TYPE="PROD"
export IMAGE_DESCRIPTION="Airflow production"
# Those constants depend on the type of image run so they are only made constants here
export AIRFLOW_EXTRAS="${AIRFLOW_EXTRAS:="${DEFAULT_PROD_EXTRAS}"}"
readonly AIRFLOW_EXTRAS
export AIRFLOW_IMAGE="${AIRFLOW_PROD_IMAGE}"
readonly AIRFLOW_IMAGE
build_images::configure_docker_registry
AIRFLOW_BRANCH_FOR_PYPI_PRELOADING="${BRANCH_NAME}"
sanity_checks::go_to_airflow_sources
}
# Builds PROD image - depending on the caching strategy (pulled, local, disabled) it
# passes the necessary docker build flags via DOCKER_CACHE_PROD_DIRECTIVE and
# DOCKER_CACHE_PROD_BUILD_DIRECTIVE (separate caching options are needed for "build" segment of the image)
# it also passes the right Build args depending on the configuration of the build
# selected by Breeze flags or environment variables.
function build_images::build_prod_images() {
build_images::print_build_info
if [[ ${SKIP_BUILDING_PROD_IMAGE} == "true" ]]; then
echo
echo "${COLOR_YELLOW}Skip building production image. Assume the one we have is good!${COLOR_RESET}"
echo "${COLOR_YELLOW}You must run './breeze build-image --production-image before for all python versions!${COLOR_RESET}"
echo
return
fi
push_pull_remove_images::pull_prod_images_if_needed
if [[ "${DOCKER_CACHE}" == "disabled" ]]; then
export DOCKER_CACHE_PROD_DIRECTIVE=("--cache-from" "${AIRFLOW_PROD_BUILD_IMAGE}")
export DOCKER_CACHE_PROD_BUILD_DIRECTIVE=("--no-cache")
elif [[ "${DOCKER_CACHE}" == "local" ]]; then
export DOCKER_CACHE_PROD_DIRECTIVE=()
export DOCKER_CACHE_PROD_BUILD_DIRECTIVE=()
elif [[ "${DOCKER_CACHE}" == "pulled" ]]; then
export DOCKER_CACHE_PROD_DIRECTIVE=(
"--cache-from" "${AIRFLOW_PROD_BUILD_IMAGE}"
"--cache-from" "${AIRFLOW_PROD_IMAGE}"
)
export DOCKER_CACHE_PROD_BUILD_DIRECTIVE=(
"--cache-from" "${AIRFLOW_PROD_BUILD_IMAGE}"
)
else
echo
echo "${COLOR_RED}ERROR: The ${DOCKER_CACHE} cache is unknown ${COLOR_RESET}"
echo
echo
exit 1
fi
set +u
local additional_dev_args=()
if [[ -n "${DEV_APT_DEPS}" ]]; then
additional_dev_args+=("--build-arg" "DEV_APT_DEPS=\"${DEV_APT_DEPS}\"")
fi
if [[ -n "${DEV_APT_COMMAND}" ]]; then
additional_dev_args+=("--build-arg" "DEV_APT_COMMAND=\"${DEV_APT_COMMAND}\"")
fi
docker_v build \
"${EXTRA_DOCKER_PROD_BUILD_FLAGS[@]}" \
--build-arg PYTHON_BASE_IMAGE="${AIRFLOW_PYTHON_BASE_IMAGE}" \
--build-arg INSTALL_MYSQL_CLIENT="${INSTALL_MYSQL_CLIENT}" \
--build-arg AIRFLOW_VERSION="${AIRFLOW_VERSION}" \
--build-arg AIRFLOW_BRANCH="${AIRFLOW_BRANCH_FOR_PYPI_PRELOADING}" \
--build-arg AIRFLOW_EXTRAS="${AIRFLOW_EXTRAS}" \
--build-arg ADDITIONAL_AIRFLOW_EXTRAS="${ADDITIONAL_AIRFLOW_EXTRAS}" \
--build-arg ADDITIONAL_PYTHON_DEPS="${ADDITIONAL_PYTHON_DEPS}" \
"${additional_dev_args[@]}" \
--build-arg INSTALL_PROVIDERS_FROM_SOURCES="${INSTALL_PROVIDERS_FROM_SOURCES}" \
--build-arg ADDITIONAL_DEV_APT_COMMAND="${ADDITIONAL_DEV_APT_COMMAND}" \
--build-arg ADDITIONAL_DEV_APT_DEPS="${ADDITIONAL_DEV_APT_DEPS}" \
--build-arg ADDITIONAL_DEV_APT_ENV="${ADDITIONAL_DEV_APT_ENV}" \
--build-arg AIRFLOW_PRE_CACHED_PIP_PACKAGES="${AIRFLOW_PRE_CACHED_PIP_PACKAGES}" \
--build-arg INSTALL_FROM_PYPI="${INSTALL_FROM_PYPI}" \
--build-arg INSTALL_FROM_DOCKER_CONTEXT_FILES="${INSTALL_FROM_DOCKER_CONTEXT_FILES}" \
--build-arg UPGRADE_TO_NEWER_DEPENDENCIES="${UPGRADE_TO_NEWER_DEPENDENCIES}" \
--build-arg CONTINUE_ON_PIP_CHECK_FAILURE="${CONTINUE_ON_PIP_CHECK_FAILURE}" \
--build-arg BUILD_ID="${CI_BUILD_ID}" \
--build-arg COMMIT_SHA="${COMMIT_SHA}" \
--build-arg CONSTRAINTS_GITHUB_REPOSITORY="${CONSTRAINTS_GITHUB_REPOSITORY}" \
--build-arg AIRFLOW_CONSTRAINTS="${AIRFLOW_CONSTRAINTS}" \
--build-arg AIRFLOW_IMAGE_REPOSITORY="https://github.com/${GITHUB_REPOSITORY}" \
--build-arg AIRFLOW_IMAGE_DATE_CREATED="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
"${DOCKER_CACHE_PROD_BUILD_DIRECTIVE[@]}" \
-t "${AIRFLOW_PROD_BUILD_IMAGE}" \
--target "airflow-build-image" \
. -f Dockerfile
local additional_runtime_args=()
if [[ -n "${RUNTIME_APT_DEPS}" ]]; then
additional_runtime_args+=("--build-arg" "RUNTIME_APT_DEPS=\"${RUNTIME_APT_DEPS}\"")
fi
if [[ -n "${RUNTIME_APT_COMMAND}" ]]; then
additional_runtime_args+=("--build-arg" "RUNTIME_APT_COMMAND=\"${RUNTIME_APT_COMMAND}\"")
fi
docker_v build \
"${EXTRA_DOCKER_PROD_BUILD_FLAGS[@]}" \
--build-arg PYTHON_BASE_IMAGE="${AIRFLOW_PYTHON_BASE_IMAGE}" \
--build-arg INSTALL_MYSQL_CLIENT="${INSTALL_MYSQL_CLIENT}" \
--build-arg ADDITIONAL_AIRFLOW_EXTRAS="${ADDITIONAL_AIRFLOW_EXTRAS}" \
--build-arg ADDITIONAL_PYTHON_DEPS="${ADDITIONAL_PYTHON_DEPS}" \
--build-arg INSTALL_PROVIDERS_FROM_SOURCES="${INSTALL_PROVIDERS_FROM_SOURCES}" \
--build-arg ADDITIONAL_DEV_APT_COMMAND="${ADDITIONAL_DEV_APT_COMMAND}" \
--build-arg ADDITIONAL_DEV_APT_DEPS="${ADDITIONAL_DEV_APT_DEPS}" \
--build-arg ADDITIONAL_DEV_APT_ENV="${ADDITIONAL_DEV_APT_ENV}" \
--build-arg ADDITIONAL_RUNTIME_APT_COMMAND="${ADDITIONAL_RUNTIME_APT_COMMAND}" \
--build-arg ADDITIONAL_RUNTIME_APT_DEPS="${ADDITIONAL_RUNTIME_APT_DEPS}" \
--build-arg ADDITIONAL_RUNTIME_APT_ENV="${ADDITIONAL_RUNTIME_APT_ENV}" \
--build-arg AIRFLOW_PRE_CACHED_PIP_PACKAGES="${AIRFLOW_PRE_CACHED_PIP_PACKAGES}" \
--build-arg INSTALL_FROM_PYPI="${INSTALL_FROM_PYPI}" \
--build-arg INSTALL_FROM_DOCKER_CONTEXT_FILES="${INSTALL_FROM_DOCKER_CONTEXT_FILES}" \
--build-arg UPGRADE_TO_NEWER_DEPENDENCIES="${UPGRADE_TO_NEWER_DEPENDENCIES}" \
--build-arg CONTINUE_ON_PIP_CHECK_FAILURE="${CONTINUE_ON_PIP_CHECK_FAILURE}" \
--build-arg AIRFLOW_VERSION="${AIRFLOW_VERSION}" \
--build-arg AIRFLOW_BRANCH="${AIRFLOW_BRANCH_FOR_PYPI_PRELOADING}" \
--build-arg AIRFLOW_EXTRAS="${AIRFLOW_EXTRAS}" \
--build-arg BUILD_ID="${CI_BUILD_ID}" \
--build-arg COMMIT_SHA="${COMMIT_SHA}" \
--build-arg CONSTRAINTS_GITHUB_REPOSITORY="${CONSTRAINTS_GITHUB_REPOSITORY}" \
--build-arg AIRFLOW_CONSTRAINTS="${AIRFLOW_CONSTRAINTS}" \
--build-arg AIRFLOW_IMAGE_REPOSITORY="https://github.com/${GITHUB_REPOSITORY}" \
--build-arg AIRFLOW_IMAGE_DATE_CREATED="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
"${additional_dev_args[@]}" \
"${additional_runtime_args[@]}" \
"${DOCKER_CACHE_PROD_DIRECTIVE[@]}" \
-t "${AIRFLOW_PROD_IMAGE}" \
--target "main" \
. -f Dockerfile
set -u
if [[ -n "${DEFAULT_PROD_IMAGE:=}" ]]; then
echo "Tagging additionally image ${AIRFLOW_PROD_IMAGE} with ${DEFAULT_PROD_IMAGE}"
docker_v tag "${AIRFLOW_PROD_IMAGE}" "${DEFAULT_PROD_IMAGE}"
fi
if [[ -n "${IMAGE_TAG=}" ]]; then
echo "Tagging additionally image ${AIRFLOW_PROD_IMAGE} with ${IMAGE_TAG}"
docker_v tag "${AIRFLOW_PROD_IMAGE}" "${IMAGE_TAG}"
fi
}
# Waits for image tag to appear in GitHub Registry, pulls it and tags with the target tag
# Parameters:
# $1 - image name to wait for
# $2 - suffix of the image to wait for
# $3, $4, ... - target tags to tag the image with
function build_images::wait_for_image_tag() {
local image_name="${1}"
local image_suffix="${2}"
shift 2
local image_to_wait_for="${image_name}${image_suffix}"
start_end::group_start "Wait for image tag ${image_to_wait_for}"
while true; do
set +e
echo "${COLOR_BLUE}Docker pull ${image_to_wait_for} ${COLOR_RESET}" >"${OUTPUT_LOG}"
docker_v pull "${image_to_wait_for}" >>"${OUTPUT_LOG}" 2>&1
set -e
local image_hash
echo "${COLOR_BLUE} Docker images -q ${image_to_wait_for}${COLOR_RESET}" >>"${OUTPUT_LOG}"
image_hash="$(docker images -q "${image_to_wait_for}" 2>>"${OUTPUT_LOG}" || true)"
if [[ -z "${image_hash}" ]]; then
echo
echo "The image ${image_to_wait_for} is not yet available. No local hash for the image. Waiting."
echo
echo "Last log:"
cat "${OUTPUT_LOG}" || true
echo
sleep 10
else
echo
echo "The image ${image_to_wait_for} with '${image_name}' tag"
echo
echo
echo "Tagging ${image_to_wait_for} as ${image_name}."
echo
docker_v tag "${image_to_wait_for}" "${image_name}"
for TARGET_TAG in "${@}"; do
echo
echo "Tagging ${image_to_wait_for} as ${TARGET_TAG}."
echo
docker_v tag "${image_to_wait_for}" "${TARGET_TAG}"
done
break
fi
done
start_end::group_end
}
# We use pulled docker image cache by default for CI images to speed up the builds
# and local to speed up iteration on kerberos tests
function build_images::determine_docker_cache_strategy() {
if [[ -z "${DOCKER_CACHE=}" ]]; then
if [[ "${PRODUCTION_IMAGE}" == "true" ]]; then
export DOCKER_CACHE="local"
else
export DOCKER_CACHE="pulled"
fi
fi
readonly DOCKER_CACHE
verbosity::print_info
verbosity::print_info "Using ${DOCKER_CACHE} cache strategy for the build."
verbosity::print_info
}
function build_images::assert_variable() {
local variable_name="${1}"
local expected_value="${2}"
local variable_value=${!variable_name}
if [[ ${variable_value} != "${expected_value}" ]]; then
echo
echo "${COLOR_RED}ERROR: Variable ${variable_name}: expected_value: '${expected_value}' but was '${variable_value}'!${COLOR_RESET}"
echo
exit 1
fi
}
function build_images::cleanup_dist() {
mkdir -pv "${AIRFLOW_SOURCES}/dist"
rm -f "${AIRFLOW_SOURCES}/dist/"*.{whl,tar.gz}
}