Skip to content

Commit ea6cf80

Browse files
authored
feat: checks for profiles and env vars for conditional services (#185)
1 parent e30a687 commit ea6cf80

File tree

2 files changed

+87
-15
lines changed

2 files changed

+87
-15
lines changed

dreadnode/cli/platform/docker_.py

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,19 @@
1111
from dreadnode.cli.api import create_api_client
1212
from dreadnode.cli.platform.constants import DEFAULT_DOCKER_PROJECT_NAME, PlatformService
1313
from dreadnode.cli.platform.schemas import LocalVersionSchema
14+
from dreadnode.cli.platform.utils.env_mgmt import read_env_file
1415
from dreadnode.cli.platform.utils.printing import print_error, print_info, print_success
1516

1617
DockerContainerState = t.Literal[
1718
"running", "exited", "paused", "restarting", "removing", "created", "dead"
1819
]
1920

2021

22+
# create a DockerError exception that I can catch
23+
class DockerError(Exception):
24+
pass
25+
26+
2127
class CaptureOutput(str, Enum):
2228
TRUE = "true"
2329
FALSE = "false"
@@ -127,6 +133,9 @@ def _build_docker_compose_base_command(
127133
for compose_file in compose_files:
128134
cmds.extend(["-f", compose_file.as_posix()])
129135

136+
for profile in _get_profiles_to_enable(selected_version):
137+
cmds.extend(["--profile", profile])
138+
130139
if selected_version.arg_overrides_env_file.exists():
131140
env_files.append(selected_version.arg_overrides_env_file)
132141

@@ -176,6 +185,61 @@ def get_required_service_names(selected_version: LocalVersionSchema) -> list[str
176185
return [name for name, cfg in services.items() if isinstance(cfg, dict) and "x-required" in cfg]
177186

178187

188+
def _get_profiles_to_enable(selected_version: LocalVersionSchema) -> list[str]:
189+
"""Get the list of profiles to enable based on environment variables.
190+
191+
If any of the `x-profile-disabled-vars` are set in the environment,
192+
the profile will be disabled.
193+
194+
E.g.
195+
196+
services:
197+
myservice:
198+
image: myimage:latest
199+
profiles:
200+
- myprofile
201+
x-profile-override-vars:
202+
- MY_SERVICE_HOST
203+
204+
If MY_SERVICE_HOST is set in the environment, the `myprofile` profile
205+
will NOT be excluded from the docker compose --profile <profile> cmd.
206+
207+
Args:
208+
selected_version: The selected version of the platform.
209+
210+
Returns:
211+
List of profile names to enable.
212+
"""
213+
214+
contents: dict[str, t.Any] = yaml.safe_load(selected_version.compose_file.read_text())
215+
services = contents.get("services", {}) or {}
216+
profiles_to_enable: set[str] = set()
217+
for service in services.values():
218+
if not isinstance(service, dict):
219+
continue
220+
profiles = service.get("profiles", [])
221+
if not profiles or not isinstance(profiles, list):
222+
continue
223+
x_override_vars = service.get("x-profile-override-vars", [])
224+
if not x_override_vars or not isinstance(x_override_vars, list):
225+
profiles_to_enable.update(profiles)
226+
continue
227+
228+
configuration_file = selected_version.configure_overrides_env_file
229+
overrides_file = selected_version.arg_overrides_env_file
230+
env_vars = {}
231+
if configuration_file.exists():
232+
env_vars.update(read_env_file(configuration_file))
233+
if overrides_file.exists():
234+
env_vars.update(read_env_file(overrides_file))
235+
# check if any of the override vars are set in the env
236+
if any(var in env_vars for var in x_override_vars):
237+
continue # skip enabling this profile
238+
profiles_to_enable.update(profiles)
239+
240+
return list(profiles_to_enable)
241+
242+
179243
def _run_docker_compose_command(
180244
args: list[str],
181245
timeout: int = 300,
@@ -224,15 +288,15 @@ def _run_docker_compose_command(
224288

225289
except subprocess.CalledProcessError as e:
226290
print_error(f"{cmd_str} failed with exit code {e.returncode}")
227-
raise
291+
raise DockerError(f"Docker command failed: {e}") from e
228292

229-
except subprocess.TimeoutExpired:
293+
except subprocess.TimeoutExpired as e:
230294
print_error(f"{cmd_str} timed out after {timeout} seconds")
231-
raise
295+
raise DockerError(f"Docker command timed out after {timeout} seconds") from e
232296

233-
except FileNotFoundError:
297+
except FileNotFoundError as e:
234298
print_error("Docker or docker compose not found. Please ensure Docker is installed.")
235-
raise
299+
raise DockerError(f"Docker compose file not found: {e}") from e
236300

237301
return result
238302

@@ -269,7 +333,7 @@ def get_available_local_images() -> list[DockerImage]:
269333
capture_output=True,
270334
)
271335
images: list[DockerImage] = []
272-
for line in cp.stdout.splitlines()[1:]: # Skip header line
336+
for line in cp.stdout.splitlines():
273337
if line.strip():
274338
img = DockerImage.from_string(line.strip())
275339
images.append(img)

dreadnode/cli/platform/start.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from dreadnode.cli.platform.docker_ import (
2+
DockerError,
23
docker_login,
34
docker_requirements_met,
45
docker_run,
6+
docker_stop,
57
get_available_local_images,
68
get_env_var_from_container,
79
get_required_images,
@@ -60,12 +62,18 @@ def start_platform(tag: str | None = None, **env_overrides: str) -> None:
6062
write_overrides_env(selected_version.arg_overrides_env_file, **env_overrides)
6163

6264
print_info(f"Starting platform: {selected_version.tag}")
63-
docker_run(selected_version)
64-
print_success(f"Platform {selected_version.tag} started successfully.")
65-
origin = get_env_var_from_container("dreadnode-ui", "ORIGIN")
66-
if origin:
67-
print_info("You can access the app at the following URLs:")
68-
print_info(f" - {origin}")
69-
else:
70-
print_info(" - Unable to determine the app URL.")
71-
print_info("Please check the container logs for more information.")
65+
try:
66+
docker_run(selected_version)
67+
print_success(f"Platform {selected_version.tag} started successfully.")
68+
origin = get_env_var_from_container("dreadnode-ui", "ORIGIN")
69+
if origin:
70+
print_info("You can access the app at the following URLs:")
71+
print_info(f" - {origin}")
72+
else:
73+
print_info(" - Unable to determine the app URL.")
74+
print_info("Please check the container logs for more information.")
75+
except DockerError as e:
76+
print_error(f"Failed to start platform {selected_version.tag}: {e}")
77+
print_info("Stopping any partially started containers...")
78+
docker_stop(selected_version)
79+
print_info("You can check the logs for more details.")

0 commit comments

Comments
 (0)