Build CI 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 CI images
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
      runs-on-as-json-self-hosted:
        description: "The array of labels (in json form) determining self-hosted runners."
        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
      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
      platform:
        description: "Platform for the build - 'linux/amd64' or 'linux/arm64'"
        required: true
        type: string
      push-image:
        description: "Whether to push image to the registry (true/false)"
        required: false
        default: "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
      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
      upgrade-to-newer-dependencies:
        description: "Whether to attempt to upgrade image to newer dependencies (false/RANDOM_VALUE)"
        required: true
        type: string
      docker-cache:
        description: "Docker cache specification to build the image (registry, local, disabled)."
        required: true
        type: string
jobs:
  build-ci-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: 110
    name: "\
${{ inputs.do-build == 'true' && 'Build' || 'Skip building' }} \
CI ${{ inputs.platform }} image\
${{ matrix.python-version }}${{ inputs.do-build == 'true' && ':' || '' }}\
${{ inputs.do-build == 'true' && inputs.image-tag || '' }}"
    # The ARM images need to be built using self-hosted runners as ARM macos public runners
    # do not yet allow us to run docker effectively and fast.
    # https://github.com/actions/runner-images/issues/9254#issuecomment-1917916016
    # https://github.com/abiosoft/colima/issues/970
    # https://github.com/actions/runner/issues/1456
    # See https://github.com/apache/airflow/pull/38640
    # NOTE!!!!! This has to be put in one line for runs-on to recognize the "fromJSON" properly !!!!
    # adding space before (with >) apparently turns the `runs-on` processed line into a string "Array"
    # instead of an array of strings.
    # yamllint disable-line rule:line-length
    runs-on: ${{ (inputs.platform == 'linux/amd64') && fromJSON(inputs.runs-on-as-json-public) || fromJSON(inputs.runs-on-as-json-self-hosted) }}
    env:
      BACKEND: sqlite
      DEFAULT_BRANCH: ${{ inputs.branch }}
      DEFAULT_CONSTRAINTS_BRANCH: ${{ inputs.constraints-branch }}
      VERSION_SUFFIX_FOR_PYPI: "dev0"
      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
        if: inputs.do-build == 'true'
        with:
          target-commit-sha: ${{ inputs.target-commit-sha }}
          pull-request-target: ${{ inputs.pull-request-target }}
          is-committer-build: ${{ inputs.is-committer-build }}
      - 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 were 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: "Start ARM instance"
        run: ./scripts/ci/images/ci_start_arm_instance_and_connect_to_docker.sh
        if: inputs.do-build == 'true' && inputs.platform == 'linux/arm64'
      - name: Login to ghcr.io
        run: echo "${{ env.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
        if: inputs.do-build == 'true'
      - name: >
          Build ${{ inputs.push-image == 'true' && ' & push ' || '' }}
          ${{ inputs.platform }}:${{ matrix.python-version }}:${{ inputs.image-tag }}
        run: >
          breeze ci-image build --builder airflow_cache --tag-as-latest --image-tag "${{ inputs.image-tag }}"
          --python "${{ matrix.python-version }}" --platform "${{ inputs.platform }}"
        env:
          DOCKER_CACHE: ${{ inputs.docker-cache }}
          INSTALL_MYSQL_CLIENT_TYPE: ${{ inputs.install-mysql-client-type }}
          UPGRADE_TO_NEWER_DEPENDENCIES: ${{ inputs.upgrade-to-newer-dependencies }}
          # 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 }}
          PUSH: ${{ inputs.push-image }}
          VERBOSE: "true"
        if: inputs.do-build == 'true'
      - name: "Stop ARM instance"
        run: ./scripts/ci/images/ci_stop_arm_instance.sh
        if: always() && inputs.do-build == 'true' && inputs.platform == 'linux/arm64'


Helm 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: Helm tests
on:  # yamllint disable-line rule:truthy
  workflow_call:
    inputs:
      runs-on-as-json-default:
        description: "The array of labels (in json form) determining default runner used for the build."
        required: true
        type: string
      runs-on-as-json-public:
        description: "The array of labels (in json form) determining public runners."
        required: true
        type: string
      helm-test-packages:
        description: "Stringified JSON array of helm test packages to test"
        required: true
        type: string
      image-tag:
        description: "Tag to set for the image"
        required: true
        type: string
      default-python-version:
        description: "Which version of python should be used by default"
        required: true
        type: string
