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

potiuk pushed a commit to branch run-scripts-from-current-version
in repository https://gitbox.apache.org/repos/asf/airflow.git

commit ec76638689be8d38275888ed07888e4e2577faef
Author: Jarek Potiuk <[email protected]>
AuthorDate: Tue Feb 17 00:17:31 2026 +0100

    Publish docs to s3 uses current version of workflows/scripts
    
    In case we built docs for past version of airflow we checkout
    both - current (main) version of code to use latest environment
    and tag version of code to build the documentation. The workflow
    mixed sub-workflows and run them from the "tag" version of workflow
    rather than from current.
    
    We are moving SBOM building to the publishing job, and we are also
    pre-processing the generated docs to include the right stable
    versions - including Airflow 2 compatibility
---
 .github/workflows/publish-docs-to-s3.yml | 121 ++++++++--------
 scripts/ci/docs/store_stable_versions.py | 240 +++++++++++++++++++++++++++++++
 2 files changed, 300 insertions(+), 61 deletions(-)

diff --git a/.github/workflows/publish-docs-to-s3.yml 
b/.github/workflows/publish-docs-to-s3.yml
index a8db5f3a5a4..1a9d9ac2dab 100644
--- a/.github/workflows/publish-docs-to-s3.yml
+++ b/.github/workflows/publish-docs-to-s3.yml
@@ -182,6 +182,15 @@ jobs:
         with:
           persist-credentials: false
           path: current-version
+      - name: "Free up disk space"
+        shell: bash
+        run: ./current-version/scripts/tools/free_up_disk_space.sh
+      - name: "Make /mnt writeable"
+        run: ./current-version/scripts/ci/make_mnt_writeable.sh
+      - name: "Move docker to /mnt"
+        run: ./current-version/scripts/ci/move_docker_to_mnt.sh
+      - name: "Copy the version retrieval script"
+        run: cp ./current-version/scripts/ci/docs/store_stable_versions.py 
/tmp/store_stable_versions.py
       # We are checking repo for both - breeze and docs from the ref provided 
as input
       # This will take longer as we need to rebuild CI image and it will not 
use cache
       # but it will build the CI image from the version of Airflow that is 
used to check out things
@@ -190,15 +199,7 @@ jobs:
         with:
           persist-credentials: false
           ref: ${{ inputs.ref }}
-          fetch-depth: 0
           fetch-tags: true
-      - name: "Free up disk space"
-        shell: bash
-        run: ./scripts/tools/free_up_disk_space.sh
-      - name: "Make /mnt writeable"
-        run: ./scripts/ci/make_mnt_writeable.sh
-      - name: "Move docker to /mnt"
-        run: ./scripts/ci/move_docker_to_mnt.sh
       - name: "Apply patch commits if provided"
         run: |
           if [[ "${APPLY_COMMITS}" != "" ]]; then
@@ -242,63 +243,24 @@ jobs:
           INCLUDE_COMMITS: ${{ startsWith(inputs.ref, 'providers') && 'true' 
|| 'false' }}
         run: >
           breeze build-docs ${INCLUDE_DOCS} --docs-only
-      - name: "Checkout current version to run SBOM generation"
-        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # 
v4.2.2
-        with:
-          persist-credentials: false
-          fetch-depth: 0
-          fetch-tags: true
-          path: current-version
-        if: inputs.build-sboms
-      - name: "Reinstall breeze from the current version"
-        run: |
-          breeze setup self-upgrade --use-current-airflow-sources
-        if: inputs.build-sboms
-        working-directory: current-version
-      - name: "Make sure SBOM dir exists and has the right permissions"
-        run: |
-          sudo mkdir -vp ./files/sbom
-          sudo chown -R "${USER}" .
-        working-directory: current-version
-        if: inputs.build-sboms
-      - name: "Prepare SBOMs using current version of Breeze"
-        env:
-          AIRFLOW_VERSION: ${{ needs.build-info.outputs.airflow-version }}
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          PYTHON_VERSION: "${{ needs.build-info.outputs.default-python-version 
}}"
-          FORCE: "true"
-        run: >
-          breeze sbom update-sbom-information
-          --airflow-version ${AIRFLOW_VERSION} --remote-name origin --force
-          --all-combinations --run-in-parallel --airflow-root-path 
"${GITHUB_WORKSPACE}"
-        working-directory: current-version
-        if: inputs.build-sboms
-      - name: "Generated SBOM files"
-        run: |
-          echo "Generated SBOM files:"
-          find ./generated/_build/docs/apache-airflow/stable/sbom/ -type f | 
sort
-        if: inputs.build-sboms
-      - name: "Reinstall breeze from ${{ inputs.ref }} reference"
-        run:
-          breeze setup self-upgrade --use-current-airflow-sources
-        if: inputs.build-sboms
-      - name: Check disk space available
-        run: df -H
-      # Here we will create temp airflow-site dir to publish docs
-      - name: Create /mnt/airflow-site directory
+      - name: "Store stable versions"
+        run: uv run /tmp/store_stable_versions.py
+      - name: "Saving build docs folder"
         run: |
