mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-05-08 14:22:46 +00:00
Allow modules to be categorized, and also sort them when generating the documentation.
This commit is contained in:
224
library/system/authorized_key
Normal file
224
library/system/authorized_key
Normal 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
366
library/system/cron
Normal 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
54
library/system/facter
Normal 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
278
library/system/group
Normal 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
217
library/system/lvg
Normal 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
150
library/system/lvol
Normal 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
313
library/system/mount
Normal 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
54
library/system/ohai
Normal 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
52
library/system/ping
Normal 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
210
library/system/seboolean
Normal 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
200
library/system/selinux
Normal 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
946
library/system/service
Normal 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
1681
library/system/setup
Normal file
File diff suppressed because it is too large
Load Diff
318
library/system/sysctl
Normal file
318
library/system/sysctl
Normal 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
1150
library/system/user
Normal file
File diff suppressed because it is too large
Load Diff
405
library/system/zfs
Normal file
405
library/system/zfs
Normal 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()
|
||||
Reference in New Issue
Block a user