feat: add Docker credential helper for Cloudsmith registries#277
Merged
Conversation
31f616c to
58327a3
Compare
8b57884 to
0b0445c
Compare
58327a3 to
910a2cd
Compare
0b0445c to
987c32f
Compare
910a2cd to
23ab3ad
Compare
65d8c53 to
646c50a
Compare
23ab3ad to
0e03731
Compare
646c50a to
5c2b23d
Compare
641bec5 to
5540a76
Compare
6e1792c to
8862812
Compare
5540a76 to
2aad86a
Compare
368db92 to
a60887d
Compare
4e72204 to
21fe5cf
Compare
1cef871 to
b4b2583
Compare
21fe5cf to
d2cd0d1
Compare
estebangarcia
requested changes
Mar 25, 2026
Comment on lines
+68
to
72
| "console_scripts": [ | ||
| "cloudsmith=cloudsmith_cli.cli.commands.main:main", | ||
| "docker-credential-cloudsmith=cloudsmith_cli.credential_helpers.docker.wrapper:main", | ||
| ] | ||
| }, |
Contributor
There was a problem hiding this comment.
question: Have you tested this with the pyz ?
Contributor
Author
There was a problem hiding this comment.
It won't work with pyz, pyz just gives a single binary. It is good you call this out though, we will have a documentation task to have the user create their own wrapper to cloudsmith credential-helper docker
Contributor
There was a problem hiding this comment.
Should revisit this comment
There was a problem hiding this comment.
Bart looks to have workaround this by introducing an install command.
74f874c to
f30e428
Compare
d2cd0d1 to
05fd479
Compare
BartoszBlizniak
approved these changes
May 28, 2026
estebangarcia
previously requested changes
Jun 3, 2026
Comment on lines
+68
to
72
| "console_scripts": [ | ||
| "cloudsmith=cloudsmith_cli.cli.commands.main:main", | ||
| "docker-credential-cloudsmith=cloudsmith_cli.credential_helpers.docker.wrapper:main", | ||
| ] | ||
| }, |
Contributor
There was a problem hiding this comment.
Should revisit this comment
| import sys | ||
|
|
||
|
|
||
| def main(): |
Contributor
There was a problem hiding this comment.
question: any reason why this can't be a CLI command instead of a separate script?
Extends cache_utils.py with merge_json_file, a safe atomic JSON-merge writer for editing user-owned config files (e.g. ~/.docker/config.json). Supports read-or-empty fallback, in-place mutation, stable indent=2 serialisation, idempotent change detection, optional .bak backup, dry_run mode, parent-dir creation, and atomic temp+replace writes. Extracts _atomic_write_text as a shared private helper so atomic_write_json retains its public signature and behaviour. Adds 27 tests covering all nine specified behaviours (foreign-key preservation, missing file/parent-dir creation, backup semantics, idempotency, dry_run, malformed input, serialisation format, return value, and atomic_write_json round-trip + permissions). Co-Authored-By: Claude <noreply@anthropic.com>
…filter Reshape the custom-domain layer to return typed CustomDomain dataclass records (host, backend_kind, enabled, validated) instead of bare host strings. Add BackendKind IntEnum mirroring the server enum. Expose get_custom_domains() and get_format_domains() so callers can filter by format. Switch list_custom_domains() in core/api/orgs.py to return to_dict() records following the repo pattern. Tighten exception handling to ApiException-only (no bare except). Rewire is_cloudsmith_domain to use enabled+validated check. Update tests for the new structured API. Co-Authored-By: Claude <noreply@anthropic.com>
…handling Move Docker credential-helper protocol logic from the click command into a transport-light `credential_helpers/docker/runtime.py`. The command is now a thin shim that calls `execute()` and passes stdout/stderr/exit-code back to the caller. The D17 protocol-boundary broad-except in `_execute_get` ensures that network/SDK errors degrade to a clean exit-1 refusal rather than crashing `docker pull`/`push`. `credentials.py` is removed; its logic lives in `runtime.py`. Co-Authored-By: Claude <noreply@anthropic.com>
Replaces the old Python wrapper entry-point with an on-PATH shell launcher written by `cloudsmith credential-helper install docker`. The installer patches ~/.docker/config.json via merge_json_file (preserving foreign keys, atomic write, .bak backup) and supports --domain, --bin-dir, and --dry-run. Adds launchers.py (write/remove/resolve_bin_dir/is_on_path), docker/installer.py (DockerInstaller), and cli/commands/credential_helper/manage.py (install/uninstall/list commands). Removes credential_helpers/docker/wrapper.py and its setup.py console_scripts entry point. Co-Authored-By: Claude <noreply@anthropic.com>
Wires custom-domain autodiscovery into `credential-helper install docker`. Discovery is best-effort: if org/credentials are absent, or if the API call fails, the default host is still registered and a clear info/warning action is returned. - custom_domains.py: add `refresh: bool = False` to `get_custom_domains` and `get_format_domains`; when True, skips the cache read and always fetches from the API (writing the fresh result back to cache). - installer.py: extend `DockerInstaller.install` with `discover`, `refresh`, `org`, `api_key`, `auth_type`, `api_host` parameters; adds a single deliberate broad-except discovery boundary with `# pylint: disable=broad-except`. - manage.py: add `--no-discover`, `--refresh`, `--org` (envvar CLOUDSMITH_ORG) options; apply `@common_api_auth_options` + `@resolve_credentials`; thread all new params into `installer.install`. - test_credential_helper_install.py: new test classes covering discovery on/off, missing org/creds skip, failure graceful degradation, dedup, --refresh cache-bypass, and CliRunner smoke tests for the new CLI flags. Co-Authored-By: Claude <noreply@anthropic.com>
Add a `bin_dir` keyword param to `DockerInstaller.uninstall` and wire it through to `resolve_bin_dir`, so that `uninstall docker --bin-dir /custom` locates and removes the launcher written by `install --bin-dir /custom` instead of silently looking in the auto-resolved directory. Thread the new `--bin-dir` Click option into `uninstall_cmd` in manage.py (matching the help-text style of the install counterpart). Two on-disk tests verify the fix: one confirms the launcher is gone after a matching uninstall, the other confirms a mismatched dir leaves the launcher intact. Co-Authored-By: Claude <noreply@anthropic.com>
…uninstall/list
Add -F/--output-format {pretty,json,pretty_json} to the three
credential-helper management commands (install, uninstall, list) following
the repo convention. JSON mode emits {"data": ...} on stdout with no
human text; pretty mode is unchanged. The runtime docker command is
untouched.
Co-Authored-By: Claude <noreply@anthropic.com>
… domains Pass BackendKind.DOCKER to is_cloudsmith_domain so the Docker credential helper only vouches for Docker-format custom domains. Adds backend_kind optional param to is_cloudsmith_domain (None default preserves existing generic behavior); updates docker/runtime.py to pass BackendKind.DOCKER; extends TestIsCloudsmithDomain and adds TestDockerRuntimeBackendKindFiltering to cover filtering correctness. Co-Authored-By: Claude <noreply@anthropic.com>
Collapse 108 tests (45 + 63) down to 61 (34 + 27) by merging near-identical cases into parametrized tests and removing layer-duplicates. All five retained guards survive: broken-pipe boundary, legacy-cache format miss, uppercase-host casing, status str-not-Path serialisation, and graceful discovery failure. Co-Authored-By: Claude <noreply@anthropic.com>
The launcher/resolve tests monkeypatched os.name="nt" on a posix host to exercise the Windows code paths. On Python < 3.12 that makes pathlib.Path build a WindowsPath, which raises NotImplementedError — crashing the whole pytest session in CI (3.11) during failure-report/cache-write. It passed locally only because 3.12+ rewrote pathlib to tolerate it. Extract the platform-specific logic (launcher filename, script body, user bin dir) into pure helpers parameterised on `windows: bool`, and test those directly instead of faking os.name. Public API and return types unchanged; behaviour on real Windows is identical. Co-Authored-By: Claude <noreply@anthropic.com>
…opilot review)
Coerce non-dict credHelpers to {} on install (Fix 1), guard non-dict
credHelpers as a no-op on uninstall (Fix 2), pass newline="" to
write_text to prevent \r\r\n on Windows (Fix 3), and require W_OK|X_OK
when selecting candidate bin dir (Fix 4). Add tests 17 and 18 covering
the two malformed-credHelpers edge cases.
Co-Authored-By: Claude <noreply@anthropic.com>
…ot review) Add missing '# Copyright 2026 Cloudsmith Ltd' header to five new source files flagged by Copilot for inconsistent licensing headers. Matches the convention established in cache_utils.py and backends.py exactly. Co-Authored-By: Claude <noreply@anthropic.com>
Member
|
imduffy15
approved these changes
Jun 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Implement the Docker credential helper protocol so Docker can automatically authenticate with Cloudsmith registries (including custom domains) without manual
docker login.Key changes:
cloudsmith credential-helper dockerCLI commanddocker-credential-cloudsmithwrapper binary (entry point)Type of Change
Additional Notes
Manually tested with: