mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-22 21:00:22 -07:00
Restructuring.
This commit is contained in:
parent
87a9034751
commit
c7eec45b73
156 changed files with 0 additions and 0 deletions
421
lib/ansible/modules/system/authorized_key
Normal file
421
lib/ansible/modules/system/authorized_key
Normal file
|
@ -0,0 +1,421 @@
|
|||
#!/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:
|
||||
- The username on the remote host whose authorized_keys file will be modified
|
||||
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 key file. If
|
||||
set, the module will create the directory, as well as set the owner and permissions
|
||||
of an existing directory. Be sure to
|
||||
set C(manage_dir=no) if you are using an alternate directory for
|
||||
authorized_keys, as set with C(path), since you could lock yourself out of
|
||||
SSH access. See the example below.
|
||||
required: false
|
||||
choices: [ "yes", "no" ]
|
||||
default: "yes"
|
||||
version_added: "1.2"
|
||||
state:
|
||||
description:
|
||||
- Whether the given key (with the given key_options) should or should not be in the file
|
||||
required: false
|
||||
choices: [ "present", "absent" ]
|
||||
default: "present"
|
||||
key_options:
|
||||
description:
|
||||
- A string of ssh key options to be prepended to the key in the authorized_keys file
|
||||
required: false
|
||||
default: null
|
||||
version_added: "1.4"
|
||||
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') }}"
|
||||
path='/etc/ssh/authorized_keys/charlie'
|
||||
manage_dir=no
|
||||
|
||||
# Using with_file
|
||||
- name: Set up authorized_keys for the deploy user
|
||||
authorized_key: user=deploy
|
||||
key="{{ item }}"
|
||||
with_file:
|
||||
- public_keys/doe-jane
|
||||
- public_keys/doe-john
|
||||
|
||||
# Using key_options:
|
||||
- authorized_key: user=charlie
|
||||
key="{{ lookup('file', '/home/charlie/.ssh/id_rsa.pub') }}"
|
||||
key_options='no-port-forwarding,host="10.0.1.1"'
|
||||
'''
|
||||
|
||||
# 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 re
|
||||
import shlex
|
||||
|
||||
class keydict(dict):
|
||||
|
||||
""" a dictionary that maintains the order of keys as they are added """
|
||||
|
||||
# http://stackoverflow.com/questions/2328235/pythonextend-the-dict-class
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(keydict,self).__init__(*args, **kw)
|
||||
self.itemlist = super(keydict,self).keys()
|
||||
def __setitem__(self, key, value):
|
||||
self.itemlist.append(key)
|
||||
super(keydict,self).__setitem__(key, value)
|
||||
def __iter__(self):
|
||||
return iter(self.itemlist)
|
||||
def keys(self):
|
||||
return self.itemlist
|
||||
def values(self):
|
||||
return [self[key] for key in self]
|
||||
def itervalues(self):
|
||||
return (self[key] for key in self)
|
||||
|
||||
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:
|
||||
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)
|
||||
|
||||
try:
|
||||
os.chown(keysfile, uid, gid)
|
||||
os.chmod(keysfile, 0600)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return keysfile
|
||||
|
||||
def parseoptions(module, options):
|
||||
'''
|
||||
reads a string containing ssh-key options
|
||||
and returns a dictionary of those options
|
||||
'''
|
||||
options_dict = keydict() #ordered dict
|
||||
if options:
|
||||
try:
|
||||
# the following regex will split on commas while
|
||||
# ignoring those commas that fall within quotes
|
||||
regex = re.compile(r'''((?:[^,"']|"[^"]*"|'[^']*')+)''')
|
||||
parts = regex.split(options)[1:-1]
|
||||
for part in parts:
|
||||
if "=" in part:
|
||||
(key, value) = part.split("=", 1)
|
||||
options_dict[key] = value
|
||||
elif part != ",":
|
||||
options_dict[part] = None
|
||||
except:
|
||||
module.fail_json(msg="invalid option string: %s" % options)
|
||||
|
||||
return options_dict
|
||||
|
||||
def parsekey(module, raw_key):
|
||||
'''
|
||||
parses a key, which may or may not contain a list
|
||||
of ssh-key options at the beginning
|
||||
'''
|
||||
|
||||
VALID_SSH2_KEY_TYPES = [
|
||||
'ssh-ed25519',
|
||||
'ecdsa-sha2-nistp256',
|
||||
'ecdsa-sha2-nistp384',
|
||||
'ecdsa-sha2-nistp521',
|
||||
'ssh-dss',
|
||||
'ssh-rsa',
|
||||
]
|
||||
|
||||
options = None # connection options
|
||||
key = None # encrypted key string
|
||||
key_type = None # type of ssh key
|
||||
type_index = None # index of keytype in key string|list
|
||||
|
||||
# remove comment yaml escapes
|
||||
raw_key = raw_key.replace('\#', '#')
|
||||
|
||||
# split key safely
|
||||
lex = shlex.shlex(raw_key)
|
||||
lex.quotes = []
|
||||
lex.commenters = '' #keep comment hashes
|
||||
lex.whitespace_split = True
|
||||
key_parts = list(lex)
|
||||
|
||||
for i in range(0, len(key_parts)):
|
||||
if key_parts[i] in VALID_SSH2_KEY_TYPES:
|
||||
type_index = i
|
||||
key_type = key_parts[i]
|
||||
break
|
||||
|
||||
# check for options
|
||||
if type_index is None:
|
||||
return None
|
||||
elif type_index > 0:
|
||||
options = " ".join(key_parts[:type_index])
|
||||
|
||||
# parse the options (if any)
|
||||
options = parseoptions(module, options)
|
||||
|
||||
# get key after the type index
|
||||
key = key_parts[(type_index + 1)]
|
||||
|
||||
# set comment to everything after the key
|
||||
if len(key_parts) > (type_index + 1):
|
||||
comment = " ".join(key_parts[(type_index + 2):])
|
||||
|
||||
return (key, key_type, options, comment)
|
||||
|
||||
def readkeys(module, filename):
|
||||
|
||||
if not os.path.isfile(filename):
|
||||
return {}
|
||||
|
||||
keys = {}
|
||||
f = open(filename)
|
||||
for line in f.readlines():
|
||||
key_data = parsekey(module, line)
|
||||
if key_data:
|
||||
# use key as identifier
|
||||
keys[key_data[0]] = key_data
|
||||
else:
|
||||
# for an invalid line, just append the line
|
||||
# to the array so it will be re-output later
|
||||
keys[line] = line
|
||||
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:
|
||||
for index, key in keys.items():
|
||||
try:
|
||||
(keyhash,type,options,comment) = key
|
||||
option_str = ""
|
||||
if options:
|
||||
option_strings = []
|
||||
for option_key in options.keys():
|
||||
if options[option_key]:
|
||||
option_strings.append("%s=%s" % (option_key, options[option_key]))
|
||||
else:
|
||||
option_strings.append("%s" % option_key)
|
||||
|
||||
option_str = ",".join(option_strings)
|
||||
option_str += " "
|
||||
key_line = "%s%s %s %s\n" % (option_str, type, keyhash, comment)
|
||||
except:
|
||||
key_line = key
|
||||
f.writelines(key_line)
|
||||
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_options = params.get("key_options", None)
|
||||
|
||||
# extract indivial keys into an array, skipping blank lines and comments
|
||||
key = [s for s in key.splitlines() if s and not s.startswith('#')]
|
||||
|
||||
|
||||
# check current state -- just get the filename, don't create file
|
||||
do_write = False
|
||||
params["keyfile"] = keyfile(module, user, do_write, path, manage_dir)
|
||||
existing_keys = readkeys(module, params["keyfile"])
|
||||
|
||||
# Check our new keys, if any of them exist we'll continue.
|
||||
for new_key in key:
|
||||
parsed_new_key = parsekey(module, new_key)
|
||||
if key_options is not None:
|
||||
parsed_options = parseoptions(module, key_options)
|
||||
parsed_new_key = (parsed_new_key[0], parsed_new_key[1], parsed_options, parsed_new_key[3])
|
||||
|
||||
if not parsed_new_key:
|
||||
module.fail_json(msg="invalid key specified: %s" % new_key)
|
||||
|
||||
present = False
|
||||
matched = False
|
||||
non_matching_keys = []
|
||||
|
||||
if parsed_new_key[0] in existing_keys:
|
||||
present = True
|
||||
# Then we check if everything matches, including
|
||||
# the key type and options. If not, we append this
|
||||
# existing key to the non-matching list
|
||||
# We only want it to match everything when the state
|
||||
# is present
|
||||
if parsed_new_key != existing_keys[parsed_new_key[0]] and state == "present":
|
||||
non_matching_keys.append(existing_keys[parsed_new_key[0]])
|
||||
else:
|
||||
matched = True
|
||||
|
||||
|
||||
# handle idempotent state=present
|
||||
if state=="present":
|
||||
if len(non_matching_keys) > 0:
|
||||
for non_matching_key in non_matching_keys:
|
||||
if non_matching_key[0] in existing_keys:
|
||||
del existing_keys[non_matching_key[0]]
|
||||
do_write = True
|
||||
|
||||
if not matched:
|
||||
existing_keys[parsed_new_key[0]] = parsed_new_key
|
||||
do_write = True
|
||||
|
||||
elif state=="absent":
|
||||
if not matched:
|
||||
continue
|
||||
del existing_keys[parsed_new_key[0]]
|
||||
do_write = True
|
||||
|
||||
if do_write:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
writekeys(module, keyfile(module, user, do_write, path, manage_dir), existing_keys)
|
||||
params['changed'] = True
|
||||
else:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=False)
|
||||
|
||||
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', default=True),
|
||||
state = dict(default='present', choices=['absent','present']),
|
||||
key_options = dict(required=False, type='str'),
|
||||
unique = dict(default=False, type='bool'),
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
results = enforce_state(module, module.params)
|
||||
module.exit_json(**results)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
524
lib/ansible/modules/system/cron
Normal file
524
lib/ansible/modules/system/cron
Normal file
|
@ -0,0 +1,524 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2012, Dane Summers <dsummers@pinedesk.biz>
|
||||
# (c) 2013, Mike Grozak <mike.grozak@gmail.com>
|
||||
# (c) 2013, Patrick Callahan <pmc@patrickcallahan.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.
|
||||
#
|
||||
# This module is based on python-crontab by Martin Owens.
|
||||
#
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: cron
|
||||
short_description: Manage cron.d and 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. The "name" parameter should be unique, and changing the "name" value
|
||||
will result in a new cron task being created (or a different one being removed)'
|
||||
version_added: "0.9"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- Description of a crontab entry.
|
||||
default: null
|
||||
required: true
|
||||
user:
|
||||
description:
|
||||
- The specific user whose crontab should be modified.
|
||||
required: false
|
||||
default: root
|
||||
job:
|
||||
description:
|
||||
- The command to execute. Required if state=present.
|
||||
required: false
|
||||
default: null
|
||||
state:
|
||||
description:
|
||||
- Whether to ensure the job is present or absent.
|
||||
required: false
|
||||
default: present
|
||||
choices: [ "present", "absent" ]
|
||||
cron_file:
|
||||
description:
|
||||
- If specified, uses this file in cron.d instead of an individual user's crontab.
|
||||
required: false
|
||||
default: null
|
||||
backup:
|
||||
description:
|
||||
- If set, 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
|
||||
minute:
|
||||
description:
|
||||
- Minute when the job should run ( 0-59, *, */2, etc )
|
||||
required: false
|
||||
default: "*"
|
||||
hour:
|
||||
description:
|
||||
- Hour when the job should run ( 0-23, *, */2, etc )
|
||||
required: false
|
||||
default: "*"
|
||||
day:
|
||||
description:
|
||||
- Day of the month the job should run ( 1-31, *, */2, etc )
|
||||
required: false
|
||||
default: "*"
|
||||
aliases: [ "dom" ]
|
||||
month:
|
||||
description:
|
||||
- Month of the year the job should run ( 1-12, *, */2, etc )
|
||||
required: false
|
||||
default: "*"
|
||||
weekday:
|
||||
description:
|
||||
- Day of the week that the job should run ( 0-6 for Sunday-Saturday, *, etc )
|
||||
required: false
|
||||
default: "*"
|
||||
aliases: [ "dow" ]
|
||||
reboot:
|
||||
description:
|
||||
- If the job should be run at reboot. This option is deprecated. Users should use special_time.
|
||||
version_added: "1.0"
|
||||
required: false
|
||||
default: "no"
|
||||
choices: [ "yes", "no" ]
|
||||
special_time:
|
||||
description:
|
||||
- Special time specification nickname.
|
||||
version_added: "1.3"
|
||||
required: false
|
||||
default: null
|
||||
choices: [ "reboot", "yearly", "annually", "monthly", "weekly", "daily", "hourly" ]
|
||||
requirements:
|
||||
- cron
|
||||
author: Dane Summers
|
||||
updates: [ 'Mike Grozak', 'Patrick Callahan' ]
|
||||
"""
|
||||
|
||||
EXAMPLES = '''
|
||||
# Ensure a job that runs at 2 and 5 exists.
|
||||
# Creates an entry like "* 5,2 * * ls -alh > /dev/null"
|
||||
- cron: name="check dirs" hour="5,2" job="ls -alh > /dev/null"
|
||||
|
||||
# Ensure an old job is no longer present. Removes any job that is prefixed
|
||||
# by "#Ansible: an old job" from the crontab
|
||||
- cron: name="an old job" state=absent
|
||||
|
||||
# Creates an entry like "@reboot /some/job.sh"
|
||||
- cron: name="a job for reboot" special_time=reboot job="/some/job.sh"
|
||||
|
||||
# Creates a cron file under /etc/cron.d
|
||||
- cron: name="yum autoupdate" weekday="2" minute=0 hour=12
|
||||
user="root" job="YUMINTERACTIVE=0 /usr/sbin/yum-autoupdate"
|
||||
cron_file=ansible_yum-autoupdate
|
||||
|
||||
# Removes a cron file from under /etc/cron.d
|
||||
- cron: cron_file=ansible_yum-autoupdate state=absent
|
||||
'''
|
||||
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
import platform
|
||||
import pipes
|
||||
|
||||
CRONCMD = "/usr/bin/crontab"
|
||||
|
||||
class CronTabError(Exception):
|
||||
pass
|
||||
|
||||
class CronTab(object):
|
||||
"""
|
||||
CronTab object to write time based crontab file
|
||||
|
||||
user - the user of the crontab (defaults to root)
|
||||
cron_file - a cron file under /etc/cron.d
|
||||
"""
|
||||
def __init__(self, module, user=None, cron_file=None):
|
||||
self.module = module
|
||||
self.user = user
|
||||
self.root = (os.getuid() == 0)
|
||||
self.lines = None
|
||||
self.ansible = "#Ansible: "
|
||||
|
||||
# select whether we dump additional debug info through syslog
|
||||
self.syslogging = False
|
||||
|
||||
if cron_file:
|
||||
self.cron_file = '/etc/cron.d/%s' % cron_file
|
||||
else:
|
||||
self.cron_file = None
|
||||
|
||||
self.read()
|
||||
|
||||
def read(self):
|
||||
# Read in the crontab from the system
|
||||
self.lines = []
|
||||
if self.cron_file:
|
||||
# read the cronfile
|
||||
try:
|
||||
f = open(self.cron_file, 'r')
|
||||
self.lines = f.read().splitlines()
|
||||
f.close()
|
||||
except IOError, e:
|
||||
# cron file does not exist
|
||||
return
|
||||
except:
|
||||
raise CronTabError("Unexpected error:", sys.exc_info()[0])
|
||||
else:
|
||||
# using safely quoted shell for now, but this really should be two non-shell calls instead. FIXME
|
||||
(rc, out, err) = self.module.run_command(self._read_user_execute(), use_unsafe_shell=True)
|
||||
|
||||
if rc != 0 and rc != 1: # 1 can mean that there are no jobs.
|
||||
raise CronTabError("Unable to read crontab")
|
||||
|
||||
lines = out.splitlines()
|
||||
count = 0
|
||||
for l in lines:
|
||||
if count > 2 or (not re.match( r'# DO NOT EDIT THIS FILE - edit the master and reinstall.', l) and
|
||||
not re.match( r'# \(/tmp/.*installed on.*\)', l) and
|
||||
not re.match( r'# \(.*version.*\)', l)):
|
||||
self.lines.append(l)
|
||||
count += 1
|
||||
|
||||
def log_message(self, message):
|
||||
if self.syslogging:
|
||||
syslog.syslog(syslog.LOG_NOTICE, 'ansible: "%s"' % message)
|
||||
|
||||
def is_empty(self):
|
||||
if len(self.lines) == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def write(self, backup_file=None):
|
||||
"""
|
||||
Write the crontab to the system. Saves all information.
|
||||
"""
|
||||
if backup_file:
|
||||
fileh = open(backup_file, 'w')
|
||||
elif self.cron_file:
|
||||
fileh = open(self.cron_file, 'w')
|
||||
else:
|
||||
filed, path = tempfile.mkstemp(prefix='crontab')
|
||||
fileh = os.fdopen(filed, 'w')
|
||||
|
||||
fileh.write(self.render())
|
||||
fileh.close()
|
||||
|
||||
# return if making a backup
|
||||
if backup_file:
|
||||
return
|
||||
|
||||
# Add the entire crontab back to the user crontab
|
||||
if not self.cron_file:
|
||||
# quoting shell args for now but really this should be two non-shell calls. FIXME
|
||||
(rc, out, err) = self.module.run_command(self._write_execute(path), use_unsafe_shell=True)
|
||||
os.unlink(path)
|
||||
|
||||
if rc != 0:
|
||||
self.module.fail_json(msg=err)
|
||||
|
||||
def add_job(self, name, job):
|
||||
# Add the comment
|
||||
self.lines.append("%s%s" % (self.ansible, name))
|
||||
|
||||
# Add the job
|
||||
self.lines.append("%s" % (job))
|
||||
|
||||
def update_job(self, name, job):
|
||||
return self._update_job(name, job, self.do_add_job)
|
||||
|
||||
def do_add_job(self, lines, comment, job):
|
||||
lines.append(comment)
|
||||
|
||||
lines.append("%s" % (job))
|
||||
|
||||
def remove_job(self, name):
|
||||
return self._update_job(name, "", self.do_remove_job)
|
||||
|
||||
def do_remove_job(self, lines, comment, job):
|
||||
return None
|
||||
|
||||
def remove_job_file(self):
|
||||
try:
|
||||
os.unlink(self.cron_file)
|
||||
return True
|
||||
except OSError, e:
|
||||
# cron file does not exist
|
||||
return False
|
||||
except:
|
||||
raise CronTabError("Unexpected error:", sys.exc_info()[0])
|
||||
|
||||
def find_job(self, name):
|
||||
comment = None
|
||||
for l in self.lines:
|
||||
if comment is not None:
|
||||
if comment == name:
|
||||
return [comment, l]
|
||||
else:
|
||||
comment = None
|
||||
elif re.match( r'%s' % self.ansible, l):
|
||||
comment = re.sub( r'%s' % self.ansible, '', l)
|
||||
|
||||
return []
|
||||
|
||||
def get_cron_job(self,minute,hour,day,month,weekday,job,special):
|
||||
if special:
|
||||
if self.cron_file:
|
||||
return "@%s %s %s" % (special, self.user, job)
|
||||
else:
|
||||
return "@%s %s" % (special, job)
|
||||
else:
|
||||
if self.cron_file:
|
||||
return "%s %s %s %s %s %s %s" % (minute,hour,day,month,weekday,self.user,job)
|
||||
else:
|
||||
return "%s %s %s %s %s %s" % (minute,hour,day,month,weekday,job)
|
||||
|
||||
return None
|
||||
|
||||
def get_jobnames(self):
|
||||
jobnames = []
|
||||
|
||||
for l in self.lines:
|
||||
if re.match( r'%s' % self.ansible, l):
|
||||
jobnames.append(re.sub( r'%s' % self.ansible, '', l))
|
||||
|
||||
return jobnames
|
||||
|
||||
def _update_job(self, name, job, addlinesfunction):
|
||||
ansiblename = "%s%s" % (self.ansible, name)
|
||||
newlines = []
|
||||
comment = None
|
||||
|
||||
for l in self.lines:
|
||||
if comment is not None:
|
||||
addlinesfunction(newlines, comment, job)
|
||||
comment = None
|
||||
elif l == ansiblename:
|
||||
comment = l
|
||||
else:
|
||||
newlines.append(l)
|
||||
|
||||
self.lines = newlines
|
||||
|
||||
if len(newlines) == 0:
|
||||
return True
|
||||
else:
|
||||
return False # TODO add some more error testing
|
||||
|
||||
def render(self):
|
||||
"""
|
||||
Render this crontab as it would be in the crontab.
|
||||
"""
|
||||
crons = []
|
||||
for cron in self.lines:
|
||||
crons.append(cron)
|
||||
|
||||
result = '\n'.join(crons)
|
||||
if result and result[-1] not in ['\n', '\r']:
|
||||
result += '\n'
|
||||
return result
|
||||
|
||||
def _read_user_execute(self):
|
||||
"""
|
||||
Returns the command line for reading a crontab
|
||||
"""
|
||||
user = ''
|
||||
if self.user:
|
||||
if platform.system() == 'SunOS':
|
||||
return "su %s -c '%s -l'" % (pipes.quote(self.user), pipes.quote(CRONCMD))
|
||||
elif platform.system() == 'AIX':
|
||||
return "%s -l %s" % (pipes.quote(CRONCMD), pipes.quote(self.user))
|
||||
elif platform.system() == 'HP-UX':
|
||||
return "%s %s %s" % (CRONCMD , '-l', pipes.quote(self.user))
|
||||
else:
|
||||
user = '-u %s' % pipes.quote(self.user)
|
||||
return "%s %s %s" % (CRONCMD , user, '-l')
|
||||
|
||||
def _write_execute(self, path):
|
||||
"""
|
||||
Return the command line for writing a crontab
|
||||
"""
|
||||
user = ''
|
||||
if self.user:
|
||||
if platform.system() in ['SunOS', 'HP-UX', 'AIX']:
|
||||
return "chown %s %s ; su '%s' -c '%s %s'" % (pipes.quote(self.user), pipes.quote(path), pipes.quote(self.user), CRONCMD, pipes.quote(path))
|
||||
else:
|
||||
user = '-u %s' % pipes.quote(self.user)
|
||||
return "%s %s %s" % (CRONCMD , user, pipes.quote(path))
|
||||
|
||||
|
||||
|
||||
#==================================================
|
||||
|
||||
def main():
|
||||
# The following example playbooks:
|
||||
#
|
||||
# - cron: name="check dirs" hour="5,2" job="ls -alh > /dev/null"
|
||||
#
|
||||
# - name: do the job
|
||||
# cron: name="do the job" hour="5,2" job="/some/dir/job.sh"
|
||||
#
|
||||
# - name: no job
|
||||
# cron: name="an old job" state=absent
|
||||
#
|
||||
# Would produce:
|
||||
# # Ansible: check dirs
|
||||
# * * 5,2 * * ls -alh > /dev/null
|
||||
# # Ansible: do the job
|
||||
# * * 5,2 * * /some/dir/job.sh
|
||||
|
||||
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(aliases=['dom'], default='*'),
|
||||
month=dict(default='*'),
|
||||
weekday=dict(aliases=['dow'], default='*'),
|
||||
reboot=dict(required=False, default=False, type='bool'),
|
||||
special_time=dict(required=False,
|
||||
default=None,
|
||||
choices=["reboot", "yearly", "annually", "monthly", "weekly", "daily", "hourly"],
|
||||
type='str')
|
||||
),
|
||||
supports_check_mode = False,
|
||||
)
|
||||
|
||||
name = module.params['name']
|
||||
user = module.params['user']
|
||||
job = module.params['job']
|
||||
cron_file = module.params['cron_file']
|
||||
state = module.params['state']
|
||||
backup = module.params['backup']
|
||||
minute = module.params['minute']
|
||||
hour = module.params['hour']
|
||||
day = module.params['day']
|
||||
month = module.params['month']
|
||||
weekday = module.params['weekday']
|
||||
reboot = module.params['reboot']
|
||||
special_time = module.params['special_time']
|
||||
do_install = state == 'present'
|
||||
|
||||
changed = False
|
||||
res_args = dict()
|
||||
|
||||
# Ensure all files generated are only writable by the owning user. Primarily relevant for the cron_file option.
|
||||
os.umask(022)
|
||||
crontab = CronTab(module, user, cron_file)
|
||||
|
||||
if crontab.syslogging:
|
||||
syslog.openlog('ansible-%s' % os.path.basename(__file__))
|
||||
syslog.syslog(syslog.LOG_NOTICE, 'cron instantiated - name: "%s"' % name)
|
||||
|
||||
# --- user input validation ---
|
||||
|
||||
if (special_time or reboot) and \
|
||||
(True in [(x != '*') for x in [minute, hour, day, month, weekday]]):
|
||||
module.fail_json(msg="You must specify time and date fields or special time.")
|
||||
|
||||
if cron_file and do_install:
|
||||
if not user:
|
||||
module.fail_json(msg="To use cron_file=... parameter you must specify user=... as well")
|
||||
|
||||
if reboot and special_time:
|
||||
module.fail_json(msg="reboot and special_time are mutually exclusive")
|
||||
|
||||
if name is None and do_install:
|
||||
module.fail_json(msg="You must specify 'name' to install a new cron job")
|
||||
|
||||
if job is None and do_install:
|
||||
module.fail_json(msg="You must specify 'job' to install a new cron job")
|
||||
|
||||
if job and name is None and not do_install:
|
||||
module.fail_json(msg="You must specify 'name' to remove a cron job")
|
||||
|
||||
if reboot:
|
||||
if special_time:
|
||||
module.fail_json(msg="reboot and special_time are mutually exclusive")
|
||||
else:
|
||||
special_time = "reboot"
|
||||
|
||||
# if requested make a backup before making a change
|
||||
if backup:
|
||||
(backuph, backup_file) = tempfile.mkstemp(prefix='crontab')
|
||||
crontab.write(backup_file)
|
||||
|
||||
if crontab.cron_file and not name and not do_install:
|
||||
changed = crontab.remove_job_file()
|
||||
module.exit_json(changed=changed,cron_file=cron_file,state=state)
|
||||
|
||||
job = crontab.get_cron_job(minute, hour, day, month, weekday, job, special_time)
|
||||
old_job = crontab.find_job(name)
|
||||
|
||||
if do_install:
|
||||
if len(old_job) == 0:
|
||||
crontab.add_job(name, job)
|
||||
changed = True
|
||||
if len(old_job) > 0 and old_job[1] != job:
|
||||
crontab.update_job(name, job)
|
||||
changed = True
|
||||
else:
|
||||
if len(old_job) > 0:
|
||||
crontab.remove_job(name)
|
||||
changed = True
|
||||
|
||||
res_args = dict(
|
||||
jobs = crontab.get_jobnames(), changed = changed
|
||||
)
|
||||
|
||||
if changed:
|
||||
crontab.write()
|
||||
|
||||
# retain the backup only if crontab or cron file have changed
|
||||
if backup:
|
||||
if changed:
|
||||
res_args['backup_file'] = backup_file
|
||||
else:
|
||||
os.unlink(backup_file)
|
||||
|
||||
if cron_file:
|
||||
res_args['cron_file'] = cron_file
|
||||
|
||||
module.exit_json(**res_args)
|
||||
|
||||
# --- should never get here
|
||||
module.exit_json(msg="Unable to execute cron task.")
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
main()
|
||||
|
403
lib/ansible/modules/system/group
Normal file
403
lib/ansible/modules/system/group
Normal file
|
@ -0,0 +1,403 @@
|
|||
#!/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 = '''
|
||||
# Example group command from Ansible Playbooks
|
||||
- group: name=somegroup state=present
|
||||
'''
|
||||
|
||||
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] == True:
|
||||
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 (0, '', '')
|
||||
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 doesn't 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] == True:
|
||||
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 (0, '', '')
|
||||
cmd.append(self.name)
|
||||
return self.execute_command(cmd)
|
||||
|
||||
# ===========================================
|
||||
|
||||
class FreeBsdGroup(Group):
|
||||
"""
|
||||
This is a FreeBSD Group manipulation class.
|
||||
|
||||
This overrides the following methods from the generic class:-
|
||||
- group_del()
|
||||
- group_add()
|
||||
- group_mod()
|
||||
"""
|
||||
|
||||
platform = 'FreeBSD'
|
||||
distribution = None
|
||||
GROUPFILE = '/etc/group'
|
||||
|
||||
def group_del(self):
|
||||
cmd = [self.module.get_bin_path('pw', True), 'groupdel', self.name]
|
||||
return self.execute_command(cmd)
|
||||
|
||||
def group_add(self, **kwargs):
|
||||
cmd = [self.module.get_bin_path('pw', True), 'groupadd', self.name]
|
||||
if self.gid is not None:
|
||||
cmd.append('-g %d' % int(self.gid))
|
||||
return self.execute_command(cmd)
|
||||
|
||||
def group_mod(self, **kwargs):
|
||||
cmd = [self.module.get_bin_path('pw', True), 'groupmod', self.name]
|
||||
info = self.group_info()
|
||||
cmd_len = len(cmd)
|
||||
if self.gid is not None and int(self.gid) != info[2]:
|
||||
cmd.append('-g %d' % int(self.gid))
|
||||
# modify the group if cmd will do anything
|
||||
if cmd_len != len(cmd):
|
||||
if self.module.check_mode:
|
||||
return (0, '', '')
|
||||
return self.execute_command(cmd)
|
||||
return (None, '', '')
|
||||
|
||||
# ===========================================
|
||||
|
||||
class OpenBsdGroup(Group):
|
||||
"""
|
||||
This is a OpenBSD Group manipulation class.
|
||||
|
||||
This overrides the following methods from the generic class:-
|
||||
- group_del()
|
||||
- group_add()
|
||||
- group_mod()
|
||||
"""
|
||||
|
||||
platform = 'OpenBSD'
|
||||
distribution = None
|
||||
GROUPFILE = '/etc/group'
|
||||
|
||||
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)]
|
||||
if self.gid is not None:
|
||||
cmd.append('-g')
|
||||
cmd.append('%d' % int(self.gid))
|
||||
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()
|
||||
cmd_len = len(cmd)
|
||||
if self.gid is not None and int(self.gid) != info[2]:
|
||||
cmd.append('-g')
|
||||
cmd.append('%d' % int(self.gid))
|
||||
if len(cmd) == 1:
|
||||
return (None, '', '')
|
||||
if self.module.check_mode:
|
||||
return (0, '', '')
|
||||
cmd.append(self.name)
|
||||
return self.execute_command(cmd)
|
||||
|
||||
# ===========================================
|
||||
|
||||
class NetBsdGroup(Group):
|
||||
"""
|
||||
This is a NetBSD Group manipulation class.
|
||||
|
||||
This overrides the following methods from the generic class:-
|
||||
- group_del()
|
||||
- group_add()
|
||||
- group_mod()
|
||||
"""
|
||||
|
||||
platform = 'NetBSD'
|
||||
distribution = None
|
||||
GROUPFILE = '/etc/group'
|
||||
|
||||
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)]
|
||||
if self.gid is not None:
|
||||
cmd.append('-g')
|
||||
cmd.append('%d' % int(self.gid))
|
||||
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()
|
||||
cmd_len = len(cmd)
|
||||
if self.gid is not None and int(self.gid) != info[2]:
|
||||
cmd.append('-g')
|
||||
cmd.append('%d' % int(self.gid))
|
||||
if len(cmd) == 1:
|
||||
return (None, '', '')
|
||||
if self.module.check_mode:
|
||||
return (0, '', '')
|
||||
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=False, 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)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
445
lib/ansible/modules/system/hostname
Executable file
445
lib/ansible/modules/system/hostname
Executable file
|
@ -0,0 +1,445 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2013, Hiroaki Nakamura <hnakamur@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: hostname
|
||||
author: Hiroaki Nakamura
|
||||
version_added: "1.4"
|
||||
short_description: Manage hostname
|
||||
requirements: [ hostname ]
|
||||
description:
|
||||
- Set system's hostname
|
||||
- Currently implemented on Debian, Ubuntu, Fedora, RedHat, openSUSE, Linaro, ScientificLinux, Arch, CentOS, AMI.
|
||||
options:
|
||||
name:
|
||||
required: true
|
||||
description:
|
||||
- Name of the host
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- hostname: name=web01
|
||||
'''
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
|
||||
# wrap get_distribution_version in case it returns a string
|
||||
def _get_distribution_version():
|
||||
distribution_version = get_distribution_version()
|
||||
|
||||
if type(distribution_version) is str:
|
||||
distribution_version = 0
|
||||
elif type(distribution_version) is None:
|
||||
distribution_version = 0
|
||||
|
||||
return distribution_version
|
||||
|
||||
|
||||
class UnimplementedStrategy(object):
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
|
||||
def get_current_hostname(self):
|
||||
self.unimplemented_error()
|
||||
|
||||
def set_current_hostname(self, name):
|
||||
self.unimplemented_error()
|
||||
|
||||
def get_permanent_hostname(self):
|
||||
self.unimplemented_error()
|
||||
|
||||
def set_permanent_hostname(self, name):
|
||||
self.unimplemented_error()
|
||||
|
||||
def unimplemented_error(self):
|
||||
platform = get_platform()
|
||||
distribution = get_distribution()
|
||||
if distribution is not None:
|
||||
msg_platform = '%s (%s)' % (platform, distribution)
|
||||
else:
|
||||
msg_platform = platform
|
||||
self.module.fail_json(
|
||||
msg='hostname module cannot be used on platform %s' % msg_platform)
|
||||
|
||||
class Hostname(object):
|
||||
"""
|
||||
This is a generic Hostname manipulation class that is subclassed
|
||||
based on platform.
|
||||
|
||||
A subclass may wish to set different strategy instance to self.strategy.
|
||||
|
||||
All subclasses MUST define platform and distribution (which may be None).
|
||||
"""
|
||||
|
||||
platform = 'Generic'
|
||||
distribution = None
|
||||
strategy_class = UnimplementedStrategy
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
return load_platform_subclass(Hostname, args, kwargs)
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.name = module.params['name']
|
||||
self.strategy = self.strategy_class(module)
|
||||
|
||||
def get_current_hostname(self):
|
||||
return self.strategy.get_current_hostname()
|
||||
|
||||
def set_current_hostname(self, name):
|
||||
self.strategy.set_current_hostname(name)
|
||||
|
||||
def get_permanent_hostname(self):
|
||||
return self.strategy.get_permanent_hostname()
|
||||
|
||||
def set_permanent_hostname(self, name):
|
||||
self.strategy.set_permanent_hostname(name)
|
||||
|
||||
class GenericStrategy(object):
|
||||
"""
|
||||
This is a generic Hostname manipulation strategy class.
|
||||
|
||||
A subclass may wish to override some or all of these methods.
|
||||
- get_current_hostname()
|
||||
- get_permanent_hostname()
|
||||
- set_current_hostname(name)
|
||||
- set_permanent_hostname(name)
|
||||
"""
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
|
||||
HOSTNAME_CMD = '/bin/hostname'
|
||||
|
||||
def get_current_hostname(self):
|
||||
cmd = [self.HOSTNAME_CMD]
|
||||
rc, out, err = self.module.run_command(cmd)
|
||||
if rc != 0:
|
||||
self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" %
|
||||
(rc, out, err))
|
||||
return out.strip()
|
||||
|
||||
def set_current_hostname(self, name):
|
||||
cmd = [self.HOSTNAME_CMD, name]
|
||||
rc, out, err = self.module.run_command(cmd)
|
||||
if rc != 0:
|
||||
self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" %
|
||||
(rc, out, err))
|
||||
|
||||
def get_permanent_hostname(self):
|
||||
return None
|
||||
|
||||
def set_permanent_hostname(self, name):
|
||||
pass
|
||||
|
||||
|
||||
# ===========================================
|
||||
|
||||
class DebianStrategy(GenericStrategy):
|
||||
"""
|
||||
This is a Debian family Hostname manipulation strategy class - it edits
|
||||
the /etc/hostname file.
|
||||
"""
|
||||
|
||||
HOSTNAME_FILE = '/etc/hostname'
|
||||
|
||||
def get_permanent_hostname(self):
|
||||
if not os.path.isfile(self.HOSTNAME_FILE):
|
||||
try:
|
||||
open(self.HOSTNAME_FILE, "a").write("")
|
||||
except IOError, err:
|
||||
self.module.fail_json(msg="failed to write file: %s" %
|
||||
str(err))
|
||||
try:
|
||||
f = open(self.HOSTNAME_FILE)
|
||||
try:
|
||||
return f.read().strip()
|
||||
finally:
|
||||
f.close()
|
||||
except Exception, err:
|
||||
self.module.fail_json(msg="failed to read hostname: %s" %
|
||||
str(err))
|
||||
|
||||
def set_permanent_hostname(self, name):
|
||||
try:
|
||||
f = open(self.HOSTNAME_FILE, 'w+')
|
||||
try:
|
||||
f.write("%s\n" % name)
|
||||
finally:
|
||||
f.close()
|
||||
except Exception, err:
|
||||
self.module.fail_json(msg="failed to update hostname: %s" %
|
||||
str(err))
|
||||
|
||||
|
||||
# ===========================================
|
||||
|
||||
class RedHatStrategy(GenericStrategy):
|
||||
"""
|
||||
This is a Redhat Hostname strategy class - it edits the
|
||||
/etc/sysconfig/network file.
|
||||
"""
|
||||
NETWORK_FILE = '/etc/sysconfig/network'
|
||||
|
||||
def get_permanent_hostname(self):
|
||||
try:
|
||||
f = open(self.NETWORK_FILE, 'rb')
|
||||
try:
|
||||
for line in f.readlines():
|
||||
if line.startswith('HOSTNAME'):
|
||||
k, v = line.split('=')
|
||||
return v.strip()
|
||||
finally:
|
||||
f.close()
|
||||
except Exception, err:
|
||||
self.module.fail_json(msg="failed to read hostname: %s" %
|
||||
str(err))
|
||||
|
||||
def set_permanent_hostname(self, name):
|
||||
try:
|
||||
lines = []
|
||||
found = False
|
||||
f = open(self.NETWORK_FILE, 'rb')
|
||||
try:
|
||||
for line in f.readlines():
|
||||
if line.startswith('HOSTNAME'):
|
||||
lines.append("HOSTNAME=%s\n" % name)
|
||||
found = True
|
||||
else:
|
||||
lines.append(line)
|
||||
finally:
|
||||
f.close()
|
||||
if not found:
|
||||
lines.append("HOSTNAME=%s\n" % name)
|
||||
f = open(self.NETWORK_FILE, 'w+')
|
||||
try:
|
||||
f.writelines(lines)
|
||||
finally:
|
||||
f.close()
|
||||
except Exception, err:
|
||||
self.module.fail_json(msg="failed to update hostname: %s" %
|
||||
str(err))
|
||||
|
||||
|
||||
# ===========================================
|
||||
|
||||
class FedoraStrategy(GenericStrategy):
|
||||
"""
|
||||
This is a Fedora family Hostname manipulation strategy class - it uses
|
||||
the hostnamectl command.
|
||||
"""
|
||||
|
||||
def get_current_hostname(self):
|
||||
cmd = ['hostname']
|
||||
rc, out, err = self.module.run_command(cmd)
|
||||
if rc != 0:
|
||||
self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" %
|
||||
(rc, out, err))
|
||||
return out.strip()
|
||||
|
||||
def set_current_hostname(self, name):
|
||||
cmd = ['hostnamectl', '--transient', 'set-hostname', name]
|
||||
rc, out, err = self.module.run_command(cmd)
|
||||
if rc != 0:
|
||||
self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" %
|
||||
(rc, out, err))
|
||||
|
||||
def get_permanent_hostname(self):
|
||||
cmd = 'hostnamectl --static status'
|
||||
rc, out, err = self.module.run_command(cmd, use_unsafe_shell=True)
|
||||
if rc != 0:
|
||||
self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" %
|
||||
(rc, out, err))
|
||||
return out.strip()
|
||||
|
||||
def set_permanent_hostname(self, name):
|
||||
cmd = ['hostnamectl', '--pretty', 'set-hostname', name]
|
||||
rc, out, err = self.module.run_command(cmd)
|
||||
if rc != 0:
|
||||
self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" %
|
||||
(rc, out, err))
|
||||
cmd = ['hostnamectl', '--static', 'set-hostname', name]
|
||||
rc, out, err = self.module.run_command(cmd)
|
||||
if rc != 0:
|
||||
self.module.fail_json(msg="Command failed rc=%d, out=%s, err=%s" %
|
||||
(rc, out, err))
|
||||
|
||||
|
||||
# ===========================================
|
||||
|
||||
class OpenRCStrategy(GenericStrategy):
|
||||
"""
|
||||
This is a Gentoo (OpenRC) Hostname manipulation strategy class - it edits
|
||||
the /etc/conf.d/hostname file.
|
||||
"""
|
||||
|
||||
HOSTNAME_FILE = '/etc/conf.d/hostname'
|
||||
|
||||
def get_permanent_hostname(self):
|
||||
try:
|
||||
with open(self.HOSTNAME_FILE, 'r') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith('hostname='):
|
||||
return line[10:].strip('"')
|
||||
return None
|
||||
except Exception, err:
|
||||
self.module.fail_json(msg="failed to read hostname: %s" %
|
||||
str(err))
|
||||
|
||||
def set_permanent_hostname(self, name):
|
||||
try:
|
||||
with open(self.HOSTNAME_FILE, 'r') as f:
|
||||
lines = [x.strip() for x in f]
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith('hostname='):
|
||||
lines[i] = 'hostname="%s"' % name
|
||||
break
|
||||
|
||||
with open(self.HOSTNAME_FILE, 'w') as f:
|
||||
f.write('\n'.join(lines) + '\n')
|
||||
except Exception, err:
|
||||
self.module.fail_json(msg="failed to update hostname: %s" %
|
||||
str(err))
|
||||
|
||||
# ===========================================
|
||||
|
||||
class FedoraHostname(Hostname):
|
||||
platform = 'Linux'
|
||||
distribution = 'Fedora'
|
||||
strategy_class = FedoraStrategy
|
||||
|
||||
class OpenSUSEHostname(Hostname):
|
||||
platform = 'Linux'
|
||||
distribution = 'Opensuse '
|
||||
strategy_class = FedoraStrategy
|
||||
|
||||
class ArchHostname(Hostname):
|
||||
platform = 'Linux'
|
||||
distribution = 'Arch'
|
||||
strategy_class = FedoraStrategy
|
||||
|
||||
class RedHat5Hostname(Hostname):
|
||||
platform = 'Linux'
|
||||
distribution = 'Redhat'
|
||||
strategy_class = RedHatStrategy
|
||||
|
||||
class RedHatServerHostname(Hostname):
|
||||
platform = 'Linux'
|
||||
distribution = 'Red hat enterprise linux server'
|
||||
distribution_version = _get_distribution_version()
|
||||
if distribution_version and LooseVersion(distribution_version) >= LooseVersion("7"):
|
||||
strategy_class = FedoraStrategy
|
||||
else:
|
||||
strategy_class = RedHatStrategy
|
||||
|
||||
class RedHatWorkstationHostname(Hostname):
|
||||
platform = 'Linux'
|
||||
distribution = 'Red hat enterprise linux workstation'
|
||||
distribution_version = _get_distribution_version()
|
||||
if distribution_version and LooseVersion(distribution_version) >= LooseVersion("7"):
|
||||
strategy_class = FedoraStrategy
|
||||
else:
|
||||
strategy_class = RedHatStrategy
|
||||
|
||||
class CentOSHostname(Hostname):
|
||||
platform = 'Linux'
|
||||
distribution = 'Centos'
|
||||
distribution_version = _get_distribution_version()
|
||||
if distribution_version and LooseVersion(distribution_version) >= LooseVersion("7"):
|
||||
strategy_class = FedoraStrategy
|
||||
else:
|
||||
strategy_class = RedHatStrategy
|
||||
|
||||
class CentOSLinuxHostname(Hostname):
|
||||
platform = 'Linux'
|
||||
distribution = 'Centos linux'
|
||||
distribution_version = _get_distribution_version()
|
||||
if distribution_version and LooseVersion(distribution_version) >= LooseVersion("7"):
|
||||
strategy_class = FedoraStrategy
|
||||
else:
|
||||
strategy_class = RedHatStrategy
|
||||
|
||||
class ScientificHostname(Hostname):
|
||||
platform = 'Linux'
|
||||
distribution = 'Scientific'
|
||||
strategy_class = RedHatStrategy
|
||||
|
||||
class ScientificLinuxHostname(Hostname):
|
||||
platform = 'Linux'
|
||||
distribution = 'Scientific linux'
|
||||
strategy_class = RedHatStrategy
|
||||
|
||||
class AmazonLinuxHostname(Hostname):
|
||||
platform = 'Linux'
|
||||
distribution = 'Amazon'
|
||||
strategy_class = RedHatStrategy
|
||||
|
||||
class DebianHostname(Hostname):
|
||||
platform = 'Linux'
|
||||
distribution = 'Debian'
|
||||
strategy_class = DebianStrategy
|
||||
|
||||
class UbuntuHostname(Hostname):
|
||||
platform = 'Linux'
|
||||
distribution = 'Ubuntu'
|
||||
strategy_class = DebianStrategy
|
||||
|
||||
class LinaroHostname(Hostname):
|
||||
platform = 'Linux'
|
||||
distribution = 'Linaro'
|
||||
strategy_class = DebianStrategy
|
||||
|
||||
class GentooHostname(Hostname):
|
||||
platform = 'Linux'
|
||||
distribution = 'Gentoo base system'
|
||||
strategy_class = OpenRCStrategy
|
||||
|
||||
# ===========================================
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
name=dict(required=True, type='str')
|
||||
)
|
||||
)
|
||||
|
||||
hostname = Hostname(module)
|
||||
|
||||
changed = False
|
||||
name = module.params['name']
|
||||
current_name = hostname.get_current_hostname()
|
||||
if current_name != name:
|
||||
hostname.set_current_hostname(name)
|
||||
changed = True
|
||||
|
||||
permanent_name = hostname.get_permanent_hostname()
|
||||
if permanent_name != name:
|
||||
hostname.set_permanent_hostname(name)
|
||||
changed = True
|
||||
|
||||
module.exit_json(changed=changed, name=name)
|
||||
|
||||
main()
|
338
lib/ansible/modules/system/mount
Executable file
338
lib/ansible/modules/system/mount
Executable file
|
@ -0,0 +1,338 @@
|
|||
#!/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 needed and appropriately configured in I(fstab).
|
||||
C(absent) and C(present) only deal with
|
||||
I(fstab) but will not affect current mounting. If specifying C(mounted) and the mount
|
||||
point is not present, the mount point will be created. Similarly, specifying C(absent) will remove the mount point directory.
|
||||
required: true
|
||||
choices: [ "present", "absent", "mounted", "unmounted" ]
|
||||
default: null
|
||||
fstab:
|
||||
description:
|
||||
- file to use instead of C(/etc/fstab). You shouldn't use that option
|
||||
unless you really know what you are doing. This might be useful if
|
||||
you need to configure mountpoints in a chroot environment.
|
||||
required: false
|
||||
default: /etc/fstab
|
||||
|
||||
notes: []
|
||||
requirements: []
|
||||
author: Seth Vidal
|
||||
'''
|
||||
EXAMPLES = '''
|
||||
# Mount DVD read-only
|
||||
- mount: name=/mnt/dvd src=/dev/sr0 fstype=iso9660 opts=ro state=present
|
||||
|
||||
# Mount up device by label
|
||||
- mount: name=/srv/disk src='LABEL=SOME_LABEL' fstype=ext4 state=present
|
||||
|
||||
# Mount up device by UUID
|
||||
- mount: name=/home src='UUID=b3e48f45-f933-4c8e-a700-22a159ec9077' fstype=xfs opts=noatime state=present
|
||||
'''
|
||||
|
||||
|
||||
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 """
|
||||
mount_bin = module.get_bin_path('mount')
|
||||
|
||||
name = kwargs['name']
|
||||
if os.path.ismount(name):
|
||||
cmd = [ mount_bin , '-o', 'remount', name ]
|
||||
else:
|
||||
cmd = [ mount_bin, 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 """
|
||||
|
||||
umount_bin = module.get_bin_path('umount')
|
||||
name = kwargs['name']
|
||||
cmd = [umount_bin, 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='/etc/fstab')
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
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 ' ' in args['opts']:
|
||||
module.fail_json(msg="unexpected space in 'opts' parameter")
|
||||
if module.params['dump'] is not None:
|
||||
args['dump'] = module.params['dump']
|
||||
if module.params['fstab'] is not None:
|
||||
args['fstab'] = module.params['fstab']
|
||||
|
||||
# if fstab file does not exist, we first need to create it. This mainly
|
||||
# happens when fstab optin is passed to the module.
|
||||
if not os.path.exists(args['fstab']):
|
||||
if not os.path.exists(os.path.dirname(args['fstab'])):
|
||||
os.makedirs(os.path.dirname(args['fstab']))
|
||||
open(args['fstab'],'a').close()
|
||||
|
||||
# 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']:
|
||||
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)))
|
||||
|
||||
name, changed = set_mount(**args)
|
||||
if state == 'mounted':
|
||||
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)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
59
lib/ansible/modules/system/ping
Normal file
59
lib/ansible/modules/system/ping
Normal file
|
@ -0,0 +1,59 @@
|
|||
#!/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
|
||||
version_added: historical
|
||||
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: {}
|
||||
author: Michael DeHaan
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Test 'webservers' status
|
||||
ansible webservers -m ping
|
||||
'''
|
||||
|
||||
import exceptions
|
||||
|
||||
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']:
|
||||
if module.params['data'] == 'crash':
|
||||
raise exceptions.Exception("boom")
|
||||
result['ping'] = module.params['data']
|
||||
module.exit_json(**result)
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
main()
|
||||
|
212
lib/ansible/modules/system/seboolean
Normal file
212
lib/ansible/modules/system/seboolean
Normal file
|
@ -0,0 +1,212 @@
|
|||
#!/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: [ 'yes', 'no' ]
|
||||
notes:
|
||||
- Not tested on any debian based system
|
||||
requirements: [ ]
|
||||
author: Stephen Fromm
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Set (httpd_can_network_connect) flag on and keep it persistent across reboots
|
||||
- seboolean: name=httpd_can_network_connect state=yes persistent=yes
|
||||
'''
|
||||
|
||||
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)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
203
lib/ansible/modules/system/selinux
Normal file
203
lib/ansible/modules/system/selinux
Normal file
|
@ -0,0 +1,203 @@
|
|||
#!/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"
|
||||
notes:
|
||||
- Not tested on any debian based system
|
||||
requirements: [ libselinux-python ]
|
||||
author: Derek Carter <goozbach@friocorte.com>
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- selinux: policy=targeted state=enforcing
|
||||
- selinux: policy=targeted state=permissive
|
||||
- selinux: state=disabled
|
||||
'''
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
try:
|
||||
import selinux
|
||||
except ImportError:
|
||||
print "failed=True msg='libselinux-python 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)
|
||||
|
||||
#################################################
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
main()
|
||||
|
1328
lib/ansible/modules/system/service
Normal file
1328
lib/ansible/modules/system/service
Normal file
File diff suppressed because it is too large
Load diff
146
lib/ansible/modules/system/setup
Normal file
146
lib/ansible/modules/system/setup
Normal file
|
@ -0,0 +1,146 @@
|
|||
#!/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: setup
|
||||
version_added: historical
|
||||
short_description: Gathers facts about remote hosts
|
||||
options:
|
||||
filter:
|
||||
version_added: "1.1"
|
||||
description:
|
||||
- if supplied, only return facts that match this shell-style (fnmatch) wildcard.
|
||||
required: false
|
||||
default: '*'
|
||||
fact_path:
|
||||
version_added: "1.3"
|
||||
description:
|
||||
- path used for local ansible facts (*.fact) - files in this dir
|
||||
will be run (if executable) and their results be added to ansible_local facts
|
||||
if a file is not executable it is read.
|
||||
File/results format can be json or ini-format
|
||||
required: false
|
||||
default: '/etc/ansible/facts.d'
|
||||
description:
|
||||
- This module is automatically called by playbooks to gather useful
|
||||
variables about remote hosts that can be used in playbooks. It can also be
|
||||
executed directly by C(/usr/bin/ansible) to check what variables are
|
||||
available to a host. Ansible provides many I(facts) about the system,
|
||||
automatically.
|
||||
notes:
|
||||
- More ansible facts will be added with successive releases. If I(facter) or
|
||||
I(ohai) are installed, variables from these programs will also be snapshotted
|
||||
into the JSON file for usage in templating. These variables are prefixed
|
||||
with C(facter_) and C(ohai_) so it's easy to tell their source. All variables are
|
||||
bubbled up to the caller. Using the ansible facts and choosing to not
|
||||
install I(facter) and I(ohai) means you can avoid Ruby-dependencies on your
|
||||
remote systems. (See also M(facter) and M(ohai).)
|
||||
- The filter option filters only the first level subkey below ansible_facts.
|
||||
- If the target host is Windows, you will not currently have the ability to use
|
||||
C(fact_path) or C(filter) as this is provided by a simpler implementation of the module.
|
||||
Different facts are returned for Windows hosts.
|
||||
author: Michael DeHaan
|
||||
'''
|
||||
|
||||
EXAMPLES = """
|
||||
# Display facts from all hosts and store them indexed by I(hostname) at C(/tmp/facts).
|
||||
ansible all -m setup --tree /tmp/facts
|
||||
|
||||
# Display only facts regarding memory found by ansible on all hosts and output them.
|
||||
ansible all -m setup -a 'filter=ansible_*_mb'
|
||||
|
||||
# Display only facts returned by facter.
|
||||
ansible all -m setup -a 'filter=facter_*'
|
||||
|
||||
# Display only facts about certain interfaces.
|
||||
ansible all -m setup -a 'filter=ansible_eth[0-2]'
|
||||
"""
|
||||
|
||||
|
||||
def run_setup(module):
|
||||
|
||||
setup_options = dict(module_setup=True)
|
||||
facts = ansible_facts(module)
|
||||
|
||||
for (k, v) in facts.items():
|
||||
setup_options["ansible_%s" % k.replace('-', '_')] = v
|
||||
|
||||
# Look for the path to the facter and ohai binary and set
|
||||
# the variable to that path.
|
||||
facter_path = module.get_bin_path('facter')
|
||||
ohai_path = module.get_bin_path('ohai')
|
||||
|
||||
# if facter is installed, and we can use --json because
|
||||
# ruby-json is ALSO installed, include facter data in the JSON
|
||||
if facter_path is not None:
|
||||
rc, out, err = module.run_command(facter_path + " --puppet --json")
|
||||
facter = True
|
||||
try:
|
||||
facter_ds = json.loads(out)
|
||||
except:
|
||||
facter = False
|
||||
if facter:
|
||||
for (k,v) in facter_ds.items():
|
||||
setup_options["facter_%s" % k] = v
|
||||
|
||||
# ditto for ohai
|
||||
if ohai_path is not None:
|
||||
rc, out, err = module.run_command(ohai_path)
|
||||
ohai = True
|
||||
try:
|
||||
ohai_ds = json.loads(out)
|
||||
except:
|
||||
ohai = False
|
||||
if ohai:
|
||||
for (k,v) in ohai_ds.items():
|
||||
k2 = "ohai_%s" % k.replace('-', '_')
|
||||
setup_options[k2] = v
|
||||
|
||||
setup_result = { 'ansible_facts': {} }
|
||||
|
||||
for (k,v) in setup_options.items():
|
||||
if module.params['filter'] == '*' or fnmatch.fnmatch(k, module.params['filter']):
|
||||
setup_result['ansible_facts'][k] = v
|
||||
|
||||
# hack to keep --verbose from showing all the setup module results
|
||||
setup_result['verbose_override'] = True
|
||||
|
||||
return setup_result
|
||||
|
||||
def main():
|
||||
global module
|
||||
module = AnsibleModule(
|
||||
argument_spec = dict(
|
||||
filter=dict(default="*", required=False),
|
||||
fact_path=dict(default='/etc/ansible/facts.d', required=False),
|
||||
),
|
||||
supports_check_mode = True,
|
||||
)
|
||||
data = run_setup(module)
|
||||
module.exit_json(**data)
|
||||
|
||||
# import module snippets
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
|
||||
from ansible.module_utils.facts import *
|
||||
|
||||
main()
|
334
lib/ansible/modules/system/sysctl
Normal file
334
lib/ansible/modules/system/sysctl
Normal file
|
@ -0,0 +1,334 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2012, David "DaviXX" CHANIAL <david.chanial@gmail.com>
|
||||
# (c) 2014, James Tanner <tanner.jc@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: Manage entries in sysctl.conf.
|
||||
description:
|
||||
- This module manipulates sysctl entries and optionally performs a C(/sbin/sysctl -p) after changing them.
|
||||
version_added: "1.0"
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The dot-separated path (aka I(key)) specifying the sysctl variable.
|
||||
required: true
|
||||
default: null
|
||||
aliases: [ 'key' ]
|
||||
value:
|
||||
description:
|
||||
- Desired value of the sysctl key.
|
||||
required: false
|
||||
default: null
|
||||
aliases: [ 'val' ]
|
||||
state:
|
||||
description:
|
||||
- Whether the entry should be present or absent in the sysctl file.
|
||||
choices: [ "present", "absent" ]
|
||||
default: present
|
||||
ignoreerrors:
|
||||
description:
|
||||
- Use this option to ignore errors about unknown keys.
|
||||
choices: [ "yes", "no" ]
|
||||
default: no
|
||||
reload:
|
||||
description:
|
||||
- If C(yes), performs a I(/sbin/sysctl -p) if the C(sysctl_file) is
|
||||
updated. If C(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 C(/etc/sysctl.conf).
|
||||
required: false
|
||||
default: /etc/sysctl.conf
|
||||
sysctl_set:
|
||||
description:
|
||||
- Verify token value with the sysctl command and set with -w if necessary
|
||||
choices: [ "yes", "no" ]
|
||||
required: false
|
||||
version_added: 1.5
|
||||
default: False
|
||||
notes: []
|
||||
requirements: []
|
||||
author: David "DaviXX" CHANIAL <david.chanial@gmail.com>
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
# Set vm.swappiness to 5 in /etc/sysctl.conf
|
||||
- sysctl: name=vm.swappiness value=5 state=present
|
||||
|
||||
# Remove kernel.panic entry from /etc/sysctl.conf
|
||||
- sysctl: name=kernel.panic state=absent sysctl_file=/etc/sysctl.conf
|
||||
|
||||
# Set kernel.panic to 3 in /tmp/test_sysctl.conf
|
||||
- sysctl: name=kernel.panic value=3 sysctl_file=/tmp/test_sysctl.conf reload=no
|
||||
|
||||
# Set ip fowarding on in /proc and do not reload the sysctl file
|
||||
- sysctl: name="net.ipv4.ip_forward" value=1 sysctl_set=yes
|
||||
|
||||
# Set ip forwarding on in /proc and in the sysctl file and reload if necessary
|
||||
- sysctl: name="net.ipv4.ip_forward" value=1 sysctl_set=yes state=present reload=yes
|
||||
'''
|
||||
|
||||
# ==============================================================
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import re
|
||||
|
||||
class SysctlModule(object):
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.args = self.module.params
|
||||
|
||||
self.sysctl_cmd = self.module.get_bin_path('sysctl', required=True)
|
||||
self.sysctl_file = self.args['sysctl_file']
|
||||
|
||||
self.proc_value = None # current token value in proc fs
|
||||
self.file_value = None # current token value in file
|
||||
self.file_lines = [] # all lines in the file
|
||||
self.file_values = {} # dict of token values
|
||||
|
||||
self.changed = False # will change occur
|
||||
self.set_proc = False # does sysctl need to set value
|
||||
self.write_file = False # does the sysctl file need to be reloaded
|
||||
|
||||
self.process()
|
||||
|
||||
# ==============================================================
|
||||
# LOGIC
|
||||
# ==============================================================
|
||||
|
||||
def process(self):
|
||||
|
||||
# Whitespace is bad
|
||||
self.args['name'] = self.args['name'].strip()
|
||||
self.args['value'] = self._parse_value(self.args['value'])
|
||||
|
||||
thisname = self.args['name']
|
||||
|
||||
# get the current proc fs value
|
||||
self.proc_value = self.get_token_curr_value(thisname)
|
||||
|
||||
# get the currect sysctl file value
|
||||
self.read_sysctl_file()
|
||||
if thisname not in self.file_values:
|
||||
self.file_values[thisname] = None
|
||||
|
||||
# update file contents with desired token/value
|
||||
self.fix_lines()
|
||||
|
||||
# what do we need to do now?
|
||||
if self.file_values[thisname] is None and self.args['state'] == "present":
|
||||
self.changed = True
|
||||
self.write_file = True
|
||||
elif self.file_values[thisname] is None and self.args['state'] == "absent":
|
||||
self.changed = False
|
||||
elif self.file_values[thisname] != self.args['value']:
|
||||
self.changed = True
|
||||
self.write_file = True
|
||||
|
||||
# use the sysctl command or not?
|
||||
if self.args['sysctl_set']:
|
||||
if self.proc_value is None:
|
||||
self.changed = True
|
||||
elif not self._values_is_equal(self.proc_value, self.args['value']):
|
||||
self.changed = True
|
||||
self.set_proc = True
|
||||
|
||||
# Do the work
|
||||
if not self.module.check_mode:
|
||||
if self.write_file:
|
||||
self.write_sysctl()
|
||||
if self.write_file and self.args['reload']:
|
||||
self.reload_sysctl()
|
||||
if self.set_proc:
|
||||
self.set_token_value(self.args['name'], self.args['value'])
|
||||
|
||||
def _values_is_equal(self, a, b):
|
||||
"""Expects two string values. It will split the string by whitespace
|
||||
and compare each value. It will return True if both lists are the same,
|
||||
contain the same elements and the same order."""
|
||||
if a is None or b is None:
|
||||
return False
|
||||
|
||||
a = a.split()
|
||||
b = b.split()
|
||||
|
||||
if len(a) != len(b):
|
||||
return False
|
||||
|
||||
return len([i for i, j in zip(a, b) if i == j]) == len(a)
|
||||
|
||||
def _parse_value(self, value):
|
||||
if value is None:
|
||||
return ''
|
||||
elif value.lower() in BOOLEANS_TRUE:
|
||||
return '1'
|
||||
elif value.lower() in BOOLEANS_FALSE:
|
||||
return '0'
|
||||
else:
|
||||
return value.strip()
|
||||
|
||||
# ==============================================================
|
||||
# SYSCTL COMMAND MANAGEMENT
|
||||
# ==============================================================
|
||||
|
||||
# Use the sysctl command to find the current value
|
||||
def get_token_curr_value(self, token):
|
||||
thiscmd = "%s -e -n %s" % (self.sysctl_cmd, token)
|
||||
rc,out,err = self.module.run_command(thiscmd)
|
||||
if rc != 0:
|
||||
return None
|
||||
else:
|
||||
return out
|
||||
|
||||
# Use the sysctl command to set the current value
|
||||
def set_token_value(self, token, value):
|
||||
if len(value.split()) > 0:
|
||||
value = '"' + value + '"'
|
||||
thiscmd = "%s -w %s=%s" % (self.sysctl_cmd, token, value)
|
||||
rc,out,err = self.module.run_command(thiscmd)
|
||||
if rc != 0:
|
||||
self.module.fail_json(msg='setting %s failed: %s' % (token, out + err))
|
||||
else:
|
||||
return rc
|
||||
|
||||
# Run sysctl -p
|
||||
def reload_sysctl(self):
|
||||
# do it
|
||||
if get_platform().lower() == 'freebsd':
|
||||
# freebsd doesn't support -p, so reload the sysctl service
|
||||
rc,out,err = self.module.run_command('/etc/rc.d/sysctl reload')
|
||||
else:
|
||||
# system supports reloading via the -p flag to sysctl, so we'll use that
|
||||
sysctl_args = [self.sysctl_cmd, '-p', self.sysctl_file]
|
||||
if self.args['ignoreerrors']:
|
||||
sysctl_args.insert(1, '-e')
|
||||
|
||||
rc,out,err = self.module.run_command(sysctl_args)
|
||||
|
||||
if rc != 0:
|
||||
self.module.fail_json(msg="Failed to reload sysctl: %s" % str(out) + str(err))
|
||||
|
||||
# ==============================================================
|
||||
# SYSCTL FILE MANAGEMENT
|
||||
# ==============================================================
|
||||
|
||||
# Get the token value from the sysctl file
|
||||
def read_sysctl_file(self):
|
||||
|
||||
lines = []
|
||||
if os.path.isfile(self.sysctl_file):
|
||||
try:
|
||||
f = open(self.sysctl_file, "r")
|
||||
lines = f.readlines()
|
||||
f.close()
|
||||
except IOError, e:
|
||||
self.module.fail_json(msg="Failed to open %s: %s" % (self.sysctl_file, str(e)))
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
self.file_lines.append(line)
|
||||
|
||||
# don't split empty lines or comments
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
|
||||
k, v = line.split('=',1)
|
||||
k = k.strip()
|
||||
v = v.strip()
|
||||
self.file_values[k] = v.strip()
|
||||
|
||||
# Fix the value in the sysctl file content
|
||||
def fix_lines(self):
|
||||
checked = []
|
||||
self.fixed_lines = []
|
||||
for line in self.file_lines:
|
||||
if not line.strip() or line.strip().startswith("#"):
|
||||
self.fixed_lines.append(line)
|
||||
continue
|
||||
tmpline = line.strip()
|
||||
k, v = line.split('=',1)
|
||||
k = k.strip()
|
||||
v = v.strip()
|
||||
if k not in checked:
|
||||
checked.append(k)
|
||||
if k == self.args['name']:
|
||||
if self.args['state'] == "present":
|
||||
new_line = "%s = %s\n" % (k, self.args['value'])
|
||||
self.fixed_lines.append(new_line)
|
||||
else:
|
||||
new_line = "%s = %s\n" % (k, v)
|
||||
self.fixed_lines.append(new_line)
|
||||
|
||||
if self.args['name'] not in checked and self.args['state'] == "present":
|
||||
new_line = "%s=%s\n" % (self.args['name'], self.args['value'])
|
||||
self.fixed_lines.append(new_line)
|
||||
|
||||
# Completely rewrite the sysctl file
|
||||
def write_sysctl(self):
|
||||
# open a tmp file
|
||||
fd, tmp_path = tempfile.mkstemp('.conf', '.ansible_m_sysctl_', os.path.dirname(self.sysctl_file))
|
||||
f = open(tmp_path,"w")
|
||||
try:
|
||||
for l in self.fixed_lines:
|
||||
f.write(l.strip() + "\n")
|
||||
except IOError, e:
|
||||
self.module.fail_json(msg="Failed to write to file %s: %s" % (tmp_path, str(e)))
|
||||
f.flush()
|
||||
f.close()
|
||||
|
||||
# replace the real one
|
||||
self.module.atomic_move(tmp_path, self.sysctl_file)
|
||||
|
||||
|
||||
# ==============================================================
|
||||
# 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']),
|
||||
reload = dict(default=True, type='bool'),
|
||||
sysctl_set = dict(default=False, type='bool'),
|
||||
ignoreerrors = dict(default=False, type='bool'),
|
||||
sysctl_file = dict(default='/etc/sysctl.conf')
|
||||
),
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
result = SysctlModule(module)
|
||||
|
||||
module.exit_json(changed=result.changed)
|
||||
sys.exit(0)
|
||||
|
||||
# import module snippets
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
1584
lib/ansible/modules/system/user
Normal file
1584
lib/ansible/modules/system/user
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue