Skip to content

Commit

Permalink
Merge pull request #2284 from craddm/fix-sre-names
Browse files Browse the repository at this point in the history
Change method of sanitising SRE names
  • Loading branch information
craddm authored Nov 12, 2024
2 parents 3263453 + 35455af commit 13520c4
Show file tree
Hide file tree
Showing 12 changed files with 31 additions and 40 deletions.
5 changes: 4 additions & 1 deletion data_safe_haven/commands/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,10 @@ def available() -> None:

@config_command_group.command()
def show(
name: Annotated[str, typer.Argument(help="Name of SRE to show")],
name: Annotated[
str,
typer.Argument(help="Name of SRE to show"),
],
file: Annotated[
Optional[Path], # noqa: UP007
typer.Option(help="File path to write configuration template to."),
Expand Down
9 changes: 4 additions & 5 deletions data_safe_haven/config/sre_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@

from typing import ClassVar, Self

from data_safe_haven.functions import json_safe
from data_safe_haven.serialisers import AzureSerialisableModel, ContextBase
from data_safe_haven.types import SafeString, SoftwarePackageCategory
from data_safe_haven.types import SafeSreName, SoftwarePackageCategory

from .config_sections import (
ConfigSectionAzure,
Expand All @@ -18,8 +17,8 @@


def sre_config_name(sre_name: str) -> str:
"""Construct a safe YAML filename given an input SRE name."""
return f"sre-{json_safe(sre_name)}.yaml"
"""Construct a YAML filename given an input SRE name."""
return f"sre-{sre_name}.yaml"


class SREConfig(AzureSerialisableModel):
Expand All @@ -31,7 +30,7 @@ class SREConfig(AzureSerialisableModel):
azure: ConfigSectionAzure
description: str
dockerhub: ConfigSectionDockerHub
name: SafeString
name: SafeSreName
sre: ConfigSectionSRE

@property
Expand Down
2 changes: 0 additions & 2 deletions data_safe_haven/functions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
alphanumeric,
b64encode,
get_key_vault_name,
json_safe,
next_occurrence,
password,
replace_separators,
Expand All @@ -18,7 +17,6 @@
"current_ip_address",
"get_key_vault_name",
"ip_address_in_list",
"json_safe",
"next_occurrence",
"password",
"replace_separators",
Expand Down
5 changes: 0 additions & 5 deletions data_safe_haven/functions/strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,6 @@ def get_key_vault_name(stack_name: str) -> str:
return f"{''.join(truncate_tokens(stack_name.split('-'), 17))}secrets"


def json_safe(input_string: str) -> str:
"""Construct a JSON-safe version of an input string"""
return alphanumeric(input_string).lower()


def next_occurrence(
hour: int, minute: int, timezone: str, *, time_format: str = "iso"
) -> str:
Expand Down
2 changes: 2 additions & 0 deletions data_safe_haven/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
Fqdn,
Guid,
IpAddress,
SafeSreName,
SafeString,
TimeZone,
UniqueList,
Expand Down Expand Up @@ -52,6 +53,7 @@
"PathType",
"PermittedDomains",
"Ports",
"SafeSreName",
"SafeString",
"SoftwarePackageCategory",
"TimeZone",
Expand Down
1 change: 1 addition & 0 deletions data_safe_haven/types/annotated_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
Fqdn = Annotated[str, AfterValidator(validators.fqdn)]
Guid = Annotated[str, AfterValidator(validators.aad_guid)]
IpAddress = Annotated[str, AfterValidator(validators.ip_address)]
SafeSreName = Annotated[str, AfterValidator(validators.safe_sre_name)]
SafeString = Annotated[str, AfterValidator(validators.safe_string)]
TimeZone = Annotated[str, AfterValidator(validators.timezone)]
TH = TypeVar("TH", bound=Hashable)
Expand Down
4 changes: 4 additions & 0 deletions data_safe_haven/validators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
typer_entra_group_name,
typer_fqdn,
typer_ip_address,
typer_safe_sre_name,
typer_safe_string,
typer_timezone,
)
Expand All @@ -18,6 +19,7 @@
entra_group_name,
fqdn,
ip_address,
safe_sre_name,
safe_string,
timezone,
unique_list,
Expand All @@ -32,6 +34,7 @@
"entra_group_name",
"fqdn",
"ip_address",
"safe_sre_name",
"safe_string",
"timezone",
"typer_aad_guid",
Expand All @@ -41,6 +44,7 @@
"typer_entra_group_name",
"typer_fqdn",
"typer_ip_address",
"typer_safe_sre_name",
"typer_safe_string",
"typer_timezone",
"unique_list",
Expand Down
1 change: 1 addition & 0 deletions data_safe_haven/validators/typer.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ def typer_validator(x: Any) -> Any:
typer_entra_group_name = typer_validator_factory(validators.entra_group_name)
typer_fqdn = typer_validator_factory(validators.fqdn)
typer_ip_address = typer_validator_factory(validators.ip_address)
typer_safe_sre_name = typer_validator_factory(validators.safe_sre_name)
typer_safe_string = typer_validator_factory(validators.safe_string)
typer_timezone = typer_validator_factory(validators.timezone)
9 changes: 8 additions & 1 deletion data_safe_haven/validators/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,19 @@ def ip_address(ip_address: str) -> str:


def safe_string(safe_string: str) -> str:
if not re.match(r"^[a-zA-Z0-9_-]*$", safe_string) or not safe_string:
if not re.match(r"^[a-zA-Z0-9_-]+$", safe_string) or not safe_string:
msg = "Expected valid string containing only letters, numbers, hyphens and underscores."
raise ValueError(msg)
return safe_string


def safe_sre_name(safe_sre_name: str) -> str:
if not re.match(r"^[a-z0-9_-]+$", safe_sre_name) or not safe_sre_name:
msg = "Expected valid string containing only lowercase letters, numbers, hyphens and underscores."
raise ValueError(msg)
return safe_sre_name


def timezone(timezone: str) -> str:
if timezone not in pytz.all_timezones:
msg = "Expected valid timezone, for example 'Europe/London'."
Expand Down
10 changes: 5 additions & 5 deletions tests/commands/test_config_sre.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ class TestUploadSRE:
def test_upload_new(
self, mocker, context, runner, sre_config_yaml, sre_config_file
):
sre_name = "SandBox"
sre_name = "sandbox"
sre_filename = sre_config_name(sre_name)
mock_exists = mocker.patch.object(
SREConfig, "remote_exists", return_value=False
Expand All @@ -191,7 +191,7 @@ def test_upload_new(
def test_upload_no_changes(
self, mocker, context, runner, sre_config, sre_config_file
):
sre_name = "SandBox"
sre_name = "sandbox"
sre_filename = sre_config_name(sre_name)
mock_exists = mocker.patch.object(SREConfig, "remote_exists", return_value=True)
mock_from_remote = mocker.patch.object(
Expand Down Expand Up @@ -249,7 +249,7 @@ def test_upload_changes(
def test_upload_changes_n(
self, mocker, context, runner, sre_config_alternate, sre_config_file
):
sre_name = "SandBox"
sre_name = "sandbox"
sre_filename = sre_config_name(sre_name)
mock_exists = mocker.patch.object(SREConfig, "remote_exists", return_value=True)
mock_from_remote = mocker.patch.object(
Expand Down Expand Up @@ -287,7 +287,7 @@ def test_upload_file_does_not_exist(self, mocker, runner):
def test_upload_invalid_config(
self, mocker, runner, context, sre_config_file, sre_config_yaml
):
sre_name = "SandBox"
sre_name = "sandbox"
sre_filename = sre_config_name(sre_name)

mock_exists = mocker.patch.object(SREConfig, "remote_exists", return_value=True)
Expand All @@ -310,7 +310,7 @@ def test_upload_invalid_config(
def test_upload_invalid_config_force(
self, mocker, runner, context, sre_config_file, sre_config_yaml
):
sre_name = "SandBox"
sre_name = "sandbox"
sre_filename = sre_config_name(sre_name)

mocker.patch.object(
Expand Down
14 changes: 2 additions & 12 deletions tests/config/test_sre_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
ConfigSectionDockerHub,
ConfigSectionSRE,
)
from data_safe_haven.config.sre_config import sre_config_name
from data_safe_haven.exceptions import (
DataSafeHavenTypeError,
)
Expand Down Expand Up @@ -126,14 +125,5 @@ def test_upload(self, mocker, context, sre_config) -> None:
context.storage_container_name,
)


@pytest.mark.parametrize(
"value,expected",
[
(r"Test SRE", "sre-testsre.yaml"),
(r"*a^b$c", "sre-abc.yaml"),
(r";'@-", "sre-.yaml"),
],
)
def test_sre_config_name(value, expected):
assert sre_config_name(value) == expected
def test_sre_config_yaml_name(self, sre_config: SREConfig) -> None:
assert sre_config.filename == "sre-sandbox.yaml"
9 changes: 0 additions & 9 deletions tests/functions/test_strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from data_safe_haven.exceptions import DataSafeHavenValueError
from data_safe_haven.functions import (
get_key_vault_name,
json_safe,
next_occurrence,
)

Expand Down Expand Up @@ -70,11 +69,3 @@ def test_invalid_timeformat(self):
)
def test_get_key_vault_name(value, expected):
assert get_key_vault_name(value) == expected


@pytest.mark.parametrize(
"value,expected",
[(r"Test SRE", "testsre"), (r"%*aBc", "abc"), (r"MY_SRE", "mysre")],
)
def test_json_safe(value, expected):
assert json_safe(value) == expected

0 comments on commit 13520c4

Please sign in to comment.