Skip to content

feat(ENG-13677): Terraform credential helper - #400

Open
tigh-latte wants to merge 16 commits into
masterfrom
tigh/terraform-credential-provider
Open

feat(ENG-13677): Terraform credential helper#400
tigh-latte wants to merge 16 commits into
masterfrom
tigh/terraform-credential-provider

Conversation

@tigh-latte

@tigh-latte tigh-latte commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a Terraform credentials helper for Cloudsmith registries so
terraform init can authenticate against a Cloudsmith Terraform registry with
no token on disk, reusing the existing Cloudsmith CLI credential chain
(--api-key/CLOUDSMITH_API_KEY, credentials.ini, the OS keyring, or OIDC).

Terraform discovers credentials helpers as executables named
terraform-credentials-<name> and only searches its plugin directories (never
$PATH). This PR ships the helper, a launcher/wrapper that satisfies that
discovery contract, an installer that wires up ~/.terraformrc, and the
repository-scoped token format the registry expects.

What's included

Command (cloudsmith credential-helper terraform)

  • Implements Terraform's credentials-helper protocol for the get verb.
    Accepts Terraform's [verb] <hostname> calling convention (verb defaults to
    get; hostname falls back to stdin) so the launcher can forward args
    verbatim.
  • Returns {"token": "..."} for a Cloudsmith host and {} for any other host
    (exit 0) so Terraform falls back to its own credential sources.
    store/forget and unknown verbs return an actionable error and a non-zero
    exit. Missing credentials for a Cloudsmith host produce a clean refusal, never
    a traceback.
  • Registered on the credential-helper group.

Installer (credential-helper install/uninstall/list terraform)

  • Writes the launcher into Terraform's plugin dir (~/.terraform.d/plugins by
    default; --bin-dir overrides) and manages the credentials_helper "cloudsmith" block in ~/.terraformrc via a regex block editor (no HCL
    parser); refuses if a different helper block already exists.
  • Bakes the resolved --org, -P/--profile, and --repo into the terraformrc
    args list so terraform init needs no environment variables. Computes the
    terraformrc change before writing the launcher so a conflict leaves no orphan.

