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"})

Reply via email to