Add the ability to merge distro settings from the persona/origins

For when its needed to specify specific overrides for the distro
configuration due to a specific openstack release having certain
known issues add a way for this to occur.

Change-Id: Ib0534cc58c2d2c514a77881b608d51505c2849db
This commit is contained in:
Joshua Harlow 2014-12-19 13:33:03 -08:00
parent 209310a76b
commit 2ef43ad2eb
6 changed files with 136 additions and 22 deletions

View File

@ -34,6 +34,7 @@ from anvil import distro
from anvil import exceptions as excp
from anvil import log as logging
from anvil import opts
from anvil import origins as _origins
from anvil import persona
from anvil import pprint
from anvil import settings
@ -95,6 +96,10 @@ def run(args):
# Ensure the anvil dirs are there if others are about to use it...
ensure_anvil_dirs(root_dir)
# Load the origins...
origins = _origins.load(args['origins_fn'],
patch_file=args.get('origins_patch'))
# Load the distro/s
possible_distros = distro.load(settings.DISTRO_DIR,
distros_patch=args.get('distros_patch'))
@ -105,11 +110,17 @@ def run(args):
except Exception as e:
raise excp.OptionException("Error loading persona file: %s due to %s" % (persona_fn, e))
else:
dist = persona_obj.match(possible_distros, args['origins_fn'],
origins_patch=args.get('origins_patch'))
dist = persona_obj.match(possible_distros, origins)
LOG.info('Persona selected distro: %s from %s possible distros',
colorizer.quote(dist.name), len(possible_distros))
# Update the dist with any other info...
dist.merge(**persona_obj.distro_updates)
dist.merge(**origins)
# Print it out...
print(dist.pformat(item_max_len=128))
# Get the object we will be running with...
runner = runner_cls(distro=dist,
root_dir=root_dir,

View File

@ -14,10 +14,10 @@
# License for the specific language governing permissions and limitations
# under the License.
import jsonpatch
import re
from anvil import exceptions
from anvil import origins as _origins
from anvil import settings
from anvil import shell as sh
from anvil import utils
@ -76,10 +76,8 @@ class YamlMergeLoader(object):
origins_opts = {}
if self._origins_path:
try:
origins = utils.load_yaml(self._origins_path)
if origins_patch:
patch = jsonpatch.JsonPatch(origins_patch)
patch.apply(origins, in_place=True)
origins = _origins.load(self._origins_path,
patch_file=origins_patch)
origins_opts = origins[component]
except KeyError:
pass

View File

@ -29,6 +29,7 @@ import six
from anvil import exceptions as excp
from anvil import importer
from anvil import log as logging
from anvil import pprint
from anvil import shell as sh
from anvil import utils
@ -44,12 +45,22 @@ class Distro(object):
install_helper, dependency_handler,
commands, components):
self.name = name
self._platform_pattern_text = platform_pattern
self._platform_pattern = re.compile(platform_pattern, re.IGNORECASE)
self._install_helper = install_helper
self._dependency_handler = dependency_handler
self._commands = commands
self._components = components
def pformat(self, item_max_len=None):
data = {
'name': self.name,
'dependency_handler': self._dependency_handler,
'commands': self._commands,
'pattern': "/%s/i" % self._platform_pattern_text,
}
return pprint.pformat(data, item_max_len=item_max_len)
def _fetch_value(self, root, keys, quiet):
end_key = keys[-1]
for k in keys[0:-1]:
@ -134,6 +145,12 @@ class Distro(object):
else:
return Component(entry_point, component_info, action_classes)
def merge(self, **kwargs):
if 'dependency_handler' in kwargs:
self._dependency_handler = utils.recursive_merge(
self._dependency_handler,
kwargs['dependency_handler'])
def _match_distros(distros):
plt = platform.platform()

40
anvil/origins.py Normal file
View File

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (C) 2014 Yahoo! Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import jsonpatch
from anvil import utils
class Origin(dict):
def __init__(self, filename, patched=False):
super(Origin, self).__init__()
self.filename = filename
self.patched = patched
def load(filename, patch_file=None):
base = utils.load_yaml(filename)
patched = False
if patch_file:
patch = jsonpatch.JsonPatch(patch_file)
patch.apply(base, in_place=True)
patched = True
origin = Origin(filename, patched=patched)
origin.update(base)
return origin

View File

@ -14,8 +14,6 @@
# License for the specific language governing permissions and limitations
# under the License.
import jsonpatch
import six
from anvil import colorizer
@ -28,22 +26,17 @@ SPECIAL_GROUPS = frozenset(['general'])
class Persona(object):
def __init__(self, supports, components, **kargs):
def __init__(self, supports, components, **kwargs):
self.distro_support = supports or []
self.source = kargs.get('source')
self.source = kwargs.pop('source', None)
self.wanted_components = utils.group_builds(components)
self.wanted_subsystems = kargs.get('subsystems') or {}
self.component_options = kargs.get('options') or {}
self.no_origins = kargs.get('no-origin') or []
self.wanted_subsystems = kwargs.pop('subsystems', {})
self.component_options = kwargs.pop('options', {})
self.no_origins = kwargs.pop('no-origin', [])
self.matched_components = []
self.distro_updates = kwargs
def match(self, distros, origins_fn, origins_patch=None):
# Filter out components that are disabled in origins file
origins = utils.load_yaml(origins_fn)
# Apply any user specified patches to origins file
if origins_patch:
patch = jsonpatch.JsonPatch(origins_patch)
patch.apply(origins, in_place=True)
def match(self, distros, origins):
for group in self.wanted_components:
for c in group:
if c not in origins:
@ -51,7 +44,7 @@ class Persona(object):
LOG.debug("Automatically enabling component %s, not"
" present in origins file %s but present in"
" desired persona %s (origin not required).",
c, origins_fn, self.source)
c, origins.filename, self.source)
origins[c] = {
'disabled': False,
}

View File

@ -306,6 +306,61 @@ def iso8601():
return datetime.now().isoformat()
def recursive_merge(a, b):
# pylint: disable=C0103
def _merge_lists(a, b):
merged = []
merged.extend(a)
merged.extend(b)
return merged
def _merge_dicts(a, b):
merged = {}
for k in six.iterkeys(a):
if k in b:
merged[k] = recursive_merge(a[k], b[k])
else:
merged[k] = a[k]
for k in six.iterkeys(b):
if k in merged:
continue
merged[k] = b[k]
return merged
def _merge_text(a, b):
return b
def _merge_int(a, b):
return b
def _merge_float(a, b):
return b
def _merge_bool(a, b):
return b
mergers = [
(list, list, _merge_lists),
(list, tuple, _merge_lists),
(tuple, tuple, _merge_lists),
(tuple, list, _merge_lists),
(dict, dict, _merge_dicts),
(six.string_types, six.string_types, _merge_text),
(int, int, _merge_int),
(bool, bool, _merge_bool),
(float, float, _merge_float),
]
merger = None
for (a_type, b_type, func) in mergers:
if isinstance(a, a_type) and isinstance(b, b_type):
merger = func
break
if not merger:
raise TypeError("Unknown how to merge '%s' with '%s'" % (type(a), type(b)))
return merger(a, b)
def merge_dicts(*dicts, **kwargs):
merged = OrderedDict()
for mp in dicts: