This is an automated email from the ASF dual-hosted git repository.
shahar1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new 122f7b43fd7 Report invalid variables in airflowctl variables import as
readable errors (#76174)
122f7b43fd7 is described below
commit 122f7b43fd73dea356212462ae7738c75342e22f
Author: Rishabh <[email protected]>
AuthorDate: Sun Nov 1 13:13:35 2026 +0530
Report invalid variables in airflowctl variables import as readable errors
(#76174)
A variable with a bad value, such as a non-string description or a blank
or over-long key, crashed `airflowctl variables import` with a raw
pydantic traceback that did not say which variable was wrong. Report the
variable key, the file and the reason instead, as
`airflowctl connections import` already does.
---
.../airflowctl/ctl/commands/variable_command.py | 17 +++++----
.../ctl/commands/test_variable_command.py | 40 ++++++++++++++++++++++
2 files changed, 51 insertions(+), 6 deletions(-)
diff --git a/airflow-ctl/src/airflowctl/ctl/commands/variable_command.py
b/airflow-ctl/src/airflowctl/ctl/commands/variable_command.py
index 19321002e44..750b94eb77f 100644
--- a/airflow-ctl/src/airflowctl/ctl/commands/variable_command.py
+++ b/airflow-ctl/src/airflowctl/ctl/commands/variable_command.py
@@ -21,7 +21,9 @@ import os
import sys
import rich
+from pydantic import ValidationError
from rich.console import Console
+from rich.markup import escape
from airflowctl.api.client import NEW_API_CLIENT, ClientKind,
provide_api_client
from airflowctl.api.datamodels.generated import (
@@ -30,6 +32,7 @@ from airflowctl.api.datamodels.generated import (
BulkCreateActionVariableBody,
VariableBody,
)
+from airflowctl.ctl.utils.validation import split_validation_errors
def _print_file_error(message: str, file_path: str) -> None:
@@ -63,13 +66,15 @@ def import_(args, api_client=NEW_API_CLIENT) -> list[str]:
if isinstance(v, dict) and "value" in v:
value, description = v["value"], v.get("description")
- vars_to_update.append(
- VariableBody(
- key=k,
- value=value,
- description=description,
+ try:
+ vars_to_update.append(VariableBody(key=k, value=value,
description=description))
+ except ValidationError as e:
+ _, invalid = split_validation_errors(VariableBody, e, str)
+ Console().print(
+ f"[red]Invalid variable {escape(repr(k))} in
{escape(args.file)}: {escape('; '.join(invalid))}",
+ soft_wrap=True,
)
- )
+ sys.exit(1)
bulk_body = BulkBodyVariableBody(
actions=[
diff --git
a/airflow-ctl/tests/airflow_ctl/ctl/commands/test_variable_command.py
b/airflow-ctl/tests/airflow_ctl/ctl/commands/test_variable_command.py
index 3e7b2714f94..8eac046cdb4 100644
--- a/airflow-ctl/tests/airflow_ctl/ctl/commands/test_variable_command.py
+++ b/airflow-ctl/tests/airflow_ctl/ctl/commands/test_variable_command.py
@@ -133,6 +133,46 @@ class TestCliVariableCommands:
assert "Invalid variable file:" in output
assert expected_json_path.as_posix() in output
+ @pytest.mark.parametrize(
+ ("variable_file", "expected_key", "expected_reason"),
+ [
+ (
+ {"my_key": {"value": "v", "description": 5}},
+ "'my_key'",
+ "description: Input should be a valid string",
+ ),
+ ({" ": "v"}, "' '", "key: String should match pattern '\\S'"),
+ ({"k" * 251: "v"}, f"'{'k' * 251}'", "key: String should have at
most 250 characters"),
+ ],
+ ids=["non-string-description", "blank-key", "too-long-key"],
+ )
+ def test_import_reports_invalid_variable(
+ self, tmp_path, monkeypatch, capsys, variable_file, expected_key,
expected_reason
+ ):
+ monkeypatch.chdir(tmp_path)
+ expected_json_path = tmp_path / self.export_file_name
+ expected_json_path.write_text(json.dumps(variable_file))
+ bulk_called = False
+
+ def bulk(variables):
+ nonlocal bulk_called
+ bulk_called = True
+
+ api_client = SimpleNamespace(variables=SimpleNamespace(bulk=bulk))
+
+ with pytest.raises(SystemExit) as exit_info:
+ variable_command.import_(
+ self.parser.parse_args(["variables", "import",
expected_json_path.as_posix()]),
+ api_client=api_client,
+ )
+
+ assert exit_info.value.code == 1
+ output = " ".join(capsys.readouterr().out.split())
+ assert (
+ f"Invalid variable {expected_key} in
{expected_json_path.as_posix()}: {expected_reason}" in output
+ )
+ assert not bulk_called
+
def test_import_error(self, api_client_maker, tmp_path, monkeypatch):
api_client = api_client_maker(
path="/api/v2/variables",