-          sudo mkdir -p /mnt/airflow-site && sudo chown -R "${USER}" 
/mnt/airflow-site
-          echo "AIRFLOW_SITE_DIRECTORY=/mnt/airflow-site/" >> "$GITHUB_ENV"
-      - name: "Publish docs to /mnt/airflow-site directory using ${{ 
inputs.ref }} reference breeze"
-        env:
-          INCLUDE_DOCS: ${{ needs.build-info.outputs.include-docs }}
-        run: >
-          breeze release-management publish-docs --override-versioned 
--run-in-parallel ${INCLUDE_DOCS}
+          if [ -d "generated/_build/" ]; then
+            mv "generated/_build/" "/mnt/_build"
+          elif [ -d "docs/_build/" ]; then
+            # handle Airflow 2 case
+            mv "docs/_build/" "/mnt/_build"
+          else
+            echo "No build docs found to save"
+            exit 1
+          fi
       - name: "Upload build docs"
         uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 
 # v4.6.2
         with:
           name: airflow-docs
-          path: /mnt/airflow-site
+          path: /mnt/_build
           retention-days: '7'
           if-no-files-found: 'error'
           overwrite: 'true'
@@ -324,10 +286,12 @@ jobs:
         # We are checking repo for both - breeze and docs from the "workflow' 
branch
         # This will take longer as we need to rebuild CI image and it will not 
use cache
         # but it will build the CI image from the version of Airflow that is 
used to check out things
+        # We also fetch the whole history to be able to prepare SBOM files
       - name: "Checkout ${{ inputs.ref }} "
         uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # 
v4.2.2
         with:
           persist-credentials: false
+          fetch-depth: 0
       - name: "Make /mnt writeable and cleanup"
         shell: bash
         run: ./scripts/ci/make_mnt_writeable.sh
@@ -337,7 +301,42 @@ jobs:
         uses: 
actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093  # v4.3.0
         with:
           name: airflow-docs
-          path: /mnt/airflow-site
+          path: /mnt/_build
+      - name: "Move doscs to generated folder"
+        run: mv /mnt/_build generated/_build
+      - name: "Make sure SBOM dir exists and has the right permissions"
+        run: |
+          sudo mkdir -vp ./files/sbom
+          sudo chown -R "${USER}" .
+        if: inputs.build-sboms
+      - name: "Prepare SBOMs"
+        env:
+          AIRFLOW_VERSION: ${{ needs.build-info.outputs.airflow-version }}
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          PYTHON_VERSION: "${{ needs.build-info.outputs.default-python-version 
}}"
+          FORCE: "true"
+        run: >
+          breeze sbom update-sbom-information
+          --airflow-version ${AIRFLOW_VERSION} --remote-name origin --force
+          --all-combinations --run-in-parallel --airflow-root-path 
"${GITHUB_WORKSPACE}"
+        if: inputs.build-sboms
+      - name: "Generated SBOM files"
+        run: |
+          echo "Generated SBOM files:"
+          find ./generated/_build/docs/apache-airflow/stable/sbom/ -type f | 
sort
+        if: inputs.build-sboms
+      - name: Check disk space available
+        run: df -H
+      # Here we will create temp airflow-site dir to publish docs
+      - name: Create /mnt/airflow-site directory
+        run: |
+          sudo mkdir -p /mnt/airflow-site && sudo chown -R "${USER}" 
/mnt/airflow-site
+          echo "AIRFLOW_SITE_DIRECTORY=/mnt/airflow-site/" >> "$GITHUB_ENV"
+      - name: "Publish docs to /mnt/airflow-site directory using ${{ 
inputs.ref }} reference breeze"
+        env:
+          INCLUDE_DOCS: ${{ needs.build-info.outputs.include-docs }}
+        run: >
+          breeze release-management publish-docs --override-versioned 
--run-in-parallel ${INCLUDE_DOCS}
       - name: Check disk space available
         run: df -H
       - name: "Update watermarks"
