From 14149ff22be04a304e793bea9dc998c0e5434eae Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Wed, 13 May 2026 02:38:22 +0300 Subject: [PATCH 01/26] SCALRCORE-38392 update tools: - kubectl: v1.35.3 -> v1.36.1 - gcloud: 564.0.0 -> 568.0.0 - aws_cli: 2.34.29 -> 2.34.45 - azure_cli: 2.85.0 -> 2.86.0 - scalr_cli: 0.17.8 -> 0.18.0 - python: 3.14.4 -> 3.14.5 - python_release: 20260408 -> 20260510 --- Dockerfile | 2 +- README.md | 38 +++++++++++++++++++------------------- versions | 14 +++++++------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5b38dc9..4f46bdb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ # # Note: This is a PUBLIC image, it should not contain any sensitive data. -FROM debian:trixie-slim +FROM debian:trixie-slim@sha256:109e2c65005bf160609e4ba6acf7783752f8502ad218e298253428690b9eaa4b ARG TARGETARCH diff --git a/README.md b/README.md index 05db1ba..b7ff78d 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,14 @@ This environment comes pre-equipped with a comprehensive suite of tools essentia * wget - File downloads from the web * ca-certificates - Trusted CA certificates * **Programming Languages** - * Python ([v3.14.4](https://www.python.org/downloads/release/python-3144/)) - General-purpose programming language (release) + * Python ([v3.14.5](https://www.python.org/downloads/release/python-3145/)) - General-purpose programming language (release) * jq - Command-line JSON processor * **Cloud Clients** - * AWS CLI ([2.34.29](https://github.com/aws/aws-cli/releases/tag/2.34.29)) - Amazon Web Services CLI. - * Azure CLI ([2.85.0](https://github.com/Azure/azure-cli/releases/tag/azure-cli-2.85.0)) - Microsoft Azure CLI. - * Google Cloud SDK ([564.0.0](https://cloud.google.com/sdk/docs/release-notes#56400)) - Stable, Alpha, Beta components. Includes kubectl authenticator. - * Kubectl ([0.35.3](https://github.com/kubernetes/kubectl/releases/tag/v0.35.3)) - Kubernetes CLI. - * Scalr CLI ([0.17.8](https://github.com/Scalr/scalr-cli/releases/tag/v0.17.8)) - The command-line to communicate with the Scalr API. + * AWS CLI ([2.34.45](https://github.com/aws/aws-cli/releases/tag/2.34.45)) - Amazon Web Services CLI. + * Azure CLI ([2.86.0](https://github.com/Azure/azure-cli/releases/tag/azure-cli-2.86.0)) - Microsoft Azure CLI. + * Google Cloud SDK ([568.0.0](https://cloud.google.com/sdk/docs/release-notes#56800)) - Stable, Alpha, Beta components. Includes kubectl authenticator. + * Kubectl ([0.36.1](https://github.com/kubernetes/kubectl/releases/tag/v0.36.1)) - Kubernetes CLI. + * Scalr CLI ([0.18.0](https://github.com/Scalr/scalr-cli/releases/tag/v0.18.0)) - The command-line to communicate with the Scalr API. The versions for Python, Cloud Clients, Kubectl, and Scalr CLI are specifically pinned and detailed in the [versions](./versions) file. All other software included in this environment is sourced directly from the Debian Trixie upstream repositories. @@ -49,13 +49,13 @@ Two Python variants are available: ```bash docker buildx build \ - --build-arg PYTHON_VERSION=3.14.4 \ - --build-arg PYTHON_RELEASE=20260408 \ - --build-arg KUBECTL_VERSION=v1.35.3 \ - --build-arg GCLOUD_VERSION=564.0.0 \ - --build-arg AWS_CLI_VERSION=2.34.29 \ - --build-arg AZURE_CLI_VERSION=2.85.0 \ - --build-arg SCALR_CLI_VERSION=0.17.8 \ + --build-arg PYTHON_VERSION=3.14.5 \ + --build-arg PYTHON_RELEASE=20260510 \ + --build-arg KUBECTL_VERSION=v1.36.1 \ + --build-arg GCLOUD_VERSION=568.0.0 \ + --build-arg AWS_CLI_VERSION=2.34.45 \ + --build-arg AZURE_CLI_VERSION=2.86.0 \ + --build-arg SCALR_CLI_VERSION=0.18.0 \ --platform linux/amd64 \ -t scalr/runner:latest --load . ``` @@ -65,12 +65,12 @@ To build the Python 3.9 variant: ```bash docker buildx build \ --build-arg PYTHON_VERSION=3.9.25 \ - --build-arg PYTHON_RELEASE=20251031 \ - --build-arg KUBECTL_VERSION=v1.35.3 \ - --build-arg GCLOUD_VERSION=564.0.0 \ - --build-arg AWS_CLI_VERSION=2.34.29 \ - --build-arg AZURE_CLI_VERSION=2.85.0 \ - --build-arg SCALR_CLI_VERSION=0.17.8 \ + --build-arg PYTHON_RELEASE=20260510 \ + --build-arg KUBECTL_VERSION=v1.36.1 \ + --build-arg GCLOUD_VERSION=568.0.0 \ + --build-arg AWS_CLI_VERSION=2.34.45 \ + --build-arg AZURE_CLI_VERSION=2.86.0 \ + --build-arg SCALR_CLI_VERSION=0.18.0 \ --platform linux/amd64 \ -t scalr/runner:latest-python39 --load . ``` diff --git a/versions b/versions index 8c9bce9..9dee372 100644 --- a/versions +++ b/versions @@ -1,7 +1,7 @@ -kubectl=v1.35.3 -gcloud=564.0.0 -aws_cli=2.34.29 -azure_cli=2.85.0 -scalr_cli=0.17.8 -python=3.14.4 -python_release=20260408 +kubectl=v1.36.1 +gcloud=568.0.0 +aws_cli=2.34.45 +azure_cli=2.86.0 +scalr_cli=0.18.0 +python=3.14.5 +python_release=20260510 From 64daa8be5935b0eeeb2134c8df9a62ab9711fe8f Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Wed, 13 May 2026 11:45:07 +0300 Subject: [PATCH 02/26] SCALRCORE-38392 update software versions; add checksums verification for build pipeline; delete su from images --- .dockerignore | 6 + .github/workflows/build.yaml | 41 ++- .github/workflows/release.yaml | 41 ++- Dockerfile | 72 ++++- README.md | 46 ++-- bump-versions.py | 484 +++++++++++++++++++++++++++++++++ bump-versions.sh | 287 ------------------- versions | 27 +- versions_python39 | 4 + 9 files changed, 637 insertions(+), 371 deletions(-) create mode 100644 .dockerignore create mode 100755 bump-versions.py delete mode 100755 bump-versions.sh create mode 100644 versions_python39 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fde3a6b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +# Default-deny: ignore everything, then re-include only what the build needs. +# The Dockerfile currently uses no COPY/ADD, so the build context can be empty. +* + +# Keep Dockerfile itself accessible to buildx. +!Dockerfile diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a1f4e4a..12baac3 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -29,28 +29,26 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Read Versions - id: versions + - name: Prepare build args + id: args run: | - echo "kubectl=$(grep '^kubectl=' versions | cut -d= -f2)" | tee -a $GITHUB_OUTPUT - echo "gcloud=$(grep '^gcloud=' versions | cut -d= -f2)" | tee -a $GITHUB_OUTPUT - echo "aws_cli=$(grep '^aws_cli=' versions | cut -d= -f2)" | tee -a $GITHUB_OUTPUT - echo "azure_cli=$(grep '^azure_cli=' versions | cut -d= -f2)" | tee -a $GITHUB_OUTPUT - echo "scalr_cli=$(grep '^scalr_cli=' versions | cut -d= -f2)" | tee -a $GITHUB_OUTPUT - echo "python=$(grep '^python=' versions | cut -d= -f2)" | tee -a $GITHUB_OUTPUT - echo "python_release=$(grep '^python_release=' versions | cut -d= -f2)" | tee -a $GITHUB_OUTPUT + { + echo 'default<> "$GITHUB_OUTPUT" + # Override block for the python39 variant — appended after defaults + # in the build-args input; later entries win in buildx. + { + echo 'python39<> "$GITHUB_OUTPUT" - name: Build Docker image uses: docker/build-push-action@v6 with: - build-args: | - KUBECTL_VERSION=${{ steps.versions.outputs.kubectl }} - GCLOUD_VERSION=${{ steps.versions.outputs.gcloud }} - AWS_CLI_VERSION=${{ steps.versions.outputs.aws_cli }} - AZURE_CLI_VERSION=${{ steps.versions.outputs.azure_cli }} - SCALR_CLI_VERSION=${{ steps.versions.outputs.scalr_cli }} - PYTHON_VERSION=${{ steps.versions.outputs.python }} - PYTHON_RELEASE=${{ steps.versions.outputs.python_release }} + build-args: ${{ steps.args.outputs.default }} cache-from: type=registry,ref=scalr/runner:buildcache cache-to: type=registry,ref=scalr/runner:buildcache load: true @@ -72,13 +70,8 @@ jobs: uses: docker/build-push-action@v6 with: build-args: | - KUBECTL_VERSION=${{ steps.versions.outputs.kubectl }} - GCLOUD_VERSION=${{ steps.versions.outputs.gcloud }} - AWS_CLI_VERSION=${{ steps.versions.outputs.aws_cli }} - AZURE_CLI_VERSION=${{ steps.versions.outputs.azure_cli }} - SCALR_CLI_VERSION=${{ steps.versions.outputs.scalr_cli }} - PYTHON_VERSION=3.9.25 - PYTHON_RELEASE=20251031 + ${{ steps.args.outputs.default }} + ${{ steps.args.outputs.python39 }} cache-from: type=registry,ref=scalr/runner:buildcache-python39 cache-to: type=registry,ref=scalr/runner:buildcache-python39 load: true diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 97734a1..00d4739 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -25,16 +25,21 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Read Versions - id: versions + - name: Prepare build args + id: args run: | - echo "kubectl=$(grep '^kubectl=' versions | cut -d= -f2)" | tee -a $GITHUB_OUTPUT - echo "gcloud=$(grep '^gcloud=' versions | cut -d= -f2)" | tee -a $GITHUB_OUTPUT - echo "aws_cli=$(grep '^aws_cli=' versions | cut -d= -f2)" | tee -a $GITHUB_OUTPUT - echo "azure_cli=$(grep '^azure_cli=' versions | cut -d= -f2)" | tee -a $GITHUB_OUTPUT - echo "scalr_cli=$(grep '^scalr_cli=' versions | cut -d= -f2)" | tee -a $GITHUB_OUTPUT - echo "python=$(grep '^python=' versions | cut -d= -f2)" | tee -a $GITHUB_OUTPUT - echo "python_release=$(grep '^python_release=' versions | cut -d= -f2)" | tee -a $GITHUB_OUTPUT + { + echo 'default<> "$GITHUB_OUTPUT" + # Override block for the python39 variant — appended after defaults + # in the build-args input; later entries win in buildx. + { + echo 'python39<> "$GITHUB_OUTPUT" - name: Format Image Tag id: image_tag @@ -44,14 +49,7 @@ jobs: - name: Build Docker image uses: docker/build-push-action@v6 with: - build-args: | - KUBECTL_VERSION=${{ steps.versions.outputs.kubectl }} - GCLOUD_VERSION=${{ steps.versions.outputs.gcloud }} - AWS_CLI_VERSION=${{ steps.versions.outputs.aws_cli }} - AZURE_CLI_VERSION=${{ steps.versions.outputs.azure_cli }} - SCALR_CLI_VERSION=${{ steps.versions.outputs.scalr_cli }} - PYTHON_VERSION=${{ steps.versions.outputs.python }} - PYTHON_RELEASE=${{ steps.versions.outputs.python_release }} + build-args: ${{ steps.args.outputs.default }} platforms: linux/amd64,linux/arm64 cache-from: type=registry,ref=scalr/runner:buildcache cache-to: type=registry,ref=scalr/runner:buildcache @@ -64,13 +62,8 @@ jobs: uses: docker/build-push-action@v6 with: build-args: | - KUBECTL_VERSION=${{ steps.versions.outputs.kubectl }} - GCLOUD_VERSION=${{ steps.versions.outputs.gcloud }} - AWS_CLI_VERSION=${{ steps.versions.outputs.aws_cli }} - AZURE_CLI_VERSION=${{ steps.versions.outputs.azure_cli }} - SCALR_CLI_VERSION=${{ steps.versions.outputs.scalr_cli }} - PYTHON_VERSION=3.9.25 - PYTHON_RELEASE=20251031 + ${{ steps.args.outputs.default }} + ${{ steps.args.outputs.python39 }} platforms: linux/amd64,linux/arm64 cache-from: type=registry,ref=scalr/runner:buildcache-python39 cache-to: type=registry,ref=scalr/runner:buildcache-python39 diff --git a/Dockerfile b/Dockerfile index 4f46bdb..d37fafc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,11 +20,7 @@ RUN </dev/null || true +EOT + ENTRYPOINT ["/usr/bin/bash"] diff --git a/README.md b/README.md index b7ff78d..e56f099 100644 --- a/README.md +++ b/README.md @@ -47,30 +47,32 @@ Two Python variants are available: ## Runner Image Building +All tool versions and SHA256 checksums are stored as `KEY=value` lines: + +- [`versions`](./versions) — defaults (kubectl, gcloud, AWS CLI, Azure CLI, Scalr CLI, Python 3.14, AWS SSM Plugin) +- [`versions_python39`](./versions_python39) — Python 3.9 overrides (consumed only by the `-python39` image) + +The snippet below forwards every entry as a `--build-arg`, so each download is +verified against a hash pinned in this repo. + +### Default image (Python 3.14) + ```bash docker buildx build \ - --build-arg PYTHON_VERSION=3.14.5 \ - --build-arg PYTHON_RELEASE=20260510 \ - --build-arg KUBECTL_VERSION=v1.36.1 \ - --build-arg GCLOUD_VERSION=568.0.0 \ - --build-arg AWS_CLI_VERSION=2.34.45 \ - --build-arg AZURE_CLI_VERSION=2.86.0 \ - --build-arg SCALR_CLI_VERSION=0.18.0 \ + $(grep -v '^#' versions | grep -v '^$' | xargs -I {} echo --build-arg={}) \ --platform linux/amd64 \ -t scalr/runner:latest --load . ``` -To build the Python 3.9 variant: +### Python 3.9 variant + +Pass both files; later args override earlier ones, so `versions_python39` +replaces the `PYTHON_*` keys from `versions`: ```bash docker buildx build \ - --build-arg PYTHON_VERSION=3.9.25 \ - --build-arg PYTHON_RELEASE=20260510 \ - --build-arg KUBECTL_VERSION=v1.36.1 \ - --build-arg GCLOUD_VERSION=568.0.0 \ - --build-arg AWS_CLI_VERSION=2.34.45 \ - --build-arg AZURE_CLI_VERSION=2.86.0 \ - --build-arg SCALR_CLI_VERSION=0.18.0 \ + $(grep -v '^#' versions | grep -v '^$' | xargs -I {} echo --build-arg={}) \ + $(grep -v '^#' versions_python39 | grep -v '^$' | xargs -I {} echo --build-arg={}) \ --platform linux/amd64 \ -t scalr/runner:latest-python39 --load . ``` @@ -80,9 +82,17 @@ docker buildx build \ To update all tool versions to their latest releases, run: ```bash -./bump-versions.sh +./bump-versions.py ``` -This script fetches the latest versions from upstream sources and updates the [versions](./versions) file and README.md. +This script fetches the latest versions from upstream sources and updates the [versions](./versions) and [versions_python39](./versions_python39) files (plus the "Included Tools" section of this README). For every tool it also refreshes the per-arch SHA256 checksums used by the Dockerfile to verify each download. -Requirements: `curl` and `jq`. +Requirements: `python3` (stdlib only, no `pip install` needed). + +GitHub's anonymous API quota is 60 requests/hour. The script makes ~5 calls to +`api.github.com` per run, so frequent reruns may hit `HTTP 403: rate limit exceeded`. +Export `GITHUB_TOKEN` (or `GH_TOKEN`) to lift the limit to 5000/hour: + +```bash +GITHUB_TOKEN=$(gh auth token) ./bump-versions.py +``` diff --git a/bump-versions.py b/bump-versions.py new file mode 100755 index 0000000..7ef23f2 --- /dev/null +++ b/bump-versions.py @@ -0,0 +1,484 @@ +#!/usr/bin/env python3 +"""Bump tool versions in ./versions and refresh pinned SHA256 checksums. + +For every tool that has a discoverable upstream version index, this script +fetches the latest release, updates ./versions, and refreshes the per-arch +SHA256 checksums the Dockerfile uses to verify each download. The AWS +Session Manager Plugin version stays manually pinned (no upstream version +index) but its SHAs are still refreshed against the pinned version. + +Stdlib only — no pip dependencies. +""" + +from __future__ import annotations + +import hashlib +import json +import logging +import os +import re +import sys +import urllib.error +import urllib.request +from pathlib import Path +from typing import Callable + +VERSIONS_FILE = Path("versions") +PYTHON39_FILE = Path("versions_python39") +README_FILE = Path("README.md") +UA = "bump-versions.py (https://github.com/Scalr/runner)" +# Optional auth — GitHub's unauthenticated API quota is 60/hour per IP; +# 5000/hour with any valid token. CI sets GITHUB_TOKEN automatically. +GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN") + + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", +) +log = logging.getLogger("bump-versions") + + +# --- HTTP / hash helpers ---------------------------------------------------- + + +def _request(url: str) -> urllib.request.Request: + headers = {"User-Agent": UA} + if GITHUB_TOKEN and "api.github.com" in url: + headers["Authorization"] = f"Bearer {GITHUB_TOKEN}" + return urllib.request.Request(url, headers=headers) + + +def http_get_text(url: str) -> str: + with urllib.request.urlopen(_request(url), timeout=60) as resp: + return resp.read().decode() + + +def http_get_json(url: str): + return json.loads(http_get_text(url)) + + +def compute_sha256_url(url: str) -> str: + """Stream URL through SHA256 without holding the whole file in memory.""" + h = hashlib.sha256() + with urllib.request.urlopen(_request(url), timeout=300) as resp: + for chunk in iter(lambda: resp.read(1 << 20), b""): + h.update(chunk) + return h.hexdigest() + + +def fetch_text_sha(url: str) -> str: + """Sidecar .sha256 file (single hash, optional trailing filename).""" + return http_get_text(url).strip().split()[0] + + +def fetch_sha_from_sumsfile(sums_url: str, asset: str) -> str: + """SHA256SUMS-style file: ' ' lines.""" + for line in http_get_text(sums_url).splitlines(): + parts = line.split() + if len(parts) >= 2 and parts[1] == asset: + return parts[0] + raise RuntimeError(f"{asset} not found in {sums_url}") + + +# --- versions file I/O ------------------------------------------------------ + + +def read_versions(path: Path = VERSIONS_FILE) -> dict[str, str]: + out: dict[str, str] = {} + if not path.exists(): + return out + for line in path.read_text().splitlines(): + line = line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + k, _, v = line.partition("=") + out[k] = v + return out + + +def write_value(key: str, value: str, path: Path = VERSIONS_FILE) -> None: + """Update an existing key= line in-place, or append if missing.""" + lines = path.read_text().splitlines() if path.exists() else [] + new_line = f"{key}={value}" + for i, line in enumerate(lines): + if line.startswith(f"{key}="): + lines[i] = new_line + break + else: + lines.append(new_line) + path.write_text("\n".join(lines) + "\n") + + +# --- README pretty-section updaters ----------------------------------------- + + +def _readme_sub(pattern: str, replacement: str) -> None: + content = README_FILE.read_text() + new, n = re.subn(pattern, replacement, content, count=1) + if n == 0: + log.warning(f"README: no match for /{pattern}/") + return + README_FILE.write_text(new) + + +def update_readme_python(version: str) -> None: + no_dots = version.replace(".", "") + _readme_sub( + r"Python \(\[v[0-9.]+\]\(https://www\.python\.org/downloads/release/python-[0-9]+/\)\)", + f"Python ([v{version}](https://www.python.org/downloads/release/python-{no_dots}/))", + ) + + +def update_readme_aws_cli(version: str) -> None: + _readme_sub( + r"AWS CLI \(\[[0-9.]+\]\(https://github\.com/aws/aws-cli/releases/tag/[0-9.]+\)\)", + f"AWS CLI ([{version}](https://github.com/aws/aws-cli/releases/tag/{version}))", + ) + + +def update_readme_azure_cli(version: str) -> None: + _readme_sub( + r"Azure CLI \(\[[0-9.]+\]\(https://github\.com/Azure/azure-cli/releases/tag/azure-cli-[0-9.]+\)\)", + f"Azure CLI ([{version}](https://github.com/Azure/azure-cli/releases/tag/azure-cli-{version}))", + ) + + +def update_readme_gcloud(version: str) -> None: + no_dots = version.replace(".", "") + _readme_sub( + r"Google Cloud SDK \(\[[0-9.]+\]\(https://cloud\.google\.com/sdk/docs/release-notes#[^)]+\)\)", + f"Google Cloud SDK ([{version}](https://cloud.google.com/sdk/docs/release-notes#{no_dots}))", + ) + + +def update_readme_kubectl(version: str) -> None: + # version is "v1.36.1"; kubectl repo uses "0.36.1" matching k8s minor. + repo_ver = version.replace("v1.", "0.", 1) + _readme_sub( + r"Kubectl \(\[[0-9.]+\]\(https://github\.com/kubernetes/kubectl/releases/tag/v[0-9.]+\)\)", + f"Kubectl ([{repo_ver}](https://github.com/kubernetes/kubectl/releases/tag/v{repo_ver}))", + ) + + +def update_readme_scalr_cli(version: str) -> None: + _readme_sub( + r"Scalr CLI \(\[[0-9.]+\]\(https://github\.com/Scalr/scalr-cli/releases/tag/v[0-9.]+\)\)", + f"Scalr CLI ([{version}](https://github.com/Scalr/scalr-cli/releases/tag/v{version}))", + ) + + +# --- upstream version fetchers ---------------------------------------------- + + +def get_latest_kubectl() -> str: + return http_get_text("https://dl.k8s.io/release/stable.txt").strip() + + +def get_latest_gcloud() -> str: + html = http_get_text("https://cloud.google.com/sdk/docs/release-notes") + m = re.search(r"(\d+\.\d+\.\d+)", html) + return m.group(1) if m else "" + + +def get_latest_aws_cli() -> str: + tags = http_get_json("https://api.github.com/repos/aws/aws-cli/tags") + for t in tags: + if t["name"].startswith("2."): + return t["name"] + return "" + + +def get_latest_azure_cli() -> str: + rel = http_get_json("https://api.github.com/repos/Azure/azure-cli/releases/latest") + return rel["tag_name"].removeprefix("azure-cli-") + + +def get_latest_scalr_cli() -> str: + rel = http_get_json("https://api.github.com/repos/Scalr/scalr-cli/releases/latest") + return rel["tag_name"].removeprefix("v") + + +def _latest_python_version(series: str) -> tuple[str, str]: + """Latest (version, release) for the given series (e.g. '3.14' or '3.9'). + + Walks recent releases newest-first — python-build-standalone ships + different CPython series per release, and older series (e.g. 3.9) appear + only in some releases, so the "latest" release may not carry every series. + """ + # per_page>10 routinely 504s here — the response is large because each + # release lists ~100 assets. 10 is plenty: 3.9 appears in most releases. + releases = http_get_json( + "https://api.github.com/repos/astral-sh/python-build-standalone/releases?per_page=10" + ) + pat = re.compile(rf"cpython-({re.escape(series)}\.\d+)") + for rel in releases: + for asset in rel.get("assets", []): + m = pat.search(asset["name"]) + if m: + return m.group(1), rel["tag_name"] + return "", "" + + +def get_latest_python_info() -> tuple[str, str]: + """Return (version, release), e.g. ('3.14.5', '20260510').""" + return _latest_python_version("3.14") + + +def get_latest_python39_info() -> tuple[str, str]: + """Return (version, release) for the latest 3.9.x in python-build-standalone.""" + return _latest_python_version("3.9") + + +# --- per-tool SHA refresh --------------------------------------------------- + + +def refresh_kubectl_shas(version: str) -> None: + write_value( + "KUBECTL_SHA256_AMD64", + fetch_text_sha( + f"https://dl.k8s.io/release/{version}/bin/linux/amd64/kubectl.sha256" + ), + ) + write_value( + "KUBECTL_SHA256_ARM64", + fetch_text_sha( + f"https://dl.k8s.io/release/{version}/bin/linux/arm64/kubectl.sha256" + ), + ) + + +def refresh_python_shas(version: str, release: str, path: Path = VERSIONS_FILE) -> None: + sums = ( + "https://github.com/astral-sh/python-build-standalone/releases/download/" + f"{release}/SHA256SUMS" + ) + write_value( + "PYTHON_SHA256_AMD64", + fetch_sha_from_sumsfile( + sums, + f"cpython-{version}+{release}-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst", + ), + path, + ) + write_value( + "PYTHON_SHA256_ARM64", + fetch_sha_from_sumsfile( + sums, + f"cpython-{version}+{release}-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst", + ), + path, + ) + + +def refresh_gcloud_shas(version: str) -> None: + base = ( + "https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/" + f"google-cloud-sdk-{version}-linux-" + ) + write_value("GCLOUD_SHA256_AMD64", compute_sha256_url(f"{base}x86_64.tar.gz")) + write_value("GCLOUD_SHA256_ARM64", compute_sha256_url(f"{base}arm.tar.gz")) + + +def refresh_aws_cli_shas(version: str) -> None: + base = "https://awscli.amazonaws.com/awscli-exe-linux-" + write_value( + "AWS_CLI_SHA256_AMD64", compute_sha256_url(f"{base}x86_64-{version}.zip") + ) + write_value( + "AWS_CLI_SHA256_ARM64", + compute_sha256_url(f"{base}aarch64-{version}.zip"), + ) + + +def refresh_scalr_cli_shas(version: str) -> None: + sums = ( + f"https://github.com/Scalr/scalr-cli/releases/download/v{version}/" + f"scalr-cli_{version}_SHA256SUMS" + ) + write_value( + "SCALR_CLI_SHA256_AMD64", + fetch_sha_from_sumsfile(sums, f"scalr-cli_{version}_linux_amd64.zip"), + ) + write_value( + "SCALR_CLI_SHA256_ARM64", + fetch_sha_from_sumsfile(sums, f"scalr-cli_{version}_linux_arm64.zip"), + ) + + +def refresh_aws_ssm_plugin_shas(version: str) -> None: + base = f"https://s3.amazonaws.com/session-manager-downloads/plugin/{version}" + write_value( + "AWS_SSM_PLUGIN_SHA256_AMD64", + compute_sha256_url(f"{base}/ubuntu_64bit/session-manager-plugin.deb"), + ) + write_value( + "AWS_SSM_PLUGIN_SHA256_ARM64", + compute_sha256_url(f"{base}/ubuntu_arm64/session-manager-plugin.deb"), + ) + + +# --- main flow -------------------------------------------------------------- + + +def bump( + label: str, + key: str, + latest: str, + current: str, + changes: list[tuple[str, str, str]], + refresh_shas: Callable[[str], None] | None = None, + update_readme: Callable[[str], None] | None = None, +) -> bool: + if latest and current != latest: + log.info(f"{label}: {current} -> {latest}") + write_value(key, latest) + if refresh_shas: + refresh_shas(latest) + if update_readme: + update_readme(latest) + changes.append((label, current, latest)) + return True + log.info(f"{label}: {current} (up to date)") + return False + + +def main() -> int: + if not VERSIONS_FILE.exists(): + log.error(f"{VERSIONS_FILE} not found (run from repo root)") + return 1 + + log.info("Fetching latest versions...") + vs = read_versions() + changes: list[tuple[str, str, str]] = [] + + bump( + "kubectl", + "KUBECTL_VERSION", + get_latest_kubectl(), + vs.get("KUBECTL_VERSION", ""), + changes, + refresh_kubectl_shas, + update_readme_kubectl, + ) + bump( + "gcloud", + "GCLOUD_VERSION", + get_latest_gcloud(), + vs.get("GCLOUD_VERSION", ""), + changes, + refresh_gcloud_shas, + update_readme_gcloud, + ) + bump( + "aws_cli", + "AWS_CLI_VERSION", + get_latest_aws_cli(), + vs.get("AWS_CLI_VERSION", ""), + changes, + refresh_aws_cli_shas, + update_readme_aws_cli, + ) + bump( + "azure_cli", + "AZURE_CLI_VERSION", + get_latest_azure_cli(), + vs.get("AZURE_CLI_VERSION", ""), + changes, + None, + update_readme_azure_cli, + ) + bump( + "scalr_cli", + "SCALR_CLI_VERSION", + get_latest_scalr_cli(), + vs.get("SCALR_CLI_VERSION", ""), + changes, + refresh_scalr_cli_shas, + update_readme_scalr_cli, + ) + + # Python has two coupled fields (version + release) and one SHA refresh. + cur_v = vs.get("PYTHON_VERSION", "") + cur_r = vs.get("PYTHON_RELEASE", "") + lat_v, lat_r = get_latest_python_info() + py_changed = False + if lat_v and cur_v != lat_v: + log.info(f"python: {cur_v} -> {lat_v}") + write_value("PYTHON_VERSION", lat_v) + update_readme_python(lat_v) + changes.append(("python", cur_v, lat_v)) + py_changed = True + else: + log.info(f"python: {cur_v} (up to date)") + if lat_r and cur_r != lat_r: + log.info(f"python_release: {cur_r} -> {lat_r}") + write_value("PYTHON_RELEASE", lat_r) + changes.append(("python_release", cur_r, lat_r)) + py_changed = True + else: + log.info(f"python_release: {cur_r} (up to date)") + if py_changed: + refresh_python_shas(lat_v, lat_r) + + # Python 3.9 variant — same upstream as 3.14, separate file with plain PYTHON_* keys. + vs39 = read_versions(PYTHON39_FILE) + cur_v39 = vs39.get("PYTHON_VERSION", "") + cur_r39 = vs39.get("PYTHON_RELEASE", "") + lat_v39, lat_r39 = get_latest_python39_info() + if not (lat_v39 and lat_r39): + log.warning("python39: no 3.9 build found in recent python-build-standalone releases; skipping") + else: + py39_changed = False + if cur_v39 != lat_v39: + log.info(f"python39: {cur_v39} -> {lat_v39}") + write_value("PYTHON_VERSION", lat_v39, PYTHON39_FILE) + changes.append(("python39", cur_v39, lat_v39)) + py39_changed = True + else: + log.info(f"python39: {cur_v39} (up to date)") + if cur_r39 != lat_r39: + log.info(f"python39_release: {cur_r39} -> {lat_r39}") + write_value("PYTHON_RELEASE", lat_r39, PYTHON39_FILE) + changes.append(("python39_release", cur_r39, lat_r39)) + py39_changed = True + else: + log.info(f"python39_release: {cur_r39} (up to date)") + if py39_changed: + refresh_python_shas(lat_v39, lat_r39, PYTHON39_FILE) + + # AWS SSM Plugin: version is manually pinned (no upstream version index), + # but its SHAs are refreshed in case the pinned version was hand-edited. + cur_ssm = vs.get("AWS_SSM_PLUGIN_VERSION", "") + if cur_ssm: + log.info(f"aws_ssm_plugin: {cur_ssm} (manually pinned; refreshing SHAs)") + refresh_aws_ssm_plugin_shas(cur_ssm) + else: + log.warning(f"aws_ssm_plugin: no version pinned in {VERSIONS_FILE}") + + if changes: + log.info("Summary of changes:") + for label, old, new in changes: + log.info(f" - {label}: {old} -> {new}") + else: + log.info("All versions are up to date.") + + log.info("Done!") + + if changes: + print() + for label, old, new in changes: + print(f"- {label}: {old} -> {new}") + + return 0 + + +if __name__ == "__main__": + try: + sys.exit(main()) + except urllib.error.URLError as e: + log.error(f"network error: {e}") + sys.exit(1) + except KeyboardInterrupt: + sys.exit(130) diff --git a/bump-versions.sh b/bump-versions.sh deleted file mode 100755 index 71018ae..0000000 --- a/bump-versions.sh +++ /dev/null @@ -1,287 +0,0 @@ -#!/usr/bin/env bash -# -# Script to bump all versions in ./versions file from their upstream sources. -# -set -euo pipefail - -VERSIONS_FILE="./versions" -README_FILE="./README.md" - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } -log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } -log_error() { echo -e "${RED}[ERROR]${NC} $1"; } - -# Check required tools -for cmd in curl jq; do - if ! command -v "$cmd" &> /dev/null; then - log_error "$cmd is required but not installed." - exit 1 - fi -done - -# Function to get current version from versions file -get_current_version() { - local key="$1" - grep "^${key}=" "$VERSIONS_FILE" | cut -d'=' -f2 -} - -# Function to update version in versions file -update_version() { - local key="$1" - local new_version="$2" - if [[ "$OSTYPE" == "darwin"* ]]; then - sed -i '' "s/^${key}=.*/${key}=${new_version}/" "$VERSIONS_FILE" - else - sed -i "s/^${key}=.*/${key}=${new_version}/" "$VERSIONS_FILE" - fi -} - -# Function to update version in README.md (docker build args) -update_readme_version() { - local build_arg="$1" - local new_version="$2" - if [[ "$OSTYPE" == "darwin"* ]]; then - sed -i '' "s/--build-arg ${build_arg}=[^ ]*/--build-arg ${build_arg}=${new_version}/" "$README_FILE" - else - sed -i "s/--build-arg ${build_arg}=[^ ]*/--build-arg ${build_arg}=${new_version}/" "$README_FILE" - fi -} - -# Function to update Python version in Included Tools section -# Format: Python ([v3.13.3](https://www.python.org/downloads/release/python-3133/)) -update_readme_python() { - local new_version="$1" - local version_no_dots="${new_version//./}" - if [[ "$OSTYPE" == "darwin"* ]]; then - sed -i '' -E "s|Python \(\[v[0-9.]+\]\(https://www\.python\.org/downloads/release/python-[0-9]+/\)\)|Python ([v${new_version}](https://www.python.org/downloads/release/python-${version_no_dots}/))|" "$README_FILE" - else - sed -i -E "s|Python \(\[v[0-9.]+\]\(https://www\.python\.org/downloads/release/python-[0-9]+/\)\)|Python ([v${new_version}](https://www.python.org/downloads/release/python-${version_no_dots}/))|" "$README_FILE" - fi -} - -# Function to update AWS CLI version in Included Tools section -# Format: AWS CLI ([2.27.1](https://github.com/aws/aws-cli/releases/tag/2.27.32)) -update_readme_aws() { - local new_version="$1" - if [[ "$OSTYPE" == "darwin"* ]]; then - sed -i '' -E "s|AWS CLI \(\[[0-9.]+\]\(https://github\.com/aws/aws-cli/releases/tag/[0-9.]+\)\)|AWS CLI ([${new_version}](https://github.com/aws/aws-cli/releases/tag/${new_version}))|" "$README_FILE" - else - sed -i -E "s|AWS CLI \(\[[0-9.]+\]\(https://github\.com/aws/aws-cli/releases/tag/[0-9.]+\)\)|AWS CLI ([${new_version}](https://github.com/aws/aws-cli/releases/tag/${new_version}))|" "$README_FILE" - fi -} - -# Function to update Azure CLI version in Included Tools section -# Format: Azure CLI ([2.71.0](https://github.com/Azure/azure-cli/releases/tag/azure-cli-2.74.0)) -update_readme_azure() { - local new_version="$1" - if [[ "$OSTYPE" == "darwin"* ]]; then - sed -i '' -E "s|Azure CLI \(\[[0-9.]+\]\(https://github\.com/Azure/azure-cli/releases/tag/azure-cli-[0-9.]+\)\)|Azure CLI ([${new_version}](https://github.com/Azure/azure-cli/releases/tag/azure-cli-${new_version}))|" "$README_FILE" - else - sed -i -E "s|Azure CLI \(\[[0-9.]+\]\(https://github\.com/Azure/azure-cli/releases/tag/azure-cli-[0-9.]+\)\)|Azure CLI ([${new_version}](https://github.com/Azure/azure-cli/releases/tag/azure-cli-${new_version}))|" "$README_FILE" - fi -} - -# Function to update Google Cloud SDK version in Included Tools section -# Format: Google Cloud SDK ([525.0.0](https://cloud.google.com/sdk/docs/release-notes#52500_2025-06-03)) -update_readme_gcloud() { - local new_version="$1" - local version_no_dots="${new_version//./}" - if [[ "$OSTYPE" == "darwin"* ]]; then - sed -i '' -E "s|Google Cloud SDK \(\[[0-9.]+\]\(https://cloud\.google\.com/sdk/docs/release-notes#[^)]+\)\)|Google Cloud SDK ([${new_version}](https://cloud.google.com/sdk/docs/release-notes#${version_no_dots}))|" "$README_FILE" - else - sed -i -E "s|Google Cloud SDK \(\[[0-9.]+\]\(https://cloud\.google\.com/sdk/docs/release-notes#[^)]+\)\)|Google Cloud SDK ([${new_version}](https://cloud.google.com/sdk/docs/release-notes#${version_no_dots}))|" "$README_FILE" - fi -} - -# Function to update Kubectl version in Included Tools section -# Format: Kubectl ([0.33.1](https://github.com/kubernetes/kubectl/releases/tag/v0.33.1)) -# Note: kubectl repo uses v0.x.y versioning matching k8s minor version -update_readme_kubectl() { - local new_version="$1" # e.g., v1.35.0 - # Convert v1.35.0 to 0.35.0 for kubectl repo versioning - local kubectl_version="${new_version//v1./0.}" - if [[ "$OSTYPE" == "darwin"* ]]; then - sed -i '' -E "s|Kubectl \(\[[0-9.]+\]\(https://github\.com/kubernetes/kubectl/releases/tag/v[0-9.]+\)\)|Kubectl ([${kubectl_version}](https://github.com/kubernetes/kubectl/releases/tag/v${kubectl_version}))|" "$README_FILE" - else - sed -i -E "s|Kubectl \(\[[0-9.]+\]\(https://github\.com/kubernetes/kubectl/releases/tag/v[0-9.]+\)\)|Kubectl ([${kubectl_version}](https://github.com/kubernetes/kubectl/releases/tag/v${kubectl_version}))|" "$README_FILE" - fi -} - -# Function to update Scalr CLI version in Included Tools section -# Format: Scalr CLI ([0.17.1](https://github.com/Scalr/scalr-cli/releases/tag/v0.17.1)) -update_readme_scalr() { - local new_version="$1" - if [[ "$OSTYPE" == "darwin"* ]]; then - sed -i '' -E "s|Scalr CLI \(\[[0-9.]+\]\(https://github\.com/Scalr/scalr-cli/releases/tag/v[0-9.]+\)\)|Scalr CLI ([${new_version}](https://github.com/Scalr/scalr-cli/releases/tag/v${new_version}))|" "$README_FILE" - else - sed -i -E "s|Scalr CLI \(\[[0-9.]+\]\(https://github\.com/Scalr/scalr-cli/releases/tag/v[0-9.]+\)\)|Scalr CLI ([${new_version}](https://github.com/Scalr/scalr-cli/releases/tag/v${new_version}))|" "$README_FILE" - fi -} - -# Fetch latest kubectl version -get_latest_kubectl() { - curl -sL https://dl.k8s.io/release/stable.txt -} - -# Fetch latest gcloud version -get_latest_gcloud() { - # Get latest version from Google Cloud SDK release notes page - curl -sL "https://cloud.google.com/sdk/docs/release-notes" 2>/dev/null | \ - grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "" -} - -# Fetch latest AWS CLI v2 version -get_latest_aws_cli() { - curl -sL "https://api.github.com/repos/aws/aws-cli/tags" | \ - jq -r '.[].name | select(startswith("2."))' | head -1 -} - -# Fetch latest Azure CLI version -get_latest_azure_cli() { - curl -sL "https://api.github.com/repos/Azure/azure-cli/releases/latest" | \ - jq -r '.tag_name' | sed 's/^azure-cli-//' -} - -# Fetch latest Scalr CLI version -get_latest_scalr_cli() { - curl -sL "https://api.github.com/repos/Scalr/scalr-cli/releases/latest" | \ - jq -r '.tag_name' | sed 's/^v//' -} - -# Fetch latest Python version and release from python-build-standalone -# Returns: "version release" (e.g., "3.13.11 20260114") -get_latest_python_info() { - local release_info - release_info=$(curl -sL "https://api.github.com/repos/astral-sh/python-build-standalone/releases/latest") - local version release - version=$(echo "$release_info" | jq -r '.assets[].name' | grep -oE 'cpython-3\.14\.[0-9]+' | sed 's/cpython-//' | head -1) - release=$(echo "$release_info" | jq -r '.tag_name') - echo "$version $release" -} - -# Track changes (compatible with bash 3.x) -CHANGES="" -CHANGES_COUNT=0 - -add_change() { - local key="$1" - local old_ver="$2" - local new_ver="$3" - if [[ -n "$CHANGES" ]]; then - CHANGES="${CHANGES}|${key}:${old_ver}:${new_ver}" - else - CHANGES="${key}:${old_ver}:${new_ver}" - fi - CHANGES_COUNT=$((CHANGES_COUNT + 1)) -} - -log_info "Fetching latest versions..." - -# Kubectl -CURRENT_KUBECTL=$(get_current_version "kubectl") -LATEST_KUBECTL=$(get_latest_kubectl) -if [[ -n "$LATEST_KUBECTL" && "$CURRENT_KUBECTL" != "$LATEST_KUBECTL" ]]; then - log_info "kubectl: $CURRENT_KUBECTL -> $LATEST_KUBECTL" - update_version "kubectl" "$LATEST_KUBECTL" - update_readme_version "KUBECTL_VERSION" "$LATEST_KUBECTL" - update_readme_kubectl "$LATEST_KUBECTL" - add_change "kubectl" "$CURRENT_KUBECTL" "$LATEST_KUBECTL" -else - log_info "kubectl: $CURRENT_KUBECTL (up to date)" -fi - -# GCloud -CURRENT_GCLOUD=$(get_current_version "gcloud") -LATEST_GCLOUD=$(get_latest_gcloud) -if [[ -n "$LATEST_GCLOUD" && "$CURRENT_GCLOUD" != "$LATEST_GCLOUD" ]]; then - log_info "gcloud: $CURRENT_GCLOUD -> $LATEST_GCLOUD" - update_version "gcloud" "$LATEST_GCLOUD" - update_readme_version "GCLOUD_VERSION" "$LATEST_GCLOUD" - update_readme_gcloud "$LATEST_GCLOUD" - add_change "gcloud" "$CURRENT_GCLOUD" "$LATEST_GCLOUD" -else - log_info "gcloud: $CURRENT_GCLOUD (up to date)" -fi - -# AWS CLI -CURRENT_AWS=$(get_current_version "aws_cli") -LATEST_AWS=$(get_latest_aws_cli) -if [[ -n "$LATEST_AWS" && "$CURRENT_AWS" != "$LATEST_AWS" ]]; then - log_info "aws_cli: $CURRENT_AWS -> $LATEST_AWS" - update_version "aws_cli" "$LATEST_AWS" - update_readme_version "AWS_CLI_VERSION" "$LATEST_AWS" - update_readme_aws "$LATEST_AWS" - add_change "aws_cli" "$CURRENT_AWS" "$LATEST_AWS" -else - log_info "aws_cli: $CURRENT_AWS (up to date)" -fi - -# Azure CLI -CURRENT_AZURE=$(get_current_version "azure_cli") -LATEST_AZURE=$(get_latest_azure_cli) -if [[ -n "$LATEST_AZURE" && "$CURRENT_AZURE" != "$LATEST_AZURE" ]]; then - log_info "azure_cli: $CURRENT_AZURE -> $LATEST_AZURE" - update_version "azure_cli" "$LATEST_AZURE" - update_readme_version "AZURE_CLI_VERSION" "$LATEST_AZURE" - update_readme_azure "$LATEST_AZURE" - add_change "azure_cli" "$CURRENT_AZURE" "$LATEST_AZURE" -else - log_info "azure_cli: $CURRENT_AZURE (up to date)" -fi - -# Scalr CLI -CURRENT_SCALR=$(get_current_version "scalr_cli") -LATEST_SCALR=$(get_latest_scalr_cli) -if [[ -n "$LATEST_SCALR" && "$CURRENT_SCALR" != "$LATEST_SCALR" ]]; then - log_info "scalr_cli: $CURRENT_SCALR -> $LATEST_SCALR" - update_version "scalr_cli" "$LATEST_SCALR" - update_readme_version "SCALR_CLI_VERSION" "$LATEST_SCALR" - update_readme_scalr "$LATEST_SCALR" - add_change "scalr_cli" "$CURRENT_SCALR" "$LATEST_SCALR" -else - log_info "scalr_cli: $CURRENT_SCALR (up to date)" -fi - -# Python (version and release) -CURRENT_PYTHON=$(get_current_version "python") -CURRENT_PYTHON_RELEASE=$(get_current_version "python_release") -read -r LATEST_PYTHON LATEST_PYTHON_RELEASE <<< "$(get_latest_python_info)" -if [[ -n "$LATEST_PYTHON" && "$CURRENT_PYTHON" != "$LATEST_PYTHON" ]]; then - log_info "python: $CURRENT_PYTHON -> $LATEST_PYTHON" - update_version "python" "$LATEST_PYTHON" - update_readme_version "PYTHON_VERSION" "$LATEST_PYTHON" - update_readme_python "$LATEST_PYTHON" - add_change "python" "$CURRENT_PYTHON" "$LATEST_PYTHON" -else - log_info "python: $CURRENT_PYTHON (up to date)" -fi -if [[ -n "$LATEST_PYTHON_RELEASE" && "$CURRENT_PYTHON_RELEASE" != "$LATEST_PYTHON_RELEASE" ]]; then - log_info "python_release: $CURRENT_PYTHON_RELEASE -> $LATEST_PYTHON_RELEASE" - update_version "python_release" "$LATEST_PYTHON_RELEASE" - update_readme_version "PYTHON_RELEASE" "$LATEST_PYTHON_RELEASE" - add_change "python_release" "$CURRENT_PYTHON_RELEASE" "$LATEST_PYTHON_RELEASE" -else - log_info "python_release: $CURRENT_PYTHON_RELEASE (up to date)" -fi - -# Print summary -if [[ $CHANGES_COUNT -gt 0 ]]; then - echo "" - log_info "Summary of changes:" - IFS='|' read -ra CHANGE_ARRAY <<< "$CHANGES" - for change in "${CHANGE_ARRAY[@]}"; do - IFS=':' read -ra PARTS <<< "$change" - echo " - ${PARTS[0]}: ${PARTS[1]} -> ${PARTS[2]}" - done -else - log_info "All versions are up to date." -fi - -echo "" -log_info "Done!" diff --git a/versions b/versions index 9dee372..d4027a9 100644 --- a/versions +++ b/versions @@ -1,7 +1,20 @@ -kubectl=v1.36.1 -gcloud=568.0.0 -aws_cli=2.34.45 -azure_cli=2.86.0 -scalr_cli=0.18.0 -python=3.14.5 -python_release=20260510 +KUBECTL_VERSION=v1.36.1 +KUBECTL_SHA256_AMD64=629d3f410e09bf49b64ae7079f7f0bda1191efed311f7d37fdbab0ad5b0ec2b7 +KUBECTL_SHA256_ARM64=59f7ee8e477fae658447607dc3c8790ac17a1b016c01c622c12070e969e2d4e7 +GCLOUD_VERSION=568.0.0 +GCLOUD_SHA256_AMD64=ef53b350c5da53b7e92d90ab6f65a85c712686858b839e44e6529d5883fd575c +GCLOUD_SHA256_ARM64=119f00184832664b0acfe758395a6b12c57f0f7dd7ec070cdfb193b7b5142b87 +AWS_CLI_VERSION=2.34.45 +AWS_CLI_SHA256_AMD64=2b382fd5c1e8d73f6aaca18e5850758fb9f70a20af9c17deb3078df0dd4af095 +AWS_CLI_SHA256_ARM64=bf29e06a06fb9bac2a3803931e6618a9f7c9f4c6632ec3b587184850b8f03433 +AZURE_CLI_VERSION=2.86.0 +SCALR_CLI_VERSION=0.18.0 +SCALR_CLI_SHA256_AMD64=5321bcebd48d30f7c3a936a5c5f10d9d3cfb77ef2974575d4df8e7ee9f890589 +SCALR_CLI_SHA256_ARM64=8dcada09b5bf10001f4e83190fc9c2a66df692beb07a74b4afe503d2fa75629e +PYTHON_VERSION=3.14.5 +PYTHON_RELEASE=20260510 +PYTHON_SHA256_AMD64=daa4e5322b2b971011a9455ba813ecdb927ed6832fe87bd3d09162b3dc6fef2c +PYTHON_SHA256_ARM64=c70c9c3f35c3fac4e211608409a15dc0c5522e3956e82793f724a6eee64e0ff5 +AWS_SSM_PLUGIN_VERSION=1.2.814.0 +AWS_SSM_PLUGIN_SHA256_AMD64=67ddd12b963c28e44126f9732075caae333cc5f55e902623bd6a107ead2bcc56 +AWS_SSM_PLUGIN_SHA256_ARM64=dd714895b7e9b9945139301311778c175e556f75fda25250d36cff19c5eb0cdf diff --git a/versions_python39 b/versions_python39 new file mode 100644 index 0000000..2e5e897 --- /dev/null +++ b/versions_python39 @@ -0,0 +1,4 @@ +PYTHON_VERSION=3.9.25 +PYTHON_RELEASE=20251031 +PYTHON_SHA256_AMD64=5afeb1b01609195371a11b2fe6a3e73a315ff6baffd5d3c4ffa2ac3c6245acf5 +PYTHON_SHA256_ARM64=1ff8edb17b2448883c56ae1046a9c55db604ed52800febe7cd0cdebb7d39fceb From 4c71ee9b3b2a8908cd8291014196f6a11f72f38d Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Wed, 13 May 2026 11:52:29 +0300 Subject: [PATCH 03/26] SCALRCORE-38392 pin debian base via versions file --- Dockerfile | 3 ++- bump-versions.py | 31 +++++++++++++++++++++++++++++++ versions | 1 + 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d37fafc..aac5650 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,8 @@ # # Note: This is a PUBLIC image, it should not contain any sensitive data. -FROM debian:trixie-slim@sha256:109e2c65005bf160609e4ba6acf7783752f8502ad218e298253428690b9eaa4b +ARG DEBIAN_BASE_DIGEST +FROM debian:trixie-slim@${DEBIAN_BASE_DIGEST} ARG TARGETARCH diff --git a/bump-versions.py b/bump-versions.py index 7ef23f2..913a7d4 100755 --- a/bump-versions.py +++ b/bump-versions.py @@ -221,6 +221,30 @@ def _latest_python_version(series: str) -> tuple[str, str]: return "", "" +def get_debian_base_digest(repo: str = "library/debian", tag: str = "trixie-slim") -> str: + """Resolve docker.io/: to its current manifest digest. + + Uses Docker Hub's anonymous v2 registry API. + """ + token = http_get_json( + f"https://auth.docker.io/token?service=registry.docker.io&scope=repository:{repo}:pull" + )["token"] + req = urllib.request.Request( + f"https://registry-1.docker.io/v2/{repo}/manifests/{tag}", + method="HEAD", + headers={ + "Authorization": f"Bearer {token}", + # Accept both multi-arch index formats — debian:trixie-slim returns the OCI form. + "Accept": ( + "application/vnd.docker.distribution.manifest.list.v2+json," + "application/vnd.oci.image.index.v1+json" + ), + }, + ) + with urllib.request.urlopen(req, timeout=60) as resp: + return resp.headers["Docker-Content-Digest"] + + def get_latest_python_info() -> tuple[str, str]: """Return (version, release), e.g. ('3.14.5', '20260510').""" return _latest_python_version("3.14") @@ -353,6 +377,13 @@ def main() -> int: vs = read_versions() changes: list[tuple[str, str, str]] = [] + bump( + "debian_base", + "DEBIAN_BASE_DIGEST", + get_debian_base_digest(), + vs.get("DEBIAN_BASE_DIGEST", ""), + changes, + ) bump( "kubectl", "KUBECTL_VERSION", diff --git a/versions b/versions index d4027a9..2ce5c2c 100644 --- a/versions +++ b/versions @@ -1,3 +1,4 @@ +DEBIAN_BASE_DIGEST=sha256:109e2c65005bf160609e4ba6acf7783752f8502ad218e298253428690b9eaa4b KUBECTL_VERSION=v1.36.1 KUBECTL_SHA256_AMD64=629d3f410e09bf49b64ae7079f7f0bda1191efed311f7d37fdbab0ad5b0ec2b7 KUBECTL_SHA256_ARM64=59f7ee8e477fae658447607dc3c8790ac17a1b016c01c622c12070e969e2d4e7 From 76a1b700df5a7b80ca99d6ae6694400a4d32dab8 Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Mon, 25 May 2026 14:00:36 +0300 Subject: [PATCH 04/26] SCALRCORE-38392 use docker-backe for build matrix --- .github/workflows/build.yaml | 62 ++++++++----------- .github/workflows/release.yaml | 50 ++++----------- Dockerfile | 51 +++++++++++++++- README.md | 73 +++++++++++++--------- bump-versions.py | 108 +++++++++++++++++++-------------- docker-bake.hcl | 60 ++++++++++++++++++ versions | 21 ------- versions.json | 42 +++++++++++++ versions_python39 | 4 -- 9 files changed, 293 insertions(+), 178 deletions(-) create mode 100644 docker-bake.hcl delete mode 100644 versions create mode 100644 versions.json delete mode 100644 versions_python39 diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 12baac3..bd744c7 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -29,33 +29,24 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Prepare build args - id: args - run: | - { - echo 'default<> "$GITHUB_OUTPUT" - # Override block for the python39 variant — appended after defaults - # in the build-args input; later entries win in buildx. - { - echo 'python39<> "$GITHUB_OUTPUT" - - - name: Build Docker image - uses: docker/build-push-action@v6 + # Build all variants single-arch so they can be loaded locally and smoke-tested. + - name: Build images + uses: docker/bake-action@v5 with: - build-args: ${{ steps.args.outputs.default }} - cache-from: type=registry,ref=scalr/runner:buildcache - cache-to: type=registry,ref=scalr/runner:buildcache + files: | + docker-bake.hcl + versions.json load: true - tags: | - scalr/runner:sha-${{ github.sha }} + set: | + *.platform=linux/amd64 + full.tags=scalr/runner:sha-${{ github.sha }} + python39.tags=scalr/runner:sha-${{ github.sha }}-python39 + slim.tags=scalr/runner:sha-${{ github.sha }}-slim + full.cache-to=type=registry,ref=scalr/runner:buildcache,mode=max + python39.cache-to=type=registry,ref=scalr/runner:buildcache-python39,mode=max + slim.cache-to=type=registry,ref=scalr/runner:buildcache-slim,mode=max - - name: Test Docker Image + - name: Test full image run: | docker run --rm scalr/runner:sha-${{ github.sha }} -xc 'gcloud version' docker run --rm scalr/runner:sha-${{ github.sha }} -xc 'aws --version' @@ -66,20 +57,17 @@ jobs: docker run --rm scalr/runner:sha-${{ github.sha }} -xc 'pip --version' docker run --rm scalr/runner:sha-${{ github.sha }} -xc 'pip install requests' - - name: Build Docker image (Python 3.9) - uses: docker/build-push-action@v6 - with: - build-args: | - ${{ steps.args.outputs.default }} - ${{ steps.args.outputs.python39 }} - cache-from: type=registry,ref=scalr/runner:buildcache-python39 - cache-to: type=registry,ref=scalr/runner:buildcache-python39 - load: true - tags: | - scalr/runner:sha-${{ github.sha }}-python39 - - - name: Test Docker Image (Python 3.9) + - name: Test python39 image run: | docker run --rm scalr/runner:sha-${{ github.sha }}-python39 -xc 'python --version' docker run --rm scalr/runner:sha-${{ github.sha }}-python39 -xc 'pip --version' docker run --rm scalr/runner:sha-${{ github.sha }}-python39 -xc 'pip install requests' + + - name: Test slim image + run: | + docker run --rm scalr/runner:sha-${{ github.sha }}-slim -xc 'git --version' + docker run --rm scalr/runner:sha-${{ github.sha }}-slim -xc 'curl --version' + docker run --rm scalr/runner:sha-${{ github.sha }}-slim -xc 'jq --version' + # Confirm python and cloud tools are absent. + docker run --rm scalr/runner:sha-${{ github.sha }}-slim -xc '! command -v python' + docker run --rm scalr/runner:sha-${{ github.sha }}-slim -xc '! command -v aws' diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 00d4739..35d97cf 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -25,52 +25,24 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Prepare build args - id: args - run: | - { - echo 'default<> "$GITHUB_OUTPUT" - # Override block for the python39 variant — appended after defaults - # in the build-args input; later entries win in buildx. - { - echo 'python39<> "$GITHUB_OUTPUT" - - name: Format Image Tag id: image_tag run: | echo "tag=${GITHUB_REF#refs/tags/}" | tee -a $GITHUB_OUTPUT - - name: Build Docker image - uses: docker/build-push-action@v6 - with: - build-args: ${{ steps.args.outputs.default }} - platforms: linux/amd64,linux/arm64 - cache-from: type=registry,ref=scalr/runner:buildcache - cache-to: type=registry,ref=scalr/runner:buildcache - push: true - tags: | - scalr/runner:latest - scalr/runner:${{ steps.image_tag.outputs.tag }} - - - name: Build Docker image (Python 3.9) - uses: docker/build-push-action@v6 + - name: Build and push images + uses: docker/bake-action@v5 + env: + VERSION: ${{ steps.image_tag.outputs.tag }} with: - build-args: | - ${{ steps.args.outputs.default }} - ${{ steps.args.outputs.python39 }} - platforms: linux/amd64,linux/arm64 - cache-from: type=registry,ref=scalr/runner:buildcache-python39 - cache-to: type=registry,ref=scalr/runner:buildcache-python39 + files: | + docker-bake.hcl + versions.json push: true - tags: | - scalr/runner:latest-python39 - scalr/runner:${{ steps.image_tag.outputs.tag }}-python39 + set: | + full.cache-to=type=registry,ref=scalr/runner:buildcache,mode=max + python39.cache-to=type=registry,ref=scalr/runner:buildcache-python39,mode=max + slim.cache-to=type=registry,ref=scalr/runner:buildcache-slim,mode=max update_changelog: name: Update Changelog diff --git a/Dockerfile b/Dockerfile index aac5650..b564204 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,22 @@ +# check=skip=InvalidDefaultArgInFrom # Runner Image for the Scalr remote backend # -------------------------------------------- # # Note: This is a PUBLIC image, it should not contain any sensitive data. +# +# Build targets: +# - base: basic tools only (internal stage, not published) +# - slim: base + security hardening (published as scalr/runner:-slim) +# - full: base + Python + cloud CLIs + hardening (published as scalr/runner: +# and scalr/runner:-python39) +# +# DEBIAN_BASE_IMAGE, DEBIAN_BASE_DIGEST and other ARGs are supplied at build +# time via bake (see docker-bake.hcl + versions.json). The skip directive +# above silences BuildKit's check for ARGs in FROM without a default. +ARG DEBIAN_BASE_IMAGE ARG DEBIAN_BASE_DIGEST -FROM debian:trixie-slim@${DEBIAN_BASE_DIGEST} +FROM ${DEBIAN_BASE_IMAGE}@${DEBIAN_BASE_DIGEST} AS base ARG TARGETARCH @@ -28,6 +40,43 @@ RUN </dev/null || true +EOT + +ENTRYPOINT ["/usr/bin/bash"] + + +# ---------------------------------------------------------------------------- +# full: base + Python + cloud CLIs + hardening (final image) +# ---------------------------------------------------------------------------- +FROM base AS full + # Install python standalone build. ARG PYTHON_VERSION ARG PYTHON_RELEASE diff --git a/README.md b/README.md index e56f099..3e78907 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The image is based on the [`debian:trixie-slim`](https://hub.docker.com/_/debian ## Included Tools -This environment comes pre-equipped with a comprehensive suite of tools essential for development, operations, and cloud interactions. Here's a breakdown of what's included: +This environment comes pre-equipped with a comprehensive suite of tools essential for development, operations, and cloud interactions. Here's a breakdown of what's included (full image; the `-slim` variant ships only the basic tools through `jq`): * **Archivators**: * zip - Create and extract ZIP archives @@ -14,7 +14,7 @@ This environment comes pre-equipped with a comprehensive suite of tools essentia * gzip - Compress and decompress `.gz` files * **Encryption**: * gnupg - Secure data encryption and signing -* **Git (v2.47.2)**: +* **Git**: * Core Git functionality * Git LFS (Large File Storage) * SSH and HTTP transport protocols @@ -32,49 +32,62 @@ This environment comes pre-equipped with a comprehensive suite of tools essentia * Kubectl ([0.36.1](https://github.com/kubernetes/kubectl/releases/tag/v0.36.1)) - Kubernetes CLI. * Scalr CLI ([0.18.0](https://github.com/Scalr/scalr-cli/releases/tag/v0.18.0)) - The command-line to communicate with the Scalr API. -The versions for Python, Cloud Clients, Kubectl, and Scalr CLI are specifically pinned and detailed in the [versions](./versions) file. All other software included in this environment is sourced directly from the Debian Trixie upstream repositories. +The versions for Python, Cloud Clients, Kubectl, and Scalr CLI are specifically pinned and detailed in [versions.json](./versions.json). All other software included in this environment is sourced directly from the Debian Trixie upstream repositories. -## Python Distribution +## Image Variants -The environment uses the [standalone Python build](https://github.com/astral-sh/python-build-standalone) provided by the [astral.sh](https://astral.sh/) team. +| Image Tag | Contents | Python Version | +|-----------|----------|----------------| +| `scalr/runner:` | Basic tools + Python + cloud CLIs | Python 3.14.x | +| `scalr/runner:-python39` | Same as default, with Python 3.9 | Python 3.9.x | +| `scalr/runner:-slim` | Basic tools only (git, curl, jq, gnupg, etc.) | — | -Two Python variants are available: +The `-slim` variant is for workflows that don't need Python or cloud CLIs and +want the smallest possible image. -| Image Tag | Python Version | -|-----------|----------------| -| `scalr/runner:` | Python 3.14.x | -| `scalr/runner:-python39` | Python 3.9.x | +### Python Distribution (default and `-python39`) + +The Python-enabled images use the [standalone Python build](https://github.com/astral-sh/python-build-standalone) provided by the [astral.sh](https://astral.sh/) team. ## Runner Image Building -All tool versions and SHA256 checksums are stored as `KEY=value` lines: +Builds are driven by [`docker-bake.hcl`](./docker-bake.hcl) (targets, tags, +cache config) and [`versions.json`](./versions.json) (pinned tool versions and +SHA256 checksums). `versions.json` is a native Docker Buildx Bake variable +file containing two maps: + +- `versions_base` — Debian base image and digest (used by every target, including `-slim`) +- `versions_full` — extra tools layered on top for the full image (kubectl, gcloud, AWS CLI, Azure CLI, Scalr CLI, Python 3.14, AWS SSM Plugin) +- `versions_python39` — Python 3.9 overrides merged on top of `versions_full` for the `-python39` image + +Always pass both files. Every download is verified by SHA256 in the Dockerfile. -- [`versions`](./versions) — defaults (kubectl, gcloud, AWS CLI, Azure CLI, Scalr CLI, Python 3.14, AWS SSM Plugin) -- [`versions_python39`](./versions_python39) — Python 3.9 overrides (consumed only by the `-python39` image) +Tags use `VERSION` from the environment, defaulting to `dev` for local builds. +There is no `latest` tag — release tags are explicit. -The snippet below forwards every entry as a `--build-arg`, so each download is -verified against a hash pinned in this repo. +The bake file declares `platforms = ["linux/amd64", "linux/arm64"]` for CI +multi-arch builds. Local builds with Docker's default driver cannot do +multi-platform, so add `--set "*.platform=linux/amd64"` (or your host arch) +and `--load` to every local command. -### Default image (Python 3.14) +### Build everything ```bash -docker buildx build \ - $(grep -v '^#' versions | grep -v '^$' | xargs -I {} echo --build-arg={}) \ - --platform linux/amd64 \ - -t scalr/runner:latest --load . +VERSION=3.0.0 docker buildx bake -f docker-bake.hcl -f versions.json \ + --set "*.platform=linux/amd64" --load ``` -### Python 3.9 variant - -Pass both files; later args override earlier ones, so `versions_python39` -replaces the `PYTHON_*` keys from `versions`: +### Build one variant ```bash -docker buildx build \ - $(grep -v '^#' versions | grep -v '^$' | xargs -I {} echo --build-arg={}) \ - $(grep -v '^#' versions_python39 | grep -v '^$' | xargs -I {} echo --build-arg={}) \ - --platform linux/amd64 \ - -t scalr/runner:latest-python39 --load . +VERSION=3.0.0 docker buildx bake -f docker-bake.hcl -f versions.json \ + --set "*.platform=linux/amd64" --load full # scalr/runner:3.0.0 + +VERSION=3.0.0 docker buildx bake -f docker-bake.hcl -f versions.json \ + --set "*.platform=linux/amd64" --load python39 # scalr/runner:3.0.0-python39 + +VERSION=3.0.0 docker buildx bake -f docker-bake.hcl -f versions.json \ + --set "*.platform=linux/amd64" --load slim # scalr/runner:3.0.0-slim ``` ## Bumping Versions @@ -85,7 +98,7 @@ To update all tool versions to their latest releases, run: ./bump-versions.py ``` -This script fetches the latest versions from upstream sources and updates the [versions](./versions) and [versions_python39](./versions_python39) files (plus the "Included Tools" section of this README). For every tool it also refreshes the per-arch SHA256 checksums used by the Dockerfile to verify each download. +This script fetches the latest versions from upstream sources and updates the `versions_base`, `versions_full`, and `versions_python39` maps in [versions.json](./versions.json) (plus the "Included Tools" section of this README). For every tool it also refreshes the per-arch SHA256 checksums used by the Dockerfile to verify each download. Requirements: `python3` (stdlib only, no `pip install` needed). diff --git a/bump-versions.py b/bump-versions.py index 913a7d4..3ab7eb7 100755 --- a/bump-versions.py +++ b/bump-versions.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 -"""Bump tool versions in ./versions and refresh pinned SHA256 checksums. +"""Bump tool versions in ./versions.json and refresh pinned SHA256 checksums. For every tool that has a discoverable upstream version index, this script -fetches the latest release, updates ./versions, and refreshes the per-arch -SHA256 checksums the Dockerfile uses to verify each download. The AWS -Session Manager Plugin version stays manually pinned (no upstream version -index) but its SHAs are still refreshed against the pinned version. +fetches the latest release, updates the VERSIONS / VERSIONS_PY39 maps in +versions.json (a Docker Buildx Bake variable file), and refreshes the +per-arch SHA256 checksums the Dockerfile uses to verify each download. The +AWS Session Manager Plugin version stays manually pinned (no upstream +version index) but its SHAs are still refreshed against the pinned version. Stdlib only — no pip dependencies. """ @@ -23,9 +24,16 @@ from pathlib import Path from typing import Callable -VERSIONS_FILE = Path("versions") -PYTHON39_FILE = Path("versions_python39") +VERSIONS_FILE = Path("versions.json") README_FILE = Path("README.md") + +# Top-level variable names in versions.json: +# versions.json -> variable.
.default. = +SECTION_BASE = "versions_base" +SECTION_FULL = "versions_full" +SECTION_PY39 = "versions_python39" +# Default section for the bump() helper — tool-version bumps live in `full`. +SECTION_DEFAULT = SECTION_FULL UA = "bump-versions.py (https://github.com/Scalr/runner)" # Optional auth — GitHub's unauthenticated API quota is 60/hour per IP; # 5000/hour with any valid token. CI sets GITHUB_TOKEN automatically. @@ -82,33 +90,29 @@ def fetch_sha_from_sumsfile(sums_url: str, asset: str) -> str: raise RuntimeError(f"{asset} not found in {sums_url}") -# --- versions file I/O ------------------------------------------------------ +# --- versions.json I/O ------------------------------------------------------ -def read_versions(path: Path = VERSIONS_FILE) -> dict[str, str]: - out: dict[str, str] = {} - if not path.exists(): - return out - for line in path.read_text().splitlines(): - line = line.strip() - if not line or line.startswith("#") or "=" not in line: - continue - k, _, v = line.partition("=") - out[k] = v - return out +def _load() -> dict: + return json.loads(VERSIONS_FILE.read_text()) -def write_value(key: str, value: str, path: Path = VERSIONS_FILE) -> None: - """Update an existing key= line in-place, or append if missing.""" - lines = path.read_text().splitlines() if path.exists() else [] - new_line = f"{key}={value}" - for i, line in enumerate(lines): - if line.startswith(f"{key}="): - lines[i] = new_line - break - else: - lines.append(new_line) - path.write_text("\n".join(lines) + "\n") +def _dump(data: dict) -> None: + VERSIONS_FILE.write_text(json.dumps(data, indent=2) + "\n") + + +def read_versions(section: str = SECTION_DEFAULT) -> dict[str, str]: + try: + return dict(_load()["variable"][section]["default"]) + except (FileNotFoundError, KeyError): + return {} + + +def write_value(key: str, value: str, section: str = SECTION_DEFAULT) -> None: + """Update or append KEY in the given top-level variable and rewrite the file.""" + data = _load() + data.setdefault("variable", {}).setdefault(section, {}).setdefault("default", {})[key] = value + _dump(data) # --- README pretty-section updaters ----------------------------------------- @@ -221,11 +225,13 @@ def _latest_python_version(series: str) -> tuple[str, str]: return "", "" -def get_debian_base_digest(repo: str = "library/debian", tag: str = "trixie-slim") -> str: - """Resolve docker.io/: to its current manifest digest. - - Uses Docker Hub's anonymous v2 registry API. +def get_debian_base_digest(image_ref: str) -> str: + """Resolve a Docker Hub image reference (e.g. "debian:trixie-slim") to + its current manifest digest. Uses Docker Hub's anonymous v2 registry API. """ + name, _, tag = image_ref.partition(":") + repo = name if "/" in name else f"library/{name}" + tag = tag or "latest" token = http_get_json( f"https://auth.docker.io/token?service=registry.docker.io&scope=repository:{repo}:pull" )["token"] @@ -273,7 +279,7 @@ def refresh_kubectl_shas(version: str) -> None: ) -def refresh_python_shas(version: str, release: str, path: Path = VERSIONS_FILE) -> None: +def refresh_python_shas(version: str, release: str, section: str = SECTION_DEFAULT) -> None: sums = ( "https://github.com/astral-sh/python-build-standalone/releases/download/" f"{release}/SHA256SUMS" @@ -284,7 +290,7 @@ def refresh_python_shas(version: str, release: str, path: Path = VERSIONS_FILE) sums, f"cpython-{version}+{release}-x86_64-unknown-linux-gnu-pgo+lto-full.tar.zst", ), - path, + section, ) write_value( "PYTHON_SHA256_ARM64", @@ -292,7 +298,7 @@ def refresh_python_shas(version: str, release: str, path: Path = VERSIONS_FILE) sums, f"cpython-{version}+{release}-aarch64-unknown-linux-gnu-pgo+lto-full.tar.zst", ), - path, + section, ) @@ -354,10 +360,11 @@ def bump( changes: list[tuple[str, str, str]], refresh_shas: Callable[[str], None] | None = None, update_readme: Callable[[str], None] | None = None, + section: str = SECTION_DEFAULT, ) -> bool: if latest and current != latest: log.info(f"{label}: {current} -> {latest}") - write_value(key, latest) + write_value(key, latest, section) if refresh_shas: refresh_shas(latest) if update_readme: @@ -375,14 +382,23 @@ def main() -> int: log.info("Fetching latest versions...") vs = read_versions() + base = read_versions(SECTION_BASE) changes: list[tuple[str, str, str]] = [] + # DEBIAN_BASE_IMAGE is read-only here — the base image (e.g. debian:trixie-slim) + # is a deliberate human choice and must not be auto-bumped. We only refresh the + # digest pinning for whatever image is currently configured. + debian_image = base.get("DEBIAN_BASE_IMAGE", "") + if not debian_image: + log.error(f"DEBIAN_BASE_IMAGE missing from {VERSIONS_FILE} ({SECTION_BASE})") + return 1 bump( - "debian_base", + "debian_base_digest", "DEBIAN_BASE_DIGEST", - get_debian_base_digest(), - vs.get("DEBIAN_BASE_DIGEST", ""), + get_debian_base_digest(debian_image), + base.get("DEBIAN_BASE_DIGEST", ""), changes, + section=SECTION_BASE, ) bump( "kubectl", @@ -453,8 +469,8 @@ def main() -> int: if py_changed: refresh_python_shas(lat_v, lat_r) - # Python 3.9 variant — same upstream as 3.14, separate file with plain PYTHON_* keys. - vs39 = read_versions(PYTHON39_FILE) + # Python 3.9 variant — same upstream as 3.14, override block with plain PYTHON_* keys. + vs39 = read_versions(SECTION_PY39) cur_v39 = vs39.get("PYTHON_VERSION", "") cur_r39 = vs39.get("PYTHON_RELEASE", "") lat_v39, lat_r39 = get_latest_python39_info() @@ -464,20 +480,20 @@ def main() -> int: py39_changed = False if cur_v39 != lat_v39: log.info(f"python39: {cur_v39} -> {lat_v39}") - write_value("PYTHON_VERSION", lat_v39, PYTHON39_FILE) + write_value("PYTHON_VERSION", lat_v39, SECTION_PY39) changes.append(("python39", cur_v39, lat_v39)) py39_changed = True else: log.info(f"python39: {cur_v39} (up to date)") if cur_r39 != lat_r39: log.info(f"python39_release: {cur_r39} -> {lat_r39}") - write_value("PYTHON_RELEASE", lat_r39, PYTHON39_FILE) + write_value("PYTHON_RELEASE", lat_r39, SECTION_PY39) changes.append(("python39_release", cur_r39, lat_r39)) py39_changed = True else: log.info(f"python39_release: {cur_r39} (up to date)") if py39_changed: - refresh_python_shas(lat_v39, lat_r39, PYTHON39_FILE) + refresh_python_shas(lat_v39, lat_r39, SECTION_PY39) # AWS SSM Plugin: version is manually pinned (no upstream version index), # but its SHAs are refreshed in case the pinned version was hand-edited. diff --git a/docker-bake.hcl b/docker-bake.hcl new file mode 100644 index 0000000..ebb57f1 --- /dev/null +++ b/docker-bake.hcl @@ -0,0 +1,60 @@ +# Docker Buildx Bake file for the Scalr runner image. +# +# Build all variants: +# docker buildx bake -f docker-bake.hcl -f versions.json +# +# Build one variant: +# docker buildx bake -f docker-bake.hcl -f versions.json full +# docker buildx bake -f docker-bake.hcl -f versions.json python39 +# docker buildx bake -f docker-bake.hcl -f versions.json slim +# +# Override the version tag (defaults to "dev" for local builds): +# VERSION=3.0.0 docker buildx bake -f docker-bake.hcl -f versions.json full + +variable "VERSION" { + default = "dev" +} + +# Versions and SHA256 checksums for tools installed inside the image. +# Populated from versions.json (a native bake variable file) and maintained +# by ./bump-versions.py. +# versions_base — base layer (Debian base digest); used by every target +# versions_full — extra tools for the full image (kubectl, cloud CLIs, Python 3.14, …) +# versions_python39 — overrides merged on top for the -python39 image +variable "versions_base" { + default = {} +} +variable "versions_full" { + default = {} +} +variable "versions_python39" { + default = {} +} + +group "default" { + targets = ["full", "python39", "slim"] +} + +target "full" { + target = "full" + platforms = ["linux/amd64", "linux/arm64"] + args = merge(versions_base, versions_full) + tags = ["scalr/runner:${VERSION}"] + cache-from = ["type=registry,ref=scalr/runner:buildcache"] +} + +target "python39" { + target = "full" + platforms = ["linux/amd64", "linux/arm64"] + args = merge(versions_base, versions_full, versions_python39) + tags = ["scalr/runner:${VERSION}-python39"] + cache-from = ["type=registry,ref=scalr/runner:buildcache-python39"] +} + +target "slim" { + target = "slim" + platforms = ["linux/amd64", "linux/arm64"] + args = versions_base + tags = ["scalr/runner:${VERSION}-slim"] + cache-from = ["type=registry,ref=scalr/runner:buildcache-slim"] +} diff --git a/versions b/versions deleted file mode 100644 index 2ce5c2c..0000000 --- a/versions +++ /dev/null @@ -1,21 +0,0 @@ -DEBIAN_BASE_DIGEST=sha256:109e2c65005bf160609e4ba6acf7783752f8502ad218e298253428690b9eaa4b -KUBECTL_VERSION=v1.36.1 -KUBECTL_SHA256_AMD64=629d3f410e09bf49b64ae7079f7f0bda1191efed311f7d37fdbab0ad5b0ec2b7 -KUBECTL_SHA256_ARM64=59f7ee8e477fae658447607dc3c8790ac17a1b016c01c622c12070e969e2d4e7 -GCLOUD_VERSION=568.0.0 -GCLOUD_SHA256_AMD64=ef53b350c5da53b7e92d90ab6f65a85c712686858b839e44e6529d5883fd575c -GCLOUD_SHA256_ARM64=119f00184832664b0acfe758395a6b12c57f0f7dd7ec070cdfb193b7b5142b87 -AWS_CLI_VERSION=2.34.45 -AWS_CLI_SHA256_AMD64=2b382fd5c1e8d73f6aaca18e5850758fb9f70a20af9c17deb3078df0dd4af095 -AWS_CLI_SHA256_ARM64=bf29e06a06fb9bac2a3803931e6618a9f7c9f4c6632ec3b587184850b8f03433 -AZURE_CLI_VERSION=2.86.0 -SCALR_CLI_VERSION=0.18.0 -SCALR_CLI_SHA256_AMD64=5321bcebd48d30f7c3a936a5c5f10d9d3cfb77ef2974575d4df8e7ee9f890589 -SCALR_CLI_SHA256_ARM64=8dcada09b5bf10001f4e83190fc9c2a66df692beb07a74b4afe503d2fa75629e -PYTHON_VERSION=3.14.5 -PYTHON_RELEASE=20260510 -PYTHON_SHA256_AMD64=daa4e5322b2b971011a9455ba813ecdb927ed6832fe87bd3d09162b3dc6fef2c -PYTHON_SHA256_ARM64=c70c9c3f35c3fac4e211608409a15dc0c5522e3956e82793f724a6eee64e0ff5 -AWS_SSM_PLUGIN_VERSION=1.2.814.0 -AWS_SSM_PLUGIN_SHA256_AMD64=67ddd12b963c28e44126f9732075caae333cc5f55e902623bd6a107ead2bcc56 -AWS_SSM_PLUGIN_SHA256_ARM64=dd714895b7e9b9945139301311778c175e556f75fda25250d36cff19c5eb0cdf diff --git a/versions.json b/versions.json new file mode 100644 index 0000000..cd68fd6 --- /dev/null +++ b/versions.json @@ -0,0 +1,42 @@ +{ + "variable": { + "versions_base": { + "default": { + "DEBIAN_BASE_IMAGE": "debian:trixie-slim", + "DEBIAN_BASE_DIGEST": "sha256:109e2c65005bf160609e4ba6acf7783752f8502ad218e298253428690b9eaa4b" + } + }, + "versions_full": { + "default": { + "KUBECTL_VERSION": "v1.36.1", + "KUBECTL_SHA256_AMD64": "629d3f410e09bf49b64ae7079f7f0bda1191efed311f7d37fdbab0ad5b0ec2b7", + "KUBECTL_SHA256_ARM64": "59f7ee8e477fae658447607dc3c8790ac17a1b016c01c622c12070e969e2d4e7", + "GCLOUD_VERSION": "568.0.0", + "GCLOUD_SHA256_AMD64": "ef53b350c5da53b7e92d90ab6f65a85c712686858b839e44e6529d5883fd575c", + "GCLOUD_SHA256_ARM64": "119f00184832664b0acfe758395a6b12c57f0f7dd7ec070cdfb193b7b5142b87", + "AWS_CLI_VERSION": "2.34.45", + "AWS_CLI_SHA256_AMD64": "2b382fd5c1e8d73f6aaca18e5850758fb9f70a20af9c17deb3078df0dd4af095", + "AWS_CLI_SHA256_ARM64": "bf29e06a06fb9bac2a3803931e6618a9f7c9f4c6632ec3b587184850b8f03433", + "AZURE_CLI_VERSION": "2.86.0", + "SCALR_CLI_VERSION": "0.18.0", + "SCALR_CLI_SHA256_AMD64": "5321bcebd48d30f7c3a936a5c5f10d9d3cfb77ef2974575d4df8e7ee9f890589", + "SCALR_CLI_SHA256_ARM64": "8dcada09b5bf10001f4e83190fc9c2a66df692beb07a74b4afe503d2fa75629e", + "PYTHON_VERSION": "3.14.5", + "PYTHON_RELEASE": "20260510", + "PYTHON_SHA256_AMD64": "daa4e5322b2b971011a9455ba813ecdb927ed6832fe87bd3d09162b3dc6fef2c", + "PYTHON_SHA256_ARM64": "c70c9c3f35c3fac4e211608409a15dc0c5522e3956e82793f724a6eee64e0ff5", + "AWS_SSM_PLUGIN_VERSION": "1.2.814.0", + "AWS_SSM_PLUGIN_SHA256_AMD64": "67ddd12b963c28e44126f9732075caae333cc5f55e902623bd6a107ead2bcc56", + "AWS_SSM_PLUGIN_SHA256_ARM64": "dd714895b7e9b9945139301311778c175e556f75fda25250d36cff19c5eb0cdf" + } + }, + "versions_python39": { + "default": { + "PYTHON_VERSION": "3.9.25", + "PYTHON_RELEASE": "20251031", + "PYTHON_SHA256_AMD64": "5afeb1b01609195371a11b2fe6a3e73a315ff6baffd5d3c4ffa2ac3c6245acf5", + "PYTHON_SHA256_ARM64": "1ff8edb17b2448883c56ae1046a9c55db604ed52800febe7cd0cdebb7d39fceb" + } + } + } +} diff --git a/versions_python39 b/versions_python39 deleted file mode 100644 index 2e5e897..0000000 --- a/versions_python39 +++ /dev/null @@ -1,4 +0,0 @@ -PYTHON_VERSION=3.9.25 -PYTHON_RELEASE=20251031 -PYTHON_SHA256_AMD64=5afeb1b01609195371a11b2fe6a3e73a315ff6baffd5d3c4ffa2ac3c6245acf5 -PYTHON_SHA256_ARM64=1ff8edb17b2448883c56ae1046a9c55db604ed52800febe7cd0cdebb7d39fceb From f08e62cea8b44a0fafec8df3c4dccc7dda027a80 Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Mon, 25 May 2026 14:01:35 +0300 Subject: [PATCH 05/26] SCALRCORE-38392 bump versions --- README.md | 4 ++-- versions.json | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3e78907..b4b2424 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,9 @@ This environment comes pre-equipped with a comprehensive suite of tools essentia * Python ([v3.14.5](https://www.python.org/downloads/release/python-3145/)) - General-purpose programming language (release) * jq - Command-line JSON processor * **Cloud Clients** - * AWS CLI ([2.34.45](https://github.com/aws/aws-cli/releases/tag/2.34.45)) - Amazon Web Services CLI. + * AWS CLI ([2.34.53](https://github.com/aws/aws-cli/releases/tag/2.34.53)) - Amazon Web Services CLI. * Azure CLI ([2.86.0](https://github.com/Azure/azure-cli/releases/tag/azure-cli-2.86.0)) - Microsoft Azure CLI. - * Google Cloud SDK ([568.0.0](https://cloud.google.com/sdk/docs/release-notes#56800)) - Stable, Alpha, Beta components. Includes kubectl authenticator. + * Google Cloud SDK ([569.0.0](https://cloud.google.com/sdk/docs/release-notes#56900)) - Stable, Alpha, Beta components. Includes kubectl authenticator. * Kubectl ([0.36.1](https://github.com/kubernetes/kubectl/releases/tag/v0.36.1)) - Kubernetes CLI. * Scalr CLI ([0.18.0](https://github.com/Scalr/scalr-cli/releases/tag/v0.18.0)) - The command-line to communicate with the Scalr API. diff --git a/versions.json b/versions.json index cd68fd6..4f676d0 100644 --- a/versions.json +++ b/versions.json @@ -3,7 +3,7 @@ "versions_base": { "default": { "DEBIAN_BASE_IMAGE": "debian:trixie-slim", - "DEBIAN_BASE_DIGEST": "sha256:109e2c65005bf160609e4ba6acf7783752f8502ad218e298253428690b9eaa4b" + "DEBIAN_BASE_DIGEST": "sha256:b6e2a152f22a40ff69d92cb397223c906017e1391a73c952b588e51af8883bf8" } }, "versions_full": { @@ -11,12 +11,12 @@ "KUBECTL_VERSION": "v1.36.1", "KUBECTL_SHA256_AMD64": "629d3f410e09bf49b64ae7079f7f0bda1191efed311f7d37fdbab0ad5b0ec2b7", "KUBECTL_SHA256_ARM64": "59f7ee8e477fae658447607dc3c8790ac17a1b016c01c622c12070e969e2d4e7", - "GCLOUD_VERSION": "568.0.0", - "GCLOUD_SHA256_AMD64": "ef53b350c5da53b7e92d90ab6f65a85c712686858b839e44e6529d5883fd575c", - "GCLOUD_SHA256_ARM64": "119f00184832664b0acfe758395a6b12c57f0f7dd7ec070cdfb193b7b5142b87", - "AWS_CLI_VERSION": "2.34.45", - "AWS_CLI_SHA256_AMD64": "2b382fd5c1e8d73f6aaca18e5850758fb9f70a20af9c17deb3078df0dd4af095", - "AWS_CLI_SHA256_ARM64": "bf29e06a06fb9bac2a3803931e6618a9f7c9f4c6632ec3b587184850b8f03433", + "GCLOUD_VERSION": "569.0.0", + "GCLOUD_SHA256_AMD64": "d242be22b34ef69b17925cc57022dae8ba8c02393cf7f5954b8fd105201b17e5", + "GCLOUD_SHA256_ARM64": "e2ec1a30d714162e7020ad462cba344bd90e24004fe2b53637a57d7ac7715bec", + "AWS_CLI_VERSION": "2.34.53", + "AWS_CLI_SHA256_AMD64": "b06c9865123cf1bc6f8761a0bf3161630e6f55d84363f9fda89535e2cdbe654b", + "AWS_CLI_SHA256_ARM64": "1883e4dddee2ecfab39df0ff0f13b32d50715f7ad9f02a322ecd73ee439cef8f", "AZURE_CLI_VERSION": "2.86.0", "SCALR_CLI_VERSION": "0.18.0", "SCALR_CLI_SHA256_AMD64": "5321bcebd48d30f7c3a936a5c5f10d9d3cfb77ef2974575d4df8e7ee9f890589", From a8021263a8d6708ad33ba8a0afe63686dcafc8c6 Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Mon, 25 May 2026 14:49:48 +0300 Subject: [PATCH 06/26] SCALRCORE-38392 updated readme --- Dockerfile | 7 +-- README.md | 152 +++++++++++++++++++++++++++++++++++------------------ 2 files changed, 104 insertions(+), 55 deletions(-) diff --git a/Dockerfile b/Dockerfile index b564204..444f0fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,6 +40,10 @@ RUN <-slim` — minimal set of useful tools. +- `scalr/runner:` — same as `-slim`, plus a full set of cloud CLIs (AWS, Azure, gcloud, kubectl, scalr-cli) and Python 3.14. +- `scalr/runner:-python39` — same as the full image, but with Python 3.9 instead of 3.14 (for legacy workflows). -| Image Tag | Contents | Python Version | -|-----------|----------|----------------| -| `scalr/runner:` | Basic tools + Python + cloud CLIs | Python 3.14.x | -| `scalr/runner:-python39` | Same as default, with Python 3.9 | Python 3.9.x | -| `scalr/runner:-slim` | Basic tools only (git, curl, jq, gnupg, etc.) | — | +## Included Tools -The `-slim` variant is for workflows that don't need Python or cloud CLIs and -want the smallest possible image. +Tools are grouped by which image variant they appear in. The base set is +present in every variant; the full / `-python39` sections only describe what's +added on top. -### Python Distribution (default and `-python39`) +### Base Software -The Python-enabled images use the [standalone Python build](https://github.com/astral-sh/python-build-standalone) provided by the [astral.sh](https://astral.sh/) team. +Present in all variants. These come from the pinned Debian Trixie snapshot +referenced by `DEBIAN_BASE_DIGEST` in [versions.json](./versions.json), so +their exact versions are whatever that snapshot pins. -## Runner Image Building +* **Archive tools**: + * `tar` — manipulate tar archives (from the base image) + * `gzip` — compress and decompress `.gz` files (from the base image) + * `zip`, `unzip` — create and extract ZIP archives +* **Encryption**: + * `gnupg` — secure data encryption and signing +* **Git**: + * `git-core` — core Git + * `git-lfs` — Large File Storage extension + * `openssh-client` — SSH transport for Git over SSH +* **HTTP / network**: + * `curl` — data transfer with URLs + * `wget` — file downloads from the web + * `ca-certificates` — trusted CA bundle +* **System / misc**: + * `jq` — command-line JSON processor + * `lsb-release` — Linux Standard Base release info + * `bash` (default shell / entrypoint) + +### Added in the full image + +These are pinned by exact version + SHA256 in +[versions.json](./versions.json) and downloaded during the build. The +versions below are the current pins (kept in sync with `versions.json` by +`bump-versions.py`): + +* **Programming language** + * Python ([v3.14.5](https://www.python.org/downloads/release/python-3145/)) — [standalone CPython build](https://github.com/astral-sh/python-build-standalone) from [astral.sh](https://astral.sh/) +* **Cloud CLIs** + * AWS CLI ([2.34.53](https://github.com/aws/aws-cli/releases/tag/2.34.53)) — Amazon Web Services CLI + * AWS Session Manager Plugin — SSM session support for the AWS CLI + * Azure CLI ([2.86.0](https://github.com/Azure/azure-cli/releases/tag/azure-cli-2.86.0)) — Microsoft Azure CLI + * Google Cloud SDK ([569.0.0](https://cloud.google.com/sdk/docs/release-notes#56900)) — `gcloud` with `alpha`, `beta`, and `gke-gcloud-auth-plugin` components + * Kubectl ([0.36.1](https://github.com/kubernetes/kubectl/releases/tag/v0.36.1)) — Kubernetes CLI + * Scalr CLI ([0.18.0](https://github.com/Scalr/scalr-cli/releases/tag/v0.18.0)) — command-line client for the Scalr API + +### Added in the `-python39` image + +Same as the full image, with Python 3.14 replaced by Python 3.9 (currently +[v3.9.25](https://www.python.org/downloads/release/python-3925/)). + +### Runtime user + +A non-root user `scalr` with uid/gid `1000` is created in the base layer +and is therefore present in all variants. Use it by running the container +with `--user 1000`. + +### Security hardening + +`su`/`sudo`, account/password tools (`passwd`, `chsh`, `chage`, +`useradd`/`usermod`/`groupadd`/…), `mount`/`umount`, and all SUID/SGID bits +are stripped at the end of every image build. This applies to all variants. + +## Building the Image Builds are driven by [`docker-bake.hcl`](./docker-bake.hcl) (targets, tags, -cache config) and [`versions.json`](./versions.json) (pinned tool versions and -SHA256 checksums). `versions.json` is a native Docker Buildx Bake variable -file containing two maps: +cache config) and [`versions.json`](./versions.json) (pinned tool versions +and SHA256 checksums). `versions.json` is a native Docker Buildx Bake +variable file containing three maps: - `versions_base` — Debian base image and digest (used by every target, including `-slim`) - `versions_full` — extra tools layered on top for the full image (kubectl, gcloud, AWS CLI, Azure CLI, Scalr CLI, Python 3.14, AWS SSM Plugin) - `versions_python39` — Python 3.9 overrides merged on top of `versions_full` for the `-python39` image -Always pass both files. Every download is verified by SHA256 in the Dockerfile. +Always pass both files. Every download is verified by SHA256 in the +Dockerfile. -Tags use `VERSION` from the environment, defaulting to `dev` for local builds. -There is no `latest` tag — release tags are explicit. +Tags use `VERSION` from the environment, defaulting to `dev` for local +builds. The bake file declares `platforms = ["linux/amd64", "linux/arm64"]` for CI multi-arch builds. Local builds with Docker's default driver cannot do @@ -98,13 +141,18 @@ To update all tool versions to their latest releases, run: ./bump-versions.py ``` -This script fetches the latest versions from upstream sources and updates the `versions_base`, `versions_full`, and `versions_python39` maps in [versions.json](./versions.json) (plus the "Included Tools" section of this README). For every tool it also refreshes the per-arch SHA256 checksums used by the Dockerfile to verify each download. +This script fetches the latest versions from upstream sources and updates +the `versions_base`, `versions_full`, and `versions_python39` maps in +[versions.json](./versions.json) (plus the [Included Tools](#included-tools) +section of this README). For every tool it also refreshes the per-arch +SHA256 checksums used by the Dockerfile to verify each download. Requirements: `python3` (stdlib only, no `pip install` needed). -GitHub's anonymous API quota is 60 requests/hour. The script makes ~5 calls to -`api.github.com` per run, so frequent reruns may hit `HTTP 403: rate limit exceeded`. -Export `GITHUB_TOKEN` (or `GH_TOKEN`) to lift the limit to 5000/hour: +GitHub's anonymous API quota is 60 requests/hour. The script makes ~5 calls +to `api.github.com` per run, so frequent reruns may hit `HTTP 403: rate +limit exceeded`. Export `GITHUB_TOKEN` (or `GH_TOKEN`) to lift the limit to +5000/hour: ```bash GITHUB_TOKEN=$(gh auth token) ./bump-versions.py From 6ff8013133e6c2e56da1e2a8aa89cafbcd6a7065 Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Mon, 25 May 2026 14:50:57 +0300 Subject: [PATCH 07/26] SCALRCORE-38392 updated readme --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e79a0e8..a490ba6 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,7 @@ # Runner Image used in Scalr Remote Backend This repository builds the runner images used by the Scalr platform and -on-prem Scalr Agents. Images are based on -[`debian:trixie-slim`](https://hub.docker.com/_/debian) (pinned by digest in -[versions.json](./versions.json)) and published as multi-arch manifests for -`linux/amd64` and `linux/arm64`. +on-prem Scalr Agents. ## Contents @@ -26,6 +23,11 @@ Three variants are published from this repository: - `scalr/runner:` — same as `-slim`, plus a full set of cloud CLIs (AWS, Azure, gcloud, kubectl, scalr-cli) and Python 3.14. - `scalr/runner:-python39` — same as the full image, but with Python 3.9 instead of 3.14 (for legacy workflows). +Images are based on +[`debian:trixie-slim`](https://hub.docker.com/_/debian) (pinned by digest in +[versions.json](./versions.json)) and published as multi-arch manifests for +`linux/amd64` and `linux/arm64`. + ## Included Tools Tools are grouped by which image variant they appear in. The base set is From f86d0084e978266739dc8baf1a1c279bdc52bcde Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Mon, 25 May 2026 14:52:33 +0300 Subject: [PATCH 08/26] SCALRCORE-38392 bump versions --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a490ba6..6803cb5 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,8 @@ Images are based on ## Included Tools -Tools are grouped by which image variant they appear in. The base set is -present in every variant; the full / `-python39` sections only describe what's -added on top. +The images provide a set of tools commonly used in IaC workflows, grouped +below by the variant they appear in. ### Base Software From de814b0f54ef31ea151c423ca018d9c8138d4a50 Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Mon, 25 May 2026 14:55:07 +0300 Subject: [PATCH 09/26] SCALRCORE-38392 updated readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6803cb5..37d9393 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,8 @@ on-prem Scalr Agents. Three variants are published from this repository: -- `scalr/runner:-slim` — minimal set of useful tools. -- `scalr/runner:` — same as `-slim`, plus a full set of cloud CLIs (AWS, Azure, gcloud, kubectl, scalr-cli) and Python 3.14. +- `scalr/runner:-slim` — slim image: a minimal set of tools. +- `scalr/runner:` — full image: the slim set plus cloud CLIs (AWS, Azure, gcloud, kubectl, scalr-cli) and Python 3.14. - `scalr/runner:-python39` — same as the full image, but with Python 3.9 instead of 3.14 (for legacy workflows). Images are based on From 09d6f7f9bed80b8e7d145ba3875abfdb4b732031 Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Mon, 25 May 2026 15:00:41 +0300 Subject: [PATCH 10/26] SCALRCORE-38392 updated readme --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 37d9393..fecb918 100644 --- a/README.md +++ b/README.md @@ -117,21 +117,21 @@ and `--load` to every local command. ### Build everything ```bash -VERSION=3.0.0 docker buildx bake -f docker-bake.hcl -f versions.json \ +VERSION=dev docker buildx bake -f docker-bake.hcl -f versions.json \ --set "*.platform=linux/amd64" --load ``` ### Build one variant ```bash -VERSION=3.0.0 docker buildx bake -f docker-bake.hcl -f versions.json \ - --set "*.platform=linux/amd64" --load full # scalr/runner:3.0.0 +VERSION=dev docker buildx bake -f docker-bake.hcl -f versions.json \ + --set "*.platform=linux/amd64" --load full # scalr/runner:dev -VERSION=3.0.0 docker buildx bake -f docker-bake.hcl -f versions.json \ - --set "*.platform=linux/amd64" --load python39 # scalr/runner:3.0.0-python39 +VERSION=dev docker buildx bake -f docker-bake.hcl -f versions.json \ + --set "*.platform=linux/amd64" --load python39 # scalr/runner:dev-python39 -VERSION=3.0.0 docker buildx bake -f docker-bake.hcl -f versions.json \ - --set "*.platform=linux/amd64" --load slim # scalr/runner:3.0.0-slim +VERSION=dev docker buildx bake -f docker-bake.hcl -f versions.json \ + --set "*.platform=linux/amd64" --load slim # scalr/runner:dev-slim ``` ## Bumping Versions From 1437793b4857c57f6276474f20d905028f8a97fe Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Mon, 25 May 2026 15:04:04 +0300 Subject: [PATCH 11/26] SCALRCORE-38392 updated readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fecb918..8913850 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# Runner Image used in Scalr Remote Backend +# Scalr Runner Image -This repository builds the runner images used by the Scalr platform and -on-prem Scalr Agents. +This repository hosts the build pipeline for the runner images used by the +remote backend of the Scalr platform and on-prem Scalr Agents. ## Contents From 36123bfe8a26790bf493eb812816473d67cf09c2 Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Mon, 25 May 2026 15:05:01 +0300 Subject: [PATCH 12/26] SCALRCORE-38392 updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8913850..c2b472f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Scalr Runner Image -This repository hosts the build pipeline for the runner images used by the +This repository hosts the build pipeline for the runner images used by the OpenTofu/Terraform remote backend of the Scalr platform and on-prem Scalr Agents. ## Contents From a88e888360915c5e81164d2400aff8e8707d490d Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Mon, 25 May 2026 15:07:51 +0300 Subject: [PATCH 13/26] SCALRCORE-38392 updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c2b472f..07427d2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Scalr Runner Image This repository hosts the build pipeline for the runner images used by the OpenTofu/Terraform -remote backend of the Scalr platform and on-prem Scalr Agents. +remote operations backend of the Scalr platform and [on-prem Scalr Agents](https://docs.scalr.io/docs/run-environment#runner-image). ## Contents From 00dfa5918153b839cc72fc8fbcc091ccd86a6acc Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Mon, 25 May 2026 15:09:32 +0300 Subject: [PATCH 14/26] SCALRCORE-38392 updated readme --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 07427d2..94ab0e7 100644 --- a/README.md +++ b/README.md @@ -83,8 +83,7 @@ Same as the full image, with Python 3.14 replaced by Python 3.9 (currently ### Runtime user A non-root user `scalr` with uid/gid `1000` is created in the base layer -and is therefore present in all variants. Use it by running the container -with `--user 1000`. +and is therefore present in all variants. ### Security hardening From 3df580dba3091fb45b18c30e7a37a592a346a202 Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Mon, 25 May 2026 15:09:55 +0300 Subject: [PATCH 15/26] SCALRCORE-38392 updated readme --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index 94ab0e7..b936f52 100644 --- a/README.md +++ b/README.md @@ -85,12 +85,6 @@ Same as the full image, with Python 3.14 replaced by Python 3.9 (currently A non-root user `scalr` with uid/gid `1000` is created in the base layer and is therefore present in all variants. -### Security hardening - -`su`/`sudo`, account/password tools (`passwd`, `chsh`, `chage`, -`useradd`/`usermod`/`groupadd`/…), `mount`/`umount`, and all SUID/SGID bits -are stripped at the end of every image build. This applies to all variants. - ## Building the Image Builds are driven by [`docker-bake.hcl`](./docker-bake.hcl) (targets, tags, From 8733a2066f095390d21446ecc4a8f11f2f623aaf Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Mon, 25 May 2026 15:10:05 +0300 Subject: [PATCH 16/26] SCALRCORE-38392 add workflow to publish gar images --- .github/workflows/build_and_release_gar.yaml | 105 +++++++++++++++++++ .github/workflows/release.yaml | 37 +++++++ README.md | 1 - 3 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build_and_release_gar.yaml diff --git a/.github/workflows/build_and_release_gar.yaml b/.github/workflows/build_and_release_gar.yaml new file mode 100644 index 0000000..d6eb890 --- /dev/null +++ b/.github/workflows/build_and_release_gar.yaml @@ -0,0 +1,105 @@ +name: Build and Release Runner Image (GAR only) + +# Triggered when a PR carries the `build-gar-images` label. Pushes test +# builds of all three variants to the internal GAR mirror only — Docker +# Hub is not touched. Each new push to the PR rebuilds while the label +# is applied. Images are tagged by branch name (lower-cased) so +# collaborators can pull a stable reference (e.g. scalr/runner:my-branch). +# Each image is pushed to two regional GAR repos: europe-west4 and us-central1. + +on: + pull_request: + types: [labeled, synchronize, reopened] + +permissions: + contents: read + # Required by google-github-actions/auth for workload identity federation. + id-token: write + +jobs: + build: + name: Build and Push to GAR + if: contains(github.event.pull_request.labels.*.name, 'build-gar-images') + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v3 + with: + workload_identity_provider: ${{vars.GOOGLE_WORKLOAD_IDENTITY_POOL_PROVIDER}} + service_account: ${{vars.GOOGLE_SERVICE_ACCOUNT_EMAIL}} + token_format: access_token + + - name: Configure Docker for GAR + run: | + gcloud auth configure-docker ${{ vars.EU_DEV_MIRROR_LOCATION }}-docker.pkg.dev --quiet + gcloud auth configure-docker ${{ vars.US_PROD_MIRROR_LOCATION }}-docker.pkg.dev --quiet + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + # PR source branch, lower-cased and sanitized for use as a Docker tag + # (Docker tags are case-sensitive but downstream agents lower-case + # image refs; slashes are illegal and become dashes). + - name: Resolve branch tag + id: branch + run: | + raw='${{ github.head_ref }}' + sanitized="${raw,,}" + sanitized="${sanitized//\//-}" + echo "tag=${sanitized}" | tee -a $GITHUB_OUTPUT + + # Two regional GAR mirrors — EU dev and US production. + - name: Compose GAR image paths + id: gar + run: | + echo "image_eu=${{ vars.EU_DEV_MIRROR_LOCATION }}-docker.pkg.dev/${{ vars.EU_DEV_GOOGLE_PROJECT }}/main/scalr/runner" | tee -a $GITHUB_OUTPUT + echo "image_us=${{ vars.US_PROD_MIRROR_LOCATION }}-docker.pkg.dev/${{ vars.US_PROD_GOOGLE_PROJECT }}/main/scalr/runner" | tee -a $GITHUB_OUTPUT + + - name: Build and push images + uses: docker/bake-action@v5 + env: + VERSION: ${{ steps.branch.outputs.tag }} + with: + files: | + docker-bake.hcl + versions.json + push: true + # Replace each target's tag list (first `tags=` removes the Docker + # Hub default from docker-bake.hcl; subsequent lines add to the + # list) so a single build pushes to both regional GAR repos. + # The europe-west4 mirror also holds the per-branch buildcache. + set: | + full.tags=${{ steps.gar.outputs.image_eu }}:${{ steps.branch.outputs.tag }} + full.tags=${{ steps.gar.outputs.image_us }}:${{ steps.branch.outputs.tag }} + python39.tags=${{ steps.gar.outputs.image_eu }}:${{ steps.branch.outputs.tag }}-python39 + python39.tags=${{ steps.gar.outputs.image_us }}:${{ steps.branch.outputs.tag }}-python39 + slim.tags=${{ steps.gar.outputs.image_eu }}:${{ steps.branch.outputs.tag }}-slim + slim.tags=${{ steps.gar.outputs.image_us }}:${{ steps.branch.outputs.tag }}-slim + full.cache-from=type=registry,ref=${{ steps.gar.outputs.image_eu }}:buildcache-${{ steps.branch.outputs.tag }} + python39.cache-from=type=registry,ref=${{ steps.gar.outputs.image_eu }}:buildcache-${{ steps.branch.outputs.tag }}-python39 + slim.cache-from=type=registry,ref=${{ steps.gar.outputs.image_eu }}:buildcache-${{ steps.branch.outputs.tag }}-slim + full.cache-to=type=registry,ref=${{ steps.gar.outputs.image_eu }}:buildcache-${{ steps.branch.outputs.tag }},mode=max + python39.cache-to=type=registry,ref=${{ steps.gar.outputs.image_eu }}:buildcache-${{ steps.branch.outputs.tag }}-python39,mode=max + slim.cache-to=type=registry,ref=${{ steps.gar.outputs.image_eu }}:buildcache-${{ steps.branch.outputs.tag }}-slim,mode=max + + - name: Summary + run: | + cat <> $GITHUB_STEP_SUMMARY + Pushed GAR test images for branch \`${{ steps.branch.outputs.tag }}\`: + + europe-west4: + - \`${{ steps.gar.outputs.image_eu }}:${{ steps.branch.outputs.tag }}\` + - \`${{ steps.gar.outputs.image_eu }}:${{ steps.branch.outputs.tag }}-python39\` + - \`${{ steps.gar.outputs.image_eu }}:${{ steps.branch.outputs.tag }}-slim\` + + us-central1: + - \`${{ steps.gar.outputs.image_us }}:${{ steps.branch.outputs.tag }}\` + - \`${{ steps.gar.outputs.image_us }}:${{ steps.branch.outputs.tag }}-python39\` + - \`${{ steps.gar.outputs.image_us }}:${{ steps.branch.outputs.tag }}-slim\` + EOF diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 35d97cf..0744db4 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -5,6 +5,12 @@ on: tags: - "*.*.*" +permissions: + # Needed by the update_changelog job to git-push CHANGELOG.md back to main. + contents: write + # Required by google-github-actions/auth for workload identity federation. + id-token: write + jobs: build: name: Build and Push @@ -19,6 +25,20 @@ jobs: username: ${{ vars.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_PASSWORD }} + # Authenticate to Google Artifact Registry (GAR) so the same build can be + # mirrored to the internal Scalr registry alongside Docker Hub. + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v3 + with: + workload_identity_provider: ${{vars.GOOGLE_WORKLOAD_IDENTITY_POOL_PROVIDER}} + service_account: ${{vars.GOOGLE_SERVICE_ACCOUNT_EMAIL}} + token_format: access_token + + - name: Configure Docker for GAR + run: | + gcloud auth configure-docker ${{ vars.EU_DEV_MIRROR_LOCATION }}-docker.pkg.dev --quiet + gcloud auth configure-docker ${{ vars.US_PROD_MIRROR_LOCATION }}-docker.pkg.dev --quiet + - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -30,6 +50,13 @@ jobs: run: | echo "tag=${GITHUB_REF#refs/tags/}" | tee -a $GITHUB_OUTPUT + # Two regional GAR mirrors — EU dev and US production. + - name: Compose GAR image paths + id: gar + run: | + echo "image_eu=${{ vars.EU_DEV_MIRROR_LOCATION }}-docker.pkg.dev/${{ vars.EU_DEV_GOOGLE_PROJECT }}/main/scalr/runner" | tee -a $GITHUB_OUTPUT + echo "image_us=${{ vars.US_PROD_MIRROR_LOCATION }}-docker.pkg.dev/${{ vars.US_PROD_GOOGLE_PROJECT }}/main/scalr/runner" | tee -a $GITHUB_OUTPUT + - name: Build and push images uses: docker/bake-action@v5 env: @@ -39,7 +66,17 @@ jobs: docker-bake.hcl versions.json push: true + # Append both GAR region tags to each target so a single push writes + # to Docker Hub (declared in docker-bake.hcl) and both regional GAR + # mirrors. Cache-to is injected here because the docker-container + # driver supports it, while the bake file stays local-driver-friendly. set: | + full.tags+=${{ steps.gar.outputs.image_eu }}:${{ steps.image_tag.outputs.tag }} + full.tags+=${{ steps.gar.outputs.image_us }}:${{ steps.image_tag.outputs.tag }} + python39.tags+=${{ steps.gar.outputs.image_eu }}:${{ steps.image_tag.outputs.tag }}-python39 + python39.tags+=${{ steps.gar.outputs.image_us }}:${{ steps.image_tag.outputs.tag }}-python39 + slim.tags+=${{ steps.gar.outputs.image_eu }}:${{ steps.image_tag.outputs.tag }}-slim + slim.tags+=${{ steps.gar.outputs.image_us }}:${{ steps.image_tag.outputs.tag }}-slim full.cache-to=type=registry,ref=scalr/runner:buildcache,mode=max python39.cache-to=type=registry,ref=scalr/runner:buildcache-python39,mode=max slim.cache-to=type=registry,ref=scalr/runner:buildcache-slim,mode=max diff --git a/README.md b/README.md index b936f52..fd8a8be 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ remote operations backend of the Scalr platform and [on-prem Scalr Agents](https - [Added in the full image](#added-in-the-full-image) - [Added in the `-python39` image](#added-in-the--python39-image) - [Runtime user](#runtime-user) - - [Security hardening](#security-hardening) - [Building the Image](#building-the-image) - [Bumping Versions](#bumping-versions) From 2ccfc67f6ce9a8048986e6927053bf1169faa3ec Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Mon, 25 May 2026 17:29:37 +0300 Subject: [PATCH 17/26] SCALRCORE-38392 setup GAR build pipeline --- .github/workflows/build_and_release_gar.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_and_release_gar.yaml b/.github/workflows/build_and_release_gar.yaml index d6eb890..72f13de 100644 --- a/.github/workflows/build_and_release_gar.yaml +++ b/.github/workflows/build_and_release_gar.yaml @@ -26,6 +26,7 @@ jobs: uses: actions/checkout@v4 - name: Authenticate to Google Cloud + id: gcp-auth uses: google-github-actions/auth@v3 with: workload_identity_provider: ${{vars.GOOGLE_WORKLOAD_IDENTITY_POOL_PROVIDER}} @@ -34,8 +35,8 @@ jobs: - name: Configure Docker for GAR run: | - gcloud auth configure-docker ${{ vars.EU_DEV_MIRROR_LOCATION }}-docker.pkg.dev --quiet gcloud auth configure-docker ${{ vars.US_PROD_MIRROR_LOCATION }}-docker.pkg.dev --quiet + gcloud auth configure-docker ${{ vars.EU_DEV_MIRROR_LOCATION }}-docker.pkg.dev --quiet - name: Set up QEMU uses: docker/setup-qemu-action@v3 From f1ad38248dd43cc525d5d235629e930b90aa0aec Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Mon, 25 May 2026 17:53:49 +0300 Subject: [PATCH 18/26] SCALRCORE-38392 setup GAR build pipeline --- .github/workflows/build_and_release_gar.yaml | 100 +++++++++++-------- .github/workflows/release.yaml | 18 +++- 2 files changed, 71 insertions(+), 47 deletions(-) diff --git a/.github/workflows/build_and_release_gar.yaml b/.github/workflows/build_and_release_gar.yaml index 72f13de..6660380 100644 --- a/.github/workflows/build_and_release_gar.yaml +++ b/.github/workflows/build_and_release_gar.yaml @@ -1,11 +1,15 @@ name: Build and Release Runner Image (GAR only) # Triggered when a PR carries the `build-gar-images` label. Pushes test -# builds of all three variants to the internal GAR mirror only — Docker -# Hub is not touched. Each new push to the PR rebuilds while the label -# is applied. Images are tagged by branch name (lower-cased) so -# collaborators can pull a stable reference (e.g. scalr/runner:my-branch). -# Each image is pushed to two regional GAR repos: europe-west4 and us-central1. +# builds of all three variants to the EU dev GAR mirror only — Docker +# Hub and the US production mirror are never touched here (production +# is reserved for the release.yaml workflow). +# +# Tags are derived from the PR source branch, lower-cased and prefixed +# with `branch-` so they cannot collide with the semver release tags +# produced by release.yaml. Example: branch `0.2.0` → `branch-0.2.0`, +# not `0.2.0`. This prevents a PR from a maliciously-named branch from +# overwriting an existing release image. on: pull_request: @@ -25,6 +29,25 @@ jobs: - name: Checkout uses: actions/checkout@v4 + # Defense-in-depth: refuse to build on behalf of senders without + # write-or-better access on this repo. Covers two paths: + # - `labeled` event: the user who added the label must be trusted. + # - `synchronize` event: the user who pushed must be trusted. + # GitHub already strips secrets/vars for `pull_request` runs from + # forks, so this is layered on top of that built-in protection. + - name: Verify sender authorization + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ACTOR: ${{ github.event.sender.login }} + REPO: ${{ github.repository }} + run: | + set -euo pipefail + role=$(gh api "repos/${REPO}/collaborators/${ACTOR}/permission" --jq .permission) + case "$role" in + admin|maintain|write) echo "OK: ${ACTOR} has ${role} on ${REPO}";; + *) echo "::error::Sender ${ACTOR} has '${role}' permission; needs write or higher"; exit 1;; + esac + - name: Authenticate to Google Cloud id: gcp-auth uses: google-github-actions/auth@v3 @@ -33,10 +56,12 @@ jobs: service_account: ${{vars.GOOGLE_SERVICE_ACCOUNT_EMAIL}} token_format: access_token - - name: Configure Docker for GAR - run: | - gcloud auth configure-docker ${{ vars.US_PROD_MIRROR_LOCATION }}-docker.pkg.dev --quiet - gcloud auth configure-docker ${{ vars.EU_DEV_MIRROR_LOCATION }}-docker.pkg.dev --quiet + - name: Login to GAR (EU dev) + uses: docker/login-action@v3 + with: + registry: ${{ vars.EU_DEV_MIRROR_LOCATION }}-docker.pkg.dev + username: oauth2accesstoken + password: ${{ steps.gcp-auth.outputs.access_token }} - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -44,23 +69,22 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - # PR source branch, lower-cased and sanitized for use as a Docker tag - # (Docker tags are case-sensitive but downstream agents lower-case - # image refs; slashes are illegal and become dashes). + # PR source branch → Docker tag. Lower-cased (downstream agents + # lower-case image refs), slashes → dashes (illegal in tags), and + # prefixed with `branch-` so the tag can never collide with a + # semver release tag pushed by release.yaml. - name: Resolve branch tag id: branch run: | raw='${{ github.head_ref }}' sanitized="${raw,,}" sanitized="${sanitized//\//-}" - echo "tag=${sanitized}" | tee -a $GITHUB_OUTPUT + echo "tag=branch-${sanitized}" | tee -a $GITHUB_OUTPUT - # Two regional GAR mirrors — EU dev and US production. - - name: Compose GAR image paths + - name: Compose GAR image path id: gar run: | - echo "image_eu=${{ vars.EU_DEV_MIRROR_LOCATION }}-docker.pkg.dev/${{ vars.EU_DEV_GOOGLE_PROJECT }}/main/scalr/runner" | tee -a $GITHUB_OUTPUT - echo "image_us=${{ vars.US_PROD_MIRROR_LOCATION }}-docker.pkg.dev/${{ vars.US_PROD_GOOGLE_PROJECT }}/main/scalr/runner" | tee -a $GITHUB_OUTPUT + echo "image=${{ vars.EU_DEV_MIRROR_LOCATION }}-docker.pkg.dev/${{ vars.EU_DEV_GOOGLE_PROJECT }}/main/scalr/runner" | tee -a $GITHUB_OUTPUT - name: Build and push images uses: docker/bake-action@v5 @@ -71,36 +95,26 @@ jobs: docker-bake.hcl versions.json push: true - # Replace each target's tag list (first `tags=` removes the Docker - # Hub default from docker-bake.hcl; subsequent lines add to the - # list) so a single build pushes to both regional GAR repos. - # The europe-west4 mirror also holds the per-branch buildcache. + # Replace each target's tag list (`tags=` removes the Docker Hub + # default from docker-bake.hcl) so the build pushes only to the EU + # dev GAR mirror, which also holds the per-branch buildcache. set: | - full.tags=${{ steps.gar.outputs.image_eu }}:${{ steps.branch.outputs.tag }} - full.tags=${{ steps.gar.outputs.image_us }}:${{ steps.branch.outputs.tag }} - python39.tags=${{ steps.gar.outputs.image_eu }}:${{ steps.branch.outputs.tag }}-python39 - python39.tags=${{ steps.gar.outputs.image_us }}:${{ steps.branch.outputs.tag }}-python39 - slim.tags=${{ steps.gar.outputs.image_eu }}:${{ steps.branch.outputs.tag }}-slim - slim.tags=${{ steps.gar.outputs.image_us }}:${{ steps.branch.outputs.tag }}-slim - full.cache-from=type=registry,ref=${{ steps.gar.outputs.image_eu }}:buildcache-${{ steps.branch.outputs.tag }} - python39.cache-from=type=registry,ref=${{ steps.gar.outputs.image_eu }}:buildcache-${{ steps.branch.outputs.tag }}-python39 - slim.cache-from=type=registry,ref=${{ steps.gar.outputs.image_eu }}:buildcache-${{ steps.branch.outputs.tag }}-slim - full.cache-to=type=registry,ref=${{ steps.gar.outputs.image_eu }}:buildcache-${{ steps.branch.outputs.tag }},mode=max - python39.cache-to=type=registry,ref=${{ steps.gar.outputs.image_eu }}:buildcache-${{ steps.branch.outputs.tag }}-python39,mode=max - slim.cache-to=type=registry,ref=${{ steps.gar.outputs.image_eu }}:buildcache-${{ steps.branch.outputs.tag }}-slim,mode=max + full.tags=${{ steps.gar.outputs.image }}:${{ steps.branch.outputs.tag }} + python39.tags=${{ steps.gar.outputs.image }}:${{ steps.branch.outputs.tag }}-python39 + slim.tags=${{ steps.gar.outputs.image }}:${{ steps.branch.outputs.tag }}-slim + full.cache-from=type=registry,ref=${{ steps.gar.outputs.image }}:buildcache-${{ steps.branch.outputs.tag }} + python39.cache-from=type=registry,ref=${{ steps.gar.outputs.image }}:buildcache-${{ steps.branch.outputs.tag }}-python39 + slim.cache-from=type=registry,ref=${{ steps.gar.outputs.image }}:buildcache-${{ steps.branch.outputs.tag }}-slim + full.cache-to=type=registry,ref=${{ steps.gar.outputs.image }}:buildcache-${{ steps.branch.outputs.tag }},mode=max + python39.cache-to=type=registry,ref=${{ steps.gar.outputs.image }}:buildcache-${{ steps.branch.outputs.tag }}-python39,mode=max + slim.cache-to=type=registry,ref=${{ steps.gar.outputs.image }}:buildcache-${{ steps.branch.outputs.tag }}-slim,mode=max - name: Summary run: | cat <> $GITHUB_STEP_SUMMARY - Pushed GAR test images for branch \`${{ steps.branch.outputs.tag }}\`: - - europe-west4: - - \`${{ steps.gar.outputs.image_eu }}:${{ steps.branch.outputs.tag }}\` - - \`${{ steps.gar.outputs.image_eu }}:${{ steps.branch.outputs.tag }}-python39\` - - \`${{ steps.gar.outputs.image_eu }}:${{ steps.branch.outputs.tag }}-slim\` + Pushed GAR test images (EU dev) for branch \`${{ github.head_ref }}\`: - us-central1: - - \`${{ steps.gar.outputs.image_us }}:${{ steps.branch.outputs.tag }}\` - - \`${{ steps.gar.outputs.image_us }}:${{ steps.branch.outputs.tag }}-python39\` - - \`${{ steps.gar.outputs.image_us }}:${{ steps.branch.outputs.tag }}-slim\` + - \`${{ steps.gar.outputs.image }}:${{ steps.branch.outputs.tag }}\` + - \`${{ steps.gar.outputs.image }}:${{ steps.branch.outputs.tag }}-python39\` + - \`${{ steps.gar.outputs.image }}:${{ steps.branch.outputs.tag }}-slim\` EOF diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 0744db4..e7f7bd3 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -28,16 +28,26 @@ jobs: # Authenticate to Google Artifact Registry (GAR) so the same build can be # mirrored to the internal Scalr registry alongside Docker Hub. - name: Authenticate to Google Cloud + id: gcp-auth uses: google-github-actions/auth@v3 with: workload_identity_provider: ${{vars.GOOGLE_WORKLOAD_IDENTITY_POOL_PROVIDER}} service_account: ${{vars.GOOGLE_SERVICE_ACCOUNT_EMAIL}} token_format: access_token - - name: Configure Docker for GAR - run: | - gcloud auth configure-docker ${{ vars.EU_DEV_MIRROR_LOCATION }}-docker.pkg.dev --quiet - gcloud auth configure-docker ${{ vars.US_PROD_MIRROR_LOCATION }}-docker.pkg.dev --quiet + - name: Login to GAR (EU dev) + uses: docker/login-action@v3 + with: + registry: ${{ vars.EU_DEV_MIRROR_LOCATION }}-docker.pkg.dev + username: oauth2accesstoken + password: ${{ steps.gcp-auth.outputs.access_token }} + + - name: Login to GAR (US prod) + uses: docker/login-action@v3 + with: + registry: ${{ vars.US_PROD_MIRROR_LOCATION }}-docker.pkg.dev + username: oauth2accesstoken + password: ${{ steps.gcp-auth.outputs.access_token }} - name: Set up QEMU uses: docker/setup-qemu-action@v3 From 58879b23a6119ea33ff02a54493c20933f5569a7 Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Mon, 25 May 2026 18:08:05 +0300 Subject: [PATCH 19/26] SCALRCORE-38392 pin older gloud 564.0.0 for python39 image --- .github/workflows/build_and_release_gar.yaml | 4 ++-- README.md | 9 ++++++--- versions.json | 5 ++++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build_and_release_gar.yaml b/.github/workflows/build_and_release_gar.yaml index 6660380..55d8ef9 100644 --- a/.github/workflows/build_and_release_gar.yaml +++ b/.github/workflows/build_and_release_gar.yaml @@ -1,4 +1,4 @@ -name: Build and Release Runner Image (GAR only) +name: Build and Release Runner Image (EU dev GAR) # Triggered when a PR carries the `build-gar-images` label. Pushes test # builds of all three variants to the EU dev GAR mirror only — Docker @@ -22,7 +22,7 @@ permissions: jobs: build: - name: Build and Push to GAR + name: Build and Push to GAR (EU dev) if: contains(github.event.pull_request.labels.*.name, 'build-gar-images') runs-on: ubuntu-latest steps: diff --git a/README.md b/README.md index fd8a8be..870a4e1 100644 --- a/README.md +++ b/README.md @@ -116,14 +116,17 @@ VERSION=dev docker buildx bake -f docker-bake.hcl -f versions.json \ ### Build one variant ```bash +# scalr/runner:dev VERSION=dev docker buildx bake -f docker-bake.hcl -f versions.json \ - --set "*.platform=linux/amd64" --load full # scalr/runner:dev + --set "*.platform=linux/amd64" --load full +# scalr/runner:dev-python39 VERSION=dev docker buildx bake -f docker-bake.hcl -f versions.json \ - --set "*.platform=linux/amd64" --load python39 # scalr/runner:dev-python39 + --set "*.platform=linux/amd64" --load python39 +# scalr/runner:dev-slim VERSION=dev docker buildx bake -f docker-bake.hcl -f versions.json \ - --set "*.platform=linux/amd64" --load slim # scalr/runner:dev-slim + --set "*.platform=linux/amd64" --load slim ``` ## Bumping Versions diff --git a/versions.json b/versions.json index 4f676d0..2278c10 100644 --- a/versions.json +++ b/versions.json @@ -35,7 +35,10 @@ "PYTHON_VERSION": "3.9.25", "PYTHON_RELEASE": "20251031", "PYTHON_SHA256_AMD64": "5afeb1b01609195371a11b2fe6a3e73a315ff6baffd5d3c4ffa2ac3c6245acf5", - "PYTHON_SHA256_ARM64": "1ff8edb17b2448883c56ae1046a9c55db604ed52800febe7cd0cdebb7d39fceb" + "PYTHON_SHA256_ARM64": "1ff8edb17b2448883c56ae1046a9c55db604ed52800febe7cd0cdebb7d39fceb", + "GCLOUD_VERSION": "564.0.0", + "GCLOUD_SHA256_AMD64": "e6e85df426861590178505dde16e1c7c06c806d24a6e2f62b2bdc39f677371c7", + "GCLOUD_SHA256_ARM64": "92f7581e434ae44f89d379936e6e7a20f0c9a78439b5834f9b21bde8b3ed8ec6" } } } From efcea2357d814bac9d98f14acfbd4419cf18fd8e Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Mon, 25 May 2026 18:10:16 +0300 Subject: [PATCH 20/26] SCALRCORE-38392 updated readme --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 870a4e1..badc1a9 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,13 @@ versions below are the current pins (kept in sync with `versions.json` by Same as the full image, with Python 3.14 replaced by Python 3.9 (currently [v3.9.25](https://www.python.org/downloads/release/python-3925/)). +**Google Cloud SDK pin.** Newer `gcloud` releases dropped Python 3.9 +support, so the SDK is pinned to `564.0.0` (the last version that still +works on Python 3.9) via the `versions_python39` map. `bump-versions.py` +does not auto-bump this pin — if you ever change it, recompute the +per-arch SHA256s for the new version by hand. The full (Python 3.14) +image continues to track the latest gcloud release. + ### Runtime user A non-root user `scalr` with uid/gid `1000` is created in the base layer From 24a7b05c3f96686bdb059bd45bfcca9ad69a9a27 Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Mon, 25 May 2026 18:25:39 +0300 Subject: [PATCH 21/26] SCALRCORE-38392 setup GAR build pipeline --- .github/workflows/build_and_release_gar.yaml | 26 +++++++++++---- .github/workflows/release.yaml | 34 ++++++++++++++++++++ 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build_and_release_gar.yaml b/.github/workflows/build_and_release_gar.yaml index 55d8ef9..74f9c0b 100644 --- a/.github/workflows/build_and_release_gar.yaml +++ b/.github/workflows/build_and_release_gar.yaml @@ -110,11 +110,23 @@ jobs: slim.cache-to=type=registry,ref=${{ steps.gar.outputs.image }}:buildcache-${{ steps.branch.outputs.tag }}-slim,mode=max - name: Summary + env: + BRANCH: ${{ github.head_ref }} + TAG: ${{ steps.branch.outputs.tag }} + IMG: ${{ steps.gar.outputs.image }} run: | - cat <> $GITHUB_STEP_SUMMARY - Pushed GAR test images (EU dev) for branch \`${{ github.head_ref }}\`: - - - \`${{ steps.gar.outputs.image }}:${{ steps.branch.outputs.tag }}\` - - \`${{ steps.gar.outputs.image }}:${{ steps.branch.outputs.tag }}-python39\` - - \`${{ steps.gar.outputs.image }}:${{ steps.branch.outputs.tag }}-slim\` - EOF + refs=( + "${IMG}:${TAG}" + "${IMG}:${TAG}-python39" + "${IMG}:${TAG}-slim" + ) + echo "::group::Published: GAR — EU dev (branch ${BRANCH})" + for ref in "${refs[@]}"; do echo " - ${ref}"; done + echo "::endgroup::" + { + echo "## Published runner images — branch \`${BRANCH}\`" + echo "" + echo "**GAR — EU dev**" + echo "" + for ref in "${refs[@]}"; do echo "- \`${ref}\`"; done + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index e7f7bd3..25803b7 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -91,6 +91,40 @@ jobs: python39.cache-to=type=registry,ref=scalr/runner:buildcache-python39,mode=max slim.cache-to=type=registry,ref=scalr/runner:buildcache-slim,mode=max + - name: Summary + env: + TAG: ${{ steps.image_tag.outputs.tag }} + IMG_EU: ${{ steps.gar.outputs.image_eu }} + IMG_US: ${{ steps.gar.outputs.image_us }} + run: | + render() { + local title="$1"; shift + echo "::group::Published: ${title}" + for ref in "$@"; do echo " - ${ref}"; done + echo "::endgroup::" + { + echo "" + echo "**${title}**" + echo "" + for ref in "$@"; do echo "- \`${ref}\`"; done + } >> "$GITHUB_STEP_SUMMARY" + } + { + echo "## Published runner images — \`${TAG}\`" + } >> "$GITHUB_STEP_SUMMARY" + render "Docker Hub" \ + "scalr/runner:${TAG}" \ + "scalr/runner:${TAG}-python39" \ + "scalr/runner:${TAG}-slim" + render "GAR — EU dev" \ + "${IMG_EU}:${TAG}" \ + "${IMG_EU}:${TAG}-python39" \ + "${IMG_EU}:${TAG}-slim" + render "GAR — US prod" \ + "${IMG_US}:${TAG}" \ + "${IMG_US}:${TAG}-python39" \ + "${IMG_US}:${TAG}-slim" + update_changelog: name: Update Changelog runs-on: ubuntu-latest From b48b81c71898e6371ad6cc0c53b5da59f9f567b2 Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Mon, 25 May 2026 18:30:41 +0300 Subject: [PATCH 22/26] SCALRCORE-38392 setup GAR build pipeline --- .github/workflows/build_and_release_gar.yaml | 19 +++---- .github/workflows/release.yaml | 60 ++++++++++++-------- 2 files changed, 44 insertions(+), 35 deletions(-) diff --git a/.github/workflows/build_and_release_gar.yaml b/.github/workflows/build_and_release_gar.yaml index 74f9c0b..d1a7186 100644 --- a/.github/workflows/build_and_release_gar.yaml +++ b/.github/workflows/build_and_release_gar.yaml @@ -109,24 +109,23 @@ jobs: python39.cache-to=type=registry,ref=${{ steps.gar.outputs.image }}:buildcache-${{ steps.branch.outputs.tag }}-python39,mode=max slim.cache-to=type=registry,ref=${{ steps.gar.outputs.image }}:buildcache-${{ steps.branch.outputs.tag }}-slim,mode=max - - name: Summary + - name: Report published images env: BRANCH: ${{ github.head_ref }} TAG: ${{ steps.branch.outputs.tag }} IMG: ${{ steps.gar.outputs.image }} run: | - refs=( - "${IMG}:${TAG}" - "${IMG}:${TAG}-python39" - "${IMG}:${TAG}-slim" - ) - echo "::group::Published: GAR — EU dev (branch ${BRANCH})" - for ref in "${refs[@]}"; do echo " - ${ref}"; done - echo "::endgroup::" + echo "Published GAR (EU dev) images for branch ${BRANCH}" + echo "" + echo " ${IMG}:${TAG}" + echo " ${IMG}:${TAG}-python39" + echo " ${IMG}:${TAG}-slim" { echo "## Published runner images — branch \`${BRANCH}\`" echo "" echo "**GAR — EU dev**" echo "" - for ref in "${refs[@]}"; do echo "- \`${ref}\`"; done + echo "- \`${IMG}:${TAG}\`" + echo "- \`${IMG}:${TAG}-python39\`" + echo "- \`${IMG}:${TAG}-slim\`" } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 25803b7..4ce1f76 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -91,39 +91,49 @@ jobs: python39.cache-to=type=registry,ref=scalr/runner:buildcache-python39,mode=max slim.cache-to=type=registry,ref=scalr/runner:buildcache-slim,mode=max - - name: Summary + - name: Report published images env: TAG: ${{ steps.image_tag.outputs.tag }} IMG_EU: ${{ steps.gar.outputs.image_eu }} IMG_US: ${{ steps.gar.outputs.image_us }} run: | - render() { - local title="$1"; shift - echo "::group::Published: ${title}" - for ref in "$@"; do echo " - ${ref}"; done - echo "::endgroup::" - { - echo "" - echo "**${title}**" - echo "" - for ref in "$@"; do echo "- \`${ref}\`"; done - } >> "$GITHUB_STEP_SUMMARY" - } + echo "Published runner images for release ${TAG}" + echo "" + echo "Docker Hub:" + echo " scalr/runner:${TAG}" + echo " scalr/runner:${TAG}-python39" + echo " scalr/runner:${TAG}-slim" + echo "" + echo "GAR — EU dev:" + echo " ${IMG_EU}:${TAG}" + echo " ${IMG_EU}:${TAG}-python39" + echo " ${IMG_EU}:${TAG}-slim" + echo "" + echo "GAR — US prod:" + echo " ${IMG_US}:${TAG}" + echo " ${IMG_US}:${TAG}-python39" + echo " ${IMG_US}:${TAG}-slim" { echo "## Published runner images — \`${TAG}\`" + echo "" + echo "**Docker Hub**" + echo "" + echo "- \`scalr/runner:${TAG}\`" + echo "- \`scalr/runner:${TAG}-python39\`" + echo "- \`scalr/runner:${TAG}-slim\`" + echo "" + echo "**GAR — EU dev**" + echo "" + echo "- \`${IMG_EU}:${TAG}\`" + echo "- \`${IMG_EU}:${TAG}-python39\`" + echo "- \`${IMG_EU}:${TAG}-slim\`" + echo "" + echo "**GAR — US prod**" + echo "" + echo "- \`${IMG_US}:${TAG}\`" + echo "- \`${IMG_US}:${TAG}-python39\`" + echo "- \`${IMG_US}:${TAG}-slim\`" } >> "$GITHUB_STEP_SUMMARY" - render "Docker Hub" \ - "scalr/runner:${TAG}" \ - "scalr/runner:${TAG}-python39" \ - "scalr/runner:${TAG}-slim" - render "GAR — EU dev" \ - "${IMG_EU}:${TAG}" \ - "${IMG_EU}:${TAG}-python39" \ - "${IMG_EU}:${TAG}-slim" - render "GAR — US prod" \ - "${IMG_US}:${TAG}" \ - "${IMG_US}:${TAG}-python39" \ - "${IMG_US}:${TAG}-slim" update_changelog: name: Update Changelog From 2c9504c0d46eaa77bf986aa6122b69a8f28cff07 Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Mon, 25 May 2026 19:31:49 +0300 Subject: [PATCH 23/26] SCALRCORE-38392 remove buldcache artifacts from dockerhub repos --- .github/workflows/build.yaml | 11 ++++++++--- .github/workflows/release.yaml | 13 ++++++++----- docker-bake.hcl | 27 ++++++++++++--------------- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index bd744c7..174a4f8 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -37,14 +37,19 @@ jobs: docker-bake.hcl versions.json load: true + # Use GitHub Actions cache (type=gha) — scoped per target, lives in + # GitHub's per-repo cache backend, not the public Docker Hub repo. set: | *.platform=linux/amd64 full.tags=scalr/runner:sha-${{ github.sha }} python39.tags=scalr/runner:sha-${{ github.sha }}-python39 slim.tags=scalr/runner:sha-${{ github.sha }}-slim - full.cache-to=type=registry,ref=scalr/runner:buildcache,mode=max - python39.cache-to=type=registry,ref=scalr/runner:buildcache-python39,mode=max - slim.cache-to=type=registry,ref=scalr/runner:buildcache-slim,mode=max + full.cache-from=type=gha,scope=full + full.cache-to=type=gha,scope=full,mode=max + python39.cache-from=type=gha,scope=python39 + python39.cache-to=type=gha,scope=python39,mode=max + slim.cache-from=type=gha,scope=slim + slim.cache-to=type=gha,scope=slim,mode=max - name: Test full image run: | diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 4ce1f76..0931f69 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -78,8 +78,8 @@ jobs: push: true # Append both GAR region tags to each target so a single push writes # to Docker Hub (declared in docker-bake.hcl) and both regional GAR - # mirrors. Cache-to is injected here because the docker-container - # driver supports it, while the bake file stays local-driver-friendly. + # mirrors. Cache uses GitHub Actions cache (per-repo, private) so + # nothing cache-related leaks into the public Docker Hub repo. set: | full.tags+=${{ steps.gar.outputs.image_eu }}:${{ steps.image_tag.outputs.tag }} full.tags+=${{ steps.gar.outputs.image_us }}:${{ steps.image_tag.outputs.tag }} @@ -87,9 +87,12 @@ jobs: python39.tags+=${{ steps.gar.outputs.image_us }}:${{ steps.image_tag.outputs.tag }}-python39 slim.tags+=${{ steps.gar.outputs.image_eu }}:${{ steps.image_tag.outputs.tag }}-slim slim.tags+=${{ steps.gar.outputs.image_us }}:${{ steps.image_tag.outputs.tag }}-slim - full.cache-to=type=registry,ref=scalr/runner:buildcache,mode=max - python39.cache-to=type=registry,ref=scalr/runner:buildcache-python39,mode=max - slim.cache-to=type=registry,ref=scalr/runner:buildcache-slim,mode=max + full.cache-from=type=gha,scope=full + full.cache-to=type=gha,scope=full,mode=max + python39.cache-from=type=gha,scope=python39 + python39.cache-to=type=gha,scope=python39,mode=max + slim.cache-from=type=gha,scope=slim + slim.cache-to=type=gha,scope=slim,mode=max - name: Report published images env: diff --git a/docker-bake.hcl b/docker-bake.hcl index ebb57f1..0f1cac1 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -36,25 +36,22 @@ group "default" { } target "full" { - target = "full" - platforms = ["linux/amd64", "linux/arm64"] - args = merge(versions_base, versions_full) - tags = ["scalr/runner:${VERSION}"] - cache-from = ["type=registry,ref=scalr/runner:buildcache"] + target = "full" + platforms = ["linux/amd64", "linux/arm64"] + args = merge(versions_base, versions_full) + tags = ["scalr/runner:${VERSION}"] } target "python39" { - target = "full" - platforms = ["linux/amd64", "linux/arm64"] - args = merge(versions_base, versions_full, versions_python39) - tags = ["scalr/runner:${VERSION}-python39"] - cache-from = ["type=registry,ref=scalr/runner:buildcache-python39"] + target = "full" + platforms = ["linux/amd64", "linux/arm64"] + args = merge(versions_base, versions_full, versions_python39) + tags = ["scalr/runner:${VERSION}-python39"] } target "slim" { - target = "slim" - platforms = ["linux/amd64", "linux/arm64"] - args = versions_base - tags = ["scalr/runner:${VERSION}-slim"] - cache-from = ["type=registry,ref=scalr/runner:buildcache-slim"] + target = "slim" + platforms = ["linux/amd64", "linux/arm64"] + args = versions_base + tags = ["scalr/runner:${VERSION}-slim"] } From 028f56f7ed9bd5283052a37ef178c712189a043f Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Tue, 26 May 2026 00:05:04 +0300 Subject: [PATCH 24/26] test release --- .github/workflows/test_release.yaml | 144 ++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 .github/workflows/test_release.yaml diff --git a/.github/workflows/test_release.yaml b/.github/workflows/test_release.yaml new file mode 100644 index 0000000..625f839 --- /dev/null +++ b/.github/workflows/test_release.yaml @@ -0,0 +1,144 @@ +name: Test Release Pipeline + +# PR-triggered mirror of release.yaml — used to exercise the release pipeline +# end-to-end without actually publishing a release. Runs only when the PR +# carries the `test` label; rebuilds on every push to that PR while the label +# is applied. +# +# Differences from release.yaml: +# - Triggered by `test` label on a PR, not by a semver tag push. +# - Image tag is derived from the PR branch (prefixed `test-`) so it cannot +# collide with real release tags. +# - Docker Hub login + push is commented out (temporary) — pushes only to +# the internal GAR mirrors. +# - No update_changelog job. + +on: + pull_request: + types: [labeled, synchronize, reopened] + +permissions: + contents: read + # Required by google-github-actions/auth for workload identity federation. + id-token: write + +jobs: + build: + name: Build and Push (test) + if: contains(github.event.pull_request.labels.*.name, 'test') + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + # Docker Hub login temporarily disabled — see workflow header. + # - name: Login to Docker Hub + # uses: docker/login-action@v3 + # with: + # username: ${{ vars.DOCKERHUB_USERNAME }} + # password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Authenticate to Google Cloud + id: gcp-auth + uses: google-github-actions/auth@v3 + with: + workload_identity_provider: ${{vars.GOOGLE_WORKLOAD_IDENTITY_POOL_PROVIDER}} + service_account: ${{vars.GOOGLE_SERVICE_ACCOUNT_EMAIL}} + token_format: access_token + + - name: Login to GAR (EU dev) + uses: docker/login-action@v3 + with: + registry: ${{ vars.EU_DEV_MIRROR_LOCATION }}-docker.pkg.dev + username: oauth2accesstoken + password: ${{ steps.gcp-auth.outputs.access_token }} + + - name: Login to GAR (US prod) + uses: docker/login-action@v3 + with: + registry: ${{ vars.US_PROD_MIRROR_LOCATION }}-docker.pkg.dev + username: oauth2accesstoken + password: ${{ steps.gcp-auth.outputs.access_token }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + # PR source branch → Docker tag. Lower-cased, slashes → dashes, and + # prefixed `test-` so the tag cannot collide with a semver release tag + # pushed by release.yaml. + - name: Format Image Tag + id: image_tag + run: | + raw='${{ github.head_ref }}' + sanitized="${raw,,}" + sanitized="${sanitized//\//-}" + echo "tag=test-${sanitized}" | tee -a $GITHUB_OUTPUT + + # Two regional GAR mirrors — EU dev and US production. + - name: Compose GAR image paths + id: gar + run: | + echo "image_eu=${{ vars.EU_DEV_MIRROR_LOCATION }}-docker.pkg.dev/${{ vars.EU_DEV_GOOGLE_PROJECT }}/main/scalr/runner" | tee -a $GITHUB_OUTPUT + echo "image_us=${{ vars.US_PROD_MIRROR_LOCATION }}-docker.pkg.dev/${{ vars.US_PROD_GOOGLE_PROJECT }}/main/scalr/runner" | tee -a $GITHUB_OUTPUT + + - name: Build and push images + uses: docker/bake-action@v5 + env: + VERSION: ${{ steps.image_tag.outputs.tag }} + with: + files: | + docker-bake.hcl + versions.json + push: true + # Replace each target's tag list (`tags=` removes the Docker Hub + # default from docker-bake.hcl since DH login is disabled) so the + # build pushes only to the two GAR mirrors. + set: | + full.tags=${{ steps.gar.outputs.image_eu }}:${{ steps.image_tag.outputs.tag }} + full.tags=${{ steps.gar.outputs.image_us }}:${{ steps.image_tag.outputs.tag }} + python39.tags=${{ steps.gar.outputs.image_eu }}:${{ steps.image_tag.outputs.tag }}-python39 + python39.tags=${{ steps.gar.outputs.image_us }}:${{ steps.image_tag.outputs.tag }}-python39 + slim.tags=${{ steps.gar.outputs.image_eu }}:${{ steps.image_tag.outputs.tag }}-slim + slim.tags=${{ steps.gar.outputs.image_us }}:${{ steps.image_tag.outputs.tag }}-slim + full.cache-from=type=gha,scope=full + full.cache-to=type=gha,scope=full,mode=max + python39.cache-from=type=gha,scope=python39 + python39.cache-to=type=gha,scope=python39,mode=max + slim.cache-from=type=gha,scope=slim + slim.cache-to=type=gha,scope=slim,mode=max + + - name: Report published images + env: + TAG: ${{ steps.image_tag.outputs.tag }} + IMG_EU: ${{ steps.gar.outputs.image_eu }} + IMG_US: ${{ steps.gar.outputs.image_us }} + run: | + echo "Published test runner images for tag ${TAG}" + echo "" + echo "GAR — EU dev:" + echo " ${IMG_EU}:${TAG}" + echo " ${IMG_EU}:${TAG}-python39" + echo " ${IMG_EU}:${TAG}-slim" + echo "" + echo "GAR — US prod:" + echo " ${IMG_US}:${TAG}" + echo " ${IMG_US}:${TAG}-python39" + echo " ${IMG_US}:${TAG}-slim" + { + echo "## Published test runner images — \`${TAG}\`" + echo "" + echo "**GAR — EU dev**" + echo "" + echo "- \`${IMG_EU}:${TAG}\`" + echo "- \`${IMG_EU}:${TAG}-python39\`" + echo "- \`${IMG_EU}:${TAG}-slim\`" + echo "" + echo "**GAR — US prod**" + echo "" + echo "- \`${IMG_US}:${TAG}\`" + echo "- \`${IMG_US}:${TAG}-python39\`" + echo "- \`${IMG_US}:${TAG}-slim\`" + } >> "$GITHUB_STEP_SUMMARY" From cc1bb7ab7c17c785feec016c83bb74f78658c6fe Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Tue, 26 May 2026 00:12:41 +0300 Subject: [PATCH 25/26] test release --- .github/workflows/test_release.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test_release.yaml b/.github/workflows/test_release.yaml index 625f839..3f5423b 100644 --- a/.github/workflows/test_release.yaml +++ b/.github/workflows/test_release.yaml @@ -46,12 +46,12 @@ jobs: service_account: ${{vars.GOOGLE_SERVICE_ACCOUNT_EMAIL}} token_format: access_token - - name: Login to GAR (EU dev) - uses: docker/login-action@v3 - with: - registry: ${{ vars.EU_DEV_MIRROR_LOCATION }}-docker.pkg.dev - username: oauth2accesstoken - password: ${{ steps.gcp-auth.outputs.access_token }} + # - name: Login to GAR (EU dev) + # uses: docker/login-action@v3 + # with: + # registry: ${{ vars.EU_DEV_MIRROR_LOCATION }}-docker.pkg.dev + # username: oauth2accesstoken + # password: ${{ steps.gcp-auth.outputs.access_token }} - name: Login to GAR (US prod) uses: docker/login-action@v3 From a60c57370d7919c07b21c4c03890e029c200382e Mon Sep 17 00:00:00 2001 From: Serhii Babak Date: Tue, 26 May 2026 00:17:53 +0300 Subject: [PATCH 26/26] test release --- .github/workflows/test_release.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test_release.yaml b/.github/workflows/test_release.yaml index 3f5423b..7019e61 100644 --- a/.github/workflows/test_release.yaml +++ b/.github/workflows/test_release.yaml @@ -94,14 +94,11 @@ jobs: versions.json push: true # Replace each target's tag list (`tags=` removes the Docker Hub - # default from docker-bake.hcl since DH login is disabled) so the - # build pushes only to the two GAR mirrors. + # default from docker-bake.hcl since DH login is disabled). EU dev + # push is temporarily disabled — only US prod is pushed. set: | - full.tags=${{ steps.gar.outputs.image_eu }}:${{ steps.image_tag.outputs.tag }} full.tags=${{ steps.gar.outputs.image_us }}:${{ steps.image_tag.outputs.tag }} - python39.tags=${{ steps.gar.outputs.image_eu }}:${{ steps.image_tag.outputs.tag }}-python39 python39.tags=${{ steps.gar.outputs.image_us }}:${{ steps.image_tag.outputs.tag }}-python39 - slim.tags=${{ steps.gar.outputs.image_eu }}:${{ steps.image_tag.outputs.tag }}-slim slim.tags=${{ steps.gar.outputs.image_us }}:${{ steps.image_tag.outputs.tag }}-slim full.cache-from=type=gha,scope=full full.cache-to=type=gha,scope=full,mode=max