jobs:
  tests-helm:
    timeout-minutes: 80
    name: "Unit tests Helm: ${{ matrix.helm-test-package }}"
    runs-on: ${{ fromJSON(inputs.runs-on-as-json-default) }}
    strategy:
      fail-fast: false
      matrix:
        helm-test-package: ${{ fromJSON(inputs.helm-test-packages) }}
    env:
      # Always use default Python version of CI image for preparing packages
      PYTHON_MAJOR_MINOR_VERSION: "${{ inputs.default-python-version }}"
      PARALLEL_TEST_TYPES: "Helm"
      BACKEND: "none"
      DB_RESET: "false"
      JOB_ID: "helm-tests"
      USE_XDIST: "true"
      IMAGE_TAG: "${{ inputs.image-tag }}"
      GITHUB_REPOSITORY: ${{ github.repository }}
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      GITHUB_USERNAME: ${{ github.actor }}
      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: "Prepare breeze & CI image: ${{inputs.default-python-version}}:${{inputs.image-tag}}"
        uses: ./.github/actions/prepare_breeze_and_image
      - name: "Helm Unit Tests: ${{ matrix.helm-test-package }}"
        run: breeze testing helm-tests --helm-test-package "${{ matrix.helm-test-package }}"

  tests-helm-release:
    timeout-minutes: 80
    name: "Release Helm"
    runs-on: ${{ fromJSON(inputs.runs-on-as-json-public) }}
    env:
      PYTHON_MAJOR_MINOR_VERSION: "${{inputs.default-python-version}}"
    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: Setup git for tagging
        run: |
          git config --global user.email "name@example.com"
          git config --global user.name "Your Name"
      - name: "Remove old artifacts"
        run: rm -rf dist/*
      - name: "Setup k8s/helm environment"
        run: breeze k8s setup-env
      - name: "Install helm gpg plugin"
        run: >
          breeze k8s shell -c "helm plugin install https://github.com/technosophos/helm-gpg
          --version 6303407eb63deaeb1b2f24de611e3468a27ec05b" || true
      - name: "Helm release tarball"
        run: >
          breeze release-management prepare-helm-chart-tarball --ignore-version-check --override-tag
          --skip-tag-signing --version 0.0.0 --version-suffix dev0
      - name: Generate GPG key for signing
        # Sometimes the key will be already added to the keyring, so we ignore the error
        run: gpg --batch --passphrase '' --quick-gen-key dev@airflow.apache.org default default || true
      - name: "Helm release package"
        run: >
          breeze release-management prepare-helm-chart-package --sign-email dev@airflow.apache.org
      - name: "Sign artifacts for ASF distribution"
        run: ./dev/sign.sh dist/airflow-*.tgz dist/airflow-*-source.tar.gz
        env:
          SIGN_WITH: dev@airflow.apache.org
      - name: "Fetch Git Tags"
        run: git fetch --tags
      - name: "Test helm chart issue generation automatically"
        # Adding same tags for now, will address in a follow-up
        run: >
          breeze release-management generate-issue-content-helm-chart --limit-pr-count 10
          --latest --verbose
      - name: "Upload Helm artifacts"
        uses: actions/upload-artifact@v4
        with:
          name: Helm artifacts
          path: ./dist/airflow-*
          retention-days: 7
          if-no-files-found: error


Push image cache

# 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: Push image cache
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
      runs-on-as-json-self-hosted:
        description: "The array of labels (in json form) determining self-hosted runners."
        required: true
        type: string
      cache-type:
        description: "Type of cache to push (Early / Regular)."
        required: true
        type: string
      include-prod-images:
        description: "Whether to build PROD image cache additionally to CI image cache (true/false)."
        required: true
        type: string
      push-latest-images:
        description: "Whether to also push latest images (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"
      platform:
        description: "Platform for the build - 'linux/amd64' or 'linux/arm64'"
        required: true
        type: string
      python-versions:
        description: "JSON-formatted array of Python versions to build images from"
        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
      use-uv:
        description: "Whether to use uv to build the image (true/false)"
        required: true
        type: string
      include-success-outputs:
        description: "Whether to include success outputs (true/false)."
        required: true
        type: string
      docker-cache:
        description: "Docker cache specification to build the image (registry, local, disabled)."
        required: true
        type: string
jobs:
  push-ci-image-cache:
    name: "Push CI ${{ inputs.cache-type }}:${{ matrix.python }} image cache "
    # NOTE!!!!! This has to be put in one line for runs-on to recognize the "fromJSON" properly !!!!
    # adding space before (with >) apparently turns the `runs-on` processed line into a string "Array"
    # instead of an array of strings.
    # yamllint disable-line rule:line-length
    runs-on: ${{ (inputs.platform == 'linux/amd64') && fromJSON(inputs.runs-on-as-json-public) || fromJSON(inputs.runs-on-as-json-self-hosted) }}
    strategy:
      fail-fast: false
      matrix:
        python: ${{ fromJSON(inputs.python-versions) }}
    env:
      COMMIT_SHA: ${{ github.sha }}
      # 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
      DEBIAN_VERSION: ${{ inputs.debian-version }}
      DEFAULT_BRANCH: ${{ inputs.branch }}
      DEFAULT_CONSTRAINTS_BRANCH: ${{ inputs.constraints-branch }}
      DOCKER_CACHE: ${{ inputs.docker-cache }}
      GITHUB_REPOSITORY: ${{ github.repository }}
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      GITHUB_USERNAME: ${{ github.actor }}
      INCLUDE_SUCCESS_OUTPUTS: "${{ inputs.include-success-outputs }}"
      INSTALL_MYSQL_CLIENT_TYPE: ${{ inputs.install-mysql-client-type }}
      USE_UV: ${{ inputs.use-uv }}
      UPGRADE_TO_NEWER_DEPENDENCIES: "false"
      VERBOSE: "true"
      VERSION_SUFFIX_FOR_PYPI: "dev0"
    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: "Start ARM instance"
        run: ./scripts/ci/images/ci_start_arm_instance_and_connect_to_docker.sh
        if: inputs.platform == 'linux/arm64'
      - name: Login to ghcr.io
        run: echo "${{ env.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
      - name: "Push CI ${{ inputs.cache-type }} cache: ${{ matrix.python }} ${{ inputs.platform }}"
        run: >
          breeze ci-image build --builder airflow_cache --prepare-buildx-cache
          --platform "${{ inputs.platform }}" --python ${{ matrix.python }}
      - name: "Stop ARM instance"
        run: ./scripts/ci/images/ci_stop_arm_instance.sh
        if: always() && inputs.platform == 'linux/arm64'
      - name: "Push CI latest images: ${{ matrix.python }} (linux/amd64 only)"
        run: >
          breeze ci-image build --builder airflow_cache --push
          --python "${{ matrix.python }}" --platform "${{ inputs.platform }}"
        if: inputs.push-latest-images == 'true' && inputs.platform == 'linux/amd64'

  push-prod-image-cache:
    name: "Push PROD ${{ inputs.cache-type }}:${{ matrix.python }} image cache"
    # NOTE!!!!! This has to be put in one line for runs-on to recognize the "fromJSON" properly !!!!
    # adding space before (with >) apparently turns the `runs-on` processed line into a string "Array"
    # instead of an array of strings.
    # yamllint disable-line rule:line-length
    runs-on: ${{ (inputs.platform == 'linux/amd64') && fromJSON(inputs.runs-on-as-json-public) || fromJSON(inputs.runs-on-as-json-self-hosted) }}
    strategy:
      fail-fast: false
      matrix:
        python: ${{ fromJSON(inputs.python-versions) }}
    env:
      COMMIT_SHA: ${{ github.sha }}
      # 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
      DEBIAN_VERSION: ${{ inputs.debian-version }}
      DEFAULT_BRANCH: ${{ inputs.branch }}
      DEFAULT_CONSTRAINTS_BRANCH: ${{ inputs.constraints-branch }}
      DOCKER_CACHE: ${{ inputs.docker-cache }}
      GITHUB_REPOSITORY: ${{ github.repository }}
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      GITHUB_USERNAME: ${{ github.actor }}
      INSTALL_MYSQL_CLIENT_TYPE: ${{ inputs.install-mysql-client-type }}
      UPGRADE_TO_NEWER_DEPENDENCIES: "false"
      USE_UV: ${{ inputs.branch == 'main' && inputs.use-uv || 'false' }}
      VERBOSE: "true"
      VERSION_SUFFIX_FOR_PYPI: "dev0"
    if: inputs.include-prod-images == '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: "Cleanup dist and context file"
        run: rm -fv ./dist/* ./docker-context-files/*
      - name: "Download packages prepared as artifacts"
        uses: actions/download-artifact@v4
        with:
          name: prod-packages
          path: ./docker-context-files
      - name: "Start ARM instance"
        run: ./scripts/ci/images/ci_start_arm_instance_and_connect_to_docker.sh
        if: inputs.platform == 'linux/arm64'
      - name: Login to ghcr.io
        run: echo "${{ env.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
      - name: "Push PROD ${{ inputs.cache-type }} cache: ${{ matrix.python-version }} ${{ inputs.platform }}"
        run: >
          breeze prod-image build --builder airflow_cache
          --prepare-buildx-cache --platform "${{ inputs.platform }}"
          --install-packages-from-context --airflow-constraints-mode constraints-source-providers
          --python ${{ matrix.python }}
      - name: "Stop ARM instance"
        run: ./scripts/ci/images/ci_stop_arm_instance.sh
        if: always() && inputs.platform == 'linux/arm64'
        # We only push "AMD" images as it is really only needed for any kind of automated builds in CI
        # and currently there is not an easy way to make multi-platform image from two separate builds
        # and we can do it after we stopped the ARM instance as it is not needed anymore
      - name: "Push PROD latest image: ${{ matrix.python }} (linux/amd64 ONLY)"
        run: >
          breeze prod-image build --builder airflow_cache --install-packages-from-context
          --push --platform "${{ inputs.platform }}"
        if: inputs.push-latest-images == 'true' && inputs.platform == 'linux/amd64'


Provider 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: Provider tests
on:  # yamllint disable-line rule:truthy
  workflow_call:
    inputs:
      runs-on-as-json-default:
        description: "The array of labels (in json form) determining default runner used for the build."
        required: true
        type: string
      image-tag:
        description: "Tag to set for the image"
        required: true
        type: string
      default-python-version:
        description: "Which version of python should be used by default"
        required: true
        type: string
      upgrade-to-newer-dependencies:
        description: "Whether to upgrade to newer dependencies"
        required: true
        type: string
      affected-providers-list-as-string:
        description: "List of affected providers as string"
        required: false
        type: string
      providers-compatibility-checks:
        description: >
          JSON-formatted array of providers compatibility checks in the form of array of dicts
          (airflow-version, python-versions, remove-providers, run-tests)
        required: true
        type: string
      providers-test-types-list-as-string:
        description: "List of parallel provider test types as string"
        required: true
        type: string
      skip-provider-tests:
        description: "Whether to skip provider tests (true/false)"
        required: true
        type: string
      python-versions:
        description: "JSON-formatted array of Python versions to build images from"
        required: true
        type: string
jobs:
  prepare-install-verify-provider-packages-wheel:
    timeout-minutes: 80
    name: "Provider packages wheel build and verify"
    runs-on: ${{ fromJSON(inputs.runs-on-as-json-default) }}
    env:
      GITHUB_REPOSITORY: ${{ github.repository }}
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      GITHUB_USERNAME: ${{ github.actor }}
      IMAGE_TAG: "${{ inputs.image-tag }}"
      INCLUDE_NOT_READY_PROVIDERS: "true"
      PYTHON_MAJOR_MINOR_VERSION: "${{ inputs.default-python-version }}"
      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: >
          Prepare breeze & CI image: ${{ inputs.default-python-version }}:${{ inputs.image-tag }}
        uses: ./.github/actions/prepare_breeze_and_image
      - name: "Cleanup dist files"
        run: rm -fv ./dist/*
      - name: "Prepare provider documentation"
        run: >
          breeze release-management prepare-provider-documentation --include-not-ready-providers
          --non-interactive
      - name: "Prepare provider packages: wheel"
        run: >
          breeze release-management prepare-provider-packages --include-not-ready-providers
          --version-suffix-for-pypi dev0 --package-format wheel
      - name: "Prepare airflow package: wheel"
        run: breeze release-management prepare-airflow-package --version-suffix-for-pypi dev0
      - name: "Verify wheel packages with twine"
        run: |
          pipx uninstall twine || true
          pipx install twine && twine check dist/*.whl
      - name: "Test providers issue generation automatically"
        run: >
          breeze release-management generate-issue-content-providers
          --only-available-in-dist --disable-progress
      - name: "Generate source constraints from CI image"
        shell: bash
        run: >
          breeze release-management generate-constraints
          --airflow-constraints-mode constraints-source-providers --answer yes
      - name: "Install and verify all provider packages and airflow via wheel files"
        run: >
          breeze release-management verify-provider-packages
          --use-packages-from-dist
          --package-format wheel
          --use-airflow-version wheel
          --airflow-constraints-reference default
          --providers-constraints-location
          /files/constraints-${{env.PYTHON_MAJOR_MINOR_VERSION}}/constraints-source-providers-${{env.PYTHON_MAJOR_MINOR_VERSION}}.txt
        env:
          AIRFLOW_SKIP_CONSTRAINTS: "${{ inputs.upgrade-to-newer-dependencies }}"
      - name: "Prepare airflow package: wheel without suffix and skipping the tag check"
        run: >
          breeze release-management prepare-provider-packages --skip-tag-check --package-format wheel

  prepare-install-provider-packages-sdist:
    timeout-minutes: 80
    name: "Provider packages sdist build and install"
    runs-on: ${{ fromJSON(inputs.runs-on-as-json-default) }}
    env:
      GITHUB_REPOSITORY: ${{ github.repository }}
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      GITHUB_USERNAME: ${{ github.actor }}
      IMAGE_TAG: "${{ inputs.image-tag }}"
      INCLUDE_NOT_READY_PROVIDERS: "true"
      PYTHON_MAJOR_MINOR_VERSION: "${{ inputs.default-python-version }}"
      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: >
          Prepare breeze & CI image: ${{ inputs.default-python-version }}:${{ inputs.image-tag }}
        uses: ./.github/actions/prepare_breeze_and_image
      - name: "Cleanup dist files"
        run: rm -fv ./dist/*
      - name: "Prepare provider packages: sdist"
        run: >
          breeze release-management prepare-provider-packages --include-not-ready-providers
          --version-suffix-for-pypi dev0 --package-format sdist
          ${{ inputs.affected-providers-list-as-string }}
      - name: "Prepare airflow package: sdist"
        run: >
          breeze release-management prepare-airflow-package
          --version-suffix-for-pypi dev0 --package-format sdist
      - name: "Verify sdist packages with twine"
        run: |
          pipx uninstall twine || true
          pipx install twine && twine check dist/*.tar.gz
      - name: "Generate source constraints from CI image"
        shell: bash
        run: >
          breeze release-management generate-constraints
          --airflow-constraints-mode constraints-source-providers --answer yes
      - name: "Install all provider packages and airflow via sdist files"
        run: >
          breeze release-management install-provider-packages
          --use-packages-from-dist
          --package-format sdist
          --use-airflow-version sdist
          --airflow-constraints-reference default
          --providers-constraints-location
          /files/constraints-${{env.PYTHON_MAJOR_MINOR_VERSION}}/constraints-source-providers-${{env.PYTHON_MAJOR_MINOR_VERSION}}.txt
          --run-in-parallel
        if: inputs.affected-providers-list-as-string == ''
      - name: "Install affected provider packages and airflow via sdist files"
        run: >
          breeze release-management install-provider-packages
          --use-packages-from-dist
          --package-format sdist
          --use-airflow-version sdist
          --airflow-constraints-reference default
          --providers-constraints-location
          /files/constraints-${{env.PYTHON_MAJOR_MINOR_VERSION}}/constraints-source-providers-${{env.PYTHON_MAJOR_MINOR_VERSION}}.txt
          --run-in-parallel
        if: inputs.affected-providers-list-as-string != ''

  providers-compatibility-checks:
    timeout-minutes: 80
    name: Compat ${{ matrix.airflow-version }}:P${{ matrix.python-version }} provider check
    runs-on: ${{ fromJSON(inputs.runs-on-as-json-default) }}
    strategy:
      fail-fast: false
      matrix:
        include: ${{fromJSON(inputs.providers-compatibility-checks)}}
    env:
      GITHUB_REPOSITORY: ${{ github.repository }}
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      GITHUB_USERNAME: ${{ github.actor }}
      IMAGE_TAG: "${{ inputs.image-tag }}"
      INCLUDE_NOT_READY_PROVIDERS: "true"
      PYTHON_MAJOR_MINOR_VERSION: "${{ inputs.default-python-version }}"
      VERSION_SUFFIX_FOR_PYPI: "dev0"
      VERBOSE: "true"
    if: inputs.skip-provider-tests != '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: "Prepare breeze & CI image: ${{ matrix.python-version }}:${{ inputs.image-tag }}"
        uses: ./.github/actions/prepare_breeze_and_image
      - name: "Cleanup dist files"
        run: rm -fv ./dist/*
      - name: "Prepare provider packages: wheel"
        run: >
          breeze release-management prepare-provider-packages --include-not-ready-providers
          --package-format wheel
      - name: >
          Remove incompatible Airflow
          ${{ matrix.airflow-version }}:Python ${{ matrix.python-version }} provider packages
        run: |
          for provider in ${{ matrix.remove-providers }}; do
            echo "Removing incompatible provider: ${provider}"
            rm -vf dist/apache_airflow_providers_${provider/./_}*
          done
        if: matrix.remove-providers != ''
      - name: "Download airflow package: wheel"
        run: |
          pip download "apache-airflow==${{ matrix.airflow-version }}" -d dist --no-deps
      - name: >
          Install and verify all provider packages and airflow on
          Airflow ${{ matrix.airflow-version }}:Python ${{ matrix.python-version }}
        # We do not need to run import check if we run tests, the tests should cover all the import checks
        # automatically
        if: matrix.run-tests != 'true'
        run: >
          breeze release-management verify-provider-packages
          --use-packages-from-dist
          --package-format wheel
          --use-airflow-version wheel
          --airflow-constraints-reference constraints-${{matrix.airflow-version}}
          --providers-skip-constraints
          --install-airflow-with-constraints
      - name: >
          Run provider unit tests on
          Airflow ${{ matrix.airflow-version }}:Python ${{ matrix.python-version }}
        if: matrix.run-tests == 'true'
        run: >
          breeze testing tests --run-in-parallel
          --parallel-test-types "${{ inputs.providers-test-types-list-as-string }}"
          --use-packages-from-dist
          --package-format wheel
          --use-airflow-version "${{ matrix.airflow-version }}"
          --airflow-constraints-reference constraints-${{matrix.airflow-version}}
          --install-airflow-with-constraints
          --providers-skip-constraints
          --skip-providers "${{ matrix.remove-providers }}"


Unit 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: Unit tests
on:  # yamllint disable-line rule:truthy
  workflow_call:
    inputs:
      runs-on-as-json-default:
        description: "The array of labels (in json form) determining default runner used for the build."
        required: true
        type: string
      backend:
        description: "The backend to run the tests on"
        required: true
        type: string
      test-scope:
        description: "The scope of the test to run: ('DB', 'Non-DB', 'All', 'ARM collection')"
        required: true
        type: string
      test-name:
        description: "The name of the test to run"
        required: true
        type: string
      test-name-separator:
        description: "The separator to use after the test name"
        required: false
        default: ":"
        type: string
      image-tag:
        description: "Tag to set for the image"
        required: true
        type: string
      python-versions:
        description: "The list of python versions (stringified JSON array) to run the tests on."
        required: true
        type: string
      backend-versions:
        description: "The list of backend versions (stringified JSON array) to run the tests on."
        required: true
        type: string
      excludes:
        description: "Excluded combos (stringified JSON array of python-version/backend-version dicts)"
        required: true
        type: string
      parallel-test-types-list-as-string:
        description: "The list of parallel test types to run separated by spaces"
        required: true
        type: string
      run-migration-tests:
        description: "Whether to run migration tests or not (true/false)"
        required: false
        default: "false"
        type: string
      run-coverage:
        description: "Whether to run coverage or not (true/false)"
        required: true
        type: string
      debug-resources:
        description: "Whether to debug resources or not (true/false)"
        required: true
        type: string
      include-success-outputs:
        description: "Whether to include success outputs or not (true/false)"
        required: false
        default: "false"
        type: string
      downgrade-sqlalchemy:
        description: "Whether to downgrade SQLAlchemy or not (true/false)"
        required: false
        default: "false"
        type: string
      upgrade-boto:
        description: "Whether to upgrade boto or not (true/false)"
        required: false
        default: "false"
        type: string
      pydantic:
        description: "The version of pydantic to use"
        required: false
        default: "v2"
        type: string
      downgrade-pendulum:
        description: "Whether to downgrade pendulum or not (true/false)"
        required: false
        default: "false"
        type: string
      enable-aip-44:
        description: "Whether to enable AIP-44 or not (true/false)"
        required: false
        default: "true"
        type: string
      force-lowest-dependencies:
        description: "Whether to force lowest dependencies for the tests or not (true/false)"
        required: false
        default: "false"
        type: string
      monitor-delay-time-in-seconds:
        description: "How much time to wait between printing parallel monitor summary"
        required: false
        default: 20
        type: number
jobs:
  tests:
    timeout-minutes: 120
    name: "\
      ${{ inputs.test-scope }}:\
      ${{ inputs.test-name }}${{ inputs.test-name-separator }}${{ matrix.backend-version }}:\
      ${{matrix.python-version}}: ${{ inputs.parallel-test-types-list-as-string }}"
    runs-on: ${{ fromJSON(inputs.runs-on-as-json-default) }}
    strategy:
      fail-fast: false
      matrix:
        python-version: "${{fromJSON(inputs.python-versions)}}"
        backend-version: "${{fromJSON(inputs.backend-versions)}}"
        exclude: "${{fromJSON(inputs.excludes)}}"
    env:
      # yamllint disable rule:line-length
      AIRFLOW_ENABLE_AIP_44: "${{ inputs.enable-aip-44 }}"
      BACKEND: "${{ inputs.backend }}"
      BACKEND_VERSION: "${{ matrix.backend-version }}"
      DB_RESET: "true"
      DEBUG_RESOURCES: "${{ inputs.debug-resources }}"
      DOWNGRADE_SQLALCHEMY: "${{ inputs.downgrade-sqlalchemy }}"
      DOWNGRADE_PENDULUM: "${{ inputs.downgrade-pendulum }}"
      ENABLE_COVERAGE: "${{ inputs.run-coverage }}"
      FORCE_LOWEST_DEPENDENCIES: "${{ inputs.force-lowest-dependencies }}"
      GITHUB_REPOSITORY: ${{ github.repository }}
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      GITHUB_USERNAME: ${{ github.actor }}
      IMAGE_TAG: "${{ inputs.image-tag }}"
      INCLUDE_SUCCESS_OUTPUTS: ${{ inputs.include-success-outputs }}
      # yamllint disable rule:line-length
      JOB_ID: "${{ inputs.test-scope }}-${{ inputs.test-name }}-${{inputs.backend}}-${{ matrix.backend-version }}-${{ matrix.python-version }}"
      MOUNT_SOURCES: "skip"
      PARALLEL_TEST_TYPES: "${{ inputs.parallel-test-types-list-as-string }}"
      PYDANTIC: "${{ inputs.pydantic }}"
      PYTHON_MAJOR_MINOR_VERSION: "${{ matrix.python-version }}"
      UPGRADE_BOTO: "${{ inputs.upgrade-boto }}"
      AIRFLOW_MONITOR_DELAY_TIME_IN_SECONDS: "${{inputs.monitor-delay-time-in-seconds}}"
      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: "Prepare breeze & CI image: ${{matrix.python-version}}:${{ inputs.image-tag }}"
        uses: ./.github/actions/prepare_breeze_and_image
      - name: >
          Migration Tests:
          ${{ matrix.python-version }}:${{ inputs.parallel-test-types-list-as-string }}
        uses: ./.github/actions/migration_tests
        if: inputs.run-migration-tests == 'true'
      - name: >
          ${{ inputs.test-scope }} Tests ${{ inputs.test-name }} ${{ matrix.backend-version }}
          Py${{ matrix.python-version }}:${{ inputs.parallel-test-types-list-as-string}}
        run: |
          if [[ "${{ inputs.test-scope }}" == "DB" ]]; then
            breeze testing db-tests \
              --parallel-test-types "${{ inputs.parallel-test-types-list-as-string }}"
          elif [[ "${{ inputs.test-scope }}" == "Non-DB" ]]; then
            breeze testing non-db-tests \
              --parallel-test-types "${{ inputs.parallel-test-types-list-as-string }}"
          elif [[ "${{ inputs.test-scope }}" == "All" ]]; then
            breeze testing tests --run-in-parallel \
              --parallel-test-types "${{ inputs.parallel-test-types-list-as-string }}"
          elif [[ "${{ inputs.test-scope }}" == "Quarantined" ]]; then
            breeze testing tests --test-type "All-Quarantined" || true
          elif [[ "${{ inputs.test-scope }}" == "ARM collection" ]]; then
            breeze testing tests --collect-only --remove-arm-packages
          elif [[ "${{ inputs.test-scope }}" == "System" ]]; then
            breeze testing tests tests/system/example_empty.py --system core
          else
            echo "Unknown test scope: ${{ inputs.test-scope }}"
            exit 1
          fi
      - name: "Post Tests success"
        uses: ./.github/actions/post_tests_success
        with:
          codecov-token: ${{ secrets.CODECOV_TOKEN }}
          python-version: ${{ matrix.python-version }}
        if: success()
      - name: "Post Tests failure"
        uses: ./.github/actions/post_tests_failure
        if: failure()


Special 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: Special tests
on:  # yamllint disable-line rule:truthy
  workflow_call:
    inputs:
      runs-on-as-json-default:
        description: "The array of labels (in json form) determining default runner used for the build."
        required: true
        type: string
      image-tag:
        description: "Tag to set for the image"
        required: true
        type: string
      parallel-test-types-list-as-string:
        description: "The list of parallel test types to run separated by spaces"
        required: true
        type: string
      separate-test-types-list-as-string:
        description: "The list of separate provider test types to run separated by spaces"
        required: true
        type: string
      run-coverage:
        description: "Whether to run coverage or not (true/false)"
        required: true
        type: string
      default-python-version:
        description: "Which version of python should be used by default"
        required: true
        type: string
      python-versions:
        description: "The list of python versions (stringified JSON array) to run the tests on."
        required: true
        type: string
      default-postgres-version:
        description: "The default version of the postgres to use"
        required: true
        type: string
      canary-run:
        description: "Whether to run canary tests or not (true/false)"
        required: true
        type: string
      upgrade-to-newer-dependencies:
        description: "Whether to upgrade to newer dependencies or not (true/false)"
        required: true
        type: string
      debug-resources:
        description: "Whether to debug resources or not (true/false)"
        required: true
        type: string
jobs:
  tests-min-sqlalchemy:
    name: "Min SQLAlchemy test"
    uses: ./.github/workflows/run-unit-tests.yml
    permissions:
      contents: read
      packages: read
    secrets: inherit
    with:
      runs-on-as-json-default: ${{ inputs.runs-on-as-json-default }}
      downgrade-sqlalchemy: "true"
      test-name: "MinSQLAlchemy-Postgres"
      test-scope: "DB"
      backend: "postgres"
      image-tag: ${{ inputs.image-tag }}
      python-versions: "['${{ inputs.default-python-version }}']"
      backend-versions: "['${{ inputs.default-postgres-version }}']"
      excludes: "[]"
      parallel-test-types-list-as-string: ${{ inputs.parallel-test-types-list-as-string }}
      include-success-outputs: ${{ needs.build-info.outputs.include-success-outputs }}
      run-coverage: ${{ inputs.run-coverage }}
      debug-resources: ${{ inputs.debug-resources }}

  tests-boto:
    name: "Latest Boto test"
    uses: ./.github/workflows/run-unit-tests.yml
    permissions:
      contents: read
      packages: read
    secrets: inherit
    with:
      runs-on-as-json-default: ${{ inputs.runs-on-as-json-default }}
      upgrade-boto: "true"
      test-name: "LatestBoto-Postgres"
      test-scope: "All"
      backend: "postgres"
      image-tag: ${{ inputs.image-tag }}
      python-versions: "['${{ inputs.default-python-version }}']"
      backend-versions: "['${{ inputs.default-postgres-version }}']"
      excludes: "[]"
      parallel-test-types-list-as-string: ${{ inputs.parallel-test-types-list-as-string }}
      include-success-outputs: ${{ needs.build-info.outputs.include-success-outputs }}
      run-coverage: ${{ inputs.run-coverage }}
      debug-resources: ${{ inputs.debug-resources }}

  tests-pydantic-v1:
    name: "Pydantic v1 test"
    uses: ./.github/workflows/run-unit-tests.yml
    permissions:
      contents: read
      packages: read
    secrets: inherit
    with:
      runs-on-as-json-default: ${{ inputs.runs-on-as-json-default }}
      pydantic: "v1"
      test-name: "Pydantic-V1-Postgres"
      test-scope: "All"
      backend: "postgres"
      image-tag: ${{ inputs.image-tag }}
      python-versions: "['${{ inputs.default-python-version }}']"
      backend-versions: "['${{ inputs.default-postgres-version }}']"
      excludes: "[]"
      parallel-test-types-list-as-string: ${{ inputs.parallel-test-types-list-as-string }}
      include-success-outputs: ${{ needs.build-info.outputs.include-success-outputs }}
      run-coverage: ${{ inputs.run-coverage }}
      debug-resources: ${{ inputs.debug-resources }}

  tests-pydantic-none:
    name: "Pydantic removed test"
    uses: ./.github/workflows/run-unit-tests.yml
    permissions:
      contents: read
      packages: read
    secrets: inherit
    with:
      runs-on-as-json-default: ${{ inputs.runs-on-as-json-default }}
      pydantic: "none"
      test-name: "Pydantic-Removed-Postgres"
      test-scope: "All"
      backend: "postgres"
      image-tag: ${{ inputs.image-tag }}
      python-versions: "['${{ inputs.default-python-version }}']"
      backend-versions: "['${{ inputs.default-postgres-version }}']"
      excludes: "[]"
      parallel-test-types-list-as-string: ${{ inputs.parallel-test-types-list-as-string }}
      include-success-outputs: ${{ needs.build-info.outputs.include-success-outputs }}
      run-coverage: ${{ inputs.run-coverage }}
      debug-resources: ${{ inputs.debug-resources }}

  tests-pendulum-2:
    name: "Pendulum2 test"
    uses: ./.github/workflows/run-unit-tests.yml
    permissions:
      contents: read
      packages: read
    secrets: inherit
    with:
      runs-on-as-json-default: ${{ inputs.runs-on-as-json-default }}
      downgrade-pendulum: "true"
      test-name: "Pendulum2-Postgres"
      test-scope: "All"
      backend: "postgres"
      image-tag: ${{ inputs.image-tag }}
      python-versions: "['${{ inputs.default-python-version }}']"
      backend-versions: "['${{ inputs.default-postgres-version }}']"
      excludes: "[]"
      parallel-test-types-list-as-string: ${{ inputs.parallel-test-types-list-as-string }}
      include-success-outputs: ${{ needs.build-info.outputs.include-success-outputs }}
      run-coverage: ${{ inputs.run-coverage }}
      debug-resources: ${{ inputs.debug-resources }}

  tests-in-progress-disabled:
    name: "In progress disabled test"
    uses: ./.github/workflows/run-unit-tests.yml
    permissions:
      contents: read
      packages: read
    secrets: inherit
    with:
      runs-on-as-json-default: ${{ inputs.runs-on-as-json-default }}
      enable-aip-44: "false"
      test-name: "InProgressDisabled-Postgres"
      test-scope: "All"
      backend: "postgres"
      image-tag: ${{ inputs.image-tag }}
      python-versions: "['${{ inputs.default-python-version }}']"
      backend-versions: "['${{ inputs.default-postgres-version }}']"
      excludes: "[]"
      parallel-test-types-list-as-string: ${{ inputs.parallel-test-types-list-as-string }}
      include-success-outputs: ${{ needs.build-info.outputs.include-success-outputs }}
      run-coverage: ${{ inputs.run-coverage }}
      debug-resources: ${{ inputs.debug-resources }}

  tests-with-lowest-direct-resolution:
    name: "Lowest direct dependency resolution tests"
    uses: ./.github/workflows/run-unit-tests.yml
    permissions:
      contents: read
      packages: read
    secrets: inherit
    with:
      runs-on-as-json-default: ${{ inputs.runs-on-as-json-default }}
      test-name: "LowestDeps-Postgres"
      force-lowest-dependencies: "true"
      test-scope: "All"
      backend: "postgres"
      image-tag: ${{ inputs.image-tag }}
      python-versions: ${{ inputs.python-versions }}
      backend-versions: "['${{ inputs.default-postgres-version }}']"
      excludes: "[]"
      parallel-test-types-list-as-string: ${{ inputs.separate-test-types-list-as-string }}
      include-success-outputs: ${{ needs.build-info.outputs.include-success-outputs }}
      run-coverage: ${{ inputs.run-coverage }}
      debug-resources: ${{ inputs.debug-resources }}
      monitor-delay-time-in-seconds: 120

  tests-quarantined:
    name: "Quarantined test"
    uses: ./.github/workflows/run-unit-tests.yml
    permissions:
      contents: read
      packages: read
    secrets: inherit
    with:
      runs-on-as-json-default: ${{ inputs.runs-on-as-json-default }}
      test-name: "Postgres"
      test-scope: "Quarantined"
      backend: "postgres"
      image-tag: ${{ inputs.image-tag }}
      python-versions: "['${{ inputs.default-python-version }}']"
      backend-versions: "['${{ inputs.default-postgres-version }}']"
      excludes: "[]"
      parallel-test-types-list-as-string: ${{ inputs.parallel-test-types-list-as-string }}
      include-success-outputs: ${{ needs.build-info.outputs.include-success-outputs }}
      run-coverage: ${{ inputs.run-coverage }}
      debug-resources: ${{ inputs.debug-resources }}

  tests-arm-collection:
    name: "ARM Collection test"
    uses: ./.github/workflows/run-unit-tests.yml
    permissions:
      contents: read
      packages: read
    secrets: inherit
    with:
      runs-on-as-json-default: ${{ inputs.runs-on-as-json-default }}
      test-name: "Postgres"
      test-scope: "ARM collection"
      backend: "postgres"
      image-tag: ${{ inputs.image-tag }}
      python-versions: "['${{ inputs.default-python-version }}']"
      backend-versions: "['${{ inputs.default-postgres-version }}']"
      excludes: "[]"
      parallel-test-types-list-as-string: ${{ inputs.parallel-test-types-list-as-string }}
      include-success-outputs: ${{ needs.build-info.outputs.include-success-outputs }}
      run-coverage: ${{ inputs.run-coverage }}
      debug-resources: ${{ inputs.debug-resources }}

  tests-system:
    name: "System test"
    uses: ./.github/workflows/run-unit-tests.yml
    permissions:
      contents: read
      packages: read
    secrets: inherit
    with:
      runs-on-as-json-default: ${{ inputs.runs-on-as-json-default }}
      test-name: "SystemTest"
      test-scope: "System"
      backend: "postgres"
      image-tag: ${{ inputs.image-tag }}
      python-versions: "['${{ inputs.default-python-version }}']"
      backend-versions: "['${{ inputs.default-postgres-version }}']"
      excludes: "[]"
      parallel-test-types-list-as-string: ${{ inputs.parallel-test-types-list-as-string }}
      include-success-outputs: ${{ needs.build-info.outputs.include-success-outputs }}
      run-coverage: ${{ inputs.run-coverage }}
      debug-resources: ${{ inputs.debug-resources }}


Basic 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: Basic tests
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
      run-www-tests:
        description: "Whether to run WWW tests (true/false)"
        required: true
        type: string
      needs-api-codegen:
        description: "Whether to run API codegen (true/false)"
        required: true
        type: string
      basic-checks-only:
        description: "Whether to run only basic checks (true/false)"
        required: true
        type: string
      skip-pre-commits:
        description: "Whether to skip pre-commits (true/false)"
        required: true
        type: string
      default-python-version:
        description: "Which version of python should be used by default"
        required: true
        type: string
      canary-run:
        description: "Whether to run canary tests (true/false)"
        required: true
        type: string
      latest-versions-only:
        description: "Whether to run only latest version checks (true/false)"
        required: true
        type: string
jobs:
  run-breeze-tests:
    timeout-minutes: 10
    name: Breeze unit tests
    runs-on: ${{ fromJSON(inputs.runs-on-as-json-public) }}
    steps:
      - name: "Cleanup repo"
        shell: bash
        run: docker run -v "${GITHUB_WORKSPACE}:/workspace" -u 0:0 bash -c "rm -rf /workspace/*"
      - uses: actions/checkout@v4
        with:
          # Need to fetch all history for selective checks tests
          fetch-depth: 0
          persist-credentials: false
      - name: "Cleanup docker"
        run: ./scripts/ci/cleanup_docker.sh
      - uses: actions/setup-python@v5
        with:
          python-version: "${{ inputs.default-python-version }}"
          cache: 'pip'
          cache-dependency-path: ./dev/breeze/pyproject.toml
      - run: pip install --editable ./dev/breeze/
      - run: python -m pytest -n auto --color=yes
        working-directory: ./dev/breeze/

  tests-www:
    timeout-minutes: 10
    name: React WWW tests
    runs-on: ${{ fromJSON(inputs.runs-on-as-json-public) }}
    if: inputs.run-www-tests == '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: "Setup node"
        uses: actions/setup-node@v4
        with:
          node-version: 21
      - name: "Cache eslint"
        uses: actions/cache@v4
        with:
          path: 'airflow/www/node_modules'
          key: ${{ runner.os }}-www-node-modules-${{ hashFiles('airflow/www/**/yarn.lock') }}
      - run: yarn --cwd airflow/www/ install --frozen-lockfile --non-interactive
      - run: yarn --cwd airflow/www/ run test
        env:
          FORCE_COLOR: 2

  test-openapi-client:
    timeout-minutes: 10
    name: "Test OpenAPI client"
    runs-on: ${{ fromJSON(inputs.runs-on-as-json-public) }}
    if: inputs.needs-api-codegen == '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:
          fetch-depth: 2
          persist-credentials: false
      - name: "Cleanup docker"
        run: ./scripts/ci/cleanup_docker.sh
      - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
        uses: actions/checkout@v4
        with:
          repository: "apache/airflow-client-python"
          fetch-depth: 1
          persist-credentials: false
          path: ./airflow-client-python
      - name: "Install Breeze"
        uses: ./.github/actions/breeze
      - name: "Generate client with breeze"
        run: >
          breeze release-management prepare-python-client --package-format both
          --version-suffix-for-pypi dev0 --python-client-repo ./airflow-client-python
      - name: "Show diff"
        run: git diff --color HEAD
        working-directory: ./airflow-client-python
      - name: Install hatch
        run: |
          python -m pip install --upgrade pipx
          pipx ensurepath
          pipx install hatch --force
      - name: Run tests
        run: hatch run run-coverage
        env:
          HATCH_ENV: "test"
        working-directory: ./clients/python
      - name: "Install Airflow in editable mode with fab for webserver tests"
        run: pip install -e ".[fab]"
      - name: "Install Python client"
        run: pip install ./dist/apache_airflow_client-*.whl
      - name: "Initialize Airflow DB and start webserver"
        run: |
          airflow db init
          # Let scheduler runs a few loops and get all DAG files from example DAGs serialized to DB
          airflow scheduler --num-runs 100
          airflow users create --username admin --password admin --firstname Admin --lastname Admin \
             --role Admin --email admin@example.org
          killall python || true  # just in case there is a webserver running in the background
          nohup airflow webserver --port 8080 &
          echo "Started webserver"
        env:
          AIRFLOW__API__AUTH_BACKENDS: airflow.api.auth.backend.session,airflow.api.auth.backend.basic_auth
          AIRFLOW__WEBSERVER__EXPOSE_CONFIG: "True"
          AIRFLOW__CORE__LOAD_EXAMPLES: "True"
          AIRFLOW_HOME: "${{ github.workspace }}/airflow_home"
      - name: "Waiting for the webserver to be available"
        run: |
          timeout 30 bash -c 'until nc -z $0 $1; do echo "sleeping"; sleep 1; done' localhost 8080
          sleep 5
      - name: "Run test python client"
        run: python ./clients/python/test_python_client.py
        env:
          FORCE_COLOR: "standard"
      - name: "Stop running webserver"
        run: killall python || true  # just in case there is a webserver running in the background
        if: always()
      - name: "Upload python client packages"
        uses: actions/upload-artifact@v4
        with:
          name: python-client-packages
          path: ./dist/apache_airflow_client-*
          retention-days: 7
          if-no-files-found: error
      - name: "Upload logs from failed tests"
        uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: python-client-failed-logs
          path: "${{ github.workspace }}/airflow_home/logs"
          retention-days: 7

  # Those checks are run if no image needs to be built for checks. This is for simple changes that
  # Do not touch any of the python code or any of the important files that might require building
  # The CI Docker image and they can be run entirely using the pre-commit virtual environments on host
  static-checks-basic-checks-only:
    timeout-minutes: 30
    name: "Static checks: basic checks only"
    runs-on: ${{ fromJSON(inputs.runs-on-as-json-public) }}
    if: inputs.basic-checks-only == '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: "Setup python"
        uses: actions/setup-python@v5
        with:
          python-version: ${{ inputs.default-python-version }}
          cache: 'pip'
          cache-dependency-path: ./dev/breeze/pyproject.toml
      - name: "Setup python"
        uses: actions/setup-python@v5
        with:
          python-version: "${{ inputs.default-python-version }}"
          cache: 'pip'
          cache-dependency-path: ./dev/breeze/pyproject.toml
      - name: "Install Breeze"
        uses: ./.github/actions/breeze
        id: breeze
      - name: Cache pre-commit envs
        uses: actions/cache@v4
        with:
          path: ~/.cache/pre-commit
          # yamllint disable-line rule:line-length
          key: "pre-commit-${{steps.breeze.outputs.host-python-version}}-${{ hashFiles('.pre-commit-config.yaml') }}"
          restore-keys: "\
            pre-commit-${{steps.breeze.outputs.host-python-version}}-\
            ${{ hashFiles('.pre-commit-config.yaml') }}\n
            pre-commit-${{steps.breeze.outputs.host-python-version}}-"
      - name: Fetch incoming commit ${{ github.sha }} with its parent
        uses: actions/checkout@v4
        with:
          ref: ${{ github.sha }}
          fetch-depth: 2
          persist-credentials: false
      - name: "Static checks: basic checks only"
        run: >
          breeze static-checks --show-diff-on-failure --color always --initialize-environment
          --skip-image-upgrade-check --commit-ref "${{ github.sha }}"
        env:
          VERBOSE: "false"
          SKIP_BREEZE_PRE_COMMITS: "true"
          SKIP: ${{ inputs.skip-pre-commits }}
          COLUMNS: "250"

  test-git-clone-on-windows:
    timeout-minutes: 5
    name: "Test git clone on Windows"
    runs-on: ["windows-latest"]
    steps:
      - name: "Checkout ${{ github.ref }} ( ${{ github.sha }} )"
        uses: actions/checkout@v4
        with:
          fetch-depth: 2
          persist-credentials: false

  upgrade-check:
    timeout-minutes: 45
    name: "Upgrade checks"
    runs-on: ${{ fromJSON(inputs.runs-on-as-json-public) }}
    env:
      PYTHON_MAJOR_MINOR_VERSION: "${{ inputs.default-python-version }}"
    if: inputs.canary-run == 'true' && inputs.latest-versions-only != '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
      # Install python from scratch. No cache used. We always want to have fresh version of everything
      - uses: actions/setup-python@v5
        with:
          python-version: "${{ inputs.default-python-version }}"
      - name: "Install latest pre-commit"
        run: pip install pre-commit
      - name: "Autoupdate all pre-commits"
        run: pre-commit autoupdate
      - name: "Run automated upgrade for black"
        run: >
          pre-commit run
          --all-files --show-diff-on-failure --color always --verbose
          --hook-stage manual
          update-black-version
        if: always()
      - name: "Run automated upgrade for build dependencies"
        run: >
          pre-commit run
          --all-files --show-diff-on-failure --color always --verbose
          --hook-stage manual
          update-build-dependencies
        if: always()
      - name: "Run automated upgrade for chart dependencies"
        run: >
          pre-commit run
          --all-files --show-diff-on-failure --color always --verbose
          --hook-stage manual
          update-chart-dependencies
        if: always()
      # For UV we are not failing the upgrade installers check if it is updated because
      # it is upgraded very frequently, so we want to manually upgrade it rather than
      # get notified about it - until it stabilizes in 1.* version
      - name: "Run automated upgrade for uv (open to see if new version is updated)"
        run: >
          pre-commit run
          --all-files --show-diff-on-failure --color always --verbose
          --hook-stage manual update-installers || true
        if: always()
        env:
          UPGRADE_UV: "true"
          UPGRADE_PIP: "false"
      - name: "Run automated upgrade for pip"
        run: >
          pre-commit run
          --all-files --show-diff-on-failure --color always --verbose
          --hook-stage manual update-installers
        if: always()
        env:
          UPGRADE_UV: "false"
          UPGRADE_PIP: "true"

  test-airflow-release-commands:
    timeout-minutes: 80
    name: "Test Airflow release commands"
    runs-on: ${{ fromJSON(inputs.runs-on-as-json-public) }}
    env:
      PYTHON_MAJOR_MINOR_VERSION: "${{ inputs.default-python-version }}"
      IMAGE_TAG: ${{ inputs.image-tag }}
      GITHUB_REPOSITORY: ${{ github.repository }}
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      GITHUB_USERNAME: ${{ github.actor }}
      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: "Cleanup dist files"
        run: rm -fv ./dist/*
      - name: Setup git for tagging
        run: |
          git config --global user.email "name@example.com"
          git config --global user.name "Your Name"
      - name: Install twine
        run: pip install twine
      - name: "Check Airflow create minor branch command"
        run: breeze release-management create-minor-branch --version-branch 2-8 --answer yes
      - name: "Check Airflow RC process command"
        run: >
          breeze release-management start-rc-process --version 2.8.3rc1 --previous-version 2.8.0 --answer yes
      - name: "Check Airflow release process command"
        run: >
          breeze release-management start-release --release-candidate 2.8.3rc1 --previous-release 2.8.0
          --answer yes
      - name: "Fetch all git tags"
        run: git fetch --tags >/dev/null 2>&1 || true
      - name: "Test airflow core issue generation automatically"
        run: |
          breeze release-management generate-issue-content-core \
          --limit-pr-count 25 --latest --verbose


K8s 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: K8s tests
on:  # yamllint disable-line rule:truthy
  workflow_call:
    inputs:
      runs-on-as-json-default:
        description: "The array of labels (in json form) determining default runner used for the build."
        required: true
        type: string
      image-tag:
        description: "Tag to set for the image"
        required: true
        type: string
      python-versions-list-as-string:
        description: "List of Python versions to test: space separated string"
        required: true
        type: string
      kubernetes-versions-list-as-string:
        description: "List of Kubernetes versions to test"
        required: true
        type: string
      kubernetes-combos-list-as-string:
        description: "List of combinations of Kubernetes and Python versions to test: space separated string"
        required: true
        type: string
      include-success-outputs:
        description: "Whether to include success outputs"
        required: true
        type: string
      debug-resources:
        description: "Whether to debug resources"
        required: true
        type: string
jobs:
  tests-kubernetes:
    timeout-minutes: 240
    name: "\
      K8S System:${{ matrix.executor }} - ${{ matrix.use-standard-naming }} - \
      ${{ inputs.kubernetes-versions-list-as-string }}"
    runs-on: ${{ fromJSON(inputs.runs-on-as-json-default) }}
    strategy:
      matrix:
        executor: [KubernetesExecutor, CeleryExecutor, LocalExecutor]
        use-standard-naming: [true, false]
      fail-fast: false
    env:
      DEBUG_RESOURCES: ${{ inputs.debug-resources }}
      INCLUDE_SUCCESS_OUTPUTS: ${{ inputs.include-success-outputs }}
      IMAGE_TAG: ${{ inputs.image-tag }}
      GITHUB_REPOSITORY: ${{ github.repository }}
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      GITHUB_USERNAME: ${{ github.actor }}
      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
        id: breeze
      - name: Login to ghcr.io
        run: echo "${{ env.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
      - name: Pull PROD images ${{ inputs.python-versions-list-as-string }}:${{ inputs.image-tag }}
        run: breeze prod-image pull --run-in-parallel --tag-as-latest
        env:
          PYTHON_VERSIONS: ${{ inputs.python-versions-list-as-string }}
          # Force more parallelism for pull even on public images
          PARALLELISM: 6
      - name: "Cache bin folder with tools for kubernetes testing"
        uses: actions/cache@v4
        with:
          path: ".build/.k8s-env"
          key: "\
            k8s-env-${{ steps.breeze.outputs.host-python-version }}-\
            ${{ hashFiles('scripts/ci/kubernetes/k8s_requirements.txt','hatch_build.py') }}"
      - name: Run complete K8S tests ${{ inputs.kubernetes-combos-list-as-string }}
        run: breeze k8s run-complete-tests --run-in-parallel --upgrade --no-copy-local-sources
        env:
          PYTHON_VERSIONS: ${{ inputs.python-versions-list-as-string }}
          KUBERNETES_VERSIONS: ${{ inputs.kubernetes-versions-list-as-string }}
          EXECUTOR: ${{ matrix.executor }}
          USE_STANDARD_NAMING: ${{ matrix.use-standard-naming }}
          VERBOSE: "false"
      - name: Upload KinD logs on failure ${{ inputs.kubernetes-combos-list-as-string }}
        uses: actions/upload-artifact@v4
        if: failure() || cancelled()
        with:
          name: kind-logs-${{ matrix.executor }}-${{ matrix.use-standard-naming }}
          path: /tmp/kind_logs_*
          retention-days: 7
      - name: Upload test resource logs on failure ${{ inputs.kubernetes-combos-list-as-string }}
        uses: actions/upload-artifact@v4
        if: failure() || cancelled()
        with:
          name: k8s-test-resources-${{ matrix.executor }}-${{ matrix.use-standard-naming }}
          path: /tmp/k8s_test_resources_*
          retention-days: 7
      - name: "Delete clusters just in case they are left"
        run: breeze k8s delete-cluster --all
        if: always()


Integration 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: Integration tests
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
      image-tag:
        description: "Tag to set for the image"
        required: true
        type: string
      testable-integrations:
        description: "The list of testable integrations as JSON array."
        required: true
        type: string
      default-postgres-version:
        description: "Default version of Postgres to use"
        required: true
        type: string
      default-mysql-version:
        description: "Default version of MySQL to use"
        required: true
        type: string
      skip-provider-tests:
        description: "Skip provider tests (true/false)"
        required: true
        type: string
      run-coverage:
        description: "Run coverage (true/false)"
        required: true
        type: string
      default-python-version:
        description: "Which version of python should be used by default"
        required: true
        type: string
      debug-resources:
        description: "Debug resources (true/false)"
        required: true
        type: string
jobs:
  tests-integration:
    timeout-minutes: 130
    name: "Integration Tests: ${{ matrix.integration }}"
    runs-on: ${{ fromJSON(inputs.runs-on-as-json-public) }}
    strategy:
      fail-fast: false
      matrix:
        integration: ${{ fromJSON(inputs.testable-integrations) }}
    env:
      IMAGE_TAG: "${{ inputs.image-tag }}"
      BACKEND: "postgres"
      BACKEND_VERSION: ${{ inputs.default-postgres-version }}"
      PYTHON_MAJOR_MINOR_VERSION: "${{ inputs.default-python-version }}"
      JOB_ID: "integration-${{ matrix.integration }}"
      SKIP_PROVIDER_TESTS: "${{ inputs.skip-provider-tests }}"
      ENABLE_COVERAGE: "${{ inputs.run-coverage}}"
      DEBUG_RESOURCES: "${{ inputs.debug-resources }}"
      GITHUB_REPOSITORY: ${{ github.repository }}
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      GITHUB_USERNAME: ${{ github.actor }}
      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: "Prepare breeze & CI image: ${{ inputs.default-python-version }}:${{ inputs.image-tag }}"
        uses: ./.github/actions/prepare_breeze_and_image
      - name: "Integration Tests: ${{ matrix.integration }}"
        run: ./scripts/ci/testing/run_integration_tests_with_retry.sh ${{ matrix.integration }}
      - name: "Post Tests success: Integration Tests ${{ matrix.integration }}"
        uses: ./.github/actions/post_tests_success
        with:
          codecov-token: ${{ secrets.CODECOV_TOKEN }}
          python-version: ${{ inputs.default-python-version }}
      - name: "Post Tests failure: Integration Tests ${{ matrix.integration }}"
        uses: ./.github/actions/post_tests_failure
        if: failure()


Finalize 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: Finalize tests
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
      runs-on-as-json-self-hosted:
        description: "The array of labels (in json form) determining self-hosted runners."
        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 test"
        required: true
        type: string
      python-versions-list-as-string:
        description: "Stringified array of all Python versions to test - separated by spaces."
        required: true
        type: string
      branch:
        description: "The default branch to use for the build"
        required: true
        type: string
      constraints-branch:
        description: "The branch to use for constraints"
        required: true
        type: string
      default-python-version:
        description: "Which version of python should be used by default"
        required: true
        type: string
      in-workflow-build:
        description: "Whether the build is executed as part of the workflow (true/false)"
        required: true
        type: string
      upgrade-to-newer-dependencies:
        description: "Whether to upgrade to newer dependencies (true/false)"
        required: true
        type: string
      docker-cache:
        description: "Docker cache specification to build the image (registry, local, disabled)."
        required: true
        type: string
      include-success-outputs:
        description: "Whether to include success outputs (true/false)"
        required: true
        type: string
      canary-run:
        description: "Whether this is a canary run (true/false)"
        required: true
        type: string
jobs:
  update-constraints:
    runs-on: ${{ fromJSON(inputs.runs-on-as-json-public) }}
    timeout-minutes: 80
    name: "Update constraints"
    permissions:
      contents: write
      packages: read
    env:
      DEBUG_RESOURCES: ${{ inputs.debug-resources}}
      PYTHON_VERSIONS: ${{ inputs.python-versions-list-as-string }}
      IMAGE_TAG: ${{ inputs.image-tag }}
      GITHUB_REPOSITORY: ${{ github.repository }}
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      GITHUB_USERNAME: ${{ github.actor }}
      VERBOSE: "true"
    if: inputs.upgrade-to-newer-dependencies != 'false'
    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:
          # Needed to perform push action
          persist-credentials: false
      - name: "Cleanup docker"
        run: ./scripts/ci/cleanup_docker.sh
      - name: "Set constraints branch name"
        id: constraints-branch
        run: ./scripts/ci/constraints/ci_branch_constraints.sh >> ${GITHUB_OUTPUT}
      - name: Checkout ${{ steps.constraints-branch.outputs.branch }}
        uses: actions/checkout@v4
        with:
          path: "constraints"
          ref: ${{ steps.constraints-branch.outputs.branch }}
          persist-credentials: true
          fetch-depth: 0
      - name: "Download constraints from the constraints generated by build CI image"
        uses: actions/download-artifact@v4
        with:
          name: constraints
          path: ./files
      - name: "Diff in constraints for Python: ${{ inputs.python-versions-list-as-string }}"
        run: ./scripts/ci/constraints/ci_diff_constraints.sh
      # only commit and push constraints in canary runs (main)
      - name: "Commit changed constraint files for Python: ${{ inputs.python-versions-list-as-string }}"
        run: ./scripts/ci/constraints/ci_commit_constraints.sh
        if: inputs.canary-run == 'true'
      - name: "Push changes"
        if: inputs.canary-run == 'true'
        working-directory: "constraints"
        run:
          git push

  push-buildx-cache-to-github-registry-amd:
    name: Push Regular AMD Image Cache
    needs: [update-constraints]
    uses: ./.github/workflows/push-image-cache.yml
    permissions:
      contents: read
      packages: write
    secrets: inherit
    with:
      runs-on-as-json-public: ${{ inputs.runs-on-as-json-public }}
      runs-on-as-json-self-hosted: ${{ inputs.runs-on-as-json-self-hosted }}
      cache-type: "Regular AMD"
      include-prod-images: "true"
      push-latest-images: "true"
      platform: "linux/amd64"
      python-versions: ${{ inputs.python-versions }}
      branch: ${{ inputs.branch }}
      constraints-branch: ${{ inputs.constraints-branch }}
      use-uv: "true"
      include-success-outputs: ${{ inputs.include-success-outputs }}
      docker-cache: ${{ inputs.docker-cache }}
    if: inputs.canary-run == 'true'

  push-buildx-cache-to-github-registry-arm:
    name: Push Regular ARM Image Cache
    needs: [update-constraints]
    uses: ./.github/workflows/push-image-cache.yml
    permissions:
      contents: read
      packages: write
    secrets: inherit
    with:
      runs-on-as-json-public: ${{ inputs.runs-on-as-json-public }}
      runs-on-as-json-self-hosted: ${{ inputs.runs-on-as-json-self-hosted }}
      cache-type: "Regular ARM"
      include-prod-images: "true"
      push-latest-images: "true"
      platform: "linux/arm64"
      python-versions: ${{ inputs.python-versions }}
      branch: ${{ inputs.branch }}
      constraints-branch: ${{ inputs.constraints-branch }}
      use-uv: "true"
      include-success-outputs: ${{ inputs.include-success-outputs }}
      docker-cache: ${{ inputs.docker-cache }}
    if: inputs.canary-run == 'true'

  summarize-warnings:
    timeout-minutes: 15
    name: "Summarize warnings"
    runs-on: ${{ fromJSON(inputs.runs-on-as-json-public) }}
    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: "Download all artifacts from the current build"
        uses: actions/download-artifact@v4
        with:
          path: ./artifacts
      - name: "Setup python"
        uses: actions/setup-python@v5
        with:
          python-version: ${{ inputs.default-python-version }}
      - name: "Summarize all warnings"
        run: |
          ./scripts/ci/testing/summarize_captured_warnings.py ./artifacts \
            --pattern "**/warnings-*.txt" \
            --output ./files
      - name: "Upload artifact for summarized warnings"
        uses: actions/upload-artifact@v4
        with:
          name: test-summarized-warnings
          path: ./files/warn-summary-*.txt
          retention-days: 7
          if-no-files-found: ignore
          overwrite: true


