CI

---
name: CI

on:
  push:
    branches: [master]
  pull_request:

# If another push to the same PR or branch happens while this workflow is still running,
# cancel the earlier run in favor of the next run.
#
# There's no point in testing an outdated version of the code. GitHub only allows
# a limited number of job runners to be active at the same time, so it's better to cancel
# pointless jobs early so that more useful jobs can run sooner.
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  POETRY_VERSION: "1.7.1"

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - id: files
        uses: Ana06/get-changed-files@v2.2.0
      - id: set-matrix
        run: |
          python .github/scripts/check_diff.py ${{ steps.files.outputs.all }} >> $GITHUB_OUTPUT
    outputs:
      dirs-to-lint: ${{ steps.set-matrix.outputs.dirs-to-lint }}
      dirs-to-test: ${{ steps.set-matrix.outputs.dirs-to-test }}
      dirs-to-extended-test: ${{ steps.set-matrix.outputs.dirs-to-extended-test }}
      docs-edited: ${{ steps.set-matrix.outputs.docs-edited }}
  lint:
    name: cd ${{ matrix.working-directory }}
    needs: [ build ]
    if: ${{ needs.build.outputs.dirs-to-lint != '[]' }}
    strategy:
      matrix:
        working-directory: ${{ fromJson(needs.build.outputs.dirs-to-lint) }}
    uses: ./.github/workflows/_lint.yml
    with:
      working-directory: ${{ matrix.working-directory }}
    secrets: inherit

  test:
    name: cd ${{ matrix.working-directory }}
    needs: [ build ]
    if: ${{ needs.build.outputs.dirs-to-test != '[]' }}
    strategy:
      matrix:
        working-directory: ${{ fromJson(needs.build.outputs.dirs-to-test) }}
    uses: ./.github/workflows/_test.yml
    with:
      working-directory: ${{ matrix.working-directory }}
    secrets: inherit

  test-doc-imports:
    needs: [ build ]
    if: ${{ needs.build.outputs.dirs-to-test != '[]' || needs.build.outputs.docs-edited }}
    uses: ./.github/workflows/_test_doc_imports.yml
    secrets: inherit

  compile-integration-tests:
    name: cd ${{ matrix.working-directory }}
    needs: [ build ]
    if: ${{ needs.build.outputs.dirs-to-test != '[]' }}
    strategy:
      matrix:
        working-directory: ${{ fromJson(needs.build.outputs.dirs-to-test) }}
    uses: ./.github/workflows/_compile_integration_test.yml
    with:
      working-directory: ${{ matrix.working-directory }}
    secrets: inherit

  dependencies:
    name: cd ${{ matrix.working-directory }}
    needs: [ build ]
    if: ${{ needs.build.outputs.dirs-to-test != '[]' }}
    strategy:
      matrix:
        working-directory: ${{ fromJson(needs.build.outputs.dirs-to-test) }}
    uses: ./.github/workflows/_dependencies.yml
    with:
      working-directory: ${{ matrix.working-directory }}
    secrets: inherit

  extended-tests:
    name: "cd ${{ matrix.working-directory }} / make extended_tests #${{ matrix.python-version }}"
    needs: [ build ]
    if: ${{ needs.build.outputs.dirs-to-extended-test != '[]' }}
    strategy:
      matrix:
        # note different variable for extended test dirs
        working-directory: ${{ fromJson(needs.build.outputs.dirs-to-extended-test) }}
        python-version:
          - "3.8"
          - "3.9"
          - "3.10"
          - "3.11"
          - "3.12"
    runs-on: ubuntu-latest
    defaults:
      run:
        working-directory: ${{ matrix.working-directory }}
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }}
        uses: "./.github/actions/poetry_setup"
        with:
          python-version: ${{ matrix.python-version }}
          poetry-version: ${{ env.POETRY_VERSION }}
          working-directory: ${{ matrix.working-directory }}
          cache-key: extended

      - name: Install dependencies
        shell: bash
        run: |
          echo "Running extended tests, installing dependencies with poetry..."
          poetry install --with test
          poetry run pip install uv
          poetry run uv pip install -r extended_testing_deps.txt

      - name: Run extended tests
        run: make extended_tests

      - name: Ensure the tests did not create any additional files
        shell: bash
        run: |
          set -eu

          STATUS="$(git status)"
          echo "$STATUS"

          # grep will exit non-zero if the target message isn't found,
          # and `set -e` above will cause the step to fail.
          echo "$STATUS" | grep 'nothing to commit, working tree clean'
  ci_success:
    name: "CI Success"
    needs: [build, lint, test, compile-integration-tests, dependencies, extended-tests, test-doc-imports]
    if: |
      always()
    runs-on: ubuntu-latest
    env:
      JOBS_JSON: ${{ toJSON(needs) }}
      RESULTS_JSON: ${{ toJSON(needs.*.result) }}
      EXIT_CODE: ${{!contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') && '0' || '1'}}
    steps:
      - name: "CI Success"
        run: |
          echo $JOBS_JSON
          echo $RESULTS_JSON
          echo "Exiting with $EXIT_CODE"
          exit $EXIT_CODE


dependencies

name: dependencies

on:
  workflow_call:
    inputs:
      working-directory:
        required: true
        type: string
        description: "From which folder this pipeline executes"
      langchain-location:
        required: false
        type: string
        description: "Relative path to the langchain library folder"

env:
  POETRY_VERSION: "1.7.1"

jobs:
  build:
    defaults:
      run:
        working-directory: ${{ inputs.working-directory }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version:
          - "3.8"
          - "3.9"
          - "3.10"
          - "3.11"
          - "3.12"
    name: dependency checks ${{ matrix.python-version }}
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }}
        uses: "./.github/actions/poetry_setup"
        with:
          python-version: ${{ matrix.python-version }}
          poetry-version: ${{ env.POETRY_VERSION }}
          working-directory: ${{ inputs.working-directory }}
          cache-key: pydantic-cross-compat

      - name: Install dependencies
        shell: bash
        run: poetry install

      - name: Check imports with base dependencies
        shell: bash
        run: poetry run make check_imports

      - name: Install test dependencies
        shell: bash
        run: poetry install --with test

      - name: Install langchain editable
        working-directory: ${{ inputs.working-directory }}
        if: ${{ inputs.langchain-location }}
        env:
          LANGCHAIN_LOCATION: ${{ inputs.langchain-location }}
        run: |
          poetry run pip install -e "$LANGCHAIN_LOCATION"

      - name: Install the opposite major version of pydantic
        # If normal tests use pydantic v1, here we'll use v2, and vice versa.
        shell: bash
        # airbyte currently doesn't support pydantic v2
        if: ${{ !startsWith(inputs.working-directory, 'libs/partners/airbyte') }}
        run: |
          # Determine the major part of pydantic version
          REGULAR_VERSION=$(poetry run python -c "import pydantic; print(pydantic.__version__)" | cut -d. -f1)

          if [[ "$REGULAR_VERSION" == "1" ]]; then
            PYDANTIC_DEP=">=2.1,<3"
            TEST_WITH_VERSION="2"
          elif [[ "$REGULAR_VERSION" == "2" ]]; then
            PYDANTIC_DEP="<2"
            TEST_WITH_VERSION="1"
          else
            echo "Unexpected pydantic major version '$REGULAR_VERSION', cannot determine which version to use for cross-compatibility test."
            exit 1
          fi

          # Install via `pip` instead of `poetry add` to avoid changing lockfile,
          # which would prevent caching from working: the cache would get saved
          # to a different key than where it gets loaded from.
          poetry run pip install "pydantic${PYDANTIC_DEP}"

          # Ensure that the correct pydantic is installed now.
          echo "Checking pydantic version... Expecting ${TEST_WITH_VERSION}"

          # Determine the major part of pydantic version
          CURRENT_VERSION=$(poetry run python -c "import pydantic; print(pydantic.__version__)" | cut -d. -f1)

          # Check that the major part of pydantic version is as expected, if not
          # raise an error
          if [[ "$CURRENT_VERSION" != "$TEST_WITH_VERSION" ]]; then
            echo "Error: expected pydantic version ${CURRENT_VERSION} to have been installed, but found: ${TEST_WITH_VERSION}"
            exit 1
          fi
          echo "Found pydantic version ${CURRENT_VERSION}, as expected"
      - name: Run pydantic compatibility tests
        # airbyte currently doesn't support pydantic v2
        if: ${{ !startsWith(inputs.working-directory, 'libs/partners/airbyte') }}
        shell: bash
        run: make test

      - name: Ensure the tests did not create any additional files
        shell: bash
        run: |
          set -eu

          STATUS="$(git status)"
          echo "$STATUS"

          # grep will exit non-zero if the target message isn't found,
          # and `set -e` above will cause the step to fail.
          echo "$STATUS" | grep 'nothing to commit, working tree clean'


Integration tests

name: Integration tests

on:
  workflow_dispatch:
    inputs:
      working-directory:
        required: true
        type: string

env:
  POETRY_VERSION: "1.7.1"

jobs:
  build:
    defaults:
      run:
        working-directory: ${{ inputs.working-directory }}
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version:
          - "3.8"
          - "3.11"
    name: Python ${{ matrix.python-version }}
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }}
        uses: "./.github/actions/poetry_setup"
        with:
          python-version: ${{ matrix.python-version }}
          poetry-version: ${{ env.POETRY_VERSION }}
          working-directory: ${{ inputs.working-directory }}
          cache-key: core

      - name: Install dependencies
        shell: bash
        run: poetry install --with test,test_integration

      - name: Install deps outside pyproject
        if: ${{ startsWith(inputs.working-directory, 'libs/community/') }}
        shell: bash
        run: poetry run pip install "boto3<2" "google-cloud-aiplatform<2"

      - name: 'Authenticate to Google Cloud'
        id: 'auth'
        uses: google-github-actions/auth@v2
        with:
          credentials_json: '${{ secrets.GOOGLE_CREDENTIALS }}'

      - name: Run integration tests
        shell: bash
        env:
          AI21_API_KEY: ${{ secrets.AI21_API_KEY }}
          FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
          GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          AZURE_OPENAI_API_VERSION: ${{ secrets.AZURE_OPENAI_API_VERSION }}
          AZURE_OPENAI_API_BASE: ${{ secrets.AZURE_OPENAI_API_BASE }}
          AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }}
          AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_CHAT_DEPLOYMENT_NAME }}
          AZURE_OPENAI_LLM_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_LLM_DEPLOYMENT_NAME }}
          AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME: ${{ secrets.AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME }}
          MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
          TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
          NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
          GOOGLE_SEARCH_API_KEY: ${{ secrets.GOOGLE_SEARCH_API_KEY }}
          GOOGLE_CSE_ID: ${{ secrets.GOOGLE_CSE_ID }}
          EXA_API_KEY: ${{ secrets.EXA_API_KEY }}
          NOMIC_API_KEY: ${{ secrets.NOMIC_API_KEY }}
          WATSONX_APIKEY: ${{ secrets.WATSONX_APIKEY }}
          WATSONX_PROJECT_ID: ${{ secrets.WATSONX_PROJECT_ID }}
          PINECONE_API_KEY: ${{ secrets.PINECONE_API_KEY }}
          PINECONE_ENVIRONMENT: ${{ secrets.PINECONE_ENVIRONMENT }}
          ASTRA_DB_API_ENDPOINT: ${{ secrets.ASTRA_DB_API_ENDPOINT }}
          ASTRA_DB_APPLICATION_TOKEN: ${{ secrets.ASTRA_DB_APPLICATION_TOKEN }}
          ASTRA_DB_KEYSPACE: ${{ secrets.ASTRA_DB_KEYSPACE }}
          ES_URL: ${{ secrets.ES_URL }}
          ES_CLOUD_ID: ${{ secrets.ES_CLOUD_ID }}
          ES_API_KEY: ${{ secrets.ES_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # for airbyte
          MONGODB_ATLAS_URI: ${{ secrets.MONGODB_ATLAS_URI }}
          VOYAGE_API_KEY: ${{ secrets.VOYAGE_API_KEY }}
          COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
          UPSTAGE_API_KEY: ${{ secrets.UPSTAGE_API_KEY }}
        run: |
          make integration_tests

      - name: Ensure the tests did not create any additional files
        shell: bash
        run: |
          set -eu

          STATUS="$(git status)"
          echo "$STATUS"

          # grep will exit non-zero if the target message isn't found,
          # and `set -e` above will cause the step to fail.
          echo "$STATUS" | grep 'nothing to commit, working tree clean'


LangChain People

name: LangChain People

on:
  schedule:
    - cron: "0 14 1 * *"
  push:
    branches: [jacob/people]
  workflow_dispatch:
    inputs:
      debug_enabled:
        description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
        required: false
        default: 'false'

jobs:
  langchain-people:
    if: github.repository_owner == 'langchain-ai'
    runs-on: ubuntu-latest
    steps:
      - name: Dump GitHub context
        env:
          GITHUB_CONTEXT: ${{ toJson(github) }}
        run: echo "$GITHUB_CONTEXT"
      - uses: actions/checkout@v4
      # Ref: https://github.com/actions/runner/issues/2033
      - name: Fix git safe.directory in container
        run: mkdir -p /home/runner/work/_temp/_github_home && printf "[safe]\n\tdirectory = /github/workspace" > /home/runner/work/_temp/_github_home/.gitconfig
      # Allow debugging with tmate
      - name: Setup tmate session
        uses: mxschmitt/action-tmate@v3
        if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }}
        with:
          limit-access-to-actor: true
      - uses: ./.github/actions/people
        with:
          token: ${{ secrets.LANGCHAIN_PEOPLE_GITHUB_TOKEN }}


