Init: mediaserver

This commit is contained in:
2023-02-08 12:13:28 +01:00
parent 848bc9739c
commit f7c23d4ba9
31914 changed files with 6175775 additions and 0 deletions

View File

@@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2014, Hewlett-Packard Development Company, L.P.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
class ModuleDocFragment(object):
# Standard openstack documentation fragment
DOCUMENTATION = r'''
options:
cloud:
description:
- Named cloud or cloud config to operate against.
If I(cloud) is a string, it references a named cloud config as defined
in an OpenStack clouds.yaml file. Provides default values for I(auth)
and I(auth_type). This parameter is not needed if I(auth) is provided
or if OpenStack OS_* environment variables are present.
If I(cloud) is a dict, it contains a complete cloud configuration like
would be in a section of clouds.yaml.
type: raw
auth:
description:
- Dictionary containing auth information as needed by the cloud's auth
plugin strategy. For the default I(password) plugin, this would contain
I(auth_url), I(username), I(password), I(project_name) and any
information about domains (for example, I(user_domain_name) or
I(project_domain_name)) if the cloud supports them.
For other plugins,
this param will need to contain whatever parameters that auth plugin
requires. This parameter is not needed if a named cloud is provided or
OpenStack OS_* environment variables are present.
type: dict
auth_type:
description:
- Name of the auth plugin to use. If the cloud uses something other than
password authentication, the name of the plugin should be indicated here
and the contents of the I(auth) parameter should be updated accordingly.
type: str
region_name:
description:
- Name of the region.
type: str
wait:
description:
- Should ansible wait until the requested resource is complete.
type: bool
default: yes
timeout:
description:
- How long should ansible wait for the requested resource.
type: int
default: 180
api_timeout:
description:
- How long should the socket layer wait before timing out for API calls.
If this is omitted, nothing will be passed to the requests library.
type: int
validate_certs:
description:
- Whether or not SSL API requests should be verified.
- Before Ansible 2.3 this defaulted to C(yes).
type: bool
aliases: [ verify ]
ca_cert:
description:
- A path to a CA Cert bundle that can be used as part of verifying
SSL API requests.
type: str
aliases: [ cacert ]
client_cert:
description:
- A path to a client certificate to use as part of the SSL transaction.
type: str
aliases: [ cert ]
client_key:
description:
- A path to a client key to use as part of the SSL transaction.
type: str
aliases: [ key ]
interface:
description:
- Endpoint URL type to fetch from the service catalog.
type: str
choices: [ admin, internal, public ]
default: public
aliases: [ endpoint_type ]
availability_zone:
description:
- Ignored. Present for backwards compatibility
type: str
sdk_log_path:
description:
- Path to the logfile of the OpenStackSDK. If empty no log is written
type: str
sdk_log_level:
description: Log level of the OpenStackSDK
type: str
default: INFO
choices: [INFO, DEBUG]
requirements:
- python >= 3.6
- openstacksdk >= 0.36, < 0.99.0
notes:
- The standard OpenStack environment variables, such as C(OS_USERNAME)
may be used instead of providing explicit values.
- Auth information is driven by openstacksdk, which means that values
can come from a yaml config file in /etc/ansible/openstack.yaml,
/etc/openstack/clouds.yaml or ~/.config/openstack/clouds.yaml, then from
standard environment variables, then finally by explicit parameters in
plays. More information can be found at
U(https://docs.openstack.org/openstacksdk/)
'''

View File

