mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-05-06 13:22:48 +00:00
Relocating extras into lib/ansible/modules/ after merge
This commit is contained in:
committed by
Matt Clay
parent
c65ba07d2c
commit
011ea55a8f
0
lib/ansible/modules/cloud/cloudstack/__init__.py
Normal file
0
lib/ansible/modules/cloud/cloudstack/__init__.py
Normal file
385
lib/ansible/modules/cloud/cloudstack/cs_account.py
Normal file
385
lib/ansible/modules/cloud/cloudstack/cs_account.py
Normal file
@@ -0,0 +1,385 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_account
|
||||
short_description: Manages accounts on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Create, disable, lock, enable and remove accounts.
|
||||
version_added: '2.0'
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of account.
|
||||
required: true
|
||||
username:
|
||||
description:
|
||||
- Username of the user to be created if account did not exist.
|
||||
- Required on C(state=present).
|
||||
required: false
|
||||
default: null
|
||||
password:
|
||||
description:
|
||||
- Password of the user to be created if account did not exist.
|
||||
- Required on C(state=present).
|
||||
required: false
|
||||
default: null
|
||||
first_name:
|
||||
description:
|
||||
- First name of the user to be created if account did not exist.
|
||||
- Required on C(state=present).
|
||||
required: false
|
||||
default: null
|
||||
last_name:
|
||||
description:
|
||||
- Last name of the user to be created if account did not exist.
|
||||
- Required on C(state=present).
|
||||
required: false
|
||||
default: null
|
||||
email:
|
||||
description:
|
||||
- Email of the user to be created if account did not exist.
|
||||
- Required on C(state=present).
|
||||
required: false
|
||||
default: null
|
||||
timezone:
|
||||
description:
|
||||
- Timezone of the user to be created if account did not exist.
|
||||
required: false
|
||||
default: null
|
||||
network_domain:
|
||||
description:
|
||||
- Network domain of the account.
|
||||
required: false
|
||||
default: null
|
||||
account_type:
|
||||
description:
|
||||
- Type of the account.
|
||||
required: false
|
||||
default: 'user'
|
||||
choices: [ 'user', 'root_admin', 'domain_admin' ]
|
||||
domain:
|
||||
description:
|
||||
- Domain the account is related to.
|
||||
required: false
|
||||
default: 'ROOT'
|
||||
state:
|
||||
description:
|
||||
- State of the account.
|
||||
- C(unlocked) is an alias for C(enabled).
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent', 'enabled', 'disabled', 'locked', 'unlocked' ]
|
||||
poll_async:
|
||||
description:
|
||||
- Poll async jobs until job has finished.
|
||||
required: false
|
||||
default: true
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# create an account in domain 'CUSTOMERS'
|
||||
local_action:
|
||||
module: cs_account
|
||||
name: customer_xy
|
||||
username: customer_xy
|
||||
password: S3Cur3
|
||||
last_name: Doe
|
||||
first_name: John
|
||||
email: john.doe@example.com
|
||||
domain: CUSTOMERS
|
||||
|
||||
# Lock an existing account in domain 'CUSTOMERS'
|
||||
local_action:
|
||||
module: cs_account
|
||||
name: customer_xy
|
||||
domain: CUSTOMERS
|
||||
state: locked
|
||||
|
||||
# Disable an existing account in domain 'CUSTOMERS'
|
||||
local_action:
|
||||
module: cs_account
|
||||
name: customer_xy
|
||||
domain: CUSTOMERS
|
||||
state: disabled
|
||||
|
||||
# Enable an existing account in domain 'CUSTOMERS'
|
||||
local_action:
|
||||
module: cs_account
|
||||
name: customer_xy
|
||||
domain: CUSTOMERS
|
||||
state: enabled
|
||||
|
||||
# Remove an account in domain 'CUSTOMERS'
|
||||
local_action:
|
||||
module: cs_account
|
||||
name: customer_xy
|
||||
domain: CUSTOMERS
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the account.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 87b1e0ce-4e01-11e4-bb66-0050569e64b8
|
||||
name:
|
||||
description: Name of the account.
|
||||
returned: success
|
||||
type: string
|
||||
sample: linus@example.com
|
||||
account_type:
|
||||
description: Type of the account.
|
||||
returned: success
|
||||
type: string
|
||||
sample: user
|
||||
state:
|
||||
description: State of the account.
|
||||
returned: success
|
||||
type: string
|
||||
sample: enabled
|
||||
network_domain:
|
||||
description: Network domain of the account.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example.local
|
||||
domain:
|
||||
description: Domain the account is related.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ROOT
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
class AnsibleCloudStackAccount(AnsibleCloudStack):
|
||||
|
||||
def __init__(self, module):
|
||||
super(AnsibleCloudStackAccount, self).__init__(module)
|
||||
self.returns = {
|
||||
'networkdomain': 'network_domain',
|
||||
}
|
||||
self.account = None
|
||||
self.account_types = {
|
||||
'user': 0,
|
||||
'root_admin': 1,
|
||||
'domain_admin': 2,
|
||||
}
|
||||
|
||||
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'),
|
||||
}
|
||||
accounts = self.cs.listAccounts(**args)
|
||||
if accounts:
|
||||
account_name = self.module.params.get('name')
|
||||
for a in accounts['account']:
|
||||
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.cs.enableAccount(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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.cs.disableAccount(**args)
|
||||
|
||||
if 'errortext' in account:
|
||||
self.module.fail_json(msg="Failed: '%s'" % account['errortext'])
|
||||
|
||||
poll_async = self.module.params.get('poll_async')
|
||||
if poll_async:
|
||||
account = self.poll_job(account, 'account')
|
||||
return account
|
||||
|
||||
def present_account(self):
|
||||
required_params = [
|
||||
'email',
|
||||
'username',
|
||||
'password',
|
||||
'first_name',
|
||||
'last_name',
|
||||
]
|
||||
self.module.fail_on_missing_params(required_params=required_params)
|
||||
|
||||
account = self.get_account()
|
||||
|
||||
if not account:
|
||||
self.result['changed'] = True
|
||||
|
||||
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')
|
||||
}
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.createAccount(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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.cs.deleteAccount(id=account['id'])
|
||||
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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(default=None),
|
||||
domain=dict(default='ROOT'),
|
||||
email=dict(default=None),
|
||||
first_name=dict(default=None),
|
||||
last_name=dict(default=None),
|
||||
username=dict(default=None),
|
||||
password=dict(default=None, no_log=True),
|
||||
timezone=dict(default=None),
|
||||
poll_async=dict(type='bool', default=True),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
255
lib/ansible/modules/cloud/cloudstack/cs_affinitygroup.py
Normal file
255
lib/ansible/modules/cloud/cloudstack/cs_affinitygroup.py
Normal file
@@ -0,0 +1,255 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_affinitygroup
|
||||
short_description: Manages affinity groups on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Create and remove affinity groups.
|
||||
version_added: '2.0'
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the affinity group.
|
||||
required: true
|
||||
affinty_type:
|
||||
description:
|
||||
- Type of the affinity group. If not specified, first found affinity type is used.
|
||||
required: false
|
||||
default: null
|
||||
description:
|
||||
description:
|
||||
- Description of the affinity group.
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- State of the affinity group.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent' ]
|
||||
domain:
|
||||
description:
|
||||
- Domain the affinity group is related to.
|
||||
required: false
|
||||
default: null
|
||||
account:
|
||||
description:
|
||||
- Account the affinity group is related to.
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- Name of the project the affinity group is related to.
|
||||
required: false
|
||||
default: null
|
||||
poll_async:
|
||||
description:
|
||||
- Poll async jobs until job has finished.
|
||||
required: false
|
||||
default: true
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a affinity group
|
||||
- local_action:
|
||||
module: cs_affinitygroup
|
||||
name: haproxy
|
||||
affinty_type: host anti-affinity
|
||||
|
||||
# Remove a affinity group
|
||||
- local_action:
|
||||
module: cs_affinitygroup
|
||||
name: haproxy
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the affinity group.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 87b1e0ce-4e01-11e4-bb66-0050569e64b8
|
||||
name:
|
||||
description: Name of affinity group.
|
||||
returned: success
|
||||
type: string
|
||||
sample: app
|
||||
description:
|
||||
description: Description of affinity group.
|
||||
returned: success
|
||||
type: string
|
||||
sample: application affinity group
|
||||
affinity_type:
|
||||
description: Type of affinity group.
|
||||
returned: success
|
||||
type: string
|
||||
sample: host anti-affinity
|
||||
project:
|
||||
description: Name of project the affinity group is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Production
|
||||
domain:
|
||||
description: Domain the affinity group is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example domain
|
||||
account:
|
||||
description: Account the affinity group is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example account
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
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.cs.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('affinty_type')
|
||||
|
||||
affinity_types = self.cs.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 '%s' not found" % 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.cs.createAffinityGroup(**args)
|
||||
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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.cs.deleteAffinityGroup(**args)
|
||||
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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),
|
||||
affinty_type=dict(default=None),
|
||||
description=dict(default=None),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
domain=dict(default=None),
|
||||
account=dict(default=None),
|
||||
project=dict(default=None),
|
||||
poll_async=dict(type='bool', default=True),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
421
lib/ansible/modules/cloud/cloudstack/cs_cluster.py
Normal file
421
lib/ansible/modules/cloud/cloudstack/cs_cluster.py
Normal file
@@ -0,0 +1,421 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_cluster
|
||||
short_description: Manages host clusters on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Create, update and remove clusters.
|
||||
version_added: "2.1"
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- name of the cluster.
|
||||
required: true
|
||||
zone:
|
||||
description:
|
||||
- Name of the zone in which the cluster belongs to.
|
||||
- If not set, default zone is used.
|
||||
required: false
|
||||
default: null
|
||||
pod:
|
||||
description:
|
||||
- Name of the pod in which the cluster belongs to.
|
||||
required: false
|
||||
default: null
|
||||
cluster_type:
|
||||
description:
|
||||
- Type of the cluster.
|
||||
- Required if C(state=present)
|
||||
required: false
|
||||
default: null
|
||||
choices: [ 'CloudManaged', 'ExternalManaged' ]
|
||||
hypervisor:
|
||||
description:
|
||||
- Name the hypervisor to be used.
|
||||
- Required if C(state=present).
|
||||
required: false
|
||||
default: none
|
||||
choices: [ 'KVM', 'VMware', 'BareMetal', 'XenServer', 'LXC', 'HyperV', 'UCS', 'OVM' ]
|
||||
url:
|
||||
description:
|
||||
- URL for the cluster
|
||||
required: false
|
||||
default: null
|
||||
username:
|
||||
description:
|
||||
- Username for the cluster.
|
||||
required: false
|
||||
default: null
|
||||
password:
|
||||
description:
|
||||
- Password for the cluster.
|
||||
required: false
|
||||
default: null
|
||||
guest_vswitch_name:
|
||||
description:
|
||||
- Name of virtual switch used for guest traffic in the cluster.
|
||||
- This would override zone wide traffic label setting.
|
||||
required: false
|
||||
default: null
|
||||
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)
|
||||
required: false
|
||||
default: null
|
||||
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.
|
||||
required: false
|
||||
default: null
|
||||
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)
|
||||
required: false
|
||||
default: null
|
||||
choices: [ 'vmwaresvs', 'vmwaredvs' ]
|
||||
vms_ip_address:
|
||||
description:
|
||||
- IP address of the VSM associated with this cluster.
|
||||
required: false
|
||||
default: null
|
||||
vms_username:
|
||||
description:
|
||||
- Username for the VSM associated with this cluster.
|
||||
required: false
|
||||
default: null
|
||||
vms_password:
|
||||
description:
|
||||
- Password for the VSM associated with this cluster.
|
||||
required: false
|
||||
default: null
|
||||
ovm3_cluster:
|
||||
description:
|
||||
- Ovm3 native OCFS2 clustering enabled for cluster.
|
||||
required: false
|
||||
default: null
|
||||
ovm3_pool:
|
||||
description:
|
||||
- Ovm3 native pooling enabled for cluster.
|
||||
required: false
|
||||
default: null
|
||||
ovm3_vip:
|
||||
description:
|
||||
- Ovm3 vip to use for pool (and cluster).
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- State of the cluster.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent', 'disabled', 'enabled' ]
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Ensure a cluster is present
|
||||
- local_action:
|
||||
module: cs_cluster
|
||||
name: kvm-cluster-01
|
||||
zone: ch-zrh-ix-01
|
||||
hypervisor: KVM
|
||||
cluster_type: CloudManaged
|
||||
|
||||
# Ensure a cluster is disabled
|
||||
- local_action:
|
||||
module: cs_cluster
|
||||
name: kvm-cluster-01
|
||||
zone: ch-zrh-ix-01
|
||||
state: disabled
|
||||
|
||||
# Ensure a cluster is enabled
|
||||
- local_action:
|
||||
module: cs_cluster
|
||||
name: kvm-cluster-01
|
||||
zone: ch-zrh-ix-01
|
||||
state: enabled
|
||||
|
||||
# Ensure a cluster is absent
|
||||
- local_action:
|
||||
module: cs_cluster
|
||||
name: kvm-cluster-01
|
||||
zone: ch-zrh-ix-01
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the cluster.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6
|
||||
name:
|
||||
description: Name of the cluster.
|
||||
returned: success
|
||||
type: string
|
||||
sample: cluster01
|
||||
allocation_state:
|
||||
description: State of the cluster.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Enabled
|
||||
cluster_type:
|
||||
description: Type of the cluster.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ExternalManaged
|
||||
cpu_overcommit_ratio:
|
||||
description: The CPU overcommit ratio of the cluster.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 1.0
|
||||
memory_overcommit_ratio:
|
||||
description: The memory overcommit ratio of the cluster.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 1.0
|
||||
managed_state:
|
||||
description: Whether this cluster is managed by CloudStack.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Managed
|
||||
ovm3_vip:
|
||||
description: Ovm3 VIP to use for pooling and/or clustering
|
||||
returned: success
|
||||
type: string
|
||||
sample: 10.10.10.101
|
||||
hypervisor:
|
||||
description: Hypervisor of the cluster
|
||||
returned: success
|
||||
type: string
|
||||
sample: VMware
|
||||
zone:
|
||||
description: Name of zone the cluster is in.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ch-gva-2
|
||||
pod:
|
||||
description: Name of pod the cluster is in.
|
||||
returned: success
|
||||
type: string
|
||||
sample: pod01
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
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.cs.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.cs.listClusters(**args)
|
||||
if clusters:
|
||||
self.cluster = clusters['cluster'][0]
|
||||
return self.cluster
|
||||
|
||||
args['name'] = self.module.params.get('name')
|
||||
clusters = self.cs.listClusters(**args)
|
||||
if clusters:
|
||||
self.cluster = clusters['cluster'][0]
|
||||
# fix differnt 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.cs.addCluster(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
# 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.cs.updateCluster(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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:
|
||||
res = self.cs.deleteCluster(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
return cluster
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = cs_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
name=dict(required=True),
|
||||
zone=dict(default=None),
|
||||
pod=dict(default=None),
|
||||
cluster_type=dict(choices=['CloudManaged', 'ExternalManaged'], default=None),
|
||||
hypervisor=dict(choices=CS_HYPERVISORS, default=None),
|
||||
state=dict(choices=['present', 'enabled', 'disabled', 'absent'], default='present'),
|
||||
url=dict(default=None),
|
||||
username=dict(default=None),
|
||||
password=dict(default=None, no_log=True),
|
||||
guest_vswitch_name=dict(default=None),
|
||||
guest_vswitch_type=dict(choices=['vmwaresvs', 'vmwaredvs'], default=None),
|
||||
public_vswitch_name=dict(default=None),
|
||||
public_vswitch_type=dict(choices=['vmwaresvs', 'vmwaredvs'], default=None),
|
||||
vms_ip_address=dict(default=None),
|
||||
vms_username=dict(default=None),
|
||||
vms_password=dict(default=None, no_log=True),
|
||||
ovm3_cluster=dict(default=None),
|
||||
ovm3_pool=dict(default=None),
|
||||
ovm3_vip=dict(default=None),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
292
lib/ansible/modules/cloud/cloudstack/cs_configuration.py
Normal file
292
lib/ansible/modules/cloud/cloudstack/cs_configuration.py
Normal file
@@ -0,0 +1,292 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_configuration
|
||||
short_description: Manages configuration on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Manages global, zone, account, storage and cluster configurations.
|
||||
version_added: "2.1"
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the configuration.
|
||||
required: true
|
||||
value:
|
||||
description:
|
||||
- Value of the configuration.
|
||||
required: true
|
||||
account:
|
||||
description:
|
||||
- Ensure the value for corresponding account.
|
||||
required: false
|
||||
default: null
|
||||
domain:
|
||||
description:
|
||||
- Domain the account is related to.
|
||||
- Only considered if C(account) is used.
|
||||
required: false
|
||||
default: ROOT
|
||||
zone:
|
||||
description:
|
||||
- Ensure the value for corresponding zone.
|
||||
required: false
|
||||
default: null
|
||||
storage:
|
||||
description:
|
||||
- Ensure the value for corresponding storage pool.
|
||||
required: false
|
||||
default: null
|
||||
cluster:
|
||||
description:
|
||||
- Ensure the value for corresponding cluster.
|
||||
required: false
|
||||
default: null
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Ensure global configuration
|
||||
- local_action:
|
||||
module: cs_configuration
|
||||
name: router.reboot.when.outofband.migrated
|
||||
value: false
|
||||
|
||||
# Ensure zone configuration
|
||||
- local_action:
|
||||
module: cs_configuration
|
||||
name: router.reboot.when.outofband.migrated
|
||||
zone: ch-gva-01
|
||||
value: true
|
||||
|
||||
# Ensure storage configuration
|
||||
- local_action:
|
||||
module: cs_configuration
|
||||
name: storage.overprovisioning.factor
|
||||
storage: storage01
|
||||
value: 2.0
|
||||
|
||||
# Ensure account configuration
|
||||
- local_action:
|
||||
module: cs_configuration:
|
||||
name: allow.public.user.templates
|
||||
value: false
|
||||
account: acme inc
|
||||
domain: customers
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
category:
|
||||
description: Category of the configuration.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Advanced
|
||||
scope:
|
||||
description: Scope (zone/cluster/storagepool/account) of the parameter that needs to be updated.
|
||||
returned: success
|
||||
type: string
|
||||
sample: storagepool
|
||||
description:
|
||||
description: Description of the configuration.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Setup the host to do multipath
|
||||
name:
|
||||
description: Name of the configuration.
|
||||
returned: success
|
||||
type: string
|
||||
sample: zone.vlan.capacity.notificationthreshold
|
||||
value:
|
||||
description: Value of the configuration.
|
||||
returned: success
|
||||
type: string
|
||||
sample: "0.75"
|
||||
account:
|
||||
description: Account of the configuration.
|
||||
returned: success
|
||||
type: string
|
||||
sample: admin
|
||||
Domain:
|
||||
description: Domain of account of the configuration.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ROOT
|
||||
zone:
|
||||
description: Zone of the configuration.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ch-gva-01
|
||||
cluster:
|
||||
description: Cluster of the configuration.
|
||||
returned: success
|
||||
type: string
|
||||
sample: cluster01
|
||||
storage:
|
||||
description: Storage of the configuration.
|
||||
returned: success
|
||||
type: string
|
||||
sample: storage01
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
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 = {}
|
||||
args['name'] = self.module.params.get('name')
|
||||
args['accountid'] = self.get_account(key='id')
|
||||
args['storageid'] = self.get_storage(key='id')
|
||||
args['zoneid'] = self.get_zone(key='id')
|
||||
args['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 = {}
|
||||
args['name'] = cluster_name
|
||||
clusters = self.cs.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 = {}
|
||||
args['name'] = storage_pool_name
|
||||
storage_pools = self.cs.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()
|
||||
configurations = self.cs.listConfigurations(**args)
|
||||
if not configurations:
|
||||
self.module.fail_json(msg="Configuration %s not found." % args['name'])
|
||||
configuration = configurations['configuration'][0]
|
||||
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.cs.updateConfiguration(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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(default=None),
|
||||
storage = dict(default=None),
|
||||
cluster = dict(default=None),
|
||||
account = dict(default=None),
|
||||
domain = dict(default='ROOT')
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
acs_configuration = AnsibleCloudStackConfiguration(module)
|
||||
configuration = acs_configuration.present_configuration()
|
||||
result = acs_configuration.get_result(configuration)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
274
lib/ansible/modules/cloud/cloudstack/cs_domain.py
Normal file
274
lib/ansible/modules/cloud/cloudstack/cs_domain.py
Normal file
@@ -0,0 +1,274 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_domain
|
||||
short_description: Manages domains on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Create, update and remove domains.
|
||||
version_added: '2.0'
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
path:
|
||||
description:
|
||||
- Path of the domain.
|
||||
- Prefix C(ROOT/) or C(/ROOT/) in path is optional.
|
||||
required: true
|
||||
network_domain:
|
||||
description:
|
||||
- Network domain for networks in the domain.
|
||||
required: false
|
||||
default: null
|
||||
clean_up:
|
||||
description:
|
||||
- Clean up all domain resources like child domains and accounts.
|
||||
- Considered on C(state=absent).
|
||||
required: false
|
||||
default: false
|
||||
state:
|
||||
description:
|
||||
- State of the domain.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent' ]
|
||||
poll_async:
|
||||
description:
|
||||
- Poll async jobs until job has finished.
|
||||
required: false
|
||||
default: true
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a domain
|
||||
local_action:
|
||||
module: cs_domain
|
||||
path: ROOT/customers
|
||||
network_domain: customers.example.com
|
||||
|
||||
# Create another subdomain
|
||||
local_action:
|
||||
module: cs_domain
|
||||
path: ROOT/customers/xy
|
||||
network_domain: xy.customers.example.com
|
||||
|
||||
# Remove a domain
|
||||
local_action:
|
||||
module: cs_domain
|
||||
path: ROOT/customers/xy
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the domain.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 87b1e0ce-4e01-11e4-bb66-0050569e64b8
|
||||
name:
|
||||
description: Name of the domain.
|
||||
returned: success
|
||||
type: string
|
||||
sample: customers
|
||||
path:
|
||||
description: Domain path.
|
||||
returned: success
|
||||
type: string
|
||||
sample: /ROOT/customers
|
||||
parent_domain:
|
||||
description: Parent domain of the domain.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ROOT
|
||||
network_domain:
|
||||
description: Network domain of the domain.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example.local
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
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 = {}
|
||||
args['listall'] = True
|
||||
|
||||
domains = self.cs.listDomains(**args)
|
||||
if domains:
|
||||
for d in domains['domain']:
|
||||
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 = {}
|
||||
args['name'] = self.get_name()
|
||||
args['parentdomainid'] = self.get_parent_domain(key='id')
|
||||
args['networkdomain'] = self.module.params.get('network_domain')
|
||||
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.createDomain(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
domain = res['domain']
|
||||
return domain
|
||||
|
||||
|
||||
def update_domain(self, domain):
|
||||
args = {}
|
||||
args['id'] = domain['id']
|
||||
args['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.cs.updateDomain(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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 = {}
|
||||
args['id'] = domain['id']
|
||||
args['cleanup'] = self.module.params.get('clean_up')
|
||||
res = self.cs.deleteDomain(**args)
|
||||
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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(default=None),
|
||||
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
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
226
lib/ansible/modules/cloud/cloudstack/cs_facts.py
Normal file
226
lib/ansible/modules/cloud/cloudstack/cs_facts.py
Normal file
@@ -0,0 +1,226 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
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.
|
||||
version_added: '2.0'
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
filter:
|
||||
description:
|
||||
- Filter for a specific fact.
|
||||
required: false
|
||||
default: null
|
||||
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
|
||||
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
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
cloudstack_availability_zone:
|
||||
description: zone the instance is deployed in.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ch-gva-2
|
||||
cloudstack_instance_id:
|
||||
description: UUID of the instance.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ab4e80b0-3e7e-4936-bdc5-e334ba5b0139
|
||||
cloudstack_local_hostname:
|
||||
description: local hostname of the instance.
|
||||
returned: success
|
||||
type: string
|
||||
sample: VM-ab4e80b0-3e7e-4936-bdc5-e334ba5b0139
|
||||
cloudstack_local_ipv4:
|
||||
description: local IPv4 of the instance.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 185.19.28.35
|
||||
cloudstack_public_hostname:
|
||||
description: public IPv4 of the router. Same as C(cloudstack_public_ipv4).
|
||||
returned: success
|
||||
type: string
|
||||
sample: VM-ab4e80b0-3e7e-4936-bdc5-e334ba5b0139
|
||||
cloudstack_public_ipv4:
|
||||
description: public IPv4 of the router.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 185.19.28.35
|
||||
cloudstack_service_offering:
|
||||
description: service offering of the instance.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Micro 512mb 1cpu
|
||||
cloudstack_user_data:
|
||||
description: data of the instance provided by users.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: { "bla": "foo" }
|
||||
'''
|
||||
|
||||
import os
|
||||
|
||||
try:
|
||||
import yaml
|
||||
has_lib_yaml = True
|
||||
except ImportError:
|
||||
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):
|
||||
self.facts = ansible_facts(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.iteritems():
|
||||
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.load(self._fetch(CS_USERDATA_BASE_URL))
|
||||
except:
|
||||
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 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',
|
||||
]),
|
||||
),
|
||||
supports_check_mode=False
|
||||
)
|
||||
|
||||
if not has_lib_yaml:
|
||||
module.fail_json(msg="missing python library: yaml")
|
||||
|
||||
cs_facts = CloudStackFacts().run()
|
||||
cs_facts_result = dict(changed=False, ansible_facts=cs_facts)
|
||||
module.exit_json(**cs_facts_result)
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
from ansible.module_utils.facts import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
433
lib/ansible/modules/cloud/cloudstack/cs_firewall.py
Normal file
433
lib/ansible/modules/cloud/cloudstack/cs_firewall.py
Normal file
@@ -0,0 +1,433 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_firewall
|
||||
short_description: Manages firewall rules on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Creates and removes firewall rules.
|
||||
version_added: '2.0'
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
ip_address:
|
||||
description:
|
||||
- Public IP address the ingress rule is assigned to.
|
||||
- Required if C(type=ingress).
|
||||
required: false
|
||||
default: null
|
||||
network:
|
||||
description:
|
||||
- Network the egress rule is related to.
|
||||
- Required if C(type=egress).
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- State of the firewall rule.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent' ]
|
||||
type:
|
||||
description:
|
||||
- Type of the firewall rule.
|
||||
required: false
|
||||
default: 'ingress'
|
||||
choices: [ 'ingress', 'egress' ]
|
||||
protocol:
|
||||
description:
|
||||
- Protocol of the firewall rule.
|
||||
- C(all) is only available if C(type=egress)
|
||||
required: false
|
||||
default: 'tcp'
|
||||
choices: [ 'tcp', 'udp', 'icmp', 'all' ]
|
||||
cidr:
|
||||
description:
|
||||
- CIDR (full notation) to be used for firewall rule.
|
||||
required: false
|
||||
default: '0.0.0.0/0'
|
||||
start_port:
|
||||
description:
|
||||
- Start port for this rule. Considered if C(protocol=tcp) or C(protocol=udp).
|
||||
required: false
|
||||
default: null
|
||||
aliases: [ 'port' ]
|
||||
end_port:
|
||||
description:
|
||||
- End port for this rule. Considered if C(protocol=tcp) or C(protocol=udp). If not specified, equal C(start_port).
|
||||
required: false
|
||||
default: null
|
||||
icmp_type:
|
||||
description:
|
||||
- Type of the icmp message being sent. Considered if C(protocol=icmp).
|
||||
required: false
|
||||
default: null
|
||||
icmp_code:
|
||||
description:
|
||||
- Error code for this icmp message. Considered if C(protocol=icmp).
|
||||
required: false
|
||||
default: null
|
||||
domain:
|
||||
description:
|
||||
- Domain the firewall rule is related to.
|
||||
required: false
|
||||
default: null
|
||||
account:
|
||||
description:
|
||||
- Account the firewall rule is related to.
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- Name of the project the firewall rule is related to.
|
||||
required: false
|
||||
default: null
|
||||
zone:
|
||||
description:
|
||||
- Name of the zone in which the virtual machine is in.
|
||||
- If not set, default zone is used.
|
||||
required: false
|
||||
default: null
|
||||
poll_async:
|
||||
description:
|
||||
- Poll async jobs until job has finished.
|
||||
required: false
|
||||
default: true
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Allow inbound port 80/tcp from 1.2.3.4 to 4.3.2.1
|
||||
- local_action:
|
||||
module: cs_firewall
|
||||
ip_address: 4.3.2.1
|
||||
port: 80
|
||||
cidr: 1.2.3.4/32
|
||||
|
||||
# Allow inbound tcp/udp port 53 to 4.3.2.1
|
||||
- local_action:
|
||||
module: cs_firewall
|
||||
ip_address: 4.3.2.1
|
||||
port: 53
|
||||
protocol: '{{ item }}'
|
||||
with_items:
|
||||
- tcp
|
||||
- udp
|
||||
|
||||
# Ensure firewall rule is removed
|
||||
- local_action:
|
||||
module: cs_firewall
|
||||
ip_address: 4.3.2.1
|
||||
start_port: 8000
|
||||
end_port: 8888
|
||||
cidr: 17.0.0.0/8
|
||||
state: absent
|
||||
|
||||
# Allow all outbound traffic
|
||||
- local_action:
|
||||
module: cs_firewall
|
||||
network: my_network
|
||||
type: egress
|
||||
protocol: all
|
||||
|
||||
# Allow only HTTP outbound traffic for an IP
|
||||
- local_action:
|
||||
module: cs_firewall
|
||||
network: my_network
|
||||
type: egress
|
||||
port: 80
|
||||
cidr: 10.101.1.20
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the rule.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6
|
||||
ip_address:
|
||||
description: IP address of the rule if C(type=ingress)
|
||||
returned: success
|
||||
type: string
|
||||
sample: 10.100.212.10
|
||||
type:
|
||||
description: Type of the rule.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ingress
|
||||
cidr:
|
||||
description: CIDR of the rule.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 0.0.0.0/0
|
||||
protocol:
|
||||
description: Protocol of the rule.
|
||||
returned: success
|
||||
type: string
|
||||
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: string
|
||||
sample: my_network
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
class AnsibleCloudStackFirewall(AnsibleCloudStack):
|
||||
|
||||
def __init__(self, module):
|
||||
super(AnsibleCloudStackFirewall, self).__init__(module)
|
||||
self.returns = {
|
||||
'cidrlist': 'cidr',
|
||||
'startport': 'start_port',
|
||||
'endpoint': '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:
|
||||
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')
|
||||
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 = {}
|
||||
args['account'] = self.get_account('name')
|
||||
args['domainid'] = self.get_domain('id')
|
||||
args['projectid'] = self.get_project('id')
|
||||
|
||||
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")
|
||||
firewall_rules = self.cs.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")
|
||||
firewall_rules = self.cs.listFirewallRules(**args)
|
||||
|
||||
if firewall_rules and 'firewallrule' in firewall_rules:
|
||||
for rule in firewall_rules['firewallrule']:
|
||||
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._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_cidr_match(self, rule, cidr):
|
||||
return cidr == rule['cidrlist']
|
||||
|
||||
|
||||
def create_firewall_rule(self):
|
||||
firewall_rule = self.get_firewall_rule()
|
||||
if not firewall_rule:
|
||||
self.result['changed'] = True
|
||||
|
||||
args = {}
|
||||
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')
|
||||
|
||||
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.cs.createEgressFirewallRule(**args)
|
||||
else:
|
||||
args['ipaddressid'] = self.get_ip_address('id')
|
||||
res = self.cs.createFirewallRule(**args)
|
||||
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
poll_async = self.module.params.get('poll_async')
|
||||
if poll_async:
|
||||
firewall_rule = self.poll_job(res, 'firewallrule')
|
||||
return firewall_rule
|
||||
|
||||
|
||||
def remove_firewall_rule(self):
|
||||
firewall_rule = self.get_firewall_rule()
|
||||
if firewall_rule:
|
||||
self.result['changed'] = True
|
||||
|
||||
args = {}
|
||||
args['id'] = firewall_rule['id']
|
||||
|
||||
fw_type = self.module.params.get('type')
|
||||
if not self.module.check_mode:
|
||||
if fw_type == 'egress':
|
||||
res = self.cs.deleteEgressFirewallRule(**args)
|
||||
else:
|
||||
res = self.cs.deleteFirewallRule(**args)
|
||||
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
poll_async = self.module.params.get('poll_async')
|
||||
if poll_async:
|
||||
res = 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')
|
||||
return self.result
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = cs_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
ip_address = dict(default=None),
|
||||
network = dict(default=None),
|
||||
cidr = dict(default='0.0.0.0/0'),
|
||||
protocol = dict(choices=['tcp', 'udp', 'icmp', 'all'], default='tcp'),
|
||||
type = dict(choices=['ingress', 'egress'], default='ingress'),
|
||||
icmp_type = dict(type='int', default=None),
|
||||
icmp_code = dict(type='int', default=None),
|
||||
start_port = dict(type='int', aliases=['port'], default=None),
|
||||
end_port = dict(type='int', default=None),
|
||||
state = dict(choices=['present', 'absent'], default='present'),
|
||||
zone = dict(default=None),
|
||||
domain = dict(default=None),
|
||||
account = dict(default=None),
|
||||
project = dict(default=None),
|
||||
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,
|
||||
required_one_of = (
|
||||
['ip_address', 'network'],
|
||||
),
|
||||
mutually_exclusive = (
|
||||
['icmp_type', 'start_port'],
|
||||
['icmp_type', 'end_port'],
|
||||
['ip_address', 'network'],
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
1034
lib/ansible/modules/cloud/cloudstack/cs_instance.py
Normal file
1034
lib/ansible/modules/cloud/cloudstack/cs_instance.py
Normal file
File diff suppressed because it is too large
Load Diff
278
lib/ansible/modules/cloud/cloudstack/cs_instance_facts.py
Normal file
278
lib/ansible/modules/cloud/cloudstack/cs_instance_facts.py
Normal file
@@ -0,0 +1,278 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
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.
|
||||
version_added: "2.1"
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name or display name of the instance.
|
||||
required: true
|
||||
domain:
|
||||
description:
|
||||
- Domain the instance is related to.
|
||||
required: false
|
||||
default: null
|
||||
account:
|
||||
description:
|
||||
- Account the instance is related to.
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- Project the instance is related to.
|
||||
required: false
|
||||
default: null
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- cs_instance_facts:
|
||||
name: web-vm-1
|
||||
delegate_to: localhost
|
||||
|
||||
- debug:
|
||||
var: cloudstack_instance
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
cloudstack_instance.id:
|
||||
description: UUID of the instance.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6
|
||||
cloudstack_instance.name:
|
||||
description: Name of the instance.
|
||||
returned: success
|
||||
type: string
|
||||
sample: web-01
|
||||
cloudstack_instance.display_name:
|
||||
description: Display name of the instance.
|
||||
returned: success
|
||||
type: string
|
||||
sample: web-01
|
||||
cloudstack_instance.group:
|
||||
description: Group name of the instance is related.
|
||||
returned: success
|
||||
type: string
|
||||
sample: web
|
||||
created:
|
||||
description: Date of the instance was created.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 2014-12-01T14:57:57+0100
|
||||
cloudstack_instance.password_enabled:
|
||||
description: True if password setting is enabled.
|
||||
returned: success
|
||||
type: boolean
|
||||
sample: true
|
||||
cloudstack_instance.password:
|
||||
description: The password of the instance if exists.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Ge2oe7Do
|
||||
cloudstack_instance.ssh_key:
|
||||
description: Name of SSH key deployed to instance.
|
||||
returned: success
|
||||
type: string
|
||||
sample: key@work
|
||||
cloudstack_instance.domain:
|
||||
description: Domain the instance is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example domain
|
||||
cloudstack_instance.account:
|
||||
description: Account the instance is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example account
|
||||
cloudstack_instance.project:
|
||||
description: Name of project the instance is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Production
|
||||
cloudstack_instance.default_ip:
|
||||
description: Default IP address of the instance.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 10.23.37.42
|
||||
cloudstack_instance.public_ip:
|
||||
description: Public IP address with instance via static NAT rule.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 1.2.3.4
|
||||
cloudstack_instance.iso:
|
||||
description: Name of ISO the instance was deployed with.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Debian-8-64bit
|
||||
cloudstack_instance.template:
|
||||
description: Name of template the instance was deployed with.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Debian-8-64bit
|
||||
cloudstack_instance.service_offering:
|
||||
description: Name of the service offering the instance has.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 2cpu_2gb
|
||||
cloudstack_instance.zone:
|
||||
description: Name of zone the instance is in.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ch-gva-2
|
||||
cloudstack_instance.state:
|
||||
description: State of the instance.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Running
|
||||
cloudstack_instance.security_groups:
|
||||
description: Security groups the instance is in.
|
||||
returned: success
|
||||
type: list
|
||||
sample: '[ "default" ]'
|
||||
cloudstack_instance.affinity_groups:
|
||||
description: Affinity groups the instance is in.
|
||||
returned: success
|
||||
type: list
|
||||
sample: '[ "webservers" ]'
|
||||
cloudstack_instance.tags:
|
||||
description: List of resource tags associated with the instance.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: '[ { "key": "foo", "value": "bar" } ]'
|
||||
cloudstack_instance.hypervisor:
|
||||
description: Hypervisor related to this instance.
|
||||
returned: success
|
||||
type: string
|
||||
sample: KVM
|
||||
cloudstack_instance.instance_name:
|
||||
description: Internal name of the instance (ROOT admin only).
|
||||
returned: success
|
||||
type: string
|
||||
sample: i-44-3992-VM
|
||||
'''
|
||||
|
||||
import base64
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
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',
|
||||
}
|
||||
self.facts = {
|
||||
'cloudstack_instance': None,
|
||||
}
|
||||
|
||||
|
||||
def get_instance(self):
|
||||
instance = self.instance
|
||||
if not instance:
|
||||
instance_name = self.module.params.get('name')
|
||||
|
||||
args = {}
|
||||
args['account'] = self.get_account(key='name')
|
||||
args['domainid'] = self.get_domain(key='id')
|
||||
args['projectid'] = self.get_project(key='id')
|
||||
# Do not pass zoneid, as the instance name must be unique across zones.
|
||||
instances = self.cs.listVirtualMachines(**args)
|
||||
if instances:
|
||||
for v in instances['virtualmachine']:
|
||||
if instance_name.lower() in [ v['name'].lower(), v['displayname'].lower(), v['id'] ]:
|
||||
self.instance = v
|
||||
break
|
||||
return self.instance
|
||||
|
||||
|
||||
def run(self):
|
||||
instance = self.get_instance()
|
||||
if not instance:
|
||||
self.module.fail_json(msg="Instance not found: %s" % self.module.params.get('name'))
|
||||
self.facts['cloudstack_instance'] = self.get_result(instance)
|
||||
return self.facts
|
||||
|
||||
|
||||
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']
|
||||
return self.result
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = cs_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
name = dict(required=True),
|
||||
domain = dict(default=None),
|
||||
account = dict(default=None),
|
||||
project = dict(default=None),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
cs_instance_facts = AnsibleCloudStackInstanceFacts(module=module).run()
|
||||
cs_facts_result = dict(changed=False, ansible_facts=cs_instance_facts)
|
||||
module.exit_json(**cs_facts_result)
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
205
lib/ansible/modules/cloud/cloudstack/cs_instancegroup.py
Normal file
205
lib/ansible/modules/cloud/cloudstack/cs_instancegroup.py
Normal file
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_instancegroup
|
||||
short_description: Manages instance groups on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Create and remove instance groups.
|
||||
version_added: '2.0'
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the instance group.
|
||||
required: true
|
||||
domain:
|
||||
description:
|
||||
- Domain the instance group is related to.
|
||||
required: false
|
||||
default: null
|
||||
account:
|
||||
description:
|
||||
- Account the instance group is related to.
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- Project the instance group is related to.
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- State of the instance group.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent' ]
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create an instance group
|
||||
- local_action:
|
||||
module: cs_instancegroup
|
||||
name: loadbalancers
|
||||
|
||||
# Remove an instance group
|
||||
- local_action:
|
||||
module: cs_instancegroup
|
||||
name: loadbalancers
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the instance group.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6
|
||||
name:
|
||||
description: Name of the instance group.
|
||||
returned: success
|
||||
type: string
|
||||
sample: webservers
|
||||
created:
|
||||
description: Date when the instance group was created.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 2015-05-03T15:05:51+0200
|
||||
domain:
|
||||
description: Domain the instance group is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example domain
|
||||
account:
|
||||
description: Account the instance group is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example account
|
||||
project:
|
||||
description: Project the instance group is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example project
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
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 = {}
|
||||
args['account'] = self.get_account('name')
|
||||
args['domainid'] = self.get_domain('id')
|
||||
args['projectid'] = self.get_project('id')
|
||||
|
||||
instance_groups = self.cs.listInstanceGroups(**args)
|
||||
if instance_groups:
|
||||
for g in instance_groups['instancegroup']:
|
||||
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 = {}
|
||||
args['name'] = self.module.params.get('name')
|
||||
args['account'] = self.get_account('name')
|
||||
args['domainid'] = self.get_domain('id')
|
||||
args['projectid'] = self.get_project('id')
|
||||
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.createInstanceGroup(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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:
|
||||
res = self.cs.deleteInstanceGroup(id=instance_group['id'])
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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(default=None),
|
||||
account = dict(default=None),
|
||||
project = dict(default=None),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
244
lib/ansible/modules/cloud/cloudstack/cs_ip_address.py
Normal file
244
lib/ansible/modules/cloud/cloudstack/cs_ip_address.py
Normal file
@@ -0,0 +1,244 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, Darren Worrall <darren@iweb.co.uk>
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
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 C(state=present)
|
||||
version_added: '2.0'
|
||||
author:
|
||||
- "Darren Worrall (@dazworrall)"
|
||||
- "René Moser (@resmo)"
|
||||
options:
|
||||
ip_address:
|
||||
description:
|
||||
- Public IP address.
|
||||
- Required if C(state=absent)
|
||||
required: false
|
||||
default: null
|
||||
domain:
|
||||
description:
|
||||
- Domain the IP address is related to.
|
||||
required: false
|
||||
default: null
|
||||
network:
|
||||
description:
|
||||
- Network the IP address is related to.
|
||||
required: false
|
||||
default: null
|
||||
vpc:
|
||||
description:
|
||||
- VPC the IP address is related to.
|
||||
required: false
|
||||
default: null
|
||||
version_added: "2.2"
|
||||
account:
|
||||
description:
|
||||
- Account the IP address is related to.
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- Name of the project the IP address is related to.
|
||||
required: false
|
||||
default: null
|
||||
zone:
|
||||
description:
|
||||
- Name of the zone in which the IP address is in.
|
||||
- If not set, default zone is used.
|
||||
required: false
|
||||
default: null
|
||||
poll_async:
|
||||
description:
|
||||
- Poll async jobs until job has finished.
|
||||
required: false
|
||||
default: true
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Associate an IP address conditonally
|
||||
- local_action:
|
||||
module: cs_ip_address
|
||||
network: My Network
|
||||
register: ip_address
|
||||
when: instance.public_ip is undefined
|
||||
|
||||
# Disassociate an IP address
|
||||
- local_action:
|
||||
module: cs_ip_address
|
||||
ip_address: 1.2.3.4
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the Public IP address.
|
||||
returned: success
|
||||
type: string
|
||||
sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f
|
||||
ip_address:
|
||||
description: Public IP address.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 1.2.3.4
|
||||
zone:
|
||||
description: Name of zone the IP address is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ch-gva-2
|
||||
project:
|
||||
description: Name of project the IP address is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Production
|
||||
account:
|
||||
description: Account the IP address is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example account
|
||||
domain:
|
||||
description: Domain the IP address is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example domain
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
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)
|
||||
|
||||
ip_address = self.module.params.get('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.cs.listPublicIpAddresses(**args)
|
||||
|
||||
if ip_addresses:
|
||||
self.ip_address = ip_addresses['publicipaddress'][0]
|
||||
return self._get_by_key(key, self.ip_address)
|
||||
|
||||
def associate_ip_address(self):
|
||||
self.result['changed'] = True
|
||||
args = {
|
||||
'account': self.get_account(key='name'),
|
||||
'domainid': self.get_domain(key='id'),
|
||||
'projectid': self.get_project(key='id'),
|
||||
'networkid': self.get_network(key='id'),
|
||||
'zoneid': self.get_zone(key='id'),
|
||||
}
|
||||
ip_address = None
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.associateIpAddress(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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:
|
||||
res = self.cs.disassociateIpAddress(id=ip_address['id'])
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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(default=None),
|
||||
network = dict(default=None),
|
||||
zone = dict(default=None),
|
||||
domain = dict(default=None),
|
||||
account = dict(default=None),
|
||||
project = dict(default=None),
|
||||
poll_async = dict(type='bool', default=True),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
required_if=[
|
||||
('state', 'absent', ['ip_address']),
|
||||
],
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
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.associate_ip_address()
|
||||
|
||||
result = acs_ip_address.get_result(ip_address)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
339
lib/ansible/modules/cloud/cloudstack/cs_iso.py
Normal file
339
lib/ansible/modules/cloud/cloudstack/cs_iso.py
Normal file
@@ -0,0 +1,339 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_iso
|
||||
short_description: Manages ISO images on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Register and remove ISO images.
|
||||
version_added: '2.0'
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the ISO.
|
||||
required: true
|
||||
url:
|
||||
description:
|
||||
- URL where the ISO can be downloaded from. Required if C(state) is present.
|
||||
required: false
|
||||
default: null
|
||||
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 C(state) is present.
|
||||
required: false
|
||||
default: null
|
||||
is_ready:
|
||||
description:
|
||||
- This flag is used for searching existing ISOs. If set to C(true), it will only list ISO ready for deployment e.g. successfully downloaded and installed. Recommended to set it to C(false).
|
||||
required: false
|
||||
default: false
|
||||
aliases: []
|
||||
is_public:
|
||||
description:
|
||||
- Register the ISO to be publicly available to all users. Only used if C(state) is present.
|
||||
required: false
|
||||
default: false
|
||||
is_featured:
|
||||
description:
|
||||
- Register the ISO to be featured. Only used if C(state) is present.
|
||||
required: false
|
||||
default: false
|
||||
is_dynamically_scalable:
|
||||
description:
|
||||
- Register the ISO having XS/VMWare tools installed inorder to support dynamic scaling of VM cpu/memory. Only used if C(state) is present.
|
||||
required: false
|
||||
default: false
|
||||
aliases: []
|
||||
checksum:
|
||||
description:
|
||||
- The MD5 checksum value of this ISO. If set, we search by checksum instead of name.
|
||||
required: false
|
||||
default: false
|
||||
bootable:
|
||||
description:
|
||||
- Register the ISO to be bootable. Only used if C(state) is present.
|
||||
required: false
|
||||
default: true
|
||||
domain:
|
||||
description:
|
||||
- Domain the ISO is related to.
|
||||
required: false
|
||||
default: null
|
||||
account:
|
||||
description:
|
||||
- Account the ISO is related to.
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- Name of the project the ISO to be registered in.
|
||||
required: false
|
||||
default: null
|
||||
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.
|
||||
required: false
|
||||
default: null
|
||||
iso_filter:
|
||||
description:
|
||||
- Name of the filter used to search for the ISO.
|
||||
required: false
|
||||
default: 'self'
|
||||
choices: [ 'featured', 'self', 'selfexecutable','sharedexecutable','executable', 'community' ]
|
||||
state:
|
||||
description:
|
||||
- State of the ISO.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent' ]
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Register an ISO if ISO name does not already exist.
|
||||
- local_action:
|
||||
module: 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)
|
||||
|
||||
# Register an ISO with given name if ISO md5 checksum does not already exist.
|
||||
- local_action:
|
||||
module: 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
|
||||
|
||||
# Remove an ISO by name
|
||||
- local_action:
|
||||
module: cs_iso
|
||||
name: Debian 7 64-bit
|
||||
state: absent
|
||||
|
||||
# Remove an ISO by checksum
|
||||
- local_action:
|
||||
module: cs_iso
|
||||
name: Debian 7 64-bit
|
||||
checksum: 0b31bccccb048d20b551f70830bb7ad0
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the ISO.
|
||||
returned: success
|
||||
type: string
|
||||
sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f
|
||||
name:
|
||||
description: Name of the ISO.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Debian 7 64-bit
|
||||
display_text:
|
||||
description: Text to be displayed of the ISO.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Debian 7.7 64-bit minimal 2015-03-19
|
||||
zone:
|
||||
description: Name of zone the ISO is registered in.
|
||||
returned: success
|
||||
type: string
|
||||
sample: zuerich
|
||||
status:
|
||||
description: Status of the ISO.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Successfully Installed
|
||||
is_ready:
|
||||
description: True if the ISO is ready to be deployed from.
|
||||
returned: success
|
||||
type: boolean
|
||||
sample: true
|
||||
checksum:
|
||||
description: MD5 checksum of the ISO.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 0b31bccccb048d20b551f70830bb7ad0
|
||||
created:
|
||||
description: Date of registering.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 2015-03-29T14:57:06+0200
|
||||
domain:
|
||||
description: Domain the ISO is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example domain
|
||||
account:
|
||||
description: Account the ISO is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example account
|
||||
project:
|
||||
description: Project the ISO is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example project
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
class AnsibleCloudStackIso(AnsibleCloudStack):
|
||||
|
||||
def __init__(self, module):
|
||||
super(AnsibleCloudStackIso, self).__init__(module)
|
||||
self.returns = {
|
||||
'checksum': 'checksum',
|
||||
'status': 'status',
|
||||
'isready': 'is_ready',
|
||||
}
|
||||
self.iso = None
|
||||
|
||||
def register_iso(self):
|
||||
iso = self.get_iso()
|
||||
if not iso:
|
||||
|
||||
args = {}
|
||||
args['zoneid'] = self.get_zone('id')
|
||||
args['domainid'] = self.get_domain('id')
|
||||
args['account'] = self.get_account('name')
|
||||
args['projectid'] = self.get_project('id')
|
||||
args['bootable'] = self.module.params.get('bootable')
|
||||
args['ostypeid'] = self.get_os_type('id')
|
||||
args['name'] = self.module.params.get('name')
|
||||
args['displaytext'] = self.module.params.get('name')
|
||||
args['checksum'] = self.module.params.get('checksum')
|
||||
args['isdynamicallyscalable'] = self.module.params.get('is_dynamically_scalable')
|
||||
args['isfeatured'] = self.module.params.get('is_featured')
|
||||
args['ispublic'] = self.module.params.get('is_public')
|
||||
|
||||
if args['bootable'] and not args['ostypeid']:
|
||||
self.module.fail_json(msg="OS type 'os_type' is requried if 'bootable=true'.")
|
||||
|
||||
args['url'] = self.module.params.get('url')
|
||||
if not args['url']:
|
||||
self.module.fail_json(msg="URL is requried.")
|
||||
|
||||
self.result['changed'] = True
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.registerIso(**args)
|
||||
iso = res['iso'][0]
|
||||
return iso
|
||||
|
||||
|
||||
def get_iso(self):
|
||||
if not self.iso:
|
||||
|
||||
args = {}
|
||||
args['isready'] = self.module.params.get('is_ready')
|
||||
args['isofilter'] = self.module.params.get('iso_filter')
|
||||
args['domainid'] = self.get_domain('id')
|
||||
args['account'] = self.get_account('name')
|
||||
args['projectid'] = self.get_project('id')
|
||||
args['zoneid'] = self.get_zone('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.cs.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 remove_iso(self):
|
||||
iso = self.get_iso()
|
||||
if iso:
|
||||
self.result['changed'] = True
|
||||
|
||||
args = {}
|
||||
args['id'] = iso['id']
|
||||
args['projectid'] = self.get_project('id')
|
||||
args['zoneid'] = self.get_zone('id')
|
||||
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.deleteIso(**args)
|
||||
return iso
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = cs_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
name = dict(required=True),
|
||||
url = dict(default=None),
|
||||
os_type = dict(default=None),
|
||||
zone = dict(default=None),
|
||||
iso_filter = dict(default='self', choices=[ 'featured', 'self', 'selfexecutable','sharedexecutable','executable', 'community' ]),
|
||||
domain = dict(default=None),
|
||||
account = dict(default=None),
|
||||
project = dict(default=None),
|
||||
checksum = dict(default=None),
|
||||
is_ready = dict(type='bool', default=False),
|
||||
bootable = dict(type='bool', default=True),
|
||||
is_featured = dict(type='bool', default=False),
|
||||
is_dynamically_scalable = dict(type='bool', default=False),
|
||||
state = dict(choices=['present', 'absent'], default='present'),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
acs_iso = AnsibleCloudStackIso(module)
|
||||
|
||||
state = module.params.get('state')
|
||||
if state in ['absent']:
|
||||
iso = acs_iso.remove_iso()
|
||||
else:
|
||||
iso = acs_iso.register_iso()
|
||||
|
||||
result = acs_iso.get_result(iso)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
384
lib/ansible/modules/cloud/cloudstack/cs_loadbalancer_rule.py
Normal file
384
lib/ansible/modules/cloud/cloudstack/cs_loadbalancer_rule.py
Normal file
@@ -0,0 +1,384 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, Darren Worrall <darren@iweb.co.uk>
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_loadbalancer_rule
|
||||
short_description: Manages load balancer rules on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Add, update and remove load balancer rules.
|
||||
version_added: '2.0'
|
||||
author:
|
||||
- "Darren Worrall (@dazworrall)"
|
||||
- "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the load balancer rule.
|
||||
required: true
|
||||
description:
|
||||
description:
|
||||
- The description of the load balancer rule.
|
||||
required: false
|
||||
default: null
|
||||
algorithm:
|
||||
description:
|
||||
- Load balancer algorithm
|
||||
- Required when using C(state=present).
|
||||
required: false
|
||||
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 C(state=present).
|
||||
- Can not be changed once the rule exists due API limitation.
|
||||
required: false
|
||||
default: null
|
||||
public_port:
|
||||
description:
|
||||
- The public port from where the network traffic will be load balanced from.
|
||||
- Required when using C(state=present).
|
||||
- Can not be changed once the rule exists due API limitation.
|
||||
required: true
|
||||
default: null
|
||||
ip_address:
|
||||
description:
|
||||
- Public IP address from where the network traffic will be load balanced from.
|
||||
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.
|
||||
required: false
|
||||
default: false
|
||||
cidr:
|
||||
description:
|
||||
- CIDR (full notation) to be used for firewall rule if required.
|
||||
required: false
|
||||
default: null
|
||||
protocol:
|
||||
description:
|
||||
- The protocol to be used on the load balancer
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- Name of the project the load balancer IP address is related to.
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- State of the rule.
|
||||
required: true
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent' ]
|
||||
domain:
|
||||
description:
|
||||
- Domain the rule is related to.
|
||||
required: false
|
||||
default: null
|
||||
account:
|
||||
description:
|
||||
- Account the rule is related to.
|
||||
required: false
|
||||
default: null
|
||||
zone:
|
||||
description:
|
||||
- Name of the zone in which the rule shoud be created.
|
||||
- If not set, default zone is used.
|
||||
required: false
|
||||
default: null
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a load balancer rule
|
||||
- local_action:
|
||||
module: cs_loadbalancer_rule
|
||||
name: balance_http
|
||||
public_ip: 1.2.3.4
|
||||
algorithm: leastconn
|
||||
public_port: 80
|
||||
private_port: 8080
|
||||
|
||||
# update algorithm of an existing load balancer rule
|
||||
- local_action:
|
||||
module: cs_loadbalancer_rule
|
||||
name: balance_http
|
||||
public_ip: 1.2.3.4
|
||||
algorithm: roundrobin
|
||||
public_port: 80
|
||||
private_port: 8080
|
||||
|
||||
# Delete a load balancer rule
|
||||
- local_action:
|
||||
module: cs_loadbalancer_rule
|
||||
name: balance_http
|
||||
public_ip: 1.2.3.4
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the rule.
|
||||
returned: success
|
||||
type: string
|
||||
sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f
|
||||
zone:
|
||||
description: Name of zone the rule is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ch-gva-2
|
||||
project:
|
||||
description: Name of project the rule is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Production
|
||||
account:
|
||||
description: Account the rule is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example account
|
||||
domain:
|
||||
description: Domain the rule is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example domain
|
||||
algorithm:
|
||||
description: Load balancer algorithm used.
|
||||
returned: success
|
||||
type: string
|
||||
sample: "source"
|
||||
cidr:
|
||||
description: CIDR to forward traffic from.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ""
|
||||
name:
|
||||
description: Name of the rule.
|
||||
returned: success
|
||||
type: string
|
||||
sample: "http-lb"
|
||||
description:
|
||||
description: Description of the rule.
|
||||
returned: success
|
||||
type: string
|
||||
sample: "http load balancer rule"
|
||||
protocol:
|
||||
description: Protocol of the rule.
|
||||
returned: success
|
||||
type: string
|
||||
sample: "tcp"
|
||||
public_port:
|
||||
description: Public port.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 80
|
||||
private_port:
|
||||
description: Private IP address.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 80
|
||||
public_ip:
|
||||
description: Public IP address.
|
||||
returned: success
|
||||
type: string
|
||||
sample: "1.2.3.4"
|
||||
tags:
|
||||
description: List of resource tags associated with the rule.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: '[ { "key": "foo", "value": "bar" } ]'
|
||||
state:
|
||||
description: State of the rule.
|
||||
returned: success
|
||||
type: string
|
||||
sample: "Add"
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
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.cs.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'),
|
||||
'publicipid': self.get_ip_address(key='id'),
|
||||
'name': self.module.params.get('name'),
|
||||
}
|
||||
|
||||
|
||||
def present_lb_rule(self):
|
||||
missing_params = []
|
||||
for required_params in [
|
||||
'algorithm',
|
||||
'private_port',
|
||||
'public_port',
|
||||
]:
|
||||
if not self.module.params.get(required_params):
|
||||
missing_params.append(required_params)
|
||||
if missing_params:
|
||||
self.module.fail_json(msg="missing required arguments: %s" % ','.join(missing_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['algorithm'] = self.module.params.get('algorithm')
|
||||
args['privateport'] = self.module.params.get('private_port')
|
||||
args['publicport'] = self.module.params.get('public_port')
|
||||
args['cidrlist'] = self.module.params.get('cidr')
|
||||
args['description'] = self.module.params.get('description')
|
||||
args['protocol'] = self.module.params.get('protocol')
|
||||
res = self.cs.createLoadBalancerRule(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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 = {}
|
||||
args['id'] = rule['id']
|
||||
args['algorithm'] = self.module.params.get('algorithm')
|
||||
args['description'] = self.module.params.get('description')
|
||||
if self.has_changed(args, rule):
|
||||
self.result['changed'] = True
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.updateLoadBalancerRule(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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.cs.deleteLoadBalancerRule(id=rule['id'])
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
poll_async = self.module.params.get('poll_async')
|
||||
if poll_async:
|
||||
res = self.poll_job(res, 'loadbalancer')
|
||||
return rule
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = cs_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
name = dict(required=True),
|
||||
description = dict(default=None),
|
||||
algorithm = dict(choices=['source', 'roundrobin', 'leastconn'], default='source'),
|
||||
private_port = dict(type='int', default=None),
|
||||
public_port = dict(type='int', default=None),
|
||||
protocol = dict(default=None),
|
||||
state = dict(choices=['present', 'absent'], default='present'),
|
||||
ip_address = dict(required=True, aliases=['public_ip']),
|
||||
cidr = dict(default=None),
|
||||
project = dict(default=None),
|
||||
open_firewall = dict(type='bool', default=False),
|
||||
tags = dict(type='list', aliases=['tag'], default=None),
|
||||
zone = dict(default=None),
|
||||
domain = dict(default=None),
|
||||
account = dict(default=None),
|
||||
poll_async = dict(type='bool', default=True),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,364 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, Darren Worrall <darren@iweb.co.uk>
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
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.
|
||||
version_added: '2.0'
|
||||
author:
|
||||
- "Darren Worrall (@dazworrall)"
|
||||
- "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The name of the load balancer rule.
|
||||
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 C(name) is not unique.
|
||||
required: false
|
||||
default: null
|
||||
aliases: [ 'public_ip' ]
|
||||
vms:
|
||||
description:
|
||||
- List of VMs to assign to or remove from the rule.
|
||||
required: true
|
||||
type: list
|
||||
aliases: [ 'vm' ]
|
||||
state:
|
||||
description:
|
||||
- Should the VMs be present or absent from the rule.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent' ]
|
||||
project:
|
||||
description:
|
||||
- Name of the project the firewall rule is related to.
|
||||
required: false
|
||||
default: null
|
||||
domain:
|
||||
description:
|
||||
- Domain the rule is related to.
|
||||
required: false
|
||||
default: null
|
||||
account:
|
||||
description:
|
||||
- Account the rule is related to.
|
||||
required: false
|
||||
default: null
|
||||
zone:
|
||||
description:
|
||||
- Name of the zone in which the rule should be located.
|
||||
- If not set, default zone is used.
|
||||
required: false
|
||||
default: null
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Add VMs to an exising load balancer
|
||||
- local_action:
|
||||
module: cs_loadbalancer_rule_member
|
||||
name: balance_http
|
||||
vms:
|
||||
- web01
|
||||
- web02
|
||||
|
||||
# Remove a VM from an existing load balancer
|
||||
- local_action:
|
||||
module: cs_loadbalancer_rule_member
|
||||
name: balance_http
|
||||
vms:
|
||||
- web01
|
||||
- web02
|
||||
state: absent
|
||||
|
||||
# Rolling upgrade of hosts
|
||||
- hosts: webservers
|
||||
serial: 1
|
||||
pre_tasks:
|
||||
- name: Remove from load balancer
|
||||
local_action:
|
||||
module: cs_loadbalancer_rule_member
|
||||
name: balance_http
|
||||
vm: "{{ ansible_hostname }}"
|
||||
state: absent
|
||||
tasks:
|
||||
# Perform update
|
||||
post_tasks:
|
||||
- name: Add to load balancer
|
||||
local_action:
|
||||
module: cs_loadbalancer_rule_member
|
||||
name: balance_http
|
||||
vm: "{{ ansible_hostname }}"
|
||||
state: present
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the rule.
|
||||
returned: success
|
||||
type: string
|
||||
sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f
|
||||
zone:
|
||||
description: Name of zone the rule is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ch-gva-2
|
||||
project:
|
||||
description: Name of project the rule is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Production
|
||||
account:
|
||||
description: Account the rule is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example account
|
||||
domain:
|
||||
description: Domain the rule is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example domain
|
||||
algorithm:
|
||||
description: Load balancer algorithm used.
|
||||
returned: success
|
||||
type: string
|
||||
sample: "source"
|
||||
cidr:
|
||||
description: CIDR to forward traffic from.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ""
|
||||
name:
|
||||
description: Name of the rule.
|
||||
returned: success
|
||||
type: string
|
||||
sample: "http-lb"
|
||||
description:
|
||||
description: Description of the rule.
|
||||
returned: success
|
||||
type: string
|
||||
sample: "http load balancer rule"
|
||||
protocol:
|
||||
description: Protocol of the rule.
|
||||
returned: success
|
||||
type: string
|
||||
sample: "tcp"
|
||||
public_port:
|
||||
description: Public port.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 80
|
||||
private_port:
|
||||
description: Private IP address.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 80
|
||||
public_ip:
|
||||
description: Public IP address.
|
||||
returned: success
|
||||
type: string
|
||||
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: dict
|
||||
sample: '[ { "key": "foo", "value": "bar" } ]'
|
||||
state:
|
||||
description: State of the rule.
|
||||
returned: success
|
||||
type: string
|
||||
sample: "Add"
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
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['name'] = self.module.params.get('name')
|
||||
args['zoneid'] = self.get_zone(key='id')
|
||||
if self.module.params.get('ip_address'):
|
||||
args['publicipid'] = self.get_ip_address(key='id')
|
||||
rules = self.cs.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.cs.listLoadBalancerRuleInstances(id=rule['id'])
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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 = self.cs.assignToLoadBalancerRule
|
||||
to_change = set(wanted_names) - set(existing.keys())
|
||||
else:
|
||||
cs_func = self.cs.removeFromLoadBalancerRule
|
||||
to_change = set(wanted_names) & set(existing.keys())
|
||||
|
||||
if not to_change:
|
||||
return rule
|
||||
|
||||
args = self._get_common_args()
|
||||
vms = self.cs.listVirtualMachines(**args)
|
||||
to_change_ids = []
|
||||
for name in to_change:
|
||||
for vm in vms.get('virtualmachine', []):
|
||||
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 = cs_func(
|
||||
id = rule['id'],
|
||||
virtualmachineids = to_change_ids,
|
||||
)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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(default=None, aliases=['public_ip']),
|
||||
vms = dict(required=True, aliases=['vm'], type='list'),
|
||||
state = dict(choices=['present', 'absent'], default='present'),
|
||||
zone = dict(default=None),
|
||||
domain = dict(default=None),
|
||||
project = dict(default=None),
|
||||
account = dict(default=None),
|
||||
poll_async = dict(type='bool', default=True),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
564
lib/ansible/modules/cloud/cloudstack/cs_network.py
Normal file
564
lib/ansible/modules/cloud/cloudstack/cs_network.py
Normal file
@@ -0,0 +1,564 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_network
|
||||
short_description: Manages networks on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Create, update, restart and delete networks.
|
||||
version_added: '2.0'
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name (case sensitive) of the network.
|
||||
required: true
|
||||
display_text:
|
||||
description:
|
||||
- Display text of the network.
|
||||
- If not specified, C(name) will be used as C(display_text).
|
||||
required: false
|
||||
default: null
|
||||
network_offering:
|
||||
description:
|
||||
- Name of the offering for the network.
|
||||
- Required if C(state=present).
|
||||
required: false
|
||||
default: null
|
||||
start_ip:
|
||||
description:
|
||||
- The beginning IPv4 address of the network belongs to.
|
||||
- Only considered on create.
|
||||
required: false
|
||||
default: null
|
||||
end_ip:
|
||||
description:
|
||||
- The ending IPv4 address of the network belongs to.
|
||||
- If not specified, value of C(start_ip) is used.
|
||||
- Only considered on create.
|
||||
required: false
|
||||
default: null
|
||||
gateway:
|
||||
description:
|
||||
- The gateway of the network.
|
||||
- Required for shared networks and isolated networks when it belongs to a VPC.
|
||||
- Only considered on create.
|
||||
required: false
|
||||
default: null
|
||||
netmask:
|
||||
description:
|
||||
- The netmask of the network.
|
||||
- Required for shared networks and isolated networks when it belongs to a VPC.
|
||||
- Only considered on create.
|
||||
required: false
|
||||
default: null
|
||||
start_ipv6:
|
||||
description:
|
||||
- The beginning IPv6 address of the network belongs to.
|
||||
- Only considered on create.
|
||||
required: false
|
||||
default: null
|
||||
end_ipv6:
|
||||
description:
|
||||
- The ending IPv6 address of the network belongs to.
|
||||
- If not specified, value of C(start_ipv6) is used.
|
||||
- Only considered on create.
|
||||
required: false
|
||||
default: null
|
||||
cidr_ipv6:
|
||||
description:
|
||||
- CIDR of IPv6 network, must be at least /64.
|
||||
- Only considered on create.
|
||||
required: false
|
||||
default: null
|
||||
gateway_ipv6:
|
||||
description:
|
||||
- The gateway of the IPv6 network.
|
||||
- Required for shared networks.
|
||||
- Only considered on create.
|
||||
required: false
|
||||
default: null
|
||||
vlan:
|
||||
description:
|
||||
- The ID or VID of the network.
|
||||
required: false
|
||||
default: null
|
||||
vpc:
|
||||
description:
|
||||
- Name of the VPC of the network.
|
||||
required: false
|
||||
default: null
|
||||
isolated_pvlan:
|
||||
description:
|
||||
- The isolated private VLAN for this network.
|
||||
required: false
|
||||
default: null
|
||||
clean_up:
|
||||
description:
|
||||
- Cleanup old network elements.
|
||||
- Only considered on C(state=restarted).
|
||||
required: false
|
||||
default: false
|
||||
acl_type:
|
||||
description:
|
||||
- Access control type.
|
||||
- Only considered on create.
|
||||
required: false
|
||||
default: account
|
||||
choices: [ 'account', 'domain' ]
|
||||
network_domain:
|
||||
description:
|
||||
- The network domain.
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- State of the network.
|
||||
required: false
|
||||
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.
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- Name of the project the network to be deployed in.
|
||||
required: false
|
||||
default: null
|
||||
domain:
|
||||
description:
|
||||
- Domain the network is related to.
|
||||
required: false
|
||||
default: null
|
||||
account:
|
||||
description:
|
||||
- Account the network is related to.
|
||||
required: false
|
||||
default: null
|
||||
poll_async:
|
||||
description:
|
||||
- Poll async jobs until job has finished.
|
||||
required: false
|
||||
default: true
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# create a network
|
||||
- local_action:
|
||||
module: cs_network
|
||||
name: my network
|
||||
zone: gva-01
|
||||
network_offering: DefaultIsolatedNetworkOfferingWithSourceNatService
|
||||
network_domain: example.com
|
||||
|
||||
# update a network
|
||||
- local_action:
|
||||
module: cs_network
|
||||
name: my network
|
||||
display_text: network of domain example.local
|
||||
network_domain: example.local
|
||||
|
||||
# restart a network with clean up
|
||||
- local_action:
|
||||
module: cs_network
|
||||
name: my network
|
||||
clean_up: yes
|
||||
state: restared
|
||||
|
||||
# remove a network
|
||||
- local_action:
|
||||
module: cs_network
|
||||
name: my network
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the network.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6
|
||||
name:
|
||||
description: Name of the network.
|
||||
returned: success
|
||||
type: string
|
||||
sample: web project
|
||||
display_text:
|
||||
description: Display text of the network.
|
||||
returned: success
|
||||
type: string
|
||||
sample: web project
|
||||
dns1:
|
||||
description: IP address of the 1st nameserver.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 1.2.3.4
|
||||
dns2:
|
||||
description: IP address of the 2nd nameserver.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 1.2.3.4
|
||||
cidr:
|
||||
description: IPv4 network CIDR.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 10.101.64.0/24
|
||||
gateway:
|
||||
description: IPv4 gateway.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 10.101.64.1
|
||||
netmask:
|
||||
description: IPv4 netmask.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 255.255.255.0
|
||||
cidr_ipv6:
|
||||
description: IPv6 network CIDR.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 2001:db8::/64
|
||||
gateway_ipv6:
|
||||
description: IPv6 gateway.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 2001:db8::1
|
||||
state:
|
||||
description: State of the network.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Implemented
|
||||
zone:
|
||||
description: Name of zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ch-gva-2
|
||||
domain:
|
||||
description: Domain the network is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ROOT
|
||||
account:
|
||||
description: Account the network is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example account
|
||||
project:
|
||||
description: Name of project.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Production
|
||||
tags:
|
||||
description: List of resource tags associated with the network.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: '[ { "key": "foo", "value": "bar" } ]'
|
||||
acl_type:
|
||||
description: Access type of the network (Domain, Account).
|
||||
returned: success
|
||||
type: string
|
||||
sample: Account
|
||||
broadcast_domain_type:
|
||||
description: Broadcast domain type of the network.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Vlan
|
||||
type:
|
||||
description: Type of the network.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Isolated
|
||||
traffic_type:
|
||||
description: Traffic type of the network.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Guest
|
||||
state:
|
||||
description: State of the network (Allocated, Implemented, Setup).
|
||||
returned: success
|
||||
type: string
|
||||
sample: Allocated
|
||||
is_persistent:
|
||||
description: Whether the network is persistent or not.
|
||||
returned: success
|
||||
type: boolean
|
||||
sample: false
|
||||
network_domain:
|
||||
description: The network domain
|
||||
returned: success
|
||||
type: string
|
||||
sample: example.local
|
||||
network_offering:
|
||||
description: The network offering name.
|
||||
returned: success
|
||||
type: string
|
||||
sample: DefaultIsolatedNetworkOfferingWithSourceNatService
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
class AnsibleCloudStackNetwork(AnsibleCloudStack):
|
||||
|
||||
def __init__(self, module):
|
||||
super(AnsibleCloudStackNetwork, self).__init__(module)
|
||||
self.returns = {
|
||||
'networkdomain': 'network domain',
|
||||
'networkofferingname': 'network_offering',
|
||||
'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_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 = {}
|
||||
args['zoneid'] = self.get_zone(key='id')
|
||||
|
||||
network_offerings = self.cs.listNetworkOfferings(**args)
|
||||
if network_offerings:
|
||||
for no in network_offerings['networkoffering']:
|
||||
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 = {}
|
||||
args['name'] = self.module.params.get('name')
|
||||
args['displaytext'] = self.get_or_fallback('display_text', 'name')
|
||||
args['networkdomain'] = self.module.params.get('network_domain')
|
||||
args['networkofferingid'] = self.get_network_offering(key='id')
|
||||
return args
|
||||
|
||||
|
||||
def get_network(self):
|
||||
if not self.network:
|
||||
network = self.module.params.get('name')
|
||||
|
||||
args = {}
|
||||
args['zoneid'] = self.get_zone(key='id')
|
||||
args['projectid'] = self.get_project(key='id')
|
||||
args['account'] = self.get_account(key='name')
|
||||
args['domainid'] = self.get_domain(key='id')
|
||||
|
||||
networks = self.cs.listNetworks(**args)
|
||||
if networks:
|
||||
for n in networks['network']:
|
||||
if network in [ n['name'], n['displaytext'], n['id']]:
|
||||
self.network = n
|
||||
break
|
||||
return self.network
|
||||
|
||||
|
||||
def present_network(self):
|
||||
network = self.get_network()
|
||||
if not network:
|
||||
network = self.create_network(network)
|
||||
else:
|
||||
network = self.update_network(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.cs.updateNetwork(**args)
|
||||
|
||||
if 'errortext' in network:
|
||||
self.module.fail_json(msg="Failed: '%s'" % network['errortext'])
|
||||
|
||||
poll_async = self.module.params.get('poll_async')
|
||||
if network and poll_async:
|
||||
network = self.poll_job(network, 'network')
|
||||
return network
|
||||
|
||||
|
||||
def create_network(self, network):
|
||||
self.result['changed'] = True
|
||||
|
||||
args = self._get_args()
|
||||
args['acltype'] = self.module.params.get('acl_type')
|
||||
args['zoneid'] = self.get_zone(key='id')
|
||||
args['projectid'] = self.get_project(key='id')
|
||||
args['account'] = self.get_account(key='name')
|
||||
args['domainid'] = self.get_domain(key='id')
|
||||
args['startip'] = self.module.params.get('start_ip')
|
||||
args['endip'] = self.get_or_fallback('end_ip', 'start_ip')
|
||||
args['netmask'] = self.module.params.get('netmask')
|
||||
args['gateway'] = self.module.params.get('gateway')
|
||||
args['startipv6'] = self.module.params.get('start_ipv6')
|
||||
args['endipv6'] = self.get_or_fallback('end_ipv6', 'start_ipv6')
|
||||
args['ip6cidr'] = self.module.params.get('cidr_ipv6')
|
||||
args['ip6gateway'] = self.module.params.get('gateway_ipv6')
|
||||
args['vlan'] = self.module.params.get('vlan')
|
||||
args['isolatedpvlan'] = self.module.params.get('isolated_pvlan')
|
||||
args['subdomainaccess'] = self.module.params.get('subdomain_access')
|
||||
args['vpcid'] = self.get_vpc(key='id')
|
||||
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.createNetwork(**args)
|
||||
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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 = {}
|
||||
args['id'] = network['id']
|
||||
args['cleanup'] = self.module.params.get('clean_up')
|
||||
|
||||
if not self.module.check_mode:
|
||||
network = self.cs.restartNetwork(**args)
|
||||
|
||||
if 'errortext' in network:
|
||||
self.module.fail_json(msg="Failed: '%s'" % network['errortext'])
|
||||
|
||||
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 = {}
|
||||
args['id'] = network['id']
|
||||
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.deleteNetwork(**args)
|
||||
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
poll_async = self.module.params.get('poll_async')
|
||||
if res and poll_async:
|
||||
res = self.poll_job(res, 'network')
|
||||
return network
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = cs_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
name = dict(required=True),
|
||||
display_text = dict(default=None),
|
||||
network_offering = dict(default=None),
|
||||
zone = dict(default=None),
|
||||
start_ip = dict(default=None),
|
||||
end_ip = dict(default=None),
|
||||
gateway = dict(default=None),
|
||||
netmask = dict(default=None),
|
||||
start_ipv6 = dict(default=None),
|
||||
end_ipv6 = dict(default=None),
|
||||
cidr_ipv6 = dict(default=None),
|
||||
gateway_ipv6 = dict(default=None),
|
||||
vlan = dict(default=None),
|
||||
vpc = dict(default=None),
|
||||
isolated_pvlan = dict(default=None),
|
||||
clean_up = dict(type='bool', default=False),
|
||||
network_domain = dict(default=None),
|
||||
state = dict(choices=['present', 'absent', 'restarted' ], default='present'),
|
||||
acl_type = dict(choices=['account', 'domain'], default='account'),
|
||||
project = dict(default=None),
|
||||
domain = dict(default=None),
|
||||
account = dict(default=None),
|
||||
poll_async = dict(type='bool', default=True),
|
||||
))
|
||||
required_together = cs_required_together()
|
||||
required_together.extend([
|
||||
['start_ip', 'netmask', 'gateway'],
|
||||
['start_ipv6', 'cidr_ipv6', 'gateway_ipv6'],
|
||||
])
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=required_together,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
acs_network = AnsibleCloudStackNetwork(module)
|
||||
|
||||
state = module.params.get('state')
|
||||
if state in ['absent']:
|
||||
network = acs_network.absent_network()
|
||||
|
||||
elif state in ['restarted']:
|
||||
network = acs_network.restart_network()
|
||||
|
||||
else:
|
||||
network = acs_network.present_network()
|
||||
|
||||
result = acs_network.get_result(network)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
297
lib/ansible/modules/cloud/cloudstack/cs_nic.py
Normal file
297
lib/ansible/modules/cloud/cloudstack/cs_nic.py
Normal file
@@ -0,0 +1,297 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_nic
|
||||
short_description: Manages NICs and secondary IPs of an instance on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Add and remove secondary IPs to and from a NIC.
|
||||
version_added: "2.3"
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
vm:
|
||||
description:
|
||||
- Name of instance.
|
||||
required: true
|
||||
aliases: ['name']
|
||||
network:
|
||||
description:
|
||||
- Name of the network.
|
||||
- Required to find the NIC if instance has multiple networks assigned.
|
||||
required: false
|
||||
default: null
|
||||
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.
|
||||
required: false
|
||||
default: null
|
||||
aliases: ['secondary_ip']
|
||||
vpc:
|
||||
description:
|
||||
- Name of the VPC the C(vm) is related to.
|
||||
required: false
|
||||
default: null
|
||||
domain:
|
||||
description:
|
||||
- Domain the instance is related to.
|
||||
required: false
|
||||
default: null
|
||||
account:
|
||||
description:
|
||||
- Account the instance is related to.
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- Name of the project the instance is deployed in.
|
||||
required: false
|
||||
default: null
|
||||
zone:
|
||||
description:
|
||||
- Name of the zone in which the instance is deployed in.
|
||||
- If not set, default zone is used.
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- State of the ipaddress.
|
||||
required: false
|
||||
default: "present"
|
||||
choices: [ 'present', 'absent' ]
|
||||
poll_async:
|
||||
description:
|
||||
- Poll async jobs until job has finished.
|
||||
required: false
|
||||
default: true
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Assign a specific IP to the default NIC of the VM
|
||||
- local_action:
|
||||
module: cs_nic
|
||||
vm: customer_xy
|
||||
vm_guest_ip: 10.10.10.10
|
||||
|
||||
# Assign an IP to the default NIC of the VM
|
||||
# Note: If vm_guest_ip is not set, you will get a new IP address on every run.
|
||||
- local_action:
|
||||
module: cs_nic
|
||||
vm: customer_xy
|
||||
|
||||
# Remove a specific IP from the default NIC
|
||||
- local_action:
|
||||
module: cs_nic
|
||||
vm: customer_xy
|
||||
vm_guest_ip: 10.10.10.10
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the nic.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 87b1e0ce-4e01-11e4-bb66-0050569e64b8
|
||||
vm:
|
||||
description: Name of the VM.
|
||||
returned: success
|
||||
type: string
|
||||
sample: web-01
|
||||
ip_address:
|
||||
description: Primary IP of the NIC.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 10.10.10.10
|
||||
netmask:
|
||||
description: Netmask of the NIC.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 255.255.255.0
|
||||
mac_address:
|
||||
description: MAC address of the NIC.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 02:00:33:31:00:e4
|
||||
vm_guest_ip:
|
||||
description: Secondary IP of the NIC.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 10.10.10.10
|
||||
network:
|
||||
description: Name of the network if not default.
|
||||
returned: success
|
||||
type: string
|
||||
sample: sync network
|
||||
domain:
|
||||
description: Domain the VM is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example domain
|
||||
account:
|
||||
description: Account the VM is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example account
|
||||
project:
|
||||
description: Name of project the VM is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Production
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
class AnsibleCloudStackNic(AnsibleCloudStack):
|
||||
|
||||
def __init__(self, module):
|
||||
super(AnsibleCloudStackNic, 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'),
|
||||
'networkdid': self.get_network(key='id'),
|
||||
}
|
||||
nics = self.cs.listNics(**args)
|
||||
if nics:
|
||||
self.nic = nics['nic'][0]
|
||||
return self.nic
|
||||
self.module.fail_json("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(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.cs.addIpToNic(**args)
|
||||
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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(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.cs.removeIpFromNic(id=secondary_ip['id'])
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % nic['errortext'])
|
||||
|
||||
poll_async = self.module.params.get('poll_async')
|
||||
if poll_async:
|
||||
self.poll_job(res, 'nicsecondaryip')
|
||||
return nic
|
||||
|
||||
def get_result(self, nic):
|
||||
super(AnsibleCloudStackNic, 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
|
||||
self.result['domain'] = self.get_domain(key='path')
|
||||
self.result['account'] = self.get_account(key='name')
|
||||
self.result['project'] = self.get_project(key='name')
|
||||
return self.result
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = cs_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
vm=dict(required=True, aliases=['name']),
|
||||
vm_guest_ip=dict(default=None, aliases=['secondary_ip']),
|
||||
network=dict(default=None),
|
||||
vpc=dict(default=None),
|
||||
state=dict(choices=['present', 'absent'], default='present'),
|
||||
domain=dict(default=None),
|
||||
account=dict(default=None),
|
||||
project=dict(default=None),
|
||||
zone=dict(default=None),
|
||||
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'])
|
||||
])
|
||||
)
|
||||
|
||||
try:
|
||||
acs_nic = AnsibleCloudStackNic(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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
305
lib/ansible/modules/cloud/cloudstack/cs_pod.py
Normal file
305
lib/ansible/modules/cloud/cloudstack/cs_pod.py
Normal file
@@ -0,0 +1,305 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_pod
|
||||
short_description: Manages pods on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Create, update, delete pods.
|
||||
version_added: "2.1"
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the pod.
|
||||
required: true
|
||||
id:
|
||||
description:
|
||||
- uuid of the exising pod.
|
||||
default: null
|
||||
required: false
|
||||
start_ip:
|
||||
description:
|
||||
- Starting IP address for the Pod.
|
||||
- Required on C(state=present)
|
||||
default: null
|
||||
required: false
|
||||
end_ip:
|
||||
description:
|
||||
- Ending IP address for the Pod.
|
||||
default: null
|
||||
required: false
|
||||
netmask:
|
||||
description:
|
||||
- Netmask for the Pod.
|
||||
- Required on C(state=present)
|
||||
default: null
|
||||
required: false
|
||||
gateway:
|
||||
description:
|
||||
- Gateway for the Pod.
|
||||
- Required on C(state=present)
|
||||
default: null
|
||||
required: false
|
||||
zone:
|
||||
description:
|
||||
- Name of the zone in which the pod belongs to.
|
||||
- If not set, default zone is used.
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- State of the pod.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'enabled', 'disabled', 'absent' ]
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Ensure a pod is present
|
||||
- local_action:
|
||||
module: 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
|
||||
|
||||
# Ensure a pod is disabled
|
||||
- local_action:
|
||||
module: cs_pod
|
||||
name: pod1
|
||||
zone: ch-zrh-ix-01
|
||||
state: disabled
|
||||
|
||||
# Ensure a pod is enabled
|
||||
- local_action:
|
||||
module: cs_pod
|
||||
name: pod1
|
||||
zone: ch-zrh-ix-01
|
||||
state: enabled
|
||||
|
||||
# Ensure a pod is absent
|
||||
- local_action:
|
||||
module: cs_pod
|
||||
name: pod1
|
||||
zone: ch-zrh-ix-01
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the pod.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6
|
||||
name:
|
||||
description: Name of the pod.
|
||||
returned: success
|
||||
type: string
|
||||
sample: pod01
|
||||
start_ip:
|
||||
description: Starting IP of the pod.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 10.100.1.101
|
||||
end_ip:
|
||||
description: Ending IP of the pod.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 10.100.1.254
|
||||
netmask:
|
||||
description: Netmask of the pod.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 255.255.255.0
|
||||
gateway:
|
||||
description: Gateway of the pod.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 10.100.1.1
|
||||
allocation_state:
|
||||
description: State of the pod.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Enabled
|
||||
zone:
|
||||
description: Name of zone the pod is in.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ch-gva-2
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
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 = {}
|
||||
args['name'] = self.module.params.get('name')
|
||||
args['zoneid'] = self.get_zone(key='id')
|
||||
args['startip'] = self.module.params.get('start_ip')
|
||||
args['endip'] = self.module.params.get('end_ip')
|
||||
args['netmask'] = self.module.params.get('netmask')
|
||||
args['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 = {}
|
||||
|
||||
uuid = self.module.params.get('id')
|
||||
if uuid:
|
||||
args['id'] = uuid
|
||||
args['zoneid'] = self.get_zone(key='id')
|
||||
pods = self.cs.listPods(**args)
|
||||
if pods:
|
||||
self.pod = pods['pod'][0]
|
||||
return self.pod
|
||||
|
||||
args['name'] = self.module.params.get('name')
|
||||
args['zoneid'] = self.get_zone(key='id')
|
||||
pods = self.cs.listPods(**args)
|
||||
if pods:
|
||||
self.pod = pods['pod'][0]
|
||||
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.cs.createPod(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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.cs.updatePod(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
pod = res['pod']
|
||||
return pod
|
||||
|
||||
|
||||
def absent_pod(self):
|
||||
pod = self.get_pod()
|
||||
if pod:
|
||||
self.result['changed'] = True
|
||||
|
||||
args = {}
|
||||
args['id'] = pod['id']
|
||||
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.deletePod(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
return pod
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = cs_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
id = dict(default=None),
|
||||
name = dict(required=True),
|
||||
gateway = dict(default=None),
|
||||
netmask = dict(default=None),
|
||||
start_ip = dict(default=None),
|
||||
end_ip = dict(default=None),
|
||||
zone = dict(default=None),
|
||||
state = dict(choices=['present', 'enabled', 'disabled', 'absent'], default='present'),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
387
lib/ansible/modules/cloud/cloudstack/cs_portforward.py
Normal file
387
lib/ansible/modules/cloud/cloudstack/cs_portforward.py
Normal file
@@ -0,0 +1,387 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_portforward
|
||||
short_description: Manages port forwarding rules on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Create, update and remove port forwarding rules.
|
||||
version_added: '2.0'
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
ip_address:
|
||||
description:
|
||||
- Public IP address the rule is assigned to.
|
||||
required: true
|
||||
vm:
|
||||
description:
|
||||
- Name of virtual machine which we make the port forwarding rule for.
|
||||
- Required if C(state=present).
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- State of the port forwarding rule.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent' ]
|
||||
protocol:
|
||||
description:
|
||||
- Protocol of the port forwarding rule.
|
||||
required: false
|
||||
default: 'tcp'
|
||||
choices: [ 'tcp', 'udp' ]
|
||||
public_port:
|
||||
description:
|
||||
- Start public port for this rule.
|
||||
required: true
|
||||
public_end_port:
|
||||
description:
|
||||
- End public port for this rule.
|
||||
- If not specified equal C(public_port).
|
||||
required: false
|
||||
default: null
|
||||
private_port:
|
||||
description:
|
||||
- Start private port for this rule.
|
||||
required: true
|
||||
private_end_port:
|
||||
description:
|
||||
- End private port for this rule.
|
||||
- If not specified equal C(private_port).
|
||||
required: false
|
||||
default: null
|
||||
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.
|
||||
required: false
|
||||
default: false
|
||||
vm_guest_ip:
|
||||
description:
|
||||
- VM guest NIC secondary IP address for the port forwarding rule.
|
||||
required: false
|
||||
default: false
|
||||
domain:
|
||||
description:
|
||||
- Domain the C(vm) is related to.
|
||||
required: false
|
||||
default: null
|
||||
account:
|
||||
description:
|
||||
- Account the C(vm) is related to.
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- Name of the project the C(vm) is located in.
|
||||
required: false
|
||||
default: null
|
||||
zone:
|
||||
description:
|
||||
- Name of the zone in which the virtual machine is in.
|
||||
- If not set, default zone is used.
|
||||
required: false
|
||||
default: null
|
||||
poll_async:
|
||||
description:
|
||||
- Poll async jobs until job has finished.
|
||||
required: false
|
||||
default: true
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# 1.2.3.4:80 -> web01:8080
|
||||
- local_action:
|
||||
module: cs_portforward
|
||||
ip_address: 1.2.3.4
|
||||
vm: web01
|
||||
public_port: 80
|
||||
private_port: 8080
|
||||
|
||||
# forward SSH and open firewall
|
||||
- local_action:
|
||||
module: cs_portforward
|
||||
ip_address: '{{ public_ip }}'
|
||||
vm: '{{ inventory_hostname }}'
|
||||
public_port: '{{ ansible_ssh_port }}'
|
||||
private_port: 22
|
||||
open_firewall: true
|
||||
|
||||
# forward DNS traffic, but do not open firewall
|
||||
- local_action:
|
||||
module: cs_portforward
|
||||
ip_address: 1.2.3.4
|
||||
vm: '{{ inventory_hostname }}'
|
||||
public_port: 53
|
||||
private_port: 53
|
||||
protocol: udp
|
||||
|
||||
# remove ssh port forwarding
|
||||
- local_action:
|
||||
module: cs_portforward
|
||||
ip_address: 1.2.3.4
|
||||
public_port: 22
|
||||
private_port: 22
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the public IP address.
|
||||
returned: success
|
||||
type: string
|
||||
sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f
|
||||
ip_address:
|
||||
description: Public IP address.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 1.2.3.4
|
||||
protocol:
|
||||
description: Protocol.
|
||||
returned: success
|
||||
type: string
|
||||
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
|
||||
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: string
|
||||
sample: web-01
|
||||
vm_display_name:
|
||||
description: Display name of the virtual machine.
|
||||
returned: success
|
||||
type: string
|
||||
sample: web-01
|
||||
vm_guest_ip:
|
||||
description: IP of the virtual machine.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 10.101.65.152
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
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')
|
||||
public_end_port = self.get_or_fallback('public_end_port', 'public_port')
|
||||
private_port = self.module.params.get('private_port')
|
||||
private_end_port = self.get_or_fallback('private_end_port', 'private_port')
|
||||
|
||||
args = {}
|
||||
args['ipaddressid'] = self.get_ip_address(key='id')
|
||||
args['account'] = self.get_account(key='name')
|
||||
args['domainid'] = self.get_domain(key='id')
|
||||
args['projectid'] = self.get_project(key='id')
|
||||
portforwarding_rules = self.cs.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()
|
||||
return portforwarding_rule
|
||||
|
||||
|
||||
def create_portforwarding_rule(self):
|
||||
args = {}
|
||||
args['protocol'] = self.module.params.get('protocol')
|
||||
args['publicport'] = self.module.params.get('public_port')
|
||||
args['publicendport'] = self.get_or_fallback('public_end_port', 'public_port')
|
||||
args['privateport'] = self.module.params.get('private_port')
|
||||
args['privateendport'] = self.get_or_fallback('private_end_port', 'private_port')
|
||||
args['openfirewall'] = self.module.params.get('open_firewall')
|
||||
args['vmguestip'] = self.get_vm_guest_ip()
|
||||
args['ipaddressid'] = self.get_ip_address(key='id')
|
||||
args['virtualmachineid'] = self.get_vm(key='id')
|
||||
args['account'] = self.get_account(key='name')
|
||||
args['domainid'] = self.get_domain(key='id')
|
||||
|
||||
portforwarding_rule = None
|
||||
self.result['changed'] = True
|
||||
if not self.module.check_mode:
|
||||
portforwarding_rule = self.cs.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 = {}
|
||||
args['protocol'] = self.module.params.get('protocol')
|
||||
args['publicport'] = self.module.params.get('public_port')
|
||||
args['publicendport'] = self.get_or_fallback('public_end_port', 'public_port')
|
||||
args['privateport'] = self.module.params.get('private_port')
|
||||
args['privateendport'] = self.get_or_fallback('private_end_port', 'private_port')
|
||||
args['vmguestip'] = self.get_vm_guest_ip()
|
||||
args['ipaddressid'] = self.get_ip_address(key='id')
|
||||
args['virtualmachineid'] = self.get_vm(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.cs.updatePortForwardingRule(**args)
|
||||
self.absent_portforwarding_rule()
|
||||
portforwarding_rule = self.cs.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 = {}
|
||||
args['id'] = portforwarding_rule['id']
|
||||
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.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:
|
||||
# Bad bad API does not always return int when it should.
|
||||
for search_key, return_key in self.returns_to_int.iteritems():
|
||||
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', default=None),
|
||||
private_port = dict(type='int', required=True),
|
||||
private_end_port = dict(type='int', default=None),
|
||||
state = dict(choices=['present', 'absent'], default='present'),
|
||||
open_firewall = dict(type='bool', default=False),
|
||||
vm_guest_ip = dict(default=None),
|
||||
vm = dict(default=None),
|
||||
zone = dict(default=None),
|
||||
domain = dict(default=None),
|
||||
account = dict(default=None),
|
||||
project = dict(default=None),
|
||||
poll_async = dict(type='bool', default=True),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
311
lib/ansible/modules/cloud/cloudstack/cs_project.py
Normal file
311
lib/ansible/modules/cloud/cloudstack/cs_project.py
Normal file
@@ -0,0 +1,311 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_project
|
||||
short_description: Manages projects on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Create, update, suspend, activate and remove projects.
|
||||
version_added: '2.0'
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the project.
|
||||
required: true
|
||||
display_text:
|
||||
description:
|
||||
- Display text of the project.
|
||||
- If not specified, C(name) will be used as C(display_text).
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- State of the project.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent', 'active', 'suspended' ]
|
||||
domain:
|
||||
description:
|
||||
- Domain the project is related to.
|
||||
required: false
|
||||
default: null
|
||||
account:
|
||||
description:
|
||||
- Account the project is related to.
|
||||
required: false
|
||||
default: null
|
||||
tags:
|
||||
description:
|
||||
- List of tags. Tags are a list of dictionaries having keys C(key) and C(value).
|
||||
- "If you want to delete all tags, set a empty list e.g. C(tags: [])."
|
||||
required: false
|
||||
default: null
|
||||
version_added: "2.2"
|
||||
poll_async:
|
||||
description:
|
||||
- Poll async jobs until job has finished.
|
||||
required: false
|
||||
default: true
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a project
|
||||
- local_action:
|
||||
module: cs_project
|
||||
name: web
|
||||
tags:
|
||||
- { key: admin, value: john }
|
||||
- { key: foo, value: bar }
|
||||
|
||||
# Rename a project
|
||||
- local_action:
|
||||
module: cs_project
|
||||
name: web
|
||||
display_text: my web project
|
||||
|
||||
# Suspend an existing project
|
||||
- local_action:
|
||||
module: cs_project
|
||||
name: web
|
||||
state: suspended
|
||||
|
||||
# Activate an existing project
|
||||
- local_action:
|
||||
module: cs_project
|
||||
name: web
|
||||
state: active
|
||||
|
||||
# Remove a project
|
||||
- local_action:
|
||||
module: cs_project
|
||||
name: web
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the project.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6
|
||||
name:
|
||||
description: Name of the project.
|
||||
returned: success
|
||||
type: string
|
||||
sample: web project
|
||||
display_text:
|
||||
description: Display text of the project.
|
||||
returned: success
|
||||
type: string
|
||||
sample: web project
|
||||
state:
|
||||
description: State of the project.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Active
|
||||
domain:
|
||||
description: Domain the project is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example domain
|
||||
account:
|
||||
description: Account the project is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example account
|
||||
tags:
|
||||
description: List of resource tags associated with the project.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: '[ { "key": "foo", "value": "bar" } ]'
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
class AnsibleCloudStackProject(AnsibleCloudStack):
|
||||
|
||||
|
||||
def get_project(self):
|
||||
if not self.project:
|
||||
project = self.module.params.get('name')
|
||||
|
||||
args = {}
|
||||
args['account'] = self.get_account(key='name')
|
||||
args['domainid'] = self.get_domain(key='id')
|
||||
|
||||
projects = self.cs.listProjects(**args)
|
||||
if projects:
|
||||
for p in projects['project']:
|
||||
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 = {}
|
||||
args['id'] = project['id']
|
||||
args['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.cs.updateProject(**args)
|
||||
|
||||
if 'errortext' in project:
|
||||
self.module.fail_json(msg="Failed: '%s'" % project['errortext'])
|
||||
|
||||
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 = {}
|
||||
args['name'] = self.module.params.get('name')
|
||||
args['displaytext'] = self.get_or_fallback('display_text', 'name')
|
||||
args['account'] = self.get_account('name')
|
||||
args['domainid'] = self.get_domain('id')
|
||||
|
||||
if not self.module.check_mode:
|
||||
project = self.cs.createProject(**args)
|
||||
|
||||
if 'errortext' in project:
|
||||
self.module.fail_json(msg="Failed: '%s'" % project['errortext'])
|
||||
|
||||
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 = {}
|
||||
args['id'] = project['id']
|
||||
|
||||
if not self.module.check_mode:
|
||||
if state == 'suspended':
|
||||
project = self.cs.suspendProject(**args)
|
||||
else:
|
||||
project = self.cs.activateProject(**args)
|
||||
|
||||
if 'errortext' in project:
|
||||
self.module.fail_json(msg="Failed: '%s'" % project['errortext'])
|
||||
|
||||
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 = {}
|
||||
args['id'] = project['id']
|
||||
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.deleteProject(**args)
|
||||
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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(default=None),
|
||||
state = dict(choices=['present', 'absent', 'active', 'suspended' ], default='present'),
|
||||
domain = dict(default=None),
|
||||
account = dict(default=None),
|
||||
poll_async = dict(type='bool', default=True),
|
||||
tags=dict(type='list', aliases=['tag'], default=None),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
208
lib/ansible/modules/cloud/cloudstack/cs_region.py
Normal file
208
lib/ansible/modules/cloud/cloudstack/cs_region.py
Normal file
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': 'preview',
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_region
|
||||
short_description: Manages regions on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Add, update and remove regions.
|
||||
version_added: "2.3"
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
id:
|
||||
description:
|
||||
- ID of the region.
|
||||
- Must be an number (int).
|
||||
required: true
|
||||
name:
|
||||
description:
|
||||
- Name of the region.
|
||||
- Required if C(state=present)
|
||||
required: false
|
||||
default: null
|
||||
endpoint:
|
||||
description:
|
||||
- Endpoint URL of the region.
|
||||
- Required if C(state=present)
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- State of the region.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent' ]
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# create a region
|
||||
local_action:
|
||||
module: cs_region
|
||||
id: 2
|
||||
name: geneva
|
||||
endpoint: https://cloud.gva.example.com
|
||||
|
||||
# remove a region with ID 2
|
||||
local_action:
|
||||
module: cs_region
|
||||
id: 2
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: ID of the region.
|
||||
returned: success
|
||||
type: int
|
||||
sample: 1
|
||||
name:
|
||||
description: Name of the region.
|
||||
returned: success
|
||||
type: string
|
||||
sample: local
|
||||
endpoint:
|
||||
description: Endpoint of the region.
|
||||
returned: success
|
||||
type: string
|
||||
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.cloudstack import *
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
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.cs.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.cs.addRegion(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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.cs.updateRegion(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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:
|
||||
res = self.cs.removeRegion(id=region['id'])
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
return region
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = cs_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
id=dict(required=True, type='int'),
|
||||
name=dict(default=None),
|
||||
endpoint=dict(default=None),
|
||||
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
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
220
lib/ansible/modules/cloud/cloudstack/cs_resourcelimit.py
Normal file
220
lib/ansible/modules/cloud/cloudstack/cs_resourcelimit.py
Normal file
@@ -0,0 +1,220 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_resourcelimit
|
||||
short_description: Manages resource limits on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Manage limits of resources for domains, accounts and projects.
|
||||
version_added: "2.1"
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
resource_type:
|
||||
description:
|
||||
- Type of the resource.
|
||||
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).
|
||||
required: false
|
||||
default: -1
|
||||
aliases: [ 'max' ]
|
||||
domain:
|
||||
description:
|
||||
- Domain the resource is related to.
|
||||
required: false
|
||||
default: null
|
||||
account:
|
||||
description:
|
||||
- Account the resource is related to.
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- Name of the project the resource is related to.
|
||||
required: false
|
||||
default: null
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Update a resource limit for instances of a domain
|
||||
local_action:
|
||||
module: cs_resourcelimit
|
||||
type: instance
|
||||
limit: 10
|
||||
domain: customers
|
||||
|
||||
# Update a resource limit for instances of an account
|
||||
local_action:
|
||||
module: cs_resourcelimit
|
||||
type: instance
|
||||
limit: 12
|
||||
account: moserre
|
||||
domain: customers
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
recource_type:
|
||||
description: Type of the resource
|
||||
returned: success
|
||||
type: string
|
||||
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: string
|
||||
sample: example domain
|
||||
account:
|
||||
description: Account the resource is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example account
|
||||
project:
|
||||
description: Project the resource is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example project
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
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 = {}
|
||||
args['account'] = self.get_account(key='name')
|
||||
args['domainid'] = self.get_domain(key='id')
|
||||
args['projectid'] = self.get_project(key='id')
|
||||
args['resourcetype'] = self.get_resource_type()
|
||||
resource_limit = self.cs.listResourceLimits(**args)
|
||||
if resource_limit:
|
||||
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 = {}
|
||||
args['account'] = self.get_account(key='name')
|
||||
args['domainid'] = self.get_domain(key='id')
|
||||
args['projectid'] = self.get_project(key='id')
|
||||
args['resourcetype'] = self.get_resource_type()
|
||||
args['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.cs.updateResourceLimit(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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']),
|
||||
domain = dict(default=None),
|
||||
account = dict(default=None),
|
||||
project = dict(default=None),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
acs_resource_limit = AnsibleCloudStackResourceLimit(module)
|
||||
resource_limit = acs_resource_limit.update_resource_limit()
|
||||
result = acs_resource_limit.get_result(resource_limit)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
378
lib/ansible/modules/cloud/cloudstack/cs_router.py
Normal file
378
lib/ansible/modules/cloud/cloudstack/cs_router.py
Normal file
@@ -0,0 +1,378 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_router
|
||||
short_description: Manages routers on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Start, restart, stop and destroy routers.
|
||||
- C(state=present) is not able to create routers, use M(cs_network) instead.
|
||||
version_added: "2.2"
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the router.
|
||||
required: true
|
||||
service_offering:
|
||||
description:
|
||||
- Name or id of the service offering of the router.
|
||||
required: false
|
||||
default: null
|
||||
domain:
|
||||
description:
|
||||
- Domain the router is related to.
|
||||
required: false
|
||||
default: null
|
||||
account:
|
||||
description:
|
||||
- Account the router is related to.
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- Name of the project the router is related to.
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- State of the router.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent', 'started', 'stopped', 'restarted' ]
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Ensure the router has the desired service offering, no matter if
|
||||
# the router is running or not.
|
||||
- local_action:
|
||||
module: cs_router
|
||||
name: r-40-VM
|
||||
service_offering: System Offering for Software Router
|
||||
|
||||
# Ensure started
|
||||
- local_action:
|
||||
module: cs_router
|
||||
name: r-40-VM
|
||||
state: started
|
||||
|
||||
# Ensure started with desired service offering.
|
||||
# If the service offerings changes, router will be rebooted.
|
||||
- local_action:
|
||||
module: cs_router
|
||||
name: r-40-VM
|
||||
service_offering: System Offering for Software Router
|
||||
state: started
|
||||
|
||||
# Ensure stopped
|
||||
- local_action:
|
||||
module: cs_router
|
||||
name: r-40-VM
|
||||
state: stopped
|
||||
|
||||
# Remove a router
|
||||
- local_action:
|
||||
module: cs_router
|
||||
name: r-40-VM
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the router.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6
|
||||
name:
|
||||
description: Name of the router.
|
||||
returned: success
|
||||
type: string
|
||||
sample: r-40-VM
|
||||
created:
|
||||
description: Date of the router was created.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 2014-12-01T14:57:57+0100
|
||||
template_version:
|
||||
description: Version of the system VM template.
|
||||
returned: success
|
||||
type: string
|
||||
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: string
|
||||
sample: UNKNOWN
|
||||
role:
|
||||
description: Role of the router.
|
||||
returned: success
|
||||
type: string
|
||||
sample: VIRTUAL_ROUTER
|
||||
zone:
|
||||
description: Name of zone the router is in.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ch-gva-2
|
||||
service_offering:
|
||||
description: Name of the service offering the router has.
|
||||
returned: success
|
||||
type: string
|
||||
sample: System Offering For Software Router
|
||||
state:
|
||||
description: State of the router.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Active
|
||||
domain:
|
||||
description: Domain the router is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ROOT
|
||||
account:
|
||||
description: Account the router is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: admin
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
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 = {}
|
||||
args['issystem'] = True
|
||||
|
||||
service_offerings = self.cs.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 = {}
|
||||
args['projectid'] = self.get_project(key='id')
|
||||
args['account'] = self.get_account(key='name')
|
||||
args['domainid'] = self.get_domain(key='id')
|
||||
|
||||
routers = self.cs.listRouters(**args)
|
||||
if routers:
|
||||
for r in routers['router']:
|
||||
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 = {}
|
||||
args['id'] = router['id']
|
||||
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.startRouter(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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 = {}
|
||||
args['id'] = router['id']
|
||||
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.stopRouter(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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 = {}
|
||||
args['id'] = router['id']
|
||||
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.rebootRouter(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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 = {}
|
||||
args['id'] = router['id']
|
||||
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.destroyRouter(**args)
|
||||
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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 = {}
|
||||
args['id'] = router['id']
|
||||
args['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.cs.changeServiceForRouter(**args)
|
||||
|
||||
if 'errortext' in router:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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(default=None),
|
||||
state = dict(choices=['present', 'started', 'stopped', 'restarted', 'absent'], default="present"),
|
||||
domain = dict(default=None),
|
||||
account = dict(default=None),
|
||||
project = dict(default=None),
|
||||
poll_async = dict(type='bool', default=True),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
223
lib/ansible/modules/cloud/cloudstack/cs_securitygroup.py
Normal file
223
lib/ansible/modules/cloud/cloudstack/cs_securitygroup.py
Normal file
@@ -0,0 +1,223 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_securitygroup
|
||||
short_description: Manages security groups on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Create and remove security groups.
|
||||
version_added: '2.0'
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the security group.
|
||||
required: true
|
||||
description:
|
||||
description:
|
||||
- Description of the security group.
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- State of the security group.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent' ]
|
||||
domain:
|
||||
description:
|
||||
- Domain the security group is related to.
|
||||
required: false
|
||||
default: null
|
||||
account:
|
||||
description:
|
||||
- Account the security group is related to.
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- Name of the project the security group to be created in.
|
||||
required: false
|
||||
default: null
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a security group
|
||||
- local_action:
|
||||
module: cs_securitygroup
|
||||
name: default
|
||||
description: default security group
|
||||
|
||||
# Remove a security group
|
||||
- local_action:
|
||||
module: cs_securitygroup
|
||||
name: default
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the security group.
|
||||
returned: success
|
||||
type: string
|
||||
sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f
|
||||
name:
|
||||
description: Name of security group.
|
||||
returned: success
|
||||
type: string
|
||||
sample: app
|
||||
description:
|
||||
description: Description of security group.
|
||||
returned: success
|
||||
type: string
|
||||
sample: application security group
|
||||
tags:
|
||||
description: List of resource tags associated with the security group.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: '[ { "key": "foo", "value": "bar" } ]'
|
||||
project:
|
||||
description: Name of project the security group is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Production
|
||||
domain:
|
||||
description: Domain the security group is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example domain
|
||||
account:
|
||||
description: Account the security group is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example account
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
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 = {}
|
||||
args['projectid'] = self.get_project(key='id')
|
||||
args['account'] = self.get_account(key='name')
|
||||
args['domainid'] = self.get_domain(key='id')
|
||||
args['securitygroupname'] = self.module.params.get('name')
|
||||
|
||||
sgs = self.cs.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 = {}
|
||||
args['name'] = self.module.params.get('name')
|
||||
args['projectid'] = self.get_project(key='id')
|
||||
args['account'] = self.get_account(key='name')
|
||||
args['domainid'] = self.get_domain(key='id')
|
||||
args['description'] = self.module.params.get('description')
|
||||
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.createSecurityGroup(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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 = {}
|
||||
args['name'] = self.module.params.get('name')
|
||||
args['projectid'] = self.get_project(key='id')
|
||||
args['account'] = self.get_account(key='name')
|
||||
args['domainid'] = self.get_domain(key='id')
|
||||
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.deleteSecurityGroup(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
return security_group
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = cs_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
name = dict(required=True),
|
||||
description = dict(default=None),
|
||||
state = dict(choices=['present', 'absent'], default='present'),
|
||||
project = dict(default=None),
|
||||
account = dict(default=None),
|
||||
domain = dict(default=None),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
425
lib/ansible/modules/cloud/cloudstack/cs_securitygroup_rule.py
Normal file
425
lib/ansible/modules/cloud/cloudstack/cs_securitygroup_rule.py
Normal file
@@ -0,0 +1,425 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_securitygroup_rule
|
||||
short_description: Manages security group rules on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Add and remove security group rules.
|
||||
version_added: '2.0'
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
security_group:
|
||||
description:
|
||||
- Name of the security group the rule is related to. The security group must be existing.
|
||||
required: true
|
||||
state:
|
||||
description:
|
||||
- State of the security group rule.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent' ]
|
||||
protocol:
|
||||
description:
|
||||
- Protocol of the security group rule.
|
||||
required: false
|
||||
default: 'tcp'
|
||||
choices: [ 'tcp', 'udp', 'icmp', 'ah', 'esp', 'gre' ]
|
||||
type:
|
||||
description:
|
||||
- Ingress or egress security group rule.
|
||||
required: false
|
||||
default: 'ingress'
|
||||
choices: [ 'ingress', 'egress' ]
|
||||
cidr:
|
||||
description:
|
||||
- CIDR (full notation) to be used for security group rule.
|
||||
required: false
|
||||
default: '0.0.0.0/0'
|
||||
user_security_group:
|
||||
description:
|
||||
- Security group this rule is based of.
|
||||
required: false
|
||||
default: null
|
||||
start_port:
|
||||
description:
|
||||
- Start port for this rule. Required if C(protocol=tcp) or C(protocol=udp).
|
||||
required: false
|
||||
default: null
|
||||
aliases: [ 'port' ]
|
||||
end_port:
|
||||
description:
|
||||
- End port for this rule. Required if C(protocol=tcp) or C(protocol=udp), but C(start_port) will be used if not set.
|
||||
required: false
|
||||
default: null
|
||||
icmp_type:
|
||||
description:
|
||||
- Type of the icmp message being sent. Required if C(protocol=icmp).
|
||||
required: false
|
||||
default: null
|
||||
icmp_code:
|
||||
description:
|
||||
- Error code for this icmp message. Required if C(protocol=icmp).
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- Name of the project the security group to be created in.
|
||||
required: false
|
||||
default: null
|
||||
poll_async:
|
||||
description:
|
||||
- Poll async jobs until job has finished.
|
||||
required: false
|
||||
default: true
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
---
|
||||
# Allow inbound port 80/tcp from 1.2.3.4 added to security group 'default'
|
||||
- local_action:
|
||||
module: cs_securitygroup_rule
|
||||
security_group: default
|
||||
port: 80
|
||||
cidr: 1.2.3.4/32
|
||||
|
||||
# Allow tcp/udp outbound added to security group 'default'
|
||||
- local_action:
|
||||
module: cs_securitygroup_rule
|
||||
security_group: default
|
||||
type: egress
|
||||
start_port: 1
|
||||
end_port: 65535
|
||||
protocol: '{{ item }}'
|
||||
with_items:
|
||||
- tcp
|
||||
- udp
|
||||
|
||||
# Allow inbound icmp from 0.0.0.0/0 added to security group 'default'
|
||||
- local_action:
|
||||
module: cs_securitygroup_rule
|
||||
security_group: default
|
||||
protocol: icmp
|
||||
icmp_code: -1
|
||||
icmp_type: -1
|
||||
|
||||
# Remove rule inbound port 80/tcp from 0.0.0.0/0 from security group 'default'
|
||||
- local_action:
|
||||
module: cs_securitygroup_rule
|
||||
security_group: default
|
||||
port: 80
|
||||
state: absent
|
||||
|
||||
# Allow inbound port 80/tcp from security group web added to security group 'default'
|
||||
- local_action:
|
||||
module: cs_securitygroup_rule
|
||||
security_group: default
|
||||
port: 80
|
||||
user_security_group: web
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the of the rule.
|
||||
returned: success
|
||||
type: string
|
||||
sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f
|
||||
security_group:
|
||||
description: security group of the rule.
|
||||
returned: success
|
||||
type: string
|
||||
sample: default
|
||||
type:
|
||||
description: type of the rule.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ingress
|
||||
cidr:
|
||||
description: CIDR of the rule.
|
||||
returned: success and cidr is defined
|
||||
type: string
|
||||
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: string
|
||||
sample: default
|
||||
protocol:
|
||||
description: protocol of the rule.
|
||||
returned: success
|
||||
type: string
|
||||
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
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
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 not (start_port and end_port):
|
||||
self.module.fail_json(msg="no start_port or end_port set for protocol '%s'" % protocol)
|
||||
|
||||
if protocol == 'icmp' and not (icmp_type and icmp_code):
|
||||
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 = {}
|
||||
args['securitygroupname'] = security_group_name
|
||||
args['projectid'] = self.get_project('id')
|
||||
sgs = self.cs.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.cs.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.cs.authorizeSecurityGroupEgress(**args)
|
||||
|
||||
if res and 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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.cs.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.cs.revokeSecurityGroupEgress(id=rule['ruleid'])
|
||||
|
||||
if res and 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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(default=None),
|
||||
protocol = dict(choices=['tcp', 'udp', 'icmp', 'ah', 'esp', 'gre'], default='tcp'),
|
||||
icmp_type = dict(type='int', default=None),
|
||||
icmp_code = dict(type='int', default=None),
|
||||
start_port = dict(type='int', default=None, aliases=['port']),
|
||||
end_port = dict(type='int', default=None),
|
||||
state = dict(choices=['present', 'absent'], default='present'),
|
||||
project = dict(default=None),
|
||||
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
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
387
lib/ansible/modules/cloud/cloudstack/cs_snapshot_policy.py
Normal file
387
lib/ansible/modules/cloud/cloudstack/cs_snapshot_policy.py
Normal file
@@ -0,0 +1,387 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_snapshot_policy
|
||||
short_description: Manages volume snapshot policies on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Create, update and delete volume snapshot policies.
|
||||
version_added: '2.2'
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
volume:
|
||||
description:
|
||||
- Name of the volume.
|
||||
- Either C(volume) or C(vm) is required.
|
||||
required: false
|
||||
default: null
|
||||
volume_type:
|
||||
description:
|
||||
- Type of the volume.
|
||||
required: false
|
||||
default: null
|
||||
choices:
|
||||
- DATADISK
|
||||
- ROOT
|
||||
version_added: "2.3"
|
||||
vm:
|
||||
description:
|
||||
- Name of the instance to select the volume from.
|
||||
- Use C(volume_type) if VM has a DATADISK and ROOT volume.
|
||||
- In case of C(volume_type=DATADISK), additionally use C(device_id) if VM has more than one DATADISK volume.
|
||||
- Either C(volume) or C(vm) is required.
|
||||
required: false
|
||||
default: null
|
||||
version_added: "2.3"
|
||||
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.
|
||||
required: false
|
||||
default: null
|
||||
version_added: "2.3"
|
||||
vpc:
|
||||
description:
|
||||
- Name of the vpc the instance is deployed in.
|
||||
required: false
|
||||
default: null
|
||||
version_added: "2.3"
|
||||
interval_type:
|
||||
description:
|
||||
- Interval of the snapshot.
|
||||
required: false
|
||||
default: 'daily'
|
||||
choices: [ 'hourly', 'daily', 'weekly', 'monthly' ]
|
||||
aliases: [ 'interval' ]
|
||||
max_snaps:
|
||||
description:
|
||||
- Max number of snapshots.
|
||||
required: false
|
||||
default: 8
|
||||
aliases: [ 'max' ]
|
||||
schedule:
|
||||
description:
|
||||
- Time the snapshot is scheduled. Required if C(state=present).
|
||||
- 'Format for C(interval_type=HOURLY): C(MM)'
|
||||
- 'Format for C(interval_type=DAILY): C(MM:HH)'
|
||||
- 'Format for C(interval_type=WEEKLY): C(MM:HH:DD (1-7))'
|
||||
- 'Format for C(interval_type=MONTHLY): C(MM:HH:DD (1-28))'
|
||||
required: false
|
||||
default: null
|
||||
time_zone:
|
||||
description:
|
||||
- Specifies a timezone for this command.
|
||||
required: false
|
||||
default: 'UTC'
|
||||
aliases: [ 'timezone' ]
|
||||
state:
|
||||
description:
|
||||
- State of the snapshot policy.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent' ]
|
||||
domain:
|
||||
description:
|
||||
- Domain the volume is related to.
|
||||
required: false
|
||||
default: null
|
||||
account:
|
||||
description:
|
||||
- Account the volume is related to.
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- Name of the project the volume is related to.
|
||||
required: false
|
||||
default: null
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Ensure a snapshot policy daily at 1h00 UTC
|
||||
- local_action:
|
||||
module: cs_snapshot_policy
|
||||
volume: ROOT-478
|
||||
schedule: '00:1'
|
||||
max_snaps: 3
|
||||
|
||||
# Ensure a snapshot policy daily at 1h00 UTC on the second DATADISK of VM web-01
|
||||
- local_action:
|
||||
module: cs_snapshot_policy
|
||||
vm: web-01
|
||||
volume_type: DATADISK
|
||||
device_id: 2
|
||||
schedule: '00:1'
|
||||
max_snaps: 3
|
||||
|
||||
# Ensure a snapshot policy hourly at minute 5 UTC
|
||||
- local_action:
|
||||
module: cs_snapshot_policy
|
||||
volume: ROOT-478
|
||||
schedule: '5'
|
||||
interval_type: hourly
|
||||
max_snaps: 1
|
||||
|
||||
# Ensure a snapshot policy weekly on Sunday at 05h00, TZ Europe/Zurich
|
||||
- local_action:
|
||||
module: cs_snapshot_policy
|
||||
volume: ROOT-478
|
||||
schedule: '00:5:1'
|
||||
interval_type: weekly
|
||||
max_snaps: 1
|
||||
time_zone: 'Europe/Zurich'
|
||||
|
||||
# Ensure a snapshot policy is absent
|
||||
- local_action:
|
||||
module: cs_snapshot_policy
|
||||
volume: ROOT-478
|
||||
interval_type: hourly
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the snapshot policy.
|
||||
returned: success
|
||||
type: string
|
||||
sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f
|
||||
interval_type:
|
||||
description: interval type of the snapshot policy.
|
||||
returned: success
|
||||
type: string
|
||||
sample: daily
|
||||
schedule:
|
||||
description: schedule of the snapshot policy.
|
||||
returned: success
|
||||
type: string
|
||||
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: string
|
||||
sample: Etc/UTC
|
||||
volume:
|
||||
description: the volume of the snapshot policy.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Etc/UTC
|
||||
zone:
|
||||
description: Name of zone the volume is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ch-gva-2
|
||||
project:
|
||||
description: Name of project the volume is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Production
|
||||
account:
|
||||
description: Account the volume is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example account
|
||||
domain:
|
||||
description: Domain the volume is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example domain
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
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'),
|
||||
'type': self.module.params.get('volume_type'),
|
||||
}
|
||||
volumes = self.cs.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.cs.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.cs.createSnapshotPolicy(**args)
|
||||
policy = res['snapshotpolicy']
|
||||
if 'errortext' in policy:
|
||||
self.module.fail_json(msg="Failed: '%s'" % policy['errortext'])
|
||||
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:
|
||||
res = self.cs.deleteSnapshotPolicies(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % policy['errortext'])
|
||||
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(default=None),
|
||||
volume_type=dict(choices=['DATADISK', 'ROOT'], default=None),
|
||||
vm=dict(default=None),
|
||||
device_id=dict(type='int', default=None),
|
||||
vpc=dict(default=None),
|
||||
interval_type=dict(default='daily', choices=['hourly', 'daily', 'weekly', 'monthly'], aliases=['interval']),
|
||||
schedule=dict(default=None),
|
||||
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(default=None),
|
||||
account=dict(default=None),
|
||||
project=dict(default=None),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
required_one_of = (
|
||||
['vm', 'volume'],
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
255
lib/ansible/modules/cloud/cloudstack/cs_sshkeypair.py
Normal file
255
lib/ansible/modules/cloud/cloudstack/cs_sshkeypair.py
Normal file
@@ -0,0 +1,255 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
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.
|
||||
version_added: '2.0'
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of public key.
|
||||
required: true
|
||||
domain:
|
||||
description:
|
||||
- Domain the public key is related to.
|
||||
required: false
|
||||
default: null
|
||||
account:
|
||||
description:
|
||||
- Account the public key is related to.
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- Name of the project the public key to be registered in.
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- State of the public key.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent' ]
|
||||
public_key:
|
||||
description:
|
||||
- String of the public key.
|
||||
required: false
|
||||
default: null
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# 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 }}'
|
||||
|
||||
# remove a public key by its name:
|
||||
- cs_sshkeypair:
|
||||
name: linus@example.com
|
||||
state: absent
|
||||
delegate_to: localhost
|
||||
|
||||
# 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: string
|
||||
sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f
|
||||
name:
|
||||
description: Name of the SSH public key.
|
||||
returned: success
|
||||
type: string
|
||||
sample: linus@example.com
|
||||
fingerprint:
|
||||
description: Fingerprint of the SSH public key.
|
||||
returned: success
|
||||
type: string
|
||||
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: string
|
||||
sample: "-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQCkeFYjI+4k8bWfIRMzp4pCzhlopNydbbwRu824P5ilD4ATWMUG\nvEtuCQ2Mp5k5Bma30CdYHgh2/SbxC5RxXSUKTUJtTKpoJUy8PAhb1nn9dnfkC2oU\naRVi9NRUgypTIZxMpgooHOxvAzWxbZCyh1W+91Ld3FNaGxTLqTgeevY84wIDAQAB\nAoGAcwQwgLyUwsNB1vmjWwE0QEmvHS4FlhZyahhi4hGfZvbzAxSWHIK7YUT1c8KU\n9XsThEIN8aJ3GvcoL3OAqNKRnoNb14neejVHkYRadhxqc0GVN6AUIyCqoEMpvhFI\nQrinM572ORzv5ffRjCTbvZcYlW+sqFKNo5e8pYIB8TigpFECQQDu7bg9vkvg8xPs\nkP1K+EH0vsR6vUfy+m3euXjnbJtiP7RoTkZk0JQMOmexgy1qQhISWT0e451wd62v\nJ7M0trl5AkEAsDivJnMIlCCCypwPN4tdNUYpe9dtidR1zLmb3SA7wXk5xMUgLZI9\ncWPjBCMt0KKShdDhQ+hjXAyKQLF7iAPuOwJABjdHCMwvmy2XwhrPjCjDRoPEBtFv\n0sFzJE08+QBZVogDwIbwy+SlRWArnHGmN9J6N+H8dhZD3U4vxZPJ1MBAOQJBAJxO\nCv1dt1Q76gbwmYa49LnWO+F+2cgRTVODpr5iYt5fOmBQQRRqzFkRMkFvOqn+KVzM\nQ6LKM6dn8BEl295vLhUCQQCVDWzoSk3GjL3sOjfAUTyAj8VAXM69llaptxWWySPM\nE9pA+8rYmHfohYFx7FD5/KWCO+sfmxTNB48X0uwyE8tO\n-----END RSA PRIVATE KEY-----\n"
|
||||
'''
|
||||
|
||||
try:
|
||||
import sshpubkeys
|
||||
has_lib_sshpubkeys = True
|
||||
except ImportError:
|
||||
has_lib_sshpubkeys = False
|
||||
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
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 = {}
|
||||
args['domainid'] = self.get_domain('id')
|
||||
args['account'] = self.get_account('name')
|
||||
args['projectid'] = self.get_project('id')
|
||||
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:
|
||||
res = self.cs.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:
|
||||
self.cs.deleteSSHKeyPair(**args)
|
||||
args['publickey'] = public_key
|
||||
res = self.cs.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 = {}
|
||||
args['domainid'] = self.get_domain('id')
|
||||
args['account'] = self.get_account('name')
|
||||
args['projectid'] = self.get_project('id')
|
||||
args['name'] = self.module.params.get('name')
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.createSSHKeyPair(**args)
|
||||
ssh_key = res['keypair']
|
||||
return ssh_key
|
||||
|
||||
|
||||
def remove_ssh_key(self):
|
||||
ssh_key = self.get_ssh_key()
|
||||
if ssh_key:
|
||||
self.result['changed'] = True
|
||||
args = {}
|
||||
args['domainid'] = self.get_domain('id')
|
||||
args['account'] = self.get_account('name')
|
||||
args['projectid'] = self.get_project('id')
|
||||
args['name'] = self.module.params.get('name')
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.deleteSSHKeyPair(**args)
|
||||
return ssh_key
|
||||
|
||||
|
||||
def get_ssh_key(self):
|
||||
if not self.ssh_key:
|
||||
args = {}
|
||||
args['domainid'] = self.get_domain('id')
|
||||
args['account'] = self.get_account('name')
|
||||
args['projectid'] = self.get_project('id')
|
||||
args['name'] = self.module.params.get('name')
|
||||
|
||||
ssh_keys = self.cs.listSSHKeyPairs(**args)
|
||||
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)
|
||||
return key.hash()
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = cs_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
name = dict(required=True),
|
||||
public_key = dict(default=None),
|
||||
domain = dict(default=None),
|
||||
account = dict(default=None),
|
||||
project = dict(default=None),
|
||||
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="python library sshpubkeys required: pip install sshpubkeys")
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
282
lib/ansible/modules/cloud/cloudstack/cs_staticnat.py
Normal file
282
lib/ansible/modules/cloud/cloudstack/cs_staticnat.py
Normal file
@@ -0,0 +1,282 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_staticnat
|
||||
short_description: Manages static NATs on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Create, update and remove static NATs.
|
||||
version_added: '2.0'
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
ip_address:
|
||||
description:
|
||||
- Public IP address the static NAT is assigned to.
|
||||
required: true
|
||||
vm:
|
||||
description:
|
||||
- Name of virtual machine which we make the static NAT for.
|
||||
- Required if C(state=present).
|
||||
required: false
|
||||
default: null
|
||||
vm_guest_ip:
|
||||
description:
|
||||
- VM guest NIC secondary IP address for the static NAT.
|
||||
required: false
|
||||
default: false
|
||||
network:
|
||||
description:
|
||||
- Network the IP address is related to.
|
||||
required: false
|
||||
default: null
|
||||
version_added: "2.2"
|
||||
vpc:
|
||||
description:
|
||||
- Name of the VPC.
|
||||
required: false
|
||||
default: null
|
||||
version_added: "2.3"
|
||||
state:
|
||||
description:
|
||||
- State of the static NAT.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent' ]
|
||||
domain:
|
||||
description:
|
||||
- Domain the static NAT is related to.
|
||||
required: false
|
||||
default: null
|
||||
account:
|
||||
description:
|
||||
- Account the static NAT is related to.
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- Name of the project the static NAT is related to.
|
||||
required: false
|
||||
default: null
|
||||
zone:
|
||||
description:
|
||||
- Name of the zone in which the virtual machine is in.
|
||||
- If not set, default zone is used.
|
||||
required: false
|
||||
default: null
|
||||
poll_async:
|
||||
description:
|
||||
- Poll async jobs until job has finished.
|
||||
required: false
|
||||
default: true
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# create a static NAT: 1.2.3.4 -> web01
|
||||
- local_action:
|
||||
module: cs_staticnat
|
||||
ip_address: 1.2.3.4
|
||||
vm: web01
|
||||
|
||||
# remove a static NAT
|
||||
- local_action:
|
||||
module: cs_staticnat
|
||||
ip_address: 1.2.3.4
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the ip_address.
|
||||
returned: success
|
||||
type: string
|
||||
sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f
|
||||
ip_address:
|
||||
description: Public IP address.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 1.2.3.4
|
||||
vm_name:
|
||||
description: Name of the virtual machine.
|
||||
returned: success
|
||||
type: string
|
||||
sample: web-01
|
||||
vm_display_name:
|
||||
description: Display name of the virtual machine.
|
||||
returned: success
|
||||
type: string
|
||||
sample: web-01
|
||||
vm_guest_ip:
|
||||
description: IP of the virtual machine.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 10.101.65.152
|
||||
zone:
|
||||
description: Name of zone the static NAT is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ch-gva-2
|
||||
project:
|
||||
description: Name of project the static NAT is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Production
|
||||
account:
|
||||
description: Account the static NAT is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example account
|
||||
domain:
|
||||
description: Domain the static NAT is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example domain
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
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 = {}
|
||||
args['virtualmachineid'] = self.get_vm(key='id')
|
||||
args['ipaddressid'] = ip_address['id']
|
||||
args['vmguestip'] = self.get_vm_guest_ip()
|
||||
args['networkid'] = self.get_network(key='id')
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.enableStaticNat(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
# 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 = {}
|
||||
args['virtualmachineid'] = self.get_vm(key='id')
|
||||
args['ipaddressid'] = ip_address['id']
|
||||
args['vmguestip'] = self.get_vm_guest_ip()
|
||||
|
||||
# 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.cs.disableStaticNat(ipaddressid=ip_address['id'])
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
self.poll_job(res, 'staticnat')
|
||||
res = self.cs.enableStaticNat(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
# 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.cs.disableStaticNat(ipaddressid=ip_address['id'])
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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(default=None),
|
||||
vm_guest_ip = dict(default=None),
|
||||
network = dict(default=None),
|
||||
vpc = dict(default=None),
|
||||
state = dict(choices=['present', 'absent'], default='present'),
|
||||
zone = dict(default=None),
|
||||
domain = dict(default=None),
|
||||
account = dict(default=None),
|
||||
project = dict(default=None),
|
||||
poll_async = dict(type='bool', default=True),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
672
lib/ansible/modules/cloud/cloudstack/cs_template.py
Normal file
672
lib/ansible/modules/cloud/cloudstack/cs_template.py
Normal file
@@ -0,0 +1,672 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_template
|
||||
short_description: Manages templates on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Register a template from URL, create a template from a ROOT volume of a stopped VM or its snapshot, extract and delete templates.
|
||||
version_added: '2.0'
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the template.
|
||||
required: true
|
||||
url:
|
||||
description:
|
||||
- URL of where the template is hosted on C(state=present).
|
||||
- URL to which the template would be extracted on C(state=extracted).
|
||||
- Mutually exclusive with C(vm).
|
||||
required: false
|
||||
default: null
|
||||
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 C(url).
|
||||
required: false
|
||||
default: null
|
||||
snapshot:
|
||||
description:
|
||||
- Name of the snapshot, created from the VM ROOT volume, the template will be created from.
|
||||
- C(vm) is required together with this argument.
|
||||
required: false
|
||||
default: null
|
||||
os_type:
|
||||
description:
|
||||
- OS type that best represents the OS of this template.
|
||||
required: false
|
||||
default: null
|
||||
checksum:
|
||||
description:
|
||||
- The MD5 checksum value of this template.
|
||||
- If set, we search by checksum instead of name.
|
||||
required: false
|
||||
default: false
|
||||
is_ready:
|
||||
description:
|
||||
- This flag is used for searching existing templates.
|
||||
- If set to C(true), it will only list template ready for deployment e.g. successfully downloaded and installed.
|
||||
- Recommended to set it to C(false).
|
||||
required: false
|
||||
default: false
|
||||
is_public:
|
||||
description:
|
||||
- Register the template to be publicly available to all users.
|
||||
- Only used if C(state) is present.
|
||||
required: false
|
||||
default: false
|
||||
is_featured:
|
||||
description:
|
||||
- Register the template to be featured.
|
||||
- Only used if C(state) is present.
|
||||
required: false
|
||||
default: false
|
||||
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 C(state) is present.
|
||||
required: false
|
||||
default: false
|
||||
cross_zones:
|
||||
description:
|
||||
- Whether the template should be synced or removed across zones.
|
||||
- Only used if C(state) is present or absent.
|
||||
required: false
|
||||
default: false
|
||||
mode:
|
||||
description:
|
||||
- Mode for the template extraction.
|
||||
- Only used if C(state=extracted).
|
||||
required: false
|
||||
default: 'http_download'
|
||||
choices: [ 'http_download', 'ftp_upload' ]
|
||||
domain:
|
||||
description:
|
||||
- Domain the template, snapshot or VM is related to.
|
||||
required: false
|
||||
default: null
|
||||
account:
|
||||
description:
|
||||
- Account the template, snapshot or VM is related to.
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- Name of the project the template to be registered in.
|
||||
required: false
|
||||
default: null
|
||||
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.
|
||||
required: false
|
||||
default: null
|
||||
template_filter:
|
||||
description:
|
||||
- Name of the filter used to search for the template.
|
||||
required: false
|
||||
default: 'self'
|
||||
choices: [ 'featured', 'self', 'selfexecutable', 'sharedexecutable', 'executable', 'community' ]
|
||||
hypervisor:
|
||||
description:
|
||||
- Name the hypervisor to be used for creating the new template.
|
||||
- Relevant when using C(state=present).
|
||||
required: false
|
||||
default: null
|
||||
choices: [ 'KVM', 'VMware', 'BareMetal', 'XenServer', 'LXC', 'HyperV', 'UCS', 'OVM' ]
|
||||
requires_hvm:
|
||||
description:
|
||||
- true if this template requires HVM.
|
||||
required: false
|
||||
default: false
|
||||
password_enabled:
|
||||
description:
|
||||
- True if the template supports the password reset feature.
|
||||
required: false
|
||||
default: false
|
||||
template_tag:
|
||||
description:
|
||||
- the tag for this template.
|
||||
required: false
|
||||
default: null
|
||||
sshkey_enabled:
|
||||
description:
|
||||
- True if the template supports the sshkey upload feature.
|
||||
required: false
|
||||
default: false
|
||||
is_routing:
|
||||
description:
|
||||
- True if the template type is routing i.e., if template is used to deploy router.
|
||||
- Only considered if C(url) is used.
|
||||
required: false
|
||||
default: false
|
||||
format:
|
||||
description:
|
||||
- The format for the template.
|
||||
- Relevant when using C(state=present).
|
||||
required: false
|
||||
default: null
|
||||
choices: [ 'QCOW2', 'RAW', 'VHD', 'OVA' ]
|
||||
is_extractable:
|
||||
description:
|
||||
- True if the template or its derivatives are extractable.
|
||||
required: false
|
||||
default: false
|
||||
details:
|
||||
description:
|
||||
- Template details in key/value pairs.
|
||||
required: false
|
||||
default: null
|
||||
bits:
|
||||
description:
|
||||
- 32 or 64 bits support.
|
||||
required: false
|
||||
default: '64'
|
||||
display_text:
|
||||
description:
|
||||
- Display text of the template.
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- State of the template.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent', 'extacted' ]
|
||||
poll_async:
|
||||
description:
|
||||
- Poll async jobs until job has finished.
|
||||
required: false
|
||||
default: true
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Register a systemvm template
|
||||
- local_action:
|
||||
module: 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)
|
||||
|
||||
# Create a template from a stopped virtual machine's volume
|
||||
- local_action:
|
||||
module: cs_template
|
||||
name: debian-base-template
|
||||
vm: debian-base-vm
|
||||
os_type: Debian GNU/Linux 7(64-bit)
|
||||
zone: tokio-ix
|
||||
password_enabled: yes
|
||||
is_public: yes
|
||||
|
||||
# Create a template from a virtual machine's root volume snapshot
|
||||
- local_action:
|
||||
module: cs_template
|
||||
name: debian-base-template
|
||||
vm: debian-base-vm
|
||||
snapshot: ROOT-233_2015061509114
|
||||
os_type: Debian GNU/Linux 7(64-bit)
|
||||
zone: tokio-ix
|
||||
password_enabled: yes
|
||||
is_public: yes
|
||||
|
||||
# Remove a template
|
||||
- local_action:
|
||||
module: cs_template
|
||||
name: systemvm-4.2
|
||||
cross_zones: yes
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the template.
|
||||
returned: success
|
||||
type: string
|
||||
sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f
|
||||
name:
|
||||
description: Name of the template.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Debian 7 64-bit
|
||||
display_text:
|
||||
description: Display text of the template.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Debian 7.7 64-bit minimal 2015-03-19
|
||||
checksum:
|
||||
description: MD5 checksum of the template.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 0b31bccccb048d20b551f70830bb7ad0
|
||||
status:
|
||||
description: Status of the template.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Download Complete
|
||||
is_ready:
|
||||
description: True if the template is ready to be deployed from.
|
||||
returned: success
|
||||
type: boolean
|
||||
sample: true
|
||||
is_public:
|
||||
description: True if the template is public.
|
||||
returned: success
|
||||
type: boolean
|
||||
sample: true
|
||||
is_featured:
|
||||
description: True if the template is featured.
|
||||
returned: success
|
||||
type: boolean
|
||||
sample: true
|
||||
is_extractable:
|
||||
description: True if the template is extractable.
|
||||
returned: success
|
||||
type: boolean
|
||||
sample: true
|
||||
format:
|
||||
description: Format of the template.
|
||||
returned: success
|
||||
type: string
|
||||
sample: OVA
|
||||
os_type:
|
||||
description: Typo of the OS.
|
||||
returned: success
|
||||
type: string
|
||||
sample: CentOS 6.5 (64-bit)
|
||||
password_enabled:
|
||||
description: True if the reset password feature is enabled, false otherwise.
|
||||
returned: success
|
||||
type: boolean
|
||||
sample: false
|
||||
sshkey_enabled:
|
||||
description: true if template is sshkey enabled, false otherwise.
|
||||
returned: success
|
||||
type: boolean
|
||||
sample: false
|
||||
cross_zones:
|
||||
description: true if the template is managed across all zones, false otherwise.
|
||||
returned: success
|
||||
type: boolean
|
||||
sample: false
|
||||
template_type:
|
||||
description: Type of the template.
|
||||
returned: success
|
||||
type: string
|
||||
sample: USER
|
||||
created:
|
||||
description: Date of registering.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 2015-03-29T14:57:06+0200
|
||||
template_tag:
|
||||
description: Template tag related to this template.
|
||||
returned: success
|
||||
type: string
|
||||
sample: special
|
||||
hypervisor:
|
||||
description: Hypervisor related to this template.
|
||||
returned: success
|
||||
type: string
|
||||
sample: VMware
|
||||
mode:
|
||||
description: Mode of extraction
|
||||
returned: success
|
||||
type: string
|
||||
sample: http_download
|
||||
state:
|
||||
description: State of the extracted template
|
||||
returned: success
|
||||
type: string
|
||||
sample: DOWNLOAD_URL_CREATED
|
||||
url:
|
||||
description: Url to which the template is extracted to
|
||||
returned: success
|
||||
type: string
|
||||
sample: "http://1.2.3.4/userdata/eb307f13-4aca-45e8-b157-a414a14e6b04.ova"
|
||||
tags:
|
||||
description: List of resource tags associated with the template.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: '[ { "key": "foo", "value": "bar" } ]'
|
||||
zone:
|
||||
description: Name of zone the template is registered in.
|
||||
returned: success
|
||||
type: string
|
||||
sample: zuerich
|
||||
domain:
|
||||
description: Domain the template is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example domain
|
||||
account:
|
||||
description: Account the template is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example account
|
||||
project:
|
||||
description: Name of project the template is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Production
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
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',
|
||||
'tempaltetype': 'template_type',
|
||||
'ostypename': 'os_type',
|
||||
'crossZones': 'cross_zones',
|
||||
'isextractable': 'is_extractable',
|
||||
'isfeatured': 'is_featured',
|
||||
'ispublic': 'is_public',
|
||||
'format': 'format',
|
||||
'hypervisor': 'hypervisor',
|
||||
'url': 'url',
|
||||
'extractMode': 'mode',
|
||||
'state': 'state',
|
||||
}
|
||||
|
||||
|
||||
def _get_args(self):
|
||||
args = {}
|
||||
args['name'] = self.module.params.get('name')
|
||||
args['displaytext'] = self.get_or_fallback('display_text', 'name')
|
||||
args['bits'] = self.module.params.get('bits')
|
||||
args['isdynamicallyscalable'] = self.module.params.get('is_dynamically_scalable')
|
||||
args['isextractable'] = self.module.params.get('is_extractable')
|
||||
args['isfeatured'] = self.module.params.get('is_featured')
|
||||
args['ispublic'] = self.module.params.get('is_public')
|
||||
args['passwordenabled'] = self.module.params.get('password_enabled')
|
||||
args['requireshvm'] = self.module.params.get('requires_hvm')
|
||||
args['templatetag'] = self.module.params.get('template_tag')
|
||||
args['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 = {}
|
||||
args['account'] = self.get_account(key='name')
|
||||
args['domainid'] = self.get_domain(key='id')
|
||||
args['projectid'] = self.get_project(key='id')
|
||||
args['virtualmachineid'] = self.get_vm(key='id')
|
||||
args['type'] = "ROOT"
|
||||
|
||||
volumes = self.cs.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 = {}
|
||||
args['account'] = self.get_account(key='name')
|
||||
args['domainid'] = self.get_domain(key='id')
|
||||
args['projectid'] = self.get_project(key='id')
|
||||
args['volumeid'] = self.get_root_volume('id')
|
||||
snapshots = self.cs.listSnapshots(**args)
|
||||
if snapshots:
|
||||
for s in snapshots['snapshot']:
|
||||
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 create_template(self):
|
||||
template = self.get_template()
|
||||
if not template:
|
||||
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.cs.createTemplate(**args)
|
||||
|
||||
if 'errortext' in template:
|
||||
self.module.fail_json(msg="Failed: '%s'" % template['errortext'])
|
||||
|
||||
poll_async = self.module.params.get('poll_async')
|
||||
if poll_async:
|
||||
template = self.poll_job(template, 'template')
|
||||
return template
|
||||
|
||||
|
||||
def register_template(self):
|
||||
required_params = [
|
||||
'format',
|
||||
'url',
|
||||
'hypervisor',
|
||||
]
|
||||
self.module.fail_on_missing_params(required_params=required_params)
|
||||
template = self.get_template()
|
||||
if not template:
|
||||
self.result['changed'] = True
|
||||
args = self._get_args()
|
||||
args['url'] = self.module.params.get('url')
|
||||
args['format'] = self.module.params.get('format')
|
||||
args['checksum'] = self.module.params.get('checksum')
|
||||
args['isextractable'] = self.module.params.get('is_extractable')
|
||||
args['isrouting'] = self.module.params.get('is_routing')
|
||||
args['sshkeyenabled'] = self.module.params.get('sshkey_enabled')
|
||||
args['hypervisor'] = self.get_hypervisor()
|
||||
args['domainid'] = self.get_domain(key='id')
|
||||
args['account'] = self.get_account(key='name')
|
||||
args['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:
|
||||
res = self.cs.registerTemplate(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
template = res['template']
|
||||
return template
|
||||
|
||||
|
||||
def get_template(self):
|
||||
args = {}
|
||||
args['isready'] = self.module.params.get('is_ready')
|
||||
args['templatefilter'] = self.module.params.get('template_filter')
|
||||
args['domainid'] = self.get_domain(key='id')
|
||||
args['account'] = self.get_account(key='name')
|
||||
args['projectid'] = self.get_project(key='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')
|
||||
|
||||
templates = self.cs.listTemplates(**args)
|
||||
if templates:
|
||||
# if checksum is set, we only look on that.
|
||||
if not checksum:
|
||||
return templates['template'][0]
|
||||
else:
|
||||
for i in templates['template']:
|
||||
if 'checksum' in i and i['checksum'] == checksum:
|
||||
return i
|
||||
return None
|
||||
|
||||
|
||||
def extract_template(self):
|
||||
template = self.get_template()
|
||||
if not template:
|
||||
self.module.fail_json(msg="Failed: template not found")
|
||||
|
||||
args = {}
|
||||
args['id'] = template['id']
|
||||
args['url'] = self.module.params.get('url')
|
||||
args['mode'] = self.module.params.get('mode')
|
||||
args['zoneid'] = self.get_zone(key='id')
|
||||
|
||||
self.result['changed'] = True
|
||||
|
||||
if not self.module.check_mode:
|
||||
template = self.cs.extractTemplate(**args)
|
||||
|
||||
if 'errortext' in template:
|
||||
self.module.fail_json(msg="Failed: '%s'" % template['errortext'])
|
||||
|
||||
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 = {}
|
||||
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.cs.deleteTemplate(**args)
|
||||
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
poll_async = self.module.params.get('poll_async')
|
||||
if poll_async:
|
||||
res = self.poll_job(res, 'template')
|
||||
return template
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = cs_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
name = dict(required=True),
|
||||
display_text = dict(default=None),
|
||||
url = dict(default=None),
|
||||
vm = dict(default=None),
|
||||
snapshot = dict(default=None),
|
||||
os_type = dict(default=None),
|
||||
is_ready = dict(type='bool', default=False),
|
||||
is_public = dict(type='bool', default=True),
|
||||
is_featured = dict(type='bool', default=False),
|
||||
is_dynamically_scalable = dict(type='bool', default=False),
|
||||
is_extractable = dict(type='bool', default=False),
|
||||
is_routing = dict(type='bool', default=False),
|
||||
checksum = dict(default=None),
|
||||
template_filter = dict(default='self', choices=['featured', 'self', 'selfexecutable', 'sharedexecutable', 'executable', 'community']),
|
||||
hypervisor = dict(choices=CS_HYPERVISORS, default=None),
|
||||
requires_hvm = dict(type='bool', default=False),
|
||||
password_enabled = dict(type='bool', default=False),
|
||||
template_tag = dict(default=None),
|
||||
sshkey_enabled = dict(type='bool', default=False),
|
||||
format = dict(choices=['QCOW2', 'RAW', 'VHD', 'OVA'], default=None),
|
||||
details = dict(default=None),
|
||||
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(default=None),
|
||||
domain = dict(default=None),
|
||||
account = dict(default=None),
|
||||
project = dict(default=None),
|
||||
poll_async = dict(type='bool', default=True),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
mutually_exclusive = (
|
||||
['url', 'vm'],
|
||||
['zone', 'cross_zones'],
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
acs_tpl = AnsibleCloudStackTemplate(module)
|
||||
|
||||
state = module.params.get('state')
|
||||
if state in ['absent']:
|
||||
tpl = acs_tpl.remove_template()
|
||||
|
||||
elif state in ['extracted']:
|
||||
tpl = acs_tpl.extract_template()
|
||||
|
||||
else:
|
||||
if module.params.get('url'):
|
||||
tpl = acs_tpl.register_template()
|
||||
elif module.params.get('vm'):
|
||||
tpl = acs_tpl.create_template()
|
||||
else:
|
||||
module.fail_json(msg="one of the following is required on state=present: url,vm")
|
||||
|
||||
result = acs_tpl.get_result(tpl)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
455
lib/ansible/modules/cloud/cloudstack/cs_user.py
Normal file
455
lib/ansible/modules/cloud/cloudstack/cs_user.py
Normal file
@@ -0,0 +1,455 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_user
|
||||
short_description: Manages users on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Create, update, disable, lock, enable and remove users.
|
||||
version_added: '2.0'
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
username:
|
||||
description:
|
||||
- Username of the user.
|
||||
required: true
|
||||
account:
|
||||
description:
|
||||
- Account the user will be created under.
|
||||
- Required on C(state=present).
|
||||
required: false
|
||||
default: null
|
||||
password:
|
||||
description:
|
||||
- Password of the user to be created.
|
||||
- Required on C(state=present).
|
||||
- Only considered on creation and will not be updated if user exists.
|
||||
required: false
|
||||
default: null
|
||||
first_name:
|
||||
description:
|
||||
- First name of the user.
|
||||
- Required on C(state=present).
|
||||
required: false
|
||||
default: null
|
||||
last_name:
|
||||
description:
|
||||
- Last name of the user.
|
||||
- Required on C(state=present).
|
||||
required: false
|
||||
default: null
|
||||
email:
|
||||
description:
|
||||
- Email of the user.
|
||||
- Required on C(state=present).
|
||||
required: false
|
||||
default: null
|
||||
timezone:
|
||||
description:
|
||||
- Timezone of the user.
|
||||
required: false
|
||||
default: null
|
||||
domain:
|
||||
description:
|
||||
- Domain the user is related to.
|
||||
required: false
|
||||
default: 'ROOT'
|
||||
state:
|
||||
description:
|
||||
- State of the user.
|
||||
- C(unlocked) is an alias for C(enabled).
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent', 'enabled', 'disabled', 'locked', 'unlocked' ]
|
||||
poll_async:
|
||||
description:
|
||||
- Poll async jobs until job has finished.
|
||||
required: false
|
||||
default: true
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# create an user in domain 'CUSTOMERS'
|
||||
local_action:
|
||||
module: cs_user
|
||||
account: developers
|
||||
username: johndoe
|
||||
password: S3Cur3
|
||||
last_name: Doe
|
||||
first_name: John
|
||||
email: john.doe@example.com
|
||||
domain: CUSTOMERS
|
||||
|
||||
# Lock an existing user in domain 'CUSTOMERS'
|
||||
local_action:
|
||||
module: cs_user
|
||||
username: johndoe
|
||||
domain: CUSTOMERS
|
||||
state: locked
|
||||
|
||||
# Disable an existing user in domain 'CUSTOMERS'
|
||||
local_action:
|
||||
module: cs_user
|
||||
username: johndoe
|
||||
domain: CUSTOMERS
|
||||
state: disabled
|
||||
|
||||
# Enable/unlock an existing user in domain 'CUSTOMERS'
|
||||
local_action:
|
||||
module: cs_user
|
||||
username: johndoe
|
||||
domain: CUSTOMERS
|
||||
state: enabled
|
||||
|
||||
# Remove an user in domain 'CUSTOMERS'
|
||||
local_action:
|
||||
module: cs_user
|
||||
name: customer_xy
|
||||
domain: CUSTOMERS
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the user.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 87b1e0ce-4e01-11e4-bb66-0050569e64b8
|
||||
username:
|
||||
description: Username of the user.
|
||||
returned: success
|
||||
type: string
|
||||
sample: johndoe
|
||||
fist_name:
|
||||
description: First name of the user.
|
||||
returned: success
|
||||
type: string
|
||||
sample: John
|
||||
last_name:
|
||||
description: Last name of the user.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Doe
|
||||
email:
|
||||
description: Emailof the user.
|
||||
returned: success
|
||||
type: string
|
||||
sample: john.doe@example.com
|
||||
api_key:
|
||||
description: API key of the user.
|
||||
returned: success
|
||||
type: string
|
||||
sample: JLhcg8VWi8DoFqL2sSLZMXmGojcLnFrOBTipvBHJjySODcV4mCOo29W2duzPv5cALaZnXj5QxDx3xQfaQt3DKg
|
||||
api_secret:
|
||||
description: API secret of the user.
|
||||
returned: success
|
||||
type: string
|
||||
sample: FUELo3LB9fa1UopjTLPdqLv_6OXQMJZv9g9N4B_Ao3HFz8d6IGFCV9MbPFNM8mwz00wbMevja1DoUNDvI8C9-g
|
||||
account:
|
||||
description: Account name of the user.
|
||||
returned: success
|
||||
type: string
|
||||
sample: developers
|
||||
account_type:
|
||||
description: Type of the account.
|
||||
returned: success
|
||||
type: string
|
||||
sample: user
|
||||
timezone:
|
||||
description: Timezone of the user.
|
||||
returned: success
|
||||
type: string
|
||||
sample: enabled
|
||||
created:
|
||||
description: Date the user was created.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Doe
|
||||
state:
|
||||
description: State of the user.
|
||||
returned: success
|
||||
type: string
|
||||
sample: enabled
|
||||
domain:
|
||||
description: Domain the user is related.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ROOT
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
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': 'api_secret',
|
||||
'apikey': '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 = {}
|
||||
args['domainid'] = self.get_domain('id')
|
||||
users = self.cs.listUsers(**args)
|
||||
if users:
|
||||
user_name = self.module.params.get('username')
|
||||
for u in users['user']:
|
||||
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 = {}
|
||||
args['id'] = user['id']
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.enableUser(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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 = {}
|
||||
args['id'] = user['id']
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.lockUser(**args)
|
||||
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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 = {}
|
||||
args['id'] = user['id']
|
||||
if not self.module.check_mode:
|
||||
user = self.cs.disableUser(**args)
|
||||
if 'errortext' in user:
|
||||
self.module.fail_json(msg="Failed: '%s'" % user['errortext'])
|
||||
|
||||
poll_async = self.module.params.get('poll_async')
|
||||
if poll_async:
|
||||
user = self.poll_job(user, 'user')
|
||||
return user
|
||||
|
||||
|
||||
def present_user(self):
|
||||
missing_params = []
|
||||
for required_params in [
|
||||
'account',
|
||||
'email',
|
||||
'password',
|
||||
'first_name',
|
||||
'last_name',
|
||||
]:
|
||||
if not self.module.params.get(required_params):
|
||||
missing_params.append(required_params)
|
||||
if missing_params:
|
||||
self.module.fail_json(msg="missing required arguments: %s" % ','.join(missing_params))
|
||||
|
||||
user = self.get_user()
|
||||
if user:
|
||||
user = self._update_user(user)
|
||||
else:
|
||||
user = self._create_user(user)
|
||||
return user
|
||||
|
||||
|
||||
def _create_user(self, user):
|
||||
self.result['changed'] = True
|
||||
|
||||
args = {}
|
||||
args['account'] = self.get_account(key='name')
|
||||
args['domainid'] = self.get_domain('id')
|
||||
args['username'] = self.module.params.get('username')
|
||||
args['password'] = self.module.params.get('password')
|
||||
args['firstname'] = self.module.params.get('first_name')
|
||||
args['lastname'] = self.module.params.get('last_name')
|
||||
args['email'] = self.module.params.get('email')
|
||||
args['timezone'] = self.module.params.get('timezone')
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.createUser(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
user = res['user']
|
||||
# register user api keys
|
||||
res = self.cs.registerUserKeys(id=user['id'])
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
user.update(res['userkeys'])
|
||||
return user
|
||||
|
||||
|
||||
def _update_user(self, user):
|
||||
args = {}
|
||||
args['id'] = user['id']
|
||||
args['firstname'] = self.module.params.get('first_name')
|
||||
args['lastname'] = self.module.params.get('last_name')
|
||||
args['email'] = self.module.params.get('email')
|
||||
args['timezone'] = self.module.params.get('timezone')
|
||||
if self.has_changed(args, user):
|
||||
self.result['changed'] = True
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.updateUser(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
user = res['user']
|
||||
# register user api keys
|
||||
if 'apikey' not in user:
|
||||
self.result['changed'] = True
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.registerUserKeys(id=user['id'])
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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:
|
||||
res = self.cs.deleteUser(id=user['id'])
|
||||
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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
|
||||
return self.result
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = cs_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
username = dict(required=True),
|
||||
account = dict(default=None),
|
||||
state = dict(choices=['present', 'absent', 'enabled', 'disabled', 'locked', 'unlocked'], default='present'),
|
||||
domain = dict(default='ROOT'),
|
||||
email = dict(default=None),
|
||||
first_name = dict(default=None),
|
||||
last_name = dict(default=None),
|
||||
password = dict(default=None, no_log=True),
|
||||
timezone = dict(default=None),
|
||||
poll_async = dict(type='bool', default=True),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
acs_acc = AnsibleCloudStackUser(module)
|
||||
|
||||
state = module.params.get('state')
|
||||
|
||||
if state in ['absent']:
|
||||
user = acs_acc.absent_user()
|
||||
|
||||
elif state in ['enabled', 'unlocked']:
|
||||
user = acs_acc.enable_user()
|
||||
|
||||
elif state in ['disabled']:
|
||||
user = acs_acc.disable_user()
|
||||
|
||||
elif state in ['locked']:
|
||||
user = acs_acc.lock_user()
|
||||
|
||||
else:
|
||||
user = acs_acc.present_user()
|
||||
|
||||
result = acs_acc.get_result(user)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
304
lib/ansible/modules/cloud/cloudstack/cs_vmsnapshot.py
Normal file
304
lib/ansible/modules/cloud/cloudstack/cs_vmsnapshot.py
Normal file
@@ -0,0 +1,304 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_vmsnapshot
|
||||
short_description: Manages VM snapshots on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Create, remove and revert VM from snapshots.
|
||||
version_added: '2.0'
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Unique Name of the snapshot. In CloudStack terms display name.
|
||||
required: true
|
||||
aliases: ['display_name']
|
||||
vm:
|
||||
description:
|
||||
- Name of the virtual machine.
|
||||
required: true
|
||||
description:
|
||||
description:
|
||||
- Description of the snapshot.
|
||||
required: false
|
||||
default: null
|
||||
snapshot_memory:
|
||||
description:
|
||||
- Snapshot memory if set to true.
|
||||
required: false
|
||||
default: false
|
||||
zone:
|
||||
description:
|
||||
- Name of the zone in which the VM is in. If not set, default zone is used.
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- Name of the project the VM is assigned to.
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- State of the snapshot.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent', 'revert' ]
|
||||
domain:
|
||||
description:
|
||||
- Domain the VM snapshot is related to.
|
||||
required: false
|
||||
default: null
|
||||
account:
|
||||
description:
|
||||
- Account the VM snapshot is related to.
|
||||
required: false
|
||||
default: null
|
||||
poll_async:
|
||||
description:
|
||||
- Poll async jobs until job has finished.
|
||||
required: false
|
||||
default: true
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create a VM snapshot of disk and memory before an upgrade
|
||||
- local_action:
|
||||
module: cs_vmsnapshot
|
||||
name: Snapshot before upgrade
|
||||
vm: web-01
|
||||
snapshot_memory: yes
|
||||
|
||||
# Revert a VM to a snapshot after a failed upgrade
|
||||
- local_action:
|
||||
module: cs_vmsnapshot
|
||||
name: Snapshot before upgrade
|
||||
vm: web-01
|
||||
state: revert
|
||||
|
||||
# Remove a VM snapshot after successful upgrade
|
||||
- local_action:
|
||||
module: cs_vmsnapshot
|
||||
name: Snapshot before upgrade
|
||||
vm: web-01
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the snapshot.
|
||||
returned: success
|
||||
type: string
|
||||
sample: a6f7a5fc-43f8-11e5-a151-feff819cdc9f
|
||||
name:
|
||||
description: Name of the snapshot.
|
||||
returned: success
|
||||
type: string
|
||||
sample: snapshot before update
|
||||
display_name:
|
||||
description: Display name of the snapshot.
|
||||
returned: success
|
||||
type: string
|
||||
sample: snapshot before update
|
||||
created:
|
||||
description: date of the snapshot.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 2015-03-29T14:57:06+0200
|
||||
current:
|
||||
description: true if snapshot is current
|
||||
returned: success
|
||||
type: boolean
|
||||
sample: True
|
||||
state:
|
||||
description: state of the vm snapshot
|
||||
returned: success
|
||||
type: string
|
||||
sample: Allocated
|
||||
type:
|
||||
description: type of vm snapshot
|
||||
returned: success
|
||||
type: string
|
||||
sample: DiskAndMemory
|
||||
description:
|
||||
description: description of vm snapshot
|
||||
returned: success
|
||||
type: string
|
||||
sample: snapshot brought to you by Ansible
|
||||
domain:
|
||||
description: Domain the the vm snapshot is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example domain
|
||||
account:
|
||||
description: Account the vm snapshot is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example account
|
||||
project:
|
||||
description: Name of project the vm snapshot is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Production
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
class AnsibleCloudStackVmSnapshot(AnsibleCloudStack):
|
||||
|
||||
def __init__(self, module):
|
||||
super(AnsibleCloudStackVmSnapshot, self).__init__(module)
|
||||
self.returns = {
|
||||
'type': 'type',
|
||||
'current': 'current',
|
||||
}
|
||||
|
||||
|
||||
def get_snapshot(self):
|
||||
args = {}
|
||||
args['virtualmachineid'] = self.get_vm('id')
|
||||
args['account'] = self.get_account('name')
|
||||
args['domainid'] = self.get_domain('id')
|
||||
args['projectid'] = self.get_project('id')
|
||||
args['name'] = self.module.params.get('name')
|
||||
|
||||
snapshots = self.cs.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 = {}
|
||||
args['virtualmachineid'] = self.get_vm('id')
|
||||
args['name'] = self.module.params.get('name')
|
||||
args['description'] = self.module.params.get('description')
|
||||
args['snapshotmemory'] = self.module.params.get('snapshot_memory')
|
||||
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.createVMSnapshot(**args)
|
||||
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
poll_async = self.module.params.get('poll_async')
|
||||
if res and poll_async:
|
||||
snapshot = self.poll_job(res, 'vmsnapshot')
|
||||
|
||||
return snapshot
|
||||
|
||||
|
||||
def remove_snapshot(self):
|
||||
snapshot = self.get_snapshot()
|
||||
if snapshot:
|
||||
self.result['changed'] = True
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.deleteVMSnapshot(vmsnapshotid=snapshot['id'])
|
||||
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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.cs.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(default=None),
|
||||
zone = dict(default=None),
|
||||
snapshot_memory = dict(type='bool', default=False),
|
||||
state = dict(choices=['present', 'absent', 'revert'], default='present'),
|
||||
domain = dict(default=None),
|
||||
account = dict(default=None),
|
||||
project = dict(default=None),
|
||||
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,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
496
lib/ansible/modules/cloud/cloudstack/cs_volume.py
Normal file
496
lib/ansible/modules/cloud/cloudstack/cs_volume.py
Normal file
@@ -0,0 +1,496 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, Jefferson Girão <jefferson@girao.net>
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_volume
|
||||
short_description: Manages volumes on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Create, destroy, attach, detach volumes.
|
||||
version_added: "2.1"
|
||||
author:
|
||||
- "Jefferson Girão (@jeffersongirao)"
|
||||
- "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the volume.
|
||||
- C(name) can only contain ASCII letters.
|
||||
required: true
|
||||
account:
|
||||
description:
|
||||
- Account the volume is related to.
|
||||
required: false
|
||||
default: null
|
||||
custom_id:
|
||||
description:
|
||||
- Custom id to the resource.
|
||||
- Allowed to Root Admins only.
|
||||
required: false
|
||||
default: null
|
||||
disk_offering:
|
||||
description:
|
||||
- Name of the disk offering to be used.
|
||||
- Required one of C(disk_offering), C(snapshot) if volume is not already C(state=present).
|
||||
required: false
|
||||
default: null
|
||||
display_volume:
|
||||
description:
|
||||
- Whether to display the volume to the end user or not.
|
||||
- Allowed to Root Admins only.
|
||||
required: false
|
||||
default: true
|
||||
domain:
|
||||
description:
|
||||
- Name of the domain the volume to be deployed in.
|
||||
required: false
|
||||
default: null
|
||||
max_iops:
|
||||
description:
|
||||
- Max iops
|
||||
required: false
|
||||
default: null
|
||||
min_iops:
|
||||
description:
|
||||
- Min iops
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- Name of the project the volume to be deployed in.
|
||||
required: false
|
||||
default: null
|
||||
size:
|
||||
description:
|
||||
- Size of disk in GB
|
||||
required: false
|
||||
default: null
|
||||
snapshot:
|
||||
description:
|
||||
- The snapshot name for the disk volume.
|
||||
- Required one of C(disk_offering), C(snapshot) if volume is not already C(state=present).
|
||||
required: false
|
||||
default: null
|
||||
force:
|
||||
description:
|
||||
- Force removal of volume even it is attached to a VM.
|
||||
- Considered on C(state=absnet) only.
|
||||
required: false
|
||||
default: false
|
||||
shrink_ok:
|
||||
description:
|
||||
- Whether to allow to shrink the volume.
|
||||
required: false
|
||||
default: false
|
||||
vm:
|
||||
description:
|
||||
- Name of the virtual machine to attach the volume to.
|
||||
required: false
|
||||
default: null
|
||||
zone:
|
||||
description:
|
||||
- Name of the zone in which the volume should be deployed.
|
||||
- If not set, default zone is used.
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- State of the volume.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'absent', 'attached', 'detached' ]
|
||||
poll_async:
|
||||
description:
|
||||
- Poll async jobs until job has finished.
|
||||
required: false
|
||||
default: true
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Create volume within project, zone with specified storage options
|
||||
- local_action:
|
||||
module: cs_volume
|
||||
name: web-vm-1-volume
|
||||
project: Integration
|
||||
zone: ch-zrh-ix-01
|
||||
disk_offering: PerfPlus Storage
|
||||
size: 20
|
||||
|
||||
# Create/attach volume to instance
|
||||
- local_action:
|
||||
module: cs_volume
|
||||
name: web-vm-1-volume
|
||||
disk_offering: PerfPlus Storage
|
||||
size: 20
|
||||
vm: web-vm-1
|
||||
state: attached
|
||||
|
||||
# Detach volume
|
||||
- local_action:
|
||||
module: cs_volume
|
||||
name: web-vm-1-volume
|
||||
state: detached
|
||||
|
||||
# Remove volume
|
||||
- local_action:
|
||||
module: cs_volume
|
||||
name: web-vm-1-volume
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
id:
|
||||
description: ID of the volume.
|
||||
returned: success
|
||||
type: string
|
||||
sample:
|
||||
name:
|
||||
description: Name of the volume.
|
||||
returned: success
|
||||
type: string
|
||||
sample: web-volume-01
|
||||
display_name:
|
||||
description: Display name of the volume.
|
||||
returned: success
|
||||
type: string
|
||||
sample: web-volume-01
|
||||
group:
|
||||
description: Group the volume belongs to
|
||||
returned: success
|
||||
type: string
|
||||
sample: web
|
||||
domain:
|
||||
description: Domain the volume belongs to
|
||||
returned: success
|
||||
type: string
|
||||
sample: example domain
|
||||
project:
|
||||
description: Project the volume belongs to
|
||||
returned: success
|
||||
type: string
|
||||
sample: Production
|
||||
zone:
|
||||
description: Name of zone the volume is in.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ch-gva-2
|
||||
created:
|
||||
description: Date of the volume was created.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 2014-12-01T14:57:57+0100
|
||||
attached:
|
||||
description: Date of the volume was attached.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 2014-12-01T14:57:57+0100
|
||||
type:
|
||||
description: Disk volume type.
|
||||
returned: success
|
||||
type: string
|
||||
sample: DATADISK
|
||||
size:
|
||||
description: Size of disk volume.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 20
|
||||
vm:
|
||||
description: Name of the vm the volume is attached to (not returned when detached)
|
||||
returned: success
|
||||
type: string
|
||||
sample: web-01
|
||||
state:
|
||||
description: State of the volume
|
||||
returned: success
|
||||
type: string
|
||||
sample: Attached
|
||||
device_id:
|
||||
description: Id of the device on user vm the volume is attached to (not returned when detached)
|
||||
returned: success
|
||||
type: string
|
||||
sample: 1
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
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',
|
||||
}
|
||||
self.volume = None
|
||||
|
||||
#TODO implement in cloudstack utils
|
||||
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.cs.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.module.fail_json(msg="Disk offering '%s' not found" % disk_offering)
|
||||
|
||||
|
||||
def get_volume(self):
|
||||
if not self.volume:
|
||||
args = {}
|
||||
args['account'] = self.get_account(key='name')
|
||||
args['domainid'] = self.get_domain(key='id')
|
||||
args['projectid'] = self.get_project(key='id')
|
||||
args['zoneid'] = self.get_zone(key='id')
|
||||
args['displayvolume'] = self.module.params.get('display_volume')
|
||||
args['type'] = 'DATADISK'
|
||||
|
||||
volumes = self.cs.listVolumes(**args)
|
||||
if volumes:
|
||||
volume_name = self.module.params.get('name')
|
||||
for v in volumes['volume']:
|
||||
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 = {}
|
||||
args['name'] = snapshot
|
||||
args['account'] = self.get_account('name')
|
||||
args['domainid'] = self.get_domain('id')
|
||||
args['projectid'] = self.get_project('id')
|
||||
|
||||
snapshots = self.cs.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 = {}
|
||||
args['name'] = self.module.params.get('name')
|
||||
args['account'] = self.get_account(key='name')
|
||||
args['domainid'] = self.get_domain(key='id')
|
||||
args['diskofferingid'] = disk_offering_id
|
||||
args['displayvolume'] = self.module.params.get('display_volume')
|
||||
args['maxiops'] = self.module.params.get('max_iops')
|
||||
args['miniops'] = self.module.params.get('min_iops')
|
||||
args['projectid'] = self.get_project(key='id')
|
||||
args['size'] = self.module.params.get('size')
|
||||
args['snapshotid'] = snapshot_id
|
||||
args['zoneid'] = self.get_zone(key='id')
|
||||
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.createVolume(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
poll_async = self.module.params.get('poll_async')
|
||||
if poll_async:
|
||||
volume = self.poll_job(res, '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 = {}
|
||||
args['id'] = volume['id']
|
||||
args['virtualmachineid'] = self.get_vm(key='id')
|
||||
args['deviceid'] = self.module.params.get('device_id')
|
||||
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.attachVolume(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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.cs.detachVolume(id=volume['id'])
|
||||
if 'errortext' in volume:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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.cs.deleteVolume(id=volume['id'])
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
poll_async = self.module.params.get('poll_async')
|
||||
if poll_async:
|
||||
res = self.poll_job(res, 'volume')
|
||||
|
||||
return volume
|
||||
|
||||
|
||||
def update_volume(self, volume):
|
||||
args_resize = {}
|
||||
args_resize['id'] = volume['id']
|
||||
args_resize['diskofferingid'] = self.get_disk_offering(key='id')
|
||||
args_resize['maxiops'] = self.module.params.get('max_iops')
|
||||
args_resize['miniops'] = self.module.params.get('min_iops')
|
||||
args_resize['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.cs.resizeVolume(**args_resize)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
poll_async = self.module.params.get('poll_async')
|
||||
if poll_async:
|
||||
volume = self.poll_job(res, 'volume')
|
||||
self.volume = volume
|
||||
|
||||
return volume
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = cs_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
name = dict(required=True),
|
||||
disk_offering = dict(default=None),
|
||||
display_volume = dict(type='bool', default=None),
|
||||
max_iops = dict(type='int', default=None),
|
||||
min_iops = dict(type='int', default=None),
|
||||
size = dict(type='int', default=None),
|
||||
snapshot = dict(default=None),
|
||||
vm = dict(default=None),
|
||||
device_id = dict(type='int', default=None),
|
||||
custom_id = dict(default=None),
|
||||
force = dict(type='bool', default=False),
|
||||
shrink_ok = dict(type='bool', default=False),
|
||||
state = dict(choices=['present', 'absent', 'attached', 'detached'], default='present'),
|
||||
zone = dict(default=None),
|
||||
domain = dict(default=None),
|
||||
account = dict(default=None),
|
||||
project = dict(default=None),
|
||||
poll_async = dict(type='bool', default=True),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
mutually_exclusive = (
|
||||
['snapshot', 'disk_offering'],
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
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()
|
||||
else:
|
||||
volume = acs_vol.present_volume()
|
||||
|
||||
result = acs_vol.get_result(volume)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
391
lib/ansible/modules/cloud/cloudstack/cs_vpc.py
Normal file
391
lib/ansible/modules/cloud/cloudstack/cs_vpc.py
Normal file
@@ -0,0 +1,391 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it an/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http//www.gnu.or/license/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_vpc
|
||||
short_description: "Manages VPCs on Apache CloudStack based clouds."
|
||||
description:
|
||||
- "Create, update and delete VPCs."
|
||||
version_added: "2.3"
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- "Name of the VPC."
|
||||
required: true
|
||||
display_text:
|
||||
description:
|
||||
- "Display text of the VPC."
|
||||
- "If not set, C(name) will be used for creating."
|
||||
required: false
|
||||
default: null
|
||||
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 C(state=present)."
|
||||
required: false
|
||||
default: null
|
||||
network_domain:
|
||||
description:
|
||||
- "Network domain for the VPC."
|
||||
- "All networks inside the VPC will belong to this domain."
|
||||
required: false
|
||||
default: null
|
||||
vpc_offering:
|
||||
description:
|
||||
- "Name of the VPC offering."
|
||||
- "If not set, default VPC offering is used."
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- "State of the VPC."
|
||||
required: false
|
||||
default: present
|
||||
choices:
|
||||
- present
|
||||
- absent
|
||||
- restarted
|
||||
domain:
|
||||
description:
|
||||
- "Domain the VPC is related to."
|
||||
required: false
|
||||
default: null
|
||||
account:
|
||||
description:
|
||||
- "Account the VPC is related to."
|
||||
required: false
|
||||
default: null
|
||||
project:
|
||||
description:
|
||||
- "Name of the project the VPC is related to."
|
||||
required: false
|
||||
default: null
|
||||
zone:
|
||||
description:
|
||||
- "Name of the zone."
|
||||
- "If not set, default zone is used."
|
||||
required: false
|
||||
default: null
|
||||
tags:
|
||||
description:
|
||||
- "List of tags. Tags are a list of dictionaries having keys C(key) and C(value)."
|
||||
- "For deleting all tags, set an empty list e.g. C(tags: [])."
|
||||
required: false
|
||||
default: null
|
||||
aliases:
|
||||
- tag
|
||||
poll_async:
|
||||
description:
|
||||
- "Poll async jobs until job has finished."
|
||||
required: false
|
||||
default: true
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Ensure a VPC is present
|
||||
- local_action:
|
||||
module: cs_vpc
|
||||
name: my_vpc
|
||||
display_text: My example VPC
|
||||
cidr: 10.10.0.0/16
|
||||
|
||||
# Ensure a VPC is absent
|
||||
- local_action:
|
||||
module: cs_vpc
|
||||
name: my_vpc
|
||||
state: absent
|
||||
|
||||
# Ensure a VPC is restarted
|
||||
- local_action:
|
||||
module: cs_vpc
|
||||
name: my_vpc
|
||||
state: restarted
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: "UUID of the VPC."
|
||||
returned: success
|
||||
type: string
|
||||
sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6
|
||||
name:
|
||||
description: "Name of the VPC."
|
||||
returned: success
|
||||
type: string
|
||||
sample: my_vpc
|
||||
display_text:
|
||||
description: "Display text of the VPC."
|
||||
returned: success
|
||||
type: string
|
||||
sample: My example VPC
|
||||
cidr:
|
||||
description: "CIDR of the VPC."
|
||||
returned: success
|
||||
type: string
|
||||
sample: 10.10.0.0/16
|
||||
network_domain:
|
||||
description: "Network domain of the VPC."
|
||||
returned: success
|
||||
type: string
|
||||
sample: example.com
|
||||
region_level_vpc:
|
||||
description: "Whether the VPC is region level or not."
|
||||
returned: success
|
||||
type: boolean
|
||||
sample: true
|
||||
restart_required:
|
||||
description: "Wheter the VPC router needs a restart or not."
|
||||
returned: success
|
||||
type: boolean
|
||||
sample: true
|
||||
distributed_vpc_router:
|
||||
description: "Whether the VPC uses distributed router or not."
|
||||
returned: success
|
||||
type: boolean
|
||||
sample: true
|
||||
redundant_vpc_router:
|
||||
description: "Whether the VPC has redundant routers or not."
|
||||
returned: success
|
||||
type: boolean
|
||||
sample: true
|
||||
domain:
|
||||
description: "Domain the VPC is related to."
|
||||
returned: success
|
||||
type: string
|
||||
sample: example domain
|
||||
account:
|
||||
description: "Account the VPC is related to."
|
||||
returned: success
|
||||
type: string
|
||||
sample: example account
|
||||
project:
|
||||
description: "Name of project the VPC is related to."
|
||||
returned: success
|
||||
type: string
|
||||
sample: Production
|
||||
zone:
|
||||
description: "Name of zone the VPC is in."
|
||||
returned: success
|
||||
type: string
|
||||
sample: ch-gva-2
|
||||
state:
|
||||
description: "State of the VPC."
|
||||
returned: success
|
||||
type: string
|
||||
sample: Enabled
|
||||
tags:
|
||||
description: "List of resource tags associated with the VPC."
|
||||
returned: success
|
||||
type: dict
|
||||
sample: '[ { "key": "foo", "value": "bar" } ]'
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
|
||||
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
|
||||
self.vpc_offering = None
|
||||
|
||||
def get_vpc_offering(self, key=None):
|
||||
if self.vpc_offering:
|
||||
return self._get_by_key(key, self.vpc_offering)
|
||||
|
||||
vpc_offering = self.module.params.get('vpc_offering')
|
||||
args = {}
|
||||
if vpc_offering:
|
||||
args['name'] = vpc_offering
|
||||
else:
|
||||
args['isdefault'] = True
|
||||
|
||||
vpc_offerings = self.cs.listVPCOfferings(**args)
|
||||
if vpc_offerings:
|
||||
self.vpc_offering = vpc_offerings['vpcoffering'][0]
|
||||
return self._get_by_key(key, self.vpc_offering)
|
||||
self.module.fail_json(msg="VPC offering '%s' not found" % vpc_offering)
|
||||
|
||||
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'),
|
||||
}
|
||||
vpcs = self.cs.listVPCs()
|
||||
if vpcs:
|
||||
vpc_name = self.module.params.get('name')
|
||||
for v in vpcs['vpc']:
|
||||
if vpc_name.lower() in [ v['name'].lower(), v['id']]:
|
||||
self.vpc = v
|
||||
break
|
||||
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'],
|
||||
}
|
||||
res = self.cs.restartVPC(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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'),
|
||||
'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'),
|
||||
}
|
||||
self.result['diff']['after'] = args
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.createVPC(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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.cs.updateVPC(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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.cs.deleteVPC(id=vpc['id'])
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
|
||||
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(default=None),
|
||||
display_text=dict(default=None),
|
||||
vpc_offering=dict(default=None),
|
||||
network_domain=dict(default=None),
|
||||
state=dict(choices=['present', 'absent', 'restarted'], default='present'),
|
||||
domain=dict(default=None),
|
||||
account=dict(default=None),
|
||||
project=dict(default=None),
|
||||
zone=dict(default=None),
|
||||
tags=dict(type='list', aliases=['tag'], default=None),
|
||||
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,
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
406
lib/ansible/modules/cloud/cloudstack/cs_zone.py
Normal file
406
lib/ansible/modules/cloud/cloudstack/cs_zone.py
Normal file
@@ -0,0 +1,406 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: cs_zone
|
||||
short_description: Manages zones on Apache CloudStack based clouds.
|
||||
description:
|
||||
- Create, update and remove zones.
|
||||
version_added: "2.1"
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the zone.
|
||||
required: true
|
||||
id:
|
||||
description:
|
||||
- uuid of the exising zone.
|
||||
default: null
|
||||
required: false
|
||||
state:
|
||||
description:
|
||||
- State of the zone.
|
||||
required: false
|
||||
default: 'present'
|
||||
choices: [ 'present', 'enabled', 'disabled', 'absent' ]
|
||||
domain:
|
||||
description:
|
||||
- Domain the zone is related to.
|
||||
- Zone is a public zone if not set.
|
||||
required: false
|
||||
default: null
|
||||
network_domain:
|
||||
description:
|
||||
- Network domain for the zone.
|
||||
required: false
|
||||
default: null
|
||||
network_type:
|
||||
description:
|
||||
- Network type of the zone.
|
||||
required: false
|
||||
default: basic
|
||||
choices: [ 'basic', 'advanced' ]
|
||||
dns1:
|
||||
description:
|
||||
- First DNS for the zone.
|
||||
- Required if C(state=present)
|
||||
required: false
|
||||
default: null
|
||||
dns2:
|
||||
description:
|
||||
- Second DNS for the zone.
|
||||
required: false
|
||||
default: null
|
||||
internal_dns1:
|
||||
description:
|
||||
- First internal DNS for the zone.
|
||||
- If not set C(dns1) will be used on C(state=present).
|
||||
required: false
|
||||
default: null
|
||||
internal_dns2:
|
||||
description:
|
||||
- Second internal DNS for the zone.
|
||||
required: false
|
||||
default: null
|
||||
dns1_ipv6:
|
||||
description:
|
||||
- First DNS for IPv6 for the zone.
|
||||
required: false
|
||||
default: null
|
||||
dns2_ipv6:
|
||||
description:
|
||||
- Second DNS for IPv6 for the zone.
|
||||
required: false
|
||||
default: null
|
||||
guest_cidr_address:
|
||||
description:
|
||||
- Guest CIDR address for the zone.
|
||||
required: false
|
||||
default: null
|
||||
dhcp_provider:
|
||||
description:
|
||||
- DHCP provider for the Zone.
|
||||
required: false
|
||||
default: null
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Ensure a zone is present
|
||||
- local_action:
|
||||
module: cs_zone
|
||||
name: ch-zrh-ix-01
|
||||
dns1: 8.8.8.8
|
||||
dns2: 8.8.4.4
|
||||
network_type: basic
|
||||
|
||||
# Ensure a zone is disabled
|
||||
- local_action:
|
||||
module: cs_zone
|
||||
name: ch-zrh-ix-01
|
||||
state: disabled
|
||||
|
||||
# Ensure a zone is enabled
|
||||
- local_action:
|
||||
module: cs_zone
|
||||
name: ch-zrh-ix-01
|
||||
state: enabled
|
||||
|
||||
# Ensure a zone is absent
|
||||
- local_action:
|
||||
module: cs_zone
|
||||
name: ch-zrh-ix-01
|
||||
state: absent
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
id:
|
||||
description: UUID of the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6
|
||||
name:
|
||||
description: Name of the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: zone01
|
||||
dns1:
|
||||
description: First DNS for the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 8.8.8.8
|
||||
dns2:
|
||||
description: Second DNS for the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 8.8.4.4
|
||||
internal_dns1:
|
||||
description: First internal DNS for the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 8.8.8.8
|
||||
internal_dns2:
|
||||
description: Second internal DNS for the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 8.8.4.4
|
||||
dns1_ipv6:
|
||||
description: First IPv6 DNS for the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: "2001:4860:4860::8888"
|
||||
dns2_ipv6:
|
||||
description: Second IPv6 DNS for the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: "2001:4860:4860::8844"
|
||||
allocation_state:
|
||||
description: State of the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Enabled
|
||||
domain:
|
||||
description: Domain the zone is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ROOT
|
||||
network_domain:
|
||||
description: Network domain for the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example.com
|
||||
network_type:
|
||||
description: Network type for the zone.
|
||||
returned: success
|
||||
type: string
|
||||
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: string
|
||||
sample: 10.1.1.0/24
|
||||
dhcp_provider:
|
||||
description: DHCP provider for the zone
|
||||
returned: success
|
||||
type: string
|
||||
sample: VirtualRouter
|
||||
zone_token:
|
||||
description: Zone token
|
||||
returned: success
|
||||
type: string
|
||||
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" } ]
|
||||
'''
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
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 = {}
|
||||
args['name'] = self.module.params.get('name')
|
||||
args['dns1'] = self.module.params.get('dns1')
|
||||
args['dns2'] = self.module.params.get('dns2')
|
||||
args['internaldns1'] = self.get_or_fallback('internal_dns1', 'dns1')
|
||||
args['internaldns2'] = self.get_or_fallback('internal_dns2', 'dns2')
|
||||
args['ipv6dns1'] = self.module.params.get('dns1_ipv6')
|
||||
args['ipv6dns2'] = self.module.params.get('dns2_ipv6')
|
||||
args['networktype'] = self.module.params.get('network_type')
|
||||
args['domain'] = self.module.params.get('network_domain')
|
||||
args['localstorageenabled'] = self.module.params.get('local_storage_enabled')
|
||||
args['guestcidraddress'] = self.module.params.get('guest_cidr_address')
|
||||
args['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.cs.listZones(**args)
|
||||
if zones:
|
||||
self.zone = zones['zone'][0]
|
||||
return self.zone
|
||||
|
||||
args['name'] = self.module.params.get('name')
|
||||
zones = self.cs.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.cs.createZone(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
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.cs.updateZone(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
zone = res['zone']
|
||||
return zone
|
||||
|
||||
|
||||
def absent_zone(self):
|
||||
zone = self.get_zone()
|
||||
if zone:
|
||||
self.result['changed'] = True
|
||||
|
||||
args = {}
|
||||
args['id'] = zone['id']
|
||||
|
||||
if not self.module.check_mode:
|
||||
res = self.cs.deleteZone(**args)
|
||||
if 'errortext' in res:
|
||||
self.module.fail_json(msg="Failed: '%s'" % res['errortext'])
|
||||
return zone
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = cs_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
id = dict(default=None),
|
||||
name = dict(required=True),
|
||||
dns1 = dict(default=None),
|
||||
dns2 = dict(default=None),
|
||||
internal_dns1 = dict(default=None),
|
||||
internal_dns2 = dict(default=None),
|
||||
dns1_ipv6 = dict(default=None),
|
||||
dns2_ipv6 = dict(default=None),
|
||||
network_type = dict(default='basic', choices=['Basic', 'basic', 'Advanced', 'advanced']),
|
||||
network_domain = dict(default=None),
|
||||
guest_cidr_address = dict(default=None),
|
||||
dhcp_provider = dict(default=None),
|
||||
local_storage_enabled = dict(default=None),
|
||||
securitygroups_enabled = dict(default=None),
|
||||
state = dict(choices=['present', 'enabled', 'disabled', 'absent'], default='present'),
|
||||
domain = dict(default=None),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
required_together=cs_required_together(),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
except CloudStackException as e:
|
||||
module.fail_json(msg='CloudStackException: %s' % str(e))
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
205
lib/ansible/modules/cloud/cloudstack/cs_zone_facts.py
Normal file
205
lib/ansible/modules/cloud/cloudstack/cs_zone_facts.py
Normal file
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2016, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
ANSIBLE_METADATA = {'status': ['stableinterface'],
|
||||
'supported_by': 'community',
|
||||
'version': '1.0'}
|
||||
|
||||
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.
|
||||
version_added: "2.1"
|
||||
author: "René Moser (@resmo)"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Name of the zone.
|
||||
required: true
|
||||
extends_documentation_fragment: cloudstack
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- cs_zone_facts:
|
||||
name: ch-gva-1
|
||||
delegate_to: localhost
|
||||
|
||||
- debug:
|
||||
var: cloudstack_zone
|
||||
'''
|
||||
|
||||
RETURN = '''
|
||||
---
|
||||
cloudstack_zone.id:
|
||||
description: UUID of the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6
|
||||
cloudstack_zone.name:
|
||||
description: Name of the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: zone01
|
||||
cloudstack_zone.dns1:
|
||||
description: First DNS for the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 8.8.8.8
|
||||
cloudstack_zone.dns2:
|
||||
description: Second DNS for the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 8.8.4.4
|
||||
cloudstack_zone.internal_dns1:
|
||||
description: First internal DNS for the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 8.8.8.8
|
||||
cloudstack_zone.internal_dns2:
|
||||
description: Second internal DNS for the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: 8.8.4.4
|
||||
cloudstack_zone.dns1_ipv6:
|
||||
description: First IPv6 DNS for the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: "2001:4860:4860::8888"
|
||||
cloudstack_zone.dns2_ipv6:
|
||||
description: Second IPv6 DNS for the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: "2001:4860:4860::8844"
|
||||
cloudstack_zone.allocation_state:
|
||||
description: State of the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: Enabled
|
||||
cloudstack_zone.domain:
|
||||
description: Domain the zone is related to.
|
||||
returned: success
|
||||
type: string
|
||||
sample: ROOT
|
||||
cloudstack_zone.network_domain:
|
||||
description: Network domain for the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: example.com
|
||||
cloudstack_zone.network_type:
|
||||
description: Network type for the zone.
|
||||
returned: success
|
||||
type: string
|
||||
sample: basic
|
||||
cloudstack_zone.local_storage_enabled:
|
||||
description: Local storage offering enabled.
|
||||
returned: success
|
||||
type: bool
|
||||
sample: false
|
||||
cloudstack_zone.securitygroups_enabled:
|
||||
description: Security groups support is enabled.
|
||||
returned: success
|
||||
type: bool
|
||||
sample: false
|
||||
cloudstack_zone.guest_cidr_address:
|
||||
description: Guest CIDR address for the zone
|
||||
returned: success
|
||||
type: string
|
||||
sample: 10.1.1.0/24
|
||||
cloudstack_zone.dhcp_provider:
|
||||
description: DHCP provider for the zone
|
||||
returned: success
|
||||
type: string
|
||||
sample: VirtualRouter
|
||||
cloudstack_zone.zone_token:
|
||||
description: Zone token
|
||||
returned: success
|
||||
type: string
|
||||
sample: ccb0a60c-79c8-3230-ab8b-8bdbe8c45bb7
|
||||
cloudstack_zone.tags:
|
||||
description: List of resource tags associated with the zone.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: [ { "key": "foo", "value": "bar" } ]
|
||||
'''
|
||||
|
||||
import base64
|
||||
|
||||
# import cloudstack common
|
||||
from ansible.module_utils.cloudstack import *
|
||||
|
||||
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',
|
||||
}
|
||||
self.facts = {
|
||||
'cloudstack_zone': None,
|
||||
}
|
||||
|
||||
|
||||
def get_zone(self):
|
||||
if not self.zone:
|
||||
# TODO: add param key signature in get_zone()
|
||||
self.module.params['zone'] = self.module.params.get('name')
|
||||
super(AnsibleCloudStackZoneFacts, self).get_zone()
|
||||
return self.zone
|
||||
|
||||
|
||||
def run(self):
|
||||
zone = self.get_zone()
|
||||
self.facts['cloudstack_zone'] = self.get_result(zone)
|
||||
return self.facts
|
||||
|
||||
|
||||
def main():
|
||||
argument_spec = cs_argument_spec()
|
||||
argument_spec.update(dict(
|
||||
name = dict(required=True),
|
||||
))
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
cs_zone_facts = AnsibleCloudStackZoneFacts(module=module).run()
|
||||
cs_facts_result = dict(changed=False, ansible_facts=cs_zone_facts)
|
||||
module.exit_json(**cs_facts_result)
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user