mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
cloudstack migrated to dedicated collection ngine_io.cloudstack (#173)
* cloudstack migrated to dedicated collection ngine_io.cloudstack * remove leftovers * remove more leftovers
This commit is contained in:
parent
6ccf3682ac
commit
ec52007c8d
312 changed files with 0 additions and 37203 deletions
41
.github/BOTMETA.yml
vendored
41
.github/BOTMETA.yml
vendored
|
@ -129,9 +129,6 @@ files:
|
||||||
$doc_fragments/avi.py:
|
$doc_fragments/avi.py:
|
||||||
$doc_fragments/ce.py:
|
$doc_fragments/ce.py:
|
||||||
$doc_fragments/cloudscale.py:
|
$doc_fragments/cloudscale.py:
|
||||||
$doc_fragments/cloudstack.py:
|
|
||||||
maintainers: $team_cloudstack
|
|
||||||
labels: cloudstack
|
|
||||||
$doc_fragments/docker.py:
|
$doc_fragments/docker.py:
|
||||||
maintainers: $team_docker
|
maintainers: $team_docker
|
||||||
labels: cloud docker
|
labels: cloud docker
|
||||||
|
@ -227,9 +224,6 @@ files:
|
||||||
$module_utils/cloudscale.py:
|
$module_utils/cloudscale.py:
|
||||||
maintainers: $team_cloudscale
|
maintainers: $team_cloudscale
|
||||||
labels: cloudscale
|
labels: cloudscale
|
||||||
$module_utils/cloudstack.py:
|
|
||||||
maintainers: $team_cloudstack
|
|
||||||
labels: cloudstack
|
|
||||||
$module_utils/docker/:
|
$module_utils/docker/:
|
||||||
maintainers: $team_docker
|
maintainers: $team_docker
|
||||||
labels: cloud
|
labels: cloud
|
||||||
|
@ -402,40 +396,6 @@ files:
|
||||||
maintainers: gaudenz
|
maintainers: gaudenz
|
||||||
$modules/cloud/cloudscale/cloudscale_volume.py:
|
$modules/cloud/cloudscale/cloudscale_volume.py:
|
||||||
authors: gaudenz href resmo
|
authors: gaudenz href resmo
|
||||||
$modules/cloud/cloudstack/:
|
|
||||||
authors: resmo
|
|
||||||
maintainers: $team_cloudstack
|
|
||||||
$modules/cloud/cloudstack/cs_disk_offering.py:
|
|
||||||
authors: dpassante resmo
|
|
||||||
maintainers: rhtyd
|
|
||||||
$modules/cloud/cloudstack/cs_image_store.py:
|
|
||||||
authors: PatTheSilent
|
|
||||||
$modules/cloud/cloudstack/cs_instance_nic.py:
|
|
||||||
authors: marcaurele resmo
|
|
||||||
$modules/cloud/cloudstack/cs_instance_password_reset.py:
|
|
||||||
authors: onitake
|
|
||||||
$modules/cloud/cloudstack/cs_ip_address.py:
|
|
||||||
authors: dazworrall resmo
|
|
||||||
$modules/cloud/cloudstack/cs_network_offering.py:
|
|
||||||
authors: dpassante
|
|
||||||
maintainers: rhtyd
|
|
||||||
$modules/cloud/cloudstack/cs_physical_network.py:
|
|
||||||
authors: PatTheSilent netservers
|
|
||||||
$modules/cloud/cloudstack/cs_role_permission.py:
|
|
||||||
authors: dpassante
|
|
||||||
maintainers: rhtyd
|
|
||||||
$modules/cloud/cloudstack/cs_storage_pool.py:
|
|
||||||
authors: netservers resmo
|
|
||||||
$modules/cloud/cloudstack/cs_traffic_type.py:
|
|
||||||
authors: PatTheSilent
|
|
||||||
$modules/cloud/cloudstack/cs_vlan_ip_range.py:
|
|
||||||
authors: dpassante
|
|
||||||
maintainers: rhtyd
|
|
||||||
$modules/cloud/cloudstack/cs_volume.py:
|
|
||||||
authors: jeffersongirao resmo
|
|
||||||
$modules/cloud/cloudstack/cs_vpc_offering.py:
|
|
||||||
authors: dpassante
|
|
||||||
maintainers: rhtyd
|
|
||||||
$modules/cloud/digital_ocean/_digital_ocean.py:
|
$modules/cloud/digital_ocean/_digital_ocean.py:
|
||||||
authors: zbal
|
authors: zbal
|
||||||
$modules/cloud/digital_ocean/:
|
$modules/cloud/digital_ocean/:
|
||||||
|
@ -2178,7 +2138,6 @@ macros:
|
||||||
team_avi: ericsysmin grastogi23 khaltore
|
team_avi: ericsysmin grastogi23 khaltore
|
||||||
team_bsd: JoergFiedler MacLemon bcoca dch jasperla mekanix opoplawski overhacked tuxillo
|
team_bsd: JoergFiedler MacLemon bcoca dch jasperla mekanix opoplawski overhacked tuxillo
|
||||||
team_cloudscale: gaudenz resmo
|
team_cloudscale: gaudenz resmo
|
||||||
team_cloudstack: dpassante rhtyd
|
|
||||||
team_cyberark_conjur: jvanderhoof ryanprior
|
team_cyberark_conjur: jvanderhoof ryanprior
|
||||||
team_digital_ocean: BondAnthony mgregson
|
team_digital_ocean: BondAnthony mgregson
|
||||||
team_docker: DBendit WojciechowskiPiotr akshay196 danihodovic dariko felixfontein jwitko kassiansun tbouvet
|
team_docker: DBendit WojciechowskiPiotr akshay196 danihodovic dariko felixfontein jwitko kassiansun tbouvet
|
||||||
|
|
|
@ -4,14 +4,6 @@ plugin_routing:
|
||||||
deprecation:
|
deprecation:
|
||||||
removal_date: TBD
|
removal_date: TBD
|
||||||
warning_text: see plugin documentation for details
|
warning_text: see plugin documentation for details
|
||||||
cs_instance_facts:
|
|
||||||
deprecation:
|
|
||||||
removal_date: TBD
|
|
||||||
warning_text: see plugin documentation for details
|
|
||||||
cs_zone_facts:
|
|
||||||
deprecation:
|
|
||||||
removal_date: TBD
|
|
||||||
warning_text: see plugin documentation for details
|
|
||||||
digital_ocean:
|
digital_ocean:
|
||||||
deprecation:
|
deprecation:
|
||||||
removal_date: TBD
|
removal_date: TBD
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
# -*- 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
|
|
||||||
requirements:
|
|
||||||
- python >= 2.6
|
|
||||||
- cs >= 0.6.10
|
|
||||||
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.
|
|
||||||
'''
|
|
|
@ -1,664 +0,0 @@
|
||||||
# -*- 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'),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
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',
|
|
||||||
}
|
|
||||||
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'],
|
|
||||||
})
|
|
||||||
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_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.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
|
|
|
@ -1,460 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: create an account in domain 'CUSTOMERS'
|
|
||||||
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
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Lock an existing account in domain 'CUSTOMERS'
|
|
||||||
cs_account:
|
|
||||||
name: customer_xy
|
|
||||||
domain: CUSTOMERS
|
|
||||||
state: locked
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Disable an existing account in domain 'CUSTOMERS'
|
|
||||||
cs_account:
|
|
||||||
name: customer_xy
|
|
||||||
domain: CUSTOMERS
|
|
||||||
state: disabled
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Enable an existing account in domain 'CUSTOMERS'
|
|
||||||
cs_account:
|
|
||||||
name: customer_xy
|
|
||||||
domain: CUSTOMERS
|
|
||||||
state: enabled
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove an account in domain 'CUSTOMERS'
|
|
||||||
cs_account:
|
|
||||||
name: customer_xy
|
|
||||||
domain: CUSTOMERS
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Create a single user LDAP account in domain 'CUSTOMERS'
|
|
||||||
cs_account:
|
|
||||||
name: customer_xy
|
|
||||||
username: customer_xy
|
|
||||||
domain: CUSTOMERS
|
|
||||||
ldap_domain: cn=customer_xy,cn=team_xy,ou=People,dc=domain,dc=local
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Create a LDAP account in domain 'CUSTOMERS' and bind it to a LDAP group
|
|
||||||
cs_account:
|
|
||||||
name: team_xy
|
|
||||||
username: customer_xy
|
|
||||||
domain: CUSTOMERS
|
|
||||||
ldap_domain: cn=team_xy,ou=People,dc=domain,dc=local
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,235 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: cs_affinitygroup
|
|
||||||
short_description: Manages affinity groups on Apache CloudStack based clouds.
|
|
||||||
description:
|
|
||||||
- Create and remove affinity groups.
|
|
||||||
author: René Moser (@resmo)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Create a affinity group
|
|
||||||
cs_affinitygroup:
|
|
||||||
name: haproxy
|
|
||||||
affinity_type: host anti-affinity
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove a affinity group
|
|
||||||
cs_affinitygroup:
|
|
||||||
name: haproxy
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,393 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: cs_cluster
|
|
||||||
short_description: Manages host clusters on Apache CloudStack based clouds.
|
|
||||||
description:
|
|
||||||
- Create, update and remove clusters.
|
|
||||||
author: René Moser (@resmo)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Ensure a cluster is present
|
|
||||||
cs_cluster:
|
|
||||||
name: kvm-cluster-01
|
|
||||||
zone: ch-zrh-ix-01
|
|
||||||
hypervisor: KVM
|
|
||||||
cluster_type: CloudManaged
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure a cluster is disabled
|
|
||||||
cs_cluster:
|
|
||||||
name: kvm-cluster-01
|
|
||||||
zone: ch-zrh-ix-01
|
|
||||||
state: disabled
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure a cluster is enabled
|
|
||||||
cs_cluster:
|
|
||||||
name: kvm-cluster-01
|
|
||||||
zone: ch-zrh-ix-01
|
|
||||||
state: enabled
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure a cluster is absent
|
|
||||||
cs_cluster:
|
|
||||||
name: kvm-cluster-01
|
|
||||||
zone: ch-zrh-ix-01
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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_pod(self, key=None):
|
|
||||||
args = {
|
|
||||||
'name': self.module.params.get('pod'),
|
|
||||||
'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_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()
|
|
|
@ -1,277 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Ensure global configuration
|
|
||||||
cs_configuration:
|
|
||||||
name: router.reboot.when.outofband.migrated
|
|
||||||
value: false
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure zone configuration
|
|
||||||
cs_configuration:
|
|
||||||
name: router.reboot.when.outofband.migrated
|
|
||||||
zone: ch-gva-01
|
|
||||||
value: true
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure storage configuration
|
|
||||||
cs_configuration:
|
|
||||||
name: storage.overprovisioning.factor
|
|
||||||
storage: storage01
|
|
||||||
value: 2.0
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure account configuration
|
|
||||||
cs_configuration:
|
|
||||||
name: allow.public.user.templates
|
|
||||||
value: false
|
|
||||||
account: acme inc
|
|
||||||
domain: customers
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
||||||
if self.has_changed(args, configuration, ['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()
|
|
|
@ -1,381 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (c) 2018, David Passante <@dpassante>
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
default: no
|
|
||||||
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
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Create a disk offering with local storage
|
|
||||||
cs_disk_offering:
|
|
||||||
name: small
|
|
||||||
display_text: Small 10GB
|
|
||||||
disk_size: 10
|
|
||||||
storage_type: local
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Create or update a disk offering with shared storage
|
|
||||||
cs_disk_offering:
|
|
||||||
name: small
|
|
||||||
display_text: Small 10GB
|
|
||||||
disk_size: 10
|
|
||||||
storage_type: shared
|
|
||||||
storage_tags: SAN01
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove a disk offering
|
|
||||||
cs_disk_offering:
|
|
||||||
name: small
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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', 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()
|
|
|
@ -1,251 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: cs_domain
|
|
||||||
short_description: Manages domains on Apache CloudStack based clouds.
|
|
||||||
description:
|
|
||||||
- Create, update and remove domains.
|
|
||||||
author: René Moser (@resmo)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Create a domain
|
|
||||||
cs_domain:
|
|
||||||
path: ROOT/customers
|
|
||||||
network_domain: customers.example.com
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Create another subdomain
|
|
||||||
cs_domain:
|
|
||||||
path: ROOT/customers/xy
|
|
||||||
network_domain: xy.customers.example.com
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove a domain
|
|
||||||
cs_domain:
|
|
||||||
path: ROOT/customers/xy
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,234 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
cs_facts:
|
|
||||||
|
|
||||||
# Gather specific fact on instances
|
|
||||||
- name: Gather cloudstack facts
|
|
||||||
cs_facts: filter=cloudstack_instance_id
|
|
||||||
|
|
||||||
# Gather specific fact on instances with a given meta_data_host
|
|
||||||
- name: Gather cloudstack facts
|
|
||||||
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()
|
|
|
@ -1,447 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: cs_firewall
|
|
||||||
short_description: Manages firewall rules on Apache CloudStack based clouds.
|
|
||||||
description:
|
|
||||||
- Creates and removes firewall rules.
|
|
||||||
author: René Moser (@resmo)
|
|
||||||
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.
|
|
||||||
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
|
|
||||||
aliases: [ tag ]
|
|
||||||
extends_documentation_fragment:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Allow inbound port 80/tcp from 1.2.3.4 to 4.3.2.1
|
|
||||||
cs_firewall:
|
|
||||||
ip_address: 4.3.2.1
|
|
||||||
port: 80
|
|
||||||
cidr: 1.2.3.4/32
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Allow inbound tcp/udp port 53 to 4.3.2.1
|
|
||||||
cs_firewall:
|
|
||||||
ip_address: 4.3.2.1
|
|
||||||
port: 53
|
|
||||||
protocol: '{{ item }}'
|
|
||||||
with_items:
|
|
||||||
- tcp
|
|
||||||
- udp
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure firewall rule is removed
|
|
||||||
cs_firewall:
|
|
||||||
ip_address: 4.3.2.1
|
|
||||||
start_port: 8000
|
|
||||||
end_port: 8888
|
|
||||||
cidr: 17.0.0.0/8
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Allow all outbound traffic
|
|
||||||
cs_firewall:
|
|
||||||
network: my_network
|
|
||||||
type: egress
|
|
||||||
protocol: all
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Allow only HTTP outbound traffic for an IP
|
|
||||||
cs_firewall:
|
|
||||||
network: my_network
|
|
||||||
type: egress
|
|
||||||
port: 80
|
|
||||||
cidr: 10.101.1.20
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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', 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', aliases=['tag'], default=None),
|
|
||||||
))
|
|
||||||
|
|
||||||
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()
|
|
|
@ -1,627 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: cs_host
|
|
||||||
short_description: Manages hosts on Apache CloudStack based clouds.
|
|
||||||
description:
|
|
||||||
- Create, update and remove hosts.
|
|
||||||
author: René Moser (@resmo)
|
|
||||||
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
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Ensure a host is present but disabled
|
|
||||||
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
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure an existing host is disabled
|
|
||||||
cs_host:
|
|
||||||
name: pod01.zone01.example.com
|
|
||||||
zone: zone01
|
|
||||||
allocation_state: disabled
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure an existing host is enabled
|
|
||||||
cs_host:
|
|
||||||
name: pod01.zone01.example.com
|
|
||||||
zone: zone01
|
|
||||||
allocation_state: enabled
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure a host is absent
|
|
||||||
cs_host:
|
|
||||||
name: pod01.zone01.example.com
|
|
||||||
zone: zone01
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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_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" % pod_name)
|
|
||||||
|
|
||||||
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', 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()
|
|
|
@ -1,252 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {
|
|
||||||
'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'
|
|
||||||
}
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: cs_image_store
|
|
||||||
|
|
||||||
short_description: Manages CloudStack Image Stores.
|
|
||||||
|
|
||||||
|
|
||||||
description:
|
|
||||||
- Deploy, remove, recreate CloudStack Image Stores.
|
|
||||||
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
|
|
||||||
author:
|
|
||||||
- Patryk Cichy (@PatTheSilent)
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Add a Image Store (NFS)
|
|
||||||
cs_image_store:
|
|
||||||
zone: zone-01
|
|
||||||
name: nfs-01
|
|
||||||
provider: NFS
|
|
||||||
url: nfs://192.168.21.16/exports/secondary
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
# Change the NFS share URL and force a Image Store recreation
|
|
||||||
- name: Change the NFS url
|
|
||||||
cs_image_store:
|
|
||||||
zone: zone-01
|
|
||||||
name: nfs-01
|
|
||||||
provider: NFS
|
|
||||||
force_recreate: yes
|
|
||||||
url: nfs://192.168.21.10/shares/secondary
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: delete the image store
|
|
||||||
cs_image_store:
|
|
||||||
name: nfs-01
|
|
||||||
zone: zone-01
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,373 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['deprecated'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: cs_instance_facts
|
|
||||||
short_description: Gathering facts from the API of instances from Apache CloudStack based clouds.
|
|
||||||
description:
|
|
||||||
- Gathering facts from the API of an instance.
|
|
||||||
deprecated:
|
|
||||||
removed_in: "2.13"
|
|
||||||
why: Transformed into an info module.
|
|
||||||
alternative: Use M(cs_instance_info) instead.
|
|
||||||
author: René Moser (@resmo)
|
|
||||||
options:
|
|
||||||
name:
|
|
||||||
description:
|
|
||||||
- Name or display name of the instance.
|
|
||||||
type: str
|
|
||||||
required: true
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: gather instance facts
|
|
||||||
cs_instance_facts:
|
|
||||||
name: web-vm-1
|
|
||||||
delegate_to: localhost
|
|
||||||
register: vm
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: cloudstack_instance
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: vm
|
|
||||||
'''
|
|
||||||
|
|
||||||
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: 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 ansible_collections.community.general.plugins.module_utils.cloudstack import AnsibleCloudStack, cs_argument_spec
|
|
||||||
|
|
||||||
|
|
||||||
class AnsibleCloudStackInstanceFacts(AnsibleCloudStack):
|
|
||||||
|
|
||||||
def __init__(self, module):
|
|
||||||
super(AnsibleCloudStackInstanceFacts, self).__init__(module)
|
|
||||||
self.instance = None
|
|
||||||
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',
|
|
||||||
}
|
|
||||||
self.facts = {
|
|
||||||
'cloudstack_instance': None,
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_instance(self):
|
|
||||||
instance = self.instance
|
|
||||||
if not instance:
|
|
||||||
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 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_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):
|
|
||||||
instance = self.get_instance()
|
|
||||||
if not instance:
|
|
||||||
self.module.fail_json(msg="Instance not found: %s" % self.module.params.get('name'))
|
|
||||||
return instance
|
|
||||||
|
|
||||||
def get_result(self, instance):
|
|
||||||
super(AnsibleCloudStackInstanceFacts, self).get_result(instance)
|
|
||||||
if 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'] and 'ipaddress' in nic:
|
|
||||||
self.result['default_ip'] = nic['ipaddress']
|
|
||||||
self.result['nic'] = instance['nic']
|
|
||||||
volumes = self.get_volumes(instance)
|
|
||||||
if volumes:
|
|
||||||
self.result['volumes'] = volumes
|
|
||||||
return self.result
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
argument_spec = cs_argument_spec()
|
|
||||||
argument_spec.update(dict(
|
|
||||||
name=dict(required=True),
|
|
||||||
domain=dict(),
|
|
||||||
account=dict(),
|
|
||||||
project=dict(),
|
|
||||||
))
|
|
||||||
|
|
||||||
module = AnsibleModule(
|
|
||||||
argument_spec=argument_spec,
|
|
||||||
supports_check_mode=True,
|
|
||||||
)
|
|
||||||
acs_instance_facts = AnsibleCloudStackInstanceFacts(module=module)
|
|
||||||
cs_instance_facts = acs_instance_facts.get_result_and_facts(
|
|
||||||
facts_name='cloudstack_instance',
|
|
||||||
resource=acs_instance_facts.run()
|
|
||||||
)
|
|
||||||
module.exit_json(**cs_instance_facts)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
|
@ -1,377 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Gather instance information
|
|
||||||
cs_instance_info:
|
|
||||||
name: web-vm-1
|
|
||||||
delegate_to: localhost
|
|
||||||
register: vm
|
|
||||||
|
|
||||||
- name: Show the returned results of the registered variable
|
|
||||||
debug:
|
|
||||||
msg: "{{ vm }}"
|
|
||||||
|
|
||||||
- name: Gather information from all instances
|
|
||||||
cs_instance_info:
|
|
||||||
delegate_to: localhost
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,290 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (c) 2017, Marc-Aurèle Brothier @marcaurele
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Add a nic on another network
|
|
||||||
cs_instance_nic:
|
|
||||||
vm: privnet
|
|
||||||
network: privNetForBasicZone
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure IP address on a nic
|
|
||||||
cs_instance_nic:
|
|
||||||
vm: privnet
|
|
||||||
ip_address: 10.10.11.32
|
|
||||||
network: privNetForBasicZone
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove a secondary nic
|
|
||||||
cs_instance_nic:
|
|
||||||
vm: privnet
|
|
||||||
state: absent
|
|
||||||
network: privNetForBasicZone
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,273 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Assign a specific IP to the default NIC of the VM
|
|
||||||
cs_instance_nic_secondaryip:
|
|
||||||
vm: customer_xy
|
|
||||||
vm_guest_ip: 10.10.10.10
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
# 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
|
|
||||||
cs_instance_nic_secondaryip:
|
|
||||||
vm: customer_xy
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove a specific IP from the default NIC
|
|
||||||
cs_instance_nic_secondaryip:
|
|
||||||
vm: customer_xy
|
|
||||||
vm_guest_ip: 10.10.10.10
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,158 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: stop the virtual machine before resetting the password
|
|
||||||
cs_instance:
|
|
||||||
name: myvirtualmachine
|
|
||||||
state: stopped
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: reset and get new default password
|
|
||||||
cs_instance_password_reset:
|
|
||||||
vm: myvirtualmachine
|
|
||||||
register: root
|
|
||||||
delegate_to: localhost
|
|
||||||
- debug:
|
|
||||||
msg: "new default password is {{ root.password }}"
|
|
||||||
|
|
||||||
- name: boot the virtual machine to activate the new password
|
|
||||||
cs_instance:
|
|
||||||
name: myvirtualmachine
|
|
||||||
state: started
|
|
||||||
delegate_to: localhost
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,187 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: cs_instancegroup
|
|
||||||
short_description: Manages instance groups on Apache CloudStack based clouds.
|
|
||||||
description:
|
|
||||||
- Create and remove instance groups.
|
|
||||||
author: René Moser (@resmo)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Create an instance group
|
|
||||||
cs_instancegroup:
|
|
||||||
name: loadbalancers
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove an instance group
|
|
||||||
cs_instancegroup:
|
|
||||||
name: loadbalancers
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,284 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
aliases: [ tag ]
|
|
||||||
poll_async:
|
|
||||||
description:
|
|
||||||
- Poll async jobs until job has finished.
|
|
||||||
type: bool
|
|
||||||
default: yes
|
|
||||||
extends_documentation_fragment:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Associate an IP address conditionally
|
|
||||||
cs_ip_address:
|
|
||||||
network: My Network
|
|
||||||
register: ip_address
|
|
||||||
when: instance.public_ip is undefined
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Disassociate an IP address
|
|
||||||
cs_ip_address:
|
|
||||||
ip_address: 1.2.3.4
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Associate an IP address with tags
|
|
||||||
cs_ip_address:
|
|
||||||
network: My Network
|
|
||||||
tags:
|
|
||||||
- key: myCustomID
|
|
||||||
- value: 5510c31a-416e-11e8-9013-02000a6b00bf
|
|
||||||
register: ip_address
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Disassociate an IP address with tags
|
|
||||||
cs_ip_address:
|
|
||||||
state: absent
|
|
||||||
tags:
|
|
||||||
- key: myCustomID
|
|
||||||
- value: 5510c31a-416e-11e8-9013-02000a6b00bf
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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 = {
|
|
||||||
'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', 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()
|
|
|
@ -1,443 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: cs_iso
|
|
||||||
short_description: Manages ISO images on Apache CloudStack based clouds.
|
|
||||||
description:
|
|
||||||
- Register and remove ISO images.
|
|
||||||
author: René Moser (@resmo)
|
|
||||||
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
|
|
||||||
aliases: [ tag ]
|
|
||||||
extends_documentation_fragment:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Register an ISO if ISO name does not already exist
|
|
||||||
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)
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Register an ISO with given name if ISO md5 checksum does not already exist
|
|
||||||
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
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove an ISO by name
|
|
||||||
cs_iso:
|
|
||||||
name: Debian 7 64-bit
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove an ISO by checksum
|
|
||||||
cs_iso:
|
|
||||||
name: Debian 7 64-bit
|
|
||||||
checksum: 0b31bccccb048d20b551f70830bb7ad0
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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', 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()
|
|
|
@ -1,378 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (c) 2015, Darren Worrall <darren@iweb.co.uk>
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
required: true
|
|
||||||
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
|
|
||||||
aliases: [ tag ]
|
|
||||||
network:
|
|
||||||
description:
|
|
||||||
- Name of the network.
|
|
||||||
type: str
|
|
||||||
vpc:
|
|
||||||
description:
|
|
||||||
- Name of the VPC.
|
|
||||||
type: str
|
|
||||||
extends_documentation_fragment:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Create a load balancer rule
|
|
||||||
cs_loadbalancer_rule:
|
|
||||||
name: balance_http
|
|
||||||
public_ip: 1.2.3.4
|
|
||||||
algorithm: leastconn
|
|
||||||
public_port: 80
|
|
||||||
private_port: 8080
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Update algorithm of an existing load balancer rule
|
|
||||||
cs_loadbalancer_rule:
|
|
||||||
name: balance_http
|
|
||||||
public_ip: 1.2.3.4
|
|
||||||
algorithm: roundrobin
|
|
||||||
public_port: 80
|
|
||||||
private_port: 8080
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Delete a load balancer rule
|
|
||||||
cs_loadbalancer_rule:
|
|
||||||
name: balance_http
|
|
||||||
public_ip: 1.2.3.4
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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', 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()
|
|
|
@ -1,350 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Add VMs to an existing load balancer
|
|
||||||
cs_loadbalancer_rule_member:
|
|
||||||
name: balance_http
|
|
||||||
vms:
|
|
||||||
- web01
|
|
||||||
- web02
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove a VM from an existing load balancer
|
|
||||||
cs_loadbalancer_rule_member:
|
|
||||||
name: balance_http
|
|
||||||
vms:
|
|
||||||
- web01
|
|
||||||
- web02
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
# Rolling upgrade of hosts
|
|
||||||
- hosts: webservers
|
|
||||||
serial: 1
|
|
||||||
pre_tasks:
|
|
||||||
- name: Remove from load balancer
|
|
||||||
cs_loadbalancer_rule_member:
|
|
||||||
name: balance_http
|
|
||||||
vm: "{{ ansible_hostname }}"
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
tasks:
|
|
||||||
# Perform update
|
|
||||||
post_tasks:
|
|
||||||
- name: Add to load balancer
|
|
||||||
cs_loadbalancer_rule_member:
|
|
||||||
name: balance_http
|
|
||||||
vm: "{{ ansible_hostname }}"
|
|
||||||
state: present
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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'),
|
|
||||||
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()
|
|
|
@ -1,636 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: cs_network
|
|
||||||
short_description: Manages networks on Apache CloudStack based clouds.
|
|
||||||
description:
|
|
||||||
- Create, update, restart and delete networks.
|
|
||||||
author: René Moser (@resmo)
|
|
||||||
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
|
|
||||||
aliases: [ tag ]
|
|
||||||
extends_documentation_fragment:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Create a network
|
|
||||||
cs_network:
|
|
||||||
name: my network
|
|
||||||
zone: gva-01
|
|
||||||
network_offering: DefaultIsolatedNetworkOfferingWithSourceNatService
|
|
||||||
network_domain: example.com
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Create a VPC tier
|
|
||||||
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
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Update a network
|
|
||||||
cs_network:
|
|
||||||
name: my network
|
|
||||||
display_text: network of domain example.local
|
|
||||||
network_domain: example.local
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Restart a network with clean up
|
|
||||||
cs_network:
|
|
||||||
name: my network
|
|
||||||
clean_up: yes
|
|
||||||
state: restarted
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove a network
|
|
||||||
cs_network:
|
|
||||||
name: my network
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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', aliases=['tag']),
|
|
||||||
))
|
|
||||||
required_together = cs_required_together()
|
|
||||||
required_together.extend([
|
|
||||||
['netmask', 'gateway'],
|
|
||||||
])
|
|
||||||
|
|
||||||
module = AnsibleModule(
|
|
||||||
argument_spec=argument_spec,
|
|
||||||
required_together=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()
|
|
|
@ -1,202 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
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)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: create a network ACL
|
|
||||||
cs_network_acl:
|
|
||||||
name: Webserver ACL
|
|
||||||
description: a more detailed description of the ACL
|
|
||||||
vpc: customers
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: remove a network ACL
|
|
||||||
cs_network_acl:
|
|
||||||
name: Webserver ACL
|
|
||||||
vpc: customers
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,461 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
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)
|
|
||||||
options:
|
|
||||||
network_acl:
|
|
||||||
description:
|
|
||||||
- Name of the network ACL.
|
|
||||||
type: str
|
|
||||||
required: true
|
|
||||||
aliases: [ acl ]
|
|
||||||
cidrs:
|
|
||||||
description:
|
|
||||||
- CIDRs of the rule.
|
|
||||||
type: list
|
|
||||||
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
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: create a network ACL rule, allow port 80 ingress
|
|
||||||
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
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: create a network ACL rule, deny port range 8000-9000 ingress for 10.20.0.0/16 and 10.22.0.0/16
|
|
||||||
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
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: remove a network ACL rule
|
|
||||||
cs_network_acl_rule:
|
|
||||||
network_acl: web
|
|
||||||
rule_position: 1
|
|
||||||
vpc: my vpc
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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', 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', 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()
|
|
|
@ -1,424 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
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
|
|
||||||
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
|
|
||||||
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
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Create a network offering and enable it
|
|
||||||
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' }
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
|
|
||||||
- name: Remove a network offering
|
|
||||||
cs_network_offering:
|
|
||||||
name: my_network_offering
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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', 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'),
|
|
||||||
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', aliases=['service_capability']),
|
|
||||||
service_offering=dict(),
|
|
||||||
service_providers=dict(type='list', 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()
|
|
|
@ -1,483 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
nsps_disabled:
|
|
||||||
description:
|
|
||||||
- List of Network Service Providers to disable.
|
|
||||||
type: list
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Ensure a network is present
|
|
||||||
cs_physical_network:
|
|
||||||
name: net01
|
|
||||||
zone: zone01
|
|
||||||
isolation_method: VLAN
|
|
||||||
broadcast_domain_range: ZONE
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Set a tag on a network
|
|
||||||
cs_physical_network:
|
|
||||||
name: net01
|
|
||||||
tag: overlay
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove tag on a network
|
|
||||||
cs_physical_network:
|
|
||||||
name: net01
|
|
||||||
tag: ""
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure a network is enabled with specific nsps enabled
|
|
||||||
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
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure a network is disabled
|
|
||||||
cs_physical_network:
|
|
||||||
name: net01
|
|
||||||
zone: zone01
|
|
||||||
state: disabled
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure a network is enabled
|
|
||||||
cs_physical_network:
|
|
||||||
name: net01
|
|
||||||
zone: zone01
|
|
||||||
state: enabled
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure a network is absent
|
|
||||||
cs_physical_network:
|
|
||||||
name: net01
|
|
||||||
zone: zone01
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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'),
|
|
||||||
nsps_enabled=dict(type='list'),
|
|
||||||
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()
|
|
|
@ -1,297 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: cs_pod
|
|
||||||
short_description: Manages pods on Apache CloudStack based clouds.
|
|
||||||
description:
|
|
||||||
- Create, update, delete pods.
|
|
||||||
author: René Moser (@resmo)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Ensure a pod is present
|
|
||||||
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
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure a pod is disabled
|
|
||||||
cs_pod:
|
|
||||||
name: pod1
|
|
||||||
zone: ch-zrh-ix-01
|
|
||||||
state: disabled
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure a pod is enabled
|
|
||||||
cs_pod:
|
|
||||||
name: pod1
|
|
||||||
zone: ch-zrh-ix-01
|
|
||||||
state: enabled
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure a pod is absent
|
|
||||||
cs_pod:
|
|
||||||
name: pod1
|
|
||||||
zone: ch-zrh-ix-01
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,396 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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.
|
|
||||||
- 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.
|
|
||||||
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
|
|
||||||
aliases: [ tag ]
|
|
||||||
extends_documentation_fragment:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: 1.2.3.4:80 -> web01:8080
|
|
||||||
cs_portforward:
|
|
||||||
ip_address: 1.2.3.4
|
|
||||||
vm: web01
|
|
||||||
public_port: 80
|
|
||||||
private_port: 8080
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: forward SSH and open firewall
|
|
||||||
cs_portforward:
|
|
||||||
ip_address: '{{ public_ip }}'
|
|
||||||
vm: '{{ inventory_hostname }}'
|
|
||||||
public_port: '{{ ansible_ssh_port }}'
|
|
||||||
private_port: 22
|
|
||||||
open_firewall: true
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: forward DNS traffic, but do not open firewall
|
|
||||||
cs_portforward:
|
|
||||||
ip_address: 1.2.3.4
|
|
||||||
vm: '{{ inventory_hostname }}'
|
|
||||||
public_port: 53
|
|
||||||
private_port: 53
|
|
||||||
protocol: udp
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: remove ssh port forwarding
|
|
||||||
cs_portforward:
|
|
||||||
ip_address: 1.2.3.4
|
|
||||||
public_port: 22
|
|
||||||
private_port: 22
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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', 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()
|
|
|
@ -1,279 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
aliases: [ tag ]
|
|
||||||
poll_async:
|
|
||||||
description:
|
|
||||||
- Poll async jobs until job has finished.
|
|
||||||
type: bool
|
|
||||||
default: yes
|
|
||||||
extends_documentation_fragment:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Create a project
|
|
||||||
cs_project:
|
|
||||||
name: web
|
|
||||||
tags:
|
|
||||||
- { key: admin, value: john }
|
|
||||||
- { key: foo, value: bar }
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Rename a project
|
|
||||||
cs_project:
|
|
||||||
name: web
|
|
||||||
display_text: my web project
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Suspend an existing project
|
|
||||||
cs_project:
|
|
||||||
name: web
|
|
||||||
state: suspended
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Activate an existing project
|
|
||||||
cs_project:
|
|
||||||
name: web
|
|
||||||
state: active
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove a project
|
|
||||||
cs_project:
|
|
||||||
name: web
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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', 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()
|
|
|
@ -1,193 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: cs_region
|
|
||||||
short_description: Manages regions on Apache CloudStack based clouds.
|
|
||||||
description:
|
|
||||||
- Add, update and remove regions.
|
|
||||||
author: René Moser (@resmo)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: create a region
|
|
||||||
cs_region:
|
|
||||||
id: 2
|
|
||||||
name: geneva
|
|
||||||
endpoint: https://cloud.gva.example.com
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: remove a region with ID 2
|
|
||||||
cs_region:
|
|
||||||
id: 2
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,208 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Update a resource limit for instances of a domain
|
|
||||||
cs_resourcelimit:
|
|
||||||
type: instance
|
|
||||||
limit: 10
|
|
||||||
domain: customers
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Update a resource limit for instances of an account
|
|
||||||
cs_resourcelimit:
|
|
||||||
type: instance
|
|
||||||
limit: 12
|
|
||||||
account: moserre
|
|
||||||
domain: customers
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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=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()
|
|
|
@ -1,212 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: cs_role
|
|
||||||
short_description: Manages user roles on Apache CloudStack based clouds.
|
|
||||||
description:
|
|
||||||
- Create, update, delete user roles.
|
|
||||||
author: René Moser (@resmo)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Ensure an user role is present
|
|
||||||
cs_role:
|
|
||||||
name: myrole_user
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure a role having particular ID is named as myrole_user
|
|
||||||
cs_role:
|
|
||||||
name: myrole_user
|
|
||||||
id: 04589590-ac63-4ffc-93f5-b698b8ac38b6
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure a role is absent
|
|
||||||
cs_role:
|
|
||||||
name: myrole_user
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,352 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
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)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Create a role permission
|
|
||||||
cs_role_permission:
|
|
||||||
role: My_Custom_role
|
|
||||||
name: createVPC
|
|
||||||
permission: allow
|
|
||||||
description: My comments
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove a role permission
|
|
||||||
cs_role_permission:
|
|
||||||
state: absent
|
|
||||||
role: My_Custom_role
|
|
||||||
name: createVPC
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Update a system role permission
|
|
||||||
cs_role_permission:
|
|
||||||
role: Domain Admin
|
|
||||||
name: createVPC
|
|
||||||
permission: deny
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Update rules order. Move the rule at the top of list
|
|
||||||
cs_role_permission:
|
|
||||||
role: Domain Admin
|
|
||||||
name: createVPC
|
|
||||||
parent: 0
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,377 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
# Ensure the router has the desired service offering, no matter if
|
|
||||||
# the router is running or not.
|
|
||||||
- name: Present router
|
|
||||||
cs_router:
|
|
||||||
name: r-40-VM
|
|
||||||
service_offering: System Offering for Software Router
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure started
|
|
||||||
cs_router:
|
|
||||||
name: r-40-VM
|
|
||||||
state: started
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
# Ensure started with desired service offering.
|
|
||||||
# If the service offerings changes, router will be rebooted.
|
|
||||||
- name: Ensure started with desired service offering
|
|
||||||
cs_router:
|
|
||||||
name: r-40-VM
|
|
||||||
service_offering: System Offering for Software Router
|
|
||||||
state: started
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure stopped
|
|
||||||
cs_router:
|
|
||||||
name: r-40-VM
|
|
||||||
state: stopped
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove a router
|
|
||||||
cs_router:
|
|
||||||
name: r-40-VM
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,200 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: cs_securitygroup
|
|
||||||
short_description: Manages security groups on Apache CloudStack based clouds.
|
|
||||||
description:
|
|
||||||
- Create and remove security groups.
|
|
||||||
author: René Moser (@resmo)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: create a security group
|
|
||||||
cs_securitygroup:
|
|
||||||
name: default
|
|
||||||
description: default security group
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: remove a security group
|
|
||||||
cs_securitygroup:
|
|
||||||
name: default
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,389 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
---
|
|
||||||
- name: allow inbound port 80/tcp from 1.2.3.4 added to security group 'default'
|
|
||||||
cs_securitygroup_rule:
|
|
||||||
security_group: default
|
|
||||||
port: 80
|
|
||||||
cidr: 1.2.3.4/32
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: allow tcp/udp outbound added to security group 'default'
|
|
||||||
cs_securitygroup_rule:
|
|
||||||
security_group: default
|
|
||||||
type: egress
|
|
||||||
start_port: 1
|
|
||||||
end_port: 65535
|
|
||||||
protocol: '{{ item }}'
|
|
||||||
with_items:
|
|
||||||
- tcp
|
|
||||||
- udp
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: allow inbound icmp from 0.0.0.0/0 added to security group 'default'
|
|
||||||
cs_securitygroup_rule:
|
|
||||||
security_group: default
|
|
||||||
protocol: icmp
|
|
||||||
icmp_code: -1
|
|
||||||
icmp_type: -1
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: remove rule inbound port 80/tcp from 0.0.0.0/0 from security group 'default'
|
|
||||||
cs_securitygroup_rule:
|
|
||||||
security_group: default
|
|
||||||
port: 80
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: allow inbound port 80/tcp from security group web added to security group 'default'
|
|
||||||
cs_securitygroup_rule:
|
|
||||||
security_group: default
|
|
||||||
port: 80
|
|
||||||
user_security_group: web
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,582 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
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
|
|
||||||
default: no
|
|
||||||
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
|
|
||||||
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
|
|
||||||
aliases:
|
|
||||||
- storage_tag
|
|
||||||
is_customized:
|
|
||||||
description:
|
|
||||||
- Whether the offering is customizable or not.
|
|
||||||
type: bool
|
|
||||||
extends_documentation_fragment:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Create a non-volatile compute service offering with local storage
|
|
||||||
cs_service_offering:
|
|
||||||
name: Micro
|
|
||||||
display_text: Micro 512mb 1cpu
|
|
||||||
cpu_number: 1
|
|
||||||
cpu_speed: 2198
|
|
||||||
memory: 512
|
|
||||||
host_tags: eco
|
|
||||||
storage_type: local
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Create a volatile compute service offering with shared storage
|
|
||||||
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
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Create or update a volatile compute service offering with shared storage
|
|
||||||
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
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Create or update a custom compute service offering
|
|
||||||
cs_service_offering:
|
|
||||||
name: custom
|
|
||||||
display_text: custom compute offer
|
|
||||||
is_customized: yes
|
|
||||||
storage_type: shared
|
|
||||||
host_tags: eco
|
|
||||||
storage_tags: eco
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove a compute service offering
|
|
||||||
cs_service_offering:
|
|
||||||
name: Tiny
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Create or update a system offering for the console proxy
|
|
||||||
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
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove a system offering
|
|
||||||
cs_service_offering:
|
|
||||||
name: System Offering for Console Proxy 2GB
|
|
||||||
is_system: yes
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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', 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'),
|
|
||||||
storage_type=dict(choices=['local', 'shared']),
|
|
||||||
system_vm_type=dict(choices=['domainrouter', 'consoleproxy', 'secondarystoragevm']),
|
|
||||||
storage_tags=dict(type='list', 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()
|
|
|
@ -1,358 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: ensure a snapshot policy daily at 1h00 UTC
|
|
||||||
cs_snapshot_policy:
|
|
||||||
volume: ROOT-478
|
|
||||||
schedule: '00:1'
|
|
||||||
max_snaps: 3
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: ensure a snapshot policy daily at 1h00 UTC on the second DATADISK of VM web-01
|
|
||||||
cs_snapshot_policy:
|
|
||||||
vm: web-01
|
|
||||||
volume_type: DATADISK
|
|
||||||
device_id: 2
|
|
||||||
schedule: '00:1'
|
|
||||||
max_snaps: 3
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: ensure a snapshot policy hourly at minute 5 UTC
|
|
||||||
cs_snapshot_policy:
|
|
||||||
volume: ROOT-478
|
|
||||||
schedule: '5'
|
|
||||||
interval_type: hourly
|
|
||||||
max_snaps: 1
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: ensure a snapshot policy weekly on Sunday at 05h00, TZ Europe/Zurich
|
|
||||||
cs_snapshot_policy:
|
|
||||||
volume: ROOT-478
|
|
||||||
schedule: '00:5:1'
|
|
||||||
interval_type: weekly
|
|
||||||
max_snaps: 1
|
|
||||||
time_zone: 'Europe/Zurich'
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: ensure a snapshot policy is absent
|
|
||||||
cs_snapshot_policy:
|
|
||||||
volume: ROOT-478
|
|
||||||
interval_type: hourly
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,267 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: create a new private / public key pair
|
|
||||||
cs_sshkeypair:
|
|
||||||
name: linus@example.com
|
|
||||||
delegate_to: localhost
|
|
||||||
register: key
|
|
||||||
- debug:
|
|
||||||
msg: 'Private key is {{ key.private_key }}'
|
|
||||||
|
|
||||||
- name: remove a public key by its name
|
|
||||||
cs_sshkeypair:
|
|
||||||
name: linus@example.com
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: register your existing local public key
|
|
||||||
cs_sshkeypair:
|
|
||||||
name: linus@example.com
|
|
||||||
public_key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,255 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Create a static NAT for IP 1.2.3.4 to web01
|
|
||||||
cs_staticnat:
|
|
||||||
ip_address: 1.2.3.4
|
|
||||||
vm: web01
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove a static NAT
|
|
||||||
cs_staticnat:
|
|
||||||
ip_address: 1.2.3.4
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,510 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (c) 2017, Netservers Ltd. <support@netservers.co.uk>
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: ensure a zone scoped storage_pool is present
|
|
||||||
cs_storage_pool:
|
|
||||||
zone: zone01
|
|
||||||
storage_url: rbd://admin:SECRET@ceph-mons.domain/poolname
|
|
||||||
provider: DefaultPrimary
|
|
||||||
name: Ceph RBD
|
|
||||||
scope: zone
|
|
||||||
hypervisor: KVM
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: ensure a cluster scoped storage_pool is disabled
|
|
||||||
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
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: ensure a cluster scoped storage_pool is in maintenance
|
|
||||||
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
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: ensure a storage_pool is absent
|
|
||||||
cs_storage_pool:
|
|
||||||
name: Ceph RBD
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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_pod(self, key=None):
|
|
||||||
pod = self.module.params.get('pod')
|
|
||||||
if not pod:
|
|
||||||
return None
|
|
||||||
args = {
|
|
||||||
'name': pod,
|
|
||||||
'zoneid': self.get_zone(key='id'),
|
|
||||||
}
|
|
||||||
pods = self.query_api('listPods', **args)
|
|
||||||
if pods:
|
|
||||||
return self._get_by_key(key, pods['pod'][0])
|
|
||||||
|
|
||||||
self.fail_json(msg="Pod %s not found" % self.module.params.get('pod'))
|
|
||||||
|
|
||||||
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', 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()
|
|
|
@ -1,744 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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 (since version 2.7), extract and delete templates.
|
|
||||||
author: René Moser (@resmo)
|
|
||||||
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_ready:
|
|
||||||
description:
|
|
||||||
- "Note: this flag was not implemented and therefore marked as deprecated."
|
|
||||||
- Deprecated, will be removed in version 2.11.
|
|
||||||
type: bool
|
|
||||||
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
|
|
||||||
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
|
|
||||||
aliases: [ tag ]
|
|
||||||
extends_documentation_fragment:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: register a systemvm template
|
|
||||||
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)
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Create a template from a stopped virtual machine's volume
|
|
||||||
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
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
# Note: Use template_find_option(s) when a template name is not unique
|
|
||||||
- name: Create a template from a stopped virtual machine's volume
|
|
||||||
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
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: create a template from a virtual machine's root volume snapshot
|
|
||||||
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
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove a template
|
|
||||||
cs_template:
|
|
||||||
name: systemvm-4.2
|
|
||||||
cross_zones: yes
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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")
|
|
||||||
|
|
||||||
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_ready=dict(type='bool', removed_in_version='2.11'),
|
|
||||||
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', choices=['display_text', 'checksum', 'cross_zones'], aliases=['template_find_option'], default=[]),
|
|
||||||
hypervisor=dict(),
|
|
||||||
requires_hvm=dict(type='bool'),
|
|
||||||
password_enabled=dict(type='bool'),
|
|
||||||
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', 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()
|
|
|
@ -1,327 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
author:
|
|
||||||
- Patryk Cichy (@PatTheSilent)
|
|
||||||
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
|
|
||||||
cs_traffic_type:
|
|
||||||
physical_network: public-network
|
|
||||||
traffic_type: Guest
|
|
||||||
zone: test-zone
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: update traffic type
|
|
||||||
cs_traffic_type:
|
|
||||||
physical_network: public-network
|
|
||||||
traffic_type: Guest
|
|
||||||
kvm_networklabel: cloudbr0
|
|
||||||
zone: test-zone
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: remove traffic type
|
|
||||||
cs_traffic_type:
|
|
||||||
physical_network: public-network
|
|
||||||
traffic_type: Public
|
|
||||||
state: absent
|
|
||||||
zone: test-zone
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,445 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Create an user in domain 'CUSTOMERS'
|
|
||||||
cs_user:
|
|
||||||
account: developers
|
|
||||||
username: johndoe
|
|
||||||
password: S3Cur3
|
|
||||||
last_name: Doe
|
|
||||||
first_name: John
|
|
||||||
email: john.doe@example.com
|
|
||||||
domain: CUSTOMERS
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Lock an existing user in domain 'CUSTOMERS'
|
|
||||||
cs_user:
|
|
||||||
username: johndoe
|
|
||||||
domain: CUSTOMERS
|
|
||||||
state: locked
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Disable an existing user in domain 'CUSTOMERS'
|
|
||||||
cs_user:
|
|
||||||
username: johndoe
|
|
||||||
domain: CUSTOMERS
|
|
||||||
state: disabled
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Enable/unlock an existing user in domain 'CUSTOMERS'
|
|
||||||
cs_user:
|
|
||||||
username: johndoe
|
|
||||||
domain: CUSTOMERS
|
|
||||||
state: enabled
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove an user in domain 'CUSTOMERS'
|
|
||||||
cs_user:
|
|
||||||
name: customer_xy
|
|
||||||
domain: CUSTOMERS
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,388 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
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
|
|
||||||
extends_documentation_fragment:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: create a VLAN IP range for network test
|
|
||||||
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
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: remove a VLAN IP range for network test
|
|
||||||
cs_vlan_ip_range:
|
|
||||||
state: absent
|
|
||||||
network: test
|
|
||||||
start_ip: 10.2.4.10
|
|
||||||
end_ip: 10.2.4.100
|
|
||||||
zone: zone-02
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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'),
|
|
||||||
}
|
|
||||||
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'),
|
|
||||||
for_virtual_network=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()
|
|
|
@ -1,284 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
aliases: [ tag ]
|
|
||||||
extends_documentation_fragment:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Create a VM snapshot of disk and memory before an upgrade
|
|
||||||
cs_vmsnapshot:
|
|
||||||
name: Snapshot before upgrade
|
|
||||||
vm: web-01
|
|
||||||
snapshot_memory: yes
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Revert a VM to a snapshot after a failed upgrade
|
|
||||||
cs_vmsnapshot:
|
|
||||||
name: Snapshot before upgrade
|
|
||||||
vm: web-01
|
|
||||||
state: revert
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove a VM snapshot after successful upgrade
|
|
||||||
cs_vmsnapshot:
|
|
||||||
name: Snapshot before upgrade
|
|
||||||
vm: web-01
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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', 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()
|
|
|
@ -1,572 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (c) 2015, Jefferson Girão <jefferson@girao.net>
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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.
|
|
||||||
- The choices C(extracted) and C(uploaded) were added in version 2.8.
|
|
||||||
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
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: create volume within project and zone with specified storage options
|
|
||||||
cs_volume:
|
|
||||||
name: web-vm-1-volume
|
|
||||||
project: Integration
|
|
||||||
zone: ch-zrh-ix-01
|
|
||||||
disk_offering: PerfPlus Storage
|
|
||||||
size: 20
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: create/attach volume to instance
|
|
||||||
cs_volume:
|
|
||||||
name: web-vm-1-volume
|
|
||||||
disk_offering: PerfPlus Storage
|
|
||||||
size: 20
|
|
||||||
vm: web-vm-1
|
|
||||||
state: attached
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: detach volume
|
|
||||||
cs_volume:
|
|
||||||
name: web-vm-1-volume
|
|
||||||
state: detached
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: remove volume
|
|
||||||
cs_volume:
|
|
||||||
name: web-vm-1-volume
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
# New in version 2.8
|
|
||||||
- name: Extract DATA volume to make it downloadable
|
|
||||||
cs_volume:
|
|
||||||
state: extracted
|
|
||||||
name: web-vm-1-volume
|
|
||||||
register: data_vol_out
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Create new volume by downloading source volume
|
|
||||||
cs_volume:
|
|
||||||
state: uploaded
|
|
||||||
name: web-vm-1-volume-2
|
|
||||||
format: VHD
|
|
||||||
url: "{{ data_vol_out.url }}"
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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', 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()
|
|
|
@ -1,398 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: cs_vpc
|
|
||||||
short_description: "Manages VPCs on Apache CloudStack based clouds."
|
|
||||||
description:
|
|
||||||
- Create, update and delete VPCs.
|
|
||||||
author: René Moser (@resmo)
|
|
||||||
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
|
|
||||||
aliases: [ tag ]
|
|
||||||
poll_async:
|
|
||||||
description:
|
|
||||||
- Poll async jobs until job has finished.
|
|
||||||
default: yes
|
|
||||||
type: bool
|
|
||||||
extends_documentation_fragment:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Ensure a VPC is present but not started after creating
|
|
||||||
cs_vpc:
|
|
||||||
name: my_vpc
|
|
||||||
display_text: My example VPC
|
|
||||||
cidr: 10.10.0.0/16
|
|
||||||
state: stopped
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure a VPC is present and started after creating
|
|
||||||
cs_vpc:
|
|
||||||
name: my_vpc
|
|
||||||
display_text: My example VPC
|
|
||||||
cidr: 10.10.0.0/16
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure a VPC is absent
|
|
||||||
cs_vpc:
|
|
||||||
name: my_vpc
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure a VPC is restarted with clean up
|
|
||||||
cs_vpc:
|
|
||||||
name: my_vpc
|
|
||||||
clean_up: yes
|
|
||||||
state: restarted
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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', 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()
|
|
|
@ -1,323 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
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
|
|
||||||
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
|
|
||||||
aliases: [ service_provider ]
|
|
||||||
poll_async:
|
|
||||||
description:
|
|
||||||
- Poll async jobs until job has finished.
|
|
||||||
default: yes
|
|
||||||
type: bool
|
|
||||||
extends_documentation_fragment:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Create a vpc offering and enable it
|
|
||||||
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'}
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Create a vpc offering with redundant router
|
|
||||||
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}
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Create a region level vpc offering with distributed router
|
|
||||||
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}
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove a vpc offering
|
|
||||||
cs_vpc_offering:
|
|
||||||
name: my_vpc_offering
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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', aliases=['service_capability']),
|
|
||||||
service_offering=dict(),
|
|
||||||
supported_services=dict(type='list', aliases=['supported_service']),
|
|
||||||
service_providers=dict(type='list', 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()
|
|
|
@ -1,355 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
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)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = r'''
|
|
||||||
- name: Create a VPN connection with activated VPN gateway
|
|
||||||
cs_vpn_connection:
|
|
||||||
vpn_customer_gateway: my vpn connection
|
|
||||||
vpc: my vpc
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Create a VPN connection and force VPN gateway activation
|
|
||||||
cs_vpn_connection:
|
|
||||||
vpn_customer_gateway: my vpn connection
|
|
||||||
vpc: my vpc
|
|
||||||
force: yes
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove a vpn connection
|
|
||||||
cs_vpn_connection:
|
|
||||||
vpn_customer_gateway: my vpn connection
|
|
||||||
vpc: my vpc
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,348 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = r'''
|
|
||||||
- name: Create a vpn customer gateway
|
|
||||||
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"
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Remove a vpn customer gateway
|
|
||||||
cs_vpn_customer_gateway:
|
|
||||||
name: my vpn customer gateway
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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', 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()
|
|
|
@ -1,210 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['preview'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Ensure a vpn gateway is present
|
|
||||||
cs_vpn_gateway:
|
|
||||||
vpc: my VPC
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure a vpn gateway is absent
|
|
||||||
cs_vpn_gateway:
|
|
||||||
vpc: my VPC
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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 ansible_collections.community.general.plugins.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()
|
|
|
@ -1,385 +0,0 @@
|
||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# (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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: cs_zone
|
|
||||||
short_description: Manages zones on Apache CloudStack based clouds.
|
|
||||||
description:
|
|
||||||
- Create, update and remove zones.
|
|
||||||
author: René Moser (@resmo)
|
|
||||||
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:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Ensure a zone is present
|
|
||||||
cs_zone:
|
|
||||||
name: ch-zrh-ix-01
|
|
||||||
dns1: 8.8.8.8
|
|
||||||
dns2: 8.8.4.4
|
|
||||||
network_type: basic
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure a zone is disabled
|
|
||||||
cs_zone:
|
|
||||||
name: ch-zrh-ix-01
|
|
||||||
state: disabled
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure a zone is enabled
|
|
||||||
cs_zone:
|
|
||||||
name: ch-zrh-ix-01
|
|
||||||
state: enabled
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Ensure a zone is absent
|
|
||||||
cs_zone:
|
|
||||||
name: ch-zrh-ix-01
|
|
||||||
state: absent
|
|
||||||
delegate_to: localhost
|
|
||||||
'''
|
|
||||||
|
|
||||||
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: dict
|
|
||||||
sample: [ { "key": "foo", "value": "bar" } ]
|
|
||||||
'''
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible_collections.community.general.plugins.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()
|
|
|
@ -1,201 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['deprecated'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
DOCUMENTATION = '''
|
|
||||||
---
|
|
||||||
module: cs_zone_facts
|
|
||||||
short_description: Gathering facts of zones from Apache CloudStack based clouds.
|
|
||||||
description:
|
|
||||||
- Gathering facts from the API of a zone.
|
|
||||||
- Sets Ansible facts accessible by the key C(cloudstack_zone) and since version 2.6 also returns results.
|
|
||||||
deprecated:
|
|
||||||
removed_in: "2.13"
|
|
||||||
why: Transformed into an info module.
|
|
||||||
alternative: Use M(cs_zone_info) instead.
|
|
||||||
author: René Moser (@resmo)
|
|
||||||
options:
|
|
||||||
zone:
|
|
||||||
description:
|
|
||||||
- Name of the zone.
|
|
||||||
type: str
|
|
||||||
required: true
|
|
||||||
aliases: [ name ]
|
|
||||||
extends_documentation_fragment:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Gather facts from a zone
|
|
||||||
cs_zone_facts:
|
|
||||||
name: ch-gva-1
|
|
||||||
register: zone
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Show the returned results of the registered variable
|
|
||||||
debug:
|
|
||||||
var: zone
|
|
||||||
|
|
||||||
- name: Show the facts by the ansible_facts key cloudstack_zone
|
|
||||||
debug:
|
|
||||||
var: cloudstack_zone
|
|
||||||
'''
|
|
||||||
|
|
||||||
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: dict
|
|
||||||
sample: [ { "key": "foo", "value": "bar" } ]
|
|
||||||
'''
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible_collections.community.general.plugins.module_utils.cloudstack import (
|
|
||||||
AnsibleCloudStack,
|
|
||||||
cs_argument_spec,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AnsibleCloudStackZoneFacts(AnsibleCloudStack):
|
|
||||||
|
|
||||||
def __init__(self, module):
|
|
||||||
super(AnsibleCloudStackZoneFacts, 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):
|
|
||||||
return super(AnsibleCloudStackZoneFacts, self).get_zone()
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
argument_spec = cs_argument_spec()
|
|
||||||
argument_spec.update(dict(
|
|
||||||
zone=dict(required=True, aliases=['name']),
|
|
||||||
))
|
|
||||||
|
|
||||||
module = AnsibleModule(
|
|
||||||
argument_spec=argument_spec,
|
|
||||||
supports_check_mode=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
acs_zone_facts = AnsibleCloudStackZoneFacts(module=module)
|
|
||||||
result = acs_zone_facts.get_result_and_facts(
|
|
||||||
facts_name='cloudstack_zone',
|
|
||||||
resource=acs_zone_facts.get_zone()
|
|
||||||
)
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
|
@ -1,213 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
||||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
||||||
'status': ['stableinterface'],
|
|
||||||
'supported_by': 'community'}
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
options:
|
|
||||||
zone:
|
|
||||||
description:
|
|
||||||
- Name of the zone.
|
|
||||||
- If not specified, all zones are returned
|
|
||||||
type: str
|
|
||||||
aliases: [ name ]
|
|
||||||
extends_documentation_fragment:
|
|
||||||
- community.general.cloudstack
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Gather information from a zone
|
|
||||||
cs_zone_info:
|
|
||||||
zone: ch-gva-1
|
|
||||||
register: zone
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- name: Show the returned results of the registered variable
|
|
||||||
debug:
|
|
||||||
msg: "{{ zone }}"
|
|
||||||
|
|
||||||
- name: Gather information from all zones
|
|
||||||
cs_zone_info:
|
|
||||||
register: zones
|
|
||||||
delegate_to: localhost
|
|
||||||
|
|
||||||
- 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: dict
|
|
||||||
sample: [ { "key": "foo", "value": "bar" } ]
|
|
||||||
'''
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible_collections.community.general.plugins.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()
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_account.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_affinitygroup.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_cluster.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_configuration.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_disk_offering.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_domain.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_facts.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_firewall.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_host.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_image_store.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_instance.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_instance_facts.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_instance_info.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_instance_nic.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_instance_nic_secondaryip.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_instance_password_reset.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_instancegroup.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_ip_address.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_iso.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_loadbalancer_rule.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_loadbalancer_rule_member.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_network.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_network_acl.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_network_acl_rule.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_network_offering.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_physical_network.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_pod.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_portforward.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_project.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_region.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_resourcelimit.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_role.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_role_permission.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_router.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_securitygroup.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_securitygroup_rule.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_service_offering.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_snapshot_policy.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_sshkeypair.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_staticnat.py
|
|
|
@ -1 +0,0 @@
|
||||||
./cloud/cloudstack/cs_storage_pool.py
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue