This is an automated email from the ASF dual-hosted git repository.
potiuk pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v3-1-test by this push:
new be8d921bdf8 [v3-1-test] Implement integration test for airflowctl with
3.1 (#56124) (#56986)
be8d921bdf8 is described below
commit be8d921bdf809f89d23d4aea84b3af61bc0c36bc
Author: Bugra Ozturk <[email protected]>
AuthorDate: Fri Oct 24 14:32:49 2025 +0200
[v3-1-test] Implement integration test for airflowctl with 3.1 (#56124)
(#56986)
* [v3-1-test] Implement integration test for airflowctl with 3.1 (#56124)
* Implement initial integration test for airflowctl with 3.1
* password can be passed without interaction, update integration tests
* Add AIRFLOW_CLI_DEBUG_MODE for enhanced CLI debugging and update
integration tests to skip keyring
* Warn user while running each command if debug mode enabled and explicitly
state it shouldn't be used unless debugging or integration tests
* Move python-on-whales to devel-common, use shared docker-composer file,
update documentation mistakes
* remove shared python-on-whales from airflow-ctl-tests/
Co-authored-by: Jarek Potiuk <[email protected]>
* Decouple docker compose logic from test method to pytest_sessionstart in
conftest
* Move python_on_whale import to file level
* Reorder dependencies in pyproject.toml for consistency
* Add workspace to main pyproject.toml, remove unused variable, move
console to singleton __init__.py
* Add workspace to main pyproject.toml, remove unused variable, move
console to singleton __init__.py
---------
(cherry picked from commit 80911e833903315bb2afd4caa6069aad65103dc5)
Co-authored-by: Bugra Ozturk <[email protected]>
Co-authored-by: Jarek Potiuk <[email protected]>
* Remove e2e tests came from merge and rerun breeze images for all commands
to fix accepting changes while merging
* Add env file to compose and update error
---------
Co-authored-by: Jarek Potiuk <[email protected]>
---
.dockerignore | 1 +
.github/workflows/additional-prod-image-tests.yml | 31 +++
Dockerfile | 1 +
Dockerfile.ci | 1 +
.../docs/howto/docker-compose/docker-compose.yaml | 2 +
.../pyproject.toml | 7 +-
.../tests/airflowctl_tests/__init__.py | 24 ++
.../tests/airflowctl_tests/conftest.py | 253 +++++++++++++++++++++
.../tests/airflowctl_tests/constants.py | 32 +++
.../airflowctl_tests/test_airflowctl_commands.py | 77 +++++++
airflow-ctl/docs/cli-and-env-variables-ref.rst | 7 +
airflow-ctl/src/airflowctl/api/client.py | 25 +-
airflow-ctl/src/airflowctl/ctl/cli_config.py | 9 +-
.../airflow_ctl/ctl/commands/test_auth_command.py | 6 +-
dev/breeze/doc/05_test_commands.rst | 21 +-
.../output_setup_check-all-params-in-groups.svg | 10 +-
.../output_setup_check-all-params-in-groups.txt | 2 +-
.../output_setup_regenerate-command-images.svg | 36 +--
.../output_setup_regenerate-command-images.txt | 2 +-
dev/breeze/doc/images/output_testing.svg | 30 +--
dev/breeze/doc/images/output_testing.txt | 2 +-
...utput_testing_airflow-ctl-integration-tests.svg | 144 ++++++++++++
...utput_testing_airflow-ctl-integration-tests.txt | 1 +
.../airflow_breeze/commands/testing_commands.py | 56 +++++
.../commands/testing_commands_config.py | 15 +-
dev/breeze/src/airflow_breeze/global_constants.py | 2 +
dev/breeze/src/airflow_breeze/utils/run_tests.py | 5 +
devel-common/pyproject.toml | 1 +
docker-tests/pyproject.toml | 1 -
kubernetes-tests/pyproject.toml | 1 -
pyproject.toml | 4 +
.../docker/install_airflow_when_building_images.sh | 1 +
task-sdk-tests/pyproject.toml | 1 -
33 files changed, 757 insertions(+), 54 deletions(-)
diff --git a/.dockerignore b/.dockerignore
index edce6e9e78a..c7fb2e60c7b 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -47,6 +47,7 @@
!helm-tests
!kubernetes-tests
!task-sdk-tests
+!airflow-ctl-tests
!shared/
# Add scripts so that we can use them inside the container
diff --git a/.github/workflows/additional-prod-image-tests.yml
b/.github/workflows/additional-prod-image-tests.yml
index 714761bceb3..c7d4bde61c8 100644
--- a/.github/workflows/additional-prod-image-tests.yml
+++ b/.github/workflows/additional-prod-image-tests.yml
@@ -191,3 +191,34 @@ jobs:
id: breeze
- name: "Run Task SDK integration tests"
run: breeze testing task-sdk-integration-tests
+
+ airflow-ctl-integration-tests:
+ timeout-minutes: 60
+ name: "Airflow CTL integration tests with PROD image"
+ runs-on: ${{ fromJSON(inputs.runners) }}
+ env:
+ 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@11bd71901bbe5b1630ceea73d27597364c9af683 #
v4.2.2
+ with:
+ fetch-depth: 2
+ persist-credentials: false
+ - name: "Prepare breeze & PROD image: ${{ env.PYTHON_MAJOR_MINOR_VERSION
}}"
+ uses: ./.github/actions/prepare_breeze_and_image
+ with:
+ platform: ${{ inputs.platform }}
+ image-type: "prod"
+ python: ${{ env.PYTHON_MAJOR_MINOR_VERSION }}
+ use-uv: ${{ inputs.use-uv }}
+ make-mnt-writeable-and-cleanup: true
+ id: breeze
+ - name: "Run airflowctl integration tests"
+ run: breeze testing airflow-ctl-integration-tests
diff --git a/Dockerfile b/Dockerfile
index 0f7f5007b3a..8fab2a4ae01 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1116,6 +1116,7 @@ function install_from_sources() {
--editable ./airflow-core --editable ./task-sdk --editable
./airflow-ctl \
--editable ./kubernetes-tests --editable ./docker-tests
--editable ./helm-tests \
--editable ./task-sdk-tests \
+ --editable ./airflow-ctl-tests \
--editable ./devel-common[all] --editable ./dev \
--group dev --group docs --group docs-gen --group leveldb"
local -a projects_with_devel_dependencies
diff --git a/Dockerfile.ci b/Dockerfile.ci
index 74c7692031d..744fe240a10 100644
--- a/Dockerfile.ci
+++ b/Dockerfile.ci
@@ -870,6 +870,7 @@ function install_from_sources() {
--editable ./airflow-core --editable ./task-sdk --editable
./airflow-ctl \
--editable ./kubernetes-tests --editable ./docker-tests
--editable ./helm-tests \
--editable ./task-sdk-tests \
+ --editable ./airflow-ctl-tests \
--editable ./devel-common[all] --editable ./dev \
--group dev --group docs --group docs-gen --group leveldb"
local -a projects_with_devel_dependencies
diff --git a/airflow-core/docs/howto/docker-compose/docker-compose.yaml
b/airflow-core/docs/howto/docker-compose/docker-compose.yaml
index 2c2a614c9ef..3892e044143 100644
--- a/airflow-core/docs/howto/docker-compose/docker-compose.yaml
+++ b/airflow-core/docs/howto/docker-compose/docker-compose.yaml
@@ -51,6 +51,8 @@ x-airflow-common:
# and uncomment the "build" line below, Then run `docker-compose build` to
build the images.
image: ${AIRFLOW_IMAGE_NAME:-apache/airflow:|version|}
# build: .
+ env_file:
+ - ${ENV_FILE_PATH:-.env}
environment:
&airflow-common-env
AIRFLOW__CORE__EXECUTOR: CeleryExecutor
diff --git a/task-sdk-tests/pyproject.toml b/airflow-ctl-tests/pyproject.toml
similarity index 92%
copy from task-sdk-tests/pyproject.toml
copy to airflow-ctl-tests/pyproject.toml
index 5e6ac78373c..d401975ad73 100644
--- a/task-sdk-tests/pyproject.toml
+++ b/airflow-ctl-tests/pyproject.toml
@@ -21,8 +21,8 @@ requires = [ "hatchling==1.27.0" ]
build-backend = "hatchling.build"
[project]
-name = "apache-airflow-task-sdk-tests"
-description = "Task SDK tests for Apache Airflow"
+name = "apache-airflow-ctl-tests"
+description = "Airflow CTL tests for Apache Airflow"
classifiers = [
"Private :: Do Not Upload",
]
@@ -36,9 +36,8 @@ maintainers = [
version = "0.0.1"
dependencies = [
- "apache-airflow-core",
+ "apache-airflow-ctl",
"apache-airflow-devel-common",
- "python-on-whales>=0.70.0",
]
[tool.pytest.ini_options]
diff --git a/airflow-ctl-tests/tests/airflowctl_tests/__init__.py
b/airflow-ctl-tests/tests/airflowctl_tests/__init__.py
new file mode 100644
index 00000000000..973b8fdce34
--- /dev/null
+++ b/airflow-ctl-tests/tests/airflowctl_tests/__init__.py
@@ -0,0 +1,24 @@
+# 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.
+from __future__ import annotations
+
+from rich.console import Console
+
+console = Console(width=400, color_system="standard")
+
+
+__all__ = ["console"]
diff --git a/airflow-ctl-tests/tests/airflowctl_tests/conftest.py
b/airflow-ctl-tests/tests/airflowctl_tests/conftest.py
new file mode 100644
index 00000000000..202f5d3ca4e
--- /dev/null
+++ b/airflow-ctl-tests/tests/airflowctl_tests/conftest.py
@@ -0,0 +1,253 @@
+# 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.
+from __future__ import annotations
+
+import os
+import subprocess
+import sys
+
+import pytest
+from python_on_whales import DockerClient, docker
+
+from airflowctl_tests import console
+from airflowctl_tests.constants import (
+ AIRFLOW_ROOT_PATH,
+ DOCKER_COMPOSE_FILE_PATH,
+ DOCKER_IMAGE,
+)
+
+docker_client = None
+
+
+# Pytest hook to run at the start of the session
+def pytest_sessionstart(session):
+ """Install airflowctl at the very start of the pytest session."""
+ airflow_ctl_version = os.environ.get("AIRFLOW_CTL_VERSION", "1.0.0")
+ console.print(f"[yellow]Installing
apache-airflow-ctl=={airflow_ctl_version} via pytest_sessionstart...")
+
+ airflow_ctl_path = AIRFLOW_ROOT_PATH / "airflow-ctl"
+ console.print(f"[blue]Installing from: {airflow_ctl_path}")
+
+ # Install directly to current UV environment
+ console.print("[blue]Installing to current UV environment...")
+ console.print(f"[blue]Current Python: {sys.executable}")
+
+ try:
+ cmd = ["uv", "pip", "install", str(airflow_ctl_path)]
+ console.print(f"[cyan]Running command: {' '.join(cmd)}")
+ subprocess.check_call(cmd)
+ console.print("[green]airflowctl installed successfully to UV
environment via pytest_sessionstart!")
+ except (subprocess.CalledProcessError, FileNotFoundError) as e:
+ console.print(f"[yellow]UV installation failed: {e}")
+ raise
+
+ console.print("[yellow]Verifying airflowctl installation via
pytest_sessionstart...")
+ try:
+ result = subprocess.run(
+ [
+ sys.executable,
+ "-c",
+ "import airflowctl.api.client; print('✅ airflowctl import
successful via pytest_sessionstart')",
+ ],
+ capture_output=True,
+ text=True,
+ check=True,
+ )
+ console.print(f"[green]{result.stdout.strip()}")
+ except subprocess.CalledProcessError as e:
+ console.print("[red]❌ airflowctl import verification failed via
pytest_sessionstart:")
+ console.print(f"[red]Return code: {e.returncode}")
+ console.print(f"[red]Stdout: {e.stdout}")
+ console.print(f"[red]Stderr: {e.stderr}")
+ raise
+
+ docker_compose_up(session.config._tmp_path_factory)
+
+
+def print_diagnostics(compose, compose_version, docker_version):
+ """Print diagnostic information when test fails."""
+ console.print("[red]=== DIAGNOSTIC INFORMATION ===[/]")
+ console.print(f"Docker version: {docker_version}")
+ console.print(f"Docker Compose version: {compose_version}")
+ console.print("\n[yellow]Container Status:[/]")
+ try:
+ containers = compose.compose.ps()
+ for container in containers:
+ console.print(f" {container.name}: {container.state}")
+ except Exception as e:
+ console.print(f" Error getting container status: {e}")
+
+ console.print("\n[yellow]Container Logs:[/]")
+ try:
+ logs = compose.compose.logs()
+ console.print(logs)
+ except Exception as e:
+ console.print(f" Error getting logs: {e}")
+
+
+def debug_environment():
+ """Debug the Python environment setup in CI."""
+ import os
+ import subprocess
+ import sys
+ from pathlib import Path
+
+ console.print("[yellow]===== CI ENVIRONMENT DEBUG =====")
+ console.print(f"[blue]Python executable: {sys.executable}")
+ console.print(f"[blue]Python version: {sys.version}")
+ console.print(f"[blue]Working directory: {os.getcwd()}")
+ console.print(f"[blue]VIRTUAL_ENV: {os.environ.get('VIRTUAL_ENV', 'Not
set')}")
+ console.print(f"[blue]PYTHONPATH: {os.environ.get('PYTHONPATH', 'Not
set')}")
+
+ console.print(f"[blue]Python executable exists:
{Path(sys.executable).exists()}")
+ if Path(sys.executable).is_symlink():
+ console.print(f"[blue]Python executable is symlink to:
{Path(sys.executable).readlink()}")
+
+ try:
+ uv_python = subprocess.check_output(["uv", "python", "find"],
text=True).strip()
+ console.print(f"[cyan]UV Python: {uv_python}")
+ console.print(f"[green]Match: {uv_python == sys.executable}")
+
+ console.print(f"[cyan]UV Python exists: {Path(uv_python).exists()}")
+ if Path(uv_python).is_symlink():
+ console.print(f"[cyan]UV Python is symlink to:
{Path(uv_python).readlink()}")
+ except Exception as e:
+ console.print(f"[red]UV Python error: {e}")
+
+ # Check what's installed in current environment
+ try:
+ import airflowctl
+
+ console.print(f"[green]✅ airflow already available:
{airflowctl.__file__}")
+ except ImportError:
+ console.print("[red]❌ airflowctl not available in current environment")
+
+ console.print("[yellow]================================")
+
+
+def docker_compose_up(tmp_path_factory):
+ """Fixture to spin up Docker Compose environment for the test session."""
+ from shutil import copyfile
+
+ global docker_client
+
+ tmp_dir = tmp_path_factory.mktemp("airflow-ctl-test")
+ console.print(f"[yellow]Tests are run in {tmp_dir}")
+
+ # Copy docker-compose.yaml to temp directory
+ tmp_docker_compose_file = tmp_dir / "docker-compose.yaml"
+ copyfile(DOCKER_COMPOSE_FILE_PATH, tmp_docker_compose_file)
+
+ dot_env_file = tmp_dir / ".env"
+ dot_env_file.write_text(
+ f"AIRFLOW_UID={os.getuid()}\n"
+ # To enable debug mode for airflowctl CLI
+ "AIRFLOW_CTL_CLI_DEBUG_MODE=true\n"
+ # To enable config operations to work
+ "AIRFLOW__API__EXPOSE_CONFIG=true\n"
+ )
+
+ # Set environment variables for the test
+ os.environ["AIRFLOW_IMAGE_NAME"] = DOCKER_IMAGE
+ os.environ["AIRFLOW_CTL_VERSION"] = os.environ.get("AIRFLOW_CTL_VERSION",
"1.0.0")
+ os.environ["ENV_FILE_PATH"] = str(tmp_dir / ".env")
+
+ # Initialize Docker client
+ docker_client = DockerClient(compose_files=[str(tmp_docker_compose_file)])
+
+ try:
+ console.print(f"[blue]Spinning up airflow environment using
{DOCKER_IMAGE}")
+ docker_client.compose.up(detach=True, wait=True)
+ console.print("[green]Docker compose started for airflowctl test\n")
+ except Exception:
+ print_diagnostics(docker_client.compose,
docker_client.compose.version(), docker.version())
+ debug_environment()
+ docker_compose_down()
+ raise
+
+
+def docker_compose_down():
+ """Tear down Docker Compose environment."""
+ global docker_client
+ if docker_client:
+ docker_client.compose.down(remove_orphans=True, volumes=True,
quiet=True)
+
+
+def pytest_sessionfinish(session, exitstatus):
+ """Tear down test environment at the end of the pytest session."""
+ if not os.environ.get("SKIP_DOCKER_COMPOSE_DELETION"):
+ docker_compose_down()
+
+
+# Fixtures for tests
[email protected]
+def login_command():
+ # Passing password via command line is insecure but acceptable for testing
purposes
+ # Please do not do this in production, it enables possibility of exposing
your credentials
+ return "auth login --username airflow --password airflow"
+
+
[email protected]
+def login_output():
+ return "Login successful! Welcome to airflowctl!"
+
+
[email protected]
+def date_param():
+ import random
+ from datetime import datetime, timedelta
+
+ from dateutil.relativedelta import relativedelta
+
+ # original datetime string
+ dt_str = "2025-10-25T00:02:00+00:00"
+
+ # parse to datetime object
+ dt = datetime.fromisoformat(dt_str)
+
+ # boundaries
+ start = dt - relativedelta(months=1)
+ end = dt + relativedelta(months=1)
+
+ # pick random time between start and end
+ delta = end - start
+ random_seconds = random.randint(0, int(delta.total_seconds()))
+ random_dt = start + timedelta(seconds=random_seconds)
+ return random_dt.isoformat()
+
+
[email protected]
+def test_commands(login_command, date_param):
+ # Define test commands to run with actual running API server
+ return [
+ login_command,
+ "backfills list",
+ "config get --section core --option executor",
+ "connections create --connection-id=test_con --conn-type=mysql
--password=TEST_PASS -o json",
+ "connections list",
+ "connections list -o yaml",
+ "connections list -o tabledags list",
+ f"dagrun trigger --dag-id=example_bash_operator
--logical-date={date_param} --run-after={date_param}",
+ "dagrun list --dag-id example_bash_operator --state success --limit=1",
+ "jobs list",
+ "pools create --name=test_pool --slots=5",
+ "pools list",
+ "providers list",
+ "variables create --key=test_key --value=test_value",
+ "variables list",
+ "version --remote",
+ ]
diff --git a/airflow-ctl-tests/tests/airflowctl_tests/constants.py
b/airflow-ctl-tests/tests/airflowctl_tests/constants.py
new file mode 100644
index 00000000000..549a9ad8c74
--- /dev/null
+++ b/airflow-ctl-tests/tests/airflowctl_tests/constants.py
@@ -0,0 +1,32 @@
+# 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.
+from __future__ import annotations
+
+import os
+from pathlib import Path
+
+AIRFLOW_ROOT_PATH = Path(__file__).resolve().parents[3]
+
+DEFAULT_PYTHON_MAJOR_MINOR_VERSION = "3.10"
+DEFAULT_DOCKER_IMAGE =
f"ghcr.io/apache/airflow/main/prod/python{DEFAULT_PYTHON_MAJOR_MINOR_VERSION}:latest"
+DOCKER_IMAGE = os.environ.get("DOCKER_IMAGE") or DEFAULT_DOCKER_IMAGE
+
+DOCKER_COMPOSE_HOST_PORT = os.environ.get("HOST_PORT", "localhost:8080")
+
+DOCKER_COMPOSE_FILE_PATH = (
+ AIRFLOW_ROOT_PATH / "airflow-core" / "docs" / "howto" / "docker-compose" /
"docker-compose.yaml"
+)
diff --git
a/airflow-ctl-tests/tests/airflowctl_tests/test_airflowctl_commands.py
b/airflow-ctl-tests/tests/airflowctl_tests/test_airflowctl_commands.py
new file mode 100644
index 00000000000..d17e646cb06
--- /dev/null
+++ b/airflow-ctl-tests/tests/airflowctl_tests/test_airflowctl_commands.py
@@ -0,0 +1,77 @@
+# 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.
+from __future__ import annotations
+
+import os
+from subprocess import PIPE, STDOUT, Popen
+
+from airflowctl_tests import console
+
+
+def test_airflowctl_commands(login_command, login_output, test_commands):
+ """Test airflowctl commands using docker-compose environment."""
+ host_envs = os.environ.copy()
+ host_envs["AIRFLOW_CLI_DEBUG_MODE"] = "true"
+ # Testing commands of airflowctl
+ for command in test_commands:
+ command_from_config = f"airflowctl {command}"
+ # We need to run auth login first for all commands except login itself
+ if command != login_command:
+ run_command = f"airflowctl {login_command} &&
{command_from_config}"
+ else:
+ run_command = command_from_config
+ console.print(f"[yellow]Running command: {command}")
+
+ # Give some time for the command to execute and output to be ready
+ proc = Popen(run_command.encode(), stdout=PIPE, stderr=STDOUT,
stdin=PIPE, shell=True, env=host_envs)
+ stdout_result, stderr_result = proc.communicate(timeout=60)
+
+ # CLI command gave errors
+ if stderr_result:
+ console.print(
+ f"[red]Errors while executing command
'{command_from_config}':\n{stderr_result.decode()}"
+ )
+
+ # Decode the output
+ stdout_result = stdout_result.decode()
+ # We need to trim auth login output if the command is not login itself
and clean backspaces
+ if command != login_command:
+ if login_output not in stdout_result:
+ console.print(
+ f"[red]❌ Login output not found before command output for
'{command_from_config}'"
+ )
+ console.print(f"[red]Full output:\n{stdout_result}\n")
+ raise AssertionError("Login output not found before command
output")
+ stdout_result = stdout_result.split(f"{login_output}\n")[1].strip()
+ else:
+ stdout_result = stdout_result.strip()
+
+ # This is a common error message that is thrown by client when
something is wrong
+ # Please ensure it is aligning with
airflowctl.api.client.get_json_error
+ airflowctl_client_server_response_error = "Server error"
+ airflowctl_command_error = "command error: argument GROUP_OR_COMMAND:
invalid choice"
+ if (
+ airflowctl_client_server_response_error in stdout_result
+ or airflowctl_command_error in stdout_result
+ ):
+ console.print(f"[red]❌ Output contained unexpected text for
command '{command_from_config}'")
+ console.print(f"[red]Did not expect to
find:\n{airflowctl_client_server_response_error}\n")
+ console.print(f"[red]But got:\n{stdout_result}\n")
+ raise AssertionError(f"Output contained unexpected
text\nOutput:\n{stdout_result}")
+ console.print(f"[green]✅ Output did not contain unexpected text for
command '{command_from_config}'")
+ console.print(f"[cyan]Result:\n{stdout_result}\n")
+ proc.kill()
diff --git a/airflow-ctl/docs/cli-and-env-variables-ref.rst
b/airflow-ctl/docs/cli-and-env-variables-ref.rst
index aebe2c56579..2d63d47d5be 100644
--- a/airflow-ctl/docs/cli-and-env-variables-ref.rst
+++ b/airflow-ctl/docs/cli-and-env-variables-ref.rst
@@ -53,3 +53,10 @@ Environment Variables
required if you have multiple environments set up and want to
specify which one to use. If not set, the default environment
will be used which is production.
+
+.. envvar:: AIRFLOW_CLI_DEBUG_MODE
+
+ This variable can be used to enable debug mode for the CLI.
+ It disables some features such as keyring integration and save credentials
to file.
+ It is only meant to use if either you are developing airflowctl or running
API integration tests.
+ Please do not use this variable unless you know what you are doing.
diff --git a/airflow-ctl/src/airflowctl/api/client.py
b/airflow-ctl/src/airflowctl/api/client.py
index 4f2ee2fbd8d..09947833baf 100644
--- a/airflow-ctl/src/airflowctl/api/client.py
+++ b/airflow-ctl/src/airflowctl/api/client.py
@@ -94,6 +94,8 @@ def get_json_error(response: httpx.Response):
"""Raise a ServerResponseError if we can extract error info from the
error."""
err = ServerResponseError.from_response(response)
if err:
+ # This part is used in integration tests to verify the error message
+ # If you are updating here don't forget to update the airflow-ctl-tests
log.warning("Server error ", extra=dict(err.response.json()))
raise err
@@ -133,8 +135,15 @@ class Credentials:
os.makedirs(default_config_dir, exist_ok=True)
with open(os.path.join(default_config_dir,
self.input_cli_config_file), "w") as f:
json.dump({"api_url": self.api_url}, f)
+
try:
- keyring.set_password("airflowctl",
f"api_token-{self.api_environment}", self.api_token)
+ if os.getenv("AIRFLOW_CLI_DEBUG_MODE") == "true":
+ with open(
+ os.path.join(default_config_dir,
f"debug_creds_{self.input_cli_config_file}"), "w"
+ ) as f:
+ json.dump({f"api_token_{self.api_environment}":
self.api_token}, f)
+ else:
+ keyring.set_password("airflowctl",
f"api_token_{self.api_environment}", self.api_token)
except NoKeyringError as e:
log.error(e)
except TypeError as e:
@@ -145,12 +154,20 @@ class Credentials:
def load(self) -> Credentials:
"""Load the credentials from keyring and URL from disk file."""
default_config_dir = os.environ.get("AIRFLOW_HOME",
os.path.expanduser("~/airflow"))
- credential_path = os.path.join(default_config_dir,
self.input_cli_config_file)
+ config_path = os.path.join(default_config_dir,
self.input_cli_config_file)
try:
- with open(credential_path) as f:
+ with open(config_path) as f:
credentials = json.load(f)
self.api_url = credentials["api_url"]
- self.api_token = keyring.get_password("airflowctl",
f"api_token-{self.api_environment}")
+ if os.getenv("AIRFLOW_CLI_DEBUG_MODE") == "true":
+ debug_creds_path = os.path.join(
+ default_config_dir,
f"debug_creds_{self.input_cli_config_file}"
+ )
+ with open(debug_creds_path) as df:
+ debug_credentials = json.load(df)
+ self.api_token =
debug_credentials.get(f"api_token_{self.api_environment}")
+ else:
+ self.api_token = keyring.get_password("airflowctl",
f"api_token_{self.api_environment}")
except FileNotFoundError:
if self.client_kind == ClientKind.AUTH:
# Saving the URL set from the Auth Commands if Kind is AUTH
diff --git a/airflow-ctl/src/airflowctl/ctl/cli_config.py
b/airflow-ctl/src/airflowctl/ctl/cli_config.py
index 085fe5abec4..fb6a7dfde0d 100644
--- a/airflow-ctl/src/airflowctl/ctl/cli_config.py
+++ b/airflow-ctl/src/airflowctl/ctl/cli_config.py
@@ -66,6 +66,12 @@ def lazy_load_command(import_path: str) -> Callable:
def safe_call_command(function: Callable, args: Iterable[Arg]) -> None:
import sys
+ if os.getenv("AIRFLOW_CLI_DEBUG_MODE") == "true":
+ rich.print(
+ "[yellow]Debug mode is enabled. Please be aware that your
credentials are not secure.\n"
+ "Please unset AIRFLOW_CLI_DEBUG_MODE or set it to false.[/yellow]"
+ )
+
try:
function(args)
except AirflowCtlCredentialNotFoundException as e:
@@ -187,7 +193,8 @@ class Password(argparse.Action):
"""Custom action to prompt for password input."""
def __call__(self, parser, namespace, values, option_string=None):
- values = getpass.getpass()
+ if values is None:
+ values = getpass.getpass()
setattr(namespace, self.dest, values)
diff --git a/airflow-ctl/tests/airflow_ctl/ctl/commands/test_auth_command.py
b/airflow-ctl/tests/airflow_ctl/ctl/commands/test_auth_command.py
index c5285710d58..0ad428ee5ba 100644
--- a/airflow-ctl/tests/airflow_ctl/ctl/commands/test_auth_command.py
+++ b/airflow-ctl/tests/airflow_ctl/ctl/commands/test_auth_command.py
@@ -67,7 +67,7 @@ class TestCliAuthCommands:
assert json.load(f) == {"api_url": "http://localhost:8080"}
mock_keyring.set_password.assert_called_once_with(
- "airflowctl", "api_token-TEST_AUTH_LOGIN", "TEST_TOKEN"
+ "airflowctl", "api_token_TEST_AUTH_LOGIN", "TEST_TOKEN"
)
# Test auth login with username and password
@@ -102,7 +102,7 @@ class TestCliAuthCommands:
)
mock_keyring.set_password.assert_has_calls(
[
- mock.call("airflowctl", "api_token-production", ""),
- mock.call("airflowctl", "api_token-production",
"TEST_TOKEN"),
+ mock.call("airflowctl", "api_token_production", ""),
+ mock.call("airflowctl", "api_token_production",
"TEST_TOKEN"),
]
)
diff --git a/dev/breeze/doc/05_test_commands.rst
b/dev/breeze/doc/05_test_commands.rst
index adb20969803..6d46779f749 100644
--- a/dev/breeze/doc/05_test_commands.rst
+++ b/dev/breeze/doc/05_test_commands.rst
@@ -197,6 +197,25 @@ Here is the detailed set of options for the ``breeze
testing airflow-ctl-tests``
:width: 100%
:alt: Breeze testing airflow-ctl-tests
+Running airflowctl integration tests
+..................................
+
+You can use Breeze to run the airflowctl integration tests. Those tests are
run using Production image by default
+and the tests are running with the docker-compose we have for airflowctl tests.
+
+.. image:: ./images/output_testing_airflow-ctl-integration-tests.svg
+ :target:
https://raw.githubusercontent.com/apache/airflow/main/dev/breeze/images/output_testing_airflow-ctl-integration-tests.svg
+ :width: 100%
+ :alt: Breeze testing airflow-ctl-integration-tests
+
+You can also iterate over those tests with pytest command but unlike regular
unit tests, they need to be run in
+a local venv. You can build the prod image with breeze and that will be used
by default if present to run the tests.
+
+You can override the ``DOCKER_IMAGE`` environment variable to point to the
image to test using the
+``breeze testing airflow-ctl-integration-tests`` command.
+
+The airflowctl tests are in ``airflow-ctl-tests/`` folder in the main repo.
+
Running integration core tests
...............................
@@ -323,7 +342,7 @@ Running task-sdk integration tests
You can use Breeze to run the task sdk integration tests. Those tests are run
using Production image by default
and the tests are running with the docker-compose we have for task-sdk tests.
-.. image:: ./images/output_testing_docker-compose-tests.svg
+.. image:: ./images/output_testing_task-sdk-integration-tests.svg
:target:
https://raw.githubusercontent.com/apache/airflow/main/dev/breeze/images/output_testing_task-sdk-integration-tests.svg
:width: 100%
:alt: Breeze testing task-sdk-integration-tests
diff --git a/dev/breeze/doc/images/output_setup_check-all-params-in-groups.svg
b/dev/breeze/doc/images/output_setup_check-all-params-in-groups.svg
index 53d05594312..966d335728c 100644
--- a/dev/breeze/doc/images/output_setup_check-all-params-in-groups.svg
+++ b/dev/breeze/doc/images/output_setup_check-all-params-in-groups.svg
@@ -219,11 +219,11 @@
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="752"
textLength="12.2"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-30)">│</text><text
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="752"
textLength="1256.6"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-30)">sbom:export-dependency-information | sbom:generate-providers-requirements |            &
[...]
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0"
y="776.4" textLength="12.2"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-31)">│</text><text
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="776.4"
textLength="1256.6"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-31)">sbom:update-sbom-information | setup | setup:autocomplete | setup:check-all-params-in-groups |     
[...]
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0"
y="800.8" textLength="12.2"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-32)">│</text><text
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="800.8"
textLength="1256.6"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-32)">setup:config | setup:regenerate-command-images | setup:self-upgrade | setup:synchronize-local-mounts | </text><text
cla [...]
-</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0"
y="825.2" textLength="12.2"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-33)">│</text><text
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="825.2"
textLength="1256.6"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-33)">setup:version | shell | start-airflow | testing | testing:airflow-ctl-tests |      &
[...]
-</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0"
y="849.6" textLength="12.2"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-34)">│</text><text
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="849.6"
textLength="1256.6"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-34)">testing:core-integration-tests | testing:core-tests | testing:docker-compose-tests | testing:helm-tests</text><text
class="breeze [...]
-</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="874"
textLength="12.2"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-35)">│</text><text
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="874"
textLength="1256.6"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-35)">| testing:providers-integration-tests | testing:providers-tests | testing:python-api-client-tests |    </text><te
[...]
-</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0"
y="898.4" textLength="12.2"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-36)">│</text><text
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="898.4"
textLength="1256.6"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-36)">testing:system-tests | testing:task-sdk-integration-tests | testing:task-sdk-tests | workflow-run |    <
[...]
-</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0"
y="922.8" textLength="12.2"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-37)">│</text><text
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="922.8"
textLength="1256.6"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-37)">workflow-run:publish-docs)                      &
[...]
+</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0"
y="825.2" textLength="12.2"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-33)">│</text><text
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="825.2"
textLength="1256.6"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-33)">setup:version | shell | start-airflow | testing | testing:airflow-ctl-integration-tests |    &
[...]
+</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0"
y="849.6" textLength="12.2"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-34)">│</text><text
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="849.6"
textLength="1256.6"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-34)">testing:airflow-ctl-tests | testing:core-integration-tests | testing:core-tests |         
[...]
+</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="874"
textLength="12.2"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-35)">│</text><text
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="874"
textLength="1256.6"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-35)">testing:docker-compose-tests | testing:helm-tests | testing:providers-integration-tests |        &
[...]
+</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0"
y="898.4" textLength="12.2"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-36)">│</text><text
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="898.4"
textLength="1256.6"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-36)">testing:providers-tests | testing:python-api-client-tests | testing:system-tests |        
[...]
+</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0"
y="922.8" textLength="12.2"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-37)">│</text><text
class="breeze-setup-check-all-params-in-groups-r6" x="183" y="922.8"
textLength="1256.6"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-37)">testing:task-sdk-integration-tests | testing:task-sdk-tests | workflow-run | workflow-run:publish-docs)</text><text
class="breeze [...]
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0"
y="947.2" textLength="1464"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-38)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-setup-check-all-params-in-groups-r1" x="1464" y="947.2"
textLength="12.2"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-38)">
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0"
y="971.6" textLength="24.4"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-39)">╭─</text><text
class="breeze-setup-check-all-params-in-groups-r5" x="24.4" y="971.6"
textLength="195.2"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-39)"> Common options </text><text
class="breeze-setup-check-all-params-in-groups-r5" x="219.6" y="971.6"
textLength="1220" clip-path="url(#breeze-se [...]
</text><text class="breeze-setup-check-all-params-in-groups-r5" x="0" y="996"
textLength="12.2"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-40)">│</text><text
class="breeze-setup-check-all-params-in-groups-r4" x="24.4" y="996"
textLength="109.8"
clip-path="url(#breeze-setup-check-all-params-in-groups-line-40)">--verbose</text><text
class="breeze-setup-check-all-params-in-groups-r7" x="158.6" y="996"
textLength="24.4" clip-path="url(#breeze-setup-check-all-params-in-group [...]
diff --git a/dev/breeze/doc/images/output_setup_check-all-params-in-groups.txt
b/dev/breeze/doc/images/output_setup_check-all-params-in-groups.txt
index 2a2e1152f40..73a88df7b00 100644
--- a/dev/breeze/doc/images/output_setup_check-all-params-in-groups.txt
+++ b/dev/breeze/doc/images/output_setup_check-all-params-in-groups.txt
@@ -1 +1 @@
-b1fdbd0feeb3b5ba14f79cc8f13d8e1a
+d5bec5c837a57060f11d308e952d5e0a
diff --git a/dev/breeze/doc/images/output_setup_regenerate-command-images.svg
b/dev/breeze/doc/images/output_setup_regenerate-command-images.svg
index bb856ad3a34..9c53cb893c6 100644
--- a/dev/breeze/doc/images/output_setup_regenerate-command-images.svg
+++ b/dev/breeze/doc/images/output_setup_regenerate-command-images.svg
@@ -1,4 +1,4 @@
-<svg class="rich-terminal" viewBox="0 0 1482 1221.1999999999998"
xmlns="http://www.w3.org/2000/svg">
+<svg class="rich-terminal" viewBox="0 0 1482 1245.6"
xmlns="http://www.w3.org/2000/svg">
<!-- Generated with Rich https://www.textualize.io -->
<style>
@@ -43,7 +43,7 @@
<defs>
<clipPath id="breeze-setup-regenerate-command-images-clip-terminal">
- <rect x="0" y="0" width="1463.0" height="1170.1999999999998" />
+ <rect x="0" y="0" width="1463.0" height="1194.6" />
</clipPath>
<clipPath id="breeze-setup-regenerate-command-images-line-0">
<rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -186,9 +186,12 @@
<clipPath id="breeze-setup-regenerate-command-images-line-46">
<rect x="0" y="1123.9" width="1464" height="24.65"/>
</clipPath>
+<clipPath id="breeze-setup-regenerate-command-images-line-47">
+ <rect x="0" y="1148.3" width="1464" height="24.65"/>
+ </clipPath>
</defs>
- <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1"
x="1" y="1" width="1480" height="1219.2" rx="8"/><text
class="breeze-setup-regenerate-command-images-title" fill="#c5c8c6"
text-anchor="middle" x="740"
y="27">Command: setup regenerate-command-images</text>
+ <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1"
x="1" y="1" width="1480" height="1243.6" rx="8"/><text
class="breeze-setup-regenerate-command-images-title" fill="#c5c8c6"
text-anchor="middle" x="740"
y="27">Command: setup regenerate-command-images</text>
<g transform="translate(26,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
@@ -233,19 +236,20 @@
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="800.8"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-32)">│</text><text
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="800.8"
textLength="1220"
clip-path="url(#breeze-setup-regenerate-command-images-line-32)">sbom:export-dependency-information | sbom:generate-providers-requirements |            &
[...]
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="825.2"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-33)">│</text><text
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="825.2"
textLength="1220"
clip-path="url(#breeze-setup-regenerate-command-images-line-33)">sbom:update-sbom-information | setup | setup:autocomplete | setup:check-all-params-in-groups |     
[...]
</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="849.6"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-34)">│</text><text
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="849.6"
textLength="1220"
clip-path="url(#breeze-setup-regenerate-command-images-line-34)">setup:config | setup:regenerate-command-images | setup:self-upgrade | setup:synchronize-local-mounts</text><text
class="breeze-setup- [...]
-</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="874"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-35)">│</text><text
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="874"
textLength="1220"
clip-path="url(#breeze-setup-regenerate-command-images-line-35)">| setup:version | shell | start-airflow | testing | testing:airflow-ctl-tests |      &#
[...]
-</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="898.4"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-36)">│</text><text
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="898.4"
textLength="1220"
clip-path="url(#breeze-setup-regenerate-command-images-line-36)">testing:core-integration-tests | testing:core-tests | testing:docker-compose-tests |         
[...]
-</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="922.8"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-37)">│</text><text
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="922.8"
textLength="1220"
clip-path="url(#breeze-setup-regenerate-command-images-line-37)">testing:helm-tests | testing:providers-integration-tests | testing:providers-tests |         
[...]
-</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="947.2"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-38)">│</text><text
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="947.2"
textLength="1220"
clip-path="url(#breeze-setup-regenerate-command-images-line-38)">testing:python-api-client-tests | testing:system-tests | testing:task-sdk-integration-tests |       </t
[...]
-</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="971.6"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-39)">│</text><text
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="971.6"
textLength="1220"
clip-path="url(#breeze-setup-regenerate-command-images-line-39)">testing:task-sdk-tests | workflow-run | workflow-run:publish-docs)             
[...]
-</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="996"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-40)">│</text><text
class="breeze-setup-regenerate-command-images-r4" x="24.4" y="996"
textLength="146.4"
clip-path="url(#breeze-setup-regenerate-command-images-line-40)">--check-only</text><text
class="breeze-setup-regenerate-command-images-r1" x="219.6" y="996"
textLength="1220" clip-path="url(#breeze-setup-regenerate-command-images-l [...]
-</text><text class="breeze-setup-regenerate-command-images-r5" x="0"
y="1020.4" textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-41)">│</text><text
class="breeze-setup-regenerate-command-images-r1" x="219.6" y="1020.4"
textLength="170.8"
clip-path="url(#breeze-setup-regenerate-command-images-line-41)">together with </text><text
class="breeze-setup-regenerate-command-images-r4" x="390.4" y="1020.4"
textLength="109.8" clip-path="url(#breeze-setup-rege [...]
-</text><text class="breeze-setup-regenerate-command-images-r5" x="0"
y="1044.8" textLength="1464"
clip-path="url(#breeze-setup-regenerate-command-images-line-42)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-setup-regenerate-command-images-r1" x="1464" y="1044.8"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-42)">
-</text><text class="breeze-setup-regenerate-command-images-r5" x="0"
y="1069.2" textLength="24.4"
clip-path="url(#breeze-setup-regenerate-command-images-line-43)">╭─</text><text
class="breeze-setup-regenerate-command-images-r5" x="24.4" y="1069.2"
textLength="195.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-43)"> Common options </text><text
class="breeze-setup-regenerate-command-images-r5" x="219.6" y="1069.2"
textLength="1220" clip-path="url(#breeze-setu [...]
-</text><text class="breeze-setup-regenerate-command-images-r5" x="0"
y="1093.6" textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-44)">│</text><text
class="breeze-setup-regenerate-command-images-r4" x="24.4" y="1093.6"
textLength="109.8"
clip-path="url(#breeze-setup-regenerate-command-images-line-44)">--verbose</text><text
class="breeze-setup-regenerate-command-images-r7" x="158.6" y="1093.6"
textLength="24.4" clip-path="url(#breeze-setup-regenerate-command-im [...]
-</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1118"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-45)">│</text><text
class="breeze-setup-regenerate-command-images-r4" x="24.4" y="1118"
textLength="109.8"
clip-path="url(#breeze-setup-regenerate-command-images-line-45)">--dry-run</text><text
class="breeze-setup-regenerate-command-images-r7" x="158.6" y="1118"
textLength="24.4" clip-path="url(#breeze-setup-regenerate-command-images-l [...]
-</text><text class="breeze-setup-regenerate-command-images-r5" x="0"
y="1142.4" textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-46)">│</text><text
class="breeze-setup-regenerate-command-images-r4" x="24.4" y="1142.4"
textLength="73.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-46)">--help</text><text
class="breeze-setup-regenerate-command-images-r7" x="158.6" y="1142.4"
textLength="24.4" clip-path="url(#breeze-setup-regenerate-command-images [...]
-</text><text class="breeze-setup-regenerate-command-images-r5" x="0"
y="1166.8" textLength="1464"
clip-path="url(#breeze-setup-regenerate-command-images-line-47)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-setup-regenerate-command-images-r1" x="1464" y="1166.8"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-47)">
+</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="874"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-35)">│</text><text
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="874"
textLength="1220"
clip-path="url(#breeze-setup-regenerate-command-images-line-35)">| setup:version | shell | start-airflow | testing | testing:airflow-ctl-integration-tests |    &#
[...]
+</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="898.4"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-36)">│</text><text
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="898.4"
textLength="1220"
clip-path="url(#breeze-setup-regenerate-command-images-line-36)">testing:airflow-ctl-tests | testing:core-integration-tests | testing:core-tests |         
[...]
+</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="922.8"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-37)">│</text><text
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="922.8"
textLength="1220"
clip-path="url(#breeze-setup-regenerate-command-images-line-37)">testing:docker-compose-tests | testing:helm-tests | testing:providers-integration-tests |        &
[...]
+</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="947.2"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-38)">│</text><text
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="947.2"
textLength="1220"
clip-path="url(#breeze-setup-regenerate-command-images-line-38)">testing:providers-tests | testing:python-api-client-tests | testing:system-tests |         &#
[...]
+</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="971.6"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-39)">│</text><text
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="971.6"
textLength="1220"
clip-path="url(#breeze-setup-regenerate-command-images-line-39)">testing:task-sdk-integration-tests | testing:task-sdk-tests | workflow-run |          &#
[...]
+</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="996"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-40)">│</text><text
class="breeze-setup-regenerate-command-images-r6" x="219.6" y="996"
textLength="1220"
clip-path="url(#breeze-setup-regenerate-command-images-line-40)">workflow-run:publish-docs)                       
[...]
+</text><text class="breeze-setup-regenerate-command-images-r5" x="0"
y="1020.4" textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-41)">│</text><text
class="breeze-setup-regenerate-command-images-r4" x="24.4" y="1020.4"
textLength="146.4"
clip-path="url(#breeze-setup-regenerate-command-images-line-41)">--check-only</text><text
class="breeze-setup-regenerate-command-images-r1" x="219.6" y="1020.4"
textLength="1220" clip-path="url(#breeze-setup-regenerate-command [...]
+</text><text class="breeze-setup-regenerate-command-images-r5" x="0"
y="1044.8" textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-42)">│</text><text
class="breeze-setup-regenerate-command-images-r1" x="219.6" y="1044.8"
textLength="170.8"
clip-path="url(#breeze-setup-regenerate-command-images-line-42)">together with </text><text
class="breeze-setup-regenerate-command-images-r4" x="390.4" y="1044.8"
textLength="109.8" clip-path="url(#breeze-setup-rege [...]
+</text><text class="breeze-setup-regenerate-command-images-r5" x="0"
y="1069.2" textLength="1464"
clip-path="url(#breeze-setup-regenerate-command-images-line-43)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-setup-regenerate-command-images-r1" x="1464" y="1069.2"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-43)">
+</text><text class="breeze-setup-regenerate-command-images-r5" x="0"
y="1093.6" textLength="24.4"
clip-path="url(#breeze-setup-regenerate-command-images-line-44)">╭─</text><text
class="breeze-setup-regenerate-command-images-r5" x="24.4" y="1093.6"
textLength="195.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-44)"> Common options </text><text
class="breeze-setup-regenerate-command-images-r5" x="219.6" y="1093.6"
textLength="1220" clip-path="url(#breeze-setu [...]
+</text><text class="breeze-setup-regenerate-command-images-r5" x="0" y="1118"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-45)">│</text><text
class="breeze-setup-regenerate-command-images-r4" x="24.4" y="1118"
textLength="109.8"
clip-path="url(#breeze-setup-regenerate-command-images-line-45)">--verbose</text><text
class="breeze-setup-regenerate-command-images-r7" x="158.6" y="1118"
textLength="24.4" clip-path="url(#breeze-setup-regenerate-command-images-l [...]
+</text><text class="breeze-setup-regenerate-command-images-r5" x="0"
y="1142.4" textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-46)">│</text><text
class="breeze-setup-regenerate-command-images-r4" x="24.4" y="1142.4"
textLength="109.8"
clip-path="url(#breeze-setup-regenerate-command-images-line-46)">--dry-run</text><text
class="breeze-setup-regenerate-command-images-r7" x="158.6" y="1142.4"
textLength="24.4" clip-path="url(#breeze-setup-regenerate-command-im [...]
+</text><text class="breeze-setup-regenerate-command-images-r5" x="0"
y="1166.8" textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-47)">│</text><text
class="breeze-setup-regenerate-command-images-r4" x="24.4" y="1166.8"
textLength="73.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-47)">--help</text><text
class="breeze-setup-regenerate-command-images-r7" x="158.6" y="1166.8"
textLength="24.4" clip-path="url(#breeze-setup-regenerate-command-images [...]
+</text><text class="breeze-setup-regenerate-command-images-r5" x="0"
y="1191.2" textLength="1464"
clip-path="url(#breeze-setup-regenerate-command-images-line-48)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-setup-regenerate-command-images-r1" x="1464" y="1191.2"
textLength="12.2"
clip-path="url(#breeze-setup-regenerate-command-images-line-48)">
</text>
</g>
</g>
diff --git a/dev/breeze/doc/images/output_setup_regenerate-command-images.txt
b/dev/breeze/doc/images/output_setup_regenerate-command-images.txt
index f0afa5ca432..e6b55de9912 100644
--- a/dev/breeze/doc/images/output_setup_regenerate-command-images.txt
+++ b/dev/breeze/doc/images/output_setup_regenerate-command-images.txt
@@ -1 +1 @@
-3cb28adadcff7f43ae20d44507368acc
+4929732b82fa439a5365a1d3cf5015e3
diff --git a/dev/breeze/doc/images/output_testing.svg
b/dev/breeze/doc/images/output_testing.svg
index dee85765d4a..46f24ee7253 100644
--- a/dev/breeze/doc/images/output_testing.svg
+++ b/dev/breeze/doc/images/output_testing.svg
@@ -1,4 +1,4 @@
-<svg class="rich-terminal" viewBox="0 0 1482 757.5999999999999"
xmlns="http://www.w3.org/2000/svg">
+<svg class="rich-terminal" viewBox="0 0 1482 782.0"
xmlns="http://www.w3.org/2000/svg">
<!-- Generated with Rich https://www.textualize.io -->
<style>
@@ -42,7 +42,7 @@
<defs>
<clipPath id="breeze-testing-clip-terminal">
- <rect x="0" y="0" width="1463.0" height="706.5999999999999" />
+ <rect x="0" y="0" width="1463.0" height="731.0" />
</clipPath>
<clipPath id="breeze-testing-line-0">
<rect x="0" y="1.5" width="1464" height="24.65"/>
@@ -128,17 +128,20 @@
<clipPath id="breeze-testing-line-27">
<rect x="0" y="660.3" width="1464" height="24.65"/>
</clipPath>
+<clipPath id="breeze-testing-line-28">
+ <rect x="0" y="684.7" width="1464" height="24.65"/>
+ </clipPath>
</defs>
- <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1"
x="1" y="1" width="1480" height="755.6" rx="8"/><text
class="breeze-testing-title" fill="#c5c8c6" text-anchor="middle" x="740"
y="27">Command: testing</text>
+ <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1"
x="1" y="1" width="1480" height="780" rx="8"/><text
class="breeze-testing-title" fill="#c5c8c6" text-anchor="middle" x="740"
y="27">Command: testing</text>
<g transform="translate(26,22)">
<circle cx="0" cy="0" r="7" fill="#ff5f57"/>
<circle cx="22" cy="0" r="7" fill="#febc2e"/>
<circle cx="44" cy="0" r="7" fill="#28c840"/>
</g>
-
+
<g transform="translate(9, 41)"
clip-path="url(#breeze-testing-clip-terminal)">
-
+
<g class="breeze-testing-matrix">
<text class="breeze-testing-r1" x="1464" y="20" textLength="12.2"
clip-path="url(#breeze-testing-line-0)">
</text><text class="breeze-testing-r2" x="12.2" y="44.4" textLength="73.2"
clip-path="url(#breeze-testing-line-1)">Usage:</text><text
class="breeze-testing-r3" x="97.6" y="44.4" textLength="170.8"
clip-path="url(#breeze-testing-line-1)">breeze testing</text><text
class="breeze-testing-r1" x="280.6" y="44.4" textLength="12.2"
clip-path="url(#breeze-testing-line-1)">[</text><text class="breeze-testing-r4"
x="292.8" y="44.4" textLength="85.4"
clip-path="url(#breeze-testing-line-1)">OPT [...]
@@ -161,14 +164,15 @@
</text><text class="breeze-testing-r5" x="0" y="459.2" textLength="12.2"
clip-path="url(#breeze-testing-line-18)">│</text><text
class="breeze-testing-r4" x="24.4" y="459.2" textLength="414.8"
clip-path="url(#breeze-testing-line-18)">task-sdk-integration-tests        </text><text
class="breeze-testing-r1" x="463.6" y="459.2" textLength="976"
clip-path="url(#breeze-testing-line-18)">Run task SDK integration tests.   
[...]
</text><text class="breeze-testing-r5" x="0" y="483.6" textLength="1464"
clip-path="url(#breeze-testing-line-19)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-testing-r1" x="1464" y="483.6" textLength="12.2"
clip-path="url(#breeze-testing-line-19)">
</text><text class="breeze-testing-r5" x="0" y="508" textLength="24.4"
clip-path="url(#breeze-testing-line-20)">╭─</text><text
class="breeze-testing-r5" x="24.4" y="508" textLength="219.6"
clip-path="url(#breeze-testing-line-20)"> airflowctl Tests </text><text
class="breeze-testing-r5" x="244" y="508" textLength="1195.6"
clip-path="url(#breeze-testing-line-20)">──────────────────────────────────────────────────────────────────────────────────────────────────</text><text
cl [...]
-</text><text class="breeze-testing-r5" x="0" y="532.4" textLength="12.2"
clip-path="url(#breeze-testing-line-21)">│</text><text
class="breeze-testing-r4" x="24.4" y="532.4" textLength="280.6"
clip-path="url(#breeze-testing-line-21)">airflow-ctl-tests      </text><text
class="breeze-testing-r1" x="329.4" y="532.4" textLength="1110.2"
clip-path="url(#breeze-testing-line-21)">Run airflow-ctl tests - all airflowctl tests are
[...]
-</text><text class="breeze-testing-r5" x="0" y="556.8" textLength="1464"
clip-path="url(#breeze-testing-line-22)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-testing-r1" x="1464" y="556.8" textLength="12.2"
clip-path="url(#breeze-testing-line-22)">
-</text><text class="breeze-testing-r5" x="0" y="581.2" textLength="24.4"
clip-path="url(#breeze-testing-line-23)">╭─</text><text
class="breeze-testing-r5" x="24.4" y="581.2" textLength="158.6"
clip-path="url(#breeze-testing-line-23)"> Other Tests </text><text
class="breeze-testing-r5" x="183" y="581.2" textLength="1256.6"
clip-path="url(#breeze-testing-line-23)">───────────────────────────────────────────────────────────────────────────────────────────────────────</text><t
[...]
-</text><text class="breeze-testing-r5" x="0" y="605.6" textLength="12.2"
clip-path="url(#breeze-testing-line-24)">│</text><text
class="breeze-testing-r4" x="24.4" y="605.6" textLength="597.8"
clip-path="url(#breeze-testing-line-24)">system-tests                                     </text><text
class="breez [...]
-</text><text class="breeze-testing-r5" x="0" y="630" textLength="12.2"
clip-path="url(#breeze-testing-line-25)">│</text><text
class="breeze-testing-r4" x="24.4" y="630" textLength="597.8"
clip-path="url(#breeze-testing-line-25)">helm-tests                                       </text><text
class= [...]
-</text><text class="breeze-testing-r5" x="0" y="654.4" textLength="12.2"
clip-path="url(#breeze-testing-line-26)">│</text><text
class="breeze-testing-r4" x="24.4" y="654.4" textLength="597.8"
clip-path="url(#breeze-testing-line-26)">docker-compose-tests                             </text><text
class="breeze-testing-r1" x="646.6" y="654.4" textLe [...]
-</text><text class="breeze-testing-r5" x="0" y="678.8" textLength="12.2"
clip-path="url(#breeze-testing-line-27)">│</text><text
class="breeze-testing-r4" x="24.4" y="678.8" textLength="597.8"
clip-path="url(#breeze-testing-line-27)">python-api-client-tests                          </text><text
class="breeze-testing-r1" x="646.6" y="678.8" textLength="793" clip [...]
-</text><text class="breeze-testing-r5" x="0" y="703.2" textLength="1464"
clip-path="url(#breeze-testing-line-28)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-testing-r1" x="1464" y="703.2" textLength="12.2"
clip-path="url(#breeze-testing-line-28)">
+</text><text class="breeze-testing-r5" x="0" y="532.4" textLength="12.2"
clip-path="url(#breeze-testing-line-21)">│</text><text
class="breeze-testing-r4" x="24.4" y="532.4" textLength="427"
clip-path="url(#breeze-testing-line-21)">airflow-ctl-tests                  </text><text
class="breeze-testing-r1" x="475.8" y="532.4" textLength="963.8"
clip-path="url(#breeze-testing-line-21)">Run airflow- [...]
+</text><text class="breeze-testing-r5" x="0" y="556.8" textLength="12.2"
clip-path="url(#breeze-testing-line-22)">│</text><text
class="breeze-testing-r4" x="24.4" y="556.8" textLength="427"
clip-path="url(#breeze-testing-line-22)">airflow-ctl-integration-tests      </text><text
class="breeze-testing-r1" x="475.8" y="556.8" textLength="963.8"
clip-path="url(#breeze-testing-line-22)">Run airflowctl integration tests.     
[...]
+</text><text class="breeze-testing-r5" x="0" y="581.2" textLength="1464"
clip-path="url(#breeze-testing-line-23)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-testing-r1" x="1464" y="581.2" textLength="12.2"
clip-path="url(#breeze-testing-line-23)">
+</text><text class="breeze-testing-r5" x="0" y="605.6" textLength="24.4"
clip-path="url(#breeze-testing-line-24)">╭─</text><text
class="breeze-testing-r5" x="24.4" y="605.6" textLength="158.6"
clip-path="url(#breeze-testing-line-24)"> Other Tests </text><text
class="breeze-testing-r5" x="183" y="605.6" textLength="1256.6"
clip-path="url(#breeze-testing-line-24)">───────────────────────────────────────────────────────────────────────────────────────────────────────</text><t
[...]
+</text><text class="breeze-testing-r5" x="0" y="630" textLength="12.2"
clip-path="url(#breeze-testing-line-25)">│</text><text
class="breeze-testing-r4" x="24.4" y="630" textLength="597.8"
clip-path="url(#breeze-testing-line-25)">system-tests                                     </text><text
class="breeze-te [...]
+</text><text class="breeze-testing-r5" x="0" y="654.4" textLength="12.2"
clip-path="url(#breeze-testing-line-26)">│</text><text
class="breeze-testing-r4" x="24.4" y="654.4" textLength="597.8"
clip-path="url(#breeze-testing-line-26)">helm-tests                                       </text><text
cl [...]
+</text><text class="breeze-testing-r5" x="0" y="678.8" textLength="12.2"
clip-path="url(#breeze-testing-line-27)">│</text><text
class="breeze-testing-r4" x="24.4" y="678.8" textLength="597.8"
clip-path="url(#breeze-testing-line-27)">docker-compose-tests                             </text><text
class="breeze-testing-r1" x="646.6" y="678.8" textLe [...]
+</text><text class="breeze-testing-r5" x="0" y="703.2" textLength="12.2"
clip-path="url(#breeze-testing-line-28)">│</text><text
class="breeze-testing-r4" x="24.4" y="703.2" textLength="597.8"
clip-path="url(#breeze-testing-line-28)">python-api-client-tests                          </text><text
class="breeze-testing-r1" x="646.6" y="703.2" textLength="793" clip [...]
+</text><text class="breeze-testing-r5" x="0" y="727.6" textLength="1464"
clip-path="url(#breeze-testing-line-29)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-testing-r1" x="1464" y="727.6" textLength="12.2"
clip-path="url(#breeze-testing-line-29)">
</text>
</g>
</g>
diff --git a/dev/breeze/doc/images/output_testing.txt
b/dev/breeze/doc/images/output_testing.txt
index 4c8b01d5b33..8af7b605cec 100644
--- a/dev/breeze/doc/images/output_testing.txt
+++ b/dev/breeze/doc/images/output_testing.txt
@@ -1 +1 @@
-274babc2dd223dd8f885ffc9c2e5d08f
+4f9401ad7a5bc3411a8d41a33580d7e7
diff --git
a/dev/breeze/doc/images/output_testing_airflow-ctl-integration-tests.svg
b/dev/breeze/doc/images/output_testing_airflow-ctl-integration-tests.svg
new file mode 100644
index 00000000000..e6630729158
--- /dev/null
+++ b/dev/breeze/doc/images/output_testing_airflow-ctl-integration-tests.svg
@@ -0,0 +1,144 @@
+<svg class="rich-terminal" viewBox="0 0 1482 562.4"
xmlns="http://www.w3.org/2000/svg">
+ <!-- Generated with Rich https://www.textualize.io -->
+ <style>
+
+ @font-face {
+ font-family: "Fira Code";
+ src: local("FiraCode-Regular"),
+
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Regular.woff2")
format("woff2"),
+
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Regular.woff")
format("woff");
+ font-style: normal;
+ font-weight: 400;
+ }
+ @font-face {
+ font-family: "Fira Code";
+ src: local("FiraCode-Bold"),
+
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff2/FiraCode-Bold.woff2")
format("woff2"),
+
url("https://cdnjs.cloudflare.com/ajax/libs/firacode/6.2.0/woff/FiraCode-Bold.woff")
format("woff");
+ font-style: bold;
+ font-weight: 700;
+ }
+
+ .breeze-testing-airflow-ctl-integration-tests-matrix {
+ font-family: Fira Code, monospace;
+ font-size: 20px;
+ line-height: 24.4px;
+ font-variant-east-asian: full-width;
+ }
+
+ .breeze-testing-airflow-ctl-integration-tests-title {
+ font-size: 18px;
+ font-weight: bold;
+ font-family: arial;
+ }
+
+ .breeze-testing-airflow-ctl-integration-tests-r1 { fill: #c5c8c6 }
+.breeze-testing-airflow-ctl-integration-tests-r2 { fill: #d0b344 }
+.breeze-testing-airflow-ctl-integration-tests-r3 { fill: #c5c8c6;font-weight:
bold }
+.breeze-testing-airflow-ctl-integration-tests-r4 { fill: #68a0b3;font-weight:
bold }
+.breeze-testing-airflow-ctl-integration-tests-r5 { fill: #868887 }
+.breeze-testing-airflow-ctl-integration-tests-r6 { fill: #98a84b;font-weight:
bold }
+.breeze-testing-airflow-ctl-integration-tests-r7 { fill: #8d7b39 }
+ </style>
+
+ <defs>
+ <clipPath id="breeze-testing-airflow-ctl-integration-tests-clip-terminal">
+ <rect x="0" y="0" width="1463.0" height="511.4" />
+ </clipPath>
+ <clipPath id="breeze-testing-airflow-ctl-integration-tests-line-0">
+ <rect x="0" y="1.5" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-testing-airflow-ctl-integration-tests-line-1">
+ <rect x="0" y="25.9" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-testing-airflow-ctl-integration-tests-line-2">
+ <rect x="0" y="50.3" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-testing-airflow-ctl-integration-tests-line-3">
+ <rect x="0" y="74.7" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-testing-airflow-ctl-integration-tests-line-4">
+ <rect x="0" y="99.1" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-testing-airflow-ctl-integration-tests-line-5">
+ <rect x="0" y="123.5" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-testing-airflow-ctl-integration-tests-line-6">
+ <rect x="0" y="147.9" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-testing-airflow-ctl-integration-tests-line-7">
+ <rect x="0" y="172.3" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-testing-airflow-ctl-integration-tests-line-8">
+ <rect x="0" y="196.7" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-testing-airflow-ctl-integration-tests-line-9">
+ <rect x="0" y="221.1" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-testing-airflow-ctl-integration-tests-line-10">
+ <rect x="0" y="245.5" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-testing-airflow-ctl-integration-tests-line-11">
+ <rect x="0" y="269.9" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-testing-airflow-ctl-integration-tests-line-12">
+ <rect x="0" y="294.3" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-testing-airflow-ctl-integration-tests-line-13">
+ <rect x="0" y="318.7" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-testing-airflow-ctl-integration-tests-line-14">
+ <rect x="0" y="343.1" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-testing-airflow-ctl-integration-tests-line-15">
+ <rect x="0" y="367.5" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-testing-airflow-ctl-integration-tests-line-16">
+ <rect x="0" y="391.9" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-testing-airflow-ctl-integration-tests-line-17">
+ <rect x="0" y="416.3" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-testing-airflow-ctl-integration-tests-line-18">
+ <rect x="0" y="440.7" width="1464" height="24.65"/>
+ </clipPath>
+<clipPath id="breeze-testing-airflow-ctl-integration-tests-line-19">
+ <rect x="0" y="465.1" width="1464" height="24.65"/>
+ </clipPath>
+ </defs>
+
+ <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1"
x="1" y="1" width="1480" height="560.4" rx="8"/><text
class="breeze-testing-airflow-ctl-integration-tests-title" fill="#c5c8c6"
text-anchor="middle" x="740"
y="27">Command: testing airflow-ctl-integration-tests</text>
+ <g transform="translate(26,22)">
+ <circle cx="0" cy="0" r="7" fill="#ff5f57"/>
+ <circle cx="22" cy="0" r="7" fill="#febc2e"/>
+ <circle cx="44" cy="0" r="7" fill="#28c840"/>
+ </g>
+
+ <g transform="translate(9, 41)"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-clip-terminal)">
+
+ <g class="breeze-testing-airflow-ctl-integration-tests-matrix">
+ <text class="breeze-testing-airflow-ctl-integration-tests-r1" x="1464"
y="20" textLength="12.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-0)">
+</text><text class="breeze-testing-airflow-ctl-integration-tests-r2" x="12.2"
y="44.4" textLength="73.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-1)">Usage:</text><text
class="breeze-testing-airflow-ctl-integration-tests-r3" x="97.6" y="44.4"
textLength="536.8"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-1)">breeze testing airflow-ctl-integration-tests</text><text
class="breeze-testing-airflow-ctl-integration-tests-r1" x="646.6" y= [...]
+</text><text class="breeze-testing-airflow-ctl-integration-tests-r1" x="1464"
y="68.8" textLength="12.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-2)">
+</text><text class="breeze-testing-airflow-ctl-integration-tests-r1" x="12.2"
y="93.2" textLength="402.6"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-3)">Run airflowctl integration tests.</text><text
class="breeze-testing-airflow-ctl-integration-tests-r1" x="1464" y="93.2"
textLength="12.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-3)">
+</text><text class="breeze-testing-airflow-ctl-integration-tests-r1" x="1464"
y="117.6" textLength="12.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-4)">
+</text><text class="breeze-testing-airflow-ctl-integration-tests-r5" x="0"
y="142" textLength="24.4"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-5)">╭─</text><text
class="breeze-testing-airflow-ctl-integration-tests-r5" x="24.4" y="142"
textLength="329.4"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-5)"> Docker-compose tests flag </text><text
class="breeze-testing-airflow-ctl-integration-tests-r5" x="353.8" y="142"
textLength [...]
+</text><text class="breeze-testing-airflow-ctl-integration-tests-r5" x="0"
y="166.4" textLength="12.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-6)">│</text><text
class="breeze-testing-airflow-ctl-integration-tests-r4" x="24.4" y="166.4"
textLength="146.4"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-6)">--image-name</text><text
class="breeze-testing-airflow-ctl-integration-tests-r6" x="414.8" y="166.4"
textLength="24.4" clip-path="url(#breeze [...]
+</text><text class="breeze-testing-airflow-ctl-integration-tests-r5" x="0"
y="190.8" textLength="12.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-7)">│</text><text
class="breeze-testing-airflow-ctl-integration-tests-r4" x="24.4" y="190.8"
textLength="97.6"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-7)">--python</text><text
class="breeze-testing-airflow-ctl-integration-tests-r6" x="414.8" y="190.8"
textLength="24.4" clip-path="url(#breeze-test [...]
+</text><text class="breeze-testing-airflow-ctl-integration-tests-r5" x="0"
y="215.2" textLength="12.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-8)">│</text><text
class="breeze-testing-airflow-ctl-integration-tests-r7" x="463.6" y="215.2"
textLength="732"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-8)">(>3.10< | 3.11 | 3.12 | 3.13)             
[...]
+</text><text class="breeze-testing-airflow-ctl-integration-tests-r5" x="0"
y="239.6" textLength="12.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-9)">│</text><text
class="breeze-testing-airflow-ctl-integration-tests-r5" x="463.6" y="239.6"
textLength="732"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-9)">[default: 3.10]                    &#
[...]
+</text><text class="breeze-testing-airflow-ctl-integration-tests-r5" x="0"
y="264" textLength="12.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-10)">│</text><text
class="breeze-testing-airflow-ctl-integration-tests-r4" x="24.4" y="264"
textLength="366"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-10)">--skip-docker-compose-deletion</text><text
class="breeze-testing-airflow-ctl-integration-tests-r1" x="463.6" y="264"
textLength="671" clip-path=" [...]
+</text><text class="breeze-testing-airflow-ctl-integration-tests-r5" x="0"
y="288.4" textLength="12.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-11)">│</text><text
class="breeze-testing-airflow-ctl-integration-tests-r4" x="24.4" y="288.4"
textLength="305"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-11)">--include-success-outputs</text><text
class="breeze-testing-airflow-ctl-integration-tests-r1" x="463.6" y="288.4"
textLength="841.8" clip-pat [...]
+</text><text class="breeze-testing-airflow-ctl-integration-tests-r5" x="0"
y="312.8" textLength="12.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-12)">│</text><text
class="breeze-testing-airflow-ctl-integration-tests-r4" x="24.4" y="312.8"
textLength="231.8"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-12)">--github-repository</text><text
class="breeze-testing-airflow-ctl-integration-tests-r6" x="414.8" y="312.8"
textLength="24.4" clip-path="ur [...]
+</text><text class="breeze-testing-airflow-ctl-integration-tests-r5" x="0"
y="337.2" textLength="12.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-13)">│</text><text
class="breeze-testing-airflow-ctl-integration-tests-r5" x="463.6" y="337.2"
textLength="585.6"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-13)">[default: apache/airflow]                  
[...]
+</text><text class="breeze-testing-airflow-ctl-integration-tests-r5" x="0"
y="361.6" textLength="12.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-14)">│</text><text
class="breeze-testing-airflow-ctl-integration-tests-r4" x="24.4" y="361.6"
textLength="256.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-14)">--airflow-ctl-version</text><text
class="breeze-testing-airflow-ctl-integration-tests-r1" x="463.6" y="361.6"
textLength="353.8" clip-path= [...]
+</text><text class="breeze-testing-airflow-ctl-integration-tests-r5" x="0"
y="386" textLength="1464"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-15)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-testing-airflow-ctl-integration-tests-r1" x="1464" y="386"
textLength="12.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-15)">
+</text><text class="breeze-testing-airflow-ctl-integration-tests-r5" x="0"
y="410.4" textLength="24.4"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-16)">╭─</text><text
class="breeze-testing-airflow-ctl-integration-tests-r5" x="24.4" y="410.4"
textLength="195.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-16)"> Common options </text><text
class="breeze-testing-airflow-ctl-integration-tests-r5" x="219.6" y="410.4"
textLength="1220" [...]
+</text><text class="breeze-testing-airflow-ctl-integration-tests-r5" x="0"
y="434.8" textLength="12.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-17)">│</text><text
class="breeze-testing-airflow-ctl-integration-tests-r4" x="24.4" y="434.8"
textLength="109.8"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-17)">--verbose</text><text
class="breeze-testing-airflow-ctl-integration-tests-r6" x="158.6" y="434.8"
textLength="24.4" clip-path="url(#breeze- [...]
+</text><text class="breeze-testing-airflow-ctl-integration-tests-r5" x="0"
y="459.2" textLength="12.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-18)">│</text><text
class="breeze-testing-airflow-ctl-integration-tests-r4" x="24.4" y="459.2"
textLength="109.8"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-18)">--dry-run</text><text
class="breeze-testing-airflow-ctl-integration-tests-r6" x="158.6" y="459.2"
textLength="24.4" clip-path="url(#breeze- [...]
+</text><text class="breeze-testing-airflow-ctl-integration-tests-r5" x="0"
y="483.6" textLength="12.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-19)">│</text><text
class="breeze-testing-airflow-ctl-integration-tests-r4" x="24.4" y="483.6"
textLength="73.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-19)">--help</text><text
class="breeze-testing-airflow-ctl-integration-tests-r6" x="158.6" y="483.6"
textLength="24.4" clip-path="url(#breeze-test [...]
+</text><text class="breeze-testing-airflow-ctl-integration-tests-r5" x="0"
y="508" textLength="1464"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-20)">╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯</text><text
class="breeze-testing-airflow-ctl-integration-tests-r1" x="1464" y="508"
textLength="12.2"
clip-path="url(#breeze-testing-airflow-ctl-integration-tests-line-20)">
+</text>
+ </g>
+ </g>
+</svg>
diff --git
a/dev/breeze/doc/images/output_testing_airflow-ctl-integration-tests.txt
b/dev/breeze/doc/images/output_testing_airflow-ctl-integration-tests.txt
new file mode 100644
index 00000000000..9d92f133d1c
--- /dev/null
+++ b/dev/breeze/doc/images/output_testing_airflow-ctl-integration-tests.txt
@@ -0,0 +1 @@
+fdff2a26a3d0f8aee8b54ad4309e88d4
diff --git a/dev/breeze/src/airflow_breeze/commands/testing_commands.py
b/dev/breeze/src/airflow_breeze/commands/testing_commands.py
index c0334b1fa88..ff078f60d20 100644
--- a/dev/breeze/src/airflow_breeze/commands/testing_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/testing_commands.py
@@ -831,6 +831,62 @@ def task_sdk_integration_tests(
sys.exit(return_code)
+@group_for_testing.command(
+ name="airflow-ctl-integration-tests",
+ context_settings=dict(
+ ignore_unknown_options=True,
+ allow_extra_args=True,
+ ),
+)
+@option_python
+@option_image_name
+@option_skip_docker_compose_deletion
+@option_github_repository
+@option_include_success_outputs
+@option_verbose
+@option_dry_run
[email protected](
+ "--airflow-ctl-version",
+ help="Version of airflowctl to test",
+ default="1.0.0",
+ show_default=True,
+ envvar="AIRFLOW_CTL_VERSION",
+)
[email protected]("extra_pytest_args", nargs=-1, type=click.Path(path_type=str))
+def airflowctl_integration_tests(
+ python: str,
+ image_name: str | None,
+ skip_docker_compose_deletion: bool,
+ github_repository: str,
+ include_success_outputs: bool,
+ airflow_ctl_version: str,
+ extra_pytest_args: tuple,
+):
+ """Run airflowctl integration tests."""
+ # Export the AIRFLOW_CTL_VERSION environment variable for the test
+ import os
+
+ perform_environment_checks()
+
+ os.environ["AIRFLOW_CTL_VERSION"] = airflow_ctl_version
+ image_name = image_name or os.environ.get("DOCKER_IMAGE")
+
+ if image_name is None:
+ build_params = BuildProdParams(python=python,
github_repository=github_repository)
+ image_name = build_params.airflow_image_name
+
+ get_console().print(f"[info]Running airflowctl integration tests with PROD
image: {image_name}[/]")
+ get_console().print(f"[info]Using airflowctl version:
{airflow_ctl_version}[/]")
+ return_code, info = run_docker_compose_tests(
+ image_name=image_name,
+ include_success_outputs=include_success_outputs,
+ extra_pytest_args=extra_pytest_args,
+ skip_docker_compose_deletion=skip_docker_compose_deletion,
+ test_type="airflow-ctl-integration",
+ )
+ sys.exit(return_code)
+
+
@group_for_testing.command(
name="airflow-ctl-tests",
help="Run airflow-ctl tests - all airflowctl tests are non-DB bound
tests.",
diff --git a/dev/breeze/src/airflow_breeze/commands/testing_commands_config.py
b/dev/breeze/src/airflow_breeze/commands/testing_commands_config.py
index 9a9e706fdc3..ff0f8012d91 100644
--- a/dev/breeze/src/airflow_breeze/commands/testing_commands_config.py
+++ b/dev/breeze/src/airflow_breeze/commands/testing_commands_config.py
@@ -152,7 +152,7 @@ TESTING_COMMANDS: list[dict[str, str | list[str]]] = [
},
{
"name": "airflowctl Tests",
- "commands": ["airflow-ctl-tests"],
+ "commands": ["airflow-ctl-tests", "airflow-ctl-integration-tests"],
},
{
"name": "Other Tests",
@@ -206,6 +206,19 @@ TESTING_PARAMETERS: dict[str, list[dict[str, str |
list[str]]]] = {
],
},
],
+ "breeze testing airflow-ctl-integration-tests": [
+ {
+ "name": "Docker-compose tests flag",
+ "options": [
+ "--image-name",
+ "--python",
+ "--skip-docker-compose-deletion",
+ "--include-success-outputs",
+ "--github-repository",
+ "--airflow-ctl-version",
+ ],
+ }
+ ],
"breeze testing core-integration-tests": [
TEST_OPTIONS_DB,
TEST_ENVIRONMENT_DB,
diff --git a/dev/breeze/src/airflow_breeze/global_constants.py
b/dev/breeze/src/airflow_breeze/global_constants.py
index 523412ed4d8..8b765ef0b86 100644
--- a/dev/breeze/src/airflow_breeze/global_constants.py
+++ b/dev/breeze/src/airflow_breeze/global_constants.py
@@ -275,6 +275,7 @@ class GroupOfTests(Enum):
TASK_SDK = "task-sdk"
TASK_SDK_INTEGRATION = "task-sdk-integration"
CTL = "airflow-ctl"
+ CTL_INTEGRATION = "airflow-ctl-integration"
HELM = "helm"
INTEGRATION_CORE = "integration-core"
INTEGRATION_PROVIDERS = "integration-providers"
@@ -312,6 +313,7 @@ ALLOWED_TEST_TYPE_CHOICES: dict[GroupOfTests, list[str]] = {
GroupOfTests.TASK_SDK_INTEGRATION: [ALL_TEST_TYPE],
GroupOfTests.HELM: [ALL_TEST_TYPE, *all_helm_test_packages()],
GroupOfTests.CTL: [ALL_TEST_TYPE],
+ GroupOfTests.CTL_INTEGRATION: [ALL_TEST_TYPE],
}
diff --git a/dev/breeze/src/airflow_breeze/utils/run_tests.py
b/dev/breeze/src/airflow_breeze/utils/run_tests.py
index b38dfe4561f..9b67c7dea70 100644
--- a/dev/breeze/src/airflow_breeze/utils/run_tests.py
+++ b/dev/breeze/src/airflow_breeze/utils/run_tests.py
@@ -47,6 +47,8 @@ TASK_SDK_TESTS_ROOT_PATH = AIRFLOW_ROOT_PATH /
"task-sdk-tests"
TASK_SDK_TESTS_TESTS_MODULE_PATH = TASK_SDK_TESTS_ROOT_PATH / "tests" /
"task_sdk_tests"
TASK_SDK_TESTS_REQUIREMENTS = TASK_SDK_TESTS_ROOT_PATH / "requirements.txt"
+AIRFLOW_CTL_TESTS_ROOT_PATH = AIRFLOW_ROOT_PATH / "airflow-ctl-tests"
+
IGNORE_DB_INIT_FOR_TEST_GROUPS = [
GroupOfTests.HELM,
GroupOfTests.PYTHON_API_CLIENT,
@@ -111,6 +113,9 @@ def run_docker_compose_tests(
if test_type == "task-sdk-integration":
test_path = Path("tests") / "task_sdk_tests" /
"test_task_sdk_health.py"
cwd = TASK_SDK_TESTS_ROOT_PATH.as_posix()
+ elif test_type == "airflow-ctl-integration":
+ test_path = Path("tests") / "airflowctl_tests" /
"test_airflowctl_commands.py"
+ cwd = AIRFLOW_CTL_TESTS_ROOT_PATH.as_posix()
else:
test_path = Path("tests") / "docker_tests" /
"test_docker_compose_quick_start.py"
cwd = DOCKER_TESTS_ROOT_PATH.as_posix()
diff --git a/devel-common/pyproject.toml b/devel-common/pyproject.toml
index d297bedbbcd..7f4ea1f4318 100644
--- a/devel-common/pyproject.toml
+++ b/devel-common/pyproject.toml
@@ -40,6 +40,7 @@ dependencies = [
"time-machine>=2.15.0",
"wheel>=0.42.0",
"yamllint>=1.33.0",
+ "python-on-whales>=0.70.0",
"apache-airflow-devel-common[basic]"
]
diff --git a/docker-tests/pyproject.toml b/docker-tests/pyproject.toml
index 7a8363750ed..25c20506506 100644
--- a/docker-tests/pyproject.toml
+++ b/docker-tests/pyproject.toml
@@ -37,7 +37,6 @@ version = "0.0.1"
dependencies = [
"apache-airflow-devel-common",
- "python-on-whales>=0.70.0",
]
[tool.pytest.ini_options]
diff --git a/kubernetes-tests/pyproject.toml b/kubernetes-tests/pyproject.toml
index 6be8235f4d1..2ccb1824be1 100644
--- a/kubernetes-tests/pyproject.toml
+++ b/kubernetes-tests/pyproject.toml
@@ -41,7 +41,6 @@ dependencies = [
"apache-airflow-task-sdk",
# Requests 3 if it will be released, will be heavily breaking.
"requests>=2.32.0,<3",
- "python-on-whales>=0.70.0",
]
[tool.pytest.ini_options]
diff --git a/pyproject.toml b/pyproject.toml
index 62995a5b42a..4c15515f57b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1007,6 +1007,7 @@ mypy_path = [
"$MYPY_CONFIG_FILE_DIR/task-sdk/tests",
"$MYPY_CONFIG_FILE_DIR/airflow-ctl/src",
"$MYPY_CONFIG_FILE_DIR/airflow-ctl/tests",
+ "$MYPY_CONFIG_FILE_DIR/airflow-ctl-tests/tests",
"$MYPY_CONFIG_FILE_DIR/dev",
"$MYPY_CONFIG_FILE_DIR/devel-common/src",
"$MYPY_CONFIG_FILE_DIR/helm-tests/tests",
@@ -1263,6 +1264,7 @@ dev = [
"apache-airflow-kubernetes-tests",
"apache-airflow-task-sdk",
"apache-airflow-ctl",
+ "apache-airflow-ctl-tests",
"apache-airflow-shared-logging",
"apache-airflow-shared-secrets-masker",
"apache-airflow-shared-timezones",
@@ -1304,6 +1306,7 @@ apache-airflow-breeze = {workspace = true}
apache-airflow-dev = {workspace = true}
apache-airflow-core = {workspace = true}
apache-airflow-ctl = {workspace = true}
+apache-airflow-ctl-tests = {workspace = true}
apache-airflow-task-sdk = { workspace = true }
apache-airflow-devel-common = { workspace = true }
apache-airflow-docker-tests = { workspace = true }
@@ -1421,6 +1424,7 @@ members = [
"airflow-core",
"dev/breeze",
"airflow-ctl",
+ "airflow-ctl-tests",
"dev",
"devel-common",
"docker-tests",
diff --git a/scripts/docker/install_airflow_when_building_images.sh
b/scripts/docker/install_airflow_when_building_images.sh
index ca45faee1f1..c93c0387a63 100644
--- a/scripts/docker/install_airflow_when_building_images.sh
+++ b/scripts/docker/install_airflow_when_building_images.sh
@@ -81,6 +81,7 @@ function install_from_sources() {
--editable ./airflow-core --editable ./task-sdk --editable
./airflow-ctl \
--editable ./kubernetes-tests --editable ./docker-tests
--editable ./helm-tests \
--editable ./task-sdk-tests \
+ --editable ./airflow-ctl-tests \
--editable ./devel-common[all] --editable ./dev \
--group dev --group docs --group docs-gen --group leveldb"
local -a projects_with_devel_dependencies
diff --git a/task-sdk-tests/pyproject.toml b/task-sdk-tests/pyproject.toml
index 5e6ac78373c..1de482b3c87 100644
--- a/task-sdk-tests/pyproject.toml
+++ b/task-sdk-tests/pyproject.toml
@@ -38,7 +38,6 @@ version = "0.0.1"
dependencies = [
"apache-airflow-core",
"apache-airflow-devel-common",
- "python-on-whales>=0.70.0",
]
[tool.pytest.ini_options]