diff --git a/dreadnode_cli/agent/cli.py b/dreadnode_cli/agent/cli.py index 0a50e2b..36088c8 100644 --- a/dreadnode_cli/agent/cli.py +++ b/dreadnode_cli/agent/cli.py @@ -255,7 +255,15 @@ def push( print() print(f":wrench: Building agent from [b]{directory}[/] ...") image = docker.build(directory, force_rebuild=rebuild) - repository = f"{registry}/{server_config.username}/agents/{agent_config.project_name}" + agent_name = docker.sanitized_agent_name(agent_config.project_name) + + if not agent_name: + raise Exception("Failed to sanitize agent name, please use a different name") + + elif agent_name != agent_config.project_name: + print(f":four_leaf_clover: Agent name normalized to [bold magenta]{agent_name}[/]") + + repository = f"{registry}/{server_config.username}/agents/{agent_name}" tag = tag or image.id[-8:] print() diff --git a/dreadnode_cli/agent/docker.py b/dreadnode_cli/agent/docker.py index 6fa0e4a..35e82c6 100644 --- a/dreadnode_cli/agent/docker.py +++ b/dreadnode_cli/agent/docker.py @@ -1,4 +1,5 @@ import pathlib +import re import typing as t import docker # type: ignore @@ -63,6 +64,23 @@ def login(registry: str, username: str, password: str) -> None: client.api.login(username=username, password=password, registry=registry) +def sanitized_agent_name(name: str) -> str: + """ + Sanitizes an agent name to be used as a Docker repository name. + """ + + # convert to lowercase + name = name.lower() + # replace non-alphanumeric characters with hyphens + name = re.sub(r"[^\w\s-]", "", name) + # replace one or more whitespace characters with a single hyphen + name = re.sub(r"[-\s]+", "-", name) + # remove leading or trailing hyphens + name = name.strip("-") + + return name + + def build(directory: str | pathlib.Path, *, force_rebuild: bool = False) -> Image: if client is None: raise Exception("Docker not available") diff --git a/dreadnode_cli/agent/tests/test_docker.py b/dreadnode_cli/agent/tests/test_docker.py index 37f78c2..979ce5c 100644 --- a/dreadnode_cli/agent/tests/test_docker.py +++ b/dreadnode_cli/agent/tests/test_docker.py @@ -174,3 +174,28 @@ def test_get_registry_custom_platform_base_domain() -> None: config = _create_test_server_config("dev-crucible.example.com") assert docker.get_registry(config) == "dev-registry.example.com" + + +def test_sanitized_agent_name() -> None: + # Test basic name + assert docker.sanitized_agent_name("test-agent") == "test-agent" + assert docker.sanitized_agent_name("test- agent") == "test-agent" + assert docker.sanitized_agent_name("test_agent") == "test_agent" + + # Test spaces + assert docker.sanitized_agent_name("test agent") == "test-agent" + assert docker.sanitized_agent_name("test multiple spaces") == "test-multiple-spaces" + + # Test special characters + assert docker.sanitized_agent_name("test!@#$%^&*()agent") == "testagent" + assert docker.sanitized_agent_name("test_agent.123") == "test_agent123" + assert docker.sanitized_agent_name("test/agent\\path") == "testagentpath" + + # Test mixed case + assert docker.sanitized_agent_name("TestAgent") == "testagent" + assert docker.sanitized_agent_name("TEST AGENT") == "test-agent" + + # Test edge cases + assert docker.sanitized_agent_name(" spaced name ") == "spaced-name" + assert docker.sanitized_agent_name("!!!###") == "" + assert docker.sanitized_agent_name("123 456") == "123-456"