This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new aeb692f901f Skip date-shaped tags in upgrade_important_versions image 
bumper (#66588)
aeb692f901f is described below

commit aeb692f901ffc3540185eeda75da3c4f634b538c
Author: Jarek Potiuk <[email protected]>
AuthorDate: Fri May 8 17:46:29 2026 +0200

    Skip date-shaped tags in upgrade_important_versions image bumper (#66588)
    
    The CI image bumper picked alpine's daily-edge tag 20260127 over the
    3.23 release in apache/airflow#66580 -- and the same logic would
    auto-pin any other Docker Hub image to its date-stamped daily build
    under the right circumstances. Root cause:
    packaging.version.Version("20260127") parses as a valid single-component
    PEP 440 version and sorts higher than 3.23, so the bumper's "latest
    version" selection picked the daily.
    
    Pre-filter the tag list against a date-shaped regex (v?\d{8}(\.\d+)?)
    before parsing as a Version. Belt-and-braces: also reject parsed Version
    objects whose major component is implausibly large (>= 10000), catching
    any date-shaped value the regex somehow misses. Add `edge` to the
    existing floating-tag skiplist while we're here.
    
    Tests cover the alpine real-tag mix (release + date + edge), busybox
    style (mixed dotted releases + bare-major aliases), v-prefixed date
    tags, and date-with-revision-suffix variants.
---
 scripts/ci/prek/upgrade_important_versions.py      | 33 ++++++--
 .../ci/prek/test_upgrade_important_versions.py     | 94 ++++++++++++++++++++++
 2 files changed, 122 insertions(+), 5 deletions(-)

diff --git a/scripts/ci/prek/upgrade_important_versions.py 
b/scripts/ci/prek/upgrade_important_versions.py
index 64c3151cb4f..4c1798017ff 100755
--- a/scripts/ci/prek/upgrade_important_versions.py
+++ b/scripts/ci/prek/upgrade_important_versions.py
@@ -300,6 +300,14 @@ def get_latest_lts_node_version() -> str:
     return latest_version
 
 
+# Match date-shaped tags published by official images for daily / edge
+# builds, e.g. Alpine's `20260127` or `v20260127`. These parse as valid
+# PEP 440 versions and would otherwise sort above any normal release tag.
+# Pattern: optional leading `v`, then 8 digits (YYYYMMDD), optionally
+# followed by `.N` (revision suffix on the same date).
+_DATE_SHAPED_TAG_RE = re.compile(r"^v?\d{8}(\.\d+)?$")
+
+
 def get_latest_image_version(image: str) -> str:
     """
     Fetch the latest tag released for a DockerHub image.
@@ -340,17 +348,32 @@ def get_latest_image_version(image: str) -> str:
     version_tags = []
     for tag in tags:
         tag_name = tag["name"]
-        # Skip tags like 'latest', 'stable', '0', 'v0', etc.
-        if tag_name in ["latest", "stable", "main", "master", "0", "v0"]:
+        # Skip well-known floating tags.
+        if tag_name in ["latest", "stable", "main", "master", "0", "v0", 
"edge"]:
+            continue
+        # Skip date-shaped tags (`YYYYMMDD`, `YYYYMMDD.N`, `vYYYYMMDD`).
+        # Several official images publish daily / edge builds under
+        # date-stamped numeric tags (e.g. Alpine's `20260127`). PEP 440
+        # parses those as valid `Version("20260127")` and they sort higher
+        # than any normal release version like `3.23`, so the bumper would
+        # auto-pin the daily edge image instead of the latest release.
+        if _DATE_SHAPED_TAG_RE.match(tag_name):
             continue
         try:
-            # Try to parse as version to filter out non-version tags
-            # Remove leading 'v' if present
+            # Try to parse as version to filter out non-version tags.
+            # Remove leading 'v' if present.
             version_str = tag_name.lstrip("v")
             version_obj = Version(version_str)
+            # Belt-and-braces sanity check: any version whose major component
+            # is implausibly large (≥ 10000) is a date stamp the regex above
+            # missed, not a release. The largest legitimate image major
+            # version on Docker Hub today is in the low hundreds (e.g.
+            # `node:24`), so 10000 is a safe ceiling.
+            if version_obj.major >= 10000:
+                continue
             version_tags.append((version_obj, tag_name))
         except Exception:
-            # Skip tags that don't parse as versions
+            # Skip tags that don't parse as versions.
             continue
 
     if not version_tags:
diff --git a/scripts/tests/ci/prek/test_upgrade_important_versions.py 
b/scripts/tests/ci/prek/test_upgrade_important_versions.py
new file mode 100644
index 00000000000..af5e5ea7736
--- /dev/null
+++ b/scripts/tests/ci/prek/test_upgrade_important_versions.py
@@ -0,0 +1,94 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Unit tests for ``scripts/ci/prek/upgrade_important_versions.py``."""
+
+from __future__ import annotations
+
+from unittest import mock
+
+import pytest
+
+
[email protected]
+def fake_dockerhub_response(monkeypatch):
+    """Patch `requests.get` used by `get_latest_image_version` to return a 
fixed tag list."""
+
+    def _install(tag_names: list[str]) -> mock.MagicMock:
+        from ci.prek import upgrade_important_versions as uiv
+
+        response = mock.MagicMock()
+        response.raise_for_status = mock.MagicMock()
+        response.json = mock.MagicMock(return_value={"results": [{"name": n} 
for n in tag_names]})
+        get = mock.MagicMock(return_value=response)
+        monkeypatch.setattr(uiv.requests, "get", get)
+        return get
+
+    return _install
+
+
[email protected](
+    ("tags", "expected"),
+    [
+        pytest.param(
+            ["20260127", "3.23.5", "3.23", "3.22.0", "3", "latest", "edge"],
+            "3.23.5",
+            id="alpine-real-tag-mix-rejects-date-and-edge",
+        ),
+        pytest.param(
+            ["1.37.0", "1.37", "1", "1.36.1", "musl", "stable"],
+            "1.37.0",
+            id="busybox-style-tags",
+        ),
+        pytest.param(
+            ["v20260127", "v3.23.5"],
+            "v3.23.5",
+            id="v-prefixed-tags-with-date",
+        ),
+        pytest.param(
+            ["20260127.1", "3.23.5"],
+            "3.23.5",
+            id="date-with-revision-suffix-still-rejected",
+        ),
+    ],
+)
+def 
test_get_latest_image_version_rejects_date_shaped_tags(fake_dockerhub_response, 
tags, expected):
+    """A date-shaped daily-build tag must not be picked over a release tag.
+
+    Regression for the alpine `20260127` bump in apache/airflow#66580 — the
+    bumper used `packaging.version.Version` to sort tags, but PEP 440 happily
+    parses `20260127` as a single-component version that sorts above `3.23`,
+    so the script auto-pinned the daily edge image instead of the latest
+    release. The fix filters date-shaped tags before the version sort.
+    """
+    fake_dockerhub_response(tags)
+    from ci.prek.upgrade_important_versions import get_latest_image_version
+
+    assert get_latest_image_version("alpine") == expected
+
+
+def test_date_shaped_tag_regex_matches_only_date_stamps():
+    """The pre-filter regex matches date-stamped tags, not legitimate 
releases."""
+    from ci.prek.upgrade_important_versions import _DATE_SHAPED_TAG_RE
+
+    # Date-shaped tags — should match (and therefore be skipped).
+    for tag in ["20260127", "v20260127", "20260127.1", "v20260127.10"]:
+        assert _DATE_SHAPED_TAG_RE.match(tag) is not None, f"expected match 
for {tag!r}"
+
+    # Release tags — must not match.
+    for tag in ["3.23", "3.23.5", "1.37.0", "v1.37.0", "1", "3", "latest", 
"stable"]:
+        assert _DATE_SHAPED_TAG_RE.match(tag) is None, f"unexpected match for 
{tag!r}"

Reply via email to