This is an automated email from the ASF dual-hosted git repository. ephraimanierobi pushed a commit to branch v3-1-test in repository https://gitbox.apache.org/repos/asf/airflow.git
commit 9eb17092ef3d5bae9b60b8c648f8e355f7745097 Author: Bugra Ozturk <[email protected]> AuthorDate: Sat Jan 10 14:54:10 2026 +0100 Sync airflowctl to v3-1-test (#60350) --- airflow-ctl-tests/pyproject.toml | 6 +- .../tests/airflowctl_tests/conftest.py | 111 ++++------- .../tests/airflowctl_tests/constants.py | 7 +- .../airflowctl_tests/fixtures/pools_export.json | 38 ++++ .../fixtures/test_connections.json | 11 + .../airflowctl_tests/fixtures/test_pools.json | 8 + .../airflowctl_tests/fixtures/test_variables.json | 7 + .../fixtures/variables_export.json | 8 + .../airflowctl_tests/test_airflowctl_commands.py | 222 ++++++++++++++++----- airflow-ctl/.pre-commit-config.yaml | 2 +- airflow-ctl/NOTICE | 2 +- airflow-ctl/RELEASE_NOTES.rst | 4 +- airflow-ctl/docs/conf.py | 12 +- airflow-ctl/docs/images/command_hashes.txt | 4 +- airflow-ctl/docs/images/output_connections.svg | 141 ++++++------- airflow-ctl/docs/images/output_variables.svg | 114 +++++------ .../docs/installation/installing-from-pypi.rst | 5 +- .../docs/installation/installing-from-sources.rst | 22 +- airflow-ctl/newsfragments/59850.significant.rst | 30 +++ airflow-ctl/pyproject.toml | 20 +- airflow-ctl/src/airflowctl/__init__.py | 2 +- airflow-ctl/src/airflowctl/ctl/cli_config.py | 30 +-- .../airflowctl/ctl/commands/variable_command.py | 22 -- airflow-ctl/tests/airflow_ctl/api/test_client.py | 2 +- .../tests/airflow_ctl/api/test_operations.py | 8 +- .../airflow_ctl/ctl/commands/test_dag_command.py | 8 +- .../ctl/commands/test_variable_command.py | 20 -- .../ci/prek/check_airflowctl_command_coverage.py | 150 ++++++++++++++ .../in_container/run_capture_airflowctl_help.py | 6 +- 29 files changed, 640 insertions(+), 382 deletions(-) diff --git a/airflow-ctl-tests/pyproject.toml b/airflow-ctl-tests/pyproject.toml index 24c7f561ed1..2cb3a8109b2 100644 --- a/airflow-ctl-tests/pyproject.toml +++ b/airflow-ctl-tests/pyproject.toml @@ -26,7 +26,7 @@ description = "Airflow CTL tests for Apache Airflow" classifiers = [ "Private :: Do Not Upload", ] -requires-python = ">=3.10,!=3.13" +requires-python = ">=3.10,!=3.14" authors = [ { name = "Apache Software Foundation", email = "[email protected]" }, ] @@ -69,3 +69,7 @@ exclude = ["*"] [tool.hatch.build.targets.wheel] bypass-selection = true + +[tool.ruff.lint.per-file-ignores] + +"!test/*" = ["S101"] diff --git a/airflow-ctl-tests/tests/airflowctl_tests/conftest.py b/airflow-ctl-tests/tests/airflowctl_tests/conftest.py index 13dacf5d8c5..a34b4804d65 100644 --- a/airflow-ctl-tests/tests/airflowctl_tests/conftest.py +++ b/airflow-ctl-tests/tests/airflowctl_tests/conftest.py @@ -20,7 +20,6 @@ import os import subprocess import sys -import pytest from python_on_whales import DockerClient, docker from airflowctl_tests import console @@ -30,14 +29,18 @@ from airflowctl_tests.constants import ( DOCKER_IMAGE, ) -docker_client = None + +class _CtlTestState: + docker_client: DockerClient | None = 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_version = os.environ.get("AIRFLOW_CTL_VERSION", "0.1.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}") @@ -50,12 +53,16 @@ def pytest_sessionstart(session): 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!") + 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...") + console.print( + "[yellow]Verifying airflowctl installation via pytest_sessionstart..." + ) try: result = subprocess.run( [ @@ -69,7 +76,9 @@ def pytest_sessionstart(session): ) console.print(f"[green]{result.stdout.strip()}") except subprocess.CalledProcessError as e: - console.print("[red]❌ airflowctl import verification failed via pytest_sessionstart:") + 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}") @@ -115,7 +124,9 @@ def debug_environment(): 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()}") + 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() @@ -124,7 +135,9 @@ def debug_environment(): 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()}") + 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}") @@ -143,8 +156,6 @@ 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}") @@ -167,14 +178,20 @@ def docker_compose_up(tmp_path_factory): os.environ["ENV_FILE_PATH"] = str(tmp_dir / ".env") # Initialize Docker client - docker_client = DockerClient(compose_files=[str(tmp_docker_compose_file)]) + _CtlTestState.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) + _CtlTestState.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()) + print_diagnostics( + _CtlTestState.docker_client.compose, + _CtlTestState.docker_client.compose.version(), + docker.version(), + ) debug_environment() docker_compose_down() raise @@ -182,71 +199,13 @@ def docker_compose_up(tmp_path_factory): def docker_compose_down(): """Tear down Docker Compose environment.""" - if docker_client: - docker_client.compose.down(remove_orphans=True, volumes=True, quiet=True) + if _CtlTestState.docker_client: + _CtlTestState.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, - "backfill 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 index 549a9ad8c74..3f40c99d84b 100644 --- a/airflow-ctl-tests/tests/airflowctl_tests/constants.py +++ b/airflow-ctl-tests/tests/airflowctl_tests/constants.py @@ -28,5 +28,10 @@ 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" + AIRFLOW_ROOT_PATH + / "airflow-core" + / "docs" + / "howto" + / "docker-compose" + / "docker-compose.yaml" ) diff --git a/airflow-ctl-tests/tests/airflowctl_tests/fixtures/pools_export.json b/airflow-ctl-tests/tests/airflowctl_tests/fixtures/pools_export.json new file mode 100644 index 00000000000..4752ad7801f --- /dev/null +++ b/airflow-ctl-tests/tests/airflowctl_tests/fixtures/pools_export.json @@ -0,0 +1,38 @@ +[ + { + "deferred_slots": 0, + "description": "Default pool", + "include_deferred": false, + "name": "default_pool", + "occupied_slots": 0, + "open_slots": 128, + "queued_slots": 0, + "running_slots": 0, + "scheduled_slots": 0, + "slots": 128 + }, + { + "deferred_slots": 0, + "description": null, + "include_deferred": false, + "name": "test_pool", + "occupied_slots": 0, + "open_slots": 10, + "queued_slots": 0, + "running_slots": 0, + "scheduled_slots": 0, + "slots": 10 + }, + { + "deferred_slots": 0, + "description": "Test pool for import", + "include_deferred": false, + "name": "test_import_pool", + "occupied_slots": 0, + "open_slots": 10, + "queued_slots": 0, + "running_slots": 0, + "scheduled_slots": 0, + "slots": 10 + } +] diff --git a/airflow-ctl-tests/tests/airflowctl_tests/fixtures/test_connections.json b/airflow-ctl-tests/tests/airflowctl_tests/fixtures/test_connections.json new file mode 100644 index 00000000000..0141f399ec2 --- /dev/null +++ b/airflow-ctl-tests/tests/airflowctl_tests/fixtures/test_connections.json @@ -0,0 +1,11 @@ +{ + "test_import_conn": { + "conn_type": "postgres", + "host": "localhost", + "login": "testuser", + "password": "testpass", + "port": 5432, + "extra": "{}", + "description": "Test connection for import" + } +} diff --git a/airflow-ctl-tests/tests/airflowctl_tests/fixtures/test_pools.json b/airflow-ctl-tests/tests/airflowctl_tests/fixtures/test_pools.json new file mode 100644 index 00000000000..691e1d62d56 --- /dev/null +++ b/airflow-ctl-tests/tests/airflowctl_tests/fixtures/test_pools.json @@ -0,0 +1,8 @@ +[ + { + "name": "test_import_pool", + "slots": 10, + "description": "Test pool for import", + "include_deferred": false + } +] diff --git a/airflow-ctl-tests/tests/airflowctl_tests/fixtures/test_variables.json b/airflow-ctl-tests/tests/airflowctl_tests/fixtures/test_variables.json new file mode 100644 index 00000000000..05de7d2fc8b --- /dev/null +++ b/airflow-ctl-tests/tests/airflowctl_tests/fixtures/test_variables.json @@ -0,0 +1,7 @@ +{ + "test_import_var": "import_value", + "test_import_var_with_desc": { + "value": "import_value_with_description", + "description": "Test variable with description" + } +} diff --git a/airflow-ctl-tests/tests/airflowctl_tests/fixtures/variables_export.json b/airflow-ctl-tests/tests/airflowctl_tests/fixtures/variables_export.json new file mode 100644 index 00000000000..f8aed8ce379 --- /dev/null +++ b/airflow-ctl-tests/tests/airflowctl_tests/fixtures/variables_export.json @@ -0,0 +1,8 @@ +{ + "test_import_var": "import_value", + "test_import_var_with_desc": { + "description": "Test variable with description", + "value": "import_value_with_description" + }, + "test_key": "updated_value" +} diff --git a/airflow-ctl-tests/tests/airflowctl_tests/test_airflowctl_commands.py b/airflow-ctl-tests/tests/airflowctl_tests/test_airflowctl_commands.py index d17e646cb06..9ba407b1aa8 100644 --- a/airflow-ctl-tests/tests/airflowctl_tests/test_airflowctl_commands.py +++ b/airflow-ctl-tests/tests/airflowctl_tests/test_airflowctl_commands.py @@ -19,59 +19,181 @@ from __future__ import annotations import os from subprocess import PIPE, STDOUT, Popen +import pytest + from airflowctl_tests import console -def test_airflowctl_commands(login_command, login_output, test_commands): +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() + + +LOGIN_COMMAND = "auth login --username airflow --password airflow" +LOGIN_OUTPUT = "Login successful! Welcome to airflowctl!" +ONE_DATE_PARAM = date_param() +TEST_COMMANDS = [ + # 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 + LOGIN_COMMAND, + # Assets commands + "assets list", + "assets get --asset-id=1", + "assets create-event --asset-id=1", + # Backfill commands + "backfill list", + # Config commands + "config get --section core --option executor", + "config list", + "config lint", + # Connections commands + "connections create --connection-id=test_con --conn-type=mysql --password=TEST_PASS -o json", + "connections list", + "connections list -o yaml", + "connections list -o table", + "connections get --conn-id=test_con", + "connections get --conn-id=test_con -o json", + "connections update --connection-id=test_con --conn-type=postgres", + "connections import tests/airflowctl_tests/fixtures/test_connections.json", + "connections delete --conn-id=test_con", + "connections delete --conn-id=test_import_conn", + # DAGs commands + "dags list", + "dags get --dag-id=example_bash_operator", + "dags get-details --dag-id=example_bash_operator", + "dags get-stats --dag-ids=example_bash_operator", + "dags get-version --dag-id=example_bash_operator --version-number=1", + "dags list-import-errors", + "dags list-version --dag-id=example_bash_operator", + "dags list-warning", + # Order of trigger and pause/unpause is important for test stability because state checked + f"dags trigger --dag-id=example_bash_operator --logical-date={ONE_DATE_PARAM} --run-after={ONE_DATE_PARAM}", + "dags pause example_bash_operator", + "dags unpause example_bash_operator", + # DAG Run commands + f'dagrun get --dag-id=example_bash_operator --dag-run-id="manual__{ONE_DATE_PARAM}"', + "dags update --dag-id=example_bash_operator --no-is-paused", + # DAG Run commands + "dagrun list --dag-id example_bash_operator --state success --limit=1", + # Jobs commands + "jobs list", + # Pools commands + "pools create --name=test_pool --slots=5", + "pools list", + "pools get --pool-name=test_pool", + "pools get --pool-name=test_pool -o yaml", + "pools update --pool=test_pool --slots=10", + "pools import tests/airflowctl_tests/fixtures/test_pools.json", + "pools delete --pool=test_pool", + "pools delete --pool=test_import_pool", + # Providers commands + "providers list", + # Variables commands + "variables create --key=test_key --value=test_value", + "variables list", + "variables get --variable-key=test_key", + "variables get --variable-key=test_key -o table", + "variables update --key=test_key --value=updated_value", + "variables import tests/airflowctl_tests/fixtures/test_variables.json", + "variables delete --variable-key=test_key", + "variables delete --variable-key=test_import_var", + "variables delete --variable-key=test_import_var_with_desc", + # Version command + "version --remote", +] + + [email protected](reruns=3, reruns_delay=1) [email protected]( + "command", + TEST_COMMANDS, + ids=[" ".join(command.split(" ", 2)[:2]) for command in TEST_COMMANDS], +) +def test_airflowctl_commands(command: str): """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() + + 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_bytes, stderr_result = proc.communicate(timeout=60) + + # CLI command gave errors + assert not stderr_result, ( + f"Errors while executing command '{command_from_config}':\n{stderr_result.decode()}" + ) + + # Decode the output + stdout_result = stdout_bytes.decode() + # We need to trim auth login output if the command is not login itself and clean backspaces + if command != LOGIN_COMMAND: + assert LOGIN_OUTPUT in stdout_result, ( + f"❌ Login output not found before command output for '{command_from_config}'", + f"\nFull output:\n{stdout_result}", + ) + stdout_result = stdout_result.split(f"{LOGIN_OUTPUT}\n")[1].strip() + else: + stdout_result = stdout_result.strip() + + # Check for non-zero exit code + assert proc.returncode == 0, ( + f"❌ Command '{command_from_config}' exited with code {proc.returncode}", + f"\nOutput:\n{stdout_result}", + ) + + # Error patterns to detect failures that might otherwise slip through + # Please ensure it is aligning with airflowctl.api.client.get_json_error + error_patterns = [ + "Server error", + "command error", + "unrecognized arguments", + "invalid choice", + "Traceback (most recent call last):", + ] + matched_error = next( + (error for error in error_patterns if error in stdout_result), None + ) + assert not matched_error, ( + f"❌ Output contained unexpected text for command '{command_from_config}'", + f"\nMatched error pattern: {matched_error}", + f"\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/.pre-commit-config.yaml b/airflow-ctl/.pre-commit-config.yaml index 8926b9e7336..00eada7f645 100644 --- a/airflow-ctl/.pre-commit-config.yaml +++ b/airflow-ctl/.pre-commit-config.yaml @@ -16,7 +16,7 @@ # under the License. --- default_stages: [pre-commit, pre-push] -minimum_prek_version: '0.2.0' +minimum_prek_version: '0.0.28' default_language_version: python: python3 node: 22.19.0 diff --git a/airflow-ctl/NOTICE b/airflow-ctl/NOTICE index e02aab0589f..a51bd9390d0 100644 --- a/airflow-ctl/NOTICE +++ b/airflow-ctl/NOTICE @@ -1,5 +1,5 @@ Apache Airflow -Copyright 2016-2025 The Apache Software Foundation +Copyright 2016-2026 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). diff --git a/airflow-ctl/RELEASE_NOTES.rst b/airflow-ctl/RELEASE_NOTES.rst index 70d19556a9f..58f1ecbeacb 100644 --- a/airflow-ctl/RELEASE_NOTES.rst +++ b/airflow-ctl/RELEASE_NOTES.rst @@ -15,10 +15,10 @@ specific language governing permissions and limitations under the License. -airflowctl 0.1.0 (2025-11-05) +airflowctl 0.1.0 (2025-11-03) ----------------------------- -Release of airflowctl, a command-line tool. There are lots of great features to use from start. +Beta Release of airflowctl, a command-line tool. There are lots of great features to use from start. Please check the documentation for quick start and usage instructions. Please visit quick start guide: :doc:`/start` diff --git a/airflow-ctl/docs/conf.py b/airflow-ctl/docs/conf.py index 5a19b2adea3..08f867ee43f 100644 --- a/airflow-ctl/docs/conf.py +++ b/airflow-ctl/docs/conf.py @@ -207,18 +207,12 @@ jinja_contexts = { "config_ctx": {"configs": configs, "deprecated_options": deprecated_options}, "quick_start_ctx": {"doc_root_url": f"https://airflow.apache.org/docs/apache-airflow/{PACKAGE_VERSION}/"}, "official_download_page": { - "base_url": f"https://downloads.apache.org/airflow/airflow-ctl/{PACKAGE_VERSION}", - "closer_lua_url": f"https://www.apache.org/dyn/closer.lua/airflow/airflow-ctl/{PACKAGE_VERSION}", - "airflowctl_version": PACKAGE_VERSION, + "base_url": f"https://downloads.apache.org/airflow/{PACKAGE_VERSION}", + "closer_lua_url": f"https://www.apache.org/dyn/closer.lua/airflow/{PACKAGE_VERSION}", + "airflow_version": PACKAGE_VERSION, }, } -# Use for generate rst_epilog and other post-generation substitutions -global_substitutions = { - "version": PACKAGE_VERSION, - "experimental": "This is an :ref:`experimental feature <experimental>`.", -} - # -- Options for sphinx.ext.autodoc -------------------------------------------- # See: https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html diff --git a/airflow-ctl/docs/images/command_hashes.txt b/airflow-ctl/docs/images/command_hashes.txt index 6792a0234ef..c0e3b5995fa 100644 --- a/airflow-ctl/docs/images/command_hashes.txt +++ b/airflow-ctl/docs/images/command_hashes.txt @@ -3,12 +3,12 @@ assets:b3ae2b933e54528bf486ff28e887804d auth:f396d4bce90215599dde6ad0a8f30f29 backfill:bbce9859a2d1ce054ad22db92dea8c05 config:cb175bedf29e8a2c2c6a2ebd13d770a7 -connections:a16225e1c7d28488d0da612752669b4b +connections:e34b6b93f64714986139958c1f370428 dags:287a128a71c97d2b537e09a5c7c73c09 dagrun:f47ed2a89ed0f8c71f79dba53a3a3882 jobs:7f8680afff230eb9940bc7fca727bd52 pools:03fc7d948cbecf16ff8d640eb8f0ce43 providers:1c0afb2dff31d93ab2934b032a2250ab -variables:0b04188937b3c364204ef4cc9a541c62 +variables:0354f8f4b0dde1c3771ed1568692c6ae version:d4a7a6229b3a204f114283b62eac789b auth login:5277c653ff6dce51f37472dc0bda9775 diff --git a/airflow-ctl/docs/images/output_connections.svg b/airflow-ctl/docs/images/output_connections.svg index 8283756115f..7dc48cce16a 100644 --- a/airflow-ctl/docs/images/output_connections.svg +++ b/airflow-ctl/docs/images/output_connections.svg @@ -1,4 +1,4 @@ -<svg class="rich-terminal" viewBox="0 0 811 562.4" xmlns="http://www.w3.org/2000/svg"> +<svg class="rich-terminal" viewBox="0 0 933 489.2" xmlns="http://www.w3.org/2000/svg"> <!-- Generated with Rich https://www.textualize.io --> <style> @@ -19,123 +19,110 @@ font-weight: 700; } - .terminal-3087056112-matrix { + .terminal-1162206820-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-3087056112-title { + .terminal-1162206820-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-3087056112-r1 { fill: #ff8700 } -.terminal-3087056112-r2 { fill: #c5c8c6 } -.terminal-3087056112-r3 { fill: #808080 } -.terminal-3087056112-r4 { fill: #68a0b3 } -.terminal-3087056112-r5 { fill: #c5c8c6;font-weight: bold } + .terminal-1162206820-r1 { fill: #ff8700 } +.terminal-1162206820-r2 { fill: #c5c8c6 } +.terminal-1162206820-r3 { fill: #808080 } +.terminal-1162206820-r4 { fill: #68a0b3 } </style> <defs> - <clipPath id="terminal-3087056112-clip-terminal"> - <rect x="0" y="0" width="792.0" height="511.4" /> + <clipPath id="terminal-1162206820-clip-terminal"> + <rect x="0" y="0" width="914.0" height="438.2" /> </clipPath> - <clipPath id="terminal-3087056112-line-0"> - <rect x="0" y="1.5" width="793" height="24.65"/> + <clipPath id="terminal-1162206820-line-0"> + <rect x="0" y="1.5" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-3087056112-line-1"> - <rect x="0" y="25.9" width="793" height="24.65"/> +<clipPath id="terminal-1162206820-line-1"> + <rect x="0" y="25.9" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-3087056112-line-2"> - <rect x="0" y="50.3" width="793" height="24.65"/> +<clipPath id="terminal-1162206820-line-2"> + <rect x="0" y="50.3" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-3087056112-line-3"> - <rect x="0" y="74.7" width="793" height="24.65"/> +<clipPath id="terminal-1162206820-line-3"> + <rect x="0" y="74.7" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-3087056112-line-4"> - <rect x="0" y="99.1" width="793" height="24.65"/> +<clipPath id="terminal-1162206820-line-4"> + <rect x="0" y="99.1" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-3087056112-line-5"> - <rect x="0" y="123.5" width="793" height="24.65"/> +<clipPath id="terminal-1162206820-line-5"> + <rect x="0" y="123.5" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-3087056112-line-6"> - <rect x="0" y="147.9" width="793" height="24.65"/> +<clipPath id="terminal-1162206820-line-6"> + <rect x="0" y="147.9" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-3087056112-line-7"> - <rect x="0" y="172.3" width="793" height="24.65"/> +<clipPath id="terminal-1162206820-line-7"> + <rect x="0" y="172.3" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-3087056112-line-8"> - <rect x="0" y="196.7" width="793" height="24.65"/> +<clipPath id="terminal-1162206820-line-8"> + <rect x="0" y="196.7" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-3087056112-line-9"> - <rect x="0" y="221.1" width="793" height="24.65"/> +<clipPath id="terminal-1162206820-line-9"> + <rect x="0" y="221.1" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-3087056112-line-10"> - <rect x="0" y="245.5" width="793" height="24.65"/> +<clipPath id="terminal-1162206820-line-10"> + <rect x="0" y="245.5" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-3087056112-line-11"> - <rect x="0" y="269.9" width="793" height="24.65"/> +<clipPath id="terminal-1162206820-line-11"> + <rect x="0" y="269.9" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-3087056112-line-12"> - <rect x="0" y="294.3" width="793" height="24.65"/> +<clipPath id="terminal-1162206820-line-12"> + <rect x="0" y="294.3" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-3087056112-line-13"> - <rect x="0" y="318.7" width="793" height="24.65"/> +<clipPath id="terminal-1162206820-line-13"> + <rect x="0" y="318.7" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-3087056112-line-14"> - <rect x="0" y="343.1" width="793" height="24.65"/> +<clipPath id="terminal-1162206820-line-14"> + <rect x="0" y="343.1" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-3087056112-line-15"> - <rect x="0" y="367.5" width="793" height="24.65"/> +<clipPath id="terminal-1162206820-line-15"> + <rect x="0" y="367.5" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-3087056112-line-16"> - <rect x="0" y="391.9" width="793" height="24.65"/> - </clipPath> -<clipPath id="terminal-3087056112-line-17"> - <rect x="0" y="416.3" width="793" height="24.65"/> - </clipPath> -<clipPath id="terminal-3087056112-line-18"> - <rect x="0" y="440.7" width="793" height="24.65"/> - </clipPath> -<clipPath id="terminal-3087056112-line-19"> - <rect x="0" y="465.1" width="793" height="24.65"/> +<clipPath id="terminal-1162206820-line-16"> + <rect x="0" y="391.9" width="915" height="24.65"/> </clipPath> </defs> - <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="809" height="560.4" rx="8"/> + <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="931" height="487.2" rx="8"/> <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(#terminal-3087056112-clip-terminal)"> + <g transform="translate(9, 41)" clip-path="url(#terminal-1162206820-clip-terminal)"> - <g class="terminal-3087056112-matrix"> - <text class="terminal-3087056112-r1" x="0" y="20" textLength="73.2" clip-path="url(#terminal-3087056112-line-0)">Usage:</text><text class="terminal-3087056112-r3" x="85.4" y="20" textLength="268.4" clip-path="url(#terminal-3087056112-line-0)">airflowctl connections</text><text class="terminal-3087056112-r2" x="353.8" y="20" textLength="24.4" clip-path="url(#terminal-3087056112-line-0)"> [</text><text class="terminal-3087056112-r4" x="378.2" y="20" textLength="24.4" clip-pat [...] -</text><text class="terminal-3087056112-r2" x="793" y="44.4" textLength="12.2" clip-path="url(#terminal-3087056112-line-1)"> -</text><text class="terminal-3087056112-r2" x="0" y="68.8" textLength="366" clip-path="url(#terminal-3087056112-line-2)">Perform Connections operations</text><text class="terminal-3087056112-r2" x="793" y="68.8" textLength="12.2" clip-path="url(#terminal-3087056112-line-2)"> -</text><text class="terminal-3087056112-r2" x="793" y="93.2" textLength="12.2" clip-path="url(#terminal-3087056112-line-3)"> -</text><text class="terminal-3087056112-r1" x="0" y="117.6" textLength="256.2" clip-path="url(#terminal-3087056112-line-4)">Positional Arguments:</text><text class="terminal-3087056112-r2" x="793" y="117.6" textLength="12.2" clip-path="url(#terminal-3087056112-line-4)"> -</text><text class="terminal-3087056112-r4" x="24.4" y="142" textLength="85.4" clip-path="url(#terminal-3087056112-line-5)">COMMAND</text><text class="terminal-3087056112-r2" x="793" y="142" textLength="12.2" clip-path="url(#terminal-3087056112-line-5)"> -</text><text class="terminal-3087056112-r4" x="48.8" y="166.4" textLength="73.2" clip-path="url(#terminal-3087056112-line-6)">create</text><text class="terminal-3087056112-r2" x="231.8" y="166.4" textLength="292.8" clip-path="url(#terminal-3087056112-line-6)">Perform create operation</text><text class="terminal-3087056112-r2" x="793" y="166.4" textLength="12.2" clip-path="url(#terminal-3087056112-line-6)"> -</text><text class="terminal-3087056112-r4" x="48.8" y="190.8" textLength="183" clip-path="url(#terminal-3087056112-line-7)">create-defaults</text><text class="terminal-3087056112-r2" x="793" y="190.8" textLength="12.2" clip-path="url(#terminal-3087056112-line-7)"> -</text><text class="terminal-3087056112-r2" x="231.8" y="215.2" textLength="402.6" clip-path="url(#terminal-3087056112-line-8)">Perform create_defaults operation</text><text class="terminal-3087056112-r2" x="793" y="215.2" textLength="12.2" clip-path="url(#terminal-3087056112-line-8)"> -</text><text class="terminal-3087056112-r4" x="48.8" y="239.6" textLength="73.2" clip-path="url(#terminal-3087056112-line-9)">delete</text><text class="terminal-3087056112-r2" x="231.8" y="239.6" textLength="292.8" clip-path="url(#terminal-3087056112-line-9)">Perform delete operation</text><text class="terminal-3087056112-r2" x="793" y="239.6" textLength="12.2" clip-path="url(#terminal-3087056112-line-9)"> -</text><text class="terminal-3087056112-r4" x="48.8" y="264" textLength="36.6" clip-path="url(#terminal-3087056112-line-10)">get</text><text class="terminal-3087056112-r2" x="231.8" y="264" textLength="256.2" clip-path="url(#terminal-3087056112-line-10)">Perform get operation</text><text class="terminal-3087056112-r2" x="793" y="264" textLength="12.2" clip-path="url(#terminal-3087056112-line-10)"> -</text><text class="terminal-3087056112-r4" x="48.8" y="288.4" textLength="73.2" clip-path="url(#terminal-3087056112-line-11)">import</text><text class="terminal-3087056112-r2" x="231.8" y="288.4" textLength="549" clip-path="url(#terminal-3087056112-line-11)">Import connections from a file. This feature </text><text class="terminal-3087056112-r2" x="793" y="288.4" textLength="12.2" clip-path="url(#terminal-3087056112-line-11)"> -</text><text class="terminal-3087056112-r2" x="0" y="312.8" textLength="390.4" clip-path="url(#terminal-3087056112-line-12)">is compatible with airflow CLI `</text><text class="terminal-3087056112-r5" x="390.4" y="312.8" textLength="329.4" clip-path="url(#terminal-3087056112-line-12)">airflow connections export </text><text class="terminal-3087056112-r2" x="793" y="312.8" textLength="12.2" clip-path="url(#terminal-3087056112-line-12)"> -</text><text class="terminal-3087056112-r5" x="0" y="337.2" textLength="73.2" clip-path="url(#terminal-3087056112-line-13)">a.json</text><text class="terminal-3087056112-r2" x="73.2" y="337.2" textLength="329.4" clip-path="url(#terminal-3087056112-line-13)">` command. Export it from `</text><text class="terminal-3087056112-r5" x="402.6" y="337.2" textLength="134.2" clip-path="url(#terminal-3087056112-line-13)">airflow CLI</text><text class="terminal-30870561 [...] -</text><text class="terminal-3087056112-r2" x="0" y="361.6" textLength="317.2" clip-path="url(#terminal-3087056112-line-14)">securely via this command.</text><text class="terminal-3087056112-r2" x="793" y="361.6" textLength="12.2" clip-path="url(#terminal-3087056112-line-14)"> -</text><text class="terminal-3087056112-r4" x="48.8" y="386" textLength="48.8" clip-path="url(#terminal-3087056112-line-15)">list</text><text class="terminal-3087056112-r2" x="231.8" y="386" textLength="268.4" clip-path="url(#terminal-3087056112-line-15)">Perform list operation</text><text class="terminal-3087056112-r2" x="793" y="386" textLength="12.2" clip-path="url(#terminal-3087056112-line-15)"> -</text><text class="terminal-3087056112-r4" x="48.8" y="410.4" textLength="48.8" clip-path="url(#terminal-3087056112-line-16)">test</text><text class="terminal-3087056112-r2" x="231.8" y="410.4" textLength="268.4" clip-path="url(#terminal-3087056112-line-16)">Perform test operation</text><text class="terminal-3087056112-r2" x="793" y="410.4" textLength="12.2" clip-path="url(#terminal-3087056112-line-16)"> -</text><text class="terminal-3087056112-r4" x="48.8" y="434.8" textLength="73.2" clip-path="url(#terminal-3087056112-line-17)">update</text><text class="terminal-3087056112-r2" x="231.8" y="434.8" textLength="292.8" clip-path="url(#terminal-3087056112-line-17)">Perform update operation</text><text class="terminal-3087056112-r2" x="793" y="434.8" textLength="12.2" clip-path="url(#terminal-3087056112-line-17)"> -</text><text class="terminal-3087056112-r2" x="793" y="459.2" textLength="12.2" clip-path="url(#terminal-3087056112-line-18)"> -</text><text class="terminal-3087056112-r1" x="0" y="483.6" textLength="97.6" clip-path="url(#terminal-3087056112-line-19)">Options:</text><text class="terminal-3087056112-r2" x="793" y="483.6" textLength="12.2" clip-path="url(#terminal-3087056112-line-19)"> -</text><text class="terminal-3087056112-r4" x="24.4" y="508" textLength="24.4" clip-path="url(#terminal-3087056112-line-20)">-h</text><text class="terminal-3087056112-r2" x="48.8" y="508" textLength="24.4" clip-path="url(#terminal-3087056112-line-20)">, </text><text class="terminal-3087056112-r4" x="73.2" y="508" textLength="73.2" clip-path="url(#terminal-3087056112-line-20)">--help</text><text class="terminal-3087056112-r2" x="231.8" y="508" textLength="378.2" clip-path="url(#termi [...] + <g class="terminal-1162206820-matrix"> + <text class="terminal-1162206820-r1" x="0" y="20" textLength="73.2" clip-path="url(#terminal-1162206820-line-0)">Usage:</text><text class="terminal-1162206820-r3" x="85.4" y="20" textLength="268.4" clip-path="url(#terminal-1162206820-line-0)">airflowctl connections</text><text class="terminal-1162206820-r2" x="353.8" y="20" textLength="24.4" clip-path="url(#terminal-1162206820-line-0)"> [</text><text class="terminal-1162206820-r4" x="378.2" y="20" textLength="24.4" clip-pat [...] +</text><text class="terminal-1162206820-r2" x="915" y="44.4" textLength="12.2" clip-path="url(#terminal-1162206820-line-1)"> +</text><text class="terminal-1162206820-r2" x="0" y="68.8" textLength="366" clip-path="url(#terminal-1162206820-line-2)">Perform Connections operations</text><text class="terminal-1162206820-r2" x="915" y="68.8" textLength="12.2" clip-path="url(#terminal-1162206820-line-2)"> +</text><text class="terminal-1162206820-r2" x="915" y="93.2" textLength="12.2" clip-path="url(#terminal-1162206820-line-3)"> +</text><text class="terminal-1162206820-r1" x="0" y="117.6" textLength="256.2" clip-path="url(#terminal-1162206820-line-4)">Positional Arguments:</text><text class="terminal-1162206820-r2" x="915" y="117.6" textLength="12.2" clip-path="url(#terminal-1162206820-line-4)"> +</text><text class="terminal-1162206820-r4" x="24.4" y="142" textLength="85.4" clip-path="url(#terminal-1162206820-line-5)">COMMAND</text><text class="terminal-1162206820-r2" x="915" y="142" textLength="12.2" clip-path="url(#terminal-1162206820-line-5)"> +</text><text class="terminal-1162206820-r4" x="48.8" y="166.4" textLength="73.2" clip-path="url(#terminal-1162206820-line-6)">create</text><text class="terminal-1162206820-r2" x="231.8" y="166.4" textLength="292.8" clip-path="url(#terminal-1162206820-line-6)">Perform create operation</text><text class="terminal-1162206820-r2" x="915" y="166.4" textLength="12.2" clip-path="url(#terminal-1162206820-line-6)"> +</text><text class="terminal-1162206820-r4" x="48.8" y="190.8" textLength="183" clip-path="url(#terminal-1162206820-line-7)">create-defaults</text><text class="terminal-1162206820-r2" x="915" y="190.8" textLength="12.2" clip-path="url(#terminal-1162206820-line-7)"> +</text><text class="terminal-1162206820-r2" x="231.8" y="215.2" textLength="402.6" clip-path="url(#terminal-1162206820-line-8)">Perform create_defaults operation</text><text class="terminal-1162206820-r2" x="915" y="215.2" textLength="12.2" clip-path="url(#terminal-1162206820-line-8)"> +</text><text class="terminal-1162206820-r4" x="48.8" y="239.6" textLength="73.2" clip-path="url(#terminal-1162206820-line-9)">delete</text><text class="terminal-1162206820-r2" x="231.8" y="239.6" textLength="292.8" clip-path="url(#terminal-1162206820-line-9)">Perform delete operation</text><text class="terminal-1162206820-r2" x="915" y="239.6" textLength="12.2" clip-path="url(#terminal-1162206820-line-9)"> +</text><text class="terminal-1162206820-r4" x="48.8" y="264" textLength="36.6" clip-path="url(#terminal-1162206820-line-10)">get</text><text class="terminal-1162206820-r2" x="231.8" y="264" textLength="256.2" clip-path="url(#terminal-1162206820-line-10)">Perform get operation</text><text class="terminal-1162206820-r2" x="915" y="264" textLength="12.2" clip-path="url(#terminal-1162206820-line-10)"> +</text><text class="terminal-1162206820-r4" x="48.8" y="288.4" textLength="73.2" clip-path="url(#terminal-1162206820-line-11)">import</text><text class="terminal-1162206820-r2" x="231.8" y="288.4" textLength="671" clip-path="url(#terminal-1162206820-line-11)">Import connections from a file exported with local CLI.</text><text class="terminal-1162206820-r2" x="915" y="288.4" textLength="12.2" clip-path="url(#terminal-1162206820-line-11)"> +</text><text class="terminal-1162206820-r4" x="48.8" y="312.8" textLength="48.8" clip-path="url(#terminal-1162206820-line-12)">list</text><text class="terminal-1162206820-r2" x="231.8" y="312.8" textLength="268.4" clip-path="url(#terminal-1162206820-line-12)">Perform list operation</text><text class="terminal-1162206820-r2" x="915" y="312.8" textLength="12.2" clip-path="url(#terminal-1162206820-line-12)"> +</text><text class="terminal-1162206820-r4" x="48.8" y="337.2" textLength="48.8" clip-path="url(#terminal-1162206820-line-13)">test</text><text class="terminal-1162206820-r2" x="231.8" y="337.2" textLength="268.4" clip-path="url(#terminal-1162206820-line-13)">Perform test operation</text><text class="terminal-1162206820-r2" x="915" y="337.2" textLength="12.2" clip-path="url(#terminal-1162206820-line-13)"> +</text><text class="terminal-1162206820-r4" x="48.8" y="361.6" textLength="73.2" clip-path="url(#terminal-1162206820-line-14)">update</text><text class="terminal-1162206820-r2" x="231.8" y="361.6" textLength="292.8" clip-path="url(#terminal-1162206820-line-14)">Perform update operation</text><text class="terminal-1162206820-r2" x="915" y="361.6" textLength="12.2" clip-path="url(#terminal-1162206820-line-14)"> +</text><text class="terminal-1162206820-r2" x="915" y="386" textLength="12.2" clip-path="url(#terminal-1162206820-line-15)"> +</text><text class="terminal-1162206820-r1" x="0" y="410.4" textLength="97.6" clip-path="url(#terminal-1162206820-line-16)">Options:</text><text class="terminal-1162206820-r2" x="915" y="410.4" textLength="12.2" clip-path="url(#terminal-1162206820-line-16)"> +</text><text class="terminal-1162206820-r4" x="24.4" y="434.8" textLength="24.4" clip-path="url(#terminal-1162206820-line-17)">-h</text><text class="terminal-1162206820-r2" x="48.8" y="434.8" textLength="24.4" clip-path="url(#terminal-1162206820-line-17)">, </text><text class="terminal-1162206820-r4" x="73.2" y="434.8" textLength="73.2" clip-path="url(#terminal-1162206820-line-17)">--help</text><text class="terminal-1162206820-r2" x="231.8" y="434.8" textLength="378.2" clip-path="ur [...] </text> </g> </g> diff --git a/airflow-ctl/docs/images/output_variables.svg b/airflow-ctl/docs/images/output_variables.svg index 31599fa47c9..78c24d31074 100644 --- a/airflow-ctl/docs/images/output_variables.svg +++ b/airflow-ctl/docs/images/output_variables.svg @@ -1,4 +1,4 @@ -<svg class="rich-terminal" viewBox="0 0 811 440.4" xmlns="http://www.w3.org/2000/svg"> +<svg class="rich-terminal" viewBox="0 0 933 416.0" xmlns="http://www.w3.org/2000/svg"> <!-- Generated with Rich https://www.textualize.io --> <style> @@ -19,102 +19,98 @@ font-weight: 700; } - .terminal-4094392876-matrix { + .terminal-938427142-matrix { font-family: Fira Code, monospace; font-size: 20px; line-height: 24.4px; font-variant-east-asian: full-width; } - .terminal-4094392876-title { + .terminal-938427142-title { font-size: 18px; font-weight: bold; font-family: arial; } - .terminal-4094392876-r1 { fill: #ff8700 } -.terminal-4094392876-r2 { fill: #c5c8c6 } -.terminal-4094392876-r3 { fill: #808080 } -.terminal-4094392876-r4 { fill: #68a0b3 } + .terminal-938427142-r1 { fill: #ff8700 } +.terminal-938427142-r2 { fill: #c5c8c6 } +.terminal-938427142-r3 { fill: #808080 } +.terminal-938427142-r4 { fill: #68a0b3 } </style> <defs> - <clipPath id="terminal-4094392876-clip-terminal"> - <rect x="0" y="0" width="792.0" height="389.4" /> + <clipPath id="terminal-938427142-clip-terminal"> + <rect x="0" y="0" width="914.0" height="365.0" /> </clipPath> - <clipPath id="terminal-4094392876-line-0"> - <rect x="0" y="1.5" width="793" height="24.65"/> + <clipPath id="terminal-938427142-line-0"> + <rect x="0" y="1.5" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-4094392876-line-1"> - <rect x="0" y="25.9" width="793" height="24.65"/> +<clipPath id="terminal-938427142-line-1"> + <rect x="0" y="25.9" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-4094392876-line-2"> - <rect x="0" y="50.3" width="793" height="24.65"/> +<clipPath id="terminal-938427142-line-2"> + <rect x="0" y="50.3" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-4094392876-line-3"> - <rect x="0" y="74.7" width="793" height="24.65"/> +<clipPath id="terminal-938427142-line-3"> + <rect x="0" y="74.7" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-4094392876-line-4"> - <rect x="0" y="99.1" width="793" height="24.65"/> +<clipPath id="terminal-938427142-line-4"> + <rect x="0" y="99.1" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-4094392876-line-5"> - <rect x="0" y="123.5" width="793" height="24.65"/> +<clipPath id="terminal-938427142-line-5"> + <rect x="0" y="123.5" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-4094392876-line-6"> - <rect x="0" y="147.9" width="793" height="24.65"/> +<clipPath id="terminal-938427142-line-6"> + <rect x="0" y="147.9" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-4094392876-line-7"> - <rect x="0" y="172.3" width="793" height="24.65"/> +<clipPath id="terminal-938427142-line-7"> + <rect x="0" y="172.3" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-4094392876-line-8"> - <rect x="0" y="196.7" width="793" height="24.65"/> +<clipPath id="terminal-938427142-line-8"> + <rect x="0" y="196.7" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-4094392876-line-9"> - <rect x="0" y="221.1" width="793" height="24.65"/> +<clipPath id="terminal-938427142-line-9"> + <rect x="0" y="221.1" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-4094392876-line-10"> - <rect x="0" y="245.5" width="793" height="24.65"/> +<clipPath id="terminal-938427142-line-10"> + <rect x="0" y="245.5" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-4094392876-line-11"> - <rect x="0" y="269.9" width="793" height="24.65"/> +<clipPath id="terminal-938427142-line-11"> + <rect x="0" y="269.9" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-4094392876-line-12"> - <rect x="0" y="294.3" width="793" height="24.65"/> +<clipPath id="terminal-938427142-line-12"> + <rect x="0" y="294.3" width="915" height="24.65"/> </clipPath> -<clipPath id="terminal-4094392876-line-13"> - <rect x="0" y="318.7" width="793" height="24.65"/> - </clipPath> -<clipPath id="terminal-4094392876-line-14"> - <rect x="0" y="343.1" width="793" height="24.65"/> +<clipPath id="terminal-938427142-line-13"> + <rect x="0" y="318.7" width="915" height="24.65"/> </clipPath> </defs> - <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="809" height="438.4" rx="8"/> + <rect fill="#292929" stroke="rgba(255,255,255,0.35)" stroke-width="1" x="1" y="1" width="931" height="414" rx="8"/> <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(#terminal-4094392876-clip-terminal)"> + <g transform="translate(9, 41)" clip-path="url(#terminal-938427142-clip-terminal)"> - <g class="terminal-4094392876-matrix"> - <text class="terminal-4094392876-r1" x="0" y="20" textLength="73.2" clip-path="url(#terminal-4094392876-line-0)">Usage:</text><text class="terminal-4094392876-r3" x="85.4" y="20" textLength="244" clip-path="url(#terminal-4094392876-line-0)">airflowctl variables</text><text class="terminal-4094392876-r2" x="329.4" y="20" textLength="24.4" clip-path="url(#terminal-4094392876-line-0)"> [</text><text class="terminal-4094392876-r4" x="353.8" y="20" textLength="24.4" clip-path="u [...] -</text><text class="terminal-4094392876-r2" x="793" y="44.4" textLength="12.2" clip-path="url(#terminal-4094392876-line-1)"> -</text><text class="terminal-4094392876-r2" x="0" y="68.8" textLength="341.6" clip-path="url(#terminal-4094392876-line-2)">Perform Variables operations</text><text class="terminal-4094392876-r2" x="793" y="68.8" textLength="12.2" clip-path="url(#terminal-4094392876-line-2)"> -</text><text class="terminal-4094392876-r2" x="793" y="93.2" textLength="12.2" clip-path="url(#terminal-4094392876-line-3)"> -</text><text class="terminal-4094392876-r1" x="0" y="117.6" textLength="256.2" clip-path="url(#terminal-4094392876-line-4)">Positional Arguments:</text><text class="terminal-4094392876-r2" x="793" y="117.6" textLength="12.2" clip-path="url(#terminal-4094392876-line-4)"> -</text><text class="terminal-4094392876-r4" x="24.4" y="142" textLength="85.4" clip-path="url(#terminal-4094392876-line-5)">COMMAND</text><text class="terminal-4094392876-r2" x="793" y="142" textLength="12.2" clip-path="url(#terminal-4094392876-line-5)"> -</text><text class="terminal-4094392876-r4" x="48.8" y="166.4" textLength="73.2" clip-path="url(#terminal-4094392876-line-6)">create</text><text class="terminal-4094392876-r2" x="170.8" y="166.4" textLength="292.8" clip-path="url(#terminal-4094392876-line-6)">Perform create operation</text><text class="terminal-4094392876-r2" x="793" y="166.4" textLength="12.2" clip-path="url(#terminal-4094392876-line-6)"> -</text><text class="terminal-4094392876-r4" x="48.8" y="190.8" textLength="73.2" clip-path="url(#terminal-4094392876-line-7)">delete</text><text class="terminal-4094392876-r2" x="170.8" y="190.8" textLength="292.8" clip-path="url(#terminal-4094392876-line-7)">Perform delete operation</text><text class="terminal-4094392876-r2" x="793" y="190.8" textLength="12.2" clip-path="url(#terminal-4094392876-line-7)"> -</text><text class="terminal-4094392876-r4" x="48.8" y="215.2" textLength="73.2" clip-path="url(#terminal-4094392876-line-8)">export</text><text class="terminal-4094392876-r2" x="170.8" y="215.2" textLength="244" clip-path="url(#terminal-4094392876-line-8)">Export all variables</text><text class="terminal-4094392876-r2" x="793" y="215.2" textLength="12.2" clip-path="url(#terminal-4094392876-line-8)"> -</text><text class="terminal-4094392876-r4" x="48.8" y="239.6" textLength="36.6" clip-path="url(#terminal-4094392876-line-9)">get</text><text class="terminal-4094392876-r2" x="170.8" y="239.6" textLength="256.2" clip-path="url(#terminal-4094392876-line-9)">Perform get operation</text><text class="terminal-4094392876-r2" x="793" y="239.6" textLength="12.2" clip-path="url(#terminal-4094392876-line-9)"> -</text><text class="terminal-4094392876-r4" x="48.8" y="264" textLength="73.2" clip-path="url(#terminal-4094392876-line-10)">import</text><text class="terminal-4094392876-r2" x="170.8" y="264" textLength="195.2" clip-path="url(#terminal-4094392876-line-10)">Import variables</text><text class="terminal-4094392876-r2" x="793" y="264" textLength="12.2" clip-path="url(#terminal-4094392876-line-10)"> -</text><text class="terminal-4094392876-r4" x="48.8" y="288.4" textLength="48.8" clip-path="url(#terminal-4094392876-line-11)">list</text><text class="terminal-4094392876-r2" x="170.8" y="288.4" textLength="268.4" clip-path="url(#terminal-4094392876-line-11)">Perform list operation</text><text class="terminal-4094392876-r2" x="793" y="288.4" textLength="12.2" clip-path="url(#terminal-4094392876-line-11)"> -</text><text class="terminal-4094392876-r4" x="48.8" y="312.8" textLength="73.2" clip-path="url(#terminal-4094392876-line-12)">update</text><text class="terminal-4094392876-r2" x="170.8" y="312.8" textLength="292.8" clip-path="url(#terminal-4094392876-line-12)">Perform update operation</text><text class="terminal-4094392876-r2" x="793" y="312.8" textLength="12.2" clip-path="url(#terminal-4094392876-line-12)"> -</text><text class="terminal-4094392876-r2" x="793" y="337.2" textLength="12.2" clip-path="url(#terminal-4094392876-line-13)"> -</text><text class="terminal-4094392876-r1" x="0" y="361.6" textLength="97.6" clip-path="url(#terminal-4094392876-line-14)">Options:</text><text class="terminal-4094392876-r2" x="793" y="361.6" textLength="12.2" clip-path="url(#terminal-4094392876-line-14)"> -</text><text class="terminal-4094392876-r4" x="24.4" y="386" textLength="24.4" clip-path="url(#terminal-4094392876-line-15)">-h</text><text class="terminal-4094392876-r2" x="48.8" y="386" textLength="24.4" clip-path="url(#terminal-4094392876-line-15)">, </text><text class="terminal-4094392876-r4" x="73.2" y="386" textLength="73.2" clip-path="url(#terminal-4094392876-line-15)">--help</text><text class="terminal-4094392876-r2" x="170.8" y="386" textLength="378.2" clip-path="url(#termi [...] + <g class="terminal-938427142-matrix"> + <text class="terminal-938427142-r1" x="0" y="20" textLength="73.2" clip-path="url(#terminal-938427142-line-0)">Usage:</text><text class="terminal-938427142-r3" x="85.4" y="20" textLength="244" clip-path="url(#terminal-938427142-line-0)">airflowctl variables</text><text class="terminal-938427142-r2" x="329.4" y="20" textLength="24.4" clip-path="url(#terminal-938427142-line-0)"> [</text><text class="terminal-938427142-r4" x="353.8" y="20" textLength="24.4" clip-path="url(#ter [...] +</text><text class="terminal-938427142-r2" x="915" y="44.4" textLength="12.2" clip-path="url(#terminal-938427142-line-1)"> +</text><text class="terminal-938427142-r2" x="0" y="68.8" textLength="341.6" clip-path="url(#terminal-938427142-line-2)">Perform Variables operations</text><text class="terminal-938427142-r2" x="915" y="68.8" textLength="12.2" clip-path="url(#terminal-938427142-line-2)"> +</text><text class="terminal-938427142-r2" x="915" y="93.2" textLength="12.2" clip-path="url(#terminal-938427142-line-3)"> +</text><text class="terminal-938427142-r1" x="0" y="117.6" textLength="256.2" clip-path="url(#terminal-938427142-line-4)">Positional Arguments:</text><text class="terminal-938427142-r2" x="915" y="117.6" textLength="12.2" clip-path="url(#terminal-938427142-line-4)"> +</text><text class="terminal-938427142-r4" x="24.4" y="142" textLength="85.4" clip-path="url(#terminal-938427142-line-5)">COMMAND</text><text class="terminal-938427142-r2" x="915" y="142" textLength="12.2" clip-path="url(#terminal-938427142-line-5)"> +</text><text class="terminal-938427142-r4" x="48.8" y="166.4" textLength="73.2" clip-path="url(#terminal-938427142-line-6)">create</text><text class="terminal-938427142-r2" x="170.8" y="166.4" textLength="292.8" clip-path="url(#terminal-938427142-line-6)">Perform create operation</text><text class="terminal-938427142-r2" x="915" y="166.4" textLength="12.2" clip-path="url(#terminal-938427142-line-6)"> +</text><text class="terminal-938427142-r4" x="48.8" y="190.8" textLength="73.2" clip-path="url(#terminal-938427142-line-7)">delete</text><text class="terminal-938427142-r2" x="170.8" y="190.8" textLength="292.8" clip-path="url(#terminal-938427142-line-7)">Perform delete operation</text><text class="terminal-938427142-r2" x="915" y="190.8" textLength="12.2" clip-path="url(#terminal-938427142-line-7)"> +</text><text class="terminal-938427142-r4" x="48.8" y="215.2" textLength="36.6" clip-path="url(#terminal-938427142-line-8)">get</text><text class="terminal-938427142-r2" x="170.8" y="215.2" textLength="256.2" clip-path="url(#terminal-938427142-line-8)">Perform get operation</text><text class="terminal-938427142-r2" x="915" y="215.2" textLength="12.2" clip-path="url(#terminal-938427142-line-8)"> +</text><text class="terminal-938427142-r4" x="48.8" y="239.6" textLength="73.2" clip-path="url(#terminal-938427142-line-9)">import</text><text class="terminal-938427142-r2" x="170.8" y="239.6" textLength="646.6" clip-path="url(#terminal-938427142-line-9)">Import variables from a file exported with local CLI.</text><text class="terminal-938427142-r2" x="915" y="239.6" textLength="12.2" clip-path="url(#terminal-938427142-line-9)"> +</text><text class="terminal-938427142-r4" x="48.8" y="264" textLength="48.8" clip-path="url(#terminal-938427142-line-10)">list</text><text class="terminal-938427142-r2" x="170.8" y="264" textLength="268.4" clip-path="url(#terminal-938427142-line-10)">Perform list operation</text><text class="terminal-938427142-r2" x="915" y="264" textLength="12.2" clip-path="url(#terminal-938427142-line-10)"> +</text><text class="terminal-938427142-r4" x="48.8" y="288.4" textLength="73.2" clip-path="url(#terminal-938427142-line-11)">update</text><text class="terminal-938427142-r2" x="170.8" y="288.4" textLength="292.8" clip-path="url(#terminal-938427142-line-11)">Perform update operation</text><text class="terminal-938427142-r2" x="915" y="288.4" textLength="12.2" clip-path="url(#terminal-938427142-line-11)"> +</text><text class="terminal-938427142-r2" x="915" y="312.8" textLength="12.2" clip-path="url(#terminal-938427142-line-12)"> +</text><text class="terminal-938427142-r1" x="0" y="337.2" textLength="97.6" clip-path="url(#terminal-938427142-line-13)">Options:</text><text class="terminal-938427142-r2" x="915" y="337.2" textLength="12.2" clip-path="url(#terminal-938427142-line-13)"> +</text><text class="terminal-938427142-r4" x="24.4" y="361.6" textLength="24.4" clip-path="url(#terminal-938427142-line-14)">-h</text><text class="terminal-938427142-r2" x="48.8" y="361.6" textLength="24.4" clip-path="url(#terminal-938427142-line-14)">, </text><text class="terminal-938427142-r4" x="73.2" y="361.6" textLength="73.2" clip-path="url(#terminal-938427142-line-14)">--help</text><text class="terminal-938427142-r2" x="170.8" y="361.6" textLength="378.2" clip-path="url(#term [...] </text> </g> </g> diff --git a/airflow-ctl/docs/installation/installing-from-pypi.rst b/airflow-ctl/docs/installation/installing-from-pypi.rst index 9ec3a5e73ab..cecd66c354e 100644 --- a/airflow-ctl/docs/installation/installing-from-pypi.rst +++ b/airflow-ctl/docs/installation/installing-from-pypi.rst @@ -24,7 +24,7 @@ PyPI <https://pypi.org/project/apache-airflow-ctl/>`__. Installation tools '''''''''''''''''' -Only ``pip`` and ``uv`` installation is currently officially supported. +Only ``pip`` installation is currently officially supported. .. note:: @@ -33,8 +33,7 @@ Only ``pip`` and ``uv`` installation is currently officially supported. ``pip`` - especially when it comes to constraint vs. requirements management. Installing via ``Poetry`` or ``pip-tools`` is not currently supported. If you wish to install airflow using those tools you should use the constraints and convert them to appropriate - format and workflow that your tool requires. Uv follows ``pip`` approach - with ``uv pip`` so it should work similarly. + format and workflow that your tool requires. There are known issues with ``bazel`` that might lead to circular dependencies when using it to install Airflow. Please switch to ``pip`` if you encounter such problems. ``Bazel`` community works on fixing diff --git a/airflow-ctl/docs/installation/installing-from-sources.rst b/airflow-ctl/docs/installation/installing-from-sources.rst index 2d2f9a421f5..ad1e616873d 100644 --- a/airflow-ctl/docs/installation/installing-from-sources.rst +++ b/airflow-ctl/docs/installation/installing-from-sources.rst @@ -23,8 +23,9 @@ Released packages .. jinja:: official_download_page - This page describes downloading and verifying Airflow Ctl version ``|version|`` using officially released packages. - You can also install ``airflowctl`` - as most Python packages - via :doc:`PyPI <installing-from-pypi>`. + This page describes downloading and verifying Airflow® version + ``{{ airflow_version }}`` using officially released packages. + You can also install ``Apache airflowctl`` - as most Python packages - via :doc:`PyPI <installing-from-pypi>`. You can choose different version of Airflow by selecting a different version from the drop-down at the top-left of the page. @@ -33,13 +34,22 @@ can use if you want to verify the origin of the packages and want to verify chec the packages. The packages are available via the `Official Apache Software Foundations Downloads <https://dlcdn.apache.org/>`_ -The {{ airflowctl_version }} downloads of Airflow Ctl are available at: +As of version 2.8 Airflow follows PEP 517/518 and uses ``pyproject.toml`` file to define build dependencies +and build process and it requires relatively modern versions of packaging tools to get airflow built from +local sources or ``sdist`` packages, as PEP 517 compliant build hooks are used to determine dynamic build +dependencies. In case of ``pip`` it means that at least version 22.1.0 is needed (released at the beginning of +2022) to build or install Airflow from sources. This does not affect the ability of installing Airflow from +released wheel packages. + +The |version| downloads of airflowctl are available at: .. jinja:: official_download_page - * `Sources package for airflow-ctl: <{{ closer_lua_url }}/apache_airflow_ctl-{{ airflowctl_version }}-source.tar.gz>`__ (`asc <{{ base_url }}/apache_airflow_ctl-{{ airflowctl_version }}-source.tar.gz.asc>`__, `sha512 <{{ base_url }}/apache_airflow_ctl-{{ airflowctl_version }}-source.tar.gz.sha512>`__) - * `Sdist package for airflow-ctl distributions <{{ closer_lua_url }}/apache_airflow_ctl-{{ airflowctl_version }}.tar.gz>`__ (`asc <{{ base_url }}/apache_airflow_ctl-{{ airflowctl_version }}.tar.gz.asc>`__, `sha512 <{{ base_url }}/apache_airflow_ctl-{{ airflowctl_version }}.tar.gz.sha512>`__) - * `Whl package for airflow-ctl distribution <{{ closer_lua_url }}/apache_airflow_ctl-{{ airflowctl_version }}-py3-none-any.whl>`__ (`asc <{{ base_url }}/apache_airflow_ctl-{{ airflowctl_version }}-py3-none-any.whl.asc>`__, `sha512 <{{ base_url }}/apache_airflow_ctl-{{ airflowctl_version }}-py3-none-any.whl.sha512>`__) + * `Sources package for airflow <{{ closer_lua_url }}/apache-airflow-ctl-{{ airflowctl_version }}-source.tar.gz>`__ (`asc <{{ base_url }}/apache-airflow-ctl-{{ airflowctl_version }}-source.tar.gz.asc>`__, `sha512 <{{ base_url }}/apache-airflow-ctl-{{ airflowctl_version }}-source.tar.gz.sha512>`__) + * `Sdist package for airflow meta package <{{ closer_lua_url }}/apache-airflow-ctl-{{ airflowctl_version }}.tar.gz>`__ (`asc <{{ base_url }}/apache-airflow-ctl-{{ airflowctl_version }}.tar.gz.asc>`__, `sha512 <{{ base_url }}/apache-airflow-ctl-{{ airflowctl_version }}.tar.gz.sha512>`__) + * `Whl package for airflow meta package <{{ closer_lua_url }}/apache_airflow_ctl-{{ airflowctl_version }}-py3-none-any.whl>`__ (`asc <{{ base_url }}/apache_airflow_ctl-{{ airflowctl_version }}-py3-none-any.whl.asc>`__, `sha512 <{{ base_url }}/apache_airflow_ctl-{{ airflowctl_version }}-py3-none-any.whl.sha512>`__) + * `Sdist package for airflow core package <{{ closer_lua_url }}/apache-airflow_ctl-{{ airflowctl_version }}.tar.gz>`__ (`asc <{{ base_url }}/apache-airflow_ctl-{{ airflowctl_version }}.tar.gz.asc>`__, `sha512 <{{ base_url }}/apache-airflow_ctl-{{ airflowctl_version }}.tar.gz.sha512>`__) + * `Whl package for airflow core package <{{ closer_lua_url }}/apache_airflow_ctl-{{ airflowctl_version }}-py3-none-any.whl>`__ (`asc <{{ base_url }}/apache_airflow_ctl-{{ airflowctl_version }}-py3-none-any.whl.asc>`__, `sha512 <{{ base_url }}/apache_airflow_ctl-{{ airflowctl_version }}-py3-none-any.whl.sha512>`__) If you want to install from the source code, you can download from the sources link above, it will contain a ``INSTALL`` file containing details on how you can build and install airflowctl. diff --git a/airflow-ctl/newsfragments/59850.significant.rst b/airflow-ctl/newsfragments/59850.significant.rst new file mode 100644 index 00000000000..dfb10ae852c --- /dev/null +++ b/airflow-ctl/newsfragments/59850.significant.rst @@ -0,0 +1,30 @@ +Remove ``airflowctl variables export`` command + +The ``airflowctl variables export`` command has been removed from the remote CLI. Variable export functionality is only available through the local Airflow CLI. + +**What changed:** + +- Removed ``airflowctl variables export`` command + +**Migration:** + +Use the local Airflow CLI command for exporting variables: + +.. code-block:: bash + + # Old (no longer available) + airflowctl variables export variables.json + + # New (use local CLI) + airflow variables export variables.json + +* Types of change + + * [ ] Dag changes + * [ ] Config changes + * [ ] API changes + * [x] CLI changes + * [ ] Behaviour changes + * [ ] Plugin changes + * [ ] Dependency changes + * [ ] Code interface changes diff --git a/airflow-ctl/pyproject.toml b/airflow-ctl/pyproject.toml index 8c2fe12a7c5..bbfeffcd72d 100644 --- a/airflow-ctl/pyproject.toml +++ b/airflow-ctl/pyproject.toml @@ -20,8 +20,6 @@ name = "apache-airflow-ctl" dynamic = ["version"] description = "Apache Airflow command line tool for communicating with an Apache Airflow, using the API." readme = { file = "README.md", content-type = "text/markdown" } -license = "Apache-2.0" -license-files = ["LICENSE", "NOTICE"] # We do not want to upper-bind Python version, as we do not know if we will support Python 3.14+ # out-of-the box. Airflow-ctl is a small tool that does not have many dependencies and does not use # sophisticated features of Python, so it should work with Python 3.14+ once all it's dependencies are @@ -46,20 +44,6 @@ classifiers = [ "Framework :: Apache Airflow", ] -[project.urls] -"Bug Tracker" = "https://github.com/apache/airflow/issues" -Documentation = "https://airflow.apache.org/docs/apache-airflow-ctl/stable/index.html" -Downloads = "https://archive.apache.org/dist/airflow/airflow-ctl/" -Homepage = "https://airflow.apache.org/" -"Release Notes" = "https://airflow.apache.org/docs/apache-airflow-ctl/stable/changelog.html" -"Slack Chat" = "https://s.apache.org/airflow-slack" -"Source Code" = "https://github.com/apache/airflow" -LinkedIn = "https://www.linkedin.com/company/apache-airflow/" -Mastodon = "https://fosstodon.org/@airflow" -Bluesky = "https://bsky.app/profile/apache-airflow.bsky.social" -YouTube = "https://www.youtube.com/channel/UCSXwxpWZQ7XZ1WL3wqevChA/" - - [project.optional-dependencies] "dev" = [ "keyrings.alt>=5.0.2", @@ -163,7 +147,7 @@ input = "../airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-ge output = "src/airflowctl/api/datamodels/generated.py" ## pytest settings ## -[tool.pytest] +[tool.pytest.ini_options] addopts = [ "--tb=short", "-rasl", @@ -196,7 +180,7 @@ testpaths = [ ] asyncio_default_fixture_loop_scope = "function" -pythonpath = ["tests"] +pythonpath = "tests" # Keep temporary directories (created by `tmp_path`) for 2 recent runs only failed tests. tmp_path_retention_count = "2" diff --git a/airflow-ctl/src/airflowctl/__init__.py b/airflow-ctl/src/airflowctl/__init__.py index 32a64efabc5..f3fd163907d 100644 --- a/airflow-ctl/src/airflowctl/__init__.py +++ b/airflow-ctl/src/airflowctl/__init__.py @@ -19,4 +19,4 @@ from __future__ import annotations __path__ = __import__("pkgutil").extend_path(__path__, __name__) -__version__ = "0.1.0" +__version__ = "0.1.1" diff --git a/airflow-ctl/src/airflowctl/ctl/cli_config.py b/airflow-ctl/src/airflowctl/ctl/cli_config.py index da548530eb0..a578396acd4 100644 --- a/airflow-ctl/src/airflowctl/ctl/cli_config.py +++ b/airflow-ctl/src/airflowctl/ctl/cli_config.py @@ -74,13 +74,11 @@ def safe_call_command(function: Callable, args: Iterable[Arg]) -> None: try: function(args) - except AirflowCtlCredentialNotFoundException as e: - rich.print(f"command failed due to {e}") - sys.exit(1) - except AirflowCtlConnectionException as e: - rich.print(f"command failed due to {e}") - sys.exit(1) - except AirflowCtlNotFoundException as e: + except ( + AirflowCtlCredentialNotFoundException, + AirflowCtlConnectionException, + AirflowCtlNotFoundException, + ) as e: rich.print(f"command failed due to {e}") sys.exit(1) except (httpx.RemoteProtocolError, httpx.ReadError) as e: @@ -202,8 +200,7 @@ class Password(argparse.Action): ARG_FILE = Arg( flags=("file",), metavar="FILEPATH", - help="File path to read from or write to. " - "For import commands, it is a file to read from. For export commands, it is a file to write to.", + help="File path to read from for import commands.", ) ARG_OUTPUT = Arg( ( @@ -254,9 +251,8 @@ ARG_AUTH_PASSWORD = Arg( # Dag Commands Args ARG_DAG_ID = Arg( - flags=("--dag-id",), + flags=("dag_id",), type=str, - dest="dag_id", help="The DAG ID of the DAG to pause or unpause", ) @@ -804,9 +800,7 @@ CONFIG_COMMANDS = ( CONNECTION_COMMANDS = ( ActionCommand( name="import", - help="Import connections from a file. " - "This feature is compatible with airflow CLI `airflow connections export a.json` command. " - "Export it from `airflow CLI` and import it securely via this command.", + help="Import connections from a file exported with local CLI.", func=lazy_load_command("airflowctl.ctl.commands.connection_command.import_"), args=(Arg(flags=("file",), metavar="FILEPATH", help="Connections JSON file"),), ), @@ -854,16 +848,10 @@ POOL_COMMANDS = ( VARIABLE_COMMANDS = ( ActionCommand( name="import", - help="Import variables", + help="Import variables from a file exported with local CLI.", func=lazy_load_command("airflowctl.ctl.commands.variable_command.import_"), args=(ARG_FILE, ARG_VARIABLE_ACTION_ON_EXISTING_KEY), ), - ActionCommand( - name="export", - help="Export all variables", - func=lazy_load_command("airflowctl.ctl.commands.variable_command.export"), - args=(ARG_FILE,), - ), ) core_commands: list[CLICommand] = [ diff --git a/airflow-ctl/src/airflowctl/ctl/commands/variable_command.py b/airflow-ctl/src/airflowctl/ctl/commands/variable_command.py index d5ffed31d8c..466be2ccac7 100644 --- a/airflow-ctl/src/airflowctl/ctl/commands/variable_command.py +++ b/airflow-ctl/src/airflowctl/ctl/commands/variable_command.py @@ -19,7 +19,6 @@ from __future__ import annotations import json import os import sys -from pathlib import Path import rich @@ -79,24 +78,3 @@ def import_(args, api_client=NEW_API_CLIENT) -> list[str]: rich.print(success_message.format(success=result.create.success)) return result.create.success - - -@provide_api_client(kind=ClientKind.CLI) -def export(args, api_client=NEW_API_CLIENT) -> None: - """Export all the variables to the file.""" - success_message = "[green]Export successful! {total_entries} variable(s) to {file}[/green]" - var_dict = {} - variables = api_client.variables.list() - - for variable in variables.variables: - if variable.description: - var_dict[variable.key] = { - "value": variable.value, - "description": variable.description, - } - else: - var_dict[variable.key] = variable.value - - with open(Path(args.file), "w") as var_file: - json.dump(var_dict, var_file, sort_keys=True, indent=4) - rich.print(success_message.format(total_entries=variables.total_entries, file=args.file)) diff --git a/airflow-ctl/tests/airflow_ctl/api/test_client.py b/airflow-ctl/tests/airflow_ctl/api/test_client.py index e2adcadff5e..39ffc436706 100644 --- a/airflow-ctl/tests/airflow_ctl/api/test_client.py +++ b/airflow-ctl/tests/airflow_ctl/api/test_client.py @@ -87,7 +87,7 @@ class TestClient: assert err.value.args == ("Client error message: {'detail': 'Not found'}",) @pytest.mark.parametrize( - "base_url, client_kind, expected_base_url", + ("base_url", "client_kind", "expected_base_url"), [ ("http://localhost:8080", ClientKind.CLI, "http://localhost:8080/api/v2/"), ("http://localhost:8080", ClientKind.AUTH, "http://localhost:8080/auth/"), diff --git a/airflow-ctl/tests/airflow_ctl/api/test_operations.py b/airflow-ctl/tests/airflow_ctl/api/test_operations.py index 1e7391b99d2..0f92aedb3a3 100644 --- a/airflow-ctl/tests/airflow_ctl/api/test_operations.py +++ b/airflow-ctl/tests/airflow_ctl/api/test_operations.py @@ -128,7 +128,7 @@ class TestBaseOperations: client.connections.get("1") @pytest.mark.parametrize( - "total_entries, limit, expected_response", + ("total_entries", "limit", "expected_response"), [ (1, 50, (HelloCollectionResponse(hellos=[HelloResponse(name="hello")], total_entries=1))), ( @@ -1066,7 +1066,7 @@ class TestPoolsOperations: pools=[pool_response], total_entries=1, ) - pool_bulk_aresponse = BulkResponse( + pool_bulk_response = BulkResponse( create=BulkActionResponse(success=[pool_name], errors=[]), update=None, delete=None, @@ -1102,11 +1102,11 @@ class TestPoolsOperations: def test_bulk(self): def handle_request(request: httpx.Request) -> httpx.Response: assert request.url.path == "/api/v2/pools" - return httpx.Response(200, json=json.loads(self.pool_bulk_aresponse.model_dump_json())) + return httpx.Response(200, json=json.loads(self.pool_bulk_response.model_dump_json())) client = make_api_client(transport=httpx.MockTransport(handle_request)) response = client.pools.bulk(pools=self.pools_bulk_body) - assert response == self.pool_bulk_aresponse + assert response == self.pool_bulk_response def test_delete(self): def handle_request(request: httpx.Request) -> httpx.Response: diff --git a/airflow-ctl/tests/airflow_ctl/ctl/commands/test_dag_command.py b/airflow-ctl/tests/airflow_ctl/ctl/commands/test_dag_command.py index 98fb318024b..fbc6ee1240b 100644 --- a/airflow-ctl/tests/airflow_ctl/ctl/commands/test_dag_command.py +++ b/airflow-ctl/tests/airflow_ctl/ctl/commands/test_dag_command.py @@ -93,7 +93,7 @@ class TestDagCommands: ) assert self.dag_response_paused.is_paused is False dag_response_dict = dag_command.pause( - self.parser.parse_args(["dags", "pause", "--dag-id", self.dag_id]), + self.parser.parse_args(["dags", "pause", self.dag_id]), api_client=api_client, ) assert dag_response_dict["is_paused"] is False @@ -107,7 +107,7 @@ class TestDagCommands: ) with pytest.raises(SystemExit): dag_command.pause( - self.parser.parse_args(["dags", "pause", "--dag-id", self.dag_id]), + self.parser.parse_args(["dags", "pause", self.dag_id]), api_client=api_client, ) @@ -120,7 +120,7 @@ class TestDagCommands: ) assert self.dag_response_unpaused.is_paused is True dag_response_dict = dag_command.unpause( - self.parser.parse_args(["dags", "unpause", "--dag-id", self.dag_id]), + self.parser.parse_args(["dags", "unpause", self.dag_id]), api_client=api_client, ) assert dag_response_dict["is_paused"] is True @@ -134,6 +134,6 @@ class TestDagCommands: ) with pytest.raises(SystemExit): dag_command.unpause( - self.parser.parse_args(["dags", "unpause", "--dag-id", self.dag_id]), + self.parser.parse_args(["dags", "unpause", self.dag_id]), api_client=api_client, ) diff --git a/airflow-ctl/tests/airflow_ctl/ctl/commands/test_variable_command.py b/airflow-ctl/tests/airflow_ctl/ctl/commands/test_variable_command.py index e0c9ce16d8d..9703c8f866b 100644 --- a/airflow-ctl/tests/airflow_ctl/ctl/commands/test_variable_command.py +++ b/airflow-ctl/tests/airflow_ctl/ctl/commands/test_variable_command.py @@ -17,7 +17,6 @@ from __future__ import annotations import json -import os import pytest @@ -104,22 +103,3 @@ class TestCliVariableCommands: self.parser.parse_args(["variables", "import", expected_json_path.as_posix()]), api_client=api_client, ) - - def test_export(self, api_client_maker, tmp_path, monkeypatch): - api_client = api_client_maker( - path="/api/v2/variables", - response_json=self.variable_collection_response.model_dump(), - expected_http_status_code=200, - kind=ClientKind.CLI, - ) - - monkeypatch.chdir(tmp_path) - expected_json_path = (tmp_path / self.export_file_name).as_posix() - variable_command.export( - self.parser.parse_args(["variables", "export", expected_json_path]), - api_client=api_client, - ) - assert os.path.exists(tmp_path / self.export_file_name) - - with open(expected_json_path) as f: - assert json.load(f) == {self.key: {"description": self.description, "value": self.value}} diff --git a/scripts/ci/prek/check_airflowctl_command_coverage.py b/scripts/ci/prek/check_airflowctl_command_coverage.py new file mode 100755 index 00000000000..8a81dd098c6 --- /dev/null +++ b/scripts/ci/prek/check_airflowctl_command_coverage.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# /// script +# requires-python = ">=3.10,<3.11" +# dependencies = [ +# "rich>=13.6.0", +# ] +# /// +""" +Check that all airflowctl CLI commands have integration test coverage by comparing commands from operations.py against test_commands in conftest.py. +""" + +from __future__ import annotations + +import ast +import re +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent.resolve())) +from common_prek_utils import AIRFLOW_ROOT_PATH, console + +OPERATIONS_FILE = AIRFLOW_ROOT_PATH / "airflow-ctl" / "src" / "airflowctl" / "api" / "operations.py" +CTL_TESTS_FILE = ( + AIRFLOW_ROOT_PATH / "airflow-ctl-tests" / "tests" / "airflowctl_tests" / "test_airflowctl_commands.py" +) + +# Operations excluded from CLI (see cli_config.py) +EXCLUDED_OPERATION_CLASSES = {"BaseOperations", "LoginOperations", "VersionOperations"} +EXCLUDED_METHODS = { + "__init__", + "__init_subclass__", + "error", + "_check_flag_and_exit_if_server_response_error", + "bulk", + "export", +} + +EXCLUDED_COMMANDS = { + "assets delete-dag-queued-events", + "assets delete-queued-event", + "assets delete-queued-events", + "assets get-by-alias", + "assets get-dag-queued-event", + "assets get-dag-queued-events", + "assets get-queued-events", + "assets list-by-alias", + "assets materialize", + "backfill cancel", + "backfill create", + "backfill create-dry-run", + "backfill get", + "backfill pause", + "backfill unpause", + "connections create-defaults", + "connections test", + "dags delete", + "dags get-import-error", + "dags get-tags", +} + + +def parse_operations() -> dict[str, list[str]]: + commands: dict[str, list[str]] = {} + + with open(OPERATIONS_FILE) as f: + tree = ast.parse(f.read(), filename=str(OPERATIONS_FILE)) + + for node in ast.walk(tree): + if isinstance(node, ast.ClassDef) and node.name.endswith("Operations"): + if node.name in EXCLUDED_OPERATION_CLASSES: + continue + + group_name = node.name.replace("Operations", "").lower() + commands[group_name] = [] + + for child in node.body: + if isinstance(child, ast.FunctionDef): + method_name = child.name + if method_name in EXCLUDED_METHODS or method_name.startswith("_"): + continue + subcommand = method_name.replace("_", "-") + commands[group_name].append(subcommand) + + return commands + + +def parse_tested_commands() -> set[str]: + tested: set[str] = set() + + with open(CTL_TESTS_FILE) as f: + content = f.read() + + # Match command patterns like "assets list", "dags list-import-errors", etc. + # Also handles f-strings like f"dagrun get..." or f'dagrun get...' + pattern = r'f?["\']([a-z]+(?:-[a-z]+)*\s+[a-z]+(?:-[a-z]+)*)' + for match in re.findall(pattern, content): + parts = match.split() + if len(parts) >= 2: + tested.add(f"{parts[0]} {parts[1]}") + + return tested + + +def main(): + available = parse_operations() + tested = parse_tested_commands() + + missing = [] + for group, subcommands in sorted(available.items()): + for subcommand in sorted(subcommands): + cmd = f"{group} {subcommand}" + if cmd not in tested and cmd not in EXCLUDED_COMMANDS: + missing.append(cmd) + + if missing: + console.print("[red]ERROR: Commands not covered by integration tests:[/]") + for cmd in missing: + console.print(f" [red]- {cmd}[/]") + console.print() + console.print("[yellow]Fix by either:[/]") + console.print(f"1. Add test to {CTL_TESTS_FILE}") + console.print(f"2. Add to EXCLUDED_COMMANDS in {__file__}") + sys.exit(1) + + total = sum(len(cmds) for cmds in available.values()) + console.print( + f"[green]All {total} CLI commands covered ({len(tested)} tested, {len(EXCLUDED_COMMANDS)} excluded)[/]" + ) + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/scripts/in_container/run_capture_airflowctl_help.py b/scripts/in_container/run_capture_airflowctl_help.py index 733cc3145b2..b35472bffca 100644 --- a/scripts/in_container/run_capture_airflowctl_help.py +++ b/scripts/in_container/run_capture_airflowctl_help.py @@ -84,7 +84,7 @@ def regenerate_help_images_for_all_airflowctl_commands(commands: list[str], skip os.makedirs(AIRFLOWCTL_IMAGES_PATH, exist_ok=True) env = os.environ.copy() env["TERM"] = "xterm-256color" - env["COLUMNS"] = "65" + env["COLUMNS"] = "75" old_hash_dict = {} new_hash_dict = {} @@ -100,8 +100,8 @@ def regenerate_help_images_for_all_airflowctl_commands(commands: list[str], skip # Check for changes changed_commands = [] - for command in commands: - command = command or "main" + for command_raw in commands: + command = command_raw or "main" console.print(f"[bright_blue]Checking command: {command}[/]", end="") if skip_hash_check:
