kiwi-scp/kiwi_scp/config.py

203 lines
5.1 KiB
Python
Raw Normal View History

2021-10-13 01:05:46 +00:00
import re
2021-10-12 17:06:49 +00:00
from ipaddress import IPv4Network
from pathlib import Path
2021-10-13 01:05:46 +00:00
from typing import Optional, Dict, List, Any
2020-08-04 18:24:19 +00:00
2021-10-13 01:05:46 +00:00
import yaml
2021-10-12 17:08:35 +00:00
from pydantic import BaseModel, constr, root_validator, validator
2020-08-06 01:45:12 +00:00
2021-10-13 01:05:46 +00:00
from ._constants import RE_SEMVER, RE_VARNAME, HEADER_KIWI_CONF_NAME
# indent yaml lists
class _KiwiDumper(yaml.Dumper):
def increase_indent(self, flow=False, indentless=False):
return super().increase_indent(flow, False)
2021-10-12 17:06:49 +00:00
2020-08-04 14:52:30 +00:00
2021-10-12 17:08:35 +00:00
class _Storage(BaseModel):
2021-10-11 00:58:49 +00:00
"""a storage subsection"""
2020-08-13 08:48:01 +00:00
2021-10-12 17:06:49 +00:00
directory: Path
2020-08-06 01:45:12 +00:00
2021-10-13 01:05:46 +00:00
@property
def kiwi_dict(self) -> Dict[str, Any]:
"""write this object as a dictionary of strings"""
return {"directory": str(self.directory)}
2020-08-13 08:48:01 +00:00
2021-10-12 17:08:35 +00:00
class _Project(BaseModel):
2021-10-11 00:58:49 +00:00
"""a project subsection"""
2020-08-06 12:34:25 +00:00
2021-10-13 01:05:46 +00:00
name: constr(regex=RE_VARNAME)
2021-10-11 00:58:49 +00:00
enabled: bool = True
2021-10-12 17:06:49 +00:00
override_storage: Optional[_Storage]
2020-08-06 12:34:25 +00:00
2021-10-13 01:05:46 +00:00
@property
def kiwi_dict(self) -> Dict[str, Any]:
"""write this object as a dictionary of strings"""
if self.override_storage is None:
return {self.name: self.enabled}
else:
result = self.dict(exclude={"override_storage"})
result["override_storage"] = self.override_storage.kiwi_dict
return result
2021-10-12 17:08:35 +00:00
@root_validator(pre=True)
2021-10-11 00:58:49 +00:00
@classmethod
2021-10-12 17:06:49 +00:00
def unify_project(cls, values):
"""parse different project notations"""
if "name" in values:
# default format
return values
elif len(values) == 1:
# short format:
# - <name>: <enabled>
2020-08-11 15:23:24 +00:00
2021-10-12 17:06:49 +00:00
name, enabled = list(values.items())[0]
return {
"name": name,
"enabled": True if enabled is None else enabled
}
2020-08-11 15:23:24 +00:00
2021-10-12 17:06:49 +00:00
else:
# undefined format
raise ValueError
2020-08-20 11:29:08 +00:00
2021-10-12 17:08:35 +00:00
class _Network(BaseModel):
2021-10-11 00:58:49 +00:00
"""a network subsection"""
2020-08-20 11:29:08 +00:00
2021-10-12 17:17:28 +00:00
name: constr(to_lower=True, regex=RE_VARNAME)
2021-10-12 17:06:49 +00:00
cidr: IPv4Network
2020-08-13 08:48:01 +00:00
2021-10-13 01:05:46 +00:00
@property
def kiwi_dict(self) -> Dict[str, Any]:
"""write this object as a dictionary of strings"""
return {
"name": self.name,
"cidr": str(self.cidr)
}
2020-08-06 12:34:25 +00:00
2021-10-12 17:08:35 +00:00
class Config(BaseModel):
2021-10-11 00:58:49 +00:00
"""represents a kiwi.yml"""
2021-10-12 17:17:28 +00:00
version: constr(regex=RE_SEMVER) = "0.2.0"
2021-10-13 00:16:09 +00:00
shells: Optional[List[Path]] = [
Path("/bin/bash"),
2021-10-12 17:17:28 +00:00
]
2021-10-11 00:58:49 +00:00
projects: Optional[List[_Project]]
2021-10-12 17:17:28 +00:00
2021-10-13 01:05:46 +00:00
environment: Dict[str, Optional[str]] = {}
2021-10-13 00:16:09 +00:00
storage: _Storage = _Storage(
directory="/var/local/kiwi",
)
2021-10-12 17:17:28 +00:00
2021-10-13 00:16:09 +00:00
network: _Network = _Network(
name="kiwi_hub",
cidr="10.22.46.0/24",
)
2021-10-13 01:05:46 +00:00
@property
def kiwi_dict(self) -> Dict[str, Any]:
"""write this object as a dictionary of strings"""
result = {
"version": self.version,
"shells": [str(shell) for shell in self.shells],
}
if self.projects:
result["projects"] = [
project.kiwi_dict
for project in self.projects
]
if self.environment:
result["environment"] = self.environment
result["storage"] = self.storage.kiwi_dict
result["network"] = self.network.kiwi_dict
return result
@property
def kiwi_yml(self) -> str:
"""dump a kiwi.yml file"""
yml_string = yaml.dump(
self.kiwi_dict,
Dumper=_KiwiDumper,
default_flow_style=False,
sort_keys=False,
)
# insert newline before every main key
yml_string = re.sub(r'^(\S)', r'\n\1', yml_string, flags=re.MULTILINE)
# load header comment from file
with open(HEADER_KIWI_CONF_NAME, 'r') as stream:
yml_string = stream.read() + yml_string
return yml_string
2021-10-12 17:08:35 +00:00
@validator("environment", pre=True)
2020-08-06 11:43:45 +00:00
@classmethod
2021-10-12 17:17:28 +00:00
def unify_environment(cls, value) -> Dict[str, Optional[str]]:
2021-10-12 17:06:49 +00:00
"""parse different environment notations"""
def parse_str(var_val: str) -> (str, Optional[str]):
"""parse a "<variable>=<value>" string"""
idx = var_val.find("=")
if idx == -1:
# don't split, just define the variable
return var_val, None
else:
# split string, set variable to value
return var_val[:idx], var_val[idx + 1:]
if value is None:
# empty environment
2021-10-12 17:17:28 +00:00
return {}
2021-10-12 17:06:49 +00:00
elif isinstance(value, dict):
# native dict format
2021-10-11 00:58:49 +00:00
return value
2021-10-12 17:06:49 +00:00
2021-10-11 00:58:49 +00:00
elif isinstance(value, list):
2021-10-12 17:06:49 +00:00
# list format (multiple strings)
2021-10-11 00:58:49 +00:00
result: Dict[str, Optional[str]] = {}
for item in value:
2021-10-12 17:06:49 +00:00
key, value = parse_str(item)
2021-10-11 00:58:49 +00:00
result[key] = value
return result
2021-10-12 17:06:49 +00:00
elif isinstance(value, str):
# string format (single variable):
# "<var>=<value>"
key, value = parse_str(value)
return {key: value}
elif isinstance(value, int):
# integer format (just define single oddly named variable)
return {str(value): None}
2021-10-11 00:58:49 +00:00
else:
2021-10-12 17:06:49 +00:00
# undefined format
raise ValueError