Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
3919c34
feat(no-ticket): install the Cloudsmith Maven tools for Maven, Gradle…
cloudsmith-iduffy Oct 22, 2026
c44465a
fix(no-ticket): ignore comments and tag spaces in .mvn/extensions.xml
cloudsmith-iduffy Oct 22, 2026
d993893
feat(no-ticket): set up the Maven tools for every build, with --local…
cloudsmith-iduffy Oct 22, 2026
c8817f8
fix(no-ticket): keep other Maven extensions and use the command's TLS…
cloudsmith-iduffy Oct 22, 2026
c9e9890
Merge origin/master into iduffy/credential-helper-jvm
cloudsmith-iduffy Oct 28, 2026
60aa3fe
refactor(no-ticket): let Click check the Maven and sbt options
cloudsmith-iduffy Oct 28, 2026
8db4db5
Merge branch 'master' into iduffy/credential-helper-jvm
cloudsmith-bot Oct 28, 2026
383554c
Merge remote-tracking branch 'origin/master' into iduffy/credential-h…
Copilot Nov 2, 2026
2f1c291
Merge remote-tracking branch 'origin/master' into HEAD
cloudsmith-iduffy Nov 2, 2026
a821cf5
Keep other extensions in the Maven extension class path
cloudsmith-iduffy Nov 3, 2026
9d6bb25
Keep Gradle and sbt settings that install did not write
cloudsmith-iduffy Nov 3, 2026
7e9456a
List the sbt plugin file of the current project
cloudsmith-iduffy Nov 3, 2026
771b168
Look for the JAR only in the last Maven class path value
cloudsmith-iduffy Nov 3, 2026
36c60c7
Find the Maven extension with spaces in its child tags
cloudsmith-iduffy Nov 3, 2026
528a8c9
Find the closing extensions tag with spaces before its >
cloudsmith-iduffy Nov 3, 2026
bf49683
Merge origin/master into iduffy/credential-helper-jvm
cloudsmith-iduffy Nov 3, 2026
87ece28
Keep comments next to the Maven extension on uninstall
cloudsmith-iduffy Nov 3, 2026
7a5d820
Skip unreadable Maven config files in status
cloudsmith-iduffy Nov 3, 2026
326d636
Keep config file bytes that are not UTF-8
cloudsmith-iduffy Nov 3, 2026
37d2a18
Replace a downloaded Maven tools file only after the write succeeds
cloudsmith-iduffy Nov 3, 2026
b9872d4
Report only owned Gradle and sbt files, and accept non-UTF-8 XML
cloudsmith-iduffy Nov 3, 2026
d821e32
Escape shell characters in the JAR path of the .mavenrc block
cloudsmith-iduffy Nov 3, 2026
5100305
Write Maven tools config files atomically and keep lines apart on rem…
cloudsmith-iduffy Nov 3, 2026
14c3ccc
Create the temporary config copy with the mode of the file it replaces
cloudsmith-iduffy Nov 3, 2026
4620253
Find a Maven extension with namespace prefixes or attributes
cloudsmith-iduffy Nov 3, 2026
4dd8628
Add the Maven extension with the prefix of a prefixed extensions element
cloudsmith-iduffy Nov 3, 2026
cd7273c
Find sbt and Maven projects the way sbt and Maven do
cloudsmith-iduffy Nov 3, 2026
df934d5
Change only a class path option that starts a word of MAVEN_OPTS
cloudsmith-iduffy Nov 3, 2026
cf5441e
Find the Maven class path option after a tab or a new line
cloudsmith-iduffy Nov 3, 2026
738c9ef
Keep an extensions.xml with comments when uninstall removes the last …
cloudsmith-iduffy Nov 3, 2026
6356d43
Turn off the other user-level Maven activation on install
cloudsmith-iduffy Nov 3, 2026
a09bf0f
Find an sbt build that has only .sbt or .scala files in project/
cloudsmith-iduffy Nov 3, 2026
e099c11
Merge branch 'master' into iduffy/credential-helper-jvm
cloudsmith-iduffy Nov 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added

