diff --git a/src/dda/cli/tools/ddgl/__init__.py b/src/dda/cli/tools/ddgl/__init__.py new file mode 100644 index 00000000..73786053 --- /dev/null +++ b/src/dda/cli/tools/ddgl/__init__.py @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2026-present Datadog, Inc. +# +# SPDX-License-Identifier: MIT +from __future__ import annotations + +from dda.cli.base import dynamic_group + + +@dynamic_group( + short_help="Manage ddgl", +) +def cmd() -> None: + pass diff --git a/src/dda/cli/tools/ddgl/setup/__init__.py b/src/dda/cli/tools/ddgl/setup/__init__.py new file mode 100644 index 00000000..3e7596e3 --- /dev/null +++ b/src/dda/cli/tools/ddgl/setup/__init__.py @@ -0,0 +1,61 @@ +import click + +from dda.cli.application import Application +from dda.cli.base import dynamic_command, pass_app +from dda.utils.platform import which + + +def _check_ddgl() -> bool: + """Check if ddgl is installed and available on $PATH""" + out = which("ddgl") or "" + return len(out.strip()) > 0 + + +@dynamic_command(short_help="Setup ddgl, creating a config file and adding it to PATH") +@click.option("--dev", is_flag=True, help="Use the latest dev version of ddgl from `main`.") +@click.option("--ref", help="Use ddgl from a specific git ref", default=None) +@click.option("--no-config", is_flag=True, help="Don't overwrite or drop a config file") +@click.option("--force", is_flag=True, help="Force install and overwrite config file") +@pass_app +def cmd(app: Application, *, ref: str | None, dev: bool, no_config: bool, force: bool) -> None: + if not force and _check_ddgl(): + app.abort( + "ddgl is already installed. Use --force to reinstall and reset config or `dda tool ddgl update` to update.", + code=0, + ) + + # Early-exit if ddtool is not on PATH, since the config we drop relies on it for creating gitlab tokens + if not which("ddtool"): + app.abort( + "`ddtool` not found on PATH. This command is only meant for Datadog employees with access to `ddtool`. Please install it using `dogbrew install ddtool`", + code=1, + ) + + if dev: + ref = "main" + + # Use a specific git ref if provided or use latest release on PyPI + package = f"git+https://github.com/DataDog/ddgl-cli@{ref}" if ref else "ddgl" + + app.tools.uv.install_tool(package, force=force) + + # Make sure ddgl is in path + if not _check_ddgl(): + app.abort("ddgl not found in $PATH after install. Maybe run `uv tool update-shell` ?") + + if not no_config: + from dda.utils.fs import Path + + ddgl_config_path = Path(app.subprocess.capture(["ddgl", "config", "path"]).strip()) + if not force and ddgl_config_path.exists(): + app.display_info(f"Config file found at {ddgl_config_path}, not overwriting. Use --force to overwrite.") + return + + # Overwrite the existing file + ddgl_config_path.write_text( + """ + gitlab_url="https://gitlab.ddbuild.io" + github_fallback = true + token_command = ["ddtool", "auth", "gitlab", "token"] + """ + ) diff --git a/src/dda/cli/tools/ddgl/update/__init__.py b/src/dda/cli/tools/ddgl/update/__init__.py new file mode 100644 index 00000000..4ffed4c1 --- /dev/null +++ b/src/dda/cli/tools/ddgl/update/__init__.py @@ -0,0 +1,23 @@ +import click + +from dda.cli.application import Application +from dda.cli.base import dynamic_command, pass_app + + +@dynamic_command(short_help="Upgrade ddgl, optionally to a specific version.") +@click.argument("version", required=False, help="The specific version or git ref to install") +@click.option( + "--git", + "-g", + is_flag=True, + help="Use this flag if you are trying to upgrade to a specific git ref or a version not yet release on PyPI.", +) +@pass_app +def cmd(app: Application, *, git: bool, version: str | None = None) -> None: + package = "ddgl" if not git else "https://github.com/DataDog/ddgl-cli" + if version: + if git: + package += f"@{version}" + else: + package += f"=={version}" + app.tools.uv.upgrade_tool(package) diff --git a/src/dda/tools/uv.py b/src/dda/tools/uv.py index 66f176c6..8ebca8c7 100644 --- a/src/dda/tools/uv.py +++ b/src/dda/tools/uv.py @@ -58,3 +58,27 @@ def virtual_env(self, path: Path) -> VirtualEnv: ) return VirtualEnv(path) + + def install_tool(self, package: str, force: bool = False) -> None: # noqa: FBT001,FBT002 + """ + Run `uv tool install `, installing a tool usable outside of dda. + """ + args = ["tool", "install", package] + if force: + args.append("--force") + self.wait(args, message=f"Installing {package}") + + def upgrade_tool(self, package: str) -> None: + """ + Run `uv tool upgrade `. Use package_name== or repo@ref to install a specific version. + """ + # Extract the version + if "==" in package: + name, version = package.split("==") + elif "@" in package: + name, version = package.split("@") + else: + name, version = package, "latest" + args = ["tool", "upgrade", "--prerelease=allow", package] + self.app.display_debug(f"Running {args}") + self.wait(args, message=f"Upgrading {name} to {version}")