Browse Source

molecule create makes a static inventory

So we do not need openstack.py and makes things simpler. The
molecule/infrastructure/create.yml playbook creates

* a file for
  molecule (molecule/*/.molecule/instance_config.yml) which will be
  converted by molecule
  into (molecule/*/.molecule/ansible_inventory.yml) and used for all
  molecule commands for a given scenario.
* an inventory file (01-hosts.yml) built from the hosts-base.yml file
  by adding the IP address of each VM

The inventory can then be used with ansible-playbook -i inventory and
will concatenate all files found in the directory, in lexicographical
order.
keep-around/441bb62798e1d13de9e6fa259e2b013ebcc09baf
Loïc Dachary 4 years ago
parent
commit
d3936a2426
  1. 1
      .gitignore
  2. 11
      docs/ansible.rst
  3. 4
      hosts-base.yml
  4. 5
      inventory/02-all.yml
  5. 52
      molecule/infrastructure/create.yml
  6. 2
      molecule/infrastructure/destroy.yml
  7. 266
      openstack.py

1
.gitignore

@ -6,4 +6,5 @@ __pycache__
.cache
pytestdebug.log
docs/_build/
inventory/01-hosts.yml
private-key.yml

11
docs/ansible.rst

@ -13,9 +13,18 @@ machine as follows:
ansible-playbook --private-key infrastructure_key \
--user debian \
-i openstack.py \
-i inventory \
securedrop-club-playbook.yml
Inventory
---------
The ansible inventory is created by the
``molecule/infrastructure/create.yml`` playbook and stored in the
``hosts.yml`` file every time the ``molecule create`` command runs.
The inventory is read from the ``hosts-base.yml`` file, the IP address of each
host is added and the result is written into ``hosts.yml``.
Updating
--------

4
hosts-base.yml

@ -0,0 +1,4 @@
all:
hosts:
gitlab-host:
ansible_port: 2222

5
inventory/02-all.yml

@ -0,0 +1,5 @@
all:
children:
# bind_clients:
# hosts:
# icinga-host:

52
molecule/infrastructure/create.yml

@ -7,9 +7,7 @@
molecule_file: "{{ lookup('env', 'MOLECULE_FILE') }}"
molecule_instance_config: "{{ lookup('env', 'MOLECULE_INSTANCE_CONFIG') }}"
molecule_yml: "{{ lookup('file', molecule_file) | from_yaml }}"
# secrets: "{{ lookup('file', '../secrets.yml') | from_yaml }}"
user: debian
ssh_port: 22
hosts_orig: "{{ lookup('file', '../../hosts-base.yml') | from_yaml }}"
vars_files:
- ../../clouds.yml
- ../../private-key.yml
@ -18,22 +16,38 @@
- { role: vm, state: present }
tasks:
- name: Populate instance config dict
- name: instance configuration for molecule
set_fact:
instance_conf_dict: {
'instance': "{{ item.openstack.name }}",
'address': "{{ item.openstack.accessIPv4 }}",
'user': "{{ user }}",
'port': "{{ ssh_port }}",
'identity_file': "{{ ssh_private_keyfile }}", }
with_items: "{{ server.results }}"
register: instance_config_dict
# when: server.changed | bool
instance_conf: |
[
{% for s in server.results %}
{
'instance': '{{ s.openstack.name }}',
'address': '{{ s.openstack.accessIPv4 }}',
'identity_file': '{{ ssh_private_keyfile }}',
'port': '{% if s.openstack.name in hosts_orig.all.hosts and hosts_orig.all.hosts[s.openstack.name].ansible_port is defined %}{{ hosts_orig.all.hosts[s.openstack.name].ansible_port }}{% else %}22{% endif %}',
'user': '{% if s.openstack.name in hosts_orig.all.hosts and hosts_orig.all.hosts[s.openstack.name].ansible_user is defined %}{{ hosts_orig.all.hosts[s.openstack.name].ansible_user }}{% else %}debian{% endif %}',
},
{% endfor %}
]
- name: Convert instance config dict to a list
- name: hosts updates for inventory
set_fact:
instance_conf: "{{ instance_config_dict.results | map(attribute='ansible_facts.instance_conf_dict') | list }}"
# when: server.changed | bool
hosts_updates: |
{
'all': {
'hosts': {
{% for s in server.results %}
'{{ s.openstack.name }}':
{
'ansible_port': '{% if s.openstack.name in hosts_orig.all.hosts and hosts_orig.all.hosts[s.openstack.name].ansible_port is defined %}{{ hosts_orig.all.hosts[s.openstack.name].ansible_port }}{% else %}22{% endif %}',
'ansible_host': '{{ s.openstack.accessIPv4 }}',
'ansible_user': '{% if s.openstack.name in hosts_orig.all.hosts and hosts_orig.all.hosts[s.openstack.name].ansible_user is defined %}{{ hosts_orig.all.hosts[s.openstack.name].ansible_user }}{% else %}debian{% endif %}',
},
{% endfor %}
}
}
}
- name: Dump instance config
copy:
@ -41,4 +55,8 @@
# https://github.com/ansible/ansible/issues/20885
content: "{{ instance_conf | to_json | from_json | molecule_to_yaml | molecule_header }}"
dest: "{{ molecule_instance_config }}"
# when: server.changed | bool
- name: Dump hosts_conf
copy:
content: "{{ hosts_orig | combine(hosts_updates, recursive=True) | to_yaml }}"
dest: "../../inventory/01-hosts.yml"

