Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(services): Adding list-services command #3

Merged
merged 6 commits into from
Sep 6, 2024
Merged
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
21 changes: 17 additions & 4 deletions src/commands/list_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
from argparse import _SubParsersAction
from argparse import ArgumentParser
from argparse import Namespace
from typing import Optional

from configs.service_config import load_service_config
from services import find_matching_service
from services import Service


def add_parser(subparsers: _SubParsersAction[ArgumentParser]) -> None:
Expand All @@ -22,14 +25,24 @@ def add_parser(subparsers: _SubParsersAction[ArgumentParser]) -> None:

def list_dependencies(args: Namespace) -> None:
"""List the dependencies of a service."""
config = load_service_config(args.service_name)
service_name = args.service_name

dependencies = config.service_config.dependencies
service: Optional[Service] = None
if service_name is not None:
try:
IanWoodard marked this conversation as resolved.
Show resolved Hide resolved
service = find_matching_service(service_name)
except Exception as e:
print(e)
return

# Note: If no service name is provided, the current directory is assumed to be the location of the service
service_config = load_service_config(service)
dependencies = service_config.dependencies

if not dependencies:
print(f"No dependencies found for {config.service_config.service_name}")
print(f"No dependencies found for {service_config.service_name}")
return

print(f"Dependencies of {config.service_config.service_name}:")
print(f"Dependencies of {service_config.service_name}:")
for dependency_key, dependency_info in dependencies.items():
print("-", dependency_key, ":", dependency_info.description)
31 changes: 31 additions & 0 deletions src/commands/list_services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from __future__ import annotations

from argparse import _SubParsersAction
from argparse import ArgumentParser
from argparse import Namespace

from services import get_local_services
from utils.devenv import get_coderoot


def add_parser(subparsers: _SubParsersAction[ArgumentParser]) -> None:
parser = subparsers.add_parser(
"list-services", help="List the services installed locally"
)
parser.set_defaults(func=list_services)


def list_services(args: Namespace) -> None:
"""List the services installed locally."""

# Get all of the services installed locally
coderoot = get_coderoot()
services = get_local_services(coderoot)

if not services:
print("No services found")
return

print("Services installed locally:")
for service in services:
print("-", service.name, f"({service.repo_path})")
46 changes: 23 additions & 23 deletions src/configs/service_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
from typing import Dict
from typing import List
from typing import Optional
from typing import TYPE_CHECKING

import yaml
from constants import DEVSERVICES_DIR_NAME
from constants import DOCKER_COMPOSE_FILE_NAME
from utils.devenv import get_coderoot

if TYPE_CHECKING:
from services import Service


@dataclass
Expand Down Expand Up @@ -45,31 +48,18 @@ class Config:
service_config: ServiceConfig


def load_service_config(service_name: Optional[str]) -> Config:
"""Load the devservices config for a service."""
if not service_name:
current_dir = os.getcwd()
config_path = os.path.join(
current_dir, DEVSERVICES_DIR_NAME, DOCKER_COMPOSE_FILE_NAME
)
if not os.path.exists(config_path):
raise FileNotFoundError(
f"Config file not found in current directory: {config_path}"
)
else:
coderoot = get_coderoot()
service_path = os.path.join(coderoot, service_name)
config_path = os.path.join(
service_path, DEVSERVICES_DIR_NAME, DOCKER_COMPOSE_FILE_NAME
def load_service_config_from_file(repo_path: str) -> ServiceConfig:
config_path = os.path.join(
repo_path, DEVSERVICES_DIR_NAME, DOCKER_COMPOSE_FILE_NAME
)
if not os.path.exists(config_path):
raise FileNotFoundError(
f"Config file not found in current directory: {config_path}"
)
if not os.path.exists(config_path):
raise FileNotFoundError(
f"Config file for {service_name} not found from code root: {config_path}"
)
with open(config_path, "r") as stream:
try:
config = yaml.safe_load(stream)
service_config_data = config.get("x-sentry-devservices-config", {})
service_config_data = config.get("x-sentry-service-config", {})
dependencies = {
key: Dependency(**value)
for key, value in service_config_data.get("dependencies", {}).items()
Expand All @@ -81,10 +71,20 @@ def load_service_config(service_name: Optional[str]) -> Config:
modes=service_config_data.get("modes", {}),
)

return Config(service_config=service_config)
return service_config
except FileNotFoundError as fnf:
raise FileNotFoundError(f"Config file not found: {config_path}") from fnf
except yaml.YAMLError as yml_error:
raise yaml.YAMLError(
f"Error parsing config file: {config_path}"
) from yml_error


def load_service_config(service: Optional[Service] = None) -> ServiceConfig:
"""Load the service config for a repo."""
if service is None:
current_dir = os.getcwd()
return load_service_config_from_file(current_dir)
if not isinstance(service.service_config, ServiceConfig):
raise TypeError("service_config must be of type ServiceConfig")
return service.service_config
2 changes: 2 additions & 0 deletions src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import argparse

from commands import list_dependencies
from commands import list_services
from commands import start
from commands import stop

Expand All @@ -19,6 +20,7 @@ def main() -> None:
start.add_parser(subparsers)
stop.add_parser(subparsers)
list_dependencies.add_parser(subparsers)
list_services.add_parser(subparsers)

args = parser.parse_args()

Expand Down
52 changes: 52 additions & 0 deletions src/services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from __future__ import annotations

import os
from dataclasses import dataclass
from typing import List
from typing import TYPE_CHECKING

from utils.devenv import get_coderoot

if TYPE_CHECKING:
from configs.service_config import ServiceConfig


@dataclass
class Service:
name: str
repo_path: str
service_config: ServiceConfig


def get_local_services(coderoot: str) -> List[Service]:
"""Get a list of services in the coderoot."""
from configs.service_config import load_service_config_from_file

services = []
for repo in os.listdir(coderoot):
repo_path = os.path.join(coderoot, repo)
try:
service_config = load_service_config_from_file(repo_path)
except FileNotFoundError:
continue
except Exception:
continue
service_name = service_config.service_name
services.append(
Service(
name=service_name,
repo_path=repo_path,
service_config=service_config,
)
)
return services


def find_matching_service(service_name: str) -> Service:
"""Find a service with the given name."""
coderoot = get_coderoot()
services = get_local_services(coderoot)
for service in services:
if service.name.lower() == service_name.lower():
return service
raise Exception(f'Service "{service_name}" not found')