Skip to content

Commit

Permalink
🚚 Move SerialisableConfig to AzureSerialisableModel
Browse files Browse the repository at this point in the history
  • Loading branch information
jemrobinson committed Apr 26, 2024
1 parent 4745276 commit 6ace359
Show file tree
Hide file tree
Showing 5 changed files with 26 additions and 55 deletions.
2 changes: 0 additions & 2 deletions data_safe_haven/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@
ConfigSubsectionRemoteDesktopOpts,
)
from .pulumi import DSHPulumiConfig, DSHPulumiProject
from .serialisable_config import SerialisableConfig

__all__ = [
"Config",
"ConfigSectionAzure",
"ConfigSectionSHM",
"ConfigSectionSRE",
"ConfigSubsectionRemoteDesktopOpts",
"SerialisableConfig",
"DSHPulumiConfig",
"DSHPulumiProject",
]
5 changes: 2 additions & 3 deletions data_safe_haven/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from data_safe_haven.exceptions import DataSafeHavenConfigError
from data_safe_haven.functions.validators import validate_unique_list
from data_safe_haven.utility import (
AzureSerialisableModel,
DatabaseSystem,
LoggingSingleton,
SoftwarePackageCategory,
Expand All @@ -27,8 +28,6 @@
UniqueList,
)

from .serialisable_config import SerialisableConfig


class ConfigSectionAzure(BaseModel, validate_assignment=True):
subscription_id: Guid
Expand Down Expand Up @@ -186,7 +185,7 @@ def update(
)


class Config(SerialisableConfig):
class Config(AzureSerialisableModel):
config_type: ClassVar[str] = "Config"
filename: ClassVar[str] = "config.yaml"
azure: ConfigSectionAzure
Expand Down
4 changes: 2 additions & 2 deletions data_safe_haven/config/pulumi.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from pydantic import BaseModel

from .serialisable_config import SerialisableConfig
from data_safe_haven.utility import AzureSerialisableModel


class DSHPulumiProject(BaseModel, validate_assignment=True):
Expand All @@ -21,7 +21,7 @@ def __hash__(self) -> int:
return hash(self.stack_config)


class DSHPulumiConfig(SerialisableConfig):
class DSHPulumiConfig(AzureSerialisableModel):
config_type: ClassVar[str] = "Pulumi"
filename: ClassVar[str] = "pulumi.yaml"
projects: dict[str, DSHPulumiProject]
Expand Down
2 changes: 2 additions & 0 deletions data_safe_haven/utility/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .azure_serialisable_model import AzureSerialisableModel
from .directories import config_dir
from .enums import DatabaseSystem, SoftwarePackageCategory
from .file_reader import FileReader
Expand All @@ -7,6 +8,7 @@
from .yaml_serialisable_model import YAMLSerialisableModel

__all__ = [
"AzureSerialisableModel",
"config_dir",
"DatabaseSystem",
"FileReader",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,64 +1,21 @@
from __future__ import annotations

from typing import Any, ClassVar, TypeVar

import yaml
from pydantic import BaseModel, ValidationError
from yaml import YAMLError

from data_safe_haven.context import Context
from data_safe_haven.exceptions import (
DataSafeHavenConfigError,
DataSafeHavenParameterError,
)
from data_safe_haven.external import AzureApi

T = TypeVar("T", bound="SerialisableConfig")
from .yaml_serialisable_model import YAMLSerialisableModel

T = TypeVar("T", bound="AzureSerialisableModel")

class SerialisableConfig(BaseModel, validate_assignment=True):

class AzureSerialisableModel(YAMLSerialisableModel):
"""Base class for configuration that can be written to Azure storage"""

config_type: ClassVar[str] = "SerialisableConfig"
filename: ClassVar[str] = "config.yaml"

def to_yaml(self) -> str:
"""Write configuration to a YAML formatted string"""
return yaml.dump(self.model_dump(mode="json"), indent=2)

def upload(self, context: Context) -> None:
"""Upload configuration data to Azure storage"""
azure_api = AzureApi(subscription_name=context.subscription_name)
azure_api.upload_blob(
self.to_yaml(),
self.filename,
context.resource_group_name,
context.storage_account_name,
context.storage_container_name,
)

@classmethod
def from_yaml(cls: type[T], settings_yaml: str) -> T:
"""Construct configuration from a YAML string"""
try:
settings_dict = yaml.safe_load(settings_yaml)
except YAMLError as exc:
msg = f"Could not parse {cls.config_type} configuration as YAML.\n{exc}"
raise DataSafeHavenConfigError(msg) from exc

if not isinstance(settings_dict, dict):
msg = f"Unable to parse {cls.config_type} configuration as a dict."
raise DataSafeHavenConfigError(msg)

try:
return cls.model_validate(settings_dict)
except ValidationError as exc:
msg = f"Could not load {cls.config_type} configuration.\n{exc}"
raise DataSafeHavenParameterError(msg) from exc

@classmethod
def from_remote(cls: type[T], context: Context) -> T:
"""Construct configuration from a YAML file in Azure storage"""
"""Construct an AzureSerialisableModel from a YAML file in Azure storage."""
azure_api = AzureApi(subscription_name=context.subscription_name)
config_yaml = azure_api.download_blob(
cls.filename,
Expand All @@ -72,6 +29,10 @@ def from_remote(cls: type[T], context: Context) -> T:
def from_remote_or_create(
cls: type[T], context: Context, **default_args: dict[Any, Any]
) -> T:
"""
Construct an AzureSerialisableModel from a YAML file in Azure storage, or from
default arguments if no such file exists.
"""
azure_api = AzureApi(subscription_name=context.subscription_name)
if azure_api.blob_exists(
cls.filename,
Expand All @@ -82,3 +43,14 @@ def from_remote_or_create(
return cls.from_remote(context)
else:
return cls(**default_args)

def upload(self, context: Context) -> None:
"""Serialise an AzureSerialisableModel to a YAML file in Azure storage."""
azure_api = AzureApi(subscription_name=context.subscription_name)
azure_api.upload_blob(
self.to_yaml(),
self.filename,
context.resource_group_name,
context.storage_account_name,
context.storage_container_name,
)

0 comments on commit 6ace359

Please sign in to comment.