Copying snapshot of extras modules

This commit is contained in:
James Cammarata
2014-09-26 01:01:01 +00:00
committed by Matt Clay
parent 8afa090417
commit d4b117843a
100 changed files with 25242 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Ansible module to manage symbolic link alternatives.
(c) 2014, Gabe Mulley <gabe.mulley@gmail.com>
This file is part of Ansible
Ansible is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Ansible is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Ansible. If not, see <http://www.gnu.org/licenses/>.
"""
DOCUMENTATION = '''
---
module: alternatives
short_description: Manages alternative programs for common commands
description:
- Manages symbolic links using the 'update-alternatives' tool provided on debian-like systems.
- Useful when multiple programs are installed but provide similar functionality (e.g. different editors).
version_added: "1.6"
options:
name:
description:
- The generic name of the link.
required: true
path:
description:
- The path to the real executable that the link should point to.
required: true
link:
description:
- The path to the symbolic link that should point to the real executable.
required: false
requirements: [ update-alternatives ]
'''
EXAMPLES = '''
- name: correct java version selected
alternatives: name=java path=/usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java
- name: alternatives link created
alternatives: name=hadoop-conf link=/etc/hadoop/conf path=/etc/hadoop/conf.ansible
'''
DEFAULT_LINK_PRIORITY = 50
def main():
module = AnsibleModule(
argument_spec = dict(
name = dict(required=True),
path = dict(required=True),
link = dict(required=False),
)
)
params = module.params
name = params['name']
path = params['path']
link = params['link']
UPDATE_ALTERNATIVES = module.get_bin_path('update-alternatives',True)
current_path = None
all_alternatives = []
(rc, query_output, query_error) = module.run_command(
[UPDATE_ALTERNATIVES, '--query', name]
)
# Gather the current setting and all alternatives from the query output.
# Query output should look something like this:
# Name: java
# Link: /usr/bin/java
# Slaves:
# java.1.gz /usr/share/man/man1/java.1.gz
# Status: manual
# Best: /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java
# Value: /usr/lib/jvm/java-6-openjdk-amd64/jre/bin/java
# Alternative: /usr/lib/jvm/java-6-openjdk-amd64/jre/bin/java
# Priority: 1061
# Slaves:
# java.1.gz /usr/lib/jvm/java-6-openjdk-amd64/jre/man/man1/java.1.gz
# Alternative: /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java
# Priority: 1071
# Slaves:
# java.1.gz /usr/lib/jvm/java-7-openjdk-amd64/jre/man/man1/java.1.gz
if rc == 0:
for line in query_output.splitlines():
split_line = line.split(':')
if len(split_line) == 2:
key = split_line[0]
value = split_line[1].strip()
if key == 'Value':
current_path = value
elif key == 'Alternative':
all_alternatives.append(value)
elif key == 'Link' and not link:
link = value
if current_path != path:
try:
# install the requested path if necessary
if path not in all_alternatives:
module.run_command(
[UPDATE_ALTERNATIVES, '--install', link, name, path, str(DEFAULT_LINK_PRIORITY)],
check_rc=True
)
# select the requested path
module.run_command(
[UPDATE_ALTERNATIVES, '--set', name, path],
check_rc=True
)
module.exit_json(changed=True)
except subprocess.CalledProcessError, cpe:
module.fail_json(msg=str(dir(cpe)))
else:
module.exit_json(changed=False)
# import module snippets
from ansible.module_utils.basic import *
main()

View File

@@ -0,0 +1,200 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# (c) 2014, Richard Isaacson <richard.c.isaacson@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
---
module: at
short_description: Schedule the execution of a command or script file via the at command.
description:
- Use this module to schedule a command or script file to run once in the future.
- All jobs are executed in the 'a' queue.
version_added: "1.5"
options:
command:
description:
- A command to be executed in the future.
required: false
default: null
script_file:
description:
- An existing script file to be executed in the future.
required: false
default: null
count:
description:
- The count of units in the future to execute the command or script file.
required: true
units:
description:
- The type of units in the future to execute the command or script file.
required: true
choices: ["minutes", "hours", "days", "weeks"]
state:
description:
- The state dictates if the command or script file should be evaluated as present(added) or absent(deleted).
required: false
choices: ["present", "absent"]
default: "present"
unique:
description:
- If a matching job is present a new job will not be added.
required: false
default: false
requirements:
- at
author: Richard Isaacson
'''
EXAMPLES = '''
# Schedule a command to execute in 20 minutes as root.
- at: command="ls -d / > /dev/null" count=20 units="minutes"
# Match a command to an existing job and delete the job.
- at: command="ls -d / > /dev/null" state="absent"
# Schedule a command to execute in 20 minutes making sure it is unique in the queue.
- at: command="ls -d / > /dev/null" unique=true count=20 units="minutes"
'''
import os
import tempfile
def add_job(module, result, at_cmd, count, units, command, script_file):
at_command = "%s now + %s %s -f %s" % (at_cmd, count, units, script_file)
rc, out, err = module.run_command(at_command, check_rc=True)
if command:
os.unlink(script_file)
result['changed'] = True
def delete_job(module, result, at_cmd, command, script_file):
for matching_job in get_matching_jobs(module, at_cmd, script_file):
at_command = "%s -d %s" % (at_cmd, matching_job)
rc, out, err = module.run_command(at_command, check_rc=True)
result['changed'] = True
if command:
os.unlink(script_file)
module.exit_json(**result)
def get_matching_jobs(module, at_cmd, script_file):
matching_jobs = []
atq_cmd = module.get_bin_path('atq', True)
# Get list of job numbers for the user.
atq_command = "%s" % atq_cmd
rc, out, err = module.run_command(atq_command, check_rc=True)
current_jobs = out.splitlines()
if len(current_jobs) == 0:
return matching_jobs
# Read script_file into a string.
script_file_string = open(script_file).read().strip()
# Loop through the jobs.
# If the script text is contained in a job add job number to list.
for current_job in current_jobs:
split_current_job = current_job.split()
at_command = "%s -c %s" % (at_cmd, split_current_job[0])
rc, out, err = module.run_command(at_command, check_rc=True)
if script_file_string in out:
matching_jobs.append(split_current_job[0])
# Return the list.
return matching_jobs
def create_tempfile(command):
filed, script_file = tempfile.mkstemp(prefix='at')
fileh = os.fdopen(filed, 'w')
fileh.write(command)
fileh.close()
return script_file
def main():
module = AnsibleModule(
argument_spec = dict(
command=dict(required=False,
type='str'),
script_file=dict(required=False,
type='str'),
count=dict(required=False,
type='int'),
units=dict(required=False,
default=None,
choices=['minutes', 'hours', 'days', 'weeks'],
type='str'),
state=dict(required=False,
default='present',
choices=['present', 'absent'],
type='str'),
unique=dict(required=False,
default=False,
type='bool')
),
mutually_exclusive=[['command', 'script_file']],
required_one_of=[['command', 'script_file']],
supports_check_mode=False
)
at_cmd = module.get_bin_path('at', True)
command = module.params['command']
script_file = module.params['script_file']
count = module.params['count']
units = module.params['units']
state = module.params['state']
unique = module.params['unique']
if (state == 'present') and (not count or not units):
module.fail_json(msg="present state requires count and units")
result = {'state': state, 'changed': False}
# If command transform it into a script_file
if command:
script_file = create_tempfile(command)
# if absent remove existing and return
if state == 'absent':
delete_job(module, result, at_cmd, command, script_file)
# if unique if existing return unchanged
if unique:
if len(get_matching_jobs(module, at_cmd, script_file)) != 0:
if command:
os.unlink(script_file)
module.exit_json(**result)
result['script_file'] = script_file
result['count'] = count
result['units'] = units
add_job(module, result, at_cmd, count, units, command, script_file)
module.exit_json(**result)
# import module snippets
from ansible.module_utils.basic import *
main()

View File

@@ -0,0 +1,187 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2014, Nate Coraor <nate@bx.psu.edu>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
DOCUMENTATION = '''
---
module: capabilities
short_description: Manage Linux capabilities
description:
- This module manipulates files privileges using the Linux capabilities(7) system.
version_added: "1.6"
options:
path:
description:
- Specifies the path to the file to be managed.
required: true
default: null
capability:
description:
- Desired capability to set (with operator and flags, if state is C(present)) or remove (if state is C(absent))
required: true
default: null
aliases: [ 'cap' ]
state:
description:
- Whether the entry should be present or absent in the file's capabilities.
choices: [ "present", "absent" ]
default: present
notes:
- The capabilities system will automatically transform operators and flags
into the effective set, so (for example, cap_foo=ep will probably become
cap_foo+ep). This module does not attempt to determine the final operator
and flags to compare, so you will want to ensure that your capabilities
argument matches the final capabilities.
requirements: []
author: Nate Coraor <nate@bx.psu.edu>
'''
EXAMPLES = '''
# Set cap_sys_chroot+ep on /foo
- capabilities: path=/foo capability=cap_sys_chroot+ep state=present
# Remove cap_net_bind_service from /bar
- capabilities: path=/bar capability=cap_net_bind_service state=absent
'''
OPS = ( '=', '-', '+' )
# ==============================================================
import os
import tempfile
import re
class CapabilitiesModule(object):
platform = 'Linux'
distribution = None
def __init__(self, module):
self.module = module
self.path = module.params['path'].strip()
self.capability = module.params['capability'].strip().lower()
self.state = module.params['state']
self.getcap_cmd = module.get_bin_path('getcap', required=True)
self.setcap_cmd = module.get_bin_path('setcap', required=True)
self.capability_tup = self._parse_cap(self.capability, op_required=self.state=='present')
self.run()
def run(self):
current = self.getcap(self.path)
caps = [ cap[0] for cap in current ]
if self.state == 'present' and self.capability_tup not in current:
# need to add capability
if self.module.check_mode:
self.module.exit_json(changed=True, msg='capabilities changed')
else:
# remove from current cap list if it's already set (but op/flags differ)
current = filter(lambda x: x[0] != self.capability_tup[0], current)
# add new cap with correct op/flags
current.append( self.capability_tup )
self.module.exit_json(changed=True, state=self.state, msg='capabilities changed', stdout=self.setcap(self.path, current))
elif self.state == 'absent' and self.capability_tup[0] in caps:
# need to remove capability
if self.module.check_mode:
self.module.exit_json(changed=True, msg='capabilities changed')
else:
# remove from current cap list and then set current list
current = filter(lambda x: x[0] != self.capability_tup[0], current)
self.module.exit_json(changed=True, state=self.state, msg='capabilities changed', stdout=self.setcap(self.path, current))
self.module.exit_json(changed=False, state=self.state)
def getcap(self, path):
rval = []
cmd = "%s -v %s" % (self.getcap_cmd, path)
rc, stdout, stderr = self.module.run_command(cmd)
# If file xattrs are set but no caps are set the output will be:
# '/foo ='
# If file xattrs are unset the output will be:
# '/foo'
# If the file does not eixst the output will be (with rc == 0...):
# '/foo (No such file or directory)'
if rc != 0 or (stdout.strip() != path and stdout.count(' =') != 1):
self.module.fail_json(msg="Unable to get capabilities of %s" % path, stdout=stdout.strip(), stderr=stderr)
if stdout.strip() != path:
caps = stdout.split(' =')[1].strip().split()
for cap in caps:
cap = cap.lower()
# getcap condenses capabilities with the same op/flags into a
# comma-separated list, so we have to parse that
if ',' in cap:
cap_group = cap.split(',')
cap_group[-1], op, flags = self._parse_cap(cap_group[-1])
for subcap in cap_group:
rval.append( ( subcap, op, flags ) )
else:
rval.append(self._parse_cap(cap))
return rval
def setcap(self, path, caps):
caps = ' '.join([ ''.join(cap) for cap in caps ])
cmd = "%s '%s' %s" % (self.setcap_cmd, caps, path)
rc, stdout, stderr = self.module.run_command(cmd)
if rc != 0:
self.module.fail_json(msg="Unable to set capabilities of %s" % path, stdout=stdout, stderr=stderr)
else:
return stdout
def _parse_cap(self, cap, op_required=True):
opind = -1
try:
i = 0
while opind == -1:
opind = cap.find(OPS[i])
i += 1
except:
if op_required:
self.module.fail_json(msg="Couldn't find operator (one of: %s)" % str(OPS))
else:
return (cap, None, None)
op = cap[opind]
cap, flags = cap.split(op)
return (cap, op, flags)
# ==============================================================
# main
def main():
# defining module
module = AnsibleModule(
argument_spec = dict(
path = dict(aliases=['key'], required=True),
capability = dict(aliases=['cap'], required=True),
state = dict(default='present', choices=['present', 'absent']),
),
supports_check_mode=True
)
CapabilitiesModule(module)
sys.exit(0)
# import module snippets
from ansible.module_utils.basic import *
main()

View File

@@ -0,0 +1,170 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Ansible module to configure .deb packages.
(c) 2014, Brian Coca <briancoca+ansible@gmail.com>
This file is part of Ansible
Ansible is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Ansible is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Ansible. If not, see <http://www.gnu.org/licenses/>.
"""
DOCUMENTATION = '''
---
module: debconf
short_description: Configure a .deb package
description:
- Configure a .deb package using debconf-set-selections. Or just query
existing selections.
version_added: "1.6"
notes:
- This module requires the command line debconf tools.
- A number of questions have to be answered (depending on the package).
Use 'debconf-show <package>' on any Debian or derivative with the package
installed to see questions/settings available.
requirements: [ debconf, debconf-utils ]
options:
name:
description:
- Name of package to configure.
required: true
default: null
aliases: ['pkg']
question:
description:
- A debconf configuration setting
required: false
default: null
aliases: ['setting', 'selection']
vtype:
description:
- The type of the value supplied
required: false
default: null
choices: [string, password, boolean, select, multiselect, note, error, title, text]
aliases: []
value:
description:
- Value to set the configuration to
required: false
default: null
aliases: ['answer']
unseen:
description:
- Do not set 'seen' flag when pre-seeding
required: false
default: False
aliases: []
author: Brian Coca
'''
EXAMPLES = '''
# Set default locale to fr_FR.UTF-8
debconf: name=locales question='locales/default_environment_locale' value=fr_FR.UTF-8 vtype='select'
# set to generate locales:
debconf: name=locales question='locales/locales_to_be_generated' value='en_US.UTF-8 UTF-8, fr_FR.UTF-8 UTF-8' vtype='multiselect'
# Accept oracle license
debconf: name='oracle-java7-installer' question='shared/accepted-oracle-license-v1-1' value='true' vtype='select'
# Specifying package you can register/return the list of questions and current values
debconf: name='tzdata'
'''
import pipes
def get_selections(module, pkg):
cmd = [module.get_bin_path('debconf-show', True), pkg]
rc, out, err = module.run_command(' '.join(cmd))
if rc != 0:
module.fail_json(msg=err)
selections = {}
for line in out.splitlines():
(key, value) = line.split(':', 1)
selections[ key.strip('*').strip() ] = value.strip()
return selections
def set_selection(module, pkg, question, vtype, value, unseen):
data = ' '.join([ question, vtype, value ])
setsel = module.get_bin_path('debconf-set-selections', True)
cmd = ["echo %s %s |" % (pipes.quote(pkg), pipes.quote(data)), setsel]
if unseen:
cmd.append('-u')
return module.run_command(' '.join(cmd), use_unsafe_shell=True)
def main():
module = AnsibleModule(
argument_spec = dict(
name = dict(required=True, aliases=['pkg'], type='str'),
question = dict(required=False, aliases=['setting', 'selection'], type='str'),
vtype = dict(required=False, type='str', choices=['string', 'password', 'boolean', 'select', 'multiselect', 'note', 'error', 'title', 'text']),
value= dict(required=False, type='str'),
unseen = dict(required=False, type='bool'),
),
required_together = ( ['question','vtype', 'value'],),
supports_check_mode=True,
)
#TODO: enable passing array of options and/or debconf file from get-selections dump
pkg = module.params["name"]
question = module.params["question"]
vtype = module.params["vtype"]
value = module.params["value"]
unseen = module.params["unseen"]
prev = get_selections(module, pkg)
diff = ''
changed = False
msg = ""
if question is not None:
if vtype is None or value is None:
module.fail_json(msg="when supplying a question you must supply a valid vtype and value")
if not question in prev or prev[question] != value:
changed = True
if changed:
if not module.check_mode:
rc, msg, e = set_selection(module, pkg, question, vtype, value, unseen)
if rc:
module.fail_json(msg=e)
curr = { question: value }
if question in prev:
prev = {question: prev[question]}
else:
prev[question] = ''
module.exit_json(changed=changed, msg=msg, current=curr, previous=prev)
module.exit_json(changed=changed, msg=msg, current=prev)
# import module snippets
from ansible.module_utils.basic import *
main()

View File

@@ -0,0 +1,56 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
DOCUMENTATION = '''
---
module: facter
short_description: Runs the discovery program I(facter) on the remote system
description:
- Runs the I(facter) discovery program
(U(https://github.com/puppetlabs/facter)) on the remote system, returning
JSON data that can be useful for inventory purposes.
version_added: "0.2"
options: {}
notes: []
requirements: [ "facter", "ruby-json" ]
author: Michael DeHaan
'''
EXAMPLES = '''
# Example command-line invocation
ansible www.example.net -m facter
'''
def main():
module = AnsibleModule(
argument_spec = dict()
)
cmd = ["/usr/bin/env", "facter", "--json"]
rc, out, err = module.run_command(cmd, check_rc=True)
module.exit_json(**json.loads(out))
# import module snippets
from ansible.module_utils.basic import *
main()

View File

@@ -0,0 +1,119 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2013, Alexander Bulimov <lazywolf0@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
---
author: Alexander Bulimov
module: filesystem
short_description: Makes file system on block device
description:
- This module creates file system.
version_added: "1.2"
options:
fstype:
description:
- File System type to be created.
required: true
dev:
description:
- Target block device.
required: true
force:
choices: [ "yes", "no" ]
default: "no"
description:
- If yes, allows to create new filesystem on devices that already has filesystem.
required: false
opts:
description:
- List of options to be passed to mkfs command.
notes:
- uses mkfs command
'''
EXAMPLES = '''
# Create a ext2 filesystem on /dev/sdb1.
- filesystem: fstype=ext2 dev=/dev/sdb1
# Create a ext4 filesystem on /dev/sdb1 and check disk blocks.
- filesystem: fstype=ext4 dev=/dev/sdb1 opts="-cc"
'''
def main():
module = AnsibleModule(
argument_spec = dict(
fstype=dict(required=True, aliases=['type']),
dev=dict(required=True, aliases=['device']),
opts=dict(),
force=dict(type='bool', default='no'),
),
supports_check_mode=True,
)
dev = module.params['dev']
fstype = module.params['fstype']
opts = module.params['opts']
force = module.boolean(module.params['force'])
changed = False
if not os.path.exists(dev):
module.fail_json(msg="Device %s not found."%dev)
cmd = module.get_bin_path('blkid', required=True)
rc,raw_fs,err = module.run_command("%s -c /dev/null -o value -s TYPE %s" % (cmd, dev))
fs = raw_fs.strip()
if fs == fstype:
module.exit_json(changed=False)
elif fs and not force:
module.fail_json(msg="'%s' is already used as %s, use force=yes to overwrite"%(dev,fs), rc=rc, err=err)
### create fs
if module.check_mode:
changed = True
else:
mkfs = module.get_bin_path('mkfs', required=True)
cmd = None
if fstype in ['ext2', 'ext3', 'ext4', 'ext4dev']:
force_flag="-F"
elif fstype in ['btrfs']:
force_flag="-f"
else:
force_flag=""
if opts is None:
cmd = "%s -t %s %s '%s'" % (mkfs, fstype, force_flag, dev)
else:
cmd = "%s -t %s %s %s '%s'" % (mkfs, fstype, force_flag, opts, dev)
rc,_,err = module.run_command(cmd)
if rc == 0:
changed = True
else:
module.fail_json(msg="Creating filesystem %s on device '%s' failed"%(fstype,dev), rc=rc, err=err)
module.exit_json(changed=changed)
# import module snippets
from ansible.module_utils.basic import *
main()

View File

@@ -0,0 +1,398 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2013, Adam Miller (maxamillion@fedoraproject.org)
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
---
module: firewalld
short_description: Manage arbitrary ports/services with firewalld
description:
- This module allows for addition or deletion of services and ports either tcp or udp in either running or permanent firewalld rules
version_added: "1.4"
options:
service:
description:
- "Name of a service to add/remove to/from firewalld - service must be listed in /etc/services"
required: false
default: null
port:
description:
- "Name of a port to add/remove to/from firewalld must be in the form PORT/PROTOCOL"
required: false
default: null
rich_rule:
description:
- "Rich rule to add/remove to/from firewalld"
required: false
default: null
zone:
description:
- 'The firewalld zone to add/remove to/from (NOTE: default zone can be configured per system but "public" is default from upstream. Available choices can be extended based on per-system configs, listed here are "out of the box" defaults).'
required: false
default: system-default(public)
choices: [ "work", "drop", "internal", "external", "trusted", "home", "dmz", "public", "block"]
permanent:
description:
- "Should this configuration be in the running firewalld configuration or persist across reboots"
required: true
default: true
state:
description:
- "Should this port accept(enabled) or reject(disabled) connections"
required: true
default: enabled
timeout:
description:
- "The amount of time the rule should be in effect for when non-permanent"
required: false
default: 0
notes:
- Not tested on any debian based system
requirements: [ firewalld >= 0.2.11 ]
author: Adam Miller <maxamillion@fedoraproject.org>
'''
EXAMPLES = '''
- firewalld: service=https permanent=true state=enabled
- firewalld: port=8081/tcp permanent=true state=disabled
- firewalld: zone=dmz service=http permanent=true state=enabled
- firewalld: rich_rule='rule service name="ftp" audit limit value="1/m" accept' permanent=true state=enabled
'''
import os
import re
import sys
try:
import firewall.config
FW_VERSION = firewall.config.VERSION
from firewall.client import FirewallClient
fw = FirewallClient()
if not fw.connected:
raise Exception('failed to connect to the firewalld daemon')
except ImportError:
print "failed=True msg='firewalld required for this module'"
sys.exit(1)
except Exception, e:
print "failed=True msg='%s'" % str(e)
sys.exit(1)
################
# port handling
#
def get_port_enabled(zone, port_proto):
if port_proto in fw.getPorts(zone):
return True
else:
return False
def set_port_enabled(zone, port, protocol, timeout):
fw.addPort(zone, port, protocol, timeout)
def set_port_disabled(zone, port, protocol):
fw.removePort(zone, port, protocol)
def get_port_enabled_permanent(zone, port_proto):
fw_zone = fw.config().getZoneByName(zone)
fw_settings = fw_zone.getSettings()
if tuple(port_proto) in fw_settings.getPorts():
return True
else:
return False
def set_port_enabled_permanent(zone, port, protocol):
fw_zone = fw.config().getZoneByName(zone)
fw_settings = fw_zone.getSettings()
fw_settings.addPort(port, protocol)
fw_zone.update(fw_settings)
def set_port_disabled_permanent(zone, port, protocol):
fw_zone = fw.config().getZoneByName(zone)
fw_settings = fw_zone.getSettings()
fw_settings.removePort(port, protocol)
fw_zone.update(fw_settings)
####################
# service handling
#
def get_service_enabled(zone, service):
if service in fw.getServices(zone):
return True
else:
return False
def set_service_enabled(zone, service, timeout):
fw.addService(zone, service, timeout)
def set_service_disabled(zone, service):
fw.removeService(zone, service)
def get_service_enabled_permanent(zone, service):
fw_zone = fw.config().getZoneByName(zone)
fw_settings = fw_zone.getSettings()
if service in fw_settings.getServices():
return True
else:
return False
def set_service_enabled_permanent(zone, service):
fw_zone = fw.config().getZoneByName(zone)
fw_settings = fw_zone.getSettings()
fw_settings.addService(service)
fw_zone.update(fw_settings)
def set_service_disabled_permanent(zone, service):
fw_zone = fw.config().getZoneByName(zone)
fw_settings = fw_zone.getSettings()
fw_settings.removeService(service)
fw_zone.update(fw_settings)
####################
# rich rule handling
#
def get_rich_rule_enabled(zone, rule):
if rule in fw.getRichRules(zone):
return True
else:
return False
def set_rich_rule_enabled(zone, rule, timeout):
fw.addRichRule(zone, rule, timeout)
def set_rich_rule_disabled(zone, rule):
fw.removeRichRule(zone, rule)
def get_rich_rule_enabled_permanent(zone, rule):
fw_zone = fw.config().getZoneByName(zone)
fw_settings = fw_zone.getSettings()
if rule in fw_settings.getRichRules():
return True
else:
return False
def set_rich_rule_enabled_permanent(zone, rule):
fw_zone = fw.config().getZoneByName(zone)
fw_settings = fw_zone.getSettings()
fw_settings.addRichRule(rule)
fw_zone.update(fw_settings)
def set_rich_rule_disabled_permanent(zone, rule):
fw_zone = fw.config().getZoneByName(zone)
fw_settings = fw_zone.getSettings()
fw_settings.removeRichRule(rule)
fw_zone.update(fw_settings)
def main():
module = AnsibleModule(
argument_spec = dict(
service=dict(required=False,default=None),
port=dict(required=False,default=None),
rich_rule=dict(required=False,default=None),
zone=dict(required=False,default=None),
permanent=dict(type='bool',required=True),
state=dict(choices=['enabled', 'disabled'], required=True),
timeout=dict(type='int',required=False,default=0),
),
supports_check_mode=True
)
## Pre-run version checking
if FW_VERSION < "0.2.11":
module.fail_json(msg='unsupported version of firewalld, requires >= 2.0.11')
## Global Vars
changed=False
msgs = []
service = module.params['service']
rich_rule = module.params['rich_rule']
if module.params['port'] != None:
port, protocol = module.params['port'].split('/')
if protocol == None:
module.fail_json(msg='improper port format (missing protocol?)')
else:
port = None
if module.params['zone'] != None:
zone = module.params['zone']
else:
zone = fw.getDefaultZone()
permanent = module.params['permanent']
desired_state = module.params['state']
timeout = module.params['timeout']
## Check for firewalld running
try:
if fw.connected == False:
module.fail_json(msg='firewalld service must be running')
except AttributeError:
module.fail_json(msg="firewalld connection can't be established,\
version likely too old. Requires firewalld >= 2.0.11")
modification_count = 0
if service != None:
modification_count += 1
if port != None:
modification_count += 1
if rich_rule != None:
modification_count += 1
if modification_count > 1:
module.fail_json(msg='can only operate on port, service or rich_rule at once')
if service != None:
if permanent:
is_enabled = get_service_enabled_permanent(zone, service)
msgs.append('Permanent operation')
if desired_state == "enabled":
if is_enabled == False:
if module.check_mode:
module.exit_json(changed=True)
set_service_enabled_permanent(zone, service)
changed=True
elif desired_state == "disabled":
if is_enabled == True:
if module.check_mode:
module.exit_json(changed=True)
set_service_disabled_permanent(zone, service)
changed=True
else:
is_enabled = get_service_enabled(zone, service)
msgs.append('Non-permanent operation')
if desired_state == "enabled":
if is_enabled == False:
if module.check_mode:
module.exit_json(changed=True)
set_service_enabled(zone, service, timeout)
changed=True
elif desired_state == "disabled":
if is_enabled == True:
if module.check_mode:
module.exit_json(changed=True)
set_service_disabled(zone, service)
changed=True
if changed == True:
msgs.append("Changed service %s to %s" % (service, desired_state))
if port != None:
if permanent:
is_enabled = get_port_enabled_permanent(zone, [port, protocol])
msgs.append('Permanent operation')
if desired_state == "enabled":
if is_enabled == False:
if module.check_mode:
module.exit_json(changed=True)
set_port_enabled_permanent(zone, port, protocol)
changed=True
elif desired_state == "disabled":
if is_enabled == True:
if module.check_mode:
module.exit_json(changed=True)
set_port_disabled_permanent(zone, port, protocol)
changed=True
else:
is_enabled = get_port_enabled(zone, [port,protocol])
msgs.append('Non-permanent operation')
if desired_state == "enabled":
if is_enabled == False:
if module.check_mode:
module.exit_json(changed=True)
set_port_enabled(zone, port, protocol, timeout)
changed=True
elif desired_state == "disabled":
if is_enabled == True:
if module.check_mode:
module.exit_json(changed=True)
set_port_disabled(zone, port, protocol)
changed=True
if changed == True:
msgs.append("Changed port %s to %s" % ("%s/%s" % (port, protocol), \
desired_state))
if rich_rule != None:
if permanent:
is_enabled = get_rich_rule_enabled_permanent(zone, rich_rule)
msgs.append('Permanent operation')
if desired_state == "enabled":
if is_enabled == False:
if module.check_mode:
module.exit_json(changed=True)
set_rich_rule_enabled_permanent(zone, rich_rule)
changed=True
elif desired_state == "disabled":
if is_enabled == True:
if module.check_mode:
module.exit_json(changed=True)
set_rich_rule_disabled_permanent(zone, rich_rule)
changed=True
else:
is_enabled = get_rich_rule_enabled(zone, rich_rule)
msgs.append('Non-permanent operation')
if desired_state == "enabled":
if is_enabled == False:
if module.check_mode:
module.exit_json(changed=True)
set_rich_rule_enabled(zone, rich_rule, timeout)
changed=True
elif desired_state == "disabled":
if is_enabled == True:
if module.check_mode:
module.exit_json(changed=True)
set_rich_rule_disabled(zone, rich_rule)
changed=True
if changed == True:
msgs.append("Changed rich_rule %s to %s" % (rich_rule, desired_state))
module.exit_json(changed=changed, msg=', '.join(msgs))
#################################################
# import module snippets
from ansible.module_utils.basic import *
main()

View File

@@ -0,0 +1,143 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2014, Brian Coca <brian.coca+dev@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
DOCUMENTATION = '''
---
module: getent
short_description: a wrapper to the unix getent utility
description:
- Runs getent against one of it's various databases and returns information into
the host's facts
version_added: "1.8"
options:
database:
required: True
description:
- the name of a getent database supported by the target system (passwd, group,
hosts, etc).
key:
required: False
default: ''
description:
- key from which to return values from the specified database, otherwise the
full contents are returned.
split:
required: False
default: None
description:
- "character used to split the database values into lists/arrays such as ':' or '\t', otherwise it will try to pick one depending on the database"
fail_key:
required: False
default: True
description:
- If a supplied key is missing this will make the task fail if True
notes:
- "Not all databases support enumeration, check system documentation for details"
requirements: [ ]
author: Brian Coca
'''
EXAMPLES = '''
# get root user info
- getent: database=passwd key=root
register: root_info
# get all groups
- getent: database=group split=':'
register: groups
# get all hosts, split by tab
- getent: database=hosts
register: hosts
# get http service info, no error if missing
- getent: database=services key=http fail_key=False
register: http_info
# get user password hash (requires sudo/root)
- getent: database=shadow key=www-data split=:
register: pw_hash
'''
def main():
module = AnsibleModule(
argument_spec = dict(
database = dict(required=True),
key = dict(required=False, default=None),
split = dict(required=False, default=None),
fail_key = dict(required=False, default=True),
),
supports_check_mode = True,
)
colon = [ 'passwd', 'shadow', 'group', 'gshadow' ]
database = module.params['database']
key = module.params.get('key')
split = module.params.get('split')
fail_key = module.params.get('fail_key')
getent_bin = module.get_bin_path('getent', True)
if key is not None:
cmd = [ getent_bin, database, key ]
else:
cmd = [ getent_bin, database ]
if split is None and database in colon:
split = ':'
try:
rc, out, err = module.run_command(cmd)
except Exception, e:
module.fail_json(msg=str(e))
msg = "Unexpected failure!"
dbtree = 'getent_%s' % database
results = { dbtree: {} }
if rc == 0:
for line in out.splitlines():
record = line.split(split)
results[dbtree][record[0]] = record[1:]
module.exit_json(ansible_facts=results)
elif rc == 1:
msg = "Missing arguments, or database unknown."
elif rc == 2:
msg = "One or more supplied key could not be found in the database."
if not fail_key:
results[dbtree][key] = None
module.exit_json(ansible_facts=results, msg=msg)
elif rc == 3:
msg = "Enumeration not supported on this database."
module.fail_json(msg=msg)
# import module snippets
from ansible.module_utils.basic import *
main()