diff --git a/scripts/ci/docs/store_stable_versions.py 
b/scripts/ci/docs/store_stable_versions.py
new file mode 100755
index 00000000000..6579d614dfe
--- /dev/null
+++ b/scripts/ci/docs/store_stable_versions.py
@@ -0,0 +1,240 @@
+#!/usr/bin/env python3
+# 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.
+# /// script
+# requires-python = ">=3.8"
+# dependencies = [
+#     "pyyaml>=6.0",
+# ]
+# ///
+
+"""
+This script retrieves versions from versioned doc packages built and stores 
them in stable.txt files.
+
+It should be run after building docs but before saving/uploading the build 
artifacts.
+"""
+
+from __future__ import annotations
+
+import os
+import re
+import shutil
+import sys
+from pathlib import Path
+
+import yaml
+
+
+def get_airflow_version(airflow_root: Path) -> str | None:
+    """Get Airflow version from airflow/__init__.py."""
+    # Try Airflow 3.x location first
+    init_file = airflow_root / "airflow-core" / "src" / "airflow" / 
"__init__.py"
+    if not init_file.exists():
+        # Fallback to Airflow 2.x location
+        init_file = airflow_root / "airflow" / "__init__.py"
+
+    if not init_file.exists():
+        return None
+
+    content = init_file.read_text()
+    match = re.search(r'^__version__\s*=\s*["\']([^"\']+)["\']', content, 
re.MULTILINE)
+    if match:
+        return match.group(1)
+    return None
+
+
+def get_version_from_provider_yaml(provider_yaml_path: Path) -> str | None:
+    """Get version from provider.yaml file (first version in the versions 
list)."""
+    if not provider_yaml_path.exists():
+        return None
+
+    try:
+        with open(provider_yaml_path) as f:
+            data = yaml.safe_load(f)
+            if "versions" in data and len(data["versions"]) > 0:
+                # versions is a list of version strings, get the first one
+                return str(data["versions"][0])
+    except Exception:
+        pass
+    return None
+
+
+def get_version_from_pyproject_toml(pyproject_path: Path) -> str | None:
+    """Get version from pyproject.toml file."""
+    if not pyproject_path.exists():
+        return None
+
+    content = pyproject_path.read_text()
+    match = re.search(r'^version\s*=\s*["\']([^"\']+)["\']', content, 
re.MULTILINE)
+    if match:
+        return match.group(1)
+    return None
+
+
+def get_helm_chart_version(chart_yaml_path: Path) -> str | None:
+    """Get version from Chart.yaml file."""
+    if not chart_yaml_path.exists():
+        return None
+
+    content = chart_yaml_path.read_text()
+    match = re.search(r"^version:\s*(.+)$", content, re.MULTILINE)
+    if match:
+        return match.group(1).strip()
+    return None
+
+
+def get_package_version(package_name: str, airflow_root: Path) -> str | None:
+    """Get version for a package based on its type and metadata location."""
+    if package_name == "apache-airflow":
+        return get_airflow_version(airflow_root)
+
+    if package_name == "apache-airflow-ctl":
+        # Try provider.yaml first
+        provider_yaml = airflow_root / "airflow-ctl" / "src" / "airflow_ctl" / 
"provider.yaml"
+        version = get_version_from_provider_yaml(provider_yaml)
+        if version:
+            return version
+        # Fallback to pyproject.toml
+        pyproject = airflow_root / "airflow-ctl" / "pyproject.toml"
+        return get_version_from_pyproject_toml(pyproject)
+
+    if package_name == "task-sdk":
+        # Try provider.yaml first
+        provider_yaml = airflow_root / "task-sdk" / "src" / "task_sdk" / 
"provider.yaml"
+        version = get_version_from_provider_yaml(provider_yaml)
+        if version:
+            return version
+        # Fallback to pyproject.toml
+        pyproject = airflow_root / "task-sdk" / "pyproject.toml"
+        return get_version_from_pyproject_toml(pyproject)
+
+    if package_name == "helm-chart":
+        chart_yaml = airflow_root / "chart" / "Chart.yaml"
+        return get_helm_chart_version(chart_yaml)
+
+    if package_name.startswith("apache-airflow-providers-"):
+        # Get provider version from provider.yaml
+        provider_short_name = 
package_name.replace("apache-airflow-providers-", "").replace("-", "/")
+
+        # Try Airflow 3.x location first (providers/{provider}/provider.yaml)
+        provider_yaml = airflow_root / "providers" / provider_short_name / 
"provider.yaml"
+        version = get_version_from_provider_yaml(provider_yaml)
+        if version:
+            return version
+
+        # Fallback to Airflow 2.x location 
(airflow/providers/{provider}/provider.yaml)
+        provider_yaml = airflow_root / "airflow" / "providers" / 
provider_short_name / "provider.yaml"
+        return get_version_from_provider_yaml(provider_yaml)
+
+    print(f"Unknown package type: {package_name}")
+    return None
+
+
+def main() -> int:
+    """Main function to process all documentation packages."""
+    # Get configuration from environment or defaults
+    docs_build_dir = Path(os.environ.get("DOCS_BUILD_DIR", 
"generated/_build/docs"))
+    airflow_root = Path(os.environ.get("AIRFLOW_ROOT", os.getcwd()))
+
+    # Change to airflow root directory
+    os.chdir(airflow_root)
+
+    print("=" * 42)
+    print("Storing stable versions for built docs")
+    print("=" * 42)
+
+    # Check if docs build directory exists
+    if not docs_build_dir.exists():
+        print(f"Error: Docs build directory not found at {docs_build_dir}")
+        # Try alternate location for Airflow 2 compatibility
+        alt_docs_dir = Path("docs/_build/docs")
+        if alt_docs_dir.exists():
+            docs_build_dir = alt_docs_dir
+            print(f"Found alternate location at {docs_build_dir}")
+        else:
+            print("No docs build directory found, exiting")
+            return 1
+
+    # Non-versioned packages to skip
+    non_versioned_packages = {"apache-airflow-providers", "docker-stack"}
+
+    stable_files_created = []
+
+    # Process each package in the docs build directory
+    for package_dir in sorted(docs_build_dir.iterdir()):
+        if not package_dir.is_dir():
+            continue
+
+        package_name = package_dir.name
+
+        # Skip non-versioned packages
+        if package_name in non_versioned_packages:
+            print(f"Skipping non-versioned package: {package_name}")
+            continue
+
+        # Check if this package has a stable directory (indicating it's 
versioned)
+        stable_dir = package_dir / "stable"
+        if not stable_dir.exists() or not stable_dir.is_dir():
+            print(f"Skipping non-versioned package (no stable dir): 
{package_name}")
+            continue
+
+        print(f"Processing versioned package: {package_name}")
+
+        # Get the version for this package
+        version = get_package_version(package_name, airflow_root)
+
+        if not version:
+            print(f"  Warning: Could not determine version for {package_name}, 
skipping")
+            continue
+
+        print(f"  Version: {version}")
+
+        # Create stable.txt file
+        stable_file = package_dir / "stable.txt"
+        stable_file.write_text(version + "\n")
+        print(f"  Created: {stable_file}")
+        stable_files_created.append((package_name, version))
+
+        # Also create a version-specific copy of the stable docs
+        version_dir = package_dir / version
+        if not version_dir.exists():
+            print(f"  Copying stable docs to versioned directory: 
{version_dir}")
+            shutil.copytree(stable_dir, version_dir)
+        else:
+            print(f"  Version directory already exists: {version_dir}")
+
+    print()
+    print("=" * 42)
+    print("Stable version files created successfully")
+    print("=" * 42)
+    print()
+
+    if stable_files_created:
+        print("Summary of stable.txt files:")
+        for package_name, version in stable_files_created:
+            print(f"  {package_name}: {version}")
+    else:
+        print("No stable.txt files created")
+
+    print()
+    print("Done!")
+
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())

Reply via email to