nginx-amplify-agent/amplify/agent/tanks/config.py

239 lines
7.4 KiB
Python

# -*- coding: utf-8 -*-
from collections import defaultdict
from amplify.agent.common.config.abstract import AbstractConfig
__author__ = "Grant Hulegaard"
__copyright__ = "Copyright (C) Nginx, Inc. All rights reserved."
__license__ = ""
__maintainer__ = "Grant Hulegaard"
__email__ = "grant.hulegaard@nginx.com"
class ConfigTank(object):
"""
AbstractConfig manager that is able to initialize and abstractly access any
of the managed configs.
"""
def __init__(self):
self._configs = {} if '_configs' not in self.__dict__ else self._configs
self._path_index = {} if '_path_index' not in self.__dict__ else self._path_index
self._name_index = {} if '_name_index' not in self.__dict__ else self._name_index
self._section_index = {} if '_section_index' not in self.__dict__ else self._section_index
def __idx(self, config):
path = config.filename
if path in self._path_index:
_current_idx = self._path_index[path]
else:
_current_idx = len(self._configs)
# save the config
self._configs.update({
_current_idx: config
})
# index the pathname
if path not in self._path_index:
self._path_index.update({
path: _current_idx
})
# index the filename
filename = path.split('/')[-1]
if filename not in self._name_index:
self._name_index.update({
filename: _current_idx
})
# index the sections
for section in config.config:
# only index if section has not been indexed before
if section not in self._section_index:
self._section_index[section] = _current_idx
def __unidx(self, config):
path = config.filename
if path in self._path_index:
_current_idx = self._path_index[path]
else:
return # config not in tank
del self._path_index[path]
filename = path.split('/')[-1]
del self._name_index[filename]
for section, idx in list(self._section_index.items()):
if idx == _current_idx:
del self._section_index[section]
del self._configs[_current_idx]
def reindex(self):
for config in list(self._configs.values()):
self.__idx(config)
def full_index(self):
configs = self._configs.values()
self._configs = {}
self._path_index = {}
self._name_index = {}
self._section_index = {}
for config in configs:
self.__idx(config)
@property
def default(self):
"""Returns the first (agent) config if it exists"""
return self._configs[0] if len(self._configs) else None
def __getattr__(self, attr):
if 0 in self._configs:
return getattr(self._configs[0], attr)
else:
raise AttributeError(
"'%s' object has no attribute '%s'" % (
self.__class__.__name__,
attr
)
)
def __getitem__(self, section):
"""Get implementation which calls .get and raises KeyError if DNE"""
result = self.get(section)
if result is None:
raise KeyError(section)
return result
def __setitem__(self, section, value):
if section in self._section_index:
config = self._configs[self._section_index[section]]
else:
config = self._configs[0]
config[section] = value
self.__idx(config)
def get(self, section, default=None):
"""
Simple get method that is designed to operate like dict.get(). Will
map a section to a config and then return the section from said config
as if the config was directly referenced.
"""
# re-index just in case
self.reindex()
if section in self._section_index:
return self._configs[self._section_index[section]][section]
else:
return default
def get_config(self, filename):
"""
Simple method for returning the direct config object (pierce the
abstracton).
"""
if filename in self._path_index:
return self._configs[self._path_index[filename]]
elif filename in self._name_index:
return self._configs[self._name_index[filename]]
elif filename in self._configs:
return self._configs[filename]
else:
raise KeyError(filename)
def load(self, filename):
"""
Try to intialize, and then index/store a config from a file.
:param filename: String Filepath to try and read from.
"""
config = AbstractConfig(config_file=filename)
self.__idx(config)
def add(self, config):
"""
Index/store an already initialized config.
:param config: AbstractConfig Initialized AbstractConfig instance
"""
self.__idx(config)
def remove(self, config):
self.__unidx(config)
self.full_index()
def save(self, section, key, value, target=None):
"""
Maps section to a config and then passes save call to that config.
"""
if section in self._section_index:
config = self._configs[self._section_index[section]]
elif target is not None:
if target in self._path_index:
config = self._configs[self._path_index[target]]
elif target in self._name_index:
config = self._configs[self._name_index[target]]
elif target in self._configs:
config = self._configs[0]
else:
raise KeyError(target)
else:
raise KeyError(section)
config.save(section, key, value)
# reindex in case new sections were added
self.__idx(config)
def apply(self, patch, target=None):
"""
Iterates through a prospective patch, separating 1st level sections by
config and then passes the patch call to those configs individually.
"""
changes = 0
indexed_patch = defaultdict(dict)
if target is None:
# split the patch into sub-patches based on config they apply to
for section in patch.keys():
if section in self._section_index:
# if the section is known, link the update to the config
indexed_patch[self._section_index[section]].update(
patch[section]
)
else:
# if the section is unknown, link the update to the 0 config
indexed_patch[0].update(patch[section])
else:
if target in self._path_index:
indexed_patch[self._path_index[target]].update(patch)
elif target in self._name_index:
indexed_patch[self._name_index[target]].update(patch)
elif target in self._configs:
indexed_patch[0].update(patch)
else:
raise KeyError(target)
# iterate through the now split patches and apply them
for index, patch in indexed_patch.items():
config = self._configs[index]
changes += config.apply(patch)
# reindex in case sections were added
self.__idx(config)
return changes