diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 20:03:01 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-14 20:03:01 +0000 |
commit | a453ac31f3428614cceb99027f8efbdb9258a40b (patch) | |
tree | f61f87408f32a8511cbd91799f9cececb53e0374 /collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins | |
parent | Initial commit. (diff) | |
download | ansible-a453ac31f3428614cceb99027f8efbdb9258a40b.tar.xz ansible-a453ac31f3428614cceb99027f8efbdb9258a40b.zip |
Adding upstream version 2.10.7+merged+base+2.10.8+dfsg.upstream/2.10.7+merged+base+2.10.8+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins')
58 files changed, 19881 insertions, 0 deletions
diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/doc_fragments/__init__.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/doc_fragments/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/doc_fragments/__init__.py diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/doc_fragments/cloudstack.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/doc_fragments/cloudstack.py new file mode 100644 index 00000000..af13a3af --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/doc_fragments/cloudstack.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +class ModuleDocFragment(object): + + # Standard cloudstack documentation fragment + DOCUMENTATION = r''' +options: + api_key: + description: + - API key of the CloudStack API. + - If not given, the C(CLOUDSTACK_KEY) env variable is considered. + - As the last option, the value is taken from the ini config file, also see the notes. + type: str + api_secret: + description: + - Secret key of the CloudStack API. + - If not set, the C(CLOUDSTACK_SECRET) env variable is considered. + - As the last option, the value is taken from the ini config file, also see the notes. + type: str + api_url: + description: + - URL of the CloudStack API e.g. https://cloud.example.com/client/api. + - If not given, the C(CLOUDSTACK_ENDPOINT) env variable is considered. + - As the last option, the value is taken from the ini config file, also see the notes. + type: str + api_http_method: + description: + - HTTP method used to query the API endpoint. + - If not given, the C(CLOUDSTACK_METHOD) env variable is considered. + - As the last option, the value is taken from the ini config file, also see the notes. + - Fallback value is C(get) if not specified. + type: str + choices: [ get, post ] + api_timeout: + description: + - HTTP timeout in seconds. + - If not given, the C(CLOUDSTACK_TIMEOUT) env variable is considered. + - As the last option, the value is taken from the ini config file, also see the notes. + - Fallback value is 10 seconds if not specified. + type: int + api_region: + description: + - Name of the ini section in the C(cloustack.ini) file. + - If not given, the C(CLOUDSTACK_REGION) env variable is considered. + type: str + default: cloudstack + api_verify_ssl_cert: + description: + - CA authority cert file. + - If not given, the C(CLOUDSTACK_VERIFY) env variable is considered. + - As the last option, the value is taken from the ini config file, also see the notes. + - Fallback value is C(null) if not specified. + type: str +requirements: + - python >= 2.6 + - cs >= 0.9.0 +notes: + - Ansible uses the C(cs) library's configuration method if credentials are not + provided by the arguments C(api_url), C(api_key), C(api_secret). + Configuration is read from several locations, in the following order. + The C(CLOUDSTACK_ENDPOINT), C(CLOUDSTACK_KEY), C(CLOUDSTACK_SECRET) and + C(CLOUDSTACK_METHOD). C(CLOUDSTACK_TIMEOUT) environment variables. + A C(CLOUDSTACK_CONFIG) environment variable pointing to an C(.ini) file. + A C(cloudstack.ini) file in the current working directory. + A C(.cloudstack.ini) file in the users home directory. + Optionally multiple credentials and endpoints can be specified using ini sections in C(cloudstack.ini). + Use the argument C(api_region) to select the section name, default section is C(cloudstack). + See https://github.com/exoscale/cs for more information. + - A detailed guide about cloudstack modules can be found in the L(CloudStack Cloud Guide,../scenario_guides/guide_cloudstack.html). + - This module supports check mode. +''' diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/inventory/__init__.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/inventory/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/inventory/__init__.py diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/module_utils/cloudstack.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/module_utils/cloudstack.py new file mode 100644 index 00000000..ac310ecb --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/module_utils/cloudstack.py @@ -0,0 +1,685 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +import os +import sys +import time +import traceback + +from ansible.module_utils._text import to_text, to_native +from ansible.module_utils.basic import missing_required_lib + +CS_IMP_ERR = None +try: + from cs import CloudStack, CloudStackException, read_config + HAS_LIB_CS = True +except ImportError: + CS_IMP_ERR = traceback.format_exc() + HAS_LIB_CS = False + + +if sys.version_info > (3,): + long = int + + +def cs_argument_spec(): + return dict( + api_key=dict(default=os.environ.get('CLOUDSTACK_KEY')), + api_secret=dict(default=os.environ.get('CLOUDSTACK_SECRET'), no_log=True), + api_url=dict(default=os.environ.get('CLOUDSTACK_ENDPOINT')), + api_http_method=dict(choices=['get', 'post'], default=os.environ.get('CLOUDSTACK_METHOD')), + api_timeout=dict(type='int', default=os.environ.get('CLOUDSTACK_TIMEOUT')), + api_region=dict(default=os.environ.get('CLOUDSTACK_REGION') or 'cloudstack'), + api_verify_ssl_cert=dict(default=os.environ.get('CLOUDSTACK_VERIFY')), + ) + + +def cs_required_together(): + return [['api_key', 'api_secret']] + + +class AnsibleCloudStack: + + def __init__(self, module): + if not HAS_LIB_CS: + module.fail_json(msg=missing_required_lib('cs'), exception=CS_IMP_ERR) + + self.result = { + 'changed': False, + 'diff': { + 'before': dict(), + 'after': dict() + } + } + + # Common returns, will be merged with self.returns + # search_for_key: replace_with_key + self.common_returns = { + 'id': 'id', + 'name': 'name', + 'created': 'created', + 'zonename': 'zone', + 'state': 'state', + 'project': 'project', + 'account': 'account', + 'domain': 'domain', + 'displaytext': 'display_text', + 'displayname': 'display_name', + 'description': 'description', + } + + # Init returns dict for use in subclasses + self.returns = {} + # these values will be casted to int + self.returns_to_int = {} + # these keys will be compared case sensitive in self.has_changed() + self.case_sensitive_keys = [ + 'id', + 'displaytext', + 'displayname', + 'description', + ] + + self.module = module + self._cs = None + + # Helper for VPCs + self._vpc_networks_ids = None + + self.domain = None + self.account = None + self.project = None + self.ip_address = None + self.network = None + self.physical_network = None + self.vpc = None + self.zone = None + self.vm = None + self.vm_default_nic = None + self.os_type = None + self.hypervisor = None + self.capabilities = None + self.network_acl = None + + @property + def cs(self): + if self._cs is None: + api_config = self.get_api_config() + self._cs = CloudStack(**api_config) + return self._cs + + def get_api_config(self): + api_region = self.module.params.get('api_region') or os.environ.get('CLOUDSTACK_REGION') + try: + config = read_config(api_region) + except KeyError: + config = {} + + api_config = { + 'endpoint': self.module.params.get('api_url') or config.get('endpoint'), + 'key': self.module.params.get('api_key') or config.get('key'), + 'secret': self.module.params.get('api_secret') or config.get('secret'), + 'timeout': self.module.params.get('api_timeout') or config.get('timeout') or 10, + 'method': self.module.params.get('api_http_method') or config.get('method') or 'get', + 'verify': self.module.params.get('api_verify_ssl_cert') or config.get('verify'), + } + self.result.update({ + 'api_region': api_region, + 'api_url': api_config['endpoint'], + 'api_key': api_config['key'], + 'api_timeout': int(api_config['timeout']), + 'api_http_method': api_config['method'], + 'api_verify_ssl_cert': api_config['verify'], + }) + if not all([api_config['endpoint'], api_config['key'], api_config['secret']]): + self.fail_json(msg="Missing api credentials: can not authenticate") + return api_config + + def fail_json(self, **kwargs): + self.result.update(kwargs) + self.module.fail_json(**self.result) + + def get_or_fallback(self, key=None, fallback_key=None): + value = self.module.params.get(key) + if not value: + value = self.module.params.get(fallback_key) + return value + + def has_changed(self, want_dict, current_dict, only_keys=None, skip_diff_for_keys=None): + result = False + for key, value in want_dict.items(): + + # Optionally limit by a list of keys + if only_keys and key not in only_keys: + continue + + # Skip None values + if value is None: + continue + + if key in current_dict: + if isinstance(value, (int, float, long, complex)): + + # ensure we compare the same type + if isinstance(value, int): + current_dict[key] = int(current_dict[key]) + elif isinstance(value, float): + current_dict[key] = float(current_dict[key]) + elif isinstance(value, long): + current_dict[key] = long(current_dict[key]) + elif isinstance(value, complex): + current_dict[key] = complex(current_dict[key]) + + if value != current_dict[key]: + if skip_diff_for_keys and key not in skip_diff_for_keys: + self.result['diff']['before'][key] = current_dict[key] + self.result['diff']['after'][key] = value + result = True + else: + before_value = to_text(current_dict[key]) + after_value = to_text(value) + + if self.case_sensitive_keys and key in self.case_sensitive_keys: + if before_value != after_value: + if skip_diff_for_keys and key not in skip_diff_for_keys: + self.result['diff']['before'][key] = before_value + self.result['diff']['after'][key] = after_value + result = True + + # Test for diff in case insensitive way + elif before_value.lower() != after_value.lower(): + if skip_diff_for_keys and key not in skip_diff_for_keys: + self.result['diff']['before'][key] = before_value + self.result['diff']['after'][key] = after_value + result = True + else: + if skip_diff_for_keys and key not in skip_diff_for_keys: + self.result['diff']['before'][key] = None + self.result['diff']['after'][key] = to_text(value) + result = True + return result + + def _get_by_key(self, key=None, my_dict=None): + if my_dict is None: + my_dict = {} + if key: + if key in my_dict: + return my_dict[key] + self.fail_json(msg="Something went wrong: %s not found" % key) + return my_dict + + def query_api(self, command, **args): + try: + res = getattr(self.cs, command)(**args) + + if 'errortext' in res: + self.fail_json(msg="Failed: '%s'" % res['errortext']) + + except CloudStackException as e: + self.fail_json(msg='CloudStackException: %s' % to_native(e)) + + except Exception as e: + self.fail_json(msg=to_native(e)) + + return res + + def get_network_acl(self, key=None): + if self.network_acl is None: + args = { + 'name': self.module.params.get('network_acl'), + 'vpcid': self.get_vpc(key='id'), + } + network_acls = self.query_api('listNetworkACLLists', **args) + if network_acls: + self.network_acl = network_acls['networkacllist'][0] + self.result['network_acl'] = self.network_acl['name'] + if self.network_acl: + return self._get_by_key(key, self.network_acl) + else: + self.fail_json(msg="Network ACL %s not found" % self.module.params.get('network_acl')) + + def get_vpc(self, key=None): + """Return a VPC dictionary or the value of given key of.""" + if self.vpc: + return self._get_by_key(key, self.vpc) + + vpc = self.module.params.get('vpc') + if not vpc: + vpc = os.environ.get('CLOUDSTACK_VPC') + if not vpc: + return None + + args = { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'zoneid': self.get_zone(key='id'), + } + vpcs = self.query_api('listVPCs', **args) + if not vpcs: + self.fail_json(msg="No VPCs available.") + + for v in vpcs['vpc']: + if vpc in [v['name'], v['displaytext'], v['id']]: + # Fail if the identifyer matches more than one VPC + if self.vpc: + self.fail_json(msg="More than one VPC found with the provided identifyer '%s'" % vpc) + else: + self.vpc = v + self.result['vpc'] = v['name'] + if self.vpc: + return self._get_by_key(key, self.vpc) + self.fail_json(msg="VPC '%s' not found" % vpc) + + def is_vpc_network(self, network_id): + """Returns True if network is in VPC.""" + # This is an efficient way to query a lot of networks at a time + if self._vpc_networks_ids is None: + args = { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'zoneid': self.get_zone(key='id'), + } + vpcs = self.query_api('listVPCs', **args) + self._vpc_networks_ids = [] + if vpcs: + for vpc in vpcs['vpc']: + for n in vpc.get('network', []): + self._vpc_networks_ids.append(n['id']) + return network_id in self._vpc_networks_ids + + def get_physical_network(self, key=None): + if self.physical_network: + return self._get_by_key(key, self.physical_network) + physical_network = self.module.params.get('physical_network') + args = { + 'zoneid': self.get_zone(key='id') + } + physical_networks = self.query_api('listPhysicalNetworks', **args) + if not physical_networks: + self.fail_json(msg="No physical networks available.") + + for net in physical_networks['physicalnetwork']: + if physical_network in [net['name'], net['id']]: + self.physical_network = net + self.result['physical_network'] = net['name'] + return self._get_by_key(key, self.physical_network) + self.fail_json(msg="Physical Network '%s' not found" % physical_network) + + def get_network(self, key=None): + """Return a network dictionary or the value of given key of.""" + if self.network: + return self._get_by_key(key, self.network) + + network = self.module.params.get('network') + if not network: + vpc_name = self.get_vpc(key='name') + if vpc_name: + self.fail_json(msg="Could not find network for VPC '%s' due missing argument: network" % vpc_name) + return None + + args = { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'zoneid': self.get_zone(key='id'), + 'vpcid': self.get_vpc(key='id') + } + networks = self.query_api('listNetworks', **args) + if not networks: + self.fail_json(msg="No networks available.") + + for n in networks['network']: + # ignore any VPC network if vpc param is not given + if 'vpcid' in n and not self.get_vpc(key='id'): + continue + if network in [n['displaytext'], n['name'], n['id']]: + self.result['network'] = n['name'] + self.network = n + return self._get_by_key(key, self.network) + self.fail_json(msg="Network '%s' not found" % network) + + def get_project(self, key=None): + if self.project: + return self._get_by_key(key, self.project) + + project = self.module.params.get('project') + if not project: + project = os.environ.get('CLOUDSTACK_PROJECT') + if not project: + return None + args = { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id') + } + projects = self.query_api('listProjects', **args) + if projects: + for p in projects['project']: + if project.lower() in [p['name'].lower(), p['id']]: + self.result['project'] = p['name'] + self.project = p + return self._get_by_key(key, self.project) + self.fail_json(msg="project '%s' not found" % project) + + def get_pod(self, key=None): + pod_name = self.module.params.get('pod') + if not pod_name: + return None + args = { + 'name': pod_name, + 'zoneid': self.get_zone(key='id'), + } + pods = self.query_api('listPods', **args) + if pods: + return self._get_by_key(key, pods['pod'][0]) + self.module.fail_json(msg="Pod %s not found in zone %s" % (self.module.params.get('pod'), self.get_zone(key='name'))) + + def get_ip_address(self, key=None): + if self.ip_address: + return self._get_by_key(key, self.ip_address) + + ip_address = self.module.params.get('ip_address') + if not ip_address: + self.fail_json(msg="IP address param 'ip_address' is required") + + args = { + 'ipaddress': ip_address, + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'vpcid': self.get_vpc(key='id'), + } + + ip_addresses = self.query_api('listPublicIpAddresses', **args) + + if not ip_addresses: + self.fail_json(msg="IP address '%s' not found" % args['ipaddress']) + + self.ip_address = ip_addresses['publicipaddress'][0] + return self._get_by_key(key, self.ip_address) + + def get_vm_guest_ip(self): + vm_guest_ip = self.module.params.get('vm_guest_ip') + default_nic = self.get_vm_default_nic() + + if not vm_guest_ip: + return default_nic['ipaddress'] + + for secondary_ip in default_nic['secondaryip']: + if vm_guest_ip == secondary_ip['ipaddress']: + return vm_guest_ip + self.fail_json(msg="Secondary IP '%s' not assigned to VM" % vm_guest_ip) + + def get_vm_default_nic(self): + if self.vm_default_nic: + return self.vm_default_nic + + nics = self.query_api('listNics', virtualmachineid=self.get_vm(key='id')) + if nics: + for n in nics['nic']: + if n['isdefault']: + self.vm_default_nic = n + return self.vm_default_nic + self.fail_json(msg="No default IP address of VM '%s' found" % self.module.params.get('vm')) + + def get_vm(self, key=None, filter_zone=True): + if self.vm: + return self._get_by_key(key, self.vm) + + vm = self.module.params.get('vm') + if not vm: + self.fail_json(msg="Virtual machine param 'vm' is required") + + args = { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'zoneid': self.get_zone(key='id') if filter_zone else None, + 'fetch_list': True, + } + vms = self.query_api('listVirtualMachines', **args) + if vms: + for v in vms: + if vm.lower() in [v['name'].lower(), v['displayname'].lower(), v['id']]: + self.vm = v + return self._get_by_key(key, self.vm) + self.fail_json(msg="Virtual machine '%s' not found" % vm) + + def get_disk_offering(self, key=None): + disk_offering = self.module.params.get('disk_offering') + if not disk_offering: + return None + + # Do not add domain filter for disk offering listing. + disk_offerings = self.query_api('listDiskOfferings') + if disk_offerings: + for d in disk_offerings['diskoffering']: + if disk_offering in [d['displaytext'], d['name'], d['id']]: + return self._get_by_key(key, d) + self.fail_json(msg="Disk offering '%s' not found" % disk_offering) + + def get_zone(self, key=None): + if self.zone: + return self._get_by_key(key, self.zone) + + zone = self.module.params.get('zone') + if not zone: + zone = os.environ.get('CLOUDSTACK_ZONE') + zones = self.query_api('listZones') + + if not zones: + self.fail_json(msg="No zones available. Please create a zone first") + + # use the first zone if no zone param given + if not zone: + self.module.deprecate( + msg="Using first zone as default is deprecated because of unreliable API, zone needs to be defined.", + version="2.0.0", + collection_name="ngine_io.cloudstack" + ) + self.zone = zones['zone'][0] + self.result['zone'] = self.zone['name'] + return self._get_by_key(key, self.zone) + + if zones: + for z in zones['zone']: + if zone.lower() in [z['name'].lower(), z['id']]: + self.result['zone'] = z['name'] + self.zone = z + return self._get_by_key(key, self.zone) + self.fail_json(msg="zone '%s' not found" % zone) + + def get_os_type(self, key=None): + if self.os_type: + return self._get_by_key(key, self.zone) + + os_type = self.module.params.get('os_type') + if not os_type: + return None + + os_types = self.query_api('listOsTypes') + if os_types: + for o in os_types['ostype']: + if os_type in [o['description'], o['id']]: + self.os_type = o + return self._get_by_key(key, self.os_type) + self.fail_json(msg="OS type '%s' not found" % os_type) + + def get_hypervisor(self): + if self.hypervisor: + return self.hypervisor + + hypervisor = self.module.params.get('hypervisor') + hypervisors = self.query_api('listHypervisors') + + # use the first hypervisor if no hypervisor param given + if not hypervisor: + self.hypervisor = hypervisors['hypervisor'][0]['name'] + return self.hypervisor + + for h in hypervisors['hypervisor']: + if hypervisor.lower() == h['name'].lower(): + self.hypervisor = h['name'] + return self.hypervisor + self.fail_json(msg="Hypervisor '%s' not found" % hypervisor) + + def get_account(self, key=None): + if self.account: + return self._get_by_key(key, self.account) + + account = self.module.params.get('account') + if not account: + account = os.environ.get('CLOUDSTACK_ACCOUNT') + if not account: + return None + + domain = self.module.params.get('domain') + if not domain: + self.fail_json(msg="Account must be specified with Domain") + + args = { + 'name': account, + 'domainid': self.get_domain(key='id'), + 'listall': True + } + accounts = self.query_api('listAccounts', **args) + if accounts: + self.account = accounts['account'][0] + self.result['account'] = self.account['name'] + return self._get_by_key(key, self.account) + self.fail_json(msg="Account '%s' not found" % account) + + def get_domain(self, key=None): + if self.domain: + return self._get_by_key(key, self.domain) + + domain = self.module.params.get('domain') + if not domain: + domain = os.environ.get('CLOUDSTACK_DOMAIN') + if not domain: + return None + + args = { + 'listall': True, + } + domains = self.query_api('listDomains', **args) + if domains: + for d in domains['domain']: + if d['path'].lower() in [domain.lower(), "root/" + domain.lower(), "root" + domain.lower()]: + self.domain = d + self.result['domain'] = d['path'] + return self._get_by_key(key, self.domain) + self.fail_json(msg="Domain '%s' not found" % domain) + + def query_tags(self, resource, resource_type): + args = { + 'resourceid': resource['id'], + 'resourcetype': resource_type, + } + tags = self.query_api('listTags', **args) + return self.get_tags(resource=tags, key='tag') + + def get_tags(self, resource=None, key='tags'): + existing_tags = [] + for tag in resource.get(key) or []: + existing_tags.append({'key': tag['key'], 'value': tag['value']}) + return existing_tags + + def _process_tags(self, resource, resource_type, tags, operation="create"): + if tags: + self.result['changed'] = True + if not self.module.check_mode: + args = { + 'resourceids': resource['id'], + 'resourcetype': resource_type, + 'tags': tags, + } + if operation == "create": + response = self.query_api('createTags', **args) + else: + response = self.query_api('deleteTags', **args) + self.poll_job(response) + + def _tags_that_should_exist_or_be_updated(self, resource, tags): + existing_tags = self.get_tags(resource) + return [tag for tag in tags if tag not in existing_tags] + + def _tags_that_should_not_exist(self, resource, tags): + existing_tags = self.get_tags(resource) + return [tag for tag in existing_tags if tag not in tags] + + def ensure_tags(self, resource, resource_type=None): + if not resource_type or not resource: + self.fail_json(msg="Error: Missing resource or resource_type for tags.") + + if 'tags' in resource: + tags = self.module.params.get('tags') + if tags is not None: + self._process_tags(resource, resource_type, self._tags_that_should_not_exist(resource, tags), operation="delete") + self._process_tags(resource, resource_type, self._tags_that_should_exist_or_be_updated(resource, tags)) + resource['tags'] = self.query_tags(resource=resource, resource_type=resource_type) + return resource + + def get_capabilities(self, key=None): + if self.capabilities: + return self._get_by_key(key, self.capabilities) + capabilities = self.query_api('listCapabilities') + self.capabilities = capabilities['capability'] + return self._get_by_key(key, self.capabilities) + + def poll_job(self, job=None, key=None): + if 'jobid' in job: + while True: + res = self.query_api('queryAsyncJobResult', jobid=job['jobid']) + if res['jobstatus'] != 0 and 'jobresult' in res: + + if 'errortext' in res['jobresult']: + self.fail_json(msg="Failed: '%s'" % res['jobresult']['errortext']) + + if key and key in res['jobresult']: + job = res['jobresult'][key] + + break + time.sleep(2) + return job + + def update_result(self, resource, result=None): + if result is None: + result = dict() + if resource: + returns = self.common_returns.copy() + returns.update(self.returns) + for search_key, return_key in returns.items(): + if search_key in resource: + result[return_key] = resource[search_key] + + # Bad bad API does not always return int when it should. + for search_key, return_key in self.returns_to_int.items(): + if search_key in resource: + result[return_key] = int(resource[search_key]) + + if 'tags' in resource: + result['tags'] = resource['tags'] + return result + + def get_result(self, resource): + return self.update_result(resource, self.result) + + def get_result_and_facts(self, facts_name, resource): + result = self.get_result(resource) + + ansible_facts = { + facts_name: result.copy() + } + for k in ['diff', 'changed']: + if k in ansible_facts[facts_name]: + del ansible_facts[facts_name][k] + + result.update(ansible_facts=ansible_facts) + return result diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/__init__.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/__init__.py diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_account.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_account.py new file mode 100644 index 00000000..694f62d7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_account.py @@ -0,0 +1,450 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_account +short_description: Manages accounts on Apache CloudStack based clouds. +description: + - Create, disable, lock, enable and remove accounts. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name of account. + type: str + required: true + username: + description: + - Username of the user to be created if account did not exist. + - Required on I(state=present). + type: str + password: + description: + - Password of the user to be created if account did not exist. + - Required on I(state=present) if I(ldap_domain) is not set. + type: str + first_name: + description: + - First name of the user to be created if account did not exist. + - Required on I(state=present) if I(ldap_domain) is not set. + type: str + last_name: + description: + - Last name of the user to be created if account did not exist. + - Required on I(state=present) if I(ldap_domain) is not set. + type: str + email: + description: + - Email of the user to be created if account did not exist. + - Required on I(state=present) if I(ldap_domain) is not set. + type: str + timezone: + description: + - Timezone of the user to be created if account did not exist. + type: str + network_domain: + description: + - Network domain of the account. + type: str + account_type: + description: + - Type of the account. + type: str + choices: [ user, root_admin, domain_admin ] + default: user + domain: + description: + - Domain the account is related to. + type: str + default: ROOT + role: + description: + - Creates the account under the specified role name or id. + type: str + ldap_domain: + description: + - Name of the LDAP group or OU to bind. + - If set, account will be linked to LDAP. + type: str + ldap_type: + description: + - Type of the ldap name. GROUP or OU, defaults to GROUP. + type: str + choices: [ GROUP, OU ] + default: GROUP + state: + description: + - State of the account. + - C(unlocked) is an alias for C(enabled). + type: str + choices: [ present, absent, enabled, disabled, locked, unlocked ] + default: present + poll_async: + description: + - Poll async jobs until job has finished. + type: bool + default: yes +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack + +''' + +EXAMPLES = ''' +- name: create an account in domain 'CUSTOMERS' + ngine_io.cloudstack.cs_account: + name: customer_xy + username: customer_xy + password: S3Cur3 + last_name: Doe + first_name: John + email: john.doe@example.com + domain: CUSTOMERS + role: Domain Admin + +- name: Lock an existing account in domain 'CUSTOMERS' + ngine_io.cloudstack.cs_account: + name: customer_xy + domain: CUSTOMERS + state: locked + +- name: Disable an existing account in domain 'CUSTOMERS' + ngine_io.cloudstack.cs_account: + name: customer_xy + domain: CUSTOMERS + state: disabled + +- name: Enable an existing account in domain 'CUSTOMERS' + ngine_io.cloudstack.cs_account: + name: customer_xy + domain: CUSTOMERS + state: enabled + +- name: Remove an account in domain 'CUSTOMERS' + ngine_io.cloudstack.cs_account: + name: customer_xy + domain: CUSTOMERS + state: absent + +- name: Create a single user LDAP account in domain 'CUSTOMERS' + ngine_io.cloudstack.cs_account: + name: customer_xy + username: customer_xy + domain: CUSTOMERS + ldap_domain: cn=customer_xy,cn=team_xy,ou=People,dc=domain,dc=local + +- name: Create a LDAP account in domain 'CUSTOMERS' and bind it to a LDAP group + ngine_io.cloudstack.cs_account: + name: team_xy + username: customer_xy + domain: CUSTOMERS + ldap_domain: cn=team_xy,ou=People,dc=domain,dc=local +''' + +RETURN = ''' +--- +id: + description: UUID of the account. + returned: success + type: str + sample: 87b1e0ce-4e01-11e4-bb66-0050569e64b8 +name: + description: Name of the account. + returned: success + type: str + sample: linus@example.com +account_type: + description: Type of the account. + returned: success + type: str + sample: user +state: + description: State of the account. + returned: success + type: str + sample: enabled +network_domain: + description: Network domain of the account. + returned: success + type: str + sample: example.local +domain: + description: Domain the account is related. + returned: success + type: str + sample: ROOT +role: + description: The role name of the account + returned: success + type: str + sample: Domain Admin +''' + +# import cloudstack common +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackAccount(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackAccount, self).__init__(module) + self.returns = { + 'networkdomain': 'network_domain', + 'rolename': 'role', + } + self.account = None + self.account_types = { + 'user': 0, + 'root_admin': 1, + 'domain_admin': 2, + } + + def get_role_id(self): + role_param = self.module.params.get('role') + role_id = None + + if role_param: + role_list = self.query_api('listRoles') + for role in role_list['role']: + if role_param in [role['name'], role['id']]: + role_id = role['id'] + + if not role_id: + self.module.fail_json(msg="Role not found: %s" % role_param) + + return role_id + + def get_account_type(self): + account_type = self.module.params.get('account_type') + return self.account_types[account_type] + + def get_account(self): + if not self.account: + args = { + 'listall': True, + 'domainid': self.get_domain(key='id'), + 'fetch_list': True, + } + accounts = self.query_api('listAccounts', **args) + if accounts: + account_name = self.module.params.get('name') + for a in accounts: + if account_name == a['name']: + self.account = a + break + + return self.account + + def enable_account(self): + account = self.get_account() + if not account: + account = self.present_account() + + if account['state'].lower() != 'enabled': + self.result['changed'] = True + args = { + 'id': account['id'], + 'account': self.module.params.get('name'), + 'domainid': self.get_domain(key='id') + } + if not self.module.check_mode: + res = self.query_api('enableAccount', **args) + account = res['account'] + return account + + def lock_account(self): + return self.lock_or_disable_account(lock=True) + + def disable_account(self): + return self.lock_or_disable_account() + + def lock_or_disable_account(self, lock=False): + account = self.get_account() + if not account: + account = self.present_account() + + # we need to enable the account to lock it. + if lock and account['state'].lower() == 'disabled': + account = self.enable_account() + + if (lock and account['state'].lower() != 'locked' or + not lock and account['state'].lower() != 'disabled'): + self.result['changed'] = True + args = { + 'id': account['id'], + 'account': self.module.params.get('name'), + 'domainid': self.get_domain(key='id'), + 'lock': lock, + } + if not self.module.check_mode: + account = self.query_api('disableAccount', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + account = self.poll_job(account, 'account') + return account + + def present_account(self): + account = self.get_account() + + if not account: + self.result['changed'] = True + + if self.module.params.get('ldap_domain'): + required_params = [ + 'domain', + 'username', + ] + self.module.fail_on_missing_params(required_params=required_params) + + account = self.create_ldap_account(account) + + else: + required_params = [ + 'email', + 'username', + 'password', + 'first_name', + 'last_name', + ] + self.module.fail_on_missing_params(required_params=required_params) + + account = self.create_account(account) + + return account + + def create_ldap_account(self, account): + args = { + 'account': self.module.params.get('name'), + 'domainid': self.get_domain(key='id'), + 'accounttype': self.get_account_type(), + 'networkdomain': self.module.params.get('network_domain'), + 'username': self.module.params.get('username'), + 'timezone': self.module.params.get('timezone'), + 'roleid': self.get_role_id() + } + if not self.module.check_mode: + res = self.query_api('ldapCreateAccount', **args) + account = res['account'] + + args = { + 'account': self.module.params.get('name'), + 'domainid': self.get_domain(key='id'), + 'accounttype': self.get_account_type(), + 'ldapdomain': self.module.params.get('ldap_domain'), + 'type': self.module.params.get('ldap_type') + } + + self.query_api('linkAccountToLdap', **args) + + return account + + def create_account(self, account): + args = { + 'account': self.module.params.get('name'), + 'domainid': self.get_domain(key='id'), + 'accounttype': self.get_account_type(), + 'networkdomain': self.module.params.get('network_domain'), + 'username': self.module.params.get('username'), + 'password': self.module.params.get('password'), + 'firstname': self.module.params.get('first_name'), + 'lastname': self.module.params.get('last_name'), + 'email': self.module.params.get('email'), + 'timezone': self.module.params.get('timezone'), + 'roleid': self.get_role_id() + } + if not self.module.check_mode: + res = self.query_api('createAccount', **args) + account = res['account'] + + return account + + def absent_account(self): + account = self.get_account() + if account: + self.result['changed'] = True + + if not self.module.check_mode: + res = self.query_api('deleteAccount', id=account['id']) + + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(res, 'account') + return account + + def get_result(self, account): + super(AnsibleCloudStackAccount, self).get_result(account) + if account: + if 'accounttype' in account: + for key, value in self.account_types.items(): + if value == account['accounttype']: + self.result['account_type'] = key + break + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + state=dict(choices=['present', 'absent', 'enabled', 'disabled', 'locked', 'unlocked'], default='present'), + account_type=dict(choices=['user', 'root_admin', 'domain_admin'], default='user'), + network_domain=dict(), + domain=dict(default='ROOT'), + email=dict(), + first_name=dict(), + last_name=dict(), + username=dict(), + password=dict(no_log=True), + timezone=dict(), + role=dict(), + ldap_domain=dict(), + ldap_type=dict(choices=['GROUP', 'OU'], default='GROUP'), + poll_async=dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_acc = AnsibleCloudStackAccount(module) + + state = module.params.get('state') + + if state in ['absent']: + account = acs_acc.absent_account() + + elif state in ['enabled', 'unlocked']: + account = acs_acc.enable_account() + + elif state in ['disabled']: + account = acs_acc.disable_account() + + elif state in ['locked']: + account = acs_acc.lock_account() + + else: + account = acs_acc.present_account() + + result = acs_acc.get_result(account) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_affinitygroup.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_affinitygroup.py new file mode 100644 index 00000000..46c1b176 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_affinitygroup.py @@ -0,0 +1,230 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_affinitygroup +short_description: Manages affinity groups on Apache CloudStack based clouds. +description: + - Create and remove affinity groups. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name of the affinity group. + type: str + required: true + affinity_type: + description: + - Type of the affinity group. If not specified, first found affinity type is used. + type: str + description: + description: + - Description of the affinity group. + type: str + state: + description: + - State of the affinity group. + type: str + choices: [ present, absent ] + default: present + domain: + description: + - Domain the affinity group is related to. + type: str + account: + description: + - Account the affinity group is related to. + type: str + project: + description: + - Name of the project the affinity group is related to. + type: str + poll_async: + description: + - Poll async jobs until job has finished. + type: bool + default: yes +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack + +''' + +EXAMPLES = ''' +- name: Create a affinity group + ngine_io.cloudstack.cs_affinitygroup: + name: haproxy + affinity_type: host anti-affinity + +- name: Remove a affinity group + ngine_io.cloudstack.cs_affinitygroup: + name: haproxy + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the affinity group. + returned: success + type: str + sample: 87b1e0ce-4e01-11e4-bb66-0050569e64b8 +name: + description: Name of affinity group. + returned: success + type: str + sample: app +description: + description: Description of affinity group. + returned: success + type: str + sample: application affinity group +affinity_type: + description: Type of affinity group. + returned: success + type: str + sample: host anti-affinity +project: + description: Name of project the affinity group is related to. + returned: success + type: str + sample: Production +domain: + description: Domain the affinity group is related to. + returned: success + type: str + sample: example domain +account: + description: Account the affinity group is related to. + returned: success + type: str + sample: example account +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackAffinityGroup(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackAffinityGroup, self).__init__(module) + self.returns = { + 'type': 'affinity_type', + } + self.affinity_group = None + + def get_affinity_group(self): + if not self.affinity_group: + + args = { + 'projectid': self.get_project(key='id'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'name': self.module.params.get('name'), + } + affinity_groups = self.query_api('listAffinityGroups', **args) + if affinity_groups: + self.affinity_group = affinity_groups['affinitygroup'][0] + return self.affinity_group + + def get_affinity_type(self): + affinity_type = self.module.params.get('affinity_type') + + affinity_types = self.query_api('listAffinityGroupTypes', ) + if affinity_types: + if not affinity_type: + return affinity_types['affinityGroupType'][0]['type'] + + for a in affinity_types['affinityGroupType']: + if a['type'] == affinity_type: + return a['type'] + self.module.fail_json(msg="affinity group type not found: %s" % affinity_type) + + def create_affinity_group(self): + affinity_group = self.get_affinity_group() + if not affinity_group: + self.result['changed'] = True + + args = { + 'name': self.module.params.get('name'), + 'type': self.get_affinity_type(), + 'description': self.module.params.get('description'), + 'projectid': self.get_project(key='id'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + } + if not self.module.check_mode: + res = self.query_api('createAffinityGroup', **args) + + poll_async = self.module.params.get('poll_async') + if res and poll_async: + affinity_group = self.poll_job(res, 'affinitygroup') + return affinity_group + + def remove_affinity_group(self): + affinity_group = self.get_affinity_group() + if affinity_group: + self.result['changed'] = True + + args = { + 'name': self.module.params.get('name'), + 'projectid': self.get_project(key='id'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + } + if not self.module.check_mode: + res = self.query_api('deleteAffinityGroup', **args) + + poll_async = self.module.params.get('poll_async') + if res and poll_async: + self.poll_job(res, 'affinitygroup') + return affinity_group + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + affinity_type=dict(), + description=dict(), + state=dict(choices=['present', 'absent'], default='present'), + domain=dict(), + account=dict(), + project=dict(), + poll_async=dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_ag = AnsibleCloudStackAffinityGroup(module) + + state = module.params.get('state') + if state in ['absent']: + affinity_group = acs_ag.remove_affinity_group() + else: + affinity_group = acs_ag.create_affinity_group() + + result = acs_ag.get_result(affinity_group) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_cluster.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_cluster.py new file mode 100644 index 00000000..88ea35f2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_cluster.py @@ -0,0 +1,375 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_cluster +short_description: Manages host clusters on Apache CloudStack based clouds. +description: + - Create, update and remove clusters. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - name of the cluster. + type: str + required: true + zone: + description: + - Name of the zone in which the cluster belongs to. + - If not set, default zone is used. + type: str + pod: + description: + - Name of the pod in which the cluster belongs to. + type: str + cluster_type: + description: + - Type of the cluster. + - Required if I(state=present) + type: str + choices: [ CloudManaged, ExternalManaged ] + hypervisor: + description: + - Name the hypervisor to be used. + - Required if I(state=present). + - Possible values are C(KVM), C(VMware), C(BareMetal), C(XenServer), C(LXC), C(HyperV), C(UCS), C(OVM), C(Simulator). + type: str + url: + description: + - URL for the cluster + type: str + username: + description: + - Username for the cluster. + type: str + password: + description: + - Password for the cluster. + type: str + guest_vswitch_name: + description: + - Name of virtual switch used for guest traffic in the cluster. + - This would override zone wide traffic label setting. + type: str + guest_vswitch_type: + description: + - Type of virtual switch used for guest traffic in the cluster. + - Allowed values are, vmwaresvs (for VMware standard vSwitch) and vmwaredvs (for VMware distributed vSwitch) + type: str + choices: [ vmwaresvs, vmwaredvs ] + public_vswitch_name: + description: + - Name of virtual switch used for public traffic in the cluster. + - This would override zone wide traffic label setting. + type: str + public_vswitch_type: + description: + - Type of virtual switch used for public traffic in the cluster. + - Allowed values are, vmwaresvs (for VMware standard vSwitch) and vmwaredvs (for VMware distributed vSwitch) + type: str + choices: [ vmwaresvs, vmwaredvs ] + vms_ip_address: + description: + - IP address of the VSM associated with this cluster. + type: str + vms_username: + description: + - Username for the VSM associated with this cluster. + type: str + vms_password: + description: + - Password for the VSM associated with this cluster. + type: str + ovm3_cluster: + description: + - Ovm3 native OCFS2 clustering enabled for cluster. + type: str + ovm3_pool: + description: + - Ovm3 native pooling enabled for cluster. + type: str + ovm3_vip: + description: + - Ovm3 vip to use for pool (and cluster). + type: str + state: + description: + - State of the cluster. + type: str + choices: [ present, absent, disabled, enabled ] + default: present +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Ensure a cluster is present + ngine_io.cloudstack.cs_cluster: + name: kvm-cluster-01 + zone: ch-zrh-ix-01 + hypervisor: KVM + cluster_type: CloudManaged + +- name: Ensure a cluster is disabled + ngine_io.cloudstack.cs_cluster: + name: kvm-cluster-01 + zone: ch-zrh-ix-01 + state: disabled + +- name: Ensure a cluster is enabled + ngine_io.cloudstack.cs_cluster: + name: kvm-cluster-01 + zone: ch-zrh-ix-01 + state: enabled + +- name: Ensure a cluster is absent + ngine_io.cloudstack.cs_cluster: + name: kvm-cluster-01 + zone: ch-zrh-ix-01 + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the cluster. + returned: success + type: str + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 +name: + description: Name of the cluster. + returned: success + type: str + sample: cluster01 +allocation_state: + description: State of the cluster. + returned: success + type: str + sample: Enabled +cluster_type: + description: Type of the cluster. + returned: success + type: str + sample: ExternalManaged +cpu_overcommit_ratio: + description: The CPU overcommit ratio of the cluster. + returned: success + type: str + sample: 1.0 +memory_overcommit_ratio: + description: The memory overcommit ratio of the cluster. + returned: success + type: str + sample: 1.0 +managed_state: + description: Whether this cluster is managed by CloudStack. + returned: success + type: str + sample: Managed +ovm3_vip: + description: Ovm3 VIP to use for pooling and/or clustering + returned: success + type: str + sample: 10.10.10.101 +hypervisor: + description: Hypervisor of the cluster + returned: success + type: str + sample: VMware +zone: + description: Name of zone the cluster is in. + returned: success + type: str + sample: ch-gva-2 +pod: + description: Name of pod the cluster is in. + returned: success + type: str + sample: pod01 +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackCluster(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackCluster, self).__init__(module) + self.returns = { + 'allocationstate': 'allocation_state', + 'hypervisortype': 'hypervisor', + 'clustertype': 'cluster_type', + 'podname': 'pod', + 'managedstate': 'managed_state', + 'memoryovercommitratio': 'memory_overcommit_ratio', + 'cpuovercommitratio': 'cpu_overcommit_ratio', + 'ovm3vip': 'ovm3_vip', + } + self.cluster = None + + def _get_common_cluster_args(self): + args = { + 'clustername': self.module.params.get('name'), + 'hypervisor': self.module.params.get('hypervisor'), + 'clustertype': self.module.params.get('cluster_type'), + } + state = self.module.params.get('state') + if state in ['enabled', 'disabled']: + args['allocationstate'] = state.capitalize() + return args + + def get_cluster(self): + if not self.cluster: + args = {} + + uuid = self.module.params.get('id') + if uuid: + args['id'] = uuid + clusters = self.query_api('listClusters', **args) + if clusters: + self.cluster = clusters['cluster'][0] + return self.cluster + + args['name'] = self.module.params.get('name') + clusters = self.query_api('listClusters', **args) + if clusters: + self.cluster = clusters['cluster'][0] + # fix different return from API then request argument given + self.cluster['hypervisor'] = self.cluster['hypervisortype'] + self.cluster['clustername'] = self.cluster['name'] + return self.cluster + + def present_cluster(self): + cluster = self.get_cluster() + if cluster: + cluster = self._update_cluster() + else: + cluster = self._create_cluster() + return cluster + + def _create_cluster(self): + required_params = [ + 'cluster_type', + 'hypervisor', + ] + self.module.fail_on_missing_params(required_params=required_params) + + args = self._get_common_cluster_args() + args['zoneid'] = self.get_zone(key='id') + args['podid'] = self.get_pod(key='id') + args['url'] = self.module.params.get('url') + args['username'] = self.module.params.get('username') + args['password'] = self.module.params.get('password') + args['guestvswitchname'] = self.module.params.get('guest_vswitch_name') + args['guestvswitchtype'] = self.module.params.get('guest_vswitch_type') + args['publicvswitchtype'] = self.module.params.get('public_vswitch_name') + args['publicvswitchtype'] = self.module.params.get('public_vswitch_type') + args['vsmipaddress'] = self.module.params.get('vms_ip_address') + args['vsmusername'] = self.module.params.get('vms_username') + args['vmspassword'] = self.module.params.get('vms_password') + args['ovm3cluster'] = self.module.params.get('ovm3_cluster') + args['ovm3pool'] = self.module.params.get('ovm3_pool') + args['ovm3vip'] = self.module.params.get('ovm3_vip') + + self.result['changed'] = True + + cluster = None + if not self.module.check_mode: + res = self.query_api('addCluster', **args) + + # API returns a list as result CLOUDSTACK-9205 + if isinstance(res['cluster'], list): + cluster = res['cluster'][0] + else: + cluster = res['cluster'] + return cluster + + def _update_cluster(self): + cluster = self.get_cluster() + + args = self._get_common_cluster_args() + args['id'] = cluster['id'] + + if self.has_changed(args, cluster): + self.result['changed'] = True + + if not self.module.check_mode: + res = self.query_api('updateCluster', **args) + cluster = res['cluster'] + + return cluster + + def absent_cluster(self): + cluster = self.get_cluster() + if cluster: + self.result['changed'] = True + + args = { + 'id': cluster['id'], + } + + if not self.module.check_mode: + self.query_api('deleteCluster', **args) + + return cluster + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + zone=dict(), + pod=dict(), + cluster_type=dict(choices=['CloudManaged', 'ExternalManaged']), + hypervisor=dict(), + state=dict(choices=['present', 'enabled', 'disabled', 'absent'], default='present'), + url=dict(), + username=dict(), + password=dict(no_log=True), + guest_vswitch_name=dict(), + guest_vswitch_type=dict(choices=['vmwaresvs', 'vmwaredvs']), + public_vswitch_name=dict(), + public_vswitch_type=dict(choices=['vmwaresvs', 'vmwaredvs']), + vms_ip_address=dict(), + vms_username=dict(), + vms_password=dict(no_log=True), + ovm3_cluster=dict(), + ovm3_pool=dict(), + ovm3_vip=dict(), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_cluster = AnsibleCloudStackCluster(module) + + state = module.params.get('state') + if state in ['absent']: + cluster = acs_cluster.absent_cluster() + else: + cluster = acs_cluster.present_cluster() + + result = acs_cluster.get_result(cluster) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_configuration.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_configuration.py new file mode 100644 index 00000000..dd11d932 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_configuration.py @@ -0,0 +1,270 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_configuration +short_description: Manages configuration on Apache CloudStack based clouds. +description: + - Manages global, zone, account, storage and cluster configurations. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name of the configuration. + type: str + required: true + value: + description: + - Value of the configuration. + type: str + required: true + account: + description: + - Ensure the value for corresponding account. + type: str + domain: + description: + - Domain the account is related to. + - Only considered if I(account) is used. + type: str + default: ROOT + zone: + description: + - Ensure the value for corresponding zone. + type: str + storage: + description: + - Ensure the value for corresponding storage pool. + type: str + cluster: + description: + - Ensure the value for corresponding cluster. + type: str +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Ensure global configuration + ngine_io.cloudstack.cs_configuration: + name: router.reboot.when.outofband.migrated + value: false + +- name: Ensure zone configuration + ngine_io.cloudstack.cs_configuration: + name: router.reboot.when.outofband.migrated + zone: ch-gva-01 + value: true + +- name: Ensure storage configuration + ngine_io.cloudstack.cs_configuration: + name: storage.overprovisioning.factor + storage: storage01 + value: 2.0 + +- name: Ensure account configuration + ngine_io.cloudstack.cs_configuration: + name: allow.public.user.templates + value: false + account: acme inc + domain: customers +''' + +RETURN = ''' +--- +category: + description: Category of the configuration. + returned: success + type: str + sample: Advanced +scope: + description: Scope (zone/cluster/storagepool/account) of the parameter that needs to be updated. + returned: success + type: str + sample: storagepool +description: + description: Description of the configuration. + returned: success + type: str + sample: Setup the host to do multipath +name: + description: Name of the configuration. + returned: success + type: str + sample: zone.vlan.capacity.notificationthreshold +value: + description: Value of the configuration. + returned: success + type: str + sample: "0.75" +account: + description: Account of the configuration. + returned: success + type: str + sample: admin +Domain: + description: Domain of account of the configuration. + returned: success + type: str + sample: ROOT +zone: + description: Zone of the configuration. + returned: success + type: str + sample: ch-gva-01 +cluster: + description: Cluster of the configuration. + returned: success + type: str + sample: cluster01 +storage: + description: Storage of the configuration. + returned: success + type: str + sample: storage01 +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackConfiguration(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackConfiguration, self).__init__(module) + self.returns = { + 'category': 'category', + 'scope': 'scope', + 'value': 'value', + } + self.storage = None + self.account = None + self.cluster = None + + def _get_common_configuration_args(self): + args = { + 'name': self.module.params.get('name'), + 'accountid': self.get_account(key='id'), + 'storageid': self.get_storage(key='id'), + 'zoneid': self.get_zone(key='id'), + 'clusterid': self.get_cluster(key='id'), + } + return args + + def get_zone(self, key=None): + # make sure we do net use the default zone + zone = self.module.params.get('zone') + if zone: + return super(AnsibleCloudStackConfiguration, self).get_zone(key=key) + + def get_cluster(self, key=None): + if not self.cluster: + cluster_name = self.module.params.get('cluster') + if not cluster_name: + return None + args = { + 'name': cluster_name, + } + clusters = self.query_api('listClusters', **args) + if clusters: + self.cluster = clusters['cluster'][0] + self.result['cluster'] = self.cluster['name'] + else: + self.module.fail_json(msg="Cluster %s not found." % cluster_name) + return self._get_by_key(key=key, my_dict=self.cluster) + + def get_storage(self, key=None): + if not self.storage: + storage_pool_name = self.module.params.get('storage') + if not storage_pool_name: + return None + args = { + 'name': storage_pool_name, + } + storage_pools = self.query_api('listStoragePools', **args) + if storage_pools: + self.storage = storage_pools['storagepool'][0] + self.result['storage'] = self.storage['name'] + else: + self.module.fail_json(msg="Storage pool %s not found." % storage_pool_name) + return self._get_by_key(key=key, my_dict=self.storage) + + def get_configuration(self): + configuration = None + args = self._get_common_configuration_args() + args['fetch_list'] = True + configurations = self.query_api('listConfigurations', **args) + if not configurations: + self.module.fail_json(msg="Configuration %s not found." % args['name']) + for config in configurations: + if args['name'] == config['name']: + configuration = config + return configuration + + def get_value(self): + value = str(self.module.params.get('value')) + if value in ('True', 'False'): + value = value.lower() + return value + + def present_configuration(self): + configuration = self.get_configuration() + args = self._get_common_configuration_args() + args['value'] = self.get_value() + empty_value = args['value'] in [None, ''] and 'value' not in configuration + if self.has_changed(args, configuration, ['value']) and not empty_value: + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('updateConfiguration', **args) + configuration = res['configuration'] + return configuration + + def get_result(self, configuration): + self.result = super(AnsibleCloudStackConfiguration, self).get_result(configuration) + if self.account: + self.result['account'] = self.account['name'] + self.result['domain'] = self.domain['path'] + elif self.zone: + self.result['zone'] = self.zone['name'] + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + value=dict(type='str', required=True), + zone=dict(), + storage=dict(), + cluster=dict(), + account=dict(), + domain=dict(default='ROOT') + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_configuration = AnsibleCloudStackConfiguration(module) + configuration = acs_configuration.present_configuration() + result = acs_configuration.get_result(configuration) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_disk_offering.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_disk_offering.py new file mode 100644 index 00000000..b9bd4570 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_disk_offering.py @@ -0,0 +1,375 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2018, David Passante <@dpassante> +# Copyright (c) 2017, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_disk_offering +description: + - Create and delete disk offerings for guest VMs. + - Update display_text or display_offering of existing disk offering. +short_description: Manages disk offerings on Apache CloudStack based clouds. +author: + - David Passante (@dpassante) + - René Moser (@resmo) +version_added: 0.1.0 +options: + disk_size: + description: + - Size of the disk offering in GB (1GB = 1,073,741,824 bytes). + type: int + bytes_read_rate: + description: + - Bytes read rate of the disk offering. + type: int + bytes_write_rate: + description: + - Bytes write rate of the disk offering. + type: int + display_text: + description: + - Display text of the disk offering. + - If not set, C(name) will be used as C(display_text) while creating. + type: str + domain: + description: + - Domain the disk offering is related to. + - Public for all domains and subdomains if not set. + type: str + hypervisor_snapshot_reserve: + description: + - Hypervisor snapshot reserve space as a percent of a volume. + - Only for managed storage using Xen or VMware. + type: int + customized: + description: + - Whether disk offering iops is custom or not. + type: bool + iops_read_rate: + description: + - IO requests read rate of the disk offering. + type: int + iops_write_rate: + description: + - IO requests write rate of the disk offering. + type: int + iops_max: + description: + - Max. iops of the disk offering. + type: int + iops_min: + description: + - Min. iops of the disk offering. + type: int + name: + description: + - Name of the disk offering. + type: str + required: true + provisioning_type: + description: + - Provisioning type used to create volumes. + type: str + choices: [ thin, sparse, fat ] + state: + description: + - State of the disk offering. + type: str + choices: [ present, absent ] + default: present + storage_type: + description: + - The storage type of the disk offering. + type: str + choices: [ local, shared ] + storage_tags: + description: + - The storage tags for this disk offering. + type: list + elements: str + aliases: [ storage_tag ] + display_offering: + description: + - An optional field, whether to display the offering to the end user or not. + type: bool +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Create a disk offering with local storage + ngine_io.cloudstack.cs_disk_offering: + name: small + display_text: Small 10GB + disk_size: 10 + storage_type: local + +- name: Create or update a disk offering with shared storage + ngine_io.cloudstack.cs_disk_offering: + name: small + display_text: Small 10GB + disk_size: 10 + storage_type: shared + storage_tags: SAN01 + +- name: Remove a disk offering + ngine_io.cloudstack.cs_disk_offering: + name: small + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the disk offering + returned: success + type: str + sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f +disk_size: + description: Size of the disk offering in GB + returned: success + type: int + sample: 10 +iops_max: + description: Max iops of the disk offering + returned: success + type: int + sample: 1000 +iops_min: + description: Min iops of the disk offering + returned: success + type: int + sample: 500 +bytes_read_rate: + description: Bytes read rate of the disk offering + returned: success + type: int + sample: 1000 +bytes_write_rate: + description: Bytes write rate of the disk offering + returned: success + type: int + sample: 1000 +iops_read_rate: + description: IO requests per second read rate of the disk offering + returned: success + type: int + sample: 1000 +iops_write_rate: + description: IO requests per second write rate of the disk offering + returned: success + type: int + sample: 1000 +created: + description: Date the offering was created + returned: success + type: str + sample: 2017-11-19T10:48:59+0000 +display_text: + description: Display text of the offering + returned: success + type: str + sample: Small 10GB +domain: + description: Domain the offering is into + returned: success + type: str + sample: ROOT +storage_tags: + description: List of storage tags + returned: success + type: list + sample: [ 'eco' ] +customized: + description: Whether the offering uses custom IOPS or not + returned: success + type: bool + sample: false +name: + description: Name of the system offering + returned: success + type: str + sample: Micro +provisioning_type: + description: Provisioning type used to create volumes + returned: success + type: str + sample: thin +storage_type: + description: Storage type used to create volumes + returned: success + type: str + sample: shared +display_offering: + description: Whether to display the offering to the end user or not. + returned: success + type: bool + sample: false +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackDiskOffering(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackDiskOffering, self).__init__(module) + self.returns = { + 'disksize': 'disk_size', + 'diskBytesReadRate': 'bytes_read_rate', + 'diskBytesWriteRate': 'bytes_write_rate', + 'diskIopsReadRate': 'iops_read_rate', + 'diskIopsWriteRate': 'iops_write_rate', + 'maxiops': 'iops_max', + 'miniops': 'iops_min', + 'hypervisorsnapshotreserve': 'hypervisor_snapshot_reserve', + 'customized': 'customized', + 'provisioningtype': 'provisioning_type', + 'storagetype': 'storage_type', + 'tags': 'storage_tags', + 'displayoffering': 'display_offering', + } + + self.disk_offering = None + + def get_disk_offering(self): + args = { + 'name': self.module.params.get('name'), + 'domainid': self.get_domain(key='id'), + } + disk_offerings = self.query_api('listDiskOfferings', **args) + if disk_offerings: + for disk_offer in disk_offerings['diskoffering']: + if args['name'] == disk_offer['name']: + self.disk_offering = disk_offer + + return self.disk_offering + + def present_disk_offering(self): + disk_offering = self.get_disk_offering() + if not disk_offering: + disk_offering = self._create_offering(disk_offering) + else: + disk_offering = self._update_offering(disk_offering) + + return disk_offering + + def absent_disk_offering(self): + disk_offering = self.get_disk_offering() + if disk_offering: + self.result['changed'] = True + if not self.module.check_mode: + args = { + 'id': disk_offering['id'], + } + self.query_api('deleteDiskOffering', **args) + return disk_offering + + def _create_offering(self, disk_offering): + self.result['changed'] = True + + args = { + 'name': self.module.params.get('name'), + 'displaytext': self.get_or_fallback('display_text', 'name'), + 'disksize': self.module.params.get('disk_size'), + 'bytesreadrate': self.module.params.get('bytes_read_rate'), + 'byteswriterate': self.module.params.get('bytes_write_rate'), + 'customized': self.module.params.get('customized'), + 'domainid': self.get_domain(key='id'), + 'hypervisorsnapshotreserve': self.module.params.get('hypervisor_snapshot_reserve'), + 'iopsreadrate': self.module.params.get('iops_read_rate'), + 'iopswriterate': self.module.params.get('iops_write_rate'), + 'maxiops': self.module.params.get('iops_max'), + 'miniops': self.module.params.get('iops_min'), + 'provisioningtype': self.module.params.get('provisioning_type'), + 'diskofferingdetails': self.module.params.get('disk_offering_details'), + 'storagetype': self.module.params.get('storage_type'), + 'tags': self.module.params.get('storage_tags'), + 'displayoffering': self.module.params.get('display_offering'), + } + if not self.module.check_mode: + res = self.query_api('createDiskOffering', **args) + disk_offering = res['diskoffering'] + return disk_offering + + def _update_offering(self, disk_offering): + args = { + 'id': disk_offering['id'], + 'name': self.module.params.get('name'), + 'displaytext': self.get_or_fallback('display_text', 'name'), + 'displayoffering': self.module.params.get('display_offering'), + } + if self.has_changed(args, disk_offering): + self.result['changed'] = True + + if not self.module.check_mode: + res = self.query_api('updateDiskOffering', **args) + disk_offering = res['diskoffering'] + return disk_offering + + def get_result(self, disk_offering): + super(AnsibleCloudStackDiskOffering, self).get_result(disk_offering) + if disk_offering: + # Prevent confusion, the api returns a tags key for storage tags. + if 'tags' in disk_offering: + self.result['storage_tags'] = disk_offering['tags'].split(',') or [disk_offering['tags']] + if 'tags' in self.result: + del self.result['tags'] + + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + display_text=dict(), + domain=dict(), + disk_size=dict(type='int'), + display_offering=dict(type='bool'), + hypervisor_snapshot_reserve=dict(type='int'), + bytes_read_rate=dict(type='int'), + bytes_write_rate=dict(type='int'), + customized=dict(type='bool'), + iops_read_rate=dict(type='int'), + iops_write_rate=dict(type='int'), + iops_max=dict(type='int'), + iops_min=dict(type='int'), + provisioning_type=dict(choices=['thin', 'sparse', 'fat']), + storage_type=dict(choices=['local', 'shared']), + storage_tags=dict(type='list', elements='str', aliases=['storage_tag']), + state=dict(choices=['present', 'absent'], default='present'), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_do = AnsibleCloudStackDiskOffering(module) + + state = module.params.get('state') + if state == "absent": + disk_offering = acs_do.absent_disk_offering() + else: + disk_offering = acs_do.present_disk_offering() + + result = acs_do.get_result(disk_offering) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_domain.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_domain.py new file mode 100644 index 00000000..68177164 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_domain.py @@ -0,0 +1,244 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_domain +short_description: Manages domains on Apache CloudStack based clouds. +description: + - Create, update and remove domains. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + path: + description: + - Path of the domain. + - Prefix C(ROOT/) or C(/ROOT/) in path is optional. + type: str + required: true + network_domain: + description: + - Network domain for networks in the domain. + type: str + clean_up: + description: + - Clean up all domain resources like child domains and accounts. + - Considered on I(state=absent). + type: bool + default: no + state: + description: + - State of the domain. + type: str + choices: [ present, absent ] + default: present + poll_async: + description: + - Poll async jobs until job has finished. + type: bool + default: yes +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Create a domain + ngine_io.cloudstack.cs_domain: + path: ROOT/customers + network_domain: customers.example.com + +- name: Create another subdomain + ngine_io.cloudstack.cs_domain: + path: ROOT/customers/xy + network_domain: xy.customers.example.com + +- name: Remove a domain + ngine_io.cloudstack.cs_domain: + path: ROOT/customers/xy + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the domain. + returned: success + type: str + sample: 87b1e0ce-4e01-11e4-bb66-0050569e64b8 +name: + description: Name of the domain. + returned: success + type: str + sample: customers +path: + description: Domain path. + returned: success + type: str + sample: /ROOT/customers +parent_domain: + description: Parent domain of the domain. + returned: success + type: str + sample: ROOT +network_domain: + description: Network domain of the domain. + returned: success + type: str + sample: example.local +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackDomain(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackDomain, self).__init__(module) + self.returns = { + 'path': 'path', + 'networkdomain': 'network_domain', + 'parentdomainname': 'parent_domain', + } + self.domain = None + + def _get_domain_internal(self, path=None): + if not path: + path = self.module.params.get('path') + + if path.endswith('/'): + self.module.fail_json(msg="Path '%s' must not end with /" % path) + + path = path.lower() + + if path.startswith('/') and not path.startswith('/root/'): + path = "root" + path + elif not path.startswith('root/'): + path = "root/" + path + + args = { + 'listall': True, + 'fetch_list': True, + } + + domains = self.query_api('listDomains', **args) + if domains: + for d in domains: + if path == d['path'].lower(): + return d + return None + + def get_name(self): + # last part of the path is the name + name = self.module.params.get('path').split('/')[-1:] + return name + + def get_domain(self, key=None): + if not self.domain: + self.domain = self._get_domain_internal() + return self._get_by_key(key, self.domain) + + def get_parent_domain(self, key=None): + path = self.module.params.get('path') + # cut off last /* + path = '/'.join(path.split('/')[:-1]) + if not path: + return None + parent_domain = self._get_domain_internal(path=path) + if not parent_domain: + self.module.fail_json(msg="Parent domain path %s does not exist" % path) + return self._get_by_key(key, parent_domain) + + def present_domain(self): + domain = self.get_domain() + if not domain: + domain = self.create_domain(domain) + else: + domain = self.update_domain(domain) + return domain + + def create_domain(self, domain): + self.result['changed'] = True + + args = { + 'name': self.get_name(), + 'parentdomainid': self.get_parent_domain(key='id'), + 'networkdomain': self.module.params.get('network_domain') + } + if not self.module.check_mode: + res = self.query_api('createDomain', **args) + domain = res['domain'] + return domain + + def update_domain(self, domain): + args = { + 'id': domain['id'], + 'networkdomain': self.module.params.get('network_domain') + } + if self.has_changed(args, domain): + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('updateDomain', **args) + domain = res['domain'] + return domain + + def absent_domain(self): + domain = self.get_domain() + if domain: + self.result['changed'] = True + + if not self.module.check_mode: + args = { + 'id': domain['id'], + 'cleanup': self.module.params.get('clean_up') + } + res = self.query_api('deleteDomain', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + res = self.poll_job(res, 'domain') + return domain + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + path=dict(required=True), + state=dict(choices=['present', 'absent'], default='present'), + network_domain=dict(), + clean_up=dict(type='bool', default=False), + poll_async=dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_dom = AnsibleCloudStackDomain(module) + + state = module.params.get('state') + if state in ['absent']: + domain = acs_dom.absent_domain() + else: + domain = acs_dom.present_domain() + + result = acs_dom.get_result(domain) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_facts.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_facts.py new file mode 100644 index 00000000..e60d5e80 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_facts.py @@ -0,0 +1,231 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_facts +short_description: Gather facts on instances of Apache CloudStack based clouds. +description: + - This module fetches data from the metadata API in CloudStack. The module must be called from within the instance itself. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + filter: + description: + - Filter for a specific fact. + type: str + choices: + - cloudstack_service_offering + - cloudstack_availability_zone + - cloudstack_public_hostname + - cloudstack_public_ipv4 + - cloudstack_local_hostname + - cloudstack_local_ipv4 + - cloudstack_instance_id + - cloudstack_user_data + meta_data_host: + description: + - Host or IP of the meta data API service. + - If not set, determination by parsing the dhcp lease file. + type: str +requirements: [ yaml ] +''' + +EXAMPLES = ''' +# Gather all facts on instances +- name: Gather cloudstack facts + ngine_io.cloudstack.cs_facts: + +# Gather specific fact on instances +- name: Gather cloudstack facts + ngine_io.cloudstack.cs_facts: filter=cloudstack_instance_id + +# Gather specific fact on instances with a given meta_data_host +- name: Gather cloudstack facts + ngine_io.cloudstack.cs_facts: + filter: cloudstack_instance_id + meta_data_host: 169.254.169.254 +''' + +RETURN = ''' +--- +cloudstack_availability_zone: + description: zone the instance is deployed in. + returned: success + type: str + sample: ch-gva-2 +cloudstack_instance_id: + description: UUID of the instance. + returned: success + type: str + sample: ab4e80b0-3e7e-4936-bdc5-e334ba5b0139 +cloudstack_local_hostname: + description: local hostname of the instance. + returned: success + type: str + sample: VM-ab4e80b0-3e7e-4936-bdc5-e334ba5b0139 +cloudstack_local_ipv4: + description: local IPv4 of the instance. + returned: success + type: str + sample: 185.19.28.35 +cloudstack_public_hostname: + description: public IPv4 of the router. Same as I(cloudstack_public_ipv4). + returned: success + type: str + sample: VM-ab4e80b0-3e7e-4936-bdc5-e334ba5b0139 +cloudstack_public_ipv4: + description: public IPv4 of the router. + returned: success + type: str + sample: 185.19.28.35 +cloudstack_service_offering: + description: service offering of the instance. + returned: success + type: str + sample: Micro 512mb 1cpu +cloudstack_user_data: + description: data of the instance provided by users. + returned: success + type: dict + sample: { "bla": "foo" } +''' + +import os +import traceback +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils.urls import fetch_url +from ansible.module_utils.facts import ansible_collector, default_collectors + +YAML_IMP_ERR = None +try: + import yaml + HAS_LIB_YAML = True +except ImportError: + YAML_IMP_ERR = traceback.format_exc() + HAS_LIB_YAML = False + +CS_METADATA_BASE_URL = "http://%s/latest/meta-data" +CS_USERDATA_BASE_URL = "http://%s/latest/user-data" + + +class CloudStackFacts(object): + + def __init__(self): + collector = ansible_collector.get_ansible_collector(all_collector_classes=default_collectors.collectors, + filter_spec='default_ipv4', + gather_subset=['!all', 'network'], + gather_timeout=10) + self.facts = collector.collect(module) + + self.api_ip = None + self.fact_paths = { + 'cloudstack_service_offering': 'service-offering', + 'cloudstack_availability_zone': 'availability-zone', + 'cloudstack_public_hostname': 'public-hostname', + 'cloudstack_public_ipv4': 'public-ipv4', + 'cloudstack_local_hostname': 'local-hostname', + 'cloudstack_local_ipv4': 'local-ipv4', + 'cloudstack_instance_id': 'instance-id' + } + + def run(self): + result = {} + filter = module.params.get('filter') + if not filter: + for key, path in self.fact_paths.items(): + result[key] = self._fetch(CS_METADATA_BASE_URL + "/" + path) + result['cloudstack_user_data'] = self._get_user_data_json() + else: + if filter == 'cloudstack_user_data': + result['cloudstack_user_data'] = self._get_user_data_json() + elif filter in self.fact_paths: + result[filter] = self._fetch(CS_METADATA_BASE_URL + "/" + self.fact_paths[filter]) + return result + + def _get_user_data_json(self): + try: + # this data come form users, we try what we can to parse it... + return yaml.safe_load(self._fetch(CS_USERDATA_BASE_URL)) + except Exception: + return None + + def _fetch(self, path): + api_ip = self._get_api_ip() + if not api_ip: + return None + api_url = path % api_ip + (response, info) = fetch_url(module, api_url, force=True) + if response: + data = response.read() + else: + data = None + return data + + def _get_dhcp_lease_file(self): + """Return the path of the lease file.""" + default_iface = self.facts['default_ipv4']['interface'] + dhcp_lease_file_locations = [ + '/var/lib/dhcp/dhclient.%s.leases' % default_iface, # debian / ubuntu + '/var/lib/dhclient/dhclient-%s.leases' % default_iface, # centos 6 + '/var/lib/dhclient/dhclient--%s.lease' % default_iface, # centos 7 + '/var/db/dhclient.leases.%s' % default_iface, # openbsd + ] + for file_path in dhcp_lease_file_locations: + if os.path.exists(file_path): + return file_path + module.fail_json(msg="Could not find dhclient leases file.") + + def _get_api_ip(self): + """Return the IP of the DHCP server.""" + if module.params.get('meta_data_host'): + return module.params.get('meta_data_host') + elif not self.api_ip: + dhcp_lease_file = self._get_dhcp_lease_file() + for line in open(dhcp_lease_file): + if 'dhcp-server-identifier' in line: + # get IP of string "option dhcp-server-identifier 185.19.28.176;" + line = line.translate(None, ';') + self.api_ip = line.split()[2] + break + if not self.api_ip: + module.fail_json(msg="No dhcp-server-identifier found in leases file.") + return self.api_ip + + +def main(): + global module + module = AnsibleModule( + argument_spec=dict( + filter=dict(default=None, choices=[ + 'cloudstack_service_offering', + 'cloudstack_availability_zone', + 'cloudstack_public_hostname', + 'cloudstack_public_ipv4', + 'cloudstack_local_hostname', + 'cloudstack_local_ipv4', + 'cloudstack_instance_id', + 'cloudstack_user_data', + ]), + meta_data_host=dict(), + ), + supports_check_mode=True + ) + + if not HAS_LIB_YAML: + module.fail_json(msg=missing_required_lib("PyYAML"), exception=YAML_IMP_ERR) + + cs_facts = CloudStackFacts().run() + cs_facts_result = dict(changed=False, ansible_facts=cs_facts) + module.exit_json(**cs_facts_result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_firewall.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_firewall.py new file mode 100644 index 00000000..f8878877 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_firewall.py @@ -0,0 +1,439 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright: (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_firewall +short_description: Manages firewall rules on Apache CloudStack based clouds. +description: + - Creates and removes firewall rules. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + ip_address: + description: + - Public IP address the ingress rule is assigned to. + - Required if I(type=ingress). + type: str + network: + description: + - Network the egress rule is related to. + - Required if I(type=egress). + type: str + state: + description: + - State of the firewall rule. + type: str + default: present + choices: [ present, absent ] + type: + description: + - Type of the firewall rule. + type: str + default: ingress + choices: [ ingress, egress ] + protocol: + description: + - Protocol of the firewall rule. + - C(all) is only available if I(type=egress). + type: str + default: tcp + choices: [ tcp, udp, icmp, all ] + cidrs: + description: + - List of CIDRs (full notation) to be used for firewall rule. + - Since version 2.5, it is a list of CIDR. + elements: str + type: list + default: 0.0.0.0/0 + aliases: [ cidr ] + start_port: + description: + - Start port for this rule. + - Considered if I(protocol=tcp) or I(protocol=udp). + type: int + aliases: [ port ] + end_port: + description: + - End port for this rule. Considered if I(protocol=tcp) or I(protocol=udp). + - If not specified, equal I(start_port). + type: int + icmp_type: + description: + - Type of the icmp message being sent. + - Considered if I(protocol=icmp). + type: int + icmp_code: + description: + - Error code for this icmp message. + - Considered if I(protocol=icmp). + type: int + domain: + description: + - Domain the firewall rule is related to. + type: str + account: + description: + - Account the firewall rule is related to. + type: str + project: + description: + - Name of the project the firewall rule is related to. + type: str + zone: + description: + - Name of the zone in which the virtual machine is in. + - If not set, default zone is used. + type: str + poll_async: + description: + - Poll async jobs until job has finished. + type: bool + default: yes + tags: + description: + - List of tags. Tags are a list of dictionaries having keys I(key) and I(value). + - "To delete all tags, set an empty list e.g. I(tags: [])." + type: list + elements: dict + aliases: [ tag ] +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Allow inbound port 80/tcp from 1.2.3.4 to 4.3.2.1 + ngine_io.cloudstack.cs_firewall: + ip_address: 4.3.2.1 + port: 80 + cidr: 1.2.3.4/32 + +- name: Allow inbound tcp/udp port 53 to 4.3.2.1 + ngine_io.cloudstack.cs_firewall: + ip_address: 4.3.2.1 + port: 53 + protocol: '{{ item }}' + with_items: + - tcp + - udp + +- name: Ensure firewall rule is removed + ngine_io.cloudstack.cs_firewall: + ip_address: 4.3.2.1 + start_port: 8000 + end_port: 8888 + cidr: 17.0.0.0/8 + state: absent + +- name: Allow all outbound traffic + ngine_io.cloudstack.cs_firewall: + network: my_network + type: egress + protocol: all + +- name: Allow only HTTP outbound traffic for an IP + ngine_io.cloudstack.cs_firewall: + network: my_network + type: egress + port: 80 + cidr: 10.101.1.20 +''' + +RETURN = ''' +--- +id: + description: UUID of the rule. + returned: success + type: str + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 +ip_address: + description: IP address of the rule if C(type=ingress) + returned: success + type: str + sample: 10.100.212.10 +type: + description: Type of the rule. + returned: success + type: str + sample: ingress +cidr: + description: CIDR string of the rule. + returned: success + type: str + sample: 0.0.0.0/0 +cidrs: + description: CIDR list of the rule. + returned: success + type: list + sample: [ '0.0.0.0/0' ] +protocol: + description: Protocol of the rule. + returned: success + type: str + sample: tcp +start_port: + description: Start port of the rule. + returned: success + type: int + sample: 80 +end_port: + description: End port of the rule. + returned: success + type: int + sample: 80 +icmp_code: + description: ICMP code of the rule. + returned: success + type: int + sample: 1 +icmp_type: + description: ICMP type of the rule. + returned: success + type: int + sample: 1 +network: + description: Name of the network if C(type=egress) + returned: success + type: str + sample: my_network +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackFirewall(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackFirewall, self).__init__(module) + self.returns = { + 'cidrlist': 'cidr', + 'startport': 'start_port', + 'endport': 'end_port', + 'protocol': 'protocol', + 'ipaddress': 'ip_address', + 'icmpcode': 'icmp_code', + 'icmptype': 'icmp_type', + } + self.firewall_rule = None + self.network = None + + def get_firewall_rule(self): + if not self.firewall_rule: + cidrs = self.module.params.get('cidrs') + protocol = self.module.params.get('protocol') + start_port = self.module.params.get('start_port') + end_port = self.get_or_fallback('end_port', 'start_port') + icmp_code = self.module.params.get('icmp_code') + icmp_type = self.module.params.get('icmp_type') + fw_type = self.module.params.get('type') + + if protocol in ['tcp', 'udp'] and not (start_port and end_port): + self.module.fail_json(msg="missing required argument for protocol '%s': start_port or end_port" % protocol) + + if protocol == 'icmp' and not icmp_type: + self.module.fail_json(msg="missing required argument for protocol 'icmp': icmp_type") + + if protocol == 'all' and fw_type != 'egress': + self.module.fail_json(msg="protocol 'all' could only be used for type 'egress'") + + args = { + 'account': self.get_account('name'), + 'domainid': self.get_domain('id'), + 'projectid': self.get_project('id'), + 'fetch_list': True, + } + if fw_type == 'egress': + args['networkid'] = self.get_network(key='id') + if not args['networkid']: + self.module.fail_json(msg="missing required argument for type egress: network") + + # CloudStack 4.11 use the network cidr for 0.0.0.0/0 in egress + # That is why we need to replace it. + network_cidr = self.get_network(key='cidr') + egress_cidrs = [network_cidr if cidr == '0.0.0.0/0' else cidr for cidr in cidrs] + + firewall_rules = self.query_api('listEgressFirewallRules', **args) + else: + args['ipaddressid'] = self.get_ip_address('id') + if not args['ipaddressid']: + self.module.fail_json(msg="missing required argument for type ingress: ip_address") + egress_cidrs = None + + firewall_rules = self.query_api('listFirewallRules', **args) + + if firewall_rules: + for rule in firewall_rules: + type_match = self._type_cidrs_match(rule, cidrs, egress_cidrs) + + protocol_match = ( + self._tcp_udp_match(rule, protocol, start_port, end_port) or + self._icmp_match(rule, protocol, icmp_code, icmp_type) or + self._egress_all_match(rule, protocol, fw_type) + ) + + if type_match and protocol_match: + self.firewall_rule = rule + break + return self.firewall_rule + + def _tcp_udp_match(self, rule, protocol, start_port, end_port): + return ( + protocol in ['tcp', 'udp'] and + protocol == rule['protocol'] and + start_port == int(rule['startport']) and + end_port == int(rule['endport']) + ) + + def _egress_all_match(self, rule, protocol, fw_type): + return ( + protocol in ['all'] and + protocol == rule['protocol'] and + fw_type == 'egress' + ) + + def _icmp_match(self, rule, protocol, icmp_code, icmp_type): + return ( + protocol == 'icmp' and + protocol == rule['protocol'] and + icmp_code == rule['icmpcode'] and + icmp_type == rule['icmptype'] + ) + + def _type_cidrs_match(self, rule, cidrs, egress_cidrs): + if egress_cidrs is not None: + return ",".join(egress_cidrs) == rule['cidrlist'] or ",".join(cidrs) == rule['cidrlist'] + else: + return ",".join(cidrs) == rule['cidrlist'] + + def create_firewall_rule(self): + firewall_rule = self.get_firewall_rule() + if not firewall_rule: + self.result['changed'] = True + + args = { + 'cidrlist': self.module.params.get('cidrs'), + 'protocol': self.module.params.get('protocol'), + 'startport': self.module.params.get('start_port'), + 'endport': self.get_or_fallback('end_port', 'start_port'), + 'icmptype': self.module.params.get('icmp_type'), + 'icmpcode': self.module.params.get('icmp_code') + } + + fw_type = self.module.params.get('type') + if not self.module.check_mode: + if fw_type == 'egress': + args['networkid'] = self.get_network(key='id') + res = self.query_api('createEgressFirewallRule', **args) + else: + args['ipaddressid'] = self.get_ip_address('id') + res = self.query_api('createFirewallRule', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + firewall_rule = self.poll_job(res, 'firewallrule') + + if firewall_rule: + firewall_rule = self.ensure_tags(resource=firewall_rule, resource_type='Firewallrule') + self.firewall_rule = firewall_rule + + return firewall_rule + + def remove_firewall_rule(self): + firewall_rule = self.get_firewall_rule() + if firewall_rule: + self.result['changed'] = True + + args = { + 'id': firewall_rule['id'] + } + + fw_type = self.module.params.get('type') + if not self.module.check_mode: + if fw_type == 'egress': + res = self.query_api('deleteEgressFirewallRule', **args) + else: + res = self.query_api('deleteFirewallRule', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(res, 'firewallrule') + return firewall_rule + + def get_result(self, firewall_rule): + super(AnsibleCloudStackFirewall, self).get_result(firewall_rule) + if firewall_rule: + self.result['type'] = self.module.params.get('type') + if self.result['type'] == 'egress': + self.result['network'] = self.get_network(key='displaytext') + if 'cidrlist' in firewall_rule: + self.result['cidrs'] = firewall_rule['cidrlist'].split(',') or [firewall_rule['cidrlist']] + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + ip_address=dict(), + network=dict(), + cidrs=dict(type='list', elements='str', default='0.0.0.0/0', aliases=['cidr']), + protocol=dict(choices=['tcp', 'udp', 'icmp', 'all'], default='tcp'), + type=dict(choices=['ingress', 'egress'], default='ingress'), + icmp_type=dict(type='int'), + icmp_code=dict(type='int'), + start_port=dict(type='int', aliases=['port']), + end_port=dict(type='int'), + state=dict(choices=['present', 'absent'], default='present'), + zone=dict(), + domain=dict(), + account=dict(), + project=dict(), + poll_async=dict(type='bool', default=True), + tags=dict(type='list', elements='dict', aliases=['tag']), + )) + + required_together = cs_required_together() + required_together.extend([ + ['icmp_type', 'icmp_code'], + ]) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=required_together, + required_one_of=( + ['ip_address', 'network'], + ), + mutually_exclusive=( + ['icmp_type', 'start_port'], + ['icmp_type', 'end_port'], + ['ip_address', 'network'], + ), + supports_check_mode=True + ) + + acs_fw = AnsibleCloudStackFirewall(module) + + state = module.params.get('state') + if state in ['absent']: + fw_rule = acs_fw.remove_firewall_rule() + else: + fw_rule = acs_fw.create_firewall_rule() + + result = acs_fw.get_result(fw_rule) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_host.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_host.py new file mode 100644 index 00000000..111b4e0e --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_host.py @@ -0,0 +1,607 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_host +short_description: Manages hosts on Apache CloudStack based clouds. +description: + - Create, update and remove hosts. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name of the host. + type: str + required: true + aliases: [ ip_address ] + url: + description: + - Url of the host used to create a host. + - If not provided, C(http://) and param I(name) is used as url. + - Only considered if I(state=present) and host does not yet exist. + type: str + username: + description: + - Username for the host. + - Required if I(state=present) and host does not yet exist. + type: str + password: + description: + - Password for the host. + - Required if I(state=present) and host does not yet exist. + type: str + pod: + description: + - Name of the pod. + - Required if I(state=present) and host does not yet exist. + type: str + cluster: + description: + - Name of the cluster. + type: str + hypervisor: + description: + - Name of the cluster. + - Required if I(state=present) and host does not yet exist. + - Possible values are C(KVM), C(VMware), C(BareMetal), C(XenServer), C(LXC), C(HyperV), C(UCS), C(OVM), C(Simulator). + type: str + allocation_state: + description: + - Allocation state of the host. + type: str + choices: [ enabled, disabled, maintenance ] + host_tags: + description: + - Tags of the host. + type: list + elements: str + aliases: [ host_tag ] + state: + description: + - State of the host. + type: str + default: present + choices: [ present, absent ] + zone: + description: + - Name of the zone in which the host should be deployed. + - If not set, default zone is used. + type: str +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Ensure a host is present but disabled + ngine_io.cloudstack.cs_host: + name: pod01.zone01.example.com + cluster: vcenter.example.com/zone01/cluster01 + pod: pod01 + zone: zone01 + hypervisor: VMware + allocation_state: disabled + host_tags: + - perf + - gpu + +- name: Ensure an existing host is disabled + ngine_io.cloudstack.cs_host: + name: pod01.zone01.example.com + zone: zone01 + allocation_state: disabled + +- name: Ensure an existing host is enabled + ngine_io.cloudstack.cs_host: + name: pod01.zone01.example.com + zone: zone01 + allocation_state: enabled + +- name: Ensure a host is absent + ngine_io.cloudstack.cs_host: + name: pod01.zone01.example.com + zone: zone01 + state: absent +''' + +RETURN = ''' +--- +capabilities: + description: Capabilities of the host. + returned: success + type: str + sample: hvm +cluster: + description: Cluster of the host. + returned: success + type: str + sample: vcenter.example.com/zone/cluster01 +cluster_type: + description: Type of the cluster of the host. + returned: success + type: str + sample: ExternalManaged +cpu_allocated: + description: Amount in percent of the host's CPU currently allocated. + returned: success + type: str + sample: 166.25% +cpu_number: + description: Number of CPUs of the host. + returned: success + type: str + sample: 24 +cpu_sockets: + description: Number of CPU sockets of the host. + returned: success + type: int + sample: 2 +cpu_speed: + description: CPU speed in Mhz + returned: success + type: int + sample: 1999 +cpu_used: + description: Amount of the host's CPU currently used. + returned: success + type: str + sample: 33.6% +cpu_with_overprovisioning: + description: Amount of the host's CPU after applying the cpu.overprovisioning.factor. + returned: success + type: str + sample: 959520.0 +created: + description: Date when the host was created. + returned: success + type: str + sample: 2015-05-03T15:05:51+0200 +disconnected: + description: Date when the host was disconnected. + returned: success + type: str + sample: 2015-05-03T15:05:51+0200 +disk_size_allocated: + description: Host's currently allocated disk size. + returned: success + type: int + sample: 2593 +disk_size_total: + description: Total disk size of the host + returned: success + type: int + sample: 259300 +events: + description: Events available for the host + returned: success + type: str + sample: "Ping; HostDown; AgentConnected; AgentDisconnected; PingTimeout; ShutdownRequested; Remove; StartAgentRebalance; ManagementServerDown" +ha_host: + description: Whether the host is a HA host. + returned: success + type: bool + sample: false +has_enough_capacity: + description: Whether the host has enough CPU and RAM capacity to migrate a VM to it. + returned: success + type: bool + sample: true +host_tags: + description: Comma-separated list of tags for the host. + returned: success + type: str + sample: "perf" +hypervisor: + description: Host's hypervisor. + returned: success + type: str + sample: VMware +hypervisor_version: + description: Hypervisor version. + returned: success + type: str + sample: 5.1 +ip_address: + description: IP address of the host + returned: success + type: str + sample: 10.10.10.1 +is_local_storage_active: + description: Whether the local storage is available or not. + returned: success + type: bool + sample: false +last_pinged: + description: Date and time the host was last pinged. + returned: success + type: str + sample: "1970-01-17T17:27:32+0100" +management_server_id: + description: Management server ID of the host. + returned: success + type: int + sample: 345050593418 +memory_allocated: + description: Amount of the host's memory currently allocated. + returned: success + type: int + sample: 69793218560 +memory_total: + description: Total of memory of the host. + returned: success + type: int + sample: 206085263360 +memory_used: + description: Amount of the host's memory currently used. + returned: success + type: int + sample: 65504776192 +name: + description: Name of the host. + returned: success + type: str + sample: esx32.example.com +network_kbs_read: + description: Incoming network traffic on the host. + returned: success + type: int + sample: 0 +network_kbs_write: + description: Outgoing network traffic on the host. + returned: success + type: int + sample: 0 +os_category: + description: OS category name of the host. + returned: success + type: str + sample: ... +out_of_band_management: + description: Host out-of-band management information. + returned: success + type: str + sample: ... +pod: + description: Pod name of the host. + returned: success + type: str + sample: Pod01 +removed: + description: Date and time the host was removed. + returned: success + type: str + sample: "1970-01-17T17:27:32+0100" +resource_state: + description: Resource state of the host. + returned: success + type: str + sample: Enabled +allocation_state:: + description: Allocation state of the host. + returned: success + type: str + sample: enabled +state: + description: State of the host. + returned: success + type: str + sample: Up +suitable_for_migration: + description: Whether this host is suitable (has enough capacity and satisfies all conditions like hosttags, max guests VM limit, etc) to migrate a VM + to it or not. + returned: success + type: str + sample: true +host_type: + description: Type of the host. + returned: success + type: str + sample: Routing +host_version: + description: Version of the host. + returned: success + type: str + sample: 4.5.2 +gpu_group: + description: GPU cards present in the host. + returned: success + type: list + sample: [] +zone: + description: Zone of the host. + returned: success + type: str + sample: zone01 +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) +import time + + +class AnsibleCloudStackHost(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackHost, self).__init__(module) + self.returns = { + 'averageload': 'average_load', + 'capabilities': 'capabilities', + 'clustername': 'cluster', + 'clustertype': 'cluster_type', + 'cpuallocated': 'cpu_allocated', + 'cpunumber': 'cpu_number', + 'cpusockets': 'cpu_sockets', + 'cpuspeed': 'cpu_speed', + 'cpuused': 'cpu_used', + 'cpuwithoverprovisioning': 'cpu_with_overprovisioning', + 'disconnected': 'disconnected', + 'details': 'details', + 'disksizeallocated': 'disk_size_allocated', + 'disksizetotal': 'disk_size_total', + 'events': 'events', + 'hahost': 'ha_host', + 'hasenoughcapacity': 'has_enough_capacity', + 'hypervisor': 'hypervisor', + 'hypervisorversion': 'hypervisor_version', + 'ipaddress': 'ip_address', + 'islocalstorageactive': 'is_local_storage_active', + 'lastpinged': 'last_pinged', + 'managementserverid': 'management_server_id', + 'memoryallocated': 'memory_allocated', + 'memorytotal': 'memory_total', + 'memoryused': 'memory_used', + 'networkkbsread': 'network_kbs_read', + 'networkkbswrite': 'network_kbs_write', + 'oscategoryname': 'os_category', + 'outofbandmanagement': 'out_of_band_management', + 'podname': 'pod', + 'removed': 'removed', + 'resourcestate': 'resource_state', + 'suitableformigration': 'suitable_for_migration', + 'type': 'host_type', + 'version': 'host_version', + 'gpugroup': 'gpu_group', + } + # States only usable by the updateHost API + self.allocation_states_for_update = { + 'enabled': 'Enable', + 'disabled': 'Disable', + } + self.host = None + + def get_cluster(self, key=None): + cluster_name = self.module.params.get('cluster') + if not cluster_name: + return None + args = { + 'name': cluster_name, + 'zoneid': self.get_zone(key='id'), + } + clusters = self.query_api('listClusters', **args) + if clusters: + return self._get_by_key(key, clusters['cluster'][0]) + self.module.fail_json(msg="Cluster %s not found" % cluster_name) + + def get_host_tags(self): + host_tags = self.module.params.get('host_tags') + if host_tags is None: + return None + return ','.join(host_tags) + + def get_host(self, refresh=False): + if self.host is not None and not refresh: + return self.host + + name = self.module.params.get('name') + args = { + 'zoneid': self.get_zone(key='id'), + 'fetch_list': True, + } + res = self.query_api('listHosts', **args) + if res: + for h in res: + if name in [h['ipaddress'], h['name']]: + self.host = h + return self.host + + def _handle_allocation_state(self, host): + allocation_state = self.module.params.get('allocation_state') + if not allocation_state: + return host + + host = self._set_host_allocation_state(host) + + # In case host in maintenance and target is maintenance + if host['allocationstate'].lower() == allocation_state and allocation_state == 'maintenance': + return host + + # Cancel maintenance if target state is enabled/disabled + elif allocation_state in list(self.allocation_states_for_update.keys()): + host = self.disable_maintenance(host) + host = self._update_host(host, self.allocation_states_for_update[allocation_state]) + + # Only an enabled host can put in maintenance + elif allocation_state == 'maintenance': + host = self._update_host(host, 'Enable') + host = self.enable_maintenance(host) + + return host + + def _set_host_allocation_state(self, host): + if host is None: + host['allocationstate'] = 'Enable' + + # Set host allocationstate to be disabled/enabled + elif host['resourcestate'].lower() in list(self.allocation_states_for_update.keys()): + host['allocationstate'] = self.allocation_states_for_update[host['resourcestate'].lower()] + + else: + host['allocationstate'] = host['resourcestate'] + + return host + + def present_host(self): + host = self.get_host() + + if not host: + host = self._create_host(host) + else: + host = self._update_host(host) + + if host: + host = self._handle_allocation_state(host) + + return host + + def _get_url(self): + url = self.module.params.get('url') + if url: + return url + else: + return "http://%s" % self.module.params.get('name') + + def _create_host(self, host): + required_params = [ + 'password', + 'username', + 'hypervisor', + 'pod', + ] + self.module.fail_on_missing_params(required_params=required_params) + self.result['changed'] = True + args = { + 'hypervisor': self.module.params.get('hypervisor'), + 'url': self._get_url(), + 'username': self.module.params.get('username'), + 'password': self.module.params.get('password'), + 'podid': self.get_pod(key='id'), + 'zoneid': self.get_zone(key='id'), + 'clusterid': self.get_cluster(key='id'), + 'hosttags': self.get_host_tags(), + } + if not self.module.check_mode: + host = self.query_api('addHost', **args) + host = host['host'][0] + return host + + def _update_host(self, host, allocation_state=None): + args = { + 'id': host['id'], + 'hosttags': self.get_host_tags(), + 'allocationstate': allocation_state, + } + + if allocation_state is not None: + host = self._set_host_allocation_state(host) + + if self.has_changed(args, host): + self.result['changed'] = True + if not self.module.check_mode: + host = self.query_api('updateHost', **args) + host = host['host'] + + return host + + def absent_host(self): + host = self.get_host() + if host: + self.result['changed'] = True + args = { + 'id': host['id'], + } + if not self.module.check_mode: + res = self.enable_maintenance(host) + if res: + res = self.query_api('deleteHost', **args) + return host + + def enable_maintenance(self, host): + if host['resourcestate'] not in ['PrepareForMaintenance', 'Maintenance']: + self.result['changed'] = True + args = { + 'id': host['id'], + } + if not self.module.check_mode: + res = self.query_api('prepareHostForMaintenance', **args) + self.poll_job(res, 'host') + host = self._poll_for_maintenance() + return host + + def disable_maintenance(self, host): + if host['resourcestate'] in ['PrepareForMaintenance', 'Maintenance']: + self.result['changed'] = True + args = { + 'id': host['id'], + } + if not self.module.check_mode: + res = self.query_api('cancelHostMaintenance', **args) + host = self.poll_job(res, 'host') + return host + + def _poll_for_maintenance(self): + for i in range(0, 300): + time.sleep(2) + host = self.get_host(refresh=True) + if not host: + return None + elif host['resourcestate'] != 'PrepareForMaintenance': + return host + self.fail_json(msg="Polling for maintenance timed out") + + def get_result(self, host): + super(AnsibleCloudStackHost, self).get_result(host) + if host: + self.result['allocation_state'] = host['resourcestate'].lower() + self.result['host_tags'] = host['hosttags'].split(',') if host.get('hosttags') else [] + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True, aliases=['ip_address']), + url=dict(), + password=dict(no_log=True), + username=dict(), + hypervisor=dict(), + allocation_state=dict(choices=['enabled', 'disabled', 'maintenance']), + pod=dict(), + cluster=dict(), + host_tags=dict(type='list', elements='str', aliases=['host_tag']), + zone=dict(), + state=dict(choices=['present', 'absent'], default='present'), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_host = AnsibleCloudStackHost(module) + + state = module.params.get('state') + if state == 'absent': + host = acs_host.absent_host() + else: + host = acs_host.present_host() + + result = acs_host.get_result(host) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_image_store.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_image_store.py new file mode 100644 index 00000000..b97d6e26 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_image_store.py @@ -0,0 +1,246 @@ +#!/usr/bin/python + +# Copyright: (c) 2019, Patryk Cichy @PatTheSilent +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_image_store + +short_description: Manages CloudStack Image Stores. + + +description: + - Deploy, remove, recreate CloudStack Image Stores. + +version_added: 0.1.0 +options: + url: + description: + - The URL for the Image Store. + - Required when I(state=present). + type: str + name: + description: + - The ID of the Image Store. Required when deleting a Image Store. + required: true + type: str + zone: + description: + - The Zone name for the Image Store. + required: true + type: str + state: + description: + - Stage of the Image Store + choices: [present, absent] + default: present + type: str + provider: + description: + - The image store provider name. Required when creating a new Image Store + type: str + force_recreate: + description: + - Set to C(yes) if you're changing an existing Image Store. + - This will force the recreation of the Image Store. + - Recreation might fail if there are snapshots present on the Image Store. Delete them before running the recreation. + type: bool + default: no + +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack + + +author: + - Patryk Cichy (@PatTheSilent) +''' + +EXAMPLES = ''' +- name: Add a Image Store (NFS) + ngine_io.cloudstack.cs_image_store: + zone: zone-01 + name: nfs-01 + provider: NFS + url: nfs://192.168.21.16/exports/secondary + + +# Change the NFS share URL and force a Image Store recreation +- name: Change the NFS url + ngine_io.cloudstack.cs_image_store: + zone: zone-01 + name: nfs-01 + provider: NFS + force_recreate: yes + url: nfs://192.168.21.10/shares/secondary + +- name: delete the image store + ngine_io.cloudstack.cs_image_store: + name: nfs-01 + zone: zone-01 + state: absent + +''' + +RETURN = ''' +id: + description: the ID of the image store + type: str + returned: success + sample: feb11a84-a093-45eb-b84d-7f680313c40b +name: + description: the name of the image store + type: str + returned: success + sample: nfs-01 +protocol: + description: the protocol of the image store + type: str + returned: success + sample: nfs +provider_name: + description: the provider name of the image store + type: str + returned: success + sample: NFS +scope: + description: the scope of the image store + type: str + returned: success + sample: ZONE +url: + description: the url of the image store + type: str + sample: nfs://192.168.21.16/exports/secondary + returned: success +zone: + description: the Zone name of the image store + type: str + returned: success + sample: zone-01 +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import AnsibleCloudStack, cs_argument_spec, cs_required_together + + +class AnsibleCloudstackImageStore(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudstackImageStore, self).__init__(module) + self.returns = { + 'protocol': 'protocol', + 'providername': 'provider_name', + 'scope': 'scope', + 'url': 'url' + } + self.image_store = None + + def get_storage_providers(self, storage_type="image"): + args = { + 'type': storage_type + } + storage_provides = self.query_api('listStorageProviders', **args) + return [provider.get('name') for provider in storage_provides.get('dataStoreProvider')] + + def get_image_store(self): + if self.image_store: + return self.image_store + image_store = self.module.params.get('name') + args = { + 'name': self.module.params.get('name'), + 'zoneid': self.get_zone(key='id') + } + + image_stores = self.query_api('listImageStores', **args) + if image_stores: + for img_s in image_stores.get('imagestore'): + if image_store.lower() in [img_s['name'].lower(), img_s['id']]: + self.image_store = img_s + break + + return self.image_store + + def present_image_store(self): + provider_list = self.get_storage_providers() + image_store = self.get_image_store() + + if self.module.params.get('provider') not in provider_list: + self.module.fail_json( + msg='Provider %s is not in the provider list (%s). Please specify a correct provider' % ( + self.module.params.get('provider'), provider_list)) + args = { + 'name': self.module.params.get('name'), + 'url': self.module.params.get('url'), + 'zoneid': self.get_zone(key='id'), + 'provider': self.module.params.get('provider') + } + if not image_store: + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('addImageStore', **args) + self.image_store = res.get('imagestore') + else: + # Cloudstack API expects 'provider' but returns 'providername' + args['providername'] = args.pop('provider') + if self.has_changed(args, image_store): + if self.module.params.get('force_recreate'): + self.absent_image_store() + self.image_store = None + self.image_store = self.present_image_store() + else: + self.module.warn("Changes to the Image Store won't be applied" + "Use force_recreate=yes to allow the store to be recreated") + + return self.image_store + + def absent_image_store(self): + image_store = self.get_image_store() + if image_store: + self.result['changed'] = True + if not self.module.check_mode: + args = { + 'id': image_store.get('id') + } + self.query_api('deleteImageStore', **args) + return image_store + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + url=dict(), + name=dict(required=True), + zone=dict(required=True), + provider=dict(), + force_recreate=dict(type='bool', default=False), + state=dict(choices=['present', 'absent'], default='present'), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + required_if=[ + ('state', 'present', ['url', 'provider']), + ], + supports_check_mode=True + ) + + acis_do = AnsibleCloudstackImageStore(module) + + state = module.params.get('state') + if state == "absent": + image_store = acis_do.absent_image_store() + else: + image_store = acis_do.present_image_store() + + result = acis_do.get_result(image_store) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_instance.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_instance.py new file mode 100644 index 00000000..97a0ef93 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_instance.py @@ -0,0 +1,1101 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_instance +short_description: Manages instances and virtual machines on Apache CloudStack based clouds. +description: + - Deploy, start, update, scale, restart, restore, stop and destroy instances. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Host name of the instance. C(name) can only contain ASCII letters. + - Name will be generated (UUID) by CloudStack if not specified and can not be changed afterwards. + - Either C(name) or C(display_name) is required. + type: str + display_name: + description: + - Custom display name of the instances. + - Display name will be set to I(name) if not specified. + - Either I(name) or I(display_name) is required. + type: str + group: + description: + - Group in where the new instance should be in. + type: str + state: + description: + - State of the instance. + type: str + default: present + choices: [ deployed, started, stopped, restarted, restored, destroyed, expunged, present, absent ] + service_offering: + description: + - Name or id of the service offering of the new instance. + - If not set, first found service offering is used. + type: str + cpu: + description: + - The number of CPUs to allocate to the instance, used with custom service offerings + type: int + cpu_speed: + description: + - The clock speed/shares allocated to the instance, used with custom service offerings + type: int + memory: + description: + - The memory allocated to the instance, used with custom service offerings + type: int + template: + description: + - Name, display text or id of the template to be used for creating the new instance. + - Required when using I(state=present). + - Mutually exclusive with I(iso) option. + type: str + iso: + description: + - Name or id of the ISO to be used for creating the new instance. + - Required when using I(state=present). + - Mutually exclusive with I(template) option. + type: str + template_filter: + description: + - Name of the filter used to search for the template or iso. + - Used for params I(iso) or I(template) on I(state=present). + - The filter C(all) was added in 2.6. + type: str + default: executable + choices: [ all, featured, self, selfexecutable, sharedexecutable, executable, community ] + aliases: [ iso_filter ] + hypervisor: + description: + - Name the hypervisor to be used for creating the new instance. + - Relevant when using I(state=present), but only considered if not set on ISO/template. + - If not set or found on ISO/template, first found hypervisor will be used. + - Possible values are C(KVM), C(VMware), C(BareMetal), C(XenServer), C(LXC), C(HyperV), C(UCS), C(OVM), C(Simulator). + type: str + keyboard: + description: + - Keyboard device type for the instance. + type: str + choices: [ 'de', 'de-ch', 'es', 'fi', 'fr', 'fr-be', 'fr-ch', 'is', 'it', 'jp', 'nl-be', 'no', 'pt', 'uk', 'us' ] + networks: + description: + - List of networks to use for the new instance. + type: list + elements: str + aliases: [ network ] + ip_address: + description: + - IPv4 address for default instance's network during creation. + type: str + ip6_address: + description: + - IPv6 address for default instance's network. + type: str + ip_to_networks: + description: + - "List of mappings in the form I({'network': NetworkName, 'ip': 1.2.3.4})" + - Mutually exclusive with I(networks) option. + type: list + elements: dict + aliases: [ ip_to_network ] + disk_offering: + description: + - Name of the disk offering to be used. + type: str + disk_size: + description: + - Disk size in GByte required if deploying instance from ISO. + type: int + root_disk_size: + description: + - Root disk size in GByte required if deploying instance with KVM hypervisor and want resize the root disk size at startup + (need CloudStack >= 4.4, cloud-initramfs-growroot installed and enabled in the template) + type: int + security_groups: + description: + - List of security groups the instance to be applied to. + type: list + elements: str + aliases: [ security_group ] + host: + description: + - Host on which an instance should be deployed or started on. + - Only considered when I(state=started) or instance is running. + - Requires root admin privileges. + type: str + domain: + description: + - Domain the instance is related to. + type: str + account: + description: + - Account the instance is related to. + type: str + project: + description: + - Name of the project the instance to be deployed in. + type: str + zone: + description: + - Name of the zone in which the instance should be deployed. + - If not set, default zone is used. + type: str + ssh_key: + description: + - Name of the SSH key to be deployed on the new instance. + type: str + affinity_groups: + description: + - Affinity groups names to be applied to the new instance. + type: list + elements: str + aliases: [ affinity_group ] + user_data: + description: + - Optional data (ASCII) that can be sent to the instance upon a successful deployment. + - The data will be automatically base64 encoded. + - Consider switching to HTTP_POST by using I(CLOUDSTACK_METHOD=post) to increase the HTTP_GET size limit of 2KB to 32 KB. + type: str + force: + description: + - Force stop/start the instance if required to apply changes, otherwise a running instance will not be changed. + type: bool + default: no + allow_root_disk_shrink: + description: + - Enables a volume shrinkage when the new size is smaller than the old one. + type: bool + default: no + tags: + description: + - List of tags. Tags are a list of dictionaries having keys C(key) and C(value). + - "If you want to delete all tags, set a empty list e.g. I(tags: [])." + type: list + elements: dict + aliases: [ tag ] + poll_async: + description: + - Poll async jobs until job has finished. + type: bool + default: yes + details: + description: + - Map to specify custom parameters. + type: dict +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +# NOTE: Names of offerings and ISOs depending on the CloudStack configuration. +- name: create a instance from an ISO + ngine_io.cloudstack.cs_instance: + name: web-vm-1 + iso: Linux Debian 7 64-bit + hypervisor: VMware + project: Integration + zone: ch-zrh-ix-01 + service_offering: 1cpu_1gb + disk_offering: PerfPlus Storage + disk_size: 20 + networks: + - Server Integration + - Sync Integration + - Storage Integration + +- name: for changing a running instance, use the 'force' parameter + ngine_io.cloudstack.cs_instance: + name: web-vm-1 + display_name: web-vm-01.example.com + iso: Linux Debian 7 64-bit + service_offering: 2cpu_2gb + force: yes + +# NOTE: user_data can be used to kickstart the instance using cloud-init yaml config. +- name: create or update a instance on Exoscale's public cloud using display_name. + ngine_io.cloudstack.cs_instance: + display_name: web-vm-1 + template: Linux Debian 7 64-bit + service_offering: Tiny + ssh_key: john@example.com + tags: + - key: admin + value: john + - key: foo + value: bar + user_data: | + #cloud-config + packages: + - nginx + +- name: create an instance with multiple interfaces specifying the IP addresses + ngine_io.cloudstack.cs_instance: + name: web-vm-1 + template: Linux Debian 7 64-bit + service_offering: Tiny + ip_to_networks: + - network: NetworkA + ip: 10.1.1.1 + - network: NetworkB + ip: 192.0.2.1 + +- name: ensure an instance is stopped + ngine_io.cloudstack.cs_instance: + name: web-vm-1 + state: stopped + +- name: ensure an instance is running + ngine_io.cloudstack.cs_instance: + name: web-vm-1 + state: started + +- name: remove an instance + ngine_io.cloudstack.cs_instance: + name: web-vm-1 + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the instance. + returned: success + type: str + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 +name: + description: Name of the instance. + returned: success + type: str + sample: web-01 +display_name: + description: Display name of the instance. + returned: success + type: str + sample: web-01 +group: + description: Group name of the instance is related. + returned: success + type: str + sample: web +created: + description: Date of the instance was created. + returned: success + type: str + sample: 2014-12-01T14:57:57+0100 +password_enabled: + description: True if password setting is enabled. + returned: success + type: bool + sample: true +password: + description: The password of the instance if exists. + returned: if available + type: str + sample: Ge2oe7Do +ssh_key: + description: Name of SSH key deployed to instance. + returned: if available + type: str + sample: key@work +domain: + description: Domain the instance is related to. + returned: success + type: str + sample: example domain +account: + description: Account the instance is related to. + returned: success + type: str + sample: example account +project: + description: Name of project the instance is related to. + returned: success + type: str + sample: Production +default_ip: + description: Default IP address of the instance. + returned: success + type: str + sample: 10.23.37.42 +default_ip6: + description: Default IPv6 address of the instance. + returned: if available + type: str + sample: 2a04:c43:c00:a07:4b4:beff:fe00:74 +public_ip: + description: Public IP address with instance via static NAT rule. + returned: if available + type: str + sample: 1.2.3.4 +iso: + description: Name of ISO the instance was deployed with. + returned: if available + type: str + sample: Debian-8-64bit +template: + description: Name of template the instance was deployed with. + returned: success + type: str + sample: Linux Debian 9 64-bit +template_display_text: + description: Display text of template the instance was deployed with. + returned: success + type: str + sample: Linux Debian 9 64-bit 200G Disk (2017-10-08-622866) +service_offering: + description: Name of the service offering the instance has. + returned: success + type: str + sample: 2cpu_2gb +zone: + description: Name of zone the instance is in. + returned: success + type: str + sample: ch-gva-2 +state: + description: State of the instance. + returned: success + type: str + sample: Running +security_groups: + description: Security groups the instance is in. + returned: success + type: list + sample: '[ "default" ]' +affinity_groups: + description: Affinity groups the instance is in. + returned: success + type: list + sample: '[ "webservers" ]' +tags: + description: List of resource tags associated with the instance. + returned: success + type: list + sample: '[ { "key": "foo", "value": "bar" } ]' +hypervisor: + description: Hypervisor related to this instance. + returned: success + type: str + sample: KVM +host: + description: Hostname of hypervisor an instance is running on. + returned: success and instance is running + type: str + sample: host-01.example.com +instance_name: + description: Internal name of the instance (ROOT admin only). + returned: success + type: str + sample: i-44-3992-VM +user-data: + description: Optional data sent to the instance. + returned: success + type: str + sample: VXNlciBkYXRhIGV4YW1wbGUK +''' + +import base64 +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_bytes, to_text +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackInstance(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackInstance, self).__init__(module) + self.returns = { + 'group': 'group', + 'hypervisor': 'hypervisor', + 'instancename': 'instance_name', + 'publicip': 'public_ip', + 'passwordenabled': 'password_enabled', + 'password': 'password', + 'serviceofferingname': 'service_offering', + 'isoname': 'iso', + 'templatename': 'template', + 'templatedisplaytext': 'template_display_text', + 'keypair': 'ssh_key', + 'hostname': 'host', + } + self.instance = None + self.template = None + self.iso = None + + def get_service_offering_id(self): + service_offering = self.module.params.get('service_offering') + + service_offerings = self.query_api('listServiceOfferings') + if service_offerings: + if not service_offering: + return service_offerings['serviceoffering'][0]['id'] + + for s in service_offerings['serviceoffering']: + if service_offering in [s['name'], s['id']]: + return s['id'] + self.fail_json(msg="Service offering '%s' not found" % service_offering) + + def get_host_id(self): + host_name = self.module.params.get('host') + if not host_name: + return None + + args = { + 'type': 'routing', + 'zoneid': self.get_zone(key='id'), + } + hosts = self.query_api('listHosts', **args) + if hosts: + for h in hosts['host']: + if h['name'] == host_name: + return h['id'] + + self.fail_json(msg="Host '%s' not found" % host_name) + + def get_template_or_iso(self, key=None): + template = self.module.params.get('template') + iso = self.module.params.get('iso') + + if not template and not iso: + return None + + args = { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'zoneid': self.get_zone(key='id'), + 'isrecursive': True, + 'fetch_list': True, + } + + if template: + if self.template: + return self._get_by_key(key, self.template) + + rootdisksize = self.module.params.get('root_disk_size') + args['templatefilter'] = self.module.params.get('template_filter') + args['fetch_list'] = True + templates = self.query_api('listTemplates', **args) + if templates: + for t in templates: + if template in [t.get('displaytext', None), t['name'], t['id']]: + if rootdisksize and t['size'] > rootdisksize * 1024 ** 3: + continue + self.template = t + return self._get_by_key(key, self.template) + + if rootdisksize: + more_info = " (with size <= %s)" % rootdisksize + else: + more_info = "" + + self.module.fail_json(msg="Template '%s' not found%s" % (template, more_info)) + + elif iso: + if self.iso: + return self._get_by_key(key, self.iso) + + args['isofilter'] = self.module.params.get('template_filter') + args['fetch_list'] = True + isos = self.query_api('listIsos', **args) + if isos: + for i in isos: + if iso in [i['displaytext'], i['name'], i['id']]: + self.iso = i + return self._get_by_key(key, self.iso) + + self.module.fail_json(msg="ISO '%s' not found" % iso) + + def get_instance(self): + instance = self.instance + if not instance: + instance_name = self.get_or_fallback('name', 'display_name') + args = { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'fetch_list': True, + } + # Do not pass zoneid, as the instance name must be unique across zones. + instances = self.query_api('listVirtualMachines', **args) + if instances: + for v in instances: + if instance_name.lower() in [v['name'].lower(), v['displayname'].lower(), v['id']]: + self.instance = v + break + return self.instance + + def _get_instance_user_data(self, instance): + # Query the user data if we need to + if 'userdata' in instance: + return instance['userdata'] + + user_data = "" + if self.get_user_data() is not None and instance.get('id'): + res = self.query_api('getVirtualMachineUserData', virtualmachineid=instance['id']) + user_data = res['virtualmachineuserdata'].get('userdata', "") + return user_data + + def get_iptonetwork_mappings(self): + network_mappings = self.module.params.get('ip_to_networks') + if network_mappings is None: + return + + if network_mappings and self.module.params.get('networks'): + self.module.fail_json(msg="networks and ip_to_networks are mutually exclusive.") + + network_names = [n['network'] for n in network_mappings] + ids = self.get_network_ids(network_names) + res = [] + for i, data in enumerate(network_mappings): + res.append({'networkid': ids[i], 'ip': data['ip']}) + return res + + def get_ssh_keypair(self, key=None, name=None, fail_on_missing=True): + ssh_key_name = name or self.module.params.get('ssh_key') + if ssh_key_name is None: + return + + args = { + 'domainid': self.get_domain('id'), + 'account': self.get_account('name'), + 'projectid': self.get_project('id'), + 'name': ssh_key_name, + } + ssh_key_pairs = self.query_api('listSSHKeyPairs', **args) + if 'sshkeypair' in ssh_key_pairs: + return self._get_by_key(key=key, my_dict=ssh_key_pairs['sshkeypair'][0]) + + elif fail_on_missing: + self.module.fail_json(msg="SSH key not found: %s" % ssh_key_name) + + def ssh_key_has_changed(self): + ssh_key_name = self.module.params.get('ssh_key') + if ssh_key_name is None: + return False + + # Fails if keypair for param is inexistent + param_ssh_key_fp = self.get_ssh_keypair(key='fingerprint') + + # CloudStack 4.5 does return keypair on instance for a non existent key. + instance_ssh_key_name = self.instance.get('keypair') + if instance_ssh_key_name is None: + return True + + # Get fingerprint for keypair of instance but do not fail if inexistent. + instance_ssh_key_fp = self.get_ssh_keypair(key='fingerprint', name=instance_ssh_key_name, fail_on_missing=False) + if not instance_ssh_key_fp: + return True + + # Compare fingerprints to ensure the keypair changed + if instance_ssh_key_fp != param_ssh_key_fp: + return True + return False + + def security_groups_has_changed(self): + security_groups = self.module.params.get('security_groups') + if security_groups is None: + return False + + security_groups = [s.lower() for s in security_groups] + instance_security_groups = self.instance.get('securitygroup') or [] + + instance_security_group_names = [] + for instance_security_group in instance_security_groups: + if instance_security_group['name'].lower() not in security_groups: + return True + else: + instance_security_group_names.append(instance_security_group['name'].lower()) + + for security_group in security_groups: + if security_group not in instance_security_group_names: + return True + return False + + def get_network_ids(self, network_names=None): + if network_names is None: + network_names = self.module.params.get('networks') + + if not network_names: + return None + + args = { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'zoneid': self.get_zone(key='id'), + 'fetch_list': True, + } + networks = self.query_api('listNetworks', **args) + if not networks: + self.module.fail_json(msg="No networks available") + + network_ids = [] + network_displaytexts = [] + for network_name in network_names: + for n in networks: + if network_name in [n['displaytext'], n['name'], n['id']]: + network_ids.append(n['id']) + network_displaytexts.append(n['name']) + break + + if len(network_ids) != len(network_names): + self.module.fail_json(msg="Could not find all networks, networks list found: %s" % network_displaytexts) + + return network_ids + + def present_instance(self, start_vm=True): + instance = self.get_instance() + + if not instance: + instance = self.deploy_instance(start_vm=start_vm) + else: + instance = self.recover_instance(instance=instance) + instance = self.update_instance(instance=instance, start_vm=start_vm) + + # In check mode, we do not necessarily have an instance + if instance: + instance = self.ensure_tags(resource=instance, resource_type='UserVm') + # refresh instance data + self.instance = instance + + return instance + + def get_user_data(self): + user_data = self.module.params.get('user_data') + if user_data is not None: + user_data = to_text(base64.b64encode(to_bytes(user_data))) + return user_data + + def get_details(self): + details = self.module.params.get('details') + cpu = self.module.params.get('cpu') + cpu_speed = self.module.params.get('cpu_speed') + memory = self.module.params.get('memory') + if all([cpu, cpu_speed, memory]): + details.extends({ + 'cpuNumber': cpu, + 'cpuSpeed': cpu_speed, + 'memory': memory, + }) + + return details + + def deploy_instance(self, start_vm=True): + self.result['changed'] = True + networkids = self.get_network_ids() + if networkids is not None: + networkids = ','.join(networkids) + + args = {} + args['templateid'] = self.get_template_or_iso(key='id') + if not args['templateid']: + self.module.fail_json(msg="Template or ISO is required.") + + args['zoneid'] = self.get_zone(key='id') + args['serviceofferingid'] = self.get_service_offering_id() + args['account'] = self.get_account(key='name') + args['domainid'] = self.get_domain(key='id') + args['projectid'] = self.get_project(key='id') + args['diskofferingid'] = self.get_disk_offering(key='id') + args['networkids'] = networkids + args['iptonetworklist'] = self.get_iptonetwork_mappings() + args['userdata'] = self.get_user_data() + args['keyboard'] = self.module.params.get('keyboard') + args['ipaddress'] = self.module.params.get('ip_address') + args['ip6address'] = self.module.params.get('ip6_address') + args['name'] = self.module.params.get('name') + args['displayname'] = self.get_or_fallback('display_name', 'name') + args['group'] = self.module.params.get('group') + args['keypair'] = self.get_ssh_keypair(key='name') + args['size'] = self.module.params.get('disk_size') + args['startvm'] = start_vm + args['rootdisksize'] = self.module.params.get('root_disk_size') + args['affinitygroupnames'] = self.module.params.get('affinity_groups') + args['details'] = self.get_details() + args['securitygroupnames'] = self.module.params.get('security_groups') + args['hostid'] = self.get_host_id() + + template_iso = self.get_template_or_iso() + if 'hypervisor' not in template_iso: + args['hypervisor'] = self.get_hypervisor() + + instance = None + if not self.module.check_mode: + instance = self.query_api('deployVirtualMachine', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + instance = self.poll_job(instance, 'virtualmachine') + return instance + + def update_instance(self, instance, start_vm=True): + # Service offering data + args_service_offering = { + 'id': instance['id'], + } + if self.module.params.get('service_offering'): + args_service_offering['serviceofferingid'] = self.get_service_offering_id() + service_offering_changed = self.has_changed(args_service_offering, instance) + + # Instance data + args_instance_update = { + 'id': instance['id'], + 'userdata': self.get_user_data(), + } + instance['userdata'] = self._get_instance_user_data(instance) + args_instance_update['ostypeid'] = self.get_os_type(key='id') + if self.module.params.get('group'): + args_instance_update['group'] = self.module.params.get('group') + if self.module.params.get('display_name'): + args_instance_update['displayname'] = self.module.params.get('display_name') + instance_changed = self.has_changed(args_instance_update, instance) + + ssh_key_changed = self.ssh_key_has_changed() + + security_groups_changed = self.security_groups_has_changed() + + # Volume data + args_volume_update = {} + root_disk_size = self.module.params.get('root_disk_size') + root_disk_size_changed = False + + if root_disk_size is not None: + res = self.query_api('listVolumes', type='ROOT', virtualmachineid=instance['id']) + [volume] = res['volume'] + + size = volume['size'] >> 30 + + args_volume_update['id'] = volume['id'] + args_volume_update['size'] = root_disk_size + + shrinkok = self.module.params.get('allow_root_disk_shrink') + if shrinkok: + args_volume_update['shrinkok'] = shrinkok + + root_disk_size_changed = root_disk_size != size + + changed = [ + service_offering_changed, + instance_changed, + security_groups_changed, + ssh_key_changed, + root_disk_size_changed, + ] + + if any(changed): + force = self.module.params.get('force') + instance_state = instance['state'].lower() + if instance_state == 'stopped' or force: + self.result['changed'] = True + if not self.module.check_mode: + + # Ensure VM has stopped + instance = self.stop_instance() + instance = self.poll_job(instance, 'virtualmachine') + self.instance = instance + + # Change service offering + if service_offering_changed: + res = self.query_api('changeServiceForVirtualMachine', **args_service_offering) + instance = res['virtualmachine'] + self.instance = instance + + # Update VM + if instance_changed or security_groups_changed: + if security_groups_changed: + args_instance_update['securitygroupnames'] = ','.join(self.module.params.get('security_groups')) + res = self.query_api('updateVirtualMachine', **args_instance_update) + instance = res['virtualmachine'] + self.instance = instance + + # Reset SSH key + if ssh_key_changed: + # SSH key data + args_ssh_key = {} + args_ssh_key['id'] = instance['id'] + args_ssh_key['projectid'] = self.get_project(key='id') + args_ssh_key['keypair'] = self.module.params.get('ssh_key') + instance = self.query_api('resetSSHKeyForVirtualMachine', **args_ssh_key) + instance = self.poll_job(instance, 'virtualmachine') + self.instance = instance + + # Root disk size + if root_disk_size_changed: + async_result = self.query_api('resizeVolume', **args_volume_update) + self.poll_job(async_result, 'volume') + + # Start VM again if it was running before + if instance_state == 'running' and start_vm: + instance = self.start_instance() + else: + self.module.warn("Changes won't be applied to running instances. " + "Use force=true to allow the instance %s to be stopped/started." % instance['name']) + + # migrate to other host + host_changed = all([ + instance['state'].lower() in ['starting', 'running'], + instance.get('hostname') is not None, + self.module.params.get('host') is not None, + self.module.params.get('host') != instance.get('hostname') + ]) + if host_changed: + self.result['changed'] = True + args_host = { + 'virtualmachineid': instance['id'], + 'hostid': self.get_host_id(), + } + if not self.module.check_mode: + res = self.query_api('migrateVirtualMachine', **args_host) + instance = self.poll_job(res, 'virtualmachine') + + return instance + + def recover_instance(self, instance): + if instance['state'].lower() in ['destroying', 'destroyed']: + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('recoverVirtualMachine', id=instance['id']) + instance = res['virtualmachine'] + return instance + + def absent_instance(self): + instance = self.get_instance() + if instance: + if instance['state'].lower() not in ['expunging', 'destroying', 'destroyed']: + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('destroyVirtualMachine', id=instance['id']) + + poll_async = self.module.params.get('poll_async') + if poll_async: + instance = self.poll_job(res, 'virtualmachine') + return instance + + def expunge_instance(self): + instance = self.get_instance() + if instance: + res = {} + if instance['state'].lower() in ['destroying', 'destroyed']: + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('destroyVirtualMachine', id=instance['id'], expunge=True) + + elif instance['state'].lower() not in ['expunging']: + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('destroyVirtualMachine', id=instance['id'], expunge=True) + + poll_async = self.module.params.get('poll_async') + if poll_async: + res = self.poll_job(res, 'virtualmachine') + return instance + + def stop_instance(self): + instance = self.get_instance() + # in check mode instance may not be instantiated + if instance: + if instance['state'].lower() in ['stopping', 'stopped']: + return instance + + if instance['state'].lower() in ['starting', 'running']: + self.result['changed'] = True + if not self.module.check_mode: + instance = self.query_api('stopVirtualMachine', id=instance['id']) + + poll_async = self.module.params.get('poll_async') + if poll_async: + instance = self.poll_job(instance, 'virtualmachine') + return instance + + def start_instance(self): + instance = self.get_instance() + # in check mode instance may not be instantiated + if instance: + if instance['state'].lower() in ['starting', 'running']: + return instance + + if instance['state'].lower() in ['stopped', 'stopping']: + self.result['changed'] = True + if not self.module.check_mode: + args = { + 'id': instance['id'], + 'hostid': self.get_host_id(), + } + instance = self.query_api('startVirtualMachine', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + instance = self.poll_job(instance, 'virtualmachine') + return instance + + def restart_instance(self): + instance = self.get_instance() + # in check mode instance may not be instantiated + if instance: + if instance['state'].lower() in ['running', 'starting']: + self.result['changed'] = True + if not self.module.check_mode: + instance = self.query_api('rebootVirtualMachine', id=instance['id']) + + poll_async = self.module.params.get('poll_async') + if poll_async: + instance = self.poll_job(instance, 'virtualmachine') + + elif instance['state'].lower() in ['stopping', 'stopped']: + instance = self.start_instance() + return instance + + def restore_instance(self): + instance = self.get_instance() + self.result['changed'] = True + # in check mode instance may not be instantiated + if instance: + args = {} + args['templateid'] = self.get_template_or_iso(key='id') + args['virtualmachineid'] = instance['id'] + res = self.query_api('restoreVirtualMachine', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + instance = self.poll_job(res, 'virtualmachine') + return instance + + def get_result(self, instance): + super(AnsibleCloudStackInstance, self).get_result(instance) + if instance: + self.result['user_data'] = self._get_instance_user_data(instance) + if 'securitygroup' in instance: + security_groups = [] + for securitygroup in instance['securitygroup']: + security_groups.append(securitygroup['name']) + self.result['security_groups'] = security_groups + if 'affinitygroup' in instance: + affinity_groups = [] + for affinitygroup in instance['affinitygroup']: + affinity_groups.append(affinitygroup['name']) + self.result['affinity_groups'] = affinity_groups + if 'nic' in instance: + for nic in instance['nic']: + if nic['isdefault']: + if 'ipaddress' in nic: + self.result['default_ip'] = nic['ipaddress'] + if 'ip6address' in nic: + self.result['default_ip6'] = nic['ip6address'] + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(), + display_name=dict(), + group=dict(), + state=dict(choices=['present', 'deployed', 'started', 'stopped', 'restarted', 'restored', 'absent', 'destroyed', 'expunged'], default='present'), + service_offering=dict(), + cpu=dict(type='int'), + cpu_speed=dict(type='int'), + memory=dict(type='int'), + template=dict(), + iso=dict(), + template_filter=dict( + default="executable", + aliases=['iso_filter'], + choices=['all', 'featured', 'self', 'selfexecutable', 'sharedexecutable', 'executable', 'community'] + ), + networks=dict(type='list', elements='str', aliases=['network']), + ip_to_networks=dict(type='list', elements='dict', aliases=['ip_to_network']), + ip_address=dict(), + ip6_address=dict(), + disk_offering=dict(), + disk_size=dict(type='int'), + root_disk_size=dict(type='int'), + keyboard=dict(type='str', choices=['de', 'de-ch', 'es', 'fi', 'fr', 'fr-be', 'fr-ch', 'is', 'it', 'jp', 'nl-be', 'no', 'pt', 'uk', 'us']), + hypervisor=dict(), + host=dict(), + security_groups=dict(type='list', elements='str', aliases=['security_group']), + affinity_groups=dict(type='list', elements='str', aliases=['affinity_group']), + domain=dict(), + account=dict(), + project=dict(), + user_data=dict(), + zone=dict(), + ssh_key=dict(), + force=dict(type='bool', default=False), + tags=dict(type='list', elements='dict', aliases=['tag']), + details=dict(type='dict'), + poll_async=dict(type='bool', default=True), + allow_root_disk_shrink=dict(type='bool', default=False), + )) + + required_together = cs_required_together() + required_together.extend([ + ['cpu', 'cpu_speed', 'memory'], + ]) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=required_together, + required_one_of=( + ['display_name', 'name'], + ), + mutually_exclusive=( + ['template', 'iso'], + ), + supports_check_mode=True + ) + + acs_instance = AnsibleCloudStackInstance(module) + + state = module.params.get('state') + + if state in ['absent', 'destroyed']: + instance = acs_instance.absent_instance() + + elif state in ['expunged']: + instance = acs_instance.expunge_instance() + + elif state in ['restored']: + acs_instance.present_instance() + instance = acs_instance.restore_instance() + + elif state in ['present', 'deployed']: + instance = acs_instance.present_instance() + + elif state in ['stopped']: + acs_instance.present_instance(start_vm=False) + instance = acs_instance.stop_instance() + + elif state in ['started']: + acs_instance.present_instance() + instance = acs_instance.start_instance() + + elif state in ['restarted']: + acs_instance.present_instance() + instance = acs_instance.restart_instance() + + if instance and 'state' in instance and instance['state'].lower() == 'error': + module.fail_json(msg="Instance named '%s' in error state." % module.params.get('name')) + + result = acs_instance.get_result(instance) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_instance_info.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_instance_info.py new file mode 100644 index 00000000..5b111bec --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_instance_info.py @@ -0,0 +1,370 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_instance_info +short_description: Gathering information from the API of instances from Apache CloudStack based clouds. +description: + - Gathering information from the API of an instance. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name or display name of the instance. + - If not specified, all instances are returned + type: str + required: false + domain: + description: + - Domain the instance is related to. + type: str + account: + description: + - Account the instance is related to. + type: str + project: + description: + - Project the instance is related to. + type: str +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Gather instance information + ngine_io.cloudstack.cs_instance_info: + name: web-vm-1 + register: vm + +- name: Show the returned results of the registered variable + debug: + msg: "{{ vm }}" + +- name: Gather information from all instances + ngine_io.cloudstack.cs_instance_info: + register: vms + +- name: Show information on all instances + debug: + msg: "{{ vms }}" +''' + +RETURN = ''' +--- +instances: + description: A list of matching instances. + type: list + returned: success + contains: + id: + description: UUID of the instance. + returned: success + type: str + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 + name: + description: Name of the instance. + returned: success + type: str + sample: web-01 + display_name: + description: Display name of the instance. + returned: success + type: str + sample: web-01 + group: + description: Group name of the instance is related. + returned: success + type: str + sample: web + created: + description: Date of the instance was created. + returned: success + type: str + sample: 2014-12-01T14:57:57+0100 + password_enabled: + description: True if password setting is enabled. + returned: success + type: bool + sample: true + password: + description: The password of the instance if exists. + returned: success + type: str + sample: Ge2oe7Do + ssh_key: + description: Name of SSH key deployed to instance. + returned: success + type: str + sample: key@work + domain: + description: Domain the instance is related to. + returned: success + type: str + sample: example domain + account: + description: Account the instance is related to. + returned: success + type: str + sample: example account + project: + description: Name of project the instance is related to. + returned: success + type: str + sample: Production + default_ip: + description: Default IP address of the instance. + returned: success + type: str + sample: 10.23.37.42 + public_ip: + description: Public IP address with instance via static NAT rule. + returned: success + type: str + sample: 1.2.3.4 + iso: + description: Name of ISO the instance was deployed with. + returned: success + type: str + sample: Debian-8-64bit + template: + description: Name of template the instance was deployed with. + returned: success + type: str + sample: Debian-8-64bit + service_offering: + description: Name of the service offering the instance has. + returned: success + type: str + sample: 2cpu_2gb + zone: + description: Name of zone the instance is in. + returned: success + type: str + sample: ch-gva-2 + state: + description: State of the instance. + returned: success + type: str + sample: Running + security_groups: + description: Security groups the instance is in. + returned: success + type: list + sample: '[ "default" ]' + affinity_groups: + description: Affinity groups the instance is in. + returned: success + type: list + sample: '[ "webservers" ]' + tags: + description: List of resource tags associated with the instance. + returned: success + type: list + sample: '[ { "key": "foo", "value": "bar" } ]' + hypervisor: + description: Hypervisor related to this instance. + returned: success + type: str + sample: KVM + host: + description: Host the instance is running on. + returned: success and instance is running + type: str + sample: host01.example.com + instance_name: + description: Internal name of the instance (ROOT admin only). + returned: success + type: str + sample: i-44-3992-VM + volumes: + description: List of dictionaries of the volumes attached to the instance. + returned: success + type: list + sample: '[ { name: "ROOT-1369", type: "ROOT", size: 10737418240 }, { name: "data01, type: "DATADISK", size: 10737418240 } ]' + nic: + description: List of dictionaries of the instance nics. + returned: success + type: complex + contains: + broadcasturi: + description: The broadcast uri of the nic. + returned: success + type: str + sample: vlan://2250 + gateway: + description: The gateway of the nic. + returned: success + type: str + sample: 10.1.2.1 + id: + description: The ID of the nic. + returned: success + type: str + sample: 5dc74fa3-2ec3-48a0-9e0d-6f43365336a9 + ipaddress: + description: The ip address of the nic. + returned: success + type: str + sample: 10.1.2.3 + isdefault: + description: True if nic is default, false otherwise. + returned: success + type: bool + sample: true + isolationuri: + description: The isolation uri of the nic. + returned: success + type: str + sample: vlan://2250 + macaddress: + description: The mac address of the nic. + returned: success + type: str + sample: 06:a2:03:00:08:12 + netmask: + description: The netmask of the nic. + returned: success + type: str + sample: 255.255.255.0 + networkid: + description: The ID of the corresponding network. + returned: success + type: str + sample: 432ce27b-c2bb-4e12-a88c-a919cd3a3017 + networkname: + description: The name of the corresponding network. + returned: success + type: str + sample: network1 + traffictype: + description: The traffic type of the nic. + returned: success + type: str + sample: Guest + type: + description: The type of the network. + returned: success + type: str + sample: Shared +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import AnsibleCloudStack, cs_argument_spec + + +class AnsibleCloudStackInstanceInfo(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackInstanceInfo, self).__init__(module) + self.returns = { + 'group': 'group', + 'hypervisor': 'hypervisor', + 'instancename': 'instance_name', + 'publicip': 'public_ip', + 'passwordenabled': 'password_enabled', + 'password': 'password', + 'serviceofferingname': 'service_offering', + 'isoname': 'iso', + 'templatename': 'template', + 'keypair': 'ssh_key', + 'hostname': 'host', + } + + def get_instances(self): + instance_name = self.module.params.get('name') + + args = { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'fetch_list': True, + } + # Do not pass zoneid, as the instance name must be unique across zones. + instances = self.query_api('listVirtualMachines', **args) + if not instance_name: + return instances or [] + if instances: + for v in instances: + if instance_name.lower() in [v['name'].lower(), v['displayname'].lower(), v['id']]: + return [v] + return [] + + def get_volumes(self, instance): + volume_details = [] + if instance: + args = { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'virtualmachineid': instance['id'], + 'fetch_list': True, + } + + volumes = self.query_api('listVolumes', **args) + if volumes: + for vol in volumes: + volume_details.append({'size': vol['size'], 'type': vol['type'], 'name': vol['name']}) + return volume_details + + def run(self): + instances = self.get_instances() + if self.module.params.get('name') and not instances: + self.module.fail_json(msg="Instance not found: %s" % self.module.params.get('name')) + return { + 'instances': [self.update_result(resource) for resource in instances] + } + + def update_result(self, instance, result=None): + result = super(AnsibleCloudStackInstanceInfo, self).update_result(instance, result) + if instance: + if 'securitygroup' in instance: + security_groups = [] + for securitygroup in instance['securitygroup']: + security_groups.append(securitygroup['name']) + result['security_groups'] = security_groups + if 'affinitygroup' in instance: + affinity_groups = [] + for affinitygroup in instance['affinitygroup']: + affinity_groups.append(affinitygroup['name']) + result['affinity_groups'] = affinity_groups + if 'nic' in instance: + for nic in instance['nic']: + if nic['isdefault'] and 'ipaddress' in nic: + result['default_ip'] = nic['ipaddress'] + result['nic'] = instance['nic'] + volumes = self.get_volumes(instance) + if volumes: + result['volumes'] = volumes + return result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(), + domain=dict(), + account=dict(), + project=dict(), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + acs_instance_info = AnsibleCloudStackInstanceInfo(module=module) + cs_instance_info = acs_instance_info.run() + module.exit_json(**cs_instance_info) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_instance_nic.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_instance_nic.py new file mode 100644 index 00000000..11ea45fc --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_instance_nic.py @@ -0,0 +1,285 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017, Marc-Aurèle Brothier @marcaurele +# Copyright (c) 2017, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_instance_nic +short_description: Manages NICs of an instance on Apache CloudStack based clouds. +description: + - Add and remove nic to and from network +author: + - Marc-Aurèle Brothier (@marcaurele) + - René Moser (@resmo) +version_added: 0.1.0 +options: + vm: + description: + - Name of instance. + required: true + type: str + aliases: [ name ] + network: + description: + - Name of the network. + type: str + required: true + ip_address: + description: + - IP address to be used for the nic. + type: str + vpc: + description: + - Name of the VPC the I(vm) is related to. + type: str + domain: + description: + - Domain the instance is related to. + type: str + account: + description: + - Account the instance is related to. + type: str + project: + description: + - Name of the project the instance is deployed in. + type: str + zone: + description: + - Name of the zone in which the instance is deployed in. + - If not set, default zone is used. + type: str + state: + description: + - State of the nic. + type: str + default: present + choices: [ present, absent ] + poll_async: + description: + - Poll async jobs until job has finished. + type: bool + default: yes +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Add a nic on another network + ngine_io.cloudstack.cs_instance_nic: + vm: privnet + network: privNetForBasicZone + +- name: Ensure IP address on a nic + ngine_io.cloudstack.cs_instance_nic: + vm: privnet + ip_address: 10.10.11.32 + network: privNetForBasicZone + +- name: Remove a secondary nic + ngine_io.cloudstack.cs_instance_nic: + vm: privnet + state: absent + network: privNetForBasicZone +''' + +RETURN = ''' +--- +id: + description: UUID of the nic. + returned: success + type: str + sample: 87b1e0ce-4e01-11e4-bb66-0050569e64b8 +vm: + description: Name of the VM. + returned: success + type: str + sample: web-01 +ip_address: + description: Primary IP of the NIC. + returned: success + type: str + sample: 10.10.10.10 +netmask: + description: Netmask of the NIC. + returned: success + type: str + sample: 255.255.255.0 +mac_address: + description: MAC address of the NIC. + returned: success + type: str + sample: 02:00:33:31:00:e4 +network: + description: Name of the network if not default. + returned: success + type: str + sample: sync network +domain: + description: Domain the VM is related to. + returned: success + type: str + sample: example domain +account: + description: Account the VM is related to. + returned: success + type: str + sample: example account +project: + description: Name of project the VM is related to. + returned: success + type: str + sample: Production +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackInstanceNic(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackInstanceNic, self).__init__(module) + self.nic = None + self.returns = { + 'ipaddress': 'ip_address', + 'macaddress': 'mac_address', + 'netmask': 'netmask', + } + + def get_nic(self): + if self.nic: + return self.nic + args = { + 'virtualmachineid': self.get_vm(key='id'), + 'networkid': self.get_network(key='id'), + } + nics = self.query_api('listNics', **args) + if nics: + self.nic = nics['nic'][0] + return self.nic + return None + + def get_nic_from_result(self, result): + for nic in result.get('nic') or []: + if nic['networkid'] == self.get_network(key='id'): + return nic + + def add_nic(self): + self.result['changed'] = True + args = { + 'virtualmachineid': self.get_vm(key='id'), + 'networkid': self.get_network(key='id'), + 'ipaddress': self.module.params.get('ip_address'), + } + if not self.module.check_mode: + res = self.query_api('addNicToVirtualMachine', **args) + + if self.module.params.get('poll_async'): + vm = self.poll_job(res, 'virtualmachine') + self.nic = self.get_nic_from_result(result=vm) + return self.nic + + def update_nic(self, nic): + # Do not try to update if no IP address is given + ip_address = self.module.params.get('ip_address') + if not ip_address: + return nic + + args = { + 'nicid': nic['id'], + 'ipaddress': ip_address, + } + if self.has_changed(args, nic, ['ipaddress']): + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('updateVmNicIp', **args) + + if self.module.params.get('poll_async'): + vm = self.poll_job(res, 'virtualmachine') + self.nic = self.get_nic_from_result(result=vm) + return self.nic + + def remove_nic(self, nic): + self.result['changed'] = True + args = { + 'virtualmachineid': self.get_vm(key='id'), + 'nicid': nic['id'], + } + if not self.module.check_mode: + res = self.query_api('removeNicFromVirtualMachine', **args) + + if self.module.params.get('poll_async'): + self.poll_job(res, 'virtualmachine') + return nic + + def present_nic(self): + nic = self.get_nic() + if not nic: + nic = self.add_nic() + else: + nic = self.update_nic(nic) + return nic + + def absent_nic(self): + nic = self.get_nic() + if nic: + return self.remove_nic(nic) + return nic + + def get_result(self, nic): + super(AnsibleCloudStackInstanceNic, self).get_result(nic) + if nic and not self.module.params.get('network'): + self.module.params['network'] = nic.get('networkid') + self.result['network'] = self.get_network(key='name') + self.result['vm'] = self.get_vm(key='name') + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + vm=dict(required=True, aliases=['name']), + network=dict(required=True), + vpc=dict(), + ip_address=dict(), + state=dict(choices=['present', 'absent'], default='present'), + domain=dict(), + account=dict(), + project=dict(), + zone=dict(), + poll_async=dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True, + ) + + acs_nic = AnsibleCloudStackInstanceNic(module) + + state = module.params.get('state') + if state == 'absent': + nic = acs_nic.absent_nic() + else: + nic = acs_nic.present_nic() + + result = acs_nic.get_result(nic) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_instance_nic_secondaryip.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_instance_nic_secondaryip.py new file mode 100644 index 00000000..7dabe7d7 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_instance_nic_secondaryip.py @@ -0,0 +1,267 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_instance_nic_secondaryip +short_description: Manages secondary IPs of an instance on Apache CloudStack based clouds. +description: + - Add and remove secondary IPs to and from a NIC of an instance. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + vm: + description: + - Name of instance. + type: str + required: true + aliases: [ name ] + network: + description: + - Name of the network. + - Required to find the NIC if instance has multiple networks assigned. + type: str + vm_guest_ip: + description: + - Secondary IP address to be added to the instance nic. + - If not set, the API always returns a new IP address and idempotency is not given. + type: str + aliases: [ secondary_ip ] + vpc: + description: + - Name of the VPC the I(vm) is related to. + type: str + domain: + description: + - Domain the instance is related to. + type: str + account: + description: + - Account the instance is related to. + type: str + project: + description: + - Name of the project the instance is deployed in. + type: str + zone: + description: + - Name of the zone in which the instance is deployed in. + - If not set, default zone is used. + type: str + state: + description: + - State of the ipaddress. + type: str + default: present + choices: [ present, absent ] + poll_async: + description: + - Poll async jobs until job has finished. + type: bool + default: yes +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack + +''' + +EXAMPLES = ''' +- name: Assign a specific IP to the default NIC of the VM + ngine_io.cloudstack.cs_instance_nic_secondaryip: + vm: customer_xy + vm_guest_ip: 10.10.10.10 + +# Note: If vm_guest_ip is not set, you will get a new IP address on every run. +- name: Assign an IP to the default NIC of the VM + ngine_io.cloudstack.cs_instance_nic_secondaryip: + vm: customer_xy + +- name: Remove a specific IP from the default NIC + ngine_io.cloudstack.cs_instance_nic_secondaryip: + vm: customer_xy + vm_guest_ip: 10.10.10.10 + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the NIC. + returned: success + type: str + sample: 87b1e0ce-4e01-11e4-bb66-0050569e64b8 +vm: + description: Name of the VM. + returned: success + type: str + sample: web-01 +ip_address: + description: Primary IP of the NIC. + returned: success + type: str + sample: 10.10.10.10 +netmask: + description: Netmask of the NIC. + returned: success + type: str + sample: 255.255.255.0 +mac_address: + description: MAC address of the NIC. + returned: success + type: str + sample: 02:00:33:31:00:e4 +vm_guest_ip: + description: Secondary IP of the NIC. + returned: success + type: str + sample: 10.10.10.10 +network: + description: Name of the network if not default. + returned: success + type: str + sample: sync network +domain: + description: Domain the VM is related to. + returned: success + type: str + sample: example domain +account: + description: Account the VM is related to. + returned: success + type: str + sample: example account +project: + description: Name of project the VM is related to. + returned: success + type: str + sample: Production +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackInstanceNicSecondaryIp(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackInstanceNicSecondaryIp, self).__init__(module) + self.vm_guest_ip = self.module.params.get('vm_guest_ip') + self.nic = None + self.returns = { + 'ipaddress': 'ip_address', + 'macaddress': 'mac_address', + 'netmask': 'netmask', + } + + def get_nic(self): + if self.nic: + return self.nic + args = { + 'virtualmachineid': self.get_vm(key='id'), + 'networkid': self.get_network(key='id'), + } + nics = self.query_api('listNics', **args) + if nics: + self.nic = nics['nic'][0] + return self.nic + self.fail_json(msg="NIC for VM %s in network %s not found" % (self.get_vm(key='name'), self.get_network(key='name'))) + + def get_secondary_ip(self): + nic = self.get_nic() + if self.vm_guest_ip: + secondary_ips = nic.get('secondaryip') or [] + for secondary_ip in secondary_ips: + if secondary_ip['ipaddress'] == self.vm_guest_ip: + return secondary_ip + return None + + def present_nic_ip(self): + nic = self.get_nic() + if not self.get_secondary_ip(): + self.result['changed'] = True + args = { + 'nicid': nic['id'], + 'ipaddress': self.vm_guest_ip, + } + + if not self.module.check_mode: + res = self.query_api('addIpToNic', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + nic = self.poll_job(res, 'nicsecondaryip') + # Save result for RETURNS + self.vm_guest_ip = nic['ipaddress'] + return nic + + def absent_nic_ip(self): + nic = self.get_nic() + secondary_ip = self.get_secondary_ip() + if secondary_ip: + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('removeIpFromNic', id=secondary_ip['id']) + + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(res, 'nicsecondaryip') + return nic + + def get_result(self, nic): + super(AnsibleCloudStackInstanceNicSecondaryIp, self).get_result(nic) + if nic and not self.module.params.get('network'): + self.module.params['network'] = nic.get('networkid') + self.result['network'] = self.get_network(key='name') + self.result['vm'] = self.get_vm(key='name') + self.result['vm_guest_ip'] = self.vm_guest_ip + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + vm=dict(required=True, aliases=['name']), + vm_guest_ip=dict(aliases=['secondary_ip']), + network=dict(), + vpc=dict(), + state=dict(choices=['present', 'absent'], default='present'), + domain=dict(), + account=dict(), + project=dict(), + zone=dict(), + poll_async=dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True, + required_if=([ + ('state', 'absent', ['vm_guest_ip']) + ]) + ) + + acs_instance_nic_secondaryip = AnsibleCloudStackInstanceNicSecondaryIp(module) + state = module.params.get('state') + + if state == 'absent': + nic = acs_instance_nic_secondaryip.absent_nic_ip() + else: + nic = acs_instance_nic_secondaryip.present_nic_ip() + + result = acs_instance_nic_secondaryip.get_result(nic) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_instance_password_reset.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_instance_password_reset.py new file mode 100644 index 00000000..58b9b741 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_instance_password_reset.py @@ -0,0 +1,151 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2018, Gregor Riepl <onitake@gmail.com> +# based on cs_sshkeypair (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_instance_password_reset +short_description: Allows resetting VM the default passwords on Apache CloudStack based clouds. +description: + - Resets the default user account's password on an instance. + - Requires cloud-init to be installed in the virtual machine. + - The passwordenabled flag must be set on the template associated with the VM. +author: Gregor Riepl (@onitake) +version_added: 0.1.0 +options: + vm: + description: + - Name of the virtual machine to reset the password on. + type: str + required: true + domain: + description: + - Name of the domain the virtual machine belongs to. + type: str + account: + description: + - Account the virtual machine belongs to. + type: str + project: + description: + - Name of the project the virtual machine belongs to. + type: str + zone: + description: + - Name of the zone in which the instance is deployed. + - If not set, the default zone is used. + type: str + poll_async: + description: + - Poll async jobs until job has finished. + type: bool + default: yes +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: stop the virtual machine before resetting the password + ngine_io.cloudstack.cs_instance: + name: myvirtualmachine + state: stopped + +- name: reset and get new default password + ngine_io.cloudstack.cs_instance_password_reset: + vm: myvirtualmachine + register: root + +- debug: + msg: "new default password is {{ root.password }}" + +- name: boot the virtual machine to activate the new password + ngine_io.cloudstack.cs_instance: + name: myvirtualmachine + state: started + when: root is changed +''' + +RETURN = ''' +--- +id: + description: ID of the virtual machine. + returned: success + type: str + sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f +password: + description: The new default password. + returned: success + type: str + sample: ahQu5nuNge3keesh +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_required_together, + cs_argument_spec +) + + +class AnsibleCloudStackPasswordReset(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackPasswordReset, self).__init__(module) + self.returns = { + 'password': 'password', + } + self.password = None + + def reset_password(self): + args = { + 'id': self.get_vm(key='id'), + } + + res = None + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('resetPasswordForVirtualMachine', **args) + + poll_async = self.module.params.get('poll_async') + if res and poll_async: + res = self.poll_job(res, 'virtualmachine') + + if res and 'password' in res: + self.password = res['password'] + + return self.password + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + vm=dict(required=True), + domain=dict(), + account=dict(), + project=dict(), + zone=dict(), + poll_async=dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_password = AnsibleCloudStackPasswordReset(module) + password = acs_password.reset_password() + result = acs_password.get_result({'password': password}) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_instancegroup.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_instancegroup.py new file mode 100644 index 00000000..56783b2a --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_instancegroup.py @@ -0,0 +1,181 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_instancegroup +short_description: Manages instance groups on Apache CloudStack based clouds. +description: + - Create and remove instance groups. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name of the instance group. + type: str + required: true + domain: + description: + - Domain the instance group is related to. + type: str + account: + description: + - Account the instance group is related to. + type: str + project: + description: + - Project the instance group is related to. + type: str + state: + description: + - State of the instance group. + type: str + default: present + choices: [ present, absent ] +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Create an instance group + ngine_io.cloudstack.cs_instancegroup: + name: loadbalancers + +- name: Remove an instance group + ngine_io.cloudstack.cs_instancegroup: + name: loadbalancers + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the instance group. + returned: success + type: str + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 +name: + description: Name of the instance group. + returned: success + type: str + sample: webservers +created: + description: Date when the instance group was created. + returned: success + type: str + sample: 2015-05-03T15:05:51+0200 +domain: + description: Domain the instance group is related to. + returned: success + type: str + sample: example domain +account: + description: Account the instance group is related to. + returned: success + type: str + sample: example account +project: + description: Project the instance group is related to. + returned: success + type: str + sample: example project +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackInstanceGroup(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackInstanceGroup, self).__init__(module) + self.instance_group = None + + def get_instance_group(self): + if self.instance_group: + return self.instance_group + + name = self.module.params.get('name') + + args = { + 'account': self.get_account('name'), + 'domainid': self.get_domain('id'), + 'projectid': self.get_project('id'), + 'fetch_list': True, + } + instance_groups = self.query_api('listInstanceGroups', **args) + if instance_groups: + for g in instance_groups: + if name in [g['name'], g['id']]: + self.instance_group = g + break + return self.instance_group + + def present_instance_group(self): + instance_group = self.get_instance_group() + if not instance_group: + self.result['changed'] = True + + args = { + 'name': self.module.params.get('name'), + 'account': self.get_account('name'), + 'domainid': self.get_domain('id'), + 'projectid': self.get_project('id'), + } + if not self.module.check_mode: + res = self.query_api('createInstanceGroup', **args) + instance_group = res['instancegroup'] + return instance_group + + def absent_instance_group(self): + instance_group = self.get_instance_group() + if instance_group: + self.result['changed'] = True + if not self.module.check_mode: + self.query_api('deleteInstanceGroup', id=instance_group['id']) + return instance_group + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + state=dict(default='present', choices=['present', 'absent']), + domain=dict(), + account=dict(), + project=dict(), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_ig = AnsibleCloudStackInstanceGroup(module) + + state = module.params.get('state') + if state in ['absent']: + instance_group = acs_ig.absent_instance_group() + else: + instance_group = acs_ig.present_instance_group() + + result = acs_ig.get_result(instance_group) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_ip_address.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_ip_address.py new file mode 100644 index 00000000..04236d3d --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_ip_address.py @@ -0,0 +1,279 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, Darren Worrall <darren@iweb.co.uk> +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_ip_address +short_description: Manages public IP address associations on Apache CloudStack based clouds. +description: + - Acquires and associates a public IP to an account or project. + - Due to API limitations this is not an idempotent call, so be sure to only + conditionally call this when I(state=present). + - Tagging the IP address can also make the call idempotent. +author: + - Darren Worrall (@dazworrall) + - René Moser (@resmo) +version_added: 0.1.0 +options: + ip_address: + description: + - Public IP address. + - Required if I(state=absent) and I(tags) is not set. + type: str + domain: + description: + - Domain the IP address is related to. + type: str + network: + description: + - Network the IP address is related to. + - Mutually exclusive with I(vpc). + type: str + vpc: + description: + - VPC the IP address is related to. + - Mutually exclusive with I(network). + type: str + account: + description: + - Account the IP address is related to. + type: str + project: + description: + - Name of the project the IP address is related to. + type: str + zone: + description: + - Name of the zone in which the IP address is in. + - If not set, default zone is used. + type: str + state: + description: + - State of the IP address. + type: str + default: present + choices: [ present, absent ] + tags: + description: + - List of tags. Tags are a list of dictionaries having keys I(key) and I(value). + - Tags can be used as an unique identifier for the IP Addresses. + - In this case, at least one of them must be unique to ensure idempotency. + type: list + elements: dict + aliases: [ tag ] + poll_async: + description: + - Poll async jobs until job has finished. + type: bool + default: yes +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Associate an IP address conditionally + ngine_io.cloudstack.cs_ip_address: + network: My Network + register: ip_address + when: instance.public_ip is undefined + +- name: Disassociate an IP address + ngine_io.cloudstack.cs_ip_address: + ip_address: 1.2.3.4 + state: absent + +- name: Associate an IP address with tags + ngine_io.cloudstack.cs_ip_address: + network: My Network + tags: + - key: myCustomID + value: 5510c31a-416e-11e8-9013-02000a6b00bf + register: ip_address + +- name: Disassociate an IP address with tags + ngine_io.cloudstack.cs_ip_address: + state: absent + tags: + - key: myCustomID + value: 5510c31a-416e-11e8-9013-02000a6b00bf +''' + +RETURN = ''' +--- +id: + description: UUID of the Public IP address. + returned: success + type: str + sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f +ip_address: + description: Public IP address. + returned: success + type: str + sample: 1.2.3.4 +zone: + description: Name of zone the IP address is related to. + returned: success + type: str + sample: ch-gva-2 +project: + description: Name of project the IP address is related to. + returned: success + type: str + sample: Production +account: + description: Account the IP address is related to. + returned: success + type: str + sample: example account +domain: + description: Domain the IP address is related to. + returned: success + type: str + sample: example domain +tags: + description: List of resource tags associated with the IP address. + returned: success + type: dict + sample: '[ { "key": "myCustomID", "value": "5510c31a-416e-11e8-9013-02000a6b00bf" } ]' +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackIPAddress(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackIPAddress, self).__init__(module) + self.returns = { + 'ipaddress': 'ip_address', + } + + def get_ip_address(self, key=None): + if self.ip_address: + return self._get_by_key(key, self.ip_address) + args = { + 'ipaddress': self.module.params.get('ip_address'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'vpcid': self.get_vpc(key='id'), + } + ip_addresses = self.query_api('listPublicIpAddresses', **args) + + if ip_addresses: + tags = self.module.params.get('tags') + for ip_addr in ip_addresses['publicipaddress']: + if ip_addr['ipaddress'] == args['ipaddress'] != '': + self.ip_address = ip_addresses['publicipaddress'][0] + elif tags: + if sorted([tag for tag in tags if tag in ip_addr['tags']]) == sorted(tags): + self.ip_address = ip_addr + return self._get_by_key(key, self.ip_address) + + def present_ip_address(self): + ip_address = self.get_ip_address() + + if not ip_address: + ip_address = self.associate_ip_address(ip_address) + + if ip_address: + ip_address = self.ensure_tags(resource=ip_address, resource_type='publicipaddress') + + return ip_address + + def associate_ip_address(self, ip_address): + self.result['changed'] = True + args = { + # ipaddress only works with CloudStack >=v4.13 + 'ipaddress': self.module.params.get('ip_address'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + # For the VPC case networkid is irrelevant, special case and we have to ignore it here. + 'networkid': self.get_network(key='id') if not self.module.params.get('vpc') else None, + 'zoneid': self.get_zone(key='id'), + 'vpcid': self.get_vpc(key='id'), + } + ip_address = None + if not self.module.check_mode: + res = self.query_api('associateIpAddress', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + ip_address = self.poll_job(res, 'ipaddress') + return ip_address + + def disassociate_ip_address(self): + ip_address = self.get_ip_address() + if not ip_address: + return None + if ip_address['isstaticnat']: + self.module.fail_json(msg="IP address is allocated via static nat") + + self.result['changed'] = True + if not self.module.check_mode: + self.module.params['tags'] = [] + ip_address = self.ensure_tags(resource=ip_address, resource_type='publicipaddress') + + res = self.query_api('disassociateIpAddress', id=ip_address['id']) + + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(res, 'ipaddress') + return ip_address + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + ip_address=dict(required=False), + state=dict(choices=['present', 'absent'], default='present'), + vpc=dict(), + network=dict(), + zone=dict(), + domain=dict(), + account=dict(), + project=dict(), + tags=dict(type='list', elements='dict', aliases=['tag']), + poll_async=dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + required_if=[ + ('state', 'absent', ['ip_address', 'tags'], True), + ], + mutually_exclusive=( + ['vpc', 'network'], + ), + supports_check_mode=True + ) + + acs_ip_address = AnsibleCloudStackIPAddress(module) + + state = module.params.get('state') + if state in ['absent']: + ip_address = acs_ip_address.disassociate_ip_address() + else: + ip_address = acs_ip_address.present_ip_address() + + result = acs_ip_address.get_result(ip_address) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_iso.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_iso.py new file mode 100644 index 00000000..cecca5eb --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_iso.py @@ -0,0 +1,436 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_iso +short_description: Manages ISO images on Apache CloudStack based clouds. +description: + - Register and remove ISO images. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name of the ISO. + type: str + required: true + display_text: + description: + - Display text of the ISO. + - If not specified, I(name) will be used. + type: str + url: + description: + - URL where the ISO can be downloaded from. Required if I(state) is present. + type: str + os_type: + description: + - Name of the OS that best represents the OS of this ISO. If the iso is bootable this parameter needs to be passed. Required if I(state) is present. + type: str + is_ready: + description: + - This flag is used for searching existing ISOs. If set to C(yes), it will only list ISO ready for deployment e.g. + successfully downloaded and installed. Recommended to set it to C(no). + type: bool + default: no + is_public: + description: + - Register the ISO to be publicly available to all users. Only used if I(state) is present. + type: bool + is_featured: + description: + - Register the ISO to be featured. Only used if I(state) is present. + type: bool + is_dynamically_scalable: + description: + - Register the ISO having XS/VMware tools installed inorder to support dynamic scaling of VM cpu/memory. Only used if I(state) is present. + type: bool + checksum: + description: + - The MD5 checksum value of this ISO. If set, we search by checksum instead of name. + type: str + bootable: + description: + - Register the ISO to be bootable. Only used if I(state) is present. + type: bool + domain: + description: + - Domain the ISO is related to. + type: str + account: + description: + - Account the ISO is related to. + type: str + project: + description: + - Name of the project the ISO to be registered in. + type: str + zone: + description: + - Name of the zone you wish the ISO to be registered or deleted from. + - If not specified, first zone found will be used. + type: str + cross_zones: + description: + - Whether the ISO should be synced or removed across zones. + - Mutually exclusive with I(zone). + type: bool + default: no + iso_filter: + description: + - Name of the filter used to search for the ISO. + type: str + default: self + choices: [ featured, self, selfexecutable,sharedexecutable,executable, community ] + state: + description: + - State of the ISO. + type: str + default: present + choices: [ present, absent ] + poll_async: + description: + - Poll async jobs until job has finished. + type: bool + default: yes + tags: + description: + - List of tags. Tags are a list of dictionaries having keys I(key) and I(value). + - "To delete all tags, set a empty list e.g. I(tags: [])." + type: list + elements: dict + aliases: [ tag ] +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Register an ISO if ISO name does not already exist + ngine_io.cloudstack.cs_iso: + name: Debian 7 64-bit + url: http://mirror.switch.ch/ftp/mirror/debian-cd/current/amd64/iso-cd/debian-7.7.0-amd64-netinst.iso + os_type: Debian GNU/Linux 7(64-bit) + +- name: Register an ISO with given name if ISO md5 checksum does not already exist + ngine_io.cloudstack.cs_iso: + name: Debian 7 64-bit + url: http://mirror.switch.ch/ftp/mirror/debian-cd/current/amd64/iso-cd/debian-7.7.0-amd64-netinst.iso + os_type: Debian GNU/Linux 7(64-bit) + checksum: 0b31bccccb048d20b551f70830bb7ad0 + +- name: Remove an ISO by name + ngine_io.cloudstack.cs_iso: + name: Debian 7 64-bit + state: absent + +- name: Remove an ISO by checksum + ngine_io.cloudstack.cs_iso: + name: Debian 7 64-bit + checksum: 0b31bccccb048d20b551f70830bb7ad0 + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the ISO. + returned: success + type: str + sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f +name: + description: Name of the ISO. + returned: success + type: str + sample: Debian 7 64-bit +display_text: + description: Text to be displayed of the ISO. + returned: success + type: str + sample: Debian 7.7 64-bit minimal 2015-03-19 +zone: + description: Name of zone the ISO is registered in. + returned: success + type: str + sample: zuerich +status: + description: Status of the ISO. + returned: success + type: str + sample: Successfully Installed +is_ready: + description: True if the ISO is ready to be deployed from. + returned: success + type: bool + sample: true +is_public: + description: True if the ISO is public. + returned: success + type: bool + sample: true +bootable: + description: True if the ISO is bootable. + returned: success + type: bool + sample: true +is_featured: + description: True if the ISO is featured. + returned: success + type: bool + sample: true +format: + description: Format of the ISO. + returned: success + type: str + sample: ISO +os_type: + description: Typo of the OS. + returned: success + type: str + sample: CentOS 6.5 (64-bit) +checksum: + description: MD5 checksum of the ISO. + returned: success + type: str + sample: 0b31bccccb048d20b551f70830bb7ad0 +created: + description: Date of registering. + returned: success + type: str + sample: 2015-03-29T14:57:06+0200 +cross_zones: + description: true if the ISO is managed across all zones, false otherwise. + returned: success + type: bool + sample: false +domain: + description: Domain the ISO is related to. + returned: success + type: str + sample: example domain +account: + description: Account the ISO is related to. + returned: success + type: str + sample: example account +project: + description: Project the ISO is related to. + returned: success + type: str + sample: example project +tags: + description: List of resource tags associated with the ISO. + returned: success + type: dict + sample: '[ { "key": "foo", "value": "bar" } ]' +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackIso(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackIso, self).__init__(module) + self.returns = { + 'checksum': 'checksum', + 'status': 'status', + 'isready': 'is_ready', + 'crossZones': 'cross_zones', + 'format': 'format', + 'ostypename': 'os_type', + 'isfeatured': 'is_featured', + 'bootable': 'bootable', + 'ispublic': 'is_public', + + } + self.iso = None + + def _get_common_args(self): + return { + 'name': self.module.params.get('name'), + 'displaytext': self.get_or_fallback('display_text', 'name'), + 'isdynamicallyscalable': self.module.params.get('is_dynamically_scalable'), + 'ostypeid': self.get_os_type('id'), + 'bootable': self.module.params.get('bootable'), + } + + def register_iso(self): + args = self._get_common_args() + args.update({ + 'domainid': self.get_domain('id'), + 'account': self.get_account('name'), + 'projectid': self.get_project('id'), + 'checksum': self.module.params.get('checksum'), + 'isfeatured': self.module.params.get('is_featured'), + 'ispublic': self.module.params.get('is_public'), + }) + + if not self.module.params.get('cross_zones'): + args['zoneid'] = self.get_zone(key='id') + else: + args['zoneid'] = -1 + + if args['bootable'] and not args['ostypeid']: + self.module.fail_json(msg="OS type 'os_type' is required if 'bootable=true'.") + + args['url'] = self.module.params.get('url') + if not args['url']: + self.module.fail_json(msg="URL is required.") + + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('registerIso', **args) + self.iso = res['iso'][0] + return self.iso + + def present_iso(self): + iso = self.get_iso() + if not iso: + iso = self.register_iso() + else: + iso = self.update_iso(iso) + + if iso: + iso = self.ensure_tags(resource=iso, resource_type='ISO') + self.iso = iso + return iso + + def update_iso(self, iso): + args = self._get_common_args() + args.update({ + 'id': iso['id'], + }) + if self.has_changed(args, iso): + self.result['changed'] = True + + if not self.module.params.get('cross_zones'): + args['zoneid'] = self.get_zone(key='id') + else: + # Workaround API does not return cross_zones=true + self.result['cross_zones'] = True + args['zoneid'] = -1 + + if not self.module.check_mode: + res = self.query_api('updateIso', **args) + self.iso = res['iso'] + return self.iso + + def get_iso(self): + if not self.iso: + args = { + 'isready': self.module.params.get('is_ready'), + 'isofilter': self.module.params.get('iso_filter'), + 'domainid': self.get_domain('id'), + 'account': self.get_account('name'), + 'projectid': self.get_project('id'), + } + + if not self.module.params.get('cross_zones'): + args['zoneid'] = self.get_zone(key='id') + + # if checksum is set, we only look on that. + checksum = self.module.params.get('checksum') + if not checksum: + args['name'] = self.module.params.get('name') + + isos = self.query_api('listIsos', **args) + if isos: + if not checksum: + self.iso = isos['iso'][0] + else: + for i in isos['iso']: + if i['checksum'] == checksum: + self.iso = i + break + return self.iso + + def absent_iso(self): + iso = self.get_iso() + if iso: + self.result['changed'] = True + + args = { + 'id': iso['id'], + 'projectid': self.get_project('id'), + } + + if not self.module.params.get('cross_zones'): + args['zoneid'] = self.get_zone(key='id') + + if not self.module.check_mode: + res = self.query_api('deleteIso', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(res, 'iso') + return iso + + def get_result(self, iso): + super(AnsibleCloudStackIso, self).get_result(iso) + # Workaround API does not return cross_zones=true + if self.module.params.get('cross_zones'): + self.result['cross_zones'] = True + if 'zone' in self.result: + del self.result['zone'] + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + display_text=dict(), + url=dict(), + os_type=dict(), + zone=dict(), + cross_zones=dict(type='bool', default=False), + iso_filter=dict(default='self', choices=['featured', 'self', 'selfexecutable', 'sharedexecutable', 'executable', 'community']), + domain=dict(), + account=dict(), + project=dict(), + checksum=dict(), + is_ready=dict(type='bool', default=False), + bootable=dict(type='bool'), + is_featured=dict(type='bool'), + is_public=dict(type='bool'), + is_dynamically_scalable=dict(type='bool'), + state=dict(choices=['present', 'absent'], default='present'), + poll_async=dict(type='bool', default=True), + tags=dict(type='list', elements='dict', aliases=['tag']), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + mutually_exclusive=( + ['zone', 'cross_zones'], + ), + supports_check_mode=True + ) + + acs_iso = AnsibleCloudStackIso(module) + + state = module.params.get('state') + if state in ['absent']: + iso = acs_iso.absent_iso() + else: + iso = acs_iso.present_iso() + + result = acs_iso.get_result(iso) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_loadbalancer_rule.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_loadbalancer_rule.py new file mode 100644 index 00000000..12a7d13a --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_loadbalancer_rule.py @@ -0,0 +1,371 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, Darren Worrall <darren@iweb.co.uk> +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_loadbalancer_rule +short_description: Manages load balancer rules on Apache CloudStack based clouds. +description: + - Add, update and remove load balancer rules. +author: + - Darren Worrall (@dazworrall) + - René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - The name of the load balancer rule. + type: str + required: true + description: + description: + - The description of the load balancer rule. + type: str + algorithm: + description: + - Load balancer algorithm + - Required when using I(state=present). + type: str + choices: [ source, roundrobin, leastconn ] + default: source + private_port: + description: + - The private port of the private ip address/virtual machine where the network traffic will be load balanced to. + - Required when using I(state=present). + - Can not be changed once the rule exists due API limitation. + type: int + public_port: + description: + - The public port from where the network traffic will be load balanced from. + - Required when using I(state=present). + - Can not be changed once the rule exists due API limitation. + type: int + ip_address: + description: + - Public IP address from where the network traffic will be load balanced from. + type: str + required: true + aliases: [ public_ip ] + open_firewall: + description: + - Whether the firewall rule for public port should be created, while creating the new rule. + - Use M(cs_firewall) for managing firewall rules. + type: bool + default: no + cidr: + description: + - CIDR (full notation) to be used for firewall rule if required. + type: str + protocol: + description: + - The protocol to be used on the load balancer + type: str + project: + description: + - Name of the project the load balancer IP address is related to. + type: str + state: + description: + - State of the rule. + type: str + default: present + choices: [ present, absent ] + domain: + description: + - Domain the rule is related to. + type: str + account: + description: + - Account the rule is related to. + type: str + zone: + description: + - Name of the zone in which the rule should be created. + - If not set, default zone is used. + type: str + poll_async: + description: + - Poll async jobs until job has finished. + type: bool + default: yes + tags: + description: + - List of tags. Tags are a list of dictionaries having keys I(key) and I(value). + - "To delete all tags, set a empty list e.g. I(tags: [])." + type: list + elements: dict + aliases: [ tag ] + network: + description: + - Name of the network. + type: str + vpc: + description: + - Name of the VPC. + type: str +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Create a load balancer rule + ngine_io.cloudstack.cs_loadbalancer_rule: + name: balance_http + public_ip: 1.2.3.4 + algorithm: leastconn + public_port: 80 + private_port: 8080 + +- name: Update algorithm of an existing load balancer rule + ngine_io.cloudstack.cs_loadbalancer_rule: + name: balance_http + public_ip: 1.2.3.4 + algorithm: roundrobin + public_port: 80 + private_port: 8080 + +- name: Delete a load balancer rule + ngine_io.cloudstack.cs_loadbalancer_rule: + name: balance_http + public_ip: 1.2.3.4 + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the rule. + returned: success + type: str + sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f +zone: + description: Name of zone the rule is related to. + returned: success + type: str + sample: ch-gva-2 +project: + description: Name of project the rule is related to. + returned: success + type: str + sample: Production +account: + description: Account the rule is related to. + returned: success + type: str + sample: example account +domain: + description: Domain the rule is related to. + returned: success + type: str + sample: example domain +algorithm: + description: Load balancer algorithm used. + returned: success + type: str + sample: source +cidr: + description: CIDR to forward traffic from. + returned: success + type: str + sample: 0.0.0.0/0 +name: + description: Name of the rule. + returned: success + type: str + sample: http-lb +description: + description: Description of the rule. + returned: success + type: str + sample: http load balancer rule +protocol: + description: Protocol of the rule. + returned: success + type: str + sample: tcp +public_port: + description: Public port. + returned: success + type: int + sample: 80 +private_port: + description: Private IP address. + returned: success + type: int + sample: 80 +public_ip: + description: Public IP address. + returned: success + type: str + sample: 1.2.3.4 +tags: + description: List of resource tags associated with the rule. + returned: success + type: list + sample: '[ { "key": "foo", "value": "bar" } ]' +state: + description: State of the rule. + returned: success + type: str + sample: Add +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackLBRule(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackLBRule, self).__init__(module) + self.returns = { + 'publicip': 'public_ip', + 'algorithm': 'algorithm', + 'cidrlist': 'cidr', + 'protocol': 'protocol', + } + # these values will be casted to int + self.returns_to_int = { + 'publicport': 'public_port', + 'privateport': 'private_port', + } + + def get_rule(self, **kwargs): + rules = self.query_api('listLoadBalancerRules', **kwargs) + if rules: + return rules['loadbalancerrule'][0] + + def _get_common_args(self): + return { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'zoneid': self.get_zone(key='id') if self.module.params.get('zone') else None, + 'publicipid': self.get_ip_address(key='id'), + 'name': self.module.params.get('name'), + } + + def present_lb_rule(self): + required_params = [ + 'algorithm', + 'private_port', + 'public_port', + ] + self.module.fail_on_missing_params(required_params=required_params) + + args = self._get_common_args() + rule = self.get_rule(**args) + if rule: + rule = self._update_lb_rule(rule) + else: + rule = self._create_lb_rule(rule) + + if rule: + rule = self.ensure_tags(resource=rule, resource_type='LoadBalancer') + return rule + + def _create_lb_rule(self, rule): + self.result['changed'] = True + if not self.module.check_mode: + args = self._get_common_args() + args.update({ + 'algorithm': self.module.params.get('algorithm'), + 'privateport': self.module.params.get('private_port'), + 'publicport': self.module.params.get('public_port'), + 'cidrlist': self.module.params.get('cidr'), + 'description': self.module.params.get('description'), + 'protocol': self.module.params.get('protocol'), + 'networkid': self.get_network(key='id'), + }) + res = self.query_api('createLoadBalancerRule', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + rule = self.poll_job(res, 'loadbalancer') + return rule + + def _update_lb_rule(self, rule): + args = { + 'id': rule['id'], + 'algorithm': self.module.params.get('algorithm'), + 'description': self.module.params.get('description'), + } + if self.has_changed(args, rule): + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('updateLoadBalancerRule', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + rule = self.poll_job(res, 'loadbalancer') + return rule + + def absent_lb_rule(self): + args = self._get_common_args() + rule = self.get_rule(**args) + if rule: + self.result['changed'] = True + if rule and not self.module.check_mode: + res = self.query_api('deleteLoadBalancerRule', id=rule['id']) + + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(res, 'loadbalancer') + return rule + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + description=dict(), + algorithm=dict(choices=['source', 'roundrobin', 'leastconn'], default='source'), + private_port=dict(type='int'), + public_port=dict(type='int'), + protocol=dict(), + state=dict(choices=['present', 'absent'], default='present'), + ip_address=dict(required=True, aliases=['public_ip']), + cidr=dict(), + project=dict(), + open_firewall=dict(type='bool', default=False), + tags=dict(type='list', elements='dict', aliases=['tag']), + zone=dict(), + domain=dict(), + account=dict(), + vpc=dict(), + network=dict(), + poll_async=dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_lb_rule = AnsibleCloudStackLBRule(module) + + state = module.params.get('state') + if state in ['absent']: + rule = acs_lb_rule.absent_lb_rule() + else: + rule = acs_lb_rule.present_lb_rule() + + result = acs_lb_rule.get_result(rule) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_loadbalancer_rule_member.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_loadbalancer_rule_member.py new file mode 100644 index 00000000..6922bb83 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_loadbalancer_rule_member.py @@ -0,0 +1,345 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, Darren Worrall <darren@iweb.co.uk> +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_loadbalancer_rule_member +short_description: Manages load balancer rule members on Apache CloudStack based clouds. +description: + - Add and remove load balancer rule members. +author: + - Darren Worrall (@dazworrall) + - René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - The name of the load balancer rule. + type: str + required: true + ip_address: + description: + - Public IP address from where the network traffic will be load balanced from. + - Only needed to find the rule if I(name) is not unique. + type: str + aliases: [ public_ip ] + vms: + description: + - List of VMs to assign to or remove from the rule. + type: list + elements: str + required: true + aliases: [ vm ] + state: + description: + - Should the VMs be present or absent from the rule. + type: str + default: present + choices: [ present, absent ] + project: + description: + - Name of the project the firewall rule is related to. + type: str + domain: + description: + - Domain the rule is related to. + type: str + account: + description: + - Account the rule is related to. + type: str + zone: + description: + - Name of the zone in which the rule should be located. + - If not set, default zone is used. + type: str + poll_async: + description: + - Poll async jobs until job has finished. + type: bool + default: yes +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Add VMs to an existing load balancer + ngine_io.cloudstack.cs_loadbalancer_rule_member: + name: balance_http + vms: + - web01 + - web02 + +- name: Remove a VM from an existing load balancer + ngine_io.cloudstack.cs_loadbalancer_rule_member: + name: balance_http + vms: + - web01 + - web02 + state: absent + + +# Rolling upgrade of hosts +- hosts: webservers + serial: 1 + pre_tasks: + - name: Remove from load balancer + ngine_io.cloudstack.cs_loadbalancer_rule_member: + name: balance_http + vm: "{{ ansible_hostname }}" + state: absent + tasks: + # Perform update + post_tasks: + - name: Add to load balancer + ngine_io.cloudstack.cs_loadbalancer_rule_member: + name: balance_http + vm: "{{ ansible_hostname }}" + state: present + +''' + +RETURN = ''' +--- +id: + description: UUID of the rule. + returned: success + type: str + sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f +zone: + description: Name of zone the rule is related to. + returned: success + type: str + sample: ch-gva-2 +project: + description: Name of project the rule is related to. + returned: success + type: str + sample: Production +account: + description: Account the rule is related to. + returned: success + type: str + sample: example account +domain: + description: Domain the rule is related to. + returned: success + type: str + sample: example domain +algorithm: + description: Load balancer algorithm used. + returned: success + type: str + sample: source +cidr: + description: CIDR to forward traffic from. + returned: success + type: str + sample: 0.0.0.0/0 +name: + description: Name of the rule. + returned: success + type: str + sample: http-lb +description: + description: Description of the rule. + returned: success + type: str + sample: http load balancer rule +protocol: + description: Protocol of the rule. + returned: success + type: str + sample: tcp +public_port: + description: Public port. + returned: success + type: int + sample: 80 +private_port: + description: Private IP address. + returned: success + type: int + sample: 80 +public_ip: + description: Public IP address. + returned: success + type: str + sample: 1.2.3.4 +vms: + description: Rule members. + returned: success + type: list + sample: '[ "web01", "web02" ]' +tags: + description: List of resource tags associated with the rule. + returned: success + type: list + sample: '[ { "key": "foo", "value": "bar" } ]' +state: + description: State of the rule. + returned: success + type: str + sample: Add +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackLBRuleMember(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackLBRuleMember, self).__init__(module) + self.returns = { + 'publicip': 'public_ip', + 'algorithm': 'algorithm', + 'cidrlist': 'cidr', + 'protocol': 'protocol', + } + # these values will be casted to int + self.returns_to_int = { + 'publicport': 'public_port', + 'privateport': 'private_port', + } + + def get_rule(self): + args = self._get_common_args() + args.update({ + 'name': self.module.params.get('name'), + 'zoneid': self.get_zone(key='id') if self.module.params.get('zone') else None, + }) + if self.module.params.get('ip_address'): + args['publicipid'] = self.get_ip_address(key='id') + + rules = self.query_api('listLoadBalancerRules', **args) + if rules: + if len(rules['loadbalancerrule']) > 1: + self.module.fail_json(msg="More than one rule having name %s. Please pass 'ip_address' as well." % args['name']) + return rules['loadbalancerrule'][0] + return None + + def _get_common_args(self): + return { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + } + + def _get_members_of_rule(self, rule): + res = self.query_api('listLoadBalancerRuleInstances', id=rule['id']) + return res.get('loadbalancerruleinstance', []) + + def _ensure_members(self, operation): + if operation not in ['add', 'remove']: + self.module.fail_json(msg="Bad operation: %s" % operation) + + rule = self.get_rule() + if not rule: + self.module.fail_json(msg="Unknown rule: %s" % self.module.params.get('name')) + + existing = {} + for vm in self._get_members_of_rule(rule=rule): + existing[vm['name']] = vm['id'] + + wanted_names = self.module.params.get('vms') + + if operation == 'add': + cs_func = 'assignToLoadBalancerRule' + to_change = set(wanted_names) - set(existing.keys()) + else: + cs_func = 'removeFromLoadBalancerRule' + to_change = set(wanted_names) & set(existing.keys()) + + if not to_change: + return rule + + args = self._get_common_args() + args['fetch_list'] = True + vms = self.query_api('listVirtualMachines', **args) + to_change_ids = [] + for name in to_change: + for vm in vms: + if vm['name'] == name: + to_change_ids.append(vm['id']) + break + else: + self.module.fail_json(msg="Unknown VM: %s" % name) + + if to_change_ids: + self.result['changed'] = True + + if to_change_ids and not self.module.check_mode: + res = self.query_api( + cs_func, + id=rule['id'], + virtualmachineids=to_change_ids, + ) + + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(res) + rule = self.get_rule() + return rule + + def add_members(self): + return self._ensure_members('add') + + def remove_members(self): + return self._ensure_members('remove') + + def get_result(self, rule): + super(AnsibleCloudStackLBRuleMember, self).get_result(rule) + if rule: + self.result['vms'] = [] + for vm in self._get_members_of_rule(rule=rule): + self.result['vms'].append(vm['name']) + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + ip_address=dict(aliases=['public_ip']), + vms=dict(required=True, aliases=['vm'], type='list', elements='str'), + state=dict(choices=['present', 'absent'], default='present'), + zone=dict(), + domain=dict(), + project=dict(), + account=dict(), + poll_async=dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_lb_rule_member = AnsibleCloudStackLBRuleMember(module) + + state = module.params.get('state') + if state in ['absent']: + rule = acs_lb_rule_member.remove_members() + else: + rule = acs_lb_rule_member.add_members() + + result = acs_lb_rule_member.get_result(rule) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_network.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_network.py new file mode 100644 index 00000000..3dc37999 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_network.py @@ -0,0 +1,633 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_network +short_description: Manages networks on Apache CloudStack based clouds. +description: + - Create, update, restart and delete networks. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name (case sensitive) of the network. + type: str + required: true + display_text: + description: + - Display text of the network. + - If not specified, I(name) will be used as I(display_text). + type: str + network_offering: + description: + - Name of the offering for the network. + - Required if I(state=present). + type: str + start_ip: + description: + - The beginning IPv4 address of the network belongs to. + - Only considered on create. + type: str + end_ip: + description: + - The ending IPv4 address of the network belongs to. + - If not specified, value of I(start_ip) is used. + - Only considered on create. + type: str + gateway: + description: + - The gateway of the network. + - Required for shared networks and isolated networks when it belongs to a VPC. + - Only considered on create. + type: str + netmask: + description: + - The netmask of the network. + - Required for shared networks and isolated networks when it belongs to a VPC. + - Only considered on create. + type: str + start_ipv6: + description: + - The beginning IPv6 address of the network belongs to. + - Only considered on create. + type: str + end_ipv6: + description: + - The ending IPv6 address of the network belongs to. + - If not specified, value of I(start_ipv6) is used. + - Only considered on create. + type: str + cidr_ipv6: + description: + - CIDR of IPv6 network, must be at least /64. + - Only considered on create. + type: str + gateway_ipv6: + description: + - The gateway of the IPv6 network. + - Required for shared networks. + - Only considered on create. + type: str + vlan: + description: + - The ID or VID of the network. + type: str + vpc: + description: + - Name of the VPC of the network. + type: str + isolated_pvlan: + description: + - The isolated private VLAN for this network. + type: str + clean_up: + description: + - Cleanup old network elements. + - Only considered on I(state=restarted). + default: no + type: bool + acl_type: + description: + - Access control type for the network. + - If not specified, Cloudstack will default to C(account) for isolated networks + - and C(domain) for shared networks. + - Only considered on create. + type: str + choices: [ account, domain ] + acl: + description: + - The name of the access control list for the VPC network tier. + type: str + subdomain_access: + description: + - Defines whether to allow subdomains to use networks dedicated to their parent domain(s). + - Should be used with I(acl_type=domain). + - Only considered on create. + type: bool + network_domain: + description: + - The network domain. + type: str + state: + description: + - State of the network. + type: str + default: present + choices: [ present, absent, restarted ] + zone: + description: + - Name of the zone in which the network should be deployed. + - If not set, default zone is used. + type: str + project: + description: + - Name of the project the network to be deployed in. + type: str + domain: + description: + - Domain the network is related to. + type: str + account: + description: + - Account the network is related to. + type: str + poll_async: + description: + - Poll async jobs until job has finished. + default: yes + type: bool + tags: + description: + - List of tags. Tags are a list of dictionaries having keys I(key) and I(value). + - "To delete all tags, set a empty list e.g. I(tags: [])." + type: list + elements: dict + aliases: [ tag ] +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Create a network + ngine_io.cloudstack.cs_network: + name: my network + zone: gva-01 + network_offering: DefaultIsolatedNetworkOfferingWithSourceNatService + network_domain: example.com + +- name: Create a network with start and end IP + ngine_io.cloudstack.cs_network: + name: Private Network + network_offering: PrivNet + start_ip: 10.12.9.10 + end_ip: 10.12.9.100 + netmask: 255.255.255.0 + zone: gva-01 + +- name: Create a VPC tier + ngine_io.cloudstack.cs_network: + name: my VPC tier 1 + zone: gva-01 + vpc: my VPC + network_offering: DefaultIsolatedNetworkOfferingForVpcNetworks + gateway: 10.43.0.1 + netmask: 255.255.255.0 + acl: my web acl + +- name: Update a network + ngine_io.cloudstack.cs_network: + name: my network + display_text: network of domain example.local + network_domain: example.local + +- name: Restart a network with clean up + ngine_io.cloudstack.cs_network: + name: my network + clean_up: yes + state: restarted + +- name: Remove a network + ngine_io.cloudstack.cs_network: + name: my network + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the network. + returned: success + type: str + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 +name: + description: Name of the network. + returned: success + type: str + sample: web project +display_text: + description: Display text of the network. + returned: success + type: str + sample: web project +dns1: + description: IP address of the 1st nameserver. + returned: success + type: str + sample: 1.2.3.4 +dns2: + description: IP address of the 2nd nameserver. + returned: success + type: str + sample: 1.2.3.4 +cidr: + description: IPv4 network CIDR. + returned: success + type: str + sample: 10.101.64.0/24 +gateway: + description: IPv4 gateway. + returned: success + type: str + sample: 10.101.64.1 +netmask: + description: IPv4 netmask. + returned: success + type: str + sample: 255.255.255.0 +cidr_ipv6: + description: IPv6 network CIDR. + returned: if available + type: str + sample: 2001:db8::/64 +gateway_ipv6: + description: IPv6 gateway. + returned: if available + type: str + sample: 2001:db8::1 +zone: + description: Name of zone. + returned: success + type: str + sample: ch-gva-2 +domain: + description: Domain the network is related to. + returned: success + type: str + sample: ROOT +account: + description: Account the network is related to. + returned: success + type: str + sample: example account +project: + description: Name of project. + returned: success + type: str + sample: Production +tags: + description: List of resource tags associated with the network. + returned: success + type: list + sample: '[ { "key": "foo", "value": "bar" } ]' +acl_type: + description: Access type of the network (Domain, Account). + returned: success + type: str + sample: Account +acl: + description: Name of the access control list for the VPC network tier. + returned: success + type: str + sample: My ACL +acl_id: + description: ID of the access control list for the VPC network tier. + returned: success + type: str + sample: dfafcd55-0510-4b8c-b6c5-b8cedb4cfd88 +broadcast_domain_type: + description: Broadcast domain type of the network. + returned: success + type: str + sample: Vlan +type: + description: Type of the network. + returned: success + type: str + sample: Isolated +traffic_type: + description: Traffic type of the network. + returned: success + type: str + sample: Guest +state: + description: State of the network (Allocated, Implemented, Setup). + returned: success + type: str + sample: Allocated +is_persistent: + description: Whether the network is persistent or not. + returned: success + type: bool + sample: false +network_domain: + description: The network domain + returned: success + type: str + sample: example.local +network_offering: + description: The network offering name. + returned: success + type: str + sample: DefaultIsolatedNetworkOfferingWithSourceNatService +network_offering_display_text: + description: The network offering display text. + returned: success + type: str + sample: Offering for Isolated Vpc networks with Source Nat service enabled +network_offering_conserve_mode: + description: Whether the network offering has IP conserve mode enabled or not. + returned: success + type: bool + sample: false +network_offering_availability: + description: The availability of the network offering the network is created from + returned: success + type: str + sample: Optional +is_system: + description: Whether the network is system related or not. + returned: success + type: bool + sample: false +vpc: + description: Name of the VPC. + returned: if available + type: str + sample: My VPC +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackNetwork(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackNetwork, self).__init__(module) + self.returns = { + 'networkdomain': 'network_domain', + 'networkofferingname': 'network_offering', + 'networkofferingdisplaytext': 'network_offering_display_text', + 'networkofferingconservemode': 'network_offering_conserve_mode', + 'networkofferingavailability': 'network_offering_availability', + 'aclid': 'acl_id', + 'issystem': 'is_system', + 'ispersistent': 'is_persistent', + 'acltype': 'acl_type', + 'type': 'type', + 'traffictype': 'traffic_type', + 'ip6gateway': 'gateway_ipv6', + 'ip6cidr': 'cidr_ipv6', + 'gateway': 'gateway', + 'cidr': 'cidr', + 'netmask': 'netmask', + 'broadcastdomaintype': 'broadcast_domain_type', + 'dns1': 'dns1', + 'dns2': 'dns2', + } + self.network = None + + def get_network_acl(self, key=None, acl_id=None): + if acl_id is not None: + args = { + 'id': acl_id, + 'vpcid': self.get_vpc(key='id'), + } + else: + acl_name = self.module.params.get('acl') + if not acl_name: + return + + args = { + 'name': acl_name, + 'vpcid': self.get_vpc(key='id'), + } + network_acls = self.query_api('listNetworkACLLists', **args) + if network_acls: + acl = network_acls['networkacllist'][0] + return self._get_by_key(key, acl) + + def get_network_offering(self, key=None): + network_offering = self.module.params.get('network_offering') + if not network_offering: + self.module.fail_json(msg="missing required arguments: network_offering") + + args = { + 'zoneid': self.get_zone(key='id'), + 'fetch_list': True, + } + + network_offerings = self.query_api('listNetworkOfferings', **args) + if network_offerings: + for no in network_offerings: + if network_offering in [no['name'], no['displaytext'], no['id']]: + return self._get_by_key(key, no) + self.module.fail_json(msg="Network offering '%s' not found" % network_offering) + + def _get_args(self): + args = { + 'name': self.module.params.get('name'), + 'displaytext': self.get_or_fallback('display_text', 'name'), + 'networkdomain': self.module.params.get('network_domain'), + 'networkofferingid': self.get_network_offering(key='id') + } + return args + + def get_network(self, refresh=False): + if not self.network or refresh: + network = self.module.params.get('name') + args = { + 'zoneid': self.get_zone(key='id'), + 'projectid': self.get_project(key='id'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'vpcid': self.get_vpc(key='id'), + 'fetch_list': True, + } + networks = self.query_api('listNetworks', **args) + if networks: + for n in networks: + if network in [n['name'], n['displaytext'], n['id']]: + self.network = n + self.network['acl'] = self.get_network_acl(key='name', acl_id=n.get('aclid')) + break + return self.network + + def present_network(self): + if self.module.params.get('acl') is not None and self.module.params.get('vpc') is None: + self.module.fail_json(msg="Missing required params: vpc") + + network = self.get_network() + if not network: + network = self.create_network(network) + else: + network = self.update_network(network) + + if network: + network = self.ensure_tags(resource=network, resource_type='Network') + + return network + + def update_network(self, network): + args = self._get_args() + args['id'] = network['id'] + + if self.has_changed(args, network): + self.result['changed'] = True + if not self.module.check_mode: + network = self.query_api('updateNetwork', **args) + + poll_async = self.module.params.get('poll_async') + if network and poll_async: + network = self.poll_job(network, 'network') + + # Skip ACL check if the network is not a VPC tier + if network.get('aclid') != self.get_network_acl(key='id'): + self.result['changed'] = True + if not self.module.check_mode: + args = { + 'aclid': self.get_network_acl(key='id'), + 'networkid': network['id'], + } + network = self.query_api('replaceNetworkACLList', **args) + if self.module.params.get('poll_async'): + self.poll_job(network, 'networkacllist') + network = self.get_network(refresh=True) + return network + + def create_network(self, network): + self.result['changed'] = True + + args = self._get_args() + args.update({ + 'acltype': self.module.params.get('acl_type'), + 'aclid': self.get_network_acl(key='id'), + 'zoneid': self.get_zone(key='id'), + 'projectid': self.get_project(key='id'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'startip': self.module.params.get('start_ip'), + 'endip': self.get_or_fallback('end_ip', 'start_ip'), + 'netmask': self.module.params.get('netmask'), + 'gateway': self.module.params.get('gateway'), + 'startipv6': self.module.params.get('start_ipv6'), + 'endipv6': self.get_or_fallback('end_ipv6', 'start_ipv6'), + 'ip6cidr': self.module.params.get('cidr_ipv6'), + 'ip6gateway': self.module.params.get('gateway_ipv6'), + 'vlan': self.module.params.get('vlan'), + 'isolatedpvlan': self.module.params.get('isolated_pvlan'), + 'subdomainaccess': self.module.params.get('subdomain_access'), + 'vpcid': self.get_vpc(key='id') + }) + + if not self.module.check_mode: + res = self.query_api('createNetwork', **args) + + network = res['network'] + return network + + def restart_network(self): + network = self.get_network() + + if not network: + self.module.fail_json(msg="No network named '%s' found." % self.module.params('name')) + + # Restarting only available for these states + if network['state'].lower() in ['implemented', 'setup']: + self.result['changed'] = True + + args = { + 'id': network['id'], + 'cleanup': self.module.params.get('clean_up') + } + + if not self.module.check_mode: + network = self.query_api('restartNetwork', **args) + + poll_async = self.module.params.get('poll_async') + if network and poll_async: + network = self.poll_job(network, 'network') + return network + + def absent_network(self): + network = self.get_network() + if network: + self.result['changed'] = True + + args = { + 'id': network['id'] + } + + if not self.module.check_mode: + res = self.query_api('deleteNetwork', **args) + + poll_async = self.module.params.get('poll_async') + if res and poll_async: + self.poll_job(res, 'network') + return network + + def get_result(self, network): + super(AnsibleCloudStackNetwork, self).get_result(network) + if network: + self.result['acl'] = self.get_network_acl(key='name', acl_id=network.get('aclid')) + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + display_text=dict(), + network_offering=dict(), + zone=dict(), + start_ip=dict(), + end_ip=dict(), + gateway=dict(), + netmask=dict(), + start_ipv6=dict(), + end_ipv6=dict(), + cidr_ipv6=dict(), + gateway_ipv6=dict(), + vlan=dict(), + vpc=dict(), + isolated_pvlan=dict(), + clean_up=dict(type='bool', default=False), + network_domain=dict(), + subdomain_access=dict(type='bool'), + state=dict(choices=['present', 'absent', 'restarted'], default='present'), + acl=dict(), + acl_type=dict(choices=['account', 'domain']), + project=dict(), + domain=dict(), + account=dict(), + poll_async=dict(type='bool', default=True), + tags=dict(type='list', elements='dict', aliases=['tag']), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_network = AnsibleCloudStackNetwork(module) + + state = module.params.get('state') + if state == 'absent': + network = acs_network.absent_network() + + elif state == 'restarted': + network = acs_network.restart_network() + + else: + network = acs_network.present_network() + + result = acs_network.get_result(network) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_network_acl.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_network_acl.py new file mode 100644 index 00000000..b6cd0e18 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_network_acl.py @@ -0,0 +1,197 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_network_acl +short_description: Manages network access control lists (ACL) on Apache CloudStack based clouds. +description: + - Create and remove network ACLs. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name of the network ACL. + type: str + required: true + description: + description: + - Description of the network ACL. + - If not set, identical to I(name). + type: str + vpc: + description: + - VPC the network ACL is related to. + type: str + required: true + state: + description: + - State of the network ACL. + type: str + default: present + choices: [ present, absent ] + domain: + description: + - Domain the network ACL rule is related to. + type: str + account: + description: + - Account the network ACL rule is related to. + type: str + project: + description: + - Name of the project the network ACL is related to. + type: str + zone: + description: + - Name of the zone the VPC is related to. + - If not set, default zone is used. + type: str + poll_async: + description: + - Poll async jobs until job has finished. + type: bool + default: yes +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: create a network ACL + ngine_io.cloudstack.cs_network_acl: + name: Webserver ACL + description: a more detailed description of the ACL + vpc: customers + +- name: remove a network ACL + ngine_io.cloudstack.cs_network_acl: + name: Webserver ACL + vpc: customers + state: absent +''' + +RETURN = ''' +--- +name: + description: Name of the network ACL. + returned: success + type: str + sample: customer acl +description: + description: Description of the network ACL. + returned: success + type: str + sample: Example description of a network ACL +vpc: + description: VPC of the network ACL. + returned: success + type: str + sample: customer vpc +zone: + description: Zone the VPC is related to. + returned: success + type: str + sample: ch-gva-2 +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackNetworkAcl(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackNetworkAcl, self).__init__(module) + + def get_network_acl(self): + args = { + 'name': self.module.params.get('name'), + 'vpcid': self.get_vpc(key='id'), + } + network_acls = self.query_api('listNetworkACLLists', **args) + if network_acls: + return network_acls['networkacllist'][0] + return None + + def present_network_acl(self): + network_acl = self.get_network_acl() + if not network_acl: + self.result['changed'] = True + args = { + 'name': self.module.params.get('name'), + 'description': self.get_or_fallback('description', 'name'), + 'vpcid': self.get_vpc(key='id') + } + if not self.module.check_mode: + res = self.query_api('createNetworkACLList', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + network_acl = self.poll_job(res, 'networkacllist') + + return network_acl + + def absent_network_acl(self): + network_acl = self.get_network_acl() + if network_acl: + self.result['changed'] = True + args = { + 'id': network_acl['id'], + } + if not self.module.check_mode: + res = self.query_api('deleteNetworkACLList', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(res, 'networkacllist') + + return network_acl + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + description=dict(), + vpc=dict(required=True), + state=dict(choices=['present', 'absent'], default='present'), + zone=dict(), + domain=dict(), + account=dict(), + project=dict(), + poll_async=dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_network_acl = AnsibleCloudStackNetworkAcl(module) + + state = module.params.get('state') + if state == 'absent': + network_acl = acs_network_acl.absent_network_acl() + else: + network_acl = acs_network_acl.present_network_acl() + + result = acs_network_acl.get_result(network_acl) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_network_acl_rule.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_network_acl_rule.py new file mode 100644 index 00000000..acce9e2c --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_network_acl_rule.py @@ -0,0 +1,457 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_network_acl_rule +short_description: Manages network access control list (ACL) rules on Apache CloudStack based clouds. +description: + - Add, update and remove network ACL rules. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + network_acl: + description: + - Name of the network ACL. + type: str + required: true + aliases: [ acl ] + cidrs: + description: + - CIDRs of the rule. + type: list + elements: str + default: [ 0.0.0.0/0 ] + aliases: [ cidr ] + rule_position: + description: + - The position of the network ACL rule. + type: int + required: true + aliases: [ number ] + protocol: + description: + - Protocol of the rule + choices: [ tcp, udp, icmp, all, by_number ] + type: str + default: tcp + protocol_number: + description: + - Protocol number from 1 to 256 required if I(protocol=by_number). + type: int + start_port: + description: + - Start port for this rule. + - Considered if I(protocol=tcp) or I(protocol=udp). + type: int + aliases: [ port ] + end_port: + description: + - End port for this rule. + - Considered if I(protocol=tcp) or I(protocol=udp). + - If not specified, equal I(start_port). + type: int + icmp_type: + description: + - Type of the icmp message being sent. + - Considered if I(protocol=icmp). + type: int + icmp_code: + description: + - Error code for this icmp message. + - Considered if I(protocol=icmp). + type: int + vpc: + description: + - VPC the network ACL is related to. + type: str + required: true + traffic_type: + description: + - Traffic type of the rule. + type: str + choices: [ ingress, egress ] + default: ingress + aliases: [ type ] + action_policy: + description: + - Action policy of the rule. + type: str + choices: [ allow, deny ] + default: allow + aliases: [ action ] + tags: + description: + - List of tags. Tags are a list of dictionaries having keys I(key) and I(value). + - "If you want to delete all tags, set a empty list e.g. I(tags: [])." + type: list + elements: dict + aliases: [ tag ] + domain: + description: + - Domain the VPC is related to. + type: str + account: + description: + - Account the VPC is related to. + type: str + project: + description: + - Name of the project the VPC is related to. + type: str + zone: + description: + - Name of the zone the VPC related to. + - If not set, default zone is used. + type: str + state: + description: + - State of the network ACL rule. + type: str + default: present + choices: [ present, absent ] + poll_async: + description: + - Poll async jobs until job has finished. + type: bool + default: yes +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: create a network ACL rule, allow port 80 ingress + ngine_io.cloudstack.cs_network_acl_rule: + network_acl: web + rule_position: 1 + vpc: my vpc + traffic_type: ingress + action_policy: allow + port: 80 + cidr: 0.0.0.0/0 + +- name: create a network ACL rule, deny port range 8000-9000 ingress for 10.20.0.0/16 and 10.22.0.0/16 + ngine_io.cloudstack.cs_network_acl_rule: + network_acl: web + rule_position: 1 + vpc: my vpc + traffic_type: ingress + action_policy: deny + start_port: 8000 + end_port: 9000 + cidrs: + - 10.20.0.0/16 + - 10.22.0.0/16 + +- name: remove a network ACL rule + ngine_io.cloudstack.cs_network_acl_rule: + network_acl: web + rule_position: 1 + vpc: my vpc + state: absent +''' + +RETURN = ''' +--- +network_acl: + description: Name of the network ACL. + returned: success + type: str + sample: customer acl +cidr: + description: CIDR of the network ACL rule. + returned: success + type: str + sample: 0.0.0.0/0 +cidrs: + description: CIDRs of the network ACL rule. + returned: success + type: list + sample: [ 0.0.0.0/0 ] +rule_position: + description: Position of the network ACL rule. + returned: success + type: int + sample: 1 +action_policy: + description: Action policy of the network ACL rule. + returned: success + type: str + sample: deny +traffic_type: + description: Traffic type of the network ACL rule. + returned: success + type: str + sample: ingress +protocol: + description: Protocol of the network ACL rule. + returned: success + type: str + sample: tcp +protocol_number: + description: Protocol number in case protocol is by number. + returned: success + type: int + sample: 8 +start_port: + description: Start port of the network ACL rule. + returned: success + type: int + sample: 80 +end_port: + description: End port of the network ACL rule. + returned: success + type: int + sample: 80 +icmp_code: + description: ICMP code of the network ACL rule. + returned: success + type: int + sample: 8 +icmp_type: + description: ICMP type of the network ACL rule. + returned: success + type: int + sample: 0 +state: + description: State of the network ACL rule. + returned: success + type: str + sample: Active +vpc: + description: VPC of the network ACL. + returned: success + type: str + sample: customer vpc +tags: + description: List of resource tags associated with the network ACL rule. + returned: success + type: list + sample: '[ { "key": "foo", "value": "bar" } ]' +domain: + description: Domain the network ACL rule is related to. + returned: success + type: str + sample: example domain +account: + description: Account the network ACL rule is related to. + returned: success + type: str + sample: example account +project: + description: Name of project the network ACL rule is related to. + returned: success + type: str + sample: Production +zone: + description: Zone the VPC is related to. + returned: success + type: str + sample: ch-gva-2 +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackNetworkAclRule(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackNetworkAclRule, self).__init__(module) + self.returns = { + 'cidrlist': 'cidr', + 'action': 'action_policy', + 'protocol': 'protocol', + 'icmpcode': 'icmp_code', + 'icmptype': 'icmp_type', + 'number': 'rule_position', + 'traffictype': 'traffic_type', + } + # these values will be casted to int + self.returns_to_int = { + 'startport': 'start_port', + 'endport': 'end_port', + } + + def get_network_acl_rule(self): + args = { + 'aclid': self.get_network_acl(key='id'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + } + network_acl_rules = self.query_api('listNetworkACLs', **args) + for acl_rule in network_acl_rules.get('networkacl', []): + if acl_rule['number'] == self.module.params.get('rule_position'): + return acl_rule + return None + + def present_network_acl_rule(self): + network_acl_rule = self.get_network_acl_rule() + + protocol = self.module.params.get('protocol') + start_port = self.module.params.get('start_port') + end_port = self.get_or_fallback('end_port', 'start_port') + icmp_type = self.module.params.get('icmp_type') + icmp_code = self.module.params.get('icmp_code') + + if protocol in ['tcp', 'udp'] and (start_port is None or end_port is None): + self.module.fail_json(msg="protocol is %s but the following are missing: start_port, end_port" % protocol) + + elif protocol == 'icmp' and (icmp_type is None or icmp_code is None): + self.module.fail_json(msg="protocol is icmp but the following are missing: icmp_type, icmp_code") + + elif protocol == 'by_number' and self.module.params.get('protocol_number') is None: + self.module.fail_json(msg="protocol is by_number but the following are missing: protocol_number") + + if not network_acl_rule: + network_acl_rule = self._create_network_acl_rule(network_acl_rule) + else: + network_acl_rule = self._update_network_acl_rule(network_acl_rule) + + if network_acl_rule: + network_acl_rule = self.ensure_tags(resource=network_acl_rule, resource_type='NetworkACL') + return network_acl_rule + + def absent_network_acl_rule(self): + network_acl_rule = self.get_network_acl_rule() + if network_acl_rule: + self.result['changed'] = True + args = { + 'id': network_acl_rule['id'], + } + if not self.module.check_mode: + res = self.query_api('deleteNetworkACL', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(res, 'networkacl') + + return network_acl_rule + + def _create_network_acl_rule(self, network_acl_rule): + self.result['changed'] = True + protocol = self.module.params.get('protocol') + args = { + 'aclid': self.get_network_acl(key='id'), + 'action': self.module.params.get('action_policy'), + 'protocol': protocol if protocol != 'by_number' else self.module.params.get('protocol_number'), + 'startport': self.module.params.get('start_port'), + 'endport': self.get_or_fallback('end_port', 'start_port'), + 'number': self.module.params.get('rule_position'), + 'icmpcode': self.module.params.get('icmp_code'), + 'icmptype': self.module.params.get('icmp_type'), + 'traffictype': self.module.params.get('traffic_type'), + 'cidrlist': self.module.params.get('cidrs'), + } + if not self.module.check_mode: + res = self.query_api('createNetworkACL', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + network_acl_rule = self.poll_job(res, 'networkacl') + + return network_acl_rule + + def _update_network_acl_rule(self, network_acl_rule): + protocol = self.module.params.get('protocol') + args = { + 'id': network_acl_rule['id'], + 'action': self.module.params.get('action_policy'), + 'protocol': protocol if protocol != 'by_number' else str(self.module.params.get('protocol_number')), + 'startport': self.module.params.get('start_port'), + 'endport': self.get_or_fallback('end_port', 'start_port'), + 'icmpcode': self.module.params.get('icmp_code'), + 'icmptype': self.module.params.get('icmp_type'), + 'traffictype': self.module.params.get('traffic_type'), + 'cidrlist': ",".join(self.module.params.get('cidrs')), + } + if self.has_changed(args, network_acl_rule): + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('updateNetworkACLItem', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + network_acl_rule = self.poll_job(res, 'networkacl') + + return network_acl_rule + + def get_result(self, network_acl_rule): + super(AnsibleCloudStackNetworkAclRule, self).get_result(network_acl_rule) + if network_acl_rule: + if 'cidrlist' in network_acl_rule: + self.result['cidrs'] = network_acl_rule['cidrlist'].split(',') or [network_acl_rule['cidrlist']] + if network_acl_rule['protocol'] not in ['tcp', 'udp', 'icmp', 'all']: + self.result['protocol_number'] = int(network_acl_rule['protocol']) + self.result['protocol'] = 'by_number' + self.result['action_policy'] = self.result['action_policy'].lower() + self.result['traffic_type'] = self.result['traffic_type'].lower() + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + network_acl=dict(required=True, aliases=['acl']), + rule_position=dict(required=True, type='int', aliases=['number']), + vpc=dict(required=True), + cidrs=dict(type='list', elements='str', default=['0.0.0.0/0'], aliases=['cidr']), + protocol=dict(choices=['tcp', 'udp', 'icmp', 'all', 'by_number'], default='tcp'), + protocol_number=dict(type='int'), + traffic_type=dict(choices=['ingress', 'egress'], aliases=['type'], default='ingress'), + action_policy=dict(choices=['allow', 'deny'], aliases=['action'], default='allow'), + icmp_type=dict(type='int'), + icmp_code=dict(type='int'), + start_port=dict(type='int', aliases=['port']), + end_port=dict(type='int'), + state=dict(choices=['present', 'absent'], default='present'), + zone=dict(), + domain=dict(), + account=dict(), + project=dict(), + tags=dict(type='list', elements='dict', aliases=['tag']), + poll_async=dict(type='bool', default=True), + )) + + required_together = cs_required_together() + required_together.extend([ + ['icmp_type', 'icmp_code'], + ]) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + mutually_exclusive=( + ['icmp_type', 'start_port'], + ['icmp_type', 'end_port'], + ), + supports_check_mode=True + ) + + acs_network_acl_rule = AnsibleCloudStackNetworkAclRule(module) + + state = module.params.get('state') + if state == 'absent': + network_acl_rule = acs_network_acl_rule.absent_network_acl_rule() + else: + network_acl_rule = acs_network_acl_rule.present_network_acl_rule() + + result = acs_network_acl_rule.get_result(network_acl_rule) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_network_offering.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_network_offering.py new file mode 100644 index 00000000..a5c5cb93 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_network_offering.py @@ -0,0 +1,422 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017, David Passante (@dpassante) +# Copyright (c) 2017, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_network_offering +short_description: Manages network offerings on Apache CloudStack based clouds. +description: + - Create, update, enable, disable and remove network offerings. +author: David Passante (@dpassante) +version_added: 0.1.0 +options: + state: + description: + - State of the network offering. + type: str + choices: [ enabled, present, disabled, absent] + default: present + display_text: + description: + - Display text of the network offerings. + type: str + guest_ip_type: + description: + - Guest type of the network offering. + type: str + choices: [ Shared, Isolated ] + name: + description: + - The name of the network offering. + type: str + required: true + supported_services: + description: + - Services supported by the network offering. + - A list of one or more items from the choice list. + type: list + elements: str + choices: [ Dns, PortForwarding, Dhcp, SourceNat, UserData, Firewall, StaticNat, Vpn, Lb ] + aliases: [ supported_service ] + traffic_type: + description: + - The traffic type for the network offering. + type: str + default: Guest + availability: + description: + - The availability of network offering. Default value is Optional + type: str + conserve_mode: + description: + - Whether the network offering has IP conserve mode enabled. + type: bool + details: + description: + - Network offering details in key/value pairs. + - with service provider as a value + type: list + elements: dict + egress_default_policy: + description: + - Whether the default egress policy is allow or to deny. + type: str + choices: [ allow, deny ] + persistent: + description: + - True if network offering supports persistent networks + - defaulted to false if not specified + type: bool + keepalive_enabled: + description: + - If true keepalive will be turned on in the loadbalancer. + - At the time of writing this has only an effect on haproxy. + - the mode http and httpclose options are unset in the haproxy conf file. + type: bool + max_connections: + description: + - Maximum number of concurrent connections supported by the network offering. + type: int + network_rate: + description: + - Data transfer rate in megabits per second allowed. + type: int + service_capabilities: + description: + - Desired service capabilities as part of network offering. + type: list + elements: str + aliases: [ service_capability ] + service_offering: + description: + - The service offering name or ID used by virtual router provider. + type: str + service_providers: + description: + - Provider to service mapping. + - If not specified, the provider for the service will be mapped to the default provider on the physical network. + type: list + elements: dict + aliases: [ service_provider ] + specify_ip_ranges: + description: + - Whether the network offering supports specifying IP ranges. + - Defaulted to C(no) by the API if not specified. + type: bool + specify_vlan: + description: + - Whether the network offering supports vlans or not. + type: bool + for_vpc: + description: + - Whether the offering is meant to be used for VPC or not. + type: bool +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Create a network offering and enable it + ngine_io.cloudstack.cs_network_offering: + name: my_network_offering + display_text: network offering description + state: enabled + guest_ip_type: Isolated + supported_services: [ Dns, PortForwarding, Dhcp, SourceNat, UserData, Firewall, StaticNat, Vpn, Lb ] + service_providers: + - { service: 'dns', provider: 'virtualrouter' } + - { service: 'dhcp', provider: 'virtualrouter' } + +- name: Remove a network offering + ngine_io.cloudstack.cs_network_offering: + name: my_network_offering + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the network offering. + returned: success + type: str + sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f +name: + description: The name of the network offering. + returned: success + type: str + sample: MyCustomNetworkOffering +display_text: + description: The display text of the network offering. + returned: success + type: str + sample: My network offering +state: + description: The state of the network offering. + returned: success + type: str + sample: Enabled +guest_ip_type: + description: Guest type of the network offering. + returned: success + type: str + sample: Isolated +availability: + description: The availability of network offering. + returned: success + type: str + sample: Optional +service_offering_id: + description: The service offering ID. + returned: success + type: str + sample: c5f7a5fc-43f8-11e5-a151-feff819cdc9f +max_connections: + description: The maximum number of concurrent connections to be handled by LB. + returned: success + type: int + sample: 300 +network_rate: + description: The network traffic transfer ate in Mbit/s. + returned: success + type: int + sample: 200 +traffic_type: + description: The traffic type. + returned: success + type: str + sample: Guest +egress_default_policy: + description: Default egress policy. + returned: success + type: str + sample: allow +is_persistent: + description: Whether persistent networks are supported or not. + returned: success + type: bool + sample: false +is_default: + description: Whether network offering is the default offering or not. + returned: success + type: bool + sample: false +for_vpc: + description: Whether the offering is meant to be used for VPC or not. + returned: success + type: bool + sample: false +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackNetworkOffering(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackNetworkOffering, self).__init__(module) + self.returns = { + 'guestiptype': 'guest_ip_type', + 'availability': 'availability', + 'serviceofferingid': 'service_offering_id', + 'networkrate': 'network_rate', + 'maxconnections': 'max_connections', + 'traffictype': 'traffic_type', + 'isdefault': 'is_default', + 'ispersistent': 'is_persistent', + 'forvpc': 'for_vpc' + } + self.network_offering = None + + def get_service_offering_id(self): + service_offering = self.module.params.get('service_offering') + if not service_offering: + return None + + args = { + 'issystem': True + } + + service_offerings = self.query_api('listServiceOfferings', **args) + if service_offerings: + for s in service_offerings['serviceoffering']: + if service_offering in [s['name'], s['id']]: + return s['id'] + self.fail_json(msg="Service offering '%s' not found" % service_offering) + + def get_network_offering(self): + if self.network_offering: + return self.network_offering + + args = { + 'name': self.module.params.get('name'), + 'guestiptype': self.module.params.get('guest_type'), + } + no = self.query_api('listNetworkOfferings', **args) + if no: + self.network_offering = no['networkoffering'][0] + + return self.network_offering + + def create_or_update(self): + network_offering = self.get_network_offering() + + if not network_offering: + network_offering = self.create_network_offering() + + return self.update_network_offering(network_offering=network_offering) + + def create_network_offering(self): + network_offering = None + self.result['changed'] = True + + args = { + 'state': self.module.params.get('state'), + 'displaytext': self.module.params.get('display_text'), + 'guestiptype': self.module.params.get('guest_ip_type'), + 'name': self.module.params.get('name'), + 'supportedservices': self.module.params.get('supported_services'), + 'traffictype': self.module.params.get('traffic_type'), + 'availability': self.module.params.get('availability'), + 'conservemode': self.module.params.get('conserve_mode'), + 'details': self.module.params.get('details'), + 'egressdefaultpolicy': self.module.params.get('egress_default_policy') == 'allow', + 'ispersistent': self.module.params.get('persistent'), + 'keepaliveenabled': self.module.params.get('keepalive_enabled'), + 'maxconnections': self.module.params.get('max_connections'), + 'networkrate': self.module.params.get('network_rate'), + 'servicecapabilitylist': self.module.params.get('service_capabilities'), + 'serviceofferingid': self.get_service_offering_id(), + 'serviceproviderlist': self.module.params.get('service_providers'), + 'specifyipranges': self.module.params.get('specify_ip_ranges'), + 'specifyvlan': self.module.params.get('specify_vlan'), + 'forvpc': self.module.params.get('for_vpc'), + } + + required_params = [ + 'display_text', + 'guest_ip_type', + 'supported_services', + 'service_providers', + ] + + self.module.fail_on_missing_params(required_params=required_params) + + if not self.module.check_mode: + res = self.query_api('createNetworkOffering', **args) + network_offering = res['networkoffering'] + + return network_offering + + def delete_network_offering(self): + network_offering = self.get_network_offering() + + if network_offering: + self.result['changed'] = True + if not self.module.check_mode: + self.query_api('deleteNetworkOffering', id=network_offering['id']) + + return network_offering + + def update_network_offering(self, network_offering): + if not network_offering: + return network_offering + + args = { + 'id': network_offering['id'], + 'state': self.module.params.get('state'), + 'displaytext': self.module.params.get('display_text'), + 'name': self.module.params.get('name'), + 'availability': self.module.params.get('availability'), + 'maxconnections': self.module.params.get('max_connections'), + } + + if args['state'] in ['enabled', 'disabled']: + args['state'] = args['state'].title() + else: + del args['state'] + + if self.has_changed(args, network_offering): + self.result['changed'] = True + + if not self.module.check_mode: + res = self.query_api('updateNetworkOffering', **args) + network_offering = res['networkoffering'] + + return network_offering + + def get_result(self, network_offering): + super(AnsibleCloudStackNetworkOffering, self).get_result(network_offering) + if network_offering: + self.result['egress_default_policy'] = 'allow' if network_offering.get('egressdefaultpolicy') else 'deny' + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + state=dict(choices=['enabled', 'present', 'disabled', 'absent'], default='present'), + display_text=dict(), + guest_ip_type=dict(choices=['Shared', 'Isolated']), + name=dict(required=True), + supported_services=dict(type='list', elements='str', aliases=['supported_service'], choices=[ + 'Dns', + 'PortForwarding', + 'Dhcp', + 'SourceNat', + 'UserData', + 'Firewall', + 'StaticNat', + 'Vpn', + 'Lb', + ]), + traffic_type=dict(default='Guest'), + availability=dict(), + conserve_mode=dict(type='bool'), + details=dict(type='list', elements='dict'), + egress_default_policy=dict(choices=['allow', 'deny']), + persistent=dict(type='bool'), + keepalive_enabled=dict(type='bool'), + max_connections=dict(type='int'), + network_rate=dict(type='int'), + service_capabilities=dict(type='list', elements='str', aliases=['service_capability']), + service_offering=dict(), + service_providers=dict(type='list', elements='dict', aliases=['service_provider']), + specify_ip_ranges=dict(type='bool'), + specify_vlan=dict(type='bool'), + for_vpc=dict(type='bool'), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_network_offering = AnsibleCloudStackNetworkOffering(module) + + state = module.params.get('state') + if state in ['absent']: + network_offering = acs_network_offering.delete_network_offering() + else: + network_offering = acs_network_offering.create_or_update() + + result = acs_network_offering.get_result(network_offering) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_physical_network.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_physical_network.py new file mode 100644 index 00000000..2148afc2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_physical_network.py @@ -0,0 +1,473 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017, Netservers Ltd. <support@netservers.co.uk> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_physical_network +short_description: Manages physical networks on Apache CloudStack based clouds. +description: + - Create, update and remove networks. + - Enabled and disabled Network Service Providers + - Enables Internal LoadBalancer and VPC/VirtualRouter elements as required +author: + - Netservers Ltd. (@netservers) + - Patryk Cichy (@PatTheSilent) +version_added: 0.1.0 +options: + name: + description: + - Name of the physical network. + required: true + aliases: + - physical_network + type: str + zone: + description: + - Name of the zone in which the network belongs. + - If not set, default zone is used. + type: str + broadcast_domain_range: + description: + - broadcast domain range for the physical network[Pod or Zone]. + choices: [ POD, ZONE ] + type: str + domain: + description: + - Domain the network is owned by. + type: str + isolation_method: + description: + - Isolation method for the physical network. + choices: [ VLAN, GRE, L3 ] + type: str + network_speed: + description: + - The speed for the physical network. + choices: [1G, 10G] + type: str + tags: + description: + - A tag to identify this network. + - Physical networks support only one tag. + - To remove an existing tag pass an empty string. + aliases: + - tag + type: str + vlan: + description: + - The VLAN/VNI Ranges of the physical network. + type: str + nsps_enabled: + description: + - List of Network Service Providers to enable. + type: list + elements: str + nsps_disabled: + description: + - List of Network Service Providers to disable. + type: list + elements: str + state: + description: + - State of the physical network. + default: present + type: str + choices: [ present, absent, disabled, enabled ] + poll_async: + description: + - Poll async jobs until job has finished. + default: yes + type: bool +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Ensure a network is present + ngine_io.cloudstack.cs_physical_network: + name: net01 + zone: zone01 + isolation_method: VLAN + broadcast_domain_range: ZONE + +- name: Set a tag on a network + ngine_io.cloudstack.cs_physical_network: + name: net01 + tag: overlay + +- name: Remove tag on a network + ngine_io.cloudstack.cs_physical_network: + name: net01 + tag: "" + +- name: Ensure a network is enabled with specific nsps enabled + ngine_io.cloudstack.cs_physical_network: + name: net01 + zone: zone01 + isolation_method: VLAN + vlan: 100-200,300-400 + broadcast_domain_range: ZONE + state: enabled + nsps_enabled: + - virtualrouter + - internallbvm + - vpcvirtualrouter + +- name: Ensure a network is disabled + ngine_io.cloudstack.cs_physical_network: + name: net01 + zone: zone01 + state: disabled + +- name: Ensure a network is enabled + ngine_io.cloudstack.cs_physical_network: + name: net01 + zone: zone01 + state: enabled + +- name: Ensure a network is absent + ngine_io.cloudstack.cs_physical_network: + name: net01 + zone: zone01 + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the network. + returned: success + type: str + sample: 3f8f25cd-c498-443f-9058-438cfbcbff50 +name: + description: Name of the network. + returned: success + type: str + sample: net01 +state: + description: State of the network [Enabled/Disabled]. + returned: success + type: str + sample: Enabled +broadcast_domain_range: + description: broadcastdomainrange of the network [POD / ZONE]. + returned: success + type: str + sample: ZONE +isolation_method: + description: isolationmethod of the network [VLAN/GRE/L3]. + returned: success + type: str + sample: VLAN +network_speed: + description: networkspeed of the network [1G/10G]. + returned: success + type: str + sample: 1G +zone: + description: Name of zone the physical network is in. + returned: success + type: str + sample: ch-gva-2 +domain: + description: Name of domain the network is in. + returned: success + type: str + sample: domain1 +nsps: + description: list of enabled or disabled Network Service Providers + type: complex + returned: on enabling/disabling of Network Service Providers + contains: + enabled: + description: list of Network Service Providers that were enabled + returned: on Network Service Provider enabling + type: list + sample: + - virtualrouter + disabled: + description: list of Network Service Providers that were disabled + returned: on Network Service Provider disabling + type: list + sample: + - internallbvm +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackPhysicalNetwork(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackPhysicalNetwork, self).__init__(module) + self.returns = { + 'isolationmethods': 'isolation_method', + 'broadcastdomainrange': 'broadcast_domain_range', + 'networkspeed': 'network_speed', + 'vlan': 'vlan', + 'tags': 'tags', + } + self.nsps = [] + self.vrouters = None + self.loadbalancers = None + + def _get_common_args(self): + args = { + 'name': self.module.params.get('name'), + 'isolationmethods': self.module.params.get('isolation_method'), + 'broadcastdomainrange': self.module.params.get('broadcast_domain_range'), + 'networkspeed': self.module.params.get('network_speed'), + 'tags': self.module.params.get('tags'), + 'vlan': self.module.params.get('vlan'), + } + + state = self.module.params.get('state') + if state in ['enabled', 'disabled']: + args['state'] = state.capitalize() + return args + + def get_physical_network(self, key=None): + physical_network = self.module.params.get('name') + if self.physical_network: + return self._get_by_key(key, self.physical_network) + + args = { + 'zoneid': self.get_zone(key='id') + } + physical_networks = self.query_api('listPhysicalNetworks', **args) + if physical_networks: + for net in physical_networks['physicalnetwork']: + if physical_network.lower() in [net['name'].lower(), net['id']]: + self.physical_network = net + self.result['physical_network'] = net['name'] + break + + return self._get_by_key(key, self.physical_network) + + def get_nsp(self, name=None): + if not self.nsps: + args = { + 'physicalnetworkid': self.get_physical_network(key='id') + } + res = self.query_api('listNetworkServiceProviders', **args) + + self.nsps = res['networkserviceprovider'] + + names = [] + for nsp in self.nsps: + names.append(nsp['name']) + if nsp['name'].lower() == name.lower(): + return nsp + + self.module.fail_json(msg="Failed: '{0}' not in network service providers list '[{1}]'".format(name, names)) + + def update_nsp(self, name=None, state=None, service_list=None): + nsp = self.get_nsp(name) + if not service_list and nsp['state'] == state: + return nsp + + args = { + 'id': nsp['id'], + 'servicelist': service_list, + 'state': state + } + if not self.module.check_mode: + res = self.query_api('updateNetworkServiceProvider', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + nsp = self.poll_job(res, 'networkserviceprovider') + + self.result['changed'] = True + return nsp + + def get_vrouter_element(self, nsp_name='virtualrouter'): + nsp = self.get_nsp(nsp_name) + nspid = nsp['id'] + if self.vrouters is None: + self.vrouters = dict() + res = self.query_api('listVirtualRouterElements', ) + for vrouter in res['virtualrouterelement']: + self.vrouters[vrouter['nspid']] = vrouter + + if nspid not in self.vrouters: + self.module.fail_json(msg="Failed: No VirtualRouterElement found for nsp '%s'" % nsp_name) + + return self.vrouters[nspid] + + def get_loadbalancer_element(self, nsp_name='internallbvm'): + nsp = self.get_nsp(nsp_name) + nspid = nsp['id'] + if self.loadbalancers is None: + self.loadbalancers = dict() + res = self.query_api('listInternalLoadBalancerElements', ) + for loadbalancer in res['internalloadbalancerelement']: + self.loadbalancers[loadbalancer['nspid']] = loadbalancer + + if nspid not in self.loadbalancers: + self.module.fail_json(msg="Failed: No Loadbalancer found for nsp '%s'" % nsp_name) + + return self.loadbalancers[nspid] + + def set_vrouter_element_state(self, enabled, nsp_name='virtualrouter'): + vrouter = self.get_vrouter_element(nsp_name) + if vrouter['enabled'] == enabled: + return vrouter + + args = { + 'id': vrouter['id'], + 'enabled': enabled + } + if not self.module.check_mode: + res = self.query_api('configureVirtualRouterElement', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + vrouter = self.poll_job(res, 'virtualrouterelement') + + self.result['changed'] = True + return vrouter + + def set_loadbalancer_element_state(self, enabled, nsp_name='internallbvm'): + loadbalancer = self.get_loadbalancer_element(nsp_name=nsp_name) + if loadbalancer['enabled'] == enabled: + return loadbalancer + + args = { + 'id': loadbalancer['id'], + 'enabled': enabled + } + if not self.module.check_mode: + res = self.query_api('configureInternalLoadBalancerElement', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + loadbalancer = self.poll_job(res, 'internalloadbalancerelement') + + self.result['changed'] = True + return loadbalancer + + def present_network(self): + network = self.get_physical_network() + if network: + network = self._update_network() + else: + network = self._create_network() + return network + + def _create_network(self): + self.result['changed'] = True + args = dict(zoneid=self.get_zone(key='id')) + args.update(self._get_common_args()) + if self.get_domain(key='id'): + args['domainid'] = self.get_domain(key='id') + + if not self.module.check_mode: + resource = self.query_api('createPhysicalNetwork', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + self.network = self.poll_job(resource, 'physicalnetwork') + + return self.network + + def _update_network(self): + network = self.get_physical_network() + + args = dict(id=network['id']) + args.update(self._get_common_args()) + + if self.has_changed(args, network): + self.result['changed'] = True + + if not self.module.check_mode: + resource = self.query_api('updatePhysicalNetwork', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + self.physical_network = self.poll_job(resource, 'physicalnetwork') + return self.physical_network + + def absent_network(self): + physical_network = self.get_physical_network() + if physical_network: + self.result['changed'] = True + args = { + 'id': physical_network['id'], + } + if not self.module.check_mode: + resource = self.query_api('deletePhysicalNetwork', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(resource, 'success') + + return physical_network + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True, aliases=['physical_network']), + zone=dict(), + domain=dict(), + vlan=dict(), + nsps_disabled=dict(type='list', elements='str'), + nsps_enabled=dict(type='list', elements='str'), + network_speed=dict(choices=['1G', '10G']), + broadcast_domain_range=dict(choices=['POD', 'ZONE']), + isolation_method=dict(choices=['VLAN', 'GRE', 'L3']), + state=dict(choices=['present', 'enabled', 'disabled', 'absent'], default='present'), + tags=dict(aliases=['tag']), + poll_async=dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_network = AnsibleCloudStackPhysicalNetwork(module) + state = module.params.get('state') + nsps_disabled = module.params.get('nsps_disabled', []) + nsps_enabled = module.params.get('nsps_enabled', []) + + if state in ['absent']: + network = acs_network.absent_network() + else: + network = acs_network.present_network() + if nsps_disabled is not None: + for name in nsps_disabled: + acs_network.update_nsp(name=name, state='Disabled') + + if nsps_enabled is not None: + for nsp_name in nsps_enabled: + if nsp_name.lower() in ['virtualrouter', 'vpcvirtualrouter']: + acs_network.set_vrouter_element_state(enabled=True, nsp_name=nsp_name) + elif nsp_name.lower() == 'internallbvm': + acs_network.set_loadbalancer_element_state(enabled=True, nsp_name=nsp_name) + + acs_network.update_nsp(name=nsp_name, state='Enabled') + + result = acs_network.get_result(network) + + if nsps_enabled: + result['nsps_enabled'] = nsps_enabled + if nsps_disabled: + result['nsps_disabled'] = nsps_disabled + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_pod.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_pod.py new file mode 100644 index 00000000..90ab2ef5 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_pod.py @@ -0,0 +1,289 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_pod +short_description: Manages pods on Apache CloudStack based clouds. +description: + - Create, update, delete pods. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name of the pod. + type: str + required: true + id: + description: + - uuid of the existing pod. + type: str + start_ip: + description: + - Starting IP address for the Pod. + - Required on I(state=present) + type: str + end_ip: + description: + - Ending IP address for the Pod. + type: str + netmask: + description: + - Netmask for the Pod. + - Required on I(state=present) + type: str + gateway: + description: + - Gateway for the Pod. + - Required on I(state=present) + type: str + zone: + description: + - Name of the zone in which the pod belongs to. + - If not set, default zone is used. + type: str + state: + description: + - State of the pod. + type: str + default: present + choices: [ present, enabled, disabled, absent ] +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Ensure a pod is present + ngine_io.cloudstack.cs_pod: + name: pod1 + zone: ch-zrh-ix-01 + start_ip: 10.100.10.101 + gateway: 10.100.10.1 + netmask: 255.255.255.0 + +- name: Ensure a pod is disabled + ngine_io.cloudstack.cs_pod: + name: pod1 + zone: ch-zrh-ix-01 + state: disabled + +- name: Ensure a pod is enabled + ngine_io.cloudstack.cs_pod: + name: pod1 + zone: ch-zrh-ix-01 + state: enabled + +- name: Ensure a pod is absent + ngine_io.cloudstack.cs_pod: + name: pod1 + zone: ch-zrh-ix-01 + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the pod. + returned: success + type: str + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 +name: + description: Name of the pod. + returned: success + type: str + sample: pod01 +start_ip: + description: Starting IP of the pod. + returned: success + type: str + sample: 10.100.1.101 +end_ip: + description: Ending IP of the pod. + returned: success + type: str + sample: 10.100.1.254 +netmask: + description: Netmask of the pod. + returned: success + type: str + sample: 255.255.255.0 +gateway: + description: Gateway of the pod. + returned: success + type: str + sample: 10.100.1.1 +allocation_state: + description: State of the pod. + returned: success + type: str + sample: Enabled +zone: + description: Name of zone the pod is in. + returned: success + type: str + sample: ch-gva-2 +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackPod(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackPod, self).__init__(module) + self.returns = { + 'endip': 'end_ip', + 'startip': 'start_ip', + 'gateway': 'gateway', + 'netmask': 'netmask', + 'allocationstate': 'allocation_state', + } + self.pod = None + + def _get_common_pod_args(self): + args = { + 'name': self.module.params.get('name'), + 'zoneid': self.get_zone(key='id'), + 'startip': self.module.params.get('start_ip'), + 'endip': self.module.params.get('end_ip'), + 'netmask': self.module.params.get('netmask'), + 'gateway': self.module.params.get('gateway') + } + state = self.module.params.get('state') + if state in ['enabled', 'disabled']: + args['allocationstate'] = state.capitalize() + return args + + def get_pod(self): + if not self.pod: + args = { + 'zoneid': self.get_zone(key='id') + } + + uuid = self.module.params.get('id') + if uuid: + args['id'] = uuid + else: + args['name'] = self.module.params.get('name') + + pods = self.query_api('listPods', **args) + if pods: + for pod in pods['pod']: + if not args['name']: + self.pod = self._transform_ip_list(pod) + break + elif args['name'] == pod['name']: + self.pod = self._transform_ip_list(pod) + break + return self.pod + + def present_pod(self): + pod = self.get_pod() + if pod: + pod = self._update_pod() + else: + pod = self._create_pod() + return pod + + def _create_pod(self): + required_params = [ + 'start_ip', + 'netmask', + 'gateway', + ] + self.module.fail_on_missing_params(required_params=required_params) + + pod = None + self.result['changed'] = True + args = self._get_common_pod_args() + if not self.module.check_mode: + res = self.query_api('createPod', **args) + pod = res['pod'] + return pod + + def _update_pod(self): + pod = self.get_pod() + args = self._get_common_pod_args() + args['id'] = pod['id'] + + if self.has_changed(args, pod): + self.result['changed'] = True + + if not self.module.check_mode: + res = self.query_api('updatePod', **args) + pod = res['pod'] + return pod + + def absent_pod(self): + pod = self.get_pod() + if pod: + self.result['changed'] = True + + args = { + 'id': pod['id'] + } + if not self.module.check_mode: + self.query_api('deletePod', **args) + return pod + + def _transform_ip_list(self, resource): + """ Workaround for 4.11 return API break """ + keys = ['endip', 'startip'] + if resource: + for key in keys: + if key in resource and isinstance(resource[key], list): + resource[key] = resource[key][0] + return resource + + def get_result(self, pod): + pod = self._transform_ip_list(pod) + super(AnsibleCloudStackPod, self).get_result(pod) + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + id=dict(), + name=dict(required=True), + gateway=dict(), + netmask=dict(), + start_ip=dict(), + end_ip=dict(), + zone=dict(), + state=dict(choices=['present', 'enabled', 'disabled', 'absent'], default='present'), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_pod = AnsibleCloudStackPod(module) + state = module.params.get('state') + if state in ['absent']: + pod = acs_pod.absent_pod() + else: + pod = acs_pod.present_pod() + + result = acs_pod.get_result(pod) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_portforward.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_portforward.py new file mode 100644 index 00000000..3c80ccbb --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_portforward.py @@ -0,0 +1,398 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_portforward +short_description: Manages port forwarding rules on Apache CloudStack based clouds. +description: + - Create, update and remove port forwarding rules. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + ip_address: + description: + - Public IP address the rule is assigned to. + type: str + required: true + vm: + description: + - Name of virtual machine which we make the port forwarding rule for. + - Required if I(state=present). + type: str + state: + description: + - State of the port forwarding rule. + type: str + default: present + choices: [ present, absent ] + protocol: + description: + - Protocol of the port forwarding rule. + type: str + default: tcp + choices: [ tcp, udp ] + public_port: + description: + - Start public port for this rule. + type: int + required: true + public_end_port: + description: + - End public port for this rule. + - If not specified equal I(public_port). + type: int + private_port: + description: + - Start private port for this rule. + type: int + required: true + private_end_port: + description: + - End private port for this rule. + - If not specified equal I(private_port). + type: int + open_firewall: + description: + - Whether the firewall rule for public port should be created, while creating the new rule. + - Not supported when forwarding ports in a VPC. + - Use M(cs_firewall) for managing firewall rules. + default: no + type: bool + vm_guest_ip: + description: + - VM guest NIC secondary IP address for the port forwarding rule. + type: str + network: + description: + - Name of the network. Required when forwarding ports in a VPC. + type: str + vpc: + description: + - Name of the VPC. + type: str + domain: + description: + - Domain the I(vm) is related to. + type: str + account: + description: + - Account the I(vm) is related to. + type: str + project: + description: + - Name of the project the I(vm) is located in. + type: str + zone: + description: + - Name of the zone in which the virtual machine is in. + - If not set, default zone is used. + type: str + poll_async: + description: + - Poll async jobs until job has finished. + default: yes + type: bool + tags: + description: + - List of tags. Tags are a list of dictionaries having keys I(key) and I(value). + - "To delete all tags, set a empty list e.g. I(tags: [])." + type: list + elements: dict + aliases: [ tag ] +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: 1.2.3.4:80 -> web01:8080 + ngine_io.cloudstack.cs_portforward: + ip_address: 1.2.3.4 + vm: web01 + public_port: 80 + private_port: 8080 + +- name: forward SSH and open firewall + ngine_io.cloudstack.cs_portforward: + ip_address: '{{ public_ip }}' + vm: '{{ inventory_hostname }}' + public_port: '{{ ansible_ssh_port }}' + private_port: 22 + open_firewall: true + +- name: forward DNS traffic, but do not open firewall + ngine_io.cloudstack.cs_portforward: + ip_address: 1.2.3.4 + vm: '{{ inventory_hostname }}' + public_port: 53 + private_port: 53 + protocol: udp + +- name: remove ssh port forwarding + ngine_io.cloudstack.cs_portforward: + ip_address: 1.2.3.4 + public_port: 22 + private_port: 22 + state: absent + +- name: forward SSH in backend tier of VPC + ngine_io.cloudstack.cs_portforward: + ip_address: '{{ public_ip }}' + vm: '{{ inventory_hostname }}' + public_port: '{{ ansible_ssh_port }}' + private_port: 22 + vpc: myVPC + network: backend +''' + +RETURN = ''' +--- +id: + description: UUID of the public IP address. + returned: success + type: str + sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f +ip_address: + description: Public IP address. + returned: success + type: str + sample: 1.2.3.4 +protocol: + description: Protocol. + returned: success + type: str + sample: tcp +private_port: + description: Start port on the virtual machine's IP address. + returned: success + type: int + sample: 80 +private_end_port: + description: End port on the virtual machine's IP address. + returned: success + type: int + sample: 80 +public_port: + description: Start port on the public IP address. + returned: success + type: int + sample: 80 +public_end_port: + description: End port on the public IP address. + returned: success + type: int + sample: 80 +tags: + description: Tags related to the port forwarding. + returned: success + type: list + sample: [] +vm_name: + description: Name of the virtual machine. + returned: success + type: str + sample: web-01 +vm_display_name: + description: Display name of the virtual machine. + returned: success + type: str + sample: web-01 +vm_guest_ip: + description: IP of the virtual machine. + returned: success + type: str + sample: 10.101.65.152 +vpc: + description: Name of the VPC. + returned: success + type: str + sample: my_vpc +network: + description: Name of the network. + returned: success + type: str + sample: dmz +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import AnsibleCloudStack, cs_argument_spec, cs_required_together + + +class AnsibleCloudStackPortforwarding(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackPortforwarding, self).__init__(module) + self.returns = { + 'virtualmachinedisplayname': 'vm_display_name', + 'virtualmachinename': 'vm_name', + 'ipaddress': 'ip_address', + 'vmguestip': 'vm_guest_ip', + 'publicip': 'public_ip', + 'protocol': 'protocol', + } + # these values will be casted to int + self.returns_to_int = { + 'publicport': 'public_port', + 'publicendport': 'public_end_port', + 'privateport': 'private_port', + 'privateendport': 'private_end_port', + } + self.portforwarding_rule = None + + def get_portforwarding_rule(self): + if not self.portforwarding_rule: + protocol = self.module.params.get('protocol') + public_port = self.module.params.get('public_port') + + args = { + 'ipaddressid': self.get_ip_address(key='id'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + } + portforwarding_rules = self.query_api('listPortForwardingRules', **args) + + if portforwarding_rules and 'portforwardingrule' in portforwarding_rules: + for rule in portforwarding_rules['portforwardingrule']: + if (protocol == rule['protocol'] and + public_port == int(rule['publicport'])): + self.portforwarding_rule = rule + break + return self.portforwarding_rule + + def present_portforwarding_rule(self): + portforwarding_rule = self.get_portforwarding_rule() + if portforwarding_rule: + portforwarding_rule = self.update_portforwarding_rule(portforwarding_rule) + else: + portforwarding_rule = self.create_portforwarding_rule() + + if portforwarding_rule: + portforwarding_rule = self.ensure_tags(resource=portforwarding_rule, resource_type='PortForwardingRule') + self.portforwarding_rule = portforwarding_rule + + return portforwarding_rule + + def create_portforwarding_rule(self): + args = { + 'protocol': self.module.params.get('protocol'), + 'publicport': self.module.params.get('public_port'), + 'publicendport': self.get_or_fallback('public_end_port', 'public_port'), + 'privateport': self.module.params.get('private_port'), + 'privateendport': self.get_or_fallback('private_end_port', 'private_port'), + 'openfirewall': self.module.params.get('open_firewall'), + 'vmguestip': self.get_vm_guest_ip(), + 'ipaddressid': self.get_ip_address(key='id'), + 'virtualmachineid': self.get_vm(key='id'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'networkid': self.get_network(key='id'), + } + + portforwarding_rule = None + self.result['changed'] = True + if not self.module.check_mode: + portforwarding_rule = self.query_api('createPortForwardingRule', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + portforwarding_rule = self.poll_job(portforwarding_rule, 'portforwardingrule') + return portforwarding_rule + + def update_portforwarding_rule(self, portforwarding_rule): + args = { + 'protocol': self.module.params.get('protocol'), + 'publicport': self.module.params.get('public_port'), + 'publicendport': self.get_or_fallback('public_end_port', 'public_port'), + 'privateport': self.module.params.get('private_port'), + 'privateendport': self.get_or_fallback('private_end_port', 'private_port'), + 'vmguestip': self.get_vm_guest_ip(), + 'ipaddressid': self.get_ip_address(key='id'), + 'virtualmachineid': self.get_vm(key='id'), + 'networkid': self.get_network(key='id'), + } + + if self.has_changed(args, portforwarding_rule): + self.result['changed'] = True + if not self.module.check_mode: + # API broken in 4.2.1?, workaround using remove/create instead of update + # portforwarding_rule = self.query_api('updatePortForwardingRule', **args) + self.absent_portforwarding_rule() + portforwarding_rule = self.query_api('createPortForwardingRule', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + portforwarding_rule = self.poll_job(portforwarding_rule, 'portforwardingrule') + return portforwarding_rule + + def absent_portforwarding_rule(self): + portforwarding_rule = self.get_portforwarding_rule() + + if portforwarding_rule: + self.result['changed'] = True + args = { + 'id': portforwarding_rule['id'], + } + if not self.module.check_mode: + res = self.query_api('deletePortForwardingRule', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(res, 'portforwardingrule') + return portforwarding_rule + + def get_result(self, portforwarding_rule): + super(AnsibleCloudStackPortforwarding, self).get_result(portforwarding_rule) + if portforwarding_rule: + for search_key, return_key in self.returns_to_int.items(): + if search_key in portforwarding_rule: + self.result[return_key] = int(portforwarding_rule[search_key]) + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + ip_address=dict(required=True), + protocol=dict(choices=['tcp', 'udp'], default='tcp'), + public_port=dict(type='int', required=True), + public_end_port=dict(type='int'), + private_port=dict(type='int', required=True), + private_end_port=dict(type='int'), + state=dict(choices=['present', 'absent'], default='present'), + open_firewall=dict(type='bool', default=False), + vm_guest_ip=dict(), + vm=dict(), + vpc=dict(), + network=dict(), + zone=dict(), + domain=dict(), + account=dict(), + project=dict(), + poll_async=dict(type='bool', default=True), + tags=dict(type='list', elements='dict', aliases=['tag']), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_pf = AnsibleCloudStackPortforwarding(module) + state = module.params.get('state') + if state in ['absent']: + pf_rule = acs_pf.absent_portforwarding_rule() + else: + pf_rule = acs_pf.present_portforwarding_rule() + + result = acs_pf.get_result(pf_rule) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_project.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_project.py new file mode 100644 index 00000000..c52444ed --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_project.py @@ -0,0 +1,271 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_project +short_description: Manages projects on Apache CloudStack based clouds. +description: + - Create, update, suspend, activate and remove projects. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name of the project. + type: str + required: true + display_text: + description: + - Display text of the project. + - If not specified, I(name) will be used as I(display_text). + type: str + state: + description: + - State of the project. + type: str + default: present + choices: [ present, absent, active, suspended ] + domain: + description: + - Domain the project is related to. + type: str + account: + description: + - Account the project is related to. + type: str + tags: + description: + - List of tags. Tags are a list of dictionaries having keys I(key) and I(value). + - "If you want to delete all tags, set a empty list e.g. I(tags: [])." + type: list + elements: dict + aliases: [ tag ] + poll_async: + description: + - Poll async jobs until job has finished. + type: bool + default: yes +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Create a project + ngine_io.cloudstack.cs_project: + name: web + tags: + - { key: admin, value: john } + - { key: foo, value: bar } + +- name: Rename a project + ngine_io.cloudstack.cs_project: + name: web + display_text: my web project + +- name: Suspend an existing project + ngine_io.cloudstack.cs_project: + name: web + state: suspended + +- name: Activate an existing project + ngine_io.cloudstack.cs_project: + name: web + state: active + +- name: Remove a project + ngine_io.cloudstack.cs_project: + name: web + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the project. + returned: success + type: str + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 +name: + description: Name of the project. + returned: success + type: str + sample: web project +display_text: + description: Display text of the project. + returned: success + type: str + sample: web project +state: + description: State of the project. + returned: success + type: str + sample: Active +domain: + description: Domain the project is related to. + returned: success + type: str + sample: example domain +account: + description: Account the project is related to. + returned: success + type: str + sample: example account +tags: + description: List of resource tags associated with the project. + returned: success + type: list + sample: '[ { "key": "foo", "value": "bar" } ]' +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackProject(AnsibleCloudStack): + + def get_project(self): + if not self.project: + project = self.module.params.get('name') + + args = { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'fetch_list': True, + } + projects = self.query_api('listProjects', **args) + if projects: + for p in projects: + if project.lower() in [p['name'].lower(), p['id']]: + self.project = p + break + return self.project + + def present_project(self): + project = self.get_project() + if not project: + project = self.create_project(project) + else: + project = self.update_project(project) + if project: + project = self.ensure_tags(resource=project, resource_type='project') + # refresh resource + self.project = project + return project + + def update_project(self, project): + args = { + 'id': project['id'], + 'displaytext': self.get_or_fallback('display_text', 'name') + } + if self.has_changed(args, project): + self.result['changed'] = True + if not self.module.check_mode: + project = self.query_api('updateProject', **args) + + poll_async = self.module.params.get('poll_async') + if project and poll_async: + project = self.poll_job(project, 'project') + return project + + def create_project(self, project): + self.result['changed'] = True + + args = { + 'name': self.module.params.get('name'), + 'displaytext': self.get_or_fallback('display_text', 'name'), + 'account': self.get_account('name'), + 'domainid': self.get_domain('id') + } + if not self.module.check_mode: + project = self.query_api('createProject', **args) + + poll_async = self.module.params.get('poll_async') + if project and poll_async: + project = self.poll_job(project, 'project') + return project + + def state_project(self, state='active'): + project = self.present_project() + + if project['state'].lower() != state: + self.result['changed'] = True + + args = { + 'id': project['id'] + } + if not self.module.check_mode: + if state == 'suspended': + project = self.query_api('suspendProject', **args) + else: + project = self.query_api('activateProject', **args) + + poll_async = self.module.params.get('poll_async') + if project and poll_async: + project = self.poll_job(project, 'project') + return project + + def absent_project(self): + project = self.get_project() + if project: + self.result['changed'] = True + + args = { + 'id': project['id'] + } + if not self.module.check_mode: + res = self.query_api('deleteProject', **args) + + poll_async = self.module.params.get('poll_async') + if res and poll_async: + res = self.poll_job(res, 'project') + return project + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + display_text=dict(), + state=dict(choices=['present', 'absent', 'active', 'suspended'], default='present'), + domain=dict(), + account=dict(), + poll_async=dict(type='bool', default=True), + tags=dict(type='list', elements='dict', aliases=['tag']), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_project = AnsibleCloudStackProject(module) + + state = module.params.get('state') + if state in ['absent']: + project = acs_project.absent_project() + + elif state in ['active', 'suspended']: + project = acs_project.state_project(state=state) + + else: + project = acs_project.present_project() + + result = acs_project.get_result(project) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_region.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_region.py new file mode 100644 index 00000000..327f7c1e --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_region.py @@ -0,0 +1,187 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_region +short_description: Manages regions on Apache CloudStack based clouds. +description: + - Add, update and remove regions. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + id: + description: + - ID of the region. + - Must be an number (int). + type: int + required: true + name: + description: + - Name of the region. + - Required if I(state=present) + type: str + endpoint: + description: + - Endpoint URL of the region. + - Required if I(state=present) + type: str + state: + description: + - State of the region. + type: str + default: present + choices: [ present, absent ] +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: create a region + ngine_io.cloudstack.cs_region: + id: 2 + name: geneva + endpoint: https://cloud.gva.example.com + +- name: remove a region with ID 2 + ngine_io.cloudstack.cs_region: + id: 2 + state: absent +''' + +RETURN = ''' +--- +id: + description: ID of the region. + returned: success + type: int + sample: 1 +name: + description: Name of the region. + returned: success + type: str + sample: local +endpoint: + description: Endpoint of the region. + returned: success + type: str + sample: http://cloud.example.com +gslb_service_enabled: + description: Whether the GSLB service is enabled or not. + returned: success + type: bool + sample: true +portable_ip_service_enabled: + description: Whether the portable IP service is enabled or not. + returned: success + type: bool + sample: true +''' + + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackRegion(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackRegion, self).__init__(module) + self.returns = { + 'endpoint': 'endpoint', + 'gslbserviceenabled': 'gslb_service_enabled', + 'portableipserviceenabled': 'portable_ip_service_enabled', + } + + def get_region(self): + id = self.module.params.get('id') + regions = self.query_api('listRegions', id=id) + if regions: + return regions['region'][0] + return None + + def present_region(self): + region = self.get_region() + if not region: + region = self._create_region(region=region) + else: + region = self._update_region(region=region) + return region + + def _create_region(self, region): + self.result['changed'] = True + args = { + 'id': self.module.params.get('id'), + 'name': self.module.params.get('name'), + 'endpoint': self.module.params.get('endpoint') + } + if not self.module.check_mode: + res = self.query_api('addRegion', **args) + region = res['region'] + return region + + def _update_region(self, region): + args = { + 'id': self.module.params.get('id'), + 'name': self.module.params.get('name'), + 'endpoint': self.module.params.get('endpoint') + } + if self.has_changed(args, region): + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('updateRegion', **args) + region = res['region'] + return region + + def absent_region(self): + region = self.get_region() + if region: + self.result['changed'] = True + if not self.module.check_mode: + self.query_api('removeRegion', id=region['id']) + return region + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + id=dict(required=True, type='int'), + name=dict(), + endpoint=dict(), + state=dict(choices=['present', 'absent'], default='present'), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + required_if=[ + ('state', 'present', ['name', 'endpoint']), + ], + supports_check_mode=True + ) + + acs_region = AnsibleCloudStackRegion(module) + + state = module.params.get('state') + if state == 'absent': + region = acs_region.absent_region() + else: + region = acs_region.present_region() + + result = acs_region.get_result(region) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_resourcelimit.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_resourcelimit.py new file mode 100644 index 00000000..fdca3bdd --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_resourcelimit.py @@ -0,0 +1,202 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_resourcelimit +short_description: Manages resource limits on Apache CloudStack based clouds. +description: + - Manage limits of resources for domains, accounts and projects. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + resource_type: + description: + - Type of the resource. + type: str + required: true + choices: + - instance + - ip_address + - volume + - snapshot + - template + - network + - vpc + - cpu + - memory + - primary_storage + - secondary_storage + aliases: [ type ] + limit: + description: + - Maximum number of the resource. + - Default is unlimited C(-1). + type: int + default: -1 + aliases: [ max ] + domain: + description: + - Domain the resource is related to. + type: str + account: + description: + - Account the resource is related to. + type: str + project: + description: + - Name of the project the resource is related to. + type: str +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Update a resource limit for instances of a domain + ngine_io.cloudstack.cs_resourcelimit: + type: instance + limit: 10 + domain: customers + +- name: Update a resource limit for instances of an account + ngine_io.cloudstack.cs_resourcelimit: + type: instance + limit: 12 + account: moserre + domain: customers +''' + +RETURN = ''' +--- +recource_type: + description: Type of the resource + returned: success + type: str + sample: instance +limit: + description: Maximum number of the resource. + returned: success + type: int + sample: -1 +domain: + description: Domain the resource is related to. + returned: success + type: str + sample: example domain +account: + description: Account the resource is related to. + returned: success + type: str + sample: example account +project: + description: Project the resource is related to. + returned: success + type: str + sample: example project +''' + +# import cloudstack common +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_required_together, + cs_argument_spec +) + + +RESOURCE_TYPES = { + 'instance': 0, + 'ip_address': 1, + 'volume': 2, + 'snapshot': 3, + 'template': 4, + 'network': 6, + 'vpc': 7, + 'cpu': 8, + 'memory': 9, + 'primary_storage': 10, + 'secondary_storage': 11, +} + + +class AnsibleCloudStackResourceLimit(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackResourceLimit, self).__init__(module) + self.returns = { + 'max': 'limit', + } + + def get_resource_type(self): + resource_type = self.module.params.get('resource_type') + return RESOURCE_TYPES.get(resource_type) + + def get_resource_limit(self): + args = { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'resourcetype': self.get_resource_type() + } + resource_limit = self.query_api('listResourceLimits', **args) + if resource_limit: + if 'limit' in resource_limit['resourcelimit'][0]: + resource_limit['resourcelimit'][0]['limit'] = int(resource_limit['resourcelimit'][0]) + return resource_limit['resourcelimit'][0] + self.module.fail_json(msg="Resource limit type '%s' not found." % self.module.params.get('resource_type')) + + def update_resource_limit(self): + resource_limit = self.get_resource_limit() + + args = { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'resourcetype': self.get_resource_type(), + 'max': self.module.params.get('limit', -1) + } + + if self.has_changed(args, resource_limit): + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('updateResourceLimit', **args) + resource_limit = res['resourcelimit'] + return resource_limit + + def get_result(self, resource_limit): + self.result = super(AnsibleCloudStackResourceLimit, self).get_result(resource_limit) + self.result['resource_type'] = self.module.params.get('resource_type') + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + resource_type=dict(required=True, choices=list(RESOURCE_TYPES.keys()), aliases=['type']), + limit=dict(default=-1, aliases=['max'], type='int'), + domain=dict(), + account=dict(), + project=dict(), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_resource_limit = AnsibleCloudStackResourceLimit(module) + resource_limit = acs_resource_limit.update_resource_limit() + result = acs_resource_limit.get_result(resource_limit) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_role.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_role.py new file mode 100644 index 00000000..01f23479 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_role.py @@ -0,0 +1,205 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_role +short_description: Manages user roles on Apache CloudStack based clouds. +description: + - Create, update, delete user roles. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name of the role. + type: str + required: true + uuid: + description: + - ID of the role. + - If provided, I(uuid) is used as key. + type: str + aliases: [ id ] + role_type: + description: + - Type of the role. + - Only considered for creation. + type: str + default: User + choices: [ User, DomainAdmin, ResourceAdmin, Admin ] + description: + description: + - Description of the role. + type: str + state: + description: + - State of the role. + type: str + default: present + choices: [ present, absent ] +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Ensure an user role is present + ngine_io.cloudstack.cs_role: + name: myrole_user + +- name: Ensure a role having particular ID is named as myrole_user + ngine_io.cloudstack.cs_role: + name: myrole_user + id: 04589590-ac63-4ffc-93f5-b698b8ac38b6 + +- name: Ensure a role is absent + ngine_io.cloudstack.cs_role: + name: myrole_user + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the role. + returned: success + type: str + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 +name: + description: Name of the role. + returned: success + type: str + sample: myrole +description: + description: Description of the role. + returned: success + type: str + sample: "This is my role description" +role_type: + description: Type of the role. + returned: success + type: str + sample: User +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackRole(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackRole, self).__init__(module) + self.returns = { + 'type': 'role_type', + } + + def get_role(self): + uuid = self.module.params.get('uuid') + if uuid: + args = { + 'id': uuid, + } + roles = self.query_api('listRoles', **args) + if roles: + return roles['role'][0] + else: + args = { + 'name': self.module.params.get('name'), + } + roles = self.query_api('listRoles', **args) + if roles: + return roles['role'][0] + return None + + def present_role(self): + role = self.get_role() + if role: + role = self._update_role(role) + else: + role = self._create_role(role) + return role + + def _create_role(self, role): + self.result['changed'] = True + args = { + 'name': self.module.params.get('name'), + 'type': self.module.params.get('role_type'), + 'description': self.module.params.get('description'), + } + if not self.module.check_mode: + res = self.query_api('createRole', **args) + role = res['role'] + return role + + def _update_role(self, role): + args = { + 'id': role['id'], + 'name': self.module.params.get('name'), + 'description': self.module.params.get('description'), + } + if self.has_changed(args, role): + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('updateRole', **args) + + # The API as in 4.9 does not return an updated role yet + if 'role' not in res: + role = self.get_role() + else: + role = res['role'] + return role + + def absent_role(self): + role = self.get_role() + if role: + self.result['changed'] = True + args = { + 'id': role['id'], + } + if not self.module.check_mode: + self.query_api('deleteRole', **args) + return role + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + uuid=dict(aliases=['id']), + name=dict(required=True), + description=dict(), + role_type=dict(choices=['User', 'DomainAdmin', 'ResourceAdmin', 'Admin'], default='User'), + state=dict(choices=['present', 'absent'], default='present'), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_role = AnsibleCloudStackRole(module) + state = module.params.get('state') + if state == 'absent': + role = acs_role.absent_role() + else: + role = acs_role.present_role() + + result = acs_role.get_result(role) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_role_permission.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_role_permission.py new file mode 100644 index 00000000..8755f158 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_role_permission.py @@ -0,0 +1,345 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017, David Passante (@dpassante) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_role_permission +short_description: Manages role permissions on Apache CloudStack based clouds. +description: + - Create, update and remove CloudStack role permissions. + - Managing role permissions only supported in CloudStack >= 4.9. +author: David Passante (@dpassante) +version_added: 0.1.0 +options: + name: + description: + - The API name of the permission. + type: str + required: true + role: + description: + - Name or ID of the role. + type: str + required: true + permission: + description: + - The rule permission, allow or deny. Defaulted to deny. + type: str + choices: [ allow, deny ] + default: deny + state: + description: + - State of the role permission. + type: str + choices: [ present, absent ] + default: present + description: + description: + - The description of the role permission. + type: str + parent: + description: + - The parent role permission uuid. use 0 to move this rule at the top of the list. + type: str +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Create a role permission + ngine_io.cloudstack.cs_role_permission: + role: My_Custom_role + name: createVPC + permission: allow + description: My comments + +- name: Remove a role permission + ngine_io.cloudstack.cs_role_permission: + state: absent + role: My_Custom_role + name: createVPC + +- name: Update a system role permission + ngine_io.cloudstack.cs_role_permission: + role: Domain Admin + name: createVPC + permission: deny + +- name: Update rules order. Move the rule at the top of list + ngine_io.cloudstack.cs_role_permission: + role: Domain Admin + name: createVPC + parent: 0 +''' + +RETURN = ''' +--- +id: + description: The ID of the role permission. + returned: success + type: str + sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f +name: + description: The API name of the permission. + returned: success + type: str + sample: createVPC +permission: + description: The permission type of the api name. + returned: success + type: str + sample: allow +role_id: + description: The ID of the role to which the role permission belongs. + returned: success + type: str + sample: c6f7a5fc-43f8-11e5-a151-feff819cdc7f +description: + description: The description of the role permission + returned: success + type: str + sample: Deny createVPC for users +''' + +from distutils.version import LooseVersion + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackRolePermission(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackRolePermission, self).__init__(module) + cloudstack_min_version = LooseVersion('4.9.2') + + self.returns = { + 'id': 'id', + 'roleid': 'role_id', + 'rule': 'name', + 'permission': 'permission', + 'description': 'description', + } + self.role_permission = None + + self.cloudstack_version = self._cloudstack_ver() + + if self.cloudstack_version < cloudstack_min_version: + self.fail_json(msg="This module requires CloudStack >= %s." % cloudstack_min_version) + + def _cloudstack_ver(self): + capabilities = self.get_capabilities() + return LooseVersion(capabilities['cloudstackversion']) + + def _get_role_id(self): + role = self.module.params.get('role') + if not role: + return None + + res = self.query_api('listRoles') + roles = res['role'] + if roles: + for r in roles: + if role in [r['name'], r['id']]: + return r['id'] + self.fail_json(msg="Role '%s' not found" % role) + + def _get_role_perm(self): + role_permission = self.role_permission + + args = { + 'roleid': self._get_role_id(), + } + + rp = self.query_api('listRolePermissions', **args) + + if rp: + role_permission = rp['rolepermission'] + + return role_permission + + def _get_rule(self, rule=None): + if not rule: + rule = self.module.params.get('name') + + if self._get_role_perm(): + for _rule in self._get_role_perm(): + if rule == _rule['rule'] or rule == _rule['id']: + return _rule + + return None + + def _get_rule_order(self): + perms = self._get_role_perm() + rules = [] + + if perms: + for i, rule in enumerate(perms): + rules.append(rule['id']) + + return rules + + def replace_rule(self): + old_rule = self._get_rule() + + if old_rule: + rules_order = self._get_rule_order() + old_pos = rules_order.index(old_rule['id']) + + self.remove_role_perm() + + new_rule = self.create_role_perm() + + if new_rule: + perm_order = self.order_permissions(int(old_pos - 1), new_rule['id']) + + return perm_order + + return None + + def order_permissions(self, parent, rule_id): + rules = self._get_rule_order() + + if isinstance(parent, int): + parent_pos = parent + elif parent == '0': + parent_pos = -1 + else: + parent_rule = self._get_rule(parent) + if not parent_rule: + self.fail_json(msg="Parent rule '%s' not found" % parent) + + parent_pos = rules.index(parent_rule['id']) + + r_id = rules.pop(rules.index(rule_id)) + + rules.insert((parent_pos + 1), r_id) + rules = ','.join(map(str, rules)) + + return rules + + def create_or_update_role_perm(self): + role_permission = self._get_rule() + + if not role_permission: + role_permission = self.create_role_perm() + else: + role_permission = self.update_role_perm(role_permission) + + return role_permission + + def create_role_perm(self): + role_permission = None + + self.result['changed'] = True + + args = { + 'rule': self.module.params.get('name'), + 'description': self.module.params.get('description'), + 'roleid': self._get_role_id(), + 'permission': self.module.params.get('permission'), + } + + if not self.module.check_mode: + res = self.query_api('createRolePermission', **args) + role_permission = res['rolepermission'] + + return role_permission + + def update_role_perm(self, role_perm): + perm_order = None + + if not self.module.params.get('parent'): + args = { + 'ruleid': role_perm['id'], + 'roleid': role_perm['roleid'], + 'permission': self.module.params.get('permission'), + } + + if self.has_changed(args, role_perm, only_keys=['permission']): + self.result['changed'] = True + + if not self.module.check_mode: + if self.cloudstack_version >= LooseVersion('4.11.0'): + self.query_api('updateRolePermission', **args) + role_perm = self._get_rule() + else: + perm_order = self.replace_rule() + else: + perm_order = self.order_permissions(self.module.params.get('parent'), role_perm['id']) + + if perm_order: + args = { + 'roleid': role_perm['roleid'], + 'ruleorder': perm_order, + } + + self.result['changed'] = True + + if not self.module.check_mode: + self.query_api('updateRolePermission', **args) + role_perm = self._get_rule() + + return role_perm + + def remove_role_perm(self): + role_permission = self._get_rule() + + if role_permission: + self.result['changed'] = True + + args = { + 'id': role_permission['id'], + } + + if not self.module.check_mode: + self.query_api('deleteRolePermission', **args) + + return role_permission + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + role=dict(required=True), + name=dict(required=True), + permission=dict(choices=['allow', 'deny'], default='deny'), + description=dict(), + state=dict(choices=['present', 'absent'], default='present'), + parent=dict(), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + mutually_exclusive=( + ['permission', 'parent'], + ), + supports_check_mode=True + ) + + acs_role_perm = AnsibleCloudStackRolePermission(module) + + state = module.params.get('state') + if state in ['absent']: + role_permission = acs_role_perm.remove_role_perm() + else: + role_permission = acs_role_perm.create_or_update_role_perm() + + result = acs_role_perm.get_result(role_permission) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_router.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_router.py new file mode 100644 index 00000000..e14a7609 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_router.py @@ -0,0 +1,368 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_router +short_description: Manages routers on Apache CloudStack based clouds. +description: + - Start, restart, stop and destroy routers. + - I(state=present) is not able to create routers, use M(cs_network) instead. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name of the router. + type: str + required: true + service_offering: + description: + - Name or id of the service offering of the router. + type: str + domain: + description: + - Domain the router is related to. + type: str + account: + description: + - Account the router is related to. + type: str + project: + description: + - Name of the project the router is related to. + type: str + zone: + description: + - Name of the zone the router is deployed in. + - If not set, all zones are used. + type: str + state: + description: + - State of the router. + type: str + default: present + choices: [ present, absent, started, stopped, restarted ] + poll_async: + description: + - Poll async jobs until job has finished. + default: yes + type: bool +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +# Ensure the router has the desired service offering, no matter if +# the router is running or not. +- name: Present router + ngine_io.cloudstack.cs_router: + name: r-40-VM + service_offering: System Offering for Software Router + +- name: Ensure started + ngine_io.cloudstack.cs_router: + name: r-40-VM + state: started + +# Ensure started with desired service offering. +# If the service offerings changes, router will be rebooted. +- name: Ensure started with desired service offering + ngine_io.cloudstack.cs_router: + name: r-40-VM + service_offering: System Offering for Software Router + state: started + +- name: Ensure stopped + ngine_io.cloudstack.cs_router: + name: r-40-VM + state: stopped + +- name: Remove a router + ngine_io.cloudstack.cs_router: + name: r-40-VM + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the router. + returned: success + type: str + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 +name: + description: Name of the router. + returned: success + type: str + sample: r-40-VM +created: + description: Date of the router was created. + returned: success + type: str + sample: 2014-12-01T14:57:57+0100 +template_version: + description: Version of the system VM template. + returned: success + type: str + sample: 4.5.1 +requires_upgrade: + description: Whether the router needs to be upgraded to the new template. + returned: success + type: bool + sample: false +redundant_state: + description: Redundant state of the router. + returned: success + type: str + sample: UNKNOWN +role: + description: Role of the router. + returned: success + type: str + sample: VIRTUAL_ROUTER +zone: + description: Name of zone the router is in. + returned: success + type: str + sample: ch-gva-2 +service_offering: + description: Name of the service offering the router has. + returned: success + type: str + sample: System Offering For Software Router +state: + description: State of the router. + returned: success + type: str + sample: Active +domain: + description: Domain the router is related to. + returned: success + type: str + sample: ROOT +account: + description: Account the router is related to. + returned: success + type: str + sample: admin +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackRouter(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackRouter, self).__init__(module) + self.returns = { + 'serviceofferingname': 'service_offering', + 'version': 'template_version', + 'requiresupgrade': 'requires_upgrade', + 'redundantstate': 'redundant_state', + 'role': 'role' + } + self.router = None + + def get_service_offering_id(self): + service_offering = self.module.params.get('service_offering') + if not service_offering: + return None + + args = { + 'issystem': True + } + + service_offerings = self.query_api('listServiceOfferings', **args) + if service_offerings: + for s in service_offerings['serviceoffering']: + if service_offering in [s['name'], s['id']]: + return s['id'] + self.module.fail_json(msg="Service offering '%s' not found" % service_offering) + + def get_router(self): + if not self.router: + router = self.module.params.get('name') + + args = { + 'projectid': self.get_project(key='id'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'listall': True, + 'fetch_list': True, + } + + if self.module.params.get('zone'): + args['zoneid'] = self.get_zone(key='id') + + routers = self.query_api('listRouters', **args) + if routers: + for r in routers: + if router.lower() in [r['name'].lower(), r['id']]: + self.router = r + break + return self.router + + def start_router(self): + router = self.get_router() + if not router: + self.module.fail_json(msg="Router not found") + + if router['state'].lower() != "running": + self.result['changed'] = True + + args = { + 'id': router['id'], + } + + if not self.module.check_mode: + res = self.query_api('startRouter', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + router = self.poll_job(res, 'router') + return router + + def stop_router(self): + router = self.get_router() + if not router: + self.module.fail_json(msg="Router not found") + + if router['state'].lower() != "stopped": + self.result['changed'] = True + + args = { + 'id': router['id'], + } + + if not self.module.check_mode: + res = self.query_api('stopRouter', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + router = self.poll_job(res, 'router') + return router + + def reboot_router(self): + router = self.get_router() + if not router: + self.module.fail_json(msg="Router not found") + + self.result['changed'] = True + + args = { + 'id': router['id'], + } + + if not self.module.check_mode: + res = self.query_api('rebootRouter', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + router = self.poll_job(res, 'router') + return router + + def absent_router(self): + router = self.get_router() + if router: + self.result['changed'] = True + + args = { + 'id': router['id'], + } + + if not self.module.check_mode: + res = self.query_api('destroyRouter', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(res, 'router') + return router + + def present_router(self): + router = self.get_router() + if not router: + self.module.fail_json(msg="Router can not be created using the API, see cs_network.") + + args = { + 'id': router['id'], + 'serviceofferingid': self.get_service_offering_id(), + } + + state = self.module.params.get('state') + + if self.has_changed(args, router): + self.result['changed'] = True + + if not self.module.check_mode: + current_state = router['state'].lower() + + self.stop_router() + router = self.query_api('changeServiceForRouter', **args) + + if state in ['restarted', 'started']: + router = self.start_router() + + # if state=present we get to the state before the service + # offering change. + elif state == "present" and current_state == "running": + router = self.start_router() + + elif state == "started": + router = self.start_router() + + elif state == "stopped": + router = self.stop_router() + + elif state == "restarted": + router = self.reboot_router() + + return router + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + service_offering=dict(), + state=dict(choices=['present', 'started', 'stopped', 'restarted', 'absent'], default="present"), + domain=dict(), + account=dict(), + project=dict(), + zone=dict(), + poll_async=dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_router = AnsibleCloudStackRouter(module) + + state = module.params.get('state') + if state in ['absent']: + router = acs_router.absent_router() + else: + router = acs_router.present_router() + + result = acs_router.get_result(router) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_securitygroup.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_securitygroup.py new file mode 100644 index 00000000..5d8ba2b9 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_securitygroup.py @@ -0,0 +1,193 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_securitygroup +short_description: Manages security groups on Apache CloudStack based clouds. +description: + - Create and remove security groups. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name of the security group. + type: str + required: true + description: + description: + - Description of the security group. + type: str + state: + description: + - State of the security group. + type: str + default: present + choices: [ present, absent ] + domain: + description: + - Domain the security group is related to. + type: str + account: + description: + - Account the security group is related to. + type: str + project: + description: + - Name of the project the security group to be created in. + type: str +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: create a security group + ngine_io.cloudstack.cs_securitygroup: + name: default + description: default security group + +- name: remove a security group + ngine_io.cloudstack.cs_securitygroup: + name: default + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the security group. + returned: success + type: str + sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f +name: + description: Name of security group. + returned: success + type: str + sample: app +description: + description: Description of security group. + returned: success + type: str + sample: application security group +tags: + description: List of resource tags associated with the security group. + returned: success + type: list + sample: '[ { "key": "foo", "value": "bar" } ]' +project: + description: Name of project the security group is related to. + returned: success + type: str + sample: Production +domain: + description: Domain the security group is related to. + returned: success + type: str + sample: example domain +account: + description: Account the security group is related to. + returned: success + type: str + sample: example account +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import AnsibleCloudStack, cs_argument_spec, cs_required_together + + +class AnsibleCloudStackSecurityGroup(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackSecurityGroup, self).__init__(module) + self.security_group = None + + def get_security_group(self): + if not self.security_group: + + args = { + 'projectid': self.get_project(key='id'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'securitygroupname': self.module.params.get('name'), + } + sgs = self.query_api('listSecurityGroups', **args) + if sgs: + self.security_group = sgs['securitygroup'][0] + return self.security_group + + def create_security_group(self): + security_group = self.get_security_group() + if not security_group: + self.result['changed'] = True + + args = { + 'name': self.module.params.get('name'), + 'projectid': self.get_project(key='id'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'description': self.module.params.get('description'), + } + + if not self.module.check_mode: + res = self.query_api('createSecurityGroup', **args) + security_group = res['securitygroup'] + + return security_group + + def remove_security_group(self): + security_group = self.get_security_group() + if security_group: + self.result['changed'] = True + + args = { + 'name': self.module.params.get('name'), + 'projectid': self.get_project(key='id'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + } + + if not self.module.check_mode: + self.query_api('deleteSecurityGroup', **args) + + return security_group + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + description=dict(), + state=dict(choices=['present', 'absent'], default='present'), + project=dict(), + account=dict(), + domain=dict(), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_sg = AnsibleCloudStackSecurityGroup(module) + + state = module.params.get('state') + if state in ['absent']: + sg = acs_sg.remove_security_group() + else: + sg = acs_sg.create_security_group() + + result = acs_sg.get_result(sg) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_securitygroup_rule.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_securitygroup_rule.py new file mode 100644 index 00000000..2bd269e8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_securitygroup_rule.py @@ -0,0 +1,379 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_securitygroup_rule +short_description: Manages security group rules on Apache CloudStack based clouds. +description: + - Add and remove security group rules. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + security_group: + description: + - Name of the security group the rule is related to. The security group must be existing. + type: str + required: true + state: + description: + - State of the security group rule. + type: str + default: present + choices: [ present, absent ] + protocol: + description: + - Protocol of the security group rule. + type: str + default: tcp + choices: [ tcp, udp, icmp, ah, esp, gre ] + type: + description: + - Ingress or egress security group rule. + type: str + default: ingress + choices: [ ingress, egress ] + cidr: + description: + - CIDR (full notation) to be used for security group rule. + type: str + default: 0.0.0.0/0 + user_security_group: + description: + - Security group this rule is based of. + type: str + start_port: + description: + - Start port for this rule. Required if I(protocol=tcp) or I(protocol=udp). + type: int + aliases: [ port ] + end_port: + description: + - End port for this rule. Required if I(protocol=tcp) or I(protocol=udp), but I(start_port) will be used if not set. + type: int + icmp_type: + description: + - Type of the icmp message being sent. Required if I(protocol=icmp). + type: int + icmp_code: + description: + - Error code for this icmp message. Required if I(protocol=icmp). + type: int + project: + description: + - Name of the project the security group to be created in. + type: str + poll_async: + description: + - Poll async jobs until job has finished. + default: yes + type: bool +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +--- +- name: allow inbound port 80/tcp from 1.2.3.4 added to security group 'default' + ngine_io.cloudstack.cs_securitygroup_rule: + security_group: default + port: 80 + cidr: 1.2.3.4/32 + +- name: allow tcp/udp outbound added to security group 'default' + ngine_io.cloudstack.cs_securitygroup_rule: + security_group: default + type: egress + start_port: 1 + end_port: 65535 + protocol: '{{ item }}' + with_items: + - tcp + - udp + +- name: allow inbound icmp from 0.0.0.0/0 added to security group 'default' + ngine_io.cloudstack.cs_securitygroup_rule: + security_group: default + protocol: icmp + icmp_code: -1 + icmp_type: -1 + +- name: remove rule inbound port 80/tcp from 0.0.0.0/0 from security group 'default' + ngine_io.cloudstack.cs_securitygroup_rule: + security_group: default + port: 80 + state: absent + +- name: allow inbound port 80/tcp from security group web added to security group 'default' + ngine_io.cloudstack.cs_securitygroup_rule: + security_group: default + port: 80 + user_security_group: web +''' + +RETURN = ''' +--- +id: + description: UUID of the of the rule. + returned: success + type: str + sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f +security_group: + description: security group of the rule. + returned: success + type: str + sample: default +type: + description: type of the rule. + returned: success + type: str + sample: ingress +cidr: + description: CIDR of the rule. + returned: success and cidr is defined + type: str + sample: 0.0.0.0/0 +user_security_group: + description: user security group of the rule. + returned: success and user_security_group is defined + type: str + sample: default +protocol: + description: protocol of the rule. + returned: success + type: str + sample: tcp +start_port: + description: start port of the rule. + returned: success + type: int + sample: 80 +end_port: + description: end port of the rule. + returned: success + type: int + sample: 80 +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import AnsibleCloudStack, cs_argument_spec, cs_required_together + + +class AnsibleCloudStackSecurityGroupRule(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackSecurityGroupRule, self).__init__(module) + self.returns = { + 'icmptype': 'icmp_type', + 'icmpcode': 'icmp_code', + 'endport': 'end_port', + 'startport': 'start_port', + 'protocol': 'protocol', + 'cidr': 'cidr', + 'securitygroupname': 'user_security_group', + } + + def _tcp_udp_match(self, rule, protocol, start_port, end_port): + return (protocol in ['tcp', 'udp'] and + protocol == rule['protocol'] and + start_port == int(rule['startport']) and + end_port == int(rule['endport'])) + + def _icmp_match(self, rule, protocol, icmp_code, icmp_type): + return (protocol == 'icmp' and + protocol == rule['protocol'] and + icmp_code == int(rule['icmpcode']) and + icmp_type == int(rule['icmptype'])) + + def _ah_esp_gre_match(self, rule, protocol): + return (protocol in ['ah', 'esp', 'gre'] and + protocol == rule['protocol']) + + def _type_security_group_match(self, rule, security_group_name): + return (security_group_name and + 'securitygroupname' in rule and + security_group_name == rule['securitygroupname']) + + def _type_cidr_match(self, rule, cidr): + return ('cidr' in rule and + cidr == rule['cidr']) + + def _get_rule(self, rules): + user_security_group_name = self.module.params.get('user_security_group') + cidr = self.module.params.get('cidr') + protocol = self.module.params.get('protocol') + start_port = self.module.params.get('start_port') + end_port = self.get_or_fallback('end_port', 'start_port') + icmp_code = self.module.params.get('icmp_code') + icmp_type = self.module.params.get('icmp_type') + + if protocol in ['tcp', 'udp'] and (start_port is None or end_port is None): + self.module.fail_json(msg="no start_port or end_port set for protocol '%s'" % protocol) + + if protocol == 'icmp' and (icmp_type is None or icmp_code is None): + self.module.fail_json(msg="no icmp_type or icmp_code set for protocol '%s'" % protocol) + + for rule in rules: + if user_security_group_name: + type_match = self._type_security_group_match(rule, user_security_group_name) + else: + type_match = self._type_cidr_match(rule, cidr) + + protocol_match = (self._tcp_udp_match(rule, protocol, start_port, end_port) or + self._icmp_match(rule, protocol, icmp_code, icmp_type) or + self._ah_esp_gre_match(rule, protocol)) + + if type_match and protocol_match: + return rule + return None + + def get_security_group(self, security_group_name=None): + if not security_group_name: + security_group_name = self.module.params.get('security_group') + args = { + 'securitygroupname': security_group_name, + 'projectid': self.get_project('id'), + } + sgs = self.query_api('listSecurityGroups', **args) + if not sgs or 'securitygroup' not in sgs: + self.module.fail_json(msg="security group '%s' not found" % security_group_name) + return sgs['securitygroup'][0] + + def add_rule(self): + security_group = self.get_security_group() + + args = {} + user_security_group_name = self.module.params.get('user_security_group') + + # the user_security_group and cidr are mutually_exclusive, but cidr is defaulted to 0.0.0.0/0. + # that is why we ignore if we have a user_security_group. + if user_security_group_name: + args['usersecuritygrouplist'] = [] + user_security_group = self.get_security_group(user_security_group_name) + args['usersecuritygrouplist'].append({ + 'group': user_security_group['name'], + 'account': user_security_group['account'], + }) + else: + args['cidrlist'] = self.module.params.get('cidr') + + args['protocol'] = self.module.params.get('protocol') + args['startport'] = self.module.params.get('start_port') + args['endport'] = self.get_or_fallback('end_port', 'start_port') + args['icmptype'] = self.module.params.get('icmp_type') + args['icmpcode'] = self.module.params.get('icmp_code') + args['projectid'] = self.get_project('id') + args['securitygroupid'] = security_group['id'] + + rule = None + res = None + sg_type = self.module.params.get('type') + if sg_type == 'ingress': + if 'ingressrule' in security_group: + rule = self._get_rule(security_group['ingressrule']) + if not rule: + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('authorizeSecurityGroupIngress', **args) + + elif sg_type == 'egress': + if 'egressrule' in security_group: + rule = self._get_rule(security_group['egressrule']) + if not rule: + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('authorizeSecurityGroupEgress', **args) + + poll_async = self.module.params.get('poll_async') + if res and poll_async: + security_group = self.poll_job(res, 'securitygroup') + key = sg_type + "rule" # ingressrule / egressrule + if key in security_group: + rule = security_group[key][0] + return rule + + def remove_rule(self): + security_group = self.get_security_group() + rule = None + res = None + sg_type = self.module.params.get('type') + if sg_type == 'ingress': + rule = self._get_rule(security_group['ingressrule']) + if rule: + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('revokeSecurityGroupIngress', id=rule['ruleid']) + + elif sg_type == 'egress': + rule = self._get_rule(security_group['egressrule']) + if rule: + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('revokeSecurityGroupEgress', id=rule['ruleid']) + + poll_async = self.module.params.get('poll_async') + if res and poll_async: + res = self.poll_job(res, 'securitygroup') + return rule + + def get_result(self, security_group_rule): + super(AnsibleCloudStackSecurityGroupRule, self).get_result(security_group_rule) + self.result['type'] = self.module.params.get('type') + self.result['security_group'] = self.module.params.get('security_group') + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + security_group=dict(required=True), + type=dict(choices=['ingress', 'egress'], default='ingress'), + cidr=dict(default='0.0.0.0/0'), + user_security_group=dict(), + protocol=dict(choices=['tcp', 'udp', 'icmp', 'ah', 'esp', 'gre'], default='tcp'), + icmp_type=dict(type='int'), + icmp_code=dict(type='int'), + start_port=dict(type='int', aliases=['port']), + end_port=dict(type='int'), + state=dict(choices=['present', 'absent'], default='present'), + project=dict(), + poll_async=dict(type='bool', default=True), + )) + required_together = cs_required_together() + required_together.extend([ + ['icmp_type', 'icmp_code'], + ]) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=required_together, + mutually_exclusive=( + ['icmp_type', 'start_port'], + ['icmp_type', 'end_port'], + ['icmp_code', 'start_port'], + ['icmp_code', 'end_port'], + ), + supports_check_mode=True + ) + + acs_sg_rule = AnsibleCloudStackSecurityGroupRule(module) + + state = module.params.get('state') + if state in ['absent']: + sg_rule = acs_sg_rule.remove_rule() + else: + sg_rule = acs_sg_rule.add_rule() + + result = acs_sg_rule.get_result(sg_rule) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_service_offering.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_service_offering.py new file mode 100644 index 00000000..8d17d521 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_service_offering.py @@ -0,0 +1,574 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_service_offering +description: + - Create and delete service offerings for guest and system VMs. + - Update display_text of existing service offering. +short_description: Manages service offerings on Apache CloudStack based clouds. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + disk_bytes_read_rate: + description: + - Bytes read rate of the disk offering. + type: int + aliases: [ bytes_read_rate ] + disk_bytes_write_rate: + description: + - Bytes write rate of the disk offering. + type: int + aliases: [ bytes_write_rate ] + cpu_number: + description: + - The number of CPUs of the service offering. + type: int + cpu_speed: + description: + - The CPU speed of the service offering in MHz. + type: int + limit_cpu_usage: + description: + - Restrict the CPU usage to committed service offering. + type: bool + deployment_planner: + description: + - The deployment planner heuristics used to deploy a VM of this offering. + - If not set, the value of global config I(vm.deployment.planner) is used. + type: str + display_text: + description: + - Display text of the service offering. + - If not set, I(name) will be used as I(display_text) while creating. + type: str + domain: + description: + - Domain the service offering is related to. + - Public for all domains and subdomains if not set. + type: str + host_tags: + description: + - The host tags for this service offering. + type: list + elements: str + aliases: + - host_tag + hypervisor_snapshot_reserve: + description: + - Hypervisor snapshot reserve space as a percent of a volume. + - Only for managed storage using Xen or VMware. + type: int + is_iops_customized: + description: + - Whether compute offering iops is custom or not. + type: bool + aliases: [ disk_iops_customized ] + disk_iops_read_rate: + description: + - IO requests read rate of the disk offering. + type: int + disk_iops_write_rate: + description: + - IO requests write rate of the disk offering. + type: int + disk_iops_max: + description: + - Max. iops of the compute offering. + type: int + disk_iops_min: + description: + - Min. iops of the compute offering. + type: int + is_system: + description: + - Whether it is a system VM offering or not. + type: bool + default: no + is_volatile: + description: + - Whether the virtual machine needs to be volatile or not. + - Every reboot of VM the root disk is detached then destroyed and a fresh root disk is created and attached to VM. + type: bool + memory: + description: + - The total memory of the service offering in MB. + type: int + name: + description: + - Name of the service offering. + type: str + required: true + network_rate: + description: + - Data transfer rate in Mb/s allowed. + - Supported only for non-system offering and system offerings having I(system_vm_type=domainrouter). + type: int + offer_ha: + description: + - Whether HA is set for the service offering. + type: bool + provisioning_type: + description: + - Provisioning type used to create volumes. + type: str + choices: + - thin + - sparse + - fat + service_offering_details: + description: + - Details for planner, used to store specific parameters. + - A list of dictionaries having keys C(key) and C(value). + type: list + elements: dict + state: + description: + - State of the service offering. + type: str + choices: + - present + - absent + default: present + storage_type: + description: + - The storage type of the service offering. + type: str + choices: + - local + - shared + system_vm_type: + description: + - The system VM type. + - Required if I(is_system=yes). + type: str + choices: + - domainrouter + - consoleproxy + - secondarystoragevm + storage_tags: + description: + - The storage tags for this service offering. + type: list + elements: str + aliases: + - storage_tag + is_customized: + description: + - Whether the offering is customizable or not. + type: bool +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Create a non-volatile compute service offering with local storage + ngine_io.cloudstack.cs_service_offering: + name: Micro + display_text: Micro 512mb 1cpu + cpu_number: 1 + cpu_speed: 2198 + memory: 512 + host_tags: eco + storage_type: local + +- name: Create a volatile compute service offering with shared storage + ngine_io.cloudstack.cs_service_offering: + name: Tiny + display_text: Tiny 1gb 1cpu + cpu_number: 1 + cpu_speed: 2198 + memory: 1024 + storage_type: shared + is_volatile: yes + host_tags: eco + storage_tags: eco + +- name: Create or update a volatile compute service offering with shared storage + ngine_io.cloudstack.cs_service_offering: + name: Tiny + display_text: Tiny 1gb 1cpu + cpu_number: 1 + cpu_speed: 2198 + memory: 1024 + storage_type: shared + is_volatile: yes + host_tags: eco + storage_tags: eco + +- name: Create or update a custom compute service offering + ngine_io.cloudstack.cs_service_offering: + name: custom + display_text: custom compute offer + is_customized: yes + storage_type: shared + host_tags: eco + storage_tags: eco + +- name: Remove a compute service offering + ngine_io.cloudstack.cs_service_offering: + name: Tiny + state: absent + +- name: Create or update a system offering for the console proxy + ngine_io.cloudstack.cs_service_offering: + name: System Offering for Console Proxy 2GB + display_text: System Offering for Console Proxy 2GB RAM + is_system: yes + system_vm_type: consoleproxy + cpu_number: 1 + cpu_speed: 2198 + memory: 2048 + storage_type: shared + storage_tags: perf + +- name: Remove a system offering + ngine_io.cloudstack.cs_service_offering: + name: System Offering for Console Proxy 2GB + is_system: yes + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the service offering + returned: success + type: str + sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f +cpu_number: + description: Number of CPUs in the service offering + returned: success + type: int + sample: 4 +cpu_speed: + description: Speed of CPUs in MHz in the service offering + returned: success + type: int + sample: 2198 +disk_iops_max: + description: Max iops of the disk offering + returned: success + type: int + sample: 1000 +disk_iops_min: + description: Min iops of the disk offering + returned: success + type: int + sample: 500 +disk_bytes_read_rate: + description: Bytes read rate of the service offering + returned: success + type: int + sample: 1000 +disk_bytes_write_rate: + description: Bytes write rate of the service offering + returned: success + type: int + sample: 1000 +disk_iops_read_rate: + description: IO requests per second read rate of the service offering + returned: success + type: int + sample: 1000 +disk_iops_write_rate: + description: IO requests per second write rate of the service offering + returned: success + type: int + sample: 1000 +created: + description: Date the offering was created + returned: success + type: str + sample: 2017-11-19T10:48:59+0000 +display_text: + description: Display text of the offering + returned: success + type: str + sample: Micro 512mb 1cpu +domain: + description: Domain the offering is into + returned: success + type: str + sample: ROOT +host_tags: + description: List of host tags + returned: success + type: list + sample: [ 'eco' ] +storage_tags: + description: List of storage tags + returned: success + type: list + sample: [ 'eco' ] +is_system: + description: Whether the offering is for system VMs or not + returned: success + type: bool + sample: false +is_iops_customized: + description: Whether the offering uses custom IOPS or not + returned: success + type: bool + sample: false +is_volatile: + description: Whether the offering is volatile or not + returned: success + type: bool + sample: false +limit_cpu_usage: + description: Whether the CPU usage is restricted to committed service offering + returned: success + type: bool + sample: false +memory: + description: Memory of the system offering + returned: success + type: int + sample: 512 +name: + description: Name of the system offering + returned: success + type: str + sample: Micro +offer_ha: + description: Whether HA support is enabled in the offering or not + returned: success + type: bool + sample: false +provisioning_type: + description: Provisioning type used to create volumes + returned: success + type: str + sample: thin +storage_type: + description: Storage type used to create volumes + returned: success + type: str + sample: shared +system_vm_type: + description: System VM type of this offering + returned: success + type: str + sample: consoleproxy +service_offering_details: + description: Additioanl service offering details + returned: success + type: dict + sample: "{'vgpuType': 'GRID K180Q','pciDevice':'Group of NVIDIA Corporation GK107GL [GRID K1] GPUs'}" +network_rate: + description: Data transfer rate in megabits per second allowed + returned: success + type: int + sample: 1000 +is_customized: + description: Whether the offering is customizable or not + returned: success + type: bool + sample: false +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackServiceOffering(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackServiceOffering, self).__init__(module) + self.returns = { + 'cpunumber': 'cpu_number', + 'cpuspeed': 'cpu_speed', + 'deploymentplanner': 'deployment_planner', + 'diskBytesReadRate': 'disk_bytes_read_rate', + 'diskBytesWriteRate': 'disk_bytes_write_rate', + 'diskIopsReadRate': 'disk_iops_read_rate', + 'diskIopsWriteRate': 'disk_iops_write_rate', + 'maxiops': 'disk_iops_max', + 'miniops': 'disk_iops_min', + 'hypervisorsnapshotreserve': 'hypervisor_snapshot_reserve', + 'iscustomized': 'is_customized', + 'iscustomizediops': 'is_iops_customized', + 'issystem': 'is_system', + 'isvolatile': 'is_volatile', + 'limitcpuuse': 'limit_cpu_usage', + 'memory': 'memory', + 'networkrate': 'network_rate', + 'offerha': 'offer_ha', + 'provisioningtype': 'provisioning_type', + 'serviceofferingdetails': 'service_offering_details', + 'storagetype': 'storage_type', + 'systemvmtype': 'system_vm_type', + 'tags': 'storage_tags', + } + + def get_service_offering(self): + args = { + 'name': self.module.params.get('name'), + 'domainid': self.get_domain(key='id'), + 'issystem': self.module.params.get('is_system'), + 'systemvmtype': self.module.params.get('system_vm_type'), + } + service_offerings = self.query_api('listServiceOfferings', **args) + if service_offerings: + return service_offerings['serviceoffering'][0] + + def present_service_offering(self): + service_offering = self.get_service_offering() + if not service_offering: + service_offering = self._create_offering(service_offering) + else: + service_offering = self._update_offering(service_offering) + + return service_offering + + def absent_service_offering(self): + service_offering = self.get_service_offering() + if service_offering: + self.result['changed'] = True + if not self.module.check_mode: + args = { + 'id': service_offering['id'], + } + self.query_api('deleteServiceOffering', **args) + return service_offering + + def _create_offering(self, service_offering): + self.result['changed'] = True + + system_vm_type = self.module.params.get('system_vm_type') + is_system = self.module.params.get('is_system') + + required_params = [] + if is_system and not system_vm_type: + required_params.append('system_vm_type') + self.module.fail_on_missing_params(required_params=required_params) + + args = { + 'name': self.module.params.get('name'), + 'displaytext': self.get_or_fallback('display_text', 'name'), + 'bytesreadrate': self.module.params.get('disk_bytes_read_rate'), + 'byteswriterate': self.module.params.get('disk_bytes_write_rate'), + 'cpunumber': self.module.params.get('cpu_number'), + 'cpuspeed': self.module.params.get('cpu_speed'), + 'customizediops': self.module.params.get('is_iops_customized'), + 'deploymentplanner': self.module.params.get('deployment_planner'), + 'domainid': self.get_domain(key='id'), + 'hosttags': self.module.params.get('host_tags'), + 'hypervisorsnapshotreserve': self.module.params.get('hypervisor_snapshot_reserve'), + 'iopsreadrate': self.module.params.get('disk_iops_read_rate'), + 'iopswriterate': self.module.params.get('disk_iops_write_rate'), + 'maxiops': self.module.params.get('disk_iops_max'), + 'miniops': self.module.params.get('disk_iops_min'), + 'issystem': is_system, + 'isvolatile': self.module.params.get('is_volatile'), + 'memory': self.module.params.get('memory'), + 'networkrate': self.module.params.get('network_rate'), + 'offerha': self.module.params.get('offer_ha'), + 'provisioningtype': self.module.params.get('provisioning_type'), + 'serviceofferingdetails': self.module.params.get('service_offering_details'), + 'storagetype': self.module.params.get('storage_type'), + 'systemvmtype': system_vm_type, + 'tags': self.module.params.get('storage_tags'), + 'limitcpuuse': self.module.params.get('limit_cpu_usage'), + 'customized': self.module.params.get('is_customized') + } + if not self.module.check_mode: + res = self.query_api('createServiceOffering', **args) + service_offering = res['serviceoffering'] + return service_offering + + def _update_offering(self, service_offering): + args = { + 'id': service_offering['id'], + 'name': self.module.params.get('name'), + 'displaytext': self.get_or_fallback('display_text', 'name'), + } + if self.has_changed(args, service_offering): + self.result['changed'] = True + + if not self.module.check_mode: + res = self.query_api('updateServiceOffering', **args) + service_offering = res['serviceoffering'] + return service_offering + + def get_result(self, service_offering): + super(AnsibleCloudStackServiceOffering, self).get_result(service_offering) + if service_offering: + if 'hosttags' in service_offering: + self.result['host_tags'] = service_offering['hosttags'].split(',') or [service_offering['hosttags']] + + # Prevent confusion, the api returns a tags key for storage tags. + if 'tags' in service_offering: + self.result['storage_tags'] = service_offering['tags'].split(',') or [service_offering['tags']] + if 'tags' in self.result: + del self.result['tags'] + + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + display_text=dict(), + cpu_number=dict(type='int'), + cpu_speed=dict(type='int'), + limit_cpu_usage=dict(type='bool'), + deployment_planner=dict(), + domain=dict(), + host_tags=dict(type='list', elements='str', aliases=['host_tag']), + hypervisor_snapshot_reserve=dict(type='int'), + disk_bytes_read_rate=dict(type='int', aliases=['bytes_read_rate']), + disk_bytes_write_rate=dict(type='int', aliases=['bytes_write_rate']), + disk_iops_read_rate=dict(type='int'), + disk_iops_write_rate=dict(type='int'), + disk_iops_max=dict(type='int'), + disk_iops_min=dict(type='int'), + is_system=dict(type='bool', default=False), + is_volatile=dict(type='bool'), + is_iops_customized=dict(type='bool', aliases=['disk_iops_customized']), + memory=dict(type='int'), + network_rate=dict(type='int'), + offer_ha=dict(type='bool'), + provisioning_type=dict(choices=['thin', 'sparse', 'fat']), + service_offering_details=dict(type='list', elements='dict'), + storage_type=dict(choices=['local', 'shared']), + system_vm_type=dict(choices=['domainrouter', 'consoleproxy', 'secondarystoragevm']), + storage_tags=dict(type='list', elements='str', aliases=['storage_tag']), + state=dict(choices=['present', 'absent'], default='present'), + is_customized=dict(type='bool'), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_so = AnsibleCloudStackServiceOffering(module) + + state = module.params.get('state') + if state == "absent": + service_offering = acs_so.absent_service_offering() + else: + service_offering = acs_so.present_service_offering() + + result = acs_so.get_result(service_offering) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_snapshot_policy.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_snapshot_policy.py new file mode 100644 index 00000000..bd11f367 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_snapshot_policy.py @@ -0,0 +1,349 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_snapshot_policy +short_description: Manages volume snapshot policies on Apache CloudStack based clouds. +description: + - Create, update and delete volume snapshot policies. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + volume: + description: + - Name of the volume. + - Either I(volume) or I(vm) is required. + type: str + volume_type: + description: + - Type of the volume. + type: str + choices: + - DATADISK + - ROOT + vm: + description: + - Name of the instance to select the volume from. + - Use I(volume_type) if VM has a DATADISK and ROOT volume. + - In case of I(volume_type=DATADISK), additionally use I(device_id) if VM has more than one DATADISK volume. + - Either I(volume) or I(vm) is required. + type: str + device_id: + description: + - ID of the device on a VM the volume is attached to. + - This will only be considered if VM has multiple DATADISK volumes. + type: int + vpc: + description: + - Name of the vpc the instance is deployed in. + type: str + interval_type: + description: + - Interval of the snapshot. + type: str + default: daily + choices: [ hourly, daily, weekly, monthly ] + aliases: [ interval ] + max_snaps: + description: + - Max number of snapshots. + type: int + default: 8 + aliases: [ max ] + schedule: + description: + - Time the snapshot is scheduled. Required if I(state=present). + - 'Format for I(interval_type=HOURLY): C(MM)' + - 'Format for I(interval_type=DAILY): C(MM:HH)' + - 'Format for I(interval_type=WEEKLY): C(MM:HH:DD (1-7))' + - 'Format for I(interval_type=MONTHLY): C(MM:HH:DD (1-28))' + type: str + time_zone: + description: + - Specifies a timezone for this command. + type: str + default: UTC + aliases: [ timezone ] + state: + description: + - State of the snapshot policy. + type: str + default: present + choices: [ present, absent ] + domain: + description: + - Domain the volume is related to. + type: str + account: + description: + - Account the volume is related to. + type: str + project: + description: + - Name of the project the volume is related to. + type: str +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: ensure a snapshot policy daily at 1h00 UTC + ngine_io.cloudstack.cs_snapshot_policy: + volume: ROOT-478 + schedule: '00:1' + max_snaps: 3 + +- name: ensure a snapshot policy daily at 1h00 UTC on the second DATADISK of VM web-01 + ngine_io.cloudstack.cs_snapshot_policy: + vm: web-01 + volume_type: DATADISK + device_id: 2 + schedule: '00:1' + max_snaps: 3 + +- name: ensure a snapshot policy hourly at minute 5 UTC + ngine_io.cloudstack.cs_snapshot_policy: + volume: ROOT-478 + schedule: '5' + interval_type: hourly + max_snaps: 1 + +- name: ensure a snapshot policy weekly on Sunday at 05h00, TZ Europe/Zurich + ngine_io.cloudstack.cs_snapshot_policy: + volume: ROOT-478 + schedule: '00:5:1' + interval_type: weekly + max_snaps: 1 + time_zone: 'Europe/Zurich' + +- name: ensure a snapshot policy is absent + ngine_io.cloudstack.cs_snapshot_policy: + volume: ROOT-478 + interval_type: hourly + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the snapshot policy. + returned: success + type: str + sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f +interval_type: + description: interval type of the snapshot policy. + returned: success + type: str + sample: daily +schedule: + description: schedule of the snapshot policy. + returned: success + type: str + sample: +max_snaps: + description: maximum number of snapshots retained. + returned: success + type: int + sample: 10 +time_zone: + description: the time zone of the snapshot policy. + returned: success + type: str + sample: Etc/UTC +volume: + description: the volume of the snapshot policy. + returned: success + type: str + sample: Etc/UTC +zone: + description: Name of zone the volume is related to. + returned: success + type: str + sample: ch-gva-2 +project: + description: Name of project the volume is related to. + returned: success + type: str + sample: Production +account: + description: Account the volume is related to. + returned: success + type: str + sample: example account +domain: + description: Domain the volume is related to. + returned: success + type: str + sample: example domain +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackSnapshotPolicy(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackSnapshotPolicy, self).__init__(module) + self.returns = { + 'schedule': 'schedule', + 'timezone': 'time_zone', + 'maxsnaps': 'max_snaps', + } + self.interval_types = { + 'hourly': 0, + 'daily': 1, + 'weekly': 2, + 'monthly': 3, + } + self.volume = None + + def get_interval_type(self): + interval_type = self.module.params.get('interval_type') + return self.interval_types[interval_type] + + def get_volume(self, key=None): + if self.volume: + return self._get_by_key(key, self.volume) + + args = { + 'name': self.module.params.get('volume'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'virtualmachineid': self.get_vm(key='id', filter_zone=False), + 'type': self.module.params.get('volume_type'), + } + volumes = self.query_api('listVolumes', **args) + if volumes: + if volumes['count'] > 1: + device_id = self.module.params.get('device_id') + if not device_id: + self.module.fail_json(msg="Found more then 1 volume: combine params 'vm', 'volume_type', 'device_id' and/or 'volume' to select the volume") + else: + for v in volumes['volume']: + if v.get('deviceid') == device_id: + self.volume = v + return self._get_by_key(key, self.volume) + self.module.fail_json(msg="No volume found with device id %s" % device_id) + self.volume = volumes['volume'][0] + return self._get_by_key(key, self.volume) + return None + + def get_snapshot_policy(self): + args = { + 'volumeid': self.get_volume(key='id') + } + policies = self.query_api('listSnapshotPolicies', **args) + if policies: + for policy in policies['snapshotpolicy']: + if policy['intervaltype'] == self.get_interval_type(): + return policy + return None + + def present_snapshot_policy(self): + required_params = [ + 'schedule', + ] + self.module.fail_on_missing_params(required_params=required_params) + + policy = self.get_snapshot_policy() + args = { + 'id': policy.get('id') if policy else None, + 'intervaltype': self.module.params.get('interval_type'), + 'schedule': self.module.params.get('schedule'), + 'maxsnaps': self.module.params.get('max_snaps'), + 'timezone': self.module.params.get('time_zone'), + 'volumeid': self.get_volume(key='id') + } + if not policy or (policy and self.has_changed(policy, args, only_keys=['schedule', 'maxsnaps', 'timezone'])): + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('createSnapshotPolicy', **args) + policy = res['snapshotpolicy'] + return policy + + def absent_snapshot_policy(self): + policy = self.get_snapshot_policy() + if policy: + self.result['changed'] = True + args = { + 'id': policy['id'] + } + if not self.module.check_mode: + self.query_api('deleteSnapshotPolicies', **args) + return policy + + def get_result(self, policy): + super(AnsibleCloudStackSnapshotPolicy, self).get_result(policy) + if policy and 'intervaltype' in policy: + for key, value in self.interval_types.items(): + if value == policy['intervaltype']: + self.result['interval_type'] = key + break + volume = self.get_volume() + if volume: + volume_results = { + 'volume': volume.get('name'), + 'zone': volume.get('zonename'), + 'project': volume.get('project'), + 'account': volume.get('account'), + 'domain': volume.get('domain'), + } + self.result.update(volume_results) + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + volume=dict(), + volume_type=dict(choices=['DATADISK', 'ROOT']), + vm=dict(), + device_id=dict(type='int'), + vpc=dict(), + interval_type=dict(default='daily', choices=['hourly', 'daily', 'weekly', 'monthly'], aliases=['interval']), + schedule=dict(), + time_zone=dict(default='UTC', aliases=['timezone']), + max_snaps=dict(type='int', default=8, aliases=['max']), + state=dict(choices=['present', 'absent'], default='present'), + domain=dict(), + account=dict(), + project=dict(), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + required_one_of=( + ['vm', 'volume'], + ), + supports_check_mode=True + ) + + acs_snapshot_policy = AnsibleCloudStackSnapshotPolicy(module) + + state = module.params.get('state') + if state in ['absent']: + policy = acs_snapshot_policy.absent_snapshot_policy() + else: + policy = acs_snapshot_policy.present_snapshot_policy() + + result = acs_snapshot_policy.get_result(policy) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_sshkeypair.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_sshkeypair.py new file mode 100644 index 00000000..3e32171c --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_sshkeypair.py @@ -0,0 +1,261 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_sshkeypair +short_description: Manages SSH keys on Apache CloudStack based clouds. +description: + - Create, register and remove SSH keys. + - If no key was found and no public key was provided and a new SSH + private/public key pair will be created and the private key will be returned. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name of public key. + type: str + required: true + domain: + description: + - Domain the public key is related to. + type: str + account: + description: + - Account the public key is related to. + type: str + project: + description: + - Name of the project the public key to be registered in. + type: str + state: + description: + - State of the public key. + type: str + default: present + choices: [ present, absent ] + public_key: + description: + - String of the public key. + type: str +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: create a new private / public key pair + ngine_io.cloudstack.cs_sshkeypair: + name: linus@example.com + register: key + +- debug: + msg: 'Private key is {{ key.private_key }}' + +- name: remove a public key by its name + ngine_io.cloudstack.cs_sshkeypair: + name: linus@example.com + state: absent + +- name: register your existing local public key + ngine_io.cloudstack.cs_sshkeypair: + name: linus@example.com + public_key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" +''' + +RETURN = ''' +--- +id: + description: UUID of the SSH public key. + returned: success + type: str + sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f +name: + description: Name of the SSH public key. + returned: success + type: str + sample: linus@example.com +fingerprint: + description: Fingerprint of the SSH public key. + returned: success + type: str + sample: "86:5e:a3:e8:bd:95:7b:07:7c:c2:5c:f7:ad:8b:09:28" +private_key: + description: Private key of generated SSH keypair. + returned: changed + type: str + sample: "-----BEGIN RSA PRIVATE KEY-----\nMII...8tO\n-----END RSA PRIVATE KEY-----\n" +''' + +import traceback + +SSHPUBKEYS_IMP_ERR = None +try: + import sshpubkeys + HAS_LIB_SSHPUBKEYS = True +except ImportError: + SSHPUBKEYS_IMP_ERR = traceback.format_exc() + HAS_LIB_SSHPUBKEYS = False + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils._text import to_native +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_required_together, + cs_argument_spec +) + + +class AnsibleCloudStackSshKey(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackSshKey, self).__init__(module) + self.returns = { + 'privatekey': 'private_key', + 'fingerprint': 'fingerprint', + } + self.ssh_key = None + + def register_ssh_key(self, public_key): + ssh_key = self.get_ssh_key() + args = self._get_common_args() + name = self.module.params.get('name') + + res = None + if not ssh_key: + self.result['changed'] = True + args['publickey'] = public_key + if not self.module.check_mode: + args['name'] = name + res = self.query_api('registerSSHKeyPair', **args) + else: + fingerprint = self._get_ssh_fingerprint(public_key) + if ssh_key['fingerprint'] != fingerprint: + self.result['changed'] = True + if not self.module.check_mode: + # delete the ssh key with matching name but wrong fingerprint + args['name'] = name + self.query_api('deleteSSHKeyPair', **args) + + elif ssh_key['name'].lower() != name.lower(): + self.result['changed'] = True + if not self.module.check_mode: + # delete the ssh key with matching fingerprint but wrong name + args['name'] = ssh_key['name'] + self.query_api('deleteSSHKeyPair', **args) + # First match for key retrievement will be the fingerprint. + # We need to make another lookup if there is a key with identical name. + self.ssh_key = None + ssh_key = self.get_ssh_key() + if ssh_key and ssh_key['fingerprint'] != fingerprint: + args['name'] = name + self.query_api('deleteSSHKeyPair', **args) + + if not self.module.check_mode and self.result['changed']: + args['publickey'] = public_key + args['name'] = name + res = self.query_api('registerSSHKeyPair', **args) + + if res and 'keypair' in res: + ssh_key = res['keypair'] + + return ssh_key + + def create_ssh_key(self): + ssh_key = self.get_ssh_key() + if not ssh_key: + self.result['changed'] = True + args = self._get_common_args() + args['name'] = self.module.params.get('name') + if not self.module.check_mode: + res = self.query_api('createSSHKeyPair', **args) + ssh_key = res['keypair'] + return ssh_key + + def remove_ssh_key(self, name=None): + ssh_key = self.get_ssh_key() + if ssh_key: + self.result['changed'] = True + args = self._get_common_args() + args['name'] = name or self.module.params.get('name') + if not self.module.check_mode: + self.query_api('deleteSSHKeyPair', **args) + return ssh_key + + def _get_common_args(self): + return { + 'domainid': self.get_domain('id'), + 'account': self.get_account('name'), + 'projectid': self.get_project('id') + } + + def get_ssh_key(self): + if not self.ssh_key: + public_key = self.module.params.get('public_key') + if public_key: + # Query by fingerprint of the public key + args_fingerprint = self._get_common_args() + args_fingerprint['fingerprint'] = self._get_ssh_fingerprint(public_key) + ssh_keys = self.query_api('listSSHKeyPairs', **args_fingerprint) + if ssh_keys and 'sshkeypair' in ssh_keys: + self.ssh_key = ssh_keys['sshkeypair'][0] + # When key has not been found by fingerprint, use the name + if not self.ssh_key: + args_name = self._get_common_args() + args_name['name'] = self.module.params.get('name') + ssh_keys = self.query_api('listSSHKeyPairs', **args_name) + if ssh_keys and 'sshkeypair' in ssh_keys: + self.ssh_key = ssh_keys['sshkeypair'][0] + return self.ssh_key + + def _get_ssh_fingerprint(self, public_key): + key = sshpubkeys.SSHKey(public_key) + if hasattr(key, 'hash_md5'): + return key.hash_md5().replace(to_native('MD5:'), to_native('')) + return key.hash() + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + public_key=dict(), + domain=dict(), + account=dict(), + project=dict(), + state=dict(choices=['present', 'absent'], default='present'), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + if not HAS_LIB_SSHPUBKEYS: + module.fail_json(msg=missing_required_lib("sshpubkeys"), exception=SSHPUBKEYS_IMP_ERR) + + acs_sshkey = AnsibleCloudStackSshKey(module) + state = module.params.get('state') + if state in ['absent']: + ssh_key = acs_sshkey.remove_ssh_key() + else: + public_key = module.params.get('public_key') + if public_key: + ssh_key = acs_sshkey.register_ssh_key(public_key) + else: + ssh_key = acs_sshkey.create_ssh_key() + + result = acs_sshkey.get_result(ssh_key) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_staticnat.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_staticnat.py new file mode 100644 index 00000000..5d223715 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_staticnat.py @@ -0,0 +1,249 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_staticnat +short_description: Manages static NATs on Apache CloudStack based clouds. +description: + - Create, update and remove static NATs. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + ip_address: + description: + - Public IP address the static NAT is assigned to. + type: str + required: true + vm: + description: + - Name of virtual machine which we make the static NAT for. + - Required if I(state=present). + type: str + vm_guest_ip: + description: + - VM guest NIC secondary IP address for the static NAT. + type: str + network: + description: + - Network the IP address is related to. + type: str + vpc: + description: + - VPC the network related to. + type: str + state: + description: + - State of the static NAT. + type: str + default: present + choices: [ present, absent ] + domain: + description: + - Domain the static NAT is related to. + type: str + account: + description: + - Account the static NAT is related to. + type: str + project: + description: + - Name of the project the static NAT is related to. + type: str + zone: + description: + - Name of the zone in which the virtual machine is in. + - If not set, default zone is used. + type: str + poll_async: + description: + - Poll async jobs until job has finished. + type: bool + default: yes +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Create a static NAT for IP 1.2.3.4 to web01 + ngine_io.cloudstack.cs_staticnat: + ip_address: 1.2.3.4 + vm: web01 + +- name: Remove a static NAT + ngine_io.cloudstack.cs_staticnat: + ip_address: 1.2.3.4 + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the ip_address. + returned: success + type: str + sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f +ip_address: + description: Public IP address. + returned: success + type: str + sample: 1.2.3.4 +vm_name: + description: Name of the virtual machine. + returned: success + type: str + sample: web-01 +vm_display_name: + description: Display name of the virtual machine. + returned: success + type: str + sample: web-01 +vm_guest_ip: + description: IP of the virtual machine. + returned: success + type: str + sample: 10.101.65.152 +zone: + description: Name of zone the static NAT is related to. + returned: success + type: str + sample: ch-gva-2 +project: + description: Name of project the static NAT is related to. + returned: success + type: str + sample: Production +account: + description: Account the static NAT is related to. + returned: success + type: str + sample: example account +domain: + description: Domain the static NAT is related to. + returned: success + type: str + sample: example domain +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackStaticNat(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackStaticNat, self).__init__(module) + self.returns = { + 'virtualmachinedisplayname': 'vm_display_name', + 'virtualmachinename': 'vm_name', + 'ipaddress': 'ip_address', + 'vmipaddress': 'vm_guest_ip', + } + + def create_static_nat(self, ip_address): + self.result['changed'] = True + args = { + 'virtualmachineid': self.get_vm(key='id'), + 'ipaddressid': ip_address['id'], + 'vmguestip': self.get_vm_guest_ip(), + 'networkid': self.get_network(key='id') + } + if not self.module.check_mode: + self.query_api('enableStaticNat', **args) + + # reset ip address and query new values + self.ip_address = None + ip_address = self.get_ip_address() + return ip_address + + def update_static_nat(self, ip_address): + args = { + 'virtualmachineid': self.get_vm(key='id'), + 'ipaddressid': ip_address['id'], + 'vmguestip': self.get_vm_guest_ip(), + 'networkid': self.get_network(key='id') + } + # make an alias, so we can use has_changed() + ip_address['vmguestip'] = ip_address['vmipaddress'] + if self.has_changed(args, ip_address, ['vmguestip', 'virtualmachineid']): + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('disableStaticNat', ipaddressid=ip_address['id']) + self.poll_job(res, 'staticnat') + + self.query_api('enableStaticNat', **args) + + # reset ip address and query new values + self.ip_address = None + ip_address = self.get_ip_address() + return ip_address + + def present_static_nat(self): + ip_address = self.get_ip_address() + if not ip_address['isstaticnat']: + ip_address = self.create_static_nat(ip_address) + else: + ip_address = self.update_static_nat(ip_address) + return ip_address + + def absent_static_nat(self): + ip_address = self.get_ip_address() + if ip_address['isstaticnat']: + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('disableStaticNat', ipaddressid=ip_address['id']) + + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(res, 'staticnat') + return ip_address + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + ip_address=dict(required=True), + vm=dict(), + vm_guest_ip=dict(), + network=dict(), + vpc=dict(), + state=dict(choices=['present', 'absent'], default='present'), + zone=dict(), + domain=dict(), + account=dict(), + project=dict(), + poll_async=dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_static_nat = AnsibleCloudStackStaticNat(module) + + state = module.params.get('state') + if state in ['absent']: + ip_address = acs_static_nat.absent_static_nat() + else: + ip_address = acs_static_nat.present_static_nat() + + result = acs_static_nat.get_result(ip_address) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_storage_pool.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_storage_pool.py new file mode 100644 index 00000000..e4034587 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_storage_pool.py @@ -0,0 +1,489 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017, Netservers Ltd. <support@netservers.co.uk> +# Copyright (c) 2017, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_storage_pool +short_description: Manages Primary Storage Pools on Apache CloudStack based clouds. +description: + - Create, update, put into maintenance, disable, enable and remove storage pools. +author: + - Netservers Ltd. (@netservers) + - René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name of the storage pool. + type: str + required: true + zone: + description: + - Name of the zone in which the host should be deployed. + - If not set, default zone is used. + type: str + storage_url: + description: + - URL of the storage pool. + - Required if I(state=present). + type: str + pod: + description: + - Name of the pod. + type: str + cluster: + description: + - Name of the cluster. + type: str + scope: + description: + - The scope of the storage pool. + - Defaults to cluster when C(cluster) is provided, otherwise zone. + type: str + choices: [ cluster, zone ] + managed: + description: + - Whether the storage pool should be managed by CloudStack. + - Only considered on creation. + type: bool + hypervisor: + description: + - Required when creating a zone scoped pool. + - Possible values are C(KVM), C(VMware), C(BareMetal), C(XenServer), C(LXC), C(HyperV), C(UCS), C(OVM), C(Simulator). + type: str + storage_tags: + description: + - Tags associated with this storage pool. + type: list + elements: str + aliases: [ storage_tag ] + provider: + description: + - Name of the storage provider e.g. SolidFire, SolidFireShared, DefaultPrimary, CloudByte. + type: str + default: DefaultPrimary + capacity_bytes: + description: + - Bytes CloudStack can provision from this storage pool. + type: int + capacity_iops: + description: + - Bytes CloudStack can provision from this storage pool. + type: int + allocation_state: + description: + - Allocation state of the storage pool. + type: str + choices: [ enabled, disabled, maintenance ] + state: + description: + - State of the storage pool. + type: str + default: present + choices: [ present, absent ] +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: ensure a zone scoped storage_pool is present + ngine_io.cloudstack.cs_storage_pool: + zone: zone01 + storage_url: rbd://admin:SECRET@ceph-mons.domain/poolname + provider: DefaultPrimary + name: Ceph RBD + scope: zone + hypervisor: KVM + +- name: ensure a cluster scoped storage_pool is disabled + ngine_io.cloudstack.cs_storage_pool: + name: Ceph RBD + zone: zone01 + cluster: cluster01 + pod: pod01 + storage_url: rbd://admin:SECRET@ceph-the-mons.domain/poolname + provider: DefaultPrimary + scope: cluster + allocation_state: disabled + +- name: ensure a cluster scoped storage_pool is in maintenance + ngine_io.cloudstack.cs_storage_pool: + name: Ceph RBD + zone: zone01 + cluster: cluster01 + pod: pod01 + storage_url: rbd://admin:SECRET@ceph-the-mons.domain/poolname + provider: DefaultPrimary + scope: cluster + allocation_state: maintenance + +- name: ensure a storage_pool is absent + ngine_io.cloudstack.cs_storage_pool: + name: Ceph RBD + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the pool. + returned: success + type: str + sample: a3fca65a-7db1-4891-b97c-48806a978a96 +created: + description: Date of the pool was created. + returned: success + type: str + sample: 2014-12-01T14:57:57+0100 +capacity_iops: + description: IOPS CloudStack can provision from this storage pool + returned: when available + type: int + sample: 60000 +zone: + description: The name of the zone. + returned: success + type: str + sample: Zone01 +cluster: + description: The name of the cluster. + returned: when scope is cluster + type: str + sample: Cluster01 +pod: + description: The name of the pod. + returned: when scope is cluster + type: str + sample: Cluster01 +disk_size_allocated: + description: The pool's currently allocated disk space. + returned: success + type: int + sample: 2443517624320 +disk_size_total: + description: The total size of the pool. + returned: success + type: int + sample: 3915055693824 +disk_size_used: + description: The pool's currently used disk size. + returned: success + type: int + sample: 1040862622180 +scope: + description: The scope of the storage pool. + returned: success + type: str + sample: cluster +hypervisor: + description: Hypervisor related to this storage pool. + returned: when available + type: str + sample: KVM +state: + description: The state of the storage pool as returned by the API. + returned: success + type: str + sample: Up +allocation_state: + description: The state of the storage pool. + returned: success + type: str + sample: enabled +path: + description: The storage pool path used in the storage_url. + returned: success + type: str + sample: poolname +overprovision_factor: + description: The overprovision factor of the storage pool. + returned: success + type: str + sample: 2.0 +suitable_for_migration: + description: Whether the storage pool is suitable to migrate a volume or not. + returned: success + type: bool + sample: false +storage_capabilities: + description: Capabilities of the storage pool. + returned: success + type: dict + sample: {"VOLUME_SNAPSHOT_QUIESCEVM": "false"} +storage_tags: + description: the tags for the storage pool. + returned: success + type: list + sample: ["perf", "ssd"] +''' + +# import cloudstack common +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackStoragePool(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackStoragePool, self).__init__(module) + self.returns = { + 'capacityiops': 'capacity_iops', + 'podname': 'pod', + 'clustername': 'cluster', + 'disksizeallocated': 'disk_size_allocated', + 'disksizetotal': 'disk_size_total', + 'disksizeused': 'disk_size_used', + 'scope': 'scope', + 'hypervisor': 'hypervisor', + 'type': 'type', + 'ip_address': 'ipaddress', + 'path': 'path', + 'overprovisionfactor': 'overprovision_factor', + 'storagecapabilities': 'storage_capabilities', + 'suitableformigration': 'suitable_for_migration', + } + self.allocation_states = { + # Host state: param state + 'Up': 'enabled', + 'Disabled': 'disabled', + 'Maintenance': 'maintenance', + } + self.storage_pool = None + + def _get_common_args(self): + return { + 'name': self.module.params.get('name'), + 'url': self.module.params.get('storage_url'), + 'zoneid': self.get_zone(key='id'), + 'provider': self.get_storage_provider(), + 'scope': self.module.params.get('scope'), + 'hypervisor': self.module.params.get('hypervisor'), + 'capacitybytes': self.module.params.get('capacity_bytes'), + 'capacityiops': self.module.params.get('capacity_iops'), + } + + def _allocation_state_enabled_disabled_changed(self, pool, allocation_state): + if allocation_state in ['enabled', 'disabled']: + for pool_state, param_state in self.allocation_states.items(): + if pool_state == pool['state'] and allocation_state != param_state: + return True + return False + + def _handle_allocation_state(self, pool, state=None): + allocation_state = state or self.module.params.get('allocation_state') + if not allocation_state: + return pool + + if self.allocation_states.get(pool['state']) == allocation_state: + return pool + + # Cancel maintenance if target state is enabled/disabled + elif allocation_state in ['enabled', 'disabled']: + pool = self._cancel_maintenance(pool) + pool = self._update_storage_pool(pool=pool, allocation_state=allocation_state) + + # Only an enabled host can put in maintenance + elif allocation_state == 'maintenance': + pool = self._update_storage_pool(pool=pool, allocation_state='enabled') + pool = self._enable_maintenance(pool=pool) + + return pool + + def _create_storage_pool(self): + args = self._get_common_args() + args.update({ + 'clusterid': self.get_cluster(key='id'), + 'podid': self.get_pod(key='id'), + 'managed': self.module.params.get('managed'), + }) + + scope = self.module.params.get('scope') + if scope is None: + args['scope'] = 'cluster' if args['clusterid'] else 'zone' + + self.result['changed'] = True + + if not self.module.check_mode: + res = self.query_api('createStoragePool', **args) + return res['storagepool'] + + def _update_storage_pool(self, pool, allocation_state=None): + args = { + 'id': pool['id'], + 'capacitybytes': self.module.params.get('capacity_bytes'), + 'capacityiops': self.module.params.get('capacity_iops'), + 'tags': self.get_storage_tags(), + } + + if self.has_changed(args, pool) or self._allocation_state_enabled_disabled_changed(pool, allocation_state): + self.result['changed'] = True + args['enabled'] = allocation_state == 'enabled' if allocation_state in ['enabled', 'disabled'] else None + if not self.module.check_mode: + res = self.query_api('updateStoragePool', **args) + pool = res['storagepool'] + return pool + + def _enable_maintenance(self, pool): + if pool['state'].lower() != "maintenance": + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('enableStorageMaintenance', id=pool['id']) + pool = self.poll_job(res, 'storagepool') + return pool + + def _cancel_maintenance(self, pool): + if pool['state'].lower() == "maintenance": + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('cancelStorageMaintenance', id=pool['id']) + pool = self.poll_job(res, 'storagepool') + return pool + + def get_storage_tags(self): + storage_tags = self.module.params.get('storage_tags') + if storage_tags is None: + return None + return ','.join(storage_tags) + + def get_storage_pool(self, key=None): + if self.storage_pool is None: + zoneid = self.get_zone(key='id') + clusterid = self.get_cluster(key='id') + podid = self.get_pod(key='id') + + args = { + 'zoneid': zoneid, + 'podid': podid, + 'clusterid': clusterid, + 'name': self.module.params.get('name'), + } + + res = self.query_api('listStoragePools', **args) + if 'storagepool' not in res: + return None + + self.storage_pool = res['storagepool'][0] + + return self.storage_pool + + def present_storage_pool(self): + pool = self.get_storage_pool() + if pool: + pool = self._update_storage_pool(pool=pool) + else: + pool = self._create_storage_pool() + + if pool: + pool = self._handle_allocation_state(pool=pool) + + return pool + + def absent_storage_pool(self): + pool = self.get_storage_pool() + if pool: + self.result['changed'] = True + + args = { + 'id': pool['id'], + } + if not self.module.check_mode: + # Only a pool in maintenance can be deleted + self._handle_allocation_state(pool=pool, state='maintenance') + self.query_api('deleteStoragePool', **args) + return pool + + def get_storage_provider(self, type="primary"): + args = { + 'type': type, + } + provider = self.module.params.get('provider') + storage_providers = self.query_api('listStorageProviders', **args) + for sp in storage_providers.get('dataStoreProvider') or []: + if sp['name'].lower() == provider.lower(): + return provider + self.fail_json(msg="Storage provider %s not found" % provider) + + def get_cluster(self, key=None): + cluster = self.module.params.get('cluster') + if not cluster: + return None + + args = { + 'name': cluster, + 'zoneid': self.get_zone(key='id'), + } + + clusters = self.query_api('listClusters', **args) + if clusters: + return self._get_by_key(key, clusters['cluster'][0]) + + self.fail_json(msg="Cluster %s not found" % cluster) + + def get_result(self, pool): + super(AnsibleCloudStackStoragePool, self).get_result(pool) + if pool: + self.result['storage_url'] = "%s://%s/%s" % (pool['type'], pool['ipaddress'], pool['path']) + self.result['scope'] = pool['scope'].lower() + self.result['storage_tags'] = pool['tags'].split(',') if pool.get('tags') else [] + self.result['allocation_state'] = self.allocation_states.get(pool['state']) + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + storage_url=dict(), + zone=dict(), + pod=dict(), + cluster=dict(), + scope=dict(choices=['zone', 'cluster']), + hypervisor=dict(), + provider=dict(default='DefaultPrimary'), + capacity_bytes=dict(type='int'), + capacity_iops=dict(type='int'), + managed=dict(type='bool'), + storage_tags=dict(type='list', elements='str', aliases=['storage_tag']), + allocation_state=dict(choices=['enabled', 'disabled', 'maintenance']), + state=dict(choices=['present', 'absent'], default='present'), + )) + + required_together = cs_required_together() + required_together.extend([ + ['pod', 'cluster'], + ]) + module = AnsibleModule( + argument_spec=argument_spec, + required_together=required_together, + required_if=[ + ('state', 'present', ['storage_url']), + ], + supports_check_mode=True + ) + + acs_storage_pool = AnsibleCloudStackStoragePool(module) + + state = module.params.get('state') + if state in ['absent']: + pool = acs_storage_pool.absent_storage_pool() + else: + pool = acs_storage_pool.present_storage_pool() + + result = acs_storage_pool.get_result(pool) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_template.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_template.py new file mode 100644 index 00000000..16902595 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_template.py @@ -0,0 +1,741 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_template +short_description: Manages templates on Apache CloudStack based clouds. +description: + - Register templates from an URL. + - Create templates from a ROOT volume of a stopped VM or its snapshot. + - Update, extract and delete templates. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name of the template. + type: str + required: true + url: + description: + - URL of where the template is hosted on I(state=present). + - URL to which the template would be extracted on I(state=extracted). + - Mutually exclusive with I(vm). + type: str + vm: + description: + - VM name the template will be created from its volume or alternatively from a snapshot. + - VM must be in stopped state if created from its volume. + - Mutually exclusive with I(url). + type: str + snapshot: + description: + - Name of the snapshot, created from the VM ROOT volume, the template will be created from. + - I(vm) is required together with this argument. + type: str + os_type: + description: + - OS type that best represents the OS of this template. + type: str + checksum: + description: + - The MD5 checksum value of this template. + - If set, we search by checksum instead of name. + type: str + is_public: + description: + - Register the template to be publicly available to all users. + - Only used if I(state) is C(present). + type: bool + is_featured: + description: + - Register the template to be featured. + - Only used if I(state) is C(present). + type: bool + is_dynamically_scalable: + description: + - Register the template having XS/VMware tools installed in order to support dynamic scaling of VM CPU/memory. + - Only used if I(state) is C(present). + type: bool + cross_zones: + description: + - Whether the template should be synced or removed across zones. + - Only used if I(state) is C(present) or C(absent). + default: no + type: bool + mode: + description: + - Mode for the template extraction. + - Only used if I(state=extracted). + type: str + default: http_download + choices: [ http_download, ftp_upload ] + domain: + description: + - Domain the template, snapshot or VM is related to. + type: str + account: + description: + - Account the template, snapshot or VM is related to. + type: str + project: + description: + - Name of the project the template to be registered in. + type: str + zone: + description: + - Name of the zone you wish the template to be registered or deleted from. + - If not specified, first found zone will be used. + type: str + template_filter: + description: + - Name of the filter used to search for the template. + - The filter C(all) was added in 2.7. + type: str + default: self + choices: [ all, featured, self, selfexecutable, sharedexecutable, executable, community ] + template_find_options: + description: + - Options to find a template uniquely. + - More than one allowed. + type: list + elements: str + choices: [ display_text, checksum, cross_zones ] + aliases: [ template_find_option ] + default: [] + hypervisor: + description: + - Name the hypervisor to be used for creating the new template. + - Relevant when using I(state=present). + - Possible values are C(KVM), C(VMware), C(BareMetal), C(XenServer), C(LXC), C(HyperV), C(UCS), C(OVM), C(Simulator). + type: str + requires_hvm: + description: + - Whether the template requires HVM or not. + - Only considered while creating the template. + type: bool + password_enabled: + description: + - Enable template password reset support. + type: bool + template_tag: + description: + - The tag for this template. + type: str + sshkey_enabled: + description: + - True if the template supports the sshkey upload feature. + - Only considered if I(url) is used (API limitation). + type: bool + is_routing: + description: + - Sets the template type to routing, i.e. if template is used to deploy routers. + - Only considered if I(url) is used. + type: bool + format: + description: + - The format for the template. + - Only considered if I(state=present). + type: str + choices: [ QCOW2, RAW, VHD, OVA ] + is_extractable: + description: + - Allows the template or its derivatives to be extractable. + type: bool + details: + description: + - Template details in key/value pairs. + type: str + bits: + description: + - 32 or 64 bits support. + type: int + default: 64 + choices: [ 32, 64 ] + display_text: + description: + - Display text of the template. + type: str + state: + description: + - State of the template. + type: str + default: present + choices: [ present, absent, extracted ] + poll_async: + description: + - Poll async jobs until job has finished. + default: yes + type: bool + tags: + description: + - List of tags. Tags are a list of dictionaries having keys I(key) and I(value). + - "To delete all tags, set a empty list e.g. I(tags: [])." + type: list + elements: dict + aliases: [ tag ] +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: register a systemvm template + ngine_io.cloudstack.cs_template: + name: systemvm-vmware-4.5 + url: "http://packages.shapeblue.com/systemvmtemplate/4.5/systemvm64template-4.5-vmware.ova" + hypervisor: VMware + format: OVA + cross_zones: yes + os_type: Debian GNU/Linux 7(64-bit) + +- name: Create a template from a stopped virtual machine's volume + ngine_io.cloudstack.cs_template: + name: Debian 9 (64-bit) 20GB ({{ ansible_date_time.date }}) + vm: debian-9-base-vm + os_type: Debian GNU/Linux 9 (64-bit) + zone: tokio-ix + password_enabled: yes + is_public: yes + + +# Note: Use template_find_option(s) when a template name is not unique +- name: Create a template from a stopped virtual machine's volume + ngine_io.cloudstack.cs_template: + name: Debian 9 (64-bit) + display_text: Debian 9 (64-bit) 20GB ({{ ansible_date_time.date }}) + template_find_option: display_text + vm: debian-9-base-vm + os_type: Debian GNU/Linux 9 (64-bit) + zone: tokio-ix + password_enabled: yes + is_public: yes + +- name: create a template from a virtual machine's root volume snapshot + ngine_io.cloudstack.cs_template: + name: Debian 9 (64-bit) Snapshot ROOT-233_2015061509114 + snapshot: ROOT-233_2015061509114 + os_type: Debian GNU/Linux 9 (64-bit) + zone: tokio-ix + password_enabled: yes + is_public: yes + +- name: Remove a template + ngine_io.cloudstack.cs_template: + name: systemvm-4.2 + cross_zones: yes + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the template or extracted object. + returned: success + type: str + sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f +name: + description: Name of the template or extracted object. + returned: success + type: str + sample: Debian 7 64-bit +display_text: + description: Display text of the template. + returned: if available + type: str + sample: Debian 7.7 64-bit minimal 2015-03-19 +checksum: + description: MD5 checksum of the template. + returned: if available + type: str + sample: 0b31bccccb048d20b551f70830bb7ad0 +status: + description: Status of the template or extracted object. + returned: success + type: str + sample: Download Complete +is_ready: + description: True if the template is ready to be deployed from. + returned: if available + type: bool + sample: true +is_public: + description: True if the template is public. + returned: if available + type: bool + sample: true +is_featured: + description: True if the template is featured. + returned: if available + type: bool + sample: true +is_extractable: + description: True if the template is extractable. + returned: if available + type: bool + sample: true +format: + description: Format of the template. + returned: if available + type: str + sample: OVA +os_type: + description: Type of the OS. + returned: if available + type: str + sample: CentOS 6.5 (64-bit) +password_enabled: + description: True if the reset password feature is enabled, false otherwise. + returned: if available + type: bool + sample: false +sshkey_enabled: + description: true if template is sshkey enabled, false otherwise. + returned: if available + type: bool + sample: false +cross_zones: + description: true if the template is managed across all zones, false otherwise. + returned: if available + type: bool + sample: false +template_type: + description: Type of the template. + returned: if available + type: str + sample: USER +created: + description: Date of registering. + returned: success + type: str + sample: 2015-03-29T14:57:06+0200 +template_tag: + description: Template tag related to this template. + returned: if available + type: str + sample: special +hypervisor: + description: Hypervisor related to this template. + returned: if available + type: str + sample: VMware +mode: + description: Mode of extraction + returned: on state=extracted + type: str + sample: http_download +state: + description: State of the extracted template + returned: on state=extracted + type: str + sample: DOWNLOAD_URL_CREATED +url: + description: Url to which the template is extracted to + returned: on state=extracted + type: str + sample: "http://1.2.3.4/userdata/eb307f13-4aca-45e8-b157-a414a14e6b04.ova" +tags: + description: List of resource tags associated with the template. + returned: if available + type: list + sample: '[ { "key": "foo", "value": "bar" } ]' +zone: + description: Name of zone the template is registered in. + returned: success + type: str + sample: zuerich +domain: + description: Domain the template is related to. + returned: success + type: str + sample: example domain +account: + description: Account the template is related to. + returned: success + type: str + sample: example account +project: + description: Name of project the template is related to. + returned: success + type: str + sample: Production +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackTemplate(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackTemplate, self).__init__(module) + self.returns = { + 'checksum': 'checksum', + 'status': 'status', + 'isready': 'is_ready', + 'templatetag': 'template_tag', + 'sshkeyenabled': 'sshkey_enabled', + 'passwordenabled': 'password_enabled', + 'templatetype': 'template_type', + 'ostypename': 'os_type', + 'crossZones': 'cross_zones', + 'format': 'format', + 'hypervisor': 'hypervisor', + 'url': 'url', + 'extractMode': 'mode', + 'state': 'state', + } + + def _get_args(self): + args = { + 'name': self.module.params.get('name'), + 'displaytext': self.get_or_fallback('display_text', 'name'), + 'bits': self.module.params.get('bits'), + 'isdynamicallyscalable': self.module.params.get('is_dynamically_scalable'), + 'isextractable': self.module.params.get('is_extractable'), + 'isfeatured': self.module.params.get('is_featured'), + 'ispublic': self.module.params.get('is_public'), + 'passwordenabled': self.module.params.get('password_enabled'), + 'requireshvm': self.module.params.get('requires_hvm'), + 'templatetag': self.module.params.get('template_tag'), + 'ostypeid': self.get_os_type(key='id'), + } + + if not args['ostypeid']: + self.module.fail_json(msg="Missing required arguments: os_type") + + return args + + def get_root_volume(self, key=None): + args = { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'virtualmachineid': self.get_vm(key='id'), + 'type': "ROOT" + } + volumes = self.query_api('listVolumes', **args) + if volumes: + return self._get_by_key(key, volumes['volume'][0]) + self.module.fail_json(msg="Root volume for '%s' not found" % self.get_vm('name')) + + def get_snapshot(self, key=None): + snapshot = self.module.params.get('snapshot') + if not snapshot: + return None + + args = { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'volumeid': self.get_root_volume('id'), + 'fetch_list': True, + } + snapshots = self.query_api('listSnapshots', **args) + if snapshots: + for s in snapshots: + if snapshot in [s['name'], s['id']]: + return self._get_by_key(key, s) + self.module.fail_json(msg="Snapshot '%s' not found" % snapshot) + + def present_template(self): + template = self.get_template() + if template: + template = self.update_template(template) + elif self.module.params.get('url'): + template = self.register_template() + elif self.module.params.get('vm'): + template = self.create_template() + else: + self.fail_json(msg="one of the following is required on state=present: url, vm") + return template + + def create_template(self): + template = None + self.result['changed'] = True + + args = self._get_args() + snapshot_id = self.get_snapshot(key='id') + if snapshot_id: + args['snapshotid'] = snapshot_id + else: + args['volumeid'] = self.get_root_volume('id') + + if not self.module.check_mode: + template = self.query_api('createTemplate', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + template = self.poll_job(template, 'template') + + if template: + template = self.ensure_tags(resource=template, resource_type='Template') + + return template + + def register_template(self): + required_params = [ + 'format', + 'url', + 'hypervisor', + ] + self.module.fail_on_missing_params(required_params=required_params) + template = None + self.result['changed'] = True + args = self._get_args() + args.update({ + 'url': self.module.params.get('url'), + 'format': self.module.params.get('format'), + 'checksum': self.module.params.get('checksum'), + 'isextractable': self.module.params.get('is_extractable'), + 'isrouting': self.module.params.get('is_routing'), + 'sshkeyenabled': self.module.params.get('sshkey_enabled'), + 'hypervisor': self.get_hypervisor(), + 'domainid': self.get_domain(key='id'), + 'account': self.get_account(key='name'), + 'projectid': self.get_project(key='id'), + }) + + if not self.module.params.get('cross_zones'): + args['zoneid'] = self.get_zone(key='id') + else: + args['zoneid'] = -1 + + if not self.module.check_mode: + self.query_api('registerTemplate', **args) + template = self.get_template() + return template + + def update_template(self, template): + args = { + 'id': template['id'], + 'displaytext': self.get_or_fallback('display_text', 'name'), + 'format': self.module.params.get('format'), + 'isdynamicallyscalable': self.module.params.get('is_dynamically_scalable'), + 'isrouting': self.module.params.get('is_routing'), + 'ostypeid': self.get_os_type(key='id'), + 'passwordenabled': self.module.params.get('password_enabled'), + } + if self.has_changed(args, template): + self.result['changed'] = True + if not self.module.check_mode: + self.query_api('updateTemplate', **args) + template = self.get_template() + + args = { + 'id': template['id'], + 'isextractable': self.module.params.get('is_extractable'), + 'isfeatured': self.module.params.get('is_featured'), + 'ispublic': self.module.params.get('is_public'), + } + if self.has_changed(args, template): + self.result['changed'] = True + if not self.module.check_mode: + self.query_api('updateTemplatePermissions', **args) + # Refresh + template = self.get_template() + + if template: + template = self.ensure_tags(resource=template, resource_type='Template') + + return template + + def _is_find_option(self, param_name): + return param_name in self.module.params.get('template_find_options') + + def _find_option_match(self, template, param_name, internal_name=None): + if not internal_name: + internal_name = param_name + + if param_name in self.module.params.get('template_find_options'): + param_value = self.module.params.get(param_name) + + if not param_value: + self.fail_json(msg="The param template_find_options has %s but param was not provided." % param_name) + + if template[internal_name] == param_value: + return True + return False + + def get_template(self): + args = { + 'name': self.module.params.get('name'), + 'templatefilter': self.module.params.get('template_filter'), + 'domainid': self.get_domain(key='id'), + 'account': self.get_account(key='name'), + 'projectid': self.get_project(key='id') + } + + cross_zones = self.module.params.get('cross_zones') + if not cross_zones: + args['zoneid'] = self.get_zone(key='id') + + template_found = None + + templates = self.query_api('listTemplates', **args) + if templates: + for tmpl in templates['template']: + + if self._is_find_option('cross_zones') and not self._find_option_match( + template=tmpl, + param_name='cross_zones', + internal_name='crossZones'): + continue + + if self._is_find_option('checksum') and not self._find_option_match( + template=tmpl, + param_name='checksum'): + continue + + if self._is_find_option('display_text') and not self._find_option_match( + template=tmpl, + param_name='display_text', + internal_name='displaytext'): + continue + + if not template_found: + template_found = tmpl + # A cross zones template has one entry per zone but the same id + elif tmpl['id'] == template_found['id']: + continue + else: + self.fail_json(msg="Multiple templates found matching provided params. Please use template_find_options.") + + return template_found + + def extract_template(self): + template = self.get_template() + if not template: + self.module.fail_json(msg="Failed: template not found") + + if self.module.params.get('cross_zones'): + self.module.warn('cross_zones parameter is ignored when state is extracted') + + args = { + 'id': template['id'], + 'url': self.module.params.get('url'), + 'mode': self.module.params.get('mode'), + 'zoneid': self.get_zone(key='id') + } + self.result['changed'] = True + + if not self.module.check_mode: + template = self.query_api('extractTemplate', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + template = self.poll_job(template, 'template') + return template + + def remove_template(self): + template = self.get_template() + if template: + self.result['changed'] = True + + args = { + 'id': template['id'] + } + if not self.module.params.get('cross_zones'): + args['zoneid'] = self.get_zone(key='id') + + if not self.module.check_mode: + res = self.query_api('deleteTemplate', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + res = self.poll_job(res, 'template') + return template + + def get_result(self, template): + super(AnsibleCloudStackTemplate, self).get_result(template) + if template: + if 'isextractable' in template: + self.result['is_extractable'] = True if template['isextractable'] else False + if 'isfeatured' in template: + self.result['is_featured'] = True if template['isfeatured'] else False + if 'ispublic' in template: + self.result['is_public'] = True if template['ispublic'] else False + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + display_text=dict(), + url=dict(), + vm=dict(), + snapshot=dict(), + os_type=dict(), + is_public=dict(type='bool'), + is_featured=dict(type='bool'), + is_dynamically_scalable=dict(type='bool'), + is_extractable=dict(type='bool'), + is_routing=dict(type='bool'), + checksum=dict(), + template_filter=dict(default='self', choices=['all', 'featured', 'self', 'selfexecutable', 'sharedexecutable', 'executable', 'community']), + template_find_options=dict( + type='list', + elements='str', + choices=['display_text', 'checksum', 'cross_zones'], + aliases=['template_find_option'], + default=[], + ), + hypervisor=dict(), + requires_hvm=dict(type='bool'), + password_enabled=dict(type='bool', no_log=False), + template_tag=dict(), + sshkey_enabled=dict(type='bool'), + format=dict(choices=['QCOW2', 'RAW', 'VHD', 'OVA']), + details=dict(), + bits=dict(type='int', choices=[32, 64], default=64), + state=dict(choices=['present', 'absent', 'extracted'], default='present'), + cross_zones=dict(type='bool', default=False), + mode=dict(choices=['http_download', 'ftp_upload'], default='http_download'), + zone=dict(), + domain=dict(), + account=dict(), + project=dict(), + poll_async=dict(type='bool', default=True), + tags=dict(type='list', elements='dict', aliases=['tag']), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + mutually_exclusive=( + ['url', 'vm'], + ['zone', 'cross_zones'], + ), + supports_check_mode=True + ) + + acs_tpl = AnsibleCloudStackTemplate(module) + + state = module.params.get('state') + if state == 'absent': + tpl = acs_tpl.remove_template() + + elif state == 'extracted': + tpl = acs_tpl.extract_template() + else: + tpl = acs_tpl.present_template() + + result = acs_tpl.get_result(tpl) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_traffic_type.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_traffic_type.py new file mode 100644 index 00000000..22ec3ec3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_traffic_type.py @@ -0,0 +1,321 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2019, Patryk D. Cichy <patryk.d.cichy@gmail.com> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_traffic_type +short_description: Manages traffic types on CloudStack Physical Networks +description: + - Add, remove, update Traffic Types associated with CloudStack Physical Networks. +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +author: + - Patryk Cichy (@PatTheSilent) +version_added: 0.1.0 +options: + physical_network: + description: + - the name of the Physical Network + required: true + type: str + zone: + description: + - Name of the zone with the physical network. + - Default zone will be used if this is empty. + type: str + traffic_type: + description: + - the trafficType to be added to the physical network. + required: true + choices: [Management, Guest, Public, Storage] + type: str + state: + description: + - State of the traffic type + choices: [present, absent] + default: present + type: str + hyperv_networklabel: + description: + - The network name label of the physical device dedicated to this traffic on a HyperV host. + type: str + isolation_method: + description: + - Use if the physical network has multiple isolation types and traffic type is public. + choices: [vlan, vxlan] + type: str + kvm_networklabel: + description: + - The network name label of the physical device dedicated to this traffic on a KVM host. + type: str + ovm3_networklabel: + description: + - The network name of the physical device dedicated to this traffic on an OVM3 host. + type: str + vlan: + description: + - The VLAN id to be used for Management traffic by VMware host. + type: str + vmware_networklabel: + description: + - The network name label of the physical device dedicated to this traffic on a VMware host. + type: str + xen_networklabel: + description: + - The network name label of the physical device dedicated to this traffic on a XenServer host. + type: str + poll_async: + description: + - Poll async jobs until job has finished. + default: yes + type: bool +''' + +EXAMPLES = ''' +- name: add a traffic type + ngine_io.cloudstack.cs_traffic_type: + physical_network: public-network + traffic_type: Guest + zone: test-zone + +- name: update traffic type + ngine_io.cloudstack.cs_traffic_type: + physical_network: public-network + traffic_type: Guest + kvm_networklabel: cloudbr0 + zone: test-zone + +- name: remove traffic type + ngine_io.cloudstack.cs_traffic_type: + physical_network: public-network + traffic_type: Public + state: absent + zone: test-zone +''' + +RETURN = ''' +--- +id: + description: ID of the network provider + returned: success + type: str + sample: 659c1840-9374-440d-a412-55ca360c9d3c +traffic_type: + description: the trafficType that was added to the physical network + returned: success + type: str + sample: Public +hyperv_networklabel: + description: The network name label of the physical device dedicated to this traffic on a HyperV host + returned: success + type: str + sample: HyperV Internal Switch +kvm_networklabel: + description: The network name label of the physical device dedicated to this traffic on a KVM host + returned: success + type: str + sample: cloudbr0 +ovm3_networklabel: + description: The network name of the physical device dedicated to this traffic on an OVM3 host + returned: success + type: str + sample: cloudbr0 +physical_network: + description: the physical network this belongs to + returned: success + type: str + sample: 28ed70b7-9a1f-41bf-94c3-53a9f22da8b6 +vmware_networklabel: + description: The network name label of the physical device dedicated to this traffic on a VMware host + returned: success + type: str + sample: Management Network +xen_networklabel: + description: The network name label of the physical device dedicated to this traffic on a XenServer host + returned: success + type: str + sample: xenbr0 +zone: + description: Name of zone the physical network is in. + returned: success + type: str + sample: ch-gva-2 +''' + +from ..module_utils.cloudstack import AnsibleCloudStack, cs_argument_spec, cs_required_together +from ansible.module_utils.basic import AnsibleModule + + +class AnsibleCloudStackTrafficType(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackTrafficType, self).__init__(module) + self.returns = { + 'traffictype': 'traffic_type', + 'hypervnetworklabel': 'hyperv_networklabel', + 'kvmnetworklabel': 'kvm_networklabel', + 'ovm3networklabel': 'ovm3_networklabel', + 'physicalnetworkid': 'physical_network', + 'vmwarenetworklabel': 'vmware_networklabel', + 'xennetworklabel': 'xen_networklabel' + } + + self.traffic_type = None + + def _get_label_args(self): + label_args = dict() + if self.module.params.get('hyperv_networklabel'): + label_args.update(dict(hypervnetworklabel=self.module.params.get('hyperv_networklabel'))) + if self.module.params.get('kvm_networklabel'): + label_args.update(dict(kvmnetworklabel=self.module.params.get('kvm_networklabel'))) + if self.module.params.get('ovm3_networklabel'): + label_args.update(dict(ovm3networklabel=self.module.params.get('ovm3_networklabel'))) + if self.module.params.get('vmware_networklabel'): + label_args.update(dict(vmwarenetworklabel=self.module.params.get('vmware_networklabel'))) + return label_args + + def _get_additional_args(self): + additional_args = dict() + + if self.module.params.get('isolation_method'): + additional_args.update(dict(isolationmethod=self.module.params.get('isolation_method'))) + + if self.module.params.get('vlan'): + additional_args.update(dict(vlan=self.module.params.get('vlan'))) + + additional_args.update(self._get_label_args()) + + return additional_args + + def get_traffic_types(self): + args = { + 'physicalnetworkid': self.get_physical_network(key='id') + } + traffic_types = self.query_api('listTrafficTypes', **args) + return traffic_types + + def get_traffic_type(self): + if self.traffic_type: + return self.traffic_type + + traffic_type = self.module.params.get('traffic_type') + + traffic_types = self.get_traffic_types() + + if traffic_types: + for t_type in traffic_types['traffictype']: + if traffic_type.lower() in [t_type['traffictype'].lower(), t_type['id']]: + self.traffic_type = t_type + break + return self.traffic_type + + def present_traffic_type(self): + traffic_type = self.get_traffic_type() + if traffic_type: + self.traffic_type = self.update_traffic_type() + else: + self.result['changed'] = True + self.traffic_type = self.add_traffic_type() + + return self.traffic_type + + def add_traffic_type(self): + traffic_type = self.module.params.get('traffic_type') + args = { + 'physicalnetworkid': self.get_physical_network(key='id'), + 'traffictype': traffic_type + } + args.update(self._get_additional_args()) + if not self.module.check_mode: + resource = self.query_api('addTrafficType', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + self.traffic_type = self.poll_job(resource, 'traffictype') + return self.traffic_type + + def absent_traffic_type(self): + traffic_type = self.get_traffic_type() + if traffic_type: + + args = { + 'id': traffic_type['id'] + } + self.result['changed'] = True + if not self.module.check_mode: + resource = self.query_api('deleteTrafficType', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(resource, 'traffictype') + + return traffic_type + + def update_traffic_type(self): + + traffic_type = self.get_traffic_type() + args = { + 'id': traffic_type['id'] + } + args.update(self._get_label_args()) + if self.has_changed(args, traffic_type): + self.result['changed'] = True + if not self.module.check_mode: + resource = self.query_api('updateTrafficType', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + self.traffic_type = self.poll_job(resource, 'traffictype') + + return self.traffic_type + + +def setup_module_object(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + physical_network=dict(required=True), + zone=dict(), + state=dict(choices=['present', 'absent'], default='present'), + traffic_type=dict(required=True, choices=['Management', 'Guest', 'Public', 'Storage']), + hyperv_networklabel=dict(), + isolation_method=dict(choices=['vlan', 'vxlan']), + kvm_networklabel=dict(), + ovm3_networklabel=dict(), + vlan=dict(), + vmware_networklabel=dict(), + xen_networklabel=dict(), + poll_async=dict(type='bool', default=True) + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + return module + + +def execute_module(module): + actt = AnsibleCloudStackTrafficType(module) + state = module.params.get('state') + + if state in ['present']: + result = actt.present_traffic_type() + else: + result = actt.absent_traffic_type() + + return actt.get_result(result) + + +def main(): + module = setup_module_object() + result = execute_module(module) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_user.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_user.py new file mode 100644 index 00000000..816e5f70 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_user.py @@ -0,0 +1,436 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_user +short_description: Manages users on Apache CloudStack based clouds. +description: + - Create, update, disable, lock, enable and remove users. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + username: + description: + - Username of the user. + type: str + required: true + account: + description: + - Account the user will be created under. + - Required on I(state=present). + type: str + password: + description: + - Password of the user to be created. + - Required on I(state=present). + - Only considered on creation and will not be updated if user exists. + type: str + first_name: + description: + - First name of the user. + - Required on I(state=present). + type: str + last_name: + description: + - Last name of the user. + - Required on I(state=present). + type: str + email: + description: + - Email of the user. + - Required on I(state=present). + type: str + timezone: + description: + - Timezone of the user. + type: str + keys_registered: + description: + - If API keys of the user should be generated. + - "Note: Keys can not be removed by the API again." + type: bool + default: no + domain: + description: + - Domain the user is related to. + type: str + default: ROOT + state: + description: + - State of the user. + - C(unlocked) is an alias for C(enabled). + type: str + default: present + choices: [ present, absent, enabled, disabled, locked, unlocked ] + poll_async: + description: + - Poll async jobs until job has finished. + type: bool + default: yes +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Create an user in domain 'CUSTOMERS' + ngine_io.cloudstack.cs_user: + account: developers + username: johndoe + password: S3Cur3 + last_name: Doe + first_name: John + email: john.doe@example.com + domain: CUSTOMERS + +- name: Lock an existing user in domain 'CUSTOMERS' + ngine_io.cloudstack.cs_user: + username: johndoe + domain: CUSTOMERS + state: locked + +- name: Disable an existing user in domain 'CUSTOMERS' + ngine_io.cloudstack.cs_user: + username: johndoe + domain: CUSTOMERS + state: disabled + +- name: Enable/unlock an existing user in domain 'CUSTOMERS' + ngine_io.cloudstack.cs_user: + username: johndoe + domain: CUSTOMERS + state: enabled + +- name: Remove an user in domain 'CUSTOMERS' + ngine_io.cloudstack.cs_user: + name: customer_xy + domain: CUSTOMERS + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the user. + returned: success + type: str + sample: 87b1e0ce-4e01-11e4-bb66-0050569e64b8 +username: + description: Username of the user. + returned: success + type: str + sample: johndoe +fist_name: + description: First name of the user. + returned: success + type: str + sample: John +last_name: + description: Last name of the user. + returned: success + type: str + sample: Doe +email: + description: Emailof the user. + returned: success + type: str + sample: john.doe@example.com +user_api_key: + description: API key of the user. + returned: success + type: str + sample: JLhcg8VWi8DoFqL2sSLZMXmGojcLnFrOBTipvBHJjySODcV4mCOo29W2duzPv5cALaZnXj5QxDx3xQfaQt3DKg +user_api_secret: + description: API secret of the user. + returned: success + type: str + sample: FUELo3LB9fa1UopjTLPdqLv_6OXQMJZv9g9N4B_Ao3HFz8d6IGFCV9MbPFNM8mwz00wbMevja1DoUNDvI8C9-g +account: + description: Account name of the user. + returned: success + type: str + sample: developers +account_type: + description: Type of the account. + returned: success + type: str + sample: user +timezone: + description: Timezone of the user. + returned: success + type: str + sample: enabled +created: + description: Date the user was created. + returned: success + type: str + sample: Doe +state: + description: State of the user. + returned: success + type: str + sample: enabled +domain: + description: Domain the user is related. + returned: success + type: str + sample: ROOT +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackUser(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackUser, self).__init__(module) + self.returns = { + 'username': 'username', + 'firstname': 'first_name', + 'lastname': 'last_name', + 'email': 'email', + 'secretkey': 'user_api_secret', + 'apikey': 'user_api_key', + 'timezone': 'timezone', + } + self.account_types = { + 'user': 0, + 'root_admin': 1, + 'domain_admin': 2, + } + self.user = None + + def get_account_type(self): + account_type = self.module.params.get('account_type') + return self.account_types[account_type] + + def get_user(self): + if not self.user: + args = { + 'domainid': self.get_domain('id'), + 'fetch_list': True, + } + + users = self.query_api('listUsers', **args) + + if users: + user_name = self.module.params.get('username') + for u in users: + if user_name.lower() == u['username'].lower(): + self.user = u + break + return self.user + + def enable_user(self): + user = self.get_user() + if not user: + user = self.present_user() + + if user['state'].lower() != 'enabled': + self.result['changed'] = True + args = { + 'id': user['id'], + } + if not self.module.check_mode: + res = self.query_api('enableUser', **args) + user = res['user'] + return user + + def lock_user(self): + user = self.get_user() + if not user: + user = self.present_user() + + # we need to enable the user to lock it. + if user['state'].lower() == 'disabled': + user = self.enable_user() + + if user['state'].lower() != 'locked': + self.result['changed'] = True + + args = { + 'id': user['id'], + } + + if not self.module.check_mode: + res = self.query_api('lockUser', **args) + user = res['user'] + + return user + + def disable_user(self): + user = self.get_user() + if not user: + user = self.present_user() + + if user['state'].lower() != 'disabled': + self.result['changed'] = True + args = { + 'id': user['id'], + } + if not self.module.check_mode: + user = self.query_api('disableUser', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + user = self.poll_job(user, 'user') + return user + + def present_user(self): + required_params = [ + 'account', + 'email', + 'password', + 'first_name', + 'last_name', + ] + self.module.fail_on_missing_params(required_params=required_params) + + user = self.get_user() + if user: + user = self._update_user(user) + else: + user = self._create_user(user) + return user + + def _get_common_args(self): + return { + 'firstname': self.module.params.get('first_name'), + 'lastname': self.module.params.get('last_name'), + 'email': self.module.params.get('email'), + 'timezone': self.module.params.get('timezone'), + } + + def _create_user(self, user): + self.result['changed'] = True + + args = self._get_common_args() + args.update({ + 'account': self.get_account(key='name'), + 'domainid': self.get_domain('id'), + 'username': self.module.params.get('username'), + 'password': self.module.params.get('password'), + }) + + if not self.module.check_mode: + res = self.query_api('createUser', **args) + user = res['user'] + + # register user api keys + if self.module.params.get('keys_registered'): + res = self.query_api('registerUserKeys', id=user['id']) + user.update(res['userkeys']) + + return user + + def _update_user(self, user): + args = self._get_common_args() + args.update({ + 'id': user['id'], + }) + + if self.has_changed(args, user): + self.result['changed'] = True + + if not self.module.check_mode: + res = self.query_api('updateUser', **args) + + user = res['user'] + + # register user api keys + if 'apikey' not in user and self.module.params.get('keys_registered'): + self.result['changed'] = True + + if not self.module.check_mode: + res = self.query_api('registerUserKeys', id=user['id']) + user.update(res['userkeys']) + return user + + def absent_user(self): + user = self.get_user() + if user: + self.result['changed'] = True + + if not self.module.check_mode: + self.query_api('deleteUser', id=user['id']) + + return user + + def get_result(self, user): + super(AnsibleCloudStackUser, self).get_result(user) + if user: + if 'accounttype' in user: + for key, value in self.account_types.items(): + if value == user['accounttype']: + self.result['account_type'] = key + break + + # secretkey has been removed since CloudStack 4.10 from listUsers API + if self.module.params.get('keys_registered') and 'apikey' in user and 'secretkey' not in user: + user_keys = self.query_api('getUserKeys', id=user['id']) + if user_keys: + self.result['user_api_secret'] = user_keys['userkeys'].get('secretkey') + + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + username=dict(required=True), + account=dict(), + state=dict(choices=['present', 'absent', 'enabled', 'disabled', 'locked', 'unlocked'], default='present'), + domain=dict(default='ROOT'), + email=dict(), + first_name=dict(), + last_name=dict(), + password=dict(no_log=True), + timezone=dict(), + keys_registered=dict(type='bool', default=False), + poll_async=dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_acc = AnsibleCloudStackUser(module) + + state = module.params.get('state') + + if state == 'absent': + user = acs_acc.absent_user() + + elif state in ['enabled', 'unlocked']: + user = acs_acc.enable_user() + + elif state == 'disabled': + user = acs_acc.disable_user() + + elif state == 'locked': + user = acs_acc.lock_user() + + else: + user = acs_acc.present_user() + + result = acs_acc.get_result(user) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vlan_ip_range.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vlan_ip_range.py new file mode 100644 index 00000000..aaca4158 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vlan_ip_range.py @@ -0,0 +1,397 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2018, David Passante <@dpassante> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_vlan_ip_range +short_description: Manages VLAN IP ranges on Apache CloudStack based clouds. +description: + - Create and delete VLAN IP range. +author: David Passante (@dpassante) +version_added: 0.1.0 +options: + network: + description: + - The network name or id. + - Required if I(for_virtual_network) and I(physical_network) are not set. + type: str + physical_network: + description: + - The physical network name or id. + type: str + start_ip: + description: + - The beginning IPv4 address in the VLAN IP range. + - Only considered on create. + type: str + required: true + end_ip: + description: + - The ending IPv4 address in the VLAN IP range. + - If not specified, value of I(start_ip) is used. + - Only considered on create. + type: str + gateway: + description: + - The gateway of the VLAN IP range. + - Required if I(state=present). + type: str + netmask: + description: + - The netmask of the VLAN IP range. + - Required if I(state=present). + type: str + start_ipv6: + description: + - The beginning IPv6 address in the IPv6 network range. + - Only considered on create. + type: str + end_ipv6: + description: + - The ending IPv6 address in the IPv6 network range. + - If not specified, value of I(start_ipv6) is used. + - Only considered on create. + type: str + gateway_ipv6: + description: + - The gateway of the IPv6 network. + - Only considered on create. + type: str + cidr_ipv6: + description: + - The CIDR of IPv6 network, must be at least /64. + type: str + vlan: + description: + - The ID or VID of the network. + - If not specified, will be defaulted to the vlan of the network. + type: str + pod: + description: + - Name of the pod. + type: str + version_added: 1.0.0 + state: + description: + - State of the network ip range. + type: str + default: present + choices: [ present, absent ] + zone: + description: + - The Zone ID of the VLAN IP range. + - If not set, default zone is used. + type: str + domain: + description: + - Domain of the account owning the VLAN. + type: str + account: + description: + - Account who owns the VLAN. + - Mutually exclusive with I(project). + type: str + project: + description: + - Project who owns the VLAN. + - Mutually exclusive with I(account). + type: str + for_virtual_network: + description: + - C(yes) if VLAN is of Virtual type, C(no) if Direct. + - If set to C(yes) but neither I(physical_network) or I(network) is set CloudStack will try to add the + VLAN range to the Physical Network with a Public traffic type. + type: bool + default: no + version_added: 1.0.0 + for_system_vms: + description: + - C(yes) if IP range is set to system vms, C(no) if not + type: bool + default: no +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: create a VLAN IP range for network test + ngine_io.cloudstack.cs_vlan_ip_range: + network: test + vlan: 98 + start_ip: 10.2.4.10 + end_ip: 10.2.4.100 + gateway: 10.2.4.1 + netmask: 255.255.255.0 + zone: zone-02 + +- name: remove a VLAN IP range for network test + ngine_io.cloudstack.cs_vlan_ip_range: + state: absent + network: test + start_ip: 10.2.4.10 + end_ip: 10.2.4.100 + zone: zone-02 +''' + +RETURN = ''' +--- +id: + description: UUID of the VLAN IP range. + returned: success + type: str + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 +network: + description: The network of vlan range + returned: if available + type: str + sample: test +vlan: + description: The ID or VID of the VLAN. + returned: success + type: str + sample: vlan://98 +gateway: + description: IPv4 gateway. + returned: success + type: str + sample: 10.2.4.1 +netmask: + description: IPv4 netmask. + returned: success + type: str + sample: 255.255.255.0 +gateway_ipv6: + description: IPv6 gateway. + returned: if available + type: str + sample: 2001:db8::1 +cidr_ipv6: + description: The CIDR of IPv6 network. + returned: if available + type: str + sample: 2001:db8::/64 +zone: + description: Name of zone. + returned: success + type: str + sample: zone-02 +domain: + description: Domain name of the VLAN IP range. + returned: success + type: str + sample: ROOT +account: + description: Account who owns the network. + returned: if available + type: str + sample: example account +project: + description: Project who owns the network. + returned: if available + type: str + sample: example project +for_systemvms: + description: Whether VLAN IP range is dedicated to system vms or not. + returned: success + type: bool + sample: false +for_virtual_network: + description: Whether VLAN IP range is of Virtual type or not. + returned: success + type: bool + sample: false +physical_network: + description: The physical network VLAN IP range belongs to. + returned: success + type: str + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 +start_ip: + description: The start ip of the VLAN IP range. + returned: success + type: str + sample: 10.2.4.10 +end_ip: + description: The end ip of the VLAN IP range. + returned: success + type: str + sample: 10.2.4.100 +start_ipv6: + description: The start ipv6 of the VLAN IP range. + returned: if available + type: str + sample: 2001:db8::10 +end_ipv6: + description: The end ipv6 of the VLAN IP range. + returned: if available + type: str + sample: 2001:db8::50 +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackVlanIpRange(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackVlanIpRange, self).__init__(module) + self.returns = { + 'startip': 'start_ip', + 'endip': 'end_ip', + 'physicalnetworkid': 'physical_network', + 'vlan': 'vlan', + 'forsystemvms': 'for_systemvms', + 'forvirtualnetwork': 'for_virtual_network', + 'gateway': 'gateway', + 'netmask': 'netmask', + 'ip6gateway': 'gateway_ipv6', + 'ip6cidr': 'cidr_ipv6', + 'startipv6': 'start_ipv6', + 'endipv6': 'end_ipv6', + } + self.ip_range = None + + def get_vlan_ip_range(self): + if not self.ip_range: + args = { + 'zoneid': self.get_zone(key='id'), + 'projectid': self.get_project(key='id'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'networkid': self.get_network(key='id'), + } + + res = self.query_api('listVlanIpRanges', **args) + if res: + ip_range_list = res['vlaniprange'] + + params = { + 'startip': self.module.params.get('start_ip'), + 'endip': self.get_or_fallback('end_ip', 'start_ip'), + } + + for ipr in ip_range_list: + if params['startip'] == ipr['startip'] and params['endip'] == ipr['endip']: + self.ip_range = ipr + break + + return self.ip_range + + def present_vlan_ip_range(self): + ip_range = self.get_vlan_ip_range() + + if not ip_range: + ip_range = self.create_vlan_ip_range() + + return ip_range + + def create_vlan_ip_range(self): + self.result['changed'] = True + + vlan = self.module.params.get('vlan') + + args = { + 'zoneid': self.get_zone(key='id'), + 'projectid': self.get_project(key='id'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'startip': self.module.params.get('start_ip'), + 'endip': self.get_or_fallback('end_ip', 'start_ip'), + 'netmask': self.module.params.get('netmask'), + 'gateway': self.module.params.get('gateway'), + 'startipv6': self.module.params.get('start_ipv6'), + 'endipv6': self.get_or_fallback('end_ipv6', 'start_ipv6'), + 'ip6gateway': self.module.params.get('gateway_ipv6'), + 'ip6cidr': self.module.params.get('cidr_ipv6'), + 'vlan': self.get_network(key='vlan') if not vlan else vlan, + 'networkid': self.get_network(key='id'), + 'forvirtualnetwork': self.module.params.get('for_virtual_network'), + 'forsystemvms': self.module.params.get('for_system_vms'), + 'podid': self.get_pod(key='id'), + } + if self.module.params.get('physical_network'): + args['physicalnetworkid'] = self.get_physical_network(key='id') + + if not self.module.check_mode: + res = self.query_api('createVlanIpRange', **args) + + self.ip_range = res['vlan'] + + return self.ip_range + + def absent_vlan_ip_range(self): + ip_range = self.get_vlan_ip_range() + + if ip_range: + self.result['changed'] = True + + args = { + 'id': ip_range['id'], + } + + if not self.module.check_mode: + self.query_api('deleteVlanIpRange', **args) + + return ip_range + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + network=dict(type='str'), + physical_network=dict(type='str'), + zone=dict(type='str'), + start_ip=dict(type='str', required=True), + end_ip=dict(type='str'), + gateway=dict(type='str'), + netmask=dict(type='str'), + start_ipv6=dict(type='str'), + end_ipv6=dict(type='str'), + gateway_ipv6=dict(type='str'), + cidr_ipv6=dict(type='str'), + vlan=dict(type='str'), + state=dict(choices=['present', 'absent'], default='present'), + domain=dict(type='str'), + account=dict(type='str'), + project=dict(type='str'), + pod=dict(type='str'), + for_virtual_network=dict(type='bool', default=False), + for_system_vms=dict(type='bool', default=False), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + mutually_exclusive=( + ['account', 'project'], + ), + required_if=(("state", "present", ("gateway", "netmask")),), + supports_check_mode=True, + ) + + acs_vlan_ip_range = AnsibleCloudStackVlanIpRange(module) + + state = module.params.get('state') + if state == 'absent': + ipr = acs_vlan_ip_range.absent_vlan_ip_range() + + else: + ipr = acs_vlan_ip_range.present_vlan_ip_range() + + result = acs_vlan_ip_range.get_result(ipr) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vmsnapshot.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vmsnapshot.py new file mode 100644 index 00000000..83b77a70 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vmsnapshot.py @@ -0,0 +1,278 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_vmsnapshot +short_description: Manages VM snapshots on Apache CloudStack based clouds. +description: + - Create, remove and revert VM from snapshots. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Unique Name of the snapshot. In CloudStack terms display name. + type: str + required: true + aliases: [ display_name ] + vm: + description: + - Name of the virtual machine. + type: str + required: true + description: + description: + - Description of the snapshot. + type: str + snapshot_memory: + description: + - Snapshot memory if set to true. + default: no + type: bool + zone: + description: + - Name of the zone in which the VM is in. If not set, default zone is used. + type: str + project: + description: + - Name of the project the VM is assigned to. + type: str + state: + description: + - State of the snapshot. + type: str + default: present + choices: [ present, absent, revert ] + domain: + description: + - Domain the VM snapshot is related to. + type: str + account: + description: + - Account the VM snapshot is related to. + type: str + poll_async: + description: + - Poll async jobs until job has finished. + default: yes + type: bool + tags: + description: + - List of tags. Tags are a list of dictionaries having keys I(key) and I(value). + - "To delete all tags, set a empty list e.g. I(tags: [])." + type: list + elements: dict + aliases: [ tag ] +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Create a VM snapshot of disk and memory before an upgrade + ngine_io.cloudstack.cs_vmsnapshot: + name: Snapshot before upgrade + vm: web-01 + snapshot_memory: yes + +- name: Revert a VM to a snapshot after a failed upgrade + ngine_io.cloudstack.cs_vmsnapshot: + name: Snapshot before upgrade + vm: web-01 + state: revert + +- name: Remove a VM snapshot after successful upgrade + ngine_io.cloudstack.cs_vmsnapshot: + name: Snapshot before upgrade + vm: web-01 + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the snapshot. + returned: success + type: str + sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f +name: + description: Name of the snapshot. + returned: success + type: str + sample: snapshot before update +display_name: + description: Display name of the snapshot. + returned: success + type: str + sample: snapshot before update +created: + description: date of the snapshot. + returned: success + type: str + sample: 2015-03-29T14:57:06+0200 +current: + description: true if the snapshot is current + returned: success + type: bool + sample: True +state: + description: state of the vm snapshot + returned: success + type: str + sample: Allocated +type: + description: type of vm snapshot + returned: success + type: str + sample: DiskAndMemory +description: + description: description of vm snapshot + returned: success + type: str + sample: snapshot brought to you by Ansible +domain: + description: Domain the vm snapshot is related to. + returned: success + type: str + sample: example domain +account: + description: Account the vm snapshot is related to. + returned: success + type: str + sample: example account +project: + description: Name of project the vm snapshot is related to. + returned: success + type: str + sample: Production +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackVmSnapshot(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackVmSnapshot, self).__init__(module) + self.returns = { + 'type': 'type', + 'current': 'current', + } + + def get_snapshot(self): + args = { + 'virtualmachineid': self.get_vm('id'), + 'account': self.get_account('name'), + 'domainid': self.get_domain('id'), + 'projectid': self.get_project('id'), + 'name': self.module.params.get('name'), + } + snapshots = self.query_api('listVMSnapshot', **args) + if snapshots: + return snapshots['vmSnapshot'][0] + return None + + def create_snapshot(self): + snapshot = self.get_snapshot() + if not snapshot: + self.result['changed'] = True + + args = { + 'virtualmachineid': self.get_vm('id'), + 'name': self.module.params.get('name'), + 'description': self.module.params.get('description'), + 'snapshotmemory': self.module.params.get('snapshot_memory'), + } + if not self.module.check_mode: + res = self.query_api('createVMSnapshot', **args) + + poll_async = self.module.params.get('poll_async') + if res and poll_async: + snapshot = self.poll_job(res, 'vmsnapshot') + + if snapshot: + snapshot = self.ensure_tags(resource=snapshot, resource_type='Snapshot') + + return snapshot + + def remove_snapshot(self): + snapshot = self.get_snapshot() + if snapshot: + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('deleteVMSnapshot', vmsnapshotid=snapshot['id']) + + poll_async = self.module.params.get('poll_async') + if res and poll_async: + res = self.poll_job(res, 'vmsnapshot') + return snapshot + + def revert_vm_to_snapshot(self): + snapshot = self.get_snapshot() + if snapshot: + self.result['changed'] = True + + if snapshot['state'] != "Ready": + self.module.fail_json(msg="snapshot state is '%s', not ready, could not revert VM" % snapshot['state']) + + if not self.module.check_mode: + res = self.query_api('revertToVMSnapshot', vmsnapshotid=snapshot['id']) + + poll_async = self.module.params.get('poll_async') + if res and poll_async: + res = self.poll_job(res, 'vmsnapshot') + return snapshot + + self.module.fail_json(msg="snapshot not found, could not revert VM") + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True, aliases=['display_name']), + vm=dict(required=True), + description=dict(), + zone=dict(), + snapshot_memory=dict(type='bool', default=False), + state=dict(choices=['present', 'absent', 'revert'], default='present'), + domain=dict(), + account=dict(), + project=dict(), + poll_async=dict(type='bool', default=True), + tags=dict(type='list', elements='dict', aliases=['tag']), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_vmsnapshot = AnsibleCloudStackVmSnapshot(module) + + state = module.params.get('state') + if state in ['revert']: + snapshot = acs_vmsnapshot.revert_vm_to_snapshot() + elif state in ['absent']: + snapshot = acs_vmsnapshot.remove_snapshot() + else: + snapshot = acs_vmsnapshot.create_snapshot() + + result = acs_vmsnapshot.get_result(snapshot) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_volume.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_volume.py new file mode 100644 index 00000000..5947675c --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_volume.py @@ -0,0 +1,561 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2015, Jefferson Girão <jefferson@girao.net> +# Copyright (c) 2015, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_volume +short_description: Manages volumes on Apache CloudStack based clouds. +description: + - Create, destroy, attach, detach, extract or upload volumes. +author: + - Jefferson Girão (@jeffersongirao) + - René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name of the volume. + - I(name) can only contain ASCII letters. + type: str + required: true + account: + description: + - Account the volume is related to. + type: str + device_id: + description: + - ID of the device on a VM the volume is attached to. + - Only considered if I(state) is C(attached). + type: int + custom_id: + description: + - Custom id to the resource. + - Allowed to Root Admins only. + type: str + disk_offering: + description: + - Name of the disk offering to be used. + - Required one of I(disk_offering), I(snapshot) if volume is not already I(state=present). + type: str + display_volume: + description: + - Whether to display the volume to the end user or not. + - Allowed to Root Admins only. + type: bool + domain: + description: + - Name of the domain the volume to be deployed in. + type: str + max_iops: + description: + - Max iops + type: int + min_iops: + description: + - Min iops + type: int + project: + description: + - Name of the project the volume to be deployed in. + type: str + size: + description: + - Size of disk in GB + type: int + snapshot: + description: + - The snapshot name for the disk volume. + - Required one of I(disk_offering), I(snapshot) if volume is not already I(state=present). + type: str + force: + description: + - Force removal of volume even it is attached to a VM. + - Considered on I(state=absent) only. + default: no + type: bool + shrink_ok: + description: + - Whether to allow to shrink the volume. + default: no + type: bool + vm: + description: + - Name of the virtual machine to attach the volume to. + type: str + zone: + description: + - Name of the zone in which the volume should be deployed. + - If not set, default zone is used. + type: str + state: + description: + - State of the volume. + type: str + default: present + choices: [ present, absent, attached, detached, extracted, uploaded ] + poll_async: + description: + - Poll async jobs until job has finished. + default: yes + type: bool + tags: + description: + - List of tags. Tags are a list of dictionaries having keys I(key) and I(value). + - "To delete all tags, set a empty list e.g. I(tags: [])." + type: list + elements: dict + aliases: [ tag ] + url: + description: + - URL to which the volume would be extracted on I(state=extracted) + - or the URL where to download the volume on I(state=uploaded). + - Only considered if I(state) is C(extracted) or C(uploaded). + type: str + mode: + description: + - Mode for the volume extraction. + - Only considered if I(state=extracted). + type: str + choices: [ http_download, ftp_upload ] + default: http_download + format: + description: + - The format for the volume. + - Only considered if I(state=uploaded). + type: str + choices: [ QCOW2, RAW, VHD, VHDX, OVA ] +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: create volume within project and zone with specified storage options + ngine_io.cloudstack.cs_volume: + name: web-vm-1-volume + project: Integration + zone: ch-zrh-ix-01 + disk_offering: PerfPlus Storage + size: 20 + +- name: create/attach volume to instance + ngine_io.cloudstack.cs_volume: + name: web-vm-1-volume + disk_offering: PerfPlus Storage + size: 20 + vm: web-vm-1 + state: attached + +- name: detach volume + ngine_io.cloudstack.cs_volume: + name: web-vm-1-volume + state: detached + +- name: remove volume + ngine_io.cloudstack.cs_volume: + name: web-vm-1-volume + state: absent + +- name: Extract DATA volume to make it downloadable + ngine_io.cloudstack.cs_volume: + state: extracted + name: web-vm-1-volume + register: data_vol_out + +- name: Create new volume by downloading source volume + ngine_io.cloudstack.cs_volume: + state: uploaded + name: web-vm-1-volume-2 + format: VHD + url: "{{ data_vol_out.url }}" +''' + +RETURN = ''' +id: + description: ID of the volume. + returned: success + type: str + sample: +name: + description: Name of the volume. + returned: success + type: str + sample: web-volume-01 +display_name: + description: Display name of the volume. + returned: success + type: str + sample: web-volume-01 +group: + description: Group the volume belongs to + returned: success + type: str + sample: web +domain: + description: Domain the volume belongs to + returned: success + type: str + sample: example domain +project: + description: Project the volume belongs to + returned: success + type: str + sample: Production +zone: + description: Name of zone the volume is in. + returned: success + type: str + sample: ch-gva-2 +created: + description: Date of the volume was created. + returned: success + type: str + sample: 2014-12-01T14:57:57+0100 +attached: + description: Date of the volume was attached. + returned: success + type: str + sample: 2014-12-01T14:57:57+0100 +type: + description: Disk volume type. + returned: success + type: str + sample: DATADISK +size: + description: Size of disk volume. + returned: success + type: int + sample: 20 +vm: + description: Name of the vm the volume is attached to (not returned when detached) + returned: success + type: str + sample: web-01 +state: + description: State of the volume + returned: success + type: str + sample: Attached +device_id: + description: Id of the device on user vm the volume is attached to (not returned when detached) + returned: success + type: int + sample: 1 +url: + description: The url of the uploaded volume or the download url depending extraction mode. + returned: success when I(state=extracted) + type: str + sample: http://1.12.3.4/userdata/387e2c7c-7c42-4ecc-b4ed-84e8367a1965.vhd +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_required_together, + cs_argument_spec +) + + +class AnsibleCloudStackVolume(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackVolume, self).__init__(module) + self.returns = { + 'group': 'group', + 'attached': 'attached', + 'vmname': 'vm', + 'deviceid': 'device_id', + 'type': 'type', + 'size': 'size', + 'url': 'url', + } + self.volume = None + + def get_volume(self): + if not self.volume: + args = { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'zoneid': self.get_zone(key='id'), + 'displayvolume': self.module.params.get('display_volume'), + 'type': 'DATADISK', + 'fetch_list': True, + } + # Do not filter on DATADISK when state=extracted + if self.module.params.get('state') == 'extracted': + del args['type'] + + volumes = self.query_api('listVolumes', **args) + if volumes: + volume_name = self.module.params.get('name') + for v in volumes: + if volume_name.lower() == v['name'].lower(): + self.volume = v + break + return self.volume + + def get_snapshot(self, key=None): + snapshot = self.module.params.get('snapshot') + if not snapshot: + return None + + args = { + 'name': snapshot, + 'account': self.get_account('name'), + 'domainid': self.get_domain('id'), + 'projectid': self.get_project('id'), + } + snapshots = self.query_api('listSnapshots', **args) + if snapshots: + return self._get_by_key(key, snapshots['snapshot'][0]) + self.module.fail_json(msg="Snapshot with name %s not found" % snapshot) + + def present_volume(self): + volume = self.get_volume() + if volume: + volume = self.update_volume(volume) + else: + disk_offering_id = self.get_disk_offering(key='id') + snapshot_id = self.get_snapshot(key='id') + + if not disk_offering_id and not snapshot_id: + self.module.fail_json(msg="Required one of: disk_offering,snapshot") + + self.result['changed'] = True + + args = { + 'name': self.module.params.get('name'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'diskofferingid': disk_offering_id, + 'displayvolume': self.module.params.get('display_volume'), + 'maxiops': self.module.params.get('max_iops'), + 'miniops': self.module.params.get('min_iops'), + 'projectid': self.get_project(key='id'), + 'size': self.module.params.get('size'), + 'snapshotid': snapshot_id, + 'zoneid': self.get_zone(key='id') + } + if not self.module.check_mode: + res = self.query_api('createVolume', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + volume = self.poll_job(res, 'volume') + if volume: + volume = self.ensure_tags(resource=volume, resource_type='Volume') + self.volume = volume + + return volume + + def attached_volume(self): + volume = self.present_volume() + + if volume: + if volume.get('virtualmachineid') != self.get_vm(key='id'): + self.result['changed'] = True + + if not self.module.check_mode: + volume = self.detached_volume() + + if 'attached' not in volume: + self.result['changed'] = True + + args = { + 'id': volume['id'], + 'virtualmachineid': self.get_vm(key='id'), + 'deviceid': self.module.params.get('device_id'), + } + if not self.module.check_mode: + res = self.query_api('attachVolume', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + volume = self.poll_job(res, 'volume') + return volume + + def detached_volume(self): + volume = self.present_volume() + + if volume: + if 'attached' not in volume: + return volume + + self.result['changed'] = True + + if not self.module.check_mode: + res = self.query_api('detachVolume', id=volume['id']) + poll_async = self.module.params.get('poll_async') + if poll_async: + volume = self.poll_job(res, 'volume') + return volume + + def absent_volume(self): + volume = self.get_volume() + + if volume: + if 'attached' in volume and not self.module.params.get('force'): + self.module.fail_json(msg="Volume '%s' is attached, use force=true for detaching and removing the volume." % volume.get('name')) + + self.result['changed'] = True + if not self.module.check_mode: + volume = self.detached_volume() + res = self.query_api('deleteVolume', id=volume['id']) + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(res, 'volume') + + return volume + + def update_volume(self, volume): + args_resize = { + 'id': volume['id'], + 'diskofferingid': self.get_disk_offering(key='id'), + 'maxiops': self.module.params.get('max_iops'), + 'miniops': self.module.params.get('min_iops'), + 'size': self.module.params.get('size') + } + # change unit from bytes to giga bytes to compare with args + volume_copy = volume.copy() + volume_copy['size'] = volume_copy['size'] / (2**30) + + if self.has_changed(args_resize, volume_copy): + + self.result['changed'] = True + if not self.module.check_mode: + args_resize['shrinkok'] = self.module.params.get('shrink_ok') + res = self.query_api('resizeVolume', **args_resize) + poll_async = self.module.params.get('poll_async') + if poll_async: + volume = self.poll_job(res, 'volume') + self.volume = volume + + return volume + + def extract_volume(self): + volume = self.get_volume() + if not volume: + self.module.fail_json(msg="Failed: volume not found") + + args = { + 'id': volume['id'], + 'url': self.module.params.get('url'), + 'mode': self.module.params.get('mode').upper(), + 'zoneid': self.get_zone(key='id') + } + self.result['changed'] = True + + if not self.module.check_mode: + res = self.query_api('extractVolume', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + volume = self.poll_job(res, 'volume') + self.volume = volume + + return volume + + def upload_volume(self): + volume = self.get_volume() + if not volume: + disk_offering_id = self.get_disk_offering(key='id') + + self.result['changed'] = True + + args = { + 'name': self.module.params.get('name'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'zoneid': self.get_zone(key='id'), + 'format': self.module.params.get('format'), + 'url': self.module.params.get('url'), + 'diskofferingid': disk_offering_id, + } + if not self.module.check_mode: + res = self.query_api('uploadVolume', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + volume = self.poll_job(res, 'volume') + if volume: + volume = self.ensure_tags(resource=volume, resource_type='Volume') + self.volume = volume + + return volume + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + disk_offering=dict(), + display_volume=dict(type='bool'), + max_iops=dict(type='int'), + min_iops=dict(type='int'), + size=dict(type='int'), + snapshot=dict(), + vm=dict(), + device_id=dict(type='int'), + custom_id=dict(), + force=dict(type='bool', default=False), + shrink_ok=dict(type='bool', default=False), + state=dict(default='present', choices=[ + 'present', + 'absent', + 'attached', + 'detached', + 'extracted', + 'uploaded', + ]), + zone=dict(), + domain=dict(), + account=dict(), + project=dict(), + poll_async=dict(type='bool', default=True), + tags=dict(type='list', elements='dict', aliases=['tag']), + url=dict(), + mode=dict(choices=['http_download', 'ftp_upload'], default='http_download'), + format=dict(choices=['QCOW2', 'RAW', 'VHD', 'VHDX', 'OVA']), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + mutually_exclusive=( + ['snapshot', 'disk_offering'], + ), + required_if=[ + ('state', 'uploaded', ['url', 'format']), + ], + supports_check_mode=True + ) + + acs_vol = AnsibleCloudStackVolume(module) + + state = module.params.get('state') + + if state in ['absent']: + volume = acs_vol.absent_volume() + elif state in ['attached']: + volume = acs_vol.attached_volume() + elif state in ['detached']: + volume = acs_vol.detached_volume() + elif state == 'extracted': + volume = acs_vol.extract_volume() + elif state == 'uploaded': + volume = acs_vol.upload_volume() + else: + volume = acs_vol.present_volume() + + result = acs_vol.get_result(volume) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vpc.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vpc.py new file mode 100644 index 00000000..45837dd2 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vpc.py @@ -0,0 +1,391 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_vpc +short_description: "Manages VPCs on Apache CloudStack based clouds." +description: + - Create, update and delete VPCs. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name of the VPC. + type: str + required: true + display_text: + description: + - Display text of the VPC. + - If not set, I(name) will be used for creating. + type: str + cidr: + description: + - CIDR of the VPC, e.g. 10.1.0.0/16 + - All VPC guest networks' CIDRs must be within this CIDR. + - Required on I(state=present). + type: str + network_domain: + description: + - Network domain for the VPC. + - All networks inside the VPC will belong to this domain. + - Only considered while creating the VPC, can not be changed. + type: str + vpc_offering: + description: + - Name of the VPC offering. + - If not set, default VPC offering is used. + type: str + clean_up: + description: + - Whether to redeploy a VPC router or not when I(state=restarted) + type: bool + state: + description: + - State of the VPC. + - The state C(present) creates a started VPC. + - The state C(stopped) is only considered while creating the VPC, added in version 2.6. + type: str + default: present + choices: + - present + - absent + - stopped + - restarted + domain: + description: + - Domain the VPC is related to. + type: str + account: + description: + - Account the VPC is related to. + type: str + project: + description: + - Name of the project the VPC is related to. + type: str + zone: + description: + - Name of the zone. + - If not set, default zone is used. + type: str + tags: + description: + - List of tags. Tags are a list of dictionaries having keys I(key) and I(value). + - "For deleting all tags, set an empty list e.g. I(tags: [])." + type: list + elements: dict + aliases: [ tag ] + poll_async: + description: + - Poll async jobs until job has finished. + default: yes + type: bool +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Ensure a VPC is present but not started after creating + ngine_io.cloudstack.cs_vpc: + name: my_vpc + display_text: My example VPC + cidr: 10.10.0.0/16 + state: stopped + +- name: Ensure a VPC is present and started after creating + ngine_io.cloudstack.cs_vpc: + name: my_vpc + display_text: My example VPC + cidr: 10.10.0.0/16 + +- name: Ensure a VPC is absent + ngine_io.cloudstack.cs_vpc: + name: my_vpc + state: absent + +- name: Ensure a VPC is restarted with clean up + ngine_io.cloudstack.cs_vpc: + name: my_vpc + clean_up: yes + state: restarted +''' + +RETURN = ''' +--- +id: + description: "UUID of the VPC." + returned: success + type: str + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 +name: + description: "Name of the VPC." + returned: success + type: str + sample: my_vpc +display_text: + description: "Display text of the VPC." + returned: success + type: str + sample: My example VPC +cidr: + description: "CIDR of the VPC." + returned: success + type: str + sample: 10.10.0.0/16 +network_domain: + description: "Network domain of the VPC." + returned: success + type: str + sample: example.com +region_level_vpc: + description: "Whether the VPC is region level or not." + returned: success + type: bool + sample: true +restart_required: + description: "Whether the VPC router needs a restart or not." + returned: success + type: bool + sample: true +distributed_vpc_router: + description: "Whether the VPC uses distributed router or not." + returned: success + type: bool + sample: true +redundant_vpc_router: + description: "Whether the VPC has redundant routers or not." + returned: success + type: bool + sample: true +domain: + description: "Domain the VPC is related to." + returned: success + type: str + sample: example domain +account: + description: "Account the VPC is related to." + returned: success + type: str + sample: example account +project: + description: "Name of project the VPC is related to." + returned: success + type: str + sample: Production +zone: + description: "Name of zone the VPC is in." + returned: success + type: str + sample: ch-gva-2 +state: + description: "State of the VPC." + returned: success + type: str + sample: Enabled +tags: + description: "List of resource tags associated with the VPC." + returned: success + type: list + sample: '[ { "key": "foo", "value": "bar" } ]' +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackVpc(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackVpc, self).__init__(module) + self.returns = { + 'cidr': 'cidr', + 'networkdomain': 'network_domain', + 'redundantvpcrouter': 'redundant_vpc_router', + 'distributedvpcrouter': 'distributed_vpc_router', + 'regionlevelvpc': 'region_level_vpc', + 'restartrequired': 'restart_required', + } + self.vpc = None + + def get_vpc_offering(self, key=None): + vpc_offering = self.module.params.get('vpc_offering') + args = { + 'state': 'Enabled', + } + if vpc_offering: + args['name'] = vpc_offering + fail_msg = "VPC offering not found or not enabled: %s" % vpc_offering + else: + args['isdefault'] = True + fail_msg = "No enabled default VPC offering found" + + vpc_offerings = self.query_api('listVPCOfferings', **args) + if vpc_offerings: + # The API name argument filter also matches substrings, we have to + # iterate over the results to get an exact match + for vo in vpc_offerings['vpcoffering']: + if 'name' in args: + if args['name'] == vo['name']: + return self._get_by_key(key, vo) + # Return the first offering found, if not queried for the name + else: + return self._get_by_key(key, vo) + self.module.fail_json(msg=fail_msg) + + def get_vpc(self): + if self.vpc: + return self.vpc + args = { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'zoneid': self.get_zone(key='id'), + 'fetch_list': True, + } + vpcs = self.query_api('listVPCs', **args) + if vpcs: + vpc_name = self.module.params.get('name') + for v in vpcs: + if vpc_name in [v['name'], v['displaytext'], v['id']]: + # Fail if the identifier matches more than one VPC + if self.vpc: + self.module.fail_json(msg="More than one VPC found with the provided identifyer: %s" % vpc_name) + else: + self.vpc = v + return self.vpc + + def restart_vpc(self): + self.result['changed'] = True + vpc = self.get_vpc() + if vpc and not self.module.check_mode: + args = { + 'id': vpc['id'], + 'cleanup': self.module.params.get('clean_up'), + } + res = self.query_api('restartVPC', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(res, 'vpc') + return vpc + + def present_vpc(self): + vpc = self.get_vpc() + if not vpc: + vpc = self._create_vpc(vpc) + else: + vpc = self._update_vpc(vpc) + + if vpc: + vpc = self.ensure_tags(resource=vpc, resource_type='Vpc') + return vpc + + def _create_vpc(self, vpc): + self.result['changed'] = True + args = { + 'name': self.module.params.get('name'), + 'displaytext': self.get_or_fallback('display_text', 'name'), + 'networkdomain': self.module.params.get('network_domain'), + 'vpcofferingid': self.get_vpc_offering(key='id'), + 'cidr': self.module.params.get('cidr'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'zoneid': self.get_zone(key='id'), + 'start': self.module.params.get('state') != 'stopped' + } + self.result['diff']['after'] = args + if not self.module.check_mode: + res = self.query_api('createVPC', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + vpc = self.poll_job(res, 'vpc') + return vpc + + def _update_vpc(self, vpc): + args = { + 'id': vpc['id'], + 'displaytext': self.module.params.get('display_text'), + } + if self.has_changed(args, vpc): + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('updateVPC', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + vpc = self.poll_job(res, 'vpc') + return vpc + + def absent_vpc(self): + vpc = self.get_vpc() + if vpc: + self.result['changed'] = True + self.result['diff']['before'] = vpc + if not self.module.check_mode: + res = self.query_api('deleteVPC', id=vpc['id']) + + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(res, 'vpc') + return vpc + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + cidr=dict(), + display_text=dict(), + vpc_offering=dict(), + network_domain=dict(), + clean_up=dict(type='bool'), + state=dict(choices=['present', 'absent', 'stopped', 'restarted'], default='present'), + domain=dict(), + account=dict(), + project=dict(), + zone=dict(), + tags=dict(type='list', elements='dict', aliases=['tag']), + poll_async=dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + required_if=[ + ('state', 'present', ['cidr']), + ], + supports_check_mode=True, + ) + + acs_vpc = AnsibleCloudStackVpc(module) + + state = module.params.get('state') + if state == 'absent': + vpc = acs_vpc.absent_vpc() + elif state == 'restarted': + vpc = acs_vpc.restart_vpc() + else: + vpc = acs_vpc.present_vpc() + + result = acs_vpc.get_result(vpc) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vpc_offering.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vpc_offering.py new file mode 100644 index 00000000..be129a1e --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vpc_offering.py @@ -0,0 +1,319 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017, David Passante (@dpassante) +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_vpc_offering +short_description: Manages vpc offerings on Apache CloudStack based clouds. +description: + - Create, update, enable, disable and remove CloudStack VPC offerings. +author: David Passante (@dpassante) +version_added: 0.1.0 +options: + name: + description: + - The name of the vpc offering + type: str + required: true + state: + description: + - State of the vpc offering. + type: str + choices: [ enabled, present, disabled, absent ] + default: present + display_text: + description: + - Display text of the vpc offerings + type: str + service_capabilities: + description: + - Desired service capabilities as part of vpc offering. + type: list + elements: dict + aliases: [ service_capability ] + service_offering: + description: + - The name or ID of the service offering for the VPC router appliance. + type: str + supported_services: + description: + - Services supported by the vpc offering + type: list + elements: str + aliases: [ supported_service ] + service_providers: + description: + - provider to service mapping. If not specified, the provider for the service will be mapped to the default provider on the physical network + type: list + elements: dict + aliases: [ service_provider ] + poll_async: + description: + - Poll async jobs until job has finished. + default: yes + type: bool +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Create a vpc offering and enable it + ngine_io.cloudstack.cs_vpc_offering: + name: my_vpc_offering + display_text: vpc offering description + state: enabled + supported_services: [ Dns, Dhcp ] + service_providers: + - {service: 'dns', provider: 'VpcVirtualRouter'} + - {service: 'dhcp', provider: 'VpcVirtualRouter'} + +- name: Create a vpc offering with redundant router + ngine_io.cloudstack.cs_vpc_offering: + name: my_vpc_offering + display_text: vpc offering description + supported_services: [ Dns, Dhcp, SourceNat ] + service_providers: + - {service: 'dns', provider: 'VpcVirtualRouter'} + - {service: 'dhcp', provider: 'VpcVirtualRouter'} + - {service: 'SourceNat', provider: 'VpcVirtualRouter'} + service_capabilities: + - {service: 'SourceNat', capabilitytype: 'RedundantRouter', capabilityvalue: true} + +- name: Create a region level vpc offering with distributed router + ngine_io.cloudstack.cs_vpc_offering: + name: my_vpc_offering + display_text: vpc offering description + state: present + supported_services: [ Dns, Dhcp, SourceNat ] + service_providers: + - {service: 'dns', provider: 'VpcVirtualRouter'} + - {service: 'dhcp', provider: 'VpcVirtualRouter'} + - {service: 'SourceNat', provider: 'VpcVirtualRouter'} + service_capabilities: + - {service: 'Connectivity', capabilitytype: 'DistributedRouter', capabilityvalue: true} + - {service: 'Connectivity', capabilitytype: 'RegionLevelVPC', capabilityvalue: true} + +- name: Remove a vpc offering + ngine_io.cloudstack.cs_vpc_offering: + name: my_vpc_offering + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the vpc offering. + returned: success + type: str + sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f +name: + description: The name of the vpc offering + returned: success + type: str + sample: MyCustomVPCOffering +display_text: + description: The display text of the vpc offering + returned: success + type: str + sample: My vpc offering +state: + description: The state of the vpc offering + returned: success + type: str + sample: Enabled +service_offering_id: + description: The service offering ID. + returned: success + type: str + sample: c5f7a5fc-43f8-11e5-a151-feff819cdc9f +is_default: + description: Whether VPC offering is the default offering or not. + returned: success + type: bool + sample: false +region_level: + description: Indicated if the offering can support region level vpc. + returned: success + type: bool + sample: false +distributed: + description: Indicates if the vpc offering supports distributed router for one-hop forwarding. + returned: success + type: bool + sample: false +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackVPCOffering(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackVPCOffering, self).__init__(module) + self.returns = { + 'serviceofferingid': 'service_offering_id', + 'isdefault': 'is_default', + 'distributedvpcrouter': 'distributed', + 'supportsregionLevelvpc': 'region_level', + } + self.vpc_offering = None + + def get_vpc_offering(self): + if self.vpc_offering: + return self.vpc_offering + + args = { + 'name': self.module.params.get('name'), + } + vo = self.query_api('listVPCOfferings', **args) + + if vo: + for vpc_offer in vo['vpcoffering']: + if args['name'] == vpc_offer['name']: + self.vpc_offering = vpc_offer + + return self.vpc_offering + + def get_service_offering_id(self): + service_offering = self.module.params.get('service_offering') + if not service_offering: + return None + + args = { + 'issystem': True + } + + service_offerings = self.query_api('listServiceOfferings', **args) + if service_offerings: + for s in service_offerings['serviceoffering']: + if service_offering in [s['name'], s['id']]: + return s['id'] + self.fail_json(msg="Service offering '%s' not found" % service_offering) + + def create_or_update(self): + vpc_offering = self.get_vpc_offering() + + if not vpc_offering: + vpc_offering = self.create_vpc_offering() + + return self.update_vpc_offering(vpc_offering) + + def create_vpc_offering(self): + vpc_offering = None + self.result['changed'] = True + args = { + 'name': self.module.params.get('name'), + 'state': self.module.params.get('state'), + 'displaytext': self.module.params.get('display_text'), + 'supportedservices': self.module.params.get('supported_services'), + 'serviceproviderlist': self.module.params.get('service_providers'), + 'serviceofferingid': self.get_service_offering_id(), + 'servicecapabilitylist': self.module.params.get('service_capabilities'), + } + + required_params = [ + 'display_text', + 'supported_services', + ] + self.module.fail_on_missing_params(required_params=required_params) + + if not self.module.check_mode: + res = self.query_api('createVPCOffering', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + vpc_offering = self.poll_job(res, 'vpcoffering') + + return vpc_offering + + def delete_vpc_offering(self): + vpc_offering = self.get_vpc_offering() + + if vpc_offering: + self.result['changed'] = True + + args = { + 'id': vpc_offering['id'], + } + + if not self.module.check_mode: + res = self.query_api('deleteVPCOffering', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + vpc_offering = self.poll_job(res, 'vpcoffering') + + return vpc_offering + + def update_vpc_offering(self, vpc_offering): + if not vpc_offering: + return vpc_offering + + args = { + 'id': vpc_offering['id'], + 'state': self.module.params.get('state'), + 'name': self.module.params.get('name'), + 'displaytext': self.module.params.get('display_text'), + } + + if args['state'] in ['enabled', 'disabled']: + args['state'] = args['state'].title() + else: + del args['state'] + + if self.has_changed(args, vpc_offering): + self.result['changed'] = True + + if not self.module.check_mode: + res = self.query_api('updateVPCOffering', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + vpc_offering = self.poll_job(res, 'vpcoffering') + + return vpc_offering + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + display_text=dict(), + state=dict(choices=['enabled', 'present', 'disabled', 'absent'], default='present'), + service_capabilities=dict(type='list', elements='dict', aliases=['service_capability']), + service_offering=dict(), + supported_services=dict(type='list', elements='str', aliases=['supported_service']), + service_providers=dict(type='list', elements='dict', aliases=['service_provider']), + poll_async=dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_vpc_offering = AnsibleCloudStackVPCOffering(module) + + state = module.params.get('state') + if state in ['absent']: + vpc_offering = acs_vpc_offering.delete_vpc_offering() + else: + vpc_offering = acs_vpc_offering.create_or_update() + + result = acs_vpc_offering.get_result(vpc_offering) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vpn_connection.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vpn_connection.py new file mode 100644 index 00000000..4f77b561 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vpn_connection.py @@ -0,0 +1,349 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +--- +module: cs_vpn_connection +short_description: Manages site-to-site VPN connections on Apache CloudStack based clouds. +description: + - Create and remove VPN connections. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + vpc: + description: + - Name of the VPC the VPN connection is related to. + type: str + required: true + vpn_customer_gateway: + description: + - Name of the VPN customer gateway. + type: str + required: true + passive: + description: + - State of the VPN connection. + - Only considered when I(state=present). + default: no + type: bool + force: + description: + - Activate the VPN gateway if not already activated on I(state=present). + - Also see M(cs_vpn_gateway). + default: no + type: bool + state: + description: + - State of the VPN connection. + type: str + default: present + choices: [ present, absent ] + zone: + description: + - Name of the zone the VPC is related to. + - If not set, default zone is used. + type: str + domain: + description: + - Domain the VPN connection is related to. + type: str + account: + description: + - Account the VPN connection is related to. + type: str + project: + description: + - Name of the project the VPN connection is related to. + type: str + poll_async: + description: + - Poll async jobs until job has finished. + default: yes + type: bool +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = r''' +- name: Create a VPN connection with activated VPN gateway + ngine_io.cloudstack.cs_vpn_connection: + vpn_customer_gateway: my vpn connection + vpc: my vpc + +- name: Create a VPN connection and force VPN gateway activation + ngine_io.cloudstack.cs_vpn_connection: + vpn_customer_gateway: my vpn connection + vpc: my vpc + force: yes + +- name: Remove a vpn connection + ngine_io.cloudstack.cs_vpn_connection: + vpn_customer_gateway: my vpn connection + vpc: my vpc + state: absent +''' + +RETURN = r''' +--- +id: + description: UUID of the VPN connection. + returned: success + type: str + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 +vpn_gateway_id: + description: UUID of the VPN gateway. + returned: success + type: str + sample: 04589590-ac63-93f5-4ffc-b698b8ac38b6 +domain: + description: Domain the VPN connection is related to. + returned: success + type: str + sample: example domain +account: + description: Account the VPN connection is related to. + returned: success + type: str + sample: example account +project: + description: Name of project the VPN connection is related to. + returned: success + type: str + sample: Production +created: + description: Date the connection was created. + returned: success + type: str + sample: 2014-12-01T14:57:57+0100 +dpd: + description: Whether dead pear detection is enabled or not. + returned: success + type: bool + sample: true +esp_lifetime: + description: Lifetime in seconds of phase 2 VPN connection. + returned: success + type: int + sample: 86400 +esp_policy: + description: IKE policy of the VPN connection. + returned: success + type: str + sample: aes256-sha1;modp1536 +force_encap: + description: Whether encapsulation for NAT traversal is enforced or not. + returned: success + type: bool + sample: true +ike_lifetime: + description: Lifetime in seconds of phase 1 VPN connection. + returned: success + type: int + sample: 86400 +ike_policy: + description: ESP policy of the VPN connection. + returned: success + type: str + sample: aes256-sha1;modp1536 +cidrs: + description: List of CIDRs of the customer gateway. + returned: success + type: list + sample: [ 10.10.10.0/24 ] +passive: + description: Whether the connection is passive or not. + returned: success + type: bool + sample: false +public_ip: + description: IP address of the VPN gateway. + returned: success + type: str + sample: 10.100.212.10 +gateway: + description: IP address of the VPN customer gateway. + returned: success + type: str + sample: 10.101.214.10 +state: + description: State of the VPN connection. + returned: success + type: str + sample: Connected +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackVpnConnection(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackVpnConnection, self).__init__(module) + self.returns = { + 'dpd': 'dpd', + 'esplifetime': 'esp_lifetime', + 'esppolicy': 'esp_policy', + 'gateway': 'gateway', + 'ikepolicy': 'ike_policy', + 'ikelifetime': 'ike_lifetime', + 'publicip': 'public_ip', + 'passive': 'passive', + 's2svpngatewayid': 'vpn_gateway_id', + } + self.vpn_customer_gateway = None + + def get_vpn_customer_gateway(self, key=None, identifier=None, refresh=False): + if not refresh and self.vpn_customer_gateway: + return self._get_by_key(key, self.vpn_customer_gateway) + + args = { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'fetch_list': True, + } + + vpn_customer_gateway = identifier or self.module.params.get('vpn_customer_gateway') + vcgws = self.query_api('listVpnCustomerGateways', **args) + if vcgws: + for vcgw in vcgws: + if vpn_customer_gateway.lower() in [vcgw['id'], vcgw['name'].lower()]: + self.vpn_customer_gateway = vcgw + return self._get_by_key(key, self.vpn_customer_gateway) + self.fail_json(msg="VPN customer gateway not found: %s" % vpn_customer_gateway) + + def get_vpn_gateway(self, key=None): + args = { + 'vpcid': self.get_vpc(key='id'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + } + vpn_gateways = self.query_api('listVpnGateways', **args) + if vpn_gateways: + return self._get_by_key(key, vpn_gateways['vpngateway'][0]) + + elif self.module.params.get('force'): + if self.module.check_mode: + return {} + res = self.query_api('createVpnGateway', **args) + vpn_gateway = self.poll_job(res, 'vpngateway') + return self._get_by_key(key, vpn_gateway) + + self.fail_json(msg="VPN gateway not found and not forced to create one") + + def get_vpn_connection(self): + args = { + 'vpcid': self.get_vpc(key='id'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + } + + vpn_conns = self.query_api('listVpnConnections', **args) + if vpn_conns: + for vpn_conn in vpn_conns['vpnconnection']: + if self.get_vpn_customer_gateway(key='id') == vpn_conn['s2scustomergatewayid']: + return vpn_conn + + def present_vpn_connection(self): + vpn_conn = self.get_vpn_connection() + + args = { + 's2scustomergatewayid': self.get_vpn_customer_gateway(key='id'), + 's2svpngatewayid': self.get_vpn_gateway(key='id'), + 'passive': self.module.params.get('passive'), + } + + if not vpn_conn: + self.result['changed'] = True + + if not self.module.check_mode: + res = self.query_api('createVpnConnection', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + vpn_conn = self.poll_job(res, 'vpnconnection') + + return vpn_conn + + def absent_vpn_connection(self): + vpn_conn = self.get_vpn_connection() + + if vpn_conn: + self.result['changed'] = True + + args = { + 'id': vpn_conn['id'] + } + + if not self.module.check_mode: + res = self.query_api('deleteVpnConnection', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(res, 'vpnconnection') + + return vpn_conn + + def get_result(self, vpn_conn): + super(AnsibleCloudStackVpnConnection, self).get_result(vpn_conn) + if vpn_conn: + if 'cidrlist' in vpn_conn: + self.result['cidrs'] = vpn_conn['cidrlist'].split(',') or [vpn_conn['cidrlist']] + # Ensure we return a bool + self.result['force_encap'] = True if vpn_conn.get('forceencap') else False + args = { + 'key': 'name', + 'identifier': vpn_conn['s2scustomergatewayid'], + 'refresh': True, + } + self.result['vpn_customer_gateway'] = self.get_vpn_customer_gateway(**args) + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + vpn_customer_gateway=dict(required=True), + vpc=dict(required=True), + domain=dict(), + account=dict(), + project=dict(), + zone=dict(), + passive=dict(type='bool', default=False), + force=dict(type='bool', default=False), + state=dict(choices=['present', 'absent'], default='present'), + poll_async=dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_vpn_conn = AnsibleCloudStackVpnConnection(module) + + state = module.params.get('state') + if state == "absent": + vpn_conn = acs_vpn_conn.absent_vpn_connection() + else: + vpn_conn = acs_vpn_conn.present_vpn_connection() + + result = acs_vpn_conn.get_result(vpn_conn) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vpn_customer_gateway.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vpn_customer_gateway.py new file mode 100644 index 00000000..915784cc --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vpn_customer_gateway.py @@ -0,0 +1,343 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: cs_vpn_customer_gateway +short_description: Manages site-to-site VPN customer gateway configurations on Apache CloudStack based clouds. +description: + - Create, update and remove VPN customer gateways. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name of the gateway. + type: str + required: true + cidrs: + description: + - List of guest CIDRs behind the gateway. + - Required if I(state=present). + type: list + elements: str + aliases: [ cidr ] + gateway: + description: + - Public IP address of the gateway. + - Required if I(state=present). + type: str + esp_policy: + description: + - ESP policy in the format e.g. C(aes256-sha1;modp1536). + - Required if I(state=present). + type: str + ike_policy: + description: + - IKE policy in the format e.g. C(aes256-sha1;modp1536). + - Required if I(state=present). + type: str + ipsec_psk: + description: + - IPsec Preshared-Key. + - Cannot contain newline or double quotes. + - Required if I(state=present). + type: str + ike_lifetime: + description: + - Lifetime in seconds of phase 1 VPN connection. + - Defaulted to 86400 by the API on creation if not set. + type: int + esp_lifetime: + description: + - Lifetime in seconds of phase 2 VPN connection. + - Defaulted to 3600 by the API on creation if not set. + type: int + dpd: + description: + - Enable Dead Peer Detection. + - Disabled per default by the API on creation if not set. + type: bool + force_encap: + description: + - Force encapsulation for NAT traversal. + - Disabled per default by the API on creation if not set. + type: bool + state: + description: + - State of the VPN customer gateway. + type: str + default: present + choices: [ present, absent ] + domain: + description: + - Domain the VPN customer gateway is related to. + type: str + account: + description: + - Account the VPN customer gateway is related to. + type: str + project: + description: + - Name of the project the VPN gateway is related to. + type: str + poll_async: + description: + - Poll async jobs until job has finished. + default: yes + type: bool +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = r''' +- name: Create a vpn customer gateway + ngine_io.cloudstack.cs_vpn_customer_gateway: + name: my vpn customer gateway + cidrs: + - 192.168.123.0/24 + - 192.168.124.0/24 + esp_policy: aes256-sha1;modp1536 + gateway: 10.10.1.1 + ike_policy: aes256-sha1;modp1536 + ipsec_psk: "S3cr3Tk3Y" + +- name: Remove a vpn customer gateway + ngine_io.cloudstack.cs_vpn_customer_gateway: + name: my vpn customer gateway + state: absent +''' + +RETURN = r''' +--- +id: + description: UUID of the VPN customer gateway. + returned: success + type: str + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 +gateway: + description: IP address of the VPN customer gateway. + returned: success + type: str + sample: 10.100.212.10 +domain: + description: Domain the VPN customer gateway is related to. + returned: success + type: str + sample: example domain +account: + description: Account the VPN customer gateway is related to. + returned: success + type: str + sample: example account +project: + description: Name of project the VPN customer gateway is related to. + returned: success + type: str + sample: Production +dpd: + description: Whether dead pear detection is enabled or not. + returned: success + type: bool + sample: true +esp_lifetime: + description: Lifetime in seconds of phase 2 VPN connection. + returned: success + type: int + sample: 86400 +esp_policy: + description: IKE policy of the VPN customer gateway. + returned: success + type: str + sample: aes256-sha1;modp1536 +force_encap: + description: Whether encapsulation for NAT traversal is enforced or not. + returned: success + type: bool + sample: true +ike_lifetime: + description: Lifetime in seconds of phase 1 VPN connection. + returned: success + type: int + sample: 86400 +ike_policy: + description: ESP policy of the VPN customer gateway. + returned: success + type: str + sample: aes256-sha1;modp1536 +name: + description: Name of this customer gateway. + returned: success + type: str + sample: my vpn customer gateway +cidrs: + description: List of CIDRs of this customer gateway. + returned: success + type: list + sample: [ 10.10.10.0/24 ] +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackVpnCustomerGateway(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackVpnCustomerGateway, self).__init__(module) + self.returns = { + 'dpd': 'dpd', + 'esplifetime': 'esp_lifetime', + 'esppolicy': 'esp_policy', + 'gateway': 'gateway', + 'ikepolicy': 'ike_policy', + 'ikelifetime': 'ike_lifetime', + 'ipaddress': 'ip_address', + } + + def _common_args(self): + return { + 'name': self.module.params.get('name'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'cidrlist': ','.join(self.module.params.get('cidrs')) if self.module.params.get('cidrs') is not None else None, + 'esppolicy': self.module.params.get('esp_policy'), + 'esplifetime': self.module.params.get('esp_lifetime'), + 'ikepolicy': self.module.params.get('ike_policy'), + 'ikelifetime': self.module.params.get('ike_lifetime'), + 'ipsecpsk': self.module.params.get('ipsec_psk'), + 'dpd': self.module.params.get('dpd'), + 'forceencap': self.module.params.get('force_encap'), + 'gateway': self.module.params.get('gateway'), + } + + def get_vpn_customer_gateway(self): + args = { + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id'), + 'fetch_list': True, + } + vpn_customer_gateway = self.module.params.get('name') + vpn_customer_gateways = self.query_api('listVpnCustomerGateways', **args) + if vpn_customer_gateways: + for vgw in vpn_customer_gateways: + if vpn_customer_gateway.lower() in [vgw['id'], vgw['name'].lower()]: + return vgw + + def present_vpn_customer_gateway(self): + vpn_customer_gateway = self.get_vpn_customer_gateway() + required_params = [ + 'cidrs', + 'esp_policy', + 'gateway', + 'ike_policy', + 'ipsec_psk', + ] + self.module.fail_on_missing_params(required_params=required_params) + + if not vpn_customer_gateway: + vpn_customer_gateway = self._create_vpn_customer_gateway(vpn_customer_gateway) + else: + vpn_customer_gateway = self._update_vpn_customer_gateway(vpn_customer_gateway) + + return vpn_customer_gateway + + def _create_vpn_customer_gateway(self, vpn_customer_gateway): + self.result['changed'] = True + args = self._common_args() + if not self.module.check_mode: + res = self.query_api('createVpnCustomerGateway', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + vpn_customer_gateway = self.poll_job(res, 'vpncustomergateway') + return vpn_customer_gateway + + def _update_vpn_customer_gateway(self, vpn_customer_gateway): + args = self._common_args() + args.update({'id': vpn_customer_gateway['id']}) + if self.has_changed(args, vpn_customer_gateway, skip_diff_for_keys=['ipsecpsk']): + self.result['changed'] = True + if not self.module.check_mode: + res = self.query_api('updateVpnCustomerGateway', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + vpn_customer_gateway = self.poll_job(res, 'vpncustomergateway') + return vpn_customer_gateway + + def absent_vpn_customer_gateway(self): + vpn_customer_gateway = self.get_vpn_customer_gateway() + if vpn_customer_gateway: + self.result['changed'] = True + args = { + 'id': vpn_customer_gateway['id'] + } + if not self.module.check_mode: + res = self.query_api('deleteVpnCustomerGateway', **args) + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(res, 'vpncustomergateway') + + return vpn_customer_gateway + + def get_result(self, vpn_customer_gateway): + super(AnsibleCloudStackVpnCustomerGateway, self).get_result(vpn_customer_gateway) + if vpn_customer_gateway: + if 'cidrlist' in vpn_customer_gateway: + self.result['cidrs'] = vpn_customer_gateway['cidrlist'].split(',') or [vpn_customer_gateway['cidrlist']] + # Ensure we return a bool + self.result['force_encap'] = True if vpn_customer_gateway.get('forceencap') else False + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name=dict(required=True), + state=dict(choices=['present', 'absent'], default='present'), + domain=dict(), + account=dict(), + project=dict(), + cidrs=dict(type='list', elements='str', aliases=['cidr']), + esp_policy=dict(), + esp_lifetime=dict(type='int'), + gateway=dict(), + ike_policy=dict(), + ike_lifetime=dict(type='int'), + ipsec_psk=dict(no_log=True), + dpd=dict(type='bool'), + force_encap=dict(type='bool'), + poll_async=dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_vpn_cgw = AnsibleCloudStackVpnCustomerGateway(module) + + state = module.params.get('state') + if state == "absent": + vpn_customer_gateway = acs_vpn_cgw.absent_vpn_customer_gateway() + else: + vpn_customer_gateway = acs_vpn_cgw.present_vpn_customer_gateway() + + result = acs_vpn_cgw.get_result(vpn_customer_gateway) + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vpn_gateway.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vpn_gateway.py new file mode 100644 index 00000000..7bc3ab0b --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_vpn_gateway.py @@ -0,0 +1,204 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2017, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_vpn_gateway +short_description: Manages site-to-site VPN gateways on Apache CloudStack based clouds. +description: + - Creates and removes VPN site-to-site gateways. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + vpc: + description: + - Name of the VPC. + type: str + required: true + state: + description: + - State of the VPN gateway. + type: str + default: present + choices: [ present, absent ] + domain: + description: + - Domain the VPN gateway is related to. + type: str + account: + description: + - Account the VPN gateway is related to. + type: str + project: + description: + - Name of the project the VPN gateway is related to. + type: str + zone: + description: + - Name of the zone the VPC is related to. + - If not set, default zone is used. + type: str + poll_async: + description: + - Poll async jobs until job has finished. + type: bool + default: yes +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Ensure a vpn gateway is present + ngine_io.cloudstack.cs_vpn_gateway: + vpc: my VPC + +- name: Ensure a vpn gateway is absent + ngine_io.cloudstack.cs_vpn_gateway: + vpc: my VPC + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the VPN site-to-site gateway. + returned: success + type: str + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 +public_ip: + description: IP address of the VPN site-to-site gateway. + returned: success + type: str + sample: 10.100.212.10 +vpc: + description: Name of the VPC. + returned: success + type: str + sample: My VPC +domain: + description: Domain the VPN site-to-site gateway is related to. + returned: success + type: str + sample: example domain +account: + description: Account the VPN site-to-site gateway is related to. + returned: success + type: str + sample: example account +project: + description: Name of project the VPN site-to-site gateway is related to. + returned: success + type: str + sample: Production +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together +) + + +class AnsibleCloudStackVpnGateway(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackVpnGateway, self).__init__(module) + self.returns = { + 'publicip': 'public_ip' + } + + def get_vpn_gateway(self): + args = { + 'vpcid': self.get_vpc(key='id'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id') + } + vpn_gateways = self.query_api('listVpnGateways', **args) + if vpn_gateways: + return vpn_gateways['vpngateway'][0] + return None + + def present_vpn_gateway(self): + vpn_gateway = self.get_vpn_gateway() + if not vpn_gateway: + self.result['changed'] = True + args = { + 'vpcid': self.get_vpc(key='id'), + 'account': self.get_account(key='name'), + 'domainid': self.get_domain(key='id'), + 'projectid': self.get_project(key='id') + } + if not self.module.check_mode: + res = self.query_api('createVpnGateway', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + vpn_gateway = self.poll_job(res, 'vpngateway') + + return vpn_gateway + + def absent_vpn_gateway(self): + vpn_gateway = self.get_vpn_gateway() + if vpn_gateway: + self.result['changed'] = True + args = { + 'id': vpn_gateway['id'] + } + if not self.module.check_mode: + res = self.query_api('deleteVpnGateway', **args) + + poll_async = self.module.params.get('poll_async') + if poll_async: + self.poll_job(res, 'vpngateway') + + return vpn_gateway + + def get_result(self, vpn_gateway): + super(AnsibleCloudStackVpnGateway, self).get_result(vpn_gateway) + if vpn_gateway: + self.result['vpc'] = self.get_vpc(key='name') + return self.result + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + vpc=dict(required=True), + state=dict(choices=['present', 'absent'], default='present'), + domain=dict(), + account=dict(), + project=dict(), + zone=dict(), + poll_async=dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_vpn_gw = AnsibleCloudStackVpnGateway(module) + + state = module.params.get('state') + if state == "absent": + vpn_gateway = acs_vpn_gw.absent_vpn_gateway() + else: + vpn_gateway = acs_vpn_gw.present_vpn_gateway() + + result = acs_vpn_gw.get_result(vpn_gateway) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_zone.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_zone.py new file mode 100644 index 00000000..51348086 --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_zone.py @@ -0,0 +1,377 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_zone +short_description: Manages zones on Apache CloudStack based clouds. +description: + - Create, update and remove zones. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + name: + description: + - Name of the zone. + type: str + required: true + id: + description: + - uuid of the existing zone. + type: str + state: + description: + - State of the zone. + type: str + default: present + choices: [ present, enabled, disabled, absent ] + domain: + description: + - Domain the zone is related to. + - Zone is a public zone if not set. + type: str + network_domain: + description: + - Network domain for the zone. + type: str + network_type: + description: + - Network type of the zone. + type: str + default: Basic + choices: [ Basic, Advanced ] + dns1: + description: + - First DNS for the zone. + - Required if I(state=present) + type: str + dns2: + description: + - Second DNS for the zone. + type: str + internal_dns1: + description: + - First internal DNS for the zone. + - If not set I(dns1) will be used on I(state=present). + type: str + internal_dns2: + description: + - Second internal DNS for the zone. + type: str + dns1_ipv6: + description: + - First DNS for IPv6 for the zone. + type: str + dns2_ipv6: + description: + - Second DNS for IPv6 for the zone. + type: str + guest_cidr_address: + description: + - Guest CIDR address for the zone. + type: str + dhcp_provider: + description: + - DHCP provider for the Zone. + type: str + local_storage_enabled: + description: + - Whether to enable local storage for the zone or not.. + type: bool + securitygroups_enabled: + description: + - Whether the zone is security group enabled or not. + type: bool +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Ensure a zone is present + ngine_io.cloudstack.cs_zone: + name: ch-zrh-ix-01 + dns1: 8.8.8.8 + dns2: 8.8.4.4 + network_type: basic + +- name: Ensure a zone is disabled + ngine_io.cloudstack.cs_zone: + name: ch-zrh-ix-01 + state: disabled + +- name: Ensure a zone is enabled + ngine_io.cloudstack.cs_zone: + name: ch-zrh-ix-01 + state: enabled + +- name: Ensure a zone is absent + ngine_io.cloudstack.cs_zone: + name: ch-zrh-ix-01 + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the zone. + returned: success + type: str + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 +name: + description: Name of the zone. + returned: success + type: str + sample: zone01 +dns1: + description: First DNS for the zone. + returned: success + type: str + sample: 8.8.8.8 +dns2: + description: Second DNS for the zone. + returned: success + type: str + sample: 8.8.4.4 +internal_dns1: + description: First internal DNS for the zone. + returned: success + type: str + sample: 8.8.8.8 +internal_dns2: + description: Second internal DNS for the zone. + returned: success + type: str + sample: 8.8.4.4 +dns1_ipv6: + description: First IPv6 DNS for the zone. + returned: success + type: str + sample: "2001:4860:4860::8888" +dns2_ipv6: + description: Second IPv6 DNS for the zone. + returned: success + type: str + sample: "2001:4860:4860::8844" +allocation_state: + description: State of the zone. + returned: success + type: str + sample: Enabled +domain: + description: Domain the zone is related to. + returned: success + type: str + sample: ROOT +network_domain: + description: Network domain for the zone. + returned: success + type: str + sample: example.com +network_type: + description: Network type for the zone. + returned: success + type: str + sample: basic +local_storage_enabled: + description: Local storage offering enabled. + returned: success + type: bool + sample: false +securitygroups_enabled: + description: Security groups support is enabled. + returned: success + type: bool + sample: false +guest_cidr_address: + description: Guest CIDR address for the zone + returned: success + type: str + sample: 10.1.1.0/24 +dhcp_provider: + description: DHCP provider for the zone + returned: success + type: str + sample: VirtualRouter +zone_token: + description: Zone token + returned: success + type: str + sample: ccb0a60c-79c8-3230-ab8b-8bdbe8c45bb7 +tags: + description: List of resource tags associated with the zone. + returned: success + type: list + sample: [ { "key": "foo", "value": "bar" } ] +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, + cs_required_together, +) + + +class AnsibleCloudStackZone(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackZone, self).__init__(module) + self.returns = { + 'dns1': 'dns1', + 'dns2': 'dns2', + 'internaldns1': 'internal_dns1', + 'internaldns2': 'internal_dns2', + 'ipv6dns1': 'dns1_ipv6', + 'ipv6dns2': 'dns2_ipv6', + 'domain': 'network_domain', + 'networktype': 'network_type', + 'securitygroupsenabled': 'securitygroups_enabled', + 'localstorageenabled': 'local_storage_enabled', + 'guestcidraddress': 'guest_cidr_address', + 'dhcpprovider': 'dhcp_provider', + 'allocationstate': 'allocation_state', + 'zonetoken': 'zone_token', + } + self.zone = None + + def _get_common_zone_args(self): + args = { + 'name': self.module.params.get('name'), + 'dns1': self.module.params.get('dns1'), + 'dns2': self.module.params.get('dns2'), + 'internaldns1': self.get_or_fallback('internal_dns1', 'dns1'), + 'internaldns2': self.get_or_fallback('internal_dns2', 'dns2'), + 'ipv6dns1': self.module.params.get('dns1_ipv6'), + 'ipv6dns2': self.module.params.get('dns2_ipv6'), + 'networktype': self.module.params.get('network_type'), + 'domain': self.module.params.get('network_domain'), + 'localstorageenabled': self.module.params.get('local_storage_enabled'), + 'guestcidraddress': self.module.params.get('guest_cidr_address'), + 'dhcpprovider': self.module.params.get('dhcp_provider'), + } + state = self.module.params.get('state') + if state in ['enabled', 'disabled']: + args['allocationstate'] = state.capitalize() + return args + + def get_zone(self): + if not self.zone: + args = {} + + uuid = self.module.params.get('id') + if uuid: + args['id'] = uuid + zones = self.query_api('listZones', **args) + if zones: + self.zone = zones['zone'][0] + return self.zone + + args['name'] = self.module.params.get('name') + zones = self.query_api('listZones', **args) + if zones: + self.zone = zones['zone'][0] + return self.zone + + def present_zone(self): + zone = self.get_zone() + if zone: + zone = self._update_zone() + else: + zone = self._create_zone() + return zone + + def _create_zone(self): + required_params = [ + 'dns1', + ] + self.module.fail_on_missing_params(required_params=required_params) + + self.result['changed'] = True + + args = self._get_common_zone_args() + args['domainid'] = self.get_domain(key='id') + args['securitygroupenabled'] = self.module.params.get('securitygroups_enabled') + + zone = None + if not self.module.check_mode: + res = self.query_api('createZone', **args) + zone = res['zone'] + return zone + + def _update_zone(self): + zone = self.get_zone() + + args = self._get_common_zone_args() + args['id'] = zone['id'] + + if self.has_changed(args, zone): + self.result['changed'] = True + + if not self.module.check_mode: + res = self.query_api('updateZone', **args) + zone = res['zone'] + return zone + + def absent_zone(self): + zone = self.get_zone() + if zone: + self.result['changed'] = True + + args = { + 'id': zone['id'] + } + if not self.module.check_mode: + self.query_api('deleteZone', **args) + + return zone + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + id=dict(), + name=dict(required=True), + dns1=dict(), + dns2=dict(), + internal_dns1=dict(), + internal_dns2=dict(), + dns1_ipv6=dict(), + dns2_ipv6=dict(), + network_type=dict(default='Basic', choices=['Basic', 'Advanced']), + network_domain=dict(), + guest_cidr_address=dict(), + dhcp_provider=dict(), + local_storage_enabled=dict(type='bool'), + securitygroups_enabled=dict(type='bool'), + state=dict(choices=['present', 'enabled', 'disabled', 'absent'], default='present'), + domain=dict(), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + acs_zone = AnsibleCloudStackZone(module) + + state = module.params.get('state') + if state in ['absent']: + zone = acs_zone.absent_zone() + else: + zone = acs_zone.present_zone() + + result = acs_zone.get_result(zone) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_zone_info.py b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_zone_info.py new file mode 100644 index 00000000..1581cdbf --- /dev/null +++ b/collections-debian-merged/ansible_collections/ngine_io/cloudstack/plugins/modules/cs_zone_info.py @@ -0,0 +1,207 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016, René Moser <mail@renemoser.net> +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: cs_zone_info +short_description: Gathering information about zones from Apache CloudStack based clouds. +description: + - Gathering information from the API of a zone. +author: René Moser (@resmo) +version_added: 0.1.0 +options: + zone: + description: + - Name of the zone. + - If not specified, all zones are returned + type: str + aliases: [ name ] +extends_documentation_fragment: +- ngine_io.cloudstack.cloudstack +''' + +EXAMPLES = ''' +- name: Gather information from a zone + ngine_io.cloudstack.cs_zone_info: + zone: ch-gva-1 + register: zone + +- name: Show the returned results of the registered variable + debug: + msg: "{{ zone }}" + +- name: Gather information from all zones + ngine_io.cloudstack.cs_zone_info: + register: zones + +- name: Show information on all zones + debug: + msg: "{{ zones }}" +''' + +RETURN = ''' +--- +zones: + description: A list of matching zones. + type: list + returned: success + contains: + id: + description: UUID of the zone. + returned: success + type: str + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 + name: + description: Name of the zone. + returned: success + type: str + sample: zone01 + dns1: + description: First DNS for the zone. + returned: success + type: str + sample: 8.8.8.8 + dns2: + description: Second DNS for the zone. + returned: success + type: str + sample: 8.8.4.4 + internal_dns1: + description: First internal DNS for the zone. + returned: success + type: str + sample: 8.8.8.8 + internal_dns2: + description: Second internal DNS for the zone. + returned: success + type: str + sample: 8.8.4.4 + dns1_ipv6: + description: First IPv6 DNS for the zone. + returned: success + type: str + sample: "2001:4860:4860::8888" + dns2_ipv6: + description: Second IPv6 DNS for the zone. + returned: success + type: str + sample: "2001:4860:4860::8844" + allocation_state: + description: State of the zone. + returned: success + type: str + sample: Enabled + domain: + description: Domain the zone is related to. + returned: success + type: str + sample: ROOT + network_domain: + description: Network domain for the zone. + returned: success + type: str + sample: example.com + network_type: + description: Network type for the zone. + returned: success + type: str + sample: basic + local_storage_enabled: + description: Local storage offering enabled. + returned: success + type: bool + sample: false + securitygroups_enabled: + description: Security groups support is enabled. + returned: success + type: bool + sample: false + guest_cidr_address: + description: Guest CIDR address for the zone + returned: success + type: str + sample: 10.1.1.0/24 + dhcp_provider: + description: DHCP provider for the zone + returned: success + type: str + sample: VirtualRouter + zone_token: + description: Zone token + returned: success + type: str + sample: ccb0a60c-79c8-3230-ab8b-8bdbe8c45bb7 + tags: + description: List of resource tags associated with the zone. + returned: success + type: list + sample: [ { "key": "foo", "value": "bar" } ] +''' + +from ansible.module_utils.basic import AnsibleModule +from ..module_utils.cloudstack import ( + AnsibleCloudStack, + cs_argument_spec, +) + + +class AnsibleCloudStackZoneInfo(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackZoneInfo, self).__init__(module) + self.returns = { + 'dns1': 'dns1', + 'dns2': 'dns2', + 'internaldns1': 'internal_dns1', + 'internaldns2': 'internal_dns2', + 'ipv6dns1': 'dns1_ipv6', + 'ipv6dns2': 'dns2_ipv6', + 'domain': 'network_domain', + 'networktype': 'network_type', + 'securitygroupsenabled': 'securitygroups_enabled', + 'localstorageenabled': 'local_storage_enabled', + 'guestcidraddress': 'guest_cidr_address', + 'dhcpprovider': 'dhcp_provider', + 'allocationstate': 'allocation_state', + 'zonetoken': 'zone_token', + } + + def get_zone(self): + if self.module.params['zone']: + zones = [super(AnsibleCloudStackZoneInfo, self).get_zone()] + else: + zones = self.query_api('listZones') + if zones: + zones = zones['zone'] + else: + zones = [] + return { + 'zones': [self.update_result(resource) for resource in zones] + } + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + zone=dict(type='str', aliases=['name']), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + acs_zone_info = AnsibleCloudStackZoneInfo(module=module) + result = acs_zone_info.get_zone() + module.exit_json(**result) + + +if __name__ == '__main__': + main() |