Allow modules to be categorized, and also sort them when generating the documentation.

This commit is contained in:
Michael DeHaan
2013-04-28 15:03:45 -04:00
parent f46bdb6343
commit 391fb98ee2
87 changed files with 118 additions and 67 deletions

View File

@@ -0,0 +1,224 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
Ansible module to add authorized_keys for ssh logins.
(c) 2012, Brad Olson <brado@movedbylight.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: authorized_key
short_description: Adds or removes an SSH authorized key
description:
- Adds or removes an SSH authorized key for a user from a remote host.
version_added: "0.5"
options:
user:
description:
- Name of the user who should have access to the remote host
required: true
default: null
aliases: []
key:
description:
- the SSH public key, as a string
required: true
default: null
path:
description:
- Alternate path to the authorized_keys file
required: false
default: "(homedir)+/.ssh/authorized_keys"
version_added: "1.2"
manage_dir:
description:
- Whether this module should manage the directory of the authorized_keys file
required: false
choices: [ "yes", "no" ]
default: "yes"
version_added: "1.2"
state:
description:
- whether the given key should or should not be in the file
required: false
choices: [ "present", "absent" ]
default: "present"
description:
- "adds or removes authorized keys for particular user accounts"
author: Brad Olson
'''
EXAMPLES = '''
# Example using key data from a local file on the management machine
authorized_key: user=charlie key="{{ lookup('file', '/home/charlie/.ssh/id_rsa.pub') }}"
# Using alternate directory locations:
authorized_key: user=charlie key="{{ lookup('file', '/home/charlie/.ssh/id_rsa.pub') }}" sshdir='/etc/ssh/authorized_keys/charlie' manage_dir=no
'''
# Makes sure the public key line is present or absent in the user's .ssh/authorized_keys.
#
# Arguments
# =========
# user = username
# key = line to add to authorized_keys for user
# path = path to the user's authorized_keys file (default: ~/.ssh/authorized_keys)
# manage_dir = whether to create, and control ownership of the directory (default: true)
# state = absent|present (default: present)
#
# see example in examples/playbooks
import sys
import os
import pwd
import os.path
import tempfile
import shutil
def keyfile(module, user, write=False, path=None, manage_dir=True):
"""
Calculate name of authorized keys file, optionally creating the
directories and file, properly setting permissions.
:param str user: name of user in passwd file
:param bool write: if True, write changes to authorized_keys file (creating directories if needed)
:param str path: if not None, use provided path rather than default of '~user/.ssh/authorized_keys'
:param bool manage_dir: if True, create and set ownership of the parent dir of the authorized_keys file
:return: full path string to authorized_keys for user
"""
try:
user_entry = pwd.getpwnam(user)
except KeyError, e:
module.fail_json(msg="Failed to lookup user %s: %s" % (user, str(e)))
if path is None:
homedir = user_entry.pw_dir
sshdir = os.path.join(homedir, ".ssh")
keysfile = os.path.join(sshdir, "authorized_keys")
else:
sshdir = os.path.dirname(path)
keysfile = path
if not write:
return keysfile
uid = user_entry.pw_uid
gid = user_entry.pw_gid
if manage_dir in BOOLEANS_TRUE:
if not os.path.exists(sshdir):
os.mkdir(sshdir, 0700)
if module.selinux_enabled():
module.set_default_selinux_context(sshdir, False)
os.chown(sshdir, uid, gid)
os.chmod(sshdir, 0700)
if not os.path.exists(keysfile):
basedir = os.path.dirname(keysfile)
if not os.path.exists(basedir):
os.makedirs(basedir)
try:
f = open(keysfile, "w") #touches file so we can set ownership and perms
finally:
f.close()
if module.selinux_enabled():
module.set_default_selinux_context(keysfile, False)
os.chown(keysfile, uid, gid)
os.chmod(keysfile, 0600)
return keysfile
def readkeys(filename):
if not os.path.isfile(filename):
return []
f = open(filename)
keys = [line.rstrip() for line in f.readlines()]
f.close()
return keys
def writekeys(module, filename, keys):
fd, tmp_path = tempfile.mkstemp('', 'tmp', os.path.dirname(filename))
f = open(tmp_path,"w")
try:
f.writelines( (key + "\n" for key in keys) )
except IOError, e:
module.fail_json(msg="Failed to write to file %s: %s" % (tmp_path, str(e)))
f.close()
module.atomic_move(tmp_path, filename)
def enforce_state(module, params):
"""
Add or remove key.
"""
user = params["user"]
key = params["key"]
path = params.get("path", None)
manage_dir = params.get("manage_dir", True)
state = params.get("state", "present")
key = key.split('\n')
# check current state -- just get the filename, don't create file
write = False
params["keyfile"] = keyfile(module, user, write, path, manage_dir)
keys = readkeys(params["keyfile"])
# Check our new keys, if any of them exist we'll continue.
for new_key in key:
present = new_key in keys
# handle idempotent state=present
if state=="present":
if present:
continue
keys.append(new_key)
write = True
writekeys(module, keyfile(module, user, write, path, manage_dir), keys)
params['changed'] = True
elif state=="absent":
if not present:
continue
keys.remove(new_key)
write = True
writekeys(module, keyfile(module, user, write, path, manage_dir), keys)
params['changed'] = True
return params
def main():
module = AnsibleModule(
argument_spec = dict(
user = dict(required=True, type='str'),
key = dict(required=True, type='str'),
path = dict(required=False, type='str'),
manage_dir = dict(required=False, type='bool'),
state = dict(default='present', choices=['absent','present'])
)
)
results = enforce_state(module, module.params)
module.exit_json(**results)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

366
library/system/cron Normal file
View File

@@ -0,0 +1,366 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# (c) 2012, Dane Summers <dsummers@pinedesk.biz>
# (c) 2013, Mike Grozak <mike.grozak@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/>.
# Cron Plugin: The goal of this plugin is to provide an indempotent method for
# setting up cron jobs on a host. The script will play well with other manually
# entered crons. Each cron job entered will be preceded with a comment
# describing the job so that it can be found later, which is required to be
# present in order for this plugin to find/modify the job.
DOCUMENTATION = """
---
module: cron
short_description: Manage crontab entries.
description:
- Use this module to manage crontab entries. This module allows you to create named
crontab entries, update, or delete them.
- 'The module includes one line with the description of the crontab entry C("#Ansible: <name>")
corresponding to the "name" passed to the module, which is used by future ansible/module calls
to find/check the state.'
version_added: "0.9"
options:
name:
description:
- Description of a crontab entry.
required: true
default:
aliases: []
user:
description:
- The specific user who's crontab should be modified.
required: false
default: root
aliases: []
job:
description:
- The command to execute.
- Required if state=present.
required: false
default:
aliases: []
state:
description:
- Whether to ensure the job is present or absent.
required: false
default: present
aliases: []
cron_file:
description:
- If specified, uses this file in cron.d versus in the main crontab
required: false
default:
aliases: []
backup:
description:
- If set, then create a backup of the crontab before it is modified.
- The location of the backup is returned in the C(backup) variable by this module.
required: false
default: false
aliases: []
minute:
description:
- Minute when the job should run ( 0-59, *, */2, etc )
required: false
default: "*"
aliases: []
hour:
description:
- Hour when the job should run ( 0-23, *, */2, etc )
required: false
default: "*"
aliases: []
day:
description:
- Day of the month the job should run ( 1-31, *, */2, etc )
required: false
default: "*"
aliases: []
month:
description:
- Month of the year the job should run ( 1-12, *, */2, etc )
required: false
default: "*"
aliases: []
weekday:
description:
- Day of the week that the job should run ( 0-7 for Sunday - Saturday, or mon, tue, * etc )
required: false
default: "*"
aliases: []
reboot:
description:
- If the job should be run at reboot, will ignore minute, hour, day, and month settings in favour of C(@reboot)
version_added: "1.0"
required: false
default: "no"
choices: [ "yes", "no" ]
aliases: []
examples:
- code: 'cron: name="check dirs" hour="5,2" job="ls -alh > /dev/null"'
description: Ensure a job that runs at 2 and 5 exists. Creates an entry like "* 5,2 * * ls -alh > /dev/null"
- code: 'cron: name="an old job" cron job="/some/dir/job.sh" state=absent'
description: 'Ensure an old job is no longer present. Removes any job that is preceded by "#Ansible: an old job" in the crontab'
- code: 'cron: name="a job for reboot" reboot=yes job="/some/job.sh"'
description: 'Creates an entry like "@reboot /some/job.sh"'
- code: 'cron: name="yum autoupdate" weekday="2" minute=0 hour=12 user="root" job="YUMINTERACTIVE=0 /usr/sbin/yum-autoupdate" cron_file=ansible_yum-autoupdate'
requirements:
- cron
author: Dane Summers
updates: Mike Grozak
"""
import re
import tempfile
import os
def get_jobs_file(module, user, tmpfile, cron_file):
if cron_file:
cmd = "cp -fp /etc/cron.d/%s %s" % (cron_file, tmpfile)
else:
cmd = "crontab -l %s > %s" % (user,tmpfile)
return module.run_command(cmd)
def install_jobs(module, user, tmpfile, cron_file):
if cron_file:
cron_file = '/etc/cron.d/%s' % cron_file
module.atomic_move(tmpfile, cron_file)
else:
cmd = "crontab %s %s" % (user, tmpfile)
return module.run_command(cmd)
def get_jobs(tmpfile):
lines = open(tmpfile).read().splitlines()
comment = None
jobs = []
for l in lines:
if comment is not None:
jobs.append([comment,l])
comment = None
elif re.match( r'#Ansible: ',l):
comment = re.sub( r'#Ansible: ', '', l)
return jobs
def find_job(name,tmpfile):
jobs = get_jobs(tmpfile)
for j in jobs:
if j[0] == name:
return j
return []
def add_job(module,name,job,tmpfile):
f = open(tmpfile, 'a')
f.write("#Ansible: %s\n%s\n" % (name, job))
f.close()
def update_job(name,job,tmpfile):
return _update_job(name,job,tmpfile,do_add_job)
def do_add_job(lines, comment, job):
lines.append(comment)
lines.append(job)
def remove_job(name,tmpfile):
return _update_job(name, "", tmpfile, do_remove_job)
def do_remove_job(lines,comment,job):
return None
def remove_job_file(cron_file):
fname = "/etc/cron.d/%s" % (cron_file)
os.unlink(fname)
def _update_job(name,job,tmpfile,addlinesfunction):
ansiblename = "#Ansible: %s" % (name)
f = open(tmpfile)
lines = f.read().splitlines()
f.close()
newlines = []
comment = None
for l in lines:
if comment is not None:
addlinesfunction(newlines,comment,job)
comment = None
elif l == ansiblename:
comment = l
else:
newlines.append(l)
f = open(tmpfile, 'w')
for l in newlines:
f.write(l)
f.write('\n')
f.close()
if len(newlines) == 0:
return True
else:
return False # TODO add some more error testing
def get_cron_job(minute,hour,day,month,weekday,job,user,cron_file,reboot):
if reboot:
if cron_file:
return "@reboot %s %s" % (user, job)
else:
return "@reboot %s" % (job)
else:
if cron_file:
return "%s %s %s %s %s %s %s" % (minute,hour,day,month,weekday,user,job)
else:
return "%s %s %s %s %s %s" % (minute,hour,day,month,weekday,job)
return None
def main():
# The following example playbooks:
# - action: cron name="check dirs" hour="5,2" job="ls -alh > /dev/null"
# - name: do the job
# action: name="do the job" cron hour="5,2" job="/some/dir/job.sh"
# - name: no job
# action: name="an old job" cron job="/some/dir/job.sh" state=absent
#
# Would produce:
# # Ansible: check dirs
# * * 5,2 * * ls -alh > /dev/null
# # Ansible: do the job
# * * 5,2 * * /some/dir/job.sh
# Function:
# 1. dump the existing cron:
# crontab -l -u <user> > /tmp/tmpfile
# 2. search for comment "^# Ansible: <name>" followed by a cron.
# 3. if absent: remove if present (and say modified), otherwise return with no mod.
# 4. if present: if the same return no mod, if not present add (and say mod), if different add (and say mod)
# 5. Install new cron (if mod):
# crontab -u <user> /tmp/tmpfile
# 6. return mod
module = AnsibleModule(
argument_spec = dict(
name=dict(required=True),
user=dict(required=False),
job=dict(required=False),
cron_file=dict(required=False),
state=dict(default='present', choices=['present', 'absent']),
backup=dict(default=False, type='bool'),
minute=dict(default='*'),
hour=dict(default='*'),
day=dict(default='*'),
month=dict(default='*'),
weekday=dict(default='*'),
reboot=dict(required=False, default=False, type='bool')
)
)
backup = module.params['backup']
name = module.params['name']
user = module.params['user']
job = module.params['job']
cron_file = module.params['cron_file']
minute = module.params['minute']
hour = module.params['hour']
day = module.params['day']
month = module.params['month']
weekday = module.params['weekday']
reboot = module.params['reboot']
state = module.params['state']
do_install = module.params['state'] == 'present'
changed = False
if reboot and (True in [(x != '*') for x in [minute, hour, day, month, weekday]]):
module.fail_json(msg="You must specify either reboot=True or any of minute, hour, day, month, weekday")
if cron_file:
if not user:
module.fail_json(msg="To use file=... parameter you must specify user=... as well")
else:
if not user:
user = ""
else:
user = "-u %s" % (user)
job = get_cron_job(minute,hour,day,month,weekday,job,user,cron_file,reboot)
rc, out, err, rm, status = (0, None, None, None, None)
if job is None and do_install:
module.fail_json(msg="You must specify 'job' to install a new cron job")
tmpfile = tempfile.NamedTemporaryFile()
(rc, out, err) = get_jobs_file(module,user,tmpfile.name, cron_file)
if rc != 0 and rc != 1: # 1 can mean that there are no jobs.
module.fail_json(msg=err)
(handle,backupfile) = tempfile.mkstemp(prefix='crontab')
(rc, out, err) = get_jobs_file(module,user,backupfile, cron_file)
if rc != 0 and rc != 1:
module.fail_json(msg=err)
old_job = find_job(name,backupfile)
if do_install:
if len(old_job) == 0:
add_job(module,name,job,tmpfile.name)
changed = True
if len(old_job) > 0 and old_job[1] != job:
update_job(name,job,tmpfile.name)
changed = True
else:
if len(old_job) > 0:
# if rm is true after the next line, file will be deleted afterwards
rm = remove_job(name,tmpfile.name)
changed = True
else:
# there is no old_jobs for deletion - we should leave everything
# as is. If the file is empty, it will be removed later
tmpfile.close()
# the file created by mks should be deleted explicitly
os.unlink(backupfile)
module.exit_json(changed=changed,cron_file=cron_file,state=state)
if changed:
# If the file is empty - remove it
if rm and cron_file:
remove_job_file(cron_file)
else:
if backup:
module.backup_local(backupfile)
(rc, out, err) = install_jobs(module,user,tmpfile.name, cron_file)
if (rc != 0):
module.fail_json(msg=err)
# get the list of jobs in file
jobnames = []
for j in get_jobs(tmpfile.name):
jobnames.append(j[0])
tmpfile.close()
if not backup:
os.unlink(backupfile)
module.exit_json(changed=changed,jobs=jobnames)
else:
module.exit_json(changed=changed,jobs=jobnames,backup=backupfile)
# include magic from lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

54
library/system/facter Normal file
View File

@@ -0,0 +1,54 @@
#!/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: {}
examples:
- code: ansible www.example.net -m facter
description: "Example command-line invocation"
notes: []
requirements: [ "facter", "ruby-json" ]
author: Michael DeHaan
'''
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))
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

