This is an automated email from the ASF dual-hosted git repository.

henry3260 pushed a commit to branch v3-3-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-3-test by this push:
     new 4c91c4ee51f [v3-3-test] Fix airflow config lint staying silent on 
conditional removal rules (#70977) (#71651)
4c91c4ee51f is described below

commit 4c91c4ee51f7c7ac8eeac0dfefd17ebc72531558
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Sat Aug 15 22:27:48 2026 +0800

    [v3-3-test] Fix airflow config lint staying silent on conditional removal 
rules (#70977) (#71651)
    
    (cherry picked from commit 39d9abaa3bf45fe043fd04eaabe2c029556c3e3a)
    
    Co-authored-by: Y-C <[email protected]>
---
 airflow-core/newsfragments/70977.bugfix.rst        |  1 +
 .../src/airflow/cli/commands/config_command.py     | 20 ++++---
 .../tests/unit/cli/commands/test_config_command.py | 62 ++++++++++++++++++++++
 3 files changed, 77 insertions(+), 6 deletions(-)

diff --git a/airflow-core/newsfragments/70977.bugfix.rst 
b/airflow-core/newsfragments/70977.bugfix.rst
new file mode 100644
index 00000000000..0a3f85b63c4
--- /dev/null
+++ b/airflow-core/newsfragments/70977.bugfix.rst
@@ -0,0 +1 @@
+Fixed ``airflow config lint`` silently skipping removal rules that only apply 
to a specific value.
diff --git a/airflow-core/src/airflow/cli/commands/config_command.py 
b/airflow-core/src/airflow/cli/commands/config_command.py
index 5f087fdfa4c..e464fa1e80b 100644
--- a/airflow-core/src/airflow/cli/commands/config_command.py
+++ b/airflow-core/src/airflow/cli/commands/config_command.py
@@ -139,12 +139,12 @@ class ConfigChange:
                 f"`{self.config.option}` configuration parameter renamed to 
`{self.renamed_to.option}` "
                 f"in the `{self.config.section}` section."
             )
-        if self.was_removed and not self.remove_if_equals:
-            return (
-                f"Removed{' deprecated' if self.was_deprecated else ''} 
`{self.config.option}` configuration parameter "
-                f"from `{self.config.section}` section. "
-                f"{self.suggestion}"
-            )
+        if self.was_removed:
+            if self.remove_if_equals is None:
+                return self._removed_message
+            # Only the exact value is dropped, so an unreadable option must 
never count as a match.
+            if conf.get(self.config.section, self.config.option, 
fallback=None) == str(self.remove_if_equals):
+                return self._removed_message
         if self.is_invalid_if is not None:
             value = conf.get(self.config.section, self.config.option)
             if value == self.is_invalid_if:
@@ -154,6 +154,14 @@ class ConfigChange:
                 )
         return None
 
+    @property
+    def _removed_message(self) -> str:
+        return (
+            f"Removed{' deprecated' if self.was_deprecated else ''} 
`{self.config.option}` configuration parameter "
+            f"from `{self.config.section}` section. "
+            f"{self.suggestion}"
+        )
+
 
 CONFIGS_CHANGES = [
     # admin
diff --git a/airflow-core/tests/unit/cli/commands/test_config_command.py 
b/airflow-core/tests/unit/cli/commands/test_config_command.py
index ecbb31fbb19..c6345b96434 100644
--- a/airflow-core/tests/unit/cli/commands/test_config_command.py
+++ b/airflow-core/tests/unit/cli/commands/test_config_command.py
@@ -531,6 +531,68 @@ class TestConfigLint:
 
         assert "Invalid value" not in normalized_output
 
+    @pytest.mark.parametrize(
+        ("remove_if_equals", "config_value", "expect_issue"),
+        [
+            pytest.param("removed_value", "removed_value", True, id="match"),
+            pytest.param("removed_value", "kept_value", False, id="no-match"),
+            pytest.param("", "", True, id="empty-string-match"),
+            pytest.param("", "kept_value", False, id="empty-string-no-match"),
+        ],
+    )
+    def test_lint_reports_conditional_removal_only_when_value_matches(
+        self, remove_if_equals, config_value, expect_issue, stdout_capture
+    ):
+        config_change = ConfigChange(
+            config=ConfigParameter("test_section", "test_option"),
+            was_removed=True,
+            remove_if_equals=remove_if_equals,
+        )
+        with (
+            mock.patch.object(config_command, "CONFIGS_CHANGES", 
[config_change]),
+            conf_vars({("test_section", "test_option"): config_value}),
+            stdout_capture as temp_stdout,
+        ):
+            
config_command.lint_config(cli_parser.get_parser().parse_args(["config", 
"lint"]))
+
+        normalized_output = re.sub(r"\s+", " ", temp_stdout.getvalue().strip())
+        expected_message = (
+            "Removed deprecated `test_option` configuration parameter from 
`test_section` section."
+        )
+
+        assert (expected_message in normalized_output) is expect_issue
+
+    @pytest.mark.parametrize(
+        ("section", "option", "value"),
+        [
+            ("core", "hostname", ":"),
+            ("email", "email_backend", 
"airflow.contrib.utils.sendgrid.send_email"),
+            ("elasticsearch", "log_id_template", 
"{dag_id}-{task_id}-{logical_date}-{try_number}"),
+            (
+                "logging",
+                "log_filename_template",
+                "{{ ti.dag_id }}/{{ ti.task_id }}/{{ ts }}/{{ try_number 
}}.log",
+            ),
+            (
+                "logging",
+                "log_filename_template",
+                "dag_id={{ ti.dag_id }}/run_id={{ ti.run_id }}/task_id={{ 
ti.task_id }}/"
+                "{% if ti.map_index >= 0 %}map_index={{ ti.map_index }}/{% 
endif %}"
+                "attempt={{ try_number }}.log",
+            ),
+        ],
+    )
+    def test_lint_detects_shipped_conditional_removals(self, section, option, 
value, stdout_capture):
+        env_var = f"AIRFLOW__{section.upper()}__{option.upper()}"
+        with mock.patch.dict(os.environ, {env_var: value}), stdout_capture as 
temp_stdout:
+            config_command.lint_config(
+                cli_parser.get_parser().parse_args(["config", "lint", 
"--section", section])
+            )
+
+        normalized_output = re.sub(r"\s+", " ", temp_stdout.getvalue().strip())
+
+        assert f"`{option}` configuration parameter from `{section}` section." 
in normalized_output
+
 
 class TestCliConfigUpdate:
     @conf_vars({("core", "executor"): "SequentialExecutor"})

Reply via email to