156 lines
6.4 KiB
Python
156 lines
6.4 KiB
Python
#!/usr/bin/env python
|
|
|
|
# Copyright 2019 Red Hat, 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.
|
|
|
|
from collections import OrderedDict
|
|
import os
|
|
import tempfile
|
|
import yaml
|
|
|
|
|
|
class TemplateDumper(yaml.SafeDumper):
|
|
def represent_ordered_dict(self, data):
|
|
return self.represent_dict(data.items())
|
|
|
|
|
|
TemplateDumper.add_representer(OrderedDict,
|
|
TemplateDumper.represent_ordered_dict)
|
|
|
|
|
|
class TripleoInventories(object):
|
|
def __init__(self, stack_to_inv_obj_map):
|
|
"""
|
|
Input: a mapping of stack->TripleoInventory objects, e.g.
|
|
stack_to_inv_obj_map['central'] = TripleoInventory('central')
|
|
stack_to_inv_obj_map['edge0'] = TripleoInventory('edge0')
|
|
"""
|
|
self.stack_to_inv_obj_map = stack_to_inv_obj_map
|
|
|
|
def _merge(self, dynamic=True):
|
|
"""Merge TripleoInventory objects"""
|
|
inventory = OrderedDict()
|
|
if dynamic:
|
|
inventory['_meta'] = {'hostvars': {}}
|
|
for stack, inv_obj in self.stack_to_inv_obj_map.items():
|
|
# convert each inventory object into an ordered dict
|
|
inv = inv_obj.list(dynamic)
|
|
# only want one undercloud, shouldn't matter which
|
|
if 'Undercloud' not in inventory.keys():
|
|
inventory['Undercloud'] = inv['Undercloud']
|
|
if dynamic:
|
|
inventory['Undercloud']['hosts'] = ['undercloud']
|
|
else:
|
|
inventory['Undercloud']['hosts'] = {'undercloud': {}}
|
|
# add 'plans' to create a list to append to
|
|
inventory['Undercloud']['vars']['plans'] = []
|
|
|
|
# save the plan for this stack in the plans list
|
|
plan = inv['Undercloud']['vars']['plan']
|
|
if plan is not None:
|
|
inventory['Undercloud']['vars']['plans'].append(plan)
|
|
|
|
for key in inv.keys():
|
|
if key != 'Undercloud':
|
|
new_key = stack + '_' + key
|
|
|
|
if key not in ('_meta', 'overcloud', stack):
|
|
# Merge into a top level group
|
|
if dynamic:
|
|
inventory.setdefault(key, {'children': []})
|
|
inventory[key]['children'].append(new_key)
|
|
inventory[key]['children'].sort()
|
|
else:
|
|
inventory.setdefault(key, {'children': {}})
|
|
inventory[key]['children'][new_key] = {}
|
|
if 'children' in inv[key].keys():
|
|
roles = []
|
|
for child in inv[key]['children']:
|
|
roles.append(stack + '_' + child)
|
|
roles.sort()
|
|
if dynamic:
|
|
inventory[new_key] = {
|
|
'children': roles
|
|
}
|
|
else:
|
|
inventory[new_key] = {
|
|
'children': {x: {} for x in roles}
|
|
}
|
|
if 'vars' in inv[key]:
|
|
inventory[new_key]['vars'] = inv[key]['vars']
|
|
if key == 'allovercloud':
|
|
# useful to have just stack name refer to children
|
|
if dynamic:
|
|
inventory[stack] = {'children': [new_key]}
|
|
else:
|
|
inventory[stack] = {'children': {new_key: {}}}
|
|
else:
|
|
if key != '_meta':
|
|
inventory[new_key] = inv[key]
|
|
elif dynamic:
|
|
inventory['_meta']['hostvars'].update(
|
|
inv['_meta'].get('hostvars', {})
|
|
)
|
|
|
|
# 'plan' doesn't make sense when using multiple plans
|
|
if len(self.stack_to_inv_obj_map) > 1:
|
|
del inventory['Undercloud']['vars']['plan']
|
|
|
|
# Ensure there is an overcloud group for backwards compat
|
|
# If we don't have an stack named "overcloud" use the first stack
|
|
if 'overcloud' not in inventory:
|
|
first_stack = list(self.stack_to_inv_obj_map.keys())[0]
|
|
if dynamic:
|
|
inventory['overcloud'] = {'children': [first_stack]}
|
|
else:
|
|
inventory['overcloud'] = {'children': {first_stack: {}}}
|
|
|
|
# sort plans list for consistency
|
|
inventory['Undercloud']['vars']['plans'].sort()
|
|
return inventory
|
|
|
|
def list(self, dynamic=True):
|
|
return self._merge(dynamic)
|
|
|
|
def write_static_inventory(self, inventory_file_path, extra_vars=None):
|
|
"""Convert OrderedDict inventory to static yaml format in a file."""
|
|
allowed_extensions = ('.yaml', '.yml', '.json')
|
|
if not os.path.splitext(inventory_file_path)[1] in allowed_extensions:
|
|
raise ValueError("Path %s does not end with one of %s extensions"
|
|
% (inventory_file_path,
|
|
",".join(allowed_extensions)))
|
|
|
|
inventory = self._merge(dynamic=False)
|
|
|
|
if extra_vars:
|
|
for var, value in extra_vars.items():
|
|
if var in inventory:
|
|
inventory[var]['vars'].update(value)
|
|
|
|
# Atomic update as concurrent tripleoclient commands can call this
|
|
inventory_file_dir = os.path.dirname(inventory_file_path)
|
|
with tempfile.NamedTemporaryFile(
|
|
'w',
|
|
dir=inventory_file_dir,
|
|
delete=False) as inventory_file:
|
|
yaml.dump(inventory, inventory_file, TemplateDumper)
|
|
os.rename(inventory_file.name, inventory_file_path)
|
|
|
|
def host(self):
|
|
# Dynamic inventory scripts must return empty json if they don't
|
|
# provide detailed info for hosts:
|
|
# http://docs.ansible.com/ansible/developing_inventory.html
|
|
return {}
|