278
library/system/group Normal file
View File

@@ -0,0 +1,278 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Stephen Fromm <sfromm@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: group
author: Stephen Fromm
version_added: "0.0.2"
short_description: Add or remove groups
requirements: [ groupadd, groupdel, groupmod ]
description:
- Manage presence of groups on a host.
options:
name:
required: true
description:
- Name of the group to manage.
gid:
required: false
description:
- Optional I(GID) to set for the group.
state:
required: false
default: "present"
choices: [ present, absent ]
description:
- Whether the group should be present or not on the remote host.
system:
required: false
default: "no"
choices: [ "yes", "no" ]
description:
- If I(yes), indicates that the group created is a system group.
examples:
- code: "group: name=somegroup state=present"
description: Example group command from Ansible Playbooks
'''
import grp
import syslog
import platform
class Group(object):
"""
This is a generic Group manipulation class that is subclassed
based on platform.
A subclass may wish to override the following action methods:-
- group_del()
- group_add()
- group_mod()
All subclasses MUST define platform and distribution (which may be None).
"""
platform = 'Generic'
distribution = None
GROUPFILE = '/etc/group'
def __new__(cls, *args, **kwargs):
return load_platform_subclass(Group, args, kwargs)
def __init__(self, module):
self.module = module
self.state = module.params['state']
self.name = module.params['name']
self.gid = module.params['gid']
self.system = module.params['system']
self.syslogging = False
def execute_command(self, cmd):
if self.syslogging:
syslog.openlog('ansible-%s' % os.path.basename(__file__))
syslog.syslog(syslog.LOG_NOTICE, 'Command %s' % '|'.join(cmd))
return self.module.run_command(cmd)
def group_del(self):
cmd = [self.module.get_bin_path('groupdel', True), self.name]
return self.execute_command(cmd)
def group_add(self, **kwargs):
cmd = [self.module.get_bin_path('groupadd', True)]
for key in kwargs:
if key == 'gid' and kwargs[key] is not None:
cmd.append('-g')
cmd.append(kwargs[key])
elif key == 'system' and kwargs[key] == 'yes':
cmd.append('-r')
cmd.append(self.name)
return self.execute_command(cmd)
def group_mod(self, **kwargs):
cmd = [self.module.get_bin_path('groupmod', True)]
info = self.group_info()
for key in kwargs:
if key == 'gid':
if kwargs[key] is not None and info[2] != int(kwargs[key]):
cmd.append('-g')
cmd.append(kwargs[key])
if len(cmd) == 1:
return (None, '', '')
if self.module.check_mode:
return (True, '', '')
cmd.append(self.name)
return self.execute_command(cmd)
def group_exists(self):
try:
if grp.getgrnam(self.name):
return True
except KeyError:
return False
def group_info(self):
if not self.group_exists():
return False
try:
info = list(grp.getgrnam(self.name))
except KeyError:
return False
return info
# ===========================================
class SunOS(Group):
"""
This is a SunOS Group manipulation class. Solaris doesnt have
the 'system' group concept.
This overrides the following methods from the generic class:-
- group_add()
"""
platform = 'SunOS'
distribution = None
GROUPFILE = '/etc/group'
def group_add(self, **kwargs):
cmd = [self.module.get_bin_path('groupadd', True)]
for key in kwargs:
if key == 'gid' and kwargs[key] is not None:
cmd.append('-g')
cmd.append(kwargs[key])
cmd.append(self.name)
return self.execute_command(cmd)
# ===========================================
class AIX(Group):
"""
This is a AIX Group manipulation class.
This overrides the following methods from the generic class:-
- group_del()
- group_add()
- group_mod()
"""
platform = 'AIX'
distribution = None
GROUPFILE = '/etc/group'
def group_del(self):
cmd = [self.module.get_bin_path('rmgroup', True), self.name]
return self.execute_command(cmd)
def group_add(self, **kwargs):
cmd = [self.module.get_bin_path('mkgroup', True)]
for key in kwargs:
if key == 'gid' and kwargs[key] is not None:
cmd.append('id='+kwargs[key])
elif key == 'system' and kwargs[key] == 'yes':
cmd.append('-a')
cmd.append(self.name)
return self.execute_command(cmd)
def group_mod(self, **kwargs):
cmd = [self.module.get_bin_path('chgroup', True)]
info = self.group_info()
for key in kwargs:
if key == 'gid':
if kwargs[key] is not None and info[2] != int(kwargs[key]):
cmd.append('id='+kwargs[key])
if len(cmd) == 1:
return (None, '', '')
if self.module.check_mode:
return (True, '', '')
cmd.append(self.name)
return self.execute_command(cmd)
# ===========================================
def main():
module = AnsibleModule(
argument_spec = dict(
state=dict(default='present', choices=['present', 'absent'], type='str'),
name=dict(required=True, type='str'),
gid=dict(default=None, type='str'),
system=dict(default='no', choices=['yes', 'no'], type='bool'),
),
supports_check_mode=True
)
group = Group(module)
if group.syslogging:
syslog.openlog('ansible-%s' % os.path.basename(__file__))
syslog.syslog(syslog.LOG_NOTICE, 'Group instantiated - platform %s' % group.platform)
if user.distribution:
syslog.syslog(syslog.LOG_NOTICE, 'Group instantiated - distribution %s' % group.distribution)
rc = None
out = ''
err = ''
result = {}
result['name'] = group.name
result['state'] = group.state
if group.state == 'absent':
if group.group_exists():
if module.check_mode:
module.exit_json(changed=True)
(rc, out, err) = group.group_del()
if rc != 0:
module.fail_json(name=group.name, msg=err)
elif group.state == 'present':
if not group.group_exists():
if module.check_mode:
module.exit_json(changed=True)
(rc, out, err) = group.group_add(gid=group.gid, system=group.system)
else:
(rc, out, err) = group.group_mod(gid=group.gid)
if rc is not None and rc != 0:
module.fail_json(name=group.name, msg=err)
if rc is None:
result['changed'] = False
else:
result['changed'] = True
if out:
result['stdout'] = out
if err:
result['stderr'] = err
if group.group_exists():
info = group.group_info()
result['system'] = group.system
result['gid'] = info[2]
module.exit_json(**result)
# include magic from lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

217
library/system/lvg Normal file
View File

@@ -0,0 +1,217 @@
#!/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
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
examples:
- description: Create a volume group on top of /dev/sda1 with physical extent size = 32MB.
code: lvg vg=vg.services pvs=/dev/sda1 pesize=32
- description: 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.
code: lvg vg=vg.services pvs=/dev/sdb1,/dev/sdc5
- description: Remove a volume group with name vg.services.
code: lvg vg=vg.services state=absent
notes:
- module does not modify PE size for already present volume group
'''
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 parse_pvs(data):
pvs = []
for line in data.splitlines():
parts = line.strip().split(';')
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),
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']
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
rc,current_pvs,err = module.run_command("pvs --noheadings -o pv_name,vg_name --separator ';'")
if rc != 0:
module.fail_json(msg="Failed executing pvs command.",rc=rc, err=err)
### check pv for devices
pvs = parse_pvs(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']))
rc,current_vgs,err = module.run_command("vgs --noheadings -o vg_name,pv_count,lv_count --separator ';'")
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
for current_dev in dev_list:
rc,_,err = module.run_command("pvcreate %s"%current_dev)
if rc == 0:
changed = True
else:
module.fail_json(msg="Creating physical volume '%s' failed"%current_dev, rc=rc, err=err)
rc,_,err = module.run_command("vgcreate -s %s %s %s"%(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
rc,_,err = module.run_command("vgremove --force %s"%(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
for current_dev in devs_to_add:
rc,_,err = module.run_command("pvcreate %s"%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
rc,_,err = module.run_command("vgextend %s %s"%(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)
rc,_,err = module.run_command("vgreduce --force %s %s"%(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)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

150
library/system/lvol Normal file
View File

@@ -0,0 +1,150 @@
#!/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 in megabytes.
state:
choices: [ "present", "absent" ]
default: present
description:
- Control if the logical volume exists.
required: false
examples:
- description: Create a logical volume of 512m.
code: lvol vg=firefly lv=test size=512
- description: Extend the logical volume to 1024m.
code: lvol vg=firefly lv=test size=1024
- description: Reduce the logical volume to 512m
code: lvol vg=firefly lv=test size=512
- description: Remove the logical volume.
code: lvol vg=firefly lv=test state=absent
notes:
- Filesystems on top of the volume are not resized.
'''
def parse_lvs(data):
lvs = []
for line in data.splitlines():
parts = line.strip().split(';')
lvs.append({
'name': parts[0],
'size': int(parts[1].split('.')[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'),
),
supports_check_mode=True,
)
vg = module.params['vg']
lv = module.params['lv']
size = module.params['size']
state = module.params['state']
if state=='present' and not size:
module.fail_json(msg="No size given.")
if size:
size = int(size)
rc,current_lvs,err = module.run_command("lvs --noheadings -o lv_name,size --units m --separator ';' %s"%(vg))
if rc != 0:
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 this_lv is None:
if state == 'present':
### create LV
if module.check_mode:
changed = True
else:
rc,_,err = module.run_command("lvcreate -n %s -L %sm %s"%(lv, size, 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)
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)
### resize LV
tool = None
if size > this_lv['size']:
tool = 'lvextend'
elif size < this_lv['size']:
tool = 'lvreduce --force'
if tool:
if module.check_mode:
changed = True
else:
rc,_,err = module.run_command("%s -L %sm %s/%s"%(tool, size, vg, this_lv['name']))
if rc == 0:
changed = True
else:
module.fail_json(msg="Unable to resize %s to %sm."%(lv,size),rc=rc,err=err)
module.exit_json(changed=changed)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

313
library/system/mount Normal file
View File

@@ -0,0 +1,313 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Red Hat, inc
# Written by Seth Vidal
# based on the mount modules from salt and puppet
#
# 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: mount
short_description: Control active and configured mount points
description:
- This module controls active and configured mount points in C(/etc/fstab).
version_added: "0.6"
options:
name:
description:
- "path to the mount point, eg: C(/mnt/files)"
required: true
default: null
aliases: []
src:
description:
- device to be mounted on I(name).
required: true
default: null
fstype:
description:
- file-system type
required: true
default: null
opts:
description:
- mount options (see fstab(8))
required: false
default: null
dump:
description:
- dump (see fstab(8))
required: false
default: null
passno:
description:
- passno (see fstab(8))
required: false
default: null
state:
description:
- If C(mounted) or C(unmounted), the device will be actively mounted or unmounted
as well as just configured in I(fstab). C(absent) and C(present) only deal with
I(fstab).
required: true
choices: [ "present", "absent", "mounted", "unmounted" ]
default: null
examples:
- code: "mount: name=/mnt/dvd src=/dev/sr0 fstype=iso9660 opts=ro state=present"
description: "Mount DVD read-only"
- code: "mount: name=/srv/disk src='LABEL=SOME_LABEL' state=present"
description: "Mount up device by label"
- code: "mount: name=/home src='UUID=b3e48f45-f933-4c8e-a700-22a159ec9077' opts=noatime state=present"
description: "Mount up device by UUID"
notes: []
requirements: []
author: Seth Vidal
'''
def write_fstab(lines, dest):
fs_w = open(dest, 'w')
for l in lines:
fs_w.write(l)
fs_w.flush()
fs_w.close()
def set_mount(**kwargs):
""" set/change a mount point location in fstab """
# kwargs: name, src, fstype, opts, dump, passno, state, fstab=/etc/fstab
args = dict(
opts = 'defaults',
dump = '0',
passno = '0',
fstab = '/etc/fstab'
)
args.update(kwargs)
new_line = '%(src)s %(name)s %(fstype)s %(opts)s %(dump)s %(passno)s\n'
to_write = []
exists = False
changed = False
for line in open(args['fstab'], 'r').readlines():
if not line.strip():
to_write.append(line)
continue
if line.strip().startswith('#'):
to_write.append(line)
continue
if len(line.split()) != 6:
# not sure what this is or why it is here
# but it is not our fault so leave it be
to_write.append(line)
continue
ld = {}
ld['src'], ld['name'], ld['fstype'], ld['opts'], ld['dump'], ld['passno'] = line.split()
if ld['name'] != args['name']:
to_write.append(line)
continue
# it exists - now see if what we have is different
exists = True
for t in ('src', 'fstype','opts', 'dump', 'passno'):
if ld[t] != args[t]:
changed = True
ld[t] = args[t]
if changed:
to_write.append(new_line % ld)
else:
to_write.append(line)
if not exists:
to_write.append(new_line % args)
changed = True
if changed:
write_fstab(to_write, args['fstab'])
return (args['name'], changed)
def unset_mount(**kwargs):
""" remove a mount point from fstab """
# kwargs: name, src, fstype, opts, dump, passno, state, fstab=/etc/fstab
args = dict(
opts = 'default',
dump = '0',
passno = '0',
fstab = '/etc/fstab'
)
args.update(kwargs)
to_write = []
changed = False
for line in open(args['fstab'], 'r').readlines():
if not line.strip():
to_write.append(line)
continue
if line.strip().startswith('#'):
to_write.append(line)
continue
if len(line.split()) != 6:
# not sure what this is or why it is here
# but it is not our fault so leave it be
to_write.append(line)
continue
ld = {}
ld['src'], ld['name'], ld['fstype'], ld['opts'], ld['dump'], ld['passno'] = line.split()
if ld['name'] != args['name']:
to_write.append(line)
continue
# if we got here we found a match - continue and mark changed
changed = True
if changed:
write_fstab(to_write, args['fstab'])
return (args['name'], changed)
def mount(module, **kwargs):
""" mount up a path or remount if needed """
name = kwargs['name']
if os.path.ismount(name):
cmd = [ '/bin/mount', '-o', 'remount', name ]
else:
cmd = [ '/bin/mount', name ]
rc, out, err = module.run_command(cmd)
if rc == 0:
return 0, ''
else:
return rc, out+err
def umount(module, **kwargs):
""" unmount a path """
name = kwargs['name']
cmd = ['/bin/umount', name]
rc, out, err = module.run_command(cmd)
if rc == 0:
return 0, ''
else:
return rc, out+err
def main():
module = AnsibleModule(
argument_spec = dict(
state = dict(required=True, choices=['present', 'absent', 'mounted', 'unmounted']),
name = dict(required=True),
opts = dict(default=None),
passno = dict(default=None),
dump = dict(default=None),
src = dict(required=True),
fstype = dict(required=True),
fstab = dict(default=None)
)
)
changed = False
rc = 0
args = {
'name': module.params['name'],
'src': module.params['src'],
'fstype': module.params['fstype']
}
if module.params['passno'] is not None:
args['passno'] = module.params['passno']
if module.params['opts'] is not None:
args['opts'] = module.params['opts']
if module.params['dump'] is not None:
args['dump'] = module.params['dump']
if module.params['fstab'] is not None:
args['fstab'] = module.params['fstab']
# absent == remove from fstab and unmounted
# unmounted == do not change fstab state, but unmount
# present == add to fstab, do not change mount state
# mounted == add to fstab if not there and make sure it is mounted, if it has changed in fstab then remount it
state = module.params['state']
name = module.params['name']
if state == 'absent':
name, changed = unset_mount(**args)
if changed:
if os.path.ismount(name):
res,msg = umount(module, **args)
if res:
module.fail_json(msg="Error unmounting %s: %s" % (name, msg))
if os.path.exists(name):
try:
os.rmdir(name)
except (OSError, IOError), e:
module.fail_json(msg="Error rmdir %s: %s" % (name, str(e)))
module.exit_json(changed=changed, **args)
if state == 'unmounted':
if os.path.ismount(name):
res,msg = umount(module, **args)
if res:
module.fail_json(msg="Error unmounting %s: %s" % (name, msg))
changed = True
module.exit_json(changed=changed, **args)
if state in ['mounted', 'present']:
name, changed = set_mount(**args)
if state == 'mounted':
if not os.path.exists(name):
try:
os.makedirs(name)
except (OSError, IOError), e:
module.fail_json(msg="Error making dir %s: %s" % (name, str(e)))
res = 0
if os.path.ismount(name):
if changed:
res,msg = mount(module, **args)
else:
changed = True
res,msg = mount(module, **args)
if res:
module.fail_json(msg="Error mounting %s: %s" % (name, msg))
module.exit_json(changed=changed, **args)
module.fail_json(msg='Unexpected position reached')
sys.exit(0)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

54
library/system/ohai Normal file
View File

@@ -0,0 +1,54 @@
#!/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: {}
examples:
- code: ansible webservers -m ohai --tree=/tmp/ohaidata
description: "Retrieve I(ohai) data from all Web servers and store in one-file per host"
notes: []
requirements: [ "ohai" ]
author: Michael DeHaan
'''
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))
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

52
library/system/ping Normal file
View File

@@ -0,0 +1,52 @@
#!/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: ping
short_description: Try to connect to host and return C(pong) on success.
description:
- A trivial test module, this module always returns C(pong) on successful
contact. It does not make sense in playbooks, but it is useful from
C(/usr/bin/ansible)
options: {}
examples:
- code: ansible webservers -m ping
description: Test 'webservers' status
author: Michael DeHaan
'''
def main():
module = AnsibleModule(
argument_spec = dict(
data=dict(required=False, default=None),
),
supports_check_mode = True
)
result = dict(ping='pong')
if module.params['data']:
result['ping'] = module.params['data']
module.exit_json(**result)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

210
library/system/seboolean Normal file
View File

@@ -0,0 +1,210 @@
#!/usr/bin/python
# (c) 2012, Stephen Fromm <sfromm@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: seboolean
short_description: Toggles SELinux booleans.
description:
- Toggles SELinux booleans.
version_added: "0.7"
options:
name:
description:
- Name of the boolean to configure
required: true
default: null
persistent:
description:
- Set to C(yes) if the boolean setting should survive a reboot
required: false
default: no
choices: [ "yes", "no" ]
state:
description:
- Desired boolean value
required: true
default: null
choices: [ true, false ]
examples:
- code: "seboolean: name=httpd_can_network_connect state=true persistent=yes"
description: Set I(httpd_can_network_connect) SELinux flag to I(true) and I(persistent)
notes:
- Not tested on any debian based system
requirements: [ ]
author: Stephen Fromm
'''
try:
import selinux
HAVE_SELINUX=True
except ImportError:
HAVE_SELINUX=False
try:
import semanage
HAVE_SEMANAGE=True
except ImportError:
HAVE_SEMANAGE=False
def has_boolean_value(module, name):
bools = []
try:
rc, bools = selinux.security_get_boolean_names()
except OSError, e:
module.fail_json(msg="Failed to get list of boolean names")
if name in bools:
return True
else:
return False
def get_boolean_value(module, name):
state = 0
try:
state = selinux.security_get_boolean_active(name)
except OSError, e:
module.fail_json(msg="Failed to determine current state for boolean %s" % name)
if state == 1:
return True
else:
return False
# The following method implements what setsebool.c does to change
# a boolean and make it persist after reboot..
def semanage_boolean_value(module, name, state):
rc = 0
value = 0
if state:
value = 1
handle = semanage.semanage_handle_create()
if handle is None:
module.fail_json(msg="Failed to create semanage library handle")
try:
managed = semanage.semanage_is_managed(handle)
if managed < 0:
module.fail_json(msg="Failed to determine whether policy is manage")
if managed == 0:
if os.getuid() == 0:
module.fail_json(msg="Cannot set persistent booleans without managed policy")
else:
module.fail_json(msg="Cannot set persistent booleans; please try as root")
if semanage.semanage_connect(handle) < 0:
module.fail_json(msg="Failed to connect to semanage")
if semanage.semanage_begin_transaction(handle) < 0:
module.fail_json(msg="Failed to begin semanage transaction")
rc, sebool = semanage.semanage_bool_create(handle)
if rc < 0:
module.fail_json(msg="Failed to create seboolean with semanage")
if semanage.semanage_bool_set_name(handle, sebool, name) < 0:
module.fail_json(msg="Failed to set seboolean name with semanage")
semanage.semanage_bool_set_value(sebool, value)
rc, boolkey = semanage.semanage_bool_key_extract(handle, sebool)
if rc < 0:
module.fail_json(msg="Failed to extract boolean key with semanage")
if semanage.semanage_bool_modify_local(handle, boolkey, sebool) < 0:
module.fail_json(msg="Failed to modify boolean key with semanage")
if semanage.semanage_bool_set_active(handle, boolkey, sebool) < 0:
module.fail_json(msg="Failed to set boolean key active with semanage")
semanage.semanage_bool_key_free(boolkey)
semanage.semanage_bool_free(sebool)
semanage.semanage_set_reload(handle, 0)
if semanage.semanage_commit(handle) < 0:
module.fail_json(msg="Failed to commit changes to semanage")
semanage.semanage_disconnect(handle)
semanage.semanage_handle_destroy(handle)
except Exception, e:
module.fail_json(msg="Failed to manage policy for boolean %s: %s" % (name, str(e)))
return True
def set_boolean_value(module, name, state):
rc = 0
value = 0
if state:
value = 1
try:
rc = selinux.security_set_boolean(name, value)
except OSError, e:
module.fail_json(msg="Failed to set boolean %s to %s" % (name, value))
if rc == 0:
return True
else:
return False
def main():
module = AnsibleModule(
argument_spec = dict(
name=dict(required=True),
persistent=dict(default='no', type='bool'),
state=dict(required=True, type='bool')
),
supports_check_mode=True
)
if not HAVE_SELINUX:
module.fail_json(msg="This module requires libselinux-python support")
if not HAVE_SEMANAGE:
module.fail_json(msg="This module requires libsemanage-python support")
if not selinux.is_selinux_enabled():
module.fail_json(msg="SELinux is disabled on this host.")
name = module.params['name']
persistent = module.params['persistent']
state = module.params['state']
result = {}
result['name'] = name
if not has_boolean_value(module, name):
module.fail_json(msg="SELinux boolean %s does not exist." % name)
cur_value = get_boolean_value(module, name)
if cur_value == state:
result['state'] = cur_value
result['changed'] = False
module.exit_json(**result)
if module.check_mode:
module.exit_json(changed=True)
if persistent:
r = semanage_boolean_value(module, name, state)
else:
r = set_boolean_value(module, name, state)
result['changed'] = r
if not r:
module.fail_json(msg="Failed to set boolean %s to %s" % (name, value))
try:
selinux.security_commit_booleans()
except:
module.fail_json(msg="Failed to commit pending boolean %s value" % name)
module.exit_json(**result)
# include magic from lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

200
library/system/selinux Normal file
View File

@@ -0,0 +1,200 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Derek Carter<goozbach@friocorte.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: selinux
short_description: Change policy and state of SELinux
description:
- Configures the SELinux mode and policy. A reboot may be required after usage. Ansible will not issue this reboot but will let you know when it is required.
version_added: "0.7"
options:
policy:
description:
- "name of the SELinux policy to use (example: C(targeted)) will be required if state is not C(disabled)"
required: false
default: null
state:
description:
- The SELinux mode
required: true
default: null
choices: [ "enforcing", "permissive", "disabled" ]
conf:
description:
- path to the SELinux configuration file, if non-standard
required: false
default: "/etc/selinux/config"
examples:
- code: "selinux: policy=targeted state=enforcing"
- code: "selinux: policy=targeted state=permissive"
- code: "selinux: state=disabled"
notes:
- Not tested on any debian based system
requirements: [ libselinux-python ]
author: Derek Carter <goozbach@friocorte.com>
'''
import os
import re
import sys
try:
import selinux
except ImportError:
print json.dumps(failed=True, msg='python-selinux required for this module')
sys.exit(1)
# getter subroutines
def get_config_state(configfile):
myfile = open(configfile, "r")
lines = myfile.readlines()
myfile.close()
for line in lines:
stateline = re.match('^SELINUX=.*$', line)
if (stateline):
return(line.split('=')[1].strip())
def get_config_policy(configfile):
myfile = open(configfile, "r")
lines = myfile.readlines()
myfile.close()
for line in lines:
stateline = re.match('^SELINUXTYPE=.*$', line)
if (stateline):
return(line.split('=')[1].strip())
# setter subroutines
def set_config_state(state, configfile):
#SELINUX=permissive
# edit config file with state value
stateline='SELINUX=%s' % state
myfile = open(configfile, "r")
lines = myfile.readlines()
myfile.close()
myfile = open(configfile, "w")
for line in lines:
myfile.write(re.sub(r'^SELINUX=.*', stateline, line))
myfile.close()
def set_state(state):
if (state == 'enforcing'):
selinux.security_setenforce(1)
elif (state == 'permissive'):
selinux.security_setenforce(0)
elif (state == 'disabled'):
pass
else:
msg = 'trying to set invalid runtime state %s' % state
module.fail_json(msg=msg)
def set_config_policy(policy, configfile):
# edit config file with state value
#SELINUXTYPE=targeted
policyline='SELINUXTYPE=%s' % policy
myfile = open(configfile, "r")
lines = myfile.readlines()
myfile.close()
myfile = open(configfile, "w")
for line in lines:
myfile.write(re.sub(r'^SELINUXTYPE=.*', policyline, line))
myfile.close()
def main():
module = AnsibleModule(
argument_spec = dict(
policy=dict(required=False),
state=dict(choices=['enforcing', 'permissive', 'disabled'], required=True),
configfile=dict(aliases=['conf','file'], default='/etc/selinux/config')
),
supports_check_mode=True
)
# global vars
changed=False
msgs = []
configfile = module.params['configfile']
policy = module.params['policy']
state = module.params['state']
runtime_enabled = selinux.is_selinux_enabled()
runtime_policy = selinux.selinux_getpolicytype()[1]
runtime_state = 'disabled'
if (runtime_enabled):
# enabled means 'enforcing' or 'permissive'
if (selinux.security_getenforce()):
runtime_state = 'enforcing'
else:
runtime_state = 'permissive'
config_policy = get_config_policy(configfile)
config_state = get_config_state(configfile)
# check to see if policy is set if state is not 'disabled'
if (state != 'disabled'):
if not policy:
module.fail_json(msg='policy is required if state is not \'disabled\'')
else:
if not policy:
policy = config_policy
# check changed values and run changes
if (policy != runtime_policy):
if module.check_mode:
module.exit_json(changed=True)
# cannot change runtime policy
msgs.append('reboot to change the loaded policy')
changed=True
if (policy != config_policy):
if module.check_mode:
module.exit_json(changed=True)
msgs.append('config policy changed from \'%s\' to \'%s\'' % (config_policy, policy))
set_config_policy(policy, configfile)
changed=True
if (state != runtime_state):
if module.check_mode:
module.exit_json(changed=True)
if (state == 'disabled'):
msgs.append('state change will take effect next reboot')
else:
if (runtime_enabled):
set_state(state)
msgs.append('runtime state changed from \'%s\' to \'%s\'' % (runtime_state, state))
else:
msgs.append('state change will take effect next reboot')
changed=True
if (state != config_state):
if module.check_mode:
module.exit_json(changed=True)
msgs.append('config state changed from \'%s\' to \'%s\'' % (config_state, state))
set_config_state(state, configfile)
changed=True
module.exit_json(changed=changed, msg=', '.join(msgs),
configfile=configfile,
policy=policy, state=state)
#################################################
# include magic from lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

946
library/system/service Normal file
View File

@@ -0,0 +1,946 @@
#!/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: service
author: Michael DeHaan
version_added: "0.1"
short_description: Manage services.
description:
- Controls services on remote hosts.
options:
name:
required: true
description:
- Name of the service.
state:
required: false
choices: [ started, stopped, restarted, reloaded ]
description:
- C(started)/C(stopped) are idempotent actions that will not run
commands unless necessary. C(restarted) will always bounce the
service. C(reloaded) will always reload. At least one of state
and enabled are required.
pattern:
required: false
version_added: "0.7"
description:
- If the service does not respond to the status command, name a
substring to look for as would be found in the output of the I(ps)
command as a stand-in for a status result. If the string is found,
the service will be assumed to be running.
enabled:
required: false
choices: [ "yes", "no" ]
description:
- Whether the service should start on boot. At least one of state and
enabled are required.
arguments:
description:
- Additional arguments provided on the command line
aliases: [ 'args' ]
examples:
- description: Example action to start service httpd, if not running
code: "service: name=httpd state=started"
- description: Example action to stop service httpd, if running
code: "service: name=httpd state=stopped"
- description: Example action to restart service httpd, in all cases
code: "service: name=httpd state=restarted"
- description: Example action to reload service httpd, in all cases
code: "service: name=httpd state=reloaded"
- description: Example action to enable service httpd, and not touch the running state
code: "service: name=httpd enabled=yes"
- description: Example action to start service foo, based on running process /usr/bin/foo
code: "service: name=foo pattern=/usr/bin/foo state=started"
- description: Example action to restart network service for interface eth0
code: "service: name=network state=restarted args=eth0"
'''
import platform
import os
import tempfile
import shlex
import select
class Service(object):
"""
This is the generic Service manipulation class that is subclassed
based on platform.
A subclass should override the following action methods:-
- get_service_tools
- service_enable
- get_service_status
- service_control
All subclasses MUST define platform and distribution (which may be None).
"""
platform = 'Generic'
distribution = None
def __new__(cls, *args, **kwargs):
return load_platform_subclass(Service, args, kwargs)
def __init__(self, module):
self.module = module
self.name = module.params['name']
self.state = module.params['state']
self.pattern = module.params['pattern']
self.enable = module.params['enabled']
self.changed = False
self.running = None
self.action = None
self.svc_cmd = None
self.svc_initscript = None
self.svc_initctl = None
self.enable_cmd = None
self.arguments = module.params.get('arguments', '')
self.rcconf_file = None
self.rcconf_key = None
self.rcconf_value = None
# select whether we dump additional debug info through syslog
self.syslogging = False
# ===========================================
# Platform specific methods (must be replaced by subclass).
def get_service_tools(self):
self.module.fail_json(msg="get_service_tools not implemented on target platform")
def service_enable(self):
self.module.fail_json(msg="service_enable not implemented on target platform")
def get_service_status(self):
self.module.fail_json(msg="get_service_status not implemented on target platform")
def service_control(self):
self.module.fail_json(msg="service_control not implemented on target platform")
# ===========================================
# Generic methods that should be used on all platforms.
def execute_command(self, cmd, daemonize=False):
if self.syslogging:
syslog.openlog('ansible-%s' % os.path.basename(__file__))
syslog.syslog(syslog.LOG_NOTICE, 'Command %s, daemonize %r' % (cmd, daemonize))
# Most things don't need to be daemonized
if not daemonize:
return self.module.run_command(cmd)
# This is complex because daemonization is hard for people.
# What we do is daemonize a part of this module, the daemon runs the
# command, picks up the return code and output, and returns it to the
# main process.
pipe = os.pipe()
pid = os.fork()
if pid == 0:
os.close(pipe[0])
# Set stdin/stdout/stderr to /dev/null
fd = os.open(os.devnull, os.O_RDWR)
if fd != 0:
os.dup2(fd, 0)
if fd != 1:
os.dup2(fd, 1)
if fd != 2:
os.dup2(fd, 2)
if fd not in (0, 1, 2):
os.close(fd)
# Make us a daemon. Yes, that's all it takes.
pid = os.fork()
if pid > 0:
os._exit(0)
os.setsid()
os.chdir("/")
pid = os.fork()
if pid > 0:
os._exit(0)
# Start the command
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=lambda: os.close(pipe[1]))
stdout = ""
stderr = ""
fds = [p.stdout, p.stderr]
# Wait for all output, or until the main process is dead and its output is done.
while fds:
rfd, wfd, efd = select.select(fds, [], fds, 1)
if not (rfd + wfd + efd) and p.poll() is not None:
break
if p.stdout in rfd:
dat = os.read(p.stdout.fileno(), 4096)
if not dat:
fds.remove(p.stdout)
stdout += dat
if p.stderr in rfd:
dat = os.read(p.stderr.fileno(), 4096)
if not dat:
fds.remove(p.stderr)
stderr += dat
p.wait()
# Return a JSON blob to parent
os.write(pipe[1], json.dumps([p.returncode, stdout, stderr]))
os.close(pipe[1])
os._exit(0)
elif pid == -1:
self.module.fail_json(msg="unable to fork")
else:
os.close(pipe[1])
os.waitpid(pid, 0)
# Wait for data from daemon process and process it.
data = ""
while True:
rfd, wfd, efd = select.select([pipe[0]], [], [pipe[0]])
if pipe[0] in rfd:
dat = os.read(pipe[0], 4096)
if not dat:
break
data += dat
return json.loads(data)
def check_ps(self):
# Set ps flags
if platform.system() == 'SunOS':
psflags = '-ef'
else:
psflags = 'auxww'
# Find ps binary
psbin = self.module.get_bin_path('ps', True)
(rc, psout, pserr) = self.execute_command('%s %s' % (psbin, psflags))
# If rc is 0, set running as appropriate
if rc == 0:
self.running = False
lines = psout.split("\n")
for line in lines:
if self.pattern in line and not "pattern=" in line:
# so as to not confuse ./hacking/test-module
self.running = True
break
def check_service_changed(self):
if self.state and self.running is None:
self.module.fail_json(msg="failed determining service state, possible typo of service name?")
# Find out if state has changed
if not self.running and self.state in ["started", "running"]:
self.changed = True
elif self.running and self.state in ["stopped","reloaded"]:
self.changed = True
elif self.state == "restarted":
self.changed = True
if self.module.check_mode and self.changed:
self.module.exit_json(changed=True, msg='service state changed')
def modify_service_state(self):
# Only do something if state will change
if self.changed:
# Control service
if self.state in ['started', 'running']:
self.action = "start"
elif self.state == 'stopped':
self.action = "stop"
elif self.state == 'reloaded':
self.action = "reload"
elif self.state == 'restarted':
self.action = "restart"
if self.module.check_mode:
self.module.exit_json(changed=True, msg='changing service state')
return self.service_control()
else:
# If nothing needs to change just say all is well
rc = 0
err = ''
out = ''
return rc, out, err
def service_enable_rcconf(self):
if self.rcconf_file is None or self.rcconf_key is None or self.rcconf_value is None:
self.module.fail_json(msg="service_enable_rcconf() requires rcconf_file, rcconf_key and rcconf_value")
changed = None
entry = '%s="%s"\n' % (self.rcconf_key, self.rcconf_value)
RCFILE = open(self.rcconf_file, "r")
new_rc_conf = []
# Build a list containing the possibly modified file.
for rcline in RCFILE:
# Parse line removing whitespaces, quotes, etc.
rcarray = shlex.split(rcline, comments=True)
if len(rcarray) >= 1 and '=' in rcarray[0]:
(key, value) = rcarray[0].split("=", 1)
if key == self.rcconf_key:
if value == self.rcconf_value:
# Since the proper entry already exists we can stop iterating.
changed = False
break
else:
# We found the key but the value is wrong, replace with new entry.
rcline = entry
changed = True
# Add line to the list.
new_rc_conf.append(rcline)
# We are done with reading the current rc.conf, close it.
RCFILE.close()
# If we did not see any trace of our entry we need to add it.
if changed is None:
new_rc_conf.append(entry)
changed = True
if changed is True:
if self.module.check_mode:
self.module.exit_json(changed=True, msg="changing service enablement")
# Create a temporary file next to the current rc.conf (so we stay on the same filesystem).
# This way the replacement operation is atomic.
rcconf_dir = os.path.dirname(self.rcconf_file)
rcconf_base = os.path.basename(self.rcconf_file)
(TMP_RCCONF, tmp_rcconf_file) = tempfile.mkstemp(dir=rcconf_dir, prefix="%s-" % rcconf_base)
# Write out the contents of the list into our temporary file.
for rcline in new_rc_conf:
os.write(TMP_RCCONF, rcline)
# Close temporary file.
os.close(TMP_RCCONF)
# Replace previous rc.conf.
self.module.atomic_move(tmp_rcconf_file, self.rcconf_file)
# ===========================================
# Subclass: Linux
class LinuxService(Service):
"""
This is the Linux Service manipulation class - it is currently supporting
a mixture of binaries and init scripts for controlling services started at
boot, as well as for controlling the current state.
"""
platform = 'Linux'
distribution = None
def get_service_tools(self):
paths = [ '/sbin', '/usr/sbin', '/bin', '/usr/bin' ]
binaries = [ 'service', 'chkconfig', 'update-rc.d', 'initctl', 'systemctl', 'start', 'stop', 'restart' ]
initpaths = [ '/etc/init.d' ]
location = dict()
for binary in binaries:
location[binary] = None
for binary in binaries:
location[binary] = self.module.get_bin_path(binary)
# Locate a tool for enable options
if location.get('chkconfig', None) and os.path.exists("/etc/init.d/%s" % self.name):
# we are using a standard SysV service
self.enable_cmd = location['chkconfig']
elif location.get('update-rc.d', None) and os.path.exists("/etc/init/%s.conf" % self.name):
# service is managed by upstart
self.enable_cmd = location['update-rc.d']
elif location.get('update-rc.d', None) and os.path.exists("/etc/init.d/%s" % self.name):
# service is managed by with SysV init scripts, but with update-rc.d
self.enable_cmd = location['update-rc.d']
elif location.get('systemctl', None):
# verify service is managed by systemd
rc, out, err = self.execute_command("%s list-unit-files" % (location['systemctl']))
# adjust the service name to account for template service unit files
index = self.name.find('@')
if index == -1:
name = self.name
else:
name = self.name[:index+1]
look_for = "%s.service" % name
for line in out.splitlines():
if line.startswith(look_for):
self.enable_cmd = location['systemctl']
break
# Locate a tool for runtime service management (start, stop etc.)
self.svc_cmd = ''
if location.get('service', None) and os.path.exists("/etc/init.d/%s" % self.name):
# SysV init script
self.svc_cmd = location['service']
elif location.get('start', None) and os.path.exists("/etc/init/%s.conf" % self.name):
# upstart -- rather than being managed by one command, start/stop/restart are actual commands
self.svc_cmd = ''
else:
# still a SysV init script, but /sbin/service isn't installed
for initdir in initpaths:
initscript = "%s/%s" % (initdir,self.name)
if os.path.isfile(initscript):
self.svc_initscript = initscript
# couldn't find anything yet, assume systemd
if self.svc_initscript is None:
if location.get('systemctl'):
self.svc_cmd = location['systemctl']
if self.svc_cmd is None and not self.svc_initscript:
self.module.fail_json(msg='cannot find \'service\' binary or init script for service, aborting')
if location.get('initctl', None):
self.svc_initctl = location['initctl']
def get_service_status(self):
self.action = "status"
rc, status_stdout, status_stderr = self.service_control()
# if we have decided the service is managed by upstart, we check for some additional output...
if self.svc_initctl and self.running is None:
# check the job status by upstart response
initctl_rc, initctl_status_stdout, initctl_status_stderr = self.execute_command("%s status %s" % (self.svc_initctl, self.name))
if initctl_status_stdout.find("stop/waiting") != -1:
self.running = False
elif initctl_status_stdout.find("start/running") != -1:
self.running = True
# if the job status is still not known check it by response code
# For reference, see:
# http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html
if self.running is None:
if rc in [1, 2, 3, 4, 69]:
self.running = False
elif rc == 0:
self.running = True
# if the job status is still not known check it by status output keywords
if self.running is None:
# first tranform the status output that could irritate keyword matching
cleanout = status_stdout.lower().replace(self.name.lower(), '')
if "stop" in cleanout:
self.running = False
elif "run" in cleanout and "not" in cleanout:
self.running = False
elif "run" in cleanout and "not" not in cleanout:
self.running = True
elif "start" in cleanout and "not" not in cleanout:
self.running = True
elif 'could not access pid file' in cleanout:
self.running = False
elif 'is dead and pid file exists' in cleanout:
self.running = False
elif 'dead but subsys locked' in cleanout:
self.running = False
elif 'dead but pid file exists' in cleanout:
self.running = False
# if the job status is still not known check it by special conditions
if self.running is None:
if self.name == 'iptables' and status_stdout.find("ACCEPT") != -1:
# iptables status command output is lame
# TODO: lookup if we can use a return code for this instead?
self.running = True
return self.running
def service_enable(self):
if self.enable_cmd is None:
self.module.fail_json(msg='service name not recognized')
# FIXME: we use chkconfig or systemctl
# to decide whether to run the command here but need something
# similar for upstart
if self.enable_cmd.endswith("chkconfig"):
(rc, out, err) = self.execute_command("%s --list %s" % (self.enable_cmd, self.name))
if 'chkconfig --add %s' % self.name in err:
self.execute_command("%s --add %s" % (self.enable_cmd, self.name))
(rc, out, err) = self.execute_command("%s --list %s" % (self.enable_cmd, self.name))
if not self.name in out:
self.module.fail_json(msg="unknown service name")
state = out.split()[-1]
if self.enable and ( "3:on" in out and "5:on" in out ):
return
elif not self.enable and ( "3:off" in out and "5:off" in out ):
return
if self.enable_cmd.endswith("systemctl"):
(rc, out, err) = self.execute_command("%s show %s.service" % (self.enable_cmd, self.name))
d = dict(line.split('=', 1) for line in out.splitlines())
if "UnitFileState" in d:
if self.enable and d["UnitFileState"] == "enabled":
return
elif not self.enable and d["UnitFileState"] == "disabled":
return
elif not self.enable:
return
# we change argument depending on real binary used
# update-rc.d wants enable/disable while
# chkconfig wants on/off
# also, systemctl needs the argument order reversed
if self.enable:
on_off = "on"
enable_disable = "enable"
else:
on_off = "off"
enable_disable = "disable"
if self.enable_cmd.endswith("update-rc.d"):
args = (self.enable_cmd, self.name, enable_disable)
elif self.enable_cmd.endswith("systemctl"):
args = (self.enable_cmd, enable_disable, self.name + ".service")
else:
args = (self.enable_cmd, self.name, on_off)
self.changed = True
if self.module.check_mode and self.changed:
self.module.exit_json(changed=True)
return self.execute_command("%s %s %s" % args)
def service_control(self):
# Decide what command to run
svc_cmd = ''
arguments = self.arguments
if self.svc_cmd:
if not self.svc_cmd.endswith("systemctl"):
# SysV take the form <cmd> <name> <action>
svc_cmd = "%s %s" % (self.svc_cmd, self.name)
else:
# systemd commands take the form <cmd> <action> <name>
svc_cmd = self.svc_cmd
arguments = "%s %s" % (self.name, arguments)
elif self.svc_initscript:
# upstart
svc_cmd = "%s" % self.svc_initscript
if self.action is not "restart":
if svc_cmd != '':
# upstart or systemd
rc_state, stdout, stderr = self.execute_command("%s %s %s" % (svc_cmd, self.action, arguments), daemonize=True)
else:
# SysV
rc_state, stdout, stderr = self.execute_command("%s %s %s" % (self.action, self.name, arguments), daemonize=True)
else:
# not all services support restart. Do it the hard way.
if svc_cmd != '':
# upstart or systemd
rc1, stdout1, stderr1 = self.execute_command("%s %s %s" % (svc_cmd, 'stop', arguments), daemonize=True)
else:
# SysV
rc1, stdout1, stderr1 = self.execute_command("%s %s %s" % ('stop', self.name, arguments), daemonize=True)
if svc_cmd != '':
# upstart or systemd
rc2, stdout2, stderr2 = self.execute_command("%s %s %s" % (svc_cmd, 'start', arguments), daemonize=True)
else:
# SysV
rc2, stdout2, stderr2 = self.execute_command("%s %s %s" % ('start', self.name, arguments), daemonize=True)
# merge return information
if rc1 != 0 and rc2 == 0:
rc_state = rc2
stdout = stdout2
stderr = stderr2
else:
rc_state = rc1 + rc2
stdout = stdout1 + stdout2
stderr = stderr1 + stderr2
return(rc_state, stdout, stderr)
# ===========================================
# Subclass: FreeBSD
class FreeBsdService(Service):
"""
This is the FreeBSD Service manipulation class - it uses the /etc/rc.conf
file for controlling services started at boot and the 'service' binary to
check status and perform direct service manipulation.
"""
platform = 'FreeBSD'
distribution = None
def get_service_tools(self):
self.svc_cmd = self.module.get_bin_path('service', True)
if not self.svc_cmd:
self.module.fail_json(msg='unable to find service binary')
def get_service_status(self):
rc, stdout, stderr = self.execute_command("%s %s %s %s" % (self.svc_cmd, self.name, 'onestatus', self.arguments))
if rc == 1:
self.running = False
elif rc == 0:
self.running = True
def service_enable(self):
if self.enable:
self.rcconf_value = "YES"
else:
self.rcconf_value = "NO"
rcfiles = [ '/etc/rc.conf','/usr/local/etc/rc.conf' ]
for rcfile in rcfiles:
if os.path.isfile(rcfile):
self.rcconf_file = rcfile
self.rcconf_key = "%s_enable" % self.name
return self.service_enable_rcconf()
def service_control(self):
if self.action is "start":
self.action = "onestart"
if self.action is "stop":
self.action = "onestop"
if self.action is "reload":
self.action = "onereload"
return self.execute_command("%s %s %s %s" % (self.svc_cmd, self.name, self.action, self.arguments))
# ===========================================
# Subclass: OpenBSD
class OpenBsdService(Service):
"""
This is the OpenBSD Service manipulation class - it uses /etc/rc.d for
service control. Enabling a service is currently not supported because the
<service>_flags variable is not boolean, you should supply a rc.conf.local
file in some other way.
"""
platform = 'OpenBSD'
distribution = None
def get_service_tools(self):
rcdir = '/etc/rc.d'
rc_script = "%s/%s" % (rcdir, self.name)
if os.path.isfile(rc_script):
self.svc_cmd = rc_script
if not self.svc_cmd:
self.module.fail_json(msg='unable to find rc.d script')
def get_service_status(self):
rc, stdout, stderr = self.execute_command("%s %s" % (self.svc_cmd, 'check'))
if rc == 1:
self.running = False
elif rc == 0:
self.running = True
def service_control(self):
return self.execute_command("%s %s" % (self.svc_cmd, self.action))
# ===========================================
# Subclass: NetBSD
class NetBsdService(Service):
"""
This is the NetBSD Service manipulation class - it uses the /etc/rc.conf
file for controlling services started at boot, check status and perform
direct service manipulation. Init scripts in /etc/rcd are used for
controlling services (start/stop) as well as for controlling the current
state.
"""
platform = 'NetBSD'
distribution = None
def get_service_tools(self):
initpaths = [ '/etc/rc.d' ] # better: $rc_directories - how to get in here? Run: sh -c '. /etc/rc.conf ; echo $rc_directories'
for initdir in initpaths:
initscript = "%s/%s" % (initdir,self.name)
if os.path.isfile(initscript):
self.svc_initscript = initscript
if not self.svc_initscript:
self.module.fail_json(msg='unable to find rc.d script')
def service_enable(self):
if self.enable:
self.rcconf_value = "YES"
else:
self.rcconf_value = "NO"
rcfiles = [ '/etc/rc.conf' ] # Overkill?
for rcfile in rcfiles:
if os.path.isfile(rcfile):
self.rcconf_file = rcfile
self.rcconf_key = "%s" % self.name
return self.service_enable_rcconf()
def get_service_status(self):
self.svc_cmd = "%s" % self.svc_initscript
rc, stdout, stderr = self.execute_command("%s %s" % (self.svc_cmd, 'onestatus'))
if rc == 1:
self.running = False
elif rc == 0:
self.running = True
def service_control(self):
if self.action is "start":
self.action = "onestart"
if self.action is "stop":
self.action = "onestop"
self.svc_cmd = "%s" % self.svc_initscript
return self.execute_command("%s %s" % (self.svc_cmd, self.action), daemonize=True)
# ===========================================
# Subclass: SunOS
class SunOSService(Service):
"""
This is the SunOS Service manipulation class - it uses the svcadm
command for controlling services, and svcs command for checking status.
It also tries to be smart about taking the service out of maintenance
state if necessary.
"""
platform = 'SunOS'
distribution = None
def get_service_tools(self):
self.svcs_cmd = self.module.get_bin_path('svcs', True)
if not self.svcs_cmd:
self.module.fail_json(msg='unable to find svcs binary')
self.svcadm_cmd = self.module.get_bin_path('svcadm', True)
if not self.svcadm_cmd:
self.module.fail_json(msg='unable to find svcadm binary')
def get_service_status(self):
status = self.get_sunos_svcs_status()
# Only 'online' is considered properly running. Everything else is off
# or has some sort of problem.
if status == 'online':
self.running = True
else:
self.running = False
def get_sunos_svcs_status(self):
rc, stdout, stderr = self.execute_command("%s %s" % (self.svcs_cmd, self.name))
if rc == 1:
if stderr:
self.module.fail_json(msg=stderr)
else:
self.module.fail_json(msg=stdout)
lines = stdout.rstrip("\n").split("\n")
status = lines[-1].split(" ")[0]
# status is one of: online, offline, degraded, disabled, maintenance, uninitialized
# see man svcs(1)
return status
def service_enable(self):
# Get current service enablement status
rc, stdout, stderr = self.execute_command("%s -l %s" % (self.svcs_cmd, self.name))
if rc != 0:
if stderr:
self.module.fail_json(msg=stderr)
else:
self.module.fail_json(msg=stdout)
enabled = False
temporary = False
# look for enabled line, which could be one of:
# enabled true (temporary)
# enabled false (temporary)
# enabled true
# enabled false
for line in stdout.split("\n"):
if line.find("enabled") == 0:
if line.find("true") != -1:
enabled = True
if line.find("temporary") != -1:
temporary = True
startup_enabled = (enabled and not temporary) or (not enabled and temporary)
if self.enable and startup_enabled:
return
elif (not self.enable) and (not startup_enabled):
return
# Mark service as started or stopped (this will have the side effect of
# actually stopping or starting the service)
if self.enable:
subcmd = "enable -rs"
else:
subcmd = "disable -s"
rc, stdout, stderr = self.execute_command("%s %s %s" % (self.svcadm_cmd, subcmd, self.name))
if rc != 0:
if stderr:
self.module.fail_json(msg=stderr)
else:
self.module.fail_json(msg=stdout)
self.changed = True
def service_control(self):
status = self.get_sunos_svcs_status()
# if starting or reloading, clear maintenace states
if self.action in ['start', 'reload', 'restart'] and status in ['maintenance', 'degraded']:
rc, stdout, stderr = self.execute_command("%s clear %s" % (self.svcadm_cmd, self.name))
if rc != 0:
return rc, stdout, stderr
status = self.get_sunos_svcs_status()
if status in ['maintenance', 'degraded']:
self.module.fail_json(msg="Failed to bring service out of %s status." % status)
if self.action == 'start':
subcmd = "enable -rst"
elif self.action == 'stop':
subcmd = "disable -st"
elif self.action == 'reload':
subcmd = "refresh"
elif self.action == 'restart' and status == 'online':
subcmd = "restart"
elif self.action == 'restart' and status != 'online':
subcmd = "enable -rst"
return self.execute_command("%s %s %s" % (self.svcadm_cmd, subcmd, self.name))
# ===========================================
# Main control flow
def main():
module = AnsibleModule(
argument_spec = dict(
name = dict(required=True),
state = dict(choices=['running', 'started', 'stopped', 'restarted', 'reloaded']),
pattern = dict(required=False, default=None),
enabled = dict(choices=BOOLEANS, type='bool'),
arguments = dict(aliases=['args'], default=''),
),
supports_check_mode=True
)
if module.params['state'] is None and module.params['enabled'] is None:
module.fail_json(msg="Neither 'state' nor 'enabled' set")
service = Service(module)
if service.syslogging:
syslog.openlog('ansible-%s' % os.path.basename(__file__))
syslog.syslog(syslog.LOG_NOTICE, 'Service instantiated - platform %s' % service.platform)
if service.distribution:
syslog.syslog(syslog.LOG_NOTICE, 'Service instantiated - distribution %s' % service.distribution)
rc = 0
out = ''
err = ''
result = {}
result['name'] = service.name
# Find service management tools
service.get_service_tools()
# Enable/disable service startup at boot if requested
if service.module.params['enabled'] is not None:
# FIXME: ideally this should detect if we need to toggle the enablement state, though
# it's unlikely the changed handler would need to fire in this case so it's a minor thing.
service.service_enable()
result['enabled'] = service.enable
if module.params['state'] is None:
# Not changing the running state, so bail out now.
result['changed'] = service.changed
module.exit_json(**result)
result['state'] = service.state
# Collect service status
if service.pattern:
service.check_ps()
service.get_service_status()
# Calculate if request will change service state
service.check_service_changed()
# Modify service state if necessary
(rc, out, err) = service.modify_service_state()
if rc != 0:
if err and err.find("is already") != -1:
# upstart got confused, one such possibility is MySQL on Ubuntu 12.04
# where status may report it has no start/stop links and we could
# not get accurate status
pass
else:
if err:
module.fail_json(msg=err)
else:
module.fail_json(msg=out)
result['changed'] = service.changed
if service.module.params['enabled'] is not None:
result['enabled'] = service.module.params['enabled']
if not service.module.params['state']:
status = service.get_service_status()
if status is None:
result['state'] = 'absent'
elif status is False:
result['state'] = 'started'
else:
result['state'] = 'stopped'
else:
# as we may have just bounced the service the service command may not
# report accurate state at this moment so just show what we ran
if service.module.params['state'] in ['started','restarted','running']:
result['state'] = 'started'
else:
result['state'] = 'stopped'
module.exit_json(**result)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

1681
library/system/setup Normal file

File diff suppressed because it is too large Load Diff

318
library/system/sysctl Normal file
View File

@@ -0,0 +1,318 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, David "DaviXX" CHANIAL <david.chanial@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: sysctl
short_description: Permit to handle sysctl.conf entries
description:
- This module manipulates sysctl entries and performs a I(/sbin/sysctl -p) after changing them.
version_added: "1.0"
options:
name:
description:
- this is the short path, decimal separated, to the sysctl entry
required: true
default: null
aliases: [ 'key' ]
value:
description:
- set the sysctl value to this entry
required: false
default: null
aliases: [ 'val' ]
state:
description:
- whether the entry should be present or absent
choices: [ "present", "absent" ]
default: present
checks:
description:
- if C(checks)=I(none) no smart/facultative checks will be made
- if C(checks)=I(before) some checks performed before any update (ie. does the sysctl key is writable ?)
- if C(checks)=I(after) some checks performed after an update (ie. does kernel give back the setted value ?)
- if C(checks)=I(both) all the smart checks I(before and after) are performed
choices: [ "none", "before", "after", "both" ]
default: both
reload:
description:
- if C(reload=yes), performs a I(/sbin/sysctl -p) if the C(sysctl_file) is updated
- if C(reload=no), does not reload I(sysctl) even if the C(sysctl_file) is updated
choices: [ "yes", "no" ]
default: "yes"
sysctl_file:
description:
- specifies the absolute path to C(sysctl.conf), if not /etc/sysctl.conf
required: false
default: /etc/sysctl.conf
examples:
- code: "sysctl: name=vm.swappiness value=5 state=present"
description: "Set vm.swappiness to 5 in /etc/sysctl.conf"
- code: "sysctl: name=kernel.panic state=absent sysctl_file=/etc/sysctl.conf"
description: "Remove kernel.panic entry from /etc/sysctl.conf"
- code: "sysctl: name=kernel.panic value=3 sysctl_file=/tmp/test_sysctl.conf check=before reload=no"
description: Set kernel.panic to 3 in /tmp/test_sysctl.conf, check if the sysctl key seems writable, but do not reload sysctl, and do not check kernel value after (not needed, because not the real /etc/sysctl.conf updated)
notes: []
requirements: []
author: David "DaviXX" CHANIAL <david.chanial@gmail.com>
'''
# ==============================================================
import os
import tempfile
import re
# ==============================================================
def reload_sysctl(**sysctl_args):
# update needed ?
if not sysctl_args['reload']:
return 0, ''
# do it
cmd = [ '/sbin/sysctl', '-p', sysctl_args['sysctl_file']]
call = subprocess.Popen(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = call.communicate()
if call.returncode == 0:
return 0, ''
else:
return call.returncode, out+err
# ==============================================================
def write_sysctl(module, lines, **sysctl_args):
# open a tmp file
fd, tmp_path = tempfile.mkstemp('.conf', '.ansible_m_sysctl_', os.path.dirname(sysctl_args['sysctl_file']))
f = open(tmp_path,"w")
try:
for l in lines:
f.write(l)
except IOError, e:
module.fail_json(msg="Failed to write to file %s: %s" % (tmp_path, str(e)))
f.flush()
f.close()
# replace the real one
module.atomic_move(tmp_path, sysctl_args['sysctl_file'])
# end
return sysctl_args
# ==============================================================
def sysctl_args_expand(**sysctl_args):
sysctl_args['key_path'] = sysctl_args['name'].replace('.' ,'/')
sysctl_args['key_path'] = '/proc/sys/' + sysctl_args['key_path']
return sysctl_args
# ==============================================================
def sysctl_args_collapse(**sysctl_args):
# go ahead
if sysctl_args.get('key_path') is not None:
del sysctl_args['key_path']
if sysctl_args['state'] == 'absent' and 'value' in sysctl_args:
del sysctl_args['value']
# end
return sysctl_args
# ==============================================================
def sysctl_check(current_step, **sysctl_args):
# checking coherence
if sysctl_args['state'] == 'absent' and sysctl_args['value'] is not None:
return 1, 'value=x must not be supplied when state=absent'
if sysctl_args['state'] == 'present' and sysctl_args['value'] is None:
return 1, 'value=x must be supplied when state=present'
if not sysctl_args['reload'] and sysctl_args['checks'] in ['after', 'both']:
return 1, 'checks cannot be set to after or both if reload=no'
# getting file stat
if not os.access(sysctl_args['key_path'], os.F_OK):
return 1, 'key_path is not an existing file, key seems invalid'
if not os.access(sysctl_args['key_path'], os.R_OK):
return 1, 'key_path is not a readable file, key seems to be uncheckable'
# no smart checks at this step ?
if sysctl_args['checks'] == 'none':
return 0, ''
if current_step == 'before' and sysctl_args['checks'] not in ['before', 'both']:
return 0, ''
if current_step == 'after' and sysctl_args['checks'] not in ['after', 'both']:
return 0, ''
# checks before
if current_step == 'before' and sysctl_args['checks'] in ['before', 'both']:
if not os.access(sysctl_args['key_path'], os.W_OK):
return 1, 'key_path is not a writable file, key seems to be read only'
return 0, ''
# checks after
if current_step == 'after' and sysctl_args['checks'] in ['after', 'both']:
if sysctl_args['value'] is not None:
# reading the virtual file
f = open(sysctl_args['key_path'],'r')
output = f.read()
f.close()
output = output.strip(' \t\n\r')
output = re.sub(r'\s+', ' ', output)
# multi positive integer values separated by spaces as described in issue #2004 :
if re.search('^([\d\s]+)$', sysctl_args['value']):
# replace all groups of spaces by one space
output = re.sub('(\s+)', ' ', output)
# normal case, finded value must be equal to the submitted value :
if output != sysctl_args['value']:
return 1, 'key seems not set to value even after update/sysctl, founded : <%s>, wanted : <%s>' % (output, sysctl_args['value'])
return 0, ''
# weird end
return 1, 'unexpected position reached'
# ==============================================================
# main
def main():
# defining module
module = AnsibleModule(
argument_spec = dict(
name = dict(aliases=['key'], required=True),
value = dict(aliases=['val'], required=False),
state = dict(default='present', choices=['present', 'absent']),
checks = dict(default='both', choices=['none', 'before', 'after', 'both']),
reload = dict(default=True, type='bool'),
sysctl_file = dict(default='/etc/sysctl.conf')
)
)
# defaults
sysctl_args = {
'changed': False,
'name': module.params['name'],
'state': module.params['state'],
'checks': module.params['checks'],
'reload': module.params['reload'],
'value': module.params.get('value'),
'sysctl_file': module.params['sysctl_file']
}
# prepare vars
sysctl_args = sysctl_args_expand(**sysctl_args)
new_line = "%s = %s\n" % (sysctl_args['name'], sysctl_args['value'])
to_write = []
founded = False
# make checks before act
res,msg = sysctl_check('before', **sysctl_args)
if res != 0:
module.fail_json(msg='checks_before failed with: ' + msg)
if not os.access(sysctl_args['sysctl_file'], os.W_OK):
try:
f = open(sysctl_args['sysctl_file'],'w')
f.close()
except IOError, e:
module.fail_json(msg='unable to create supplied sysctl file (destination directory probably missing)')
# reading the file
for line in open(sysctl_args['sysctl_file'], 'r').readlines():
if not line.strip():
to_write.append(line)
continue
if line.strip().startswith('#'):
to_write.append(line)
continue
if len(line.split('=')) != 2:
# not sure what this is or why it is here
# but it is not our fault so leave it be
to_write.append(line)
continue
# write line if not the one searched
ld = {}
ld['name'], ld['val'] = line.split('=')
ld['name'] = ld['name'].strip()
if ld['name'] != sysctl_args['name']:
to_write.append(line)
continue
# should be absent ?
if sysctl_args['state'] == 'absent':
# not writing the founded line
# mark as changed
sysctl_args['changed'] = True
# should be present
if sysctl_args['state'] == 'present':
# is the founded line equal to the wanted one ?
ld['val'] = ld['val'].strip()
if ld['val'] == sysctl_args['value']:
# line is equal, writing it without update (but cancel repeats)
if sysctl_args['changed'] == False and founded == False:
to_write.append(line)
founded = True
else:
# update the line (but cancel repeats)
if sysctl_args['changed'] == False and founded == False:
to_write.append(new_line)
sysctl_args['changed'] = True
continue
# if not changed, but should be present, so we have to add it
if sysctl_args['state'] == 'present' and sysctl_args['changed'] == False and founded == False:
to_write.append(new_line)
sysctl_args['changed'] = True
# has changed ?
res = 0
if sysctl_args['changed'] == True:
sysctl_args = write_sysctl(module, to_write, **sysctl_args)
res,msg = reload_sysctl(**sysctl_args)
# make checks after act
res,msg = sysctl_check('after', **sysctl_args)
if res != 0:
module.fail_json(msg='checks_after failed with: ' + msg)
# look at the next link to avoid this workaround
# https://groups.google.com/forum/?fromgroups=#!topic/ansible-project/LMY-dwF6SQk
changed = sysctl_args['changed']
del sysctl_args['changed']
# end
sysctl_args = sysctl_args_collapse(**sysctl_args)
module.exit_json(changed=changed, **sysctl_args)
sys.exit(0)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

1150
library/system/user Normal file

File diff suppressed because it is too large Load Diff

405
library/system/zfs Normal file
View File

@@ -0,0 +1,405 @@
#!/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]
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]
examples:
- code: zfs name=rpool/myfs state=present
description: Create a new file system called myfs in pool rpool
- code: zfs name=rpool/myvol state=present volsize=10M
description: Create a new volume called myvol in pool rpool.
- code: zfs name=rpool/myfs@mysnapshot state=present
description: Create a snapshot of rpool/myfs file system.
- code: zfs name=rpool/myfs2 state=present snapdir=enabled
description: Create a new file system called myfs2 with snapdir enabled
author: Johan Wiren
'''
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)]
cmd.append('set')
cmd.append(prop + '=' + value)
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_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):
cmd = [self.module.get_bin_path('zfs', True)]
cmd.append('get -H all')
cmd.append(self.name)
rc, out, err = self.module.run_command(' '.join(cmd))
properties=dict()
for l in out.splitlines():
p, v = l.split()[1:3]
properties[p] = 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():
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']},
'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)
# include magic from lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()