name: test_doc_imports

on:
  workflow_call:

env:
  POETRY_VERSION: "1.7.1"

jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version:
          - "3.12"
    name: "check doc imports #${{ matrix.python-version }}"
    steps:
      - uses: actions/checkout@v4

      - name: Set up Python ${{ matrix.python-version }} + Poetry ${{ env.POETRY_VERSION }}
        uses: "./.github/actions/poetry_setup"
        with:
          python-version: ${{ matrix.python-version }}
          poetry-version: ${{ env.POETRY_VERSION }}
          cache-key: core

      - name: Install dependencies
        shell: bash
        run: poetry install --with test

      - name: Install langchain editable
        run: |
          poetry run pip install -e libs/core libs/langchain libs/community libs/experimental

      - name: Check doc imports
        shell: bash
        run: |
          poetry run python docs/scripts/check_imports.py

      - name: Ensure the test did not create any additional files
        shell: bash
        run: |
          set -eu

          STATUS="$(git status)"
          echo "$STATUS"

          # grep will exit non-zero if the target message isn't found,
          # and `set -e` above will cause the step to fail.
          echo "$STATUS" | grep 'nothing to commit, working tree clean'


Integration docs lint

---
name: Integration docs lint

on:
  push:
    branches: [master]
  pull_request:

# If another push to the same PR or branch happens while this workflow is still running,
# cancel the earlier run in favor of the next run.
#
# There's no point in testing an outdated version of the code. GitHub only allows
# a limited number of job runners to be active at the same time, so it's better to cancel
# pointless jobs early so that more useful jobs can run sooner.
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.10'
      - id: files
        uses: Ana06/get-changed-files@v2.2.0
      - name: Check new docs
        run: |
          python docs/scripts/check_templates.py ${{ steps.files.outputs.added }}


Check Broken Links

name: Check Broken Links

on:
  workflow_dispatch:
  schedule:
    - cron:  '0 13 * * *'

jobs:
  check-links:
    if: github.repository_owner == 'langchain-ai'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Use Node.js 18.x
        uses: actions/setup-node@v3
        with:
          node-version: 18.x
          cache: "yarn"
          cache-dependency-path: ./docs/yarn.lock
      - name: Install dependencies
        run: yarn install --immutable --mode=skip-build
        working-directory: ./docs
      - name: Check broken links
        run: yarn check-broken-links
        working-directory: ./docs


Tests

# 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.
#
---
name: Tests
on:  # yamllint disable-line rule:truthy
  schedule:
    - cron: '28 0 * * *'
  push:
    branches: ['main', 'v[0-9]+-[0-9]+-test']
  pull_request:
    branches: ['main']
permissions:
  # All other permissions are set to none
  contents: read
  # Technically read access while waiting for images should be more than enough. However,
  # there is a bug in GitHub Actions/Packages and in case private repositories are used, you get a permission
  # denied error when attempting to just pull private image, changing the token permission to write solves the
  # issue. This is not dangerous, because if it is for "apache/airflow", only maintainers can push ci.yml
  # changes. If it is for a fork, then the token is read-only anyway.
  packages: write
env:
  GITHUB_REPOSITORY: ${{ github.repository }}
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  GITHUB_USERNAME: ${{ github.actor }}
  IMAGE_TAG: "${{ github.event.pull_request.head.sha || github.sha }}"
  VERBOSE: "true"

