Skip to content

Commit 5aec2d5

Browse files
authored
Install python from source again (#53150)
* Draft: Build python from source Builds python from source, also installs golang from official distribution. Does both of these for the ci image only. * Updates path * Adds version upgrade check for python version Adds support for using the airflow api to fetch the newest python patch version available for specific major_minor pair * Updated to use args in dockerfile for python * Added support for golang upgrade * Fixed go version sorting in pre_commit install * Added github token usage and fixed version regex Updated python fetch request during upgrade to use github token and fixed the regex * Updated dockerfile.ci file * Added support for multiple python versions Adds python version from global consts into build args now for the docker ci. * Updated python install * Increases timeout for ci image build
1 parent b9620bf commit 5aec2d5

File tree

10 files changed

+310
-19
lines changed

10 files changed

+310
-19
lines changed

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
!providers/
3939
!task-sdk/
4040
!airflow-ctl/
41+
!go-sdk/
4142

4243
# Add all "test" distributions
4344
!tests

.github/workflows/additional-ci-image-checks.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,4 @@ jobs:
150150
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
151151
run: echo "$GITHUB_TOKEN" | docker login ghcr.io -u "$actor" --password-stdin
152152
- name: "Check that image builds quickly"
153-
run: breeze shell --max-time 600 --platform "${PLATFORM}"
153+
run: breeze shell --max-time 900 --platform "${PLATFORM}"

.github/workflows/basic-tests.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,9 @@ jobs:
293293
--hook-stage manual update-installers-and-pre-commit || true
294294
if: always()
295295
env:
296+
UPGRADE_UV: "true"
297+
UPGRADE_PYTHON: "false"
298+
UPGRADE_GOLANG: "true"
296299
UPGRADE_PIP: "false"
297300
UPGRADE_PRE_COMMIT: "false"
298301
UPGRADE_NODE_LTS: "false"
@@ -303,6 +306,9 @@ jobs:
303306
--hook-stage manual update-installers-and-pre-commit
304307
if: always()
305308
env:
309+
UPGRADE_UV: "false"
310+
UPGRADE_PYTHON: "true"
311+
UPGRADE_GOLANG: "false"
306312
UPGRADE_PIP: "true"
307313
UPGRADE_PRE_COMMIT: "true"
308314
UPGRADE_NODE_LTS: "true"

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ repos:
213213
^scripts/ci/pre_commit/update_installers_and_pre_commit\.py$
214214
pass_filenames: false
215215
require_serial: true
216-
additional_dependencies: ['pyyaml>=6.0.2', 'rich>=12.4.4', 'requests>=2.31.0']
216+
additional_dependencies: ['pyyaml>=6.0.2', 'rich>=12.4.4', 'requests>=2.31.0',"packaging>=25"]
217217
- id: update-chart-dependencies
218218
name: Update chart dependencies to latest (manual)
219219
entry: ./scripts/ci/pre_commit/update_chart_dependencies.py

Dockerfile.ci

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
#
1717
# WARNING: THIS DOCKERFILE IS NOT INTENDED FOR PRODUCTION USE OR DEPLOYMENT.
1818
#
19-
ARG PYTHON_BASE_IMAGE="python:3.10-slim-bookworm"
19+
ARG BASE_IMAGE="debian:bookworm-slim"
2020

2121
##############################################################################################
2222
# This is the script image where we keep all inlined bash scripts needed in other segments
23-
# We use PYTHON_BASE_IMAGE to make sure that the scripts are different for different platforms.
23+
# We use BASE_IMAGE to make sure that the scripts are different for different platforms.
2424
##############################################################################################
25-
FROM ${PYTHON_BASE_IMAGE} as scripts
25+
FROM ${BASE_IMAGE} as scripts
2626

2727
##############################################################################################
2828
# Please DO NOT modify the inlined scripts manually. The content of those files will be
@@ -31,22 +31,27 @@ FROM ${PYTHON_BASE_IMAGE} as scripts
3131
# make the PROD Dockerfile standalone
3232
##############################################################################################
3333

34-
# The content below is automatically copied from scripts/docker/install_os_dependencies.sh
35-
COPY <<"EOF" /install_os_dependencies.sh
34+
# The content below is automatically copied from scripts/docker/install_os_dependencies_ci.sh
35+
COPY <<"EOF" /install_os_dependencies_ci.sh
3636
#!/usr/bin/env bash
3737
set -euo pipefail
3838

3939
if [[ "$#" != 1 ]]; then
40-
echo "ERROR! There should be 'runtime' or 'dev' parameter passed as argument.".
40+
echo "ERROR! There should be 'runtime', 'ci' or 'dev' parameter passed as argument.".
4141
exit 1
4242
fi
4343

44+
AIRFLOW_PYTHON_VERSION=${AIRFLOW_PYTHON_VERSION:-v3.10.10}
45+
GOLANG_MAJOR_MINOR_VERSION=${GOLANG_MAJOR_MINOR_VERSION:-1.24.4}
46+
4447
if [[ "${1}" == "runtime" ]]; then
4548
INSTALLATION_TYPE="RUNTIME"
4649
elif [[ "${1}" == "dev" ]]; then
47-
INSTALLATION_TYPE="dev"
50+
INSTALLATION_TYPE="DEV"
51+
elif [[ "${1}" == "ci" ]]; then
52+
INSTALLATION_TYPE="CI"
4853
else
49-
echo "ERROR! Wrong argument. Passed ${1} and it should be one of 'runtime' or 'dev'.".
54+
echo "ERROR! Wrong argument. Passed ${1} and it should be one of 'runtime', 'ci' or 'dev'.".
5055
exit 1
5156
fi
5257

@@ -56,7 +61,10 @@ function get_dev_apt_deps() {
5661
freetds-bin freetds-dev git graphviz graphviz-dev krb5-user ldap-utils libev4 libev-dev libffi-dev libgeos-dev \
5762
libkrb5-dev libldap2-dev libleveldb1d libleveldb-dev libsasl2-2 libsasl2-dev libsasl2-modules \
5863
libssl-dev libxmlsec1 libxmlsec1-dev locales lsb-release openssh-client pkgconf sasl2-bin \
59-
software-properties-common sqlite3 sudo unixodbc unixodbc-dev zlib1g-dev"
64+
software-properties-common sqlite3 sudo unixodbc unixodbc-dev zlib1g-dev \
65+
gdb lcov pkg-config libbz2-dev libgdbm-dev libgdbm-compat-dev liblzma-dev \
66+
libncurses5-dev libreadline6-dev libsqlite3-dev lzma lzma-dev tk-dev uuid-dev \
67+
libzstd-dev"
6068
export DEV_APT_DEPS
6169
fi
6270
}
@@ -143,14 +151,36 @@ function install_debian_runtime_dependencies() {
143151
rm -rf /var/lib/apt/lists/* /var/log/*
144152
}
145153

154+
function install_python() {
155+
git clone --branch "${AIRFLOW_PYTHON_VERSION}" --depth 1 https://github.com/python/cpython.git
156+
cd cpython
157+
./configure --enable-optimizations
158+
make -s -j "$(nproc)" all
159+
make -s -j "$(nproc)" install
160+
ln -s /usr/local/bin/python3 /usr/local/bin/python
161+
ln -s /usr/local/bin/pip3 /usr/local/bin/pip
162+
cd ..
163+
rm -rf cpython
164+
}
165+
166+
function install_golang() {
167+
curl "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"
168+
rm -rf /usr/local/go && tar -C /usr/local -xzf go"${GOLANG_MAJOR_MINOR_VERSION}".linux.tar.gz
169+
}
170+
146171
if [[ "${INSTALLATION_TYPE}" == "RUNTIME" ]]; then
147172
get_runtime_apt_deps
148173
install_debian_runtime_dependencies
149174
install_docker_cli
150175

151176
else
177+
152178
get_dev_apt_deps
153179
install_debian_dev_dependencies
180+
install_python
181+
if [[ "${INSTALLATION_TYPE}" == "CI" ]]; then
182+
install_golang
183+
fi
154184
install_docker_cli
155185
fi
156186
EOF
@@ -939,7 +969,7 @@ function environment_initialization() {
939969
CI=${CI:="false"}
940970

941971
# Added to have run-tests on path
942-
export PATH=${PATH}:${AIRFLOW_SOURCES}
972+
export PATH=${PATH}:${AIRFLOW_SOURCES}:/usr/local/go/bin/
943973

944974
mkdir -pv "${AIRFLOW_HOME}/logs/"
945975

@@ -1257,13 +1287,13 @@ COPY <<"EOF" /entrypoint_exec.sh
12571287
exec /bin/bash "${@}"
12581288
EOF
12591289

1260-
FROM ${PYTHON_BASE_IMAGE} as main
1290+
FROM ${BASE_IMAGE} as main
12611291

12621292
# Nolog bash flag is currently ignored - but you can replace it with other flags (for example
12631293
# xtrace - to show commands executed)
12641294
SHELL ["/bin/bash", "-o", "pipefail", "-o", "errexit", "-o", "nounset", "-o", "nolog", "-c"]
12651295

1266-
ARG PYTHON_BASE_IMAGE
1296+
ARG BASE_IMAGE
12671297
ARG AIRFLOW_IMAGE_REPOSITORY="https://github.com/apache/airflow"
12681298

12691299
# By increasing this number we can do force build of all dependencies.
@@ -1273,7 +1303,7 @@ ARG AIRFLOW_IMAGE_REPOSITORY="https://github.com/apache/airflow"
12731303
ARG DEPENDENCIES_EPOCH_NUMBER="15"
12741304

12751305
# Make sure noninteractive debian install is used and language variables set
1276-
ENV PYTHON_BASE_IMAGE=${PYTHON_BASE_IMAGE} \
1306+
ENV BASE_IMAGE=${BASE_IMAGE} \
12771307
DEBIAN_FRONTEND=noninteractive LANGUAGE=C.UTF-8 LANG=C.UTF-8 LC_ALL=C.UTF-8 \
12781308
LC_CTYPE=C.UTF-8 LC_MESSAGES=C.UTF-8 \
12791309
DEPENDENCIES_EPOCH_NUMBER=${DEPENDENCIES_EPOCH_NUMBER} \
@@ -1284,7 +1314,7 @@ ENV PYTHON_BASE_IMAGE=${PYTHON_BASE_IMAGE} \
12841314
UV_CACHE_DIR=/root/.cache/uv
12851315

12861316

1287-
RUN echo "Base image version: ${PYTHON_BASE_IMAGE}"
1317+
RUN echo "Base image version: ${BASE_IMAGE}"
12881318

12891319
ARG DEV_APT_COMMAND=""
12901320
ARG ADDITIONAL_DEV_APT_COMMAND=""
@@ -1299,8 +1329,13 @@ ENV DEV_APT_COMMAND=${DEV_APT_COMMAND} \
12991329
ADDITIONAL_DEV_APT_DEPS=${ADDITIONAL_DEV_APT_DEPS} \
13001330
ADDITIONAL_DEV_APT_COMMAND=${ADDITIONAL_DEV_APT_COMMAND}
13011331

1302-
COPY --from=scripts install_os_dependencies.sh /scripts/docker/
1303-
RUN bash /scripts/docker/install_os_dependencies.sh dev
1332+
ARG AIRFLOW_PYTHON_VERSION=v3.10.18
1333+
ENV AIRFLOW_PYTHON_VERSION=$AIRFLOW_PYTHON_VERSION
1334+
ENV GOLANG_MAJOR_MINOR_VERSION=1.24.4
1335+
1336+
COPY --from=scripts install_os_dependencies_ci.sh /scripts/docker/
1337+
1338+
RUN bash /scripts/docker/install_os_dependencies_ci.sh ci
13041339

13051340
COPY --from=scripts common.sh /scripts/docker/
13061341

dev/breeze/src/airflow_breeze/global_constants.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,16 @@ def generate_provider_dependencies_if_needed():
751751
},
752752
]
753753

754+
ALL_PYTHON_VERSION_TO_PATCH_VERSION: dict[str, str] = {
755+
"3.6": "v3.6.1",
756+
"3.7": "v3.7.1",
757+
"3.8": "v3.8.1",
758+
"3.9": "v3.9.23",
759+
"3.10": "v3.10.18",
760+
"3.11": "v3.11.13",
761+
"3.12": "v3.12.11",
762+
}
763+
754764
# Number of slices for low dep tests
755765
NUMBER_OF_LOW_DEP_SLICES = 5
756766

dev/breeze/src/airflow_breeze/params/build_ci_params.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from pathlib import Path
2222

2323
from airflow_breeze.branch_defaults import DEFAULT_AIRFLOW_CONSTRAINTS_BRANCH
24+
from airflow_breeze.global_constants import ALL_PYTHON_VERSION_TO_PATCH_VERSION
2425
from airflow_breeze.params.common_build_params import CommonBuildParams
2526
from airflow_breeze.utils.path_utils import BUILD_CACHE_PATH
2627

@@ -68,6 +69,9 @@ def prepare_arguments_for_docker_build_command(self) -> list[str]:
6869
self._opt_arg("UV_HTTP_TIMEOUT", get_uv_timeout(self))
6970
self._req_arg("AIRFLOW_VERSION", self.airflow_version)
7071
self._req_arg("PYTHON_BASE_IMAGE", self.python_base_image)
72+
self._req_arg(
73+
"AIRFLOW_PYTHON_VERSION", ALL_PYTHON_VERSION_TO_PATCH_VERSION.get(self.python, self.python)
74+
)
7175
if self.upgrade_to_newer_dependencies:
7276
self._opt_arg("UPGRADE_RANDOM_INDICATOR_STRING", f"{random.randrange(2**32):x}")
7377
# optional build args

scripts/ci/pre_commit/update_installers_and_pre_commit.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from pathlib import Path
2525

2626
import requests
27+
from packaging.version import Version
2728

2829
sys.path.insert(0, str(Path(__file__).parent.resolve())) # make sure common_precommit_utils is imported
2930
from common_precommit_utils import AIRFLOW_CORE_ROOT_PATH, AIRFLOW_ROOT_PATH, console
@@ -65,6 +66,35 @@ def get_latest_pypi_version(package_name: str) -> str:
6566
return latest_version
6667

6768

69+
def get_latest_python_version(python_major_minor: str, github_token: str | None) -> str | None:
70+
latest_version = None
71+
# Matches versions of vA.B.C and vA.B where C can only be numeric and v is optional
72+
version_match = re.compile(rf"^v?{python_major_minor}\.?\d*$")
73+
headers = {"User-Agent": "Python requests"}
74+
if github_token:
75+
headers["Authorization"] = f"Bearer {github_token}"
76+
for i in range(5):
77+
response = requests.get(
78+
f"https://api.github.com/repos/python/cpython/tags?per_page=100&page={i + 1}",
79+
headers=headers,
80+
)
81+
response.raise_for_status() # Ensure we got a successful response
82+
data = response.json()
83+
versions = [str(tag["name"]) for tag in data if version_match.match(tag.get("name", ""))]
84+
if versions:
85+
latest_version = sorted(versions, key=Version, reverse=True)[0]
86+
break
87+
return latest_version
88+
89+
90+
def get_latest_golang_version() -> str:
91+
response = requests.get("https://go.dev/dl/?mode=json")
92+
response.raise_for_status() # Ensure we got a successful response
93+
versions = response.json()
94+
stable_versions = [release["version"].replace("go", "") for release in versions if release["stable"]]
95+
return sorted(stable_versions, key=Version, reverse=True)[0]
96+
97+
6898
def get_latest_lts_node_version() -> str:
6999
response = requests.get("https://nodejs.org/dist/index.json")
70100
response.raise_for_status() # Ensure we got a successful response
@@ -92,6 +122,15 @@ class Quoting(Enum):
92122
(re.compile(r"(\| *`AIRFLOW_PIP_VERSION` *\| *)(`[0-9.]+`)( *\|)"), Quoting.REVERSE_SINGLE_QUOTED),
93123
]
94124

125+
PYTHON_PATTERNS: list[tuple[str, Quoting]] = [
126+
(r"(\"{python_major_minor}\": \")(v[0-9.]+)(\")", Quoting.UNQUOTED),
127+
]
128+
129+
GOLANG_PATTERNS: list[tuple[re.Pattern, Quoting]] = [
130+
(re.compile(r"(GOLANG_MAJOR_MINOR_VERSION=)([0-9.]+)"), Quoting.UNQUOTED),
131+
(re.compile(r"(\| *`GOLANG_MAJOR_MINOR_VERSION` *\| *)(`[0-9.]+`)( *\|)"), Quoting.REVERSE_SINGLE_QUOTED),
132+
]
133+
95134
UV_PATTERNS: list[tuple[re.Pattern, Quoting]] = [
96135
(re.compile(r"(AIRFLOW_UV_VERSION=)([0-9.]+)"), Quoting.UNQUOTED),
97136
(re.compile(r"(uv>=)([0-9.]+)"), Quoting.UNQUOTED),
@@ -167,6 +206,8 @@ def get_replacement(value: str, quoting: Quoting) -> str:
167206

168207
UPGRADE_UV: bool = os.environ.get("UPGRADE_UV", "true").lower() == "true"
169208
UPGRADE_PIP: bool = os.environ.get("UPGRADE_PIP", "true").lower() == "true"
209+
UPGRADE_PYTHON: bool = os.environ.get("UPGRADE_PYTHON", "true").lower() == "true"
210+
UPGRADE_GOLANG: bool = os.environ.get("UPGRADE_GOLANG", "true").lower() == "true"
170211
UPGRADE_SETUPTOOLS: bool = os.environ.get("UPGRADE_SETUPTOOLS", "true").lower() == "true"
171212
UPGRADE_PRE_COMMIT: bool = os.environ.get("UPGRADE_PRE_COMMIT", "true").lower() == "true"
172213
UPGRADE_NODE_LTS: bool = os.environ.get("UPGRADE_NODE_LTS", "true").lower() == "true"
@@ -175,6 +216,10 @@ def get_replacement(value: str, quoting: Quoting) -> str:
175216
UPGRADE_GITPYTHON: bool = os.environ.get("UPGRADE_GITPYTHON", "true").lower() == "true"
176217
UPGRADE_RICH: bool = os.environ.get("UPGRADE_RICH", "true").lower() == "true"
177218

219+
ALL_PYTHON_MAJOR_MINOR_VERSIONS = ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
220+
221+
GITHUB_TOKEN: str | None = os.environ.get("GITHUB_TOKEN")
222+
178223

179224
def replace_version(pattern: re.Pattern[str], version: str, text: str, keep_total_length: bool = True) -> str:
180225
# Assume that the pattern has up to 3 replacement groups:
@@ -205,6 +250,7 @@ def replacer(match):
205250

206251
if __name__ == "__main__":
207252
changed = False
253+
golang_version = get_latest_golang_version()
208254
pip_version = get_latest_pypi_version("pip")
209255
uv_version = get_latest_pypi_version("uv")
210256
setuptools_version = get_latest_pypi_version("setuptools")
@@ -225,6 +271,28 @@ def replacer(match):
225271
new_content = replace_version(
226272
line_pattern, get_replacement(pip_version, quoting), new_content, keep_length
227273
)
274+
if UPGRADE_PYTHON:
275+
for python_version in ALL_PYTHON_MAJOR_MINOR_VERSIONS:
276+
latest_python_version = get_latest_python_version(python_version, GITHUB_TOKEN)
277+
if latest_python_version:
278+
console.print(
279+
f"[bright_blue]Latest python {python_version} version: {latest_python_version}"
280+
)
281+
for line_format, quoting in PYTHON_PATTERNS:
282+
line_pattern = re.compile(line_format.format(python_major_minor=python_version))
283+
console.print(line_pattern)
284+
new_content = replace_version(
285+
line_pattern,
286+
get_replacement(latest_python_version, quoting),
287+
new_content,
288+
keep_length,
289+
)
290+
if UPGRADE_GOLANG:
291+
console.print(f"[bright_blue]Latest golang version: {golang_version}")
292+
for line_pattern, quoting in GOLANG_PATTERNS:
293+
new_content = replace_version(
294+
line_pattern, get_replacement(golang_version, quoting), new_content, keep_length
295+
)
228296
if UPGRADE_SETUPTOOLS:
229297
console.print(f"[bright_blue]Latest setuptools version: {setuptools_version}")
230298
for line_pattern, quoting in SETUPTOOLS_PATTERNS:

scripts/docker/entrypoint_ci.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ function environment_initialization() {
130130
CI=${CI:="false"}
131131

132132
# Added to have run-tests on path
133-
export PATH=${PATH}:${AIRFLOW_SOURCES}
133+
export PATH=${PATH}:${AIRFLOW_SOURCES}:/usr/local/go/bin/
134134

135135
mkdir -pv "${AIRFLOW_HOME}/logs/"
136136

0 commit comments

Comments
 (0)