Implement file version and a check when using it

If the configuration file structure changes over time, it might
introduce incompatibilities.

This patch introduces versioning of configuration file.

Configuration files needs to add a 'version' entry into their 'header'
like this:

header:
  version: '0.9'

After loading this file, the version is checked agains kas version.

Every version 'M.m.p' of kas is allowed to load configuration files with
version 'M.m' and all versions backwards up to and including
the '__compatible_version__' from kas/__version__.py

Signed-off-by: Claudius Heine <ch@denx.de>
This commit is contained in:
Claudius Heine 2017-06-21 13:33:00 +02:00 committed by Daniel Wagner
parent a65a85b373
commit abc80a8c59
3 changed files with 62 additions and 14 deletions

View File

@ -24,6 +24,7 @@
"""
from .__version__ import __version__
from .__version__ import __compatible_version__
__license__ = 'MIT'
__copyright__ = 'Copyright (c) Siemens AG, 2017'

View File

@ -26,3 +26,4 @@ __license__ = 'MIT'
__copyright__ = 'Copyright (c) Siemens AG, 2017'
__version__ = '0.9.0'
__compatible_version__ = '0.9'

View File

@ -30,11 +30,70 @@ import sys
import collections
import functools
import logging
from distutils.version import StrictVersion
from . import __version__, __compatible_version__
__license__ = 'MIT'
__copyright__ = 'Copyright (c) Siemens AG, 2017'
def load_config(filename):
"""
Load the configuration file and test if version is supported.
"""
(_, ext) = os.path.splitext(filename)
config = None
if ext == '.json':
import json
with open(filename, 'rb') as fds:
config = json.load(fds)
elif ext == '.yml':
import yaml
with open(filename, 'rb') as fds:
config = yaml.safe_load(fds)
else:
logging.error('Config file extension not recognized: %s',
filename)
sys.exit(1)
file_version_string = config.get('header', {}).get('version', None)
if file_version_string is None:
logging.error('Version missing: %s', filename)
sys.exit(1)
try:
if not isinstance(file_version_string, str):
logging.error('Version has to be a string: %s',
filename)
sys.exit(1)
file_version = StrictVersion()
file_version.parse(file_version_string)
kas_version = StrictVersion()
kas_version.parse(__version__)
lower_version = StrictVersion()
lower_version.parse(__compatible_version__)
# Remove patch version, because we provide limited forwards
# compatibility:
if file_version.version[2] > 0:
file_version.prerelease = None
file_version.version = tuple(list(file_version.version[:2]) + [0])
if file_version < lower_version or kas_version < file_version:
logging.error('This version of kas is compatible with version %s '
'to %s, file has version %s: %s',
lower_version, kas_version, file_version, filename)
sys.exit(1)
except ValueError:
logging.exception('Not expected version format: %s', filename)
raise
return config
class IncludeException(Exception):
"""
Class for exceptions that appear in the include mechanism.
@ -88,19 +147,6 @@ class GlobalIncludes(IncludeHandler):
def get_config(self, repos=None):
repos = repos or {}
def _internal_file_parser(filename):
(_, ext) = os.path.splitext(filename)
if ext == '.json':
import json
with open(filename, 'rb') as fds:
return json.load(fds)
elif ext == '.yml':
import yaml
with open(filename, 'rb') as fds:
return yaml.safe_load(fds)
logging.error('Config file extension not recognized: %s', ext)
sys.exit(1)
def _internal_include_handler(filename):
"""
Recursively load include files and find missing repos.
@ -129,7 +175,7 @@ class GlobalIncludes(IncludeHandler):
"""
missing_repos = []
configs = []
current_config = _internal_file_parser(filename)
current_config = load_config(filename)
if not isinstance(current_config, collections.Mapping):
raise IncludeException('Configuration file does not contain a '
'dictionary as base type')