Skip to content
Draft
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
1 change: 1 addition & 0 deletions ddev/changelog.d/24289.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for the `--env` option to `ddev env agent` to pass environment variables.
38 changes: 31 additions & 7 deletions ddev/src/ddev/cli/env/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,21 @@
from ddev.e2e.agent.interface import AgentInterface


def _invoke_check_with_retry(agent: AgentInterface, args: list[str], *, retries: int = 3, backoff: float = 0.5) -> None:
def _invoke_check_with_retry(
agent: AgentInterface,
args: list[str],
*,
env_vars: dict[str, str] | None = None,
retries: int = 3,
backoff: float = 0.5,
) -> None:
"""Invoke ``agent check`` with bounded retry to absorb transient autodiscovery-reload races."""
import subprocess
import time

for attempt in range(retries + 1):
try:
agent.invoke(args)
agent.invoke(args, env_vars=env_vars)
return
except subprocess.CalledProcessError:
if attempt >= retries:
Expand All @@ -38,8 +45,23 @@ def _invoke_check_with_retry(agent: AgentInterface, args: list[str], *, retries:
@click.argument('environment')
@click.argument('args', required=True, nargs=-1)
@click.option('--config-file', hidden=True)
@click.option(
'--env',
'env_vars',
multiple=True,
metavar='KEY=VALUE',
help='Set an environment variable for this invocation only (may be repeated)',
)
@click.pass_obj
def agent(app: Application, *, intg_name: str, environment: str, args: tuple[str, ...], config_file: str | None):
def agent(
app: Application,
*,
intg_name: str,
environment: str,
args: tuple[str, ...],
config_file: str | None,
env_vars: tuple[str, ...],
):
"""
Invoke the Agent.
"""
Expand All @@ -60,6 +82,8 @@ def agent(app: Application, *, intg_name: str, environment: str, args: tuple[str
agent_type = metadata.get(E2EMetadata.AGENT_TYPE, DEFAULT_AGENT_TYPE)
agent = get_agent_interface(agent_type)(app, integration, environment, metadata, env_data.config_file)

invoke_env_vars = dict(entry.split('=', 1) for entry in env_vars) if env_vars else None

full_args = list(args)
trigger_run = False
if full_args[0] == 'check':
Expand All @@ -75,9 +99,9 @@ def agent(app: Application, *, intg_name: str, environment: str, args: tuple[str
if config_file is None or not trigger_run:
try:
if trigger_run:
_invoke_check_with_retry(agent, full_args)
_invoke_check_with_retry(agent, full_args, env_vars=invoke_env_vars)
else:
agent.invoke(full_args)
agent.invoke(full_args, env_vars=invoke_env_vars)
except subprocess.CalledProcessError as e:
app.abort(code=e.returncode)

Expand All @@ -90,14 +114,14 @@ def agent(app: Application, *, intg_name: str, environment: str, args: tuple[str
if not env_data.config_file.is_file():
try:
env_data.write_config(config)
_invoke_check_with_retry(agent, full_args)
_invoke_check_with_retry(agent, full_args, env_vars=invoke_env_vars)
finally:
env_data.config_file.unlink()
else:
temp_config_file = env_data.config_file.parent / f'{env_data.config_file.name}.bak.example'
env_data.config_file.replace(temp_config_file)
try:
env_data.write_config(config)
_invoke_check_with_retry(agent, full_args)
_invoke_check_with_retry(agent, full_args, env_vars=invoke_env_vars)
finally:
temp_config_file.replace(env_data.config_file)
12 changes: 7 additions & 5 deletions ddev/src/ddev/e2e/agent/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,12 @@ def _python_path(self) -> str:
else f'/opt/datadog-agent/embedded/bin/python{self.python_version[0]}'
)

def _format_command(self, command: list[str]) -> list[str]:
def _format_command(self, command: list[str], *, env_vars: dict[str, str] | None = None) -> list[str]:
cmd = ['docker', 'exec']
if self._isatty:
cmd.append('-it')
for key, value in (env_vars or {}).items():
cmd.extend(['-e', f'{key}={value}'])
cmd.append(self._container_name)

if command[0] == 'pip':
Expand Down Expand Up @@ -364,11 +366,11 @@ def restart(self) -> None:
f'Unable to restart Agent container `{self._container_name}`: {process.stdout.decode("utf-8")}'
)

def invoke(self, args: list[str]) -> None:
self.run_command(['agent', *args])
def invoke(self, args: list[str], *, env_vars: dict[str, str] | None = None) -> None:
self.run_command(['agent', *args], env_vars=env_vars)

def run_command(self, args: list[str]) -> None:
self._run_command(self._format_command([*args]), check=True)
def run_command(self, args: list[str], *, env_vars: dict[str, str] | None = None) -> None:
self._run_command(self._format_command([*args], env_vars=env_vars), check=True)

def enter_shell(self) -> None:
self._run_command(self._format_command(['cmd' if self._is_windows_container else 'bash']), check=True)
Expand Down
2 changes: 1 addition & 1 deletion ddev/src/ddev/e2e/agent/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def stop(self) -> None: ...
def restart(self) -> None: ...

@abstractmethod
def invoke(self, args: list[str]) -> None: ...
def invoke(self, args: list[str], *, env_vars: dict[str, str] | None = None) -> None: ...

@abstractmethod
def enter_shell(self) -> None: ...
5 changes: 4 additions & 1 deletion ddev/src/ddev/e2e/agent/vagrant.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,10 @@ def restart(self) -> None:
self._run_commands(guest_cmds, "restart_agent_service")
self.app.display_info("Datadog Agent service restart sequence completed.")

def invoke(self, args: list[str]) -> None:
def invoke(self, args: list[str], *, env_vars: dict[str, str] | None = None) -> None:
if env_vars:
raise NotImplementedError("Per-invocation env_vars are not supported for the Vagrant agent")

agent_bin = LINUX_AGENT_BIN_PATH if not self._is_windows_vm else WINDOWS_AGENT_BIN_PATH
guest_cmd_parts = ["sudo", agent_bin] + args if not self._is_windows_vm else [agent_bin] + args
host_cmd = self._format_command(guest_cmd_parts)
Expand Down
6 changes: 3 additions & 3 deletions ddev/tests/cli/env/test_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def test_not_trigger_run(ddev, data_dir, mocker):
assert result.exit_code == 0, result.output
assert not result.output

invoke.assert_called_once_with(['status'])
invoke.assert_called_once_with(['status'], env_vars=None)


def test_trigger_run(ddev, data_dir, mocker):
Expand All @@ -51,7 +51,7 @@ def test_trigger_run(ddev, data_dir, mocker):
assert result.exit_code == 0, result.output
assert not result.output

invoke.assert_called_once_with(['check', integration, '-l', 'debug'])
invoke.assert_called_once_with(['check', integration, '-l', 'debug'], env_vars=None)


def test_trigger_run_inject_integration(ddev, data_dir, mocker):
Expand All @@ -67,4 +67,4 @@ def test_trigger_run_inject_integration(ddev, data_dir, mocker):
assert result.exit_code == 0, result.output
assert not result.output

invoke.assert_called_once_with(['check', integration, '-l', 'debug'])
invoke.assert_called_once_with(['check', integration, '-l', 'debug'], env_vars=None)
Loading