Repository-scoped, custom-domain-aware token format

  • Terraform never tells a credentials helper which repository is requested, so
    -r/--repo/--repository (or CLOUDSMITH_REPO) is required on get, and
    can also be passed to install to bake it into the terraformrc args.
  • Tokens are scoped per repository:
    • Standard *.cloudsmith.io/*.cloudsmith.com hosts → {org}/{repo}/{token}
      (the organisation is required here).
    • Custom domains (already bound to a single organisation) → {repo}/{token}
      (the org is used only to resolve the domain, never emitted in the token).
  • Added is_standard_cloudsmith_domain() to credential_helpers/common.py to
    distinguish standard hosts from custom domains (is_cloudsmith_domain was
    refactored to reuse it; behaviour unchanged).

Type of Change

  • New feature

Testing

  • Unit tests for the runtime (get/store/forget, token / empty-object /
    refusal outcomes, org-required-on-standard-domain, org-omitted-on-custom-
    domain), the CLI shim, the wrapper delegation, the terraformrc block
    helpers, and the installer (launcher + terraformrc, --org/-P/--repo
    baking, conflict handling).
  • is_standard_cloudsmith_domain unit coverage (apex/subdomain/scheme/casing
    matches, plus custom-domain and lookalike non-matches).
  • A live integration test (@pytest.mark.integration) that runs a real
    terraform init in an isolated HOME and asserts it does not fail for
    authentication reasons; skips cleanly when terraform, the wrapper, or the
    PYTEST_CLOUDSMITH_* vars are absent.
env -u CLOUDSMITH_PROFILE -u CLOUDSMITH_ORG -u CLOUDSMITH_WORKSPACE \
    -u CLOUDSMITH_KEYRING_BACKEND HOME=$(mktemp -d) \
    pytest -m "not integration"

Additional Notes

Copilot AI lite review requested due to automatic review settings August 31, 2026 08:54
@tigh-latte
tigh-latte requested a review from a team as a code owner August 31, 2026 08:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a Terraform credentials helper integration to Cloudsmith CLI so terraform init can authenticate against Cloudsmith Terraform registries via the existing CLI credential chain (env/config/keyring/OIDC), plus installer logic to wire Terraform’s plugin dir and ~/.terraformrc.

Changes:

  • Introduces Terraform helper runtime + terraformrc block editor + installer that writes a terraform-credentials-cloudsmith launcher into Terraform’s plugin directory and manages a credentials_helper "cloudsmith" block.
  • Adds CLI wiring for cloudsmith credential-helper terraform and extends credential-helper install to support Terraform-specific baked args (org/profile/repo) and next-steps guidance.
  • Adds unit + integration tests and refactors domain detection by introducing is_standard_cloudsmith_domain().

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
cloudsmith_cli/credential_helpers/terraform/terraformrc.py Text-based add/update/remove of the credentials_helper "cloudsmith" terraformrc block.
cloudsmith_cli/credential_helpers/terraform/runtime.py Implements Terraform credentials-helper protocol behavior and scoped token formatting.
cloudsmith_cli/credential_helpers/terraform/installer.py Installs/removes the launcher into Terraform plugin dir and updates terraformrc.
cloudsmith_cli/credential_helpers/terraform/init.py Exposes Terraform helper runtime functions at package level.
cloudsmith_cli/credential_helpers/common.py Adds is_standard_cloudsmith_domain() and reuses it in is_cloudsmith_domain().
cloudsmith_cli/cli/commands/credential_helper/terraform.py Click command shim for Terraform helper (parses verb/hostname, prints JSON).
cloudsmith_cli/cli/commands/credential_helper/manage.py Registers Terraform installer; adds Terraform repo bake + next-steps output.
cloudsmith_cli/cli/commands/credential_helper/init.py Registers the terraform subcommand under credential-helper.
cloudsmith_cli/cli/tests/test_startup_imports.py Adds import-safety tests for Terraform helper (and wrapper).
cloudsmith_cli/cli/tests/test_credential_helper_terraform.py Adds runtime/CLI/wrapper behavior tests for Terraform helper.
cloudsmith_cli/cli/tests/test_credential_helper_terraform_installer.py Adds terraformrc + installer + CLI install/uninstall tests.
cloudsmith_cli/cli/tests/commands/test_credential_helper.py Adds unit coverage for is_standard_cloudsmith_domain().
cloudsmith_cli/cli/tests/commands/test_credential_helper_terraform_integration.py Adds live terraform init integration test exercising helper auth path.
CHANGELOG.md Documents the new Terraform credentials helper feature.
Suppressed comments (1)

cloudsmith_cli/cli/tests/test_startup_imports.py:76

  • This test imports cloudsmith_cli.credential_helpers.terraform.wrapper, but there is no wrapper.py under cloudsmith_cli/credential_helpers/terraform/ in this PR (only __init__.py, runtime.py, installer.py, terraformrc.py). This will fail at import time and break the suite.

Either add the missing wrapper module (and its packaging entry point if it’s meant to be an installed executable), or adjust/remove the wrapper-related tests if the launcher written by TerraformInstaller is the only wrapper being shipped.


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread cloudsmith_cli/credential_helpers/terraform/runtime.py
Comment thread cloudsmith_cli/credential_helpers/terraform/terraformrc.py Outdated
Comment thread cloudsmith_cli/cli/tests/test_credential_helper_terraform.py
Comment thread cloudsmith_cli/credential_helpers/terraform/installer.py
Comment thread cloudsmith_cli/credential_helpers/terraform/installer.py Outdated
Comment thread cloudsmith_cli/credential_helpers/terraform/installer.py Outdated
Comment thread cloudsmith_cli/cli/commands/credential_helper/manage.py
Comment thread cloudsmith_cli/cli/commands/credential_helper/terraform.py
@tigh-latte
tigh-latte force-pushed the tigh/terraform-credential-provider branch from 176f131 to 6766bbf Compare September 7, 2026 11:36
@tigh-latte

Copy link
Copy Markdown
Contributor Author

@BartoszBlizniak this is ready for another look now

@BartoszBlizniak BartoszBlizniak left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two of the three earlier threads (--config-file/--credentials-file baking and draining stdin on store) are fixed correctly, thanks. The plugin-dir restriction needs another pass, see inline.

See:
https://developer.hashicorp.com/terraform/cli/config/config-file#implied-local-mirror-directories
"""
dirs: list[Path] = [_default_plugin_dir()]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allowlist is built from the "Implied Local Mirror Directories" doc, but that section describes where Terraform looks for provider mirrors, not credentials helpers. Helper discovery is much narrower in the Terraform source:

So as written this still allows installs Terraform will never see:

  1. The XDG data dirs, /usr/local/share / /usr/share, the macOS io.terraform dirs and the cwd terraform.d/plugins are never searched for helpers. Installing there "succeeds" and terraform init silently ignores the helper, which is the failure mode this change was meant to prevent.
  2. _is_recognized_plugin_dir accepts any descendant of a root, but only <root> and <root>/<os>_<arch> are searched. ~/.terraform.d/plugins/custom is not found by Terraform.
  3. Line 128 has "HashCorp" (should be HashiCorp). Moot once the branch goes, but worth noting the list was not verified against a real Terraform.

Suggest reducing the allowlist to exactly _default_plugin_dir() and _default_plugin_dir() / f"{os}_{arch}", and dropping the XDG / macOS / cwd branches and their tests. Could you also verify with a real terraform init and TF_LOG=debug that a launcher in a rejected dir is in fact not picked up, and one in the accepted dirs is?

"""
monkeypatch.setattr(Path, "home", staticmethod(lambda: tmp_path))
monkeypatch.delenv("TF_CLI_CONFIG_FILE", raising=False)
custom = tmp_path / ".terraform.d" / "plugins" / "custom"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test asserts that installing into ~/.terraform.d/plugins/custom succeeds, but Terraform does a flat ReadDir on plugins/ and plugins/<os>_<arch> only, so a launcher here is never discovered. The test currently locks in the bug. Once the allowlist is tightened, the only valid subdirectory to use here is an <os>_<arch> one, e.g. linux_amd64.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants