From c9dfd78ee7917dcce01cd0a2d0e0c61f52891815 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Veyrenc Date: Mon, 5 Oct 2026 18:05:51 +0200 Subject: [PATCH 1/8] feat: add `install_tool` method to UV tool --- src/dda/tools/uv.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/dda/tools/uv.py b/src/dda/tools/uv.py index 66f176c6..c9e76264 100644 --- a/src/dda/tools/uv.py +++ b/src/dda/tools/uv.py @@ -58,3 +58,12 @@ 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}") From 3656b6f7662b86e3ab1bc123b66cab72b49174de Mon Sep 17 00:00:00 2001 From: Pierre-Louis Veyrenc Date: Mon, 5 Oct 2026 18:06:25 +0200 Subject: [PATCH 2/8] feat: add `dda tools ddgl setup` command --- src/dda/cli/tools/ddgl/__init__.py | 13 ++++++ src/dda/cli/tools/ddgl/setup/__init__.py | 53 ++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 src/dda/cli/tools/ddgl/__init__.py create mode 100644 src/dda/cli/tools/ddgl/setup/__init__.py 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..ddf3c539 --- /dev/null +++ b/src/dda/cli/tools/ddgl/setup/__init__.py @@ -0,0 +1,53 @@ +import click + +from dda.cli.application import Application +from dda.cli.base import dynamic_command, pass_app + + +def _check_ddgl(app: Application) -> bool: + """Check if ddgl is installed and available on $PATH""" + out = app.subprocess.capture(["which", "ddgl"], check=False) + 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): + app.abort( + "ddgl is already installed. Use --force to reinstall and reset config or `dda tool ddgl update` to update.", + code=0, + ) + + 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): + 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"] + """ + ) From f822409c71bc5adee364beba168f0aab6c8a76c7 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Veyrenc Date: Mon, 5 Oct 2026 18:06:35 +0200 Subject: [PATCH 3/8] feat: add `dda tools ddgl update` command --- src/dda/cli/tools/ddgl/update/__init__.py | 18 ++++++++++++++++++ src/dda/tools/uv.py | 15 +++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 src/dda/cli/tools/ddgl/update/__init__.py 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..11051552 --- /dev/null +++ b/src/dda/cli/tools/ddgl/update/__init__.py @@ -0,0 +1,18 @@ +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 c9e76264..00e1b0af 100644 --- a/src/dda/tools/uv.py +++ b/src/dda/tools/uv.py @@ -67,3 +67,18 @@ def install_tool(self, package: str, force: bool = False) -> None: # noqa: FBT0 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}") From 5994a301400d7012a14ee83cd7ec70aca16ff812 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Veyrenc Date: Tue, 6 Oct 2026 10:08:09 +0200 Subject: [PATCH 4/8] chore: format --- src/dda/cli/tools/ddgl/update/__init__.py | 7 ++++++- src/dda/tools/uv.py | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/dda/cli/tools/ddgl/update/__init__.py b/src/dda/cli/tools/ddgl/update/__init__.py index 11051552..4ffed4c1 100644 --- a/src/dda/cli/tools/ddgl/update/__init__.py +++ b/src/dda/cli/tools/ddgl/update/__init__.py @@ -6,7 +6,12 @@ @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.") +@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" diff --git a/src/dda/tools/uv.py b/src/dda/tools/uv.py index 00e1b0af..8ebca8c7 100644 --- a/src/dda/tools/uv.py +++ b/src/dda/tools/uv.py @@ -73,9 +73,9 @@ 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: + if "==" in package: + name, version = package.split("==") + elif "@" in package: name, version = package.split("@") else: name, version = package, "latest" From 45c736e4b19122bc1854ff23a3d21980d74020ed Mon Sep 17 00:00:00 2001 From: Pierre-Louis Veyrenc Date: Tue, 6 Oct 2026 10:51:34 +0200 Subject: [PATCH 5/8] fix: use `dda.utils.platform.which` instead of binary `which` --- src/dda/cli/tools/ddgl/setup/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dda/cli/tools/ddgl/setup/__init__.py b/src/dda/cli/tools/ddgl/setup/__init__.py index ddf3c539..2f88b644 100644 --- a/src/dda/cli/tools/ddgl/setup/__init__.py +++ b/src/dda/cli/tools/ddgl/setup/__init__.py @@ -2,11 +2,12 @@ from dda.cli.application import Application from dda.cli.base import dynamic_command, pass_app +from dda.utils.platform import which def _check_ddgl(app: Application) -> bool: """Check if ddgl is installed and available on $PATH""" - out = app.subprocess.capture(["which", "ddgl"], check=False) + out = which("ddgl") or "" return len(out.strip()) > 0 From 312aaa1a0301f6b19ea1b417b66537541c875cfa Mon Sep 17 00:00:00 2001 From: Pierre-Louis Veyrenc Date: Tue, 6 Oct 2026 14:40:42 +0200 Subject: [PATCH 6/8] feat: add early-exit if ddtool not found --- src/dda/cli/tools/ddgl/setup/__init__.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/dda/cli/tools/ddgl/setup/__init__.py b/src/dda/cli/tools/ddgl/setup/__init__.py index 2f88b644..188c10de 100644 --- a/src/dda/cli/tools/ddgl/setup/__init__.py +++ b/src/dda/cli/tools/ddgl/setup/__init__.py @@ -5,7 +5,7 @@ from dda.utils.platform import which -def _check_ddgl(app: Application) -> bool: +def _check_ddgl() -> bool: """Check if ddgl is installed and available on $PATH""" out = which("ddgl") or "" return len(out.strip()) > 0 @@ -18,12 +18,19 @@ def _check_ddgl(app: Application) -> bool: @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): + 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" From a45c4710b532fc27128e802a2aebc326f98855f9 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Veyrenc Date: Tue, 6 Oct 2026 15:34:56 +0200 Subject: [PATCH 7/8] chore: format --- src/dda/cli/tools/ddgl/setup/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dda/cli/tools/ddgl/setup/__init__.py b/src/dda/cli/tools/ddgl/setup/__init__.py index 188c10de..c762ffb4 100644 --- a/src/dda/cli/tools/ddgl/setup/__init__.py +++ b/src/dda/cli/tools/ddgl/setup/__init__.py @@ -28,7 +28,7 @@ def cmd(app: Application, *, ref: str | None, dev: bool, no_config: bool, force: 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 + code=1, ) if dev: From 7451bed279ac14cde5afe5ad385c629346979ab1 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Veyrenc Date: Tue, 6 Oct 2026 15:36:48 +0200 Subject: [PATCH 8/8] fix --- src/dda/cli/tools/ddgl/setup/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dda/cli/tools/ddgl/setup/__init__.py b/src/dda/cli/tools/ddgl/setup/__init__.py index c762ffb4..3e7596e3 100644 --- a/src/dda/cli/tools/ddgl/setup/__init__.py +++ b/src/dda/cli/tools/ddgl/setup/__init__.py @@ -40,7 +40,7 @@ def cmd(app: Application, *, ref: str | None, dev: bool, no_config: bool, force: app.tools.uv.install_tool(package, force=force) # Make sure ddgl is in path - if not _check_ddgl(app): + if not _check_ddgl(): app.abort("ddgl not found in $PATH after install. Maybe run `uv tool update-shell` ?") if not no_config: