Skip to content
Merged
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
176 changes: 118 additions & 58 deletions devservices/commands/up.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from argparse import Namespace

from sentry_sdk import capture_exception
from sentry_sdk import set_context
from sentry_sdk import start_span

from devservices.constants import CONFIG_FILE_NAME
from devservices.constants import DEPENDENCY_CONFIG_VERSION
Expand Down Expand Up @@ -92,47 +94,58 @@ def up(args: Namespace, existing_status: Status | None = None) -> None:
state = State()

with Status(
lambda: console.warning(f"Starting '{service.name}' in mode: '{mode}'")
if existing_status is None
else existing_status.warning(f"Starting '{service.name}' in mode: '{mode}'"),
lambda: console.success(f"{service.name} started")
if existing_status is None
else existing_status.success(f"{service.name} started"),
lambda: (
console.warning(f"Starting '{service.name}' in mode: '{mode}'")
if existing_status is None
else existing_status.warning(f"Starting '{service.name}' in mode: '{mode}'")
),
lambda: (
console.success(f"{service.name} started")
if existing_status is None
else existing_status.success(f"{service.name} started")
),
) as status:
services_with_local_runtime = state.get_services_by_runtime(
ServiceRuntime.LOCAL
)
local_runtime_dependency_names = set()
for service_with_local_runtime in services_with_local_runtime:
if (
mode in modes
and service_with_local_runtime != service.name
and service_with_local_runtime in modes[mode]
):
local_runtime_dependency_names.add(service_with_local_runtime)
if exclude_local:
status.warning(
f"Skipping '{service_with_local_runtime}' as it is set to run locally"
)
try:
status.info("Retrieving dependencies")
remote_dependencies = install_and_verify_dependencies(
service, force_update_dependencies=True, modes=[mode]
with start_span(
op="service.dependencies.check", name="Check local runtime dependencies"
) as span:
services_with_local_runtime = state.get_services_by_runtime(
ServiceRuntime.LOCAL
)
except DependencyError as de:
capture_exception(de)
status.failure(
f"{str(de)}. If this error persists, try running `devservices purge`"
for service_with_local_runtime in services_with_local_runtime:
if (
mode in modes
and service_with_local_runtime != service.name
and service_with_local_runtime in modes[mode]
):
local_runtime_dependency_names.add(service_with_local_runtime)
if exclude_local:
status.warning(
f"Skipping '{service_with_local_runtime}' as it is set to run locally"
)
span.set_data("service_name", service.name)
span.set_data("mode", mode)
span.set_data(
"local_runtime_dependency_count", len(local_runtime_dependency_names)
)
exit(1)
except ModeDoesNotExistError as mde:
status.failure(str(mde))
exit(1)
try:
_create_devservices_network()
except subprocess.CalledProcessError:
# Network already exists, ignore the error
pass
span.set_data(
"local_runtime_dependency_names", local_runtime_dependency_names
)

context_name = f"local_runtime_dependencies.{service.name}"
set_context(
context_name,
{
"service_name": service.name,
"mode": mode,
"exclude_local": exclude_local,
"count": len(local_runtime_dependency_names),
"names": list(local_runtime_dependency_names),
},
)

remote_dependencies = _install_service_dependencies(service, mode, status)
_create_devservices_network()
# Add the service to the starting services table
state.update_service_entry(service.name, mode, StateTables.STARTING_SERVICES)
mode_dependencies = modes[mode]
Expand Down Expand Up @@ -168,31 +181,87 @@ def up(args: Namespace, existing_status: Status | None = None) -> None:
status,
)
status.warning(f"Continuing with service '{service.name}'")
try:
_up(service, [mode], remote_dependencies, mode_dependencies, status)
except DockerComposeError as dce:
capture_exception(dce, level="info")
status.failure(f"Failed to start {service.name}: {dce.stderr}")
exit(1)
with start_span(
op="service.containers.start", name="Start service containers"
) as span:
span.set_data("service_name", service.name)
span.set_data("mode", mode)
span.set_data("exclude_local", exclude_local)
try:
_up(service, [mode], remote_dependencies, mode_dependencies, status)
except DockerComposeError as dce:
capture_exception(dce, level="info")
status.failure(f"Failed to start {service.name}: {dce.stderr}")
exit(1)
# TODO: We should factor in healthchecks here before marking service as running
state.remove_service_entry(service.name, StateTables.STARTING_SERVICES)
state.update_service_entry(service.name, mode, StateTables.STARTED_SERVICES)


def _install_service_dependencies(
service: Service, mode: str, status: Status
) -> set[InstalledRemoteDependency]:
with start_span(
op="service.dependencies.install", name="Install dependencies"
) as span:
status.info("Retrieving dependencies")
span.set_data("service_name", service.name)
span.set_data("mode", mode)
try:
remote_dependencies = install_and_verify_dependencies(
service, force_update_dependencies=True, modes=[mode]
)
span.set_data("remote_dependency_count", len(remote_dependencies))
return remote_dependencies
except DependencyError as de:
capture_exception(de)
status.failure(
f"{str(de)}. If this error persists, try running `devservices purge`"
)
exit(1)
except ModeDoesNotExistError as mde:
status.failure(str(mde))
exit(1)


def _create_devservices_network() -> None:
with start_span(
op="service.network.create", name="Create devservices network"
) as span:
try:
subprocess.run(
["docker", "network", "create", "devservices"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=True,
)
span.set_data("network_created", True)
except subprocess.CalledProcessError:
# Network already exists, ignore the error
span.set_data("network_created", False)


def _pull_dependency_images(
cmd: DockerComposeCommand, current_env: dict[str, str], status: Status
) -> None:
run_cmd(cmd.full_command, current_env, retries=4)
for dependency in cmd.services:
status.info(f"Pulled image for {dependency}")
with start_span(op="service.images.pull", name="Pull dependency images") as span:
span.set_data("command", cmd.full_command)
run_cmd(cmd.full_command, current_env, retries=4)
for dependency in cmd.services:
status.info(f"Pulled image for {dependency}")


def _bring_up_dependency(
cmd: DockerComposeCommand, current_env: dict[str, str], status: Status
) -> None:
for dependency in cmd.services:
status.info(f"Starting {dependency}")
run_cmd(cmd.full_command, current_env)
with start_span(
op="service.containers.up", name="Bring up dependency containers"
) as span:
span.set_data("command", cmd.full_command)
span.set_data("services", cmd.services)
for dependency in cmd.services:
status.info(f"Starting {dependency}")
run_cmd(cmd.full_command, current_env)


def _up(
Expand Down Expand Up @@ -285,12 +354,3 @@ def _up(
except ContainerHealthcheckFailedError as e:
status.failure(str(e))
exit(1)


def _create_devservices_network() -> None:
subprocess.run(
["docker", "network", "create", "devservices"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=True,
)
Loading