@@ -0,0 +1,415 @@
# -*- coding: utf-8 -*-
# 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
# Copyright (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = '''
---
name: openstack
author: OpenStack Ansible SIG
short_description: OpenStack inventory source
description:
- Get inventory hosts from OpenStack clouds
- Uses openstack.(yml|yaml) YAML configuration file to configure the inventory plugin
- Uses standard clouds.yaml YAML configuration file to configure cloud credentials
options:
plugin:
description: token that ensures this is a source file for the 'openstack' plugin.
required: True
choices: ['openstack', 'openstack.cloud.openstack']
show_all:
description: toggles showing all vms vs only those with a working IP
type: bool
default: false
inventory_hostname:
description: |
What to register as the inventory hostname.
If set to 'uuid' the uuid of the server will be used and a
group will be created for the server name.
If set to 'name' the name of the server will be used unless
there are more than one server with the same name in which
case the 'uuid' logic will be used.
Default is to do 'name', which is the opposite of the old
openstack.py inventory script's option use_hostnames)
type: string
choices:
- name
- uuid
default: "name"
use_names:
description: |
Use the host's 'name' instead of 'interface_ip' for the 'ansible_host' and
'ansible_ssh_host' facts. This might be desired when using jump or
bastion hosts and the name is the FQDN of the host.
type: bool
default: false
expand_hostvars:
description: |
Run extra commands on each host to fill in additional
information about the host. May interrogate cinder and
neutron and can be expensive for people with many hosts.
(Note, the default value of this is opposite from the default
old openstack.py inventory script's option expand_hostvars)
type: bool
default: false
private:
description: |
Use the private interface of each server, if it has one, as
the host's IP in the inventory. This can be useful if you are
running ansible inside a server in the cloud and would rather
communicate to your servers over the private network.
type: bool
default: false
only_clouds:
description: |
List of clouds from clouds.yaml to use, instead of using
the whole list.
type: list
elements: str
default: []
fail_on_errors:
description: |
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 as many hosts as
it can from as many clouds as it can contact. (Note, the
default value of this is opposite from the old openstack.py
inventory script's option fail_on_errors)
type: bool
default: false
all_projects:
description: |
Lists servers from all projects
type: bool
default: false
clouds_yaml_path:
description: |
Override path to clouds.yaml file. If this value is given it
will be searched first. The default path for the
ansible inventory adds /etc/ansible/openstack.yaml and
/etc/ansible/openstack.yml to the regular locations documented
at https://docs.openstack.org/os-client-config/latest/user/configuration.html#config-files
type: list
elements: str
env:
- name: OS_CLIENT_CONFIG_FILE
compose:
description: Create vars from jinja2 expressions.
type: dictionary
default: {}
groups:
description: Add hosts to group based on Jinja2 conditionals.
type: dictionary
default: {}
legacy_groups:
description: Automatically create groups from host variables.
type: bool
default: true
requirements:
- "python >= 3.6"
- "openstacksdk >= 0.28, < 0.99.0"
extends_documentation_fragment:
- inventory_cache
- constructed
'''
EXAMPLES = '''
# file must be named openstack.yaml or openstack.yml
# Make the plugin behave like the default behavior of the old script
plugin: openstack.cloud.openstack
expand_hostvars: yes
fail_on_errors: yes
all_projects: yes
'''
import collections
import sys
import logging
from ansible.errors import AnsibleParserError
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
from ansible.utils.display import Display
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (
ensure_compatibility
)
display = Display()
os_logger = logging.getLogger("openstack")
try:
# Due to the name shadowing we should import other way
import importlib
sdk = importlib.import_module('openstack')
sdk_inventory = importlib.import_module('openstack.cloud.inventory')
client_config = importlib.import_module('openstack.config.loader')
sdk_exceptions = importlib.import_module("openstack.exceptions")
HAS_SDK = True
except ImportError:
display.vvvv("Couldn't import Openstack SDK modules")
HAS_SDK = False
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
''' Host inventory provider for ansible using OpenStack clouds. '''
NAME = 'openstack.cloud.openstack'
def parse(self, inventory, loader, path, cache=True):
super(InventoryModule, self).parse(inventory, loader, path)
cache_key = self._get_cache_prefix(path)
# file is config file
self._config_data = self._read_config_data(path)
msg = ''
if not self._config_data:
msg = 'File empty. this is not my config file'
elif 'plugin' in self._config_data and self._config_data['plugin'] not in (self.NAME, 'openstack'):
msg = 'plugin config file, but not for us: %s' % self._config_data['plugin']
elif 'plugin' not in self._config_data and 'clouds' not in self._config_data:
msg = "it's not a plugin configuration nor a clouds.yaml file"
elif not HAS_SDK:
msg = "openstacksdk is required for the OpenStack inventory plugin. OpenStack inventory sources will be skipped."
if not msg:
try:
ensure_compatibility(sdk.version.__version__)
except ImportError as e:
msg = ("Incompatible openstacksdk library found: {error}."
.format(error=str(e)))
if msg:
display.vvvv(msg)
raise AnsibleParserError(msg)
if 'clouds' in self._config_data:
self.display.vvvv(
"Found clouds config file instead of plugin config. "
"Using default configuration."
)
self._config_data = {}
# update cache if the user has caching enabled and the cache is being refreshed
# will update variable below in the case of an expired cache
cache_needs_update = not cache and self.get_option('cache')
if cache:
cache = self.get_option('cache')
source_data = None
if cache:
self.display.vvvv("Reading inventory data from cache: %s" % cache_key)
try:
source_data = self._cache[cache_key]
except KeyError:
# cache expired or doesn't exist yet
display.vvvv("Inventory data cache not found")
cache_needs_update = True
if not source_data:
self.display.vvvv("Getting hosts from Openstack clouds")
clouds_yaml_path = self._config_data.get('clouds_yaml_path')
if clouds_yaml_path:
config_files = (
clouds_yaml_path
+ client_config.CONFIG_FILES
)
else:
config_files = None
# Redict logging to stderr so it does not mix with output
# particular ansible-inventory JSON output
# TODO(mordred) Integrate openstack's logging with ansible's logging
if self.display.verbosity > 3:
sdk.enable_logging(debug=True, stream=sys.stderr)
else:
sdk.enable_logging(stream=sys.stderr)
cloud_inventory = sdk_inventory.OpenStackInventory(
config_files=config_files,
private=self._config_data.get('private', False))
self.display.vvvv("Found %d cloud(s) in Openstack" %
len(cloud_inventory.clouds))
only_clouds = self._config_data.get('only_clouds', [])
if only_clouds and not isinstance(only_clouds, list):
raise ValueError(
'OpenStack Inventory Config Error: only_clouds must be'
' a list')
if only_clouds:
new_clouds = []
for cloud in cloud_inventory.clouds:
self.display.vvvv("Looking at cloud : %s" % cloud.name)
if cloud.name in only_clouds:
self.display.vvvv("Selecting cloud : %s" % cloud.name)
new_clouds.append(cloud)
cloud_inventory.clouds = new_clouds
self.display.vvvv("Selected %d cloud(s)" %
len(cloud_inventory.clouds))
expand_hostvars = self._config_data.get('expand_hostvars', False)
fail_on_errors = self._config_data.get('fail_on_errors', False)
all_projects = self._config_data.get('all_projects', False)
self.use_names = self._config_data.get('use_names', False)
source_data = []
try:
source_data = cloud_inventory.list_hosts(
expand=expand_hostvars, fail_on_cloud_config=fail_on_errors,
all_projects=all_projects)
except Exception as e:
self.display.warning("Couldn't list Openstack hosts. "
"See logs for details")
os_logger.error(e.message)
finally:
if cache_needs_update:
self._cache[cache_key] = source_data
self._populate_from_source(source_data)
def _populate_from_source(self, source_data):
groups = collections.defaultdict(list)
firstpass = collections.defaultdict(list)
hostvars = {}
use_server_id = (
self._config_data.get('inventory_hostname', 'name') != 'name')
show_all = self._config_data.get('show_all', False)
for server in source_data:
if 'interface_ip' not in server and not show_all:
continue
firstpass[server['name']].append(server)
for name, servers in firstpass.items():
if len(servers) == 1 and not use_server_id:
self._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 not use_server_id:
self._append_hostvars(hostvars, groups, name, servers[0])
else:
for server in servers:
self._append_hostvars(
hostvars, groups, server['id'], server,
namegroup=True)
self._set_variables(hostvars, groups)
def _set_variables(self, hostvars, groups):
strict = self.get_option('strict')
# set vars in inventory from hostvars
for host in hostvars:
# actually update inventory
for key in hostvars[host]:
self.inventory.set_variable(host, key, hostvars[host][key])
# create composite vars
self._set_composite_vars(
self._config_data.get('compose'), self.inventory.get_host(host).get_vars(), host, strict)
# constructed groups based on conditionals
self._add_host_to_composed_groups(
self._config_data.get('groups'), hostvars[host], host, strict)
# constructed groups based on jinja expressions
self._add_host_to_keyed_groups(
self._config_data.get('keyed_groups'), hostvars[host], host, strict)
for group_name, group_hosts in groups.items():
gname = self.inventory.add_group(group_name)
for host in group_hosts:
if gname == host:
display.vvvv("Same name for host %s and group %s" % (host, gname))
self.inventory.add_host(host, gname)
else:
self.inventory.add_child(gname, host)
def _get_groups_from_server(self, 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
if 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 _append_hostvars(self, hostvars, groups, current_host,
server, namegroup=False):
if not self.use_names:
hostvars[current_host] = dict(
ansible_ssh_host=server['interface_ip'],
ansible_host=server['interface_ip'],
openstack=server,
)
if self.use_names:
hostvars[current_host] = dict(
ansible_ssh_host=server['name'],
ansible_host=server['name'],
openstack=server,
)
self.inventory.add_host(current_host)
if self.get_option('legacy_groups'):
for group in self._get_groups_from_server(server, namegroup=namegroup):
groups[group].append(current_host)
def verify_file(self, path):
if super(InventoryModule, self).verify_file(path):
for fn in ('openstack', 'clouds'):
for suffix in ('yaml', 'yml'):
maybe = '{fn}.{suffix}'.format(fn=fn, suffix=suffix)
if path.endswith(maybe):
self.display.vvvv("Valid plugin config file found")
return True
return False

View File

@@ -0,0 +1,68 @@
# This code is part of Ansible, but is an independent component.
# This particular file snippet, and this file snippet only, is BSD licensed.
# Modules you write using this snippet, which is embedded dynamically by Ansible
# still belong to the author of the module, and may assign their own license
# to the complete work.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import openstack_full_argument_spec
def ironic_argument_spec(**kwargs):
spec = dict(
auth_type=dict(required=False),
ironic_url=dict(required=False),
)
spec.update(kwargs)
return openstack_full_argument_spec(**spec)
# TODO(dtantsur): inherit the collection's base module
class IronicModule(AnsibleModule):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._update_ironic_auth()
def _update_ironic_auth(self):
"""Validate and update authentication parameters for ironic."""
if (
self.params['auth_type'] in [None, 'None', 'none']
and self.params['ironic_url'] is None
and not self.params['cloud']
and not (self.params['auth']
and self.params['auth'].get('endpoint'))
):
self.fail_json(msg=("Authentication appears to be disabled, "
"Please define either ironic_url, or cloud, "
"or auth.endpoint"))
if (
self.params['ironic_url']
and self.params['auth_type'] in [None, 'None', 'none']
and not (self.params['auth']
and self.params['auth'].get('endpoint'))
):
self.params['auth'] = dict(
endpoint=self.params['ironic_url']
)

View File

@@ -0,0 +1,470 @@
# This code is part of Ansible, but is an independent component.
# This particular file snippet, and this file snippet only, is BSD licensed.
# Modules you write using this snippet, which is embedded dynamically by Ansible
# still belong to the author of the module, and may assign their own license
# to the complete work.
#
# Copyright 2019 Red Hat, Inc.
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import abc
import copy
from ansible.module_utils.six import raise_from
try:
from ansible.module_utils.compat.version import StrictVersion
except ImportError:
try:
from distutils.version import StrictVersion
except ImportError as exc:
raise_from(ImportError('To use this plugin or module with ansible-core'
' < 2.11, you need to use Python < 3.12 with '
'distutils.version present'), exc)
import importlib
import os
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
OVERRIDES = {'os_client_config': 'config',
'os_endpoint': 'catalog_endpoint',
'os_flavor': 'compute_flavor',
'os_flavor_info': 'compute_flavor_info',
'os_group': 'identity_group',
'os_group_info': 'identity_group_info',
'os_ironic': 'baremetal_node',
'os_ironic_inspect': 'baremetal_inspect',
'os_ironic_node': 'baremetal_node_action',
'os_keystone_domain': 'identity_domain',
'os_keystone_domain_info': 'identity_domain_info',
'os_keystone_endpoint': 'endpoint',
'os_keystone_identity_provider': 'federation_idp',
'os_keystone_identity_provider_info': 'federation_idp_info',
'os_keystone_mapping': 'federation_mapping',
'os_keystone_mapping_info': 'federation_mapping_info',
'os_keystone_role': 'identity_role',
'os_keystone_service': 'catalog_service',
'os_listener': 'lb_listener',
'os_member': 'lb_member',
'os_nova_flavor': 'compute_flavor',
'os_nova_host_aggregate': 'host_aggregate',
'os_pool': 'lb_pool',
'os_user': 'identity_user',
'os_user_group': 'group_assignment',
'os_user_info': 'identity_user_info',
'os_user_role': 'role_assignment',
'os_zone': 'dns_zone'}
CUSTOM_VAR_PARAMS = ['min_ver', 'max_ver']
MINIMUM_SDK_VERSION = '0.36.0'
MAXIMUM_SDK_VERSION = '0.98.999'
def ensure_compatibility(version, min_version=None, max_version=None):
""" Raises ImportError if the specified version does not
meet the minimum and maximum version requirements"""
if min_version and MINIMUM_SDK_VERSION:
min_version = max(StrictVersion(MINIMUM_SDK_VERSION),
StrictVersion(min_version))
elif MINIMUM_SDK_VERSION:
min_version = StrictVersion(MINIMUM_SDK_VERSION)
if max_version and MAXIMUM_SDK_VERSION:
max_version = min(StrictVersion(MAXIMUM_SDK_VERSION),
StrictVersion(max_version))
elif MAXIMUM_SDK_VERSION:
max_version = StrictVersion(MAXIMUM_SDK_VERSION)
if min_version and StrictVersion(version) < min_version:
raise ImportError(
"Version MUST be >={min_version} and <={max_version}, but"
" {version} is smaller than minimum version {min_version}"
.format(version=version,
min_version=min_version,
max_version=max_version))
if max_version and StrictVersion(version) > max_version:
raise ImportError(
"Version MUST be >={min_version} and <={max_version}, but"
" {version} is larger than maximum version {max_version}"
.format(version=version,
min_version=min_version,
max_version=max_version))
def openstack_argument_spec():
# DEPRECATED: This argument spec is only used for the deprecated old
# OpenStack modules. It turns out that modern OpenStack auth is WAY
# more complex than this.
# Consume standard OpenStack environment variables.
# This is mainly only useful for ad-hoc command line operation as
# in playbooks one would assume variables would be used appropriately
OS_AUTH_URL = os.environ.get('OS_AUTH_URL', 'http://127.0.0.1:35357/v2.0/')
OS_PASSWORD = os.environ.get('OS_PASSWORD', None)
OS_REGION_NAME = os.environ.get('OS_REGION_NAME', None)
OS_USERNAME = os.environ.get('OS_USERNAME', 'admin')
OS_TENANT_NAME = os.environ.get('OS_TENANT_NAME', OS_USERNAME)
spec = dict(
login_username=dict(default=OS_USERNAME),
auth_url=dict(default=OS_AUTH_URL),
region_name=dict(default=OS_REGION_NAME),
availability_zone=dict(),
)
if OS_PASSWORD:
spec['login_password'] = dict(default=OS_PASSWORD)
else:
spec['login_password'] = dict(required=True)
if OS_TENANT_NAME:
spec['login_tenant_name'] = dict(default=OS_TENANT_NAME)
else:
spec['login_tenant_name'] = dict(required=True)
return spec
def openstack_find_nova_addresses(addresses, ext_tag, key_name=None):
ret = []
for (k, v) in iteritems(addresses):
if key_name and k == key_name:
ret.extend([addrs['addr'] for addrs in v])
else:
for interface_spec in v:
if 'OS-EXT-IPS:type' in interface_spec and interface_spec['OS-EXT-IPS:type'] == ext_tag:
ret.append(interface_spec['addr'])
return ret
def openstack_full_argument_spec(**kwargs):
spec = dict(
cloud=dict(default=None, type='raw'),
auth_type=dict(default=None),
auth=dict(default=None, type='dict', no_log=True),
region_name=dict(default=None),
availability_zone=dict(default=None),
validate_certs=dict(default=None, type='bool', aliases=['verify']),
ca_cert=dict(default=None, aliases=['cacert']),
client_cert=dict(default=None, aliases=['cert']),
client_key=dict(default=None, no_log=True, aliases=['key']),
wait=dict(default=True, type='bool'),
timeout=dict(default=180, type='int'),
api_timeout=dict(default=None, type='int'),
interface=dict(
default='public', choices=['public', 'internal', 'admin'],
aliases=['endpoint_type']),
sdk_log_path=dict(default=None, type='str'),
sdk_log_level=dict(
default='INFO', type='str', choices=['INFO', 'DEBUG']),
)
# Filter out all our custom parameters before passing to AnsibleModule
kwargs_copy = copy.deepcopy(kwargs)
for v in kwargs_copy.values():
for c in CUSTOM_VAR_PARAMS:
v.pop(c, None)
spec.update(kwargs_copy)
return spec
def openstack_module_kwargs(**kwargs):
ret = {}
for key in ('mutually_exclusive', 'required_together', 'required_one_of'):
if key in kwargs:
if key in ret:
ret[key].extend(kwargs[key])
else:
ret[key] = kwargs[key]
return ret
# for compatibility with old versions
def openstack_cloud_from_module(module, min_version=None, max_version=None):
try:
# Due to the name shadowing we should import other way
sdk = importlib.import_module('openstack')
except ImportError:
module.fail_json(msg='openstacksdk is required for this module')
try:
ensure_compatibility(sdk.version.__version__,
min_version, max_version)
except ImportError as e:
module.fail_json(
msg="Incompatible openstacksdk library found: {error}."
.format(error=str(e)))
cloud_config = module.params.pop('cloud', None)
try:
if isinstance(cloud_config, dict):
fail_message = (
"A cloud config dict was provided to the cloud parameter"
" but also a value was provided for {param}. If a cloud"
" config dict is provided, {param} should be"
" excluded.")
for param in (
'auth', 'region_name', 'validate_certs',
'ca_cert', 'client_key', 'api_timeout', 'auth_type'):
if module.params[param] is not None:
module.fail_json(msg=fail_message.format(param=param))
# For 'interface' parameter, fail if we receive a non-default value
if module.params['interface'] != 'public':
module.fail_json(msg=fail_message.format(param='interface'))
return sdk, sdk.connect(**cloud_config)
else:
return sdk, sdk.connect(
cloud=cloud_config,
auth_type=module.params['auth_type'],
auth=module.params['auth'],
region_name=module.params['region_name'],
verify=module.params['validate_certs'],
cacert=module.params['ca_cert'],
key=module.params['client_key'],
api_timeout=module.params['api_timeout'],
interface=module.params['interface'],
)
except sdk.exceptions.SDKException as e:
# Probably a cloud configuration/login error
module.fail_json(msg=str(e))
class OpenStackModule:
"""Openstack Module is a base class for all Openstack Module classes.
The class has `run` function that should be overriden in child classes,
the provided methods include:
Methods:
params: Dictionary of Ansible module parameters.
module_name: Module name (i.e. server_action)
sdk_version: Version of used OpenstackSDK.
results: Dictionary for return of Ansible module,
must include `changed` keyword.
exit, exit_json: Exit module and return data inside, must include
changed` keyword in a data.
fail, fail_json: Exit module with failure, has `msg` keyword to
specify a reason of failure.
conn: Connection to SDK object.
log: Print message to system log.
debug: Print debug message to system log, prints if Ansible Debug is
enabled or verbosity is more than 2.
check_deprecated_names: Function that checks if module was called with
a deprecated name and prints the correct name
with deprecation warning.
check_versioned: helper function to check that all arguments are known
in the current SDK version.
run: method that executes and shall be overriden in inherited classes.
Args:
deprecated_names: Should specify deprecated modules names for current
module.
argument_spec: Used for construction of Openstack common arguments.
module_kwargs: Additional arguments for Ansible Module.
"""
deprecated_names = ()
argument_spec = {}
module_kwargs = {}
module_min_sdk_version = None
module_max_sdk_version = None
def __init__(self):
"""Initialize Openstack base class.
Set up variables, connection to SDK and check if there are
deprecated names.
"""
self.ansible = AnsibleModule(
openstack_full_argument_spec(**self.argument_spec),
**self.module_kwargs)
self.params = self.ansible.params
self.module_name = self.ansible._name
self.check_mode = self.ansible.check_mode
self.sdk_version = None
self.results = {'changed': False}
self.exit = self.exit_json = self.ansible.exit_json
self.fail = self.fail_json = self.ansible.fail_json
self.warn = self.ansible.warn
self.sdk, self.conn = self.openstack_cloud_from_module()
self.check_deprecated_names()
self.setup_sdk_logging()
def log(self, msg):
"""Prints log message to system log.
Arguments:
msg {str} -- Log message
"""
self.ansible.log(msg)
def debug(self, msg):
"""Prints debug message to system log
Arguments:
msg {str} -- Debug message.
"""
if self.ansible._debug or self.ansible._verbosity > 2:
self.ansible.log(
" ".join(['[DEBUG]', msg]))
def setup_sdk_logging(self):
log_path = self.params.get('sdk_log_path')
if log_path is not None:
log_level = self.params.get('sdk_log_level')
self.sdk.enable_logging(
debug=True if log_level == 'DEBUG' else False,
http_debug=True if log_level == 'DEBUG' else False,
path=log_path
)
def check_deprecated_names(self):
"""Check deprecated module names if `deprecated_names` variable is set.
"""
new_module_name = OVERRIDES.get(self.module_name)
if self.module_name in self.deprecated_names and new_module_name:
self.ansible.deprecate(
"The '%s' module has been renamed to '%s' in openstack "
"collection: openstack.cloud.%s" % (
self.module_name, new_module_name, new_module_name),
version='2.0.0', collection_name='openstack.cloud')
def openstack_cloud_from_module(self):
"""Sets up connection to cloud using provided options. Checks if all
provided variables are supported for the used SDK version.
"""
try:
# Due to the name shadowing we should import other way
sdk = importlib.import_module('openstack')
self.sdk_version = sdk.version.__version__
except ImportError:
self.fail_json(msg='openstacksdk is required for this module')
try:
ensure_compatibility(self.sdk_version,
self.module_min_sdk_version,
self.module_max_sdk_version)
except ImportError as e:
self.fail_json(
msg="Incompatible openstacksdk library found: {error}."
.format(error=str(e)))
# Fail if there are set unsupported for this version parameters
# New parameters should NOT use 'default' but rely on SDK defaults
for param in self.argument_spec:
if (self.params[param] is not None
and 'min_ver' in self.argument_spec[param]
and StrictVersion(self.sdk_version) < self.argument_spec[param]['min_ver']):
self.fail_json(
msg="To use parameter '{param}' with module '{module}', the installed version of "
"the openstacksdk library MUST be >={min_version}.".format(
min_version=self.argument_spec[param]['min_ver'],
param=param,
module=self.module_name))
if (self.params[param] is not None
and 'max_ver' in self.argument_spec[param]
and StrictVersion(self.sdk_version) > self.argument_spec[param]['max_ver']):
self.fail_json(
msg="To use parameter '{param}' with module '{module}', the installed version of "
"the openstacksdk library MUST be <={max_version}.".format(
max_version=self.argument_spec[param]['max_ver'],
param=param,
module=self.module_name))
cloud_config = self.params.pop('cloud', None)
if isinstance(cloud_config, dict):
fail_message = (
"A cloud config dict was provided to the cloud parameter"
" but also a value was provided for {param}. If a cloud"
" config dict is provided, {param} should be"
" excluded.")
for param in (
'auth', 'region_name', 'validate_certs',
'ca_cert', 'client_key', 'api_timeout', 'auth_type'):
if self.params[param] is not None:
self.fail_json(msg=fail_message.format(param=param))
# For 'interface' parameter, fail if we receive a non-default value
if self.params['interface'] != 'public':
self.fail_json(msg=fail_message.format(param='interface'))
else:
cloud_config = dict(
cloud=cloud_config,
auth_type=self.params['auth_type'],
auth=self.params['auth'],
region_name=self.params['region_name'],
verify=self.params['validate_certs'],
cacert=self.params['ca_cert'],
key=self.params['client_key'],
api_timeout=self.params['api_timeout'],
interface=self.params['interface'],
)
try:
return sdk, sdk.connect(**cloud_config)
except sdk.exceptions.SDKException as e:
# Probably a cloud configuration/login error
self.fail_json(msg=str(e))
# Filter out all arguments that are not from current SDK version
def check_versioned(self, **kwargs):
"""Check that provided arguments are supported by current SDK version
Returns:
versioned_result {dict} dictionary of only arguments that are
supported by current SDK version. All others
are dropped.
"""
versioned_result = {}
for var_name in kwargs:
if ('min_ver' in self.argument_spec[var_name]
and StrictVersion(self.sdk_version) < self.argument_spec[var_name]['min_ver']):
continue
if ('max_ver' in self.argument_spec[var_name]
and StrictVersion(self.sdk_version) > self.argument_spec[var_name]['max_ver']):
continue
versioned_result.update({var_name: kwargs[var_name]})
return versioned_result
@abc.abstractmethod
def run(self):
"""Function for overriding in inhetired classes, it's executed by default.
"""
pass
def __call__(self):
"""Execute `run` function when calling the class.
"""
try:
results = self.run()
if results and isinstance(results, dict):
self.ansible.exit_json(**results)
except self.sdk.exceptions.OpenStackCloudException as e:
params = {
'msg': str(e),
'extra_data': {
'data': getattr(e, 'extra_data', 'None'),
'details': getattr(e, 'details', 'None'),
'response': getattr(getattr(e, 'response', ''),
'text', 'None')
}
}
self.ansible.fail_json(**params)
# if we got to this place, modules didn't exit
self.ansible.exit_json(**self.results)

Some files were not shown because too many files have changed in this diff Show More