concurrency:
  group: ci-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:

  build-info:
    name: "Build info"
    # At build-info stage we do not yet have outputs so we need to hard-code the runs-on to public runners
    runs-on: ["ubuntu-22.04"]
    env:
      GITHUB_CONTEXT: ${{ toJson(github) }}
    outputs:
      image-tag: ${{ github.event.pull_request.head.sha || github.sha }}
      docker-cache: ${{ steps.selective-checks.outputs.docker-cache }}
      affected-providers-list-as-string: >-
        ${{ steps.selective-checks.outputs.affected-providers-list-as-string }}
      upgrade-to-newer-dependencies: ${{ steps.selective-checks.outputs.upgrade-to-newer-dependencies }}
      python-versions: ${{ steps.selective-checks.outputs.python-versions }}
      python-versions-list-as-string: ${{ steps.selective-checks.outputs.python-versions-list-as-string }}
      all-python-versions-list-as-string: >-
        ${{ steps.selective-checks.outputs.all-python-versions-list-as-string }}
      default-python-version: ${{ steps.selective-checks.outputs.default-python-version }}
      kubernetes-versions-list-as-string: >-
        ${{ steps.selective-checks.outputs.kubernetes-versions-list-as-string }}
      kubernetes-combos-list-as-string: >-
        ${{ steps.selective-checks.outputs.kubernetes-combos-list-as-string }}
      default-kubernetes-version: ${{ steps.selective-checks.outputs.default-kubernetes-version }}
      postgres-versions: ${{ steps.selective-checks.outputs.postgres-versions }}
      default-postgres-version: ${{ steps.selective-checks.outputs.default-postgres-version }}
      mysql-versions: ${{ steps.selective-checks.outputs.mysql-versions }}
      default-mysql-version: ${{ steps.selective-checks.outputs.default-mysql-version }}
      default-helm-version: ${{ steps.selective-checks.outputs.default-helm-version }}
      default-kind-version: ${{ steps.selective-checks.outputs.default-kind-version }}
      full-tests-needed: ${{ steps.selective-checks.outputs.full-tests-needed }}
      parallel-test-types-list-as-string: >-
        ${{ steps.selective-checks.outputs.parallel-test-types-list-as-string }}
      providers-test-types-list-as-string: >-
        ${{ steps.selective-checks.outputs.providers-test-types-list-as-string }}
      separate-test-types-list-as-string: >-
        ${{ steps.selective-checks.outputs.separate-test-types-list-as-string }}
      include-success-outputs: ${{ steps.selective-checks.outputs.include-success-outputs }}
      postgres-exclude: ${{ steps.selective-checks.outputs.postgres-exclude }}
      mysql-exclude: ${{ steps.selective-checks.outputs.mysql-exclude }}
      sqlite-exclude: ${{ steps.selective-checks.outputs.sqlite-exclude }}
      skip-provider-tests: ${{ steps.selective-checks.outputs.skip-provider-tests }}
      run-tests: ${{ steps.selective-checks.outputs.run-tests }}
      run-amazon-tests: ${{ steps.selective-checks.outputs.run-amazon-tests }}
      run-www-tests: ${{ steps.selective-checks.outputs.run-www-tests }}
      run-kubernetes-tests: ${{ steps.selective-checks.outputs.run-kubernetes-tests }}
      basic-checks-only: ${{ steps.selective-checks.outputs.basic-checks-only }}
      ci-image-build: ${{ steps.selective-checks.outputs.ci-image-build }}
      prod-image-build: ${{ steps.selective-checks.outputs.prod-image-build }}
      docs-build: ${{ steps.selective-checks.outputs.docs-build }}
      mypy-folders: ${{ steps.selective-checks.outputs.mypy-folders }}
      needs-mypy: ${{ steps.selective-checks.outputs.needs-mypy }}
      needs-helm-tests: ${{ steps.selective-checks.outputs.needs-helm-tests }}
      needs-api-tests: ${{ steps.selective-checks.outputs.needs-api-tests }}
      needs-api-codegen: ${{ steps.selective-checks.outputs.needs-api-codegen }}
      default-branch: ${{ steps.selective-checks.outputs.default-branch }}
      default-constraints-branch: ${{ steps.selective-checks.outputs.default-constraints-branch }}
      docs-list-as-string: ${{ steps.selective-checks.outputs.docs-list-as-string }}
      skip-pre-commits: ${{ steps.selective-checks.outputs.skip-pre-commits }}
      providers-compatibility-checks: ${{ steps.selective-checks.outputs.providers-compatibility-checks }}
      helm-test-packages: ${{ steps.selective-checks.outputs.helm-test-packages }}
      debug-resources: ${{ steps.selective-checks.outputs.debug-resources }}
      runs-on-as-json-default: ${{ steps.selective-checks.outputs.runs-on-as-json-default }}
      runs-on-as-json-public: ${{ steps.selective-checks.outputs.runs-on-as-json-public }}
      runs-on-as-json-self-hosted: ${{ steps.selective-checks.outputs.runs-on-as-json-self-hosted }}
      is-self-hosted-runner: ${{ steps.selective-checks.outputs.is-self-hosted-runner }}
      is-airflow-runner: ${{ steps.selective-checks.outputs.is-airflow-runner }}
      is-amd-runner: ${{ steps.selective-checks.outputs.is-amd-runner }}
      is-arm-runner: ${{ steps.selective-checks.outputs.is-arm-runner }}
      is-vm-runner: ${{ steps.selective-checks.outputs.is-vm-runner }}
      is-k8s-runner: ${{ steps.selective-checks.outputs.is-k8s-runner }}
      latest-versions-only: ${{ steps.selective-checks.outputs.latest-versions-only }}
      chicken-egg-providers: ${{ steps.selective-checks.outputs.chicken-egg-providers }}
      has-migrations: ${{ steps.selective-checks.outputs.has-migrations }}
      source-head-repo: ${{ steps.source-run-info.outputs.source-head-repo }}
      pull-request-labels: ${{ steps.source-run-info.outputs.pr-labels }}
      in-workflow-build: ${{ steps.source-run-info.outputs.in-workflow-build }}
      build-job-description: ${{ steps.source-run-info.outputs.build-job-description }}
      testable-integrations: ${{ steps.selective-checks.outputs.testable-integrations }}
      canary-run: ${{ steps.source-run-info.outputs.canary-run }}
      run-coverage: ${{ steps.source-run-info.outputs.run-coverage }}
    steps:
      - name: "Cleanup repo"
        shell: bash
        run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm -rf /workspace/*"
      - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
        uses: actions/checkout@v4
        with:
          persist-credentials: false
      - name: "Cleanup docker"
        run: ./scripts/ci/cleanup_docker.sh
      - name: Fetch incoming commit ${{ github.sha }} with its parent
        uses: actions/checkout@v4
        with:
          ref: ${{ github.sha }}
          fetch-depth: 2
          persist-credentials: false
      - name: "Install Breeze"
        uses: ./.github/actions/breeze
      - name: "Get information about the Workflow"
        id: source-run-info
        run: breeze ci get-workflow-info 2>> ${GITHUB_OUTPUT}
      - name: Selective checks
        id: selective-checks
        env:
          PR_LABELS: "${{ steps.source-run-info.outputs.pr-labels }}"
          COMMIT_REF: "${{ github.sha }}"
          VERBOSE: "false"
        run: breeze ci selective-check 2>> ${GITHUB_OUTPUT}
      - name: env
        run: printenv
        env:
          PR_LABELS: ${{ steps.source-run-info.outputs.pr-labels }}
          GITHUB_CONTEXT: ${{ toJson(github) }}

  basic-tests:
    name: "Basic tests"
    needs: [build-info]
    uses: ./.github/workflows/basic-tests.yml
    with:
      runs-on-as-json-public: ${{ needs.build-info.outputs.runs-on-as-json-public }}
      run-www-tests: ${{needs.build-info.outputs.run-www-tests}}
      needs-api-codegen: ${{needs.build-info.outputs.needs-api-codegen}}
      default-python-version: ${{needs.build-info.outputs.default-python-version}}
      basic-checks-only: ${{needs.build-info.outputs.basic-checks-only}}
      skip-pre-commits: ${{needs.build-info.outputs.skip-pre-commits}}
      canary-run: ${{needs.build-info.outputs.canary-run}}
      latest-versions-only: ${{needs.build-info.outputs.latest-versions-only}}

  build-ci-images:
    name: >
      ${{ needs.build-info.outputs.in-workflow-build == 'true' && 'Build' || 'Skip building' }}
      CI images in-workflow
    needs: [build-info]
    uses: ./.github/workflows/ci-image-build.yml
    permissions:
      contents: read
      # This write is only given here for `push` events from "apache/airflow" repo. It is not given for PRs
      # from forks. This is to prevent malicious PRs from creating images in the "apache/airflow" repo.
      # For regular build for PRS this "build-prod-images" workflow will be skipped anyway by the
      # "in-workflow-build" condition
      packages: write
    secrets: inherit
    with:
      runs-on-as-json-public: ${{ needs.build-info.outputs.runs-on-as-json-public }}
      runs-on-as-json-self-hosted: ${{ needs.build-info.outputs.runs-on-as-json-self-hosted }}
      do-build: ${{ needs.build-info.outputs.in-workflow-build }}
      image-tag: ${{ needs.build-info.outputs.image-tag }}
      platform: "linux/amd64"
      python-versions: ${{ needs.build-info.outputs.python-versions }}
      branch: ${{ needs.build-info.outputs.default-branch }}
      use-uv: "true"
      upgrade-to-newer-dependencies: ${{ needs.build-info.outputs.upgrade-to-newer-dependencies }}
      constraints-branch: ${{ needs.build-info.outputs.default-constraints-branch }}
      docker-cache: ${{ needs.build-info.outputs.docker-cache }}

  wait-for-ci-images:
    timeout-minutes: 120
    name: "Wait for CI images"
    runs-on: ${{ fromJSON(needs.build-info.outputs.runs-on-as-json-public) }}
    needs: [build-info, build-ci-images]
    if: needs.build-info.outputs.ci-image-build == 'true'
    env:
      BACKEND: sqlite
      # Force more parallelism for pull even on public images
      PARALLELISM: 6
      INCLUDE_SUCCESS_OUTPUTS: "${{needs.build-info.outputs.include-success-outputs}}"
    steps:
      - name: "Cleanup repo"
        shell: bash
        run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm -rf /workspace/*"
        if: needs.build-info.outputs.in-workflow-build == 'false'
      - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
        uses: actions/checkout@v4
        with:
          persist-credentials: false
        if: needs.build-info.outputs.in-workflow-build == 'false'
      - name: "Cleanup docker"
        run: ./scripts/ci/cleanup_docker.sh
        if: needs.build-info.outputs.in-workflow-build == 'false'
      - name: "Install Breeze"
        uses: ./.github/actions/breeze
        if: needs.build-info.outputs.in-workflow-build == 'false'
      - name: Login to ghcr.io
        run: echo "${{ env.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
        if: needs.build-info.outputs.in-workflow-build == 'false'
      - name: Wait for CI images ${{ env.PYTHON_VERSIONS }}:${{ needs.build-info.outputs.image-tag }}
        id: wait-for-images
        run: breeze ci-image pull --run-in-parallel --wait-for-image --tag-as-latest
        env:
          PYTHON_VERSIONS: ${{ needs.build-info.outputs.python-versions-list-as-string }}
          DEBUG_RESOURCES: ${{needs.build-info.outputs.debug-resources}}
        if: needs.build-info.outputs.in-workflow-build == 'false'

  additional-ci-image-checks:
    name: "Additional CI image checks"
    needs: [build-info, wait-for-ci-images]
    uses: ./.github/workflows/additional-ci-image-checks.yml
    if: needs.build-info.outputs.canary-run == 'true'
    with:
      runs-on-as-json-default: ${{ needs.build-info.outputs.runs-on-as-json-default }}
      runs-on-as-json-public: ${{ needs.build-info.outputs.runs-on-as-json-public }}
      runs-on-as-json-self-hosted: ${{ needs.build-info.outputs.runs-on-as-json-self-hosted }}
      image-tag: ${{ needs.build-info.outputs.image-tag }}
      python-versions: ${{ needs.build-info.outputs.python-versions }}
      branch: ${{ needs.build-info.outputs.default-branch }}
      constraints-branch: ${{ needs.build-info.outputs.default-constraints-branch }}
      default-python-version: ${{ needs.build-info.outputs.default-python-version }}
      upgrade-to-newer-dependencies: ${{ needs.build-info.outputs.upgrade-to-newer-dependencies }}
      skip-pre-commits: ${{ needs.build-info.outputs.skip-pre-commits }}
      docker-cache: ${{ needs.build-info.outputs.docker-cache }}
      canary-run: ${{ needs.build-info.outputs.canary-run }}
      latest-versions-only: ${{ needs.build-info.outputs.latest-versions-only }}
      include-success-outputs: ${{ needs.build-info.outputs.include-success-outputs }}
      debug-resources: ${{ needs.build-info.outputs.debug-resources }}

  generate-constraints:
    name: "Generate constraints"
    needs: [build-info, wait-for-ci-images]
    uses: ./.github/workflows/generate-constraints.yml
    if: needs.build-info.outputs.ci-image-build == 'true'
    with:
      runs-on-as-json-public: ${{ needs.build-info.outputs.runs-on-as-json-public }}
      python-versions-list-as-string: ${{ needs.build-info.outputs.python-versions-list-as-string }}
      # generate no providers constraints only in canary builds - they take quite some time to generate
      # they are not needed for regular builds, they are only needed to update constraints in canaries
      generate-no-providers-constraints: ${{ needs.build-info.outputs.canary-run }}
      image-tag: ${{ needs.build-info.outputs.image-tag }}
      chicken-egg-providers: ${{ needs.build-info.outputs.chicken-egg-providers }}
      debug-resources: ${{ needs.build-info.outputs.debug-resources }}

  static-checks-mypy-docs:
    name: "Static checks, mypy, docs"
    needs: [build-info, wait-for-ci-images]
    uses: ./.github/workflows/static-checks-mypy-docs.yml
    secrets: inherit
    with:
      runs-on-as-json-default: ${{ needs.build-info.outputs.runs-on-as-json-default }}
      image-tag: ${{ needs.build-info.outputs.image-tag }}
      needs-mypy: ${{ needs.build-info.outputs.needs-mypy }}
      mypy-folders: ${{ needs.build-info.outputs.mypy-folders }}
      python-versions-list-as-string: ${{ needs.build-info.outputs.python-versions-list-as-string }}
      branch: ${{ needs.build-info.outputs.default-branch }}
      canary-run: ${{ needs.build-info.outputs.canary-run }}
      default-python-version: ${{ needs.build-info.outputs.default-python-version }}
      docs-list-as-string: ${{ needs.build-info.outputs.docs-list-as-string }}
      latest-versions-only: ${{ needs.build-info.outputs.latest-versions-only }}
      basic-checks-only: ${{ needs.build-info.outputs.basic-checks-only }}
      upgrade-to-newer-dependencies: ${{ needs.build-info.outputs.upgrade-to-newer-dependencies }}
      skip-pre-commits: ${{ needs.build-info.outputs.skip-pre-commits }}
      chicken-egg-providers: ${{ needs.build-info.outputs.chicken-egg-providers }}
      ci-image-build: ${{ needs.build-info.outputs.ci-image-build }}
      include-success-outputs: ${{ needs.build-info.outputs.include-success-outputs }}
      debug-resources: ${{ needs.build-info.outputs.debug-resources }}

  providers:
    name: "Provider checks"
    uses: ./.github/workflows/check-providers.yml
    needs: [build-info, wait-for-ci-images]
    permissions:
      contents: read
      packages: read
    secrets: inherit
    if: >
      needs.build-info.outputs.skip-providers-tests != 'true' &&
      needs.build-info.outputs.latest-versions-only != 'true'
    with:
      runs-on-as-json-default: ${{ needs.build-info.outputs.runs-on-as-json-default }}
      image-tag: ${{ needs.build-info.outputs.image-tag }}
      default-python-version: ${{ needs.build-info.outputs.default-python-version }}
      upgrade-to-newer-dependencies: ${{ needs.build-info.outputs.upgrade-to-newer-dependencies }}
      affected-providers-list-as-string: ${{ needs.build-info.outputs.affected-providers-list-as-string }}
      providers-compatibility-checks: ${{ needs.build-info.outputs.providers-compatibility-checks }}
      skip-provider-tests: ${{ needs.build-info.outputs.skip-provider-tests }}
      python-versions: ${{ needs.build-info.outputs.python-versions }}
      providers-test-types-list-as-string: ${{ needs.build-info.outputs.providers-test-types-list-as-string }}

  tests-helm:
    name: "Helm tests"
    uses: ./.github/workflows/helm-tests.yml
    needs: [build-info, wait-for-ci-images]
    permissions:
      contents: read
      packages: read
    secrets: inherit
    with:
      runs-on-as-json-default: ${{ needs.build-info.outputs.runs-on-as-json-default }}
      runs-on-as-json-public: ${{ needs.build-info.outputs.runs-on-as-json-public }}
      helm-test-packages: ${{ needs.build-info.outputs.helm-test-packages }}
      image-tag: ${{ needs.build-info.outputs.image-tag }}
      default-python-version: ${{ needs.build-info.outputs.default-python-version }}
    if: >
      needs.build-info.outputs.needs-helm-tests == 'true' &&
      needs.build-info.outputs.default-branch == 'main' &&
      needs.build-info.outputs.latest-versions-only != 'true'

  tests-postgres:
    name: "Postgres tests"
    uses: ./.github/workflows/run-unit-tests.yml
    needs: [build-info, wait-for-ci-images]
    permissions:
      contents: read
      packages: read
    secrets: inherit
    with:
      runs-on-as-json-default: ${{ needs.build-info.outputs.runs-on-as-json-default }}
      backend: "postgres"
      test-name: "Postgres"
      test-scope: "DB"
      image-tag: ${{ needs.build-info.outputs.image-tag }}
      python-versions: ${{ needs.build-info.outputs.python-versions }}
      backend-versions: ${{ needs.build-info.outputs.postgres-versions }}
      excludes: ${{ needs.build-info.outputs.postgres-exclude }}
      parallel-test-types-list-as-string: ${{ needs.build-info.outputs.parallel-test-types-list-as-string }}
      include-success-outputs: ${{ needs.build-info.outputs.include-success-outputs }}
      run-migration-tests: "true"
      run-coverage: ${{ needs.build-info.outputs.run-coverage }}
      debug-resources: ${{ needs.build-info.outputs.debug-resources }}
    if: needs.build-info.outputs.run-tests == 'true'

  tests-mysql:
    name: "MySQL tests"
    uses: ./.github/workflows/run-unit-tests.yml
    needs: [build-info, wait-for-ci-images]
    permissions:
      contents: read
      packages: read
    secrets: inherit
    with:
      runs-on-as-json-default: ${{ needs.build-info.outputs.runs-on-as-json-default }}
      backend: "mysql"
      test-name: "MySQL"
      test-scope: "DB"
      image-tag: ${{ needs.build-info.outputs.image-tag }}
      python-versions: ${{ needs.build-info.outputs.python-versions }}
      backend-versions: ${{ needs.build-info.outputs.mysql-versions }}
      excludes: ${{ needs.build-info.outputs.mysql-exclude }}
      parallel-test-types-list-as-string: ${{ needs.build-info.outputs.parallel-test-types-list-as-string }}
      include-success-outputs: ${{ needs.build-info.outputs.include-success-outputs }}
      run-coverage: ${{ needs.build-info.outputs.run-coverage }}
      run-migration-tests: "true"
      debug-resources: ${{ needs.build-info.outputs.debug-resources }}
    if: needs.build-info.outputs.run-tests == 'true'

  tests-sqlite:
    name: "Sqlite tests"
    uses: ./.github/workflows/run-unit-tests.yml
    needs: [build-info, wait-for-ci-images]
    permissions:
      contents: read
      packages: read
    secrets: inherit
    with:
      runs-on-as-json-default: ${{ needs.build-info.outputs.runs-on-as-json-default }}
      backend: "sqlite"
      test-name: "Sqlite"
      test-name-separator: ""
      test-scope: "DB"
      image-tag: ${{ needs.build-info.outputs.image-tag }}
      python-versions: ${{ needs.build-info.outputs.python-versions }}
      # No versions for sqlite
      backend-versions: "['']"
      excludes: ${{ needs.build-info.outputs.sqlite-exclude }}
      parallel-test-types-list-as-string: ${{ needs.build-info.outputs.parallel-test-types-list-as-string }}
      include-success-outputs: ${{ needs.build-info.outputs.include-success-outputs }}
      run-coverage: ${{ needs.build-info.outputs.run-coverage }}
      run-migration-tests: "true"
      debug-resources: ${{ needs.build-info.outputs.debug-resources }}
    if: needs.build-info.outputs.run-tests == 'true'

  tests-non-db:
    name: "Non-DB tests"
    uses: ./.github/workflows/run-unit-tests.yml
    needs: [build-info, wait-for-ci-images]
    permissions:
      contents: read
      packages: read
    secrets: inherit
    with:
      runs-on-as-json-default: ${{ needs.build-info.outputs.runs-on-as-json-default }}
      backend: "sqlite"
      test-name: ""
      test-name-separator: ""
      test-scope: "Non-DB"
      image-tag: ${{ needs.build-info.outputs.image-tag }}
      python-versions: ${{ needs.build-info.outputs.python-versions }}
      # No versions for non-db
      backend-versions: "['']"
      excludes: ${{ needs.build-info.outputs.sqlite-exclude }}
      parallel-test-types-list-as-string: ${{ needs.build-info.outputs.parallel-test-types-list-as-string }}
      include-success-outputs: ${{ needs.build-info.outputs.include-success-outputs }}
      run-coverage: ${{ needs.build-info.outputs.run-coverage }}
      debug-resources: ${{ needs.build-info.outputs.debug-resources }}
    if: needs.build-info.outputs.run-tests == 'true'

  tests-special:
    name: "Special tests"
    uses: ./.github/workflows/special-tests.yml
    needs: [build-info, wait-for-ci-images]
    permissions:
      contents: read
      packages: read
    secrets: inherit
    if: >
      needs.build-info.outputs.run-tests == 'true' &&
      (needs.build-info.outputs.canary-run == 'true' ||
       needs.build-info.outputs.upgrade-to-newer-dependencies != 'false' ||
       needs.build-info.outputs.full-tests-needed == 'true')
    with:
      runs-on-as-json-default: ${{ needs.build-info.outputs.runs-on-as-json-default }}
      image-tag: ${{ needs.build-info.outputs.image-tag }}
      parallel-test-types-list-as-string: ${{ needs.build-info.outputs.parallel-test-types-list-as-string }}
      separate-test-types-list-as-string: >-
        ${{ needs.build-info.outputs.separate-test-types-list-as-string }}
      run-coverage: ${{ needs.build-info.outputs.run-coverage }}
      default-python-version: ${{ needs.build-info.outputs.default-python-version }}
      python-versions: ${{ needs.build-info.outputs.python-versions }}
      default-postgres-version: ${{ needs.build-info.outputs.default-postgres-version }}
      canary-run: ${{ needs.build-info.outputs.canary-run }}
      upgrade-to-newer-dependencies: ${{ needs.build-info.outputs.upgrade-to-newer-dependencies }}
      debug-resources: ${{ needs.build-info.outputs.debug-resources }}

  tests-integration:
    name: Integration Tests
    needs: [build-info, wait-for-ci-images]
    uses: ./.github/workflows/integration-tests.yml
    permissions:
      contents: read
      packages: read
    secrets: inherit
    with:
      runs-on-as-json-public: ${{ needs.build-info.outputs.runs-on-as-json-public }}
      image-tag: ${{ needs.build-info.outputs.image-tag }}
      testable-integrations: ${{ needs.build-info.outputs.testable-integrations }}
      default-python-version: ${{ needs.build-info.outputs.default-python-version }}
      default-postgres-version: ${{ needs.build-info.outputs.default-postgres-version }}
      default-mysql-version: ${{ needs.build-info.outputs.default-mysql-version }}
      skip-provider-tests: ${{ needs.build-info.outputs.skip-provider-tests }}
      run-coverage: ${{ needs.build-info.outputs.run-coverage }}
      debug-resources: ${{ needs.build-info.outputs.debug-resources }}
    if: needs.build-info.outputs.run-tests == 'true'

  build-prod-images:
    name: >
      ${{ needs.build-info.outputs.in-workflow-build == 'true' && 'Build' || 'Skip building' }}
      PROD images in-workflow
    needs: [build-info, build-ci-images, generate-constraints]
    uses: ./.github/workflows/prod-image-build.yml
    permissions:
      contents: read
      # This write is only given here for `push` events from "apache/airflow" repo. It is not given for PRs
      # from forks. This is to prevent malicious PRs from creating images in the "apache/airflow" repo.
      # For regular build for PRS this "build-prod-images" workflow will be skipped anyway by the
      # "in-workflow-build" condition
      packages: write
    secrets: inherit
    with:
      runs-on-as-json-public: ${{ needs.build-info.outputs.runs-on-as-json-public }}
      build-type: "Regular"
      do-build: ${{ needs.build-info.outputs.in-workflow-build }}
      upload-package-artifact: "true"
      image-tag: ${{ needs.build-info.outputs.image-tag }}
      platform: "linux/amd64"
      python-versions: ${{ needs.build-info.outputs.python-versions }}
      default-python-version: ${{ needs.build-info.outputs.default-python-version }}
      branch: ${{ needs.build-info.outputs.default-branch }}
      push-image: "true"
      use-uv: ${{ needs.build-info.outputs.default-branch == 'main' && 'true' || 'false' }}
      build-provider-packages: ${{ needs.build-info.outputs.default-branch == 'main' }}
      upgrade-to-newer-dependencies: ${{ needs.build-info.outputs.upgrade-to-newer-dependencies }}
      chicken-egg-providers: ${{ needs.build-info.outputs.chicken-egg-providers }}
      constraints-branch: ${{ needs.build-info.outputs.default-constraints-branch }}
      docker-cache: ${{ needs.build-info.outputs.docker-cache }}

  wait-for-prod-images:
    timeout-minutes: 80
    name: "Wait for PROD images"
    runs-on: ${{ fromJSON(needs.build-info.outputs.runs-on-as-json-public) }}
    needs: [build-info, wait-for-ci-images, build-prod-images]
    if: needs.build-info.outputs.prod-image-build == 'true'
    env:
      BACKEND: sqlite
      PYTHON_MAJOR_MINOR_VERSION: "${{needs.build-info.outputs.default-python-version}}"
      # Force more parallelism for pull on public images
      PARALLELISM: 6
      INCLUDE_SUCCESS_OUTPUTS: "${{needs.build-info.outputs.include-success-outputs}}"
      IMAGE_TAG: ${{ needs.build-info.outputs.image-tag }}
    steps:
      - name: "Cleanup repo"
        shell: bash
        run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm -rf /workspace/*"
        if: needs.build-info.outputs.in-workflow-build == 'false'
      - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
        uses: actions/checkout@v4
        with:
          persist-credentials: false
        if: needs.build-info.outputs.in-workflow-build == 'false'
      - name: "Cleanup docker"
        run: ./scripts/ci/cleanup_docker.sh
        if: needs.build-info.outputs.in-workflow-build == 'false'
      - name: "Install Breeze"
        uses: ./.github/actions/breeze
        if: needs.build-info.outputs.in-workflow-build == 'false'
      - name: Login to ghcr.io
        run: echo "${{ env.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
        if: needs.build-info.outputs.in-workflow-build == 'false'
      - name: Wait for PROD images ${{ env.PYTHON_VERSIONS }}:${{ needs.build-info.outputs.image-tag }}
        # We wait for the images to be available either from "build-images.yml' run as pull_request_target
        # or from build-prod-images (or build-prod-images-release-branch) above.
        # We are utilising single job to wait for all images because this job merely waits
        # For the images to be available.
        run: breeze prod-image pull --wait-for-image --run-in-parallel
        env:
          PYTHON_VERSIONS: ${{ needs.build-info.outputs.python-versions-list-as-string }}
          DEBUG_RESOURCES: ${{ needs.build-info.outputs.debug-resources }}
        if: needs.build-info.outputs.in-workflow-build == 'false'

  additional-prod-image-tests:
    name: "Additional PROD image tests"
    needs: [build-info, wait-for-prod-images, generate-constraints]
    uses: ./.github/workflows/additional-prod-image-tests.yml
    with:
      runs-on-as-json-public: ${{ needs.build-info.outputs.runs-on-as-json-public }}
      default-branch: ${{ needs.build-info.outputs.default-branch }}
      constraints-branch: ${{ needs.build-info.outputs.default-constraints-branch }}
      image-tag: ${{ needs.build-info.outputs.image-tag }}
      upgrade-to-newer-dependencies: ${{ needs.build-info.outputs.upgrade-to-newer-dependencies }}
      chicken-egg-providers: ${{ needs.build-info.outputs.chicken-egg-providers }}
      docker-cache: ${{ needs.build-info.outputs.docker-cache }}
      default-python-version: ${{ needs.build-info.outputs.default-python-version }}
      canary-run: ${{ needs.build-info.outputs.canary-run }}
    if: needs.build-info.outputs.prod-image-build == 'true'

  tests-kubernetes:
    name: "Kubernetes tests"
    uses: ./.github/workflows/k8s-tests.yml
    needs: [build-info, wait-for-prod-images]
    permissions:
      contents: read
      packages: read
    secrets: inherit
    with:
      runs-on-as-json-default: ${{ needs.build-info.outputs.runs-on-as-json-default }}
      image-tag: ${{ needs.build-info.outputs.image-tag }}
      python-versions-list-as-string: ${{ needs.build-info.outputs.python-versions-list-as-string }}
      kubernetes-versions-list-as-string: ${{ needs.build-info.outputs.kubernetes-versions-list-as-string }}
      kubernetes-combos-list-as-string: ${{ needs.build-info.outputs.kubernetes-combos-list-as-string }}
      include-success-outputs: ${{ needs.build-info.outputs.include-success-outputs }}
      debug-resources: ${{ needs.build-info.outputs.debug-resources }}
    if: >
      ( needs.build-info.outputs.run-kubernetes-tests == 'true' ||
      needs.build-info.outputs.needs-helm-tests == 'true')

  finalize-tests:
    name: Finalize tests
    permissions:
      contents: write
      packages: write
    secrets: inherit
    needs:
      - build-info
      - generate-constraints
      - wait-for-ci-images
      - wait-for-prod-images
      - static-checks-mypy-docs
      - tests-sqlite
      - tests-mysql
      - tests-postgres
      - tests-non-db
      - tests-integration
    uses: ./.github/workflows/finalize-tests.yml
    with:
      runs-on-as-json-public: ${{ needs.build-info.outputs.runs-on-as-json-public }}
      runs-on-as-json-self-hosted: ${{ needs.build-info.outputs.runs-on-as-json-self-hosted }}
      image-tag: ${{ needs.build-info.outputs.image-tag }}
      python-versions: ${{ needs.build-info.outputs.python-versions }}
      python-versions-list-as-string: ${{ needs.build-info.outputs.python-versions-list-as-string }}
      branch: ${{ needs.build-info.outputs.default-branch }}
      constraints-branch: ${{ needs.build-info.outputs.default-constraints-branch }}
      default-python-version: ${{ needs.build-info.outputs.default-python-version }}
      in-workflow-build: ${{ needs.build-info.outputs.in-workflow-build }}
      upgrade-to-newer-dependencies: ${{ needs.build-info.outputs.upgrade-to-newer-dependencies }}
      include-success-outputs: ${{ needs.build-info.outputs.include-success-outputs }}
      docker-cache: ${{ needs.build-info.outputs.docker-cache }}
      canary-run: ${{ needs.build-info.outputs.canary-run }}


CodeQL

# 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.
#
---
name: "CodeQL"

on:  # yamllint disable-line rule:truthy
  push:
    branches: [main]
  schedule:
    - cron: '0 2 * * *'

permissions:
  contents: read
concurrency:
  group: codeql-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  selective-checks:
    name: Selective checks
    runs-on: ["ubuntu-22.04"]
    outputs:
      needs-python-scans: ${{ steps.selective-checks.outputs.needs-python-scans }}
      needs-javascript-scans: ${{ steps.selective-checks.outputs.needs-javascript-scans }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 2
          persist-credentials: false
      - name: "Install Breeze"
        uses: ./.github/actions/breeze
      - name: Selective checks
        id: selective-checks
        env:
          COMMIT_REF: "${{ github.sha }}"
          VERBOSE: "false"
        run: breeze ci selective-check 2>> ${GITHUB_OUTPUT}

  analyze:
    name: Analyze
    runs-on: ["ubuntu-22.04"]
    needs: [selective-checks]
    strategy:
      fail-fast: false
      matrix:
        # Override automatic language detection by changing the below list
        # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
        language: ['python', 'javascript']
    permissions:
      actions: read
      contents: read
      pull-requests: read
      security-events: write
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          persist-credentials: false
        if: |
          matrix.language == 'python' && needs.selective-checks.outputs.needs-python-scans == 'true' ||
          matrix.language == 'javascript' && needs.selective-checks.outputs.needs-javascript-scans == 'true'

      # Initializes the CodeQL tools for scanning.
      - name: Initialize CodeQL
        uses: github/codeql-action/init@v2
        with:
          languages: ${{ matrix.language }}
          # If you wish to specify custom queries, you can do so here or in a config file.
          # By default, queries listed here will override any specified in a config file.
          # Prefix the list here with "+" to use these queries and those in the config file.
          # queries: ./path/to/local/query, your-org/your-repo/queries@main
        if: |
          matrix.language == 'python' && needs.selective-checks.outputs.needs-python-scans == 'true' ||
          matrix.language == 'javascript' && needs.selective-checks.outputs.needs-javascript-scans == 'true'

      # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
      # If this step fails, then you should remove it and run the build manually (see below)
      - name: Autobuild
        uses: github/codeql-action/autobuild@v2
        if: |
          matrix.language == 'python' && needs.selective-checks.outputs.needs-python-scans == 'true' ||
          matrix.language == 'javascript' && needs.selective-checks.outputs.needs-javascript-scans == 'true'

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v2
        if: |
          matrix.language == 'python' && needs.selective-checks.outputs.needs-python-scans == 'true' ||
          matrix.language == 'javascript' && needs.selective-checks.outputs.needs-javascript-scans == 'true'


Close stale PRs & Issues

# 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.
#
---
# https://github.com/actions/stale
name: 'Close stale PRs & Issues'
on:  # yamllint disable-line rule:truthy
  schedule:
    - cron: '0 0 * * *'
permissions:
  # All other permissions are set to none
  pull-requests: write
  issues: write
jobs:
  stale:
    runs-on: ["ubuntu-22.04"]
    steps:
      - uses: actions/stale@v9
        with:
          stale-pr-message: >
            This pull request has been automatically marked as stale because it has not had
            recent activity. It will be closed in 5 days if no further activity occurs. Thank you
            for your contributions.
          days-before-pr-stale: 45
          days-before-pr-close: 5
          exempt-pr-labels: 'pinned,security'
          only-issue-labels: 'pending-response'
          remove-stale-when-updated: true
          days-before-issue-stale: 14
          days-before-issue-close: 7
          stale-issue-message: >
            This issue has been automatically marked as stale because it has been open for 14 days
            with no response from the author. It will be closed in next 7 days if no further
            activity occurs from the issue author.
          close-issue-message: >
            This issue has been closed because it has not received response from the issue author.


Build Images

# 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.
#
---
name: "Build Images"
run-name: >
  Build images for ${{ github.event.pull_request.title }} ${{ github.event.pull_request._links.html.href }}
on:  # yamllint disable-line rule:truthy
  pull_request_target:
permissions:
  # all other permissions are set to none
  contents: read
  pull-requests: read
  packages: read
env:
  ANSWER: "yes"
  # You can override CONSTRAINTS_GITHUB_REPOSITORY by setting secret in your repo but by default the
  # Airflow one is going to be used
  CONSTRAINTS_GITHUB_REPOSITORY: >-
    ${{ secrets.CONSTRAINTS_GITHUB_REPOSITORY != '' &&
        secrets.CONSTRAINTS_GITHUB_REPOSITORY || 'apache/airflow' }}
  # This token is WRITE one - pull_request_target type of events always have the WRITE token
  DB_RESET: "true"
  GITHUB_REPOSITORY: ${{ github.repository }}
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  GITHUB_USERNAME: ${{ github.actor }}
  IMAGE_TAG: "${{ github.event.pull_request.head.sha || github.sha }}"
  INCLUDE_SUCCESS_OUTPUTS: "true"
  USE_SUDO: "true"
  VERBOSE: "true"

concurrency:
  group: build-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  build-info:
    timeout-minutes: 10
    name: "Build Info"
    # At build-info stage we do not yet have outputs so we need to hard-code the runs-on to public runners
    runs-on: ["ubuntu-22.04"]
    env:
      TARGET_BRANCH: ${{ github.event.pull_request.base.ref }}
    outputs:
      image-tag: ${{ github.event.pull_request.head.sha || github.sha }}
      python-versions: ${{ steps.selective-checks.outputs.python-versions }}
      python-versions-list-as-string: ${{ steps.selective-checks.outputs.python-versions-list-as-string }}
      default-python-version: ${{ steps.selective-checks.outputs.default-python-version }}
      upgrade-to-newer-dependencies: ${{ steps.selective-checks.outputs.upgrade-to-newer-dependencies }}
      run-tests: ${{ steps.selective-checks.outputs.run-tests }}
      run-kubernetes-tests: ${{ steps.selective-checks.outputs.run-kubernetes-tests }}
      ci-image-build: ${{ steps.selective-checks.outputs.ci-image-build }}
      prod-image-build: ${{ steps.selective-checks.outputs.prod-image-build }}
      docker-cache: ${{ steps.selective-checks.outputs.docker-cache }}
      default-branch: ${{ steps.selective-checks.outputs.default-branch }}
      constraints-branch: ${{ steps.selective-checks.outputs.default-constraints-branch }}
      runs-on-as-json-default: ${{ steps.selective-checks.outputs.runs-on-as-json-default }}
      runs-on-as-json-public: ${{ steps.selective-checks.outputs.runs-on-as-json-public }}
      runs-on-as-json-self-hosted: ${{ steps.selective-checks.outputs.runs-on-as-json-self-hosted }}
      is-self-hosted-runner: ${{ steps.selective-checks.outputs.is-self-hosted-runner }}
      is-committer-build: ${{ steps.selective-checks.outputs.is-committer-build }}
      is-airflow-runner: ${{ steps.selective-checks.outputs.is-airflow-runner }}
      is-amd-runner: ${{ steps.selective-checks.outputs.is-amd-runner }}
      is-arm-runner: ${{ steps.selective-checks.outputs.is-arm-runner }}
      is-vm-runner: ${{ steps.selective-checks.outputs.is-vm-runner }}
      is-k8s-runner: ${{ steps.selective-checks.outputs.is-k8s-runner }}
      chicken-egg-providers: ${{ steps.selective-checks.outputs.chicken-egg-providers }}
      target-commit-sha: "${{steps.discover-pr-merge-commit.outputs.target-commit-sha ||
          github.event.pull_request.head.sha ||
          github.sha
        }}"
    if: github.repository == 'apache/airflow'
    steps:
      - name: "Cleanup repo"
        shell: bash
        run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm -rf /workspace/*"
      - name: Discover PR merge commit
        id: discover-pr-merge-commit
        run: |
          # Sometimes target-commit-sha cannot be
          TARGET_COMMIT_SHA="$(gh api '${{ github.event.pull_request.url }}' --jq .merge_commit_sha)"
          if [[ ${TARGET_COMMIT_SHA} == "" ]]; then
            # Sometimes retrieving the merge commit SHA from PR fails. We retry it once. Otherwise we
            # fall-back to github.event.pull_request.head.sha
            echo
            echo "Could not retrieve merge commit SHA from PR, waiting for 3 seconds and retrying."
            echo
            sleep 3
            TARGET_COMMIT_SHA="$(gh api '${{ github.event.pull_request.url }}' --jq .merge_commit_sha)"
            if [[ ${TARGET_COMMIT_SHA} == "" ]]; then
              echo
              echo "Could not retrieve merge commit SHA from PR, falling back to PR head SHA."
              echo
              TARGET_COMMIT_SHA="${{ github.event.pull_request.head.sha }}"
            fi
          fi
          echo "TARGET_COMMIT_SHA=${TARGET_COMMIT_SHA}"
          echo "TARGET_COMMIT_SHA=${TARGET_COMMIT_SHA}" >> ${GITHUB_ENV}
          echo "target-commit-sha=${TARGET_COMMIT_SHA}" >> ${GITHUB_OUTPUT}
        if: github.event_name == 'pull_request_target'
      # The labels in the event aren't updated when re-triggering the job, So lets hit the API to get
      # up-to-date values
      - name: Get latest PR labels
        id: get-latest-pr-labels
        run: |
          echo -n "pull-request-labels=" >> ${GITHUB_OUTPUT}
          gh api graphql --paginate -F node_id=${{github.event.pull_request.node_id}} -f query='
            query($node_id: ID!, $endCursor: String) {
              node(id:$node_id) {
                ... on PullRequest {
                  labels(first: 100, after: $endCursor) {
                    nodes { name }
                    pageInfo { hasNextPage endCursor }
                  }
                }
              }
            }' --jq '.data.node.labels.nodes[]' | jq --slurp -c '[.[].name]' >> ${GITHUB_OUTPUT}
        if: github.event_name == 'pull_request_target'
      - uses: actions/checkout@v4
        with:
          ref: ${{ env.TARGET_COMMIT_SHA }}
          persist-credentials: false
          fetch-depth: 2
      ####################################################################################################
      #  WE ONLY DO THAT CHECKOUT ABOVE TO RETRIEVE THE TARGET COMMIT AND IT'S PARENT. DO NOT RUN ANY CODE
      #  RIGHT AFTER THAT AS WE ARE GOING TO RESTORE THE TARGET BRANCH CODE IN THE NEXT STEP.
      ####################################################################################################
      - name: Checkout target branch to use ci/scripts and breeze from there.
        uses: actions/checkout@v4
        with:
          ref: ${{ github.base_ref }}
          persist-credentials: false
      ####################################################################################################
      #  HERE EVERYTHING IS PERFECTLY SAFE TO RUN. AT THIS POINT WE HAVE THE TARGET BRANCH CHECKED OUT
      #  AND WE CAN RUN ANY CODE FROM IT. WE CAN RUN BREEZE COMMANDS, WE CAN RUN SCRIPTS, WE CAN RUN
      #  COMPOSITE ACTIONS. WE CAN RUN ANYTHING THAT IS IN THE TARGET BRANCH AND THERE IS NO RISK THAT
      #  CODE WILL BE RUN FROM THE PR.
      ####################################################################################################
      - name: "Cleanup docker"
        run: ./scripts/ci/cleanup_docker.sh
      - name: "Setup python"
        uses: actions/setup-python@v5
        with:
          python-version: 3.8
      - name: "Install Breeze"
        uses: ./.github/actions/breeze
      ####################################################################################################
      #  WE RUN SELECTIVE CHECKS HERE USING THE TARGET COMMIT AND ITS PARENT TO BE ABLE TO COMPARE THEM
      #  AND SEE WHAT HAS CHANGED IN THE PR. THE CODE IS STILL RUN FROM THE TARGET BRANCH, SO IT IS SAFE
      #  TO RUN IT, WE ONLY PASS TARGET_COMMIT_SHA SO THAT SELECTIVE CHECKS CAN SEE WHAT'S COMING IN THE PR
      ####################################################################################################
      - name: Selective checks
        id: selective-checks
        env:
          PR_LABELS: "${{ steps.get-latest-pr-labels.outputs.pull-request-labels }}"
          COMMIT_REF: "${{ env.TARGET_COMMIT_SHA }}"
          VERBOSE: "false"
          AIRFLOW_SOURCES_ROOT: "${{ github.workspace }}"
        run: breeze ci selective-check 2>> ${GITHUB_OUTPUT}
      - name: env
        run: printenv
        env:
          PR_LABELS: ${{ steps.get-latest-pr-labels.outputs.pull-request-labels }}
          GITHUB_CONTEXT: ${{ toJson(github) }}

  build-ci-images:
    name: Build CI images
    permissions:
      contents: read
      packages: write
    secrets: inherit
    needs: [build-info]
    uses: ./.github/workflows/ci-image-build.yml
    # Only run this it if the PR comes from fork, otherwise build will be done "in-PR-workflow"
    if: |
      needs.build-info.outputs.ci-image-build == 'true' &&
      github.event.pull_request.head.repo.full_name != 'apache/airflow'
    with:
      runs-on-as-json-public: ${{ needs.build-info.outputs.runs-on-as-json-public }}
      runs-on-as-json-self-hosted: ${{ needs.build-info.outputs.runs-on-as-json-self-hosted }}
      do-build: ${{ needs.build-info.outputs.ci-image-build }}
      target-commit-sha: ${{ needs.build-info.outputs.target-commit-sha }}
      pull-request-target: "true"
      is-committer-build: ${{ needs.build-info.outputs.is-committer-build }}
      push-image: "true"
      use-uv: "true"
      image-tag: ${{ needs.build-info.outputs.image-tag }}
      platform: "linux/amd64"
      python-versions: ${{ needs.build-info.outputs.python-versions }}
      branch: ${{ needs.build-info.outputs.default-branch }}
      constraints-branch: ${{ needs.build-info.outputs.constraints-branch }}
      upgrade-to-newer-dependencies: ${{ needs.build-info.outputs.upgrade-to-newer-dependencies }}
      docker-cache: ${{ needs.build-info.outputs.docker-cache }}

  generate-constraints:
    name: "Generate constraints"
    needs: [build-info, build-ci-images]
    uses: ./.github/workflows/generate-constraints.yml
    with:
      runs-on-as-json-public: ${{ needs.build-info.outputs.runs-on-as-json-public }}
      python-versions-list-as-string: ${{ needs.build-info.outputs.python-versions-list-as-string }}
      # For regular PRs we do not need "no providers" constraints - they are only needed in canary builds
      generate-no-providers-constraints: "false"
      image-tag: ${{ needs.build-info.outputs.image-tag }}
      chicken-egg-providers: ${{ needs.build-info.outputs.chicken-egg-providers }}
      debug-resources: ${{ needs.build-info.outputs.debug-resources }}

  build-prod-images:
    name: Build PROD images
    permissions:
      contents: read
      packages: write
    secrets: inherit
    needs: [build-info, generate-constraints]
    uses: ./.github/workflows/prod-image-build.yml
    # Only run this it if the PR comes from fork, otherwise build will be done "in-PR-workflow"
    if: |
      needs.build-info.outputs.prod-image-build == 'true' &&
      github.event.pull_request.head.repo.full_name != 'apache/airflow'
    with:
      runs-on-as-json-public: ${{ needs.build-info.outputs.runs-on-as-json-public }}
      build-type: "Regular"
      do-build: ${{ needs.build-info.outputs.ci-image-build }}
      upload-package-artifact: "true"
      target-commit-sha: ${{ needs.build-info.outputs.target-commit-sha }}
      pull-request-target: "true"
      is-committer-build: ${{ needs.build-info.outputs.is-committer-build }}
      push-image: "true"
      use-uv: ${{ needs.build-info.outputs.default-branch == 'main' && 'true' || 'false' }}
      image-tag: ${{ needs.build-info.outputs.image-tag }}
      platform: "linux/amd64"
      python-versions: ${{ needs.build-info.outputs.python-versions }}
      default-python-version: ${{ needs.build-info.outputs.default-python-version }}
      branch: ${{ needs.build-info.outputs.default-branch }}
      constraints-branch: ${{ needs.build-info.outputs.constraints-branch }}
      build-provider-packages: "true"
      upgrade-to-newer-dependencies: ${{ needs.build-info.outputs.upgrade-to-newer-dependencies }}
      chicken-egg-providers: ${{ needs.build-info.outputs.chicken-egg-providers }}
      docker-cache: ${{ needs.build-info.outputs.docker-cache }}


Release PROD images

# 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.
#
---
name: "Release PROD images"
on:  # yamllint disable-line rule:truthy
  workflow_dispatch:
    inputs:
      airflowVersion:
        description: 'Airflow version'
        required: true
      skipLatest:
        description: 'Skip Latest: Set to true if not latest.'
        default: ''
        required: false
permissions:
  contents: read
  packages: read
concurrency:
  group: ${{ github.event.inputs.airflowVersion }}
  cancel-in-progress: true
env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
  VERBOSE: true
jobs:
  build-info:
    timeout-minutes: 10
    name: "Build Info"
    runs-on: ["ubuntu-22.04"]
    outputs:
      pythonVersions: ${{ steps.selective-checks.outputs.python-versions }}
      allPythonVersions: ${{ steps.selective-checks.outputs.all-python-versions }}
      defaultPythonVersion: ${{ steps.selective-checks.outputs.default-python-version }}
      chicken-egg-providers: ${{ steps.selective-checks.outputs.chicken-egg-providers }}
      skipLatest: ${{ github.event.inputs.skipLatest == '' && ' ' || '--skip-latest' }}
      limitPlatform: ${{ github.repository == 'apache/airflow' && ' ' || '--limit-platform linux/amd64' }}
    env:
      GITHUB_CONTEXT: ${{ toJson(github) }}
      VERBOSE: true
    steps:
      - name: "Cleanup repo"
        shell: bash
        run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm -rf /workspace/*"
      - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
        uses: actions/checkout@v4
        with:
          persist-credentials: false
      - name: "Cleanup docker"
        run: ./scripts/ci/cleanup_docker.sh
      - name: "Install Breeze"
        uses: ./.github/actions/breeze
      - name: Selective checks
        id: selective-checks
        env:
          VERBOSE: "false"
        run: breeze ci selective-check 2>> ${GITHUB_OUTPUT}
  release-images:
    timeout-minutes: 120
    name: "Release images: ${{ github.event.inputs.airflowVersion }}, ${{ matrix.python-version }}"
    runs-on: ["self-hosted", "Linux", "X64"]
    needs: [build-info]
    strategy:
      fail-fast: false
      matrix:
        python-version: ${{ fromJSON(needs.build-info.outputs.pythonVersions) }}
    if: contains(fromJSON('[
      "ashb",
      "eladkal",
      "ephraimbuddy",
      "jedcunningham",
      "kaxil",
      "pierrejeambrun",
      "potiuk",
      ]'), github.event.sender.login)
    steps:
      - name: "Cleanup repo"
        shell: bash
        run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm -rf /workspace/*"
      - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
        uses: actions/checkout@v4
        with:
          persist-credentials: false
      - name: "Cleanup docker"
        run: ./scripts/ci/cleanup_docker.sh
      - name: "Install Breeze"
        uses: ./.github/actions/breeze
      - name: Free space
        run: breeze ci free-space --answer yes
      - name: "Cleanup dist and context file"
        run: rm -fv ./dist/* ./docker-context-files/*
      - name: "Start ARM instance"
        run: ./scripts/ci/images/ci_start_arm_instance_and_connect_to_docker.sh
        if: github.repository == 'apache/airflow'
      - name: "Login to hub.docker.com"
        run: >
          echo ${{ secrets.DOCKERHUB_TOKEN }} |
          docker login --password-stdin --username ${{ secrets.DOCKERHUB_USER }}
      - name: Login to ghcr.io
        run: echo "${{ env.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
      - name: "Prepare chicken-eggs provider packages"
        # In case of provider packages which use latest dev0 version of providers, we should prepare them
        # from the source code, not from the PyPI because they have apache-airflow>=X.Y.Z dependency
        # And when we prepare them from sources they will have apache-airflow>=X.Y.Z.dev0
        shell: bash
        run: >
          breeze release-management prepare-provider-packages
          --package-format wheel
          --version-suffix-for-pypi dev0 ${{ needs.build-info.outputs.chicken-egg-providers }}
        if: needs.build-info.outputs.chicken-egg-providers != ''
      - name: "Copy dist packages to docker-context files"
        shell: bash
        run: cp -v --no-preserve=mode,ownership ./dist/*.whl ./docker-context-files
        if: needs.build-info.outputs.chicken-egg-providers != ''
      - name: >
          Release regular images: ${{ github.event.inputs.airflowVersion }}, ${{ matrix.python-version }}
        run: >
          breeze release-management release-prod-images
          --dockerhub-repo ${{ github.repository }}
          --airflow-version  ${{ github.event.inputs.airflowVersion }}
          ${{ needs.build-info.outputs.skipLatest }}
          ${{ needs.build-info.outputs.limitPlatform }}
          --limit-python ${{ matrix.python-version }}
          --chicken-egg-providers "${{ needs.build-info.outputs.chicken-egg-providers }}"
        env:
          COMMIT_SHA: ${{ github.sha }}
      - name: >
          Release slim images: ${{ github.event.inputs.airflowVersion }}, ${{ matrix.python-version }}
        run: >
          breeze release-management release-prod-images
          --dockerhub-repo ${{ github.repository }}
          --airflow-version  ${{ github.event.inputs.airflowVersion }}
          ${{ needs.build-info.outputs.skipLatest }}
          ${{ needs.build-info.outputs.limitPlatform }}
          --limit-python ${{ matrix.python-version }} --slim-images
        env:
          COMMIT_SHA: ${{ github.sha }}
      - name: "Stop ARM instance"
        run: ./scripts/ci/images/ci_stop_arm_instance.sh
        if: always() && github.repository == 'apache/airflow'
      - name: >
          Verify regular AMD64 image: ${{ github.event.inputs.airflowVersion }}, ${{ matrix.python-version }}
        run: >
          breeze prod-image verify
          --pull
          --image-name
          ${{github.repository}}:${{github.event.inputs.airflowVersion}}-python${{matrix.python-version}}
      - name: >
          Verify slim AMD64 image: ${{ github.event.inputs.airflowVersion }}, ${{ matrix.python-version }}
        run: >
          breeze prod-image verify
          --pull
          --slim-image
          --image-name
          ${{github.repository}}:slim-${{github.event.inputs.airflowVersion}}-python${{matrix.python-version}}
      - name: "Docker logout"
        run: docker logout
        if: always()


Recheck old bug reports

# 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.
#
---
# https://github.com/actions/stale
name: 'Recheck old bug reports'
on:  # yamllint disable-line rule:truthy
  schedule:
    - cron: '0 7 * * *'
permissions:
  # All other permissions are set to none
  issues: write
jobs:
  recheck-old-bug-report:
    runs-on: ["ubuntu-22.04"]
    steps:
      - uses: actions/stale@v9
        with:
          only-issue-labels: 'kind:bug'
          stale-issue-label: 'Stale Bug Report'
          days-before-issue-stale: 365
          days-before-issue-close: 30
          # Stale bot does not support defining to
          # scan only issues: https://github.com/actions/stale/issues/837
          # To avoid this job scanning also PRs and setting the defaults of the bot to them,
          # we set high number of days thus effectively this job will not do any changes on PRs
          # Possible workaround with -1 as mentioned in
          # https://github.com/actions/stale/issues/1112#issuecomment-1871654196
          days-before-pr-stale: -1
          days-before-pr-close: -1
          remove-stale-when-updated: false
          remove-issue-stale-when-updated: true
          labels-to-add-when-unstale: 'needs-triage'
          stale-issue-message: >
            This issue has been automatically marked as stale because it has been open for 365 days
            without any activity. There has been several Airflow releases since last activity on this issue.
            Kindly asking to recheck the report against latest Airflow version and let us know
            if the issue is reproducible. The issue will be closed in next 30 days if no further activity
            occurs from the issue author.
          close-issue-message: >
            This issue has been closed because it has not received response from the issue author.


Build PROD images

# 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.
#
---
name: Build PROD images
on:  # yamllint disable-line rule:truthy
  workflow_call:
    inputs:
      runs-on-as-json-public:
        description: "The array of labels (in json form) determining default runner used for the build."
        required: true
        type: string
      build-type:
        description: >
          Name of the 'type' of the build - usually 'Regular' but other types are used to test image
          variations.
        required: true
        type: string
      do-build:
        description: >
          Whether to actually do the build (true/false). If set to false, the build is done
          already in pull-request-target workflow, so we skip it here.
        required: false
        default: "true"
        type: string
      upload-package-artifact:
        description: >
          Whether to upload package artifacts (true/false). If false, the job will rely on artifacts prepared
          by the main prod-image build job.
        required: true
        type: string
      target-commit-sha:
        description: "The commit SHA to checkout for the build"
        required: false
        default: ""
        type: string
      pull-request-target:
        description: "Whether we are running this from pull-request-target workflow (true/false)"
        required: false
        default: "false"
        type: string
      is-committer-build:
        description: "Whether the build is executed by committer (true/false)"
        required: false
        default: "false"
        type: string
      push-image:
        description: "Whether to push image to the registry (true/false)"
        required: true
        type: string
      debian-version:
        description: "Base Debian distribution to use for the build (bookworm/bullseye)"
        type: string
        default: "bookworm"
      install-mysql-client-type:
        description: "MySQL client type to use during build (mariadb/mysql)"
        type: string
        default: "mariadb"
      use-uv:
        description: "Whether to use uv to build the image (true/false)"
        required: true
        type: string
      image-tag:
        description: "Tag to set for the image"
        required: true
        type: string
      python-versions:
        description: "JSON-formatted array of Python versions to build images from"
        required: true
        type: string
      default-python-version:
        description: "Which version of python should be used by default"
        required: true
        type: string
      platform:
        description: "Platform for the build - 'linux/amd64' or 'linux/arm64'"
        required: true
        type: string
      branch:
        description: "Branch used to run the CI jobs in (main/v2_*_test)."
        required: true
        type: string
      constraints-branch:
        description: "Branch used to construct constraints URL from."
        required: true
        type: string
      build-provider-packages:
        description: "Whether to build provider packages (true/false). If false providers are from PyPI"
        required: true
        type: string
      upgrade-to-newer-dependencies:
        description: "Whether to attempt to upgrade image to newer dependencies (true/false)"
        required: true
        type: string
      chicken-egg-providers:
        description: "Space-separated list of providers that should be installed from context files"
        required: true
        type: string
      docker-cache:
        description: "Docker cache specification to build the image (registry, local, disabled)."
        required: true
        type: string
jobs:

  build-prod-packages:
    name: "${{ inputs.do-build == 'true' && 'Build' || 'Skip building' }} Airflow and provider packages"
    timeout-minutes: 10
    runs-on: ${{ fromJSON(inputs.runs-on-as-json-public) }}
    env:
      PYTHON_MAJOR_MINOR_VERSION: "${{ inputs.default-python-version }}"
      VERSION_SUFFIX_FOR_PYPI: ${{ inputs.branch == 'main' && 'dev0' || '' }}
    steps:
      - name: "Cleanup repo"
        shell: bash
        run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm -rf /workspace/*"
        if: inputs.do-build == 'true' && inputs.upload-package-artifact == 'true'
      - name: "Checkout target branch"
        uses: actions/checkout@v4
        with:
          persist-credentials: false
      - name: "Checkout target commit"
        uses: ./.github/actions/checkout_target_commit
        with:
          target-commit-sha: ${{ inputs.target-commit-sha }}
          pull-request-target: ${{ inputs.pull-request-target }}
          is-committer-build: ${{ inputs.is-committer-build }}
        if: inputs.do-build == 'true' && inputs.upload-package-artifact == 'true'
      - name: "Cleanup docker"
        run: ./scripts/ci/cleanup_docker.sh
        if: inputs.do-build == 'true' && inputs.upload-package-artifact == 'true'
      - uses: actions/setup-python@v5
        with:
          python-version: "${{ inputs.default-python-version }}"
        if: inputs.do-build == 'true' && inputs.upload-package-artifact == 'true'
      - name: "Cleanup dist and context file"
        shell: bash
        run: rm -fv ./dist/* ./docker-context-files/*
        if: inputs.do-build == 'true' && inputs.upload-package-artifact == 'true'
      - name: "Install Breeze"
        uses: ./.github/actions/breeze
        if: inputs.do-build == 'true' && inputs.upload-package-artifact == 'true'
      - name: "Prepare providers packages"
        shell: bash
        run: >
          breeze release-management prepare-provider-packages
          --package-list-file ./prod_image_installed_providers.txt
          --package-format wheel
        if: >
          inputs.do-build == 'true' &&
          inputs.upload-package-artifact == 'true' &&
          inputs.build-provider-packages == 'true'
      - name: "Prepare chicken-eggs provider packages"
        shell: bash
        run: >
          breeze release-management prepare-provider-packages
          --package-format wheel ${{ inputs.chicken-egg-providers }}
        if: >
          inputs.do-build  == 'true' &&
          inputs.upload-package-artifact == 'true' &&
          inputs.chicken-egg-providers != ''
      - name: "Prepare airflow package"
        shell: bash
        run: >
          breeze release-management prepare-airflow-package --package-format wheel
        if: inputs.do-build  == 'true' && inputs.upload-package-artifact == 'true'
      - name: "Upload prepared packages as artifacts"
        uses: actions/upload-artifact@v4
        with:
          name: prod-packages
          path: ./dist
          retention-days: 7
          if-no-files-found: error
        if: inputs.do-build  == 'true' && inputs.upload-package-artifact == 'true'

  build-prod-images:
    strategy:
      fail-fast: true
      matrix:
        # yamllint disable-line rule:line-length
        python-version: ${{ inputs.do-build == 'true' && fromJSON(inputs.python-versions) || fromJSON('[""]') }}
    timeout-minutes: 80
    name: "\
${{ inputs.do-build == 'true' && 'Build' || 'Skip building' }} \
PROD ${{ inputs.build-type }} image\
${{ matrix.python-version }}${{ inputs.do-build == 'true' && ':' || '' }}\
${{ inputs.do-build == 'true' && inputs.image-tag || '' }}"
    runs-on: ${{ fromJSON(inputs.runs-on-as-json-public) }}
    needs:
      - build-prod-packages
    env:
      BACKEND: sqlite
      DEFAULT_BRANCH: ${{ inputs.branch }}
      DEFAULT_CONSTRAINTS_BRANCH: ${{ inputs.constraints-branch }}
      VERSION_SUFFIX_FOR_PYPI: ${{ inputs.branch == 'main' && 'dev0' || '' }}
      INCLUDE_NOT_READY_PROVIDERS: "true"
      # You can override CONSTRAINTS_GITHUB_REPOSITORY by setting secret in your repo but by default the
      # Airflow one is going to be used
      CONSTRAINTS_GITHUB_REPOSITORY: >-
        ${{ secrets.CONSTRAINTS_GITHUB_REPOSITORY != '' &&
        secrets.CONSTRAINTS_GITHUB_REPOSITORY || 'apache/airflow' }}
      # In builds from forks, this token is read-only. For scheduled/direct push it is WRITE one
      GITHUB_REPOSITORY: ${{ github.repository }}
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      GITHUB_USERNAME: ${{ github.actor }}
      USE_UV: ${{ inputs.use-uv }}
      VERBOSE: "true"
    steps:
      - name: "Cleanup repo"
        shell: bash
        run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm -rf /workspace/*"
        if: inputs.do-build == 'true'
      - name: "Checkout target branch"
        uses: actions/checkout@v4
        with:
          persist-credentials: false
      - name: "Checkout target commit"
        uses: ./.github/actions/checkout_target_commit
        with:
          target-commit-sha: ${{ inputs.target-commit-sha }}
          pull-request-target: ${{ inputs.pull-request-target }}
          is-committer-build: ${{ inputs.is-committer-build }}
        if: inputs.do-build == 'true'
      - name: "Cleanup docker"
        run: ./scripts/ci/cleanup_docker.sh
        if: inputs.do-build == 'true'
      - name: "Install Breeze"
        uses: ./.github/actions/breeze
        if: inputs.do-build == 'true'
      - name: "Regenerate dependencies in case they was modified manually so that we can build an image"
        shell: bash
        run: |
          pip install rich>=12.4.4 pyyaml
          python scripts/ci/pre_commit/update_providers_dependencies.py
        if: inputs.do-build == 'true' && inputs.upgrade-to-newer-dependencies != 'false'
      - name: "Cleanup dist and context file"
        shell: bash
        run: rm -fv ./dist/* ./docker-context-files/*
        if: inputs.do-build == 'true'
      - name: "Download packages prepared as artifacts"
        uses: actions/download-artifact@v4
        with:
          name: prod-packages
          path: ./docker-context-files
        if: inputs.do-build == 'true'
      - name: "Download constraints"
        uses: actions/download-artifact@v4
        with:
          name: constraints
          path: ./docker-context-files
        if: inputs.do-build == 'true'
      - name: Login to ghcr.io
        shell: bash
        run: echo "${{ env.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
        if: inputs.do-build == 'true'
      - name: "Build PROD images w/ source providers ${{ matrix.python-version }}:${{ inputs.image-tag }}"
        shell: bash
        run: >
          breeze prod-image build --tag-as-latest --image-tag "${{ inputs.image-tag }}"
          --commit-sha "${{ github.sha }}"
          --install-packages-from-context --airflow-constraints-mode constraints-source-providers
          --use-constraints-for-context-packages --python "${{ matrix.python-version }}"
        env:
          PUSH: ${{ inputs.push-image }}
          DOCKER_CACHE: ${{ inputs.docker-cache }}
          DEBIAN_VERSION: ${{ inputs.debian-version }}
          INSTALL_MYSQL_CLIENT_TYPE: ${{ inputs.install-mysql-client-type }}
          UPGRADE_TO_NEWER_DEPENDENCIES: ${{ inputs.upgrade-to-newer-dependencies }}
          INCLUDE_NOT_READY_PROVIDERS: "true"
        if: inputs.do-build == 'true' && inputs.build-provider-packages == 'true'
      - name: "Build PROD images with PyPi providers ${{ matrix.python-version }}:${{ inputs.image-tag }}"
        shell: bash
        run: >
          breeze prod-image build --builder airflow_cache --tag-as-latest
          --image-tag "${{ inputs.image-tag }}" --commit-sha "${{ github.sha }}"
          --install-packages-from-context --airflow-constraints-mode constraints
          --use-constraints-for-context-packages --python "${{ matrix.python-version }}"
        env:
          PUSH: ${{ inputs.push-image }}
          DOCKER_CACHE: ${{ inputs.docker-cache }}
          DEBIAN_VERSION: ${{ inputs.debian-version }}
          INSTALL_MYSQL_CLIENT_TYPE: ${{ inputs.install-mysql-client-type }}
          UPGRADE_TO_NEWER_DEPENDENCIES: ${{ inputs.upgrade-to-newer-dependencies }}
          INCLUDE_NOT_READY_PROVIDERS: "true"
        if: inputs.do-build == 'true' && inputs.build-provider-packages != 'true'
      - name: Verify PROD image ${{ matrix.python-version }}:${{ inputs.image-tag }}
        run: >
          breeze prod-image verify --image-tag "${{ inputs.image-tag }}"
          --python "${{ matrix.python-version }}"
        if: inputs.do-build == 'true'


PROD images extra checks

# 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.
#
---
name: PROD images extra checks
on:  # yamllint disable-line rule:truthy
  workflow_call:
    inputs:
      runs-on-as-json-public:
        description: "The array of labels (in json form) determining public runners."
        required: true
        type: string
      python-versions:
        description: "JSON-formatted array of Python versions to build images from"
        required: true
        type: string
      default-python-version:
        description: "Default Python version to use for the image"
        required: true
        type: string
      branch:
        description: "Branch used to run the CI jobs in (main/v2_*_test)."
        required: true
        type: string
      use-uv:
        description: "Whether to use uv to build the image (true/false)"
        required: true
        type: string
      image-tag:
        required: true
        type: string
      build-provider-packages:
        description: "Whether to build provider packages (true/false). If false providers are from PyPI"
        required: true
        type: string
      upgrade-to-newer-dependencies:
        description: "Whether to attempt to upgrade image to newer dependencies (false/RANDOM_VALUE)"
        required: true
        type: string
      chicken-egg-providers:
        description: "Space-separated list of providers that should be installed from context files"
        required: true
        type: string
      constraints-branch:
        description: "Branch used to construct constraints URL from."
        required: true
        type: string
      docker-cache:
        description: "Docker cache specification to build the image (registry, local, disabled)."
        required: true
        type: string
jobs:
  bullseye-image:
    uses: ./.github/workflows/prod-image-build.yml
    with:
      runs-on-as-json-public: ${{ inputs.runs-on-as-json-public }}
      build-type: "Bullseye"
      upload-package-artifact: "false"
      image-tag: bullseye-${{ inputs.image-tag }}
      debian-version: "bullseye"
      python-versions: ${{ inputs.python-versions }}
      default-python-version: ${{ inputs.default-python-version }}
      platform: "linux/amd64"
      branch: ${{ inputs.branch }}
      # Always build images during the extra checks and never push them
      push-image: "false"
      use-uv: ${{ inputs.use-uv }}
      build-provider-packages: ${{ inputs.build-provider-packages }}
      upgrade-to-newer-dependencies: ${{ inputs.upgrade-to-newer-dependencies }}
      chicken-egg-providers: ${{ inputs.chicken-egg-providers }}
      constraints-branch: ${{ inputs.constraints-branch }}
      docker-cache: ${{ inputs.docker-cache }}

  myssql-client-image:
    uses: ./.github/workflows/prod-image-build.yml
    with:
      runs-on-as-json-public: ${{ inputs.runs-on-as-json-public }}
      build-type: "MySQL Client"
      upload-package-artifact: "false"
      image-tag: mysql-${{ inputs.image-tag }}
      install-mysql-client-type: "mysql"
      python-versions: ${{ inputs.python-versions }}
      default-python-version: ${{ inputs.default-python-version }}
      platform: "linux/amd64"
      branch: ${{ inputs.branch }}
      # Always build images during the extra checks and never push them
      push-image: "false"
      use-uv: ${{ inputs.use-uv }}
      build-provider-packages: ${{ inputs.build-provider-packages }}
      upgrade-to-newer-dependencies: ${{ inputs.upgrade-to-newer-dependencies }}
      chicken-egg-providers: ${{ inputs.chicken-egg-providers }}
      constraints-branch: ${{ inputs.constraints-branch }}
      docker-cache: ${{ inputs.docker-cache }}

  pip-image:
    uses: ./.github/workflows/prod-image-build.yml
    # Skip testing PIP image on release branches as all images there are built with pip
    if: ${{ inputs.use-uv == 'true' }}
    with:
      runs-on-as-json-public: ${{ inputs.runs-on-as-json-public }}
      build-type: "pip"
      upload-package-artifact: "false"
      image-tag: mysql-${{ inputs.image-tag }}
      install-mysql-client-type: "mysql"
      python-versions: ${{ inputs.python-versions }}
      default-python-version: ${{ inputs.default-python-version }}
      platform: "linux/amd64"
      branch: ${{ inputs.branch }}
      # Always build images during the extra checks and never push them
      push-image: "false"
      use-uv: "false"
      build-provider-packages: ${{ inputs.build-provider-packages }}
      upgrade-to-newer-dependencies: ${{ inputs.upgrade-to-newer-dependencies }}
      chicken-egg-providers: ${{ inputs.chicken-egg-providers }}
      constraints-branch: ${{ inputs.constraints-branch }}
      docker-cache: ${{ inputs.docker-cache }}