This is an automated email from the ASF dual-hosted git repository. bugraoz pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push: new 180a9d837a7 feat (airflowctl): Initial airflowctl docs (#49024) 180a9d837a7 is described below commit 180a9d837a70a181d7766d52995b9d8ca668640e Author: Bugra Ozturk <bugrao...@users.noreply.github.com> AuthorDate: Fri May 16 00:20:35 2025 +0200 feat (airflowctl): Initial airflowctl docs (#49024) * feat (airflowctl): AIP-81 Initial airflowctl docs and update docs according to comments --- airflow-ctl/README.md | 22 ++ airflow-ctl/docs/changelog.rst | 31 +++ airflow-ctl/docs/cli-and-env-variables-ref.rst | 55 ++++ airflow-ctl/docs/conf.py | 294 +++++++++++++++++++++ airflow-ctl/docs/howto/index.rst | 77 ++++++ airflow-ctl/docs/index.rst | 36 +++ airflow-ctl/docs/installation/dependencies.rst | 26 ++ airflow-ctl/docs/installation/index.rst | 134 ++++++++++ .../docs/installation/installing-from-pypi.rst | 163 ++++++++++++ .../docs/installation/installing-from-sources.rst | 163 ++++++++++++ airflow-ctl/docs/installation/prerequisites.rst | 63 +++++ .../docs/installation/supported-versions.rst | 42 +++ .../airflowctl/__init__.py => docs/redirects.txt} | 13 +- airflow-ctl/docs/security.rst | 27 ++ airflow-ctl/docs/start.rst | 42 +++ airflow-ctl/docs/static/exampleinclude.css | 86 ++++++ airflow-ctl/docs/static/gh-jira-links.js | 36 +++ airflow-ctl/docs/static/redirects.js | 29 ++ airflow-ctl/docs/templates/footer.html | 29 ++ airflow-ctl/pyproject.toml | 22 +- airflow-ctl/src/airflowctl/__init__.py | 2 +- airflow-ctl/src/airflowctl/api/operations.py | 5 +- airflow-ctl/src/airflowctl/ctl/cli_config.py | 37 ++- airflow-ctl/src/airflowctl/ctl/cli_parser.py | 12 +- airflow-ctl/src/airflowctl/exceptions.py | 4 + .../tests/airflow_ctl/api/test_operations.py | 12 +- .../tests/airflow_ctl/ctl/test_cli_config.py | 14 +- devel-common/src/docs/utils/conf_constants.py | 5 + .../src/sphinx_exts/docs_build/docs_builder.py | 5 + 29 files changed, 1455 insertions(+), 31 deletions(-) diff --git a/airflow-ctl/README.md b/airflow-ctl/README.md index 6c2908b7431..8e543717d9b 100644 --- a/airflow-ctl/README.md +++ b/airflow-ctl/README.md @@ -33,11 +33,14 @@ A command-line tool for interacting with Apache Airflow instances through the Ai - Python 3.9 or later (compatible with Python >= 3.9 and < 3.13) - Network access to an Apache Airflow instance with REST API enabled +- Keyring backend installed in operating system for secure token storage ## Usage Access the tool from your terminal: +### Command Line + ```bash airflowctl --help ``` @@ -45,3 +48,22 @@ airflowctl --help ## Contributing Want to help improve Apache Airflow? Check out our [contributing documentation](https://github.com/apache/airflow/blob/main/contributing-docs/README.rst). + +### Additional Contribution Guidelines + +- Please ensure API is running while doing development testing. +- There are two ways to have a CLI command, + - Auto Generated Commands + - Implemented Commands + +#### Auto Generated Commands + +Auto generation of commands directly from operations methods under `airflow-ctl/src/airflowctl/api/operations.py`. +Whenever operation is mapped with proper datamodel and response model, it will be automatically added to the command. + +You can check each command with `airflowctl <command> --help` to see the available options. + +#### Implemented Commands + +Implemented commands are the ones which are not auto generated and need to be implemented manually. +You can check the implemented commands under `airflow-ctl/src/airflowctl/clt/commands/`. diff --git a/airflow-ctl/docs/changelog.rst b/airflow-ctl/docs/changelog.rst new file mode 100644 index 00000000000..50a8a3b7757 --- /dev/null +++ b/airflow-ctl/docs/changelog.rst @@ -0,0 +1,31 @@ + .. 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. + + +.. NOTE TO CONTRIBUTORS: + Please, only add notes to the Changelog just below the "Changelog" header when there are some breaking changes + and you want to add an explanation to the users on how they are supposed to deal with them. + The changelog is updated and maintained semi-automatically by release manager. + +``apache-airflow-ctl`` + +Changelog +--------- + +1.0.0 +..... +Initial version of the Airflow CTL. diff --git a/airflow-ctl/docs/cli-and-env-variables-ref.rst b/airflow-ctl/docs/cli-and-env-variables-ref.rst new file mode 100644 index 00000000000..db45a5d2e27 --- /dev/null +++ b/airflow-ctl/docs/cli-and-env-variables-ref.rst @@ -0,0 +1,55 @@ + .. 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. + +Command Line Interface and Environment Variables Reference +========================================================== + +Command Line Interface +'''''''''''''''''''''' + +Airflow CTL has a very rich command line interface that allows for +many types of operation on a DAG, starting services, and supporting +development and testing. + +.. note:: + For more information on usage CLI, see :doc:`cli-and-env-variables-ref` + +.. contents:: Content + :local: + :depth: 2 + +.. argparse:: + :module: airflowctl.ctl.cli_parser + :func: get_parser + :prog: airflowctl + +Environment Variables +''''''''''''''''''''' + +.. envvar:: AIRFLOW_CLI_TOKEN + + The token used to authenticate with the Airflow API. This is only + required if you are using the Airflow API and have not set up + authentication using a different method. If username and password hasn't been used. + +.. envvar:: AIRFLOW_CLI_ENVIRONMENT + + Environment name to use for the CLI. This is used to determine + which environment to use when running the CLI. This is only + 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. diff --git a/airflow-ctl/docs/conf.py b/airflow-ctl/docs/conf.py new file mode 100644 index 00000000000..d075866c8bd --- /dev/null +++ b/airflow-ctl/docs/conf.py @@ -0,0 +1,294 @@ +# Disable Flake8 because of all the sphinx imports +# +# 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. +"""Configuration of Providers docs building.""" + +from __future__ import annotations + +import logging +import os +import pathlib +import re +from typing import Any + +from docs.utils.conf_constants import ( + AIRFLOW_CTL_DOC_STATIC_PATH, + AIRFLOW_CTL_SRC_PATH, + AIRFLOW_FAVICON_PATH, + AUTOAPI_OPTIONS, + BASIC_AUTOAPI_IGNORE_PATTERNS, + BASIC_SPHINX_EXTENSIONS, + REDOC_SCRIPT_URL, + SMARTQUOTES_EXCLUDES, + SPELLING_WORDLIST_PATH, + SPHINX_DESIGN_STATIC_PATH, + SPHINX_REDOC_EXTENSIONS, + SUPPRESS_WARNINGS, + filter_autoapi_ignore_entries, + get_autodoc_mock_imports, + get_configs_and_deprecations, + get_google_intersphinx_mapping, + get_html_context, + get_html_sidebars, + get_html_theme_options, + get_intersphinx_mapping, + get_rst_epilogue, + get_rst_filepath_from_path, + skip_util_classes_extension, +) +from packaging.version import Version, parse as parse_version + +import airflowctl +from airflow.configuration import retrieve_configuration_description + +PACKAGE_NAME = "apache-airflow-ctl" +PACKAGE_VERSION = airflowctl.__version__ +SYSTEM_TESTS_DIR: pathlib.Path | None +# SYSTEM_TESTS_DIR = AIRFLOW_REPO_ROOT_PATH / "airflow-ctl" / "tests" / "system" / "core" + +os.environ["AIRFLOW_PACKAGE_NAME"] = PACKAGE_NAME + +# Hack to allow changing for piece of the code to behave differently while +# the docs are being built. The main objective was to alter the +# behavior of the utils.apply_default that was hiding function headers +os.environ["BUILDING_AIRFLOW_DOCS"] = "TRUE" + +# General information about the project. +project = PACKAGE_NAME +# # The version info for the project you're documenting +version = PACKAGE_VERSION +# The full version, including alpha/beta/rc tags. +release = PACKAGE_VERSION + +rst_epilog = get_rst_epilogue(PACKAGE_VERSION, False) + +# The language for content autogenerated by Sphinx. Refer to documentation +smartquotes_excludes = SMARTQUOTES_EXCLUDES + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = BASIC_SPHINX_EXTENSIONS + +# -- Options for sphinxcontrib.redoc ------------------------------------------- +# See: https://sphinxcontrib-redoc.readthedocs.io/en/stable/ + +extensions.extend(SPHINX_REDOC_EXTENSIONS) +redoc_script_url = REDOC_SCRIPT_URL + +extensions.extend( + [ + "autoapi.extension", + "sphinx_jinja", + "sphinx.ext.graphviz", + "sphinxcontrib.httpdomain", + "extra_files_with_substitutions", + ] +) + +exclude_patterns = [ + # We only link to selected subpackages. + "_api/airflowctl/index.rst", + "_api/airflowctl/api/*", + "_api/airflowctl/api/datamodels/*", + "_api/airflowctl/ctl/*", + "_api/airflowctl/exceptions/index.rst", + "_api/airflowctl/utils/*", + "README.rst", +] + +# Exclude top-level packages +# do not exclude these top-level modules from the doc build: +ALLOWED_TOP_LEVEL_FILES = ("exceptions.py",) + + +def add_airflow_ctl_exclude_patterns_to_sphinx(exclude_patterns: list[str]): + """ + Add excluded files to Sphinx exclude patterns. + + Excludes all files from autoapi except the ones we want to allow. + + :param root: The root directory of the package. + :param allowed_top_level_files: Tuple of allowed top-level files. + :param browsable_packages: Set of browsable packages. + :param browsable_utils: Set of browsable utils. + :param models_included: Set of included models. + """ + # first - excluded everything that is not allowed or browsable + root = AIRFLOW_CTL_SRC_PATH / "airflowctl" + for path in root.iterdir(): + if path.is_file() and path.name not in ALLOWED_TOP_LEVEL_FILES: + exclude_patterns.append(get_rst_filepath_from_path(path, root.parent)) + print(f"Excluding {path} from Sphinx docs") + + +add_airflow_ctl_exclude_patterns_to_sphinx(exclude_patterns) + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["templates"] + +# If true, keep warnings as "system message" paragraphs in the built documents. +keep_warnings = True + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = "sphinx_airflow_theme" + +html_title = "Airflow CTL Documentation" + +# A shorter title for the navigation bar. Default is the same as html_title. +html_short_title = "" + +# given, this must be the name of an image file that is the favicon of the docs +html_favicon = AIRFLOW_FAVICON_PATH.as_posix() + +# Custom static files (such as style sheets) here, +html_static_path = [AIRFLOW_CTL_DOC_STATIC_PATH.as_posix(), SPHINX_DESIGN_STATIC_PATH.as_posix()] + +# A list of JavaScript filenames. +html_js_files = ["gh-jira-links.js", "redirects.js"] + +# Substitute in links +manual_substitutions_in_generated_html = [ + "installation/installing-from-pypi.html", + "installation/installing-from-sources.html", + "installation/prerequisites.html", +] + +html_css_files = ["custom.css"] + +html_sidebars = get_html_sidebars(PACKAGE_VERSION) + +# If false, no index is generated. +html_use_index = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +html_show_copyright = False + +# html theme options +html_theme_options: dict[str, Any] = get_html_theme_options() + +conf_py_path = "/airflow-ctl/docs/" +# A dictionary of values to pass into the template engine's context for all pages. +html_context = get_html_context(conf_py_path) + +# -- Options for sphinx_jinja ------------------------------------------ +# See: https://github.com/tardyp/sphinx-jinja +airflowctl_version: Version = parse_version( + re.search( # type: ignore[union-attr,arg-type] + r"__version__ = \"([0-9.]*)(\.dev[0-9]*)?\"", + (AIRFLOW_CTL_SRC_PATH / "airflowctl" / "__init__.py").read_text(), + ).groups(0)[0] +) + + +config_descriptions = retrieve_configuration_description(include_providers=False) +configs, deprecated_options = get_configs_and_deprecations(airflowctl_version, config_descriptions) + +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/{PACKAGE_VERSION}", + "closer_lua_url": f"https://www.apache.org/dyn/closer.lua/airflow/{PACKAGE_VERSION}", + "airflow_version": PACKAGE_VERSION, + }, +} + +# -- Options for sphinx.ext.autodoc -------------------------------------------- +# See: https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html + +# This value contains a list of modules to be mocked up. This is useful when some external dependencies +# are not met at build time and break the building process. +autodoc_mock_imports = get_autodoc_mock_imports() +# The default options for autodoc directives. They are applied to all autodoc directives automatically. +autodoc_default_options = {"show-inheritance": True, "members": True} + +autodoc_typehints = "description" +autodoc_typehints_description_target = "documented" +autodoc_typehints_format = "short" + + +# -- Options for sphinx.ext.intersphinx ---------------------------------------- +# See: https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html + +# This config value contains names of other projects that should +# be linked to in this documentation. +# Inventories are only downloaded once by exts/docs_build/fetch_inventories.py. +intersphinx_mapping = get_intersphinx_mapping() +intersphinx_mapping.update(get_google_intersphinx_mapping()) + +# -- Options for sphinx.ext.viewcode ------------------------------------------- +# See: https://www.sphinx-doc.org/es/master/usage/extensions/viewcode.html + +# If this is True, viewcode extension will emit viewcode-follow-imported event to resolve the name of +# the module by other extensions. The default is True. +viewcode_follow_imported_members = True + +# -- Options for sphinx-autoapi ------------------------------------------------ +# See: https://sphinx-autoapi.readthedocs.io/en/latest/config.html + +# your API documentation from. +autoapi_dirs = [AIRFLOW_CTL_SRC_PATH.as_posix()] + +# A directory that has user-defined templates to override our default templates. +autoapi_template_dir = "autoapi_templates" + +# A list of patterns to ignore when finding files +autoapi_ignore = BASIC_AUTOAPI_IGNORE_PATTERNS + +# filter logging +autoapi_log = logging.getLogger("sphinx.autoapi.mappers.base") +autoapi_log.addFilter(filter_autoapi_ignore_entries) + +# Keep the AutoAPI generated files on the filesystem after the run. +# Useful for debugging. +autoapi_keep_files = True + +# Relative path to output the AutoAPI files into. This can also be used to place the generated documentation +# anywhere in your documentation hierarchy. +autoapi_root = "_api" + +# Whether to insert the generated documentation into the TOC tree. If this is False, the default AutoAPI +# index page is not generated and you will need to include the generated documentation in a +# TOC tree entry yourself. +autoapi_add_toctree_entry = False + +# By default autoapi will include private members -- we don't want that! +autoapi_options = AUTOAPI_OPTIONS + +suppress_warnings = SUPPRESS_WARNINGS + +# -- Options for ext.exampleinclude -------------------------------------------- +exampleinclude_sourceroot = os.path.abspath("..") + +# -- Options for ext.redirects ------------------------------------------------- +redirects_file = "redirects.txt" + +# -- Options for sphinxcontrib-spelling ---------------------------------------- +spelling_word_list_filename = [SPELLING_WORDLIST_PATH.as_posix()] +spelling_exclude_patterns = ["project.rst", "changelog.rst"] + +spelling_ignore_contributor_names = False +spelling_ignore_importable_modules = True + +graphviz_output_format = "svg" + + +def setup(sphinx): + sphinx.connect("autoapi-skip-member", skip_util_classes_extension) diff --git a/airflow-ctl/docs/howto/index.rst b/airflow-ctl/docs/howto/index.rst new file mode 100644 index 00000000000..78de222b450 --- /dev/null +++ b/airflow-ctl/docs/howto/index.rst @@ -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. + + + +How-to Guides +============= + +Setting up the sandbox in the :doc:`/start` section was easy; +building a production-grade environment requires a bit more work! + +These how-to guides will step you through common tasks in using and +configuring an Airflow CTL environment. + + +How to use Airflow CTL +---------------------- + +**Important Note** +'''''''''''''''''' +Airflow CTL needs the Airflow API running to be able to work. Please, see the login section below before use. +Otherwise, you may get errors. + +Login +''''' +Airflow CTL needs to be able to connect to the Airflow API. You should pass API URL as a parameter to the command +``--api-url``. The URL should be in the form of ``http(s)://<host>:<port>``. +You can also set the environment variable ``AIRFLOW_CLI_TOKEN`` to the token to use for authentication. + +There are two ways to authenticate with the Airflow API: +1. Using a token acquired from the Airflow API + +.. code-block:: bash + + airflowctl auth login --api-url <api_url> --api-token <token> --env <env_name:production> + +2. Using a username and password + + +.. code-block:: bash + + airflowctl auth login --api-url <api_url> --username <username> --password <password> --env <env_name:production> + +3. (optional) Using a token acquired from the Airflow API and username and password + +.. code-block:: bash + + export AIRFLOW_CLI_TOKEN=<token> + airflowctl auth login --api-url <api_url> --env <env_name> + +In both cases token is securely stored in the keyring backend. Only configuration persisted in ``~/.config/airflow`` file +is the API URL and the environment name. The token is stored in the keyring backend and is not persisted in the +configuration file. The keyring backend is used to securely store the token and is not accessible to the user. + + +For more information use + +.. code-block:: bash + + airflowctl auth login --help + +You are ready to use Airflow CTL now. You can use the command ``airflowctl --help`` to see the list of available commands. +Please, also see :doc:`/cli-and-env-variables-ref` for the list of available commands and options. diff --git a/airflow-ctl/docs/index.rst b/airflow-ctl/docs/index.rst new file mode 100644 index 00000000000..203cfc8845a --- /dev/null +++ b/airflow-ctl/docs/index.rst @@ -0,0 +1,36 @@ + .. 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. + +What is Airflow CTL®? +===================== +Airflow CTL is a command line tool that helps you manage and deploy Apache Airflow environments. + +.. toctree:: + :hidden: + :caption: Content + + installation/index + howto/index + security + changelog + +.. toctree:: + :hidden: + :caption: Usage + + start + cli-and-env-variables-ref diff --git a/airflow-ctl/docs/installation/dependencies.rst b/airflow-ctl/docs/installation/dependencies.rst new file mode 100644 index 00000000000..77d20b60907 --- /dev/null +++ b/airflow-ctl/docs/installation/dependencies.rst @@ -0,0 +1,26 @@ + .. 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. + +Dependencies +------------ + +Airflow CTL dependencies +'''''''''''''''''''''''''' + +The ``apache-airflow-ctl`` PyPI basic package only installs what's needed to get started. +Additional packages can be installed depending on what will be useful in your +environment. diff --git a/airflow-ctl/docs/installation/index.rst b/airflow-ctl/docs/installation/index.rst new file mode 100644 index 00000000000..da6c78a905a --- /dev/null +++ b/airflow-ctl/docs/installation/index.rst @@ -0,0 +1,134 @@ + .. 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. + + +Installation of Airflow CTL® +---------------------------- + +.. contents:: :local: + +.. toctree:: + :maxdepth: 1 + :caption: Installation + :hidden: + + Prerequisites <prerequisites> + Dependencies <dependencies> + Supported versions <supported-versions> + Installing from sources <installing-from-sources> + Installing from PyPI <installing-from-pypi> + +This page describes installations options that you might use when considering how to install Airflow®. +Airflow consists of many components, often distributed among many physical or virtual machines, therefore +installation of Airflow might be quite complex, depending on the options you choose. + + +Using released sources +'''''''''''''''''''''' + +More details: :doc:`installing-from-sources` + +**When this option works best** + +* This option is best if you expect to build all your software from sources. +* Apache Airflow is one of the projects that belong to the `Apache Software Foundation <https://www.apache.org/>`__. + It is a requirement for all ASF projects that they can be installed using official sources released via `Official Apache Downloads <https://dlcdn.apache.org/>`__. +* This is the best choice if you have a strong need to `verify the integrity and provenance of the software <https://www.apache.org/dyn/closer.cgi#verify>`__ + +**Intended users** + +* Users who are familiar with installing and building software from sources and are conscious about integrity and provenance + of the software they use down to the lowest level possible. + +**What are you expected to handle** + +* You are expected to build and install airflow and its components on your own. +* You should develop and handle the deployment for all components of Airflow. +* You are responsible for setting up database, creating and managing database schema with ``airflow db`` commands, + automated startup and recovery, maintenance, cleanup and upgrades of Airflow and the Airflow Providers. +* You need to setup monitoring of your system allowing you to observe resources and react to problems. +* You are expected to configure and manage appropriate resources for the installation (memory, CPU, etc) based + on the monitoring of your installation and feedback loop. See the notes about requirements. + +**What Apache Airflow Community provides for that method** + +* You have `instructions <https://github.com/apache/airflow/blob/main/INSTALL>`__ on how to build the software but due to various environments + and tools you might want to use, you might expect that there will be problems which are specific to your deployment and environment + you will have to diagnose and solve. + +**Where to ask for help** + +* The ``#user-troubleshooting`` channel on slack can be used for quick general troubleshooting questions. The + `GitHub discussions <https://github.com/apache/airflow/discussions>`__ if you look for longer discussion and have more information to share. + +* The ``#user-best-practices`` channel on slack can be used to ask for and share best practices on using and deploying airflow. + +* If you can provide description of a reproducible problem with Airflow software, you can open issue at `GitHub issues <https://github.com/apache/airflow/issues>`_ + +* If you want to contribute back to Airflow, the ``#contributors`` slack channel for building the Airflow itself + + +Using PyPI +''''''''''' + +More details: :doc:`/installation/installing-from-pypi` + +**When this option works best** + +* This installation method is useful when you are not familiar with Containers and Docker and want to install + Apache Airflow on physical or virtual machines and you are used to installing and running software using custom + deployment mechanism. + +* The only officially supported mechanism of installation is via ``pip`` using constraint mechanisms. The constraint + files are managed by Apache Airflow release managers to make sure that you can repeatably install Airflow from PyPI with all Providers and + required dependencies. + +* In case of PyPI installation you could also verify integrity and provenance of the packages + downloaded from PyPI as described at the installation page, but software you download from PyPI is pre-built + for you so that you can install it without building, and you do not build the software from sources. + +**Intended users** + +* Users who are familiar with installing and configuring Python applications, managing Python environments, + dependencies and running software with their custom deployment mechanisms. + +**What are you expected to handle** + +* You are expected to install Airflow CTL. +* You should running Airflow API server. +* You need to setup monitoring of your system allowing you to observe resources and react to problems. + +**What Apache Airflow Community provides for that method** + +* You have :doc:`/installation/installing-from-pypi` + on how to install the software but due to various environments and tools you might want to use, you might + expect that there will be problems which are specific to your deployment and environment you will have to + diagnose and solve. +* You have :doc:`/start` where you can see an example of Quick Start with running Airflow + locally which you can use to start Airflow quickly for local testing and development. + However, this is just for inspiration. Do not expect :doc:`/start` is ready for production installation, + you need to build your own production-ready deployment if you follow this approach. + +**Where to ask for help** + +* The ``#user-troubleshooting`` channel on Airflow Slack for quick general + troubleshooting questions. The `GitHub discussions <https://github.com/apache/airflow/discussions>`__ + if you look for longer discussion and have more information to share. +* The ``#user-best-practices`` channel on slack can be used to ask for and share best + practices on using and deploying airflow. +* If you can provide description of a reproducible problem with Airflow software, you can open + issue at `GitHub issues <https://github.com/apache/airflow/issues>`__ diff --git a/airflow-ctl/docs/installation/installing-from-pypi.rst b/airflow-ctl/docs/installation/installing-from-pypi.rst new file mode 100644 index 00000000000..f498c163298 --- /dev/null +++ b/airflow-ctl/docs/installation/installing-from-pypi.rst @@ -0,0 +1,163 @@ + .. 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. + +Installation from PyPI +---------------------- + +This page describes installations using the ``apache-airflow-ctl`` package `published in +PyPI <https://pypi.org/project/apache-airflow-ctl/>`__. + +Installation tools +'''''''''''''''''' + +Only ``pip`` installation is currently officially supported. + +.. note:: + + While there are some successes with using other tools like `poetry <https://python-poetry.org/>`_ or + `pip-tools <https://pypi.org/project/pip-tools/>`_, they do not share the same workflow as + ``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. + + 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 + the problem in `this PR <https://github.com/bazelbuild/rules_python/pull/1166>`_ so it might be that + newer versions of ``bazel`` will handle it. + +Typical command to install airflowctl from scratch in a reproducible way from PyPI looks like below: + +.. code-block:: bash + + pip install "apache-airflow-ctl==|version|" + +Those are just examples, see further for more explanation why those are the best practices. + +.. note:: + + Generally speaking, Python community established practice is to perform application installation in a + virtualenv created with ``virtualenv`` or ``venv`` tools. You can also use ``uv`` or ``pipx`` to install + Airflow in application dedicated virtual environment created for you. There are also other tools that can be used + to manage your virtualenv installation and you are free to choose how you are managing the environments. + Airflow has no limitation regarding to the tool of your choice when it comes to virtual environment. + + The only exception where you might consider not using virtualenv is when you are building a container + image with only Airflow installed - this is for example how Airflow is installed in the official Container + image. + +.. _installation:constraints: + +Constraints files +''''''''''''''''' + +Why we need constraints +======================= + +Airflow CTL installation can be tricky because Airflow CTL is both a library and an application. + +Libraries usually keep their dependencies open and applications usually pin them, but we should do neither +and both at the same time. We decided to keep our dependencies as open as possible +(in ``pyproject.toml``) so users can install different version of libraries if needed. This means that +from time to time plain ``pip install apache-airflow-ctl`` will not work or will produce an unusable +Airflow CTL installation. + +Reproducible Airflow CTL installation +===================================== + +In order to have a reproducible installation, we also keep a set of constraint files in the +``constraints-main``, ``constraints-2-0``, ``constraints-2-1`` etc. orphan branches and then we create a tag +for each released version e.g. :subst-code:`constraints-|version|`. + +This way, we keep a tested set of dependencies at the moment of release. This provides you with the ability +of having the exact same installation of airflowctl + dependencies as was known to be working +at the moment of release - frozen set of dependencies for that version of Airflow CTL. There is a separate +constraints file for each version of Python that Airflow CTL supports. + +You can create the URL to the file substituting the variables in the template below. + +.. code-block:: + + https://raw.githubusercontent.com/apache/airflow/airflow-ctl/constraints-${AIRFLOWCTL_VERSION}/constraints-${PYTHON_VERSION}.txt + +where: + +- ``AIRFLOW_CTL_VERSION`` - Airflow CTL version (e.g. :subst-code:`|version|`) or ``main``, ``2-0``, for latest development version +- ``PYTHON_VERSION`` Python version e.g. ``3.9``, ``3.10`` + + +Verifying installed dependencies +================================ + +You can also always run the ``pip check`` command to test if the set of your Python packages is +consistent and not conflicting. + + +.. code-block:: bash + + > pip check + No broken requirements found. + + +When you see such message and the exit code from ``pip check`` is 0, you can be sure, that there are no +conflicting dependencies in your environment. + + +Using your own constraints +========================== + +When you decide to install your own dependencies, or want to upgrade or downgrade providers, you might want +to continue being able to keep reproducible installation of Airflow CTL and those dependencies via a single command. +In order to do that, you can produce your own constraints file and use it to install Airflow CTL instead of the +one provided by the community. + +.. code-block:: bash + + pip install "apache-airflow-ctl==|version|" + pip freeze > my-constraints.txt + + +Then you can use it to create reproducible installations of your environment in a single operation via +a local constraints file: + +.. code-block:: bash + + pip install "apache-airflow-ctl==|version|" --constraint "my-constraints.txt" + + +Similarly as in case of Airflow CTL original constraints, you can also host your constraints at your own +repository or server and use it remotely from there. + +Fixing Constraints at release time +'''''''''''''''''''''''''''''''''' + +The released "versioned" constraints are mostly ``fixed`` when we release Airflow CTL version and we only +update them in exceptional circumstances. For example when we find out that the released constraints might prevent +Airflow CTL from being installed consistently from the scratch. + +In normal circumstances, the constraint files are not going to change if new version of Airflow CTL +dependencies are released - not even when those versions contain critical security fixes. +The process of Airflow CTL releases is designed around upgrading dependencies automatically where +applicable but only when we release a new version of Airflow CTL, not for already released versions. + +Between the releases you can upgrade dependencies on your own and you can keep your own constraints +updated as described in the previous section. + +The easiest way to keep-up with the latest released dependencies is to upgrade to the latest released +Airflow CTL version. Whenever we release a new version of Airflow CTL, we upgrade all dependencies to the latest +applicable versions and test them together, so if you want to keep up with those tests - staying up-to-date +with latest version of Airflow CTL is the easiest way to update those dependencies. diff --git a/airflow-ctl/docs/installation/installing-from-sources.rst b/airflow-ctl/docs/installation/installing-from-sources.rst new file mode 100644 index 00000000000..41b52763d55 --- /dev/null +++ b/airflow-ctl/docs/installation/installing-from-sources.rst @@ -0,0 +1,163 @@ + .. 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. + +Installing from Sources +----------------------- + +Released packages +''''''''''''''''' + +.. jinja:: official_download_page + + This page describes downloading and verifying Airflow® version + ``{{ airflow_version }}`` using officially released packages. + You can also install ``Apache Airflow CTL`` - 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. + +The ``source``, ``sdist`` and ``whl`` packages released are the "official" sources of installation that you +can use if you want to verify the origin of the packages and want to verify checksums and signatures of +the packages. The packages are available via the +`Official Apache Software Foundations Downloads <https://dlcdn.apache.org/>`_ + +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 Airflow CTL are available at: + +.. jinja:: official_download_page + + * `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 Airflow CTL. + +Release integrity +''''''''''''''''' + +`PGP signatures KEYS <https://downloads.apache.org/airflowctl/KEYS>`__ + +It is essential that you verify the integrity of the downloaded files using the PGP or SHA signatures. +The PGP signatures can be verified using GPG or PGP. Please download the KEYS as well as the asc +signature files for relevant distribution. It is recommended to get these files from the +main distribution directory and not from the mirrors. + +.. code-block:: bash + + gpg -i KEYS + +or + +.. code-block:: bash + + pgpk -a KEYS + +or + +.. code-block:: bash + + pgp -ka KEYS + +To verify the binaries/sources you can download the relevant asc files for it from main +distribution directory and follow the below guide. + +.. code-block:: bash + + gpg --verify apache-airflow-ctl-********.asc apache-airflow-ctl-********* + +or + +.. code-block:: bash + + pgpv apache-airflow-ctl-********.asc + +or + +.. code-block:: bash + + pgp apache-airflow-********.asc + +Example: + +.. code-block:: console + :substitutions: + + $ gpg --verify apache-airflow-ctl-|version|-source.tar.gz.asc apache-airflow-ctl-|version|-source.tar.gz + gpg: Signature made Sat 11 Sep 12:49:54 2021 BST + gpg: using RSA key CDE15C6E4D3A8EC4ECF4BA4B6674E08AD7DE406F + gpg: issuer "kaxiln...@apache.org" + gpg: Good signature from "Kaxil Naik <kaxiln...@apache.org>" [unknown] + gpg: aka "Kaxil Naik <kaxiln...@gmail.com>" [unknown] + gpg: WARNING: The key's User ID is not certified with a trusted signature! + gpg: There is no indication that the signature belongs to the owner. + Primary key fingerprint: CDE1 5C6E 4D3A 8EC4 ECF4 BA4B 6674 E08A D7DE 406F + +The "Good signature from ..." is indication that the signatures are correct. +Do not worry about the "not certified with a trusted signature" warning. Most of the certificates used +by release managers are self signed, that's why you get this warning. By importing the server in the +previous step and importing it via ID from ``KEYS`` page, you know that this is a valid Key already. + +For SHA512 sum check, download the relevant ``sha512`` and run the following: + +.. code-block:: bash + + shasum -a 512 apache-airflow-ctl--******** | diff - apache-airflow-ctl--********.sha512 + +The ``SHASUM`` of the file should match the one provided in ``.sha512`` file. + +Example: + +.. code-block:: bash + :substitutions: + + shasum -a 512 apache-airflow-ctl-|version|-source.tar.gz | diff - apache-airflow-ctl-|version|-source.tar.gz.sha512 + + +Verifying PyPI releases +''''''''''''''''''''''' + +You can verify the Airflow CTL ``.whl`` packages from PyPI by locally downloading the package and signature +and SHA sum files with the script below: + + +.. jinja:: official_download_page + + .. code-block:: bash + + #!/bin/bash + airflowctl_version="{{ airflowctl_version }}" + ctl_download_dir="$(mktemp -d)" + pip download --no-deps "apache-airflow-ctl==${airflowctl_version}" --dest "${airflow_download_dir}" + curl "https://downloads.apache.org/airflowctl/${airflowctl_version}/apache_airflow_ctl-${airflowctl_version}-py3-none-any.whl.asc" \ + -L -o "${airflowctl_download_dir}/apache_airflow_ctl-${airflowctl_version}-py3-none-any.whl.asc" + curl "https://downloads.apache.org/airflow/${airflowctl_version}/apache_airflow_ctl-${airflowctl_version}-py3-none-any.whl.sha512" \ + -L -o "${airflowctl_download_dir}/apache_airflow_ctl-${airflowctl_version}-py3-none-any.whl.sha512" + echo + echo "Please verify files downloaded to ${airflowctl_download_dir}" + ls -la "${airflowctl_download_dir}" + echo + +Once you verify the files following the instructions from previous chapter you can remove the temporary +folder created. diff --git a/airflow-ctl/docs/installation/prerequisites.rst b/airflow-ctl/docs/installation/prerequisites.rst new file mode 100644 index 00000000000..615558e450c --- /dev/null +++ b/airflow-ctl/docs/installation/prerequisites.rst @@ -0,0 +1,63 @@ + .. 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. + +Prerequisites +------------- + +Airflow CTL is tested with: + + +The minimum memory required we recommend Airflow CTL to run with is 4GB, but the actual requirements depend +wildly on the deployment options you have. +The Keyring backend needs to be installed separately into your operating system. This will enhance security. See :doc:`/security` for more information. + +Keyring Backend +''''''''''''''' +Airflow CTL uses keyring to store the API token securely. This ensures that the token is not stored in plain text and is only accessible to authorized users. + +Recommended keyring backends are: +* `macOS Keychain <https://en.wikipedia.org/wiki/Keychain_%28software%29>`_ +* `Freedesktop Secret Service <http://standards.freedesktop.org/secret-service/>`_ supports many DE including GNOME (requires `secretstorage <https://pypi.python.org/pypi/secretstorage>`_) +* `KDE4 & KDE5 KWallet <https://en.wikipedia.org/wiki/KWallet>`_ (requires `dbus <https://pypi.python.org/pypi/dbus-python>`_) +* `Windows Credential Locker <https://docs.microsoft.com/en-us/windows/uwp/security/credential-locker>`_ + +Third-Party Backends +==================== + +In addition to the backends provided by the core keyring package for +the most common and secure use cases, there +are additional keyring backend implementations available for other +use cases. Simply install them to make them available: + +- `keyrings.cryptfile <https://pypi.org/project/keyrings.cryptfile>`_ + - Encrypted text file storage. +- `keyrings.alt <https://pypi.org/project/keyrings.alt>`_ + - "alternate", possibly-insecure backends, originally part of the core package, but available for opt-in. +- `gsheet-keyring <https://pypi.org/project/gsheet-keyring>`_ + - a backend that stores secrets in a Google Sheet. For use with `ipython-secrets <https://pypi.org/project/ipython-secrets>`_. +- `bitwarden-keyring <https://pypi.org/project/bitwarden-keyring/>`_ + - a backend that stores secrets in the `BitWarden <https://bitwarden.com/>`_ password manager. +- `onepassword-keyring <https://pypi.org/project/onepassword-keyring/>`_ + - a backend that stores secrets in the `1Password <https://1password.com/>`_ password manager. +- `sagecipher <https://pypi.org/project/sagecipher>`_ + - an encryption backend which uses the ssh agent protocol's signature operation to derive the cipher key. +- `keyrings.osx_keychain_keys <https://pypi.org/project/keyrings.osx-keychain-keys>`_ + - ``OSX keychain key-management``, for private, public, and symmetric keys. +- `keyring_pass.PasswordStoreBackend <https://github.com/nazarewk/keyring_pass>`_ + - Password Store (pass) backend for python's keyring +- `keyring_jeepney <https://pypi.org/project/keyring_jeepney>`__ + - a pure Python backend using the secret service ``DBus`` API for desktop Linux (requires ``keyring<24``). diff --git a/airflow-ctl/docs/installation/supported-versions.rst b/airflow-ctl/docs/installation/supported-versions.rst new file mode 100644 index 00000000000..823d4e5b461 --- /dev/null +++ b/airflow-ctl/docs/installation/supported-versions.rst @@ -0,0 +1,42 @@ + .. 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. + +Supported versions +------------------ + +Version Life Cycle +`````````````````` + +Apache Airflow CTL is compatible with Apache Airflow® versions 3.0.0 and later. + +Apache Airflow CTL version life cycle: + + .. This table is automatically updated by pre-commit scripts/ci/pre_commit/supported_versions.py + .. Beginning of auto-generated table + +========= ===================== ========= =============== ================= ================ +Version Current Patch/Minor State First Release Limited Support EOL/Terminated +========= ===================== ========= =============== ================= ================ +1 1.0.0 Supported TBD TBD TBD +========= ===================== ========= =============== ================= ================ + + .. End of auto-generated table + + +Limited support versions will be supported with security and critical bug fix only. +EOL versions will not get any fixes nor support. +We **highly** recommend installing the latest Airflow CTL release which has richer features. diff --git a/airflow-ctl/src/airflowctl/__init__.py b/airflow-ctl/docs/redirects.txt similarity index 81% copy from airflow-ctl/src/airflowctl/__init__.py copy to airflow-ctl/docs/redirects.txt index 2782fd09001..93082af1472 100644 --- a/airflow-ctl/src/airflowctl/__init__.py +++ b/airflow-ctl/docs/redirects.txt @@ -1,4 +1,3 @@ -# # 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 @@ -15,8 +14,14 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -from __future__ import annotations -__path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore +# References +cli-ref.rst cli-and-env-variables-ref.rst +Quick Start start.rst +changelog.rst changelog.rst + +# Installation.rst +installation/index.rst -__version__ = "0.0.1" +# How to +howto/index.rst diff --git a/airflow-ctl/docs/security.rst b/airflow-ctl/docs/security.rst new file mode 100644 index 00000000000..1e83537a7a5 --- /dev/null +++ b/airflow-ctl/docs/security.rst @@ -0,0 +1,27 @@ + .. 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. + +Security +======== +Airflow CTL is leveraging Apache Airflow Public API security features and additional layers of security to ensure that your data is safe and secure. +Airflow CTL facilitates the seamless deployment of CLI and API features together, reducing redundancy and simplifying maintenance. Transitioning from direct database access to an API-driven model will enhance the CLI's capabilities and improve security. + +- **Authentication**: Airflow CTL uses authentication to ensure that only authorized users can access the system. This is done using an API token. See more on https://airflow.apache.org/docs/apache-airflow/stable/security/api.html + +- **Keyring**: Airflow CTL uses keyring to store the API token securely. This ensures that the token is not stored in plain text and is only accessible to authorized users. + +Airflow CTL API Token has its own expiration time. The default is 1 hour. You can change it in the Airflow configuration file (airflow.cfg) by setting the ``jwt_cli_expiration_time`` parameter under the ``[api_auth]`` section. The value is in seconds. This will impact all users using ``airflowctl``. diff --git a/airflow-ctl/docs/start.rst b/airflow-ctl/docs/start.rst new file mode 100644 index 00000000000..523e7696b6d --- /dev/null +++ b/airflow-ctl/docs/start.rst @@ -0,0 +1,42 @@ + .. 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. + +Quick Start +----------- + +Airflow CTL is a command line tool that helps you manage your Airflow deployments. +It is designed to be easy to use and provides a simple interface for managing your Airflow environment. + +To get started, you can use the following command to create a new Airflow CTL environment: + +.. code-block:: bash + + airflowctl auth login --username <username> --password <password> --api-url <api_url> --env <env_name> + +OR + +.. code-block:: bash + + export AIRFLOW_CLI_TOKEN=<token> + airflowctl auth login --api-url <api_url> --env <env_name> + +This command will create a new Airflow CTL environment with the specified username and password. +You can then use the following command to start the Airflow CTL environment: + +.. code-block:: bash + + airflowctl --help diff --git a/airflow-ctl/docs/static/exampleinclude.css b/airflow-ctl/docs/static/exampleinclude.css new file mode 100644 index 00000000000..b4f2a42dcc7 --- /dev/null +++ b/airflow-ctl/docs/static/exampleinclude.css @@ -0,0 +1,86 @@ +/*! + * 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. + */ + +.example-header { + position: relative; + background: #9aaa7a; + padding: 8px 16px; + margin-bottom: 0; +} + +.example-header--with-button { + padding-right: 166px; +} + +.example-header::after { + content: ''; + display: table; + clear: both; +} + +.example-title { + display: block; + padding: 4px; + margin-right: 16px; + color: #fff; + overflow-x: auto; +} + +.example-header-button { + top: 8px; + right: 16px; + position: absolute; +} + +.example-header + .highlight-python { + margin-top: 0 !important; +} + +.viewcode-button { + display: inline-block; + padding: 8px 16px; + border: 0; + margin: 0; + outline: 0; + border-radius: 2px; + box-shadow: 0 3px 6px 0 rgba(0, 0, 0, 0.3); + color: #404040; + background-color: #e7e7e7; + cursor: pointer; + font-size: 16px; + font-weight: 500; + line-height: 1; + text-decoration: none; + text-overflow: ellipsis; + overflow: hidden; + text-transform: uppercase; + transition: background-color 0.2s; + vertical-align: middle; + white-space: nowrap; +} + +.viewcode-button:visited { + color: #404040; +} + +.viewcode-button:hover, +.viewcode-button:focus { + color: #404040; + background-color: #d6d6d6; +} diff --git a/airflow-ctl/docs/static/gh-jira-links.js b/airflow-ctl/docs/static/gh-jira-links.js new file mode 100644 index 00000000000..dfbfb15ad88 --- /dev/null +++ b/airflow-ctl/docs/static/gh-jira-links.js @@ -0,0 +1,36 @@ +/*! + * 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. + */ + +document.addEventListener('DOMContentLoaded', function() { + var el = document.getElementById('release-notes'); + if (el !== null ) { + // [AIRFLOW-...] + el.innerHTML = el.innerHTML.replace( + /\[(AIRFLOW-[\d]+)\]/g, + `<a href="https://issues.apache.org/jira/browse/$1">[$1]</a>` + ); + // (#...) + el.innerHTML = el.innerHTML.replace( + /#([\d]+)/g, + (match, group1) => { + return `<a href="https://github.com/apache/airflow/pull/${group1}">#${group1}</a>` + } + ); + }; +}) diff --git a/airflow-ctl/docs/static/redirects.js b/airflow-ctl/docs/static/redirects.js new file mode 100644 index 00000000000..4b44336e440 --- /dev/null +++ b/airflow-ctl/docs/static/redirects.js @@ -0,0 +1,29 @@ +/*! + * 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. + */ + +document.addEventListener("DOMContentLoaded", function () { + const redirects = { + "zombie-undead-tasks": "task-instance-heartbeat-timeout", + "zombie-tasks": "task-instance-heartbeat-timeout", + }; + const fragment = window.location.hash.substring(1); + if (redirects[fragment]) { + window.location.hash = redirects[fragment]; + } +}); diff --git a/airflow-ctl/docs/templates/footer.html b/airflow-ctl/docs/templates/footer.html new file mode 100644 index 00000000000..10c1500f3ee --- /dev/null +++ b/airflow-ctl/docs/templates/footer.html @@ -0,0 +1,29 @@ +{# + 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. +#} + +{% extends "!footer.html" %} + +{% block extrafooter %} + <div class="footer">This page uses <a href="https://analytics.google.com/"> + Google Analytics</a> to collect statistics. You can disable it by blocking + the JavaScript coming from www.google-analytics.com. Check our + <a href="{{ pathto('privacy_notice') }}">Privacy Policy</a> + for more details. + </div> +{% endblock %} diff --git a/airflow-ctl/pyproject.toml b/airflow-ctl/pyproject.toml index 3a4796a32a9..8eeac9ed8b3 100644 --- a/airflow-ctl/pyproject.toml +++ b/airflow-ctl/pyproject.toml @@ -96,6 +96,21 @@ exclude_also = [ ] [dependency-groups] +# To build docs: +# +# uv run --group docs build-docs +# +# To enable auto-refreshing build with server: +# +# uv run --group docs build-docs --autobuild +# +# To see more options: +# +# uv run --group docs build-docs --help +# +docs = [ + "apache-airflow-devel-common[docs]" +] dev = [ "apache-airflow-ctl[dev]", ] @@ -142,7 +157,6 @@ addopts = [ "--asyncio-mode=strict", ] - norecursedirs = [ ".eggs", ] @@ -164,3 +178,9 @@ pythonpath = "tests" # Keep temporary directories (created by `tmp_path`) for 2 recent runs only failed tests. tmp_path_retention_count = "2" tmp_path_retention_policy = "failed" + +[tool.uv] +required-version = ">=0.6.3" + +[tool.uv.sources] +apache-airflow-devel-common = { workspace = true } diff --git a/airflow-ctl/src/airflowctl/__init__.py b/airflow-ctl/src/airflowctl/__init__.py index 2782fd09001..82f4e830b6a 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__) # type: ignore -__version__ = "0.0.1" +__version__ = "1.0.0" diff --git a/airflow-ctl/src/airflowctl/api/operations.py b/airflow-ctl/src/airflowctl/api/operations.py index ed015a439d9..20b10a0a92f 100644 --- a/airflow-ctl/src/airflowctl/api/operations.py +++ b/airflow-ctl/src/airflowctl/api/operations.py @@ -57,6 +57,7 @@ from airflowctl.api.datamodels.generated import ( VariableResponse, VersionInfo, ) +from airflowctl.exceptions import AirflowCtlConnectionException if TYPE_CHECKING: from airflowctl.api.client import Client @@ -104,7 +105,9 @@ def _check_flag_and_exit_if_server_response_error(func): return _exit_if_server_response_error(response=func(self, *args, **kwargs)) return func(self, *args, **kwargs) except httpx.ConnectError as e: - raise e + if "Connection refused" in str(e): + raise AirflowCtlConnectionException("Connection refused. Is the API server running?") + raise AirflowCtlConnectionException(f"Connection error: {e}") return wrapped diff --git a/airflow-ctl/src/airflowctl/ctl/cli_config.py b/airflow-ctl/src/airflowctl/ctl/cli_config.py index c8bce27b368..7e56fc61281 100644 --- a/airflow-ctl/src/airflowctl/ctl/cli_config.py +++ b/airflow-ctl/src/airflowctl/ctl/cli_config.py @@ -36,7 +36,11 @@ import rich import airflowctl.api.datamodels.generated as generated_datamodels from airflowctl.api.client import NEW_API_CLIENT, Client, ClientKind, provide_api_client from airflowctl.api.operations import BaseOperations, ServerResponseError -from airflowctl.exceptions import AirflowCtlCredentialNotFoundException, AirflowCtlNotFoundException +from airflowctl.exceptions import ( + AirflowCtlConnectionException, + AirflowCtlCredentialNotFoundException, + AirflowCtlNotFoundException, +) from airflowctl.utils.module_loading import import_string BUILD_DOCS = "BUILDING_AIRFLOW_DOCS" in os.environ @@ -60,6 +64,8 @@ def safe_call_command(function: Callable, args: Iterable[Arg]) -> None: function(args) except AirflowCtlCredentialNotFoundException as e: rich.print(f"command failed due to {e}") + except AirflowCtlConnectionException as e: + rich.print(f"command failed due to {e}") except AirflowCtlNotFoundException as e: rich.print(f"command failed due to {e}") @@ -216,7 +222,28 @@ class GroupCommand(NamedTuple): epilog: str | None = None -CLICommand = Union[ActionCommand, GroupCommand] +class GroupCommandParser(NamedTuple): + """ClI command with subcommands.""" + + name: str + help: str + subcommands: Iterable + description: str | None = None + epilog: str | None = None + + @classmethod + def from_group_command(cls, group_command: GroupCommand) -> GroupCommandParser: + """Create GroupCommandParser from GroupCommand.""" + return cls( + name=group_command.name, + help=group_command.help, + subcommands=group_command.subcommands, + description=group_command.description, + epilog=group_command.epilog, + ) + + +CLICommand = Union[ActionCommand, GroupCommand, GroupCommandParser] class CommandFactory: @@ -355,7 +382,7 @@ class CommandFactory: arg_flags=("--" + self._sanitize_arg_parameter_key(field),), arg_type=field_type.annotation, arg_action=argparse.BooleanOptionalAction if field_type.annotation is bool else None, # type: ignore - arg_help=f"Argument Type: {field_type.annotation}, {field} for {parameter_key} operation", + arg_help=f"{field} for {parameter_key} operation", arg_default=False if field_type.annotation is bool else None, ) ) @@ -370,7 +397,7 @@ class CommandFactory: arg_flags=("--" + self._sanitize_arg_parameter_key(field),), arg_type=annotation, arg_action=argparse.BooleanOptionalAction if annotation is bool else None, # type: ignore - arg_help=f"Argument Type: {annotation}, {field} for {parameter_key} operation", + arg_help=f"{field} for {parameter_key} operation", arg_default=False if annotation is bool else None, ) ) @@ -390,7 +417,7 @@ class CommandFactory: arg_action=argparse.BooleanOptionalAction if type(parameter_type) is bool else None, - arg_help=f"Argument Type: {type(parameter_type)}, {parameter_key} for {operation.get('name')} operation in {operation.get('parent').name}", + arg_help=f"{parameter_key} for {operation.get('name')} operation in {operation.get('parent').name}", arg_default=False if type(parameter_type) is bool else None, ) ) diff --git a/airflow-ctl/src/airflowctl/ctl/cli_parser.py b/airflow-ctl/src/airflowctl/ctl/cli_parser.py index 685d46322b4..138f714462a 100644 --- a/airflow-ctl/src/airflowctl/ctl/cli_parser.py +++ b/airflow-ctl/src/airflowctl/ctl/cli_parser.py @@ -38,6 +38,7 @@ from airflowctl.ctl.cli_config import ( ActionCommand, DefaultHelpParser, GroupCommand, + GroupCommandParser, core_commands, ) from airflowctl.exceptions import AirflowCtlException @@ -54,7 +55,10 @@ airflow_commands = core_commands.copy() # make a copy to prevent bad interactio log = logging.getLogger(__name__) -ALL_COMMANDS_DICT: dict[str, CLICommand] = {sp.name: sp for sp in airflow_commands} +ALL_COMMANDS_DICT: dict[str, CLICommand] = { + sp.name: GroupCommandParser.from_group_command(sp) if isinstance(sp, GroupCommand) else sp + for sp in airflow_commands +} class AirflowHelpFormatter(RichHelpFormatter): @@ -69,7 +73,7 @@ class AirflowHelpFormatter(RichHelpFormatter): self._indent() subactions = action._get_subactions() action_subcommands, group_subcommands = partition( - lambda d: isinstance(ALL_COMMANDS_DICT[d.dest], GroupCommand), subactions + lambda d: isinstance(ALL_COMMANDS_DICT[d.dest], GroupCommandParser), subactions ) yield Action([], f"\n{' ':{self._current_indent}}Groups", nargs=0) self._indent() @@ -131,7 +135,7 @@ def _add_command(subparsers: argparse._SubParsersAction, sub: CLICommand) -> Non ) sub_proc.formatter_class = LazyRichHelpFormatter - if isinstance(sub, GroupCommand): + if isinstance(sub, GroupCommandParser): _add_group_command(sub, sub_proc) elif isinstance(sub, ActionCommand): _add_action_command(sub, sub_proc) @@ -145,7 +149,7 @@ def _add_action_command(sub: ActionCommand, sub_proc: argparse.ArgumentParser) - sub_proc.set_defaults(func=sub.func) -def _add_group_command(sub: GroupCommand, sub_proc: argparse.ArgumentParser) -> None: +def _add_group_command(sub: GroupCommandParser, sub_proc: argparse.ArgumentParser) -> None: subcommands = sub.subcommands sub_subparsers = sub_proc.add_subparsers(dest="subcommand", metavar="COMMAND") sub_subparsers.required = True diff --git a/airflow-ctl/src/airflowctl/exceptions.py b/airflow-ctl/src/airflowctl/exceptions.py index b313e969b2c..515879ec359 100644 --- a/airflow-ctl/src/airflowctl/exceptions.py +++ b/airflow-ctl/src/airflowctl/exceptions.py @@ -36,3 +36,7 @@ class AirflowCtlNotFoundException(AirflowCtlException): class AirflowCtlCredentialNotFoundException(AirflowCtlNotFoundException): """Raise when a credential couldn't be found while performing an operation.""" + + +class AirflowCtlConnectionException(AirflowCtlException): + """Raise when a connection error occurs while performing an operation.""" diff --git a/airflow-ctl/tests/airflow_ctl/api/test_operations.py b/airflow-ctl/tests/airflow_ctl/api/test_operations.py index a722d15369e..631e32bd60a 100644 --- a/airflow-ctl/tests/airflow_ctl/api/test_operations.py +++ b/airflow-ctl/tests/airflow_ctl/api/test_operations.py @@ -20,8 +20,6 @@ from __future__ import annotations import datetime import json import uuid -from contextlib import redirect_stdout -from io import StringIO from typing import TYPE_CHECKING import httpx @@ -75,6 +73,7 @@ from airflowctl.api.datamodels.generated import ( VariableResponse, VersionInfo, ) +from airflowctl.exceptions import AirflowCtlConnectionException if TYPE_CHECKING: from pydantic import NonNegativeInt @@ -93,13 +92,10 @@ def make_api_client( class TestBaseOperations: def test_server_connection_refused(self): client = make_api_client(base_url="http://localhost") - with ( - pytest.raises(httpx.ConnectError), - redirect_stdout(StringIO()) as stdout, + with pytest.raises( + AirflowCtlConnectionException, match="Connection refused. Is the API server running?" ): - client.connections.get(1) - stdout = stdout.getvalue() - assert "" in stdout + client.connections.get("1") class TestAssetsOperations: diff --git a/airflow-ctl/tests/airflow_ctl/ctl/test_cli_config.py b/airflow-ctl/tests/airflow_ctl/ctl/test_cli_config.py index 94073d8350f..fe9ed6bad0a 100644 --- a/airflow-ctl/tests/airflow_ctl/ctl/test_cli_config.py +++ b/airflow-ctl/tests/airflow_ctl/ctl/test_cli_config.py @@ -46,7 +46,7 @@ def test_args(): ( "--dag-id", { - "help": "Argument Type: <class 'str'>, dag_id for backfill operation", + "help": "dag_id for backfill operation", "action": None, "default": None, "type": str, @@ -56,7 +56,7 @@ def test_args(): ( "--from-date", { - "help": "Argument Type: <class 'datetime.datetime'>, from_date for backfill operation", + "help": "from_date for backfill operation", "action": None, "default": None, "type": datetime.datetime, @@ -66,7 +66,7 @@ def test_args(): ( "--to-date", { - "help": "Argument Type: <class 'datetime.datetime'>, to_date for backfill operation", + "help": "to_date for backfill operation", "action": None, "default": None, "type": datetime.datetime, @@ -76,7 +76,7 @@ def test_args(): ( "--run-backwards", { - "help": "Argument Type: <class 'bool'>, run_backwards for backfill operation", + "help": "run_backwards for backfill operation", "action": BooleanOptionalAction, "default": False, "type": bool, @@ -86,7 +86,7 @@ def test_args(): ( "--dag-run-conf", { - "help": "Argument Type: dict[str, typing.Any], dag_run_conf for backfill operation", + "help": "dag_run_conf for backfill operation", "action": None, "default": None, "type": dict[str, Any], @@ -96,7 +96,7 @@ def test_args(): ( "--reprocess-behavior", { - "help": "Argument Type: <enum 'ReprocessBehavior'>, reprocess_behavior for backfill operation", + "help": "reprocess_behavior for backfill operation", "action": None, "default": None, "type": ReprocessBehavior, @@ -106,7 +106,7 @@ def test_args(): ( "--max-active-runs", { - "help": "Argument Type: <class 'int'>, max_active_runs for backfill operation", + "help": "max_active_runs for backfill operation", "action": None, "default": None, "type": int, diff --git a/devel-common/src/docs/utils/conf_constants.py b/devel-common/src/docs/utils/conf_constants.py index a6699b25cee..3a92e385a8b 100644 --- a/devel-common/src/docs/utils/conf_constants.py +++ b/devel-common/src/docs/utils/conf_constants.py @@ -45,6 +45,11 @@ AIRFLOW_CORE_DOCKER_COMPOSE_PATH = AIRFLOW_CORE_DOCS_PATH / "howto" / "docker-co AIRFLOW_CORE_SRC_PATH = AIRFLOW_CORE_ROOT_PATH / "src" AIRFLOW_FAVICON_PATH = AIRFLOW_CORE_SRC_PATH / "airflow" / "ui" / "public" / "pin_32.png" +AIRFLOW_CTL_ROOT_PATH = AIRFLOW_REPO_ROOT_PATH / "airflow-ctl" +AIRFLOW_CTL_DOCS_PATH = AIRFLOW_CTL_ROOT_PATH / "docs" +AIRFLOW_CTL_DOC_STATIC_PATH = AIRFLOW_CTL_DOCS_PATH / "static" +AIRFLOW_CTL_SRC_PATH = AIRFLOW_CTL_ROOT_PATH / "src" + CHART_PATH = AIRFLOW_CORE_ROOT_PATH / "chart" CHART_DOC_PATH = AIRFLOW_CORE_DOCS_PATH / "docs" diff --git a/devel-common/src/sphinx_exts/docs_build/docs_builder.py b/devel-common/src/sphinx_exts/docs_build/docs_builder.py index 44ba83e6f7a..59b3692c695 100644 --- a/devel-common/src/sphinx_exts/docs_build/docs_builder.py +++ b/devel-common/src/sphinx_exts/docs_build/docs_builder.py @@ -65,6 +65,8 @@ class AirflowDocsBuilder: self.is_docker_stack = True if self.package_name == "apache-airflow-providers": self.is_providers_summary = True + if self.package_name == "apache-airflow-ctl": + self.is_airflow_ctl = True @property def _doctree_dir(self) -> Path: @@ -117,6 +119,8 @@ class AirflowDocsBuilder: if self.package_name.startswith("apache-airflow-providers-"): package_paths = self.package_name[len("apache-airflow-providers-") :].split("-") return (AIRFLOW_CONTENT_ROOT_PATH / "providers").joinpath(*package_paths) / "docs" + if self.package_name == "apache-airflow-ctl": + return AIRFLOW_CONTENT_ROOT_PATH / "airflow-ctl" / "docs" console.print(f"[red]Unknown package name: {self.package_name}") sys.exit(1) @@ -330,6 +334,7 @@ def get_available_packages(include_suspended: bool = False, short_form: bool = F "apache-airflow", *provider_names, "apache-airflow-providers", + "apache-airflow-ctl", "helm-chart", "docker-stack", ]