View File

@@ -0,0 +1,141 @@
#!/usr/bin/python
# encoding: utf-8 -*-
# (c) 2013, Matthias Vogelgesang <matthias.vogelgesang@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import os
import re
DOCUMENTATION = '''
---
module: kernel_blacklist
author: Matthias Vogelgesang
version_added: 1.4
short_description: Blacklist kernel modules
description:
- Add or remove kernel modules from blacklist.
options:
name:
required: true
description:
- Name of kernel module to black- or whitelist.
state:
required: false
default: "present"
choices: [ present, absent ]
description:
- Whether the module should be present in the blacklist or absent.
blacklist_file:
required: false
description:
- If specified, use this blacklist file instead of
C(/etc/modprobe.d/blacklist-ansible.conf).
default: null
requirements: []
'''
EXAMPLES = '''
# Blacklist the nouveau driver module
- kernel_blacklist: name=nouveau state=present
'''
class Blacklist(object):
def __init__(self, module, filename):
if not os.path.exists(filename):
open(filename, 'a').close()
self.filename = filename
self.module = module
def get_pattern(self):
return '^blacklist\s*' + self.module + '$'
def readlines(self):
f = open(self.filename, 'r')
lines = f.readlines()
f.close()
return lines
def module_listed(self):
lines = self.readlines()
pattern = self.get_pattern()
for line in lines:
stripped = line.strip()
if stripped.startswith('#'):
continue
if re.match(pattern, stripped):
return True
return False
def remove_module(self):
lines = self.readlines()
pattern = self.get_pattern()
f = open(self.filename, 'w')
for line in lines:
if not re.match(pattern, line.strip()):
f.write(line)
f.close()
def add_module(self):
f = open(self.filename, 'a')
f.write('blacklist %s\n' % self.module)
def main():
module = AnsibleModule(
argument_spec=dict(
name=dict(required=True),
state=dict(required=False, choices=['present', 'absent'],
default='present'),
blacklist_file=dict(required=False, default=None)
),
supports_check_mode=False,
)
args = dict(changed=False, failed=False,
name=module.params['name'], state=module.params['state'])
filename = '/etc/modprobe.d/blacklist-ansible.conf'
if module.params['blacklist_file']:
filename = module.params['blacklist_file']
blacklist = Blacklist(args['name'], filename)
if blacklist.module_listed():
if args['state'] == 'absent':
blacklist.remove_module()
args['changed'] = True
else:
if args['state'] == 'present':
blacklist.add_module()
args['changed'] = True
module.exit_json(**args)
# import module snippets
from ansible.module_utils.basic import *
main()

View File

@@ -0,0 +1,151 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import os
import os.path
from subprocess import Popen, PIPE, call
DOCUMENTATION = '''
---
module: locale_gen
short_description: Creates of removes locales.
description:
- Manages locales by editing /etc/locale.gen and invoking locale-gen.
version_added: "1.6"
options:
name:
description:
- Name and encoding of the locale, such as "en_GB.UTF-8".
required: true
default: null
aliases: []
state:
description:
- Whether the locale shall be present.
required: false
choices: ["present", "absent"]
default: "present"
'''
EXAMPLES = '''
# Ensure a locale exists.
- locale_gen: name=de_CH.UTF-8 state=present
'''
# ===========================================
# location module specific support methods.
#
def is_present(name):
"""Checks if the given locale is currently installed."""
output = Popen(["locale", "-a"], stdout=PIPE).communicate()[0]
return any(fix_case(name) == fix_case(line) for line in output.splitlines())
def fix_case(name):
"""locale -a might return the encoding in either lower or upper case.
Passing through this function makes them uniform for comparisons."""
return name.replace(".utf8", ".UTF-8")
def replace_line(existing_line, new_line):
"""Replaces lines in /etc/locale.gen"""
with open("/etc/locale.gen", "r") as f:
lines = [line.replace(existing_line, new_line) for line in f]
with open("/etc/locale.gen", "w") as f:
f.write("".join(lines))
def apply_change(targetState, name, encoding):
"""Create or remove locale.
Keyword arguments:
targetState -- Desired state, either present or absent.
name -- Name including encoding such as de_CH.UTF-8.
encoding -- Encoding such as UTF-8.
"""
if targetState=="present":
# Create locale.
replace_line("# "+name+" "+encoding, name+" "+encoding)
else:
# Delete locale.
replace_line(name+" "+encoding, "# "+name+" "+encoding)
localeGenExitValue = call("locale-gen")
if localeGenExitValue!=0:
raise EnvironmentError(localeGenExitValue, "locale.gen failed to execute, it returned "+str(localeGenExitValue))
def apply_change_ubuntu(targetState, name, encoding):
"""Create or remove locale.
Keyword arguments:
targetState -- Desired state, either present or absent.
name -- Name including encoding such as de_CH.UTF-8.
encoding -- Encoding such as UTF-8.
"""
if targetState=="present":
# Create locale.
# Ubuntu's patched locale-gen automatically adds the new locale to /var/lib/locales/supported.d/local
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:
content = f.readlines()
with open("/var/lib/locales/supported.d/local", "w") as f:
for line in content:
if line!=(name+" "+encoding+"\n"):
f.write(line)
# Purge locales and regenerate.
# Please provide a patch if you know how to avoid regenerating the locales to keep!
localeGenExitValue = call(["locale-gen", "--purge"])
if localeGenExitValue!=0:
raise EnvironmentError(localeGenExitValue, "locale.gen failed to execute, it returned "+str(localeGenExitValue))
# ==============================================================
# main
def main():
module = AnsibleModule(
argument_spec = dict(
name = dict(required=True),
state = dict(choices=['present','absent'], required=True),
),
supports_check_mode=True
)
name = module.params['name']
if not "." in name:
module.fail_json(msg="Locale does not match pattern. Did you specify the encoding?")
state = module.params['state']
if not os.path.exists("/etc/locale.gen"):
if os.path.exists("/var/lib/locales/supported.d/local"):
# Ubuntu created its own system to manage locales.
ubuntuMode = True
else:
module.fail_json(msg="/etc/locale.gen and /var/lib/locales/supported.d/local are missing. Is the package “locales” installed?")
else:
# We found the common way to manage locales.
ubuntuMode = False
prev_state = "present" if is_present(name) else "absent"
changed = (prev_state!=state)
if module.check_mode:
module.exit_json(changed=changed)
else:
encoding = name.split(".")[1]
if changed:
try:
if ubuntuMode==False:
apply_change(state, name, encoding)
else:
apply_change_ubuntu(state, name, encoding)
except EnvironmentError as e:
module.fail_json(msg=e.strerror, exitValue=e.errno)
module.exit_json(name=name, changed=changed, msg="OK")
# import module snippets
from ansible.module_utils.basic import *
main()

View File

