This is an automated email from the ASF dual-hosted git repository.
weilee pushed a commit to branch v2-10-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/v2-10-test by this push:
new bbded798445 feat(cli/config): backport "airflow config lint" (#45736)
bbded798445 is described below
commit bbded7984452f6c27a726a967afe4544289194a4
Author: Wei Lee <[email protected]>
AuthorDate: Fri Feb 14 18:37:47 2025 +0800
feat(cli/config): backport "airflow config lint" (#45736)
https://github.com/apache/airflow/pull/44908
---
airflow/cli/cli_config.py | 34 +++
airflow/cli/commands/config_command.py | 379 ++++++++++++++++++++++++++++++
tests/cli/commands/test_config_command.py | 213 +++++++++++++++++
3 files changed, 626 insertions(+)
diff --git a/airflow/cli/cli_config.py b/airflow/cli/cli_config.py
index b192860f0a8..ce753e2f4bb 100644
--- a/airflow/cli/cli_config.py
+++ b/airflow/cli/cli_config.py
@@ -951,6 +951,28 @@ ARG_OPTIONAL_SECTION = Arg(
help="The section name",
)
+# config lint
+ARG_LINT_CONFIG_SECTION = Arg(
+ ("--section",),
+ help="The section name(s) to lint in the airflow config.",
+ type=string_list_type,
+)
+ARG_LINT_CONFIG_OPTION = Arg(
+ ("--option",),
+ help="The option name(s) to lint in the airflow config.",
+ type=string_list_type,
+)
+ARG_LINT_CONFIG_IGNORE_SECTION = Arg(
+ ("--ignore-section",),
+ help="The section name(s) to ignore to lint in the airflow config.",
+ type=string_list_type,
+)
+ARG_LINT_CONFIG_IGNORE_OPTION = Arg(
+ ("--ignore-option",),
+ help="The option name(s) to ignore to lint in the airflow config.",
+ type=string_list_type,
+)
+
# kubernetes cleanup-pods
ARG_NAMESPACE = Arg(
("--namespace",),
@@ -1869,6 +1891,18 @@ CONFIG_COMMANDS = (
ARG_VERBOSE,
),
),
+ ActionCommand(
+ name="lint",
+ help="lint options for the configuration changes while migrating from
Airflow 2.x to Airflow 3.0",
+
func=lazy_load_command("airflow.cli.commands.remote_commands.config_command.lint_config"),
+ args=(
+ ARG_LINT_CONFIG_SECTION,
+ ARG_LINT_CONFIG_OPTION,
+ ARG_LINT_CONFIG_IGNORE_SECTION,
+ ARG_LINT_CONFIG_IGNORE_OPTION,
+ ARG_VERBOSE,
+ ),
+ ),
)
KUBERNETES_COMMANDS = (
diff --git a/airflow/cli/commands/config_command.py
b/airflow/cli/commands/config_command.py
index 82f1943d4cf..1e0bcd17c9b 100644
--- a/airflow/cli/commands/config_command.py
+++ b/airflow/cli/commands/config_command.py
@@ -18,11 +18,14 @@
from __future__ import annotations
+from dataclasses import dataclass
from io import StringIO
+from typing import NamedTuple
import pygments
from pygments.lexers.configs import IniLexer
+from airflow.cli.simple_table import AirflowConsole
from airflow.configuration import conf
from airflow.exceptions import AirflowConfigException
from airflow.utils.cli import should_use_colors
@@ -64,3 +67,379 @@ def get_value(args):
print(value)
except AirflowConfigException:
pass
+
+
+class ConfigParameter(NamedTuple):
+ """Represents a configuration parameter."""
+
+ section: str
+ option: str
+
+
+@dataclass
+class ConfigChange:
+ """
+ Class representing the configuration changes in Airflow 3.0.
+
+ :param config: The configuration parameter being changed.
+ :param suggestion: A suggestion for replacing or handling the removed
configuration.
+ :param renamed_to: The new section and option if the configuration is
renamed.
+ """
+
+ config: ConfigParameter
+ suggestion: str = ""
+ renamed_to: ConfigParameter | None = None
+
+ @property
+ def message(self) -> str:
+ """Generate a message for this configuration change."""
+ if self.renamed_to:
+ if self.config.section != self.renamed_to.section:
+ return (
+ f"`{self.config.option}` configuration parameter moved
from `{self.config.section}` section to `"
+ f"{self.renamed_to.section}` section as
`{self.renamed_to.option}`."
+ )
+ return (
+ f"`{self.config.option}` configuration parameter renamed to
`{self.renamed_to.option}` "
+ f"in the `{self.config.section}` section."
+ )
+ return (
+ f"Removed deprecated `{self.config.option}` configuration
parameter from `{self.config.section}` section. "
+ f"{self.suggestion}"
+ )
+
+
+CONFIGS_CHANGES = [
+ # admin
+ ConfigChange(
+ config=ConfigParameter("admin", "hide_sensitive_variable_fields"),
+ renamed_to=ConfigParameter("core", "hide_sensitive_var_conn_fields"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("admin", "sensitive_variable_fields"),
+ renamed_to=ConfigParameter("core", "sensitive_var_conn_names"),
+ ),
+ # core
+ ConfigChange(
+ config=ConfigParameter("core", "check_slas"),
+ suggestion="The SLA feature is removed in Airflow 3.0, to be replaced
with Airflow Alerts in future",
+ ),
+ ConfigChange(
+ config=ConfigParameter("core", "strict_dataset_uri_validation"),
+ suggestion="Dataset URI with a defined scheme will now always be
validated strictly, "
+ "raising a hard error on validation failure.",
+ ),
+ ConfigChange(
+ config=ConfigParameter("core", "dataset_manager_class"),
+ renamed_to=ConfigParameter("core", "asset_manager_class"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("core", "dataset_manager_kwargs"),
+ renamed_to=ConfigParameter("core", "asset_manager_kwargs"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("core", "worker_precheck"),
+ renamed_to=ConfigParameter("celery", "worker_precheck"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("core", "non_pooled_task_slot_count"),
+ renamed_to=ConfigParameter("core", "default_pool_task_slot_count"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("core", "dag_concurrency"),
+ renamed_to=ConfigParameter("core", "max_active_tasks_per_dag"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("core", "sql_alchemy_conn"),
+ renamed_to=ConfigParameter("database", "sql_alchemy_conn"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("core", "sql_engine_encoding"),
+ renamed_to=ConfigParameter("database", "sql_engine_encoding"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("core", "sql_engine_collation_for_ids"),
+ renamed_to=ConfigParameter("database", "sql_engine_collation_for_ids"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("core", "sql_alchemy_pool_enabled"),
+ renamed_to=ConfigParameter("database", "sql_alchemy_pool_enabled"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("core", "sql_alchemy_pool_size"),
+ renamed_to=ConfigParameter("database", "sql_alchemy_pool_size"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("core", "sql_alchemy_max_overflow"),
+ renamed_to=ConfigParameter("database", "sql_alchemy_max_overflow"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("core", "sql_alchemy_pool_recycle"),
+ renamed_to=ConfigParameter("database", "sql_alchemy_pool_recycle"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("core", "sql_alchemy_pool_pre_ping"),
+ renamed_to=ConfigParameter("database", "sql_alchemy_pool_pre_ping"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("core", "sql_alchemy_schema"),
+ renamed_to=ConfigParameter("database", "sql_alchemy_schema"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("core", "sql_alchemy_connect_args"),
+ renamed_to=ConfigParameter("database", "sql_alchemy_connect_args"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("core", "load_default_connections"),
+ renamed_to=ConfigParameter("database", "load_default_connections"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("core", "max_db_retries"),
+ renamed_to=ConfigParameter("database", "max_db_retries"),
+ ),
+ ConfigChange(config=ConfigParameter("core", "task_runner")),
+ ConfigChange(config=ConfigParameter("core", "enable_xcom_pickling")),
+ # api
+ ConfigChange(
+ config=ConfigParameter("api", "access_control_allow_origin"),
+ renamed_to=ConfigParameter("api", "access_control_allow_origins"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("api", "auth_backend"),
+ renamed_to=ConfigParameter("api", "auth_backends"),
+ ),
+ # logging
+ ConfigChange(
+ config=ConfigParameter("logging", "enable_task_context_logger"),
+ suggestion="Remove TaskContextLogger: Replaced by the Log table for
better handling of task log "
+ "messages outside the execution context.",
+ ),
+ # metrics
+ ConfigChange(
+ config=ConfigParameter("metrics", "metrics_use_pattern_match"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("metrics", "timer_unit_consistency"),
+ suggestion="In Airflow 3.0, the `timer_unit_consistency` setting in
the `metrics` section is "
+ "removed as it is now the default behaviour. This is done to
standardize all timer and "
+ "timing metrics to milliseconds across all metric loggers",
+ ),
+ ConfigChange(
+ config=ConfigParameter("metrics", "statsd_allow_list"),
+ renamed_to=ConfigParameter("metrics", "metrics_allow_list"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("metrics", "statsd_block_list"),
+ renamed_to=ConfigParameter("metrics", "metrics_block_list"),
+ ),
+ # traces
+ ConfigChange(
+ config=ConfigParameter("traces", "otel_task_log_event"),
+ ),
+ # operators
+ ConfigChange(
+ config=ConfigParameter("operators", "allow_illegal_arguments"),
+ ),
+ # webserver
+ ConfigChange(
+ config=ConfigParameter("webserver", "allow_raw_html_descriptions"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("webserver", "cookie_samesite"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("webserver", "update_fab_perms"),
+ renamed_to=ConfigParameter("fab", "update_fab_perms"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("webserver", "auth_rate_limited"),
+ renamed_to=ConfigParameter("fab", "auth_rate_limited"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("webserver", option="auth_rate_limit"),
+ renamed_to=ConfigParameter("fab", "auth_rate_limit"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("webserver", "session_lifetime_days"),
+ renamed_to=ConfigParameter("webserver", "session_lifetime_minutes"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("webserver", "force_log_out_after"),
+ renamed_to=ConfigParameter("webserver", "session_lifetime_minutes"),
+ ),
+ # policy
+ ConfigChange(
+ config=ConfigParameter("policy", "airflow_local_settings"),
+ renamed_to=ConfigParameter("policy", "task_policy"),
+ ),
+ # scheduler
+ ConfigChange(
+ config=ConfigParameter("scheduler", "dependency_detector"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("scheduler", "processor_poll_interval"),
+ renamed_to=ConfigParameter("scheduler", "scheduler_idle_sleep_time"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("scheduler", "deactivate_stale_dags_interval"),
+ renamed_to=ConfigParameter("scheduler", "parsing_cleanup_interval"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("scheduler", "statsd_on"),
renamed_to=ConfigParameter("metrics", "statsd_on")
+ ),
+ ConfigChange(
+ config=ConfigParameter("scheduler", "max_threads"),
+ renamed_to=ConfigParameter("scheduler", "parsing_processes"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("scheduler", "statsd_host"),
+ renamed_to=ConfigParameter("metrics", "statsd_host"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("scheduler", "statsd_port"),
+ renamed_to=ConfigParameter("metrics", "statsd_port"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("scheduler", "statsd_prefix"),
+ renamed_to=ConfigParameter("metrics", "statsd_prefix"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("scheduler", "statsd_allow_list"),
+ renamed_to=ConfigParameter("metrics", "statsd_allow_list"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("scheduler", "stat_name_handler"),
+ renamed_to=ConfigParameter("metrics", "stat_name_handler"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("scheduler", "statsd_datadog_enabled"),
+ renamed_to=ConfigParameter("metrics", "statsd_datadog_enabled"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("scheduler", "statsd_datadog_tags"),
+ renamed_to=ConfigParameter("metrics", "statsd_datadog_tags"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("scheduler", "statsd_datadog_metrics_tags"),
+ renamed_to=ConfigParameter("metrics", "statsd_datadog_metrics_tags"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("scheduler", "statsd_custom_client_path"),
+ renamed_to=ConfigParameter("metrics", "statsd_custom_client_path"),
+ ),
+ # celery
+ ConfigChange(
+ config=ConfigParameter("celery", "stalled_task_timeout"),
+ renamed_to=ConfigParameter("scheduler", "task_queued_timeout"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("celery", "default_queue"),
+ renamed_to=ConfigParameter("operators", "default_queue"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("celery", "task_adoption_timeout"),
+ renamed_to=ConfigParameter("scheduler", "task_queued_timeout"),
+ ),
+ # kubernetes_executor
+ ConfigChange(
+ config=ConfigParameter("kubernetes_executor",
"worker_pods_pending_timeout"),
+ renamed_to=ConfigParameter("scheduler", "task_queued_timeout"),
+ ),
+ ConfigChange(
+ config=ConfigParameter("kubernetes_executor",
"worker_pods_pending_timeout_check_interval"),
+ renamed_to=ConfigParameter("scheduler",
"task_queued_timeout_check_interval"),
+ ),
+ # smtp
+ ConfigChange(
+ config=ConfigParameter("smtp", "smtp_user"),
+ suggestion="Please use the SMTP connection (`smtp_default`).",
+ ),
+ ConfigChange(
+ config=ConfigParameter("smtp", "smtp_password"),
+ suggestion="Please use the SMTP connection (`smtp_default`).",
+ ),
+]
+
+
+@providers_configuration_loaded
+def lint_config(args) -> None:
+ """
+ Lint the airflow.cfg file for removed, or renamed configurations.
+
+ 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.
+ Example: --section core
+
+ --option: str (optional)
+ The specific option within a section to lint.
+ Example: --option check_slas
+
+ --ignore-section: str (optional)
+ A section to ignore during linting.
+ Example: --ignore-section webserver
+
+ --ignore-option: str (optional)
+ An option to ignore during linting.
+ Example: --ignore-option smtp_user
+
+ --verbose: flag (optional)
+ Enables detailed output, including the list of ignored sections
and options.
+ Example: --verbose
+
+ Examples:
+ 1. Lint all sections and options:
+ airflow config lint
+
+ 2. Lint a specific sections:
+ airflow config lint --section core,webserver
+
+ 3. Lint a specific sections and options:
+ airflow config lint --section smtp --option smtp_user
+
+ 4. Ignore a sections:
+ irflow config lint --ignore-section webserver,api
+
+ 5. Ignore an options:
+ airflow config lint --ignore-option smtp_user,session_lifetime_days
+
+ 6. Enable verbose output:
+ airflow config lint --verbose
+
+ :param args: The CLI arguments for linting configurations.
+ """
+ console = AirflowConsole()
+ lint_issues = []
+
+ section_to_check_if_provided = args.section or []
+ option_to_check_if_provided = args.option or []
+
+ ignore_sections = args.ignore_section or []
+ ignore_options = args.ignore_option or []
+
+ for configuration in CONFIGS_CHANGES:
+ if section_to_check_if_provided and configuration.config.section not
in section_to_check_if_provided:
+ continue
+
+ if option_to_check_if_provided and configuration.config.option not in
option_to_check_if_provided:
+ continue
+
+ if configuration.config.section in ignore_sections or
configuration.config.option in ignore_options:
+ continue
+
+ if conf.has_option(configuration.config.section,
configuration.config.option):
+ lint_issues.append(configuration.message)
+
+ if lint_issues:
+ console.print("[red]Found issues in your airflow.cfg:[/red]")
+ for issue in lint_issues:
+ console.print(f" - [yellow]{issue}[/yellow]")
+ if args.verbose:
+ console.print("\n[blue]Detailed Information:[/blue]")
+ console.print(f"Ignored sections: [green]{',
'.join(ignore_sections)}[/green]")
+ console.print(f"Ignored options: [green]{',
'.join(ignore_options)}[/green]")
+ console.print("\n[red]Please update your configuration file
accordingly.[/red]")
+ else:
+ console.print("[green]No issues found in your airflow.cfg. It is ready
for Airflow 3![/green]")
diff --git a/tests/cli/commands/test_config_command.py
b/tests/cli/commands/test_config_command.py
index 030303c28ec..5e87b90e31f 100644
--- a/tests/cli/commands/test_config_command.py
+++ b/tests/cli/commands/test_config_command.py
@@ -17,11 +17,16 @@
from __future__ import annotations
import contextlib
+import os
+import re
from io import StringIO
from unittest import mock
+import pytest
+
from airflow.cli import cli_parser
from airflow.cli.commands import config_command
+from airflow.cli.commands.config_command import ConfigChange, ConfigParameter
from tests.test_utils.config import conf_vars
STATSD_CONFIG_BEGIN_WITH = "# `StatsD <https://github.com/statsd/statsd>`"
@@ -225,3 +230,211 @@ class TestCliConfigGetValue:
self.parser.parse_args(["config", "get-value", "missing-section",
"dags_folder"])
)
assert "section/key [missing-section/dags_folder] not found in config"
in caplog.text
+
+
+class TestConfigLint:
+ @pytest.mark.parametrize("removed_config", config_command.CONFIGS_CHANGES)
+ def test_lint_detects_removed_configs(self, removed_config):
+ with mock.patch("airflow.configuration.conf.has_option",
return_value=True):
+ with contextlib.redirect_stdout(StringIO()) as temp_stdout:
+
config_command.lint_config(cli_parser.get_parser().parse_args(["config",
"lint"]))
+
+ output = temp_stdout.getvalue()
+
+ normalized_output = re.sub(r"\s+", " ", output.strip())
+ normalized_message = re.sub(r"\s+", " ",
removed_config.message.strip())
+
+ assert normalized_message in normalized_output
+
+ @pytest.mark.parametrize(
+ "section, option, suggestion",
+ [
+ (
+ "core",
+ "check_slas",
+ "The SLA feature is removed in Airflow 3.0, to be replaced
with Airflow Alerts in future",
+ ),
+ (
+ "core",
+ "strict_dataset_uri_validation",
+ "Dataset URI with a defined scheme will now always be
validated strictly, raising a hard error on validation failure.",
+ ),
+ (
+ "logging",
+ "enable_task_context_logger",
+ "Remove TaskContextLogger: Replaced by the Log table for
better handling of task log messages outside the execution context.",
+ ),
+ ],
+ )
+ def test_lint_with_specific_removed_configs(self, section, option,
suggestion):
+ with mock.patch("airflow.configuration.conf.has_option",
return_value=True):
+ with contextlib.redirect_stdout(StringIO()) as temp_stdout:
+
config_command.lint_config(cli_parser.get_parser().parse_args(["config",
"lint"]))
+
+ output = temp_stdout.getvalue()
+
+ normalized_output = re.sub(r"\s+", " ", output.strip())
+
+ expected_message = f"Removed deprecated `{option}` configuration
parameter from `{section}` section."
+ assert expected_message in normalized_output
+
+ assert suggestion in normalized_output
+
+ def test_lint_specific_section_option(self):
+ with mock.patch("airflow.configuration.conf.has_option",
return_value=True):
+ with contextlib.redirect_stdout(StringIO()) as temp_stdout:
+ config_command.lint_config(
+ cli_parser.get_parser().parse_args(
+ ["config", "lint", "--section", "core", "--option",
"check_slas"]
+ )
+ )
+
+ output = temp_stdout.getvalue()
+
+ normalized_output = re.sub(r"\s+", " ", output.strip())
+
+ assert (
+ "Removed deprecated `check_slas` configuration parameter from
`core` section."
+ in normalized_output
+ )
+
+ def test_lint_with_invalid_section_option(self):
+ with mock.patch("airflow.configuration.conf.has_option",
return_value=False):
+ with contextlib.redirect_stdout(StringIO()) as temp_stdout:
+ config_command.lint_config(
+ cli_parser.get_parser().parse_args(
+ ["config", "lint", "--section", "invalid_section",
"--option", "invalid_option"]
+ )
+ )
+
+ output = temp_stdout.getvalue()
+
+ normalized_output = re.sub(r"\s+", " ", output.strip())
+
+ assert "No issues found in your airflow.cfg." in normalized_output
+
+ def test_lint_detects_multiple_issues(self):
+ with mock.patch(
+ "airflow.configuration.conf.has_option",
+ side_effect=lambda s, o: o in ["check_slas",
"strict_dataset_uri_validation"],
+ ):
+ with contextlib.redirect_stdout(StringIO()) as temp_stdout:
+
config_command.lint_config(cli_parser.get_parser().parse_args(["config",
"lint"]))
+
+ output = temp_stdout.getvalue()
+
+ normalized_output = re.sub(r"\s+", " ", output.strip())
+
+ assert (
+ "Removed deprecated `check_slas` configuration parameter from
`core` section."
+ in normalized_output
+ )
+ assert (
+ "Removed deprecated `strict_dataset_uri_validation` configuration
parameter from `core` section."
+ in normalized_output
+ )
+
+ @pytest.mark.parametrize(
+ "removed_configs",
+ [
+ [
+ (
+ "core",
+ "check_slas",
+ "The SLA feature is removed in Airflow 3.0, to be replaced
with Airflow Alerts in future",
+ ),
+ (
+ "core",
+ "strict_dataset_uri_validation",
+ "Dataset URI with a defined scheme will now always be
validated strictly, raising a hard error on validation failure.",
+ ),
+ (
+ "logging",
+ "enable_task_context_logger",
+ "Remove TaskContextLogger: Replaced by the Log table for
better handling of task log messages outside the execution context.",
+ ),
+ ],
+ ],
+ )
+ def test_lint_detects_multiple_removed_configs(self, removed_configs):
+ with mock.patch("airflow.configuration.conf.has_option",
return_value=True):
+ with contextlib.redirect_stdout(StringIO()) as temp_stdout:
+
config_command.lint_config(cli_parser.get_parser().parse_args(["config",
"lint"]))
+
+ output = temp_stdout.getvalue()
+
+ normalized_output = re.sub(r"\s+", " ", output.strip())
+
+ for section, option, suggestion in removed_configs:
+ expected_message = (
+ f"Removed deprecated `{option}` configuration parameter from
`{section}` section."
+ )
+ assert expected_message in normalized_output
+
+ if suggestion:
+ assert suggestion in normalized_output
+
+ @pytest.mark.parametrize(
+ "renamed_configs",
+ [
+ # Case 1: Renamed configurations within the same section
+ [
+ ("core", "non_pooled_task_slot_count", "core",
"default_pool_task_slot_count"),
+ ("scheduler", "processor_poll_interval", "scheduler",
"scheduler_idle_sleep_time"),
+ ],
+ # Case 2: Renamed configurations across sections
+ [
+ ("admin", "hide_sensitive_variable_fields", "core",
"hide_sensitive_var_conn_fields"),
+ ("core", "worker_precheck", "celery", "worker_precheck"),
+ ],
+ ],
+ )
+ def test_lint_detects_renamed_configs(self, renamed_configs):
+ with mock.patch("airflow.configuration.conf.has_option",
return_value=True):
+ with contextlib.redirect_stdout(StringIO()) as temp_stdout:
+
config_command.lint_config(cli_parser.get_parser().parse_args(["config",
"lint"]))
+
+ output = temp_stdout.getvalue()
+
+ normalized_output = re.sub(r"\s+", " ", output.strip())
+
+ for old_section, old_option, new_section, new_option in
renamed_configs:
+ if old_section == new_section:
+ expected_message = f"`{old_option}` configuration parameter
renamed to `{new_option}` in the `{old_section}` section."
+ else:
+ expected_message = f"`{old_option}` configuration parameter
moved from `{old_section}` section to `{new_section}` section as
`{new_option}`."
+ assert expected_message in normalized_output
+
+ @pytest.mark.parametrize(
+ "env_var, config_change, expected_message",
+ [
+ (
+ "AIRFLOW__CORE__CHECK_SLAS",
+ ConfigChange(
+ config=ConfigParameter("core", "check_slas"),
+ suggestion="The SLA feature is removed in Airflow 3.0, to
be replaced with Airflow Alerts in future",
+ ),
+ "Removed deprecated `check_slas` configuration parameter from
`core` section.",
+ ),
+ (
+ "AIRFLOW__CORE__STRICT_ASSET_URI_VALIDATION",
+ ConfigChange(
+ config=ConfigParameter("core",
"strict_dataset_uri_validation"),
+ suggestion="Dataset URI with a defined scheme will now
always be validated strictly, raising a hard error on validation failure.",
+ ),
+ "Removed deprecated `strict_dataset_uri_validation`
configuration parameter from `core` section.",
+ ),
+ ],
+ )
+ def test_lint_detects_configs_with_env_vars(self, env_var, config_change,
expected_message):
+ with mock.patch.dict(os.environ, {env_var: "some_value"}):
+ with mock.patch("airflow.configuration.conf.has_option",
return_value=True):
+ with contextlib.redirect_stdout(StringIO()) as temp_stdout:
+
config_command.lint_config(cli_parser.get_parser().parse_args(["config",
"lint"]))
+
+ output = temp_stdout.getvalue()
+
+ normalized_output = re.sub(r"\s+", " ", output.strip())
+
+ assert expected_message in normalized_output
+ assert config_change.suggestion in normalized_output