Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions airflow-ctl/src/airflowctl/ctl/cli_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,27 +357,27 @@ def _load_help_texts_yaml() -> dict[str, dict[str, str]]:
# 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=(
Expand Down
16 changes: 8 additions & 8 deletions airflow-ctl/src/airflowctl/ctl/commands/config_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
101 changes: 101 additions & 0 deletions airflow-ctl/tests/airflow_ctl/ctl/commands/test_config_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,50 @@
from airflowctl.ctl.commands import config_command
from airflowctl.ctl.commands.config_command import ConfigChange, ConfigParameter

# Section and option names where one name is a prefix of another, mirroring real pairs in
# CONFIGS_CHANGES such as auth_backend/auth_backends and sql_alchemy_conn/sql_alchemy_connect_args.
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 get_printed_lines(mock_rich_print) -> list[str]:
"""Return the strings lint passed to rich.print."""
return [call.args[0] for call in mock_rich_print.call_args_list]


def find_reported_options(mock_rich_print) -> list[str]:
"""Return the CONFUSABLE_CONFIGS_CHANGES option names that lint reported, sorted."""
printed = get_printed_lines(mock_rich_print)
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(
Expand Down Expand Up @@ -429,6 +469,67 @@ def test_lint_detects_configs_with_suggestions(self, mock_rich_print, api_client
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")
@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")
@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,
)

printed = get_printed_lines(mock_rich_print)
assert "Ignored sections: [green]api[/green]" in printed
assert "Ignored options: [green]auth_backend[/green]" in printed

@patch("airflowctl.api.client.Credentials.load")
@patch.dict(os.environ, {"AIRFLOW_CLI_TOKEN": "TEST_TOKEN"})
@patch.dict(os.environ, {"AIRFLOW_CLI_ENVIRONMENT": "TEST_CONFIG"})
Expand Down