@@ -0,0 +1,253 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2013, Alexander Bulimov <lazywolf0@gmail.com>
# based on lvol module by Jeroen Hoekx <jeroen.hoekx@dsquare.be>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
---
author: Alexander Bulimov
module: lvg
short_description: Configure LVM volume groups
description:
- This module creates, removes or resizes volume groups.
version_added: "1.1"
options:
vg:
description:
- The name of the volume group.
required: true
pvs:
description:
- List of comma-separated devices to use as physical devices in this volume group. Required when creating or resizing volume group.
required: false
pesize:
description:
- The size of the physical extent in megabytes. Must be a power of 2.
default: 4
required: false
vg_options:
description:
- Additional options to pass to C(vgcreate) when creating the volume group.
default: null
required: false
version_added: "1.6"
state:
choices: [ "present", "absent" ]
default: present
description:
- Control if the volume group exists.
required: false
force:
choices: [ "yes", "no" ]
default: "no"
description:
- If yes, allows to remove volume group with logical volumes.
required: false
notes:
- module does not modify PE size for already present volume group
'''
EXAMPLES = '''
# Create a volume group on top of /dev/sda1 with physical extent size = 32MB.
- lvg: vg=vg.services pvs=/dev/sda1 pesize=32
# Create or resize a volume group on top of /dev/sdb1 and /dev/sdc5.
# If, for example, we already have VG vg.services on top of /dev/sdb1,
# this VG will be extended by /dev/sdc5. Or if vg.services was created on
# top of /dev/sda5, we first extend it with /dev/sdb1 and /dev/sdc5,
# and then reduce by /dev/sda5.
- lvg: vg=vg.services pvs=/dev/sdb1,/dev/sdc5
# Remove a volume group with name vg.services.
- lvg: vg=vg.services state=absent
'''
def parse_vgs(data):
vgs = []
for line in data.splitlines():
parts = line.strip().split(';')
vgs.append({
'name': parts[0],
'pv_count': int(parts[1]),
'lv_count': int(parts[2]),
})
return vgs
def find_mapper_device_name(module, dm_device):
dmsetup_cmd = module.get_bin_path('dmsetup', True)
mapper_prefix = '/dev/mapper/'
rc, dm_name, err = module.run_command("%s info -C --noheadings -o name %s" % (dmsetup_cmd, dm_device))
if rc != 0:
module.fail_json(msg="Failed executing dmsetup command.", rc=rc, err=err)
mapper_device = mapper_prefix + dm_name.rstrip()
return mapper_device
def parse_pvs(module, data):
pvs = []
dm_prefix = '/dev/dm-'
for line in data.splitlines():
parts = line.strip().split(';')
if parts[0].startswith(dm_prefix):
parts[0] = find_mapper_device_name(module, parts[0])
pvs.append({
'name': parts[0],
'vg_name': parts[1],
})
return pvs
def main():
module = AnsibleModule(
argument_spec = dict(
vg=dict(required=True),
pvs=dict(type='list'),
pesize=dict(type='int', default=4),
vg_options=dict(default=''),
state=dict(choices=["absent", "present"], default='present'),
force=dict(type='bool', default='no'),
),
supports_check_mode=True,
)
vg = module.params['vg']
state = module.params['state']
force = module.boolean(module.params['force'])
pesize = module.params['pesize']
vgoptions = module.params['vg_options'].split()
if module.params['pvs']:
dev_string = ' '.join(module.params['pvs'])
dev_list = module.params['pvs']
elif state == 'present':
module.fail_json(msg="No physical volumes given.")
if state=='present':
### check given devices
for test_dev in dev_list:
if not os.path.exists(test_dev):
module.fail_json(msg="Device %s not found."%test_dev)
### get pv list
pvs_cmd = module.get_bin_path('pvs', True)
rc,current_pvs,err = module.run_command("%s --noheadings -o pv_name,vg_name --separator ';'" % pvs_cmd)
if rc != 0:
module.fail_json(msg="Failed executing pvs command.",rc=rc, err=err)
### check pv for devices
pvs = parse_pvs(module, current_pvs)
used_pvs = [ pv for pv in pvs if pv['name'] in dev_list and pv['vg_name'] and pv['vg_name'] != vg ]
if used_pvs:
module.fail_json(msg="Device %s is already in %s volume group."%(used_pvs[0]['name'],used_pvs[0]['vg_name']))
vgs_cmd = module.get_bin_path('vgs', True)
rc,current_vgs,err = module.run_command("%s --noheadings -o vg_name,pv_count,lv_count --separator ';'" % vgs_cmd)
if rc != 0:
module.fail_json(msg="Failed executing vgs command.",rc=rc, err=err)
changed = False
vgs = parse_vgs(current_vgs)
for test_vg in vgs:
if test_vg['name'] == vg:
this_vg = test_vg
break
else:
this_vg = None
if this_vg is None:
if state == 'present':
### create VG
if module.check_mode:
changed = True
else:
### create PV
pvcreate_cmd = module.get_bin_path('pvcreate', True)
for current_dev in dev_list:
rc,_,err = module.run_command("%s %s" % (pvcreate_cmd,current_dev))
if rc == 0:
changed = True
else:
module.fail_json(msg="Creating physical volume '%s' failed" % current_dev, rc=rc, err=err)
vgcreate_cmd = module.get_bin_path('vgcreate')
rc,_,err = module.run_command([vgcreate_cmd] + vgoptions + ['-s', str(pesize), vg, dev_string])
if rc == 0:
changed = True
else:
module.fail_json(msg="Creating volume group '%s' failed"%vg, rc=rc, err=err)
else:
if state == 'absent':
if module.check_mode:
module.exit_json(changed=True)
else:
if this_vg['lv_count'] == 0 or force:
### remove VG
vgremove_cmd = module.get_bin_path('vgremove', True)
rc,_,err = module.run_command("%s --force %s" % (vgremove_cmd, vg))
if rc == 0:
module.exit_json(changed=True)
else:
module.fail_json(msg="Failed to remove volume group %s"%(vg),rc=rc, err=err)
else:
module.fail_json(msg="Refuse to remove non-empty volume group %s without force=yes"%(vg))
### resize VG
current_devs = [ pv['name'] for pv in pvs if pv['vg_name'] == vg ]
devs_to_remove = list(set(current_devs) - set(dev_list))
devs_to_add = list(set(dev_list) - set(current_devs))
if devs_to_add or devs_to_remove:
if module.check_mode:
changed = True
else:
if devs_to_add:
devs_to_add_string = ' '.join(devs_to_add)
### create PV
pvcreate_cmd = module.get_bin_path('pvcreate', True)
for current_dev in devs_to_add:
rc,_,err = module.run_command("%s %s" % (pvcreate_cmd, current_dev))
if rc == 0:
changed = True
else:
module.fail_json(msg="Creating physical volume '%s' failed"%current_dev, rc=rc, err=err)
### add PV to our VG
vgextend_cmd = module.get_bin_path('vgextend', True)
rc,_,err = module.run_command("%s %s %s" % (vgextend_cmd, vg, devs_to_add_string))
if rc == 0:
changed = True
else:
module.fail_json(msg="Unable to extend %s by %s."%(vg, devs_to_add_string),rc=rc,err=err)
### remove some PV from our VG
if devs_to_remove:
devs_to_remove_string = ' '.join(devs_to_remove)
vgreduce_cmd = module.get_bin_path('vgreduce', True)
rc,_,err = module.run_command("%s --force %s %s" % (vgreduce_cmd, vg, devs_to_remove_string))
if rc == 0:
changed = True
else:
module.fail_json(msg="Unable to reduce %s by %s."%(vg, devs_to_remove_string),rc=rc,err=err)
module.exit_json(changed=changed)
# import module snippets
from ansible.module_utils.basic import *
main()

View File

@@ -0,0 +1,235 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2013, Jeroen Hoekx <jeroen.hoekx@dsquare.be>, Alexander Bulimov <lazywolf0@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
---
author: Jeroen Hoekx
module: lvol
short_description: Configure LVM logical volumes
description:
- This module creates, removes or resizes logical volumes.
version_added: "1.1"
options:
vg:
description:
- The volume group this logical volume is part of.
required: true
lv:
description:
- The name of the logical volume.
required: true
size:
description:
- The size of the logical volume, according to lvcreate(8) --size, by
default in megabytes or optionally with one of [bBsSkKmMgGtTpPeE] units; or
according to lvcreate(8) --extents as a percentage of [VG|PVS|FREE];
resizing is not supported with percentages.
state:
choices: [ "present", "absent" ]
default: present
description:
- Control if the logical volume exists.
required: false
force:
version_added: "1.5"
choices: [ "yes", "no" ]
default: "no"
description:
- Shrink or remove operations of volumes requires this switch. Ensures that
that filesystems get never corrupted/destroyed by mistake.
required: false
notes:
- Filesystems on top of the volume are not resized.
'''
EXAMPLES = '''
# Create a logical volume of 512m.
- lvol: vg=firefly lv=test size=512
# Create a logical volume of 512g.
- lvol: vg=firefly lv=test size=512g
# Create a logical volume the size of all remaining space in the volume group
- lvol: vg=firefly lv=test size=100%FREE
# Extend the logical volume to 1024m.
- lvol: vg=firefly lv=test size=1024
# Reduce the logical volume to 512m
- lvol: vg=firefly lv=test size=512 force=yes
# Remove the logical volume.
- lvol: vg=firefly lv=test state=absent force=yes
'''
import re
decimal_point = re.compile(r"(\.|,)")
def parse_lvs(data):
lvs = []
for line in data.splitlines():
parts = line.strip().split(';')
lvs.append({
'name': parts[0],
'size': int(decimal_point.split(parts[1])[0]),
})
return lvs
def main():
module = AnsibleModule(
argument_spec=dict(
vg=dict(required=True),
lv=dict(required=True),
size=dict(),
state=dict(choices=["absent", "present"], default='present'),
force=dict(type='bool', default='no'),
),
supports_check_mode=True,
)
vg = module.params['vg']
lv = module.params['lv']
size = module.params['size']
state = module.params['state']
force = module.boolean(module.params['force'])
size_opt = 'L'
size_unit = 'm'
if size:
# LVCREATE(8) -l --extents option with percentage
if '%' in size:
size_parts = size.split('%', 1)
size_percent = int(size_parts[0])
if size_percent > 100:
module.fail_json(msg="Size percentage cannot be larger than 100%")
size_whole = size_parts[1]
if size_whole == 'ORIGIN':
module.fail_json(msg="Snapshot Volumes are not supported")
elif size_whole not in ['VG', 'PVS', 'FREE']:
module.fail_json(msg="Specify extents as a percentage of VG|PVS|FREE")
size_opt = 'l'
size_unit = ''
# LVCREATE(8) -L --size option unit
elif size[-1].isalpha():
if size[-1] in 'bBsSkKmMgGtTpPeE':
size_unit = size[-1]
if size[0:-1].isdigit():
size = int(size[0:-1])
else:
module.fail_json(msg="Bad size specification for unit %s" % size_unit)
size_opt = 'L'
else:
module.fail_json(msg="Size unit should be one of [bBsSkKmMgGtTpPeE]")
# when no unit, megabytes by default
elif size.isdigit():
size = int(size)
else:
module.fail_json(msg="Bad size specification")
if size_opt == 'l':
unit = 'm'
else:
unit = size_unit
rc, current_lvs, err = module.run_command(
"lvs --noheadings -o lv_name,size --units %s --separator ';' %s" % (unit, vg))
if rc != 0:
if state == 'absent':
module.exit_json(changed=False, stdout="Volume group %s does not exist." % vg, stderr=False)
else:
module.fail_json(msg="Volume group %s does not exist." % vg, rc=rc, err=err)
changed = False
lvs = parse_lvs(current_lvs)
for test_lv in lvs:
if test_lv['name'] == lv:
this_lv = test_lv
break
else:
this_lv = None
if state == 'present' and not size:
if this_lv is None:
module.fail_json(msg="No size given.")
else:
module.exit_json(changed=False, vg=vg, lv=this_lv['name'], size=this_lv['size'])
msg = ''
if this_lv is None:
if state == 'present':
### create LV
if module.check_mode:
changed = True
else:
rc, _, err = module.run_command("lvcreate -n %s -%s %s%s %s" % (lv, size_opt, size, size_unit, vg))
if rc == 0:
changed = True
else:
module.fail_json(msg="Creating logical volume '%s' failed" % lv, rc=rc, err=err)
else:
if state == 'absent':
### remove LV
if module.check_mode:
module.exit_json(changed=True)
if not force:
module.fail_json(msg="Sorry, no removal of logical volume %s without force=yes." % (this_lv['name']))
rc, _, err = module.run_command("lvremove --force %s/%s" % (vg, this_lv['name']))
if rc == 0:
module.exit_json(changed=True)
else:
module.fail_json(msg="Failed to remove logical volume %s" % (lv), rc=rc, err=err)
elif size_opt == 'l':
module.exit_json(changed=False, msg="Resizing extents with percentage not supported.")
else:
### resize LV
tool = None
if size > this_lv['size']:
tool = 'lvextend'
elif size < this_lv['size']:
if not force:
module.fail_json(msg="Sorry, no shrinking of %s without force=yes." % (this_lv['name']))
tool = 'lvreduce --force'
if tool:
if module.check_mode:
changed = True
else:
rc, _, err = module.run_command("%s -%s %s%s %s/%s" % (tool, size_opt, size, size_unit, vg, this_lv['name']))
if rc == 0:
changed = True
elif "matches existing size" in err:
module.exit_json(changed=False, vg=vg, lv=this_lv['name'], size=this_lv['size'])
else:
module.fail_json(msg="Unable to resize %s to %s%s" % (lv, size, size_unit), rc=rc, err=err)
module.exit_json(changed=changed, msg=msg)
# import module snippets
from ansible.module_utils.basic import *
main()

