cloudsmith-cli 1.26.0__tar.gz → 1.27.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. {cloudsmith_cli-1.26.0/cloudsmith_cli.egg-info → cloudsmith_cli-1.27.0}/PKG-INFO +7 -4
  2. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/README.md +2 -2
  3. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/auth.py +129 -34
  4. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/credential_helper/__init__.py +2 -0
  5. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/credential_helper/manage.py +127 -10
  6. cloudsmith_cli-1.27.0/cloudsmith_cli/cli/commands/credential_helper/terraform.py +151 -0
  7. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/decorators.py +67 -11
  8. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/saml.py +2 -2
  9. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/webserver.py +1 -3
  10. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/models.py +6 -0
  11. cloudsmith_cli-1.27.0/cloudsmith_cli/core/credentials/providers/keyring_provider.py +95 -0
  12. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/providers/oidc_provider.py +81 -5
  13. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/keyring.py +31 -7
  14. cloudsmith_cli-1.27.0/cloudsmith_cli/core/sso.py +224 -0
  15. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/utils.py +50 -0
  16. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/credential_helpers/common.py +23 -3
  17. cloudsmith_cli-1.27.0/cloudsmith_cli/credential_helpers/terraform/__init__.py +4 -0
  18. cloudsmith_cli-1.27.0/cloudsmith_cli/credential_helpers/terraform/installer.py +496 -0
  19. cloudsmith_cli-1.27.0/cloudsmith_cli/credential_helpers/terraform/runtime.py +204 -0
  20. cloudsmith_cli-1.27.0/cloudsmith_cli/credential_helpers/terraform/terraformrc.py +165 -0
  21. cloudsmith_cli-1.27.0/cloudsmith_cli/data/VERSION +1 -0
  22. cloudsmith_cli-1.27.0/cloudsmith_cli/wrapper.py +48 -0
  23. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0/cloudsmith_cli.egg-info}/PKG-INFO +7 -4
  24. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli.egg-info/SOURCES.txt +6 -0
  25. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli.egg-info/entry_points.txt +1 -0
  26. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli.egg-info/requires.txt +4 -1
  27. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/pyproject.toml +8 -1
  28. cloudsmith_cli-1.26.0/cloudsmith_cli/core/credentials/providers/keyring_provider.py +0 -105
  29. cloudsmith_cli-1.26.0/cloudsmith_cli/core/sso.py +0 -49
  30. cloudsmith_cli-1.26.0/cloudsmith_cli/data/VERSION +0 -1
  31. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/LICENSE +0 -0
  32. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/MANIFEST.in +0 -0
  33. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/__init__.py +0 -0
  34. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/__main__.py +0 -0
  35. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/__init__.py +0 -0
  36. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/command.py +0 -0
  37. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/__init__.py +0 -0
  38. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/check.py +0 -0
  39. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/copy.py +0 -0
  40. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/credential_helper/cargo.py +0 -0
  41. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/credential_helper/docker.py +0 -0
  42. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/credential_helper/generic.py +0 -0
  43. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/credential_helper/pnpm.py +0 -0
  44. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/delete.py +0 -0
  45. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/dependencies.py +0 -0
  46. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/docs.py +0 -0
  47. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/domains.py +0 -0
  48. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/download.py +0 -0
  49. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/entitlements.py +0 -0
  50. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/help_.py +0 -0
  51. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/list_.py +0 -0
  52. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/login.py +0 -0
  53. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/logout.py +0 -0
  54. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/main.py +0 -0
  55. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/mcp.py +0 -0
  56. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/metadata.py +0 -0
  57. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/metrics/__init__.py +0 -0
  58. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/metrics/command.py +0 -0
  59. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/metrics/entitlements.py +0 -0
  60. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/metrics/packages.py +0 -0
  61. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/move.py +0 -0
  62. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/policy/__init__.py +0 -0
  63. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/policy/command.py +0 -0
  64. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/policy/deny.py +0 -0
  65. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/policy/license.py +0 -0
  66. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/policy/vulnerability.py +0 -0
  67. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/push.py +0 -0
  68. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/quarantine.py +0 -0
  69. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/quota/__init__.py +0 -0
  70. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/quota/command.py +0 -0
  71. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/quota/history.py +0 -0
  72. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/quota/quota.py +0 -0
  73. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/registry.py +0 -0
  74. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/repos.py +0 -0
  75. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/resync.py +0 -0
  76. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/status.py +0 -0
  77. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/tags.py +0 -0
  78. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/tokens.py +0 -0
  79. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/upstream.py +0 -0
  80. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/vulnerabilities.py +0 -0
  81. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/commands/whoami.py +0 -0
  82. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/config.py +0 -0
  83. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/dsc_parser.py +0 -0
  84. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/exceptions.py +0 -0
  85. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/metadata_common.py +0 -0
  86. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/table.py +0 -0
  87. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/types.py +0 -0
  88. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/utils.py +0 -0
  89. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/cli/validators.py +0 -0
  90. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/conftest.py +0 -0
  91. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/__init__.py +0 -0
  92. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/api/__init__.py +0 -0
  93. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/api/distros.py +0 -0
  94. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/api/entitlements.py +0 -0
  95. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/api/exceptions.py +0 -0
  96. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/api/files.py +0 -0
  97. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/api/init.py +0 -0
  98. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/api/metadata.py +0 -0
  99. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/api/metrics.py +0 -0
  100. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/api/orgs.py +0 -0
  101. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/api/packages.py +0 -0
  102. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/api/quota.py +0 -0
  103. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/api/rates.py +0 -0
  104. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/api/repos.py +0 -0
  105. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/api/status.py +0 -0
  106. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/api/upstreams.py +0 -0
  107. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/api/user.py +0 -0
  108. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/api/version.py +0 -0
  109. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/api/vulnerabilities.py +0 -0
  110. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/cache_utils.py +0 -0
  111. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/config.py +0 -0
  112. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/__init__.py +0 -0
  113. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/chain.py +0 -0
  114. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/oidc/__init__.py +0 -0
  115. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/oidc/cache.py +0 -0
  116. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/oidc/detectors/__init__.py +0 -0
  117. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/oidc/detectors/aws.py +0 -0
  118. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/oidc/detectors/azure_devops.py +0 -0
  119. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/oidc/detectors/base.py +0 -0
  120. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/oidc/detectors/bitbucket_pipelines.py +0 -0
  121. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/oidc/detectors/circleci.py +0 -0
  122. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/oidc/detectors/generic.py +0 -0
  123. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/oidc/detectors/github_actions.py +0 -0
  124. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/oidc/detectors/gitlab_ci.py +0 -0
  125. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/oidc/exchange.py +0 -0
  126. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/provider.py +0 -0
  127. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/providers/__init__.py +0 -0
  128. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/providers/cli_flag.py +0 -0
  129. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/providers/credentials_file.py +0 -0
  130. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/credentials/providers/env_var.py +0 -0
  131. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/download.py +0 -0
  132. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/macos_keychain.py +0 -0
  133. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/mcp/__init__.py +0 -0
  134. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/mcp/data.py +0 -0
  135. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/mcp/server.py +0 -0
  136. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/pagination.py +0 -0
  137. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/ratelimits.py +0 -0
  138. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/rest.py +0 -0
  139. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/session.py +0 -0
  140. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/core/version.py +0 -0
  141. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/credential_helpers/__init__.py +0 -0
  142. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/credential_helpers/backends.py +0 -0
  143. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/credential_helpers/cargo/__init__.py +0 -0
  144. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/credential_helpers/cargo/installer.py +0 -0
  145. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/credential_helpers/cargo/runtime.py +0 -0
  146. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/credential_helpers/custom_domains.py +0 -0
  147. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/credential_helpers/default_domains.py +0 -0
  148. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/credential_helpers/docker/__init__.py +0 -0
  149. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/credential_helpers/docker/installer.py +0 -0
  150. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/credential_helpers/docker/runtime.py +0 -0
  151. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/credential_helpers/generic.py +0 -0
  152. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/credential_helpers/launchers.py +0 -0
  153. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/credential_helpers/pnpm/__init__.py +0 -0
  154. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/credential_helpers/pnpm/installer.py +0 -0
  155. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/credential_helpers/pnpm/rc.py +0 -0
  156. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/credential_helpers/pnpm/runtime.py +0 -0
  157. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/data/config.ini +0 -0
  158. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/data/credentials.ini +0 -0
  159. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/templates/__init__.py +0 -0
  160. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/templates/auth_error.html +0 -0
  161. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/templates/auth_success.html +0 -0
  162. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli/templates/auth_two_factor.html +0 -0
  163. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli.egg-info/dependency_links.txt +0 -0
  164. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/cloudsmith_cli.egg-info/top_level.txt +0 -0
  165. {cloudsmith_cli-1.26.0 → cloudsmith_cli-1.27.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudsmith-cli
3
- Version: 1.26.0
3
+ Version: 1.27.0
4
4
  Summary: Cloudsmith Command-Line Interface (CLI)
5
5
  Author-email: Cloudsmith Ltd <support@cloudsmith.io>
6
6
  License-Expression: Apache-2.0
@@ -32,13 +32,16 @@ Requires-Dist: cloudsmith-api<3.0,>=2.0.31
32
32
  Requires-Dist: keyring>=25.4.1
33
33
  Requires-Dist: keyrings-alt>=5.0.2
34
34
  Requires-Dist: keyrings-cryptfile>=1.3.9
35
+ Requires-Dist: cryptography>=50.0.0
35
36
  Requires-Dist: mcp==1.28.1
36
- Requires-Dist: PyJWT[crypto]>=2.0.0
37
+ Requires-Dist: pydantic-settings>=2.14.2
38
+ Requires-Dist: PyJWT[crypto]>=2.13.0
37
39
  Requires-Dist: python-toon==0.1.2
38
40
  Requires-Dist: requests>=2.18.4
39
41
  Requires-Dist: requests_toolbelt>=1.0.0
40
42
  Requires-Dist: rich>=13.0.0
41
43
  Requires-Dist: semver>=2.7.9
44
+ Requires-Dist: starlette>=1.3.1
42
45
  Requires-Dist: urllib3>=2.5
43
46
  Requires-Dist: tomlkit>=0.15.0
44
47
  Provides-Extra: aws
@@ -370,7 +373,7 @@ cloudsmith auth --owner example
370
373
  Beginning authentication for the example org ...
371
374
  Your organization's SAML IDP URL is: https://example.com/some-saml-idp
372
375
 
373
- Starting webserver to begin authentication ...
376
+ Waiting for the authentication callback on port 12400 ...
374
377
 
375
378
  Authentication complete
376
379
  ```
@@ -381,7 +384,7 @@ The CLI opens the IDP URL in your browser automatically. If it can't (for exampl
381
384
  cloudsmith auth --owner example --no-browser
382
385
  ```
383
386
 
384
- *Note:* The authentication callback is served on `127.0.0.1:12400`, so the browser you open the URL in must be running on the same machine as the CLI.
387
+ *Note:* The authentication callback is served on `127.0.0.1`, on the first free port between `12400` and `12404`. The browser you open the URL in must run on the same machine as the CLI.
385
388
 
386
389
  #### Getting Your API Key
387
390
 
@@ -321,7 +321,7 @@ cloudsmith auth --owner example
321
321
  Beginning authentication for the example org ...
322
322
  Your organization's SAML IDP URL is: https://example.com/some-saml-idp
323
323
 
324
- Starting webserver to begin authentication ...
324
+ Waiting for the authentication callback on port 12400 ...
325
325
 
326
326
  Authentication complete
327
327
  ```
@@ -332,7 +332,7 @@ The CLI opens the IDP URL in your browser automatically. If it can't (for exampl
332
332
  cloudsmith auth --owner example --no-browser
333
333
  ```
334
334
 
335
- *Note:* The authentication callback is served on `127.0.0.1:12400`, so the browser you open the URL in must be running on the same machine as the CLI.
335
+ *Note:* The authentication callback is served on `127.0.0.1`, on the first free port between `12400` and `12404`. The browser you open the URL in must run on the same machine as the CLI.
336
336
 
337
337
  #### Getting Your API Key
338
338
 
@@ -3,7 +3,13 @@
3
3
  import webbrowser
4
4
 
5
5
  import click
6
+ import requests
6
7
 
8
+ from ...core.sso import (
9
+ SsoRenewalStatus,
10
+ get_access_token_expiry,
11
+ renew_sso_session,
12
+ )
7
13
  from .. import decorators, utils, validators
8
14
  from ..exceptions import handle_api_exceptions
9
15
  from ..saml import create_configured_session, get_idp_url
@@ -13,7 +19,86 @@ from .tokens import create, request_api_key
13
19
 
14
20
  # Authentication server configuration
15
21
  AUTH_SERVER_HOST = "127.0.0.1"
16
- AUTH_SERVER_PORT = 12400
22
+ AUTH_REDIRECT_HOST = "localhost"
23
+ AUTH_SERVER_PORTS = (12400, 12401, 12402, 12403, 12404)
24
+
25
+
26
+ def _create_auth_server(opts, owner, session, enable_token_creation, profile):
27
+ """Create an authentication server on the first available callback port."""
28
+ last_error = None
29
+
30
+ for port in AUTH_SERVER_PORTS:
31
+ try:
32
+ server = AuthenticationWebServer(
33
+ (AUTH_SERVER_HOST, port),
34
+ AuthenticationWebRequestHandler,
35
+ owner=owner,
36
+ session=session,
37
+ debug=opts.debug,
38
+ refresh_api_on_success=enable_token_creation,
39
+ api_opts=opts.api_config,
40
+ profile=profile,
41
+ )
42
+ return server, port
43
+ except OSError as exc:
44
+ last_error = exc
45
+
46
+ ports = ", ".join(str(port) for port in AUTH_SERVER_PORTS)
47
+ raise click.ClickException(
48
+ "Could not start the authentication callback server. "
49
+ f"Every candidate port is unavailable: {ports}. "
50
+ f"The last error was: {last_error}. "
51
+ "Stop the process that holds one of these ports, then try again."
52
+ ) from last_error
53
+
54
+
55
+ def _renew_existing_sso_session(opts, profile):
56
+ """Return a usable existing SSO session, or fall back to browser auth."""
57
+ renewal = renew_sso_session(opts.api_config.host, opts.session, profile=profile)
58
+ if renewal.status in (
59
+ SsoRenewalStatus.RENEWED,
60
+ SsoRenewalStatus.CURRENT,
61
+ ):
62
+ return renewal
63
+ if renewal.status == SsoRenewalStatus.FAILED and isinstance(
64
+ renewal.error, requests.RequestException
65
+ ):
66
+ raise click.ClickException(
67
+ "The SSO session has expired and Cloudsmith could not be reached. "
68
+ "Check your connection, then run 'cloudsmith auth' again."
69
+ ) from renewal.error
70
+ return None
71
+
72
+
73
+ def _report_active_sso_session(opts, renewal, use_stderr=False):
74
+ """Report the usable SSO session without exposing its access token."""
75
+ expires_at = get_access_token_expiry(renewal.access_token)
76
+ data = {
77
+ "authenticated": True,
78
+ "method": "sso",
79
+ "status": renewal.status.value,
80
+ "expires_at": expires_at,
81
+ "renewal_error": str(renewal.error) if renewal.error else None,
82
+ }
83
+ if utils.maybe_print_as_json(opts, data):
84
+ return
85
+
86
+ if renewal.status == SsoRenewalStatus.RENEWED:
87
+ click.secho("SSO session renewed.", fg="green", err=use_stderr)
88
+ elif renewal.error:
89
+ click.secho(
90
+ "The SSO session could not be renewed; the existing access token "
91
+ "remains active.",
92
+ fg="yellow",
93
+ err=use_stderr,
94
+ )
95
+ else:
96
+ click.secho("SSO session is active.", fg="green", err=use_stderr)
97
+ if expires_at:
98
+ click.echo(
99
+ f"Access token expires at {utils.fmt_datetime(expires_at)}.",
100
+ err=use_stderr,
101
+ )
17
102
 
18
103
 
19
104
  def _perform_saml_authentication(
@@ -28,50 +113,52 @@ def _perform_saml_authentication(
28
113
  session = create_configured_session(opts)
29
114
  api_host = opts.api_config.host
30
115
 
31
- idp_url = get_idp_url(api_host, owner, session=session)
32
-
33
- click.echo(
34
- f"Your Workspace's SAML IDP URL is: {click.style(idp_url, bold=True)}",
35
- err=use_stderr,
116
+ auth_server, port = _create_auth_server(
117
+ opts, owner, session, enable_token_creation, profile
36
118
  )
37
- click.echo(err=use_stderr)
119
+ redirect_url = f"http://{AUTH_REDIRECT_HOST}:{port}"
38
120
 
39
- if no_browser:
121
+ try:
40
122
  click.echo(
41
- "Skipping automatic browser launch (--no-browser). "
42
- "Please open the URL above manually to continue.",
123
+ f"Waiting for the authentication callback on port {port} ... ",
43
124
  err=use_stderr,
44
125
  )
45
- else:
46
- try:
47
- browser_opened = webbrowser.open(idp_url)
48
- except Exception:
49
- # Browser launch failures vary by platform (webbrowser.Error on
50
- # Cygwin/headless, anything else elsewhere), so catch broadly and
51
- # fall back to the manual URL rather than crashing.
52
- browser_opened = False
53
-
54
- if not browser_opened:
126
+
127
+ idp_url = get_idp_url(
128
+ api_host, owner, redirect_url=redirect_url, session=session
129
+ )
130
+
131
+ click.echo(
132
+ f"Your Workspace's SAML IDP URL is: {click.style(idp_url, bold=True)}",
133
+ err=use_stderr,
134
+ )
135
+ click.echo(err=use_stderr)
136
+
137
+ if no_browser:
55
138
  click.echo(
56
- "Couldn't open a browser automatically. "
139
+ "Skipping automatic browser launch (--no-browser). "
57
140
  "Please open the URL above manually to continue.",
58
141
  err=use_stderr,
59
142
  )
143
+ else:
144
+ try:
145
+ browser_opened = webbrowser.open(idp_url)
146
+ except Exception:
147
+ # Browser launch failures vary by platform (webbrowser.Error on
148
+ # Cygwin/headless, anything else elsewhere), so catch broadly and
149
+ # fall back to the manual URL rather than crashing.
150
+ browser_opened = False
60
151
 
61
- click.echo("Starting webserver to begin authentication ... ", err=use_stderr)
62
-
63
- auth_server = AuthenticationWebServer(
64
- (AUTH_SERVER_HOST, AUTH_SERVER_PORT),
65
- AuthenticationWebRequestHandler,
66
- owner=owner,
67
- session=session,
68
- debug=opts.debug,
69
- refresh_api_on_success=enable_token_creation,
70
- api_opts=opts.api_config,
71
- profile=profile,
72
- )
152
+ if not browser_opened:
153
+ click.echo(
154
+ "Couldn't open a browser automatically. "
155
+ "Please open the URL above manually to continue.",
156
+ err=use_stderr,
157
+ )
73
158
 
74
- auth_server.handle_request()
159
+ auth_server.handle_request()
160
+ finally:
161
+ auth_server.server_close()
75
162
 
76
163
 
77
164
  @main.command(aliases=["auth"])
@@ -165,6 +252,14 @@ def authenticate(
165
252
  err=True,
166
253
  )
167
254
 
255
+ if not force and not token and not request_api_key_flag:
256
+ session_result = _renew_existing_sso_session(
257
+ opts, profile=ctx.meta.get("profile")
258
+ )
259
+ if session_result:
260
+ _report_active_sso_session(opts, session_result, use_stderr)
261
+ return
262
+
168
263
  workspace = opts.org or click.prompt("Workspace", err=use_stderr)
169
264
  workspace = validators.validate_owner(ctx, None, workspace)[0]
170
265
  opts.org = workspace
@@ -14,6 +14,7 @@ from .docker import docker as docker_cmd
14
14
  from .generic import generic as generic_cmd
15
15
  from .manage import install_cmd, list_cmd, uninstall_cmd
16
16
  from .pnpm import pnpm as pnpm_cmd
17
+ from .terraform import terraform as terraform_cmd
17
18
 
18
19
 
19
20
  @click.group()
@@ -50,5 +51,6 @@ credential_helper.add_command(install_cmd, name="install")
50
51
  credential_helper.add_command(uninstall_cmd, name="uninstall")
51
52
  credential_helper.add_command(list_cmd, name="list")
52
53
  credential_helper.add_command(cargo_cmd, name="cargo")
54
+ credential_helper.add_command(terraform_cmd, name="terraform")
53
55
 
54
56
  main.add_command(credential_helper, name="credential-helper")
@@ -15,6 +15,15 @@ import click
15
15
  from cloudsmith_cli.credential_helpers.cargo.installer import CargoInstaller
16
16
  from cloudsmith_cli.credential_helpers.generic import PartialInstallError
17
17
  from cloudsmith_cli.credential_helpers.pnpm.installer import PNPMInstaller
18
+ from cloudsmith_cli.credential_helpers.terraform.installer import (
19
+ TerraformHelperExeNotFound,
20
+ TerraformInstaller,
21
+ TerraformPluginDirError,
22
+ _terraformrc_path,
23
+ )
24
+ from cloudsmith_cli.credential_helpers.terraform.terraformrc import (
25
+ TerraformrcConflictError,
26
+ )
18
27
 
19
28
  from ....credential_helpers.docker.installer import DockerInstaller
20
29
  from ... import utils
@@ -33,6 +42,7 @@ _INSTALLERS: dict[str, type] = {
33
42
  "docker": DockerInstaller,
34
43
  "pnpm": PNPMInstaller,
35
44
  "cargo": CargoInstaller,
45
+ "terraform": TerraformInstaller,
36
46
  }
37
47
 
38
48
 
@@ -65,6 +75,72 @@ def _get_installer(name: str):
65
75
  return cls()
66
76
 
67
77
 
78
+ def _terraform_helper_args(ctx, opts, repo: str | None = None) -> tuple[str, ...]:
79
+ """Build the terraformrc ``args`` list from the resolved config.
80
+
81
+ Baking ``--org``/``-r``/``-P`` into the block means ``terraform init`` works
82
+ with no environment variables and no hand-edited config. Only values
83
+ actually supplied are written, so an install without ``--org``/``--repo``/
84
+ ``-P`` leaves ``args = []``.
85
+
86
+ A non-default ``--config-file``/``--credentials-file`` is also baked in: the
87
+ helper is later invoked by ``terraform init`` in a fresh process with none
88
+ of this invocation's flags, so without pinning them Terraform would resolve
89
+ credentials from the default search path instead of the files the operator
90
+ installed with.
91
+ """
92
+ args: list[str] = []
93
+ if opts.org:
94
+ args.extend(["--org", opts.org])
95
+ if repo:
96
+ args.extend(["-r", repo])
97
+ profile = ctx.meta.get("profile")
98
+ if profile:
99
+ args.extend(["-P", profile])
100
+ config_file = ctx.meta.get("config_file")
101
+ if config_file:
102
+ args.extend(["--config-file", config_file])
103
+ creds_file = ctx.meta.get("creds_file")
104
+ if creds_file:
105
+ args.extend(["--credentials-file", creds_file])
106
+ return tuple(args)
107
+
108
+
109
+ _REPO_FLAGS = ("-r", "--repo", "--repository")
110
+
111
+
112
+ def _terraform_next_steps(helper_args: tuple[str, ...]) -> list[str]:
113
+ """Return the post-install guidance for the required repository.
114
+
115
+ Terraform never tells the credentials helper which repository is being
116
+ requested, so the helper needs the repository supplied out-of-band. If it
117
+ was not baked into the generated ``args`` at install time, tell the user how
118
+ to provide it: set ``CLOUDSMITH_REPO`` or add ``--repo`` to the terraformrc
119
+ ``args``. Returns an empty list when a repository is already configured.
120
+ """
121
+ has_repo = any(
122
+ a in _REPO_FLAGS or a.split("=", 1)[0] in _REPO_FLAGS for a in helper_args
123
+ )
124
+ if has_repo:
125
+ return []
126
+ rc_path = _terraformrc_path()
127
+ return [
128
+ (
129
+ "Next steps: the Terraform helper requires a repository, which"
130
+ " Terraform does not pass to credentials helpers. Provide it in one"
131
+ " of these ways:"
132
+ ),
133
+ (
134
+ " - export CLOUDSMITH_REPO=<your-repo> in the environment that runs"
135
+ " `terraform init`, or"
136
+ ),
137
+ (
138
+ f" - add it to the args in {rc_path}, e.g."
139
+ ' args = ["--repo", "<your-repo>", ...].'
140
+ ),
141
+ ]
142
+
143
+
68
144
  # ---------------------------------------------------------------------------
69
145
  # install
70
146
  # ---------------------------------------------------------------------------
@@ -99,6 +175,16 @@ def _get_installer(name: str):
99
175
  default=False,
100
176
  help="Bypass the custom-domain cache and fetch fresh data from the API.",
101
177
  )
178
+ @click.option(
179
+ "-r",
180
+ "--repo",
181
+ "--repository",
182
+ "repo",
183
+ default=None,
184
+ help="Terraform only: bake this repository into the terraformrc `args` "
185
+ "list (as `-r <repo>`) so `terraform init` needs no CLOUDSMITH_REPO env "
186
+ "var. Terraform does not pass the repository to a credentials helper.",
187
+ )
102
188
  @common_cli_config_options
103
189
  @common_cli_output_options
104
190
  @common_api_auth_options
@@ -113,6 +199,7 @@ def install_cmd(
113
199
  dry_run: bool,
114
200
  no_discover: bool,
115
201
  refresh: bool,
202
+ repo: str | None,
116
203
  ) -> None:
117
204
  """Install a credential helper launcher and configure the package manager.
118
205
 
@@ -140,19 +227,41 @@ def install_cmd(
140
227
  \b
141
228
  # Disable automatic custom-domain discovery
142
229
  $ cloudsmith credential-helper install HELPER --no-discover
230
+
231
+ \b
232
+ # Terraform: bake the repository into the terraformrc args so
233
+ # `terraform init` needs no CLOUDSMITH_REPO env var
234
+ $ cloudsmith credential-helper install terraform --org acme --repo my-repo
143
235
  """
144
236
  installer = _get_installer(helper)
237
+
238
+ install_kwargs = {
239
+ "bin_dir": bin_dir,
240
+ "domains": domains,
241
+ "dry_run": dry_run,
242
+ "discover": not no_discover,
243
+ "refresh": refresh,
244
+ "org": opts.org,
245
+ "credential": opts.credential,
246
+ "api_host": opts.api_host,
247
+ }
248
+
249
+ # Terraform bakes the resolved org/profile into the terraformrc `args` list
250
+ # so `terraform init` needs neither env vars nor a hand-edited config. The
251
+ # wrapper forwards these to the CLI ahead of the hostname at call time.
252
+ helper_args: tuple[str, ...] = ()
253
+ if helper == "terraform":
254
+ helper_args = _terraform_helper_args(ctx, opts, repo)
255
+ install_kwargs["helper_args"] = helper_args
256
+
145
257
  try:
146
- actions = installer.install(
147
- bin_dir=bin_dir,
148
- domains=domains,
149
- dry_run=dry_run,
150
- discover=not no_discover,
151
- refresh=refresh,
152
- org=opts.org,
153
- credential=opts.credential,
154
- api_host=opts.api_host,
155
- )
258
+ actions = installer.install(**install_kwargs)
259
+ except (
260
+ TerraformrcConflictError,
261
+ TerraformHelperExeNotFound,
262
+ TerraformPluginDirError,
263
+ ) as exc:
264
+ raise click.ClickException(str(exc))
156
265
  except OSError as exc:
157
266
  raise click.ClickException(
158
267
  f"Failed to install {helper!r} credential helper: {exc}"
@@ -166,11 +275,17 @@ def install_cmd(
166
275
  use_stderr = utils.should_use_stderr(opts)
167
276
  warnings = [a for a in actions if a.startswith("WARNING")]
168
277
  normal = [a for a in actions if not a.startswith("WARNING")]
278
+
279
+ next_steps: list[str] = []
280
+ if helper == "terraform":
281
+ next_steps = _terraform_next_steps(helper_args)
282
+
169
283
  data = {
170
284
  "helper": helper,
171
285
  "dry_run": dry_run,
172
286
  "actions": normal,
173
287
  "warnings": warnings,
288
+ "next_steps": next_steps,
174
289
  }
175
290
  if utils.maybe_print_as_json(opts, data):
176
291
  sys.exit(ec)
@@ -181,6 +296,8 @@ def install_cmd(
181
296
  click.echo(f" {action}" if dry_run else action, err=use_stderr)
182
297
  for warning in warnings:
183
298
  click.secho(f" {warning}" if dry_run else warning, err=True, fg="yellow")
299
+ for line in next_steps:
300
+ click.secho(line, err=use_stderr, fg="cyan")
184
301
  sys.exit(ec)
185
302
 
186
303
 
@@ -0,0 +1,151 @@
1
+ # Copyright 2026 Cloudsmith Ltd
2
+ """
3
+ Terraform credentials helper command.
4
+
5
+ Implements the ``get`` verb of Terraform's credentials-helper protocol for
6
+ Cloudsmith registries. The installed ``terraform-credentials-cloudsmith``
7
+ launcher forwards Terraform's invocation to this command.
8
+
9
+ See: https://developer.hashicorp.com/terraform/internals/credentials-helpers
10
+ """
11
+
12
+ import sys
13
+
14
+ import click
15
+
16
+ from ....credential_helpers.terraform import execute
17
+ from ...decorators import (
18
+ common_api_auth_options,
19
+ common_cli_config_options,
20
+ resolve_credentials,
21
+ )
22
+
23
+
24
+ @click.command(context_settings={"ignore_unknown_options": True})
25
+ @click.option(
26
+ "-r",
27
+ "--repo",
28
+ "--repository",
29
+ "repo",
30
+ required=True,
31
+ envvar="CLOUDSMITH_REPO",
32
+ help="The Cloudsmith repository the registry serves. Terraform does not "
33
+ "pass the repository to a credentials helper, so it must be configured "
34
+ "here (e.g. in the terraformrc args) to build a repository-scoped token.",
35
+ )
36
+ @click.argument("params", nargs=-1, type=click.UNPROCESSED)
37
+ @common_cli_config_options
38
+ @common_api_auth_options
39
+ @resolve_credentials
40
+ def terraform(opts, repo, params):
41
+ """
42
+ Terraform credentials helper for Cloudsmith registries.
43
+
44
+ Resolves the token for a Cloudsmith Terraform registry and prints it in
45
+ Terraform's expected JSON credentials format: ``{"token": "..."}``.
46
+
47
+ Provides credentials for all Cloudsmith Terraform registries:
48
+ ``*.cloudsmith.io``, ``*.cloudsmith.com``, and any custom domains
49
+ configured for the organisation (requires an organisation - ``--org``,
50
+ CLOUDSMITH_ORG or ``org`` in ``config.ini`` - and a valid API key/token).
51
+
52
+ Accepts Terraform's calling convention — an optional verb (``get``,
53
+ ``store``, ``forget``) followed by the hostname — so the on-PATH launcher
54
+ can forward Terraform's arguments verbatim. When no verb is given the
55
+ action is ``get``. When no hostname is given it is read from stdin. Only
56
+ ``get`` is served; ``store``/``forget`` return an error and a non-zero exit.
57
+ ``store`` first drains its stdin payload (the credentials Terraform sends)
58
+ before erroring, as the protocol requires, so Terraform sees the exit status
59
+ rather than a broken pipe.
60
+
61
+ A hostname that is not a Cloudsmith registry yields an empty object
62
+ (``{}``) and exit 0 so Terraform falls back to its own credential sources.
63
+
64
+ \b
65
+ Input (arguments or stdin):
66
+ [VERB] HOSTNAME — e.g. "get terraform.cloudsmith.io" or just
67
+ "terraform.cloudsmith.io"; HOSTNAME alone may also come from stdin.
68
+
69
+ \b
70
+ Output (stdout):
71
+ JSON: {"token": "<cloudsmith-token>"} (Cloudsmith host, token found)
72
+ JSON: {} (not a Cloudsmith host)
73
+
74
+ \b
75
+ Exit codes:
76
+ 0: Token returned, or the host is not a Cloudsmith registry
77
+ 1: Cloudsmith host with no credentials available, or an error occurred
78
+
79
+ The token is scoped to the repository. On a standard
80
+ ``*.cloudsmith.io``/``*.cloudsmith.com`` host it is ``{org}/{repo}/{token}``
81
+ and the organisation is required (``--org``, ``CLOUDSMITH_ORG`` or ``org``
82
+ in ``config.ini``); a standard host requested without an organisation is a
83
+ non-zero exit. On a custom domain — which is already bound to a single
84
+ organisation — the org is omitted and the token is ``{repo}/{token}``. The
85
+ repository is always required (``-r/--repo/--repository`` or
86
+ ``CLOUDSMITH_REPO``): Terraform does not tell a credentials helper which
87
+ repository is being requested. A non-Cloudsmith host still returns ``{}``
88
+ and exit 0. The profile can also be supplied with ``-P/--profile``. The
89
+ launcher forwards Terraform's ``args`` verbatim, so a terraformrc block such
90
+ as ``credentials_helper "cloudsmith" { args = ["--org=acme",
91
+ "--repo=my-repo", "-P", "ci"] }`` reaches this command as those options.
92
+
93
+ \b
94
+ Examples:
95
+ # Direct usage
96
+ $ cloudsmith credential-helper terraform --repo my-repo terraform.cloudsmith.io
97
+ {"token": "..."}
98
+
99
+ # Terraform's calling convention (verb + hostname)
100
+ $ cloudsmith credential-helper terraform --repo my-repo get terraform.cloudsmith.io
101
+
102
+ # Select an org and profile explicitly (no env vars needed)
103
+ $ cloudsmith credential-helper terraform --org=acme -P ci get terraform.cloudsmith.io
104
+
105
+ \b
106
+ Environment variables:
107
+ CLOUDSMITH_API_KEY: API key for authentication (optional)
108
+ CLOUDSMITH_ORG: Organisation slug (required to scope the token)
109
+ CLOUDSMITH_REPO: Repository slug the registry serves (required)
110
+ CLOUDSMITH_PROFILE: Configuration profile to load (optional)
111
+ """
112
+ # Terraform passes "<verb> <hostname>"; direct/manual use may pass just the
113
+ # hostname (verb defaults to "get") or nothing (hostname read from stdin).
114
+ verb = "get"
115
+ hostname: str | None = None
116
+ if len(params) >= 2:
117
+ verb, hostname = params[-2], params[-1]
118
+ elif len(params) == 1:
119
+ hostname = params[0]
120
+
121
+ # The `store` verb sends the new credentials as a JSON object on stdin. The
122
+ # protocol requires an unsupported store implementation to consume the whole
123
+ # payload before returning an error, otherwise Terraform can see a broken
124
+ # pipe instead of the helper's exit status. Drain stdin here; the payload is
125
+ # discarded because this helper never stores credentials.
126
+ if verb == "store":
127
+ try:
128
+ sys.stdin.read()
129
+ except (OSError, ValueError):
130
+ pass
131
+
132
+ if not hostname:
133
+ try:
134
+ hostname = sys.stdin.read().strip()
135
+ except (OSError, ValueError):
136
+ hostname = ""
137
+
138
+ exit_code, stdout, stderr = execute(
139
+ verb,
140
+ hostname,
141
+ credential=opts.credential,
142
+ api_host=opts.api_host,
143
+ org=opts.org,
144
+ repo=repo,
145
+ )
146
+
147
+ if stdout is not None:
148
+ click.echo(stdout)
149
+ if stderr is not None:
150
+ click.echo(stderr, err=True)
151
+ sys.exit(exit_code)