Skip to content
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
12 changes: 8 additions & 4 deletions tdp/cli/commands/status/generate_stales.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ def generate_stales(
Stales components are components that have been modified and need to be
reconfigured and/or restarted.
"""

from tdp.cli.utils import check_services_cleanliness, print_hosted_entity_status_log
from tdp.core.exceptions import ServiceVariablesNotInitializedErrorList
from tdp.core.variables import ClusterVariables
from tdp.dao import Dao

Expand All @@ -54,9 +54,13 @@ def generate_stales(
check_services_cleanliness(cluster_variables)

with Dao(db_engine) as dao:
stale_status_logs = dao.get_cluster_status().generate_stale_sch_logs(
cluster_variables=cluster_variables, collections=collections
)
try:
stale_status_logs = dao.get_cluster_status().generate_stale_sch_logs(
cluster_variables=cluster_variables, collections=collections
)
except ServiceVariablesNotInitializedErrorList as e:
click.echo(str(e))
click.echo("Their status will not be updated.")

dao.session.add_all(stale_status_logs)
dao.session.commit()
Expand Down
11 changes: 8 additions & 3 deletions tdp/cli/commands/vars/edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ def edit(
ServiceComponentName,
parse_entity_name,
)
from tdp.core.exceptions import ServiceVariablesNotInitializedErrorList
from tdp.core.repository.repository import EmptyCommit
from tdp.core.variables import ClusterVariables
from tdp.core.variables.schema.exceptions import InvalidSchemaError
Expand Down Expand Up @@ -147,9 +148,13 @@ def edit(

# Generate stale component list and save it to the database
with Dao(db_engine) as dao:
stale_status_logs = dao.get_cluster_status().generate_stale_sch_logs(
cluster_variables=cluster_variables, collections=collections
)
try:
stale_status_logs = dao.get_cluster_status().generate_stale_sch_logs(
cluster_variables=cluster_variables, collections=collections
)
except ServiceVariablesNotInitializedErrorList as e:
click.echo(str(e))
click.echo("Their status will not be updated.")
dao.session.add_all(stale_status_logs)
dao.session.commit()

Expand Down
16 changes: 4 additions & 12 deletions tdp/cli/commands/vars/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,8 @@ def update(
):
"""Update configuration from the given directories."""

from tdp.core.variables.cluster_variables import (
ClusterVariables,
ServicesNotInitializedError,
)
from tdp.core.exceptions import ServiceVariablesNotInitializedErrorList
from tdp.core.variables.cluster_variables import ClusterVariables
from tdp.core.variables.exceptions import ServicesUpdateError
from tdp.dao import Dao

Expand All @@ -71,14 +69,8 @@ def update(
base_validation_msg=msg,
)
# Stop the update process if some services are not initialized
except ServicesNotInitializedError as e:
error_messages = "\n".join(
f"{error.service_name} (from {error.source_definition})"
for error in e.services
)
raise click.ClickException(
f"The following services are not initialized:\n{error_messages}"
) from e
except ServiceVariablesNotInitializedErrorList as e:
raise click.ClickException(str(e)) from e
# Do not stop the update as some services may have been updated successfully
except ServicesUpdateError as e:
error_messages = "\n".join(
Expand Down
35 changes: 35 additions & 0 deletions tdp/core/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright 2025 TOSIT.IO
# SPDX-License-Identifier: Apache-2.0


from typing import Optional


class ServiceVariablesNotInitializedError(Exception):
def __init__(self, service_name: str, source: Optional[str] = None):
super().__init__(
f"Variables for service '{service_name}' have not been initialized."
)
self.name = service_name
self.source = source

def as_list_item(self) -> str:
"""Return a string representation of the error for listing."""
return f"{self.name}" + (f" (from {self.source})" if self.source else "")


class ServiceVariablesNotInitializedErrorList(Exception):
base_msg = "The following services are not initialized:"

def __init__(self, errors: list[ServiceVariablesNotInitializedError]):
super().__init__(
self.base_msg + " " + ", ".join(e.as_list_item() for e in errors)
)
self.errors = errors

def __str__(self):
return (
self.base_msg
+ "\n"
+ "\n".join(f"- {e.as_list_item()}" for e in self.errors)
)
26 changes: 16 additions & 10 deletions tdp/core/variables/cluster_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,18 @@
from pathlib import Path
from typing import TYPE_CHECKING, Optional

from tdp.core.constants import DEFAULT_VALIDATION_MESSAGE, VALIDATION_MESSAGE_FILE
from tdp.core.constants import (
DEFAULT_VALIDATION_MESSAGE,
VALIDATION_MESSAGE_FILE,
)
from tdp.core.exceptions import (
ServiceVariablesNotInitializedError,
ServiceVariablesNotInitializedErrorList,
)
from tdp.core.repository.git_repository import GitRepository
from tdp.core.repository.repository import EmptyCommit, NoVersionYet, Repository
from tdp.core.types import PathLike
from tdp.core.variables.exceptions import (
ServicesNotInitializedError,
ServicesUpdateError,
UnknownService,
UpdateError,
)
from tdp.core.variables.exceptions import ServicesUpdateError, UpdateError
from tdp.core.variables.messages import ValidationMessageBuilder
from tdp.core.variables.planner import ServiceUpdatePlanner
from tdp.core.variables.scanner import ServiceDirectoryScanner
Expand Down Expand Up @@ -156,13 +158,17 @@ def update(
(name, path) for name, path in self._collections.default_vars_dirs.items()
] + [("override", Path(p)) for p in override_folders]

unknown = []
unknown: list[ServiceVariablesNotInitializedError] = []
for _, source_path in sources:
for name, _ in ServiceDirectoryScanner.scan(source_path).items():
if name not in self:
unknown.append(UnknownService(name, source_path.as_posix()))
unknown.append(
ServiceVariablesNotInitializedError(
name, source_path.as_posix()
)
)
if unknown:
raise ServicesNotInitializedError(unknown)
raise ServiceVariablesNotInitializedErrorList(unknown)

validation_builder = ValidationMessageBuilder(
self._collections,
Expand Down
21 changes: 0 additions & 21 deletions tdp/core/variables/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,6 @@
from dataclasses import dataclass


@dataclass(frozen=True)
class UnknownService:
"""Represents a service that is referenced but not initialized."""

service_name: str
source_definition: str


class ServicesNotInitializedError(Exception):
"""Raised when some expected services are missing from the initialized set."""

def __init__(self, services: list[UnknownService]):
super().__init__(
"The following services are not initialized:\n"
+ "\n".join(
f"{e.service_name} (from {e.source_definition})" for e in services
)
)
self.services = services


@dataclass(frozen=True)
class UpdateError:
"""Represents an error that occurred during a service update."""
Expand Down
14 changes: 11 additions & 3 deletions tdp/core/variables/inspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
from collections.abc import Iterable
from typing import TYPE_CHECKING

from tdp.core.exceptions import (
ServiceVariablesNotInitializedError,
ServiceVariablesNotInitializedErrorList,
)

if TYPE_CHECKING:
from tdp.core.entities.hosted_entity import HostedEntity
from tdp.core.entities.hosted_entity_status import HostedEntityStatus
Expand All @@ -31,16 +36,17 @@ def get_modified_entities(
RuntimeError: If a service is deployed but its repository is missing.
"""
modified_entities: set[HostedEntity] = set()
not_initialized_services: set[ServiceVariablesNotInitializedError] = set()
for status in entity_statuses:
# Skip if the entity has already been listed as modified
if status.entity in modified_entities:
continue
# Raise an error if the service is deployed but its repository is missing
if status.entity.name.service not in cluster_variables:
raise RuntimeError(
f"Service '{status.entity.name.service}' is deployed but its"
+ "repository is missing."
not_initialized_services.add(
ServiceVariablesNotInitializedError(status.entity.name.service)
)
continue
# Check if the entity has been modified
if status.configured_version and cluster_variables[
status.entity.name.service
Expand All @@ -52,4 +58,6 @@ def get_modified_entities(
+ (f" for host {status.entity.host}" if status.entity.host else "")
)
modified_entities.add(status.entity)
if not_initialized_services:
raise ServiceVariablesNotInitializedErrorList(list(not_initialized_services))
return modified_entities