- Added `cloudsmith credential-helper install maven`, `install gradle`, and `install sbt`. They set up the Cloudsmith Maven tools, which get repository credentials from `cloudsmith credential-helper generic`. Each command installs the newest release from the public `cloudsmith/maven-tools` repository, for every build of the user.
- `maven` puts the extension in `~/.m2/repository` and adds it to `~/.m2/extensions.xml`, which Maven 3.10 and later read. When `mvn -v` reports an earlier Maven, the command shows a warning. With `--local`, the command adds the extension to `.mvn/extensions.xml` of the Maven project in the current directory instead. With `--mavenrc`, it adds a block to `~/.mavenrc` instead, which the `mvn` and `mvnw` scripts of all Maven versions read.
- `gradle` writes an init script to `~/.gradle/init.d` (or `$GRADLE_USER_HOME/init.d`), which applies the plugin to every Gradle build.
- `sbt` writes a global plugin to `~/.sbt/1.0/plugins`, which adds the plugin to every sbt 1 build. With `--local`, it writes `project/cloudsmith.sbt` of the sbt project in the current directory instead.
- The commands add to existing files and keep their other content. `uninstall` removes only what `install` added.

### Changed

- After a SAML login, `cloudsmith auth` saves the Workspace to `config.ini`. It writes to the `[profile:NAME]` section of the selected profile, or to `[default]` when you do not select a profile. The CLI writes to the file or directory that `--config-file` gives, or else to your user-level `config.ini`. If the `config.ini` does not exist, the CLI creates it.
Expand Down
98 changes: 95 additions & 3 deletions cloudsmith_cli/cli/commands/credential_helper/manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,16 @@
from cloudsmith_cli.core.cache_utils import ConfigFileParseError
from cloudsmith_cli.credential_helpers.cargo.installer import CargoInstaller
from cloudsmith_cli.credential_helpers.generic import PartialInstallError
from cloudsmith_cli.credential_helpers.gradle.installer import GradleInstaller
from cloudsmith_cli.credential_helpers.launchers import UnsafeLauncherValue
from cloudsmith_cli.credential_helpers.maven.extensions import ExtensionsFileError
from cloudsmith_cli.credential_helpers.maven.installer import MavenInstaller
from cloudsmith_cli.credential_helpers.maven_tools import (
ConfigFileConflict,
ProjectNotFound,
)
from cloudsmith_cli.credential_helpers.pnpm.installer import PNPMInstaller
from cloudsmith_cli.credential_helpers.sbt.installer import SbtInstaller
from cloudsmith_cli.credential_helpers.terraform.installer import (
TerraformHelperExeNotFound,
TerraformInstaller,
Expand All @@ -29,6 +37,7 @@
TerraformrcConflictError,
)

from ....core.session import create_requests_session
from ....credential_helpers.docker.installer import DockerInstaller
from ... import utils
from ...decorators import (
Expand All @@ -47,6 +56,9 @@
"pnpm": PNPMInstaller,
"cargo": CargoInstaller,
"terraform": TerraformInstaller,
"maven": MavenInstaller,
"gradle": GradleInstaller,
"sbt": SbtInstaller,
}


Expand Down Expand Up @@ -79,6 +91,42 @@ def _get_installer(name: str):
return cls()


class HelperOption(click.Option):
"""A flag that only the helpers in *helpers* accept."""

def __init__(self, *args, helpers: tuple[str, ...], **kwargs):
super().__init__(*args, **kwargs)
self.helpers = helpers


def _helper_options(ctx: click.Context, helper: str) -> dict[str, bool]:
"""Return the helper flags that were given, or fail if *helper* does not accept one."""
given = {}
for param in ctx.command.params:
if not isinstance(param, HelperOption) or not ctx.params[param.name]:
continue
if helper not in param.helpers:
raise click.UsageError(
f"{param.opts[0]} applies only to {' and '.join(param.helpers)}.", ctx
)
given[param.name] = True
return given


def _download_session(opts):
"""Return a session with the proxy and TLS settings of the command.

The session goes to the public maven-tools repository, so it does not get
the --api-headers that are for the API host.
"""
return create_requests_session(
proxy=opts.proxy,
ssl_verify=opts.ssl_verify,
ssl_verify_strict=opts.ssl_verify_strict,
user_agent=opts.api_user_agent,
)