2
molecule/infrastructure/destroy.yml

@ -5,7 +5,7 @@
vars:
molecule_file: "{{ lookup('env', 'MOLECULE_FILE') }}"
molecule_yml: "{{ lookup('file', molecule_file) | from_yaml }}"
# secrets: "{{ lookup('file', '../../secrets.yml') | from_yaml }}"
hosts_orig: "{{ lookup('file', '../../hosts-base.yml') | from_yaml }}"
vars_files:
- ../../clouds.yml
- ../../private-key.yml

266
openstack.py

@ -1,266 +0,0 @@
#!/usr/bin/env python
# Copyright (c) 2012, Marco Vito Moscaritolo <marco@agavee.com>
# Copyright (c) 2013, Jesse Keating <jesse.keating@rackspace.com>
# Copyright (c) 2015, Hewlett-Packard Development Company, L.P.
# Copyright (c) 2016, Rackspace Australia
#
# This module is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this software. If not, see <http://www.gnu.org/licenses/>.
# The OpenStack Inventory module uses os-client-config for configuration.
# https://github.com/openstack/os-client-config
# This means it will either:
# - Respect normal OS_* environment variables like other OpenStack tools
# - Read values from a clouds.yaml file.
# If you want to configure via clouds.yaml, you can put the file in:
# - Current directory
# - ~/.config/openstack/clouds.yaml
# - /etc/openstack/clouds.yaml
# - /etc/ansible/openstack.yml
# The clouds.yaml file can contain entries for multiple clouds and multiple
# regions of those clouds. If it does, this inventory module will by default
# connect to all of them and present them as one contiguous inventory. You
# can limit to one cloud by passing the `--cloud` parameter, or use the
# OS_CLOUD environment variable. If caching is enabled, and a cloud is
# selected, then per-cloud cache folders will be used.
#
# See the adjacent openstack.yml file for an example config file
# There are two ansible inventory specific options that can be set in
# the inventory section.
# expand_hostvars controls whether or not the inventory will make extra API
# calls to fill out additional information about each server
# use_hostnames changes the behavior from registering every host with its UUID
# and making a group of its hostname to only doing this if the
# hostname in question has more than one server
# fail_on_errors causes the inventory to fail and return no hosts if one cloud
# has failed (for example, bad credentials or being offline).
# When set to False, the inventory will return hosts from
# whichever other clouds it can contact. (Default: True)
#
# Also it is possible to pass the correct user by setting an ansible_user: $myuser
# metadata attribute.
import argparse
import collections
import os
import sys
import time
from distutils.version import StrictVersion
try:
import json
except:
import simplejson as json
import os_client_config
import shade
import shade.inventory
CONFIG_FILES = ['/etc/ansible/openstack.yaml', '/etc/ansible/openstack.yml']
def get_groups_from_server(server_vars, namegroup=True):
groups = []
region = server_vars['region']
cloud = server_vars['cloud']
metadata = server_vars.get('metadata', {})
# Create a group for the cloud
groups.append(cloud)
# Create a group on region
groups.append(region)
# And one by cloud_region
groups.append("%s_%s" % (cloud, region))
# Check if group metadata key in servers' metadata
if 'group' in metadata:
groups.append(metadata['group'])
for extra_group in metadata.get('groups', '').split(','):
if extra_group:
groups.append(extra_group.strip())
groups.append('instance-%s' % server_vars['id'])
if namegroup:
groups.append(server_vars['name'])
for key in ('flavor', 'image'):
if 'name' in server_vars[key]:
groups.append('%s-%s' % (key, server_vars[key]['name']))
for key, value in iter(metadata.items()):
groups.append('meta-%s_%s' % (key, value))
az = server_vars.get('az', None)
if az:
# Make groups for az, region_az and cloud_region_az
groups.append(az)
groups.append('%s_%s' % (region, az))
groups.append('%s_%s_%s' % (cloud, region, az))
return groups
def get_host_groups(inventory, refresh=False, cloud=None, hold=False):
(cache_file, cache_expiration_time) = get_cache_settings(cloud)
if is_cache_stale(cache_file, cache_expiration_time, refresh=refresh, hold=hold):
groups = to_json(get_host_groups_from_cloud(inventory))
open(cache_file, 'w').write(groups)
else:
groups = open(cache_file, 'r').read()
return groups
def append_hostvars(hostvars, groups, key, server, namegroup=False):
hostvars[key] = dict(
ansible_ssh_host=server['public_v4'],
ansible_host=server['public_v4'],
openstack=server)
metadata = server.get('metadata', {})
if 'ansible_user' in metadata:
hostvars[key]['ansible_user'] = metadata['ansible_user']
for group in get_groups_from_server(server, namegroup=namegroup):
groups[group].append(key)
def get_host_groups_from_cloud(inventory):
groups = collections.defaultdict(list)
firstpass = collections.defaultdict(list)
hostvars = {}
list_args = {}
if hasattr(inventory, 'extra_config'):
use_hostnames = inventory.extra_config['use_hostnames']
list_args['expand'] = inventory.extra_config['expand_hostvars']
if StrictVersion(shade.__version__) >= StrictVersion("1.6.0"):
list_args['fail_on_cloud_config'] = \
inventory.extra_config['fail_on_errors']
else:
use_hostnames = False
for server in inventory.list_hosts(**list_args):
if 'public_v4' not in server:
continue
firstpass[server['name']].append(server)
for name, servers in firstpass.items():
if len(servers) == 1 and use_hostnames:
append_hostvars(hostvars, groups, name, servers[0])
else:
server_ids = set()
# Trap for duplicate results
for server in servers:
server_ids.add(server['id'])
if len(server_ids) == 1 and use_hostnames:
append_hostvars(hostvars, groups, name, servers[0])
else:
for server in servers:
append_hostvars(
hostvars, groups, server['id'], server,
namegroup=True)
groups['_meta'] = {'hostvars': hostvars}
return groups
def is_cache_stale(cache_file, cache_expiration_time, refresh=False, hold=False):
''' Determines if cache file has expired, or if it is still valid '''
if refresh:
return True
if hold:
return False
if os.path.isfile(cache_file) and os.path.getsize(cache_file) > 0:
mod_time = os.path.getmtime(cache_file)
current_time = time.time()
if (mod_time + cache_expiration_time) > current_time:
return False
return True
def get_cache_settings(cloud=None):
config = os_client_config.config.OpenStackConfig(
config_files=os_client_config.config.CONFIG_FILES + CONFIG_FILES)
# For inventory-wide caching
cache_expiration_time = config.get_cache_expiration_time()
cache_path = config.get_cache_path()
if cloud:
cache_path = '{0}_{1}'.format(cache_path, cloud)
if not os.path.exists(cache_path):
os.makedirs(cache_path)
cache_file = os.path.join(cache_path, 'ansible-inventory.cache')
return (cache_file, cache_expiration_time)
def to_json(in_dict):
return json.dumps(in_dict, sort_keys=True, indent=2)
def parse_args():
parser = argparse.ArgumentParser(description='OpenStack Inventory Module')
parser.add_argument('--cloud', default=os.environ.get('OS_CLOUD'),
help='Cloud name (default: None')
parser.add_argument('--private',
action='store_true',
help='Use private address for ansible host')
parser.add_argument('--hold', action='store_true', default=os.environ.get('USE_CACHE'),
help='Force use of cached information')
parser.add_argument('--refresh', action='store_true',
help='Refresh cached information')
parser.add_argument('--debug', action='store_true', default=False,
help='Enable debug output')
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--list', action='store_true',
help='List active servers')
group.add_argument('--host', help='List details about the specific host')
return parser.parse_args()
def main():
args = parse_args()
try:
config_files = os_client_config.config.CONFIG_FILES + CONFIG_FILES
shade.simple_logging(debug=args.debug)
inventory_args = dict(
refresh=args.refresh,
config_files=config_files,
private=args.private,
cloud=args.cloud,
)
if hasattr(shade.inventory.OpenStackInventory, 'extra_config'):
inventory_args.update(dict(
config_key='ansible',
config_defaults={
'use_hostnames': False,
'expand_hostvars': True,
'fail_on_errors': True,
}
))
inventory = shade.inventory.OpenStackInventory(**inventory_args)
if args.list:
output = get_host_groups(inventory, refresh=args.refresh, cloud=args.cloud, hold=args.hold)
elif args.host:
output = to_json(inventory.get_host(args.host))
print(output)
except shade.OpenStackCloudException as e:
sys.stderr.write('%s\n' % e.message)
sys.exit(1)
sys.exit(0)
if __name__ == '__main__':
main()
Loading…
Cancel
Save