| #!/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. |
| # shellcheck shell=bash |
| set -euo pipefail |
| |
| if [[ "$#" != 1 ]]; then |
| echo |
| echo "ERROR! There should be 'runtime', 'ci' or 'dev' parameter passed as argument.". |
| echo |
| exit 1 |
| fi |
| |
| AIRFLOW_PYTHON_VERSION=${AIRFLOW_PYTHON_VERSION:-3.10.18} |
| PYTHON_LTO=${PYTHON_LTO:-true} |
| GOLANG_MAJOR_MINOR_VERSION=${GOLANG_MAJOR_MINOR_VERSION:-1.24.4} |
| TEMURIN_VERSION=${TEMURIN_VERSION:-11} |
| NODEJS_VERSION=${NODEJS_VERSION:-22.23.1} |
| # Keep in sync with the "packageManager" pin in ts-sdk/package.json so the version corepack |
| # resolves for ts-sdk is the one baked into the image. |
| PNPM_VERSION=${PNPM_VERSION:-10.28.1} |
| RUSTUP_DEFAULT_TOOLCHAIN=${RUSTUP_DEFAULT_TOOLCHAIN:-stable} |
| RUSTUP_VERSION=${RUSTUP_VERSION:-1.29.0} |
| COSIGN_VERSION=${COSIGN_VERSION:-3.0.5} |
| |
| if [[ "${1}" == "runtime" ]]; then |
| INSTALLATION_TYPE="RUNTIME" |
| elif [[ "${1}" == "dev" ]]; then |
| INSTALLATION_TYPE="DEV" |
| elif [[ "${1}" == "ci" ]]; then |
| INSTALLATION_TYPE="CI" |
| else |
| echo |
| echo "ERROR! Wrong argument. Passed ${1} and it should be one of 'runtime', 'ci' or 'dev'.". |
| echo |
| exit 1 |
| fi |
| |
| function get_dev_apt_deps() { |
| if [[ "${DEV_APT_DEPS=}" == "" ]]; then |
| DEV_APT_DEPS="\ |
| apt-transport-https \ |
| apt-utils \ |
| build-essential \ |
| dirmngr \ |
| freetds-bin \ |
| freetds-dev \ |
| git \ |
| graphviz \ |
| graphviz-dev \ |
| krb5-user \ |
| lcov \ |
| ldap-utils \ |
| libbluetooth-dev \ |
| libbz2-dev \ |
| libc6-dev \ |
| libdb-dev \ |
| libev-dev \ |
| libev4 \ |
| libffi-dev \ |
| libgdbm-compat-dev \ |
| libgdbm-dev \ |
| libgeos-dev \ |
| libkrb5-dev \ |
| libldap2-dev \ |
| libleveldb-dev \ |
| libleveldb1d \ |
| liblzma-dev \ |
| libncurses5-dev \ |
| libreadline6-dev \ |
| libsasl2-2 \ |
| libsasl2-dev \ |
| libsasl2-modules \ |
| libsqlite3-dev \ |
| libssl-dev \ |
| libxmlsec1 \ |
| libxmlsec1-dev \ |
| libzstd-dev \ |
| locales \ |
| lsb-release \ |
| lzma \ |
| lzma-dev \ |
| openssh-client \ |
| openssl \ |
| pkg-config \ |
| pkgconf \ |
| sasl2-bin \ |
| sqlite3 \ |
| sudo \ |
| tk-dev \ |
| unixodbc \ |
| unixodbc-dev \ |
| uuid-dev \ |
| wget \ |
| xz-utils \ |
| zlib1g-dev \ |
| " |
| export DEV_APT_DEPS |
| fi |
| } |
| |
| function get_runtime_apt_deps() { |
| local debian_version |
| local debian_version_apt_deps |
| # Get debian version without installing lsb_release |
| # shellcheck disable=SC1091 |
| debian_version=$(. /etc/os-release; printf '%s\n' "$VERSION_CODENAME";) |
| echo |
| echo "DEBIAN CODENAME: ${debian_version}" |
| echo |
| debian_version_apt_deps="\ |
| libffi8 \ |
| libldap-2.5-0 \ |
| libssl3 \ |
| netcat-openbsd\ |
| " |
| echo |
| echo "APPLIED INSTALLATION CONFIGURATION FOR DEBIAN VERSION: ${debian_version}" |
| echo |
| if [[ "${RUNTIME_APT_DEPS=}" == "" ]]; then |
| RUNTIME_APT_DEPS="\ |
| ${debian_version_apt_deps} \ |
| apt-transport-https \ |
| apt-utils \ |
| curl \ |
| dumb-init \ |
| freetds-bin \ |
| git \ |
| gnupg \ |
| iputils-ping \ |
| krb5-user \ |
| ldap-utils \ |
| libev4 \ |
| libgeos-dev \ |
| libsasl2-2 \ |
| libsasl2-modules \ |
| libxmlsec1 \ |
| locales \ |
| lsb-release \ |
| openssh-client \ |
| rsync \ |
| sasl2-bin \ |
| sqlite3 \ |
| sudo \ |
| unixodbc \ |
| wget\ |
| " |
| export RUNTIME_APT_DEPS |
| fi |
| } |
| |
| function install_docker_cli() { |
| apt-get update |
| apt-get install ca-certificates curl |
| install -m 0755 -d /etc/apt/keyrings |
| curl -fsSL --retry 3 --retry-delay 5 https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc |
| chmod a+r /etc/apt/keyrings/docker.asc |
| # shellcheck disable=SC1091 |
| echo \ |
| "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \ |
| $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ |
| tee /etc/apt/sources.list.d/docker.list > /dev/null |
| apt-get update |
| apt-get install -y --no-install-recommends docker-ce-cli |
| } |
| |
| function install_debian_dev_dependencies() { |
| apt-get update |
| apt-get install -yqq --no-install-recommends apt-utils >/dev/null 2>&1 |
| apt-get install -y --no-install-recommends wget curl gnupg2 lsb-release ca-certificates |
| # shellcheck disable=SC2086 |
| export ${ADDITIONAL_DEV_APT_ENV?} |
| if [[ ${DEV_APT_COMMAND} != "" ]]; then |
| bash -o pipefail -o errexit -o nounset -o nolog -c "${DEV_APT_COMMAND}" |
| fi |
| if [[ ${ADDITIONAL_DEV_APT_COMMAND} != "" ]]; then |
| bash -o pipefail -o errexit -o nounset -o nolog -c "${ADDITIONAL_DEV_APT_COMMAND}" |
| fi |
| apt-get update |
| local debian_version |
| local debian_version_apt_deps |
| # Get debian version without installing lsb_release |
| # shellcheck disable=SC1091 |
| debian_version=$(. /etc/os-release; printf '%s\n' "$VERSION_CODENAME";) |
| echo |
| echo "DEBIAN CODENAME: ${debian_version}" |
| echo |
| # shellcheck disable=SC2086 |
| apt-get install -y --no-install-recommends ${DEV_APT_DEPS} |
| } |
| |
| function install_additional_dev_dependencies() { |
| if [[ "${ADDITIONAL_DEV_APT_DEPS=}" != "" ]]; then |
| # shellcheck disable=SC2086 |
| apt-get install -y --no-install-recommends ${ADDITIONAL_DEV_APT_DEPS} |
| fi |
| } |
| |
| function link_python() { |
| # link python binaries to /usr/local/bin and /usr/python/bin with and without 3 suffix |
| # Links in /usr/local/bin are needed for tools that expect python to be there |
| # Links in /usr/python/bin are needed for tools that are detecting home of python installation including |
| # lib/site-packages. The /usr/python/bin should be first in PATH in order to help with the last part. |
| for dst in pip3 python3 python3-config; do |
| src="$(echo "${dst}" | tr -d 3)" |
| echo "Linking ${dst} in /usr/local/bin and /usr/python/bin" |
| ln -sv "/usr/python/bin/${dst}" "/usr/local/bin/${dst}" |
| for dir in /usr/local/bin /usr/python/bin; do |
| if [[ ! -e "${dir}/${src}" ]]; then |
| echo "Creating ${src} - > ${dst} link in ${dir}" |
| ln -sv "${dir}/${dst}" "${dir}/${src}" |
| fi |
| done |
| done |
| for dst in /usr/python/lib/* |
| do |
| src="/usr/local/lib/$(basename "${dst}")" |
| if [[ -e "${src}" ]]; then |
| rm -rf "${src}" |
| fi |
| echo "Linking ${dst} to ${src}" |
| ln -sv "${dst}" "${src}" |
| done |
| ldconfig |
| } |
| |
| function install_debian_runtime_dependencies() { |
| apt-get update |
| apt-get install --no-install-recommends -yqq apt-utils >/dev/null 2>&1 |
| apt-get install -y --no-install-recommends wget curl gnupg2 lsb-release ca-certificates |
| # shellcheck disable=SC2086 |
| export ${ADDITIONAL_RUNTIME_APT_ENV?} |
| if [[ "${RUNTIME_APT_COMMAND}" != "" ]]; then |
| bash -o pipefail -o errexit -o nounset -o nolog -c "${RUNTIME_APT_COMMAND}" |
| fi |
| if [[ "${ADDITIONAL_RUNTIME_APT_COMMAND}" != "" ]]; then |
| bash -o pipefail -o errexit -o nounset -o nolog -c "${ADDITIONAL_RUNTIME_APT_COMMAND}" |
| fi |
| apt-get update |
| # shellcheck disable=SC2086 |
| apt-get install -y --no-install-recommends ${RUNTIME_APT_DEPS} ${ADDITIONAL_RUNTIME_APT_DEPS} |
| apt-get autoremove -yqq --purge |
| apt-get clean |
| link_python |
| rm -rf /var/lib/apt/lists/* /var/log/* |
| } |
| |
| function install_cosign() { |
| local arch |
| arch="$(dpkg --print-architecture)" |
| declare -A cosign_sha256s=( |
| # https://github.com/sigstore/cosign/releases/download/v${COSIGN_VERSION}/cosign_checksums.txt |
| [amd64]="db15cc99e6e4837daabab023742aaddc3841ce57f193d11b7c3e06c8003642b2" |
| [arm64]="d098f3168ae4b3aa70b4ca78947329b953272b487727d1722cb3cb098a1a20ab" |
| ) |
| local cosign_sha256="${cosign_sha256s[${arch}]}" |
| if [[ -z "${cosign_sha256}" ]]; then |
| echo "Unsupported architecture for cosign: ${arch}" |
| exit 1 |
| fi |
| curl -fsSL --retry 3 --retry-delay 5 \ |
| "https://github.com/sigstore/cosign/releases/download/v${COSIGN_VERSION}/cosign-linux-${arch}" \ |
| -o /tmp/cosign |
| echo "${cosign_sha256} /tmp/cosign" | sha256sum --check |
| chmod +x /tmp/cosign |
| } |
| |
| function install_python() { |
| # If system python (3.11 in bookworm) is installed (via automatic installation of some dependencies for example), we need |
| # to fail and make sure that it is not there, because there can be strange interactions if we install |
| # newer version and system libraries are installed, because |
| # when you create a virtualenv part of the shared libraries of Python can be taken from the system |
| # Installation leading to weird errors when you want to install some modules - for example when you install ssl: |
| # /usr/python/lib/python3.11/lib-dynload/_ssl.cpython-311-aarch64-linux-gnu.so: undefined symbol: _PyModule_Add |
| if dpkg -l | grep '^ii' | grep '^ii libpython' >/dev/null; then |
| echo |
| echo "ERROR! System python is installed by one of the previous steps" |
| echo |
| echo "Please make sure that no python packages are installed by default. Displaying the reason why libpython3.11 is installed:" |
| echo |
| apt-get install -yqq aptitude >/dev/null |
| aptitude why libpython3.11 |
| echo |
| exit 1 |
| else |
| echo |
| echo "GOOD! System python is not installed - OK" |
| echo |
| fi |
| wget --tries=3 --waitretry=5 -O python.tar.xz "https://www.python.org/ftp/python/${AIRFLOW_PYTHON_VERSION%%[a-z]*}/Python-${AIRFLOW_PYTHON_VERSION}.tar.xz" |
| local major_minor_version |
| major_minor_version="${AIRFLOW_PYTHON_VERSION%.*}" |
| local major minor |
| major="${major_minor_version%.*}" |
| minor="${major_minor_version#*.}" |
| echo "Verifying Python ${AIRFLOW_PYTHON_VERSION} (${major_minor_version})" |
| if [[ "${major}" -gt 3 ]] || [[ "${major}" -eq 3 && "${minor}" -ge 11 ]]; then |
| # Sigstore verification for Python >= 3.11 (PEP 761) |
| declare -A sigstore_identities=( |
| # https://peps.python.org/pep-0664/#release-manager-and-crew |
| [3.11]="pablogsal@python.org" |
| # https://peps.python.org/pep-0693/#release-manager-and-crew |
| [3.12]="thomas@python.org" |
| # https://peps.python.org/pep-0719/#release-manager-and-crew |
| [3.13]="thomas@python.org" |
| # https://peps.python.org/pep-0745/#release-manager-and-crew |
| [3.14]="hugo@python.org" |
| ) |
| declare -A sigstore_issuers=( |
| [3.11]="https://accounts.google.com" |
| [3.12]="https://accounts.google.com" |
| [3.13]="https://accounts.google.com" |
| [3.14]="https://github.com/login/oauth" |
| ) |
| wget --tries=3 --waitretry=5 -O python.tar.xz.sigstore \ |
| "https://www.python.org/ftp/python/${AIRFLOW_PYTHON_VERSION%%[a-z]*}/Python-${AIRFLOW_PYTHON_VERSION}.tar.xz.sigstore" |
| install_cosign |
| local identity="${sigstore_identities[${major_minor_version}]}" |
| local issuer="${sigstore_issuers[${major_minor_version}]}" |
| /tmp/cosign verify-blob \ |
| --bundle python.tar.xz.sigstore \ |
| --certificate-identity "${identity}" \ |
| --certificate-oidc-issuer "${issuer}" \ |
| python.tar.xz |
| rm -f python.tar.xz.sigstore /tmp/cosign |
| else |
| # PGP verification for Python 3.10 |
| declare -A keys=( |
| # gpg: key 64E628F8D684696D: public key "Pablo Galindo Salgado <pablogsal@gmail.com>" imported |
| # https://peps.python.org/pep-0619/#release-manager-and-crew |
| [3.10]="A035C8C19219BA821ECEA86B64E628F8D684696D" |
| ) |
| wget --tries=3 --waitretry=5 -O python.tar.xz.asc \ |
| "https://www.python.org/ftp/python/${AIRFLOW_PYTHON_VERSION%%[a-z]*}/Python-${AIRFLOW_PYTHON_VERSION}.tar.xz.asc" |
| GNUPGHOME="$(mktemp -d)"; export GNUPGHOME |
| local gpg_key="${keys[${major_minor_version}]}" |
| echo "Using GPG key ${gpg_key}" |
| gpg --batch --import "/scripts/docker/keys/python-${major_minor_version}.asc" |
| gpg --batch --verify python.tar.xz.asc python.tar.xz |
| gpgconf --kill all |
| rm -rf "${GNUPGHOME}" python.tar.xz.asc |
| fi |
| mkdir -p /usr/src/python |
| tar --extract --directory /usr/src/python --strip-components=1 --file python.tar.xz |
| rm python.tar.xz |
| cd /usr/src/python |
| arch="$(dpkg --print-architecture)"; arch="${arch##*-}" |
| gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" |
| EXTRA_CFLAGS="$(dpkg-buildflags --get CFLAGS)" |
| EXTRA_CFLAGS="${EXTRA_CFLAGS:-} -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer"; |
| LDFLAGS="$(dpkg-buildflags --get LDFLAGS)" |
| LDFLAGS="${LDFLAGS:--Wl},--strip-all" |
| # Link-Time Optimization (LTO) uses MD5 checksums for object file verification during |
| # compilation. In FIPS mode, MD5 is blocked as a non-approved algorithm, causing builds |
| # to fail. The PYTHON_LTO variable allows disabling LTO for FIPS-compliant builds. |
| # See: https://github.com/apache/airflow/issues/58337 |
| local lto_option="" |
| if [[ "${PYTHON_LTO:-true}" == "true" ]]; then |
| lto_option="--with-lto" |
| fi |
| local build_log |
| build_log=$(mktemp) |
| echo "Building Python ${AIRFLOW_PYTHON_VERSION} from source..." |
| if ! ( |
| ./configure --enable-optimizations --prefix=/usr/python/ --with-ensurepip --build="$gnuArch" \ |
| --enable-loadable-sqlite-extensions --enable-option-checking=fatal \ |
| --enable-shared ${lto_option} && \ |
| make -s -j "$(nproc)" "EXTRA_CFLAGS=${EXTRA_CFLAGS:-}" \ |
| "LDFLAGS=${LDFLAGS:--Wl},-rpath='\$\$ORIGIN/../lib'" python && \ |
| make -s -j "$(nproc)" install |
| ) > "${build_log}" 2>&1; then |
| echo |
| echo "ERROR! Python build failed. Build output:" |
| echo |
| cat "${build_log}" |
| rm -f "${build_log}" |
| exit 1 |
| fi |
| rm -f "${build_log}" |
| cd / |
| rm -rf /usr/src/python |
| find /usr/python -depth \ |
| \( \ |
| \( -type d -a \( -name test -o -name tests -o -name idle_test \) \) \ |
| -o \( -type f -a \( -name 'libpython*.a' \) \) \ |
| \) -exec rm -rf '{}' + |
| link_python |
| } |
| |
| function install_golang() { |
| curl --retry 3 --retry-delay 5 "https://dl.google.com/go/go${GOLANG_MAJOR_MINOR_VERSION}.linux-$(dpkg --print-architecture).tar.gz" -o "go${GOLANG_MAJOR_MINOR_VERSION}.linux.tar.gz" |
| rm -rf /usr/local/go && tar -C /usr/local -xzf go"${GOLANG_MAJOR_MINOR_VERSION}".linux.tar.gz |
| } |
| |
| function install_jdk() { |
| # Install Eclipse Temurin JDK from the Adoptium apt repository (https://adoptium.net/installation/linux/). |
| apt-get update -qq |
| apt-get install -y --no-install-recommends wget gnupg apt-transport-https ca-certificates |
| mkdir -p /etc/apt/keyrings |
| wget -qO - https://packages.adoptium.net/artifactory/api/gpg/key/public \ |
| | tee /etc/apt/keyrings/adoptium.asc > /dev/null |
| # shellcheck disable=SC1091 |
| DISTRO_CODENAME=$(. /etc/os-release; echo "${VERSION_CODENAME}") |
| echo "deb [signed-by=/etc/apt/keyrings/adoptium.asc] \ |
| https://packages.adoptium.net/artifactory/deb ${DISTRO_CODENAME} main" \ |
| | tee /etc/apt/sources.list.d/adoptium.list > /dev/null |
| apt-get update -qq |
| apt-get install -y --no-install-recommends "temurin-${TEMURIN_VERSION}-jdk" |
| apt-get clean |
| rm -rf /var/lib/apt/lists/* |
| } |
| |
| function install_nodejs() { |
| local arch |
| arch="$(dpkg --print-architecture)" |
| declare -A nodejs_targets=( |
| [amd64]="linux-x64" |
| [arm64]="linux-arm64" |
| ) |
| declare -A nodejs_sha256s=( |
| # https://nodejs.org/dist/v${NODEJS_VERSION}/SHASUMS256.txt |
| [amd64]="9749e988f437343b7fa832c69ded82a312e41a03116d766797ac14f6f9eee578" |
| [arm64]="0294e8b915ab75f92c7513d2fcb830ae06e10684e6c603e99a87dbf8835389c1" |
| ) |
| local target="${nodejs_targets[${arch}]}" |
| local nodejs_sha256="${nodejs_sha256s[${arch}]}" |
| if [[ -z "${target}" ]]; then |
| echo "Unsupported architecture for nodejs: ${arch}" |
| exit 1 |
| fi |
| curl --retry 3 --retry-delay 5 \ |
| "https://nodejs.org/dist/v${NODEJS_VERSION}/node-v${NODEJS_VERSION}-${target}.tar.xz" \ |
| -o /tmp/nodejs.tar.xz |
| echo "${nodejs_sha256} /tmp/nodejs.tar.xz" | sha256sum --check |
| tar -xJf /tmp/nodejs.tar.xz --strip-components=1 -C /usr/local --no-same-owner |
| rm -f /tmp/nodejs.tar.xz |
| corepack enable --install-directory /usr/local/bin |
| # corepack enable only writes shims; prepare downloads and caches the pnpm binary into the |
| # image so it is available offline and matches the version ts-sdk pins. |
| corepack prepare "pnpm@${PNPM_VERSION}" --activate |
| } |
| |
| function install_rustup() { |
| local arch |
| arch="$(dpkg --print-architecture)" |
| declare -A rustup_targets=( |
| [amd64]="x86_64-unknown-linux-gnu" |
| [arm64]="aarch64-unknown-linux-gnu" |
| ) |
| declare -A rustup_sha256s=( |
| # https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/{target}/rustup-init.sha256 |
| [amd64]="4acc9acc76d5079515b46346a485974457b5a79893cfb01112423c89aeb5aa10" |
| [arm64]="9732d6c5e2a098d3521fca8145d826ae0aaa067ef2385ead08e6feac88fa5792" |
| ) |
| local target="${rustup_targets[${arch}]}" |
| local rustup_sha256="${rustup_sha256s[${arch}]}" |
| if [[ -z "${target}" ]]; then |
| echo "Unsupported architecture for rustup: ${arch}" |
| exit 1 |
| fi |
| curl --proto '=https' --tlsv1.2 -sSf --retry 3 --retry-delay 5 \ |
| "https://static.rust-lang.org/rustup/archive/${RUSTUP_VERSION}/${target}/rustup-init" \ |
| -o /tmp/rustup-init |
| echo "${rustup_sha256} /tmp/rustup-init" | sha256sum --check |
| chmod +x /tmp/rustup-init |
| /tmp/rustup-init -y --default-toolchain "${RUSTUP_DEFAULT_TOOLCHAIN}" |
| rm -f /tmp/rustup-init |
| } |
| |
| function apt_clean() { |
| apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false |
| rm -rf /var/lib/apt/lists/* /var/log/* |
| } |
| |
| if [[ "${INSTALLATION_TYPE}" == "RUNTIME" ]]; then |
| get_runtime_apt_deps |
| install_debian_runtime_dependencies |
| install_docker_cli |
| apt_clean |
| else |
| get_dev_apt_deps |
| install_debian_dev_dependencies |
| install_python |
| install_additional_dev_dependencies |
| install_rustup |
| if [[ "${INSTALLATION_TYPE}" == "CI" ]]; then |
| install_golang |
| install_jdk |
| install_nodejs |
| fi |
| install_docker_cli |
| apt_clean |
| fi |