![Rabi Mishra](/assets/img/avatar_default.png)
Earlier users had to specify merge strategy for a parameter/parameter_default in every environment file where they are used(or the defaults for that environment was used). However, the strategy for a parameter in the first environment was ignored as there is nothing to merge. With Iec3367667248d0b46196a6d569f3cbb3b200d842, we changed to use existing merge strategies, but the strategies in the first environment were still ignored. This patch fixes it. Task: 45578 Change-Id: Ic6125c6af60c1007243523cc8510a17f49c7b5af
193 lines
6.8 KiB
Python
193 lines
6.8 KiB
Python
#
|
|
# 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 collections
|
|
|
|
from oslo_log import log as logging
|
|
from oslo_serialization import jsonutils
|
|
|
|
from heat.common import environment_format as env_fmt
|
|
from heat.common import exception
|
|
from heat.common.i18n import _
|
|
|
|
ALLOWED_PARAM_MERGE_STRATEGIES = (OVERWRITE, MERGE, DEEP_MERGE) = (
|
|
'overwrite', 'merge', 'deep_merge')
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def get_param_merge_strategy(merge_strategies, param_key,
|
|
available_strategies=None):
|
|
if not available_strategies:
|
|
available_strategies = {}
|
|
|
|
if merge_strategies is None:
|
|
return OVERWRITE
|
|
|
|
env_default = merge_strategies.get('default', OVERWRITE)
|
|
merge_strategy = merge_strategies.get(
|
|
param_key, available_strategies.get(
|
|
param_key, env_default))
|
|
|
|
if merge_strategy in ALLOWED_PARAM_MERGE_STRATEGIES:
|
|
return merge_strategy
|
|
|
|
return env_default
|
|
|
|
|
|
def merge_list(old, new):
|
|
"""merges lists and comma delimited lists."""
|
|
if not old:
|
|
return new
|
|
|
|
if isinstance(new, list):
|
|
old.extend(new)
|
|
return old
|
|
else:
|
|
return ','.join([old, new])
|
|
|
|
|
|
def merge_map(old, new, deep_merge=False):
|
|
"""Merge nested dictionaries."""
|
|
if not old:
|
|
return new
|
|
|
|
for k, v in new.items():
|
|
if v is not None:
|
|
if not deep_merge:
|
|
old[k] = v
|
|
elif isinstance(v, collections.abc.Mapping):
|
|
old_v = old.get(k)
|
|
old[k] = merge_map(old_v, v, deep_merge) if old_v else v
|
|
elif (isinstance(v, collections.abc.Sequence) and
|
|
not isinstance(v, str)):
|
|
old_v = old.get(k)
|
|
old[k] = merge_list(old_v, v) if old_v else v
|
|
elif isinstance(v, str):
|
|
old[k] = ''.join([old.get(k, ''), v])
|
|
else:
|
|
old[k] = v
|
|
|
|
return old
|
|
|
|
|
|
def parse_param(p_val, p_schema):
|
|
try:
|
|
if p_schema.type == p_schema.MAP:
|
|
if not isinstance(p_val, str):
|
|
p_val = jsonutils.dumps(p_val)
|
|
if p_val:
|
|
return jsonutils.loads(p_val)
|
|
elif not isinstance(p_val, collections.abc.Sequence):
|
|
raise ValueError()
|
|
except (ValueError, TypeError) as err:
|
|
msg = _("Invalid parameter in environment %s.") % str(err)
|
|
raise ValueError(msg)
|
|
return p_val
|
|
|
|
|
|
def merge_parameters(old, new, param_schemata, strategies_in_file,
|
|
available_strategies, env_file):
|
|
|
|
def param_merge(p_key, p_value, p_schema, deep_merge=False):
|
|
p_type = p_schema.type
|
|
p_value = parse_param(p_value, p_schema)
|
|
if p_type == p_schema.MAP:
|
|
old[p_key] = merge_map(old.get(p_key, {}), p_value, deep_merge)
|
|
elif p_type == p_schema.LIST:
|
|
old[p_key] = merge_list(old.get(p_key), p_value)
|
|
elif p_type == p_schema.STRING:
|
|
old[p_key] = ''.join([old.get(p_key, ''), p_value])
|
|
elif p_type == p_schema.NUMBER:
|
|
old[p_key] = old.get(p_key, 0) + p_value
|
|
else:
|
|
raise exception.InvalidMergeStrategyForParam(strategy=MERGE,
|
|
param=p_key)
|
|
|
|
for key, value in new.items():
|
|
# if key not in param_schemata ignore it
|
|
if key in param_schemata and value is not None:
|
|
param_merge_strategy = get_param_merge_strategy(
|
|
strategies_in_file, key, available_strategies)
|
|
if key not in available_strategies:
|
|
available_strategies[key] = param_merge_strategy
|
|
|
|
elif param_merge_strategy != available_strategies[key]:
|
|
raise exception.ConflictingMergeStrategyForParam(
|
|
strategy=param_merge_strategy,
|
|
param=key, env_file=env_file)
|
|
|
|
if not old:
|
|
return new
|
|
|
|
for key, value in new.items():
|
|
# if key not in param_schemata ignore it
|
|
if key in param_schemata and value is not None:
|
|
param_merge_strategy = available_strategies[key]
|
|
if param_merge_strategy == DEEP_MERGE:
|
|
LOG.debug("Deep Merging Parameter: %s", key)
|
|
param_merge(key, value,
|
|
param_schemata[key],
|
|
deep_merge=True)
|
|
elif param_merge_strategy == MERGE:
|
|
LOG.debug("Merging Parameter: %s", key)
|
|
param_merge(key, value, param_schemata[key])
|
|
else:
|
|
LOG.debug("Overriding Parameter: %s", key)
|
|
old[key] = value
|
|
|
|
return old
|
|
|
|
|
|
def merge_environments(environment_files, files,
|
|
params, param_schemata):
|
|
"""Merges environment files into the stack input parameters.
|
|
|
|
If a list of environment files have been specified, this call will
|
|
pull the contents of each from the files dict, parse them as
|
|
environments, and merge them into the stack input params. This
|
|
behavior is the same as earlier versions of the Heat client that
|
|
performed this params population client-side.
|
|
|
|
:param environment_files: ordered names of the environment files
|
|
found in the files dict
|
|
:type environment_files: list or None
|
|
:param files: mapping of stack filenames to contents
|
|
:type files: dict
|
|
:param params: parameters describing the stack
|
|
:type params: dict
|
|
:param param_schemata: parameter schema dict
|
|
:type param_schemata: dict
|
|
"""
|
|
if not environment_files:
|
|
return
|
|
|
|
available_strategies = {}
|
|
|
|
for filename in environment_files:
|
|
raw_env = files[filename]
|
|
parsed_env = env_fmt.parse(raw_env)
|
|
strategies_in_file = parsed_env.pop(
|
|
env_fmt.PARAMETER_MERGE_STRATEGIES, {})
|
|
|
|
for section_key, section_value in parsed_env.items():
|
|
if section_value:
|
|
if section_key in (env_fmt.PARAMETERS,
|
|
env_fmt.PARAMETER_DEFAULTS):
|
|
params[section_key] = merge_parameters(
|
|
params[section_key], section_value,
|
|
param_schemata, strategies_in_file,
|
|
available_strategies, filename)
|
|
else:
|
|
params[section_key] = merge_map(params[section_key],
|
|
section_value)
|