diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e1b322c..e7548ae4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed +- The docker, pnpm, cargo and terraform launchers of the standalone binary now quote the path of the binary correctly. A path with `$` or a backtick no longer breaks the launcher on Linux and macOS. - `cloudsmith credential-helper list` now finds a launcher that `install` wrote to the default directory, `~/.cloudsmith/bin`. Before, `list` looked in the directory of the `cloudsmith` executable and reported the helper as not installed. ## [1.29.0] - 2026-10-15 diff --git a/cloudsmith_cli/cli/tests/commands/test_credential_helper.py b/cloudsmith_cli/cli/tests/commands/test_credential_helper.py index 9308d9ce..56867253 100644 --- a/cloudsmith_cli/cli/tests/commands/test_credential_helper.py +++ b/cloudsmith_cli/cli/tests/commands/test_credential_helper.py @@ -15,6 +15,7 @@ from ....core.credentials.models import CredentialResult from ....credential_helpers.backends import BackendKind from ....credential_helpers.common import ( + REFUSAL_MESSAGE, is_cloudsmith_domain, is_standard_cloudsmith_domain, ) @@ -28,7 +29,6 @@ write_cache, ) from ....credential_helpers.docker.runtime import ( - _REFUSAL_MESSAGE, execute, ) from ....credential_helpers.docker.runtime import ( @@ -133,7 +133,7 @@ def read(self): assert code == 1 assert stdout is None - assert stderr == _REFUSAL_MESSAGE + assert stderr == REFUSAL_MESSAGE # --------------------------------------------------------------------------- diff --git a/cloudsmith_cli/cli/tests/commands/test_credential_helper_generic.py b/cloudsmith_cli/cli/tests/commands/test_credential_helper_generic.py index 2fd06a5f..e90bb58c 100644 --- a/cloudsmith_cli/cli/tests/commands/test_credential_helper_generic.py +++ b/cloudsmith_cli/cli/tests/commands/test_credential_helper_generic.py @@ -6,7 +6,8 @@ from ....cli.commands.credential_helper.generic import generic from ....core.credentials.models import CredentialResult -from ....credential_helpers.generic import _REFUSAL_MESSAGE, PROTOCOL_VERSION, execute +from ....credential_helpers.common import REFUSAL_MESSAGE +from ....credential_helpers.generic import PROTOCOL_VERSION, execute TOKEN = "k_secret_token_value" @@ -54,7 +55,7 @@ def test_execute_empty_api_key_refuses(): assert code == 1 assert stdout is None - assert stderr == _REFUSAL_MESSAGE + assert stderr == REFUSAL_MESSAGE def test_execute_degrades_on_unexpected_exception(): @@ -71,7 +72,7 @@ def api_key(self): assert code == 1 assert stdout is None - assert stderr == _REFUSAL_MESSAGE + assert stderr == REFUSAL_MESSAGE def test_token_only_ever_appears_on_stdout(): @@ -109,7 +110,7 @@ def test_cli_refusal_exits_1_with_empty_stdout(runner): """A refusal must not emit a partial document on stdout.""" with patch( "cloudsmith_cli.cli.commands.credential_helper.generic.execute", - return_value=(1, None, _REFUSAL_MESSAGE), + return_value=(1, None, REFUSAL_MESSAGE), ): result = runner.invoke(generic, args=HERMETIC_ARGS, catch_exceptions=False) diff --git a/cloudsmith_cli/cli/tests/commands/test_credential_helper_install.py b/cloudsmith_cli/cli/tests/commands/test_credential_helper_install.py index 5d5158c5..cbea23d6 100644 --- a/cloudsmith_cli/cli/tests/commands/test_credential_helper_install.py +++ b/cloudsmith_cli/cli/tests/commands/test_credential_helper_install.py @@ -1402,7 +1402,7 @@ def test_frozen_install_launcher_targets_executable(tmp_path, monkeypatch): monkeypatch.setenv("DOCKER_CONFIG", str(docker_dir)) bin_dir = tmp_path / "bin" monkeypatch.setenv("PATH", str(bin_dir)) - exe = tmp_path / "cloudsmith" + exe = tmp_path / "price$USD dir" / "cloudsmith" with ( patch.object(sys, "frozen", True, create=True), @@ -1411,5 +1411,4 @@ def test_frozen_install_launcher_targets_executable(tmp_path, monkeypatch): DockerInstaller().install(bin_dir=str(bin_dir), discover=False) body = (bin_dir / "docker-credential-cloudsmith").read_text(encoding="utf-8") - assert str(exe) in body - assert "exec cloudsmith credential-helper" not in body + assert f"exec '{exe}' credential-helper docker \"$@\"" in body diff --git a/cloudsmith_cli/cli/tests/test_credential_helper_cargo.py b/cloudsmith_cli/cli/tests/test_credential_helper_cargo.py index 5987ddd9..4f7ff246 100644 --- a/cloudsmith_cli/cli/tests/test_credential_helper_cargo.py +++ b/cloudsmith_cli/cli/tests/test_credential_helper_cargo.py @@ -14,8 +14,8 @@ from ...core.credentials.models import CredentialResult from ...credential_helpers.backends import BackendKind from ...credential_helpers.cargo.installer import CargoInstaller +from ...credential_helpers.common import REFUSAL_MESSAGE from ...credential_helpers.cargo.runtime import ( - _REFUSAL_MESSAGE, SUPPORTED_VERSIONS, execute, get_credentials, @@ -190,7 +190,7 @@ def test_get_reports_not_found_when_no_credential_resolves(): assert messages[1] == {"Err": {"kind": "not-found"}} assert code == 1 - assert stderr == _REFUSAL_MESSAGE + assert stderr == REFUSAL_MESSAGE def test_get_reports_not_found_for_a_credential_without_an_api_key(): @@ -299,7 +299,7 @@ def write(self, _s): code, stderr = execute(io.StringIO(""), BrokenPipe(), credential=credential) assert code == 1 - assert stderr == _REFUSAL_MESSAGE + assert stderr == REFUSAL_MESSAGE def test_domain_lookup_failure_degrades_to_a_clean_exit(credential): @@ -310,7 +310,7 @@ def test_domain_lookup_failure_degrades_to_a_clean_exit(credential): ): code, stderr, messages = _session(_request(), credential=credential) - assert (code, stderr) == (1, _REFUSAL_MESSAGE) + assert (code, stderr) == (1, REFUSAL_MESSAGE) assert messages == [hello()] diff --git a/cloudsmith_cli/cli/tests/test_credential_helper_pnpm.py b/cloudsmith_cli/cli/tests/test_credential_helper_pnpm.py index 5d047fc1..5e5013d3 100644 --- a/cloudsmith_cli/cli/tests/test_credential_helper_pnpm.py +++ b/cloudsmith_cli/cli/tests/test_credential_helper_pnpm.py @@ -20,8 +20,8 @@ from ...core.credentials.models import CredentialResult from ...credential_helpers.backends import BackendKind +from ...credential_helpers.common import REFUSAL_MESSAGE from ...credential_helpers.pnpm.runtime import ( - _REFUSAL_MESSAGE, execute, get_pnpm_credentials, ) @@ -159,14 +159,14 @@ def test_execute_refuses_without_credentials(): """No credential is a hard refusal (exit 1) with a refusal message on stderr.""" exit_code, stdout, stderr = execute(CLOUDSMITH_HOST, credential=None) - assert (exit_code, stdout, stderr) == (1, None, _REFUSAL_MESSAGE) + assert (exit_code, stdout, stderr) == (1, None, REFUSAL_MESSAGE) def test_execute_refuses_for_a_foreign_registry(credential): """A registry we don't serve is not our concern — exit 1, no leaked token.""" exit_code, stdout, stderr = execute(FOREIGN_HOST, credential=credential) - assert (exit_code, stdout, stderr) == (1, None, _REFUSAL_MESSAGE) + assert (exit_code, stdout, stderr) == (1, None, REFUSAL_MESSAGE) def test_execute_degrades_cleanly_on_domain_lookup_failure(credential): @@ -180,7 +180,7 @@ def test_execute_degrades_cleanly_on_domain_lookup_failure(credential): ) assert (exit_code, stdout) == (1, None) - assert stderr == _REFUSAL_MESSAGE + assert stderr == REFUSAL_MESSAGE # --------------------------------------------------------------------------- diff --git a/cloudsmith_cli/cli/tests/test_credential_helper_terraform.py b/cloudsmith_cli/cli/tests/test_credential_helper_terraform.py index 07dc267a..31d4acc5 100644 --- a/cloudsmith_cli/cli/tests/test_credential_helper_terraform.py +++ b/cloudsmith_cli/cli/tests/test_credential_helper_terraform.py @@ -23,10 +23,10 @@ from ...core.api.exceptions import ApiException from ...core.credentials.models import CredentialResult from ...credential_helpers.backends import BackendKind +from ...credential_helpers.common import REFUSAL_MESSAGE from ...credential_helpers.terraform.runtime import ( _MISSING_ORG_MESSAGE, _MISSING_REPO_MESSAGE, - _REFUSAL_MESSAGE, execute, get_token, ) @@ -203,7 +203,7 @@ def test_execute_get_refuses_when_the_domain_scope_lookup_fails( repo="myrepo", ) - assert (exit_code, stdout, stderr) == (1, None, _REFUSAL_MESSAGE) + assert (exit_code, stdout, stderr) == (1, None, REFUSAL_MESSAGE) def test_execute_get_custom_domain_does_not_require_an_org_in_the_token(credential): @@ -245,7 +245,7 @@ def test_execute_get_refuses_cloudsmith_host_without_credentials(): "get", CLOUDSMITH_HOST, credential=None, org="acme", repo="myrepo" ) - assert (exit_code, stdout, stderr) == (1, None, _REFUSAL_MESSAGE) + assert (exit_code, stdout, stderr) == (1, None, REFUSAL_MESSAGE) def test_execute_get_refuses_cloudsmith_host_without_an_org(credential): @@ -322,7 +322,7 @@ def test_execute_get_degrades_cleanly_on_domain_lookup_failure(credential): ) assert (exit_code, stdout) == (1, None) - assert stderr == _REFUSAL_MESSAGE + assert stderr == REFUSAL_MESSAGE # --------------------------------------------------------------------------- diff --git a/cloudsmith_cli/credential_helpers/cargo/installer.py b/cloudsmith_cli/credential_helpers/cargo/installer.py index 5859eef2..cff50fba 100644 --- a/cloudsmith_cli/credential_helpers/cargo/installer.py +++ b/cloudsmith_cli/credential_helpers/cargo/installer.py @@ -19,7 +19,6 @@ import logging import os -import sys from pathlib import Path from typing import TYPE_CHECKING @@ -30,6 +29,7 @@ from ..common import extract_hostname, path_warning from ..domains import get_format_domains from ..launchers import ( + helper_command, is_on_path, is_windows, remove_launcher, @@ -73,7 +73,6 @@ class CargoInstaller: """ LAUNCHER_NAME = "cargo-credential-cloudsmith" - TARGET_CMD = "cloudsmith credential-helper cargo" #: The value written into Cargo's config. Cargo resolves a bare name #: through ``PATH`` (and requires the ``cargo-credential-`` prefix). PROVIDER_VALUE = "cargo-credential-cloudsmith" @@ -87,21 +86,6 @@ class CargoInstaller: name = "cargo" summary = "Cargo credential provider for Cloudsmith registries" - @classmethod - def _resolve_target_cmd(cls) -> str: - """Return the command the launcher forwards to. - - A pip/source install resolves the bare ``cloudsmith`` command via - ``PATH``. A frozen standalone binary (PyInstaller) is not guaranteed - to be on ``PATH`` under that name, so point the launcher at the - absolute executable instead — mirroring the frozen handling in - :func:`cloudsmith_cli.cli.commands.mcp._get_server_config`. The path - is quoted so a directory containing spaces still execs correctly. - """ - if getattr(sys, "frozen", False): - return f'"{sys.executable}" credential-helper cargo' - return cls.TARGET_CMD - # ------------------------------------------------------------------ # config.toml mutation # ------------------------------------------------------------------ @@ -336,7 +320,7 @@ def mutate(config: dict) -> None: # Real install launcher_path = write_launcher( - target_dir, self.LAUNCHER_NAME, self._resolve_target_cmd() + target_dir, self.LAUNCHER_NAME, helper_command(self.name) ) actions.append(f"wrote launcher {launcher_path}") diff --git a/cloudsmith_cli/credential_helpers/cargo/runtime.py b/cloudsmith_cli/credential_helpers/cargo/runtime.py index d6587543..ae22367b 100644 --- a/cloudsmith_cli/credential_helpers/cargo/runtime.py +++ b/cloudsmith_cli/credential_helpers/cargo/runtime.py @@ -19,20 +19,13 @@ import logging from ..backends import BackendKind -from ..common import is_cloudsmith_domain +from ..common import REFUSAL_MESSAGE, is_cloudsmith_domain logger = logging.getLogger(__name__) #: Protocol versions this provider implements, announced in the hello message. SUPPORTED_VERSIONS = (1,) -_REFUSAL_MESSAGE = ( - "Error: Unable to retrieve credentials. " - "Provide credentials via the CLOUDSMITH_API_KEY environment variable, " - "credentials.ini, the system keyring, or an OIDC service. " - "Verify current authentication with `cloudsmith whoami --verbose`." -) - def hello() -> dict: """Return the handshake message sent before any request is read. @@ -257,9 +250,9 @@ def execute(stdin, stdout, credential=None, api_host=None, org=None): # TypeError from json.dumps — all degrade to a clean exit. # (Exception does not catch KeyboardInterrupt/SystemExit, which is correct.) logger.debug("cargo credential-provider session failed: %s", exc, exc_info=True) - return (1, _REFUSAL_MESSAGE) + return (1, REFUSAL_MESSAGE) if refused: - return (1, _REFUSAL_MESSAGE) + return (1, REFUSAL_MESSAGE) return (0, None) diff --git a/cloudsmith_cli/credential_helpers/common.py b/cloudsmith_cli/credential_helpers/common.py index 5db71994..d6a0e98b 100644 --- a/cloudsmith_cli/credential_helpers/common.py +++ b/cloudsmith_cli/credential_helpers/common.py @@ -12,6 +12,13 @@ logger = logging.getLogger(__name__) +REFUSAL_MESSAGE = ( + "Error: Unable to retrieve credentials. " + "Provide credentials via the CLOUDSMITH_API_KEY environment variable, " + "credentials.ini, the system keyring, or an OIDC service. " + "Verify current authentication with `cloudsmith whoami --verbose`." +) + def path_warning( target_dir: Path, helper_format: str, launcher: str, windows: bool diff --git a/cloudsmith_cli/credential_helpers/docker/installer.py b/cloudsmith_cli/credential_helpers/docker/installer.py index 54cc15e7..32bec276 100644 --- a/cloudsmith_cli/credential_helpers/docker/installer.py +++ b/cloudsmith_cli/credential_helpers/docker/installer.py @@ -11,7 +11,6 @@ import json import logging import os -import sys from pathlib import Path from typing import TYPE_CHECKING @@ -20,6 +19,7 @@ from ..common import path_warning from ..domains import get_format_domains from ..launchers import ( + helper_command, is_on_path, is_windows, remove_launcher, @@ -61,28 +61,12 @@ class DockerInstaller: """ LAUNCHER_NAME = "docker-credential-cloudsmith" - TARGET_CMD = "cloudsmith credential-helper docker" HELPER_VALUE = "cloudsmith" DEFAULT_HOST = "docker.cloudsmith.io" name = "docker" summary = "Docker credential helper for Cloudsmith registries" - @classmethod - def _resolve_target_cmd(cls) -> str: - """Return the command the launcher forwards to. - - A pip/source install resolves the bare ``cloudsmith`` command via - ``PATH``. A frozen standalone binary (PyInstaller) is not guaranteed - to be on ``PATH`` under that name, so point the launcher at the - absolute executable instead — mirroring the frozen handling in - :func:`cloudsmith_cli.cli.commands.mcp._get_server_config`. The path - is quoted so a directory containing spaces still execs correctly. - """ - if getattr(sys, "frozen", False): - return f'"{sys.executable}" credential-helper docker' - return cls.TARGET_CMD - def install( self, *, @@ -225,7 +209,7 @@ def mutate(config: dict) -> None: # Real install launcher_path = write_launcher( - target_dir, self.LAUNCHER_NAME, self._resolve_target_cmd() + target_dir, self.LAUNCHER_NAME, helper_command(self.name) ) actions.append(f"wrote launcher {launcher_path}") diff --git a/cloudsmith_cli/credential_helpers/docker/runtime.py b/cloudsmith_cli/credential_helpers/docker/runtime.py index 38a2dc1c..93883a83 100644 --- a/cloudsmith_cli/credential_helpers/docker/runtime.py +++ b/cloudsmith_cli/credential_helpers/docker/runtime.py @@ -13,17 +13,10 @@ import logging from ..backends import BackendKind -from ..common import is_cloudsmith_domain +from ..common import REFUSAL_MESSAGE, is_cloudsmith_domain logger = logging.getLogger(__name__) -_REFUSAL_MESSAGE = ( - "Error: Unable to retrieve credentials. " - "Provide credentials via the CLOUDSMITH_API_KEY environment variable, " - "credentials.ini, the system keyring, or an OIDC service. " - "Verify current authentication with `cloudsmith whoami --verbose`." -) - def get_credentials(server_url, credential=None, api_host=None, org=None): """ @@ -69,7 +62,7 @@ def _execute_get( server_url, credential=credential, api_host=api_host, org=org ) if creds is None: - return (1, None, _REFUSAL_MESSAGE) + return (1, None, REFUSAL_MESSAGE) return (0, json.dumps(creds), None) except Exception as exc: # pylint: disable=broad-except @@ -80,7 +73,7 @@ def _execute_get( # This is the ONLY intentional broad except in this feature. # (Exception does not catch KeyboardInterrupt/SystemExit, which is correct.) logger.debug("docker credential-helper get failed: %s", exc, exc_info=True) - return (1, None, _REFUSAL_MESSAGE) + return (1, None, REFUSAL_MESSAGE) def execute( diff --git a/cloudsmith_cli/credential_helpers/generic.py b/cloudsmith_cli/credential_helpers/generic.py index 98e6465c..ab146ab2 100644 --- a/cloudsmith_cli/credential_helpers/generic.py +++ b/cloudsmith_cli/credential_helpers/generic.py @@ -8,17 +8,12 @@ import json import logging +from .common import REFUSAL_MESSAGE + logger = logging.getLogger(__name__) PROTOCOL_VERSION = 1 -_REFUSAL_MESSAGE = ( - "Error: Unable to retrieve credentials. " - "Provide credentials via the CLOUDSMITH_API_KEY environment variable, " - "credentials.ini, the system keyring, or an OIDC service. " - "Verify current authentication with `cloudsmith whoami --verbose`." -) - class PartialInstallError(Exception): """Raised when the installation partially fails, but scheduled post-install reporting actions still need to be executed.""" @@ -71,9 +66,9 @@ def execute(credential=None) -> tuple[int, str | None, str | None]: try: response = build_response(credential) if response is None: - return (1, None, _REFUSAL_MESSAGE) + return (1, None, REFUSAL_MESSAGE) return (0, json.dumps(response), None) except Exception as exc: # pylint: disable=broad-except logger.debug("generic credential-helper failed: %s", exc, exc_info=True) - return (1, None, _REFUSAL_MESSAGE) + return (1, None, REFUSAL_MESSAGE) diff --git a/cloudsmith_cli/credential_helpers/launchers.py b/cloudsmith_cli/credential_helpers/launchers.py index 7ba168ee..8086e6f2 100644 --- a/cloudsmith_cli/credential_helpers/launchers.py +++ b/cloudsmith_cli/credential_helpers/launchers.py @@ -14,6 +14,8 @@ from __future__ import annotations import os +import shlex +import sys from pathlib import Path @@ -27,6 +29,24 @@ def _launcher_filename(name: str, *, windows: bool) -> str: return f"{name}.cmd" if windows else name +def launcher_arg(value: str) -> str: + """Return *value* quoted as one argument of a launcher command.""" + if is_windows(): + return f'"{value}"' + return shlex.quote(value) + + +def helper_command(helper: str) -> str: + """Return the command that a launcher runs for *helper*. + + A frozen standalone binary is not guaranteed to be on ``PATH`` as + ``cloudsmith``, so the launcher runs the absolute executable. + """ + if getattr(sys, "frozen", False): + return f"{launcher_arg(sys.executable)} credential-helper {helper}" + return f"cloudsmith credential-helper {helper}" + + def _launcher_content(target_cmd: str, *, windows: bool) -> str: """Return the launcher script body for the platform. diff --git a/cloudsmith_cli/credential_helpers/pnpm/installer.py b/cloudsmith_cli/credential_helpers/pnpm/installer.py index edc8f606..838507db 100644 --- a/cloudsmith_cli/credential_helpers/pnpm/installer.py +++ b/cloudsmith_cli/credential_helpers/pnpm/installer.py @@ -1,13 +1,13 @@ # Copyright 2026 Cloudsmith Ltd import logging import os -import sys from pathlib import Path from cloudsmith_cli.credential_helpers.backends import BackendKind from cloudsmith_cli.credential_helpers.domains import get_format_domains from cloudsmith_cli.credential_helpers.generic import PartialInstallError from cloudsmith_cli.credential_helpers.launchers import ( + helper_command, remove_launcher, resolve_bin_dir, write_launcher, @@ -30,27 +30,11 @@ def _config_path() -> Path: class PNPMInstaller: LAUNCHER_NAME = "pnpm-credential-cloudsmith" - TARGET_CMD = "cloudsmith credential-helper pnpm" DEFAULT_HOST = "npm.cloudsmith.io" name = "pnpm" summary = "pnpm credential helper for Cloudsmith registries" - @classmethod - def _resolve_target_cmd(cls) -> str: - """Return the command the launcher forwards to. - - A pip/source install resolves the bare ``cloudsmith`` command via - ``PATH``. A frozen standalone binary (PyInstaller) is not guaranteed - to be on ``PATH`` under that name, so point the launcher at the - absolute executable instead — mirroring the frozen handling in - :func:`cloudsmith_cli.cli.commands.mcp._get_server_config`. The path - is quoted so a directory containing spaces still execs correctly. - """ - if getattr(sys, "frozen", False): - return f'"{sys.executable}" credential-helper pnpm' - return cls.TARGET_CMD - def install( self, *, @@ -154,7 +138,7 @@ def install( # Real install launcher_path = write_launcher( - target_dir, self.LAUNCHER_NAME, self._resolve_target_cmd(), dry_run=dry_run + target_dir, self.LAUNCHER_NAME, helper_command(self.name), dry_run=dry_run ) if dry_run: actions.append(f"would write launcher {launcher_path}") diff --git a/cloudsmith_cli/credential_helpers/pnpm/runtime.py b/cloudsmith_cli/credential_helpers/pnpm/runtime.py index ef9aa7b6..03abd576 100644 --- a/cloudsmith_cli/credential_helpers/pnpm/runtime.py +++ b/cloudsmith_cli/credential_helpers/pnpm/runtime.py @@ -2,17 +2,10 @@ import logging from ..backends import BackendKind -from ..common import is_cloudsmith_domain +from ..common import REFUSAL_MESSAGE, is_cloudsmith_domain logger = logging.getLogger(__name__) -_REFUSAL_MESSAGE = ( - "Error: Unable to retrieve credentials. " - "Provide credentials via the CLOUDSMITH_API_KEY environment variable, " - "credentials.ini, the system keyring, or an OIDC service. " - "Verify current authentication with `cloudsmith whoami --verbose`." -) - def get_pnpm_credentials(server_url, credential=None, api_host=None, org=None): """ @@ -60,10 +53,10 @@ def _get_execute( server_url, credential=credential, api_host=api_host, org=org ) if not cred: - return (1, None, _REFUSAL_MESSAGE) + return (1, None, REFUSAL_MESSAGE) return (0, cred, None) except Exception as exc: logger.debug("pnpm credential-helper get failed: %s", exc, exc_info=True) - return 1, None, _REFUSAL_MESSAGE + return 1, None, REFUSAL_MESSAGE diff --git a/cloudsmith_cli/credential_helpers/terraform/installer.py b/cloudsmith_cli/credential_helpers/terraform/installer.py index f27d38df..0390a291 100644 --- a/cloudsmith_cli/credential_helpers/terraform/installer.py +++ b/cloudsmith_cli/credential_helpers/terraform/installer.py @@ -21,7 +21,7 @@ import sys from pathlib import Path -from ..launchers import remove_launcher, write_launcher +from ..launchers import helper_command, remove_launcher, write_launcher from . import terraformrc logger = logging.getLogger(__name__) @@ -186,25 +186,10 @@ class TerraformInstaller: """ LAUNCHER_NAME = "terraform-credentials-cloudsmith" - TARGET_CMD = "cloudsmith credential-helper terraform" name = "terraform" summary = "Terraform credentials helper for Cloudsmith registries" - @classmethod - def _resolve_target_cmd(cls) -> str: - """Return the command the launcher forwards to. - - A pip/source install resolves the bare ``cloudsmith`` command via - ``PATH``. A frozen standalone binary (PyInstaller) is not guaranteed to - be on ``PATH`` under that name, so point the launcher at the absolute - executable instead. The path is quoted so a directory containing spaces - still execs correctly. - """ - if getattr(sys, "frozen", False): - return f'"{sys.executable}" credential-helper terraform' - return cls.TARGET_CMD - def _resolve_plugin_dir(self, bin_dir: str | None) -> Path: """Return the directory to install the launcher into. @@ -369,7 +354,7 @@ def install( written = self._install_exe_launcher(target_dir) else: written = write_launcher( - target_dir, self.LAUNCHER_NAME, self._resolve_target_cmd() + target_dir, self.LAUNCHER_NAME, helper_command(self.name) ) actions.append(f"wrote launcher {written}") diff --git a/cloudsmith_cli/credential_helpers/terraform/runtime.py b/cloudsmith_cli/credential_helpers/terraform/runtime.py index 926796c2..1007f0a0 100644 --- a/cloudsmith_cli/credential_helpers/terraform/runtime.py +++ b/cloudsmith_cli/credential_helpers/terraform/runtime.py @@ -33,6 +33,7 @@ from ..backends import BackendKind from ..common import ( + REFUSAL_MESSAGE, extract_hostname, is_cloudsmith_domain, is_standard_cloudsmith_domain, @@ -41,13 +42,6 @@ logger = logging.getLogger(__name__) -_REFUSAL_MESSAGE = ( - "Error: Unable to retrieve credentials. " - "Provide credentials via the CLOUDSMITH_API_KEY environment variable, " - "credentials.ini, the system keyring, or an OIDC service. " - "Verify current authentication with `cloudsmith whoami --verbose`." -) - _MISSING_ORG_MESSAGE = ( "Error: No organisation configured. " "Provide the Cloudsmith organisation via the --org flag, the " @@ -156,7 +150,7 @@ def _execute_get( token = get_token(hostname, credential=credential, api_host=api_host, org=org) if not token: - return (1, None, _REFUSAL_MESSAGE) + return (1, None, REFUSAL_MESSAGE) # Scope the token to a single repository. Standard domains also carry the # org ("{org}/{repo}/{token}"); custom domains are already org-bound, so @@ -198,7 +192,7 @@ def execute( logger.debug( "terraform credentials-helper get failed: %s", exc, exc_info=True ) - return (1, None, _REFUSAL_MESSAGE) + return (1, None, REFUSAL_MESSAGE) if verb in ("store", "forget"): # Credentials are resolved from the Cloudsmith CLI's own provider chain