207 lines
7.8 KiB
Python

# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
import abc
try:
import openstack as sdk
import otcextensions
from otcextensions import sdk as otc_sdk
from pkg_resources import parse_version as V
HAS_LIBRARIES = True
except ImportError:
HAS_LIBRARIES = False
from ansible.module_utils.basic import AnsibleModule
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),
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']),
)
spec.update(kwargs)
return spec
class OTCModule:
"""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.
"""
argument_spec = {}
module_kwargs = {}
otce_min_version = None
def __init__(self):
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.sdk_version = None
self.results = {'changed': False}
self.errors = []
self.exit = self.exit_json = self.ansible.exit_json
self.fail = self.fail_json = self.ansible.fail_json
self.sdk, self.conn = self.openstack_cloud_from_module()
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 openstack_cloud_from_module(self, min_version='0.6.9'):
if self.otce_min_version:
min_version = self.otce_min_version
if not HAS_LIBRARIES:
self.fail_json(msg='openstacksdk and otcextensions are required for this')
if min_version:
min_version = max(V('0.6.9'), V(min_version))
else:
min_version = V('0.6.9')
if V(otcextensions.__version__) < min_version:
self.fail_json(
msg="To utilize this self, the installed version of "
"the otcextensions library MUST be >={min_version}".format(
min_version=min_version))
cloud_config = self.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 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'))
conn = sdk.connect(**cloud_config)
otc_sdk.load(conn)
return sdk, conn
else:
conn = sdk.connect(
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'],
)
otc_sdk.load(conn)
return sdk, conn
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 V(self.sdk_version) < self.argument_spec[var_name]['min_ver']):
continue
if ('max_ver' in self.argument_spec[var_name]
and V(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):
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)