From 6de51b32d79ba8c3d14fc104956125f560eaa77a Mon Sep 17 00:00:00 2001 From: Quentin Santos Date: Tue, 27 Feb 2024 14:38:21 +0100 Subject: [PATCH] New command cargoLogin to login in all cargo registries (#153) * cargoSyncConfig logins in all cargo registries * Add changelog entry * Placate linters * Add comment on ignored error in CargoSyncConfigTask * Move cargo login commands to new CargoLoginTask * Use cargo:token with least priority * Add comment on order of cargo credential providers * Update changelog entry * Linting * Apply suggestions from Nicolas Co-authored-by: Nicolas Trinquier * Use TaskStatus for CargoLoginTask error handling * add log when cargo login command fails --------- Co-authored-by: Quentin Santos Co-authored-by: Nicolas Trinquier Co-authored-by: Niklas Rosenstein --- .changelog/_unreleased.toml | 6 +++ docs/docs/lang/cargo.md | 1 + kraken-build/src/kraken/std/cargo/__init__.py | 21 ++++++++++ .../src/kraken/std/cargo/tasks/cargo_login.py | 38 +++++++++++++++++++ .../std/cargo/tasks/cargo_sync_config_task.py | 11 +++++- 5 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 kraken-build/src/kraken/std/cargo/tasks/cargo_login.py diff --git a/.changelog/_unreleased.toml b/.changelog/_unreleased.toml index 08d8babf..523ac5a5 100644 --- a/.changelog/_unreleased.toml +++ b/.changelog/_unreleased.toml @@ -23,3 +23,9 @@ id = "db65bd12-a9d1-472c-a38b-6f889cdda683" type = "improvement" description = "Move bump version into publish" author = "colin.mikolajczak@helsing.ai" + +[[entries]] +id = "31a52a49-8d26-4dd7-a4bc-84fe9fb37c19" +type = "improvement" +description = "Add task to login in all cargo registries" +author = "quentin.santos@helsing.ai" diff --git a/docs/docs/lang/cargo.md b/docs/docs/lang/cargo.md index 233fe1f3..615563e1 100644 --- a/docs/docs/lang/cargo.md +++ b/docs/docs/lang/cargo.md @@ -25,6 +25,7 @@ cargo_registry( ) cargo_auth_proxy() cargo_sync_config() +cargo_login() cargo_build("debug") cargo_build("release") cargo_publish("artifactory") diff --git a/kraken-build/src/kraken/std/cargo/__init__.py b/kraken-build/src/kraken/std/cargo/__init__.py index 034ec60b..5d9079d0 100644 --- a/kraken-build/src/kraken/std/cargo/__init__.py +++ b/kraken-build/src/kraken/std/cargo/__init__.py @@ -18,6 +18,7 @@ from .tasks.cargo_deny_task import CargoDenyTask from .tasks.cargo_fmt_task import CargoFmtTask from .tasks.cargo_generate_deb import CargoGenerateDebPackage +from .tasks.cargo_login import CargoLoginTask from .tasks.cargo_publish_task import CargoPublishTask from .tasks.cargo_sqlx_database_create import CargoSqlxDatabaseCreateTask from .tasks.cargo_sqlx_database_drop import CargoSqlxDatabaseDropTask @@ -35,6 +36,7 @@ "cargo_deny", "cargo_fmt", "cargo_generate_deb_package", + "cargo_login", "cargo_publish", "cargo_registry", "cargo_sqlx_migrate", @@ -207,6 +209,25 @@ def cargo_sync_config( return task +def cargo_login( + *, + project: Project | None = None, +) -> CargoLoginTask: + """Creates a task that will be added to the build and publish support groups + to login in the Cargo registries""" + + project = project or Project.current() + cargo = CargoProject.get_or_create(project) + task = project.task("cargoLogin", CargoLoginTask, group="apply") + task.registries = Supplier.of_callable(lambda: list(cargo.registries.values())) + project.group(CARGO_BUILD_SUPPORT_GROUP_NAME).add(task) + project.group(CARGO_PUBLISH_SUPPORT_GROUP_NAME).add(task) + + # We need to have the credentials providers set up by cargoSyncConfig + task.depends_on(":cargoSyncConfig") + return task + + def cargo_clippy( *, allow: str = "staged", diff --git a/kraken-build/src/kraken/std/cargo/tasks/cargo_login.py b/kraken-build/src/kraken/std/cargo/tasks/cargo_login.py new file mode 100644 index 00000000..f41201eb --- /dev/null +++ b/kraken-build/src/kraken/std/cargo/tasks/cargo_login.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +from subprocess import run + +from kraken.core import Property, Task, TaskStatus + +from ..config import CargoRegistry + + +class CargoLoginTask(Task): + """This task runs cargo login for each registry.""" + + #: The registries to insert into the configuration. + registries: Property[list[CargoRegistry]] = Property.default_factory(list) + + def execute(self) -> TaskStatus: + for registry in self.registries.get(): + publish_token = registry.publish_token + if publish_token is None: + continue + p = run( + ["cargo", "login", "--registry", registry.alias], + cwd=self.project.directory, + capture_output=True, + input=publish_token.encode(), + ) + if p.returncode != 0: + if p.stderr.endswith(b"\nerror: config.json not found in registry\n"): + # This happens when the project's .cargo/config.toml file + # contains a regitry which does not exist (anymore); since + # that means it is not used, we can just skip configuring + # authentication on this registry + pass + else: + # unknown error, fail normally + self.logger.error("cargo login failed: %s", p.stderr.decode()) + return TaskStatus.failed("could not run cargo login") + return TaskStatus.succeeded() diff --git a/kraken-build/src/kraken/std/cargo/tasks/cargo_sync_config_task.py b/kraken-build/src/kraken/std/cargo/tasks/cargo_sync_config_task.py index 9fe2c43a..01e64e4d 100644 --- a/kraken-build/src/kraken/std/cargo/tasks/cargo_sync_config_task.py +++ b/kraken-build/src/kraken/std/cargo/tasks/cargo_sync_config_task.py @@ -36,10 +36,19 @@ def __init__(self, name: str, project: Project) -> None: def get_file_contents(self, file: Path) -> str | bytes: content = tomli.loads(file.read_text()) if not self.replace.get() and file.exists() else {} - content.setdefault("registry", {})["global-credential-providers"] = ["cargo:token"] + # It seems like credential provider should be provided in reverse order + # of preference. We put cargo:token first, to always use the keyring if + # it is available. + content.setdefault("registry", {})["global-credential-providers"] = [ + "cargo:token", + "cargo:libsecret", + "cargo:macos-keychain", + "cargo:wincred", + ] content.setdefault("registries", {})["crates-io"] = {"protocol": self.crates_io_protocol.get()} for registry in self.registries.get(): content.setdefault("registries", {})[registry.alias] = {"index": registry.index} + if self.git_fetch_with_cli.is_filled(): if self.git_fetch_with_cli.get(): content.setdefault("net", {})["git-fetch-with-cli"] = True