Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Catch config upload validation errors #2211

Merged
merged 13 commits into from
Oct 8, 2024
Merged
6 changes: 5 additions & 1 deletion data_safe_haven/commands/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,11 @@ def upload(
else:
logger.critical(f"Configuration file '{file}' not found.")
raise typer.Exit(1)
config = SREConfig.from_yaml(config_yaml)
try:
config = SREConfig.from_yaml(config_yaml)
except DataSafeHavenTypeError as exc:
logger.error("Check for missing or incorrect fields in the configuration.")
raise typer.Exit(1) from exc

# Present diff to user
if (not force) and SREConfig.remote_exists(context, filename=config.filename):
Expand Down
2 changes: 1 addition & 1 deletion data_safe_haven/serialisers/yaml_serialisable_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def from_yaml(cls: type[T], settings_yaml: str) -> T:
)
for error in exc.errors():
logger.error(
f"[red]{'.'.join(map(str, error.get('loc', [])))}: {error.get('input', '')}[/] - {error.get('msg', '')}"
f"{error.get('msg', '')}: [red]{'.'.join(map(str, error.get('loc', [])))}.[/] Original input: [red]{error.get('input', '')}[/]"
)
msg = f"{cls.config_type} configuration is invalid."
raise DataSafeHavenTypeError(msg) from exc
Expand Down
13 changes: 13 additions & 0 deletions tests/commands/test_config_sre.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,3 +331,16 @@ def test_upload_invalid_config_force(
context.storage_account_name,
context.storage_container_name,
)

def test_upload_missing_field(
self, runner, tmp_path, sre_config_yaml_missing_field
):
config_file_path = tmp_path / "config.yaml"
with open(config_file_path, "w") as f:
f.write(sre_config_yaml_missing_field)

result = runner.invoke(config_command_group, ["upload", str(config_file_path)])

assert result.exit_code == 1
assert "validation errors" in result.stdout
assert "Check for missing" in result.stdout
6 changes: 6 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,12 @@ def sre_config_yaml(request):
return yaml.dump(yaml.safe_load(content))


@fixture
def sre_config_yaml_missing_field(sre_config_yaml):
content = sre_config_yaml.replace("admin_email_address: [email protected]", "")
return yaml.dump(yaml.safe_load(content))


@fixture
def sre_project_manager(
context_no_secrets,
Expand Down
13 changes: 11 additions & 2 deletions tests/serialisers/test_yaml_serialisable_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,24 @@ def test_from_yaml_not_dict(self):
):
ExampleYAMLSerialisableModel.from_yaml(yaml)

def test_from_yaml_validation_error(self):
def test_from_yaml_validation_errors(self, caplog):
yaml = "\n".join(
["string: 'abc'", "integer: 'not an integer'", "list_of_integers: [-1,0,1]"]
[
"string: 'abc'",
"integer: 'not an integer'",
"list_of_integers: [-1,0,z,1]",
]
)
with raises(
DataSafeHavenTypeError,
match="Example configuration is invalid.",
):
ExampleYAMLSerialisableModel.from_yaml(yaml)
assert "Input should be a valid integer" in caplog.text
assert "Original input: not an integer" in caplog.text
assert "unable to parse string as an integer" in caplog.text
assert "list_of_integers.2" in caplog.text
assert "Original input: z" in caplog.text

def test_to_filepath(self, tmp_path, example_config_class):
filepath = tmp_path / "test.yaml"
Expand Down
Loading