Additional PROD image 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: Additional PROD image tests
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
      default-branch:
        description: "The default branch for the repository"
        required: true
        type: string
      constraints-branch:
        description: "Branch used to construct constraints URL from."
        required: true
        type: string
      image-tag:
        description: "Tag to set for the image"
        required: true
        type: string
      upgrade-to-newer-dependencies:
        description: "Whether to upgrade to newer dependencies (true/false)"
        required: true
        type: string
      chicken-egg-providers:
        description: "Whether to build chicken-egg provider packages in the same run (true/false)"
        required: true
        type: string
      docker-cache:
        description: "Docker cache specification to build the image (registry, local, disabled)."
        required: true
        type: string
      canary-run:
        description: "Whether to run the canary run (true/false)"
        required: true
        type: string
      default-python-version:
        description: "Which version of python should be used by default"
        required: true
        type: string
jobs:
  prod-image-extra-checks-main:
    name: PROD image extra checks (main)
    uses: ./.github/workflows/prod-image-extra-checks.yml
    with:
      runs-on-as-json-public: ${{ inputs.runs-on-as-json-public }}
      python-versions: "[ '${{ inputs.default-python-version }}' ]"
      default-python-version: ${{ inputs.default-python-version }}
      branch: ${{ inputs.default-branch }}
      use-uv: "true"
      image-tag: ${{ inputs.image-tag }}
      build-provider-packages: ${{ inputs.default-branch == 'main' }}
      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 }}
    if: inputs.default-branch == 'main' && inputs.canary-run == 'true'

  prod-image-extra-checks-release-branch:
    name: PROD image extra checks (release)
    uses: ./.github/workflows/prod-image-extra-checks.yml
    with:
      runs-on-as-json-public: ${{ inputs.runs-on-as-json-public }}
      python-versions: "[ '${{ inputs.default-python-version }}' ]"
      default-python-version: ${{ inputs.default-python-version }}
      branch: ${{ inputs.default-branch }}
      use-uv: "false"
      image-tag: ${{ inputs.image-tag }}
      build-provider-packages: ${{ inputs.default-branch == 'main' }}
      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 }}
    if: inputs.default-branch != 'main' && inputs.canary-run == 'true'

  test-examples-of-prod-image-building:
    timeout-minutes: 60
    name: "Test examples of PROD image building"
    runs-on: ${{ fromJSON(inputs.runs-on-as-json-public) }}
    env:
      GITHUB_REPOSITORY: ${{ github.repository }}
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      GITHUB_USERNAME: ${{ github.actor }}
      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:
          fetch-depth: 2
          persist-credentials: false
      - name: "Cleanup docker"
        run: ./scripts/ci/cleanup_docker.sh
      - name: "Install Breeze"
        uses: ./.github/actions/breeze
      - name: Login to ghcr.io
        shell: bash
        run: echo "${{ env.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
      - name: Pull PROD image ${{ inputs.default-python-version}}:${{ inputs.image-tag }}
        run: breeze prod-image pull --tag-as-latest
        env:
          PYTHON_MAJOR_MINOR_VERSION: "${{ inputs.default-python-version }}"
          IMAGE_TAG: "${{ inputs.image-tag }}"
      - name: "Setup python"
        uses: actions/setup-python@v5
        with:
          python-version: ${{ inputs.default-python-version }}
          cache: 'pip'
          cache-dependency-path: ./dev/requirements.txt
      - name: "Test examples of PROD image building"
        run: "
          cd ./docker_tests && \
          python -m pip install -r requirements.txt && \
          TEST_IMAGE=\"ghcr.io/${{ github.repository }}/${{ inputs.default-branch }}\
          /prod/python${{ inputs.default-python-version }}:${{ inputs.image-tag }}\" \
          python -m pytest test_examples_of_prod_image_building.py -n auto --color=yes"

  test-docker-compose-quick-start:
    timeout-minutes: 60
    name: "Docker-compose quick start with PROD image verifying"
    runs-on: ${{ fromJSON(inputs.runs-on-as-json-public) }}
    env:
      IMAGE_TAG: "${{ inputs.image-tag }}"
      PYTHON_MAJOR_MINOR_VERSION: "${{ inputs.default-python-version }}"
      GITHUB_REPOSITORY: ${{ github.repository }}
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      GITHUB_USERNAME: ${{ github.actor }}
      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:
          fetch-depth: 2
          persist-credentials: false
      - name: "Cleanup docker"
        run: ./scripts/ci/cleanup_docker.sh
      - name: "Install Breeze"
        uses: ./.github/actions/breeze
      - name: Login to ghcr.io
        shell: bash
        run: echo "${{ env.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
      - name: "Pull image ${{ inputs.default-python-version}}:${{ inputs.image-tag }}"
        run: breeze prod-image pull --tag-as-latest
      - name: "Test docker-compose quick start"
        run: breeze testing docker-compose-tests