diff --git a/.travis.yml b/.travis.yml index 62bbff5cd69..84ec3a0983a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,15 @@ sudo: false language: python +python: + - "2.7" addons: apt: sources: - deadsnakes packages: - python2.4 + - python2.6 script: - - python2.4 -m compileall -fq -x 'cloud/' . + - python2.4 -m compileall -fq -x 'cloud/|monitoring/zabbix.*\.py|/layman\.py|/maven_artifact\.py|clustering/consul.*\.py|notification/pushbullet\.py' . + - python2.6 -m compileall -fq . + - python2.7 -m compileall -fq . diff --git a/cloud/amazon/cloudtrail.py b/cloud/amazon/cloudtrail.py index b58bcd6e1d0..6a1885d6ee7 100755 --- a/cloud/amazon/cloudtrail.py +++ b/cloud/amazon/cloudtrail.py @@ -19,9 +19,11 @@ DOCUMENTATION = """ module: cloudtrail short_description: manage CloudTrail creation and deletion description: - - Creates or deletes CloudTrail configuration. Ensures logging is also enabled. This module has a dependency on python-boto >= 2.21. + - Creates or deletes CloudTrail configuration. Ensures logging is also enabled. version_added: "2.0" -author: Ted Timmons +author: "Ted Timmons (@tedder)" +requirements: + - "boto >= 2.21" options: state: description: diff --git a/cloud/cloudstack/cs_account.py b/cloud/cloudstack/cs_account.py new file mode 100644 index 00000000000..ccb29e1015f --- /dev/null +++ b/cloud/cloudstack/cs_account.py @@ -0,0 +1,408 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (c) 2015, René Moser +# +# 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 . + +DOCUMENTATION = ''' +--- +module: cs_account +short_description: Manages account 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. + required: false + default: 'present' + choices: [ 'present', 'absent', 'enabled', 'disabled', 'locked' ] + 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 = ''' +--- +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 +account_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 +''' + +try: + from cs import CloudStack, CloudStackException, read_config + has_lib_cs = True +except ImportError: + has_lib_cs = False + +# import cloudstack common +from ansible.module_utils.cloudstack import * + + +class AnsibleCloudStackAccount(AnsibleCloudStack): + + def __init__(self, module): + AnsibleCloudStack.__init__(self, module) + 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 = {} + args['listall'] = True + args['domainid'] = self.get_domain('id') + accounts = self.cs.listAccounts(**args) + if accounts: + account_name = self.module.params.get('name') + for a in accounts['account']: + if account_name in [ a['name'] ]: + self.account = a + break + + return self.account + + + def enable_account(self): + account = self.get_account() + if not account: + self.module.fail_json(msg="Failed: account not present") + + if account['state'].lower() != 'enabled': + self.result['changed'] = True + args = {} + args['id'] = account['id'] + args['account'] = self.module.params.get('name') + args['domainid'] = self.get_domain('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: + self.module.fail_json(msg="Failed: account not present") + + # 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 = {} + args['id'] = account['id'] + args['account'] = self.module.params.get('name') + args['domainid'] = self.get_domain('id') + args['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): + missing_params = [] + + if not self.module.params.get('email'): + missing_params.append('email') + + if not self.module.params.get('username'): + missing_params.append('username') + + if not self.module.params.get('password'): + missing_params.append('password') + + if not self.module.params.get('first_name'): + missing_params.append('first_name') + + if not self.module.params.get('last_name'): + missing_params.append('last_name') + + if missing_params: + self.module.fail_json(msg="missing required arguments: %s" % ','.join(missing_params)) + + account = self.get_account() + + if not account: + self.result['changed'] = True + + args = {} + args['account'] = self.module.params.get('name') + args['domainid'] = self.get_domain('id') + args['accounttype'] = self.get_account_type() + args['networkdomain'] = self.module.params.get('network_domain') + 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.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 account: + 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, 'account') + return account + + + def get_result(self, account): + if account: + if 'name' in account: + self.result['name'] = account['name'] + if 'accounttype' in account: + for key,value in self.account_types.items(): + if value == account['accounttype']: + self.result['account_type'] = key + break + if 'state' in account: + self.result['account_state'] = account['state'] + if 'domain' in account: + self.result['domain'] = account['domain'] + if 'networkdomain' in account: + self.result['network_domain'] = account['networkdomain'] + return self.result + + +def main(): + module = AnsibleModule( + argument_spec = dict( + name = dict(required=True), + state = dict(choices=['present', 'absent', 'enabled', 'disabled', 'locked' ], 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), + timezone = dict(default=None), + poll_async = dict(choices=BOOLEANS, default=True), + api_key = dict(default=None), + api_secret = dict(default=None), + api_url = dict(default=None), + api_http_method = dict(default='get'), + ), + supports_check_mode=True + ) + + if not has_lib_cs: + module.fail_json(msg="python library cs required: pip install cs") + + try: + acs_acc = AnsibleCloudStackAccount(module) + + state = module.params.get('state') + + if state in ['absent']: + account = acs_acc.absent_account() + + elif state in ['enabled']: + 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, e: + module.fail_json(msg='CloudStackException: %s' % str(e)) + + module.exit_json(**result) + +# import module snippets +from ansible.module_utils.basic import * +main() diff --git a/cloud/cloudstack/cs_affinitygroup.py b/cloud/cloudstack/cs_affinitygroup.py index 07b9cf42d6a..1a11fb537db 100644 --- a/cloud/cloudstack/cs_affinitygroup.py +++ b/cloud/cloudstack/cs_affinitygroup.py @@ -25,7 +25,7 @@ short_description: Manages affinity groups on Apache CloudStack based clouds. description: - Create and remove affinity groups. version_added: '2.0' -author: René Moser +author: '"René Moser (@resmo)" ' options: name: description: diff --git a/cloud/cloudstack/cs_firewall.py b/cloud/cloudstack/cs_firewall.py index 13f114c1b35..5c96d606e68 100644 --- a/cloud/cloudstack/cs_firewall.py +++ b/cloud/cloudstack/cs_firewall.py @@ -19,12 +19,13 @@ # along with Ansible. If not, see . 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 +author: '"René Moser (@resmo)" ' options: ip_address: description: @@ -52,9 +53,10 @@ options: - 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). + - 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: @@ -67,9 +69,19 @@ options: - 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. + - Name of the project the firewall rule is related to. required: false default: null extends_documentation_fragment: cloudstack @@ -81,8 +93,7 @@ EXAMPLES = ''' - local_action: module: cs_firewall ip_address: 4.3.2.1 - start_port: 80 - end_port: 80 + port: 80 cidr: 1.2.3.4/32 @@ -90,8 +101,7 @@ EXAMPLES = ''' - local_action: module: cs_firewall ip_address: 4.3.2.1 - start_port: 53 - end_port: 53 + port: 53 protocol: '{{ item }}' with_items: - tcp @@ -108,6 +118,45 @@ EXAMPLES = ''' state: absent ''' +RETURN = ''' +--- +ip_address: + description: IP address of the rule. + returned: success + type: string + sample: 10.100.212.10 +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 +''' + try: from cs import CloudStack, CloudStackException, read_config has_lib_cs = True @@ -128,12 +177,18 @@ class AnsibleCloudStackFirewall(AnsibleCloudStack): self.firewall_rule = None + def get_end_port(self): + if self.module.params.get('end_port'): + return self.module.params.get('end_port') + return self.module.params.get('start_port') + + 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.module.params.get('end_port') + end_port = self.get_end_port() icmp_code = self.module.params.get('icmp_code') icmp_type = self.module.params.get('icmp_type') @@ -143,9 +198,11 @@ class AnsibleCloudStackFirewall(AnsibleCloudStack): if protocol == 'icmp' and not icmp_type: self.module.fail_json(msg="no icmp_type set") - args = {} - args['ipaddressid'] = self.get_ip_address_id() - args['projectid'] = self.get_project_id() + args = {} + args['ipaddressid'] = self.get_ip_address('id') + args['account'] = self.get_account('name') + args['domainid'] = self.get_domain('id') + args['projectid'] = self.get_project('id') firewall_rules = self.cs.listFirewallRules(**args) if firewall_rules and 'firewallrule' in firewall_rules: @@ -183,14 +240,15 @@ class AnsibleCloudStackFirewall(AnsibleCloudStack): 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.module.params.get('end_port') - args['icmptype'] = self.module.params.get('icmp_type') - args['icmpcode'] = self.module.params.get('icmp_code') - args['ipaddressid'] = self.get_ip_address_id() + + 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_end_port() + args['icmptype'] = self.module.params.get('icmp_type') + args['icmpcode'] = self.module.params.get('icmp_code') + args['ipaddressid'] = self.get_ip_address('id') if not self.module.check_mode: firewall_rule = self.cs.createFirewallRule(**args) @@ -212,29 +270,43 @@ class AnsibleCloudStackFirewall(AnsibleCloudStack): def get_result(self, firewall_rule): + if firewall_rule: + if 'cidrlist' in firewall_rule: + self.result['cidr'] = firewall_rule['cidrlist'] + if 'startport' in firewall_rule: + self.result['start_port'] = int(firewall_rule['startport']) + if 'endport' in firewall_rule: + self.result['end_port'] = int(firewall_rule['endport']) + if 'protocol' in firewall_rule: + self.result['protocol'] = firewall_rule['protocol'] + if 'ipaddress' in firewall_rule: + self.result['ip_address'] = firewall_rule['ipaddress'] + if 'icmpcode' in firewall_rule: + self.result['icmp_code'] = int(firewall_rule['icmpcode']) + if 'icmptype' in firewall_rule: + self.result['icmp_type'] = int(firewall_rule['icmptype']) return self.result def main(): module = AnsibleModule( argument_spec = dict( - ip_address = dict(required=True, default=None), + ip_address = dict(required=True), cidr = dict(default='0.0.0.0/0'), protocol = dict(choices=['tcp', 'udp', 'icmp'], default='tcp'), icmp_type = dict(type='int', default=None), icmp_code = dict(type='int', default=None), - start_port = 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'), + domain = dict(default=None), + account = dict(default=None), project = dict(default=None), api_key = dict(default=None), api_secret = dict(default=None), api_url = dict(default=None), api_http_method = dict(default='get'), ), - required_together = ( - ['start_port', 'end_port'], - ), mutually_exclusive = ( ['icmp_type', 'start_port'], ['icmp_type', 'end_port'], diff --git a/cloud/cloudstack/cs_instance.py b/cloud/cloudstack/cs_instance.py index 8680f20ada5..82d33725250 100644 --- a/cloud/cloudstack/cs_instance.py +++ b/cloud/cloudstack/cs_instance.py @@ -25,7 +25,7 @@ short_description: Manages instances and virtual machines on Apache CloudStack b description: - Deploy, start, restart, stop and destroy instances on Apache CloudStack, Citrix CloudPlatform and Exoscale. version_added: '2.0' -author: René Moser +author: '"René Moser (@resmo)" ' options: name: description: @@ -106,6 +106,16 @@ options: required: false default: [] aliases: [ 'security_group' ] + 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 to be deployed in. @@ -252,6 +262,16 @@ ssh_key: returned: success type: string sample: key@work +domain: + description: Domain the instance is related to. + returned: success + type: string + sample: example domain +account: + description: Account the instance is related to. + returned: success + type: string + sample: example account project: description: Name of project the instance is related to. returned: success @@ -352,8 +372,15 @@ class AnsibleCloudStackInstance(AnsibleCloudStack): if template and iso: self.module.fail_json(msg="Template are ISO are mutually exclusive.") + args = {} + args['account'] = self.get_account('name') + args['domainid'] = self.get_domain('id') + args['projectid'] = self.get_project('id') + args['zoneid'] = self.get_zone('id') + if template: - templates = self.cs.listTemplates(templatefilter='executable') + args['templatefilter'] = 'executable' + templates = self.cs.listTemplates(**args) if templates: for t in templates['template']: if template in [ t['displaytext'], t['name'], t['id'] ]: @@ -361,7 +388,8 @@ class AnsibleCloudStackInstance(AnsibleCloudStack): self.module.fail_json(msg="Template '%s' not found" % template) elif iso: - isos = self.cs.listIsos() + args['isofilter'] = 'executable' + isos = self.cs.listIsos(**args) if isos: for i in isos['iso']: if iso in [ i['displaytext'], i['name'], i['id'] ]: @@ -375,7 +403,10 @@ class AnsibleCloudStackInstance(AnsibleCloudStack): if not disk_offering: return None - disk_offerings = self.cs.listDiskOfferings() + args = {} + args['domainid'] = self.get_domain('id') + + disk_offerings = self.cs.listDiskOfferings(**args) if disk_offerings: for d in disk_offerings['diskoffering']: if disk_offering in [ d['displaytext'], d['name'], d['id'] ]: @@ -388,9 +419,12 @@ class AnsibleCloudStackInstance(AnsibleCloudStack): if not instance: instance_name = self.module.params.get('name') - args = {} - args['projectid'] = self.get_project_id() - args['zoneid'] = self.get_zone_id() + args = {} + args['account'] = self.get_account('name') + args['domainid'] = self.get_domain('id') + args['projectid'] = self.get_project('id') + args['zoneid'] = self.get_zone('id') + instances = self.cs.listVirtualMachines(**args) if instances: for v in instances['virtualmachine']: @@ -405,9 +439,12 @@ class AnsibleCloudStackInstance(AnsibleCloudStack): if not network_names: return None - args = {} - args['zoneid'] = self.get_zone_id() - args['projectid'] = self.get_project_id() + args = {} + args['account'] = self.get_account('name') + args['domainid'] = self.get_domain('id') + args['projectid'] = self.get_project('id') + args['zoneid'] = self.get_zone('id') + networks = self.cs.listNetworks(**args) if not networks: self.module.fail_json(msg="No networks available") @@ -458,9 +495,11 @@ class AnsibleCloudStackInstance(AnsibleCloudStack): args = {} args['templateid'] = self.get_template_or_iso_id() - args['zoneid'] = self.get_zone_id() + args['zoneid'] = self.get_zone('id') args['serviceofferingid'] = self.get_service_offering_id() - args['projectid'] = self.get_project_id() + args['account'] = self.get_account('name') + args['domainid'] = self.get_domain('id') + args['projectid'] = self.get_project('id') args['diskofferingid'] = self.get_disk_offering_id() args['networkids'] = self.get_network_ids() args['hypervisor'] = self.get_hypervisor() @@ -503,7 +542,7 @@ class AnsibleCloudStackInstance(AnsibleCloudStack): args_ssh_key = {} args_ssh_key['id'] = instance['id'] args_ssh_key['keypair'] = self.module.params.get('ssh_key') - args_ssh_key['projectid'] = self.get_project_id() + args_ssh_key['projectid'] = self.get_project('id') if self._has_changed(args_service_offering, instance) or \ self._has_changed(args_instance_update, instance) or \ @@ -668,6 +707,10 @@ class AnsibleCloudStackInstance(AnsibleCloudStack): self.result['display_name'] = instance['displayname'] if 'group' in instance: self.result['group'] = instance['group'] + if 'domain' in instance: + self.result['domain'] = instance['domain'] + if 'account' in instance: + self.result['account'] = instance['account'] if 'project' in instance: self.result['project'] = instance['project'] if 'publicip' in instance: @@ -732,6 +775,8 @@ def main(): hypervisor = dict(default=None), security_groups = dict(type='list', aliases=[ 'security_group' ], default=[]), affinity_groups = dict(type='list', aliases=[ 'affinity_group' ], default=[]), + domain = dict(default=None), + account = dict(default=None), project = dict(default=None), user_data = dict(default=None), zone = dict(default=None), diff --git a/cloud/cloudstack/cs_instancegroup.py b/cloud/cloudstack/cs_instancegroup.py index 2c47a9f6f25..71aa4bfa38b 100644 --- a/cloud/cloudstack/cs_instancegroup.py +++ b/cloud/cloudstack/cs_instancegroup.py @@ -25,7 +25,7 @@ short_description: Manages instance groups on Apache CloudStack based clouds. description: - Create and remove instance groups. version_added: '2.0' -author: René Moser +author: '"René Moser (@resmo)" ' options: name: description: diff --git a/cloud/cloudstack/cs_iso.py b/cloud/cloudstack/cs_iso.py index 83af1e1783e..1bdb2ee75cc 100644 --- a/cloud/cloudstack/cs_iso.py +++ b/cloud/cloudstack/cs_iso.py @@ -25,7 +25,7 @@ short_description: Manages ISOs images on Apache CloudStack based clouds. description: - Register and remove ISO images. version_added: '2.0' -author: René Moser +author: '"René Moser (@resmo)" ' options: name: description: diff --git a/cloud/cloudstack/cs_portforward.py b/cloud/cloudstack/cs_portforward.py new file mode 100644 index 00000000000..74519fccb28 --- /dev/null +++ b/cloud/cloudstack/cs_portforward.py @@ -0,0 +1,432 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (c) 2015, René Moser +# +# 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 . + +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 specific, 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 specific, 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 + open_firewall: true + + +# remove ssh port forwarding +- local_action: + module: cs_portforward + ip_address: 1.2.3.4 + public_port: 22 + private_port: 22 + state: absent + +''' + +RETURN = ''' +--- +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: Private start port. + returned: success + type: int + sample: 80 +private_end_port: + description: Private end port. + returned: success + type: int +public_port: + description: Public start port. + returned: success + type: int + sample: 80 +public_end_port: + description: Public end port. + returned: success + type: int + sample: 80 +tags: + description: Tag srelated 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 +''' + + +try: + from cs import CloudStack, CloudStackException, read_config + has_lib_cs = True +except ImportError: + has_lib_cs = False + +# import cloudstack common +from ansible.module_utils.cloudstack import * + + +class AnsibleCloudStackPortforwarding(AnsibleCloudStack): + + def __init__(self, module): + AnsibleCloudStack.__init__(self, module) + self.portforwarding_rule = None + self.vm_default_nic = None + + + def get_public_end_port(self): + if not self.module.params.get('public_end_port'): + return self.module.params.get('public_port') + return self.module.params.get('public_end_port') + + + def get_private_end_port(self): + if not self.module.params.get('private_end_port'): + return self.module.params.get('private_port') + return self.module.params.get('private_end_port') + + + def get_vm_guest_ip(self): + vm_guest_ip = self.module.params.get('vm_guest_ip') + default_nic = self.get_vm_default_nic() + + if not vm_guest_ip: + return default_nic['ipaddress'] + + for secondary_ip in default_nic['secondaryip']: + if vm_guest_ip == secondary_ip['ipaddress']: + return vm_guest_ip + self.module.fail_json(msg="Secondary IP '%s' not assigned to VM" % vm_guest_ip) + + + def get_vm_default_nic(self): + if self.vm_default_nic: + return self.vm_default_nic + + nics = self.cs.listNics(virtualmachineid=self.get_vm(key='id')) + if nics: + for n in nics['nic']: + if n['isdefault']: + self.vm_default_nic = n + return self.vm_default_nic + self.module.fail_json(msg="No default IP address of VM '%s' found" % self.module.params.get('vm')) + + + 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_public_end_port() + private_port = self.module.params.get('private_port') + private_end_port = self.get_public_end_port() + + args = {} + args['ipaddressid'] = self.get_ip_address(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_public_end_port() + args['privateport'] = self.module.params.get('private_port') + args['privateendport'] = self.get_private_end_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') + + 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_public_end_port() + args['privateport'] = self.module.params.get('private_port') + args['privateendport'] = self.get_private_end_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') + + 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): + if portforwarding_rule: + if 'id' in portforwarding_rule: + self.result['id'] = portforwarding_rule['id'] + if 'virtualmachinedisplayname' in portforwarding_rule: + self.result['vm_display_name'] = portforwarding_rule['virtualmachinedisplayname'] + if 'virtualmachinename' in portforwarding_rule: + self.result['vm_name'] = portforwarding_rule['virtualmachinename'] + if 'ipaddress' in portforwarding_rule: + self.result['ip_address'] = portforwarding_rule['ipaddress'] + if 'vmguestip' in portforwarding_rule: + self.result['vm_guest_ip'] = portforwarding_rule['vmguestip'] + if 'publicport' in portforwarding_rule: + self.result['public_port'] = portforwarding_rule['publicport'] + if 'publicendport' in portforwarding_rule: + self.result['public_end_port'] = portforwarding_rule['publicendport'] + if 'privateport' in portforwarding_rule: + self.result['private_port'] = portforwarding_rule['privateport'] + if 'privateendport' in portforwarding_rule: + self.result['private_end_port'] = portforwarding_rule['privateendport'] + if 'protocol' in portforwarding_rule: + self.result['protocol'] = portforwarding_rule['protocol'] + if 'tags' in portforwarding_rule: + self.result['tags'] = [] + for tag in portforwarding_rule['tags']: + result_tag = {} + result_tag['key'] = tag['key'] + result_tag['value'] = tag['value'] + self.result['tags'].append(result_tag) + return self.result + + +def main(): + module = AnsibleModule( + argument_spec = 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(choices=BOOLEANS, 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(choices=BOOLEANS, default=True), + api_key = dict(default=None), + api_secret = dict(default=None), + api_url = dict(default=None), + api_http_method = dict(default='get'), + ), + supports_check_mode=True + ) + + if not has_lib_cs: + module.fail_json(msg="python library cs required: pip install cs") + + 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, e: + module.fail_json(msg='CloudStackException: %s' % str(e)) + + module.exit_json(**result) + +# import module snippets +from ansible.module_utils.basic import * +main() diff --git a/cloud/cloudstack/cs_securitygroup.py b/cloud/cloudstack/cs_securitygroup.py index 50556da5bb3..88908e559e5 100644 --- a/cloud/cloudstack/cs_securitygroup.py +++ b/cloud/cloudstack/cs_securitygroup.py @@ -25,7 +25,7 @@ short_description: Manages security groups on Apache CloudStack based clouds. description: - Create and remove security groups. version_added: '2.0' -author: René Moser +author: '"René Moser (@resmo)" ' options: name: description: diff --git a/cloud/cloudstack/cs_securitygroup_rule.py b/cloud/cloudstack/cs_securitygroup_rule.py index 1f2dac6f267..100a92df4ef 100644 --- a/cloud/cloudstack/cs_securitygroup_rule.py +++ b/cloud/cloudstack/cs_securitygroup_rule.py @@ -25,7 +25,7 @@ short_description: Manages security group rules on Apache CloudStack based cloud description: - Add and remove security group rules. version_added: '2.0' -author: René Moser +author: '"René Moser (@resmo)" ' options: security_group: description: @@ -229,18 +229,21 @@ class AnsibleCloudStackSecurityGroupRule(AnsibleCloudStack): and cidr == rule['cidr'] + def get_end_port(self): + if self.module.params.get('end_port'): + return self.module.params.get('end_port') + return self.module.params.get('start_port') + + 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.module.params.get('end_port') + end_port = self.get_end_port() icmp_code = self.module.params.get('icmp_code') icmp_type = self.module.params.get('icmp_type') - if not end_port: - end_port = start_port - 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) @@ -295,26 +298,23 @@ class AnsibleCloudStackSecurityGroupRule(AnsibleCloudStack): args['protocol'] = self.module.params.get('protocol') args['startport'] = self.module.params.get('start_port') - args['endport'] = self.module.params.get('end_port') + args['endport'] = self.get_end_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'] - if not args['endport']: - args['endport'] = args['startport'] - rule = None res = None - type = self.module.params.get('type') - if type == 'ingress': + sg_type = self.module.params.get('type') + if sg_type == 'ingress': 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 type == 'egress': + elif sg_type == 'egress': rule = self._get_rule(security_group['egressrule']) if not rule: self.result['changed'] = True @@ -327,22 +327,25 @@ class AnsibleCloudStackSecurityGroupRule(AnsibleCloudStack): poll_async = self.module.params.get('poll_async') if res and poll_async: security_group = self._poll_job(res, 'securitygroup') - return security_group + 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 - type = self.module.params.get('type') - if type == 'ingress': + 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 type == 'egress': + elif sg_type == 'egress': rule = self._get_rule(security_group['egressrule']) if rule: self.result['changed'] = True @@ -355,34 +358,30 @@ class AnsibleCloudStackSecurityGroupRule(AnsibleCloudStack): poll_async = self.module.params.get('poll_async') if res and poll_async: res = self._poll_job(res, 'securitygroup') - return security_group + return rule def get_result(self, security_group_rule): - type = self.module.params.get('type') - - key = 'ingressrule' - if type == 'egress': - key = 'egressrule' - self.result['type'] = type + self.result['type'] = self.module.params.get('type') self.result['security_group'] = self.module.params.get('security_group') - - if key in security_group_rule and security_group_rule[key]: - if 'securitygroupname' in security_group_rule[key][0]: - self.result['user_security_group'] = security_group_rule[key][0]['securitygroupname'] - if 'cidr' in security_group_rule[key][0]: - self.result['cidr'] = security_group_rule[key][0]['cidr'] - if 'protocol' in security_group_rule[key][0]: - self.result['protocol'] = security_group_rule[key][0]['protocol'] - if 'startport' in security_group_rule[key][0]: - self.result['start_port'] = security_group_rule[key][0]['startport'] - if 'endport' in security_group_rule[key][0]: - self.result['end_port'] = security_group_rule[key][0]['endport'] - if 'icmpcode' in security_group_rule[key][0]: - self.result['icmp_code'] = security_group_rule[key][0]['icmpcode'] - if 'icmptype' in security_group_rule[key][0]: - self.result['icmp_type'] = security_group_rule[key][0]['icmptype'] + + if security_group_rule: + rule = security_group_rule + if 'securitygroupname' in rule: + self.result['user_security_group'] = rule['securitygroupname'] + if 'cidr' in rule: + self.result['cidr'] = rule['cidr'] + if 'protocol' in rule: + self.result['protocol'] = rule['protocol'] + if 'startport' in rule: + self.result['start_port'] = rule['startport'] + if 'endport' in rule: + self.result['end_port'] = rule['endport'] + if 'icmpcode' in rule: + self.result['icmp_code'] = rule['icmpcode'] + if 'icmptype' in rule: + self.result['icmp_type'] = rule['icmptype'] return self.result diff --git a/cloud/cloudstack/cs_sshkeypair.py b/cloud/cloudstack/cs_sshkeypair.py index 8dd02dcd1f1..f24faee41d6 100644 --- a/cloud/cloudstack/cs_sshkeypair.py +++ b/cloud/cloudstack/cs_sshkeypair.py @@ -23,15 +23,26 @@ 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 +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. @@ -111,10 +122,11 @@ class AnsibleCloudStackSshKey(AnsibleCloudStack): def register_ssh_key(self, public_key): ssh_key = self.get_ssh_key() - - args = {} - args['projectid'] = self.get_project_id() - args['name'] = self.module.params.get('name') + 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: @@ -142,9 +154,11 @@ class AnsibleCloudStackSshKey(AnsibleCloudStack): ssh_key = self.get_ssh_key() if not ssh_key: self.result['changed'] = True - args = {} - args['projectid'] = self.get_project_id() - args['name'] = self.module.params.get('name') + 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'] @@ -155,9 +169,11 @@ class AnsibleCloudStackSshKey(AnsibleCloudStack): ssh_key = self.get_ssh_key() if ssh_key: self.result['changed'] = True - args = {} - args['name'] = self.module.params.get('name') - args['projectid'] = self.get_project_id() + 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 @@ -165,9 +181,11 @@ class AnsibleCloudStackSshKey(AnsibleCloudStack): def get_ssh_key(self): if not self.ssh_key: - args = {} - args['projectid'] = self.get_project_id() - args['name'] = self.module.params.get('name') + 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: @@ -179,10 +197,8 @@ class AnsibleCloudStackSshKey(AnsibleCloudStack): if ssh_key: if 'fingerprint' in ssh_key: self.result['fingerprint'] = ssh_key['fingerprint'] - if 'name' in ssh_key: self.result['name'] = ssh_key['name'] - if 'privatekey' in ssh_key: self.result['private_key'] = ssh_key['privatekey'] return self.result @@ -196,8 +212,10 @@ class AnsibleCloudStackSshKey(AnsibleCloudStack): def main(): module = AnsibleModule( argument_spec = dict( - name = dict(required=True, default=None), + 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'), api_key = dict(default=None), diff --git a/cloud/cloudstack/cs_vmsnapshot.py b/cloud/cloudstack/cs_vmsnapshot.py index dad660cd77c..fc11790579f 100644 --- a/cloud/cloudstack/cs_vmsnapshot.py +++ b/cloud/cloudstack/cs_vmsnapshot.py @@ -25,7 +25,7 @@ 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 +author: '"René Moser (@resmo)" ' options: name: description: @@ -62,6 +62,16 @@ options: 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. @@ -134,6 +144,21 @@ description: 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 ''' try: @@ -156,10 +181,12 @@ class AnsibleCloudStackVmSnapshot(AnsibleCloudStack): def get_snapshot(self): - args = {} - args['virtualmachineid'] = self.get_vm_id() - args['projectid'] = self.get_project_id() - args['name'] = self.module.params.get('name') + 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: @@ -172,11 +199,11 @@ class AnsibleCloudStackVmSnapshot(AnsibleCloudStack): 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') + 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) @@ -242,6 +269,12 @@ class AnsibleCloudStackVmSnapshot(AnsibleCloudStack): self.result['name'] = snapshot['name'] if 'description' in snapshot: self.result['description'] = snapshot['description'] + if 'domain' in snapshot: + self.result['domain'] = snapshot['domain'] + if 'account' in snapshot: + self.result['account'] = snapshot['account'] + if 'project' in snapshot: + self.result['project'] = snapshot['project'] return self.result @@ -251,10 +284,12 @@ def main(): name = dict(required=True, aliases=['displayname']), vm = dict(required=True), description = dict(default=None), - project = dict(default=None), zone = dict(default=None), snapshot_memory = dict(choices=BOOLEANS, 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(choices=BOOLEANS, default=True), api_key = dict(default=None), api_secret = dict(default=None), diff --git a/cloud/google/gce_img.py b/cloud/google/gce_img.py index 3b2351b3752..9cc37f8eb33 100644 --- a/cloud/google/gce_img.py +++ b/cloud/google/gce_img.py @@ -78,8 +78,10 @@ options: default: null aliases: [] -requirements: [ "libcloud" ] -author: Peter Tan +requirements: + - "python >= 2.6" + - "apache-libcloud" +author: '"Peter Tan (@tanpeter)" ' ''' EXAMPLES = ''' diff --git a/cloud/lxc/lxc_container.py b/cloud/lxc/lxc_container.py index 5f0f6bb2ad6..119d45069c3 100644 --- a/cloud/lxc/lxc_container.py +++ b/cloud/lxc/lxc_container.py @@ -26,7 +26,7 @@ short_description: Manage LXC Containers version_added: 1.8.0 description: - Management of LXC containers -author: Kevin Carter +author: '"Kevin Carter (@cloudnull)" ' options: name: description: @@ -38,6 +38,7 @@ options: - lvm - loop - btrfs + - overlayfs description: - Backend storage type for the container. required: false @@ -112,6 +113,24 @@ options: - Set the log level for a container where *container_log* was set. required: false default: INFO + clone_name: + version_added: "2.0" + description: + - Name of the new cloned server. This is only used when state is + clone. + required: false + default: false + clone_snapshot: + version_added: "2.0" + required: false + choices: + - true + - false + description: + - Create a snapshot a container when cloning. This is not supported + by all container storage backends. Enabling this may fail if the + backing store does not support snapshots. + default: false archive: choices: - true @@ -142,14 +161,21 @@ options: - absent - frozen description: - - Start a container right after it's created. + - Define the state of a container. If you clone a container using + `clone_name` the newly cloned container created in a stopped state. + The running container will be stopped while the clone operation is + happening and upon completion of the clone the original container + state will be restored. required: false default: started container_config: description: - list of 'key=value' options to use when configuring a container. required: false -requirements: ['lxc >= 1.0', 'python2-lxc >= 0.1'] +requirements: + - 'lxc >= 1.0' + - 'python >= 2.6' + - 'python2-lxc >= 0.1' notes: - Containers must have a unique name. If you attempt to create a container with a name that already exists in the users namespace the module will @@ -203,6 +229,7 @@ EXAMPLES = """ - name: Create filesystem container lxc_container: name: test-container-config + backing_store: dir container_log: true template: ubuntu state: started @@ -216,7 +243,7 @@ EXAMPLES = """ # Create an lvm container, run a complex command in it, add additional # configuration to it, create an archive of it, and finally leave the container # in a frozen state. The container archive will be compressed using bzip2 -- name: Create an lvm container +- name: Create a frozen lvm container lxc_container: name: test-container-lvm container_log: true @@ -241,14 +268,6 @@ EXAMPLES = """ - name: Debug info on container "test-container-lvm" debug: var=lvm_container_info -- name: Get information on a given container. - lxc_container: - name: test-container-config - register: config_container_info - -- name: debug info on container "test-container" - debug: var=config_container_info - - name: Run a command in a container and ensure its in a "stopped" state. lxc_container: name: test-container-started @@ -263,19 +282,19 @@ EXAMPLES = """ container_command: | echo 'hello world.' | tee /opt/frozen -- name: Start a container. +- name: Start a container lxc_container: name: test-container-stopped state: started -- name: Run a command in a container and then restart it. +- name: Run a command in a container and then restart it lxc_container: name: test-container-started state: restarted container_command: | echo 'hello world.' | tee /opt/restarted -- name: Run a complex command within a "running" container. +- name: Run a complex command within a "running" container lxc_container: name: test-container-started container_command: | @@ -295,7 +314,53 @@ EXAMPLES = """ archive: true archive_path: /opt/archives -- name: Destroy a container. +# Create a container using overlayfs, create an archive of it, create a +# snapshot clone of the container and and finally leave the container +# in a frozen state. The container archive will be compressed using gzip. +- name: Create an overlayfs container archive and clone it + lxc_container: + name: test-container-overlayfs + container_log: true + template: ubuntu + state: started + backing_store: overlayfs + template_options: --release trusty + clone_snapshot: true + clone_name: test-container-overlayfs-clone-snapshot + archive: true + archive_compression: gzip + register: clone_container_info + +- name: debug info on container "test-container" + debug: var=clone_container_info + +- name: Clone a container using snapshot + lxc_container: + name: test-container-overlayfs-clone-snapshot + backing_store: overlayfs + clone_name: test-container-overlayfs-clone-snapshot2 + clone_snapshot: true + +- name: Create a new container and clone it + lxc_container: + name: test-container-new-archive + backing_store: dir + clone_name: test-container-new-archive-clone + +- name: Archive and clone a container then destroy it + lxc_container: + name: test-container-new-archive + state: absent + clone_name: test-container-new-archive-destroyed-clone + archive: true + archive_compression: gzip + +- name: Start a cloned container. + lxc_container: + name: test-container-new-archive-destroyed-clone + state: started + +- name: Destroy a container lxc_container: name: "{{ item }}" state: absent @@ -305,6 +370,13 @@ EXAMPLES = """ - test-container-frozen - test-container-lvm - test-container-config + - test-container-overlayfs + - test-container-overlayfs-clone + - test-container-overlayfs-clone-snapshot + - test-container-overlayfs-clone-snapshot2 + - test-container-new-archive + - test-container-new-archive-clone + - test-container-new-archive-destroyed-clone """ @@ -351,6 +423,15 @@ LXC_COMMAND_MAP = { 'directory': '--dir', 'zfs_root': '--zfsroot' } + }, + 'clone': { + 'variables': { + 'backing_store': '--backingstore', + 'lxc_path': '--lxcpath', + 'fs_size': '--fssize', + 'name': '--orig', + 'clone_name': '--new' + } } } @@ -369,6 +450,9 @@ LXC_BACKING_STORE = { ], 'loop': [ 'lv_name', 'vg_name', 'thinpool', 'zfs_root' + ], + 'overlayfs': [ + 'lv_name', 'vg_name', 'fs_type', 'fs_size', 'thinpool', 'zfs_root' ] } @@ -388,7 +472,8 @@ LXC_ANSIBLE_STATES = { 'stopped': '_stopped', 'restarted': '_restarted', 'absent': '_destroyed', - 'frozen': '_frozen' + 'frozen': '_frozen', + 'clone': '_clone' } @@ -439,18 +524,16 @@ def create_script(command): f.close() # Ensure the script is executable. - os.chmod(script_file, 0755) + os.chmod(script_file, 1755) # Get temporary directory. tempdir = tempfile.gettempdir() # Output log file. - stdout = path.join(tempdir, 'lxc-attach-script.log') - stdout_file = open(stdout, 'ab') + stdout_file = open(path.join(tempdir, 'lxc-attach-script.log'), 'ab') # Error log file. - stderr = path.join(tempdir, 'lxc-attach-script.err') - stderr_file = open(stderr, 'ab') + stderr_file = open(path.join(tempdir, 'lxc-attach-script.err'), 'ab') # Execute the script command. try: @@ -482,6 +565,7 @@ class LxcContainerManagement(object): self.container_name = self.module.params['name'] self.container = self.get_container_bind() self.archive_info = None + self.clone_info = None def get_container_bind(self): return lxc.Container(name=self.container_name) @@ -502,15 +586,15 @@ class LxcContainerManagement(object): return num @staticmethod - def _container_exists(name): + def _container_exists(container_name): """Check if a container exists. - :param name: Name of the container. + :param container_name: Name of the container. :type: ``str`` :returns: True or False if the container is found. :rtype: ``bol`` """ - if [i for i in lxc.list_containers() if i == name]: + if [i for i in lxc.list_containers() if i == container_name]: return True else: return False @@ -543,6 +627,7 @@ class LxcContainerManagement(object): """ # Remove incompatible storage backend options. + variables = variables.copy() for v in LXC_BACKING_STORE[self.module.params['backing_store']]: variables.pop(v, None) @@ -655,6 +740,68 @@ class LxcContainerManagement(object): self._container_startup() self.container.freeze() + def _container_create_clone(self): + """Clone a new LXC container from an existing container. + + This method will clone an existing container to a new container using + the `clone_name` variable as the new container name. The method will + create a container if the container `name` does not exist. + + Note that cloning a container will ensure that the original container + is "stopped" before the clone can be done. Because this operation can + require a state change the method will return the original container + to its prior state upon completion of the clone. + + Once the clone is complete the new container will be left in a stopped + state. + """ + + # Ensure that the state of the original container is stopped + container_state = self._get_state() + if container_state != 'stopped': + self.state_change = True + self.container.stop() + + build_command = [ + self.module.get_bin_path('lxc-clone', True), + ] + + build_command = self._add_variables( + variables_dict=self._get_vars( + variables=LXC_COMMAND_MAP['clone']['variables'] + ), + build_command=build_command + ) + + # Load logging for the instance when creating it. + if self.module.params.get('clone_snapshot') in BOOLEANS_TRUE: + build_command.append('--snapshot') + # Check for backing_store == overlayfs if so force the use of snapshot + # If overlay fs is used and snapshot is unset the clone command will + # fail with an unsupported type. + elif self.module.params.get('backing_store') == 'overlayfs': + build_command.append('--snapshot') + + rc, return_data, err = self._run_command(build_command) + if rc != 0: + message = "Failed executing lxc-clone." + self.failure( + err=err, rc=rc, msg=message, command=' '.join( + build_command + ) + ) + else: + self.state_change = True + # Restore the original state of the origin container if it was + # not in a stopped state. + if container_state == 'running': + self.container.start() + elif container_state == 'frozen': + self.container.start() + self.container.freeze() + + return True + def _create(self): """Create a new LXC container. @@ -709,9 +856,9 @@ class LxcContainerManagement(object): rc, return_data, err = self._run_command(build_command) if rc != 0: - msg = "Failed executing lxc-create." + message = "Failed executing lxc-create." self.failure( - err=err, rc=rc, msg=msg, command=' '.join(build_command) + err=err, rc=rc, msg=message, command=' '.join(build_command) ) else: self.state_change = True @@ -751,7 +898,7 @@ class LxcContainerManagement(object): :rtype: ``str`` """ - if self._container_exists(name=self.container_name): + if self._container_exists(container_name=self.container_name): return str(self.container.state).lower() else: return str('absent') @@ -794,7 +941,7 @@ class LxcContainerManagement(object): rc=1, msg='The container [ %s ] failed to start. Check to lxc is' ' available and that the container is in a functional' - ' state.' + ' state.' % self.container_name ) def _check_archive(self): @@ -808,6 +955,23 @@ class LxcContainerManagement(object): 'archive': self._container_create_tar() } + def _check_clone(self): + """Create a compressed archive of a container. + + This will store archive_info in as self.archive_info + """ + + clone_name = self.module.params.get('clone_name') + if clone_name: + if not self._container_exists(container_name=clone_name): + self.clone_info = { + 'cloned': self._container_create_clone() + } + else: + self.clone_info = { + 'cloned': False + } + def _destroyed(self, timeout=60): """Ensure a container is destroyed. @@ -816,12 +980,15 @@ class LxcContainerManagement(object): """ for _ in xrange(timeout): - if not self._container_exists(name=self.container_name): + if not self._container_exists(container_name=self.container_name): break # Check if the container needs to have an archive created. self._check_archive() + # Check if the container is to be cloned + self._check_clone() + if self._get_state() != 'stopped': self.state_change = True self.container.stop() @@ -852,7 +1019,7 @@ class LxcContainerManagement(object): """ self.check_count(count=count, method='frozen') - if self._container_exists(name=self.container_name): + if self._container_exists(container_name=self.container_name): self._execute_command() # Perform any configuration updates @@ -871,6 +1038,9 @@ class LxcContainerManagement(object): # Check if the container needs to have an archive created. self._check_archive() + + # Check if the container is to be cloned + self._check_clone() else: self._create() count += 1 @@ -886,7 +1056,7 @@ class LxcContainerManagement(object): """ self.check_count(count=count, method='restart') - if self._container_exists(name=self.container_name): + if self._container_exists(container_name=self.container_name): self._execute_command() # Perform any configuration updates @@ -898,6 +1068,9 @@ class LxcContainerManagement(object): # Check if the container needs to have an archive created. self._check_archive() + + # Check if the container is to be cloned + self._check_clone() else: self._create() count += 1 @@ -913,7 +1086,7 @@ class LxcContainerManagement(object): """ self.check_count(count=count, method='stop') - if self._container_exists(name=self.container_name): + if self._container_exists(container_name=self.container_name): self._execute_command() # Perform any configuration updates @@ -925,6 +1098,9 @@ class LxcContainerManagement(object): # Check if the container needs to have an archive created. self._check_archive() + + # Check if the container is to be cloned + self._check_clone() else: self._create() count += 1 @@ -940,7 +1116,7 @@ class LxcContainerManagement(object): """ self.check_count(count=count, method='start') - if self._container_exists(name=self.container_name): + if self._container_exists(container_name=self.container_name): container_state = self._get_state() if container_state == 'running': pass @@ -965,6 +1141,9 @@ class LxcContainerManagement(object): # Check if the container needs to have an archive created. self._check_archive() + + # Check if the container is to be cloned + self._check_clone() else: self._create() count += 1 @@ -1007,18 +1186,18 @@ class LxcContainerManagement(object): all_lvms = [i.split() for i in stdout.splitlines()][1:] return [lv_entry[0] for lv_entry in all_lvms if lv_entry[1] == vg] - def _get_vg_free_pe(self, name): + def _get_vg_free_pe(self, vg_name): """Return the available size of a given VG. - :param name: Name of volume. - :type name: ``str`` + :param vg_name: Name of volume. + :type vg_name: ``str`` :returns: size and measurement of an LV :type: ``tuple`` """ build_command = [ 'vgdisplay', - name, + vg_name, '--units', 'g' ] @@ -1027,7 +1206,7 @@ class LxcContainerManagement(object): self.failure( err=err, rc=rc, - msg='failed to read vg %s' % name, + msg='failed to read vg %s' % vg_name, command=' '.join(build_command) ) @@ -1036,17 +1215,17 @@ class LxcContainerManagement(object): _free_pe = free_pe[0].split() return float(_free_pe[-2]), _free_pe[-1] - def _get_lv_size(self, name): + def _get_lv_size(self, lv_name): """Return the available size of a given LV. - :param name: Name of volume. - :type name: ``str`` + :param lv_name: Name of volume. + :type lv_name: ``str`` :returns: size and measurement of an LV :type: ``tuple`` """ vg = self._get_lxc_vg() - lv = os.path.join(vg, name) + lv = os.path.join(vg, lv_name) build_command = [ 'lvdisplay', lv, @@ -1080,7 +1259,7 @@ class LxcContainerManagement(object): """ vg = self._get_lxc_vg() - free_space, messurement = self._get_vg_free_pe(name=vg) + free_space, messurement = self._get_vg_free_pe(vg_name=vg) if free_space < float(snapshot_size_gb): message = ( @@ -1183,25 +1362,25 @@ class LxcContainerManagement(object): return archive_name - def _lvm_lv_remove(self, name): + def _lvm_lv_remove(self, lv_name): """Remove an LV. - :param name: The name of the logical volume - :type name: ``str`` + :param lv_name: The name of the logical volume + :type lv_name: ``str`` """ vg = self._get_lxc_vg() build_command = [ self.module.get_bin_path('lvremove', True), "-f", - "%s/%s" % (vg, name), + "%s/%s" % (vg, lv_name), ] rc, stdout, err = self._run_command(build_command) if rc != 0: self.failure( err=err, rc=rc, - msg='Failed to remove LVM LV %s/%s' % (vg, name), + msg='Failed to remove LVM LV %s/%s' % (vg, lv_name), command=' '.join(build_command) ) @@ -1213,31 +1392,71 @@ class LxcContainerManagement(object): :param temp_dir: path to the temporary local working directory :type temp_dir: ``str`` """ + # This loop is created to support overlayfs archives. This should + # squash all of the layers into a single archive. + fs_paths = container_path.split(':') + if 'overlayfs' in fs_paths: + fs_paths.pop(fs_paths.index('overlayfs')) + + for fs_path in fs_paths: + # Set the path to the container data + fs_path = os.path.dirname(fs_path) + + # Run the sync command + build_command = [ + self.module.get_bin_path('rsync', True), + '-aHAX', + fs_path, + temp_dir + ] + rc, stdout, err = self._run_command( + build_command, + unsafe_shell=True + ) + if rc != 0: + self.failure( + err=err, + rc=rc, + msg='failed to perform archive', + command=' '.join(build_command) + ) + + def _unmount(self, mount_point): + """Unmount a file system. + + :param mount_point: path on the file system that is mounted. + :type mount_point: ``str`` + """ build_command = [ - self.module.get_bin_path('rsync', True), - '-aHAX', - container_path, - temp_dir + self.module.get_bin_path('umount', True), + mount_point, ] - rc, stdout, err = self._run_command(build_command, unsafe_shell=True) + rc, stdout, err = self._run_command(build_command) if rc != 0: self.failure( err=err, rc=rc, - msg='failed to perform archive', + msg='failed to unmount [ %s ]' % mount_point, command=' '.join(build_command) ) - def _unmount(self, mount_point): - """Unmount a file system. + def _overlayfs_mount(self, lowerdir, upperdir, mount_point): + """mount an lv. + :param lowerdir: name/path of the lower directory + :type lowerdir: ``str`` + :param upperdir: name/path of the upper directory + :type upperdir: ``str`` :param mount_point: path on the file system that is mounted. :type mount_point: ``str`` """ build_command = [ - self.module.get_bin_path('umount', True), + self.module.get_bin_path('mount', True), + '-t overlayfs', + '-o lowerdir=%s,upperdir=%s' % (lowerdir, upperdir), + 'overlayfs', mount_point, ] rc, stdout, err = self._run_command(build_command) @@ -1245,8 +1464,8 @@ class LxcContainerManagement(object): self.failure( err=err, rc=rc, - msg='failed to unmount [ %s ]' % mount_point, - command=' '.join(build_command) + msg='failed to mount overlayfs:%s:%s to %s -- Command: %s' + % (lowerdir, upperdir, mount_point, build_command) ) def _container_create_tar(self): @@ -1275,13 +1494,15 @@ class LxcContainerManagement(object): # Test if the containers rootfs is a block device block_backed = lxc_rootfs.startswith(os.path.join(os.sep, 'dev')) + + # Test if the container is using overlayfs + overlayfs_backed = lxc_rootfs.startswith('overlayfs') + mount_point = os.path.join(work_dir, 'rootfs') # Set the snapshot name if needed snapshot_name = '%s_lxc_snapshot' % self.container_name - # Set the path to the container data - container_path = os.path.dirname(lxc_rootfs) container_state = self._get_state() try: # Ensure the original container is stopped or frozen @@ -1292,7 +1513,7 @@ class LxcContainerManagement(object): self.container.stop() # Sync the container data from the container_path to work_dir - self._rsync_data(container_path, temp_dir) + self._rsync_data(lxc_rootfs, temp_dir) if block_backed: if snapshot_name not in self._lvm_lv_list(): @@ -1301,7 +1522,7 @@ class LxcContainerManagement(object): # Take snapshot size, measurement = self._get_lv_size( - name=self.container_name + lv_name=self.container_name ) self._lvm_snapshot_create( source_lv=self.container_name, @@ -1322,25 +1543,33 @@ class LxcContainerManagement(object): ' up old snapshot of containers before continuing.' % snapshot_name ) - - # Restore original state of container - if container_state == 'running': - if self._get_state() == 'frozen': - self.container.unfreeze() - else: - self.container.start() + elif overlayfs_backed: + lowerdir, upperdir = lxc_rootfs.split(':')[1:] + self._overlayfs_mount( + lowerdir=lowerdir, + upperdir=upperdir, + mount_point=mount_point + ) # Set the state as changed and set a new fact self.state_change = True return self._create_tar(source_dir=work_dir) finally: - if block_backed: + if block_backed or overlayfs_backed: # unmount snapshot self._unmount(mount_point) + if block_backed: # Remove snapshot self._lvm_lv_remove(snapshot_name) + # Restore original state of container + if container_state == 'running': + if self._get_state() == 'frozen': + self.container.unfreeze() + else: + self.container.start() + # Remove tmpdir shutil.rmtree(temp_dir) @@ -1374,6 +1603,9 @@ class LxcContainerManagement(object): if self.archive_info: outcome.update(self.archive_info) + if self.clone_info: + outcome.update(self.clone_info) + self.module.exit_json( changed=self.state_change, lxc_container=outcome @@ -1450,6 +1682,14 @@ def main(): choices=[n for i in LXC_LOGGING_LEVELS.values() for n in i], default='INFO' ), + clone_name=dict( + type='str', + required=False + ), + clone_snapshot=dict( + choices=BOOLEANS, + default='false' + ), archive=dict( choices=BOOLEANS, default='false' @@ -1477,4 +1717,3 @@ def main(): # import module bits from ansible.module_utils.basic import * main() - diff --git a/cloud/misc/ovirt.py b/cloud/misc/ovirt.py index 2d54ad3f401..718f25fec2c 100755 --- a/cloud/misc/ovirt.py +++ b/cloud/misc/ovirt.py @@ -20,7 +20,7 @@ DOCUMENTATION = ''' --- module: ovirt -author: Vincent Van der Kussen +author: '"Vincent Van der Kussen (@vincentvdk)" ' short_description: oVirt/RHEV platform management description: - allows you to create new instances, either from scratch or an image, in addition to deleting or stopping instances on the oVirt/RHEV platform @@ -152,7 +152,9 @@ options: aliases: [] choices: ['present', 'absent', 'shutdown', 'started', 'restarted'] -requirements: [ "ovirt-engine-sdk" ] +requirements: + - "python >= 2.6" + - "ovirt-engine-sdk-python" ''' EXAMPLES = ''' # Basic example provisioning from image. diff --git a/cloud/misc/virt.py b/cloud/misc/virt.py index f1d36fc1964..343a3eedcf7 100644 --- a/cloud/misc/virt.py +++ b/cloud/misc/virt.py @@ -55,8 +55,13 @@ options: - XML document used with the define command required: false default: null -requirements: [ "libvirt" ] -author: Michael DeHaan, Seth Vidal +requirements: + - "python >= 2.6" + - "libvirt-python" +author: + - "Ansible Core Team" + - '"Michael DeHaan (@mpdehaan)" ' + - '"Seth Vidal (@skvidal)" ' ''' EXAMPLES = ''' diff --git a/cloud/vmware/vmware_datacenter.py b/cloud/vmware/vmware_datacenter.py index 35cf7fa4692..b1e995b965b 100644 --- a/cloud/vmware/vmware_datacenter.py +++ b/cloud/vmware/vmware_datacenter.py @@ -25,10 +25,11 @@ short_description: Manage VMware vSphere Datacenters description: - Manage VMware vSphere Datacenters version_added: 2.0 -author: Joseph Callen +author: '"Joseph Callen (@jcpowermac)" ' notes: - Tested on vSphere 5.5 requirements: + - "python >= 2.6" - PyVmomi options: hostname: diff --git a/clustering/consul b/clustering/consul.py similarity index 99% rename from clustering/consul rename to clustering/consul.py index 5db79e20c40..9195c0ff591 100644 --- a/clustering/consul +++ b/clustering/consul.py @@ -38,10 +38,11 @@ description: changed occurred. An api method is planned to supply this metadata so at that stage change management will be added. requirements: + - "python >= 2.6" - python-consul - requests version_added: "1.9" -author: Steve Gargan (steve.gargan@gmail.com) +author: '"Steve Gargan (@sgargan)" ' options: state: description: diff --git a/clustering/consul_acl b/clustering/consul_acl.py similarity index 99% rename from clustering/consul_acl rename to clustering/consul_acl.py index c481b780a64..31cb01d1404 100644 --- a/clustering/consul_acl +++ b/clustering/consul_acl.py @@ -25,11 +25,12 @@ description: rules in a consul cluster via the agent. For more details on using and configuring ACLs, see https://www.consul.io/docs/internals/acl.html. requirements: + - "python >= 2.6" - python-consul - pyhcl - requests version_added: "1.9" -author: Steve Gargan (steve.gargan@gmail.com) +author: '"Steve Gargan (@sgargan)" ' options: mgmt_token: description: diff --git a/clustering/consul_kv b/clustering/consul_kv.py similarity index 99% rename from clustering/consul_kv rename to clustering/consul_kv.py index e5a010a8c18..13437d95cce 100644 --- a/clustering/consul_kv +++ b/clustering/consul_kv.py @@ -28,10 +28,11 @@ description: represents a prefix then Note that when a value is removed, the existing value if any is returned as part of the results. requirements: + - "python >= 2.6" - python-consul - requests version_added: "1.9" -author: Steve Gargan (steve.gargan@gmail.com) +author: '"Steve Gargan (@sgargan)" ' options: state: description: diff --git a/clustering/consul_session b/clustering/consul_session.py similarity index 99% rename from clustering/consul_session rename to clustering/consul_session.py index 8e6516891d2..8e7f763a21d 100644 --- a/clustering/consul_session +++ b/clustering/consul_session.py @@ -26,10 +26,11 @@ description: to implement distributed locks. In depth documentation for working with sessions can be found here http://www.consul.io/docs/internals/sessions.html requirements: + - "python >= 2.6" - python-consul - requests version_added: "1.9" -author: Steve Gargan (steve.gargan@gmail.com) +author: '"Steve Gargan (@sgargan)" ' options: state: description: diff --git a/database/misc/mongodb_user.py b/database/misc/mongodb_user.py index 3a3cf4dfff1..83a3395216e 100644 --- a/database/misc/mongodb_user.py +++ b/database/misc/mongodb_user.py @@ -91,7 +91,7 @@ notes: - Requires the pymongo Python package on the remote host, version 2.4.2+. This can be installed using pip or the OS package manager. @see http://api.mongodb.org/python/current/installation.html requirements: [ "pymongo" ] -author: Elliott Foster +author: '"Elliott Foster (@elliotttf)" ' ''' EXAMPLES = ''' diff --git a/database/misc/redis.py b/database/misc/redis.py index eb9654631e7..42e364a8e61 100644 --- a/database/misc/redis.py +++ b/database/misc/redis.py @@ -98,7 +98,7 @@ notes: this needs to be in the redis.conf in the masterauth variable requirements: [ redis ] -author: Xabier Larrakoetxea +author: "Xabier Larrakoetxea (@slok)" ''' EXAMPLES = ''' diff --git a/database/misc/riak.py b/database/misc/riak.py index b30e7dc485d..4f10775a5ad 100644 --- a/database/misc/riak.py +++ b/database/misc/riak.py @@ -26,6 +26,9 @@ description: - This module can be used to join nodes to a cluster, check the status of the cluster. version_added: "1.2" +author: + - '"James Martin (@jsmartin)" ' + - '"Drew Kerrigan (@drewkerrigan)" ' options: command: description: diff --git a/database/mysql/mysql_replication.py b/database/mysql/mysql_replication.py index 30811cdc924..898b1510c1d 100644 --- a/database/mysql/mysql_replication.py +++ b/database/mysql/mysql_replication.py @@ -30,6 +30,7 @@ short_description: Manage MySQL replication description: - Manages MySQL server replication, slave, master status get and change master host. version_added: "1.3" +author: '"Balazs Pocze (@banyek)" ' options: mode: description: @@ -93,7 +94,7 @@ options: master_ssl: description: - same as mysql variable - possible values: 0,1 + choices: [ 0, 1 ] master_ssl_ca: description: - same as mysql variable @@ -109,7 +110,12 @@ options: master_ssl_cipher: description: - same as mysql variable - + master_auto_position: + description: + - does the host uses GTID based replication or not + required: false + default: null + version_added: "2.0" ''' EXAMPLES = ''' @@ -242,6 +248,7 @@ def main(): login_port=dict(default=3306, type='int'), login_unix_socket=dict(default=None), mode=dict(default="getslave", choices=["getmaster", "getslave", "changemaster", "stopslave", "startslave"]), + master_auto_position=dict(default=False, type='bool'), master_host=dict(default=None), master_user=dict(default=None), master_password=dict(default=None), @@ -279,6 +286,7 @@ def main(): master_ssl_cert = module.params["master_ssl_cert"] master_ssl_key = module.params["master_ssl_key"] master_ssl_cipher = module.params["master_ssl_cipher"] + master_auto_position = module.params["master_auto_position"] if not mysqldb_found: module.fail_json(msg="the python mysqldb module is required") @@ -376,6 +384,8 @@ def main(): if master_ssl_cipher: chm.append("MASTER_SSL_CIPHER=%(master_ssl_cipher)s") chm_params['master_ssl_cipher'] = master_ssl_cipher + if master_auto_position: + chm.append("MASTER_AUTO_POSITION = 1") changemaster(cursor, chm, chm_params) module.exit_json(changed=True) elif mode in "startslave": diff --git a/database/postgresql/postgresql_ext.py b/database/postgresql/postgresql_ext.py index d70107a4cf9..07ed48e9d03 100644 --- a/database/postgresql/postgresql_ext.py +++ b/database/postgresql/postgresql_ext.py @@ -65,7 +65,7 @@ notes: - This module uses I(psycopg2), a Python PostgreSQL database adapter. You must ensure that psycopg2 is installed on the host before using this module. If the remote host is the PostgreSQL server (which is the default case), then PostgreSQL must also be installed on the remote host. For Ubuntu-based systems, install the C(postgresql), C(libpq-dev), and C(python-psycopg2) packages on the remote host before using this module. requirements: [ psycopg2 ] -author: Daniel Schep +author: "Daniel Schep (@dschep)" ''' EXAMPLES = ''' diff --git a/database/postgresql/postgresql_lang.py b/database/postgresql/postgresql_lang.py index ec0507b5508..f3b1baa4d9a 100644 --- a/database/postgresql/postgresql_lang.py +++ b/database/postgresql/postgresql_lang.py @@ -95,7 +95,7 @@ notes: systems, install the postgresql, libpq-dev, and python-psycopg2 packages on the remote host before using this module. requirements: [ psycopg2 ] -author: Jens Depuydt +author: "Jens Depuydt (@jensdepuydt)" ''' EXAMPLES = ''' diff --git a/database/vertica/vertica_configuration.py b/database/vertica/vertica_configuration.py index ad74c0f23f2..ed75667b139 100644 --- a/database/vertica/vertica_configuration.py +++ b/database/vertica/vertica_configuration.py @@ -67,7 +67,7 @@ notes: and both C(ErrorMessagesPath = /opt/vertica/lib64) and C(DriverManagerEncoding = UTF-16) to be added to the C(Driver) section of either C(/etc/vertica.ini) or C($HOME/.vertica.ini). requirements: [ 'unixODBC', 'pyodbc' ] -author: Dariusz Owczarek +author: "Dariusz Owczarek (@dareko)" """ EXAMPLES = """ diff --git a/database/vertica/vertica_facts.py b/database/vertica/vertica_facts.py index b7e0ac4ad5a..705b74a04f5 100644 --- a/database/vertica/vertica_facts.py +++ b/database/vertica/vertica_facts.py @@ -59,7 +59,7 @@ notes: and both C(ErrorMessagesPath = /opt/vertica/lib64) and C(DriverManagerEncoding = UTF-16) to be added to the C(Driver) section of either C(/etc/vertica.ini) or C($HOME/.vertica.ini). requirements: [ 'unixODBC', 'pyodbc' ] -author: Dariusz Owczarek +author: "Dariusz Owczarek (@dareko)" """ EXAMPLES = """ diff --git a/database/vertica/vertica_role.py b/database/vertica/vertica_role.py index ef56a58a866..b7a0a5d66ef 100644 --- a/database/vertica/vertica_role.py +++ b/database/vertica/vertica_role.py @@ -75,7 +75,7 @@ notes: and both C(ErrorMessagesPath = /opt/vertica/lib64) and C(DriverManagerEncoding = UTF-16) to be added to the C(Driver) section of either C(/etc/vertica.ini) or C($HOME/.vertica.ini). requirements: [ 'unixODBC', 'pyodbc' ] -author: Dariusz Owczarek +author: "Dariusz Owczarek (@dareko)" """ EXAMPLES = """ diff --git a/database/vertica/vertica_schema.py b/database/vertica/vertica_schema.py index d0ed2ce05b0..39ccb0b60e8 100644 --- a/database/vertica/vertica_schema.py +++ b/database/vertica/vertica_schema.py @@ -91,7 +91,7 @@ notes: and both C(ErrorMessagesPath = /opt/vertica/lib64) and C(DriverManagerEncoding = UTF-16) to be added to the C(Driver) section of either C(/etc/vertica.ini) or C($HOME/.vertica.ini). requirements: [ 'unixODBC', 'pyodbc' ] -author: Dariusz Owczarek +author: "Dariusz Owczarek (@dareko)" """ EXAMPLES = """ diff --git a/database/vertica/vertica_user.py b/database/vertica/vertica_user.py index a011bf35adb..7c52df3163a 100644 --- a/database/vertica/vertica_user.py +++ b/database/vertica/vertica_user.py @@ -107,7 +107,7 @@ notes: and both C(ErrorMessagesPath = /opt/vertica/lib64) and C(DriverManagerEncoding = UTF-16) to be added to the C(Driver) section of either C(/etc/vertica.ini) or C($HOME/.vertica.ini). requirements: [ 'unixODBC', 'pyodbc' ] -author: Dariusz Owczarek +author: "Dariusz Owczarek (@dareko)" """ EXAMPLES = """ @@ -233,7 +233,10 @@ def present(user_facts, cursor, user, profile, resource_pool, changed = False query_fragments = ["alter user {0}".format(user)] if locked is not None and locked != (user_facts[user_key]['locked'] == 'True'): - state = 'lock' if locked else 'unlock' + if locked: + state = 'lock' + else: + state = 'unlock' query_fragments.append("account {0}".format(state)) changed = True if password and password != user_facts[user_key]['password']: diff --git a/files/patch.py b/files/patch.py index ec3a3b02c00..c2982e2380e 100755 --- a/files/patch.py +++ b/files/patch.py @@ -22,7 +22,9 @@ DOCUMENTATION = ''' --- module: patch -author: Luis Alberto Perez Lazaro, Jakub Jirutka +author: + - '"Jakub Jirutka (@jirutka)" ' + - '"Luis Alberto Perez Lazaro (@luisperlaz)" ' version_added: 1.9 description: - Apply patch files using the GNU patch tool. @@ -110,7 +112,7 @@ def apply_patch(patch_func, patch_file, basedir, dest_file=None, strip=0, dry_ru (rc, out, err) = patch_func(opts) if rc != 0: - msg = out if not err else err + msg = err or out raise PatchError(msg) diff --git a/messaging/rabbitmq_parameter.py b/messaging/rabbitmq_parameter.py index 2f78bd4ee15..6be18bdce3d 100644 --- a/messaging/rabbitmq_parameter.py +++ b/messaging/rabbitmq_parameter.py @@ -25,7 +25,7 @@ short_description: Adds or removes parameters to RabbitMQ description: - Manage dynamic, cluster-wide parameters for RabbitMQ version_added: "1.1" -author: Chris Hoffman +author: '"Chris Hoffman (@chrishoffman)"' options: component: description: diff --git a/messaging/rabbitmq_plugin.py b/messaging/rabbitmq_plugin.py index 53c38f978d5..db23df3fcc8 100644 --- a/messaging/rabbitmq_plugin.py +++ b/messaging/rabbitmq_plugin.py @@ -25,7 +25,7 @@ short_description: Adds or removes plugins to RabbitMQ description: - Enables or disables RabbitMQ plugins version_added: "1.1" -author: Chris Hoffman +author: '"Chris Hoffman (@chrishoffman)"' options: names: description: diff --git a/messaging/rabbitmq_policy.py b/messaging/rabbitmq_policy.py index 800c3822d55..a4d94decbd1 100644 --- a/messaging/rabbitmq_policy.py +++ b/messaging/rabbitmq_policy.py @@ -26,7 +26,7 @@ short_description: Manage the state of policies in RabbitMQ. description: - Manage the state of a virtual host in RabbitMQ. version_added: "1.5" -author: John Dewey +author: '"John Dewey (@retr0h)" ' options: name: description: diff --git a/messaging/rabbitmq_user.py b/messaging/rabbitmq_user.py index f494ce802d9..6333e42282e 100644 --- a/messaging/rabbitmq_user.py +++ b/messaging/rabbitmq_user.py @@ -25,7 +25,7 @@ short_description: Adds or removes users to RabbitMQ description: - Add or remove users to RabbitMQ and assign permissions version_added: "1.1" -author: Chris Hoffman +author: '"Chris Hoffman (@chrishoffman)"' options: user: description: diff --git a/messaging/rabbitmq_vhost.py b/messaging/rabbitmq_vhost.py index fd4b04a683f..dbde32393cb 100644 --- a/messaging/rabbitmq_vhost.py +++ b/messaging/rabbitmq_vhost.py @@ -26,7 +26,7 @@ short_description: Manage the state of a virtual host in RabbitMQ description: - Manage the state of a virtual host in RabbitMQ version_added: "1.1" -author: Chris Hoffman +author: '"Chris Hoffman (@choffman)"' options: name: description: diff --git a/monitoring/airbrake_deployment.py b/monitoring/airbrake_deployment.py index e1c490b881b..0036bde7daa 100644 --- a/monitoring/airbrake_deployment.py +++ b/monitoring/airbrake_deployment.py @@ -22,7 +22,7 @@ DOCUMENTATION = ''' --- module: airbrake_deployment version_added: "1.2" -author: Bruce Pennypacker +author: '"Bruce Pennypacker (@bpennypacker)" ' short_description: Notify airbrake about app deployments description: - Notify airbrake about app deployments (see http://help.airbrake.io/kb/api-2/deploy-tracking) diff --git a/monitoring/bigpanda.py b/monitoring/bigpanda.py index 11950287078..3bed44893b7 100644 --- a/monitoring/bigpanda.py +++ b/monitoring/bigpanda.py @@ -3,7 +3,7 @@ DOCUMENTATION = ''' --- module: bigpanda -author: BigPanda +author: "Hagai Kariti (@hkariti)" short_description: Notify BigPanda about deployments version_added: "1.8" description: @@ -162,7 +162,7 @@ def main(): module.exit_json(changed=True, **deployment) else: module.fail_json(msg=json.dumps(info)) - except Exception as e: + except Exception, e: module.fail_json(msg=str(e)) # import module snippets diff --git a/monitoring/boundary_meter.py b/monitoring/boundary_meter.py index da739d4306f..adc2b2433e1 100644 --- a/monitoring/boundary_meter.py +++ b/monitoring/boundary_meter.py @@ -34,7 +34,7 @@ short_description: Manage boundary meters description: - This module manages boundary meters version_added: "1.3" -author: curtis@serverascode.com +author: '"curtis (@ccollicutt)" ' requirements: - Boundary API access - bprobe is required to send data, but not to register a meter @@ -213,7 +213,7 @@ def download_request(module, name, apiid, apikey, cert_type): cert_file = open(cert_file_path, 'w') cert_file.write(body) cert_file.close - os.chmod(cert_file_path, 0o600) + os.chmod(cert_file_path, 0600) except: module.fail_json("Could not write to certificate file") diff --git a/monitoring/circonus_annotation.py b/monitoring/circonus_annotation.py new file mode 100644 index 00000000000..1585cd8080a --- /dev/null +++ b/monitoring/circonus_annotation.py @@ -0,0 +1,132 @@ +#!/usr/bin/python + +# (c) 2014-2015, Epic Games, Inc. + +import requests +import time +import json + +DOCUMENTATION = ''' +--- +module: circonus_annotation +short_description: create an annotation in circonus +description: + - Create an annotation event with a given category, title and description. Optionally start, end or durations can be provided +author: "Nick Harring (@NickatEpic)" +version_added: 2.0 +requirements: + - urllib3 + - requests + - time +options: + api_key: + description: + - Circonus API key + required: true + category: + description: + - Annotation Category + required: true + description: + description: + - Description of annotation + required: true + title: + description: + - Title of annotation + required: true + start: + description: + - Unix timestamp of event start, defaults to now + required: false + stop: + description: + - Unix timestamp of event end, defaults to now + duration + required: false + duration: + description: + - Duration in seconds of annotation, defaults to 0 + required: false +''' +EXAMPLES = ''' +# Create a simple annotation event with a source, defaults to start and end time of now +- circonus_annotation: + api_key: XXXXXXXXXXXXXXXXX + title: 'App Config Change' + description: 'This is a detailed description of the config change' + category: 'This category groups like annotations' +# Create an annotation with a duration of 5 minutes and a default start time of now +- circonus_annotation: + api_key: XXXXXXXXXXXXXXXXX + title: 'App Config Change' + description: 'This is a detailed description of the config change' + category: 'This category groups like annotations' + duration: 300 +# Create an annotation with a start_time and end_time +- circonus_annotation: + api_key: XXXXXXXXXXXXXXXXX + title: 'App Config Change' + description: 'This is a detailed description of the config change' + category: 'This category groups like annotations' + start_time: 1395940006 + end_time: 1395954407 +''' +def post_annotation(annotation, api_key): + ''' Takes annotation dict and api_key string''' + base_url = 'https://api.circonus.com/v2' + anootate_post_endpoint = '/annotation' + resp = requests.post(base_url + anootate_post_endpoint, + headers=build_headers(api_key), data=json.dumps(annotation)) + resp.raise_for_status() + return resp + +def create_annotation(module): + ''' Takes ansible module object ''' + annotation = {} + if module.params['duration'] != None: + duration = module.params['duration'] + else: + duration = 0 + if module.params['start'] != None: + start = module.params['start'] + else: + start = int(time.time()) + if module.params['stop'] != None: + stop = module.params['stop'] + else: + stop = int(time.time())+ duration + annotation['start'] = int(start) + annotation['stop'] = int(stop) + annotation['category'] = module.params['category'] + annotation['description'] = module.params['description'] + annotation['title'] = module.params['title'] + return annotation +def build_headers(api_token): + '''Takes api token, returns headers with it included.''' + headers = {'X-Circonus-App-Name': 'ansible', + 'Host': 'api.circonus.com', 'X-Circonus-Auth-Token': api_token, + 'Accept': 'application/json'} + return headers + +def main(): + '''Main function, dispatches logic''' + module = AnsibleModule( + argument_spec=dict( + start=dict(required=False, type='int'), + stop=dict(required=False, type='int'), + category=dict(required=True), + title=dict(required=True), + description=dict(required=True), + duration=dict(required=False, type='int'), + api_key=dict(required=True) + ) + ) + annotation = create_annotation(module) + try: + resp = post_annotation(annotation, module.params['api_key']) + except requests.exceptions.RequestException, err_str: + module.fail_json(msg='Request Failed', reason=err_str) + module.exit_json(changed=True, annotation=resp.json()) + +from ansible.module_utils.basic import * +main() diff --git a/monitoring/datadog_event.py b/monitoring/datadog_event.py index 5d38dd4c31d..1d6a98dc9c3 100644 --- a/monitoring/datadog_event.py +++ b/monitoring/datadog_event.py @@ -14,7 +14,7 @@ description: - "Allows to post events to DataDog (www.datadoghq.com) service." - "Uses http://docs.datadoghq.com/api/#events API." version_added: "1.3" -author: Artūras 'arturaz' Šlajus +author: '"Artūras `arturaz` Šlajus (@arturaz)" ' notes: [] requirements: [urllib2] options: diff --git a/monitoring/librato_annotation.py b/monitoring/librato_annotation.py index 63979f41bfb..88d3bb81f7b 100644 --- a/monitoring/librato_annotation.py +++ b/monitoring/librato_annotation.py @@ -29,7 +29,7 @@ short_description: create an annotation in librato description: - Create an annotation event on the given annotation stream :name. If the annotation stream does not exist, it will be created automatically version_added: "1.6" -author: Seth Edwards +author: "Seth Edwards (@sedward)" requirements: - urllib2 - base64 @@ -138,11 +138,11 @@ def post_annotation(module): headers = {} headers['Content-Type'] = 'application/json' - headers['Authorization'] = b"Basic " + base64.b64encode(user + b":" + api_key).strip() + headers['Authorization'] = "Basic " + base64.b64encode(user + ":" + api_key).strip() req = urllib2.Request(url, json_body, headers) try: response = urllib2.urlopen(req) - except urllib2.HTTPError as e: + except urllib2.HTTPError, e: module.fail_json(msg="Request Failed", reason=e.reason) response = response.read() module.exit_json(changed=True, annotation=response) diff --git a/monitoring/logentries.py b/monitoring/logentries.py index a19885ea702..75ed2e0e6dd 100644 --- a/monitoring/logentries.py +++ b/monitoring/logentries.py @@ -19,8 +19,8 @@ DOCUMENTATION = ''' --- module: logentries -author: Ivan Vanderbyl -short_description: Module for tracking logs via logentries.com +author: '"Ivan Vanderbyl (@ivanvanderbyl)" ' +short_description: Module for tracking logs via logentries.com description: - Sends logs to LogEntries in realtime version_added: "1.6" diff --git a/monitoring/monit.py b/monitoring/monit.py index 8772d22b2d8..e87d8edca5a 100644 --- a/monitoring/monit.py +++ b/monitoring/monit.py @@ -39,7 +39,7 @@ options: default: null choices: [ "present", "started", "stopped", "restarted", "monitored", "unmonitored", "reloaded" ] requirements: [ ] -author: Darryl Stoflet +author: '"Darryl Stoflet (@dstoflet)" ' ''' EXAMPLES = ''' diff --git a/monitoring/nagios.py b/monitoring/nagios.py index c564e712b04..64716e81c71 100644 --- a/monitoring/nagios.py +++ b/monitoring/nagios.py @@ -73,7 +73,7 @@ options: required: true default: null -author: Tim Bielawa +author: '"Tim Bielawa (@tbielawa)" ' requirements: [ "Nagios" ] ''' diff --git a/monitoring/newrelic_deployment.py b/monitoring/newrelic_deployment.py index 93d55832fd3..91a08da4871 100644 --- a/monitoring/newrelic_deployment.py +++ b/monitoring/newrelic_deployment.py @@ -22,7 +22,7 @@ DOCUMENTATION = ''' --- module: newrelic_deployment version_added: "1.2" -author: Matt Coddington +author: '"Matt Coddington (@mcodd)" ' short_description: Notify newrelic about app deployments description: - Notify newrelic about app deployments (see http://newrelic.github.io/newrelic_api/NewRelicApi/Deployment.html) diff --git a/monitoring/pagerduty.py b/monitoring/pagerduty.py index aa6903414dd..24c622c83a8 100644 --- a/monitoring/pagerduty.py +++ b/monitoring/pagerduty.py @@ -7,7 +7,10 @@ short_description: Create PagerDuty maintenance windows description: - This module will let you create PagerDuty maintenance windows version_added: "1.2" -author: Justin Johns +author: + - "Andrew Newdigate (@suprememoocow)" + - "Dylan Silva (@thaumos)" + - "Justin Johns" requirements: - PagerDuty API access options: diff --git a/monitoring/pingdom.py b/monitoring/pingdom.py index 0ae1af357e0..fd06a1217cb 100644 --- a/monitoring/pingdom.py +++ b/monitoring/pingdom.py @@ -7,7 +7,9 @@ short_description: Pause/unpause Pingdom alerts description: - This module will let you pause/unpause Pingdom alerts version_added: "1.2" -author: Justin Johns +author: + - "Dylan Silva (@thaumos)" + - "Justin Johns" requirements: - "This pingdom python library: https://github.com/mbabineau/pingdom-python" options: diff --git a/monitoring/rollbar_deployment.py b/monitoring/rollbar_deployment.py index 772e78fc5c2..dc064d6194d 100644 --- a/monitoring/rollbar_deployment.py +++ b/monitoring/rollbar_deployment.py @@ -22,7 +22,7 @@ DOCUMENTATION = ''' --- module: rollbar_deployment version_added: 1.6 -author: Max Riveiro +author: '"Max Riveiro (@kavu)" ' short_description: Notify Rollbar about app deployments description: - Notify Rollbar about app deployments diff --git a/monitoring/stackdriver.py b/monitoring/stackdriver.py index c36964dd9d2..570e6659ac0 100644 --- a/monitoring/stackdriver.py +++ b/monitoring/stackdriver.py @@ -8,7 +8,7 @@ short_description: Send code deploy and annotation events to stackdriver description: - Send code deploy and annotation events to Stackdriver version_added: "1.6" -author: Ben Whaley +author: "Ben Whaley (@bwhaley)" options: key: description: diff --git a/monitoring/uptimerobot.py b/monitoring/uptimerobot.py index 889d144c9b3..6d5c9c7bac0 100644 --- a/monitoring/uptimerobot.py +++ b/monitoring/uptimerobot.py @@ -6,7 +6,7 @@ module: uptimerobot short_description: Pause and start Uptime Robot monitoring description: - This module will let you start and pause Uptime Robot Monitoring -author: Nate Kingsley +author: "Nate Kingsley (@nate-kingsley)" version_added: "1.9" requirements: - Valid Uptime Robot API Key diff --git a/monitoring/zabbix_group.py b/monitoring/zabbix_group.py index 489a8617f54..f622de5a4f7 100644 --- a/monitoring/zabbix_group.py +++ b/monitoring/zabbix_group.py @@ -26,7 +26,9 @@ short_description: Add or remove a host group to Zabbix. description: - This module uses the Zabbix API to add and remove host groups. version_added: '1.8' -requirements: [ 'zabbix-api' ] +requirements: + - "python >= 2.6" + - zabbix-api options: state: description: @@ -62,7 +64,7 @@ options: required: true notes: - The module has been tested with Zabbix Server 2.2. -author: René Moser +author: '"René Moser (@resmo)" ' ''' EXAMPLES = ''' diff --git a/monitoring/zabbix_host.py b/monitoring/zabbix_host.py index c7b8e52b9e7..2d6ce04b830 100644 --- a/monitoring/zabbix_host.py +++ b/monitoring/zabbix_host.py @@ -26,9 +26,13 @@ short_description: Zabbix host creates/updates/deletes description: - This module allows you to create, modify and delete Zabbix host entries and associated group and template data. version_added: "2.0" -author: Tony Minfei Ding, Harrison Gu +author: + - "(@cove)" + - "Tony Minfei Ding" + - "Harrison Gu (@harrisongu)" requirements: - - zabbix-api python module + - "python >= 2.6" + - zabbix-api options: server_url: description: diff --git a/monitoring/zabbix_hostmacro.py b/monitoring/zabbix_hostmacro.py index b41e114d760..a8649454025 100644 --- a/monitoring/zabbix_hostmacro.py +++ b/monitoring/zabbix_hostmacro.py @@ -26,9 +26,12 @@ short_description: Zabbix host macro creates/updates/deletes description: - manages Zabbix host macros, it can create, update or delete them. version_added: "2.0" -author: Dean Hailin Song +author: + - "(@cave)" + - Dean Hailin Song requirements: - - zabbix-api python module + - "python >= 2.6" + - zabbix-api options: server_url: description: diff --git a/monitoring/zabbix_maintenance.py b/monitoring/zabbix_maintenance.py index 559f9e0e55a..02938234fbf 100644 --- a/monitoring/zabbix_maintenance.py +++ b/monitoring/zabbix_maintenance.py @@ -26,9 +26,10 @@ short_description: Create Zabbix maintenance windows description: - This module will let you create Zabbix maintenance windows. version_added: "1.8" -author: Alexander Bulimov +author: '"Alexander Bulimov (@abulimov)" ' requirements: - - zabbix-api python module + - "python >= 2.6" + - zabbix-api options: state: description: diff --git a/monitoring/zabbix_screen.py b/monitoring/zabbix_screen.py index ada2b1c6ab0..932681617f5 100644 --- a/monitoring/zabbix_screen.py +++ b/monitoring/zabbix_screen.py @@ -27,9 +27,13 @@ short_description: Zabbix screen creates/updates/deletes description: - This module allows you to create, modify and delete Zabbix screens and associated graph data. version_added: "2.0" -author: Tony Minfei Ding, Harrison Gu +author: + - "(@cove)" + - "Tony Minfei Ding" + - "Harrison Gu (@harrisongu)" requirements: - - zabbix-api python module + - "python >= 2.6" + - zabbix-api options: server_url: description: diff --git a/network/a10/a10_server.py b/network/a10/a10_server.py index 65410536eef..2d7b8cc5d9c 100644 --- a/network/a10/a10_server.py +++ b/network/a10/a10_server.py @@ -28,7 +28,7 @@ version_added: 1.8 short_description: Manage A10 Networks AX/SoftAX/Thunder/vThunder devices description: - Manage slb server objects on A10 Networks devices via aXAPI -author: Mischa Peters +author: '"Mischa Peters (@mischapeters)" ' notes: - Requires A10 Networks aXAPI 2.1 options: diff --git a/network/a10/a10_service_group.py b/network/a10/a10_service_group.py index 3627e2d12b8..8e84bf9a07d 100644 --- a/network/a10/a10_service_group.py +++ b/network/a10/a10_service_group.py @@ -28,7 +28,7 @@ version_added: 1.8 short_description: Manage A10 Networks AX/SoftAX/Thunder/vThunder devices description: - Manage slb service-group objects on A10 Networks devices via aXAPI -author: Mischa Peters +author: '"Mischa Peters (@mischapeters)" ' notes: - Requires A10 Networks aXAPI 2.1 - When a server doesn't exist and is added to the service-group the server will be created diff --git a/network/a10/a10_virtual_server.py b/network/a10/a10_virtual_server.py index 3d807c098cf..3df93f67dbe 100644 --- a/network/a10/a10_virtual_server.py +++ b/network/a10/a10_virtual_server.py @@ -28,7 +28,7 @@ version_added: 1.8 short_description: Manage A10 Networks AX/SoftAX/Thunder/vThunder devices description: - Manage slb virtual server objects on A10 Networks devices via aXAPI -author: Mischa Peters +author: '"Mischa Peters (@mischapeters)" ' notes: - Requires A10 Networks aXAPI 2.1 requirements: diff --git a/network/citrix/netscaler.py b/network/citrix/netscaler.py index b2f87aa0d08..8f78e23caac 100644 --- a/network/citrix/netscaler.py +++ b/network/citrix/netscaler.py @@ -82,7 +82,7 @@ options: choices: ['yes', 'no'] requirements: [ "urllib", "urllib2" ] -author: Nandor Sivok +author: '"Nandor Sivok (@dominis)" ' ''' EXAMPLES = ''' diff --git a/network/dnsimple.py b/network/dnsimple.py index 9aa52172f19..5cecfbd8169 100755 --- a/network/dnsimple.py +++ b/network/dnsimple.py @@ -93,7 +93,7 @@ options: default: null requirements: [ dnsimple ] -author: Alex Coomans +author: "Alex Coomans (@drcapulet)" ''' EXAMPLES = ''' diff --git a/network/dnsmadeeasy.py b/network/dnsmadeeasy.py index 148e25a5011..dc70d0e5569 100644 --- a/network/dnsmadeeasy.py +++ b/network/dnsmadeeasy.py @@ -87,7 +87,7 @@ notes: - This module returns record(s) in the "result" element when 'state' is set to 'present'. This value can be be registered and used in your playbooks. requirements: [ urllib, urllib2, hashlib, hmac ] -author: Brice Burgess +author: "Brice Burgess (@briceburg)" ''' EXAMPLES = ''' diff --git a/network/f5/bigip_facts.py b/network/f5/bigip_facts.py index f74c66b6036..4522d61bbad 100755 --- a/network/f5/bigip_facts.py +++ b/network/f5/bigip_facts.py @@ -25,7 +25,7 @@ short_description: "Collect facts from F5 BIG-IP devices" description: - "Collect facts from F5 BIG-IP devices via iControl SOAP API" version_added: "1.6" -author: Matt Hite +author: '"Matt Hite (@mhite)" ' notes: - "Requires BIG-IP software version >= 11.4" - "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)" diff --git a/network/f5/bigip_monitor_http.py b/network/f5/bigip_monitor_http.py index d131eb71eee..6a31afb2ee7 100644 --- a/network/f5/bigip_monitor_http.py +++ b/network/f5/bigip_monitor_http.py @@ -27,7 +27,7 @@ short_description: "Manages F5 BIG-IP LTM http monitors" description: - "Manages F5 BIG-IP LTM monitors via iControl SOAP API" version_added: "1.4" -author: Serge van Ginderachter +author: '"Serge van Ginderachter (@srvg)" ' notes: - "Requires BIG-IP software version >= 11" - "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)" diff --git a/network/f5/bigip_monitor_tcp.py b/network/f5/bigip_monitor_tcp.py index 5cc00fe6b68..d5855e0f15d 100644 --- a/network/f5/bigip_monitor_tcp.py +++ b/network/f5/bigip_monitor_tcp.py @@ -25,7 +25,7 @@ short_description: "Manages F5 BIG-IP LTM tcp monitors" description: - "Manages F5 BIG-IP LTM tcp monitors via iControl SOAP API" version_added: "1.4" -author: Serge van Ginderachter +author: '"Serge van Ginderachter (@srvg)" ' notes: - "Requires BIG-IP software version >= 11" - "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)" diff --git a/network/f5/bigip_node.py b/network/f5/bigip_node.py index ca212763881..31e34fdeb47 100644 --- a/network/f5/bigip_node.py +++ b/network/f5/bigip_node.py @@ -25,7 +25,7 @@ short_description: "Manages F5 BIG-IP LTM nodes" description: - "Manages F5 BIG-IP LTM nodes via iControl SOAP API" version_added: "1.4" -author: Matt Hite +author: '"Matt Hite (@mhite)" ' notes: - "Requires BIG-IP software version >= 11" - "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)" @@ -67,7 +67,23 @@ options: - Pool member state required: true default: present - choices: ['present', 'absent', 'enabled', 'disabled'] + choices: ['present', 'absent'] + aliases: [] + session_state: + description: + - Set new session availability status for node + version_added: "1.9" + required: false + default: null + choices: ['enabled', 'disabled'] + aliases: [] + monitor_state: + description: + - Set monitor availability status for node + version_added: "1.9" + required: false + default: null + choices: ['enabled', 'disabled'] aliases: [] partition: description: @@ -78,7 +94,7 @@ options: aliases: [] name: description: - - "Node name. Required when state=enabled/disabled" + - "Node name" required: false default: null choices: [] @@ -145,10 +161,30 @@ EXAMPLES = ''' partition=matthite name="{{ ansible_default_ipv4["address"] }}" - - name: Disable node - bigip_node: server=lb.mydomain.com user=admin password=mysecret - state=disabled name=mynodename - delegate_to: localhost +# The BIG-IP GUI doesn't map directly to the API calls for "Node -> +# General Properties -> State". The following states map to API monitor +# and session states. +# +# Enabled (all traffic allowed): +# monitor_state=enabled, session_state=enabled +# Disabled (only persistent or active connections allowed): +# monitor_state=enabled, session_state=disabled +# Forced offline (only active connections allowed): +# monitor_state=disabled, session_state=disabled +# +# See https://devcentral.f5.com/questions/icontrol-equivalent-call-for-b-node-down + + - name: Force node offline + local_action: > + bigip_node + server=lb.mydomain.com + user=admin + password=mysecret + state=present + session_state=disabled + monitor_state=disabled + partition=matthite + name="{{ ansible_default_ipv4["address"] }}" ''' @@ -163,13 +199,6 @@ else: # bigip_node module specific # -# map of state values -STATES={'enabled': 'STATE_ENABLED', - 'disabled': 'STATE_DISABLED'} -STATUSES={'enabled': 'SESSION_STATUS_ENABLED', - 'disabled': 'SESSION_STATUS_DISABLED', - 'offline': 'SESSION_STATUS_FORCED_DISABLED'} - def bigip_api(bigip, user, password): api = bigsuds.BIGIP(hostname=bigip, username=user, password=password) return api @@ -227,29 +256,31 @@ def delete_node_address(api, address): def set_node_description(api, name, description): api.LocalLB.NodeAddressV2.set_description(nodes=[name], - descriptions=[description]) + descriptions=[description]) def get_node_description(api, name): return api.LocalLB.NodeAddressV2.get_description(nodes=[name])[0] -def set_node_disabled(api, name): - set_node_session_enabled_state(api, name, STATES['disabled']) - result = True - desc = "" - return (result, desc) - -def set_node_enabled(api, name): - set_node_session_enabled_state(api, name, STATES['enabled']) - result = True - desc = "" - return (result, desc) - -def set_node_session_enabled_state(api, name, state): +def set_node_session_enabled_state(api, name, session_state): + session_state = "STATE_%s" % session_state.strip().upper() api.LocalLB.NodeAddressV2.set_session_enabled_state(nodes=[name], - states=[state]) + states=[session_state]) def get_node_session_status(api, name): - return api.LocalLB.NodeAddressV2.get_session_status(nodes=[name])[0] + result = api.LocalLB.NodeAddressV2.get_session_status(nodes=[name])[0] + result = result.split("SESSION_STATUS_")[-1].lower() + return result + +def set_node_monitor_state(api, name, monitor_state): + monitor_state = "STATE_%s" % monitor_state.strip().upper() + api.LocalLB.NodeAddressV2.set_monitor_state(nodes=[name], + states=[monitor_state]) + +def get_node_monitor_status(api, name): + result = api.LocalLB.NodeAddressV2.get_monitor_status(nodes=[name])[0] + result = result.split("MONITOR_STATUS_")[-1].lower() + return result + def main(): module = AnsibleModule( @@ -258,8 +289,9 @@ def main(): user = dict(type='str', required=True), password = dict(type='str', required=True), validate_certs = dict(default='yes', type='bool'), - state = dict(type='str', default='present', - choices=['present', 'absent', 'disabled', 'enabled']), + state = dict(type='str', default='present', choices=['present', 'absent']), + session_state = dict(type='str', choices=['enabled', 'disabled']), + monitor_state = dict(type='str', choices=['enabled', 'disabled']), partition = dict(type='str', default='Common'), name = dict(type='str', required=True), host = dict(type='str', aliases=['address', 'ip']), @@ -276,6 +308,8 @@ def main(): password = module.params['password'] validate_certs = module.params['validate_certs'] state = module.params['state'] + session_state = module.params['session_state'] + monitor_state = module.params['monitor_state'] partition = module.params['partition'] host = module.params['host'] name = module.params['name'] @@ -315,6 +349,13 @@ def main(): module.fail_json(msg="unable to create: %s" % desc) else: result = {'changed': True} + if session_state is not None: + set_node_session_enabled_state(api, address, + session_state) + result = {'changed': True} + if monitor_state is not None: + set_node_monitor_state(api, address, monitor_state) + result = {'changed': True} if description is not None: set_node_description(api, address, description) result = {'changed': True} @@ -328,38 +369,40 @@ def main(): module.fail_json(msg="Changing the node address is " \ "not supported by the API; " \ "delete and recreate the node.") + if session_state is not None: + session_status = get_node_session_status(api, address) + if session_state == 'enabled' and \ + session_status == 'forced_disabled': + if not module.check_mode: + set_node_session_enabled_state(api, address, + session_state) + result = {'changed': True} + elif session_state == 'disabled' and \ + session_status != 'force_disabled': + if not module.check_mode: + set_node_session_enabled_state(api, address, + session_state) + result = {'changed': True} + if monitor_state is not None: + monitor_status = get_node_monitor_status(api, address) + if monitor_state == 'enabled' and \ + monitor_status == 'forced_down': + if not module.check_mode: + set_node_monitor_state(api, address, + monitor_state) + result = {'changed': True} + elif monitor_state == 'disabled' and \ + monitor_status != 'forced_down': + if not module.check_mode: + set_node_monitor_state(api, address, + monitor_state) + result = {'changed': True} if description is not None: if get_node_description(api, address) != description: if not module.check_mode: set_node_description(api, address, description) result = {'changed': True} - elif state in ('disabled', 'enabled'): - if name is None: - module.fail_json(msg="name parameter required when " \ - "state=enabled/disabled") - if not module.check_mode: - if not node_exists(api, name): - module.fail_json(msg="node does not exist") - status = get_node_session_status(api, name) - if state == 'disabled': - if status not in (STATUSES['disabled'], STATUSES['offline']): - disabled, desc = set_node_disabled(api, name) - if not disabled: - module.fail_json(msg="unable to disable: %s" % desc) - else: - result = {'changed': True} - else: - if status != STATUSES['enabled']: - enabled, desc = set_node_enabled(api, name) - if not enabled: - module.fail_json(msg="unable to enable: %s" % desc) - else: - result = {'changed': True} - else: - # check-mode return value - result = {'changed': True} - except Exception, e: module.fail_json(msg="received exception: %s" % e) diff --git a/network/f5/bigip_pool.py b/network/f5/bigip_pool.py index 425c1e97149..2eaaf8f3a34 100644 --- a/network/f5/bigip_pool.py +++ b/network/f5/bigip_pool.py @@ -25,7 +25,7 @@ short_description: "Manages F5 BIG-IP LTM pools" description: - "Manages F5 BIG-IP LTM pools via iControl SOAP API" version_added: "1.2" -author: Matt Hite +author: '"Matt Hite (@mhite)" ' notes: - "Requires BIG-IP software version >= 11" - "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)" diff --git a/network/f5/bigip_pool_member.py b/network/f5/bigip_pool_member.py index 1304dfe33e5..bc4b7be2f7b 100644 --- a/network/f5/bigip_pool_member.py +++ b/network/f5/bigip_pool_member.py @@ -25,7 +25,7 @@ short_description: "Manages F5 BIG-IP LTM pool members" description: - "Manages F5 BIG-IP LTM pool members via iControl SOAP API" version_added: "1.4" -author: Matt Hite +author: '"Matt Hite (@mhite)" ' notes: - "Requires BIG-IP software version >= 11" - "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)" @@ -39,23 +39,14 @@ options: description: - BIG-IP host required: true - default: null - choices: [] - aliases: [] user: description: - BIG-IP username required: true - default: null - choices: [] - aliases: [] password: description: - BIG-IP password required: true - default: null - choices: [] - aliases: [] validate_certs: description: - If C(no), SSL certificates will not be validated. This should only be used @@ -70,63 +61,58 @@ options: required: true default: present choices: ['present', 'absent'] - aliases: [] + session_state: + description: + - Set new session availability status for pool member + version_added: "2.0" + required: false + default: null + choices: ['enabled', 'disabled'] + monitor_state: + description: + - Set monitor availability status for pool member + version_added: "2.0" + required: false + default: null + choices: ['enabled', 'disabled'] pool: description: - Pool name. This pool must exist. required: true - default: null - choices: [] - aliases: [] partition: description: - Partition required: false default: 'Common' - choices: [] - aliases: [] host: description: - Pool member IP required: true - default: null - choices: [] aliases: ['address', 'name'] port: description: - Pool member port required: true - default: null - choices: [] - aliases: [] connection_limit: description: - Pool member connection limit. Setting this to 0 disables the limit. required: false default: null - choices: [] - aliases: [] description: description: - Pool member description required: false default: null - choices: [] - aliases: [] rate_limit: description: - Pool member rate limit (connections-per-second). Setting this to 0 disables the limit. required: false default: null - choices: [] - aliases: [] ratio: description: - Pool member ratio weight. Valid values range from 1 through 100. New pool members -- unless overriden with this value -- default to 1. required: false default: null - choices: [] - aliases: [] ''' EXAMPLES = ''' @@ -180,6 +166,34 @@ EXAMPLES = ''' host="{{ ansible_default_ipv4["address"] }}" port=80 + + # The BIG-IP GUI doesn't map directly to the API calls for "Pool -> + # Members -> State". The following states map to API monitor + # and session states. + # + # Enabled (all traffic allowed): + # monitor_state=enabled, session_state=enabled + # Disabled (only persistent or active connections allowed): + # monitor_state=enabled, session_state=disabled + # Forced offline (only active connections allowed): + # monitor_state=disabled, session_state=disabled + # + # See https://devcentral.f5.com/questions/icontrol-equivalent-call-for-b-node-down + + - name: Force pool member offline + local_action: > + bigip_pool_member + server=lb.mydomain.com + user=admin + password=mysecret + state=present + session_state=disabled + monitor_state=disabled + pool=matthite-pool + partition=matthite + host="{{ ansible_default_ipv4["address"] }}" + port=80 + ''' try: @@ -290,6 +304,28 @@ def set_ratio(api, pool, address, port, ratio): members = [{'address': address, 'port': port}] api.LocalLB.Pool.set_member_ratio(pool_names=[pool], members=[members], ratios=[[ratio]]) +def set_member_session_enabled_state(api, pool, address, port, session_state): + members = [{'address': address, 'port': port}] + session_state = ["STATE_%s" % session_state.strip().upper()] + api.LocalLB.Pool.set_member_session_enabled_state(pool_names=[pool], members=[members], session_states=[session_state]) + +def get_member_session_status(api, pool, address, port): + members = [{'address': address, 'port': port}] + result = api.LocalLB.Pool.get_member_session_status(pool_names=[pool], members=[members])[0][0] + result = result.split("SESSION_STATUS_")[-1].lower() + return result + +def set_member_monitor_state(api, pool, address, port, monitor_state): + members = [{'address': address, 'port': port}] + monitor_state = ["STATE_%s" % monitor_state.strip().upper()] + api.LocalLB.Pool.set_member_monitor_state(pool_names=[pool], members=[members], monitor_states=[monitor_state]) + +def get_member_monitor_status(api, pool, address, port): + members = [{'address': address, 'port': port}] + result = api.LocalLB.Pool.get_member_monitor_status(pool_names=[pool], members=[members])[0][0] + result = result.split("MONITOR_STATUS_")[-1].lower() + return result + def main(): module = AnsibleModule( argument_spec = dict( @@ -298,6 +334,8 @@ def main(): password = dict(type='str', required=True), validate_certs = dict(default='yes', type='bool'), state = dict(type='str', default='present', choices=['present', 'absent']), + session_state = dict(type='str', choices=['enabled', 'disabled']), + monitor_state = dict(type='str', choices=['enabled', 'disabled']), pool = dict(type='str', required=True), partition = dict(type='str', default='Common'), host = dict(type='str', required=True, aliases=['address', 'name']), @@ -318,6 +356,8 @@ def main(): password = module.params['password'] validate_certs = module.params['validate_certs'] state = module.params['state'] + session_state = module.params['session_state'] + monitor_state = module.params['monitor_state'] partition = module.params['partition'] pool = "/%s/%s" % (partition, module.params['pool']) connection_limit = module.params['connection_limit'] @@ -366,6 +406,10 @@ def main(): set_rate_limit(api, pool, address, port, rate_limit) if ratio is not None: set_ratio(api, pool, address, port, ratio) + if session_state is not None: + set_member_session_enabled_state(api, pool, address, port, session_state) + if monitor_state is not None: + set_member_monitor_state(api, pool, address, port, monitor_state) result = {'changed': True} else: # pool member exists -- potentially modify attributes @@ -385,6 +429,26 @@ def main(): if not module.check_mode: set_ratio(api, pool, address, port, ratio) result = {'changed': True} + if session_state is not None: + session_status = get_member_session_status(api, pool, address, port) + if session_state == 'enabled' and session_status == 'forced_disabled': + if not module.check_mode: + set_member_session_enabled_state(api, pool, address, port, session_state) + result = {'changed': True} + elif session_state == 'disabled' and session_status != 'force_disabled': + if not module.check_mode: + set_member_session_enabled_state(api, pool, address, port, session_state) + result = {'changed': True} + if monitor_state is not None: + monitor_status = get_member_monitor_status(api, pool, address, port) + if monitor_state == 'enabled' and monitor_status == 'forced_down': + if not module.check_mode: + set_member_monitor_state(api, pool, address, port, monitor_state) + result = {'changed': True} + elif monitor_state == 'disabled' and monitor_status != 'forced_down': + if not module.check_mode: + set_member_monitor_state(api, pool, address, port, monitor_state) + result = {'changed': True} except Exception, e: module.fail_json(msg="received exception: %s" % e) diff --git a/network/haproxy.py b/network/haproxy.py index 38757599df5..c897349019e 100644 --- a/network/haproxy.py +++ b/network/haproxy.py @@ -91,7 +91,7 @@ examples: # enable server in 'www' backend pool with change server(s) weight - haproxy: state=enabled host={{ inventory_hostname }} socket=/var/run/haproxy.sock weight=10 backend=www -author: Ravi Bhure +author: "Ravi Bhure (@ravibhure)" ''' import socket diff --git a/network/lldp.py b/network/lldp.py index ea6dc78d7bc..3ed554f79c3 100755 --- a/network/lldp.py +++ b/network/lldp.py @@ -24,7 +24,7 @@ short_description: get details reported by lldp description: - Reads data out of lldpctl options: {} -author: Andy Hill +author: "Andy Hill (@andyhky)" notes: - Requires lldpd running and lldp enabled on switches ''' diff --git a/network/openvswitch_bridge.py b/network/openvswitch_bridge.py index 551ca707a2d..28df3e84426 100644 --- a/network/openvswitch_bridge.py +++ b/network/openvswitch_bridge.py @@ -22,7 +22,7 @@ DOCUMENTATION = ''' --- module: openvswitch_bridge version_added: 1.4 -author: David Stygstra +author: '"David Stygstra (@stygstra)" ' short_description: Manage Open vSwitch bridges requirements: [ ovs-vsctl ] description: diff --git a/network/openvswitch_port.py b/network/openvswitch_port.py index 66391937d1b..ab87ea42b4a 100644 --- a/network/openvswitch_port.py +++ b/network/openvswitch_port.py @@ -22,7 +22,7 @@ DOCUMENTATION = ''' --- module: openvswitch_port version_added: 1.4 -author: David Stygstra +author: '"David Stygstra (@stygstra)" ' short_description: Manage Open vSwitch ports requirements: [ ovs-vsctl ] description: diff --git a/network/snmp_facts.py b/network/snmp_facts.py index 85fc148cba5..81a91ee6eb2 100755 --- a/network/snmp_facts.py +++ b/network/snmp_facts.py @@ -20,7 +20,7 @@ DOCUMENTATION = ''' --- module: snmp_facts version_added: "1.9" -author: Patrick Ogenstad (@networklore) +author: "Patrick Ogenstad (@ogenstad)" short_description: Retrive facts for a device using SNMP. description: - Retrieve facts for a device using SNMP, the facts will be diff --git a/notification/campfire.py b/notification/campfire.py index 31e69fc5459..9218826a7b4 100644 --- a/notification/campfire.py +++ b/notification/campfire.py @@ -43,7 +43,7 @@ options: # informational: requirements for nodes requirements: [ urllib2, cgi ] -author: Adam Garside +author: '"Adam Garside (@fabulops)" ' ''' EXAMPLES = ''' diff --git a/notification/flowdock.py b/notification/flowdock.py index 009487fb438..aea107457fb 100644 --- a/notification/flowdock.py +++ b/notification/flowdock.py @@ -22,7 +22,7 @@ DOCUMENTATION = ''' --- module: flowdock version_added: "1.2" -author: Matt Coddington +author: '"Matt Coddington (@mcodd)" ' short_description: Send a message to a flowdock description: - Send a message to a flowdock team inbox or chat using the push API (see https://www.flowdock.com/api/team-inbox and https://www.flowdock.com/api/chat) diff --git a/notification/grove.py b/notification/grove.py index 8f4ec42be58..5c27b18c30f 100644 --- a/notification/grove.py +++ b/notification/grove.py @@ -25,11 +25,11 @@ options: required: true url: description: - - Service URL for the web client + - Service URL for the web client required: false icon_url: description: - - Icon for the service + - Icon for the service required: false validate_certs: description: @@ -39,7 +39,7 @@ options: default: 'yes' choices: ['yes', 'no'] version_added: 1.5.1 -author: Jonas Pfenniger +author: '"Jonas Pfenniger (@zimbatm)" ' ''' EXAMPLES = ''' diff --git a/notification/hipchat.py b/notification/hipchat.py index 84e01d3eb51..a3504c0ee10 100644 --- a/notification/hipchat.py +++ b/notification/hipchat.py @@ -1 +1 @@ -#!/usr/bin/python # -*- coding: utf-8 -*- DOCUMENTATION = ''' --- module: hipchat version_added: "1.2" short_description: Send a message to hipchat description: - Send a message to hipchat options: token: description: - API token. required: true room: description: - ID or name of the room. required: true from: description: - Name the message will appear be sent from. max 15 characters. Over 15, will be shorten. required: false default: Ansible msg: description: - The message body. required: true default: null color: description: - Background color for the message. Default is yellow. required: false default: yellow choices: [ "yellow", "red", "green", "purple", "gray", "random" ] msg_format: description: - message format. html or text. Default is text. required: false default: text choices: [ "text", "html" ] notify: description: - notify or not (change the tab color, play a sound, etc) required: false default: 'yes' choices: [ "yes", "no" ] validate_certs: description: - If C(no), SSL certificates will not be validated. This should only be used on personally controlled sites using self-signed certificates. required: false default: 'yes' choices: ['yes', 'no'] version_added: 1.5.1 api: description: - API url if using a self-hosted hipchat server required: false default: 'https://api.hipchat.com/v1' version_added: 1.6.0 # informational: requirements for nodes requirements: [ urllib, urllib2, requests, json ] author: WAKAYAMA Shirou, BOURDEL Paul ''' EXAMPLES = ''' - hipchat: token=AAAAAA room=notify msg="Ansible task finished" ''' # =========================================== # HipChat module specific support methods. # DEFAULT_URI = "https://api.hipchat.com/v1" MSG_URI_V1 = "/rooms/message" MSG_URI_V2 = "/room/{id_or_name}/message" NOTIFY_URI_V2 = "/room/{id_or_name}/notification" def send_msg_v1(module, token, room, msg_from, msg, msg_format='text', color='yellow', notify=False, api=MSG_URI_V1): '''sending message to hipchat v1 server''' print "Sending message to v1 server" params = {} params['room_id'] = room params['from'] = msg_from[:15] # max length is 15 params['message'] = msg params['message_format'] = msg_format params['color'] = color params['api'] = api if notify: params['notify'] = 1 else: params['notify'] = 0 url = api + MSG_URI_V1 + "?auth_token=%s" % (token) data = urllib.urlencode(params) response, info = fetch_url(module, url, data=data) if info['status'] == 200: return response.read() else: module.fail_json(msg="failed to send message, return status=%s" % str(info['status'])) def send_msg_v2(module, token, room, msg_from, msg, msg_format='text', color='yellow', notify=False, api=MSG_URI_V2): '''sending message to hipchat v2 server''' print "Sending message to v2 server" headers = {'Authorization':'Bearer %s' % token, 'Content-Type':'application/json'} body = dict() body['message'] = msg body['color'] = color body['message_format'] = msg_format if notify: POST_URL = api + NOTIFY_URI_V2 else: POST_URL = api + MSG_URI_V2 url = POST_URL.replace('{id_or_name}',room) data = json.dumps(body) response, info = fetch_url(module, url, data=data, headers=headers, method='POST') if info['status'] == 200: return response.read() else: module.fail_json(msg="failed to send message, return status=%s" % str(info['status'])) # =========================================== # Module execution. # def main(): module = AnsibleModule( argument_spec=dict( token=dict(required=True), room=dict(required=True), msg=dict(required=True), msg_from=dict(default="Ansible", aliases=['from']), color=dict(default="yellow", choices=["yellow", "red", "green", "purple", "gray", "random"]), msg_format=dict(default="text", choices=["text", "html"]), notify=dict(default=True, type='bool'), validate_certs = dict(default='yes', type='bool'), api = dict(default=DEFAULT_URI), ), supports_check_mode=True ) token = module.params["token"] room = module.params["room"] msg = module.params["msg"] msg_from = module.params["msg_from"] color = module.params["color"] msg_format = module.params["msg_format"] notify = module.params["notify"] api = module.params["api"] try: if api.find('/v2') != -1: send_msg_v2(module, token, room, msg_from, msg, msg_format, color, notify, api) else: send_msg_v1(module, token, room, msg_from, msg, msg_format, color, notify, api) except Exception, e: module.fail_json(msg="unable to sent msg: %s" % e) changed = True module.exit_json(changed=changed, room=room, msg_from=msg_from, msg=msg) # import module snippets from ansible.module_utils.basic import * from ansible.module_utils.urls import * main() \ No newline at end of file +#!/usr/bin/python # -*- coding: utf-8 -*- DOCUMENTATION = ''' --- module: hipchat version_added: "1.2" short_description: Send a message to hipchat description: - Send a message to hipchat options: token: description: - API token. required: true room: description: - ID or name of the room. required: true from: description: - Name the message will appear be sent from. max 15 characters. Over 15, will be shorten. required: false default: Ansible msg: description: - The message body. required: true default: null color: description: - Background color for the message. Default is yellow. required: false default: yellow choices: [ "yellow", "red", "green", "purple", "gray", "random" ] msg_format: description: - message format. html or text. Default is text. required: false default: text choices: [ "text", "html" ] notify: description: - notify or not (change the tab color, play a sound, etc) required: false default: 'yes' choices: [ "yes", "no" ] validate_certs: description: - If C(no), SSL certificates will not be validated. This should only be used on personally controlled sites using self-signed certificates. required: false default: 'yes' choices: ['yes', 'no'] version_added: 1.5.1 api: description: - API url if using a self-hosted hipchat server required: false default: 'https://api.hipchat.com/v1' version_added: 1.6.0 # informational: requirements for nodes requirements: [ urllib, urllib2 ] author: "WAKAYAMA Shirou (@shirou), BOURDEL Paul" ''' EXAMPLES = ''' - hipchat: token=AAAAAA room=notify msg="Ansible task finished" ''' # =========================================== # HipChat module specific support methods. # DEFAULT_URI = "https://api.hipchat.com/v1" MSG_URI_V1 = "/rooms/message" MSG_URI_V2 = "/room/{id_or_name}/message" NOTIFY_URI_V2 = "/room/{id_or_name}/notification" def send_msg_v1(module, token, room, msg_from, msg, msg_format='text', color='yellow', notify=False, api=MSG_URI_V1): '''sending message to hipchat v1 server''' print "Sending message to v1 server" params = {} params['room_id'] = room params['from'] = msg_from[:15] # max length is 15 params['message'] = msg params['message_format'] = msg_format params['color'] = color params['api'] = api if notify: params['notify'] = 1 else: params['notify'] = 0 url = api + MSG_URI_V1 + "?auth_token=%s" % (token) data = urllib.urlencode(params) if module.check_mode: # In check mode, exit before actually sending the message module.exit_json(changed=False) response, info = fetch_url(module, url, data=data) if info['status'] == 200: return response.read() else: module.fail_json(msg="failed to send message, return status=%s" % str(info['status'])) def send_msg_v2(module, token, room, msg_from, msg, msg_format='text', color='yellow', notify=False, api=MSG_URI_V2): '''sending message to hipchat v2 server''' print "Sending message to v2 server" headers = {'Authorization':'Bearer %s' % token, 'Content-Type':'application/json'} body = dict() body['message'] = msg body['color'] = color body['message_format'] = msg_format if notify: POST_URL = api + NOTIFY_URI_V2 else: POST_URL = api + MSG_URI_V2 url = POST_URL.replace('{id_or_name}',room) data = json.dumps(body) response, info = fetch_url(module, url, data=data, headers=headers, method='POST') if info['status'] == 200: return response.read() else: module.fail_json(msg="failed to send message, return status=%s" % str(info['status'])) # =========================================== # Module execution. # def main(): module = AnsibleModule( argument_spec=dict( token=dict(required=True), room=dict(required=True), msg=dict(required=True), msg_from=dict(default="Ansible", aliases=['from']), color=dict(default="yellow", choices=["yellow", "red", "green", "purple", "gray", "random"]), msg_format=dict(default="text", choices=["text", "html"]), notify=dict(default=True, type='bool'), validate_certs=dict(default='yes', type='bool'), api=dict(default=DEFAULT_URI), ), supports_check_mode=True ) token = module.params["token"] room = module.params["room"] msg = module.params["msg"] msg_from = module.params["msg_from"] color = module.params["color"] msg_format = module.params["msg_format"] notify = module.params["notify"] api = module.params["api"] try: if api.find('/v2') != -1: send_msg_v2(module, token, room, msg_from, msg, msg_format, color, notify, api) else: send_msg_v1(module, token, room, msg_from, msg, msg_format, color, notify, api) except Exception, e: module.fail_json(msg="unable to sent msg: %s" % e) changed = True module.exit_json(changed=changed, room=room, msg_from=msg_from, msg=msg) # import module snippets from ansible.module_utils.basic import * from ansible.module_utils.urls import * main() \ No newline at end of file diff --git a/notification/irc.py b/notification/irc.py index a90834f820d..8b87c41f1ba 100644 --- a/notification/irc.py +++ b/notification/irc.py @@ -80,7 +80,9 @@ options: # informational: requirements for nodes requirements: [ socket ] -author: Jan-Piet Mens, Matt Martz +author: + - '"Jan-Piet Mens (@jpmens)"' + - '"Matt Martz (@sivel)"' ''' EXAMPLES = ''' diff --git a/notification/jabber.py b/notification/jabber.py index 8a7eed37b33..466c72d1570 100644 --- a/notification/jabber.py +++ b/notification/jabber.py @@ -42,7 +42,7 @@ options: # informational: requirements for nodes requirements: [ xmpp ] -author: Brian Coca +author: "Brian Coca (@bcoca)" ''' EXAMPLES = ''' diff --git a/notification/mail.py b/notification/mail.py index ae33c5ca4ca..4feaebf5d36 100644 --- a/notification/mail.py +++ b/notification/mail.py @@ -20,7 +20,7 @@ DOCUMENTATION = """ --- -author: Dag Wieers +author: '"Dag Wieers (@dagwieers)" ' module: mail short_description: Send an email description: @@ -138,6 +138,13 @@ EXAMPLES = ''' attach="/etc/group /tmp/pavatar2.png" headers=Reply-To=john@example.com|X-Special="Something or other" charset=utf8 +# Sending an e-mail using the remote machine, not the Ansible controller node +- mail: + host='localhost' + port=25 + to="John Smith " + subject='Ansible-report' + body='System {{ ansible_hostname }} has been successfully provisioned.' ''' import os diff --git a/notification/mqtt.py b/notification/mqtt.py index d701bd9348a..c618ab69ae3 100644 --- a/notification/mqtt.py +++ b/notification/mqtt.py @@ -81,7 +81,7 @@ requirements: [ mosquitto ] notes: - This module requires a connection to an MQTT broker such as Mosquitto U(http://mosquitto.org) and the I(Paho) C(mqtt) Python client (U(https://pypi.python.org/pypi/paho-mqtt)). -author: Jan-Piet Mens +author: "Jan-Piet Mens (@jpmens)" ''' EXAMPLES = ''' diff --git a/notification/nexmo.py b/notification/nexmo.py index d4898c40cdb..a1dd9c2b64d 100644 --- a/notification/nexmo.py +++ b/notification/nexmo.py @@ -24,7 +24,7 @@ short_description: Send a SMS via nexmo description: - Send a SMS message via nexmo version_added: 1.6 -author: Matt Martz +author: '"Matt Martz (@sivel)" ' options: api_key: description: diff --git a/notification/osx_say.py b/notification/osx_say.py index 39e3da88c19..7c0ba844583 100644 --- a/notification/osx_say.py +++ b/notification/osx_say.py @@ -37,7 +37,9 @@ options: What voice to use required: false requirements: [ say ] -author: Michael DeHaan +author: + - "Ansible Core Team" + - "Michael DeHaan (@mpdehaan)" ''' EXAMPLES = ''' diff --git a/notification/pushbullet.py b/notification/pushbullet.py new file mode 100644 index 00000000000..52d785306ce --- /dev/null +++ b/notification/pushbullet.py @@ -0,0 +1,182 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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 . + +DOCUMENTATION = ''' +--- +author: "Willy Barro (@willybarro)" +requirements: [ pushbullet.py ] +module: pushbullet +short_description: Sends notifications to Pushbullet +description: + - This module sends push notifications via Pushbullet to channels or devices. +version_added: "2.0" +options: + api_key: + description: + - Push bullet API token + required: true + channel: + description: + - The channel TAG you wish to broadcast a push notification, + as seen on the "My Channels" > "Edit your channel" at + Pushbullet page. + required: false + default: null + device: + description: + - The device NAME you wish to send a push notification, + as seen on the Pushbullet main page. + required: false + default: null + push_type: + description: + - Thing you wish to push. + required: false + default: note + choices: [ "note", "link" ] + title: + description: + - Title of the notification. + required: true + body: + description: + - Body of the notification, e.g. Details of the fault you're alerting. + required: false + +notes: + - Requires pushbullet.py Python package on the remote host. + You can install it via pip with ($ pip install pushbullet.py). + See U(https://github.com/randomchars/pushbullet.py) +''' + +EXAMPLES = ''' +# Sends a push notification to a device +- pushbullet: + api_key: "ABC123abc123ABC123abc123ABC123ab" + device: "Chrome" + title: "You may see this on Google Chrome" + +# Sends a link to a device +- pushbullet: + api_key: "ABC123abc123ABC123abc123ABC123ab" + device: "Chrome" + push_type: "link" + title: "Ansible Documentation" + body: "http://docs.ansible.com/" + +# Sends a push notification to a channel +- pushbullet: + api_key: "ABC123abc123ABC123abc123ABC123ab" + channel: "my-awesome-channel" + title: "Broadcasting a message to the #my-awesome-channel folks" + +# Sends a push notification with title and body to a channel +- pushbullet: + api_key: "ABC123abc123ABC123abc123ABC123ab" + channel: "my-awesome-channel" + title: "ALERT! Signup service is down" + body: "Error rate on signup service is over 90% for more than 2 minutes" +''' + +try: + from pushbullet import PushBullet + from pushbullet.errors import InvalidKeyError, PushError +except ImportError: + pushbullet_found = False +else: + pushbullet_found = True + +# =========================================== +# Main +# + +def main(): + module = AnsibleModule( + argument_spec = dict( + api_key = dict(type='str', required=True), + channel = dict(type='str', default=None), + device = dict(type='str', default=None), + push_type = dict(type='str', default="note", choices=['note', 'link']), + title = dict(type='str', required=True), + body = dict(type='str', default=None) + ), + mutually_exclusive = ( + ['channel', 'device'], + ), + supports_check_mode=True + ) + + api_key = module.params['api_key'] + channel = module.params['channel'] + device = module.params['device'] + push_type = module.params['push_type'] + title = module.params['title'] + body = module.params['body'] + + if not pushbullet_found: + module.fail_json(msg="Python 'pushbullet.py' module is required. Install via: $ pip install pushbullet.py") + + # Init pushbullet + try: + pb = PushBullet(api_key) + target = None + except InvalidKeyError: + module.fail_json(msg="Invalid api_key") + + # Checks for channel/device + if device is None and channel is None: + module.fail_json(msg="You need to provide a channel or a device.") + + # Search for given device + if device is not None: + devices_by_nickname = {} + for d in pb.devices: + devices_by_nickname[d.nickname] = d + + if device in devices_by_nickname: + target = devices_by_nickname[device] + else: + module.fail_json(msg="Device '%s' not found. Available devices: '%s'" % (device, "', '".join(devices_by_nickname.keys()))) + + # Search for given channel + if channel is not None: + channels_by_tag = {} + for c in pb.channels: + channels_by_tag[c.channel_tag] = c + + if channel in channels_by_tag: + target = channels_by_tag[channel] + else: + module.fail_json(msg="Channel '%s' not found. Available channels: '%s'" % (channel, "', '".join(channels_by_tag.keys()))) + + # If in check mode, exit saying that we succeeded + if module.check_mode: + module.exit_json(changed=False, msg="OK") + + # Send push notification + try: + target.push_note(title, body) + module.exit_json(changed=False, msg="OK") + except PushError as e: + module.fail_json(msg="An error occurred, Pushbullet's response: %s" % str(e)) + + module.fail_json(msg="An unknown error has occurred") + +# import module snippets +from ansible.module_utils.basic import * +main() diff --git a/notification/pushover b/notification/pushover.py similarity index 97% rename from notification/pushover rename to notification/pushover.py index 3e710ca02dd..951c65f43fe 100644 --- a/notification/pushover +++ b/notification/pushover.py @@ -48,7 +48,7 @@ options: description: Message priority (see u(https://pushover.net) for details.) required: false -author: Jim Richardson +author: '"Jim Richardson (@weaselkeeper)" ' ''' EXAMPLES = ''' diff --git a/notification/sendgrid.py b/notification/sendgrid.py index d8bfb7d6a2e..6278f613ee4 100644 --- a/notification/sendgrid.py +++ b/notification/sendgrid.py @@ -53,7 +53,7 @@ options: the desired subject for the email required: true -author: Matt Makai +author: '"Matt Makai (@makaimc)" ' ''' EXAMPLES = ''' diff --git a/notification/slack.py b/notification/slack.py index 7e8a81c811b..7e5215479ab 100644 --- a/notification/slack.py +++ b/notification/slack.py @@ -24,7 +24,7 @@ short_description: Send Slack notifications description: - The M(slack) module sends notifications to U(http://slack.com) via the Incoming WebHook integration version_added: 1.6 -author: Ramon de la Fuente +author: '"Ramon de la Fuente (@ramondelafuente)" ' options: domain: description: @@ -141,9 +141,9 @@ def build_payload_for_slack(module, text, channel, username, icon_url, icon_emoj else: payload = dict(attachments=[dict(text=text, color=color)]) if channel is not None: - if (channel[0] == '#') or (channel[0] == '@') + if (channel[0] == '#') or (channel[0] == '@'): payload['channel'] = channel - else + else: payload['channel'] = '#'+channel if username is not None: payload['username'] = username diff --git a/notification/sns.py b/notification/sns.py index 54421b0e9fa..910105f0ebb 100644 --- a/notification/sns.py +++ b/notification/sns.py @@ -24,7 +24,7 @@ short_description: Send Amazon Simple Notification Service (SNS) messages description: - The M(sns) module sends notifications to a topic on your Amazon SNS account version_added: 1.6 -author: Michael J. Schultz +author: '"Michael J. Schultz (@mjschultz)" ' options: msg: description: diff --git a/notification/twilio.py b/notification/twilio.py index faae7b6f58f..568d0c60a58 100644 --- a/notification/twilio.py +++ b/notification/twilio.py @@ -58,7 +58,7 @@ options: (multimedia message) instead of a plain SMS required: false -author: Matt Makai +author: '"Matt Makai (@makaimc)" ' ''' EXAMPLES = ''' diff --git a/notification/typetalk.py b/notification/typetalk.py index b987acbe837..8e79a7617ed 100644 --- a/notification/typetalk.py +++ b/notification/typetalk.py @@ -26,7 +26,7 @@ options: - message body required: true requirements: [ urllib, urllib2, json ] -author: Takashi Someda +author: '"Takashi Someda (@tksmd)" ' ''' EXAMPLES = ''' diff --git a/packaging/language/bower.py b/packaging/language/bower.py index 085f454e639..f0dd58023ba 100644 --- a/packaging/language/bower.py +++ b/packaging/language/bower.py @@ -25,7 +25,7 @@ short_description: Manage bower packages with bower description: - Manage bower packages with bower version_added: 1.9 -author: Michael Warkentin +author: '"Michael Warkentin (@mwarkentin)" ' options: name: description: diff --git a/packaging/language/composer.py b/packaging/language/composer.py index a24b826a4de..5bbd948595a 100644 --- a/packaging/language/composer.py +++ b/packaging/language/composer.py @@ -22,7 +22,7 @@ DOCUMENTATION = ''' --- module: composer -author: Dimitrios Tydeas Mengidis +author: '"Dimitrios Tydeas Mengidis (@dmtrs)" ' short_description: Dependency Manager for PHP version_added: "1.6" description: diff --git a/packaging/language/cpanm.py b/packaging/language/cpanm.py index ec344b7aa9b..5549dab8895 100644 --- a/packaging/language/cpanm.py +++ b/packaging/language/cpanm.py @@ -73,7 +73,7 @@ examples: description: Install I(Dancer) perl package from a specific mirror notes: - Please note that U(http://search.cpan.org/dist/App-cpanminus/bin/cpanm, cpanm) must be installed on the remote host. -author: Franck Cuny +author: '"Franck Cuny (@franckcuny)" ' ''' def _is_package_installed(module, name, locallib, cpanm): diff --git a/packaging/language/maven_artifact.py b/packaging/language/maven_artifact.py index 2aeb158625b..d6dd33166dc 100644 --- a/packaging/language/maven_artifact.py +++ b/packaging/language/maven_artifact.py @@ -37,10 +37,10 @@ description: - Downloads an artifact from a maven repository given the maven coordinates provided to the module. Can retrieve - snapshots or release versions of the artifact and will resolve the latest available version if one is not - available. -author: Chris Schmidt +author: '"Chris Schmidt (@chrisisbeef)" ' requirements: - - python libxml - - python urllib2 + - "python >= 2.6" + - lxml options: group_id: description: The Maven groupId coordinate diff --git a/packaging/language/npm.py b/packaging/language/npm.py index 8407589116a..3eafcd6c2a7 100644 --- a/packaging/language/npm.py +++ b/packaging/language/npm.py @@ -25,7 +25,7 @@ short_description: Manage node.js packages with npm description: - Manage node.js packages with Node Package Manager (npm) version_added: 1.2 -author: Chris Hoffman +author: '"Chris Hoffman (@chrishoffman)" ' options: name: description: diff --git a/packaging/os/dnf.py b/packaging/os/dnf.py index 222fe4fa222..c76f39b1dd6 100644 --- a/packaging/os/dnf.py +++ b/packaging/os/dnf.py @@ -93,7 +93,7 @@ options: notes: [] # informational: requirements for nodes requirements: [ dnf ] -author: Cristian van Ee +author: '"Cristian van Ee (@DJMuggs)" ' ''' EXAMPLES = ''' diff --git a/packaging/os/homebrew.py b/packaging/os/homebrew.py index aac4efd827e..f6d63b17d3c 100644 --- a/packaging/os/homebrew.py +++ b/packaging/os/homebrew.py @@ -22,7 +22,9 @@ DOCUMENTATION = ''' --- module: homebrew -author: Andrew Dunham and Daniel Jaouen +author: + - '"Daniel Jaouen (@danieljaouen)" ' + - '"Andrew Dunham (@andrew-d)" ' short_description: Package manager for Homebrew description: - Manages Homebrew packages diff --git a/packaging/os/homebrew_cask.py b/packaging/os/homebrew_cask.py index 75acead517b..292da6c7f59 100644 --- a/packaging/os/homebrew_cask.py +++ b/packaging/os/homebrew_cask.py @@ -19,7 +19,7 @@ DOCUMENTATION = ''' --- module: homebrew_cask -author: Daniel Jaouen +author: '"Daniel Jaouen (@danieljaouen)" ' short_description: Install/uninstall homebrew casks. description: - Manages Homebrew casks. diff --git a/packaging/os/homebrew_tap.py b/packaging/os/homebrew_tap.py index d329227b980..1e0b6b66169 100644 --- a/packaging/os/homebrew_tap.py +++ b/packaging/os/homebrew_tap.py @@ -24,7 +24,7 @@ import re DOCUMENTATION = ''' --- module: homebrew_tap -author: Daniel Jaouen +author: '"Daniel Jaouen (@danieljaouen)" ' short_description: Tap a Homebrew repository. description: - Tap external Homebrew repositories. diff --git a/packaging/os/layman.py b/packaging/os/layman.py index 57c03528c9e..3cad5e35642 100644 --- a/packaging/os/layman.py +++ b/packaging/os/layman.py @@ -25,12 +25,15 @@ from urllib2 import Request, urlopen, URLError DOCUMENTATION = ''' --- module: layman -author: Jakub Jirutka +author: '"Jakub Jirutka (@jirutka)" ' version_added: "1.6" short_description: Manage Gentoo overlays description: - Uses Layman to manage an additional repositories for the Portage package manager on Gentoo Linux. Please note that Layman must be installed on a managed node prior using this module. +requirements: + - "python >= 2.6" + - layman python module options: name: description: diff --git a/packaging/os/macports.py b/packaging/os/macports.py index ae7010b1cbd..ca3a0f97426 100644 --- a/packaging/os/macports.py +++ b/packaging/os/macports.py @@ -21,7 +21,7 @@ DOCUMENTATION = ''' --- module: macports -author: Jimmy Tang +author: "Jimmy Tang (@jcftang)" short_description: Package manager for MacPorts description: - Manages MacPorts packages diff --git a/packaging/os/openbsd_pkg.py b/packaging/os/openbsd_pkg.py index 14b4ff46024..2f81753fb64 100644 --- a/packaging/os/openbsd_pkg.py +++ b/packaging/os/openbsd_pkg.py @@ -25,7 +25,7 @@ import syslog DOCUMENTATION = ''' --- module: openbsd_pkg -author: Patrik Lundin +author: '"Patrik Lundin (@eest)" ' version_added: "1.1" short_description: Manage packages on OpenBSD. description: diff --git a/packaging/os/opkg.py b/packaging/os/opkg.py index 95afd6fd8bd..8f06a03a1b2 100644 --- a/packaging/os/opkg.py +++ b/packaging/os/opkg.py @@ -20,7 +20,7 @@ DOCUMENTATION = ''' --- module: opkg -author: Patrick Pelletier +author: '"Patrick Pelletier (@skinp)" ' short_description: Package manager for OpenWrt description: - Manages OpenWrt packages diff --git a/packaging/os/pacman.py b/packaging/os/pacman.py index a91f8e3054d..463d524ff8e 100644 --- a/packaging/os/pacman.py +++ b/packaging/os/pacman.py @@ -27,7 +27,9 @@ description: - Manage packages with the I(pacman) package manager, which is used by Arch Linux and its variants. version_added: "1.0" -author: Afterburn +author: + - "'Aaron Bull Schaefer (@elasticdog)' " + - "Afterburn" notes: [] requirements: [] options: diff --git a/packaging/os/pkg5.py b/packaging/os/pkg5.py index b250a02850c..632a36796dc 100644 --- a/packaging/os/pkg5.py +++ b/packaging/os/pkg5.py @@ -19,7 +19,7 @@ DOCUMENTATION = ''' --- module: pkg5 -author: Peter Oliver +author: '"Peter Oliver (@mavit)" ' short_description: Manages packages with the Solaris 11 Image Packaging System version_added: 1.9 description: @@ -128,13 +128,18 @@ def ensure(module, state, packages, params): }, } + if params['accept_licenses']: + accept_licenses = ['--accept'] + else: + accept_licenses = [] + to_modify = filter(behaviour[state]['filter'], packages) if to_modify: rc, out, err = module.run_command( [ 'pkg', behaviour[state]['subcommand'] ] - + (['--accept'] if params['accept_licenses'] else []) + + accept_licenses + [ '-q', '--' ] + to_modify @@ -151,12 +156,12 @@ def ensure(module, state, packages, params): def is_installed(module, package): rc, out, err = module.run_command(['pkg', 'list', '--', package]) - return True if rc == 0 else False + return not bool(int(rc)) def is_latest(module, package): rc, out, err = module.run_command(['pkg', 'list', '-u', '--', package]) - return True if rc == 1 else False + return bool(int(rc)) from ansible.module_utils.basic import * diff --git a/packaging/os/pkg5_publisher.py b/packaging/os/pkg5_publisher.py index 63c62059203..1db07d512b7 100644 --- a/packaging/os/pkg5_publisher.py +++ b/packaging/os/pkg5_publisher.py @@ -19,7 +19,7 @@ DOCUMENTATION = ''' --- module: pkg5_publisher -author: Peter Oliver +author: '"Peter Oliver (@mavit)" ' short_description: Manages Solaris 11 Image Packaging System publishers version_added: 1.9 description: @@ -122,10 +122,15 @@ def set_publisher(module, params): args.append('--remove-mirror=*') args.extend(['--add-mirror=' + u for u in params['mirror']]) - if params['sticky'] != None: - args.append('--sticky' if params['sticky'] else '--non-sticky') - if params['enabled'] != None: - args.append('--enable' if params['enabled'] else '--disable') + if params['sticky'] != None and params['sticky']: + args.append('--sticky') + elif params['sticky'] != None: + args.append('--non-sticky') + + if params['enabled'] != None and params['enabled']: + args.append('--enable') + elif params['enabled'] != None: + args.append('--disable') rc, out, err = module.run_command( ["pkg", "set-publisher"] + args + [name], diff --git a/packaging/os/pkgin.py b/packaging/os/pkgin.py index 9f25094210c..33bcb5482f0 100644 --- a/packaging/os/pkgin.py +++ b/packaging/os/pkgin.py @@ -30,7 +30,9 @@ description: - "The standard package manager for SmartOS, but also usable on NetBSD or any OS that uses C(pkgsrc). (Home: U(http://pkgin.net/))" version_added: "1.0" -author: Shaun Zinck, Larry Gilbert +author: + - '"Larry Gilbert (L2G)" ' + - '"Shaun Zinck (@szinck)" ' notes: - "Known bug with pkgin < 0.8.0: if a package is removed and another package depends on it, the other package will be silently removed as diff --git a/packaging/os/pkgng.py b/packaging/os/pkgng.py index 1aa8e0c737f..132cff637e6 100644 --- a/packaging/os/pkgng.py +++ b/packaging/os/pkgng.py @@ -63,7 +63,7 @@ options: for newer pkgng versions, specify a the name of a repository configured in /usr/local/etc/pkg/repos required: false -author: bleader +author: '"bleader (@bleader)" ' notes: - When using pkgsite, be careful that already in cache packages won't be downloaded again. ''' @@ -252,9 +252,8 @@ def annotate_packages(module, pkgng_path, packages, annotation): for package in packages: for _annotation in annotations: - annotate_c += ( 1 if operation[_annotation['operation']]( - module, pkgng_path, package, - _annotation['tag'], _annotation['value']) else 0 ) + if operation[_annotation['operation']](module, pkgng_path, package, _annotation['tag'], _annotation['value']): + annotate_c += 1 if annotate_c > 0: return (True, "added %s annotations." % annotate_c) diff --git a/packaging/os/pkgutil.py b/packaging/os/pkgutil.py index 635617b4efe..62107aa0475 100644 --- a/packaging/os/pkgutil.py +++ b/packaging/os/pkgutil.py @@ -32,7 +32,7 @@ description: - Pkgutil is an advanced packaging system, which resolves dependency on installation. It is designed for CSW packages. version_added: "1.3" -author: Alexander Winkler +author: '"Alexander Winkler (@dermute)" ' options: name: description: diff --git a/packaging/os/portage.py b/packaging/os/portage.py index eb77baa14f6..2ce0379a8ec 100644 --- a/packaging/os/portage.py +++ b/packaging/os/portage.py @@ -147,7 +147,9 @@ options: choices: [ "yes" ] requirements: [ gentoolkit ] -author: Yap Sok Ann, Andrew Udvare +author: + - "Yap Sok Ann (@sayap)" + - "Andrew Udvare" notes: [] ''' @@ -422,7 +424,9 @@ def main(): if not p['package']: module.exit_json(msg='Sync successfully finished.') - packages = p['package'].split(',') if p['package'] else [] + packages = [] + if p['package']: + packages.extend(p['package'].split(',')) if p['depclean']: if packages and p['state'] not in portage_absent_states: diff --git a/packaging/os/portinstall.py b/packaging/os/portinstall.py index 068f413af72..1673c4dde37 100644 --- a/packaging/os/portinstall.py +++ b/packaging/os/portinstall.py @@ -43,7 +43,7 @@ options: choices: [ 'yes', 'no' ] required: false default: yes -author: berenddeboer +author: '"berenddeboer (@berenddeboer)" ' ''' EXAMPLES = ''' diff --git a/packaging/os/svr4pkg.py b/packaging/os/svr4pkg.py index e95d4d8643f..51cda437e7f 100644 --- a/packaging/os/svr4pkg.py +++ b/packaging/os/svr4pkg.py @@ -30,7 +30,7 @@ description: - Note that this is a very basic packaging system. It will not enforce dependencies on install or remove. version_added: "0.9" -author: Boyd Adamson +author: "Boyd Adamson (@brontitall)" options: name: description: diff --git a/packaging/os/swdepot.py b/packaging/os/swdepot.py index b41a860531f..56b33d401bf 100644 --- a/packaging/os/swdepot.py +++ b/packaging/os/swdepot.py @@ -29,7 +29,7 @@ description: - Will install, upgrade and remove packages with swdepot package manager (HP-UX) version_added: "1.4" notes: [] -author: Raul Melo +author: '"Raul Melo (@melodous)" ' options: name: description: diff --git a/packaging/os/urpmi.py b/packaging/os/urpmi.py index 320d17bfc00..c202ee27ace 100644 --- a/packaging/os/urpmi.py +++ b/packaging/os/urpmi.py @@ -57,7 +57,7 @@ options: required: false default: yes choices: [ "yes", "no" ] -author: Philippe Makowski +author: '"Philippe Makowski (@pmakowski)" ' notes: [] ''' diff --git a/packaging/os/zypper.py b/packaging/os/zypper.py index ccf901d4fa1..c175c152050 100644 --- a/packaging/os/zypper.py +++ b/packaging/os/zypper.py @@ -31,7 +31,7 @@ import re DOCUMENTATION = ''' --- module: zypper -author: Patrick Callahan +author: '"Patrick Callahan (@dirtyharrycallahan)" ' version_added: "1.2" short_description: Manage packages on SUSE and openSUSE description: diff --git a/packaging/os/zypper_repository.py b/packaging/os/zypper_repository.py index f208305fe60..3210e93d391 100644 --- a/packaging/os/zypper_repository.py +++ b/packaging/os/zypper_repository.py @@ -23,7 +23,7 @@ DOCUMENTATION = ''' --- module: zypper_repository -author: Matthias Vogelgesang +author: '"Matthias Vogelgesang (@matze)" ' version_added: "1.4" short_description: Add and remove Zypper repositories description: diff --git a/source_control/bzr.py b/source_control/bzr.py index 0d25a026f7a..5519a8af123 100644 --- a/source_control/bzr.py +++ b/source_control/bzr.py @@ -22,7 +22,7 @@ DOCUMENTATION = u''' --- module: bzr -author: André Paramés +author: '"André Paramés (@andreparames)" ' version_added: "1.1" short_description: Deploy software (or files) from bzr branches description: diff --git a/source_control/github_hooks.py b/source_control/github_hooks.py index 7aaff98f413..bb60b634cb3 100644 --- a/source_control/github_hooks.py +++ b/source_control/github_hooks.py @@ -64,7 +64,7 @@ options: default: 'json' choices: ['json', 'form'] -author: Phillip Gentry, CX Inc +author: '"Phillip Gentry, CX Inc (@pcgentry)" ' ''' EXAMPLES = ''' diff --git a/system/alternatives.py b/system/alternatives.py index ff4de59cf11..c298afc2949 100755 --- a/system/alternatives.py +++ b/system/alternatives.py @@ -30,6 +30,9 @@ description: - Manages symbolic links using the 'update-alternatives' tool - Useful when multiple programs are installed but provide similar functionality (e.g. different editors). version_added: "1.6" +author: + - '"David Wittman (@DavidWittman)" ' + - '"Gabe Mulley (@mulby)" ' options: name: description: @@ -132,7 +135,7 @@ def main(): ) module.exit_json(changed=True) - except subprocess.CalledProcessError as cpe: + except subprocess.CalledProcessError, cpe: module.fail_json(msg=str(dir(cpe))) else: module.exit_json(changed=False) diff --git a/system/at.py b/system/at.py index 770148991f1..03ac14a44aa 100644 --- a/system/at.py +++ b/system/at.py @@ -59,7 +59,7 @@ options: default: false requirements: - at -author: Richard Isaacson +author: '"Richard Isaacson (@risaacson)" ' ''' EXAMPLES = ''' diff --git a/system/capabilities.py b/system/capabilities.py index f4a9f62c0d0..0c7f2e22d0b 100644 --- a/system/capabilities.py +++ b/system/capabilities.py @@ -50,7 +50,7 @@ notes: and flags to compare, so you will want to ensure that your capabilities argument matches the final capabilities. requirements: [] -author: Nate Coraor +author: '"Nate Coraor (@natefoo)" ' ''' EXAMPLES = ''' diff --git a/system/cronvar.py b/system/cronvar.py index 23a626472c3..fe337752d59 100755 --- a/system/cronvar.py +++ b/system/cronvar.py @@ -81,7 +81,7 @@ options: default: false requirements: - cron -author: Doug Luce +author: "Doug Luce (@dougluce)" """ EXAMPLES = ''' diff --git a/system/crypttab.py b/system/crypttab.py index ccd4102c66b..5b0edc62363 100644 --- a/system/crypttab.py +++ b/system/crypttab.py @@ -69,7 +69,7 @@ options: notes: [] requirements: [] -author: Steve +author: '"Steve (@groks)" ' ''' EXAMPLES = ''' @@ -155,8 +155,11 @@ def main(): if changed and not module.check_mode: - with open(path, 'wb') as f: + try: + f = open(path, 'wb') f.write(str(crypttab)) + finally: + f.close() module.exit_json(changed=changed, msg=reason, **module.params) @@ -172,9 +175,12 @@ class Crypttab(object): os.makedirs(os.path.dirname(path)) open(path,'a').close() - with open(path, 'r') as f: + try: + f = open(path, 'r') for line in f.readlines(): self._lines.append(Line(line)) + finally: + f.close() def add(self, line): self._lines.append(line) @@ -242,10 +248,19 @@ class Line(object): def _split_line(self, line): fields = line.split() + try: + field2 = field[2] + except IndexError: + field2 = None + try: + field3 = field[3] + except IndexError: + field3 = None + return (fields[0], fields[1], - fields[2] if len(fields) >= 3 else None, - fields[3] if len(fields) >= 4 else None) + field2, + fields3) def remove(self): self.line, self.name, self.backing_device = '', None, None @@ -260,7 +275,10 @@ class Line(object): if self.valid(): fields = [self.name, self.backing_device] if self.password is not None or self.opts: - fields.append(self.password if self.password is not None else 'none') + if self.password is not None: + fields.append(self.password) + else: + self.password('none') if self.opts: fields.append(str(self.opts)) return ' '.join(fields) @@ -276,7 +294,10 @@ class Options(dict): if opts_string is not None: for opt in opts_string.split(','): kv = opt.split('=') - k, v = (kv[0], kv[1]) if len(kv) > 1 else (kv[0], None) + if len(kv) > 1: + k, v = (kv[0], kv[1]) + else: + k, v = (kv[0], None) self[k] = v def add(self, opts_string): @@ -324,8 +345,13 @@ class Options(dict): and sorted(self.items()) == sorted(obj.items())) def __str__(self): - return ','.join([k if v is None else '%s=%s' % (k, v) - for k, v in self.items()]) + ret = [] + for k, v in self.items(): + if v is None: + ret.append(k) + else: + ret.append('%s=%s' % (k, v)) + return ','.join(ret) # import module snippets from ansible.module_utils.basic import * diff --git a/system/debconf.py b/system/debconf.py index 0deaff25eb1..b249986a947 100644 --- a/system/debconf.py +++ b/system/debconf.py @@ -68,7 +68,7 @@ options: required: false default: False aliases: [] -author: Brian Coca +author: "Brian Coca (@bcoca)" ''' diff --git a/system/facter.py b/system/facter.py index a4912835447..6c09877fcbe 100644 --- a/system/facter.py +++ b/system/facter.py @@ -32,7 +32,9 @@ version_added: "0.2" options: {} notes: [] requirements: [ "facter", "ruby-json" ] -author: Michael DeHaan +author: + - "Ansible Core Team" + - "Michael DeHaan" ''' EXAMPLES = ''' diff --git a/system/filesystem.py b/system/filesystem.py index 0de5b75e38b..a2f979ecd0b 100644 --- a/system/filesystem.py +++ b/system/filesystem.py @@ -20,7 +20,7 @@ DOCUMENTATION = ''' --- -author: Alexander Bulimov +author: '"Alexander Bulimov (@abulimov)" ' module: filesystem short_description: Makes file system on block device description: diff --git a/system/firewalld.py b/system/firewalld.py index 07cb8d224e7..77cfc4b6bb8 100644 --- a/system/firewalld.py +++ b/system/firewalld.py @@ -69,7 +69,7 @@ options: notes: - Not tested on any debian based system. requirements: [ firewalld >= 0.2.11 ] -author: Adam Miller +author: '"Adam Miller (@maxamillion)" ' ''' EXAMPLES = ''' diff --git a/system/getent.py b/system/getent.py index bb6d162398c..7df9e1d795f 100644 --- a/system/getent.py +++ b/system/getent.py @@ -54,7 +54,7 @@ options: notes: - "Not all databases support enumeration, check system documentation for details" requirements: [ ] -author: Brian Coca +author: "Brian Coca (@bcoca)" ''' EXAMPLES = ''' diff --git a/system/gluster_volume.py b/system/gluster_volume.py index 2a8bc74df72..7b83c62297f 100644 --- a/system/gluster_volume.py +++ b/system/gluster_volume.py @@ -103,7 +103,7 @@ options: notes: - "Requires cli tools for GlusterFS on servers" - "Will add new bricks, but not remove them" -author: Taneli Leppä +author: '"Taneli Leppä (@rosmo)" ' """ EXAMPLES = """ @@ -145,7 +145,7 @@ def run_gluster(gargs, **kwargs): try: rc, out, err = module.run_command(args, **kwargs) if rc != 0: - module.fail_json(msg='error running gluster (%s) command (rc=%d): %s' % (' '.join(args), rc, out if out != '' else err)) + module.fail_json(msg='error running gluster (%s) command (rc=%d): %s' % (' '.join(args), rc, out or err)) except Exception, e: module.fail_json(msg='error running gluster (%s) command: %s' % (' '.join(args), str(e))) return out @@ -167,7 +167,7 @@ def run_gluster_yes(gargs): args.extend(gargs) rc, out, err = module.run_command(args, data='y\n') if rc != 0: - module.fail_json(msg='error running gluster (%s) command (rc=%d): %s' % (' '.join(args), rc, out if out != '' else err)) + module.fail_json(msg='error running gluster (%s) command (rc=%d): %s' % (' '.join(args), rc, out or err)) return out def get_peers(): diff --git a/system/kernel_blacklist.py b/system/kernel_blacklist.py index 6af08c0788c..b0901473867 100644 --- a/system/kernel_blacklist.py +++ b/system/kernel_blacklist.py @@ -25,7 +25,7 @@ import re DOCUMENTATION = ''' --- module: kernel_blacklist -author: Matthias Vogelgesang +author: '"Matthias Vogelgesang (@matze)" ' version_added: 1.4 short_description: Blacklist kernel modules description: diff --git a/system/known_hosts.py b/system/known_hosts.py index 86876cd4931..74c6b0e90c7 100644 --- a/system/known_hosts.py +++ b/system/known_hosts.py @@ -51,7 +51,7 @@ options: required: no default: present requirements: [ ] -author: Matthew Vernon +author: '"Matthew Vernon (@mcv21)" ' ''' EXAMPLES = ''' diff --git a/system/locale_gen.py b/system/locale_gen.py index c5943cd63a0..9108cfb53cd 100644 --- a/system/locale_gen.py +++ b/system/locale_gen.py @@ -13,6 +13,7 @@ short_description: Creates or removes locales. description: - Manages locales by editing /etc/locale.gen and invoking locale-gen. version_added: "1.6" +author: "Augustus Kling (@AugustusKling)" options: name: description: @@ -55,11 +56,12 @@ def is_available(name, ubuntuMode): __locales_available = '/etc/locale.gen' re_compiled = re.compile(__regexp) - with open(__locales_available, 'r') as fd: - for line in fd: - result = re_compiled.match(line) - if result and result.group('locale') == name: - return True + fd = open(__locales_available, 'r') + for line in fd: + result = re_compiled.match(line) + if result and result.group('locale') == name: + return True + fd.close() return False def is_present(name): @@ -76,10 +78,16 @@ def fix_case(name): def replace_line(existing_line, new_line): """Replaces lines in /etc/locale.gen""" - with open("/etc/locale.gen", "r") as f: + try: + f = open("/etc/locale.gen", "r") lines = [line.replace(existing_line, new_line) for line in f] - with open("/etc/locale.gen", "w") as f: + finally: + f.close() + try: + f = open("/etc/locale.gen", "w") f.write("".join(lines)) + finally: + f.close() def set_locale(name, enabled=True): """ Sets the state of the locale. Defaults to enabled. """ @@ -88,10 +96,16 @@ def set_locale(name, enabled=True): new_string = '%s \g' % (name) else: new_string = '# %s \g' % (name) - with open("/etc/locale.gen", "r") as f: + try: + f = open("/etc/locale.gen", "r") lines = [re.sub(search_string, new_string, line) for line in f] - with open("/etc/locale.gen", "w") as f: + finally: + f.close() + try: + f = open("/etc/locale.gen", "w") f.write("".join(lines)) + finally: + f.close() def apply_change(targetState, name): """Create or remove locale. @@ -124,13 +138,19 @@ def apply_change_ubuntu(targetState, name): localeGenExitValue = call(["locale-gen", name]) else: # Delete locale involves discarding the locale from /var/lib/locales/supported.d/local and regenerating all locales. - with open("/var/lib/locales/supported.d/local", "r") as f: + try: + f = open("/var/lib/locales/supported.d/local", "r") content = f.readlines() - with open("/var/lib/locales/supported.d/local", "w") as f: + finally: + f.close() + try: + f = open("/var/lib/locales/supported.d/local", "w") for line in content: locale, charset = line.split(' ') if locale != name: f.write(line) + finally: + f.close() # Purge locales and regenerate. # Please provide a patch if you know how to avoid regenerating the locales to keep! localeGenExitValue = call(["locale-gen", "--purge"]) @@ -168,7 +188,10 @@ def main(): module.fail_json(msg="The locales you've entered is not available " "on your system.") - prev_state = "present" if is_present(name) else "absent" + if is_present(name): + prev_state = "present" + else: + prev_state = "absent" changed = (prev_state!=state) if module.check_mode: @@ -180,7 +203,7 @@ def main(): apply_change(state, name) else: apply_change_ubuntu(state, name) - except EnvironmentError as e: + except EnvironmentError, e: module.fail_json(msg=e.strerror, exitValue=e.errno) module.exit_json(name=name, changed=changed, msg="OK") diff --git a/system/lvg.py b/system/lvg.py index 295ee24e3c6..955b94668dc 100644 --- a/system/lvg.py +++ b/system/lvg.py @@ -21,7 +21,7 @@ DOCUMENTATION = ''' --- -author: Alexander Bulimov +author: '"Alexander Bulimov (@abulimov)" ' module: lvg short_description: Configure LVM volume groups description: diff --git a/system/lvol.py b/system/lvol.py index d9be9e7dc70..dc5cbb66732 100644 --- a/system/lvol.py +++ b/system/lvol.py @@ -20,7 +20,9 @@ DOCUMENTATION = ''' --- -author: Jeroen Hoekx +author: + - '"Jeroen Hoekx (@jhoekx)" ' + - '"Alexander Bulimov (@abulimov)" ' module: lvol short_description: Configure LVM logical volumes description: diff --git a/system/modprobe.py b/system/modprobe.py index af845ae8cf5..bf58e435552 100644 --- a/system/modprobe.py +++ b/system/modprobe.py @@ -25,7 +25,10 @@ module: modprobe short_description: Add or remove kernel modules requirements: [] version_added: 1.4 -author: David Stygstra, Julien Dauphant, Matt Jeffery +author: + - '"David Stygstra (@stygstra)" ' + - Julien Dauphant + - Matt Jeffery description: - Add or remove kernel modules. options: diff --git a/system/ohai.py b/system/ohai.py index b50abc9db03..6f066ec5ad8 100644 --- a/system/ohai.py +++ b/system/ohai.py @@ -32,7 +32,9 @@ version_added: "0.6" options: {} notes: [] requirements: [ "ohai" ] -author: Michael DeHaan +author: + - "Ansible Core Team" + - "Michael DeHaan (@mpdehaan)" ''' EXAMPLES = ''' diff --git a/system/open_iscsi.py b/system/open_iscsi.py index c661a723d77..97652311f8d 100644 --- a/system/open_iscsi.py +++ b/system/open_iscsi.py @@ -21,7 +21,7 @@ DOCUMENTATION = ''' --- module: open_iscsi -author: Serge van Ginderachter +author: '"Serge van Ginderachter (@srvg)" ' version_added: "1.4" short_description: Manage iscsi targets with open-iscsi description: diff --git a/system/svc.py b/system/svc.py index 04749cfc134..0227a69ecd8 100755 --- a/system/svc.py +++ b/system/svc.py @@ -4,7 +4,7 @@ DOCUMENTATION = ''' --- module: svc -author: Brian Coca +author: "Brian Coca (@bcoca)" version_added: short_description: Manage daemontools services. description: diff --git a/system/ufw.py b/system/ufw.py index a49aa8c3a49..3694f2b937a 100644 --- a/system/ufw.py +++ b/system/ufw.py @@ -28,7 +28,10 @@ short_description: Manage firewall with UFW description: - Manage firewall with UFW. version_added: 1.6 -author: Aleksey Ovcharenko, Jarno Keskikangas, Ahti Kitsik +author: + - '"Aleksey Ovcharenko (@ovcharenko)" ' + - '"Jarno Keskikangas (@pyykkis)" ' + - '"Ahti Kitsik (@ahtik)" ' notes: - See C(man ufw) for more examples. requirements: diff --git a/system/zfs.py b/system/zfs.py index 93248897051..fed17b4a18d 100644 --- a/system/zfs.py +++ b/system/zfs.py @@ -206,7 +206,7 @@ options: - The zoned property. required: False choices: ['on','off'] -author: Johan Wiren +author: '"Johan Wiren (@johanwiren)" ' ''' EXAMPLES = ''' diff --git a/web_infrastructure/ejabberd_user.py b/web_infrastructure/ejabberd_user.py index d8b0384679c..79fe94fcddc 100755 --- a/web_infrastructure/ejabberd_user.py +++ b/web_infrastructure/ejabberd_user.py @@ -20,7 +20,7 @@ DOCUMENTATION = ''' --- module: ejabberd_user version_added: "1.5" -author: Peter Sprygada +author: '"Peter Sprygada (@privateip)" ' short_description: Manages users for ejabberd servers requirements: - ejabberd with mod_admin_extra @@ -113,7 +113,7 @@ class EjabberdUser(object): (rc, out, err) = self.run_command('check_account', options) except EjabberdUserException, e: (rc, out, err) = (1, None, "required attribute(s) missing") - return True if rc == 0 else False + return not bool(int(rc)) def log(self, entry): """ This method will log information to the local syslog facility """ diff --git a/web_infrastructure/jboss.py b/web_infrastructure/jboss.py index 65b44d23047..a0949c47531 100644 --- a/web_infrastructure/jboss.py +++ b/web_infrastructure/jboss.py @@ -47,7 +47,7 @@ options: notes: - "The JBoss standalone deployment-scanner has to be enabled in standalone.xml" - "Ensure no identically named application is deployed through the JBoss CLI" -author: Jeroen Hoekx +author: '"Jeroen Hoekx (@jhoekx)" ' """ EXAMPLES = """ diff --git a/web_infrastructure/jira.py b/web_infrastructure/jira.py index 950fc3dbfcf..3dc963cb6bd 100644 --- a/web_infrastructure/jira.py +++ b/web_infrastructure/jira.py @@ -99,7 +99,7 @@ options: notes: - "Currently this only works with basic-auth." -author: Steve Smith +author: '"Steve Smith (@tarka)" ' """ EXAMPLES = """ @@ -335,7 +335,7 @@ def main(): ret = method(restbase, user, passwd, module.params) - except Exception as e: + except Exception, e: return module.fail_json(msg=e.message) diff --git a/windows/win_chocolatey.py b/windows/win_chocolatey.py index 4df1f3c58e8..63ec1ecd214 100644 --- a/windows/win_chocolatey.py +++ b/windows/win_chocolatey.py @@ -86,7 +86,9 @@ options: require: false default: c:\\ansible-playbook.log aliases: [] -author: Trond Hindenes, Peter Mounce +author: + - '"Trond Hindenes (@trondhindenes)" ' + - '"Peter Mounce (@petemounce)" ' ''' # TODO: diff --git a/windows/win_updates.py b/windows/win_updates.py index 7eefd8ba331..7c93109efb9 100644 --- a/windows/win_updates.py +++ b/windows/win_updates.py @@ -41,7 +41,7 @@ options: - (anything that is a valid update category) default: critical aliases: [] -author: Peter Mounce +author: '"Peter Mounce (@petemounce)" ' ''' EXAMPLES = '''