View File

@@ -0,0 +1,115 @@
#!/usr/bin/python
#coding: utf-8 -*-
# (c) 2013, David Stygstra <david.stygstra@gmail.com>
#
# This file is part of Ansible
#
# This module 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.
#
# This software 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 this software. If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
---
module: modprobe
short_description: Add or remove kernel modules
requirements: []
version_added: 1.4
author: David Stygstra, Julien Dauphant, Matt Jeffery
description:
- Add or remove kernel modules.
options:
name:
required: true
description:
- Name of kernel module to manage.
state:
required: false
default: "present"
choices: [ present, absent ]
description:
- Whether the module should be present or absent.
params:
required: false
default: ""
version_added: "1.6"
description:
- Modules parameters.
'''
EXAMPLES = '''
# Add the 802.1q module
- modprobe: name=8021q state=present
# Add the dummy module
- modprobe: name=dummy state=present params="numdummies=2"
'''
def main():
module = AnsibleModule(
argument_spec={
'name': {'required': True},
'state': {'default': 'present', 'choices': ['present', 'absent']},
'params': {'default': ''},
},
supports_check_mode=True,
)
args = {
'changed': False,
'failed': False,
'name': module.params['name'],
'state': module.params['state'],
'params': module.params['params'],
}
# Check if module is present
try:
modules = open('/proc/modules')
present = False
module_name = args['name'].replace('-', '_') + ' '
for line in modules:
if line.startswith(module_name):
present = True
break
modules.close()
except IOError, e:
module.fail_json(msg=str(e), **args)
# Check only; don't modify
if module.check_mode:
if args['state'] == 'present' and not present:
changed = True
elif args['state'] == 'absent' and present:
changed = True
else:
changed = False
module.exit_json(changed=changed)
# Add/remove module as needed
if args['state'] == 'present':
if not present:
rc, _, err = module.run_command(['modprobe', args['name'], args['params']])
if rc != 0:
module.fail_json(msg=err, **args)
args['changed'] = True
elif args['state'] == 'absent':
if present:
rc, _, err = module.run_command(['rmmod', args['name']])
if rc != 0:
module.fail_json(msg=err, **args)
args['changed'] = True
module.exit_json(**args)
# import module snippets
from ansible.module_utils.basic import *
main()

View File

@@ -0,0 +1,56 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
DOCUMENTATION = '''
---
module: ohai
short_description: Returns inventory data from I(Ohai)
description:
- Similar to the M(facter) module, this runs the I(Ohai) discovery program
(U(http://wiki.opscode.com/display/chef/Ohai)) on the remote host and
returns JSON inventory data.
I(Ohai) data is a bit more verbose and nested than I(facter).
version_added: "0.6"
options: {}
notes: []
requirements: [ "ohai" ]
author: Michael DeHaan
'''
EXAMPLES = '''
# Retrieve (ohai) data from all Web servers and store in one-file per host
ansible webservers -m ohai --tree=/tmp/ohaidata
'''
def main():
module = AnsibleModule(
argument_spec = dict()
)
cmd = ["/usr/bin/env", "ohai"]
rc, out, err = module.run_command(cmd, check_rc=True)
module.exit_json(**json.loads(out))
# import module snippets
from ansible.module_utils.basic import *
main()

View File

@@ -0,0 +1,379 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2013, Serge van Ginderachter <serge@vanginderachter.be>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
---
module: open_iscsi
author: Serge van Ginderachter
version_added: "1.4"
short_description: Manage iscsi targets with open-iscsi
description:
- Discover targets on given portal, (dis)connect targets, mark targets to
manually or auto start, return device nodes of connected targets.
requirements:
- open_iscsi library and tools (iscsiadm)
options:
portal:
required: false
aliases: [ip]
description:
- the ip address of the iscsi target
port:
required: false
default: 3260
description:
- the port on which the iscsi target process listens
target:
required: false
aliases: [name, targetname]
description:
- the iscsi target name
login:
required: false
choices: [true, false]
description:
- whether the target node should be connected
node_auth:
required: false
default: CHAP
description:
- discovery.sendtargets.auth.authmethod
node_user:
required: false
description:
- discovery.sendtargets.auth.username
node_pass:
required: false
description:
- discovery.sendtargets.auth.password
auto_node_startup:
aliases: [automatic]
required: false
choices: [true, false]
description:
- whether the target node should be automatically connected at startup
discover:
required: false
choices: [true, false]
description:
- whether the list of target nodes on the portal should be
(re)discovered and added to the persistent iscsi database.
Keep in mind that iscsiadm discovery resets configurtion, like node.startup
to manual, hence combined with auto_node_startup=yes will allways return
a changed state.
show_nodes:
required: false
choices: [true, false]
description:
- whether the list of nodes in the persistent iscsi database should be
returned by the module
examples:
- description: perform a discovery on 10.1.2.3 and show available target
nodes
code: >
open_iscsi: show_nodes=yes discover=yes portal=10.1.2.3
- description: discover targets on portal and login to the one available
(only works if exactly one target is exported to the initiator)
code: >
open_iscsi: portal={{iscsi_target}} login=yes discover=yes
- description: connect to the named target, after updating the local
persistent database (cache)
code: >
open_iscsi: login=yes target=iqn.1986-03.com.sun:02:f8c1f9e0-c3ec-ec84-c9c9-8bfb0cd5de3d
- description: discconnect from the cached named target
code: >
open_iscsi: login=no target=iqn.1986-03.com.sun:02:f8c1f9e0-c3ec-ec84-c9c9-8bfb0cd5de3d"
'''
import glob
import time
ISCSIADM = 'iscsiadm'
def compare_nodelists(l1, l2):
l1.sort()
l2.sort()
return l1 == l2
def iscsi_get_cached_nodes(module, portal=None):
cmd = '%s --mode node' % iscsiadm_cmd
(rc, out, err) = module.run_command(cmd)
if rc == 0:
lines = out.splitlines()
nodes = []
for line in lines:
# line format is "ip:port,target_portal_group_tag targetname"
parts = line.split()
if len(parts) > 2:
module.fail_json(msg='error parsing output', cmd=cmd)
target = parts[1]
parts = parts[0].split(':')
target_portal = parts[0]
if portal is None or portal == target_portal:
nodes.append(target)
# older versions of scsiadm don't have nice return codes
# for newer versions see iscsiadm(8); also usr/iscsiadm.c for details
# err can contain [N|n]o records...
elif rc == 21 or (rc == 255 and "o records found" in err):
nodes = []
else:
module.fail_json(cmd=cmd, rc=rc, msg=err)
return nodes
def iscsi_discover(module, portal, port):
cmd = '%s --mode discovery --type sendtargets --portal %s:%s' % (iscsiadm_cmd, portal, port)
(rc, out, err) = module.run_command(cmd)
if rc > 0:
module.fail_json(cmd=cmd, rc=rc, msg=err)
def target_loggedon(module, target):
cmd = '%s --mode session' % iscsiadm_cmd
(rc, out, err) = module.run_command(cmd)
if rc == 0:
return target in out
elif rc == 21:
return False
else:
module.fail_json(cmd=cmd, rc=rc, msg=err)
def target_login(module, target):
node_auth = module.params['node_auth']
node_user = module.params['node_user']
node_pass = module.params['node_pass']
if node_user:
params = [('node.session.auth.authmethod', node_auth),
('node.session.auth.username', node_user),
('node.session.auth.password', node_pass)]
for (name, value) in params:
cmd = '%s --mode node --targetname %s --op=update --name %s --value %s' % (iscsiadm_cmd, target, name, value)
(rc, out, err) = module.run_command(cmd)
if rc > 0:
module.fail_json(cmd=cmd, rc=rc, msg=err)
cmd = '%s --mode node --targetname %s --login' % (iscsiadm_cmd, target)
(rc, out, err) = module.run_command(cmd)
if rc > 0:
module.fail_json(cmd=cmd, rc=rc, msg=err)
def target_logout(module, target):
cmd = '%s --mode node --targetname %s --logout' % (iscsiadm_cmd, target)
(rc, out, err) = module.run_command(cmd)
if rc > 0:
module.fail_json(cmd=cmd, rc=rc, msg=err)
def target_device_node(module, target):
# if anyone know a better way to find out which devicenodes get created for
# a given target...
devices = glob.glob('/dev/disk/by-path/*%s*' % target)
if len(devices) == 0:
return None
else:
devdisks = []
for dev in devices:
# exclude partitions
if "-part" not in dev:
devdisk = os.path.realpath(dev)
# only add once (multi-path?)
if devdisk not in devdisks:
devdisks.append(devdisk)
return devdisks
def target_isauto(module, target):
cmd = '%s --mode node --targetname %s' % (iscsiadm_cmd, target)
(rc, out, err) = module.run_command(cmd)
if rc == 0:
lines = out.splitlines()
for line in lines:
if 'node.startup' in line:
return 'automatic' in line
return False
else:
module.fail_json(cmd=cmd, rc=rc, msg=err)
def target_setauto(module, target):
cmd = '%s --mode node --targetname %s --op=update --name node.startup --value automatic' % (iscsiadm_cmd, target)
(rc, out, err) = module.run_command(cmd)
if rc > 0:
module.fail_json(cmd=cmd, rc=rc, msg=err)
def target_setmanual(module, target):
cmd = '%s --mode node --targetname %s --op=update --name node.startup --value manual' % (iscsiadm_cmd, target)
(rc, out, err) = module.run_command(cmd)
if rc > 0:
module.fail_json(cmd=cmd, rc=rc, msg=err)
def main():
# load ansible module object
module = AnsibleModule(
argument_spec = dict(
# target
portal = dict(required=False, aliases=['ip']),
port = dict(required=False, default=3260),
target = dict(required=False, aliases=['name', 'targetname']),
node_auth = dict(required=False, default='CHAP'),
node_user = dict(required=False),
node_pass = dict(required=False),
# actions
login = dict(type='bool', aliases=['state']),
auto_node_startup = dict(type='bool', aliases=['automatic']),
discover = dict(type='bool', default=False),
show_nodes = dict(type='bool', default=False)
),
required_together=[['discover_user', 'discover_pass'],
['node_user', 'node_pass']],
supports_check_mode=True
)
global iscsiadm_cmd
iscsiadm_cmd = module.get_bin_path('iscsiadm', required=True)
# parameters
portal = module.params['portal']
target = module.params['target']
port = module.params['port']
login = module.params['login']
automatic = module.params['auto_node_startup']
discover = module.params['discover']
show_nodes = module.params['show_nodes']
check = module.check_mode
cached = iscsi_get_cached_nodes(module, portal)
# return json dict
result = {}
result['changed'] = False
if discover:
if portal is None:
module.fail_json(msg = "Need to specify at least the portal (ip) to discover")
elif check:
nodes = cached
else:
iscsi_discover(module, portal, port)
nodes = iscsi_get_cached_nodes(module, portal)
if not compare_nodelists(cached, nodes):
result['changed'] |= True
result['cache_updated'] = True
else:
nodes = cached
if login is not None or automatic is not None:
if target is None:
if len(nodes) > 1:
module.fail_json(msg = "Need to specify a target")
else:
target = nodes[0]
else:
# check given target is in cache
check_target = False
for node in nodes:
if node == target:
check_target = True
break
if not check_target:
module.fail_json(msg = "Specified target not found")
if show_nodes:
result['nodes'] = nodes
if login is not None:
loggedon = target_loggedon(module,target)
if (login and loggedon) or (not login and not loggedon):
result['changed'] |= False
if login:
result['devicenodes'] = target_device_node(module,target)
elif not check:
if login:
target_login(module, target)
# give udev some time
time.sleep(1)
result['devicenodes'] = target_device_node(module,target)
else:
target_logout(module, target)
result['changed'] |= True
result['connection_changed'] = True
else:
result['changed'] |= True
result['connection_changed'] = True
if automatic is not None:
isauto = target_isauto(module, target)
if (automatic and isauto) or (not automatic and not isauto):
result['changed'] |= False
result['automatic_changed'] = False
elif not check:
if automatic:
target_setauto(module, target)
else:
target_setmanual(module, target)
result['changed'] |= True
result['automatic_changed'] = True
else:
result['changed'] |= True
result['automatic_changed'] = True
module.exit_json(**result)
# import module snippets
from ansible.module_utils.basic import *
main()

View File

@@ -0,0 +1,269 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2014, Ahti Kitsik <ak@ahtik.com>
# (c) 2014, Jarno Keskikangas <jarno.keskikangas@gmail.com>
# (c) 2013, Aleksey Ovcharenko <aleksey.ovcharenko@gmail.com>
# (c) 2013, James Martin <jmartin@basho.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
---
module: ufw
short_description: Manage firewall with UFW
description:
- Manage firewall with UFW.
version_added: 1.6
author: Aleksey Ovcharenko, Jarno Keskikangas, Ahti Kitsik
notes:
- See C(man ufw) for more examples.
requirements:
- C(ufw) package
options:
state:
description:
- C(enabled) reloads firewall and enables firewall on boot.
- C(disabled) unloads firewall and disables firewall on boot.
- C(reloaded) reloads firewall.
- C(reset) disables and resets firewall to installation defaults.
required: false
choices: ['enabled', 'disabled', 'reloaded', 'reset']
policy:
description:
- Change the default policy for incoming or outgoing traffic.
required: false
alias: default
choices: ['allow', 'deny', 'reject']
direction:
description:
- Select direction for a rule or default policy command.
required: false
choices: ['in', 'out', 'incoming', 'outgoing']
logging:
description:
- Toggles logging. Logged packets use the LOG_KERN syslog facility.
choices: ['on', 'off', 'low', 'medium', 'high', 'full']
required: false
insert:
description:
- Insert the corresponding rule as rule number NUM
required: false
rule:
description:
- Add firewall rule
required: false
choices: ['allow', 'deny', 'reject', 'limit']
log:
description:
- Log new connections matched to this rule
required: false
choices: ['yes', 'no']
from_ip:
description:
- Source IP address.
required: false
aliases: ['from', 'src']
default: 'any'
from_port:
description:
- Source port.
required: false
to_ip:
description:
- Destination IP address.
required: false
aliases: ['to', 'dest']
default: 'any'
to_port:
description:
- Destination port.
required: false
aliases: ['port']
proto:
description:
- TCP/IP protocol.
choices: ['any', 'tcp', 'udp', 'ipv6', 'esp', 'ah']
required: false
name:
description:
- Use profile located in C(/etc/ufw/applications.d)
required: false
aliases: ['app']
delete:
description:
- Delete rule.
required: false
choices: ['yes', 'no']
interface:
description:
- Specify interface for rule.
required: false
aliases: ['if']
'''
EXAMPLES = '''
# Allow everything and enable UFW
ufw: state=enabled policy=allow
# Set logging
ufw: logging=on
# Sometimes it is desirable to let the sender know when traffic is
# being denied, rather than simply ignoring it. In these cases, use
# reject instead of deny. In addition, log rejected connections:
ufw: rule=reject port=auth log=yes
# ufw supports connection rate limiting, which is useful for protecting
# against brute-force login attacks. ufw will deny connections if an IP
# address has attempted to initiate 6 or more connections in the last
# 30 seconds. See http://www.debian-administration.org/articles/187
# for details. Typical usage is:
ufw: rule=limit port=ssh proto=tcp
# Allow OpenSSH
ufw: rule=allow name=OpenSSH
# Delete OpenSSH rule
ufw: rule=allow name=OpenSSH delete=yes
# Deny all access to port 53:
ufw: rule=deny port=53
# Allow all access to tcp port 80:
ufw: rule=allow port=80 proto=tcp
# Allow all access from RFC1918 networks to this host:
ufw: rule=allow src={{ item }}
with_items:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
# Deny access to udp port 514 from host 1.2.3.4:
ufw: rule=deny proto=udp src=1.2.3.4 port=514
# Allow incoming access to eth0 from 1.2.3.5 port 5469 to 1.2.3.4 port 5469
ufw: rule=allow interface=eth0 direction=in proto=udp src=1.2.3.5 from_port=5469 dest=1.2.3.4 to_port=5469
# Deny all traffic from the IPv6 2001:db8::/32 to tcp port 25 on this host.
# Note that IPv6 must be enabled in /etc/default/ufw for IPv6 firewalling to work.
ufw: rule=deny proto=tcp src=2001:db8::/32 port=25
'''
from operator import itemgetter
def main():
module = AnsibleModule(
argument_spec = dict(
state = dict(default=None, choices=['enabled', 'disabled', 'reloaded', 'reset']),
default = dict(default=None, aliases=['policy'], choices=['allow', 'deny', 'reject']),
logging = dict(default=None, choices=['on', 'off', 'low', 'medium', 'high', 'full']),
direction = dict(default=None, choices=['in', 'incoming', 'out', 'outgoing']),
delete = dict(default=False, type='bool'),
insert = dict(default=None),
rule = dict(default=None, choices=['allow', 'deny', 'reject', 'limit']),
interface = dict(default=None, aliases=['if']),
log = dict(default=False, type='bool'),
from_ip = dict(default='any', aliases=['src', 'from']),
from_port = dict(default=None),
to_ip = dict(default='any', aliases=['dest', 'to']),
to_port = dict(default=None, aliases=['port']),
proto = dict(default=None, aliases=['protocol'], choices=['any', 'tcp', 'udp', 'ipv6', 'esp', 'ah']),
app = dict(default=None, aliases=['name'])
),
supports_check_mode = True,
mutually_exclusive = [['app', 'proto', 'logging']]
)
cmds = []
def execute(cmd):
cmd = ' '.join(map(itemgetter(-1), filter(itemgetter(0), cmd)))
cmds.append(cmd)
(rc, out, err) = module.run_command(cmd)
if rc != 0:
module.fail_json(msg=err or out)
params = module.params
# Ensure at least one of the command arguments are given
command_keys = ['state', 'default', 'rule', 'logging']
commands = dict((key, params[key]) for key in command_keys if params[key])
if len(commands) < 1:
module.fail_json(msg="Not any of the command arguments %s given" % commands)
if('interface' in params and 'direction' not in params):
module.fail_json(msg="Direction must be specified when creating a rule on an interface")
# Ensure ufw is available
ufw_bin = module.get_bin_path('ufw', True)
# Save the pre state and rules in order to recognize changes
(_, pre_state, _) = module.run_command(ufw_bin + ' status verbose')
(_, pre_rules, _) = module.run_command("grep '^### tuple' /lib/ufw/user*.rules")
# Execute commands
for (command, value) in commands.iteritems():
cmd = [[ufw_bin], [module.check_mode, '--dry-run']]
if command == 'state':
states = { 'enabled': 'enable', 'disabled': 'disable',
'reloaded': 'reload', 'reset': 'reset' }
execute(cmd + [['-f'], [states[value]]])
elif command == 'logging':
execute(cmd + [[command], [value]])
elif command == 'default':
execute(cmd + [[command], [value], [params['direction']]])
elif command == 'rule':
# Rules are constructed according to the long format
#
# ufw [--dry-run] [delete] [insert NUM] allow|deny|reject|limit [in|out on INTERFACE] [log|log-all] \
# [from ADDRESS [port PORT]] [to ADDRESS [port PORT]] \
# [proto protocol] [app application]
cmd.append([module.boolean(params['delete']), 'delete'])
cmd.append([params['insert'], "insert %s" % params['insert']])
cmd.append([value])
cmd.append([module.boolean(params['log']), 'log'])
for (key, template) in [('direction', "%s" ), ('interface', "on %s" ),
('from_ip', "from %s" ), ('from_port', "port %s" ),
('to_ip', "to %s" ), ('to_port', "port %s" ),
('proto', "proto %s"), ('app', "app '%s'")]:
value = params[key]
cmd.append([value, template % (value)])
execute(cmd)
# Get the new state
(_, post_state, _) = module.run_command(ufw_bin + ' status verbose')
(_, post_rules, _) = module.run_command("grep '^### tuple' /lib/ufw/user*.rules")
changed = (pre_state != post_state) or (pre_rules != post_rules)
return module.exit_json(changed=changed, commands=cmds, msg=post_state.rstrip())
# import module snippets
from ansible.module_utils.basic import *
main()

View File

@@ -0,0 +1,417 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2013, Johan Wiren <johan.wiren.se@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
DOCUMENTATION = '''
---
module: zfs
short_description: Manage zfs
description:
- Manages ZFS file systems on Solaris and FreeBSD. Can manage file systems, volumes and snapshots. See zfs(1M) for more information about the properties.
version_added: "1.1"
options:
name:
description:
- File system, snapshot or volume name e.g. C(rpool/myfs)
required: true
state:
description:
- Whether to create (C(present)), or remove (C(absent)) a file system, snapshot or volume.
required: true
choices: [present, absent]
aclinherit:
description:
- The aclinherit property.
required: False
choices: [discard,noallow,restricted,passthrough,passthrough-x]
aclmode:
description:
- The aclmode property.
required: False
choices: [discard,groupmask,passthrough]
atime:
description:
- The atime property.
required: False
choices: ['on','off']
canmount:
description:
- The canmount property.
required: False
choices: ['on','off','noauto']
casesensitivity:
description:
- The casesensitivity property.
required: False
choices: [sensitive,insensitive,mixed]
checksum:
description:
- The checksum property.
required: False
choices: ['on','off',fletcher2,fletcher4,sha256]
compression:
description:
- The compression property.
required: False
choices: ['on','off',lzjb,gzip,gzip-1,gzip-2,gzip-3,gzip-4,gzip-5,gzip-6,gzip-7,gzip-8,gzip-9,lz4,zle]
copies:
description:
- The copies property.
required: False
choices: [1,2,3]
dedup:
description:
- The dedup property.
required: False
choices: ['on','off']
devices:
description:
- The devices property.
required: False
choices: ['on','off']
exec:
description:
- The exec property.
required: False
choices: ['on','off']
jailed:
description:
- The jailed property.
required: False
choices: ['on','off']
logbias:
description:
- The logbias property.
required: False
choices: [latency,throughput]
mountpoint:
description:
- The mountpoint property.
required: False
nbmand:
description:
- The nbmand property.
required: False
choices: ['on','off']
normalization:
description:
- The normalization property.
required: False
choices: [none,formC,formD,formKC,formKD]
primarycache:
description:
- The primarycache property.
required: False
choices: [all,none,metadata]
quota:
description:
- The quota property.
required: False
readonly:
description:
- The readonly property.
required: False
choices: ['on','off']
recordsize:
description:
- The recordsize property.
required: False
refquota:
description:
- The refquota property.
required: False
refreservation:
description:
- The refreservation property.
required: False
reservation:
description:
- The reservation property.
required: False
secondarycache:
description:
- The secondarycache property.
required: False
choices: [all,none,metadata]
setuid:
description:
- The setuid property.
required: False
choices: ['on','off']
shareiscsi:
description:
- The shareiscsi property.
required: False
choices: ['on','off']
sharenfs:
description:
- The sharenfs property.
required: False
sharesmb:
description:
- The sharesmb property.
required: False
snapdir:
description:
- The snapdir property.
required: False
choices: [hidden,visible]
sync:
description:
- The sync property.
required: False
choices: ['on','off']
utf8only:
description:
- The utf8only property.
required: False
choices: ['on','off']
volsize:
description:
- The volsize property.
required: False
volblocksize:
description:
- The volblocksize property.
required: False
vscan:
description:
- The vscan property.
required: False
choices: ['on','off']
xattr:
description:
- The xattr property.
required: False
choices: ['on','off']
zoned:
description:
- The zoned property.
required: False
choices: ['on','off']
author: Johan Wiren
'''
EXAMPLES = '''
# Create a new file system called myfs in pool rpool
- zfs: name=rpool/myfs state=present
# Create a new volume called myvol in pool rpool.
- zfs: name=rpool/myvol state=present volsize=10M
# Create a snapshot of rpool/myfs file system.
- zfs: name=rpool/myfs@mysnapshot state=present
# Create a new file system called myfs2 with snapdir enabled
- zfs: name=rpool/myfs2 state=present snapdir=enabled
'''
import os
class Zfs(object):
def __init__(self, module, name, properties):
self.module = module
self.name = name
self.properties = properties
self.changed = False
self.immutable_properties = [ 'casesensitivity', 'normalization', 'utf8only' ]
def exists(self):
cmd = [self.module.get_bin_path('zfs', True)]
cmd.append('list')
cmd.append('-t all')
cmd.append(self.name)
(rc, out, err) = self.module.run_command(' '.join(cmd))
if rc == 0:
return True
else:
return False
def create(self):
if self.module.check_mode:
self.changed = True
return
properties=self.properties
volsize = properties.pop('volsize', None)
volblocksize = properties.pop('volblocksize', None)
if "@" in self.name:
action = 'snapshot'
else:
action = 'create'
cmd = [self.module.get_bin_path('zfs', True)]
cmd.append(action)
if volblocksize:
cmd.append('-b %s' % volblocksize)
if properties:
for prop, value in properties.iteritems():
cmd.append('-o %s="%s"' % (prop, value))
if volsize:
cmd.append('-V')
cmd.append(volsize)
cmd.append(self.name)
(rc, err, out) = self.module.run_command(' '.join(cmd))
if rc == 0:
self.changed=True
else:
self.module.fail_json(msg=out)
def destroy(self):
if self.module.check_mode:
self.changed = True
return
cmd = [self.module.get_bin_path('zfs', True)]
cmd.append('destroy')
cmd.append(self.name)
(rc, err, out) = self.module.run_command(' '.join(cmd))
if rc == 0:
self.changed = True
else:
self.module.fail_json(msg=out)
def set_property(self, prop, value):
if self.module.check_mode:
self.changed = True
return
cmd = self.module.get_bin_path('zfs', True)
args = [cmd, 'set', prop + '=' + value, self.name]
(rc, err, out) = self.module.run_command(args)
if rc == 0:
self.changed = True
else:
self.module.fail_json(msg=out)
def set_properties_if_changed(self):
current_properties = self.get_current_properties()
for prop, value in self.properties.iteritems():
if current_properties[prop] != value:
if prop in self.immutable_properties:
self.module.fail_json(msg='Cannot change property %s after creation.' % prop)
else:
self.set_property(prop, value)
def get_current_properties(self):
def get_properties_by_name(propname):
cmd = [self.module.get_bin_path('zfs', True)]
cmd += ['get', '-H', propname, self.name]
rc, out, err = self.module.run_command(cmd)
return [l.split('\t')[1:3] for l in out.splitlines()]
properties = dict(get_properties_by_name('all'))
if 'share.*' in properties:
# Some ZFS pools list the sharenfs and sharesmb properties
# hierarchically as share.nfs and share.smb respectively.
del properties['share.*']
for p, v in get_properties_by_name('share.all'):
alias = p.replace('.', '') # share.nfs -> sharenfs (etc)
properties[alias] = v
return properties
def run_command(self, cmd):
progname = cmd[0]
cmd[0] = module.get_bin_path(progname, True)
return module.run_command(cmd)
def main():
# FIXME: should use dict() constructor like other modules, required=False is default
module = AnsibleModule(
argument_spec = {
'name': {'required': True},
'state': {'required': True, 'choices':['present', 'absent']},
'aclinherit': {'required': False, 'choices':['discard', 'noallow', 'restricted', 'passthrough', 'passthrough-x']},
'aclmode': {'required': False, 'choices':['discard', 'groupmask', 'passthrough']},
'atime': {'required': False, 'choices':['on', 'off']},
'canmount': {'required': False, 'choices':['on', 'off', 'noauto']},
'casesensitivity': {'required': False, 'choices':['sensitive', 'insensitive', 'mixed']},
'checksum': {'required': False, 'choices':['on', 'off', 'fletcher2', 'fletcher4', 'sha256']},
'compression': {'required': False, 'choices':['on', 'off', 'lzjb', 'gzip', 'gzip-1', 'gzip-2', 'gzip-3', 'gzip-4', 'gzip-5', 'gzip-6', 'gzip-7', 'gzip-8', 'gzip-9', 'lz4', 'zle']},
'copies': {'required': False, 'choices':['1', '2', '3']},
'dedup': {'required': False, 'choices':['on', 'off']},
'devices': {'required': False, 'choices':['on', 'off']},
'exec': {'required': False, 'choices':['on', 'off']},
# Not supported
#'groupquota': {'required': False},
'jailed': {'required': False, 'choices':['on', 'off']},
'logbias': {'required': False, 'choices':['latency', 'throughput']},
'mountpoint': {'required': False},
'nbmand': {'required': False, 'choices':['on', 'off']},
'normalization': {'required': False, 'choices':['none', 'formC', 'formD', 'formKC', 'formKD']},
'primarycache': {'required': False, 'choices':['all', 'none', 'metadata']},
'quota': {'required': False},
'readonly': {'required': False, 'choices':['on', 'off']},
'recordsize': {'required': False},
'refquota': {'required': False},
'refreservation': {'required': False},
'reservation': {'required': False},
'secondarycache': {'required': False, 'choices':['all', 'none', 'metadata']},
'setuid': {'required': False, 'choices':['on', 'off']},
'shareiscsi': {'required': False, 'choices':['on', 'off']},
'sharenfs': {'required': False},
'sharesmb': {'required': False},
'snapdir': {'required': False, 'choices':['hidden', 'visible']},
'sync': {'required': False, 'choices':['on', 'off']},
# Not supported
#'userquota': {'required': False},
'utf8only': {'required': False, 'choices':['on', 'off']},
'volsize': {'required': False},
'volblocksize': {'required': False},
'vscan': {'required': False, 'choices':['on', 'off']},
'xattr': {'required': False, 'choices':['on', 'off']},
'zoned': {'required': False, 'choices':['on', 'off']},
},
supports_check_mode=True
)
state = module.params.pop('state')
name = module.params.pop('name')
# Get all valid zfs-properties
properties = dict()
for prop, value in module.params.iteritems():
if prop in ['CHECKMODE']:
continue
if value:
properties[prop] = value
result = {}
result['name'] = name
result['state'] = state
zfs=Zfs(module, name, properties)
if state == 'present':
if zfs.exists():
zfs.set_properties_if_changed()
else:
zfs.create()
elif state == 'absent':
if zfs.exists():
zfs.destroy()
result.update(zfs.properties)
result['changed'] = zfs.changed
module.exit_json(**result)
# import module snippets
from ansible.module_utils.basic import *
main()