blob: 31e000c7ed62338b9e663c7d01ed8c07732f2c56 [file] [log] [blame]
#!/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.
# This is hook build used by DockerHub. We are also using it
# on Travis CI to potentially rebuild (and refresh layers that
# are not cached) Docker images that are used to run CI jobs
set -euo pipefail
MY_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
echo "My dir: ${MY_DIR}"
AIRFLOW_ROOT=$(cd "${MY_DIR}"; cd ..; pwd)
cd "${AIRFLOW_ROOT}"
echo
echo "Airflow root directory: ${AIRFLOW_ROOT}"
echo
BUILD_CACHE_DIR="${AIRFLOW_ROOT}/.build"
mkdir -pv "${BUILD_CACHE_DIR}"
date
BUILD_START_TIME=$(date +%s)
LAST_STEP_START_TIME=${BUILD_START_TIME}
LAST_STEP_NAME=""
STEP_STARTED="false"
PYTHON_VERSION_FOR_DEFAULT_IMAGE=3.6
AIRFLOW_CI_VERBOSE=${AIRFLOW_CI_VERBOSE:="false"}
function end_step {
if [[ "${STEP_STARTED}" != "true" ]]; then
return
fi
LAST_STEP_END_TIME=$(date +%s)
echo
echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
echo " Finishing step: ${LAST_STEP_NAME}"
echo " Date: $(date)"
echo " Step time in s : $((LAST_STEP_END_TIME-LAST_STEP_START_TIME))"
echo " Total time in s: $((LAST_STEP_END_TIME-BUILD_START_TIME))"
echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
echo
STEP_STARTED="false"
}
function start_step {
end_step
LAST_STEP_NAME="${1}"
LAST_STEP_START_TIME=$(date +%s)
STEP_STARTED="true"
echo
echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
echo " Starting step: ${LAST_STEP_NAME}"
echo " Date: $(date)"
echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
echo
}
function add_image_to_push {
IMAGE=$1
IMAGES_BUILT="${IMAGES_BUILT} ${IMAGE}"
echo
echo "Adding TAG ${IMAGE} to push"
echo
echo
echo "List of tags to push now: '${IMAGES_BUILT}'"
echo
}
function save_to_file {
# shellcheck disable=SC2005
echo "$(eval echo "\$$1")" > "${BUILD_CACHE_DIR}/.$1"
}
function build_python_image {
NAME="${1}"
MY_IMAGE_TAG="${2}"
TARGET_IMAGE="${3}"
APT_DEPS_IMAGE="${4:-airflow-apt-deps-ci-slim}"
AIRFLOW_EXTRAS="${5:-all}"
AIRFLOW_USER="${6:-airflow}"
HOME="${7:-/home/airflow}"
echo "Build ${NAME} image: ${MY_IMAGE_TAG}"
echo "Python base image: ${PYTHON_BASE_IMAGE}"
set +u
if [[ "${MY_IMAGE_TAG}" == "${AIRFLOW_CI_IMAGE}" ]]; then
DOCKER_CACHE_DIRECTIVE=("${DOCKER_CACHE_DIRECTIVE_CI[@]}")
elif [[ "${MY_IMAGE_TAG}" == "${AIRFLOW_SLIM_CI_IMAGE}" ]]; then
DOCKER_CACHE_DIRECTIVE=("${DOCKER_CACHE_DIRECTIVE_CI_SLIM[@]}")
else
echo
echo "Don't know how to set cache directive for ${MY_IMAGE_TAG}. Exiting"
echo
exit 1
fi
set -x
docker build \
--build-arg PYTHON_BASE_IMAGE="${PYTHON_BASE_IMAGE}" \
--build-arg AIRFLOW_VERSION="${AIRFLOW_VERSION}" \
--build-arg APT_DEPS_IMAGE="${APT_DEPS_IMAGE}" \
--build-arg AIRFLOW_EXTRAS="${AIRFLOW_EXTRAS}" \
--build-arg AIRFLOW_USER="${AIRFLOW_USER}" \
--build-arg AIRFLOW_BRANCH="${AIRFLOW_CONTAINER_BRANCH_NAME}" \
--build-arg AIRFLOW_CONTAINER_CI_OPTIMISED_BUILD="${AIRFLOW_CONTAINER_CI_OPTIMISED_BUILD}" \
--build-arg HOME="${HOME}" \
"${DOCKER_CACHE_DIRECTIVE[@]}" \
-t "${MY_IMAGE_TAG}" \
--target "${TARGET_IMAGE}" \
.
add_image_to_push "${MY_IMAGE_TAG}"
set +x
set -u
}
start_step "Setting variables"
# You can override DOCKERHUB_USER to use your own DockerHub account and play with your
# own docker images. In this case you can build images locally and push them
export DOCKERHUB_USER=${DOCKERHUB_USER:="apache"}
# You can override DOCKERHUB_REPO to use your own DockerHub repository and play with your
# own docker images. In this case you can build images locally and push them
export DOCKERHUB_REPO=${DOCKERHUB_REPO:="airflow"}
# You can override python binary with specific version
export PYTHON_BINARY=${PYTHON_BINARY:=python}
# Determine python version from the local python on the path.
# You can override it with PYTHON_VERSION because all python version dependencies
# Are determined in Docker images. If you pass IMAGE_NAME, python version will be extracted from the name
# This default Python version is later overridden in case IMAGE_NAME is passed
export PYTHON_VERSION=\
${PYTHON_VERSION:=$(${PYTHON_BINARY} -c \
'import sys;print("%s.%s" % (sys.version_info.major, sys.version_info.minor))')}
# shellcheck source=hooks/_default_branch.sh
. "${MY_DIR}/_default_branch.sh"
# Source brnnch will be set in DockerHub
SOURCE_BRANCH=${SOURCE_BRANCH:=${DEFAULT_BRANCH}}
# if AIRFLOW_CONTAINER_BRANCH_NAME is not set it will be set to either SOURCE_BRANCH (if set in DockerHub
# or default branch configured in _default_branch.sh
AIRFLOW_CONTAINER_BRANCH_NAME=${AIRFLOW_CONTAINER_BRANCH_NAME:=${SOURCE_BRANCH}}
echo
echo "Branch: ${AIRFLOW_CONTAINER_BRANCH_NAME}"
echo
# IMAGE_NAME might come from DockerHub and we decide which PYTHON_VERSION to use based on it (see below)
# In case IMAGE_NAME is not set we determine PYTHON_VERSION based on the default python on the path
# So in virtualenvs it will use the virtualenv's PYTHON_VERSION.
export BASE_IMAGE_NAME=${IMAGE_NAME:=${DOCKERHUB_USER}/${DOCKERHUB_REPO}:${AIRFLOW_CONTAINER_BRANCH_NAME}-python${PYTHON_VERSION}}
echo
echo "BASE_IMAGE_NAME=${BASE_IMAGE_NAME:=} (final image will have -ci, -ci-slim suffixes)"
echo
# Remove index.docker.io/ prefix as it is added by default by DockerHub
export LOCAL_BASE_IMAGE_NAME=${BASE_IMAGE_NAME#index.docker.io/}
echo
echo "LOCAL_BASE_IMAGE_NAME=${LOCAL_BASE_IMAGE_NAME}"
echo
# Extract python version from image name we want to build in case it was passed
# Via IMAGE_NAME
# This nice bash construct below extracts last field after '-python' delimiter
# So for example 'airflow:master-python3.6' will produce LONG_PYTHON_VERSION=python3.6
LONG_PYTHON_VERSION="${LOCAL_BASE_IMAGE_NAME##*-}"
export LONG_PYTHON_VERSION
echo
echo "LONG_PYTHON_VERSION=${LONG_PYTHON_VERSION}"
echo
if [[ ! ${LONG_PYTHON_VERSION} =~ python[2-3]\.[0-9]+ ]]; then
echo >&2
echo >&2 "ERROR: Python version extracted from IMAGE_NAME does not match the pythonX.Y format"
echo >&2
echo >&2 "The IMAGE_NAME format should be '<BRANCH>-pythonX.Y'"
echo >&2
exit 1
fi
PYTHON_VERSION=${LONG_PYTHON_VERSION#python}
echo
echo "PYTHON_VERSION=${PYTHON_VERSION}"
echo
if [[ ! ${LOCAL_BASE_IMAGE_NAME} == ${DOCKERHUB_USER}/${DOCKERHUB_REPO}* ]]; then
echo >&2
echo >&2 "ERROR: The ${LOCAL_BASE_IMAGE_NAME} does not start with ${DOCKERHUB_USER}/${DOCKERHUB_REPO}"
echo >&2
exit 1
fi
# Extract IMAGE_TAG from LOCAL_BASE_IMAGE_NAME (apache/airflow:master-python3.6 -> master-python3.6)
IMAGE_TAG=${LOCAL_BASE_IMAGE_NAME##${DOCKERHUB_USER}/${DOCKERHUB_REPO}:}
echo
echo "Image tag: ${IMAGE_TAG}"
echo
# Extract TAG_PREFIX from IMAGE_TAG (master-python3.6 -> master)
TAG_PREFIX=${IMAGE_TAG%-*}
echo
echo "Image tag prefix: ${TAG_PREFIX}"
echo
echo
echo "Travis event type: ${TRAVIS_EVENT_TYPE:=}"
echo
# In case of CRON jobs on Travis we run builds without cache
if [[ "${TRAVIS_EVENT_TYPE:=}" == "cron" ]]; then
echo
echo "Disabling cache for CRON jobs"
echo
AIRFLOW_CONTAINER_USE_NO_CACHE=${AIRFLOW_CONTAINER_USE_NO_CACHE:="true"}
fi
# You can set AIRFLOW_CONTAINER_USE_NO_CACHE to true if you want to use standard Docker cache during build
# This way you can test building everything from the scratch
AIRFLOW_CONTAINER_USE_NO_CACHE=${AIRFLOW_CONTAINER_USE_NO_CACHE:="false"}
# If cache is not used, there is no point in pulling images for cache
if [[ "${AIRFLOW_CONTAINER_USE_NO_CACHE:=}" == "true" ]]; then
echo
echo "Pulling images is disabled becaue cache is not used"
echo
AIRFLOW_CONTAINER_USE_PULLED_IMAGES_CACHE="false"
fi
# You can set AIRFLOW_CONTAINER_USE_DOCKER_CACHE to false if you do not want to use pulled images
# as cache during build
# This way you can test building from the scratch
AIRFLOW_CONTAINER_USE_PULLED_IMAGES_CACHE=${AIRFLOW_CONTAINER_USE_PULLED_IMAGES_CACHE:="true"}
pwd
# Determine version of the Airflow from version.py
AIRFLOW_VERSION=$(cat airflow/version.py - << EOF | python
print(version.replace("+",""))
EOF
)
export AIRFLOW_VERSION
# Check if we are running in the CI environment
CI=${CI:="false"}
if [[ "${CI}" == "true" ]]; then
NON_CI="false"
else
NON_CI="true"
fi
# Extras used to to build slim airflow image
AIRFLOW_SLIM_EXTRAS=${AIRFLOW_SLIM_EXTRAS:="all"}
# Extras used to build cache and CI image
AIRFLOW_CI_EXTRAS=${AIRFLOW_CI_EXTRAS:="devel_ci"}
# Whether this is a release build
AIRFLOW_RELEASE_BUILD=${AIRFLOW_RELEASE_BUILD:="false"}
echo
echo "Airflow ${AIRFLOW_VERSION} Python: ${PYTHON_VERSION}."
echo
# Whether to push images after build
# This is set to false on CI builds
export AIRFLOW_CONTAINER_PUSH_IMAGES=${AIRFLOW_CONTAINER_PUSH_IMAGES:=${NON_CI}}
# Whether to force pull images to populate cache
export AIRFLOW_CONTAINER_FORCE_PULL_IMAGES=${AIRFLOW_CONTAINER_FORCE_PULL_IMAGES:="false"}
# In CI environment (and local force) we skip pulling latest python image
export AIRFLOW_CONTAINER_SKIP_LATEST_PYTHON_PULL=${AIRFLOW_CONTAINER_SKIP_LATEST_PYTHON_PULL:=${CI}}
# Skips downloading and building the slim image of airflow
# This is set to true by default in CI test environment (we only need full CI image then)
export AIRFLOW_CONTAINER_SKIP_SLIM_CI_IMAGE=${AIRFLOW_CONTAINER_SKIP_SLIM_CI_IMAGE:=${CI}}
echo "Skip slim image: ${AIRFLOW_CONTAINER_SKIP_SLIM_CI_IMAGE}"
# Skips downloading and building the CI image, full CI-enabled image of airflow
# This is set to true for pre-tests which do not need the whole CI image just the slim one
export AIRFLOW_CONTAINER_SKIP_CI_IMAGE=${AIRFLOW_CONTAINER_SKIP_CI_IMAGE:="false"}
echo "Skip CI image: ${AIRFLOW_CONTAINER_SKIP_CI_IMAGE}"
# Skips downloading and building the checklicence image - one used for checking the licences
export AIRFLOW_CONTAINER_SKIP_CHECKLICENCE_IMAGE=${AIRFLOW_CONTAINER_SKIP_CHECKLICENCE_IMAGE:="false"}
echo "Skip CHECKLICENCE image: ${AIRFLOW_CONTAINER_SKIP_CHECKLICENCE_IMAGE}"
# Skips pulling the airflow images - this will use cache but will build it all from scratch
export AIRFLOW_CONTAINER_SKIP_PULLING_AIRFLOW_IMAGES=${AIRFLOW_CONTAINER_SKIP_PULLING_AIRFLOW_IMAGES:="false"}
echo "Skip pulling Airflow images: ${AIRFLOW_CONTAINER_SKIP_PULLING_AIRFLOW_IMAGES}"
# Fixes permissions for git-checked out files. This is needed to have consistent build cache across
# Dockerhub, TravisCI and locally checked out code
export AIRFLOW_FIX_PERMISSIONS=${AIRFLOW_FIX_PERMISSIONS:="all"}
echo "Fixing permissions: ${AIRFLOW_FIX_PERMISSIONS}"
export AIRFLOW_CONTAINER_CI_OPTIMISED_BUILD=${AIRFLOW_CONTAINER_CI_OPTIMISED_BUILD:="true"}
echo "The build optimised for CI: ${AIRFLOW_CONTAINER_CI_OPTIMISED_BUILD}"
# Base python image for the build
export PYTHON_BASE_IMAGE=python:${PYTHON_VERSION}-slim-stretch
# Base image for checklicence image
export CHECKLICENCE_BASE_IMAGE=debian:stretch-slim
if [[ "${AIRFLOW_RELEASE_BUILD}" == "true" ]]; then
export AIRFLOW_SLIM_CI_IMAGE="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:${AIRFLOW_VERSION}-python${PYTHON_VERSION}-ci-slim"
export AIRFLOW_SLIM_CI_IMAGE_DEFAULT="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:${AIRFLOW_VERSION}-ci-slim"
export AIRFLOW_SLIM_CI_IMAGE_LATEST="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:latest-python${PYTHON_VERSION}-${TAG_PREFIX}-ci-slim"
export AIRFLOW_SLIM_CI_IMAGE_LATEST_DEFAULT="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:latest-${TAG_PREFIX}-ci-slim"
export AIRFLOW_CI_IMAGE="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:${AIRFLOW_VERSION}-python${PYTHON_VERSION}-ci"
export AIRFLOW_CI_IMAGE_DEFAULT="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:${AIRFLOW_VERSION}-ci"
export AIRFLOW_CI_IMAGE_LATEST="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:latest-python${PYTHON_VERSION}-${TAG_PREFIX}-ci"
export AIRFLOW_CI_IMAGE_LATEST_DEFAULT="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:latest-${TAG_PREFIX}-ci"
export AIRFLOW_CHECKLICENCE_IMAGE="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:checklicence"
else
export AIRFLOW_SLIM_CI_IMAGE="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:${TAG_PREFIX}-python${PYTHON_VERSION}-ci-slim"
export AIRFLOW_SLIM_CI_IMAGE_DEFAULT="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:${TAG_PREFIX}-ci-slim"
export AIRFLOW_CI_IMAGE="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:${TAG_PREFIX}-python${PYTHON_VERSION}-ci"
export AIRFLOW_CI_IMAGE_DEFAULT="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:${TAG_PREFIX}-ci"
export AIRFLOW_CHECKLICENCE_IMAGE="${DOCKERHUB_USER}/${DOCKERHUB_REPO}:checklicence"
fi
# In the future we can enable buildkit.
# It's experimental now and cache does not work out of the box with buildkit in Docker 18.09.2, buildkit 0.3.3
# It is fixed in upcoming buildkit 0.4.0.
# Buildkit will make build faster (including parallel builds of multi-stage builds).
# It will also help with simpler skipping of unused images.
export DOCKER_BUILDKIT=${DOCKER_BUILDKIT:=0}
# List of images to push at the end of the build
IMAGES_BUILT=""
end_step
AIRFLOW_CONTAINER_CLEANUP_IMAGES=${AIRFLOW_CONTAINER_CLEANUP_IMAGES:="false"}
if [[ "${AIRFLOW_CONTAINER_CLEANUP_IMAGES}" == "true" ]]; then
echo
"${AIRFLOW_ROOT}/confirm" "Removing all local images for that build."
echo
start_step "Removing images"
docker rmi "${PYTHON_BASE_IMAGE}" || true
docker rmi "${CHECKLICENCE_BASE_IMAGE}" || true
docker rmi "${AIRFLOW_SLIM_CI_IMAGE}" || true
docker rmi "${AIRFLOW_CI_IMAGE}" || true
docker rmi "${AIRFLOW_CHECKLICENCE_IMAGE}" || true
echo
echo "###################################################################"
echo "NOTE!! Removed Airflow images for Python version ${PYTHON_VERSION}."
echo " But the disk space in docker will be reclaimed only after"
echo " running 'docker system prune' command."
echo "###################################################################"
echo
end_step
exit 0
fi
start_step "Populating cache"
DOCKER_CACHE_DIRECTIVE_CI=()
DOCKER_CACHE_DIRECTIVE_CI_SLIM=()
DOCKER_CACHE_DIRECTIVE_CHECKLICENCE=()
if [[ "${AIRFLOW_CONTAINER_USE_PULLED_IMAGES_CACHE}" == "true" ]]; then
echo
echo "Pulling images to populate cache"
echo
echo
if [[ "${AIRFLOW_CONTAINER_FORCE_PULL_IMAGES}" == "true" ]]; then
if [[ ${AIRFLOW_CONTAINER_SKIP_LATEST_PYTHON_PULL} == "true" ]]; then
echo
echo "Skip force-pulling the base images."
echo
else
set -x
echo
echo "Force pull base python image."
echo
docker pull "${PYTHON_BASE_IMAGE}"
echo "Force pull base checklicence image."
echo
docker pull "${CHECKLICENCE_BASE_IMAGE}"
echo
set +x
fi
fi
IMAGES_TO_PULL=""
if [[ "${AIRFLOW_CONTAINER_SKIP_CI_IMAGE}" == "true" ]]; then
echo "Skip downloading the CI Airflow image"
else
IMAGES_TO_PULL="${IMAGES_TO_PULL} ${AIRFLOW_CI_IMAGE}"
fi
if [[ "${AIRFLOW_CONTAINER_SKIP_SLIM_CI_IMAGE}" == "true" ]]; then
echo "Skip downloading the slim Airflow image"
else
IMAGES_TO_PULL="${IMAGES_TO_PULL} ${AIRFLOW_SLIM_CI_IMAGE}"
fi
if [[ "${AIRFLOW_CONTAINER_SKIP_CHECKLICENCE_IMAGE}" == "true" ]]; then
echo "Skip downloading the CHECKLICENCE Airflow image"
else
IMAGES_TO_PULL="${IMAGES_TO_PULL} ${AIRFLOW_CHECKLICENCE_IMAGE}"
fi
DOCKER_CACHE_DIRECTIVE_CI=()
DOCKER_CACHE_DIRECTIVE_CI_SLIM=()
if [[ ${IMAGES_TO_PULL} == "" ]]; then
echo
echo "Skipping building of all images."
echo
else
for IMAGE in ${IMAGES_TO_PULL}
do
echo
echo "Checking whether image ${IMAGE} needs to be pulled."
echo
PULL_IMAGE="false"
if [[ "${AIRFLOW_CONTAINER_FORCE_PULL_IMAGES}" == "true" ]]; then
echo
echo "Pulling images is forced. Pulling ${IMAGE}"
echo
PULL_IMAGE="true"
else
IMAGE_HASH=$(docker images -q "${IMAGE}" 2> /dev/null)
if [[ "${IMAGE_HASH}" == "" ]]; then
echo
echo "No image ${IMAGE} locally available. Pulling for the first time."
echo
PULL_IMAGE="true"
else
echo
echo "Image ${IMAGE} is in local registry (${IMAGE_HASH}). Not pulling it!"
echo
PULL_IMAGE="false"
fi
fi
if [[ "${PULL_IMAGE}" == "true" ]]; then
if [[ "${AIRFLOW_CONTAINER_SKIP_PULLING_AIRFLOW_IMAGES}" == "true" ]]; then
echo
echo "Skipping pulling image ${IMAGE}"
echo
else
echo
set -x
docker pull "${IMAGE}" || true
set +x
echo
fi
fi
if [[ "${IMAGE}" == "${AIRFLOW_CI_IMAGE}" ]]; then
DOCKER_CACHE_DIRECTIVE_CI+=("--cache-from" "${IMAGE}")
elif [[ "${IMAGE}" == "${AIRFLOW_SLIM_CI_IMAGE}" ]]; then
DOCKER_CACHE_DIRECTIVE_CI_SLIM+=("--cache-from" "${IMAGE}")
elif [[ "${IMAGE}" == "${AIRFLOW_CHECKLICENCE_IMAGE}" ]]; then
DOCKER_CACHE_DIRECTIVE_CHECKLICENCE+=("--cache-from" "${IMAGE}")
else
echo
echo "Don't know how to set cache directive for ${IMAGE}. Exiting"
echo
exit 1
fi
done
fi
fi
start_step "Setting cache options"
if [[ "${AIRFLOW_CONTAINER_USE_NO_CACHE}" == "true" ]]; then
DOCKER_CACHE_DIRECTIVE_CI+=("--no-cache")
DOCKER_CACHE_DIRECTIVE_CI_SLIM+=("--no-cache")
DOCKER_CACHE_DIRECTIVE_CHECKLICENCE+=("--no-cache")
echo
echo "Skip cache for builds. Everything will be rebuilt from scratch."
echo
echo "Cache directives used: "
echo "CI build: ${DOCKER_CACHE_DIRECTIVE_CI[*]}"
echo "CI slim build: ${DOCKER_CACHE_DIRECTIVE_CI_SLIM[*]}"
echo "CI checklicence build: ${DOCKER_CACHE_DIRECTIVE_CHECKLICENCE[*]}"
echo
elif [[ "${AIRFLOW_CONTAINER_USE_PULLED_IMAGES_CACHE}" == "true" ]]; then
echo
echo "This build uses Docker cache from pulled images"
echo "Cache directives used: "
set +u
echo "CI build: ${DOCKER_CACHE_DIRECTIVE_CI[*]}"
echo "CI slim build: ${DOCKER_CACHE_DIRECTIVE_CI_SLIM[*]}"
echo "CI checklicence build: ${DOCKER_CACHE_DIRECTIVE_CHECKLICENCE[*]}"
set -u
echo
else
DOCKER_CACHE_DIRECTIVE_CI=()
DOCKER_CACHE_DIRECTIVE_CI_SLIM=()
DOCKER_CACHE_DIRECTIVE_CHECKLICENCE=()
echo
echo "Use default cache from locally built images."
echo
echo "Cache directives used: "
set +u
echo "CI build: ${DOCKER_CACHE_DIRECTIVE_CI[*]}"
echo "CI slim build: ${DOCKER_CACHE_DIRECTIVE_CI_SLIM[*]}"
echo "CI checklicence build: ${DOCKER_CACHE_DIRECTIVE_CHECKLICENCE[*]}"
set -u
echo
fi
start_step "Creating deployment directory"
STAT_BIN=stat
if [[ "${OSTYPE}" == "darwin"* ]]; then
STAT_BIN=gstat
fi
# Build id identifying the build uniquely
BUILD_ID=${BUILD_ID:="local"}
# directory where "deployment" artifacts should be placed
DEPLOY_DIR=${AIRFLOW_ROOT}/dist/${AIRFLOW_CONTAINER_BRANCH_NAME}/$(date +%Y-%m-%d)/${BUILD_ID}/${PYTHON_VERSION}
mkdir -pv "${DEPLOY_DIR}"
#
# Fixing permissions for all filex that are going to be added to Docker context
# This is necessary, because there are different default umask settings on different *NIX
# In case of some systems (especially in the CI environments) there is default +w group permission
# set automatically via UMASK when git checkout is performed.
# https://unix.stackexchange.com/questions/315121/why-is-the-default-umask-002-or-022-in-many-unix-systems-seems-insecure-by-defa
# Unfortunately default setting in git is to use UMASK by default:
# https://git-scm.com/docs/git-config/1.6.3.1#git-config-coresharedRepository
# This messes around with Docker context invalidation because the same files have different permissions
# and effectively different hash used for context validation calculation.
#
# We fix it by removing write permissions for other/group for all files that are in the Docker context.
#
if [[ "${AIRFLOW_FIX_PERMISSIONS}" == "all" || "${AIRFLOW_FIX_PERMISSIONS}" == "setup" ]]; then
start_step "Fixing permissions for CI builds for ${AIRFLOW_FIX_PERMISSIONS} files"
if [[ "${AIRFLOW_FIX_PERMISSIONS}" == "all" ]]; then
# Get all files in the context - by building a small alpine based image
# then COPY all files (.) from the context and listing the files via find method
docker build -t airflow-context . -f Dockerfile-context
ALL_FILES_TO_FIX=$(docker run airflow-context /bin/sh -c "(cd /context && find .)")
elif [[ "${AIRFLOW_FIX_PERMISSIONS}" == "setup" ]]; then
ALL_FILES_TO_FIX="\
${AIRFLOW_ROOT}/setup.py \
${AIRFLOW_ROOT}/setup.cfg \
${AIRFLOW_ROOT}/airflow/version.py \
${AIRFLOW_ROOT}/airflow/__init__.py \
${AIRFLOW_ROOT}/airflow/bin/airflow"
fi
STAT_BIN=stat
if [[ "${OSTYPE}" == "darwin"* ]]; then
STAT_BIN=gstat
fi
for FILE in ${ALL_FILES_TO_FIX}; do
ACCESS_RIGHTS=$("${STAT_BIN}" -c "%A" "${FILE}" || echo "--------")
# check if the file is group/other writeable
if [[ "${ACCESS_RIGHTS:5:1}" != "-" || "${ACCESS_RIGHTS:8:1}" != "-" ]]; then
if [[ "${AIRFLOW_CI_VERBOSE}" == "true" ]]; then
"${STAT_BIN}" --printf "%a %A %F \t%s \t-> " "${FILE}"
fi
chmod og-w "${FILE}"
if [[ "${AIRFLOW_CI_VERBOSE}" == "true" ]]; then
"${STAT_BIN}" --printf "%a %A %F \t%s \t%n\n" "${FILE}"
fi
fi
done
echo "Group/other write access removed for all $(echo "${ALL_FILES_TO_FIX}" | wc -w) files in context"
echo
echo
else
echo "Skipping fixing permissions for CI builds"
fi
start_step "Build Airflow CI slim image"
if [[ "${AIRFLOW_CONTAINER_SKIP_SLIM_CI_IMAGE}" == "true" ]]; then
echo "Skip building the Airflow CI slim image"
else
build_python_image "Airflow" \
"${AIRFLOW_SLIM_CI_IMAGE}" \
"main" \
"airflow-apt-deps-ci-slim"
save_to_file AIRFLOW_SLIM_CI_IMAGE
if [[ "${PYTHON_VERSION_FOR_DEFAULT_IMAGE}" == "${PYTHON_VERSION}" ]]; then
docker tag "${AIRFLOW_SLIM_CI_IMAGE}" "${AIRFLOW_SLIM_CI_IMAGE_DEFAULT}"
add_image_to_push "${AIRFLOW_SLIM_CI_IMAGE_DEFAULT}"
save_to_file AIRFLOW_SLIM_CI_IMAGE_DEFAULT
fi
if [[ "${AIRFLOW_RELEASE_BUILD}" == "true" ]]; then
docker tag "${AIRFLOW_SLIM_CI_IMAGE}" "${AIRFLOW_SLIM_CI_IMAGE_LATEST}"
add_image_to_push "${AIRFLOW_SLIM_CI_IMAGE_LATEST}"
save_to_file AIRFLOW_SLIM_CI_IMAGE_LATEST
if [[ "${PYTHON_VERSION_FOR_DEFAULT_IMAGE}" == "${PYTHON_VERSION}" ]]; then
docker tag "${AIRFLOW_SLIM_CI_IMAGE}" "${AIRFLOW_SLIM_CI_IMAGE_LATEST_DEFAULT}"
add_image_to_push "${AIRFLOW_SLIM_CI_IMAGE_LATEST_DEFAULT}"
save_to_file AIRFLOW_SLIM_CI_IMAGE_LATEST_DEFAULT
fi
fi
fi
if [[ "${AIRFLOW_CONTAINER_SKIP_CI_IMAGE}" == "true" ]]; then
echo "Skip building the CI full Airflow image"
else
start_step "Build Airflow CI full image"
build_python_image "Airflow CI" \
"${AIRFLOW_CI_IMAGE}" \
"main" \
"airflow-apt-deps-ci" \
"devel_ci" \
"root" \
"/root"
save_to_file AIRFLOW_CI_IMAGE
if [[ "${PYTHON_VERSION_FOR_DEFAULT_IMAGE}" == "${PYTHON_VERSION}" ]]; then
docker tag "${AIRFLOW_CI_IMAGE}" "${AIRFLOW_CI_IMAGE_DEFAULT}"
add_image_to_push "${AIRFLOW_CI_IMAGE_DEFAULT}"
save_to_file AIRFLOW_CI_IMAGE_DEFAULT
fi
if [[ "${AIRFLOW_RELEASE_BUILD}" == "true" ]]; then
docker tag "${AIRFLOW_CI_IMAGE}" "${AIRFLOW_CI_IMAGE_LATEST}"
add_image_to_push "${AIRFLOW_CI_IMAGE_LATEST}"
save_to_file AIRFLOW_CI_IMAGE_LATEST
if [[ "${PYTHON_VERSION_FOR_DEFAULT_IMAGE}" == "${PYTHON_VERSION}" ]]; then
docker tag "${AIRFLOW_CI_IMAGE}" "${AIRFLOW_CI_IMAGE_LATEST_DEFAULT}"
add_image_to_push "${AIRFLOW_CI_IMAGE_LATEST_DEFAULT}"
save_to_file AIRFLOW_CI_IMAGE_LATEST_DEFAULT
fi
fi
fi
if [[ "${AIRFLOW_CONTAINER_SKIP_CHECKLICENCE_IMAGE}" == "true" ]]; then
echo "Skip building the CHECKLICENCE image"
else
start_step "Build Airflow CHECKLICENCE image"
docker build . -f Dockerfile-checklicence \
"${DOCKER_CACHE_DIRECTIVE_CHECKLICENCE[@]}" -t ${AIRFLOW_CHECKLICENCE_IMAGE}
add_image_to_push "${AIRFLOW_CHECKLICENCE_IMAGE}"
save_to_file AIRFLOW_CHECKLICENCE_IMAGE
fi
start_step "Pushing images"
if [[ "${AIRFLOW_CONTAINER_PUSH_IMAGES}" != "false" ]]; then
echo
echo "Pushing images: ${IMAGES_BUILT}"
echo
for IMAGE in ${IMAGES_BUILT}
do
echo "Pushing image '${IMAGE}'"
docker push ${IMAGE}
done
else
echo
echo "Skip pushing images."
echo "Images built: ${IMAGES_BUILT}"
echo
fi
end_step
echo
echo "Build finished"
echo