This is an automated email from the ASF dual-hosted git repository.
shahar1 pushed a commit to branch airflow-ctl/v0-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/airflow-ctl/v0-1-test by this
push:
new 3c4202148f5 [airflow-ctl/v0-1-test] Fix airflowctl config lint filters
matching partial config names (#75690) (#75736)
3c4202148f5 is described below
commit 3c4202148f5b0518e31bdce586a467857994d5f9
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Oct 27 09:27:05 2026 +0000
[airflow-ctl/v0-1-test] Fix airflowctl config lint filters matching partial
config names (#75690) (#75736)
The lint filters were declared as plain strings, so the membership tests in
the command body compared a name against a string rather than against a
list.
Any name that happened to be a prefix of what the user typed slipped
through:
`--option auth_backends` also reported `auth_backend`, and the mirror case
on
`--ignore-option` silently suppressed warnings the user still wanted to see.
Five such pairs exist in CONFIGS_CHANGES today.
The comma-separated lists the docstring has always advertised only appeared
to
work because substring matching happened to accept them; the verbose output
joined the same strings character by character.
airflow-core already parses these four arguments with string_list_type,
which
was defined here but left unused.
(cherry picked from commit 91fbcbff93b00b73da64f5dfbb473470679475fc)
Co-authored-by: rjgoyln <[email protected]>
---
airflow-ctl/src/airflowctl/ctl/cli_config.py | 16 ++--
.../src/airflowctl/ctl/commands/config_command.py | 16 ++--
.../ctl/commands/test_config_command.py | 96 ++++++++++++++++++++++
3 files changed, 112 insertions(+), 16 deletions(-)
diff --git a/airflow-ctl/src/airflowctl/ctl/cli_config.py
b/airflow-ctl/src/airflowctl/ctl/cli_config.py
index d8f372f6ef5..c91b92b0191 100755
--- a/airflow-ctl/src/airflowctl/ctl/cli_config.py
+++ b/airflow-ctl/src/airflowctl/ctl/cli_config.py
@@ -311,27 +311,27 @@ ARG_ACTION_ON_EXISTING_KEY = Arg(
# Config arguments
ARG_CONFIG_SECTION = Arg(
flags=("--section",),
- type=str,
+ type=string_list_type,
dest="section",
- help="The section of the configuration",
+ help="The section name(s) of the configuration, comma separated",
)
ARG_CONFIG_OPTION = Arg(
flags=("--option",),
- type=str,
+ type=string_list_type,
dest="option",
- help="The option of the configuration",
+ help="The option name(s) of the configuration, comma separated",
)
ARG_CONFIG_IGNORE_SECTION = Arg(
flags=("--ignore-section",),
- type=str,
+ type=string_list_type,
dest="ignore_section",
- help="The configuration section being ignored",
+ help="The configuration section name(s) being ignored, comma separated",
)
ARG_CONFIG_IGNORE_OPTION = Arg(
flags=("--ignore-option",),
- type=str,
+ type=string_list_type,
dest="ignore_option",
- help="The configuration option being ignored",
+ help="The configuration option name(s) being ignored, comma separated",
)
ARG_CONFIG_VERBOSE = Arg(
flags=(
diff --git a/airflow-ctl/src/airflowctl/ctl/commands/config_command.py
b/airflow-ctl/src/airflowctl/ctl/commands/config_command.py
index b83600697ad..8d93d0393de 100644
--- a/airflow-ctl/src/airflowctl/ctl/commands/config_command.py
+++ b/airflow-ctl/src/airflowctl/ctl/commands/config_command.py
@@ -733,20 +733,20 @@ def lint(args, api_client=NEW_API_CLIENT) -> None:
This function scans the Airflow configuration file for parameters that are
removed or renamed in
Airflow 3.0. It provides suggestions for alternative parameters or
settings where applicable.
CLI Arguments:
- --section: str (optional)
- The specific section of the configuration to lint.
+ --section: comma separated list (optional)
+ The specific section name(s) of the configuration to lint.
Example: --section core
- --option: str (optional)
- The specific option within a section to lint.
+ --option: comma separated list (optional)
+ The specific option name(s) within a section to lint.
Example: --option check_slas
- --ignore-section: str (optional)
- A section to ignore during linting.
+ --ignore-section: comma separated list (optional)
+ The section name(s) to ignore during linting.
Example: --ignore-section webserver
- --ignore-option: str (optional)
- An option to ignore during linting.
+ --ignore-option: comma separated list (optional)
+ The option name(s) to ignore during linting.
Example: --ignore-option smtp_user
--verbose: flag (optional)
diff --git a/airflow-ctl/tests/airflow_ctl/ctl/commands/test_config_command.py
b/airflow-ctl/tests/airflow_ctl/ctl/commands/test_config_command.py
index 77705a4c63c..42f8d073c5d 100644
--- a/airflow-ctl/tests/airflow_ctl/ctl/commands/test_config_command.py
+++ b/airflow-ctl/tests/airflow_ctl/ctl/commands/test_config_command.py
@@ -27,10 +27,45 @@ from airflowctl.ctl import cli_parser
from airflowctl.ctl.commands import config_command
from airflowctl.ctl.commands.config_command import ConfigChange,
ConfigParameter
+# auth_backend/auth_backends mirrors a real substring pair in CONFIGS_CHANGES;
the api/api_auth
+# sections collide the same way, and sql_alchemy_conn overlaps neither.
+CONFUSABLE_CONFIGS_CHANGES = [
+ ConfigChange(config=ConfigParameter("api", "auth_backend")),
+ ConfigChange(config=ConfigParameter("api_auth", "auth_backends")),
+ ConfigChange(config=ConfigParameter("database", "sql_alchemy_conn")),
+]
+
+CONFUSABLE_CONFIG_RESPONSE = Config(
+ sections=[
+ ConfigSection(name=change.config.section,
options=[ConfigOption(key=change.config.option, value="x")])
+ for change in CONFUSABLE_CONFIGS_CHANGES
+ ]
+)
+
+
+def find_reported_options(mock_rich_print) -> list[str]:
+ """Return the CONFUSABLE_CONFIGS_CHANGES option names that lint reported,
sorted."""
+ printed = [call[0][0] for call in mock_rich_print.call_args_list]
+ return sorted(
+ change.config.option
+ for change in CONFUSABLE_CONFIGS_CHANGES
+ # Backticks delimit the name in the message, so `auth_backend` does
not match `auth_backends`.
+ if any(f"`{change.config.option}` configuration parameter" in line for
line in printed)
+ )
+
class TestCliConfigCommands:
parser = cli_parser.get_parser()
+ @pytest.fixture
+ def confusable_api_client(self, api_client_maker):
+ return api_client_maker(
+ path="/api/v2/config",
+ response_json=CONFUSABLE_CONFIG_RESPONSE.model_dump(),
+ expected_http_status_code=200,
+ kind=ClientKind.CLI,
+ )
+
@patch("rich.print")
def test_lint_no_issues(self, mock_rich_print, api_client_maker):
response_config = Config(
@@ -429,6 +464,67 @@ class TestCliConfigCommands:
assert "[red]Found issues in your airflow.cfg:[/red]" in calls[0]
assert "This is a test suggestion." in calls[1]
+ @pytest.mark.parametrize(
+ ("lint_args", "expected_options"),
+ [
+ pytest.param(
+ ["--option", "auth_backends"],
+ ["auth_backends"],
+ id="option-matches-whole-name-not-prefix",
+ ),
+ pytest.param(
+ ["--option", "auth_backends,sql_alchemy_conn"],
+ ["auth_backends", "sql_alchemy_conn"],
+ id="option-comma-separated-list",
+ ),
+ pytest.param(
+ ["--ignore-option", "auth_backends"],
+ ["auth_backend", "sql_alchemy_conn"],
+ id="ignore-option-matches-whole-name-not-prefix",
+ ),
+ pytest.param(
+ ["--section", "api_auth"],
+ ["auth_backends"],
+ id="section-matches-whole-name-not-prefix",
+ ),
+ pytest.param(
+ ["--section", "api_auth,database"],
+ ["auth_backends", "sql_alchemy_conn"],
+ id="section-comma-separated-list",
+ ),
+ pytest.param(
+ ["--ignore-section", "api_auth"],
+ ["auth_backend", "sql_alchemy_conn"],
+ id="ignore-section-matches-whole-name-not-prefix",
+ ),
+ ],
+ )
+ @patch("rich.print", autospec=True)
+ @patch("airflowctl.ctl.commands.config_command.CONFIGS_CHANGES",
CONFUSABLE_CONFIGS_CHANGES)
+ def test_lint_filters_match_whole_names(
+ self, mock_rich_print, lint_args, expected_options,
confusable_api_client
+ ):
+ config_command.lint(
+ self.parser.parse_args(["config", "lint", *lint_args]),
+ api_client=confusable_api_client,
+ )
+
+ assert find_reported_options(mock_rich_print) == expected_options
+
+ @patch("rich.print", autospec=True)
+ @patch("airflowctl.ctl.commands.config_command.CONFIGS_CHANGES",
CONFUSABLE_CONFIGS_CHANGES)
+ def test_lint_verbose_lists_ignored_names(self, mock_rich_print,
confusable_api_client):
+ config_command.lint(
+ self.parser.parse_args(
+ ["config", "lint", "--ignore-section", "api",
"--ignore-option", "auth_backend", "--verbose"]
+ ),
+ api_client=confusable_api_client,
+ )
+
+ calls = [call[0][0] for call in mock_rich_print.call_args_list]
+ assert "Ignored sections: [green]api[/green]" in calls
+ assert "Ignored options: [green]auth_backend[/green]" in calls
+
@patch("airflowctl.api.client.Credentials.load")
@patch.dict(os.environ, {"AIRFLOW_CLI_TOKEN": "TEST_TOKEN"})
@patch.dict(os.environ, {"AIRFLOW_CLI_ENVIRONMENT": "TEST_CONFIG"})