kiwi-scp/kiwi_scp/config.py

158 lines
4.5 KiB
Python
Raw Permalink Normal View History

2020-08-12 15:46:50 +00:00
# system
2020-08-11 15:23:24 +00:00
import copy
2020-08-06 11:43:45 +00:00
import logging
2020-08-06 12:34:25 +00:00
import os
2020-08-12 15:46:50 +00:00
import re
2020-08-04 18:24:19 +00:00
import yaml
2020-08-12 15:46:50 +00:00
# local
from ._constants import KIWI_CONF_NAME, HEADER_KIWI_CONF_NAME, DEFAULT_KIWI_CONF_NAME, VERSION_TAG_NAME
2020-08-06 01:45:12 +00:00
2020-08-04 14:52:30 +00:00
2020-08-06 11:43:45 +00:00
class Config:
2020-08-13 08:48:01 +00:00
"""represents a kiwi.yml"""
2020-08-06 11:43:45 +00:00
__yml_content = {}
2020-08-20 11:29:08 +00:00
__keys = {
2020-08-26 11:56:51 +00:00
'version': "kiwi-scp version to use in this instance",
2020-08-20 11:29:08 +00:00
'runtime:storage': "local directory for service data",
'runtime:shells': "shell preference for working in service containers",
'runtime:env': "common environment for compose yml",
'markers:project': "marker string for project directories",
'markers:disabled': "marker string for disabled projects",
'network:name': "name for local network hub",
'network:cidr': "CIDR block for local network hub",
}
2020-08-06 01:45:12 +00:00
2020-08-06 11:43:45 +00:00
def __key_resolve(self, key):
2020-08-13 08:48:01 +00:00
"""
Resolve nested dictionaries
If __yml_content is {'a': {'b': {'c': "val"}}} and key is 'a:b:c',
this returns a single dict {'c': "val"} and the direct key 'c'
"""
2020-08-05 00:08:55 +00:00
# "a:b:c" => path = ['a', 'b'], key = 'c'
path = key.split(':')
2020-08-06 01:45:12 +00:00
path, key = path[:-1], path[-1]
2020-08-04 18:24:19 +00:00
2020-08-05 00:08:55 +00:00
# resolve path
2020-08-06 11:43:45 +00:00
container = self.__yml_content
2020-08-04 18:24:19 +00:00
for step in path:
2020-08-06 11:43:45 +00:00
container = container[step]
2020-08-04 18:24:19 +00:00
2020-08-06 11:43:45 +00:00
return container, key
2020-08-06 01:45:12 +00:00
2020-08-06 12:34:25 +00:00
def __getitem__(self, key):
2020-08-13 08:48:01 +00:00
"""array-like read access to __yml_content"""
2020-08-06 12:34:25 +00:00
container, key = self.__key_resolve(key)
return container[key]
2020-08-06 11:43:45 +00:00
def __setitem__(self, key, value):
2020-08-13 08:48:01 +00:00
"""array-like write access to __yml_content"""
2020-08-06 11:43:45 +00:00
container, key = self.__key_resolve(key)
container[key] = value
2020-08-06 01:45:12 +00:00
2020-08-06 12:34:25 +00:00
def __str__(self):
2020-08-13 08:48:01 +00:00
"""dump into textual representation"""
2020-08-06 12:34:25 +00:00
# dump yml content
2020-08-18 14:36:29 +00:00
yml_string = yaml.dump(
self.__yml_content,
default_flow_style=False, sort_keys=False
).strip()
2020-08-06 12:34:25 +00:00
# insert newline before every main key
yml_string = re.sub(r'^(\S)', r'\n\1', yml_string, flags=re.MULTILINE)
2020-08-11 12:03:00 +00:00
# load header comment from file
with open(HEADER_KIWI_CONF_NAME, 'r') as stream:
yml_string = stream.read() + yml_string
2020-08-06 12:34:25 +00:00
return yml_string
2020-08-06 01:45:12 +00:00
def _update_from_file(self, filename):
2020-08-13 08:48:01 +00:00
"""return a copy updated using a kiwi.yml file"""
2020-08-06 11:43:45 +00:00
with open(filename, 'r') as stream:
try:
2020-08-13 08:48:01 +00:00
# create copy
result = Config()
result.__yml_content = copy.deepcopy(self.__yml_content)
2020-08-04 18:24:19 +00:00
2020-08-13 08:48:01 +00:00
# read file
logging.debug(f"Reading '{filename}' into '{id(result.__yml_content)}'")
result.__yml_content.update(yaml.safe_load(stream))
2020-08-11 15:23:24 +00:00
2020-08-13 08:48:01 +00:00
return result
except yaml.YAMLError as exc:
logging.error(exc)
2020-08-11 15:23:24 +00:00
2020-08-20 11:29:08 +00:00
def user_query(self, key):
"""query user for new config value"""
# prompt user as per argument
try:
result = input(f"Enter {self.__keys[key]} [{self[key]}] ").strip()
except EOFError:
print()
result = None
# store result if present
if result:
self[key] = result
def save(self):
2020-08-13 08:48:01 +00:00
"""save current yml representation in current directory's kiwi.yml"""
with open(KIWI_CONF_NAME, 'w') as stream:
2020-08-06 12:34:25 +00:00
stream.write(str(self))
2020-08-18 14:36:29 +00:00
stream.write('\n')
2020-08-06 12:34:25 +00:00
class DefaultConfig(Config):
2020-08-13 08:48:01 +00:00
"""Singleton: The default kiwi.yml file"""
__instance = None
2020-08-06 11:43:45 +00:00
@classmethod
def get(cls):
if cls.__instance is None:
2020-08-13 08:48:01 +00:00
# create singleton
cls.__instance = cls()._update_from_file(DEFAULT_KIWI_CONF_NAME)
2020-08-06 01:45:12 +00:00
2020-08-13 08:48:01 +00:00
# add version data from separate file (keeps default config cleaner)
with open(VERSION_TAG_NAME, 'r') as stream:
2020-08-13 08:48:01 +00:00
cls.__instance['version'] = stream.read().strip()
2020-08-06 01:45:12 +00:00
2020-08-13 08:48:01 +00:00
# return singleton
return cls.__instance
2020-08-06 01:45:12 +00:00
class LoadedConfig(Config):
2020-08-13 08:48:01 +00:00
"""Singleton collection: kiwi.yml files by path"""
__instances = {}
2020-08-06 11:43:45 +00:00
@classmethod
2020-08-20 10:30:06 +00:00
def get(cls, directory='.'):
if directory not in LoadedConfig.__instances:
2020-08-13 08:48:01 +00:00
# create singleton for new path
result = DefaultConfig.get()
2020-08-20 10:30:06 +00:00
# update with that dir's kiwi.yml
2020-08-13 08:48:01 +00:00
try:
2020-08-20 10:30:06 +00:00
result = result._update_from_file(os.path.join(directory, KIWI_CONF_NAME))
2020-08-13 08:48:01 +00:00
except FileNotFoundError:
2020-08-20 10:30:06 +00:00
logging.info(f"No '{KIWI_CONF_NAME}' found at '{directory}'. Using defaults.")
2020-08-06 12:34:25 +00:00
2020-08-20 10:30:06 +00:00
LoadedConfig.__instances[directory] = result
2020-08-04 18:24:19 +00:00
2020-08-13 08:48:01 +00:00
# return singleton
2020-08-20 10:30:06 +00:00
return LoadedConfig.__instances[directory]