This repository has been archived on 2020-03-24. You can view files and clone it, but cannot push or open issues or pull requests.
quay/util/config/provider/baseprovider.py
IvanCherepov c383ac1f9d
Add config validation on startup (#2903)
* WIP

* Finish schema

Add three sections: security scanning, bittorrent support and feature flags.
2017-12-01 10:46:39 -05:00

157 lines
4.9 KiB
Python

import logging
import yaml
from jsonschema import validate, ValidationError
from util.config.schema import CONFIG_SCHEMA
from util.license import LICENSE_FILENAME, LicenseDecodeError, decode_license
logger = logging.getLogger(__name__)
class CannotWriteConfigException(Exception):
""" Exception raised when the config cannot be written. """
pass
class SetupIncompleteException(Exception):
""" Exception raised when attempting to verify config that has not yet been setup. """
pass
def import_yaml(config_obj, config_file):
with open(config_file) as f:
c = yaml.safe_load(f)
if not c:
logger.debug('Empty YAML config file')
return
if isinstance(c, str):
raise Exception('Invalid YAML config file: ' + str(c))
for key in c.iterkeys():
if key.isupper():
config_obj[key] = c[key]
if config_obj.get('SETUP_COMPLETE', True):
try:
validate(config_obj, CONFIG_SCHEMA)
except ValidationError:
# TODO: Change this into a real error
logger.exception('Could not validate config schema')
else:
logger.debug('Skipping config schema validation because setup is not complete')
return config_obj
def get_yaml(config_obj):
return yaml.safe_dump(config_obj, encoding='utf-8', allow_unicode=True)
def export_yaml(config_obj, config_file):
try:
with open(config_file, 'w') as f:
f.write(get_yaml(config_obj))
except IOError as ioe:
raise CannotWriteConfigException(str(ioe))
class BaseProvider(object):
""" A configuration provider helps to load, save, and handle config override in the application.
"""
def __init__(self):
self.license = None
@property
def provider_id(self):
raise NotImplementedError
def update_app_config(self, app_config):
""" Updates the given application config object with the loaded override config. """
raise NotImplementedError
def get_config(self):
""" Returns the contents of the config override file, or None if none. """
raise NotImplementedError
def save_config(self, config_object):
""" Updates the contents of the config override file to those given. """
raise NotImplementedError
def config_exists(self):
""" Returns true if a config override file exists in the config volume. """
raise NotImplementedError
def volume_exists(self):
""" Returns whether the config override volume exists. """
raise NotImplementedError
def volume_file_exists(self, filename):
""" Returns whether the file with the given name exists under the config override volume. """
raise NotImplementedError
def get_volume_file(self, filename, mode='r'):
""" Returns a Python file referring to the given name under the config override volume. """
raise NotImplementedError
def write_volume_file(self, filename, contents):
""" Writes the given contents to the config override volumne, with the given filename. """
raise NotImplementedError
def remove_volume_file(self, filename):
""" Removes the config override volume file with the given filename. """
raise NotImplementedError
def list_volume_directory(self, path):
""" Returns a list of strings representing the names of the files found in the config override
directory under the given path. If the path doesn't exist, returns None.
"""
raise NotImplementedError
def save_volume_file(self, filename, flask_file):
""" Saves the given flask file to the config override volume, with the given
filename.
"""
raise NotImplementedError
def requires_restart(self, app_config):
""" If true, the configuration loaded into memory for the app does not match that on disk,
indicating that this container requires a restart.
"""
raise NotImplementedError
def get_volume_path(self, directory, filename):
""" Helper for constructing file paths, which may differ between providers. For example,
kubernetes can't have subfolders in configmaps """
raise NotImplementedError
def _get_license_file(self):
""" Returns the contents of the license file. """
if not self.has_license_file():
msg = 'Could not find license file. Please make sure it is in your config volume.'
raise LicenseDecodeError(msg)
try:
return self.get_volume_file(LICENSE_FILENAME)
except IOError:
msg = 'Could not open license file. Please make sure it is in your config volume.'
raise LicenseDecodeError(msg)
def get_license(self):
""" Returns the decoded license, if any. """
with self._get_license_file() as f:
license_file_contents = f.read()
return decode_license(license_file_contents)
def save_license(self, license_file_contents):
""" Saves the given contents as the license file. """
self.write_volume_file(LICENSE_FILENAME, license_file_contents)
def has_license_file(self):
""" Returns true if a license file was found in the config directory. """
return self.volume_file_exists(LICENSE_FILENAME)