BWDO-840 split out DockerConfigManager - #70
Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors Docker registry configuration handling by extracting it into a dedicated DockerConfigManager, and updates the main Docker CLI implementation to consume that manager. It also adds targeted unit tests for config/whitelist/netrc behavior and introduces a small resilience tweak to project list downloading.
Changes:
- Introduces
DockerConfigManager/RegistryConfigto encapsulate docker config, whitelist parsing, and.netrccredential resolution. - Refactors
SCDockerto use the new config manager for registry enumeration, login, and tag/image fetching flows. - Adds tests covering whitelist parsing, whitelist enforcement, config persistence behavior, and
.netrcerror handling; adjusts project-list download fallback behavior.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/docker/test_docker_config.py | Adds unit tests for docker config/whitelist/netrc handling via the new manager. |
| src/sc/docker/exceptions.py | Adds config-specific exceptions (ScDockerConfigError, NetrcError). |
| src/sc/docker/docker.py | Refactors CLI logic to use DockerConfigManager and RegistryConfig instead of inlined config/netrc/whitelist logic. |
| src/sc/docker/docker_config.py | New module implementing docker config + whitelist + netrc management. |
| src/sc/clone/project_list/project_list_manager.py | Adjusts behavior on download failure to optionally fall back to an existing local file. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| def __init__(self): | ||
| self.docker_client = docker.from_env() | ||
| self.supported_registry_types = RegistryAPIFactory.get_supported_registry_types() | ||
|
|
||
|
|
||
| self.docker_config_manager = ConfigManager('docker') | ||
| self.docker_config = self.docker_config_manager.get_config() | ||
|
|
||
| self.whitelisted_registries = self._get_whitelisted_registries() | ||
| self._validate_existing_registries() | ||
| self.config_manager = DockerConfigManager() |
| def _login_to_registry(self, registry_url: str) -> str: | ||
| username, api_token = self._get_registry_creds_by_url(registry_url) | ||
| self._docker_login(username, api_token, registry_url) | ||
| registry = self.config_manager.get_registry(registry_url) | ||
| self._docker_login(registry.username, registry.api_key, registry_url) | ||
|
|
| def list_images(self): | ||
| """List images from all your docker registries. | ||
| """ | ||
| remote_images = [] | ||
| if self.docker_config: | ||
| remote_images = self._fetch_image_names_all_registries_in_config() | ||
| remote_images = self._fetch_image_names_all_registries_in_config() | ||
|
|
| if credential_store == "config": | ||
| config_dict[registry_url]["username"] = username | ||
| config_dict[registry_url]["api_key"] = api_token | ||
|
|
| machine = registry_url.split("/")[0] | ||
| auth = creds.authenticators(machine) | ||
| if not auth: | ||
| raise NetrcError(f"No authenticators found for machine '{machine}' in .netrc") | ||
| username, _, api_key = auth | ||
| return username, api_key |
| try: | ||
| logger.info(f"Downloading project list {name} to path {str(path)}") | ||
| return self._project_list_downloader.download( | ||
| source.url, path, source.platform, source.token) | ||
| except (RuntimeError, IOError) as e: | ||
| logger.warning(f"Failed to download project list {name}: {e}") | ||
| if path.exists(): | ||
| return path | ||
| return None |
| from .registry_apis.registry_api_factory import RegistryAPIFactory | ||
| from sc.config_manager import ConfigManager | ||
|
|
||
| REGISTRY_WHITELIST = Path("/etc/sc/docker_registry_whitelist") |
4d70ede to
a0d7aec
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Suppressed comments (4)
tests/docker/test_docker_config.py:291
DockerConfigManager.add_registry(...)expectsapi_key, but this test calls it withapi_token, which will raiseTypeError: got an unexpected keyword argument 'api_token'. Update the keyword to match the method signature.
credential_store="netrc",
username="user",
api_token="token",
)
src/sc/docker/docker.py:221
- If reading credentials from
.netrcfails (NetrcError/ScDockerConfigError), the exception will currently propagate to Click and produce a stack trace. CatchScDockerExceptionhere and exit with a clear error message instead.
if netrc_input == "y":
credential_store = "netrc"
username, api_token = self.config_manager.get_netrc_creds_by_registry(registry_url)
else:
src/sc/docker/docker.py:339
get_registry()can raiseScDockerExceptionand can also returnNone; currently both cases will result in a stack trace or anAttributeErrordownstream. Handle these cases here with a friendly error + exit.
registry = self.config_manager.get_registry(registry_url)
return self._fetch_remote_tags(image, registry)
src/sc/docker/docker.py:70
DockerConfigManager.add_registry(...)can raiseScDockerConfigError(aScDockerException). Right now that will bubble up to Click and show a stack trace; previously this path printed a friendly error and exited. CatchScDockerExceptionhere and exit with a user-facing message.
self.config_manager.add_registry(
registry_url, registry_type, credential_store, username, api_token)
| credential_store="config", | ||
| username="user", | ||
| api_token="token", | ||
| ) |
| except (RuntimeError, IOError) as e: | ||
| logger.warning(f"Failed to download project list {name}: {e}") | ||
| if path.exists(): | ||
| return path | ||
| return None |
Split out docker config manager to manage the docker config, the whitelist and reading from netrc. As a separate PR I'm going to wrap docker_client, docker pull and docker run into a class.
Then the main ScDocker class becomes:
Ask ConfigManager for config.
Use DockerClient + RegistryAPI to get information on available dockers.
Use DockerClient to pull docker.
Generate command itself.
Use DockerClient to run command.
The current code is hard to follow with duplication