Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

- Add `build bazel` command
- Add `tools bazel` command group
- Add `env qa` command group for managing QA environments
- Add the `linux-container` QA environment type

***Fixed:***

Expand Down
11 changes: 11 additions & 0 deletions docs/reference/interface/env/metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Environment metadata

-----

::: dda.env.models.EnvironmentMetadata

::: dda.env.models.EnvironmentNetworkMetadata

::: dda.env.models.EnvironmentPortMetadata

::: dda.env.models.EnvironmentPort
15 changes: 15 additions & 0 deletions docs/reference/interface/env/types/qa.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# QA environment interface

-----

Environment types implementing the [`QAEnvironmentInterface`][dda.env.qa.interface.QAEnvironmentInterface] interface may be managed by the [`env qa`](../../../cli/commands.md#dda-env-qa) command group.

::: dda.env.qa.interface.QAEnvironmentConfig
options:
show_labels: true
unwrap_annotated: true

::: dda.env.qa.interface.QAEnvironmentInterface
options:
show_labels: true
show_if_no_docstring: false
3 changes: 2 additions & 1 deletion hatch.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ extra-dependencies = [
extra-dependencies = [
"mypy",
"pytest",
"types-pyyaml",
]
[envs.types.scripts]
check = "mypy --install-types --non-interactive {args:src/dda tests}"
check = "mypy {args:src/dda tests}"

[envs.docs]
dependencies = [
Expand Down
4 changes: 3 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,11 @@ nav:
- Interface:
- Tool: reference/interface/tool.md
- Environments:
- Status: reference/interface/env/status.md
- Types:
- Developer: reference/interface/env/types/dev.md
- QA: reference/interface/env/types/qa.md
- Status: reference/interface/env/status.md
- Metadata: reference/interface/env/metadata.md
- Guidelines:
- CLI: guidelines/cli.md
- Documentation: guidelines/docs.md
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ dependencies = [
"psutil~=7.0",
"pyjson5~=1.6.9",
"pywinpty~=2.0.15; sys_platform == 'win32'",
"pyyaml~=6.0.2",
"rich~=14.0",
"rich-click~=1.8.9",
"tomlkit~=0.13",
Expand Down Expand Up @@ -178,7 +179,6 @@ legacy-notifications = [
"codeowners==0.6.0",
"invoke==2.2.0",
"requests==2.32.3",
"pyyaml==6.0.1",
"slack-sdk~=3.27.1",
"tabulate[widechars]==0.9.0",
]
Expand Down
13 changes: 13 additions & 0 deletions src/dda/cli/env/qa/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: 2025-present Datadog, Inc. <dev@datadoghq.com>
#
# SPDX-License-Identifier: MIT
from __future__ import annotations

from dda.cli.base import dynamic_group


@dynamic_group(
short_help="Work with QA environments",
)
def cmd() -> None:
pass
13 changes: 13 additions & 0 deletions src/dda/cli/env/qa/config/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: 2025-present Datadog, Inc. <dev@datadoghq.com>
#
# SPDX-License-Identifier: MIT
from __future__ import annotations

from dda.cli.base import dynamic_group


@dynamic_group(
short_help="Manage Agent configuration",
)
def cmd() -> None:
pass
35 changes: 35 additions & 0 deletions src/dda/cli/env/qa/config/explore/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# SPDX-FileCopyrightText: 2025-present Datadog, Inc. <dev@datadoghq.com>
#
# SPDX-License-Identifier: MIT
from __future__ import annotations

from typing import TYPE_CHECKING

import click

from dda.cli.base import dynamic_command, pass_app
from dda.cli.env.qa.utils import option_env_type

if TYPE_CHECKING:
from dda.cli.application import Application


@dynamic_command(short_help="Open the Agent config location in your file manager")
@option_env_type()
@click.option("--id", "instance", default="default", help="Unique identifier for the environment")
@pass_app
def cmd(app: Application, *, env_type: str, instance: str) -> None:
"""
Open the Agent config location in your file manager.
"""
from dda.env.qa import get_qa_env

env = get_qa_env(env_type)(
app=app,
name=env_type,
instance=instance,
)
if not env.agent_config_dir.is_dir():
app.abort(f"QA environment `{instance}` of type `{env_type}` does not exist")

click.launch(str(env.agent_config.path), locate=True)
35 changes: 35 additions & 0 deletions src/dda/cli/env/qa/config/find/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# SPDX-FileCopyrightText: 2025-present Datadog, Inc. <dev@datadoghq.com>
#
# SPDX-License-Identifier: MIT
from __future__ import annotations

from typing import TYPE_CHECKING

import click

from dda.cli.base import dynamic_command, pass_app
from dda.cli.env.qa.utils import option_env_type

if TYPE_CHECKING:
from dda.cli.application import Application


@dynamic_command(short_help="Output the location of the Agent config")
@option_env_type()
@click.option("--id", "instance", default="default", help="Unique identifier for the environment")
@pass_app
def cmd(app: Application, *, env_type: str, instance: str) -> None:
"""
Output the location of the Agent config.
"""
from dda.env.qa import get_qa_env

env = get_qa_env(env_type)(
app=app,
name=env_type,
instance=instance,
)
if not env.agent_config_dir.is_dir():
app.abort(f"QA environment `{instance}` of type `{env_type}` does not exist")

app.display(str(env.agent_config.root_dir))
36 changes: 36 additions & 0 deletions src/dda/cli/env/qa/config/show/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# SPDX-FileCopyrightText: 2025-present Datadog, Inc. <dev@datadoghq.com>
#
# SPDX-License-Identifier: MIT
from __future__ import annotations

from typing import TYPE_CHECKING

import click

from dda.cli.base import dynamic_command, pass_app
from dda.cli.env.qa.utils import option_env_type

if TYPE_CHECKING:
from dda.cli.application import Application


@dynamic_command(short_help="Show the Agent configuration")
@option_env_type()
@click.option("--id", "instance", default="default", help="Unique identifier for the environment")
@pass_app
def cmd(app: Application, *, env_type: str, instance: str) -> None:
"""
Show the Agent configuration.
"""
from dda.cli.env.qa.config.utils import get_agent_config_info
from dda.env.qa import get_qa_env

env = get_qa_env(env_type)(
app=app,
name=env_type,
instance=instance,
)
if not env.agent_config_dir.is_dir():
app.abort(f"QA environment `{instance}` of type `{env_type}` does not exist")

app.display_table(get_agent_config_info(env.agent_config))
41 changes: 41 additions & 0 deletions src/dda/cli/env/qa/config/sync/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# SPDX-FileCopyrightText: 2025-present Datadog, Inc. <dev@datadoghq.com>
#
# SPDX-License-Identifier: MIT
from __future__ import annotations

from typing import TYPE_CHECKING

import click

from dda.cli.base import dynamic_command, pass_app
from dda.cli.env.qa.utils import option_env_type

if TYPE_CHECKING:
from dda.cli.application import Application


@dynamic_command(short_help="Sync the Agent configuration")
@option_env_type()
@click.option("--id", "instance", default="default", help="Unique identifier for the environment")
@pass_app
def cmd(app: Application, *, env_type: str, instance: str) -> None:
"""
Sync the Agent configuration.
"""
from dda.env.models import EnvironmentState
from dda.env.qa import get_qa_env

env = get_qa_env(env_type)(
app=app,
name=env_type,
instance=instance,
)
status = env.status()
expected_state = EnvironmentState.STARTED
if status.state != expected_state:
app.abort(
f"Cannot sync Agent configuration for QA environment `{instance}` of type `{env_type}` in state "
f"`{status.state}`, must be `{expected_state}`"
)

env.sync_agent_config()
13 changes: 13 additions & 0 deletions src/dda/cli/env/qa/config/templates/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# SPDX-FileCopyrightText: 2025-present Datadog, Inc. <dev@datadoghq.com>
#
# SPDX-License-Identifier: MIT
from __future__ import annotations

from dda.cli.base import dynamic_group


@dynamic_group(
short_help="Manage Agent config templates",
)
def cmd() -> None:
pass
31 changes: 31 additions & 0 deletions src/dda/cli/env/qa/config/templates/create/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# SPDX-FileCopyrightText: 2025-present Datadog, Inc. <dev@datadoghq.com>
#
# SPDX-License-Identifier: MIT
from __future__ import annotations

from typing import TYPE_CHECKING

import click

from dda.cli.base import dynamic_command, pass_app

if TYPE_CHECKING:
from dda.cli.application import Application


@dynamic_command(short_help="Create a new Agent config template")
@click.argument("name")
@pass_app
def cmd(app: Application, *, name: str) -> None:
"""
Create a new Agent config template.
"""
from dda.env.config.agent import AgentConfigTemplates

templates = AgentConfigTemplates(app)
template = templates.get(name)
if template.exists():
app.abort(f"Template already exists: {name}")

template.restore_defaults()
app.display(f"Template created: {name}")
46 changes: 46 additions & 0 deletions src/dda/cli/env/qa/config/templates/explore/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# SPDX-FileCopyrightText: 2025-present Datadog, Inc. <dev@datadoghq.com>
#
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2025-present Datadog, Inc. <dev@datadoghq.com>
#
# SPDX-License-Identifier: MIT
from __future__ import annotations

from typing import TYPE_CHECKING

import click

from dda.cli.base import dynamic_command, pass_app

if TYPE_CHECKING:
from dda.cli.application import Application


@dynamic_command(short_help="Open the Agent config templates location in your file manager")
@click.argument("name", required=False)
@pass_app
def cmd(app: Application, *, name: str | None) -> None:
"""
Open the Agent config templates location in your file manager.
"""
from dda.env.config.agent import AgentConfigTemplates

templates = AgentConfigTemplates(app)
if not name:
default_template = templates.get("default")
if default_template.exists():
location = default_template.root_dir
elif templates.root_dir.is_dir() and (entries := list(templates.root_dir.iterdir())):
location = entries[0]
else:
default_template.restore_defaults()
location = default_template.root_dir

click.launch(str(location), locate=True)
return

template = templates.get(name)
if not template.exists():
app.abort(f"Template not found: {name}")

click.launch(str(template.path), locate=True)
37 changes: 37 additions & 0 deletions src/dda/cli/env/qa/config/templates/find/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# SPDX-FileCopyrightText: 2025-present Datadog, Inc. <dev@datadoghq.com>
#
# SPDX-License-Identifier: MIT
from __future__ import annotations

from typing import TYPE_CHECKING

import click

from dda.cli.base import dynamic_command, pass_app

if TYPE_CHECKING:
from dda.cli.application import Application


@dynamic_command(short_help="Output the location of Agent config templates")
@click.argument("name", required=False)
@pass_app
def cmd(app: Application, *, name: str | None) -> None:
"""
Output the location of Agent config templates.
"""
from dda.env.config.agent import AgentConfigTemplates

templates = AgentConfigTemplates(app)
if not name:
if not any(templates):
templates.get("default").restore_defaults()

app.display(str(templates.root_dir))
return

template = templates.get(name)
if not template.exists():
app.abort(f"Template not found: {name}")

app.display(str(template.root_dir))
Loading
Loading