def _terraform_helper_args(ctx, opts, repo: str | None = None) -> tuple[str, ...]:
"""Build the terraformrc ``args`` list from the resolved config.

Expand Down Expand Up @@ -290,6 +338,24 @@ def _terraform_workspace_next_steps(helper_args: tuple[str, ...]) -> list[str]:
"list (as `-r <repo>`) so `terraform init` needs no CLOUDSMITH_REPO env "
"var. Terraform does not pass the repository to a credentials helper.",
)
@click.option(
"--local",
cls=HelperOption,
helpers=("maven", "sbt"),
is_flag=True,
default=False,
help="Maven and sbt only: set up the project in the current directory "
"instead of every build of the user.",
)
@click.option(
"--mavenrc",
cls=HelperOption,
helpers=("maven",),
is_flag=True,
default=False,
help="Maven only: load the extension through ~/.mavenrc instead of "
"~/.m2/extensions.xml. Use it for Maven 3.9 and earlier.",
)
@common_cli_config_options
@common_cli_output_options
@common_api_auth_options
Expand All @@ -305,6 +371,8 @@ def install_cmd(
no_discover: bool,
refresh: bool,
repo: str | None,
local: bool,
mavenrc: bool,
) -> None:
"""Install a credential helper launcher and configure the package manager.

Expand Down Expand Up @@ -339,6 +407,9 @@ def install_cmd(
$ cloudsmith credential-helper install terraform -w acme --repo my-repo
"""
installer = _get_installer(helper)
helper_options = _helper_options(ctx, helper)
if local and mavenrc:
raise click.UsageError("Use --local or --mavenrc, not both.", ctx)

install_kwargs = {
"bin_dir": bin_dir,
Expand All @@ -349,7 +420,10 @@ def install_cmd(
"org": opts.org,
"credential": opts.credential,
"api_host": opts.api_host,
**helper_options,
}
if getattr(installer, "downloads_files", False):
install_kwargs["session"] = _download_session(opts)

# Terraform bakes the resolved org/profile into the terraformrc `args` list
# so `terraform init` needs neither env vars nor a hand-edited config. The
Expand All @@ -369,6 +443,9 @@ def install_cmd(
TerraformrcConflictError,
TerraformHelperExeNotFound,
TerraformPluginDirError,
ProjectNotFound,
ExtensionsFileError,
ConfigFileConflict,
UnsafeLauncherValue,
) as exc:
raise click.ClickException(str(exc))
Expand Down Expand Up @@ -434,10 +511,21 @@ def install_cmd(
default=False,
help="Show what would be done without making any changes.",
)
@click.option(
"--local",
cls=HelperOption,
helpers=("maven", "sbt"),
is_flag=True,
default=False,
help="Maven and sbt only: remove the setup of the project in the current "
"directory.",
)
@common_cli_config_options
@common_cli_output_options
@click.pass_context
def uninstall_cmd(ctx, opts, helper: str, bin_dir: str | None, dry_run: bool) -> None:
def uninstall_cmd(
ctx, opts, helper: str, bin_dir: str | None, dry_run: bool, local: bool
) -> None:
"""Uninstall a credential helper launcher and remove its config entries.

HELPER is the name of the credential helper to uninstall (e.g. ``docker``).
Expand All @@ -454,8 +542,12 @@ def uninstall_cmd(ctx, opts, helper: str, bin_dir: str | None, dry_run: bool) ->
"""
installer = _get_installer(helper)
try:
actions = installer.uninstall(bin_dir=bin_dir, dry_run=dry_run)
except ConfigFileParseError as exc:
actions = installer.uninstall(
bin_dir=bin_dir,
dry_run=dry_run,
**_helper_options(ctx, helper),
)
except (ConfigFileParseError, ProjectNotFound, ConfigFileConflict) as exc:
raise click.ClickException(str(exc))
except OSError as exc:
raise click.ClickException(
Expand Down
Loading
Loading