Migrated to ansible.posix
This commit is contained in:
parent
9a7a574ffc
commit
829eb0f1d1
176 changed files with 0 additions and 11861 deletions
|
@ -1,90 +0,0 @@
|
|||
# This code is part of Ansible, but is an independent component.
|
||||
# This particular file snippet, and this file snippet only, is based on
|
||||
# Lib/posixpath.py of cpython
|
||||
# It is licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
||||
#
|
||||
# 1. This LICENSE AGREEMENT is between the Python Software Foundation
|
||||
# ("PSF"), and the Individual or Organization ("Licensee") accessing and
|
||||
# otherwise using this software ("Python") in source or binary form and
|
||||
# its associated documentation.
|
||||
#
|
||||
# 2. Subject to the terms and conditions of this License Agreement, PSF hereby
|
||||
# grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
|
||||
# analyze, test, perform and/or display publicly, prepare derivative works,
|
||||
# distribute, and otherwise use Python alone or in any derivative version,
|
||||
# provided, however, that PSF's License Agreement and PSF's notice of copyright,
|
||||
# i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
|
||||
# 2011, 2012, 2013, 2014, 2015 Python Software Foundation; All Rights Reserved"
|
||||
# are retained in Python alone or in any derivative version prepared by Licensee.
|
||||
#
|
||||
# 3. In the event Licensee prepares a derivative work that is based on
|
||||
# or incorporates Python or any part thereof, and wants to make
|
||||
# the derivative work available to others as provided herein, then
|
||||
# Licensee hereby agrees to include in any such work a brief summary of
|
||||
# the changes made to Python.
|
||||
#
|
||||
# 4. PSF is making Python available to Licensee on an "AS IS"
|
||||
# basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||
# IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
||||
# DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||
# FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
|
||||
# INFRINGE ANY THIRD PARTY RIGHTS.
|
||||
#
|
||||
# 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||
# FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||
# A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
|
||||
# OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||
#
|
||||
# 6. This License Agreement will automatically terminate upon a material
|
||||
# breach of its terms and conditions.
|
||||
#
|
||||
# 7. Nothing in this License Agreement shall be deemed to create any
|
||||
# relationship of agency, partnership, or joint venture between PSF and
|
||||
# Licensee. This License Agreement does not grant permission to use PSF
|
||||
# trademarks or trade name in a trademark sense to endorse or promote
|
||||
# products or services of Licensee, or any third party.
|
||||
#
|
||||
# 8. By copying, installing or otherwise using Python, Licensee
|
||||
# agrees to be bound by the terms and conditions of this License
|
||||
# Agreement.
|
||||
|
||||
import os
|
||||
|
||||
|
||||
def ismount(path):
|
||||
"""Test whether a path is a mount point
|
||||
This is a copy of the upstream version of ismount(). Originally this was copied here as a workaround
|
||||
until Python issue 2466 was fixed. Now it is here so this will work on older versions of Python
|
||||
that may not have the upstream fix.
|
||||
https://github.com/ansible/ansible-modules-core/issues/2186
|
||||
http://bugs.python.org/issue2466
|
||||
"""
|
||||
try:
|
||||
s1 = os.lstat(path)
|
||||
except (OSError, ValueError):
|
||||
# It doesn't exist -- so not a mount point. :-)
|
||||
return False
|
||||
else:
|
||||
# A symlink can never be a mount point
|
||||
if os.path.stat.S_ISLNK(s1.st_mode):
|
||||
return False
|
||||
|
||||
if isinstance(path, bytes):
|
||||
parent = os.path.join(path, b'..')
|
||||
else:
|
||||
parent = os.path.join(path, '..')
|
||||
parent = os.path.realpath(parent)
|
||||
try:
|
||||
s2 = os.lstat(parent)
|
||||
except (OSError, ValueError):
|
||||
return False
|
||||
|
||||
dev1 = s1.st_dev
|
||||
dev2 = s2.st_dev
|
||||
if dev1 != dev2:
|
||||
return True # path/.. on a different device as path
|
||||
ino1 = s1.st_ino
|
||||
ino2 = s2.st_ino
|
||||
if ino1 == ino2:
|
||||
return True # path/.. is the same i-node as path
|
||||
return False
|
|
@ -1,385 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2017, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['stableinterface'],
|
||||
'supported_by': 'core'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: acl
|
||||
version_added: '1.4'
|
||||
short_description: Set and retrieve file ACL information.
|
||||
description:
|
||||
- Set and retrieve file ACL information.
|
||||
options:
|
||||
path:
|
||||
description:
|
||||
- The full path of the file or object.
|
||||
type: path
|
||||
required: yes
|
||||
aliases: [ name ]
|
||||
state:
|
||||
description:
|
||||
- Define whether the ACL should be present or not.
|
||||
- The C(query) state gets the current ACL without changing it, for use in C(register) operations.
|
||||
choices: [ absent, present, query ]
|
||||
default: query
|
||||
follow:
|
||||
description:
|
||||
- Whether to follow symlinks on the path if a symlink is encountered.
|
||||
type: bool
|
||||
default: yes
|
||||
default:
|
||||
description:
|
||||
- If the target is a directory, setting this to C(yes) will make it the default ACL for entities created inside the directory.
|
||||
- Setting C(default) to C(yes) causes an error if the path is a file.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: '1.5'
|
||||
entity:
|
||||
description:
|
||||
- The actual user or group that the ACL applies to when matching entity types user or group are selected.
|
||||
version_added: '1.5'
|
||||
etype:
|
||||
description:
|
||||
- The entity type of the ACL to apply, see C(setfacl) documentation for more info.
|
||||
choices: [ group, mask, other, user ]
|
||||
version_added: '1.5'
|
||||
permissions:
|
||||
description:
|
||||
- The permissions to apply/remove can be any combination of C(r), C(w) and C(x) (read, write and execute respectively)
|
||||
version_added: '1.5'
|
||||
entry:
|
||||
description:
|
||||
- DEPRECATED.
|
||||
- The ACL to set or remove.
|
||||
- This must always be quoted in the form of C(<etype>:<qualifier>:<perms>).
|
||||
- The qualifier may be empty for some types, but the type and perms are always required.
|
||||
- C(-) can be used as placeholder when you do not care about permissions.
|
||||
- This is now superseded by entity, type and permissions fields.
|
||||
recursive:
|
||||
description:
|
||||
- Recursively sets the specified ACL.
|
||||
- Incompatible with C(state=query).
|
||||
type: bool
|
||||
default: no
|
||||
version_added: '2.0'
|
||||
use_nfsv4_acls:
|
||||
description:
|
||||
- Use NFSv4 ACLs instead of POSIX ACLs.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: '2.2'
|
||||
recalculate_mask:
|
||||
description:
|
||||
- Select if and when to recalculate the effective right masks of the files.
|
||||
- See C(setfacl) documentation for more info.
|
||||
- Incompatible with C(state=query).
|
||||
choices: [ default, mask, no_mask ]
|
||||
default: default
|
||||
version_added: '2.7'
|
||||
author:
|
||||
- Brian Coca (@bcoca)
|
||||
- Jérémie Astori (@astorije)
|
||||
notes:
|
||||
- The C(acl) module requires that ACLs are enabled on the target filesystem and that the C(setfacl) and C(getfacl) binaries are installed.
|
||||
- As of Ansible 2.0, this module only supports Linux distributions.
|
||||
- As of Ansible 2.3, the I(name) option has been changed to I(path) as default, but I(name) still works as well.
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Grant user Joe read access to a file
|
||||
acl:
|
||||
path: /etc/foo.conf
|
||||
entity: joe
|
||||
etype: user
|
||||
permissions: r
|
||||
state: present
|
||||
|
||||
- name: Removes the ACL for Joe on a specific file
|
||||
acl:
|
||||
path: /etc/foo.conf
|
||||
entity: joe
|
||||
etype: user
|
||||
state: absent
|
||||
|
||||
- name: Sets default ACL for joe on /etc/foo.d/
|
||||
acl:
|
||||
path: /etc/foo.d/
|
||||
entity: joe
|
||||
etype: user
|
||||
permissions: rw
|
||||
default: yes
|
||||
state: present
|
||||
|
||||
- name: Same as previous but using entry shorthand
|
||||
acl:
|
||||
path: /etc/foo.d/
|
||||
entry: default:user:joe:rw-
|
||||
state: present
|
||||
|
||||
- name: Obtain the ACL for a specific file
|
||||
acl:
|
||||
path: /etc/foo.conf
|
||||
register: acl_info
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
acl:
|
||||
description: Current ACL on provided path (after changes, if any)
|
||||
returned: success
|
||||
type: list
|
||||
sample: [ "user::rwx", "group::rwx", "other::rwx" ]
|
||||
'''
|
||||
|
||||
import os
|
||||
import platform
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
|
||||
def split_entry(entry):
|
||||
''' splits entry and ensures normalized return'''
|
||||
|
||||
a = entry.split(':')
|
||||
|
||||
d = None
|
||||
if entry.lower().startswith("d"):
|
||||
d = True
|
||||
a.pop(0)
|
||||
|
||||
if len(a) == 2:
|
||||
a.append(None)
|
||||
|
||||
t, e, p = a
|
||||
t = t.lower()
|
||||
|
||||
if t.startswith("u"):
|
||||
t = "user"
|
||||
elif t.startswith("g"):
|
||||
t = "group"
|
||||
elif t.startswith("m"):
|
||||
t = "mask"
|
||||
elif t.startswith("o"):
|
||||
t = "other"
|
||||
else:
|
||||
t = None
|
||||
|
||||
return [d, t, e, p]
|
||||
|
||||
|
||||
def build_entry(etype, entity, permissions=None, use_nfsv4_acls=False):
|
||||
'''Builds and returns an entry string. Does not include the permissions bit if they are not provided.'''
|
||||
if use_nfsv4_acls:
|
||||
return ':'.join([etype, entity, permissions, 'allow'])
|
||||
|
||||
if permissions:
|
||||
return etype + ':' + entity + ':' + permissions
|
||||
|
||||
return etype + ':' + entity
|
||||
|
||||
|
||||
def build_command(module, mode, path, follow, default, recursive, recalculate_mask, entry=''):
|
||||
'''Builds and returns a getfacl/setfacl command.'''
|
||||
if mode == 'set':
|
||||
cmd = [module.get_bin_path('setfacl', True)]
|
||||
cmd.extend(['-m', entry])
|
||||
elif mode == 'rm':
|
||||
cmd = [module.get_bin_path('setfacl', True)]
|
||||
cmd.extend(['-x', entry])
|
||||
else: # mode == 'get'
|
||||
cmd = [module.get_bin_path('getfacl', True)]
|
||||
# prevents absolute path warnings and removes headers
|
||||
if platform.system().lower() == 'linux':
|
||||
cmd.append('--omit-header')
|
||||
cmd.append('--absolute-names')
|
||||
|
||||
if recursive:
|
||||
cmd.append('--recursive')
|
||||
|
||||
if recalculate_mask == 'mask' and mode in ['set', 'rm']:
|
||||
cmd.append('--mask')
|
||||
elif recalculate_mask == 'no_mask' and mode in ['set', 'rm']:
|
||||
cmd.append('--no-mask')
|
||||
|
||||
if not follow:
|
||||
if platform.system().lower() == 'linux':
|
||||
cmd.append('--physical')
|
||||
elif platform.system().lower() == 'freebsd':
|
||||
cmd.append('-h')
|
||||
|
||||
if default:
|
||||
cmd.insert(1, '-d')
|
||||
|
||||
cmd.append(path)
|
||||
return cmd
|
||||
|
||||
|
||||
def acl_changed(module, cmd):
|
||||
'''Returns true if the provided command affects the existing ACLs, false otherwise.'''
|
||||
# FreeBSD do not have a --test flag, so by default, it is safer to always say "true"
|
||||
if platform.system().lower() == 'freebsd':
|
||||
return True
|
||||
|
||||
cmd = cmd[:] # lists are mutables so cmd would be overwritten without this
|
||||
cmd.insert(1, '--test')
|
||||
lines = run_acl(module, cmd)
|
||||
|
||||
for line in lines:
|
||||
if not line.endswith('*,*'):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def run_acl(module, cmd, check_rc=True):
|
||||
|
||||
try:
|
||||
(rc, out, err) = module.run_command(cmd, check_rc=check_rc)
|
||||
except Exception as e:
|
||||
module.fail_json(msg=to_native(e))
|
||||
|
||||
lines = []
|
||||
for l in out.splitlines():
|
||||
if not l.startswith('#'):
|
||||
lines.append(l.strip())
|
||||
|
||||
if lines and not lines[-1].split():
|
||||
# trim last line only when it is empty
|
||||
return lines[:-1]
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
path=dict(type='path', required=True, aliases=['name']),
|
||||
entry=dict(type='str'),
|
||||
entity=dict(type='str', default=''),
|
||||
etype=dict(
|
||||
type='str',
|
||||
choices=['group', 'mask', 'other', 'user'],
|
||||
),
|
||||
permissions=dict(type='str'),
|
||||
state=dict(
|
||||
type='str',
|
||||
default='query',
|
||||
choices=['absent', 'present', 'query'],
|
||||
),
|
||||
follow=dict(type='bool', default=True),
|
||||
default=dict(type='bool', default=False),
|
||||
recursive=dict(type='bool', default=False),
|
||||
recalculate_mask=dict(
|
||||
type='str',
|
||||
default='default',
|
||||
choices=['default', 'mask', 'no_mask'],
|
||||
),
|
||||
use_nfsv4_acls=dict(type='bool', default=False)
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if platform.system().lower() not in ['linux', 'freebsd']:
|
||||
module.fail_json(msg="The acl module is not available on this system.")
|
||||
|
||||
path = module.params.get('path')
|
||||
entry = module.params.get('entry')
|
||||
entity = module.params.get('entity')
|
||||
etype = module.params.get('etype')
|
||||
permissions = module.params.get('permissions')
|
||||
state = module.params.get('state')
|
||||
follow = module.params.get('follow')
|
||||
default = module.params.get('default')
|
||||
recursive = module.params.get('recursive')
|
||||
recalculate_mask = module.params.get('recalculate_mask')
|
||||
use_nfsv4_acls = module.params.get('use_nfsv4_acls')
|
||||
|
||||
if not os.path.exists(path):
|
||||
module.fail_json(msg="Path not found or not accessible.")
|
||||
|
||||
if state == 'query':
|
||||
if recursive:
|
||||
module.fail_json(msg="'recursive' MUST NOT be set when 'state=query'.")
|
||||
|
||||
if recalculate_mask in ['mask', 'no_mask']:
|
||||
module.fail_json(msg="'recalculate_mask' MUST NOT be set to 'mask' or 'no_mask' when 'state=query'.")
|
||||
|
||||
if not entry:
|
||||
if state == 'absent' and permissions:
|
||||
module.fail_json(msg="'permissions' MUST NOT be set when 'state=absent'.")
|
||||
|
||||
if state == 'absent' and not entity:
|
||||
module.fail_json(msg="'entity' MUST be set when 'state=absent'.")
|
||||
|
||||
if state in ['present', 'absent'] and not etype:
|
||||
module.fail_json(msg="'etype' MUST be set when 'state=%s'." % state)
|
||||
|
||||
if entry:
|
||||
if etype or entity or permissions:
|
||||
module.fail_json(msg="'entry' MUST NOT be set when 'entity', 'etype' or 'permissions' are set.")
|
||||
|
||||
if state == 'present' and not entry.count(":") in [2, 3]:
|
||||
module.fail_json(msg="'entry' MUST have 3 or 4 sections divided by ':' when 'state=present'.")
|
||||
|
||||
if state == 'absent' and not entry.count(":") in [1, 2]:
|
||||
module.fail_json(msg="'entry' MUST have 2 or 3 sections divided by ':' when 'state=absent'.")
|
||||
|
||||
if state == 'query':
|
||||
module.fail_json(msg="'entry' MUST NOT be set when 'state=query'.")
|
||||
|
||||
default_flag, etype, entity, permissions = split_entry(entry)
|
||||
if default_flag is not None:
|
||||
default = default_flag
|
||||
|
||||
if platform.system().lower() == 'freebsd':
|
||||
if recursive:
|
||||
module.fail_json(msg="recursive is not supported on that platform.")
|
||||
|
||||
changed = False
|
||||
msg = ""
|
||||
|
||||
if state == 'present':
|
||||
entry = build_entry(etype, entity, permissions, use_nfsv4_acls)
|
||||
command = build_command(
|
||||
module, 'set', path, follow,
|
||||
default, recursive, recalculate_mask, entry
|
||||
)
|
||||
changed = acl_changed(module, command)
|
||||
|
||||
if changed and not module.check_mode:
|
||||
run_acl(module, command)
|
||||
msg = "%s is present" % entry
|
||||
|
||||
elif state == 'absent':
|
||||
entry = build_entry(etype, entity, use_nfsv4_acls)
|
||||
command = build_command(
|
||||
module, 'rm', path, follow,
|
||||
default, recursive, recalculate_mask, entry
|
||||
)
|
||||
changed = acl_changed(module, command)
|
||||
|
||||
if changed and not module.check_mode:
|
||||
run_acl(module, command, False)
|
||||
msg = "%s is absent" % entry
|
||||
|
||||
elif state == 'query':
|
||||
msg = "current acl"
|
||||
|
||||
acl = run_acl(
|
||||
module,
|
||||
build_command(module, 'get', path, follow, default, recursive, recalculate_mask)
|
||||
)
|
||||
|
||||
module.exit_json(changed=changed, msg=msg, acl=acl)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,618 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2012-2013, Timothy Appnel <tim@appnel.com>
|
||||
# Copyright: (c) 2017, Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'core'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: synchronize
|
||||
version_added: "1.4"
|
||||
short_description: A wrapper around rsync to make common tasks in your playbooks quick and easy
|
||||
description:
|
||||
- C(synchronize) is a wrapper around rsync to make common tasks in your playbooks quick and easy.
|
||||
- It is run and originates on the local host where Ansible is being run.
|
||||
- Of course, you could just use the C(command) action to call rsync yourself, but you also have to add a fair number of
|
||||
boilerplate options and host facts.
|
||||
- This module is not intended to provide access to the full power of rsync, but does make the most common
|
||||
invocations easier to implement. You `still` may need to call rsync directly via C(command) or C(shell) depending on your use case.
|
||||
options:
|
||||
src:
|
||||
description:
|
||||
- Path on the source host that will be synchronized to the destination.
|
||||
- The path can be absolute or relative.
|
||||
type: str
|
||||
required: true
|
||||
dest:
|
||||
description:
|
||||
- Path on the destination host that will be synchronized from the source.
|
||||
- The path can be absolute or relative.
|
||||
type: str
|
||||
required: true
|
||||
dest_port:
|
||||
description:
|
||||
- Port number for ssh on the destination host.
|
||||
- Prior to Ansible 2.0, the ansible_ssh_port inventory var took precedence over this value.
|
||||
- This parameter defaults to the value of C(ansible_ssh_port) or C(ansible_port),
|
||||
the C(remote_port) config setting or the value from ssh client configuration
|
||||
if none of the former have been set.
|
||||
type: int
|
||||
version_added: "1.5"
|
||||
mode:
|
||||
description:
|
||||
- Specify the direction of the synchronization.
|
||||
- In push mode the localhost or delegate is the source.
|
||||
- In pull mode the remote host in context is the source.
|
||||
type: str
|
||||
choices: [ pull, push ]
|
||||
default: push
|
||||
archive:
|
||||
description:
|
||||
- Mirrors the rsync archive flag, enables recursive, links, perms, times, owner, group flags and -D.
|
||||
type: bool
|
||||
default: yes
|
||||
checksum:
|
||||
description:
|
||||
- Skip based on checksum, rather than mod-time & size; Note that that "archive" option is still enabled by default - the "checksum" option will
|
||||
not disable it.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: "1.6"
|
||||
compress:
|
||||
description:
|
||||
- Compress file data during the transfer.
|
||||
- In most cases, leave this enabled unless it causes problems.
|
||||
type: bool
|
||||
default: yes
|
||||
version_added: "1.7"
|
||||
existing_only:
|
||||
description:
|
||||
- Skip creating new files on receiver.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: "1.5"
|
||||
delete:
|
||||
description:
|
||||
- Delete files in C(dest) that don't exist (after transfer, not before) in the C(src) path.
|
||||
- This option requires C(recursive=yes).
|
||||
- This option ignores excluded files and behaves like the rsync opt --delete-excluded.
|
||||
type: bool
|
||||
default: no
|
||||
dirs:
|
||||
description:
|
||||
- Transfer directories without recursing.
|
||||
type: bool
|
||||
default: no
|
||||
recursive:
|
||||
description:
|
||||
- Recurse into directories.
|
||||
- This parameter defaults to the value of the archive option.
|
||||
type: bool
|
||||
links:
|
||||
description:
|
||||
- Copy symlinks as symlinks.
|
||||
- This parameter defaults to the value of the archive option.
|
||||
type: bool
|
||||
copy_links:
|
||||
description:
|
||||
- Copy symlinks as the item that they point to (the referent) is copied, rather than the symlink.
|
||||
type: bool
|
||||
default: no
|
||||
perms:
|
||||
description:
|
||||
- Preserve permissions.
|
||||
- This parameter defaults to the value of the archive option.
|
||||
type: bool
|
||||
times:
|
||||
description:
|
||||
- Preserve modification times.
|
||||
- This parameter defaults to the value of the archive option.
|
||||
type: bool
|
||||
owner:
|
||||
description:
|
||||
- Preserve owner (super user only).
|
||||
- This parameter defaults to the value of the archive option.
|
||||
type: bool
|
||||
group:
|
||||
description:
|
||||
- Preserve group.
|
||||
- This parameter defaults to the value of the archive option.
|
||||
type: bool
|
||||
rsync_path:
|
||||
description:
|
||||
- Specify the rsync command to run on the remote host. See C(--rsync-path) on the rsync man page.
|
||||
- To specify the rsync command to run on the local host, you need to set this your task var C(ansible_rsync_path).
|
||||
type: str
|
||||
rsync_timeout:
|
||||
description:
|
||||
- Specify a C(--timeout) for the rsync command in seconds.
|
||||
type: int
|
||||
default: 0
|
||||
set_remote_user:
|
||||
description:
|
||||
- Put user@ for the remote paths.
|
||||
- If you have a custom ssh config to define the remote user for a host
|
||||
that does not match the inventory user, you should set this parameter to C(no).
|
||||
type: bool
|
||||
default: yes
|
||||
use_ssh_args:
|
||||
description:
|
||||
- Use the ssh_args specified in ansible.cfg.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: "2.0"
|
||||
rsync_opts:
|
||||
description:
|
||||
- Specify additional rsync options by passing in an array.
|
||||
- Note that an empty string in C(rsync_opts) will end up transfer the current working directory.
|
||||
type: list
|
||||
default:
|
||||
version_added: "1.6"
|
||||
partial:
|
||||
description:
|
||||
- Tells rsync to keep the partial file which should make a subsequent transfer of the rest of the file much faster.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: "2.0"
|
||||
verify_host:
|
||||
description:
|
||||
- Verify destination host key.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: "2.0"
|
||||
private_key:
|
||||
description:
|
||||
- Specify the private key to use for SSH-based rsync connections (e.g. C(~/.ssh/id_rsa)).
|
||||
type: path
|
||||
version_added: "1.6"
|
||||
link_dest:
|
||||
description:
|
||||
- Add a destination to hard link against during the rsync.
|
||||
type: list
|
||||
default:
|
||||
version_added: "2.5"
|
||||
notes:
|
||||
- rsync must be installed on both the local and remote host.
|
||||
- For the C(synchronize) module, the "local host" is the host `the synchronize task originates on`, and the "destination host" is the host
|
||||
`synchronize is connecting to`.
|
||||
- The "local host" can be changed to a different host by using `delegate_to`. This enables copying between two remote hosts or entirely on one
|
||||
remote machine.
|
||||
- >
|
||||
The user and permissions for the synchronize `src` are those of the user running the Ansible task on the local host (or the remote_user for a
|
||||
delegate_to host when delegate_to is used).
|
||||
- The user and permissions for the synchronize `dest` are those of the `remote_user` on the destination host or the `become_user` if `become=yes` is active.
|
||||
- In Ansible 2.0 a bug in the synchronize module made become occur on the "local host". This was fixed in Ansible 2.0.1.
|
||||
- Currently, synchronize is limited to elevating permissions via passwordless sudo. This is because rsync itself is connecting to the remote machine
|
||||
and rsync doesn't give us a way to pass sudo credentials in.
|
||||
- Currently there are only a few connection types which support synchronize (ssh, paramiko, local, and docker) because a sync strategy has been
|
||||
determined for those connection types. Note that the connection for these must not need a password as rsync itself is making the connection and
|
||||
rsync does not provide us a way to pass a password to the connection.
|
||||
- Expect that dest=~/x will be ~<remote_user>/x even if using sudo.
|
||||
- Inspect the verbose output to validate the destination user/host/path are what was expected.
|
||||
- To exclude files and directories from being synchronized, you may add C(.rsync-filter) files to the source directory.
|
||||
- rsync daemon must be up and running with correct permission when using rsync protocol in source or destination path.
|
||||
- The C(synchronize) module forces `--delay-updates` to avoid leaving a destination in a broken in-between state if the underlying rsync process
|
||||
encounters an error. Those synchronizing large numbers of files that are willing to trade safety for performance should call rsync directly.
|
||||
- link_destination is subject to the same limitations as the underlying rsync daemon. Hard links are only preserved if the relative subtrees
|
||||
of the source and destination are the same. Attempts to hardlink into a directory that is a subdirectory of the source will be prevented.
|
||||
seealso:
|
||||
- module: copy
|
||||
- module: win_robocopy
|
||||
author:
|
||||
- Timothy Appnel (@tima)
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Synchronization of src on the control machine to dest on the remote hosts
|
||||
synchronize:
|
||||
src: some/relative/path
|
||||
dest: /some/absolute/path
|
||||
|
||||
- name: Synchronization using rsync protocol (push)
|
||||
synchronize:
|
||||
src: some/relative/path/
|
||||
dest: rsync://somehost.com/path/
|
||||
|
||||
- name: Synchronization using rsync protocol (pull)
|
||||
synchronize:
|
||||
mode: pull
|
||||
src: rsync://somehost.com/path/
|
||||
dest: /some/absolute/path/
|
||||
|
||||
- name: Synchronization using rsync protocol on delegate host (push)
|
||||
synchronize:
|
||||
src: /some/absolute/path/
|
||||
dest: rsync://somehost.com/path/
|
||||
delegate_to: delegate.host
|
||||
|
||||
- name: Synchronization using rsync protocol on delegate host (pull)
|
||||
synchronize:
|
||||
mode: pull
|
||||
src: rsync://somehost.com/path/
|
||||
dest: /some/absolute/path/
|
||||
delegate_to: delegate.host
|
||||
|
||||
- name: Synchronization without any --archive options enabled
|
||||
synchronize:
|
||||
src: some/relative/path
|
||||
dest: /some/absolute/path
|
||||
archive: no
|
||||
|
||||
- name: Synchronization with --archive options enabled except for --recursive
|
||||
synchronize:
|
||||
src: some/relative/path
|
||||
dest: /some/absolute/path
|
||||
recursive: no
|
||||
|
||||
- name: Synchronization with --archive options enabled except for --times, with --checksum option enabled
|
||||
synchronize:
|
||||
src: some/relative/path
|
||||
dest: /some/absolute/path
|
||||
checksum: yes
|
||||
times: no
|
||||
|
||||
- name: Synchronization without --archive options enabled except use --links
|
||||
synchronize:
|
||||
src: some/relative/path
|
||||
dest: /some/absolute/path
|
||||
archive: no
|
||||
links: yes
|
||||
|
||||
- name: Synchronization of two paths both on the control machine
|
||||
synchronize:
|
||||
src: some/relative/path
|
||||
dest: /some/absolute/path
|
||||
delegate_to: localhost
|
||||
|
||||
- name: Synchronization of src on the inventory host to the dest on the localhost in pull mode
|
||||
synchronize:
|
||||
mode: pull
|
||||
src: some/relative/path
|
||||
dest: /some/absolute/path
|
||||
|
||||
- name: Synchronization of src on delegate host to dest on the current inventory host.
|
||||
synchronize:
|
||||
src: /first/absolute/path
|
||||
dest: /second/absolute/path
|
||||
delegate_to: delegate.host
|
||||
|
||||
- name: Synchronize two directories on one remote host.
|
||||
synchronize:
|
||||
src: /first/absolute/path
|
||||
dest: /second/absolute/path
|
||||
delegate_to: "{{ inventory_hostname }}"
|
||||
|
||||
- name: Synchronize and delete files in dest on the remote host that are not found in src of localhost.
|
||||
synchronize:
|
||||
src: some/relative/path
|
||||
dest: /some/absolute/path
|
||||
delete: yes
|
||||
recursive: yes
|
||||
|
||||
# This specific command is granted su privileges on the destination
|
||||
- name: Synchronize using an alternate rsync command
|
||||
synchronize:
|
||||
src: some/relative/path
|
||||
dest: /some/absolute/path
|
||||
rsync_path: su -c rsync
|
||||
|
||||
# Example .rsync-filter file in the source directory
|
||||
# - var # exclude any path whose last part is 'var'
|
||||
# - /var # exclude any path starting with 'var' starting at the source directory
|
||||
# + /var/conf # include /var/conf even though it was previously excluded
|
||||
|
||||
- name: Synchronize passing in extra rsync options
|
||||
synchronize:
|
||||
src: /tmp/helloworld
|
||||
dest: /var/www/helloworld
|
||||
rsync_opts:
|
||||
- "--no-motd"
|
||||
- "--exclude=.git"
|
||||
|
||||
# Hardlink files if they didn't change
|
||||
- name: Use hardlinks when synchronizing filesystems
|
||||
synchronize:
|
||||
src: /tmp/path_a/foo.txt
|
||||
dest: /tmp/path_b/foo.txt
|
||||
link_dest: /tmp/path_a/
|
||||
|
||||
# Specify the rsync binary to use on remote host and on local host
|
||||
- hosts: groupofhosts
|
||||
vars:
|
||||
ansible_rsync_path: /usr/gnu/bin/rsync
|
||||
|
||||
tasks:
|
||||
- name: copy /tmp/localpath/ to remote location /tmp/remotepath
|
||||
synchronize:
|
||||
src: /tmp/localpath/
|
||||
dest: /tmp/remotepath
|
||||
rsync_path: /usr/gnu/bin/rsync
|
||||
'''
|
||||
|
||||
|
||||
import os
|
||||
import errno
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils._text import to_bytes, to_native
|
||||
from ansible.module_utils.six.moves import shlex_quote
|
||||
|
||||
|
||||
client_addr = None
|
||||
|
||||
|
||||
def substitute_controller(path):
|
||||
global client_addr
|
||||
if not client_addr:
|
||||
ssh_env_string = os.environ.get('SSH_CLIENT', None)
|
||||
try:
|
||||
client_addr, _ = ssh_env_string.split(None, 1)
|
||||
except AttributeError:
|
||||
ssh_env_string = os.environ.get('SSH_CONNECTION', None)
|
||||
try:
|
||||
client_addr, _ = ssh_env_string.split(None, 1)
|
||||
except AttributeError:
|
||||
pass
|
||||
if not client_addr:
|
||||
raise ValueError
|
||||
|
||||
if path.startswith('localhost:'):
|
||||
path = path.replace('localhost', client_addr, 1)
|
||||
return path
|
||||
|
||||
|
||||
def is_rsh_needed(source, dest):
|
||||
if source.startswith('rsync://') or dest.startswith('rsync://'):
|
||||
return False
|
||||
if ':' in source or ':' in dest:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
src=dict(type='str', required=True),
|
||||
dest=dict(type='str', required=True),
|
||||
dest_port=dict(type='int'),
|
||||
delete=dict(type='bool', default=False),
|
||||
private_key=dict(type='path'),
|
||||
rsync_path=dict(type='str'),
|
||||
_local_rsync_path=dict(type='path', default='rsync'),
|
||||
_local_rsync_password=dict(type='str', no_log=True),
|
||||
_substitute_controller=dict(type='bool', default=False),
|
||||
archive=dict(type='bool', default=True),
|
||||
checksum=dict(type='bool', default=False),
|
||||
compress=dict(type='bool', default=True),
|
||||
existing_only=dict(type='bool', default=False),
|
||||
dirs=dict(type='bool', default=False),
|
||||
recursive=dict(type='bool'),
|
||||
links=dict(type='bool'),
|
||||
copy_links=dict(type='bool', default=False),
|
||||
perms=dict(type='bool'),
|
||||
times=dict(type='bool'),
|
||||
owner=dict(type='bool'),
|
||||
group=dict(type='bool'),
|
||||
set_remote_user=dict(type='bool', default=True),
|
||||
rsync_timeout=dict(type='int', default=0),
|
||||
rsync_opts=dict(type='list', default=[]),
|
||||
ssh_args=dict(type='str'),
|
||||
partial=dict(type='bool', default=False),
|
||||
verify_host=dict(type='bool', default=False),
|
||||
mode=dict(type='str', default='push', choices=['pull', 'push']),
|
||||
link_dest=dict(type='list')
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if module.params['_substitute_controller']:
|
||||
try:
|
||||
source = substitute_controller(module.params['src'])
|
||||
dest = substitute_controller(module.params['dest'])
|
||||
except ValueError:
|
||||
module.fail_json(msg='Could not determine controller hostname for rsync to send to')
|
||||
else:
|
||||
source = module.params['src']
|
||||
dest = module.params['dest']
|
||||
dest_port = module.params['dest_port']
|
||||
delete = module.params['delete']
|
||||
private_key = module.params['private_key']
|
||||
rsync_path = module.params['rsync_path']
|
||||
rsync = module.params.get('_local_rsync_path', 'rsync')
|
||||
rsync_password = module.params.get('_local_rsync_password')
|
||||
rsync_timeout = module.params.get('rsync_timeout', 'rsync_timeout')
|
||||
archive = module.params['archive']
|
||||
checksum = module.params['checksum']
|
||||
compress = module.params['compress']
|
||||
existing_only = module.params['existing_only']
|
||||
dirs = module.params['dirs']
|
||||
partial = module.params['partial']
|
||||
# the default of these params depends on the value of archive
|
||||
recursive = module.params['recursive']
|
||||
links = module.params['links']
|
||||
copy_links = module.params['copy_links']
|
||||
perms = module.params['perms']
|
||||
times = module.params['times']
|
||||
owner = module.params['owner']
|
||||
group = module.params['group']
|
||||
rsync_opts = module.params['rsync_opts']
|
||||
ssh_args = module.params['ssh_args']
|
||||
verify_host = module.params['verify_host']
|
||||
link_dest = module.params['link_dest']
|
||||
|
||||
if '/' not in rsync:
|
||||
rsync = module.get_bin_path(rsync, required=True)
|
||||
|
||||
cmd = [rsync, '--delay-updates', '-F']
|
||||
_sshpass_pipe = None
|
||||
if rsync_password:
|
||||
try:
|
||||
module.run_command(["sshpass"])
|
||||
except OSError:
|
||||
module.fail_json(
|
||||
msg="to use rsync connection with passwords, you must install the sshpass program"
|
||||
)
|
||||
_sshpass_pipe = os.pipe()
|
||||
cmd = ['sshpass', '-d' + to_native(_sshpass_pipe[0], errors='surrogate_or_strict')] + cmd
|
||||
if compress:
|
||||
cmd.append('--compress')
|
||||
if rsync_timeout:
|
||||
cmd.append('--timeout=%s' % rsync_timeout)
|
||||
if module.check_mode:
|
||||
cmd.append('--dry-run')
|
||||
if delete:
|
||||
cmd.append('--delete-after')
|
||||
if existing_only:
|
||||
cmd.append('--existing')
|
||||
if checksum:
|
||||
cmd.append('--checksum')
|
||||
if copy_links:
|
||||
cmd.append('--copy-links')
|
||||
if archive:
|
||||
cmd.append('--archive')
|
||||
if recursive is False:
|
||||
cmd.append('--no-recursive')
|
||||
if links is False:
|
||||
cmd.append('--no-links')
|
||||
if perms is False:
|
||||
cmd.append('--no-perms')
|
||||
if times is False:
|
||||
cmd.append('--no-times')
|
||||
if owner is False:
|
||||
cmd.append('--no-owner')
|
||||
if group is False:
|
||||
cmd.append('--no-group')
|
||||
else:
|
||||
if recursive is True:
|
||||
cmd.append('--recursive')
|
||||
if links is True:
|
||||
cmd.append('--links')
|
||||
if perms is True:
|
||||
cmd.append('--perms')
|
||||
if times is True:
|
||||
cmd.append('--times')
|
||||
if owner is True:
|
||||
cmd.append('--owner')
|
||||
if group is True:
|
||||
cmd.append('--group')
|
||||
if dirs:
|
||||
cmd.append('--dirs')
|
||||
|
||||
if source.startswith('rsync://') and dest.startswith('rsync://'):
|
||||
module.fail_json(msg='either src or dest must be a localhost', rc=1)
|
||||
|
||||
if is_rsh_needed(source, dest):
|
||||
|
||||
# https://github.com/ansible/ansible/issues/15907
|
||||
has_rsh = False
|
||||
for rsync_opt in rsync_opts:
|
||||
if '--rsh' in rsync_opt:
|
||||
has_rsh = True
|
||||
break
|
||||
|
||||
# if the user has not supplied an --rsh option go ahead and add ours
|
||||
if not has_rsh:
|
||||
ssh_cmd = [module.get_bin_path('ssh', required=True), '-S', 'none']
|
||||
if private_key is not None:
|
||||
ssh_cmd.extend(['-i', private_key])
|
||||
# If the user specified a port value
|
||||
# Note: The action plugin takes care of setting this to a port from
|
||||
# inventory if the user didn't specify an explicit dest_port
|
||||
if dest_port is not None:
|
||||
ssh_cmd.extend(['-o', 'Port=%s' % dest_port])
|
||||
if not verify_host:
|
||||
ssh_cmd.extend(['-o', 'StrictHostKeyChecking=no', '-o', 'UserKnownHostsFile=/dev/null'])
|
||||
ssh_cmd_str = ' '.join(shlex_quote(arg) for arg in ssh_cmd)
|
||||
if ssh_args:
|
||||
ssh_cmd_str += ' %s' % ssh_args
|
||||
cmd.append('--rsh=%s' % ssh_cmd_str)
|
||||
|
||||
if rsync_path:
|
||||
cmd.append('--rsync-path=%s' % rsync_path)
|
||||
|
||||
if rsync_opts:
|
||||
if '' in rsync_opts:
|
||||
module.warn('The empty string is present in rsync_opts which will cause rsync to'
|
||||
' transfer the current working directory. If this is intended, use "."'
|
||||
' instead to get rid of this warning. If this is unintended, check for'
|
||||
' problems in your playbook leading to empty string in rsync_opts.')
|
||||
cmd.extend(rsync_opts)
|
||||
|
||||
if partial:
|
||||
cmd.append('--partial')
|
||||
|
||||
if link_dest:
|
||||
cmd.append('-H')
|
||||
# verbose required because rsync does not believe that adding a
|
||||
# hardlink is actually a change
|
||||
cmd.append('-vv')
|
||||
for x in link_dest:
|
||||
link_path = os.path.abspath(os.path.expanduser(x))
|
||||
destination_path = os.path.abspath(os.path.dirname(dest))
|
||||
if destination_path.find(link_path) == 0:
|
||||
module.fail_json(msg='Hardlinking into a subdirectory of the source would cause recursion. %s and %s' % (destination_path, dest))
|
||||
cmd.append('--link-dest=%s' % link_path)
|
||||
|
||||
changed_marker = '<<CHANGED>>'
|
||||
cmd.append('--out-format=' + changed_marker + '%i %n%L')
|
||||
|
||||
# expand the paths
|
||||
if '@' not in source:
|
||||
source = os.path.expanduser(source)
|
||||
if '@' not in dest:
|
||||
dest = os.path.expanduser(dest)
|
||||
|
||||
cmd.append(source)
|
||||
cmd.append(dest)
|
||||
cmdstr = ' '.join(cmd)
|
||||
|
||||
# If we are using password authentication, write the password into the pipe
|
||||
if rsync_password:
|
||||
def _write_password_to_pipe(proc):
|
||||
os.close(_sshpass_pipe[0])
|
||||
try:
|
||||
os.write(_sshpass_pipe[1], to_bytes(rsync_password) + b'\n')
|
||||
except OSError as exc:
|
||||
# Ignore broken pipe errors if the sshpass process has exited.
|
||||
if exc.errno != errno.EPIPE or proc.poll() is None:
|
||||
raise
|
||||
|
||||
(rc, out, err) = module.run_command(
|
||||
cmd, pass_fds=_sshpass_pipe,
|
||||
before_communicate_callback=_write_password_to_pipe)
|
||||
else:
|
||||
(rc, out, err) = module.run_command(cmd)
|
||||
|
||||
if rc:
|
||||
return module.fail_json(msg=err, rc=rc, cmd=cmdstr)
|
||||
|
||||
if link_dest:
|
||||
# a leading period indicates no change
|
||||
changed = (changed_marker + '.') not in out
|
||||
else:
|
||||
changed = changed_marker in out
|
||||
|
||||
out_clean = out.replace(changed_marker, '')
|
||||
out_lines = out_clean.split('\n')
|
||||
while '' in out_lines:
|
||||
out_lines.remove('')
|
||||
if module._diff:
|
||||
diff = {'prepared': out_clean}
|
||||
return module.exit_json(changed=changed, msg=out_clean,
|
||||
rc=rc, cmd=cmdstr, stdout_lines=out_lines,
|
||||
diff=diff)
|
||||
|
||||
return module.exit_json(changed=changed, msg=out_clean,
|
||||
rc=rc, cmd=cmdstr, stdout_lines=out_lines)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,198 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2014, Richard Isaacson <richard.c.isaacson@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'core'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
---
|
||||
module: at
|
||||
short_description: Schedule the execution of a command or script file via the at command
|
||||
description:
|
||||
- Use this module to schedule a command or script file to run once in the future.
|
||||
- All jobs are executed in the 'a' queue.
|
||||
version_added: "1.5"
|
||||
options:
|
||||
command:
|
||||
description:
|
||||
- A command to be executed in the future.
|
||||
type: str
|
||||
script_file:
|
||||
description:
|
||||
- An existing script file to be executed in the future.
|
||||
type: str
|
||||
count:
|
||||
description:
|
||||
- The count of units in the future to execute the command or script file.
|
||||
type: int
|
||||
required: true
|
||||
units:
|
||||
description:
|
||||
- The type of units in the future to execute the command or script file.
|
||||
type: str
|
||||
required: true
|
||||
choices: [ minutes, hours, days, weeks ]
|
||||
state:
|
||||
description:
|
||||
- The state dictates if the command or script file should be evaluated as present(added) or absent(deleted).
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
unique:
|
||||
description:
|
||||
- If a matching job is present a new job will not be added.
|
||||
type: bool
|
||||
default: no
|
||||
requirements:
|
||||
- at
|
||||
author:
|
||||
- Richard Isaacson (@risaacson)
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Schedule a command to execute in 20 minutes as root
|
||||
at:
|
||||
command: ls -d / >/dev/null
|
||||
count: 20
|
||||
units: minutes
|
||||
|
||||
- name: Match a command to an existing job and delete the job
|
||||
at:
|
||||
command: ls -d / >/dev/null
|
||||
state: absent
|
||||
|
||||
- name: Schedule a command to execute in 20 minutes making sure it is unique in the queue
|
||||
at:
|
||||
command: ls -d / >/dev/null
|
||||
count: 20
|
||||
units: minutes
|
||||
unique: yes
|
||||
'''
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
def add_job(module, result, at_cmd, count, units, command, script_file):
|
||||
at_command = "%s -f %s now + %s %s" % (at_cmd, script_file, count, units)
|
||||
rc, out, err = module.run_command(at_command, check_rc=True)
|
||||
if command:
|
||||
os.unlink(script_file)
|
||||
result['changed'] = True
|
||||
|
||||
|
||||
def delete_job(module, result, at_cmd, command, script_file):
|
||||
for matching_job in get_matching_jobs(module, at_cmd, script_file):
|
||||
at_command = "%s -d %s" % (at_cmd, matching_job)
|
||||
rc, out, err = module.run_command(at_command, check_rc=True)
|
||||
result['changed'] = True
|
||||
if command:
|
||||
os.unlink(script_file)
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
def get_matching_jobs(module, at_cmd, script_file):
|
||||
matching_jobs = []
|
||||
|
||||
atq_cmd = module.get_bin_path('atq', True)
|
||||
|
||||
# Get list of job numbers for the user.
|
||||
atq_command = "%s" % atq_cmd
|
||||
rc, out, err = module.run_command(atq_command, check_rc=True)
|
||||
current_jobs = out.splitlines()
|
||||
if len(current_jobs) == 0:
|
||||
return matching_jobs
|
||||
|
||||
# Read script_file into a string.
|
||||
with open(script_file) as script_fh:
|
||||
script_file_string = script_fh.read().strip()
|
||||
|
||||
# Loop through the jobs.
|
||||
# If the script text is contained in a job add job number to list.
|
||||
for current_job in current_jobs:
|
||||
split_current_job = current_job.split()
|
||||
at_command = "%s -c %s" % (at_cmd, split_current_job[0])
|
||||
rc, out, err = module.run_command(at_command, check_rc=True)
|
||||
if script_file_string in out:
|
||||
matching_jobs.append(split_current_job[0])
|
||||
|
||||
# Return the list.
|
||||
return matching_jobs
|
||||
|
||||
|
||||
def create_tempfile(command):
|
||||
filed, script_file = tempfile.mkstemp(prefix='at')
|
||||
fileh = os.fdopen(filed, 'w')
|
||||
fileh.write(command)
|
||||
fileh.close()
|
||||
return script_file
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
command=dict(type='str'),
|
||||
script_file=dict(type='str'),
|
||||
count=dict(type='int'),
|
||||
units=dict(type='str', choices=['minutes', 'hours', 'days', 'weeks']),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present']),
|
||||
unique=dict(type='bool', default=False),
|
||||
),
|
||||
mutually_exclusive=[['command', 'script_file']],
|
||||
required_one_of=[['command', 'script_file']],
|
||||
supports_check_mode=False,
|
||||
)
|
||||
|
||||
at_cmd = module.get_bin_path('at', True)
|
||||
|
||||
command = module.params['command']
|
||||
script_file = module.params['script_file']
|
||||
count = module.params['count']
|
||||
units = module.params['units']
|
||||
state = module.params['state']
|
||||
unique = module.params['unique']
|
||||
|
||||
if (state == 'present') and (not count or not units):
|
||||
module.fail_json(msg="present state requires count and units")
|
||||
|
||||
result = dict(
|
||||
changed=False,
|
||||
state=state,
|
||||
)
|
||||
|
||||
# If command transform it into a script_file
|
||||
if command:
|
||||
script_file = create_tempfile(command)
|
||||
|
||||
# if absent remove existing and return
|
||||
if state == 'absent':
|
||||
delete_job(module, result, at_cmd, command, script_file)
|
||||
|
||||
# if unique if existing return unchanged
|
||||
if unique:
|
||||
if len(get_matching_jobs(module, at_cmd, script_file)) != 0:
|
||||
if command:
|
||||
os.unlink(script_file)
|
||||
module.exit_json(**result)
|
||||
|
||||
result['script_file'] = script_file
|
||||
result['count'] = count
|
||||
result['units'] = units
|
||||
|
||||
add_job(module, result, at_cmd, count, units, command, script_file)
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,680 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2012, Brad Olson <brado@movedbylight.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'core'}
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: authorized_key
|
||||
short_description: Adds or removes an SSH authorized key
|
||||
description:
|
||||
- Adds or removes SSH authorized keys for particular user accounts.
|
||||
version_added: "0.5"
|
||||
options:
|
||||
user:
|
||||
description:
|
||||
- The username on the remote host whose authorized_keys file will be modified.
|
||||
type: str
|
||||
required: true
|
||||
key:
|
||||
description:
|
||||
- The SSH public key(s), as a string or (since Ansible 1.9) url (https://github.com/username.keys).
|
||||
type: str
|
||||
required: true
|
||||
path:
|
||||
description:
|
||||
- Alternate path to the authorized_keys file.
|
||||
- When unset, this value defaults to I(~/.ssh/authorized_keys).
|
||||
type: path
|
||||
version_added: "1.2"
|
||||
manage_dir:
|
||||
description:
|
||||
- Whether this module should manage the directory of the authorized key file.
|
||||
- If set to C(yes), 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.
|
||||
type: bool
|
||||
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.
|
||||
type: str
|
||||
choices: [ absent, present ]
|
||||
default: present
|
||||
key_options:
|
||||
description:
|
||||
- A string of ssh key options to be prepended to the key in the authorized_keys file.
|
||||
version_added: "1.4"
|
||||
exclusive:
|
||||
description:
|
||||
- Whether to remove all other non-specified keys from the authorized_keys file.
|
||||
- Multiple keys can be specified in a single C(key) string value by separating them by newlines.
|
||||
- This option is not loop aware, so if you use C(with_) , it will be exclusive per iteration of the loop.
|
||||
- If you want multiple keys in the file you need to pass them all to C(key) in a single batch as mentioned above.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: "1.9"
|
||||
validate_certs:
|
||||
description:
|
||||
- This only applies if using a https url as the source of the keys.
|
||||
- If set to C(no), the SSL certificates will not be validated.
|
||||
- This should only set to C(no) used on personally controlled sites using self-signed certificates as it avoids verifying the source site.
|
||||
- Prior to 2.1 the code worked as if this was set to C(yes).
|
||||
type: bool
|
||||
default: yes
|
||||
version_added: "2.1"
|
||||
comment:
|
||||
description:
|
||||
- Change the comment on the public key.
|
||||
- Rewriting the comment is useful in cases such as fetching it from GitHub or GitLab.
|
||||
- If no comment is specified, the existing comment will be kept.
|
||||
type: str
|
||||
version_added: "2.4"
|
||||
follow:
|
||||
description:
|
||||
- Follow path symlink instead of replacing it.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: "2.7"
|
||||
author: Ansible Core Team
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Set authorized key taken from file
|
||||
authorized_key:
|
||||
user: charlie
|
||||
state: present
|
||||
key: "{{ lookup('file', '/home/charlie/.ssh/id_rsa.pub') }}"
|
||||
|
||||
- name: Set authorized keys taken from url
|
||||
authorized_key:
|
||||
user: charlie
|
||||
state: present
|
||||
key: https://github.com/charlie.keys
|
||||
|
||||
- name: Set authorized key in alternate location
|
||||
authorized_key:
|
||||
user: charlie
|
||||
state: present
|
||||
key: "{{ lookup('file', '/home/charlie/.ssh/id_rsa.pub') }}"
|
||||
path: /etc/ssh/authorized_keys/charlie
|
||||
manage_dir: False
|
||||
|
||||
- name: Set up multiple authorized keys
|
||||
authorized_key:
|
||||
user: deploy
|
||||
state: present
|
||||
key: '{{ item }}'
|
||||
with_file:
|
||||
- public_keys/doe-jane
|
||||
- public_keys/doe-john
|
||||
|
||||
- name: Set authorized key defining key options
|
||||
authorized_key:
|
||||
user: charlie
|
||||
state: present
|
||||
key: "{{ lookup('file', '/home/charlie/.ssh/id_rsa.pub') }}"
|
||||
key_options: 'no-port-forwarding,from="10.0.1.1"'
|
||||
|
||||
- name: Set authorized key without validating the TLS/SSL certificates
|
||||
authorized_key:
|
||||
user: charlie
|
||||
state: present
|
||||
key: https://github.com/user.keys
|
||||
validate_certs: False
|
||||
|
||||
- name: Set authorized key, removing all the authorized keys already set
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ lookup('file', 'public_keys/doe-jane') }}"
|
||||
state: present
|
||||
exclusive: True
|
||||
|
||||
- name: Set authorized key for user ubuntu copying it from current user
|
||||
authorized_key:
|
||||
user: ubuntu
|
||||
state: present
|
||||
key: "{{ lookup('file', lookup('env','HOME') + '/.ssh/id_rsa.pub') }}"
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
exclusive:
|
||||
description: If the key has been forced to be exclusive or not.
|
||||
returned: success
|
||||
type: bool
|
||||
sample: False
|
||||
key:
|
||||
description: The key that the module was running against.
|
||||
returned: success
|
||||
type: str
|
||||
sample: https://github.com/user.keys
|
||||
key_option:
|
||||
description: Key options related to the key.
|
||||
returned: success
|
||||
type: str
|
||||
sample: null
|
||||
keyfile:
|
||||
description: Path for authorized key file.
|
||||
returned: success
|
||||
type: str
|
||||
sample: /home/user/.ssh/authorized_keys
|
||||
manage_dir:
|
||||
description: Whether this module managed the directory of the authorized key file.
|
||||
returned: success
|
||||
type: bool
|
||||
sample: True
|
||||
path:
|
||||
description: Alternate path to the authorized_keys file
|
||||
returned: success
|
||||
type: str
|
||||
sample: null
|
||||
state:
|
||||
description: Whether the given key (with the given key_options) should or should not be in the file
|
||||
returned: success
|
||||
type: str
|
||||
sample: present
|
||||
unique:
|
||||
description: Whether the key is unique
|
||||
returned: success
|
||||
type: bool
|
||||
sample: false
|
||||
user:
|
||||
description: The username on the remote host whose authorized_keys file will be modified
|
||||
returned: success
|
||||
type: str
|
||||
sample: user
|
||||
validate_certs:
|
||||
description: This only applies if using a https url as the source of the keys. If set to C(no), the SSL certificates will not be validated.
|
||||
returned: success
|
||||
type: bool
|
||||
sample: true
|
||||
'''
|
||||
|
||||
# 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 os
|
||||
import pwd
|
||||
import os.path
|
||||
import tempfile
|
||||
import re
|
||||
import shlex
|
||||
from operator import itemgetter
|
||||
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.urls import fetch_url
|
||||
|
||||
|
||||
class keydict(dict):
|
||||
|
||||
""" a dictionary that maintains the order of keys as they are added
|
||||
|
||||
This has become an abuse of the dict interface. Probably should be
|
||||
rewritten to be an entirely custom object with methods instead of
|
||||
bracket-notation.
|
||||
|
||||
Our requirements are for a data structure that:
|
||||
* Preserves insertion order
|
||||
* Can store multiple values for a single key.
|
||||
|
||||
The present implementation has the following functions used by the rest of
|
||||
the code:
|
||||
|
||||
* __setitem__(): to add a key=value. The value can never be disassociated
|
||||
with the key, only new values can be added in addition.
|
||||
* items(): to retrieve the key, value pairs.
|
||||
|
||||
Other dict methods should work but may be surprising. For instance, there
|
||||
will be multiple keys that are the same in keys() and __getitem__() will
|
||||
return a list of the values that have been set via __setitem__.
|
||||
"""
|
||||
|
||||
# http://stackoverflow.com/questions/2328235/pythonextend-the-dict-class
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(keydict, self).__init__(*args, **kw)
|
||||
self.itemlist = list(super(keydict, self).keys())
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.itemlist.append(key)
|
||||
if key in self:
|
||||
self[key].append(value)
|
||||
else:
|
||||
super(keydict, self).__setitem__(key, [value])
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.itemlist)
|
||||
|
||||
def keys(self):
|
||||
return self.itemlist
|
||||
|
||||
def _item_generator(self):
|
||||
indexes = {}
|
||||
for key in self.itemlist:
|
||||
if key in indexes:
|
||||
indexes[key] += 1
|
||||
else:
|
||||
indexes[key] = 0
|
||||
yield key, self[key][indexes[key]]
|
||||
|
||||
def iteritems(self):
|
||||
raise NotImplementedError("Do not use this as it's not available on py3")
|
||||
|
||||
def items(self):
|
||||
return list(self._item_generator())
|
||||
|
||||
def itervalues(self):
|
||||
raise NotImplementedError("Do not use this as it's not available on py3")
|
||||
|
||||
def values(self):
|
||||
return [item[1] for item in self.items()]
|
||||
|
||||
|
||||
def keyfile(module, user, write=False, path=None, manage_dir=True, follow=False):
|
||||
"""
|
||||
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
|
||||
:param bool follow: if True symlinks will be followed and not replaced
|
||||
:return: full path string to authorized_keys for user
|
||||
"""
|
||||
|
||||
if module.check_mode and path is not None:
|
||||
keysfile = path
|
||||
|
||||
if follow:
|
||||
return os.path.realpath(keysfile)
|
||||
|
||||
return keysfile
|
||||
|
||||
try:
|
||||
user_entry = pwd.getpwnam(user)
|
||||
except KeyError as e:
|
||||
if module.check_mode and path is None:
|
||||
module.fail_json(msg="Either user must exist or you must provide full path to key file in check mode")
|
||||
module.fail_json(msg="Failed to lookup user %s: %s" % (user, to_native(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 follow:
|
||||
keysfile = os.path.realpath(keysfile)
|
||||
|
||||
if not write or module.check_mode:
|
||||
return keysfile
|
||||
|
||||
uid = user_entry.pw_uid
|
||||
gid = user_entry.pw_gid
|
||||
|
||||
if manage_dir:
|
||||
if not os.path.exists(sshdir):
|
||||
os.mkdir(sshdir, int('0700', 8))
|
||||
if module.selinux_enabled():
|
||||
module.set_default_selinux_context(sshdir, False)
|
||||
os.chown(sshdir, uid, gid)
|
||||
os.chmod(sshdir, int('0700', 8))
|
||||
|
||||
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, int('0600', 8))
|
||||
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:
|
||||
# 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
|
||||
|
||||
return options_dict
|
||||
|
||||
|
||||
def parsekey(module, raw_key, rank=None):
|
||||
'''
|
||||
parses a key, which may or may not contain a list
|
||||
of ssh-key options at the beginning
|
||||
|
||||
rank indicates the keys original ordering, so that
|
||||
it can be written out in the same order.
|
||||
'''
|
||||
|
||||
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(r'\#', '#')
|
||||
|
||||
# split key safely
|
||||
lex = shlex.shlex(raw_key)
|
||||
lex.quotes = []
|
||||
lex.commenters = '' # keep comment hashes
|
||||
lex.whitespace_split = True
|
||||
key_parts = list(lex)
|
||||
|
||||
if key_parts and key_parts[0] == '#':
|
||||
# comment line, invalid line, etc.
|
||||
return (raw_key, 'skipped', None, None, rank)
|
||||
|
||||
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, rank)
|
||||
|
||||
|
||||
def readfile(filename):
|
||||
|
||||
if not os.path.isfile(filename):
|
||||
return ''
|
||||
|
||||
f = open(filename)
|
||||
try:
|
||||
return f.read()
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
|
||||
def parsekeys(module, lines):
|
||||
keys = {}
|
||||
for rank_index, line in enumerate(lines.splitlines(True)):
|
||||
key_data = parsekey(module, line, rank=rank_index)
|
||||
if key_data:
|
||||
# use key as identifier
|
||||
keys[key_data[0]] = key_data
|
||||
else:
|
||||
# for an invalid line, just set the line
|
||||
# dict key to the line so it will be re-output later
|
||||
keys[line] = (line, 'skipped', None, None, rank_index)
|
||||
return keys
|
||||
|
||||
|
||||
def writefile(module, filename, content):
|
||||
|
||||
fd, tmp_path = tempfile.mkstemp('', 'tmp', os.path.dirname(filename))
|
||||
f = open(tmp_path, "w")
|
||||
|
||||
try:
|
||||
f.write(content)
|
||||
except IOError as e:
|
||||
module.fail_json(msg="Failed to write to file %s: %s" % (tmp_path, to_native(e)))
|
||||
f.close()
|
||||
module.atomic_move(tmp_path, filename)
|
||||
|
||||
|
||||
def serialize(keys):
|
||||
lines = []
|
||||
new_keys = keys.values()
|
||||
# order the new_keys by their original ordering, via the rank item in the tuple
|
||||
ordered_new_keys = sorted(new_keys, key=itemgetter(4))
|
||||
|
||||
for key in ordered_new_keys:
|
||||
try:
|
||||
(keyhash, key_type, options, comment, rank) = key
|
||||
|
||||
option_str = ""
|
||||
if options:
|
||||
option_strings = []
|
||||
for option_key, value in options.items():
|
||||
if value is None:
|
||||
option_strings.append("%s" % option_key)
|
||||
else:
|
||||
option_strings.append("%s=%s" % (option_key, value))
|
||||
option_str = ",".join(option_strings)
|
||||
option_str += " "
|
||||
|
||||
# comment line or invalid line, just leave it
|
||||
if not key_type:
|
||||
key_line = key
|
||||
|
||||
if key_type == 'skipped':
|
||||
key_line = key[0]
|
||||
else:
|
||||
key_line = "%s%s %s %s\n" % (option_str, key_type, keyhash, comment)
|
||||
except Exception:
|
||||
key_line = key
|
||||
lines.append(key_line)
|
||||
return ''.join(lines)
|
||||
|
||||
|
||||
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)
|
||||
exclusive = params.get("exclusive", False)
|
||||
comment = params.get("comment", None)
|
||||
follow = params.get('follow', False)
|
||||
error_msg = "Error getting key from: %s"
|
||||
|
||||
# if the key is a url, request it and use it as key source
|
||||
if key.startswith("http"):
|
||||
try:
|
||||
resp, info = fetch_url(module, key)
|
||||
if info['status'] != 200:
|
||||
module.fail_json(msg=error_msg % key)
|
||||
else:
|
||||
key = resp.read()
|
||||
except Exception:
|
||||
module.fail_json(msg=error_msg % key)
|
||||
|
||||
# resp.read gives bytes on python3, convert to native string type
|
||||
key = to_native(key, errors='surrogate_or_strict')
|
||||
|
||||
# extract individual keys into an array, skipping blank lines and comments
|
||||
new_keys = [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_content = readfile(params["keyfile"])
|
||||
existing_keys = parsekeys(module, existing_content)
|
||||
|
||||
# Add a place holder for keys that should exist in the state=present and
|
||||
# exclusive=true case
|
||||
keys_to_exist = []
|
||||
|
||||
# we will order any non exclusive new keys higher than all the existing keys,
|
||||
# resulting in the new keys being written to the key file after existing keys, but
|
||||
# in the order of new_keys
|
||||
max_rank_of_existing_keys = len(existing_keys)
|
||||
|
||||
# Check our new keys, if any of them exist we'll continue.
|
||||
for rank_index, new_key in enumerate(new_keys):
|
||||
parsed_new_key = parsekey(module, new_key, rank=rank_index)
|
||||
|
||||
if not parsed_new_key:
|
||||
module.fail_json(msg="invalid key specified: %s" % new_key)
|
||||
|
||||
if key_options is not None:
|
||||
parsed_options = parseoptions(module, key_options)
|
||||
# rank here is the rank in the provided new keys, which may be unrelated to rank in existing_keys
|
||||
parsed_new_key = (parsed_new_key[0], parsed_new_key[1], parsed_options, parsed_new_key[3], parsed_new_key[4])
|
||||
|
||||
if comment is not None:
|
||||
parsed_new_key = (parsed_new_key[0], parsed_new_key[1], parsed_new_key[2], comment, parsed_new_key[4])
|
||||
|
||||
matched = False
|
||||
non_matching_keys = []
|
||||
|
||||
if parsed_new_key[0] in existing_keys:
|
||||
# Then we check if everything (except the rank at index 4) 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[:4] != existing_keys[parsed_new_key[0]][:4] and state == "present":
|
||||
non_matching_keys.append(existing_keys[parsed_new_key[0]])
|
||||
else:
|
||||
matched = True
|
||||
|
||||
# handle idempotent state=present
|
||||
if state == "present":
|
||||
keys_to_exist.append(parsed_new_key[0])
|
||||
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
|
||||
|
||||
# new key that didn't exist before. Where should it go in the ordering?
|
||||
if not matched:
|
||||
# We want the new key to be after existing keys if not exclusive (rank > max_rank_of_existing_keys)
|
||||
total_rank = max_rank_of_existing_keys + parsed_new_key[4]
|
||||
# replace existing key tuple with new parsed key with its total rank
|
||||
existing_keys[parsed_new_key[0]] = (parsed_new_key[0], parsed_new_key[1], parsed_new_key[2], parsed_new_key[3], total_rank)
|
||||
do_write = True
|
||||
|
||||
elif state == "absent":
|
||||
if not matched:
|
||||
continue
|
||||
del existing_keys[parsed_new_key[0]]
|
||||
do_write = True
|
||||
|
||||
# remove all other keys to honor exclusive
|
||||
# for 'exclusive', make sure keys are written in the order the new keys were
|
||||
if state == "present" and exclusive:
|
||||
to_remove = frozenset(existing_keys).difference(keys_to_exist)
|
||||
for key in to_remove:
|
||||
del existing_keys[key]
|
||||
do_write = True
|
||||
|
||||
if do_write:
|
||||
filename = keyfile(module, user, do_write, path, manage_dir, follow)
|
||||
new_content = serialize(existing_keys)
|
||||
|
||||
diff = None
|
||||
if module._diff:
|
||||
diff = {
|
||||
'before_header': params['keyfile'],
|
||||
'after_header': filename,
|
||||
'before': existing_content,
|
||||
'after': new_content,
|
||||
}
|
||||
params['diff'] = diff
|
||||
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True, diff=diff)
|
||||
writefile(module, filename, new_content)
|
||||
params['changed'] = True
|
||||
else:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=False)
|
||||
|
||||
return params
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
user=dict(type='str', required=True),
|
||||
key=dict(type='str', required=True),
|
||||
path=dict(type='path'),
|
||||
manage_dir=dict(type='bool', default=True),
|
||||
state=dict(type='str', default='present', choices=['absent', 'present']),
|
||||
key_options=dict(type='str'),
|
||||
exclusive=dict(type='bool', default=False),
|
||||
comment=dict(type='str'),
|
||||
validate_certs=dict(type='bool', default=True),
|
||||
follow=dict(type='bool', default=False),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
results = enforce_state(module, module.params)
|
||||
module.exit_json(**results)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,767 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2012, Red Hat, inc
|
||||
# Written by Seth Vidal
|
||||
# based on the mount modules from salt and puppet
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'core'}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: mount
|
||||
short_description: Control active and configured mount points
|
||||
description:
|
||||
- This module controls active and configured mount points in C(/etc/fstab).
|
||||
author:
|
||||
- Ansible Core Team
|
||||
- Seth Vidal (@skvidal)
|
||||
version_added: "0.6"
|
||||
options:
|
||||
path:
|
||||
description:
|
||||
- Path to the mount point (e.g. C(/mnt/files)).
|
||||
- Before Ansible 2.3 this option was only usable as I(dest), I(destfile) and I(name).
|
||||
type: path
|
||||
required: true
|
||||
aliases: [ name ]
|
||||
src:
|
||||
description:
|
||||
- Device to be mounted on I(path).
|
||||
- Required when I(state) set to C(present) or C(mounted).
|
||||
type: path
|
||||
fstype:
|
||||
description:
|
||||
- Filesystem type.
|
||||
- Required when I(state) is C(present) or C(mounted).
|
||||
type: str
|
||||
opts:
|
||||
description:
|
||||
- Mount options (see fstab(5), or vfstab(4) on Solaris).
|
||||
type: str
|
||||
dump:
|
||||
description:
|
||||
- Dump (see fstab(5)).
|
||||
- Note that if set to C(null) and I(state) set to C(present),
|
||||
it will cease to work and duplicate entries will be made
|
||||
with subsequent runs.
|
||||
- Has no effect on Solaris systems.
|
||||
type: str
|
||||
default: 0
|
||||
passno:
|
||||
description:
|
||||
- Passno (see fstab(5)).
|
||||
- Note that if set to C(null) and I(state) set to C(present),
|
||||
it will cease to work and duplicate entries will be made
|
||||
with subsequent runs.
|
||||
- Deprecated on Solaris systems.
|
||||
type: str
|
||||
default: 0
|
||||
state:
|
||||
description:
|
||||
- If C(mounted), the device will be actively mounted and appropriately
|
||||
configured in I(fstab). If the mount point is not present, the mount
|
||||
point will be created.
|
||||
- If C(unmounted), the device will be unmounted without changing I(fstab).
|
||||
- C(present) only specifies that the device is to be configured in
|
||||
I(fstab) and does not trigger or require a mount.
|
||||
- C(absent) specifies that the device mount's entry will be removed from
|
||||
I(fstab) and will also unmount the device and remove the mount
|
||||
point.
|
||||
- C(remounted) specifies that the device will be remounted for when you
|
||||
want to force a refresh on the mount itself (added in 2.9). This will
|
||||
always return changed=true.
|
||||
type: str
|
||||
required: true
|
||||
choices: [ absent, mounted, present, unmounted, remounted ]
|
||||
fstab:
|
||||
description:
|
||||
- File to use instead of C(/etc/fstab).
|
||||
- You should not use this option unless you really know what you are doing.
|
||||
- This might be useful if you need to configure mountpoints in a chroot environment.
|
||||
- OpenBSD does not allow specifying alternate fstab files with mount so do not
|
||||
use this on OpenBSD with any state that operates on the live filesystem.
|
||||
- This parameter defaults to /etc/fstab or /etc/vfstab on Solaris.
|
||||
type: str
|
||||
boot:
|
||||
description:
|
||||
- Determines if the filesystem should be mounted on boot.
|
||||
- Only applies to Solaris systems.
|
||||
type: bool
|
||||
default: yes
|
||||
version_added: '2.2'
|
||||
backup:
|
||||
description:
|
||||
- Create a backup file including the timestamp information so you can get
|
||||
the original file back if you somehow clobbered it incorrectly.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: '2.5'
|
||||
notes:
|
||||
- As of Ansible 2.3, the I(name) option has been changed to I(path) as
|
||||
default, but I(name) still works as well.
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
# Before 2.3, option 'name' was used instead of 'path'
|
||||
- name: Mount DVD read-only
|
||||
mount:
|
||||
path: /mnt/dvd
|
||||
src: /dev/sr0
|
||||
fstype: iso9660
|
||||
opts: ro,noauto
|
||||
state: present
|
||||
|
||||
- name: Mount up device by label
|
||||
mount:
|
||||
path: /srv/disk
|
||||
src: LABEL=SOME_LABEL
|
||||
fstype: ext4
|
||||
state: present
|
||||
|
||||
- name: Mount up device by UUID
|
||||
mount:
|
||||
path: /home
|
||||
src: UUID=b3e48f45-f933-4c8e-a700-22a159ec9077
|
||||
fstype: xfs
|
||||
opts: noatime
|
||||
state: present
|
||||
|
||||
- name: Unmount a mounted volume
|
||||
mount:
|
||||
path: /tmp/mnt-pnt
|
||||
state: unmounted
|
||||
|
||||
- name: Mount and bind a volume
|
||||
mount:
|
||||
path: /system/new_volume/boot
|
||||
src: /boot
|
||||
opts: bind
|
||||
state: mounted
|
||||
fstype: none
|
||||
|
||||
- name: Mount an NFS volume
|
||||
mount:
|
||||
src: 192.168.1.100:/nfs/ssd/shared_data
|
||||
path: /mnt/shared_data
|
||||
opts: rw,sync,hard,intr
|
||||
state: mounted
|
||||
fstype: nfs
|
||||
'''
|
||||
|
||||
|
||||
import os
|
||||
import platform
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.ismount import ismount
|
||||
from ansible.module_utils.six import iteritems
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
|
||||
def write_fstab(module, lines, path):
|
||||
if module.params['backup']:
|
||||
module.backup_local(path)
|
||||
|
||||
fs_w = open(path, 'w')
|
||||
|
||||
for l in lines:
|
||||
fs_w.write(l)
|
||||
|
||||
fs_w.flush()
|
||||
fs_w.close()
|
||||
|
||||
|
||||
def _escape_fstab(v):
|
||||
"""Escape invalid characters in fstab fields.
|
||||
|
||||
space (040)
|
||||
ampersand (046)
|
||||
backslash (134)
|
||||
"""
|
||||
|
||||
if isinstance(v, int):
|
||||
return v
|
||||
else:
|
||||
return(
|
||||
v.
|
||||
replace('\\', '\\134').
|
||||
replace(' ', '\\040').
|
||||
replace('&', '\\046'))
|
||||
|
||||
|
||||
def set_mount(module, args):
|
||||
"""Set/change a mount point location in fstab."""
|
||||
|
||||
to_write = []
|
||||
exists = False
|
||||
changed = False
|
||||
escaped_args = dict([(k, _escape_fstab(v)) for k, v in iteritems(args)])
|
||||
new_line = '%(src)s %(name)s %(fstype)s %(opts)s %(dump)s %(passno)s\n'
|
||||
|
||||
if platform.system() == 'SunOS':
|
||||
new_line = (
|
||||
'%(src)s - %(name)s %(fstype)s %(passno)s %(boot)s %(opts)s\n')
|
||||
|
||||
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
|
||||
|
||||
fields = line.split()
|
||||
|
||||
# Check if we got a valid line for splitting
|
||||
# (on Linux the 5th and the 6th field is optional)
|
||||
if (
|
||||
platform.system() == 'SunOS' and len(fields) != 7 or
|
||||
platform.system() == 'Linux' and len(fields) not in [4, 5, 6] or
|
||||
platform.system() not in ['SunOS', 'Linux'] and len(fields) != 6):
|
||||
to_write.append(line)
|
||||
|
||||
continue
|
||||
|
||||
ld = {}
|
||||
|
||||
if platform.system() == 'SunOS':
|
||||
(
|
||||
ld['src'],
|
||||
dash,
|
||||
ld['name'],
|
||||
ld['fstype'],
|
||||
ld['passno'],
|
||||
ld['boot'],
|
||||
ld['opts']
|
||||
) = fields
|
||||
else:
|
||||
fields_labels = ['src', 'name', 'fstype', 'opts', 'dump', 'passno']
|
||||
|
||||
# The last two fields are optional on Linux so we fill in default values
|
||||
ld['dump'] = 0
|
||||
ld['passno'] = 0
|
||||
|
||||
# Fill in the rest of the available fields
|
||||
for i, field in enumerate(fields):
|
||||
ld[fields_labels[i]] = field
|
||||
|
||||
# Check if we found the correct line
|
||||
if (
|
||||
ld['name'] != escaped_args['name'] or (
|
||||
# In the case of swap, check the src instead
|
||||
'src' in args and
|
||||
ld['name'] == 'none' and
|
||||
ld['fstype'] == 'swap' and
|
||||
ld['src'] != args['src'])):
|
||||
to_write.append(line)
|
||||
|
||||
continue
|
||||
|
||||
# If we got here we found a match - let's check if there is any
|
||||
# difference
|
||||
exists = True
|
||||
args_to_check = ('src', 'fstype', 'opts', 'dump', 'passno')
|
||||
|
||||
if platform.system() == 'SunOS':
|
||||
args_to_check = ('src', 'fstype', 'passno', 'boot', 'opts')
|
||||
|
||||
for t in args_to_check:
|
||||
if ld[t] != escaped_args[t]:
|
||||
ld[t] = escaped_args[t]
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
to_write.append(new_line % ld)
|
||||
else:
|
||||
to_write.append(line)
|
||||
|
||||
if not exists:
|
||||
to_write.append(new_line % escaped_args)
|
||||
changed = True
|
||||
|
||||
if changed and not module.check_mode:
|
||||
write_fstab(module, to_write, args['fstab'])
|
||||
|
||||
return (args['name'], changed)
|
||||
|
||||
|
||||
def unset_mount(module, args):
|
||||
"""Remove a mount point from fstab."""
|
||||
|
||||
to_write = []
|
||||
changed = False
|
||||
escaped_name = _escape_fstab(args['name'])
|
||||
|
||||
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
|
||||
|
||||
# Check if we got a valid line for splitting
|
||||
if (
|
||||
platform.system() == 'SunOS' and len(line.split()) != 7 or
|
||||
platform.system() != 'SunOS' and len(line.split()) != 6):
|
||||
to_write.append(line)
|
||||
|
||||
continue
|
||||
|
||||
ld = {}
|
||||
|
||||
if platform.system() == 'SunOS':
|
||||
(
|
||||
ld['src'],
|
||||
dash,
|
||||
ld['name'],
|
||||
ld['fstype'],
|
||||
ld['passno'],
|
||||
ld['boot'],
|
||||
ld['opts']
|
||||
) = line.split()
|
||||
else:
|
||||
(
|
||||
ld['src'],
|
||||
ld['name'],
|
||||
ld['fstype'],
|
||||
ld['opts'],
|
||||
ld['dump'],
|
||||
ld['passno']
|
||||
) = line.split()
|
||||
|
||||
if (
|
||||
ld['name'] != escaped_name or (
|
||||
# In the case of swap, check the src instead
|
||||
'src' in args and
|
||||
ld['name'] == 'none' and
|
||||
ld['fstype'] == 'swap' and
|
||||
ld['src'] != args['src'])):
|
||||
to_write.append(line)
|
||||
|
||||
continue
|
||||
|
||||
# If we got here we found a match - continue and mark changed
|
||||
changed = True
|
||||
|
||||
if changed and not module.check_mode:
|
||||
write_fstab(module, to_write, args['fstab'])
|
||||
|
||||
return (args['name'], changed)
|
||||
|
||||
|
||||
def _set_fstab_args(fstab_file):
|
||||
result = []
|
||||
|
||||
if (
|
||||
fstab_file and
|
||||
fstab_file != '/etc/fstab' and
|
||||
platform.system().lower() != 'sunos'):
|
||||
if platform.system().lower().endswith('bsd'):
|
||||
result.append('-F')
|
||||
else:
|
||||
result.append('-T')
|
||||
|
||||
result.append(fstab_file)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def mount(module, args):
|
||||
"""Mount up a path or remount if needed."""
|
||||
|
||||
mount_bin = module.get_bin_path('mount', required=True)
|
||||
name = args['name']
|
||||
cmd = [mount_bin]
|
||||
|
||||
if platform.system().lower() == 'openbsd':
|
||||
# Use module.params['fstab'] here as args['fstab'] has been set to the
|
||||
# default value.
|
||||
if module.params['fstab'] is not None:
|
||||
module.fail_json(
|
||||
msg=(
|
||||
'OpenBSD does not support alternate fstab files. Do not '
|
||||
'specify the fstab parameter for OpenBSD hosts'))
|
||||
else:
|
||||
cmd += _set_fstab_args(args['fstab'])
|
||||
|
||||
cmd += [name]
|
||||
|
||||
rc, out, err = module.run_command(cmd)
|
||||
|
||||
if rc == 0:
|
||||
return 0, ''
|
||||
else:
|
||||
return rc, out + err
|
||||
|
||||
|
||||
def umount(module, path):
|
||||
"""Unmount a path."""
|
||||
|
||||
umount_bin = module.get_bin_path('umount', required=True)
|
||||
cmd = [umount_bin, path]
|
||||
|
||||
rc, out, err = module.run_command(cmd)
|
||||
|
||||
if rc == 0:
|
||||
return 0, ''
|
||||
else:
|
||||
return rc, out + err
|
||||
|
||||
|
||||
def remount(module, args):
|
||||
"""Try to use 'remount' first and fallback to (u)mount if unsupported."""
|
||||
mount_bin = module.get_bin_path('mount', required=True)
|
||||
cmd = [mount_bin]
|
||||
|
||||
# Multiplatform remount opts
|
||||
if platform.system().lower().endswith('bsd'):
|
||||
cmd += ['-u']
|
||||
else:
|
||||
cmd += ['-o', 'remount']
|
||||
|
||||
if platform.system().lower() == 'openbsd':
|
||||
# Use module.params['fstab'] here as args['fstab'] has been set to the
|
||||
# default value.
|
||||
if module.params['fstab'] is not None:
|
||||
module.fail_json(
|
||||
msg=(
|
||||
'OpenBSD does not support alternate fstab files. Do not '
|
||||
'specify the fstab parameter for OpenBSD hosts'))
|
||||
else:
|
||||
cmd += _set_fstab_args(args['fstab'])
|
||||
|
||||
cmd += [args['name']]
|
||||
out = err = ''
|
||||
|
||||
try:
|
||||
if platform.system().lower().endswith('bsd'):
|
||||
# Note: Forcing BSDs to do umount/mount due to BSD remount not
|
||||
# working as expected (suspect bug in the BSD mount command)
|
||||
# Interested contributor could rework this to use mount options on
|
||||
# the CLI instead of relying on fstab
|
||||
# https://github.com/ansible/ansible-modules-core/issues/5591
|
||||
rc = 1
|
||||
else:
|
||||
rc, out, err = module.run_command(cmd)
|
||||
except Exception:
|
||||
rc = 1
|
||||
|
||||
msg = ''
|
||||
|
||||
if rc != 0:
|
||||
msg = out + err
|
||||
rc, msg = umount(module, args['name'])
|
||||
|
||||
if rc == 0:
|
||||
rc, msg = mount(module, args)
|
||||
|
||||
return rc, msg
|
||||
|
||||
|
||||
# Note if we wanted to put this into module_utils we'd have to get permission
|
||||
# from @jupeter -- https://github.com/ansible/ansible-modules-core/pull/2923
|
||||
# @jtyr -- https://github.com/ansible/ansible-modules-core/issues/4439
|
||||
# and @abadger to relicense from GPLv3+
|
||||
def is_bind_mounted(module, linux_mounts, dest, src=None, fstype=None):
|
||||
"""Return whether the dest is bind mounted
|
||||
|
||||
:arg module: The AnsibleModule (used for helper functions)
|
||||
:arg dest: The directory to be mounted under. This is the primary means
|
||||
of identifying whether the destination is mounted.
|
||||
:kwarg src: The source directory. If specified, this is used to help
|
||||
ensure that we are detecting that the correct source is mounted there.
|
||||
:kwarg fstype: The filesystem type. If specified this is also used to
|
||||
help ensure that we are detecting the right mount.
|
||||
:kwarg linux_mounts: Cached list of mounts for Linux.
|
||||
:returns: True if the dest is mounted with src otherwise False.
|
||||
"""
|
||||
|
||||
is_mounted = False
|
||||
|
||||
if platform.system() == 'Linux' and linux_mounts is not None:
|
||||
if src is None:
|
||||
# That's for unmounted/absent
|
||||
if dest in linux_mounts:
|
||||
is_mounted = True
|
||||
else:
|
||||
if dest in linux_mounts:
|
||||
is_mounted = linux_mounts[dest]['src'] == src
|
||||
|
||||
else:
|
||||
bin_path = module.get_bin_path('mount', required=True)
|
||||
cmd = '%s -l' % bin_path
|
||||
rc, out, err = module.run_command(cmd)
|
||||
mounts = []
|
||||
|
||||
if len(out):
|
||||
mounts = to_native(out).strip().split('\n')
|
||||
|
||||
for mnt in mounts:
|
||||
arguments = mnt.split()
|
||||
|
||||
if (
|
||||
(arguments[0] == src or src is None) and
|
||||
arguments[2] == dest and
|
||||
(arguments[4] == fstype or fstype is None)):
|
||||
is_mounted = True
|
||||
|
||||
if is_mounted:
|
||||
break
|
||||
|
||||
return is_mounted
|
||||
|
||||
|
||||
def get_linux_mounts(module, mntinfo_file="/proc/self/mountinfo"):
|
||||
"""Gather mount information"""
|
||||
|
||||
try:
|
||||
f = open(mntinfo_file)
|
||||
except IOError:
|
||||
return
|
||||
|
||||
lines = map(str.strip, f.readlines())
|
||||
|
||||
try:
|
||||
f.close()
|
||||
except IOError:
|
||||
module.fail_json(msg="Cannot close file %s" % mntinfo_file)
|
||||
|
||||
mntinfo = {}
|
||||
|
||||
for line in lines:
|
||||
fields = line.split()
|
||||
|
||||
record = {
|
||||
'id': int(fields[0]),
|
||||
'parent_id': int(fields[1]),
|
||||
'root': fields[3],
|
||||
'dst': fields[4],
|
||||
'opts': fields[5],
|
||||
'fs': fields[-3],
|
||||
'src': fields[-2]
|
||||
}
|
||||
|
||||
mntinfo[record['id']] = record
|
||||
|
||||
mounts = {}
|
||||
|
||||
for mnt in mntinfo.values():
|
||||
if mnt['parent_id'] != 1 and mnt['parent_id'] in mntinfo:
|
||||
m = mntinfo[mnt['parent_id']]
|
||||
if (
|
||||
len(m['root']) > 1 and
|
||||
mnt['root'].startswith("%s/" % m['root'])):
|
||||
# Omit the parent's root in the child's root
|
||||
# == Example:
|
||||
# 140 136 253:2 /rootfs / rw - ext4 /dev/sdb2 rw
|
||||
# 141 140 253:2 /rootfs/tmp/aaa /tmp/bbb rw - ext4 /dev/sdb2 rw
|
||||
# == Expected result:
|
||||
# src=/tmp/aaa
|
||||
mnt['root'] = mnt['root'][len(m['root']):]
|
||||
|
||||
# Prepend the parent's dst to the child's root
|
||||
# == Example:
|
||||
# 42 60 0:35 / /tmp rw - tmpfs tmpfs rw
|
||||
# 78 42 0:35 /aaa /tmp/bbb rw - tmpfs tmpfs rw
|
||||
# == Expected result:
|
||||
# src=/tmp/aaa
|
||||
if m['dst'] != '/':
|
||||
mnt['root'] = "%s%s" % (m['dst'], mnt['root'])
|
||||
src = mnt['root']
|
||||
else:
|
||||
src = mnt['src']
|
||||
|
||||
record = {
|
||||
'dst': mnt['dst'],
|
||||
'src': src,
|
||||
'opts': mnt['opts'],
|
||||
'fs': mnt['fs']
|
||||
}
|
||||
|
||||
mounts[mnt['dst']] = record
|
||||
|
||||
return mounts
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
boot=dict(type='bool', default=True),
|
||||
dump=dict(type='str'),
|
||||
fstab=dict(type='str'),
|
||||
fstype=dict(type='str'),
|
||||
path=dict(type='path', required=True, aliases=['name']),
|
||||
opts=dict(type='str'),
|
||||
passno=dict(type='str'),
|
||||
src=dict(type='path'),
|
||||
backup=dict(type='bool', default=False),
|
||||
state=dict(type='str', required=True, choices=['absent', 'mounted', 'present', 'unmounted', 'remounted']),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
required_if=(
|
||||
['state', 'mounted', ['src', 'fstype']],
|
||||
['state', 'present', ['src', 'fstype']],
|
||||
),
|
||||
)
|
||||
|
||||
# solaris args:
|
||||
# name, src, fstype, opts, boot, passno, state, fstab=/etc/vfstab
|
||||
# linux args:
|
||||
# name, src, fstype, opts, dump, passno, state, fstab=/etc/fstab
|
||||
# Note: Do not modify module.params['fstab'] as we need to know if the user
|
||||
# explicitly specified it in mount() and remount()
|
||||
if platform.system().lower() == 'sunos':
|
||||
args = dict(
|
||||
name=module.params['path'],
|
||||
opts='-',
|
||||
passno='-',
|
||||
fstab=module.params['fstab'],
|
||||
boot='yes'
|
||||
)
|
||||
if args['fstab'] is None:
|
||||
args['fstab'] = '/etc/vfstab'
|
||||
else:
|
||||
args = dict(
|
||||
name=module.params['path'],
|
||||
opts='defaults',
|
||||
dump='0',
|
||||
passno='0',
|
||||
fstab=module.params['fstab']
|
||||
)
|
||||
if args['fstab'] is None:
|
||||
args['fstab'] = '/etc/fstab'
|
||||
|
||||
# FreeBSD doesn't have any 'default' so set 'rw' instead
|
||||
if platform.system() == 'FreeBSD':
|
||||
args['opts'] = 'rw'
|
||||
|
||||
linux_mounts = []
|
||||
|
||||
# Cache all mounts here in order we have consistent results if we need to
|
||||
# call is_bind_mounted() multiple times
|
||||
if platform.system() == 'Linux':
|
||||
linux_mounts = get_linux_mounts(module)
|
||||
|
||||
if linux_mounts is None:
|
||||
args['warnings'] = (
|
||||
'Cannot open file /proc/self/mountinfo. '
|
||||
'Bind mounts might be misinterpreted.')
|
||||
|
||||
# Override defaults with user specified params
|
||||
for key in ('src', 'fstype', 'passno', 'opts', 'dump', 'fstab'):
|
||||
if module.params[key] is not None:
|
||||
args[key] = module.params[key]
|
||||
|
||||
# If fstab file does not exist, we first need to create it. This mainly
|
||||
# happens when fstab option 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']))
|
||||
try:
|
||||
open(args['fstab'], 'a').close()
|
||||
except PermissionError as e:
|
||||
module.fail_json(msg="Failed to open %s due to permission issue" % args['fstab'])
|
||||
except Exception as e:
|
||||
module.fail_json(msg="Failed to open %s due to %s" % (args['fstab'], to_native(e)))
|
||||
|
||||
# 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['path']
|
||||
changed = False
|
||||
|
||||
if state == 'absent':
|
||||
name, changed = unset_mount(module, args)
|
||||
|
||||
if changed and not module.check_mode:
|
||||
if ismount(name) or is_bind_mounted(module, linux_mounts, name):
|
||||
res, msg = umount(module, name)
|
||||
|
||||
if res:
|
||||
module.fail_json(
|
||||
msg="Error unmounting %s: %s" % (name, msg))
|
||||
|
||||
if os.path.exists(name):
|
||||
try:
|
||||
os.rmdir(name)
|
||||
except (OSError, IOError) as e:
|
||||
module.fail_json(msg="Error rmdir %s: %s" % (name, to_native(e)))
|
||||
elif state == 'unmounted':
|
||||
if ismount(name) or is_bind_mounted(module, linux_mounts, name):
|
||||
if not module.check_mode:
|
||||
res, msg = umount(module, name)
|
||||
|
||||
if res:
|
||||
module.fail_json(
|
||||
msg="Error unmounting %s: %s" % (name, msg))
|
||||
|
||||
changed = True
|
||||
elif state == 'mounted':
|
||||
if not os.path.exists(args['src']):
|
||||
module.fail_json(msg="Unable to mount %s as it does not exist" % args['src'])
|
||||
|
||||
if not os.path.exists(name) and not module.check_mode:
|
||||
try:
|
||||
os.makedirs(name)
|
||||
except (OSError, IOError) as e:
|
||||
module.fail_json(
|
||||
msg="Error making dir %s: %s" % (name, to_native(e)))
|
||||
|
||||
name, changed = set_mount(module, args)
|
||||
res = 0
|
||||
|
||||
if (
|
||||
ismount(name) or
|
||||
is_bind_mounted(
|
||||
module, linux_mounts, name, args['src'], args['fstype'])):
|
||||
if changed and not module.check_mode:
|
||||
res, msg = remount(module, args)
|
||||
changed = True
|
||||
else:
|
||||
changed = True
|
||||
|
||||
if not module.check_mode:
|
||||
res, msg = mount(module, args)
|
||||
|
||||
if res:
|
||||
module.fail_json(msg="Error mounting %s: %s" % (name, msg))
|
||||
elif state == 'present':
|
||||
name, changed = set_mount(module, args)
|
||||
elif state == 'remounted':
|
||||
if not module.check_mode:
|
||||
res, msg = remount(module, args)
|
||||
|
||||
if res:
|
||||
module.fail_json(msg="Error remounting %s: %s" % (name, msg))
|
||||
|
||||
changed = True
|
||||
else:
|
||||
module.fail_json(msg='Unexpected position reached')
|
||||
|
||||
module.exit_json(changed=changed, **args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,337 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# Copyright: (c) 2012, Stephen Fromm <sfromm@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['stableinterface'],
|
||||
'supported_by': 'core'}
|
||||
|
||||
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
|
||||
persistent:
|
||||
description:
|
||||
- Set to C(yes) if the boolean setting should survive a reboot.
|
||||
type: bool
|
||||
default: 'no'
|
||||
state:
|
||||
description:
|
||||
- Desired boolean value
|
||||
type: bool
|
||||
required: true
|
||||
ignore_selinux_state:
|
||||
description:
|
||||
- Useful for scenarios (chrooted environment) that you can't get the real SELinux state.
|
||||
type: bool
|
||||
default: false
|
||||
version_added: '2.8'
|
||||
notes:
|
||||
- Not tested on any Debian based system.
|
||||
requirements:
|
||||
- libselinux-python
|
||||
- libsemanage-python
|
||||
author:
|
||||
- Stephen Fromm (@sfromm)
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
- name: Set httpd_can_network_connect flag on and keep it persistent across reboots
|
||||
seboolean:
|
||||
name: httpd_can_network_connect
|
||||
state: yes
|
||||
persistent: yes
|
||||
'''
|
||||
|
||||
import os
|
||||
import traceback
|
||||
|
||||
SELINUX_IMP_ERR = None
|
||||
try:
|
||||
import selinux
|
||||
HAVE_SELINUX = True
|
||||
except ImportError:
|
||||
SELINUX_IMP_ERR = traceback.format_exc()
|
||||
HAVE_SELINUX = False
|
||||
|
||||
SEMANAGE_IMP_ERR = None
|
||||
try:
|
||||
import semanage
|
||||
HAVE_SEMANAGE = True
|
||||
except ImportError:
|
||||
SEMANAGE_IMP_ERR = traceback.format_exc()
|
||||
HAVE_SEMANAGE = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils.six import binary_type
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
|
||||
|
||||
def get_runtime_status(ignore_selinux_state=False):
|
||||
return True if ignore_selinux_state is True else selinux.is_selinux_enabled()
|
||||
|
||||
|
||||
def has_boolean_value(module, name):
|
||||
bools = []
|
||||
try:
|
||||
rc, bools = selinux.security_get_boolean_names()
|
||||
except OSError:
|
||||
module.fail_json(msg="Failed to get list of boolean names")
|
||||
# work around for selinux who changed its API, see
|
||||
# https://github.com/ansible/ansible/issues/25651
|
||||
if len(bools) > 0:
|
||||
if isinstance(bools[0], binary_type):
|
||||
name = to_bytes(name)
|
||||
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:
|
||||
module.fail_json(msg="Failed to determine current state for boolean %s" % name)
|
||||
if state == 1:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def semanage_get_handle(module):
|
||||
handle = semanage.semanage_handle_create()
|
||||
if not handle:
|
||||
module.fail_json(msg="Failed to create semanage library handle")
|
||||
|
||||
managed = semanage.semanage_is_managed(handle)
|
||||
if managed <= 0:
|
||||
semanage.semanage_handle_destroy(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:
|
||||
semanage.semanage_handle_destroy(handle)
|
||||
module.fail_json(msg="Failed to connect to semanage")
|
||||
|
||||
return handle
|
||||
|
||||
|
||||
def semanage_begin_transaction(module, handle):
|
||||
if semanage.semanage_begin_transaction(handle) < 0:
|
||||
semanage.semanage_handle_destroy(handle)
|
||||
module.fail_json(msg="Failed to begin semanage transaction")
|
||||
|
||||
|
||||
def semanage_set_boolean_value(module, handle, name, value):
|
||||
rc, t_b = semanage.semanage_bool_create(handle)
|
||||
if rc < 0:
|
||||
semanage.semanage_handle_destroy(handle)
|
||||
module.fail_json(msg="Failed to create seboolean with semanage")
|
||||
|
||||
if semanage.semanage_bool_set_name(handle, t_b, name) < 0:
|
||||
semanage.semanage_handle_destroy(handle)
|
||||
module.fail_json(msg="Failed to set seboolean name with semanage")
|
||||
|
||||
rc, boolkey = semanage.semanage_bool_key_extract(handle, t_b)
|
||||
if rc < 0:
|
||||
semanage.semanage_handle_destroy(handle)
|
||||
module.fail_json(msg="Failed to extract boolean key with semanage")
|
||||
|
||||
rc, exists = semanage.semanage_bool_exists(handle, boolkey)
|
||||
if rc < 0:
|
||||
semanage.semanage_handle_destroy(handle)
|
||||
module.fail_json(msg="Failed to check if boolean is defined")
|
||||
if not exists:
|
||||
semanage.semanage_handle_destroy(handle)
|
||||
module.fail_json(msg="SELinux boolean %s is not defined in persistent policy" % name)
|
||||
|
||||
rc, sebool = semanage.semanage_bool_query(handle, boolkey)
|
||||
if rc < 0:
|
||||
semanage.semanage_handle_destroy(handle)
|
||||
module.fail_json(msg="Failed to query boolean in persistent policy")
|
||||
|
||||
semanage.semanage_bool_set_value(sebool, value)
|
||||
|
||||
if semanage.semanage_bool_modify_local(handle, boolkey, sebool) < 0:
|
||||
semanage.semanage_handle_destroy(handle)
|
||||
module.fail_json(msg="Failed to modify boolean key with semanage")
|
||||
|
||||
if semanage.semanage_bool_set_active(handle, boolkey, sebool) < 0:
|
||||
semanage.semanage_handle_destroy(handle)
|
||||
module.fail_json(msg="Failed to set boolean key active with semanage")
|
||||
|
||||
semanage.semanage_bool_key_free(boolkey)
|
||||
semanage.semanage_bool_free(t_b)
|
||||
semanage.semanage_bool_free(sebool)
|
||||
|
||||
|
||||
def semanage_get_boolean_value(module, handle, name):
|
||||
rc, t_b = semanage.semanage_bool_create(handle)
|
||||
if rc < 0:
|
||||
semanage.semanage_handle_destroy(handle)
|
||||
module.fail_json(msg="Failed to create seboolean with semanage")
|
||||
|
||||
if semanage.semanage_bool_set_name(handle, t_b, name) < 0:
|
||||
semanage.semanage_handle_destroy(handle)
|
||||
module.fail_json(msg="Failed to set seboolean name with semanage")
|
||||
|
||||
rc, boolkey = semanage.semanage_bool_key_extract(handle, t_b)
|
||||
if rc < 0:
|
||||
semanage.semanage_handle_destroy(handle)
|
||||
module.fail_json(msg="Failed to extract boolean key with semanage")
|
||||
|
||||
rc, exists = semanage.semanage_bool_exists(handle, boolkey)
|
||||
if rc < 0:
|
||||
semanage.semanage_handle_destroy(handle)
|
||||
module.fail_json(msg="Failed to check if boolean is defined")
|
||||
if not exists:
|
||||
semanage.semanage_handle_destroy(handle)
|
||||
module.fail_json(msg="SELinux boolean %s is not defined in persistent policy" % name)
|
||||
|
||||
rc, sebool = semanage.semanage_bool_query(handle, boolkey)
|
||||
if rc < 0:
|
||||
semanage.semanage_handle_destroy(handle)
|
||||
module.fail_json(msg="Failed to query boolean in persistent policy")
|
||||
|
||||
value = semanage.semanage_bool_get_value(sebool)
|
||||
|
||||
semanage.semanage_bool_key_free(boolkey)
|
||||
semanage.semanage_bool_free(t_b)
|
||||
semanage.semanage_bool_free(sebool)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def semanage_commit(module, handle, load=0):
|
||||
semanage.semanage_set_reload(handle, load)
|
||||
if semanage.semanage_commit(handle) < 0:
|
||||
semanage.semanage_handle_destroy(handle)
|
||||
module.fail_json(msg="Failed to commit changes to semanage")
|
||||
|
||||
|
||||
def semanage_destroy_handle(module, handle):
|
||||
rc = semanage.semanage_disconnect(handle)
|
||||
semanage.semanage_handle_destroy(handle)
|
||||
if rc < 0:
|
||||
module.fail_json(msg="Failed to disconnect from semanage")
|
||||
|
||||
|
||||
# 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):
|
||||
value = 0
|
||||
changed = False
|
||||
if state:
|
||||
value = 1
|
||||
try:
|
||||
handle = semanage_get_handle(module)
|
||||
semanage_begin_transaction(module, handle)
|
||||
cur_value = semanage_get_boolean_value(module, handle, name)
|
||||
if cur_value != value:
|
||||
changed = True
|
||||
if not module.check_mode:
|
||||
semanage_set_boolean_value(module, handle, name, value)
|
||||
semanage_commit(module, handle)
|
||||
semanage_destroy_handle(module, handle)
|
||||
except Exception as e:
|
||||
module.fail_json(msg=u"Failed to manage policy for boolean %s: %s" % (name, to_text(e)))
|
||||
return changed
|
||||
|
||||
|
||||
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:
|
||||
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(
|
||||
ignore_selinux_state=dict(type='bool', default=False),
|
||||
name=dict(type='str', required=True),
|
||||
persistent=dict(type='bool', default=False),
|
||||
state=dict(type='bool', required=True),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if not HAVE_SELINUX:
|
||||
module.fail_json(msg=missing_required_lib('libselinux-python'), exception=SELINUX_IMP_ERR)
|
||||
|
||||
if not HAVE_SEMANAGE:
|
||||
module.fail_json(msg=missing_required_lib('libsemanage-python'), exception=SEMANAGE_IMP_ERR)
|
||||
|
||||
ignore_selinux_state = module.params['ignore_selinux_state']
|
||||
|
||||
if not get_runtime_status(ignore_selinux_state):
|
||||
module.fail_json(msg="SELinux is disabled on this host.")
|
||||
|
||||
name = module.params['name']
|
||||
persistent = module.params['persistent']
|
||||
state = module.params['state']
|
||||
|
||||
result = dict(
|
||||
name=name,
|
||||
persistent=persistent,
|
||||
state=state
|
||||
)
|
||||
changed = False
|
||||
|
||||
if hasattr(selinux, 'selinux_boolean_sub'):
|
||||
# selinux_boolean_sub allows sites to rename a boolean and alias the old name
|
||||
# Feature only available in selinux library since 2012.
|
||||
name = selinux.selinux_boolean_sub(name)
|
||||
|
||||
if not has_boolean_value(module, name):
|
||||
module.fail_json(msg="SELinux boolean %s does not exist." % name)
|
||||
|
||||
if persistent:
|
||||
changed = semanage_boolean_value(module, name, state)
|
||||
else:
|
||||
cur_value = get_boolean_value(module, name)
|
||||
if cur_value != state:
|
||||
changed = True
|
||||
if not module.check_mode:
|
||||
changed = set_boolean_value(module, name, state)
|
||||
if not changed:
|
||||
module.fail_json(msg="Failed to set boolean %s to %s" % (name, state))
|
||||
try:
|
||||
selinux.security_commit_booleans()
|
||||
except Exception:
|
||||
module.fail_json(msg="Failed to commit pending boolean %s value" % name)
|
||||
|
||||
result['changed'] = changed
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,266 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright: (c) 2012, Derek Carter<goozbach@friocorte.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['stableinterface'],
|
||||
'supported_by': 'core'
|
||||
}
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
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:
|
||||
- The name of the SELinux policy to use (e.g. C(targeted)) will be required if state is not C(disabled).
|
||||
state:
|
||||
description:
|
||||
- The SELinux mode.
|
||||
required: true
|
||||
choices: [ disabled, enforcing, permissive ]
|
||||
configfile:
|
||||
description:
|
||||
- The path to the SELinux configuration file, if non-standard.
|
||||
default: /etc/selinux/config
|
||||
aliases: [ conf, file ]
|
||||
requirements: [ libselinux-python ]
|
||||
author:
|
||||
- Derek Carter (@goozbach) <goozbach@friocorte.com>
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Enable SELinux
|
||||
selinux:
|
||||
policy: targeted
|
||||
state: enforcing
|
||||
|
||||
- name: Put SELinux in permissive mode, logging actions that would be blocked.
|
||||
selinux:
|
||||
policy: targeted
|
||||
state: permissive
|
||||
|
||||
- name: Disable SELinux
|
||||
selinux:
|
||||
state: disabled
|
||||
'''
|
||||
|
||||
RETURN = r'''
|
||||
msg:
|
||||
description: Messages that describe changes that were made.
|
||||
returned: always
|
||||
type: str
|
||||
sample: Config SELinux state changed from 'disabled' to 'permissive'
|
||||
configfile:
|
||||
description: Path to SELinux configuration file.
|
||||
returned: always
|
||||
type: str
|
||||
sample: /etc/selinux/config
|
||||
policy:
|
||||
description: Name of the SELinux policy.
|
||||
returned: always
|
||||
type: str
|
||||
sample: targeted
|
||||
state:
|
||||
description: SELinux mode.
|
||||
returned: always
|
||||
type: str
|
||||
sample: enforcing
|
||||
reboot_required:
|
||||
description: Whether or not an reboot is required for the changes to take effect.
|
||||
returned: always
|
||||
type: bool
|
||||
sample: true
|
||||
'''
|
||||
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
import traceback
|
||||
|
||||
SELINUX_IMP_ERR = None
|
||||
try:
|
||||
import selinux
|
||||
HAS_SELINUX = True
|
||||
except ImportError:
|
||||
SELINUX_IMP_ERR = traceback.format_exc()
|
||||
HAS_SELINUX = False
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||
from ansible.module_utils.facts.utils import get_file_lines
|
||||
|
||||
|
||||
# getter subroutines
|
||||
def get_config_state(configfile):
|
||||
lines = get_file_lines(configfile, strip=False)
|
||||
|
||||
for line in lines:
|
||||
stateline = re.match(r'^SELINUX=.*$', line)
|
||||
if stateline:
|
||||
return line.split('=')[1].strip()
|
||||
|
||||
|
||||
def get_config_policy(configfile):
|
||||
lines = get_file_lines(configfile, strip=False)
|
||||
|
||||
for line in lines:
|
||||
stateline = re.match(r'^SELINUXTYPE=.*$', line)
|
||||
if stateline:
|
||||
return line.split('=')[1].strip()
|
||||
|
||||
|
||||
# setter subroutines
|
||||
def set_config_state(module, state, configfile):
|
||||
# SELINUX=permissive
|
||||
# edit config file with state value
|
||||
stateline = 'SELINUX=%s' % state
|
||||
lines = get_file_lines(configfile, strip=False)
|
||||
|
||||
tmpfd, tmpfile = tempfile.mkstemp()
|
||||
|
||||
with open(tmpfile, "w") as write_file:
|
||||
for line in lines:
|
||||
write_file.write(re.sub(r'^SELINUX=.*', stateline, line) + '\n')
|
||||
|
||||
module.atomic_move(tmpfile, configfile)
|
||||
|
||||
|
||||
def set_state(module, 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(module, policy, configfile):
|
||||
if not os.path.exists('/etc/selinux/%s/policy' % policy):
|
||||
module.fail_json(msg='Policy %s does not exist in /etc/selinux/' % policy)
|
||||
|
||||
# edit config file with state value
|
||||
# SELINUXTYPE=targeted
|
||||
policyline = 'SELINUXTYPE=%s' % policy
|
||||
lines = get_file_lines(configfile, strip=False)
|
||||
|
||||
tmpfd, tmpfile = tempfile.mkstemp()
|
||||
|
||||
with open(tmpfile, "w") as write_file:
|
||||
for line in lines:
|
||||
write_file.write(re.sub(r'^SELINUXTYPE=.*', policyline, line) + '\n')
|
||||
|
||||
module.atomic_move(tmpfile, configfile)
|
||||
|
||||
|
||||
def main():
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
policy=dict(type='str'),
|
||||
state=dict(type='str', required='True', choices=['enforcing', 'permissive', 'disabled']),
|
||||
configfile=dict(type='str', default='/etc/selinux/config', aliases=['conf', 'file']),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
if not HAS_SELINUX:
|
||||
module.fail_json(msg=missing_required_lib('libselinux-python'), exception=SELINUX_IMP_ERR)
|
||||
|
||||
# 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'
|
||||
reboot_required = False
|
||||
|
||||
if runtime_enabled:
|
||||
# enabled means 'enforcing' or 'permissive'
|
||||
if selinux.security_getenforce():
|
||||
runtime_state = 'enforcing'
|
||||
else:
|
||||
runtime_state = 'permissive'
|
||||
|
||||
if not os.path.isfile(configfile):
|
||||
module.fail_json(msg="Unable to find file {0}".format(configfile),
|
||||
details="Please install SELinux-policy package, "
|
||||
"if this package is not installed previously.")
|
||||
|
||||
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("Running SELinux policy changed from '%s' to '%s'" % (runtime_policy, policy))
|
||||
changed = True
|
||||
|
||||
if policy != config_policy:
|
||||
if module.check_mode:
|
||||
module.exit_json(changed=True)
|
||||
set_config_policy(module, policy, configfile)
|
||||
msgs.append("SELinux policy configuration in '%s' changed from '%s' to '%s'" % (configfile, config_policy, policy))
|
||||
changed = True
|
||||
|
||||
if state != runtime_state:
|
||||
if runtime_enabled:
|
||||
if state == 'disabled':
|
||||
if runtime_state != 'permissive':
|
||||
# Temporarily set state to permissive
|
||||
if not module.check_mode:
|
||||
set_state(module, 'permissive')
|
||||
module.warn("SELinux state temporarily changed from '%s' to 'permissive'. State change will take effect next reboot." % (runtime_state))
|
||||
changed = True
|
||||
else:
|
||||
module.warn('SELinux state change will take effect next reboot')
|
||||
reboot_required = True
|
||||
else:
|
||||
if not module.check_mode:
|
||||
set_state(module, state)
|
||||
msgs.append("SELinux state changed from '%s' to '%s'" % (runtime_state, state))
|
||||
|
||||
# Only report changes if the file is changed.
|
||||
# This prevents the task from reporting changes every time the task is run.
|
||||
changed = True
|
||||
else:
|
||||
module.warn("Reboot is required to set SELinux state to '%s'" % state)
|
||||
reboot_required = True
|
||||
|
||||
if state != config_state:
|
||||
if not module.check_mode:
|
||||
set_config_state(module, state, configfile)
|
||||
msgs.append("Config SELinux state changed from '%s' to '%s'" % (config_state, state))
|
||||
changed = True
|
||||
|
||||
module.exit_json(changed=changed, msg=', '.join(msgs), configfile=configfile, policy=policy, state=state, reboot_required=reboot_required)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,421 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2012, David "DaviXX" CHANIAL <david.chanial@gmail.com>
|
||||
# (c) 2014, James Tanner <tanner.jc@gmail.com>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['stableinterface'],
|
||||
'supported_by': 'core'}
|
||||
|
||||
|
||||
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
|
||||
aliases: [ 'key' ]
|
||||
value:
|
||||
description:
|
||||
- Desired value of the sysctl key.
|
||||
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.
|
||||
type: bool
|
||||
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.
|
||||
type: bool
|
||||
default: 'yes'
|
||||
sysctl_file:
|
||||
description:
|
||||
- Specifies the absolute path to C(sysctl.conf), if not C(/etc/sysctl.conf).
|
||||
default: /etc/sysctl.conf
|
||||
sysctl_set:
|
||||
description:
|
||||
- Verify token value with the sysctl command and set with -w if necessary
|
||||
type: bool
|
||||
default: 'no'
|
||||
version_added: 1.5
|
||||
author: "David CHANIAL (@davixx) <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 forwarding on in /proc and verify token value with the sysctl command
|
||||
- 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 platform
|
||||
import re
|
||||
import tempfile
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils.parsing.convert_bool import BOOLEANS_FALSE, BOOLEANS_TRUE
|
||||
from ansible.module_utils._text import to_native
|
||||
|
||||
|
||||
class SysctlModule(object):
|
||||
|
||||
# We have to use LANG=C because we are capturing STDERR of sysctl to detect
|
||||
# success or failure.
|
||||
LANG_ENV = {'LANG': 'C', 'LC_ALL': 'C', 'LC_MESSAGES': 'C'}
|
||||
|
||||
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):
|
||||
|
||||
self.platform = platform.system().lower()
|
||||
|
||||
# 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 current 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] and self.args['state'] == "absent":
|
||||
self.changed = True
|
||||
self.write_file = True
|
||||
elif self.file_values[thisname] != self.args['value']:
|
||||
self.changed = True
|
||||
self.write_file = True
|
||||
# with reload=yes we should check if the current system values are
|
||||
# correct, so that we know if we should reload
|
||||
elif self.args['reload']:
|
||||
if self.proc_value is None:
|
||||
self.changed = True
|
||||
elif not self._values_is_equal(self.proc_value, self.args['value']):
|
||||
self.changed = True
|
||||
|
||||
# use the sysctl command or not?
|
||||
if self.args['sysctl_set'] and self.args['state'] == "present":
|
||||
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.changed 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 isinstance(value, bool):
|
||||
if value:
|
||||
return '1'
|
||||
else:
|
||||
return '0'
|
||||
elif isinstance(value, string_types):
|
||||
if value.lower() in BOOLEANS_TRUE:
|
||||
return '1'
|
||||
elif value.lower() in BOOLEANS_FALSE:
|
||||
return '0'
|
||||
else:
|
||||
return value.strip()
|
||||
else:
|
||||
return value
|
||||
|
||||
def _stderr_failed(self, err):
|
||||
# sysctl can fail to set a value even if it returns an exit status 0
|
||||
# (https://bugzilla.redhat.com/show_bug.cgi?id=1264080). That's why we
|
||||
# also have to check stderr for errors. For now we will only fail on
|
||||
# specific errors defined by the regex below.
|
||||
errors_regex = r'^sysctl: setting key "[^"]+": (Invalid argument|Read-only file system)$'
|
||||
return re.search(errors_regex, err, re.MULTILINE) is not None
|
||||
|
||||
# ==============================================================
|
||||
# SYSCTL COMMAND MANAGEMENT
|
||||
# ==============================================================
|
||||
|
||||
# Use the sysctl command to find the current value
|
||||
def get_token_curr_value(self, token):
|
||||
if self.platform == 'openbsd':
|
||||
# openbsd doesn't support -e, just drop it
|
||||
thiscmd = "%s -n %s" % (self.sysctl_cmd, token)
|
||||
else:
|
||||
thiscmd = "%s -e -n %s" % (self.sysctl_cmd, token)
|
||||
rc, out, err = self.module.run_command(thiscmd, environ_update=self.LANG_ENV)
|
||||
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 + '"'
|
||||
if self.platform == 'openbsd':
|
||||
# openbsd doesn't accept -w, but since it's not needed, just drop it
|
||||
thiscmd = "%s %s=%s" % (self.sysctl_cmd, token, value)
|
||||
elif self.platform == 'freebsd':
|
||||
ignore_missing = ''
|
||||
if self.args['ignoreerrors']:
|
||||
ignore_missing = '-i'
|
||||
# freebsd doesn't accept -w, but since it's not needed, just drop it
|
||||
thiscmd = "%s %s %s=%s" % (self.sysctl_cmd, ignore_missing, token, value)
|
||||
else:
|
||||
ignore_missing = ''
|
||||
if self.args['ignoreerrors']:
|
||||
ignore_missing = '-e'
|
||||
thiscmd = "%s %s -w %s=%s" % (self.sysctl_cmd, ignore_missing, token, value)
|
||||
rc, out, err = self.module.run_command(thiscmd, environ_update=self.LANG_ENV)
|
||||
if rc != 0 or self._stderr_failed(err):
|
||||
self.module.fail_json(msg='setting %s failed: %s' % (token, out + err))
|
||||
else:
|
||||
return rc
|
||||
|
||||
# Run sysctl -p
|
||||
def reload_sysctl(self):
|
||||
if self.platform == 'freebsd':
|
||||
# freebsd doesn't support -p, so reload the sysctl service
|
||||
rc, out, err = self.module.run_command('/etc/rc.d/sysctl reload', environ_update=self.LANG_ENV)
|
||||
elif self.platform == 'openbsd':
|
||||
# openbsd doesn't support -p and doesn't have a sysctl service,
|
||||
# so we have to set every value with its own sysctl call
|
||||
for k, v in self.file_values.items():
|
||||
rc = 0
|
||||
if k != self.args['name']:
|
||||
rc = self.set_token_value(k, v)
|
||||
# FIXME this check is probably not needed as set_token_value would fail_json if rc != 0
|
||||
if rc != 0:
|
||||
break
|
||||
if rc == 0 and self.args['state'] == "present":
|
||||
rc = self.set_token_value(self.args['name'], self.args['value'])
|
||||
|
||||
# set_token_value would have called fail_json in case of failure
|
||||
# so return here and do not continue to the error processing below
|
||||
# https://github.com/ansible/ansible/issues/58158
|
||||
return
|
||||
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, environ_update=self.LANG_ENV)
|
||||
|
||||
if rc != 0 or self._stderr_failed(err):
|
||||
self.module.fail_json(msg="Failed to reload sysctl: %s" % to_native(out) + to_native(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:
|
||||
with open(self.sysctl_file, "r") as read_file:
|
||||
lines = read_file.readlines()
|
||||
except IOError as e:
|
||||
self.module.fail_json(msg="Failed to open %s: %s" % (to_native(self.sysctl_file), to_native(e)))
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
self.file_lines.append(line)
|
||||
|
||||
# don't split empty lines or comments or line without equal sign
|
||||
if not line or line.startswith(("#", ";")) or "=" not in line:
|
||||
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(("#", ";")) or "=" not in line:
|
||||
self.fixed_lines.append(line)
|
||||
continue
|
||||
tmpline = line.strip()
|
||||
k, v = tmpline.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 as e:
|
||||
self.module.fail_json(msg="Failed to write to file %s: %s" % (tmp_path, to_native(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, type='str'),
|
||||
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', type='path')
|
||||
),
|
||||
supports_check_mode=True,
|
||||
required_if=[('state', 'present', ['value'])],
|
||||
)
|
||||
|
||||
if module.params['name'] is None:
|
||||
module.fail_json(msg="name cannot be None")
|
||||
if module.params['state'] == 'present' and module.params['value'] is None:
|
||||
module.fail_json(msg="value cannot be None")
|
||||
|
||||
# In case of in-line params
|
||||
if module.params['name'] == '':
|
||||
module.fail_json(msg="name cannot be blank")
|
||||
if module.params['state'] == 'present' and module.params['value'] == '':
|
||||
module.fail_json(msg="value cannot be blank")
|
||||
|
||||
result = SysctlModule(module)
|
||||
|
||||
module.exit_json(changed=result.changed)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,72 +0,0 @@
|
|||
# (c) 2015, Brian Coca <briancoca+dev@gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import os
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleAction, _AnsibleActionDone, AnsibleActionFail
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.parsing.convert_bool import boolean
|
||||
from ansible.plugins.action import ActionBase
|
||||
|
||||
|
||||
class ActionModule(ActionBase):
|
||||
|
||||
TRANSFERS_FILES = True
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
if task_vars is None:
|
||||
task_vars = dict()
|
||||
|
||||
result = super(ActionModule, self).run(tmp, task_vars)
|
||||
del tmp # tmp no longer has any effect
|
||||
|
||||
src = self._task.args.get('src', None)
|
||||
remote_src = boolean(self._task.args.get('remote_src', 'no'), strict=False)
|
||||
|
||||
try:
|
||||
if src is None:
|
||||
raise AnsibleActionFail("src is required")
|
||||
elif remote_src:
|
||||
# everything is remote, so we just execute the module
|
||||
# without changing any of the module arguments
|
||||
raise _AnsibleActionDone(result=self._execute_module(task_vars=task_vars))
|
||||
|
||||
try:
|
||||
src = self._find_needle('files', src)
|
||||
except AnsibleError as e:
|
||||
raise AnsibleActionFail(to_native(e))
|
||||
|
||||
tmp_src = self._connection._shell.join_path(self._connection._shell.tmpdir, os.path.basename(src))
|
||||
self._transfer_file(src, tmp_src)
|
||||
self._fixup_perms2((self._connection._shell.tmpdir, tmp_src))
|
||||
|
||||
new_module_args = self._task.args.copy()
|
||||
new_module_args.update(
|
||||
dict(
|
||||
src=tmp_src,
|
||||
)
|
||||
)
|
||||
|
||||
result.update(self._execute_module('patch', module_args=new_module_args, task_vars=task_vars))
|
||||
except AnsibleAction as e:
|
||||
result.update(e.result)
|
||||
finally:
|
||||
self._remove_tmp_path(self._connection._shell.tmpdir)
|
||||
return result
|
|
@ -1,420 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# (c) 2012-2013, Timothy Appnel <tim@appnel.com>
|
||||
#
|
||||
# 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/>.
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import os.path
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.common._collections_compat import MutableSequence
|
||||
from ansible.module_utils.parsing.convert_bool import boolean
|
||||
from ansible.plugins.action import ActionBase
|
||||
from ansible.plugins.loader import connection_loader
|
||||
|
||||
|
||||
class ActionModule(ActionBase):
|
||||
|
||||
def _get_absolute_path(self, path):
|
||||
original_path = path
|
||||
|
||||
if path.startswith('rsync://'):
|
||||
return path
|
||||
|
||||
if self._task._role is not None:
|
||||
path = self._loader.path_dwim_relative(self._task._role._role_path, 'files', path)
|
||||
else:
|
||||
path = self._loader.path_dwim_relative(self._loader.get_basedir(), 'files', path)
|
||||
|
||||
if original_path and original_path[-1] == '/' and path[-1] != '/':
|
||||
# make sure the dwim'd path ends in a trailing "/"
|
||||
# if the original path did
|
||||
path += '/'
|
||||
|
||||
return path
|
||||
|
||||
def _host_is_ipv6_address(self, host):
|
||||
return ':' in to_text(host, errors='surrogate_or_strict')
|
||||
|
||||
def _format_rsync_rsh_target(self, host, path, user):
|
||||
''' formats rsync rsh target, escaping ipv6 addresses if needed '''
|
||||
|
||||
user_prefix = ''
|
||||
|
||||
if path.startswith('rsync://'):
|
||||
return path
|
||||
|
||||
# If using docker or buildah, do not add user information
|
||||
if self._remote_transport not in ['docker', 'buildah'] and user:
|
||||
user_prefix = '%s@' % (user, )
|
||||
|
||||
if self._host_is_ipv6_address(host):
|
||||
return '[%s%s]:%s' % (user_prefix, host, path)
|
||||
else:
|
||||
return '%s%s:%s' % (user_prefix, host, path)
|
||||
|
||||
def _process_origin(self, host, path, user):
|
||||
|
||||
if host not in C.LOCALHOST:
|
||||
return self._format_rsync_rsh_target(host, path, user)
|
||||
|
||||
if ':' not in path and not path.startswith('/'):
|
||||
path = self._get_absolute_path(path=path)
|
||||
return path
|
||||
|
||||
def _process_remote(self, task_args, host, path, user, port_matches_localhost_port):
|
||||
"""
|
||||
:arg host: hostname for the path
|
||||
:arg path: file path
|
||||
:arg user: username for the transfer
|
||||
:arg port_matches_localhost_port: boolean whether the remote port
|
||||
matches the port used by localhost's sshd. This is used in
|
||||
conjunction with seeing whether the host is localhost to know
|
||||
if we need to have the module substitute the pathname or if it
|
||||
is a different host (for instance, an ssh tunnelled port or an
|
||||
alternative ssh port to a vagrant host.)
|
||||
"""
|
||||
transport = self._connection.transport
|
||||
# If we're connecting to a remote host or we're delegating to another
|
||||
# host or we're connecting to a different ssh instance on the
|
||||
# localhost then we have to format the path as a remote rsync path
|
||||
if host not in C.LOCALHOST or transport != "local" or \
|
||||
(host in C.LOCALHOST and not port_matches_localhost_port):
|
||||
# If we're delegating to non-localhost and but the
|
||||
# inventory_hostname host is localhost then we need the module to
|
||||
# fix up the rsync path to use the controller's public DNS/IP
|
||||
# instead of "localhost"
|
||||
if port_matches_localhost_port and host in C.LOCALHOST:
|
||||
task_args['_substitute_controller'] = True
|
||||
return self._format_rsync_rsh_target(host, path, user)
|
||||
|
||||
if ':' not in path and not path.startswith('/'):
|
||||
path = self._get_absolute_path(path=path)
|
||||
return path
|
||||
|
||||
def _override_module_replaced_vars(self, task_vars):
|
||||
""" Some vars are substituted into the modules. Have to make sure
|
||||
that those are correct for localhost when synchronize creates its own
|
||||
connection to localhost."""
|
||||
|
||||
# Clear the current definition of these variables as they came from the
|
||||
# connection to the remote host
|
||||
if 'ansible_syslog_facility' in task_vars:
|
||||
del task_vars['ansible_syslog_facility']
|
||||
for key in list(task_vars.keys()):
|
||||
if key.startswith("ansible_") and key.endswith("_interpreter"):
|
||||
del task_vars[key]
|
||||
|
||||
# Add the definitions from localhost
|
||||
for host in C.LOCALHOST:
|
||||
if host in task_vars['hostvars']:
|
||||
localhost = task_vars['hostvars'][host]
|
||||
break
|
||||
if 'ansible_syslog_facility' in localhost:
|
||||
task_vars['ansible_syslog_facility'] = localhost['ansible_syslog_facility']
|
||||
for key in localhost:
|
||||
if key.startswith("ansible_") and key.endswith("_interpreter"):
|
||||
task_vars[key] = localhost[key]
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
''' generates params and passes them on to the rsync module '''
|
||||
# When modifying this function be aware of the tricky convolutions
|
||||
# your thoughts have to go through:
|
||||
#
|
||||
# In normal ansible, we connect from controller to inventory_hostname
|
||||
# (playbook's hosts: field) or controller to delegate_to host and run
|
||||
# a module on one of those hosts.
|
||||
#
|
||||
# So things that are directly related to the core of ansible are in
|
||||
# terms of that sort of connection that always originate on the
|
||||
# controller.
|
||||
#
|
||||
# In synchronize we use ansible to connect to either the controller or
|
||||
# to the delegate_to host and then run rsync which makes its own
|
||||
# connection from controller to inventory_hostname or delegate_to to
|
||||
# inventory_hostname.
|
||||
#
|
||||
# That means synchronize needs to have some knowledge of the
|
||||
# controller to inventory_host/delegate host that ansible typically
|
||||
# establishes and use those to construct a command line for rsync to
|
||||
# connect from the inventory_host to the controller/delegate. The
|
||||
# challenge for coders is remembering which leg of the trip is
|
||||
# associated with the conditions that you're checking at any one time.
|
||||
if task_vars is None:
|
||||
task_vars = dict()
|
||||
|
||||
# We make a copy of the args here because we may fail and be asked to
|
||||
# retry. If that happens we don't want to pass the munged args through
|
||||
# to our next invocation. Munged args are single use only.
|
||||
_tmp_args = self._task.args.copy()
|
||||
|
||||
result = super(ActionModule, self).run(tmp, task_vars)
|
||||
del tmp # tmp no longer has any effect
|
||||
|
||||
# Store remote connection type
|
||||
self._remote_transport = self._connection.transport
|
||||
|
||||
# Handle docker connection options
|
||||
if self._remote_transport == 'docker':
|
||||
self._docker_cmd = self._connection.docker_cmd
|
||||
if self._play_context.docker_extra_args:
|
||||
self._docker_cmd = "%s %s" % (self._docker_cmd, self._play_context.docker_extra_args)
|
||||
|
||||
# self._connection accounts for delegate_to so
|
||||
# remote_transport is the transport ansible thought it would need
|
||||
# between the controller and the delegate_to host or the controller
|
||||
# and the remote_host if delegate_to isn't set.
|
||||
|
||||
remote_transport = False
|
||||
if self._connection.transport != 'local':
|
||||
remote_transport = True
|
||||
|
||||
try:
|
||||
delegate_to = self._task.delegate_to
|
||||
except (AttributeError, KeyError):
|
||||
delegate_to = None
|
||||
|
||||
# ssh paramiko docker buildah and local are fully supported transports. Anything
|
||||
# else only works with delegate_to
|
||||
if delegate_to is None and self._connection.transport not in \
|
||||
('ssh', 'paramiko', 'local', 'docker', 'buildah'):
|
||||
result['failed'] = True
|
||||
result['msg'] = (
|
||||
"synchronize uses rsync to function. rsync needs to connect to the remote "
|
||||
"host via ssh, docker client or a direct filesystem "
|
||||
"copy. This remote host is being accessed via %s instead "
|
||||
"so it cannot work." % self._connection.transport)
|
||||
return result
|
||||
|
||||
use_ssh_args = _tmp_args.pop('use_ssh_args', None)
|
||||
|
||||
# Parameter name needed by the ansible module
|
||||
_tmp_args['_local_rsync_path'] = task_vars.get('ansible_rsync_path') or 'rsync'
|
||||
_tmp_args['_local_rsync_password'] = task_vars.get('ansible_ssh_pass') or task_vars.get('ansible_password')
|
||||
|
||||
# rsync thinks that one end of the connection is localhost and the
|
||||
# other is the host we're running the task for (Note: We use
|
||||
# ansible's delegate_to mechanism to determine which host rsync is
|
||||
# running on so localhost could be a non-controller machine if
|
||||
# delegate_to is used)
|
||||
src_host = '127.0.0.1'
|
||||
inventory_hostname = task_vars.get('inventory_hostname')
|
||||
dest_host_inventory_vars = task_vars['hostvars'].get(inventory_hostname)
|
||||
try:
|
||||
dest_host = dest_host_inventory_vars['ansible_host']
|
||||
except KeyError:
|
||||
dest_host = dest_host_inventory_vars.get('ansible_ssh_host', inventory_hostname)
|
||||
|
||||
dest_host_ids = [hostid for hostid in (dest_host_inventory_vars.get('inventory_hostname'),
|
||||
dest_host_inventory_vars.get('ansible_host'),
|
||||
dest_host_inventory_vars.get('ansible_ssh_host'))
|
||||
if hostid is not None]
|
||||
|
||||
localhost_ports = set()
|
||||
for host in C.LOCALHOST:
|
||||
localhost_vars = task_vars['hostvars'].get(host, {})
|
||||
for port_var in C.MAGIC_VARIABLE_MAPPING['port']:
|
||||
port = localhost_vars.get(port_var, None)
|
||||
if port:
|
||||
break
|
||||
else:
|
||||
port = C.DEFAULT_REMOTE_PORT
|
||||
localhost_ports.add(port)
|
||||
|
||||
# dest_is_local tells us if the host rsync runs on is the same as the
|
||||
# host rsync puts the files on. This is about *rsync's connection*,
|
||||
# not about the ansible connection to run the module.
|
||||
dest_is_local = False
|
||||
if delegate_to is None and remote_transport is False:
|
||||
dest_is_local = True
|
||||
elif delegate_to is not None and delegate_to in dest_host_ids:
|
||||
dest_is_local = True
|
||||
|
||||
# CHECK FOR NON-DEFAULT SSH PORT
|
||||
inv_port = task_vars.get('ansible_ssh_port', None) or C.DEFAULT_REMOTE_PORT
|
||||
if _tmp_args.get('dest_port', None) is None:
|
||||
if inv_port is not None:
|
||||
_tmp_args['dest_port'] = inv_port
|
||||
|
||||
# Set use_delegate if we are going to run rsync on a delegated host
|
||||
# instead of localhost
|
||||
use_delegate = False
|
||||
if delegate_to is not None and delegate_to in dest_host_ids:
|
||||
# edge case: explicit delegate and dest_host are the same
|
||||
# so we run rsync on the remote machine targeting its localhost
|
||||
# (itself)
|
||||
dest_host = '127.0.0.1'
|
||||
use_delegate = True
|
||||
elif delegate_to is not None and remote_transport:
|
||||
# If we're delegating to a remote host then we need to use the
|
||||
# delegate_to settings
|
||||
use_delegate = True
|
||||
|
||||
# Delegate to localhost as the source of the rsync unless we've been
|
||||
# told (via delegate_to) that a different host is the source of the
|
||||
# rsync
|
||||
if not use_delegate and remote_transport:
|
||||
# Create a connection to localhost to run rsync on
|
||||
new_stdin = self._connection._new_stdin
|
||||
|
||||
# Unlike port, there can be only one shell
|
||||
localhost_shell = None
|
||||
for host in C.LOCALHOST:
|
||||
localhost_vars = task_vars['hostvars'].get(host, {})
|
||||
for shell_var in C.MAGIC_VARIABLE_MAPPING['shell']:
|
||||
localhost_shell = localhost_vars.get(shell_var, None)
|
||||
if localhost_shell:
|
||||
break
|
||||
if localhost_shell:
|
||||
break
|
||||
else:
|
||||
localhost_shell = os.path.basename(C.DEFAULT_EXECUTABLE)
|
||||
self._play_context.shell = localhost_shell
|
||||
|
||||
# Unlike port, there can be only one executable
|
||||
localhost_executable = None
|
||||
for host in C.LOCALHOST:
|
||||
localhost_vars = task_vars['hostvars'].get(host, {})
|
||||
for executable_var in C.MAGIC_VARIABLE_MAPPING['executable']:
|
||||
localhost_executable = localhost_vars.get(executable_var, None)
|
||||
if localhost_executable:
|
||||
break
|
||||
if localhost_executable:
|
||||
break
|
||||
else:
|
||||
localhost_executable = C.DEFAULT_EXECUTABLE
|
||||
self._play_context.executable = localhost_executable
|
||||
|
||||
new_connection = connection_loader.get('local', self._play_context, new_stdin)
|
||||
self._connection = new_connection
|
||||
# Override _remote_is_local as an instance attribute specifically for the synchronize use case
|
||||
# ensuring we set local tmpdir correctly
|
||||
self._connection._remote_is_local = True
|
||||
self._override_module_replaced_vars(task_vars)
|
||||
|
||||
# SWITCH SRC AND DEST HOST PER MODE
|
||||
if _tmp_args.get('mode', 'push') == 'pull':
|
||||
(dest_host, src_host) = (src_host, dest_host)
|
||||
|
||||
# MUNGE SRC AND DEST PER REMOTE_HOST INFO
|
||||
src = _tmp_args.get('src', None)
|
||||
dest = _tmp_args.get('dest', None)
|
||||
if src is None or dest is None:
|
||||
return dict(failed=True, msg="synchronize requires both src and dest parameters are set")
|
||||
|
||||
# Determine if we need a user@
|
||||
user = None
|
||||
if not dest_is_local:
|
||||
# Src and dest rsync "path" handling
|
||||
if boolean(_tmp_args.get('set_remote_user', 'yes'), strict=False):
|
||||
if use_delegate:
|
||||
user = task_vars.get('ansible_delegated_vars', dict()).get('ansible_ssh_user', None)
|
||||
if not user:
|
||||
user = task_vars.get('ansible_ssh_user') or self._play_context.remote_user
|
||||
if not user:
|
||||
user = C.DEFAULT_REMOTE_USER
|
||||
|
||||
else:
|
||||
user = task_vars.get('ansible_ssh_user') or self._play_context.remote_user
|
||||
|
||||
# Private key handling
|
||||
private_key = self._play_context.private_key_file
|
||||
|
||||
if private_key is not None:
|
||||
_tmp_args['private_key'] = private_key
|
||||
|
||||
# use the mode to define src and dest's url
|
||||
if _tmp_args.get('mode', 'push') == 'pull':
|
||||
# src is a remote path: <user>@<host>, dest is a local path
|
||||
src = self._process_remote(_tmp_args, src_host, src, user, inv_port in localhost_ports)
|
||||
dest = self._process_origin(dest_host, dest, user)
|
||||
else:
|
||||
# src is a local path, dest is a remote path: <user>@<host>
|
||||
src = self._process_origin(src_host, src, user)
|
||||
dest = self._process_remote(_tmp_args, dest_host, dest, user, inv_port in localhost_ports)
|
||||
else:
|
||||
# Still need to munge paths (to account for roles) even if we aren't
|
||||
# copying files between hosts
|
||||
if not src.startswith('/'):
|
||||
src = self._get_absolute_path(path=src)
|
||||
if not dest.startswith('/'):
|
||||
dest = self._get_absolute_path(path=dest)
|
||||
|
||||
_tmp_args['src'] = src
|
||||
_tmp_args['dest'] = dest
|
||||
|
||||
# Allow custom rsync path argument
|
||||
rsync_path = _tmp_args.get('rsync_path', None)
|
||||
|
||||
# backup original become as we are probably about to unset it
|
||||
become = self._play_context.become
|
||||
|
||||
if not dest_is_local:
|
||||
# don't escalate for docker. doing --rsync-path with docker exec fails
|
||||
# and we can switch directly to the user via docker arguments
|
||||
if self._play_context.become and not rsync_path and self._remote_transport != 'docker':
|
||||
# If no rsync_path is set, become was originally set, and dest is
|
||||
# remote then add privilege escalation here.
|
||||
if self._play_context.become_method == 'sudo':
|
||||
rsync_path = 'sudo rsync'
|
||||
# TODO: have to add in the rest of the become methods here
|
||||
|
||||
# We cannot use privilege escalation on the machine running the
|
||||
# module. Instead we run it on the machine rsync is connecting
|
||||
# to.
|
||||
self._play_context.become = False
|
||||
|
||||
_tmp_args['rsync_path'] = rsync_path
|
||||
|
||||
if use_ssh_args:
|
||||
ssh_args = [
|
||||
getattr(self._play_context, 'ssh_args', ''),
|
||||
getattr(self._play_context, 'ssh_common_args', ''),
|
||||
getattr(self._play_context, 'ssh_extra_args', ''),
|
||||
]
|
||||
_tmp_args['ssh_args'] = ' '.join([a for a in ssh_args if a])
|
||||
|
||||
# If launching synchronize against docker container
|
||||
# use rsync_opts to support container to override rsh options
|
||||
if self._remote_transport in ['docker', 'buildah'] and not use_delegate:
|
||||
# Replicate what we do in the module argumentspec handling for lists
|
||||
if not isinstance(_tmp_args.get('rsync_opts'), MutableSequence):
|
||||
tmp_rsync_opts = _tmp_args.get('rsync_opts', [])
|
||||
if isinstance(tmp_rsync_opts, string_types):
|
||||
tmp_rsync_opts = tmp_rsync_opts.split(',')
|
||||
elif isinstance(tmp_rsync_opts, (int, float)):
|
||||
tmp_rsync_opts = [to_text(tmp_rsync_opts)]
|
||||
_tmp_args['rsync_opts'] = tmp_rsync_opts
|
||||
|
||||
if '--blocking-io' not in _tmp_args['rsync_opts']:
|
||||
_tmp_args['rsync_opts'].append('--blocking-io')
|
||||
|
||||
if self._remote_transport in ['docker']:
|
||||
if become and self._play_context.become_user:
|
||||
_tmp_args['rsync_opts'].append("--rsh=%s exec -u %s -i" % (self._docker_cmd, self._play_context.become_user))
|
||||
elif user is not None:
|
||||
_tmp_args['rsync_opts'].append("--rsh=%s exec -u %s -i" % (self._docker_cmd, user))
|
||||
else:
|
||||
_tmp_args['rsync_opts'].append("--rsh=%s exec -i" % self._docker_cmd)
|
||||
elif self._remote_transport in ['buildah']:
|
||||
_tmp_args['rsync_opts'].append("--rsh=buildah run --")
|
||||
|
||||
# run the module and store the result
|
||||
result.update(self._execute_module('synchronize', module_args=_tmp_args, task_vars=task_vars))
|
||||
|
||||
return result
|
|
@ -1,466 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# (c) 2018 Matt Martz <matt@sivel.net>
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'community'}
|
||||
|
||||
DOCUMENTATION = '''
|
||||
callback: cgroup_perf_recap
|
||||
callback_type: aggregate
|
||||
requirements:
|
||||
- whitelist in configuration
|
||||
- cgroups
|
||||
short_description: Profiles system activity of tasks and full execution using cgroups
|
||||
version_added: "2.8"
|
||||
description:
|
||||
- This is an ansible callback plugin utilizes cgroups to profile system activity of ansible and
|
||||
individual tasks, and display a recap at the end of the playbook execution
|
||||
notes:
|
||||
- Requires ansible to be run from within a cgroup, such as with
|
||||
C(cgexec -g cpuacct,memory,pids:ansible_profile ansible-playbook ...)
|
||||
- This cgroup should only be used by ansible to get accurate results
|
||||
- To create the cgroup, first use a command such as
|
||||
C(sudo cgcreate -a ec2-user:ec2-user -t ec2-user:ec2-user -g cpuacct,memory,pids:ansible_profile)
|
||||
options:
|
||||
control_group:
|
||||
required: True
|
||||
description: Name of cgroups control group
|
||||
env:
|
||||
- name: CGROUP_CONTROL_GROUP
|
||||
ini:
|
||||
- section: callback_cgroup_perf_recap
|
||||
key: control_group
|
||||
cpu_poll_interval:
|
||||
description: Interval between CPU polling for determining CPU usage. A lower value may produce inaccurate
|
||||
results, a higher value may not be short enough to collect results for short tasks.
|
||||
default: 0.25
|
||||
type: float
|
||||
env:
|
||||
- name: CGROUP_CPU_POLL_INTERVAL
|
||||
ini:
|
||||
- section: callback_cgroup_perf_recap
|
||||
key: cpu_poll_interval
|
||||
memory_poll_interval:
|
||||
description: Interval between memory polling for determining memory usage. A lower value may produce inaccurate
|
||||
results, a higher value may not be short enough to collect results for short tasks.
|
||||
default: 0.25
|
||||
type: float
|
||||
env:
|
||||
- name: CGROUP_MEMORY_POLL_INTERVAL
|
||||
ini:
|
||||
- section: callback_cgroup_perf_recap
|
||||
key: memory_poll_interval
|
||||
pid_poll_interval:
|
||||
description: Interval between PID polling for determining PID count. A lower value may produce inaccurate
|
||||
results, a higher value may not be short enough to collect results for short tasks.
|
||||
default: 0.25
|
||||
type: float
|
||||
env:
|
||||
- name: CGROUP_PID_POLL_INTERVAL
|
||||
ini:
|
||||
- section: callback_cgroup_perf_recap
|
||||
key: pid_poll_interval
|
||||
display_recap:
|
||||
description: Controls whether the recap is printed at the end, useful if you will automatically
|
||||
process the output files
|
||||
env:
|
||||
- name: CGROUP_DISPLAY_RECAP
|
||||
ini:
|
||||
- section: callback_cgroup_perf_recap
|
||||
key: display_recap
|
||||
type: bool
|
||||
default: true
|
||||
file_name_format:
|
||||
description: Format of filename. Accepts C(%(counter)s), C(%(task_uuid)s),
|
||||
C(%(feature)s), C(%(ext)s). Defaults to C(%(feature)s.%(ext)s) when C(file_per_task) is C(False)
|
||||
and C(%(counter)s-%(task_uuid)s-%(feature)s.%(ext)s) when C(True)
|
||||
env:
|
||||
- name: CGROUP_FILE_NAME_FORMAT
|
||||
ini:
|
||||
- section: callback_cgroup_perf_recap
|
||||
key: file_name_format
|
||||
type: str
|
||||
default: '%(feature)s.%(ext)s'
|
||||
output_dir:
|
||||
description: Output directory for files containing recorded performance readings. If the value contains a
|
||||
single %s, the start time of the playbook run will be inserted in that space. Only the deepest
|
||||
level directory will be created if it does not exist, parent directories will not be created.
|
||||
type: path
|
||||
default: /tmp/ansible-perf-%s
|
||||
env:
|
||||
- name: CGROUP_OUTPUT_DIR
|
||||
ini:
|
||||
- section: callback_cgroup_perf_recap
|
||||
key: output_dir
|
||||
output_format:
|
||||
description: Output format, either CSV or JSON-seq
|
||||
env:
|
||||
- name: CGROUP_OUTPUT_FORMAT
|
||||
ini:
|
||||
- section: callback_cgroup_perf_recap
|
||||
key: output_format
|
||||
type: str
|
||||
default: csv
|
||||
choices:
|
||||
- csv
|
||||
- json
|
||||
file_per_task:
|
||||
description: When set as C(True) along with C(write_files), this callback will write 1 file per task
|
||||
instead of 1 file for the entire playbook run
|
||||
env:
|
||||
- name: CGROUP_FILE_PER_TASK
|
||||
ini:
|
||||
- section: callback_cgroup_perf_recap
|
||||
key: file_per_task
|
||||
type: bool
|
||||
default: False
|
||||
write_files:
|
||||
description: Dictates whether files will be written containing performance readings
|
||||
env:
|
||||
- name: CGROUP_WRITE_FILES
|
||||
ini:
|
||||
- section: callback_cgroup_perf_recap
|
||||
key: write_files
|
||||
type: bool
|
||||
default: false
|
||||
'''
|
||||
|
||||
import csv
|
||||
import datetime
|
||||
import os
|
||||
import time
|
||||
import threading
|
||||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
from functools import partial
|
||||
|
||||
from ansible.module_utils._text import to_bytes, to_text
|
||||
from ansible.module_utils.six import with_metaclass
|
||||
from ansible.parsing.ajson import AnsibleJSONEncoder, json
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
|
||||
|
||||
RS = '\x1e' # RECORD SEPARATOR
|
||||
LF = '\x0a' # LINE FEED
|
||||
|
||||
|
||||
def dict_fromkeys(keys, default=None):
|
||||
d = {}
|
||||
for key in keys:
|
||||
d[key] = default() if callable(default) else default
|
||||
return d
|
||||
|
||||
|
||||
class BaseProf(with_metaclass(ABCMeta, threading.Thread)):
|
||||
def __init__(self, path, obj=None, writer=None):
|
||||
threading.Thread.__init__(self) # pylint: disable=non-parent-init-called
|
||||
self.obj = obj
|
||||
self.path = path
|
||||
self.max = 0
|
||||
self.running = True
|
||||
self.writer = writer
|
||||
|
||||
def run(self):
|
||||
while self.running:
|
||||
self.poll()
|
||||
|
||||
@abstractmethod
|
||||
def poll(self):
|
||||
pass
|
||||
|
||||
|
||||
class MemoryProf(BaseProf):
|
||||
"""Python thread for recording memory usage"""
|
||||
def __init__(self, path, poll_interval=0.25, obj=None, writer=None):
|
||||
super(MemoryProf, self).__init__(path, obj=obj, writer=writer)
|
||||
self._poll_interval = poll_interval
|
||||
|
||||
def poll(self):
|
||||
with open(self.path) as f:
|
||||
val = int(f.read().strip()) / 1024**2
|
||||
if val > self.max:
|
||||
self.max = val
|
||||
if self.writer:
|
||||
try:
|
||||
self.writer(time.time(), self.obj.get_name(), self.obj._uuid, val)
|
||||
except ValueError:
|
||||
# We may be profiling after the playbook has ended
|
||||
self.running = False
|
||||
time.sleep(self._poll_interval)
|
||||
|
||||
|
||||
class CpuProf(BaseProf):
|
||||
def __init__(self, path, poll_interval=0.25, obj=None, writer=None):
|
||||
super(CpuProf, self).__init__(path, obj=obj, writer=writer)
|
||||
self._poll_interval = poll_interval
|
||||
|
||||
def poll(self):
|
||||
with open(self.path) as f:
|
||||
start_time = time.time() * 1000**2
|
||||
start_usage = int(f.read().strip()) / 1000
|
||||
time.sleep(self._poll_interval)
|
||||
with open(self.path) as f:
|
||||
end_time = time.time() * 1000**2
|
||||
end_usage = int(f.read().strip()) / 1000
|
||||
val = (end_usage - start_usage) / (end_time - start_time) * 100
|
||||
if val > self.max:
|
||||
self.max = val
|
||||
if self.writer:
|
||||
try:
|
||||
self.writer(time.time(), self.obj.get_name(), self.obj._uuid, val)
|
||||
except ValueError:
|
||||
# We may be profiling after the playbook has ended
|
||||
self.running = False
|
||||
|
||||
|
||||
class PidsProf(BaseProf):
|
||||
def __init__(self, path, poll_interval=0.25, obj=None, writer=None):
|
||||
super(PidsProf, self).__init__(path, obj=obj, writer=writer)
|
||||
self._poll_interval = poll_interval
|
||||
|
||||
def poll(self):
|
||||
with open(self.path) as f:
|
||||
val = int(f.read().strip())
|
||||
if val > self.max:
|
||||
self.max = val
|
||||
if self.writer:
|
||||
try:
|
||||
self.writer(time.time(), self.obj.get_name(), self.obj._uuid, val)
|
||||
except ValueError:
|
||||
# We may be profiling after the playbook has ended
|
||||
self.running = False
|
||||
time.sleep(self._poll_interval)
|
||||
|
||||
|
||||
def csv_writer(writer, timestamp, task_name, task_uuid, value):
|
||||
writer.writerow([timestamp, task_name, task_uuid, value])
|
||||
|
||||
|
||||
def json_writer(writer, timestamp, task_name, task_uuid, value):
|
||||
data = {
|
||||
'timestamp': timestamp,
|
||||
'task_name': task_name,
|
||||
'task_uuid': task_uuid,
|
||||
'value': value,
|
||||
}
|
||||
writer.write('%s%s%s' % (RS, json.dumps(data, cls=AnsibleJSONEncoder), LF))
|
||||
|
||||
|
||||
class CallbackModule(CallbackBase):
|
||||
CALLBACK_VERSION = 2.0
|
||||
CALLBACK_TYPE = 'aggregate'
|
||||
CALLBACK_NAME = 'cgroup_perf_recap'
|
||||
CALLBACK_NEEDS_WHITELIST = True
|
||||
|
||||
def __init__(self, display=None):
|
||||
super(CallbackModule, self).__init__(display)
|
||||
|
||||
self._features = ('memory', 'cpu', 'pids')
|
||||
|
||||
self._units = {
|
||||
'memory': 'MB',
|
||||
'cpu': '%',
|
||||
'pids': '',
|
||||
}
|
||||
|
||||
self.task_results = dict_fromkeys(self._features, default=list)
|
||||
self._profilers = dict.fromkeys(self._features)
|
||||
self._files = dict.fromkeys(self._features)
|
||||
self._writers = dict.fromkeys(self._features)
|
||||
|
||||
self._file_per_task = False
|
||||
self._counter = 0
|
||||
self.write_files = False
|
||||
|
||||
def _open_files(self, task_uuid=None):
|
||||
output_format = self._output_format
|
||||
output_dir = self._output_dir
|
||||
|
||||
for feature in self._features:
|
||||
data = {
|
||||
b'counter': to_bytes(self._counter),
|
||||
b'task_uuid': to_bytes(task_uuid),
|
||||
b'feature': to_bytes(feature),
|
||||
b'ext': to_bytes(output_format)
|
||||
}
|
||||
|
||||
if self._files.get(feature):
|
||||
try:
|
||||
self._files[feature].close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if self.write_files:
|
||||
filename = self._file_name_format % data
|
||||
|
||||
self._files[feature] = open(os.path.join(output_dir, filename), 'w+')
|
||||
if output_format == b'csv':
|
||||
self._writers[feature] = partial(csv_writer, csv.writer(self._files[feature]))
|
||||
elif output_format == b'json':
|
||||
self._writers[feature] = partial(json_writer, self._files[feature])
|
||||
|
||||
def set_options(self, task_keys=None, var_options=None, direct=None):
|
||||
super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
|
||||
|
||||
cpu_poll_interval = self.get_option('cpu_poll_interval')
|
||||
memory_poll_interval = self.get_option('memory_poll_interval')
|
||||
pid_poll_interval = self.get_option('pid_poll_interval')
|
||||
self._display_recap = self.get_option('display_recap')
|
||||
|
||||
control_group = to_bytes(self.get_option('control_group'), errors='surrogate_or_strict')
|
||||
self.mem_max_file = b'/sys/fs/cgroup/memory/%s/memory.max_usage_in_bytes' % control_group
|
||||
mem_current_file = b'/sys/fs/cgroup/memory/%s/memory.usage_in_bytes' % control_group
|
||||
cpu_usage_file = b'/sys/fs/cgroup/cpuacct/%s/cpuacct.usage' % control_group
|
||||
pid_current_file = b'/sys/fs/cgroup/pids/%s/pids.current' % control_group
|
||||
|
||||
for path in (self.mem_max_file, mem_current_file, cpu_usage_file, pid_current_file):
|
||||
try:
|
||||
with open(path) as f:
|
||||
pass
|
||||
except Exception as e:
|
||||
self._display.warning(
|
||||
u'Cannot open %s for reading (%s). Disabling %s' % (to_text(path), to_text(e), self.CALLBACK_NAME)
|
||||
)
|
||||
self.disabled = True
|
||||
return
|
||||
|
||||
try:
|
||||
with open(self.mem_max_file, 'w+') as f:
|
||||
f.write('0')
|
||||
except Exception as e:
|
||||
self._display.warning(
|
||||
u'Unable to reset max memory value in %s: %s' % (to_text(self.mem_max_file), to_text(e))
|
||||
)
|
||||
self.disabled = True
|
||||
return
|
||||
|
||||
try:
|
||||
with open(cpu_usage_file, 'w+') as f:
|
||||
f.write('0')
|
||||
except Exception as e:
|
||||
self._display.warning(
|
||||
u'Unable to reset CPU usage value in %s: %s' % (to_text(cpu_usage_file), to_text(e))
|
||||
)
|
||||
self.disabled = True
|
||||
return
|
||||
|
||||
self._profiler_map = {
|
||||
'memory': partial(MemoryProf, mem_current_file, poll_interval=memory_poll_interval),
|
||||
'cpu': partial(CpuProf, cpu_usage_file, poll_interval=cpu_poll_interval),
|
||||
'pids': partial(PidsProf, pid_current_file, poll_interval=pid_poll_interval),
|
||||
}
|
||||
|
||||
self.write_files = self.get_option('write_files')
|
||||
file_per_task = self.get_option('file_per_task')
|
||||
self._output_format = to_bytes(self.get_option('output_format'))
|
||||
output_dir = to_bytes(self.get_option('output_dir'), errors='surrogate_or_strict')
|
||||
try:
|
||||
output_dir %= to_bytes(datetime.datetime.now().isoformat())
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
self._output_dir = output_dir
|
||||
|
||||
file_name_format = to_bytes(self.get_option('file_name_format'))
|
||||
|
||||
if self.write_files:
|
||||
if file_per_task:
|
||||
self._file_per_task = True
|
||||
if file_name_format == b'%(feature)s.%(ext)s':
|
||||
file_name_format = b'%(counter)s-%(task_uuid)s-%(feature)s.%(ext)s'
|
||||
else:
|
||||
file_name_format = to_bytes(self.get_option('file_name_format'))
|
||||
|
||||
self._file_name_format = file_name_format
|
||||
|
||||
if not os.path.exists(output_dir):
|
||||
try:
|
||||
os.mkdir(output_dir)
|
||||
except Exception as e:
|
||||
self._display.warning(
|
||||
u'Could not create the output directory at %s: %s' % (to_text(output_dir), to_text(e))
|
||||
)
|
||||
self.disabled = True
|
||||
return
|
||||
|
||||
if not self._file_per_task:
|
||||
self._open_files()
|
||||
|
||||
def _profile(self, obj=None):
|
||||
prev_task = None
|
||||
results = dict.fromkeys(self._features)
|
||||
if not obj or self._file_per_task:
|
||||
for dummy, f in self._files.items():
|
||||
if f is None:
|
||||
continue
|
||||
try:
|
||||
f.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
for name, prof in self._profilers.items():
|
||||
prof.running = False
|
||||
|
||||
for name, prof in self._profilers.items():
|
||||
results[name] = prof.max
|
||||
prev_task = prof.obj
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
for name, result in results.items():
|
||||
if result is not None:
|
||||
try:
|
||||
self.task_results[name].append((prev_task, result))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if obj is not None:
|
||||
if self._file_per_task or self._counter == 0:
|
||||
self._open_files(task_uuid=obj._uuid)
|
||||
|
||||
for feature in self._features:
|
||||
self._profilers[feature] = self._profiler_map[feature](obj=obj, writer=self._writers[feature])
|
||||
self._profilers[feature].start()
|
||||
|
||||
self._counter += 1
|
||||
|
||||
def v2_playbook_on_task_start(self, task, is_conditional):
|
||||
self._profile(task)
|
||||
|
||||
def v2_playbook_on_stats(self, stats):
|
||||
self._profile()
|
||||
|
||||
if not self._display_recap:
|
||||
return
|
||||
|
||||
with open(self.mem_max_file) as f:
|
||||
max_results = int(f.read().strip()) / 1024 / 1024
|
||||
|
||||
self._display.banner('CGROUP PERF RECAP')
|
||||
self._display.display('Memory Execution Maximum: %0.2fMB\n' % max_results)
|
||||
for name, data in self.task_results.items():
|
||||
if name == 'memory':
|
||||
continue
|
||||
try:
|
||||
self._display.display(
|
||||
'%s Execution Maximum: %0.2f%s\n' % (name, max((t[1] for t in data)), self._units[name])
|
||||
)
|
||||
except Exception as e:
|
||||
self._display.display('%s profiling error: no results collected: %s\n' % (name, e))
|
||||
|
||||
self._display.display('\n')
|
||||
|
||||
for name, data in self.task_results.items():
|
||||
if data:
|
||||
self._display.display('%s:\n' % name)
|
||||
for task, value in data:
|
||||
self._display.display('%s (%s): %0.2f%s' % (task.get_name(), task._uuid, value, self._units[name]))
|
||||
self._display.display('\n')
|
|
@ -1,54 +0,0 @@
|
|||
# (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
callback: debug
|
||||
type: stdout
|
||||
short_description: formatted stdout/stderr display
|
||||
description:
|
||||
- Use this callback to sort through extensive debug output
|
||||
version_added: "2.4"
|
||||
extends_documentation_fragment:
|
||||
- default_callback
|
||||
requirements:
|
||||
- set as stdout in configuration
|
||||
'''
|
||||
|
||||
from ansible.plugins.callback.default import CallbackModule as CallbackModule_default
|
||||
|
||||
|
||||
class CallbackModule(CallbackModule_default): # pylint: disable=too-few-public-methods,no-init
|
||||
'''
|
||||
Override for the default callback module.
|
||||
|
||||
Render std err/out outside of the rest of the result which it prints with
|
||||
indentation.
|
||||
'''
|
||||
CALLBACK_VERSION = 2.0
|
||||
CALLBACK_TYPE = 'stdout'
|
||||
CALLBACK_NAME = 'debug'
|
||||
|
||||
def _dump_results(self, result, indent=None, sort_keys=True, keep_invocation=False):
|
||||
'''Return the text to output for a result.'''
|
||||
|
||||
# Enable JSON identation
|
||||
result['_ansible_verbose_always'] = True
|
||||
|
||||
save = {}
|
||||
for key in ['stdout', 'stdout_lines', 'stderr', 'stderr_lines', 'msg', 'module_stdout', 'module_stderr']:
|
||||
if key in result:
|
||||
save[key] = result.pop(key)
|
||||
|
||||
output = CallbackModule_default._dump_results(self, result)
|
||||
|
||||
for key in ['stdout', 'stderr', 'msg', 'module_stdout', 'module_stderr']:
|
||||
if key in save and save[key]:
|
||||
output += '\n\n%s:\n\n%s\n' % (key.upper(), save[key])
|
||||
|
||||
for key, value in save.items():
|
||||
result[key] = value
|
||||
|
||||
return output
|
|
@ -1,142 +0,0 @@
|
|||
# (c) 2016, Matt Martz <matt@sivel.net>
|
||||
# (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
callback: json
|
||||
short_description: Ansible screen output as JSON
|
||||
version_added: "2.2"
|
||||
description:
|
||||
- This callback converts all events into JSON output to stdout
|
||||
type: stdout
|
||||
requirements:
|
||||
- Set as stdout in config
|
||||
options:
|
||||
show_custom_stats:
|
||||
version_added: "2.6"
|
||||
name: Show custom stats
|
||||
description: 'This adds the custom stats set via the set_stats plugin to the play recap'
|
||||
default: False
|
||||
env:
|
||||
- name: ANSIBLE_SHOW_CUSTOM_STATS
|
||||
ini:
|
||||
- key: show_custom_stats
|
||||
section: defaults
|
||||
type: bool
|
||||
'''
|
||||
|
||||
import datetime
|
||||
import json
|
||||
|
||||
from functools import partial
|
||||
|
||||
from ansible.inventory.host import Host
|
||||
from ansible.parsing.ajson import AnsibleJSONEncoder
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
|
||||
|
||||
def current_time():
|
||||
return '%sZ' % datetime.datetime.utcnow().isoformat()
|
||||
|
||||
|
||||
class CallbackModule(CallbackBase):
|
||||
CALLBACK_VERSION = 2.0
|
||||
CALLBACK_TYPE = 'stdout'
|
||||
CALLBACK_NAME = 'json'
|
||||
|
||||
def __init__(self, display=None):
|
||||
super(CallbackModule, self).__init__(display)
|
||||
self.results = []
|
||||
|
||||
def _new_play(self, play):
|
||||
return {
|
||||
'play': {
|
||||
'name': play.get_name(),
|
||||
'id': str(play._uuid),
|
||||
'duration': {
|
||||
'start': current_time()
|
||||
}
|
||||
},
|
||||
'tasks': []
|
||||
}
|
||||
|
||||
def _new_task(self, task):
|
||||
return {
|
||||
'task': {
|
||||
'name': task.get_name(),
|
||||
'id': str(task._uuid),
|
||||
'duration': {
|
||||
'start': current_time()
|
||||
}
|
||||
},
|
||||
'hosts': {}
|
||||
}
|
||||
|
||||
def v2_playbook_on_play_start(self, play):
|
||||
self.results.append(self._new_play(play))
|
||||
|
||||
def v2_playbook_on_task_start(self, task, is_conditional):
|
||||
self.results[-1]['tasks'].append(self._new_task(task))
|
||||
|
||||
def v2_playbook_on_handler_task_start(self, task):
|
||||
self.results[-1]['tasks'].append(self._new_task(task))
|
||||
|
||||
def _convert_host_to_name(self, key):
|
||||
if isinstance(key, (Host,)):
|
||||
return key.get_name()
|
||||
return key
|
||||
|
||||
def v2_playbook_on_stats(self, stats):
|
||||
"""Display info about playbook statistics"""
|
||||
|
||||
hosts = sorted(stats.processed.keys())
|
||||
|
||||
summary = {}
|
||||
for h in hosts:
|
||||
s = stats.summarize(h)
|
||||
summary[h] = s
|
||||
|
||||
custom_stats = {}
|
||||
global_custom_stats = {}
|
||||
|
||||
if self.get_option('show_custom_stats') and stats.custom:
|
||||
custom_stats.update(dict((self._convert_host_to_name(k), v) for k, v in stats.custom.items()))
|
||||
global_custom_stats.update(custom_stats.pop('_run', {}))
|
||||
|
||||
output = {
|
||||
'plays': self.results,
|
||||
'stats': summary,
|
||||
'custom_stats': custom_stats,
|
||||
'global_custom_stats': global_custom_stats,
|
||||
}
|
||||
|
||||
self._display.display(json.dumps(output, cls=AnsibleJSONEncoder, indent=4, sort_keys=True))
|
||||
|
||||
def _record_task_result(self, on_info, result, **kwargs):
|
||||
"""This function is used as a partial to add failed/skipped info in a single method"""
|
||||
host = result._host
|
||||
task = result._task
|
||||
task_result = result._result.copy()
|
||||
task_result.update(on_info)
|
||||
task_result['action'] = task.action
|
||||
self.results[-1]['tasks'][-1]['hosts'][host.name] = task_result
|
||||
end_time = current_time()
|
||||
self.results[-1]['tasks'][-1]['task']['duration']['end'] = end_time
|
||||
self.results[-1]['play']['duration']['end'] = end_time
|
||||
|
||||
def __getattribute__(self, name):
|
||||
"""Return ``_record_task_result`` partial with a dict containing skipped/failed if necessary"""
|
||||
if name not in ('v2_runner_on_ok', 'v2_runner_on_failed', 'v2_runner_on_unreachable', 'v2_runner_on_skipped'):
|
||||
return object.__getattribute__(self, name)
|
||||
|
||||
on = name.rsplit('_', 1)[1]
|
||||
|
||||
on_info = {}
|
||||
if on in ('failed', 'skipped'):
|
||||
on_info[on] = True
|
||||
|
||||
return partial(self._record_task_result, on_info)
|
|
@ -1,119 +0,0 @@
|
|||
# (c) 2017, Tennis Smith, https://github.com/gamename
|
||||
# (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
callback: profile_roles
|
||||
type: aggregate
|
||||
short_description: adds timing information to roles
|
||||
version_added: "2.4"
|
||||
description:
|
||||
- This callback module provides profiling for ansible roles.
|
||||
requirements:
|
||||
- whitelisting in configuration
|
||||
'''
|
||||
|
||||
import collections
|
||||
import time
|
||||
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
from ansible.module_utils.six.moves import reduce
|
||||
|
||||
# define start time
|
||||
t0 = tn = time.time()
|
||||
|
||||
|
||||
def secondsToStr(t):
|
||||
# http://bytes.com/topic/python/answers/635958-handy-short-cut-formatting-elapsed-time-floating-point-seconds
|
||||
def rediv(ll, b):
|
||||
return list(divmod(ll[0], b)) + ll[1:]
|
||||
|
||||
return "%d:%02d:%02d.%03d" % tuple(
|
||||
reduce(rediv, [[t * 1000, ], 1000, 60, 60]))
|
||||
|
||||
|
||||
def filled(msg, fchar="*"):
|
||||
if len(msg) == 0:
|
||||
width = 79
|
||||
else:
|
||||
msg = "%s " % msg
|
||||
width = 79 - len(msg)
|
||||
if width < 3:
|
||||
width = 3
|
||||
filler = fchar * width
|
||||
return "%s%s " % (msg, filler)
|
||||
|
||||
|
||||
def timestamp(self):
|
||||
if self.current is not None:
|
||||
self.stats[self.current] = time.time() - self.stats[self.current]
|
||||
self.totals[self.current] += self.stats[self.current]
|
||||
|
||||
|
||||
def tasktime():
|
||||
global tn
|
||||
time_current = time.strftime('%A %d %B %Y %H:%M:%S %z')
|
||||
time_elapsed = secondsToStr(time.time() - tn)
|
||||
time_total_elapsed = secondsToStr(time.time() - t0)
|
||||
tn = time.time()
|
||||
return filled('%s (%s)%s%s' %
|
||||
(time_current, time_elapsed, ' ' * 7, time_total_elapsed))
|
||||
|
||||
|
||||
class CallbackModule(CallbackBase):
|
||||
"""
|
||||
This callback module provides profiling for ansible roles.
|
||||
"""
|
||||
CALLBACK_VERSION = 2.0
|
||||
CALLBACK_TYPE = 'aggregate'
|
||||
CALLBACK_NAME = 'profile_roles'
|
||||
CALLBACK_NEEDS_WHITELIST = True
|
||||
|
||||
def __init__(self):
|
||||
self.stats = collections.Counter()
|
||||
self.totals = collections.Counter()
|
||||
self.current = None
|
||||
super(CallbackModule, self).__init__()
|
||||
|
||||
def _record_task(self, task):
|
||||
"""
|
||||
Logs the start of each task
|
||||
"""
|
||||
self._display.display(tasktime())
|
||||
timestamp(self)
|
||||
|
||||
if task._role:
|
||||
self.current = task._role._role_name
|
||||
else:
|
||||
self.current = task.action
|
||||
|
||||
self.stats[self.current] = time.time()
|
||||
|
||||
def v2_playbook_on_task_start(self, task, is_conditional):
|
||||
self._record_task(task)
|
||||
|
||||
def v2_playbook_on_handler_task_start(self, task):
|
||||
self._record_task(task)
|
||||
|
||||
def playbook_on_setup(self):
|
||||
self._display.display(tasktime())
|
||||
|
||||
def playbook_on_stats(self, stats):
|
||||
self._display.display(tasktime())
|
||||
self._display.display(filled("", fchar="="))
|
||||
|
||||
timestamp(self)
|
||||
total_time = sum(self.totals.values())
|
||||
|
||||
# Print the timings starting with the largest one
|
||||
for result in self.totals.most_common():
|
||||
msg = u"{0:-<70}{1:->9}".format(result[0] + u' ', u' {0:.02f}s'.format(result[1]))
|
||||
self._display.display(msg)
|
||||
|
||||
msg_total = u"{0:-<70}{1:->9}".format(u'total ', u' {0:.02f}s'.format(total_time))
|
||||
self._display.display(filled("", fchar="~"))
|
||||
self._display.display(msg_total)
|
|
@ -1,194 +0,0 @@
|
|||
# (C) 2016, Joel, https://github.com/jjshoe
|
||||
# (C) 2015, Tom Paine, <github@aioue.net>
|
||||
# (C) 2014, Jharrod LaFon, @JharrodLaFon
|
||||
# (C) 2012-2013, Michael DeHaan, <michael.dehaan@gmail.com>
|
||||
# (C) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
callback: profile_tasks
|
||||
type: aggregate
|
||||
short_description: adds time information to tasks
|
||||
version_added: "2.0"
|
||||
description:
|
||||
- Ansible callback plugin for timing individual tasks and overall execution time.
|
||||
- "Mashup of 2 excellent original works: https://github.com/jlafon/ansible-profile,
|
||||
https://github.com/junaid18183/ansible_home/blob/master/ansible_plugins/callback_plugins/timestamp.py.old"
|
||||
- "Format: C(<task start timestamp> (<length of previous task>) <current elapsed playbook execution time>)"
|
||||
- It also lists the top/bottom time consuming tasks in the summary (configurable)
|
||||
- Before 2.4 only the environment variables were available for configuration.
|
||||
requirements:
|
||||
- whitelisting in configuration - see examples section below for details.
|
||||
options:
|
||||
output_limit:
|
||||
description: Number of tasks to display in the summary
|
||||
default: 20
|
||||
env:
|
||||
- name: PROFILE_TASKS_TASK_OUTPUT_LIMIT
|
||||
ini:
|
||||
- section: callback_profile_tasks
|
||||
key: task_output_limit
|
||||
sort_order:
|
||||
description: Adjust the sorting output of summary tasks
|
||||
choices: ['descending', 'ascending', 'none']
|
||||
default: 'descending'
|
||||
env:
|
||||
- name: PROFILE_TASKS_SORT_ORDER
|
||||
ini:
|
||||
- section: callback_profile_tasks
|
||||
key: sort_order
|
||||
'''
|
||||
|
||||
EXAMPLES = '''
|
||||
example: >
|
||||
To enable, add this to your ansible.cfg file in the defaults block
|
||||
[defaults]
|
||||
callback_whitelist = profile_tasks
|
||||
sample output: >
|
||||
#
|
||||
# TASK: [ensure messaging security group exists] ********************************
|
||||
# Thursday 11 June 2017 22:50:53 +0100 (0:00:00.721) 0:00:05.322 *********
|
||||
# ok: [localhost]
|
||||
#
|
||||
# TASK: [ensure db security group exists] ***************************************
|
||||
# Thursday 11 June 2017 22:50:54 +0100 (0:00:00.558) 0:00:05.880 *********
|
||||
# changed: [localhost]
|
||||
#
|
||||
'''
|
||||
|
||||
import collections
|
||||
import time
|
||||
|
||||
from ansible.module_utils.six.moves import reduce
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
|
||||
|
||||
# define start time
|
||||
t0 = tn = time.time()
|
||||
|
||||
|
||||
def secondsToStr(t):
|
||||
# http://bytes.com/topic/python/answers/635958-handy-short-cut-formatting-elapsed-time-floating-point-seconds
|
||||
def rediv(ll, b):
|
||||
return list(divmod(ll[0], b)) + ll[1:]
|
||||
|
||||
return "%d:%02d:%02d.%03d" % tuple(reduce(rediv, [[t * 1000, ], 1000, 60, 60]))
|
||||
|
||||
|
||||
def filled(msg, fchar="*"):
|
||||
if len(msg) == 0:
|
||||
width = 79
|
||||
else:
|
||||
msg = "%s " % msg
|
||||
width = 79 - len(msg)
|
||||
if width < 3:
|
||||
width = 3
|
||||
filler = fchar * width
|
||||
return "%s%s " % (msg, filler)
|
||||
|
||||
|
||||
def timestamp(self):
|
||||
if self.current is not None:
|
||||
self.stats[self.current]['time'] = time.time() - self.stats[self.current]['time']
|
||||
|
||||
|
||||
def tasktime():
|
||||
global tn
|
||||
time_current = time.strftime('%A %d %B %Y %H:%M:%S %z')
|
||||
time_elapsed = secondsToStr(time.time() - tn)
|
||||
time_total_elapsed = secondsToStr(time.time() - t0)
|
||||
tn = time.time()
|
||||
return filled('%s (%s)%s%s' % (time_current, time_elapsed, ' ' * 7, time_total_elapsed))
|
||||
|
||||
|
||||
class CallbackModule(CallbackBase):
|
||||
"""
|
||||
This callback module provides per-task timing, ongoing playbook elapsed time
|
||||
and ordered list of top 20 longest running tasks at end.
|
||||
"""
|
||||
CALLBACK_VERSION = 2.0
|
||||
CALLBACK_TYPE = 'aggregate'
|
||||
CALLBACK_NAME = 'profile_tasks'
|
||||
CALLBACK_NEEDS_WHITELIST = True
|
||||
|
||||
def __init__(self):
|
||||
self.stats = collections.OrderedDict()
|
||||
self.current = None
|
||||
|
||||
self.sort_order = None
|
||||
self.task_output_limit = None
|
||||
|
||||
super(CallbackModule, self).__init__()
|
||||
|
||||
def set_options(self, task_keys=None, var_options=None, direct=None):
|
||||
|
||||
super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
|
||||
|
||||
self.sort_order = self.get_option('sort_order')
|
||||
if self.sort_order is not None:
|
||||
if self.sort_order == 'ascending':
|
||||
self.sort_order = False
|
||||
elif self.sort_order == 'descending':
|
||||
self.sort_order = True
|
||||
elif self.sort_order == 'none':
|
||||
self.sort_order = None
|
||||
|
||||
self.task_output_limit = self.get_option('output_limit')
|
||||
if self.task_output_limit is not None:
|
||||
if self.task_output_limit == 'all':
|
||||
self.task_output_limit = None
|
||||
else:
|
||||
self.task_output_limit = int(self.task_output_limit)
|
||||
|
||||
def _record_task(self, task):
|
||||
"""
|
||||
Logs the start of each task
|
||||
"""
|
||||
self._display.display(tasktime())
|
||||
timestamp(self)
|
||||
|
||||
# Record the start time of the current task
|
||||
self.current = task._uuid
|
||||
self.stats[self.current] = {'time': time.time(), 'name': task.get_name()}
|
||||
if self._display.verbosity >= 2:
|
||||
self.stats[self.current]['path'] = task.get_path()
|
||||
|
||||
def v2_playbook_on_task_start(self, task, is_conditional):
|
||||
self._record_task(task)
|
||||
|
||||
def v2_playbook_on_handler_task_start(self, task):
|
||||
self._record_task(task)
|
||||
|
||||
def playbook_on_setup(self):
|
||||
self._display.display(tasktime())
|
||||
|
||||
def playbook_on_stats(self, stats):
|
||||
self._display.display(tasktime())
|
||||
self._display.display(filled("", fchar="="))
|
||||
|
||||
timestamp(self)
|
||||
self.current = None
|
||||
|
||||
results = self.stats.items()
|
||||
|
||||
# Sort the tasks by the specified sort
|
||||
if self.sort_order is not None:
|
||||
results = sorted(
|
||||
self.stats.items(),
|
||||
key=lambda x: x[1]['time'],
|
||||
reverse=self.sort_order,
|
||||
)
|
||||
|
||||
# Display the number of tasks specified or the default of 20
|
||||
results = results[:self.task_output_limit]
|
||||
|
||||
# Print the timings
|
||||
for uuid, result in results:
|
||||
msg = u"{0:-<{2}}{1:->9}".format(result['name'] + u' ', u' {0:.02f}s'.format(result['time']), self._display.columns - 9)
|
||||
if 'path' in result:
|
||||
msg += u"\n{0:-<{1}}".format(result['path'] + u' ', self._display.columns)
|
||||
self._display.display(msg)
|
|
@ -1,44 +0,0 @@
|
|||
# (c) 2012-2014, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
# (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
callback: skippy
|
||||
callback_type: stdout
|
||||
requirements:
|
||||
- set as main display callback
|
||||
short_description: Ansible screen output that ignores skipped status
|
||||
version_added: "2.0"
|
||||
deprecated:
|
||||
why: The 'default' callback plugin now supports this functionality
|
||||
removed_in: '2.11'
|
||||
alternative: "'default' callback plugin with 'display_skipped_hosts = no' option"
|
||||
extends_documentation_fragment:
|
||||
- default_callback
|
||||
description:
|
||||
- This callback does the same as the default except it does not output skipped host/task/item status
|
||||
'''
|
||||
|
||||
from ansible.plugins.callback.default import CallbackModule as CallbackModule_default
|
||||
|
||||
|
||||
class CallbackModule(CallbackModule_default):
|
||||
|
||||
'''
|
||||
This is the default callback interface, which simply prints messages
|
||||
to stdout when new callback events are received.
|
||||
'''
|
||||
|
||||
CALLBACK_VERSION = 2.0
|
||||
CALLBACK_TYPE = 'stdout'
|
||||
CALLBACK_NAME = 'skippy'
|
||||
|
||||
def v2_runner_on_skipped(self, result):
|
||||
pass
|
||||
|
||||
def v2_runner_item_on_skipped(self, result):
|
||||
pass
|
|
@ -1,50 +0,0 @@
|
|||
# (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
callback: timer
|
||||
callback_type: aggregate
|
||||
requirements:
|
||||
- whitelist in configuration
|
||||
short_description: Adds time to play stats
|
||||
version_added: "2.0"
|
||||
description:
|
||||
- This callback just adds total play duration to the play stats.
|
||||
'''
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
|
||||
|
||||
class CallbackModule(CallbackBase):
|
||||
"""
|
||||
This callback module tells you how long your plays ran for.
|
||||
"""
|
||||
CALLBACK_VERSION = 2.0
|
||||
CALLBACK_TYPE = 'aggregate'
|
||||
CALLBACK_NAME = 'timer'
|
||||
CALLBACK_NEEDS_WHITELIST = True
|
||||
|
||||
def __init__(self):
|
||||
|
||||
super(CallbackModule, self).__init__()
|
||||
|
||||
self.start_time = datetime.utcnow()
|
||||
|
||||
def days_hours_minutes_seconds(self, runtime):
|
||||
minutes = (runtime.seconds // 60) % 60
|
||||
r_seconds = runtime.seconds % 60
|
||||
return runtime.days, runtime.seconds // 3600, minutes, r_seconds
|
||||
|
||||
def playbook_on_stats(self, stats):
|
||||
self.v2_playbook_on_stats(stats)
|
||||
|
||||
def v2_playbook_on_stats(self, stats):
|
||||
end_time = datetime.utcnow()
|
||||
runtime = end_time - self.start_time
|
||||
self._display.display("Playbook run took %s days, %s hours, %s minutes, %s seconds" % (self.days_hours_minutes_seconds(runtime)))
|
|
@ -1,39 +0,0 @@
|
|||
# Copyright (c) 2014, Chris Church <chris@ninemoreminutes.com>
|
||||
# Copyright (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.plugins.shell import ShellBase
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: csh
|
||||
plugin_type: shell
|
||||
version_added: ""
|
||||
short_description: C shell (/bin/csh)
|
||||
description:
|
||||
- When you have no other option than to use csh
|
||||
extends_documentation_fragment:
|
||||
- shell_common
|
||||
'''
|
||||
|
||||
|
||||
class ShellModule(ShellBase):
|
||||
|
||||
# Common shell filenames that this plugin handles
|
||||
COMPATIBLE_SHELLS = frozenset(('csh', 'tcsh'))
|
||||
# Family of shells this has. Must match the filename without extension
|
||||
SHELL_FAMILY = 'csh'
|
||||
|
||||
# How to end lines in a python script one-liner
|
||||
_SHELL_EMBEDDED_PY_EOL = '\\\n'
|
||||
_SHELL_REDIRECT_ALLNULL = '>& /dev/null'
|
||||
_SHELL_AND = '&&'
|
||||
_SHELL_OR = '||'
|
||||
_SHELL_SUB_LEFT = '"`'
|
||||
_SHELL_SUB_RIGHT = '`"'
|
||||
_SHELL_GROUP_LEFT = '('
|
||||
_SHELL_GROUP_RIGHT = ')'
|
||||
|
||||
def env_prefix(self, **kwargs):
|
||||
return 'env %s' % super(ShellModule, self).env_prefix(**kwargs)
|
|
@ -1,91 +0,0 @@
|
|||
# Copyright (c) 2014, Chris Church <chris@ninemoreminutes.com>
|
||||
# Copyright (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.module_utils.six import text_type
|
||||
from ansible.module_utils.six.moves import shlex_quote
|
||||
from ansible.plugins.shell.sh import ShellModule as ShModule
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: fish
|
||||
plugin_type: shell
|
||||
version_added: ""
|
||||
short_description: fish shell (/bin/fish)
|
||||
description:
|
||||
- This is here because some people are restricted to fish.
|
||||
extends_documentation_fragment:
|
||||
- shell_common
|
||||
'''
|
||||
|
||||
|
||||
class ShellModule(ShModule):
|
||||
|
||||
# Common shell filenames that this plugin handles
|
||||
COMPATIBLE_SHELLS = frozenset(('fish',))
|
||||
# Family of shells this has. Must match the filename without extension
|
||||
SHELL_FAMILY = 'fish'
|
||||
|
||||
_SHELL_EMBEDDED_PY_EOL = '\n'
|
||||
_SHELL_REDIRECT_ALLNULL = '> /dev/null 2>&1'
|
||||
_SHELL_AND = '; and'
|
||||
_SHELL_OR = '; or'
|
||||
_SHELL_SUB_LEFT = '('
|
||||
_SHELL_SUB_RIGHT = ')'
|
||||
_SHELL_GROUP_LEFT = ''
|
||||
_SHELL_GROUP_RIGHT = ''
|
||||
|
||||
def env_prefix(self, **kwargs):
|
||||
env = self.env.copy()
|
||||
env.update(kwargs)
|
||||
return ' '.join(['set -lx %s %s;' % (k, shlex_quote(text_type(v))) for k, v in env.items()])
|
||||
|
||||
def build_module_command(self, env_string, shebang, cmd, arg_path=None):
|
||||
# don't quote the cmd if it's an empty string, because this will break pipelining mode
|
||||
if cmd.strip() != '':
|
||||
cmd = shlex_quote(cmd)
|
||||
cmd_parts = [env_string.strip(), shebang.replace("#!", "").strip(), cmd]
|
||||
if arg_path is not None:
|
||||
cmd_parts.append(arg_path)
|
||||
new_cmd = " ".join(cmd_parts)
|
||||
return new_cmd
|
||||
|
||||
def checksum(self, path, python_interp):
|
||||
# The following test is fish-compliant.
|
||||
#
|
||||
# In the following test, each condition is a check and logical
|
||||
# comparison (or or and) that sets the rc value. Every check is run so
|
||||
# the last check in the series to fail will be the rc that is
|
||||
# returned.
|
||||
#
|
||||
# If a check fails we error before invoking the hash functions because
|
||||
# hash functions may successfully take the hash of a directory on BSDs
|
||||
# (UFS filesystem?) which is not what the rest of the ansible code
|
||||
# expects
|
||||
#
|
||||
# If all of the available hashing methods fail we fail with an rc of
|
||||
# 0. This logic is added to the end of the cmd at the bottom of this
|
||||
# function.
|
||||
|
||||
# Return codes:
|
||||
# checksum: success!
|
||||
# 0: Unknown error
|
||||
# 1: Remote file does not exist
|
||||
# 2: No read permissions on the file
|
||||
# 3: File is a directory
|
||||
# 4: No python interpreter
|
||||
|
||||
# Quoting gets complex here. We're writing a python string that's
|
||||
# used by a variety of shells on the remote host to invoke a python
|
||||
# "one-liner".
|
||||
shell_escaped_path = shlex_quote(path)
|
||||
test = "set rc flag; [ -r %(p)s ] %(shell_or)s set rc 2; [ -f %(p)s ] %(shell_or)s set rc 1; [ -d %(p)s ] %(shell_and)s set rc 3; %(i)s -V 2>/dev/null %(shell_or)s set rc 4; [ x\"$rc\" != \"xflag\" ] %(shell_and)s echo \"$rc \"%(p)s %(shell_and)s exit 0" % dict(p=shell_escaped_path, i=python_interp, shell_and=self._SHELL_AND, shell_or=self._SHELL_OR) # NOQA
|
||||
csums = [
|
||||
u"({0} -c 'import hashlib; BLOCKSIZE = 65536; hasher = hashlib.sha1();{2}afile = open(\"'{1}'\", \"rb\"){2}buf = afile.read(BLOCKSIZE){2}while len(buf) > 0:{2}\thasher.update(buf){2}\tbuf = afile.read(BLOCKSIZE){2}afile.close(){2}print(hasher.hexdigest())' 2>/dev/null)".format(python_interp, shell_escaped_path, self._SHELL_EMBEDDED_PY_EOL), # NOQA Python > 2.4 (including python3)
|
||||
u"({0} -c 'import sha; BLOCKSIZE = 65536; hasher = sha.sha();{2}afile = open(\"'{1}'\", \"rb\"){2}buf = afile.read(BLOCKSIZE){2}while len(buf) > 0:{2}\thasher.update(buf){2}\tbuf = afile.read(BLOCKSIZE){2}afile.close(){2}print(hasher.hexdigest())' 2>/dev/null)".format(python_interp, shell_escaped_path, self._SHELL_EMBEDDED_PY_EOL), # NOQA Python == 2.4
|
||||
]
|
||||
|
||||
cmd = (" %s " % self._SHELL_OR).join(csums)
|
||||
cmd = "%s; %s %s (echo \'0 \'%s)" % (test, cmd, self._SHELL_OR, shell_escaped_path)
|
||||
return cmd
|
|
@ -1,2 +0,0 @@
|
|||
shippable/posix/group2
|
||||
skip/aix
|
|
@ -1,205 +0,0 @@
|
|||
# (c) 2017, Martin Krizek <mkrizek@redhat.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/>.
|
||||
|
||||
- name: Create ansible user
|
||||
user:
|
||||
name: "{{ test_user }}"
|
||||
|
||||
- name: Create ansible group
|
||||
group:
|
||||
name: "{{ test_group }}"
|
||||
|
||||
- name: Create ansible file
|
||||
file:
|
||||
path: "{{ test_file }}"
|
||||
state: touch
|
||||
|
||||
- name: Create ansible dir
|
||||
file:
|
||||
path: "{{ test_dir }}"
|
||||
state: directory
|
||||
##############################################################################
|
||||
- name: Grant ansible user read access to a file
|
||||
acl:
|
||||
path: "{{ test_file }}"
|
||||
entity: "{{ test_user }}"
|
||||
etype: user
|
||||
permissions: r
|
||||
state: present
|
||||
register: output
|
||||
|
||||
- name: get getfacl output
|
||||
shell: "getfacl {{ test_file | quote }}"
|
||||
register: getfacl_output
|
||||
|
||||
- name: verify output
|
||||
assert:
|
||||
that:
|
||||
- output is changed
|
||||
- output is not failed
|
||||
- "'user:{{ test_user }}:r--' in output.acl"
|
||||
- "'user:{{ test_user }}:r--' in getfacl_output.stdout_lines"
|
||||
##############################################################################
|
||||
- name: Obtain the acl for a specific file
|
||||
acl:
|
||||
path: "{{ test_file }}"
|
||||
register: output
|
||||
|
||||
- name: get getfacl output
|
||||
shell: "getfacl {{ test_file | quote }}"
|
||||
register: getfacl_output
|
||||
|
||||
- name: verify output
|
||||
assert:
|
||||
that:
|
||||
- output is not changed
|
||||
- output is not failed
|
||||
- "'user::rw-' in output.acl"
|
||||
- "'user:{{ test_user }}:r--' in output.acl"
|
||||
- "'group::r--' in output.acl"
|
||||
- "'mask::r--' in output.acl"
|
||||
- "'other::r--' in output.acl"
|
||||
- "'user::rw-' in getfacl_output.stdout_lines"
|
||||
- "'user:{{ test_user }}:r--' in getfacl_output.stdout_lines"
|
||||
- "'group::r--' in getfacl_output.stdout_lines"
|
||||
- "'mask::r--' in getfacl_output.stdout_lines"
|
||||
- "'other::r--' in getfacl_output.stdout_lines"
|
||||
##############################################################################
|
||||
- name: Removes the acl for ansible user on a specific file
|
||||
acl:
|
||||
path: "{{ test_file }}"
|
||||
entity: "{{ test_user }}"
|
||||
etype: user
|
||||
state: absent
|
||||
register: output
|
||||
|
||||
- name: get getfacl output
|
||||
shell: "getfacl {{ test_file | quote }}"
|
||||
register: getfacl_output
|
||||
|
||||
- name: verify output
|
||||
assert:
|
||||
that:
|
||||
- output is changed
|
||||
- output is not failed
|
||||
- "'user:{{ test_user }}:r--' not in output.acl"
|
||||
- "'user:{{ test_user }}:r--' not in getfacl_output.stdout_lines"
|
||||
##############################################################################
|
||||
- name: Sets default acl for ansible user on ansible dir
|
||||
acl:
|
||||
path: "{{ test_dir }}"
|
||||
entity: "{{ test_user }}"
|
||||
etype: user
|
||||
permissions: rw
|
||||
default: yes
|
||||
state: present
|
||||
register: output
|
||||
|
||||
- name: get getfacl output
|
||||
shell: "getfacl {{ test_dir | quote }}"
|
||||
register: getfacl_output
|
||||
|
||||
- name: verify output
|
||||
assert:
|
||||
that:
|
||||
- output is changed
|
||||
- output is not failed
|
||||
- "'user:{{ test_user }}:rw-' in output.acl"
|
||||
- "'default:user:{{ test_user }}:rw-' in getfacl_output.stdout_lines"
|
||||
##############################################################################
|
||||
- name: Cleanup
|
||||
shell: "setfacl -b {{ test_dir | quote }}"
|
||||
##############################################################################
|
||||
- name: Same as previous but using entry shorthand
|
||||
acl:
|
||||
path: "{{ test_dir }}"
|
||||
entry: "user:{{ test_user }}:rw-"
|
||||
default: yes
|
||||
state: present
|
||||
register: output
|
||||
|
||||
- name: get getfacl output
|
||||
shell: "getfacl {{ test_dir | quote }}"
|
||||
register: getfacl_output
|
||||
|
||||
- name: verify output
|
||||
assert:
|
||||
that:
|
||||
- output is changed
|
||||
- output is not failed
|
||||
- "'user:{{ test_user }}:rw-' in output.acl"
|
||||
- "'default:user:{{ test_user }}:rw-' in getfacl_output.stdout_lines"
|
||||
##############################################################################
|
||||
- name: Same as previous, to test idempotence
|
||||
acl:
|
||||
path: "{{ test_dir }}"
|
||||
entry: "user:{{ test_user }}:rw-"
|
||||
default: yes
|
||||
state: present
|
||||
register: output
|
||||
|
||||
- name: get getfacl output
|
||||
shell: "getfacl {{ test_dir | quote }}"
|
||||
register: getfacl_output
|
||||
|
||||
- name: verify output
|
||||
assert:
|
||||
that:
|
||||
- output is not changed
|
||||
- output is not failed
|
||||
- "'user:{{ test_user }}:rw-' in output.acl"
|
||||
- "'default:user:{{ test_user }}:rw-' in getfacl_output.stdout_lines"
|
||||
##############################################################################
|
||||
- name: Cleanup
|
||||
shell: "setfacl -b {{ test_dir | quote }}"
|
||||
##############################################################################
|
||||
- name: Set default acls
|
||||
acl:
|
||||
path: "{{ test_dir }}"
|
||||
entry: "{{ item }}"
|
||||
default: yes
|
||||
state: present
|
||||
with_items:
|
||||
- "user:{{ test_user }}:rw-"
|
||||
- "group:{{ test_group }}:rw-"
|
||||
|
||||
- name: Remove default group test_user acl
|
||||
acl:
|
||||
path: "{{ test_dir }}"
|
||||
entry: "group:{{ test_group }}:rw-"
|
||||
default: yes
|
||||
state: absent
|
||||
register: output
|
||||
|
||||
- name: get getfacl output
|
||||
shell: "getfacl {{ test_dir | quote }}"
|
||||
register: getfacl_output
|
||||
|
||||
- name: verify output
|
||||
assert:
|
||||
that:
|
||||
- output is changed
|
||||
- output is not failed
|
||||
- "'user::rwx' in getfacl_output.stdout_lines"
|
||||
- "'group::r-x' in getfacl_output.stdout_lines"
|
||||
- "'other::r-x' in getfacl_output.stdout_lines"
|
||||
- "'default:user::rwx' in getfacl_output.stdout_lines"
|
||||
- "'default:user:{{ test_user }}:rw-' in getfacl_output.stdout_lines"
|
||||
- "'default:group::r-x' in getfacl_output.stdout_lines"
|
||||
- "'default:mask::rwx' in getfacl_output.stdout_lines"
|
||||
- "'default:other::r-x' in getfacl_output.stdout_lines"
|
||||
- "'default:group:{{ test_group }}:rw-' not in getfacl_output.stdout_lines"
|
|
@ -1,36 +0,0 @@
|
|||
# (c) 2017, Martin Krizek <mkrizek@redhat.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/>.
|
||||
|
||||
- block:
|
||||
|
||||
- include: acl.yml
|
||||
when: ansible_system == 'Linux' # TODO enable acls mount option on FreeBSD to test it there too
|
||||
|
||||
always:
|
||||
- name: delete created directory and file
|
||||
file:
|
||||
path: '{{ item }}'
|
||||
state: absent
|
||||
with_items:
|
||||
- '{{ test_dir }}'
|
||||
- '{{ test_file }}'
|
||||
|
||||
vars:
|
||||
test_user: ansible_user
|
||||
test_group: ansible_group
|
||||
test_file: '{{ output_dir }}/ansible file'
|
||||
test_dir: "{{ output_dir }}/ansible_dir/with some space"
|
|
@ -1,2 +0,0 @@
|
|||
shippable/posix/group1
|
||||
destructive
|
|
@ -1,2 +0,0 @@
|
|||
dependencies:
|
||||
- prepare_tests
|
|
@ -1,62 +0,0 @@
|
|||
# Test code for the at module.
|
||||
# (c) 2017, 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/>.
|
||||
|
||||
- set_fact: output_dir_test={{output_dir}}/at
|
||||
|
||||
- name: make sure our testing sub-directory does not exist
|
||||
file: path="{{ output_dir_test }}" state=absent
|
||||
|
||||
- name: create our testing sub-directory
|
||||
file: path="{{ output_dir_test }}" state=directory
|
||||
|
||||
##
|
||||
## at
|
||||
##
|
||||
|
||||
- name: define distros to attempt installing at on
|
||||
set_fact:
|
||||
package_distros:
|
||||
- RedHat
|
||||
- CentOS
|
||||
- ScientificLinux
|
||||
- Fedora
|
||||
- Ubuntu
|
||||
- Debian
|
||||
- openSUSE Leap
|
||||
|
||||
- name: ensure at is installed
|
||||
package:
|
||||
name: at
|
||||
state: present
|
||||
when: ansible_distribution in package_distros
|
||||
|
||||
- name: run the first example
|
||||
at:
|
||||
command: "ls -d / > /dev/null"
|
||||
count: 20
|
||||
units: minutes
|
||||
register: at_test0
|
||||
- debug: var=at_test0
|
||||
- name: validate results
|
||||
assert:
|
||||
that:
|
||||
- 'at_test0.changed is defined'
|
||||
- 'at_test0.count is defined'
|
||||
- 'at_test0.script_file is defined'
|
||||
- 'at_test0.state is defined'
|
||||
- 'at_test0.units is defined'
|
|
@ -1,2 +0,0 @@
|
|||
needs/root
|
||||
shippable/posix/group2
|
|
@ -1,36 +0,0 @@
|
|||
dss_key_basic: ssh-dss DATA_BASIC root@testing
|
||||
dss_key_unquoted_option: idle-timeout=5m ssh-dss DATA_UNQUOTED_OPTION root@testing
|
||||
dss_key_command: command="/bin/true" ssh-dss DATA_COMMAND root@testing
|
||||
dss_key_complex_command: command="echo foo 'bar baz'" ssh-dss DATA_COMPLEX_COMMAND root@testing
|
||||
dss_key_command_single_option: no-port-forwarding,command="/bin/true" ssh-dss DATA_COMMAND_SINGLE_OPTIONS root@testing
|
||||
dss_key_command_multiple_options: no-port-forwarding,idle-timeout=5m,command="/bin/true" ssh-dss DATA_COMMAND_MULTIPLE_OPTIONS root@testing
|
||||
dss_key_trailing: ssh-dss DATA_TRAILING root@testing foo bar baz
|
||||
rsa_key_basic: ssh-rsa DATA_BASIC root@testing
|
||||
|
||||
multiple_key_base: |
|
||||
ssh-rsa DATA_BASIC 1@testing
|
||||
ssh-dss DATA_TRAILING 2@testing foo bar baz
|
||||
ssh-dss DATA_TRAILING 3@testing foo bar baz
|
||||
ecdsa-sha2-nistp521 ECDSA_DATA 4@testing
|
||||
|
||||
multiple_key_different_order: |
|
||||
ssh-dss DATA_TRAILING 2@testing foo bar baz
|
||||
ssh-dss DATA_TRAILING 3@testing foo bar baz
|
||||
ssh-rsa DATA_BASIC 1@testing
|
||||
ecdsa-sha2-nistp521 ECDSA_DATA 4@testing
|
||||
|
||||
multiple_key_different_order_2: |
|
||||
ssh-dss DATA_TRAILING 2@testing foo bar baz
|
||||
ssh-rsa WHATEVER 2.5@testing
|
||||
ssh-dss DATA_TRAILING 3@testing foo bar baz
|
||||
ssh-rsa DATA_BASIC 1@testing
|
||||
ecdsa-sha2-nistp521 ECDSA_DATA 4@testing
|
||||
|
||||
multiple_key_exclusive: |
|
||||
ssh-rsa DATA_BASIC 1@testing
|
||||
ecdsa-sha2-nistp521 ECDSA_DATA 4@testing
|
||||
|
||||
multiple_keys_comments: |
|
||||
ssh-rsa DATA_BASIC 1@testing
|
||||
# I like adding comments yo-dude-this-is-not-a-key INVALID_DATA 2@testing
|
||||
ecdsa-sha2-nistp521 ECDSA_DATA 4@testing
|
|
@ -1,5 +0,0 @@
|
|||
# I like candy
|
||||
ssh-rsa somekeydata somekeyalias
|
||||
# It is a very pleasant temperature outside today.
|
||||
ssh-rsa otherkeydata otherkeyalias
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
dependencies:
|
||||
- prepare_tests
|
|
@ -1,485 +0,0 @@
|
|||
# test code for the authorized_key module
|
||||
# (c) 2014, James Cammarata <jcammarata@ansible.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/>.
|
||||
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Setup steps
|
||||
|
||||
|
||||
- name: copy an existing file in place with comments
|
||||
copy:
|
||||
src: existing_authorized_keys
|
||||
dest: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
|
||||
- name: add multiple keys different order
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ multiple_key_different_order_2 }}"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: get the file content
|
||||
shell: cat "{{ output_dir | expanduser }}/authorized_keys"
|
||||
changed_when: no
|
||||
register: multiple_keys_existing
|
||||
|
||||
- name: assert that the key was added and comments and ordering preserved
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == True'
|
||||
- '"# I like candy" in multiple_keys_existing.stdout'
|
||||
- '"# I like candy" in multiple_keys_existing.stdout_lines[0]'
|
||||
- '"ssh-rsa DATA_BASIC 1@testing" in multiple_keys_existing.stdout'
|
||||
# The specific index is a little fragile, but I want to verify the line shows up
|
||||
# as the 3rd line in the new entries after the existing entries and comments are preserved
|
||||
- '"ssh-rsa DATA_BASIC 1@testing" in multiple_keys_existing.stdout_lines[7]'
|
||||
|
||||
# start afresh
|
||||
|
||||
- name: remove file foo.txt
|
||||
file:
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
state: absent
|
||||
|
||||
- name: touch the authorized_keys file
|
||||
file:
|
||||
dest: "{{ output_dir }}/authorized_keys"
|
||||
state: touch
|
||||
register: result
|
||||
|
||||
- name: assert that the authorized_keys file was created
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == True'
|
||||
- 'result.state == "file"'
|
||||
|
||||
- name: add multiple keys
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ multiple_key_base }}"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: assert that the key was added
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == True'
|
||||
- 'result.key == multiple_key_base'
|
||||
- 'result.key_options == None'
|
||||
|
||||
- name: add multiple keys different order
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ multiple_key_different_order }}"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: assert that the key was added
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == True'
|
||||
- 'result.key == multiple_key_different_order'
|
||||
- 'result.key_options == None'
|
||||
|
||||
- name: add multiple keys exclusive
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ multiple_key_exclusive }}"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
exclusive: true
|
||||
register: result
|
||||
|
||||
- name: assert that the key was added
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == True'
|
||||
- 'result.key == multiple_key_exclusive'
|
||||
- 'result.key_options == None'
|
||||
|
||||
- name: add multiple keys in different calls
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "ecdsa-sha2-nistp521 ECDSA_DATA 4@testing"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: add multiple keys in different calls
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "ssh-rsa DATA_BASIC 1@testing"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: get the file content
|
||||
shell: cat "{{ output_dir | expanduser }}/authorized_keys"
|
||||
changed_when: no
|
||||
register: multiple_keys_at_a_time
|
||||
|
||||
- name: assert that the key was added
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == false'
|
||||
- 'multiple_keys_at_a_time.stdout == multiple_key_exclusive.strip()'
|
||||
|
||||
- name: add multiple keys comment
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ multiple_keys_comments }}"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
exclusive: true
|
||||
register: result
|
||||
|
||||
- name: get the file content
|
||||
shell: cat "{{ output_dir | expanduser }}/authorized_keys"
|
||||
changed_when: no
|
||||
register: multiple_keys_comments
|
||||
|
||||
- name: assert that the keys exist and comment only lines were not added
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == False'
|
||||
- 'multiple_keys_comments.stdout == multiple_key_exclusive.strip()'
|
||||
- 'result.key_options == None'
|
||||
|
||||
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# basic ssh-dss key
|
||||
|
||||
- name: add basic ssh-dss key
|
||||
authorized_key: user=root key="{{ dss_key_basic }}" state=present path="{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: assert that the key was added
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == True'
|
||||
- 'result.key == dss_key_basic'
|
||||
- 'result.key_options == None'
|
||||
|
||||
- name: re-add basic ssh-dss key
|
||||
authorized_key: user=root key="{{ dss_key_basic }}" state=present path="{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: assert that nothing changed
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == False'
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# ssh-dss key with an unquoted option
|
||||
|
||||
- name: add ssh-dss key with an unquoted option
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ dss_key_unquoted_option }}"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: assert that the key was added
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == True'
|
||||
- 'result.key == dss_key_unquoted_option'
|
||||
- 'result.key_options == None'
|
||||
|
||||
- name: re-add ssh-dss key with an unquoted option
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ dss_key_unquoted_option }}"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: assert that nothing changed
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == False'
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# ssh-dss key with a leading command="/bin/foo"
|
||||
|
||||
- name: add ssh-dss key with a leading command
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ dss_key_command }}"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: assert that the key was added
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == True'
|
||||
- 'result.key == dss_key_command'
|
||||
- 'result.key_options == None'
|
||||
|
||||
- name: re-add ssh-dss key with a leading command
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ dss_key_command }}"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: assert that nothing changed
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == False'
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# ssh-dss key with a complex quoted leading command
|
||||
# ie. command="/bin/echo foo 'bar baz'"
|
||||
|
||||
- name: add ssh-dss key with a complex quoted leading command
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ dss_key_complex_command }}"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: assert that the key was added
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == True'
|
||||
- 'result.key == dss_key_complex_command'
|
||||
- 'result.key_options == None'
|
||||
|
||||
- name: re-add ssh-dss key with a complex quoted leading command
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ dss_key_complex_command }}"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: assert that nothing changed
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == False'
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# ssh-dss key with a command and a single option, which are
|
||||
# in a comma-separated list
|
||||
|
||||
- name: add ssh-dss key with a command and a single option
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ dss_key_command_single_option }}"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: assert that the key was added
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == True'
|
||||
- 'result.key == dss_key_command_single_option'
|
||||
- 'result.key_options == None'
|
||||
|
||||
- name: re-add ssh-dss key with a command and a single option
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ dss_key_command_single_option }}"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: assert that nothing changed
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == False'
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# ssh-dss key with a command and multiple other options
|
||||
|
||||
- name: add ssh-dss key with a command and multiple options
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ dss_key_command_multiple_options }}"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: assert that the key was added
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == True'
|
||||
- 'result.key == dss_key_command_multiple_options'
|
||||
- 'result.key_options == None'
|
||||
|
||||
- name: re-add ssh-dss key with a command and multiple options
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ dss_key_command_multiple_options }}"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: assert that nothing changed
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == False'
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# ssh-dss key with multiple trailing parts, which are space-
|
||||
# separated and not quoted in any way
|
||||
|
||||
- name: add ssh-dss key with trailing parts
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ dss_key_trailing }}"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: assert that the key was added
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == True'
|
||||
- 'result.key == dss_key_trailing'
|
||||
- 'result.key_options == None'
|
||||
|
||||
- name: re-add ssh-dss key with trailing parts
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ dss_key_trailing }}"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: assert that nothing changed
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == False'
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# basic ssh-dss key with mutliple permit-open options
|
||||
# https://github.com/ansible/ansible-modules-core/issues/1715
|
||||
|
||||
- name: add basic ssh-dss key with multi-opts
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ dss_key_basic }}"
|
||||
key_options: 'no-agent-forwarding,no-X11-forwarding,permitopen="10.9.8.1:8080",permitopen="10.9.8.1:9001"'
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: assert that the key with multi-opts was added
|
||||
assert:
|
||||
that:
|
||||
- 'result.changed == True'
|
||||
- 'result.key == dss_key_basic'
|
||||
- 'result.key_options == "no-agent-forwarding,no-X11-forwarding,permitopen=\"10.9.8.1:8080\",permitopen=\"10.9.8.1:9001\""'
|
||||
|
||||
- name: get the file content
|
||||
shell: cat "{{ output_dir | expanduser }}/authorized_keys" | fgrep DATA_BASIC
|
||||
changed_when: no
|
||||
register: content
|
||||
|
||||
- name: validate content
|
||||
assert:
|
||||
that:
|
||||
- 'content.stdout == "no-agent-forwarding,no-X11-forwarding,permitopen=\"10.9.8.1:8080\",permitopen=\"10.9.8.1:9001\" ssh-dss DATA_BASIC root@testing"'
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# check mode
|
||||
|
||||
- name: copy an existing file in place with comments
|
||||
copy:
|
||||
src: existing_authorized_keys
|
||||
dest: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
|
||||
- authorized_key:
|
||||
user: root
|
||||
key: "{{ multiple_key_different_order_2 }}"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
check_mode: True
|
||||
register: result
|
||||
|
||||
- name: assert that the file was not changed
|
||||
copy:
|
||||
src: existing_authorized_keys
|
||||
dest: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- 'result.changed == False'
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# comments
|
||||
|
||||
- name: Add rsa key with existing comment
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ rsa_key_basic }}"
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: Change the comment on an existing key
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ rsa_key_basic }}"
|
||||
comment: user@acme.com
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: get the file content
|
||||
shell: cat "{{ output_dir | expanduser }}/authorized_keys" | fgrep DATA_BASIC
|
||||
changed_when: no
|
||||
register: content
|
||||
|
||||
- name: Assert that comment on an existing key was changed
|
||||
assert:
|
||||
that:
|
||||
- "'user@acme.com' in content.stdout"
|
||||
|
||||
- name: Set the same key with comment to ensure no changes are reported
|
||||
authorized_key:
|
||||
user: root
|
||||
key: "{{ rsa_key_basic }}"
|
||||
comment: user@acme.com
|
||||
state: present
|
||||
path: "{{ output_dir | expanduser }}/authorized_keys"
|
||||
register: result
|
||||
|
||||
- name: Assert that no changes were made when running again
|
||||
assert:
|
||||
that:
|
||||
- not result.changed
|
||||
|
||||
- debug:
|
||||
var: "{{ item }}"
|
||||
verbosity: 1
|
||||
with_items:
|
||||
- result
|
||||
- content
|
|
@ -1,4 +0,0 @@
|
|||
needs/privileged
|
||||
needs/root
|
||||
shippable/posix/group2
|
||||
skip/aix
|
|
@ -1,342 +0,0 @@
|
|||
# (c) 2016, Toshio Kuratomi <tkuratomi@ansible.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/>.
|
||||
|
||||
- name: Create the mount point
|
||||
file:
|
||||
state: "directory"
|
||||
path: "{{ output_dir }}/mount_dest"
|
||||
|
||||
- name: Create a directory to bind mount
|
||||
file:
|
||||
state: "directory"
|
||||
path: "{{ output_dir }}/mount_source"
|
||||
|
||||
- name: Put something in the directory so we see that it worked
|
||||
copy:
|
||||
content: "Testing\n"
|
||||
dest: "{{ output_dir }}/mount_source/test_file"
|
||||
register: orig_info
|
||||
|
||||
# The opts type of bind mount only works on Linux
|
||||
- name: Bind mount a filesystem (Linux)
|
||||
mount:
|
||||
src: "{{ output_dir }}/mount_source"
|
||||
name: "{{ output_dir }}/mount_dest"
|
||||
state: "mounted"
|
||||
fstype: "None"
|
||||
opts: "bind"
|
||||
when: ansible_system == 'Linux'
|
||||
register: bind_result_linux
|
||||
|
||||
# Nullfs is freebsd only
|
||||
- name: Bind mount a filesystem (FreeBSD)
|
||||
mount:
|
||||
src: "{{ output_dir }}/mount_source"
|
||||
name: "{{ output_dir }}/mount_dest"
|
||||
state: "mounted"
|
||||
fstype: "nullfs"
|
||||
when: ansible_system == 'FreeBSD'
|
||||
register: bind_result_freebsd
|
||||
|
||||
- name: get checksum for bind mounted file
|
||||
stat:
|
||||
path: "{{ output_dir }}/mount_dest/test_file"
|
||||
when: ansible_system in ('FreeBSD', 'Linux')
|
||||
register: dest_stat
|
||||
|
||||
- name: assert the bind mount was successful
|
||||
assert:
|
||||
that:
|
||||
- "(ansible_system == 'Linux' and bind_result_linux['changed']) or (ansible_system == 'FreeBSD' and bind_result_freebsd['changed'])"
|
||||
- "dest_stat['stat']['exists']"
|
||||
- "orig_info['checksum'] == dest_stat['stat']['checksum']"
|
||||
when: ansible_system in ('FreeBSD', 'Linux')
|
||||
|
||||
# The opts type of bind mount only works on Linux
|
||||
- name: Bind mount a filesystem (Linux)
|
||||
mount:
|
||||
src: "{{ output_dir }}/mount_source"
|
||||
name: "{{ output_dir }}/mount_dest"
|
||||
state: "mounted"
|
||||
fstype: "None"
|
||||
opts: "bind"
|
||||
when: ansible_system == 'Linux'
|
||||
register: bind_result_linux
|
||||
|
||||
# Nullfs is freebsd only
|
||||
- name: Bind mount a filesystem (FreeBSD)
|
||||
mount:
|
||||
src: "{{ output_dir }}/mount_source"
|
||||
name: "{{ output_dir }}/mount_dest"
|
||||
state: "mounted"
|
||||
fstype: "nullfs"
|
||||
when: ansible_system == 'FreeBSD'
|
||||
register: bind_result_freebsd
|
||||
|
||||
- name: Make sure we didn't mount a second time
|
||||
assert:
|
||||
that:
|
||||
- "(ansible_system == 'Linux' and not bind_result_linux['changed']) or (ansible_system == 'FreeBSD' and not bind_result_freebsd['changed'])"
|
||||
when: ansible_system in ('FreeBSD', 'Linux')
|
||||
|
||||
# The opts type of bind mount only works on Linux
|
||||
- name: Remount filesystem with different opts (Linux)
|
||||
mount:
|
||||
src: "{{ output_dir }}/mount_source"
|
||||
name: "{{ output_dir }}/mount_dest"
|
||||
state: "mounted"
|
||||
fstype: "None"
|
||||
opts: "bind,ro"
|
||||
when: ansible_system == 'Linux'
|
||||
register: bind_result_linux
|
||||
|
||||
# Nullfs is freebsd only
|
||||
- name: Remount filesystem with different opts (FreeBSD)
|
||||
mount:
|
||||
src: "{{ output_dir }}/mount_source"
|
||||
name: "{{ output_dir }}/mount_dest"
|
||||
state: "mounted"
|
||||
fstype: "nullfs"
|
||||
opts: "ro"
|
||||
when: ansible_system == 'FreeBSD'
|
||||
register: bind_result_freebsd
|
||||
|
||||
- name: Get mount options
|
||||
shell: mount | grep mount_dest | grep -E -w '(ro|read-only)' | wc -l
|
||||
register: remount_options
|
||||
|
||||
- name: Make sure the filesystem now has the new opts
|
||||
assert:
|
||||
that:
|
||||
- "(ansible_system == 'Linux' and bind_result_linux['changed']) or (ansible_system == 'FreeBSD' and bind_result_freebsd['changed'])"
|
||||
- "'1' in remount_options.stdout"
|
||||
- "1 == remount_options.stdout_lines | length"
|
||||
when: ansible_system in ('FreeBSD', 'Linux')
|
||||
|
||||
- name: Unmount the bind mount
|
||||
mount:
|
||||
name: "{{ output_dir }}/mount_dest"
|
||||
state: "absent"
|
||||
when: ansible_system in ('Linux', 'FreeBSD')
|
||||
register: unmount_result
|
||||
|
||||
- name: Make sure the file no longer exists in dest
|
||||
stat:
|
||||
path: "{{ output_dir }}/mount_dest/test_file"
|
||||
when: ansible_system in ('FreeBSD', 'Linux')
|
||||
register: dest_stat
|
||||
|
||||
- name: Check that we unmounted
|
||||
assert:
|
||||
that:
|
||||
- "unmount_result['changed']"
|
||||
- "not dest_stat['stat']['exists']"
|
||||
when: ansible_system in ('FreeBSD', 'Linux')
|
||||
|
||||
- name: Create fstab record for the first swap file
|
||||
mount:
|
||||
name: none
|
||||
src: /tmp/swap1
|
||||
opts: sw
|
||||
fstype: swap
|
||||
state: present
|
||||
register: swap1_created
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Try to create fstab record for the first swap file again
|
||||
mount:
|
||||
name: none
|
||||
src: /tmp/swap1
|
||||
opts: sw
|
||||
fstype: swap
|
||||
state: present
|
||||
register: swap1_created_again
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Check that we created the swap1 record
|
||||
assert:
|
||||
that:
|
||||
- "swap1_created['changed']"
|
||||
- "not swap1_created_again['changed']"
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Create fstab record for the second swap file
|
||||
mount:
|
||||
name: none
|
||||
src: /tmp/swap2
|
||||
opts: sw
|
||||
fstype: swap
|
||||
state: present
|
||||
register: swap2_created
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Try to create fstab record for the second swap file again
|
||||
mount:
|
||||
name: none
|
||||
src: /tmp/swap1
|
||||
opts: sw
|
||||
fstype: swap
|
||||
state: present
|
||||
register: swap2_created_again
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Check that we created the swap2 record
|
||||
assert:
|
||||
that:
|
||||
- "swap2_created['changed']"
|
||||
- "not swap2_created_again['changed']"
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Remove the fstab record for the first swap file
|
||||
mount:
|
||||
name: none
|
||||
src: /tmp/swap1
|
||||
state: absent
|
||||
register: swap1_removed
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Try to remove the fstab record for the first swap file again
|
||||
mount:
|
||||
name: none
|
||||
src: /tmp/swap1
|
||||
state: absent
|
||||
register: swap1_removed_again
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Check that we removed the swap1 record
|
||||
assert:
|
||||
that:
|
||||
- "swap1_removed['changed']"
|
||||
- "not swap1_removed_again['changed']"
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Remove the fstab record for the second swap file
|
||||
mount:
|
||||
name: none
|
||||
src: /tmp/swap2
|
||||
state: absent
|
||||
register: swap2_removed
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Try to remove the fstab record for the second swap file again
|
||||
mount:
|
||||
name: none
|
||||
src: /tmp/swap2
|
||||
state: absent
|
||||
register: swap2_removed_again
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Check that we removed the swap2 record
|
||||
assert:
|
||||
that:
|
||||
- "swap2_removed['changed']"
|
||||
- "not swap2_removed_again['changed']"
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Create fstab record with missing last two fields
|
||||
copy:
|
||||
dest: /etc/fstab
|
||||
content: |
|
||||
//nas/photo /home/jik/pictures cifs defaults,credentials=/etc/security/nas.creds,uid=jik,gid=users,forceuid,forcegid,noserverino,_netdev
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Try to change the fstab record with the missing last two fields
|
||||
mount:
|
||||
src: //nas/photo
|
||||
path: /home/jik/pictures
|
||||
fstype: cifs
|
||||
opts: defaults,credentials=/etc/security/nas.creds,uid=jik,gid=users,forceuid,forcegid,noserverino,_netdev,x-systemd.mount-timeout=0
|
||||
state: present
|
||||
register: optional_fields_update
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Get the content of the fstab file
|
||||
shell: cat /etc/fstab
|
||||
register: optional_fields_content
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Check if the line containing the missing last two fields was changed
|
||||
assert:
|
||||
that:
|
||||
- "optional_fields_update['changed']"
|
||||
- "' 0 0' in optional_fields_content.stdout"
|
||||
- "1 == optional_fields_content.stdout_lines | length"
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Block to test remounted option
|
||||
block:
|
||||
- name: Create empty file
|
||||
command: dd if=/dev/zero of=/tmp/myfs.img bs=1048576 count=20
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Format FS
|
||||
filesystem:
|
||||
fstype: ext3
|
||||
dev: /tmp/myfs.img
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Mount the FS for the first time
|
||||
mount:
|
||||
path: /tmp/myfs
|
||||
src: /tmp/myfs.img
|
||||
fstype: ext2
|
||||
state: mounted
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Get the last write time
|
||||
shell: "dumpe2fs /tmp/myfs.img 2>/dev/null | grep -i last\ write\ time: |cut -d: -f2-"
|
||||
register: last_write_time
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Wait 2 second
|
||||
pause:
|
||||
seconds: 2
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Test if the FS is remounted
|
||||
mount:
|
||||
path: /tmp/myfs
|
||||
state: remounted
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Get again the last write time
|
||||
shell: "dumpe2fs /tmp/myfs.img 2>/dev/null | grep -i last\ write\ time: |cut -d: -f2-"
|
||||
register: last_write_time2
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Fail if they are the same
|
||||
fail:
|
||||
msg: "Filesytem was not remounted, testing of the module failed!"
|
||||
when: last_write is defined and last_write_time2 is defined and last_write_time.stdout == last_write_time2.stdout and ansible_system in ('Linux')
|
||||
always:
|
||||
- name: Umount the test FS
|
||||
mount:
|
||||
path: /tmp/myfs
|
||||
src: /tmp/myfs.img
|
||||
opts: loop
|
||||
state: absent
|
||||
when: ansible_system in ('Linux')
|
||||
|
||||
- name: Remove the test FS
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: absent
|
||||
loop:
|
||||
- /tmp/myfs.img
|
||||
- /tmp/myfs
|
||||
when: ansible_system in ('Linux')
|
||||
...
|
|
@ -1,3 +0,0 @@
|
|||
destructive
|
||||
shippable/posix/group2
|
||||
skip/aix
|
|
@ -1,19 +0,0 @@
|
|||
Stet clita kasd gubergren,no sea takimata sanctus est Lorem ipsum dolor
|
||||
sit amet.
|
||||
|
||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
|
||||
tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
|
||||
vero eos et accusam et justo duo dolores et ea rebum.
|
||||
|
||||
Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor
|
||||
sit amet.
|
||||
|
||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
|
||||
tempor invidunt ut labore et dolore magna aliquyam erat.
|
||||
|
||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
|
||||
tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
|
||||
vero eos et accusam et justo duo dolores et ea rebum.
|
||||
|
||||
Stet clita kasd gubergren,no sea takimata sanctus est Lorem ipsum dolor
|
||||
sit amet.
|
|
@ -1,24 +0,0 @@
|
|||
--- origin.txt 2018-05-12 10:22:14.155109584 +0200
|
||||
+++ result.txt 2018-05-12 10:18:07.230811204 +0200
|
||||
@@ -2,18 +2,12 @@
|
||||
sit amet.
|
||||
|
||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
|
||||
-tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
|
||||
-vero eos et accusam et justo duo dolores et ea rebum.
|
||||
-
|
||||
-Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor
|
||||
-sit amet.
|
||||
-
|
||||
-Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
|
||||
-tempor invidunt ut labore et dolore magna aliquyam erat.
|
||||
+tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
|
||||
+At vero eos et accusam et justo duo dolores et ea rebum.
|
||||
|
||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
|
||||
tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
|
||||
vero eos et accusam et justo duo dolores et ea rebum.
|
||||
|
||||
-Stet clita kasd gubergren,no sea takimata sanctus est Lorem ipsum dolor
|
||||
+Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor
|
||||
sit amet.
|
|
@ -1,13 +0,0 @@
|
|||
Stet clita kasd gubergren,no sea takimata sanctus est Lorem ipsum dolor
|
||||
sit amet.
|
||||
|
||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
|
||||
tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
|
||||
At vero eos et accusam et justo duo dolores et ea rebum.
|
||||
|
||||
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
|
||||
tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
|
||||
vero eos et accusam et justo duo dolores et ea rebum.
|
||||
|
||||
Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor
|
||||
sit amet.
|
|
@ -1,3 +0,0 @@
|
|||
---
|
||||
dependencies:
|
||||
- prepare_tests
|
|
@ -1,107 +0,0 @@
|
|||
---
|
||||
- name: ensure idempotency installed
|
||||
package:
|
||||
name: patch
|
||||
when: ansible_distribution != "MacOSX"
|
||||
|
||||
- name: create a directory for the result
|
||||
file:
|
||||
dest: "{{ output_dir }}/patch"
|
||||
state: directory
|
||||
register: result
|
||||
|
||||
- name: assert the directory was created
|
||||
assert:
|
||||
that:
|
||||
- "result.state == 'directory'"
|
||||
|
||||
- name: copy the origin file
|
||||
copy:
|
||||
src: "./origin.txt"
|
||||
dest: "{{ output_dir }}/patch/workfile.txt"
|
||||
register: result
|
||||
|
||||
- name: patch the origin file in check mode
|
||||
patch:
|
||||
src: result.patch
|
||||
dest: "{{ output_dir }}/patch/workfile.txt"
|
||||
check_mode: yes
|
||||
register: result
|
||||
|
||||
- name: verify patch the origin file in check mode
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: patch the origin file
|
||||
patch:
|
||||
src: result.patch
|
||||
dest: "{{ output_dir }}/patch/workfile.txt"
|
||||
register: result
|
||||
|
||||
- name: verify patch the origin file
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: test patch the origin file idempotency
|
||||
patch:
|
||||
src: result.patch
|
||||
dest: "{{ output_dir }}/patch/workfile.txt"
|
||||
register: result
|
||||
|
||||
- name: verify test patch the origin file idempotency
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
|
||||
- name: verify the resulted file matches expectations
|
||||
copy:
|
||||
src: "./result.txt"
|
||||
dest: "{{ output_dir }}/patch/workfile.txt"
|
||||
register: result
|
||||
failed_when: result is changed
|
||||
|
||||
- name: patch the workfile file in check mode state absent
|
||||
patch:
|
||||
src: result.patch
|
||||
dest: "{{ output_dir }}/patch/workfile.txt"
|
||||
state: absent
|
||||
check_mode: yes
|
||||
register: result
|
||||
|
||||
- name: verify patch the workfile file in check mode state absent
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: patch the workfile file state absent
|
||||
patch:
|
||||
src: result.patch
|
||||
dest: "{{ output_dir }}/patch/workfile.txt"
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: verify patch the workfile file state absent
|
||||
assert:
|
||||
that:
|
||||
- result is changed
|
||||
|
||||
- name: patch the workfile file state absent idempotency
|
||||
patch:
|
||||
src: result.patch
|
||||
dest: "{{ output_dir }}/patch/workfile.txt"
|
||||
state: absent
|
||||
register: result
|
||||
|
||||
- name: verify patch the workfile file state absent idempotency
|
||||
assert:
|
||||
that:
|
||||
- result is not changed
|
||||
|
||||
- name: verify the resulted file matches expectations
|
||||
copy:
|
||||
src: "./origin.txt"
|
||||
dest: "{{ output_dir }}/patch/workfile.txt"
|
||||
register: result
|
||||
failed_when: result is changed
|
|
@ -1,3 +0,0 @@
|
|||
needs/root
|
||||
shippable/posix/group2
|
||||
skip/aix
|
|
@ -1,22 +0,0 @@
|
|||
# (c) 2017, Martin Krizek <mkrizek@redhat.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/>.
|
||||
|
||||
- include: seboolean.yml
|
||||
when:
|
||||
- ansible_selinux is defined
|
||||
- ansible_selinux != False
|
||||
- ansible_selinux.status == 'enabled'
|
|
@ -1,86 +0,0 @@
|
|||
# (c) 2017, Martin Krizek <mkrizek@redhat.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/>.
|
||||
|
||||
- name: install requirements for RHEL 7 and earlier
|
||||
package:
|
||||
name: policycoreutils-python
|
||||
when:
|
||||
- ansible_distribution == 'RedHat' and ansible_distribution_major_version is version('7', '<=')
|
||||
|
||||
- name: install requirements for RHEL 8 and later
|
||||
package:
|
||||
name: policycoreutils-python-utils
|
||||
when:
|
||||
- ansible_distribution == 'RedHat' and ansible_distribution_major_version is version('8', '>=')
|
||||
|
||||
- name: Cleanup
|
||||
shell: setsebool -P httpd_can_network_connect 0
|
||||
##########################################################################################
|
||||
- name: set flag and don't keep it persistent
|
||||
seboolean:
|
||||
name: httpd_can_network_connect
|
||||
state: yes
|
||||
register: output
|
||||
|
||||
- name: get getsebool output
|
||||
shell: semanage boolean -l | grep 'httpd_can_network_connect\W'
|
||||
register: getsebool_output
|
||||
|
||||
- name: check output
|
||||
assert:
|
||||
that:
|
||||
- output is changed
|
||||
- output is not failed
|
||||
- output.name == 'httpd_can_network_connect'
|
||||
- getsebool_output.stdout.startswith('httpd_can_network_connect (on , off)')
|
||||
##########################################################################################
|
||||
- name: unset flag
|
||||
seboolean:
|
||||
name: httpd_can_network_connect
|
||||
state: no
|
||||
|
||||
- name: get getsebool output
|
||||
shell: semanage boolean -l | grep 'httpd_can_network_connect\W'
|
||||
register: getsebool_output
|
||||
|
||||
- name: check output
|
||||
assert:
|
||||
that:
|
||||
- output is changed
|
||||
- output is not failed
|
||||
- output.name == 'httpd_can_network_connect'
|
||||
- getsebool_output.stdout.startswith('httpd_can_network_connect (off , off)')
|
||||
##########################################################################################
|
||||
- name: set flag and keep it persistent
|
||||
seboolean:
|
||||
name: httpd_can_network_connect
|
||||
state: yes
|
||||
persistent: yes
|
||||
register: output
|
||||
|
||||
- name: get getsebool output
|
||||
shell: semanage boolean -l | grep 'httpd_can_network_connect\W'
|
||||
register: getsebool_output
|
||||
|
||||
- name: check output
|
||||
assert:
|
||||
that:
|
||||
- output is changed
|
||||
- output is not failed
|
||||
- output.name == 'httpd_can_network_connect'
|
||||
- getsebool_output.stdout.startswith('httpd_can_network_connect (on , on)')
|
||||
##########################################################################################
|
|
@ -1,3 +0,0 @@
|
|||
needs/root
|
||||
shippable/posix/group2
|
||||
skip/aix
|
|
@ -1,36 +0,0 @@
|
|||
# (c) 2017, Sam Doran <sdoran@redhat.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/>.
|
||||
|
||||
- debug:
|
||||
msg: SELinux is disabled
|
||||
when: ansible_selinux is defined and ansible_selinux == False
|
||||
|
||||
- debug:
|
||||
msg: SELinux is {{ ansible_selinux.status }}
|
||||
when: ansible_selinux is defined and ansible_selinux != False
|
||||
|
||||
- include: selinux.yml
|
||||
when:
|
||||
- ansible_selinux is defined
|
||||
- ansible_selinux != False
|
||||
- ansible_selinux.status == 'enabled'
|
||||
|
||||
- include: selogin.yml
|
||||
when:
|
||||
- ansible_selinux is defined
|
||||
- ansible_selinux != False
|
||||
- ansible_selinux.status == 'enabled'
|
|
@ -1,364 +0,0 @@
|
|||
# (c) 2017, Sam Doran <sdoran@redhat.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/>.
|
||||
|
||||
|
||||
# First Test
|
||||
# ##############################################################################
|
||||
# Test changing the state, which requires a reboot
|
||||
|
||||
- name: TEST 1 | Get current SELinux config file contents
|
||||
set_fact:
|
||||
selinux_config_original: "{{ lookup('file', '/etc/sysconfig/selinux').split('\n') }}"
|
||||
before_test_sestatus: "{{ ansible_selinux }}"
|
||||
|
||||
- debug:
|
||||
var: "{{ item }}"
|
||||
verbosity: 1
|
||||
with_items:
|
||||
- selinux_config_original
|
||||
- before_test_sestatus
|
||||
- ansible_selinux
|
||||
|
||||
- name: TEST 1 | Setup SELinux configuration for tests
|
||||
selinux:
|
||||
state: enforcing
|
||||
policy: targeted
|
||||
|
||||
- name: TEST 1 | Disable SELinux
|
||||
selinux:
|
||||
state: disabled
|
||||
policy: targeted
|
||||
register: _disable_test1
|
||||
|
||||
- debug:
|
||||
var: _disable_test1
|
||||
verbosity: 1
|
||||
|
||||
- name: TEST 1 | Re-gather facts
|
||||
setup:
|
||||
|
||||
- name: TEST 1 | Assert that status was changed, reboot_required is True, a warning was displayed, and SELinux is configured properly
|
||||
assert:
|
||||
that:
|
||||
- _disable_test1 is changed
|
||||
- _disable_test1.reboot_required
|
||||
- (_disable_test1.warnings | length ) >= 1
|
||||
- ansible_selinux.config_mode == 'disabled'
|
||||
- ansible_selinux.type == 'targeted'
|
||||
|
||||
- debug:
|
||||
var: ansible_selinux
|
||||
verbosity: 1
|
||||
|
||||
- name: TEST 1 | Disable SELinux again
|
||||
selinux:
|
||||
state: disabled
|
||||
policy: targeted
|
||||
register: _disable_test2
|
||||
|
||||
- debug:
|
||||
var: _disable_test2
|
||||
verbosity: 1
|
||||
|
||||
- name: TEST 1 | Assert that no change is reported, a warnking was dispalyed, and reboot_required is True
|
||||
assert:
|
||||
that:
|
||||
- _disable_test2 is not changed
|
||||
- (_disable_test1.warnings | length ) >= 1
|
||||
- _disable_test2.reboot_required
|
||||
|
||||
- name: TEST 1 | Get modified config file
|
||||
set_fact:
|
||||
selinux_config_after: "{{ lookup('file', '/etc/sysconfig/selinux').split('\n') }}"
|
||||
|
||||
- debug:
|
||||
var: selinux_config_after
|
||||
verbosity: 1
|
||||
|
||||
- name: TEST 1 | Ensure SELinux config file is properly formatted
|
||||
assert:
|
||||
that:
|
||||
- selinux_config_original | length == selinux_config_after | length
|
||||
- selinux_config_after[selinux_config_after.index('SELINUX=disabled')] is search("^SELINUX=\w+$")
|
||||
- selinux_config_after[selinux_config_after.index('SELINUXTYPE=targeted')] is search("^SELINUXTYPE=\w+$")
|
||||
|
||||
- name: TEST 1 | Reset SELinux configuration for next test
|
||||
selinux:
|
||||
state: enforcing
|
||||
policy: targeted
|
||||
|
||||
|
||||
# Second Test
|
||||
# ##############################################################################
|
||||
# Test changing only the policy, which does not require a reboot
|
||||
|
||||
- name: TEST 2 | Make sure the policy is present
|
||||
package:
|
||||
name: selinux-policy-mls
|
||||
state: present
|
||||
|
||||
- name: TEST 2 | Set SELinux policy
|
||||
selinux:
|
||||
state: enforcing
|
||||
policy: mls
|
||||
register: _state_test1
|
||||
|
||||
- debug:
|
||||
var: _state_test1
|
||||
verbosity: 1
|
||||
|
||||
- name: TEST 2 | Re-gather facts
|
||||
setup:
|
||||
|
||||
- debug:
|
||||
var: ansible_selinux
|
||||
tags: debug
|
||||
|
||||
- name: TEST 2 | Assert that status was changed, reboot_required is False, no warnings were displayed, and SELinux is configured properly
|
||||
assert:
|
||||
that:
|
||||
- _state_test1 is changed
|
||||
- not _state_test1.reboot_required
|
||||
- _state_test1.warnings is not defined
|
||||
- ansible_selinux.config_mode == 'enforcing'
|
||||
- ansible_selinux.type == 'mls'
|
||||
|
||||
- name: TEST 2 | Set SELinux policy again
|
||||
selinux:
|
||||
state: enforcing
|
||||
policy: mls
|
||||
register: _state_test2
|
||||
|
||||
- debug:
|
||||
var: _state_test2
|
||||
verbosity: 1
|
||||
|
||||
- name: TEST 2 | Assert that no change was reported, no warnings were dispalyed, and reboot_required is False
|
||||
assert:
|
||||
that:
|
||||
- _state_test2 is not changed
|
||||
- _state_test2.warnings is not defined
|
||||
- not _state_test2.reboot_required
|
||||
|
||||
- name: TEST 2 | Get modified config file
|
||||
set_fact:
|
||||
selinux_config_after: "{{ lookup('file', '/etc/sysconfig/selinux').split('\n') }}"
|
||||
|
||||
- debug:
|
||||
var: selinux_config_after
|
||||
verbosity: 1
|
||||
|
||||
- name: TEST 2 | Ensure SELinux config file is properly formatted
|
||||
assert:
|
||||
that:
|
||||
- selinux_config_original | length == selinux_config_after | length
|
||||
- selinux_config_after[selinux_config_after.index('SELINUX=enforcing')] is search("^SELINUX=\w+$")
|
||||
- selinux_config_after[selinux_config_after.index('SELINUXTYPE=mls')] is search("^SELINUXTYPE=\w+$")
|
||||
|
||||
- name: TEST 2 | Reset SELinux configuration for next test
|
||||
selinux:
|
||||
state: enforcing
|
||||
policy: targeted
|
||||
|
||||
|
||||
# Third Test
|
||||
# ##############################################################################
|
||||
# Test changing non-existing policy
|
||||
|
||||
- name: TEST 3 | Set SELinux policy
|
||||
selinux:
|
||||
state: enforcing
|
||||
policy: non-existing-selinux-policy
|
||||
register: _state_test1
|
||||
ignore_errors: yes
|
||||
|
||||
- debug:
|
||||
var: _state_test1
|
||||
verbosity: 1
|
||||
|
||||
- name: TEST 3 | Re-gather facts
|
||||
setup:
|
||||
|
||||
- debug:
|
||||
var: ansible_selinux
|
||||
tags: debug
|
||||
|
||||
- name: TEST 3 | Assert that status was not changed, the task failed, the msg contains proper information and SELinux was not changed
|
||||
assert:
|
||||
that:
|
||||
- _state_test1 is not changed
|
||||
- _state_test1 is failed
|
||||
- _state_test1.msg == 'Policy non-existing-selinux-policy does not exist in /etc/selinux/'
|
||||
- ansible_selinux.config_mode == 'enforcing'
|
||||
- ansible_selinux.type == 'targeted'
|
||||
|
||||
|
||||
# Fourth Test
|
||||
# ##############################################################################
|
||||
# Test if check mode returns correct changed values and
|
||||
# doesn't make any changes
|
||||
|
||||
|
||||
- name: TEST 4 | Set SELinux to enforcing
|
||||
selinux:
|
||||
state: enforcing
|
||||
policy: targeted
|
||||
register: _check_mode_test1
|
||||
|
||||
- debug:
|
||||
var: _check_mode_test1
|
||||
verbosity: 1
|
||||
|
||||
- name: TEST 4 | Set SELinux to enforcing in check mode
|
||||
selinux:
|
||||
state: enforcing
|
||||
policy: targeted
|
||||
register: _check_mode_test1
|
||||
check_mode: yes
|
||||
|
||||
- name: TEST 4 | Re-gather facts
|
||||
setup:
|
||||
|
||||
- debug:
|
||||
var: ansible_selinux
|
||||
verbosity: 1
|
||||
tags: debug
|
||||
|
||||
- name: TEST 4 | Assert that check mode is idempotent
|
||||
assert:
|
||||
that:
|
||||
- _check_mode_test1 is success
|
||||
- not _check_mode_test1.reboot_required
|
||||
- ansible_selinux.config_mode == 'enforcing'
|
||||
- ansible_selinux.type == 'targeted'
|
||||
|
||||
- name: TEST 4 | Set SELinux to permissive in check mode
|
||||
selinux:
|
||||
state: permissive
|
||||
policy: targeted
|
||||
register: _check_mode_test2
|
||||
check_mode: yes
|
||||
|
||||
- name: TEST 4 | Re-gather facts
|
||||
setup:
|
||||
|
||||
- debug:
|
||||
var: ansible_selinux
|
||||
verbosity: 1
|
||||
tags: debug
|
||||
|
||||
- name: TEST 4 | Assert that check mode doesn't set state permissive and returns changed
|
||||
assert:
|
||||
that:
|
||||
- _check_mode_test2 is changed
|
||||
- not _check_mode_test2.reboot_required
|
||||
- ansible_selinux.config_mode == 'enforcing'
|
||||
- ansible_selinux.type == 'targeted'
|
||||
|
||||
- name: TEST 4 | Disable SELinux in check mode
|
||||
selinux:
|
||||
state: disabled
|
||||
register: _check_mode_test3
|
||||
check_mode: yes
|
||||
|
||||
- name: TEST 4 | Re-gather facts
|
||||
setup:
|
||||
|
||||
- debug:
|
||||
var: ansible_selinux
|
||||
verbosity: 1
|
||||
tags: debug
|
||||
|
||||
- name: TEST 4 | Assert that check mode didn't change anything, status is changed, reboot_required is True, a warning was displayed
|
||||
assert:
|
||||
that:
|
||||
- _check_mode_test3 is changed
|
||||
- _check_mode_test3.reboot_required
|
||||
- (_check_mode_test3.warnings | length ) >= 1
|
||||
- ansible_selinux.config_mode == 'enforcing'
|
||||
- ansible_selinux.type == 'targeted'
|
||||
|
||||
- name: TEST 4 | Set SELinux to permissive
|
||||
selinux:
|
||||
state: permissive
|
||||
policy: targeted
|
||||
register: _check_mode_test4
|
||||
|
||||
- debug:
|
||||
var: _check_mode_test4
|
||||
verbosity: 1
|
||||
|
||||
- name: TEST 4 | Disable SELinux in check mode
|
||||
selinux:
|
||||
state: disabled
|
||||
register: _check_mode_test4
|
||||
check_mode: yes
|
||||
|
||||
- name: TEST 4 | Re-gather facts
|
||||
setup:
|
||||
|
||||
- debug:
|
||||
var: ansible_selinux
|
||||
verbosity: 1
|
||||
tags: debug
|
||||
|
||||
- name: TEST 4 | Assert that check mode didn't change anything, status is changed, reboot_required is True, a warning was displayed
|
||||
assert:
|
||||
that:
|
||||
- _check_mode_test4 is changed
|
||||
- _check_mode_test4.reboot_required
|
||||
- (_check_mode_test3.warnings | length ) >= 1
|
||||
- ansible_selinux.config_mode == 'permissive'
|
||||
- ansible_selinux.type == 'targeted'
|
||||
|
||||
- name: TEST 4 | Set SELinux to enforcing
|
||||
selinux:
|
||||
state: enforcing
|
||||
policy: targeted
|
||||
register: _check_mode_test5
|
||||
|
||||
- debug:
|
||||
var: _check_mode_test5
|
||||
verbosity: 1
|
||||
|
||||
- name: TEST 4 | Disable SELinux
|
||||
selinux:
|
||||
state: disabled
|
||||
register: _check_mode_test5
|
||||
|
||||
- name: TEST 4 | Disable SELinux in check mode
|
||||
selinux:
|
||||
state: disabled
|
||||
register: _check_mode_test5
|
||||
check_mode: yes
|
||||
|
||||
- name: TEST 4 | Re-gather facts
|
||||
setup:
|
||||
|
||||
- debug:
|
||||
var: ansible_selinux
|
||||
verbosity: 1
|
||||
tags: debug
|
||||
|
||||
- name: TEST 4 | Assert that in check mode status was not changed, reboot_required is True, a warning was displayed, and SELinux is configured properly
|
||||
assert:
|
||||
that:
|
||||
- _check_mode_test5 is success
|
||||
- _check_mode_test5.reboot_required
|
||||
- (_check_mode_test5.warnings | length ) >= 1
|
||||
- ansible_selinux.config_mode == 'disabled'
|
||||
- ansible_selinux.type == 'targeted'
|
|
@ -1,81 +0,0 @@
|
|||
---
|
||||
|
||||
- name: create user for testing
|
||||
user:
|
||||
name: seuser
|
||||
|
||||
- name: attempt to add mapping without 'seuser'
|
||||
selogin:
|
||||
login: seuser
|
||||
register: selogin_error
|
||||
ignore_errors: yes
|
||||
|
||||
- name: verify failure
|
||||
assert:
|
||||
that:
|
||||
- selogin_error is failed
|
||||
|
||||
- name: map login to SELinux user
|
||||
selogin:
|
||||
login: seuser
|
||||
seuser: staff_u
|
||||
register: selogin_new_mapping
|
||||
check_mode: "{{ item }}"
|
||||
with_items:
|
||||
- yes
|
||||
- no
|
||||
- yes
|
||||
- no
|
||||
|
||||
- name: new mapping- verify functionality and check_mode
|
||||
assert:
|
||||
that:
|
||||
- selogin_new_mapping.results[0] is changed
|
||||
- selogin_new_mapping.results[1] is changed
|
||||
- selogin_new_mapping.results[2] is not changed
|
||||
- selogin_new_mapping.results[3] is not changed
|
||||
|
||||
- name: change SELinux user login mapping
|
||||
selogin:
|
||||
login: seuser
|
||||
seuser: user_u
|
||||
register: selogin_mod_mapping
|
||||
check_mode: "{{ item }}"
|
||||
with_items:
|
||||
- yes
|
||||
- no
|
||||
- yes
|
||||
- no
|
||||
|
||||
- name: changed mapping- verify functionality and check_mode
|
||||
assert:
|
||||
that:
|
||||
- selogin_mod_mapping.results[0] is changed
|
||||
- selogin_mod_mapping.results[1] is changed
|
||||
- selogin_mod_mapping.results[2] is not changed
|
||||
- selogin_mod_mapping.results[3] is not changed
|
||||
|
||||
- name: remove SELinux user mapping
|
||||
selogin:
|
||||
login: seuser
|
||||
state: absent
|
||||
register: selogin_del_mapping
|
||||
check_mode: "{{ item }}"
|
||||
with_items:
|
||||
- yes
|
||||
- no
|
||||
- yes
|
||||
- no
|
||||
|
||||
- name: delete mapping- verify functionality and check_mode
|
||||
assert:
|
||||
that:
|
||||
- selogin_del_mapping.results[0] is changed
|
||||
- selogin_del_mapping.results[1] is changed
|
||||
- selogin_del_mapping.results[2] is not changed
|
||||
- selogin_del_mapping.results[3] is not changed
|
||||
|
||||
- name: remove test user
|
||||
user:
|
||||
name: seuser
|
||||
state: absent
|
|
@ -1 +0,0 @@
|
|||
shippable/posix/group2
|
|
@ -1 +0,0 @@
|
|||
templated_var_loaded
|
|
@ -1 +0,0 @@
|
|||
templated_var_loaded
|
|
@ -1,2 +0,0 @@
|
|||
dependencies:
|
||||
- prepare_tests
|
|
@ -1,273 +0,0 @@
|
|||
# test code for the synchronize module
|
||||
# (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/>.
|
||||
|
||||
- name: install rsync
|
||||
package:
|
||||
name: rsync
|
||||
when: ansible_distribution != "MacOSX"
|
||||
|
||||
- name: cleanup old files
|
||||
shell: rm -rf {{output_dir}}/*
|
||||
|
||||
- name: create test new files
|
||||
copy: dest={{output_dir}}/{{item}} mode=0644 content="hello world"
|
||||
with_items:
|
||||
- foo.txt
|
||||
- bar.txt
|
||||
|
||||
- name: synchronize file to new filename
|
||||
synchronize: src={{output_dir}}/foo.txt dest={{output_dir}}/foo.result
|
||||
register: sync_result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "'changed' in sync_result"
|
||||
- "sync_result.changed == true"
|
||||
- "'cmd' in sync_result"
|
||||
- "'rsync' in sync_result.cmd"
|
||||
- "'msg' in sync_result"
|
||||
- "sync_result.msg.startswith('>f+')"
|
||||
- "sync_result.msg.endswith('+ foo.txt\n')"
|
||||
|
||||
- name: test that the file was really copied over
|
||||
stat:
|
||||
path: "{{ output_dir }}/foo.result"
|
||||
register: stat_result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "stat_result.stat.exists == True"
|
||||
- "stat_result.stat.checksum == '2aae6c35c94fcfb415dbe95f408b9ce91ee846ed'"
|
||||
|
||||
- name: test that the file is not copied a second time
|
||||
synchronize: src={{output_dir}}/foo.txt dest={{output_dir}}/foo.result
|
||||
register: sync_result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "sync_result.changed == False"
|
||||
|
||||
- name: Cleanup
|
||||
file:
|
||||
state: absent
|
||||
path: "{{output_dir}}/{{item}}"
|
||||
with_items:
|
||||
- foo.result
|
||||
- bar.result
|
||||
|
||||
- name: Synchronize using the mode=push param
|
||||
synchronize:
|
||||
src: "{{output_dir}}/foo.txt"
|
||||
dest: "{{output_dir}}/foo.result"
|
||||
mode: push
|
||||
register: sync_result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "'changed' in sync_result"
|
||||
- "sync_result.changed == true"
|
||||
- "'cmd' in sync_result"
|
||||
- "'rsync' in sync_result.cmd"
|
||||
- "'msg' in sync_result"
|
||||
- "sync_result.msg.startswith('>f+')"
|
||||
- "sync_result.msg.endswith('+ foo.txt\n')"
|
||||
|
||||
- name: test that the file was really copied over
|
||||
stat:
|
||||
path: "{{ output_dir }}/foo.result"
|
||||
register: stat_result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "stat_result.stat.exists == True"
|
||||
- "stat_result.stat.checksum == '2aae6c35c94fcfb415dbe95f408b9ce91ee846ed'"
|
||||
|
||||
- name: test that the file is not copied a second time
|
||||
synchronize:
|
||||
src: "{{output_dir}}/foo.txt"
|
||||
dest: "{{output_dir}}/foo.result"
|
||||
mode: push
|
||||
register: sync_result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "sync_result.changed == False"
|
||||
|
||||
- name: Cleanup
|
||||
file:
|
||||
state: absent
|
||||
path: "{{output_dir}}/{{item}}"
|
||||
with_items:
|
||||
- foo.result
|
||||
- bar.result
|
||||
|
||||
- name: Synchronize using the mode=pull param
|
||||
synchronize:
|
||||
src: "{{output_dir}}/foo.txt"
|
||||
dest: "{{output_dir}}/foo.result"
|
||||
mode: pull
|
||||
register: sync_result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "'changed' in sync_result"
|
||||
- "sync_result.changed == true"
|
||||
- "'cmd' in sync_result"
|
||||
- "'rsync' in sync_result.cmd"
|
||||
- "'msg' in sync_result"
|
||||
- "sync_result.msg.startswith('>f+')"
|
||||
- "sync_result.msg.endswith('+ foo.txt\n')"
|
||||
|
||||
- name: test that the file was really copied over
|
||||
stat:
|
||||
path: "{{ output_dir }}/foo.result"
|
||||
register: stat_result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "stat_result.stat.exists == True"
|
||||
- "stat_result.stat.checksum == '2aae6c35c94fcfb415dbe95f408b9ce91ee846ed'"
|
||||
|
||||
- name: test that the file is not copied a second time
|
||||
synchronize:
|
||||
src: "{{output_dir}}/foo.txt"
|
||||
dest: "{{output_dir}}/foo.result"
|
||||
mode: pull
|
||||
register: sync_result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "sync_result.changed == False"
|
||||
|
||||
- name: Cleanup
|
||||
file:
|
||||
state: absent
|
||||
path: "{{output_dir}}/{{item}}"
|
||||
with_items:
|
||||
- foo.result
|
||||
- bar.result
|
||||
|
||||
- name: synchronize files using with_items (issue#5965)
|
||||
synchronize: src={{output_dir}}/{{item}} dest={{output_dir}}/{{item}}.result
|
||||
with_items:
|
||||
- foo.txt
|
||||
- bar.txt
|
||||
register: sync_result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "sync_result.changed"
|
||||
- "sync_result.msg == 'All items completed'"
|
||||
- "'results' in sync_result"
|
||||
- "sync_result.results|length == 2"
|
||||
- "sync_result.results[0].msg.endswith('+ foo.txt\n')"
|
||||
- "sync_result.results[1].msg.endswith('+ bar.txt\n')"
|
||||
|
||||
- name: Cleanup
|
||||
file:
|
||||
state: absent
|
||||
path: "{{output_dir}}/{{item}}.result"
|
||||
with_items:
|
||||
- foo.txt
|
||||
- bar.txt
|
||||
|
||||
- name: synchronize files using rsync_path (issue#7182)
|
||||
synchronize: src={{output_dir}}/foo.txt dest={{output_dir}}/foo.rsync_path rsync_path="sudo rsync"
|
||||
register: sync_result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "'changed' in sync_result"
|
||||
- "sync_result.changed == true"
|
||||
- "'cmd' in sync_result"
|
||||
- "'rsync' in sync_result.cmd"
|
||||
- "'rsync_path' in sync_result.cmd"
|
||||
- "'msg' in sync_result"
|
||||
- "sync_result.msg.startswith('>f+')"
|
||||
- "sync_result.msg.endswith('+ foo.txt\n')"
|
||||
|
||||
- name: Cleanup
|
||||
file:
|
||||
state: absent
|
||||
path: "{{output_dir}}/{{item}}"
|
||||
with_items:
|
||||
- foo.rsync_path
|
||||
|
||||
- name: add subdirectories for link-dest test
|
||||
file:
|
||||
path: "{{output_dir}}/{{item}}/"
|
||||
state: directory
|
||||
mode: 0755
|
||||
with_items:
|
||||
- directory_a
|
||||
- directory_b
|
||||
|
||||
- name: copy foo.txt into the first directory
|
||||
synchronize:
|
||||
src: "{{output_dir}}/foo.txt"
|
||||
dest: "{{output_dir}}/{{item}}/foo.txt"
|
||||
with_items:
|
||||
- directory_a
|
||||
|
||||
- name: synchronize files using link_dest
|
||||
synchronize:
|
||||
src: "{{output_dir}}/directory_a/foo.txt"
|
||||
dest: "{{output_dir}}/directory_b/foo.txt"
|
||||
link_dest:
|
||||
- "{{output_dir}}/directory_a"
|
||||
register: sync_result
|
||||
|
||||
- name: get stat information for directory_a
|
||||
stat:
|
||||
path: "{{ output_dir }}/directory_a/foo.txt"
|
||||
register: stat_result_a
|
||||
|
||||
- name: get stat information for directory_b
|
||||
stat:
|
||||
path: "{{ output_dir }}/directory_b/foo.txt"
|
||||
register: stat_result_b
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "'changed' in sync_result"
|
||||
- "sync_result.changed == true"
|
||||
- "stat_result_a.stat.inode == stat_result_b.stat.inode"
|
||||
|
||||
- name: synchronize files using link_dest that would be recursive
|
||||
synchronize:
|
||||
src: "{{output_dir}}/foo.txt"
|
||||
dest: "{{output_dir}}/foo.result"
|
||||
link_dest:
|
||||
- "{{output_dir}}"
|
||||
register: sync_result
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- sync_result is not changed
|
||||
- sync_result is failed
|
||||
|
||||
- name: Cleanup
|
||||
file:
|
||||
state: absent
|
||||
path: "{{output_dir}}/{{item}}"
|
||||
with_items:
|
||||
- "directory_b/foo.txt"
|
||||
- "directory_a/foo.txt"
|
||||
- "directory_a"
|
||||
- "directory_b"
|
|
@ -1,4 +0,0 @@
|
|||
shippable/posix/group1
|
||||
skip/aix
|
||||
skip/freebsd
|
||||
skip/osx
|
|
@ -1,12 +0,0 @@
|
|||
# sysctl settings are defined through files in
|
||||
# /usr/lib/sysctl.d/, /run/sysctl.d/, and /etc/sysctl.d/.
|
||||
#
|
||||
# Vendors settings live in /usr/lib/sysctl.d/.
|
||||
# To override a whole file, create a new file with the same in
|
||||
# /etc/sysctl.d/ and put new settings there. To override
|
||||
# only specific settings, add a file with a lexically later
|
||||
# name in /etc/sysctl.d/ and put new settings there.
|
||||
#
|
||||
# For more information, see sysctl.conf(5) and sysctl.d(5).
|
||||
vm.swappiness=1
|
||||
kernel.panic=2
|
|
@ -1,2 +0,0 @@
|
|||
dependencies:
|
||||
- prepare_tests
|
|
@ -1,291 +0,0 @@
|
|||
# Test code for the sysctl module.
|
||||
# (c) 2017, 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/>.
|
||||
|
||||
# NOTE: Testing sysctl inside an unprivileged container means that we cannot
|
||||
# apply sysctl, or it will always fail, because of that in most cases (except
|
||||
# those when it should fail) we have to use `reload=no`.
|
||||
|
||||
- name: Test inside Docker
|
||||
when:
|
||||
- ansible_facts.virtualization_type == 'docker'
|
||||
block:
|
||||
- set_fact:
|
||||
output_dir_test: "{{ output_dir }}/test_sysctl"
|
||||
|
||||
- name: make sure our testing sub-directory does not exist
|
||||
file:
|
||||
path: "{{ output_dir_test }}"
|
||||
state: absent
|
||||
|
||||
- name: create our testing sub-directory
|
||||
file:
|
||||
path: "{{ output_dir_test }}"
|
||||
state: directory
|
||||
|
||||
##
|
||||
## sysctl - file manipulation
|
||||
##
|
||||
|
||||
- name: copy the example conf to the test dir
|
||||
copy:
|
||||
src: sysctl.conf
|
||||
dest: "{{ output_dir_test }}"
|
||||
|
||||
- name: Set vm.swappiness to 5
|
||||
sysctl:
|
||||
name: vm.swappiness
|
||||
value: 5
|
||||
state: present
|
||||
reload: no
|
||||
sysctl_file: "{{ output_dir_test }}/sysctl.conf"
|
||||
register: sysctl_test0
|
||||
|
||||
- debug:
|
||||
var: sysctl_test0
|
||||
verbosity: 1
|
||||
|
||||
- name: get file content
|
||||
shell: "cat {{ output_dir_test }}/sysctl.conf | egrep -v ^\\#"
|
||||
register: sysctl_content0
|
||||
|
||||
- debug:
|
||||
var: sysctl_content0
|
||||
verbosity: 1
|
||||
|
||||
- name: Set vm.swappiness to 5 again
|
||||
sysctl:
|
||||
name: vm.swappiness
|
||||
value: 5
|
||||
state: present
|
||||
reload: no
|
||||
sysctl_file: "{{ output_dir_test }}/sysctl.conf"
|
||||
register: sysctl_test1
|
||||
|
||||
- name: validate results
|
||||
assert:
|
||||
that:
|
||||
- sysctl_test0 is changed
|
||||
- sysctl_test1 is not changed
|
||||
- 'sysctl_content0.stdout_lines[sysctl_content0.stdout_lines.index("vm.swappiness=5")] == "vm.swappiness=5"'
|
||||
|
||||
- name: Remove kernel.panic
|
||||
sysctl:
|
||||
name: kernel.panic
|
||||
value: 2
|
||||
reload: no
|
||||
state: absent
|
||||
sysctl_file: "{{ output_dir_test }}/sysctl.conf"
|
||||
register: sysctl_test2
|
||||
|
||||
- name: get file content
|
||||
shell: "cat {{ output_dir_test }}/sysctl.conf | egrep -v ^\\#"
|
||||
register: sysctl_content2
|
||||
|
||||
- debug:
|
||||
var: item
|
||||
verbosity: 1
|
||||
with_items:
|
||||
- "{{ sysctl_test2 }}"
|
||||
- "{{ sysctl_content2 }}"
|
||||
|
||||
- name: Validate results for key removal
|
||||
assert:
|
||||
that:
|
||||
- sysctl_test2 is changed
|
||||
- "'kernel.panic' not in sysctl_content2.stdout_lines"
|
||||
|
||||
- name: Test remove kernel.panic again
|
||||
sysctl:
|
||||
name: kernel.panic
|
||||
value: 2
|
||||
state: absent
|
||||
reload: no
|
||||
sysctl_file: "{{ output_dir_test }}/sysctl.conf"
|
||||
register: sysctl_test2_change_test
|
||||
|
||||
- name: Assert that no change was made
|
||||
assert:
|
||||
that:
|
||||
- sysctl_test2_change_test is not changed
|
||||
|
||||
- name: Try sysctl with an invalid value
|
||||
sysctl:
|
||||
name: net.ipv4.ip_forward
|
||||
value: foo
|
||||
register: sysctl_test3
|
||||
ignore_errors: yes
|
||||
|
||||
- debug:
|
||||
var: sysctl_test3
|
||||
verbosity: 1
|
||||
|
||||
- name: validate results for test 3
|
||||
assert:
|
||||
that:
|
||||
- sysctl_test3 is failed
|
||||
|
||||
##
|
||||
## sysctl - sysctl_set
|
||||
##
|
||||
|
||||
- name: set net.ipv4.ip_forward
|
||||
sysctl:
|
||||
name: net.ipv4.ip_forward
|
||||
value: 1
|
||||
sysctl_set: yes
|
||||
reload: no
|
||||
register: sysctl_test3
|
||||
|
||||
- name: check with sysctl command
|
||||
shell: sysctl net.ipv4.ip_forward
|
||||
register: sysctl_check3
|
||||
|
||||
- debug:
|
||||
var: item
|
||||
verbosity: 1
|
||||
with_items:
|
||||
- "{{ sysctl_test3 }}"
|
||||
- "{{ sysctl_check3 }}"
|
||||
|
||||
- name: validate results for test 3
|
||||
assert:
|
||||
that:
|
||||
- sysctl_test3 is changed
|
||||
- 'sysctl_check3.stdout_lines == ["net.ipv4.ip_forward = 1"]'
|
||||
|
||||
- name: Try sysctl with no name
|
||||
sysctl:
|
||||
name:
|
||||
value: 1
|
||||
sysctl_set: yes
|
||||
ignore_errors: True
|
||||
register: sysctl_no_name
|
||||
|
||||
- name: validate nameless results
|
||||
assert:
|
||||
that:
|
||||
- sysctl_no_name is failed
|
||||
- "sysctl_no_name.msg == 'name cannot be None'"
|
||||
|
||||
- name: Try sysctl with no value
|
||||
sysctl:
|
||||
name: Foo
|
||||
value:
|
||||
sysctl_set: yes
|
||||
ignore_errors: True
|
||||
register: sysctl_no_value
|
||||
|
||||
- name: validate nameless results
|
||||
assert:
|
||||
that:
|
||||
- sysctl_no_value is failed
|
||||
- "sysctl_no_value.msg == 'value cannot be None'"
|
||||
|
||||
- name: Try sysctl with an invalid value
|
||||
sysctl:
|
||||
name: net.ipv4.ip_forward
|
||||
value: foo
|
||||
sysctl_set: yes
|
||||
register: sysctl_test4
|
||||
ignore_errors: yes
|
||||
|
||||
- debug:
|
||||
var: sysctl_test4
|
||||
verbosity: 1
|
||||
|
||||
- name: validate results for test 4
|
||||
assert:
|
||||
that:
|
||||
- sysctl_test4 is failed
|
||||
|
||||
|
||||
- name: Test on RHEL VMs
|
||||
when:
|
||||
- ansible_facts.virtualization_type != 'docker'
|
||||
- ansible_facts.distribution == 'RedHat'
|
||||
block:
|
||||
# Test reload: yes
|
||||
- name: Set sysctl property using module
|
||||
sysctl:
|
||||
name: vm.swappiness
|
||||
value: '22'
|
||||
state: present
|
||||
reload: yes
|
||||
register: sysctl_set1
|
||||
|
||||
- name: Change sysctl property using command
|
||||
command: sysctl vm.swappiness=33
|
||||
|
||||
- name: Set sysctl property using module
|
||||
sysctl:
|
||||
name: vm.swappiness
|
||||
value: '22'
|
||||
state: present
|
||||
reload: yes
|
||||
register: sysctl_set2
|
||||
|
||||
- name: Read /etc/sysctl.conf
|
||||
command: 'egrep -v ^# /etc/sysctl.conf'
|
||||
register: sysctl_conf_content
|
||||
|
||||
- name: Get current value of vm.swappiness
|
||||
command: sysctl -n vm.swappiness
|
||||
register: sysctl_current_vm_swappiness
|
||||
|
||||
- name: Ensure changes were made appropriately
|
||||
assert:
|
||||
that:
|
||||
- sysctl_set1 is changed
|
||||
- sysctl_set2 is changed
|
||||
- "'vm.swappiness=22' in sysctl_conf_content.stdout_lines"
|
||||
- sysctl_current_vm_swappiness.stdout == '22'
|
||||
|
||||
# Test reload: yes in check mode
|
||||
- name: Set the same value using module in check mode
|
||||
sysctl:
|
||||
name: vm.swappiness
|
||||
value: '22'
|
||||
state: present
|
||||
reload: yes
|
||||
check_mode: yes
|
||||
register: sysctl_check_mode1
|
||||
|
||||
- name: Set a different value using module in check mode
|
||||
sysctl:
|
||||
name: vm.swappiness
|
||||
value: '44'
|
||||
state: present
|
||||
reload: yes
|
||||
check_mode: yes
|
||||
register: sysctl_check_mode2
|
||||
|
||||
- name: Read /etc/sysctl.conf
|
||||
command: 'egrep -v ^# /etc/sysctl.conf'
|
||||
register: sysctl_check_mode_conf_content
|
||||
|
||||
- name: Get current value of vm.swappiness
|
||||
command: sysctl -n vm.swappiness
|
||||
register: sysctl_check_mode_current_vm_swappiness
|
||||
|
||||
- name: Ensure no changes were made in check mode
|
||||
assert:
|
||||
that:
|
||||
- sysctl_check_mode1 is success
|
||||
- sysctl_check_mode2 is changed
|
||||
- "'vm.swappiness=22' in sysctl_check_mode_conf_content.stdout_lines"
|
||||
- sysctl_check_mode_current_vm_swappiness.stdout == '22'
|
|
@ -84,8 +84,6 @@ lib/ansible/module_utils/facts/utils.py future-import-boilerplate
|
|||
lib/ansible/module_utils/facts/utils.py metaclass-boilerplate
|
||||
lib/ansible/module_utils/gcp_utils.py future-import-boilerplate
|
||||
lib/ansible/module_utils/gcp_utils.py metaclass-boilerplate
|
||||
lib/ansible/module_utils/ismount.py future-import-boilerplate
|
||||
lib/ansible/module_utils/ismount.py metaclass-boilerplate
|
||||
lib/ansible/module_utils/json_utils.py future-import-boilerplate
|
||||
lib/ansible/module_utils/json_utils.py metaclass-boilerplate
|
||||
lib/ansible/module_utils/net_tools/netbox/netbox_utils.py future-import-boilerplate
|
||||
|
@ -1488,7 +1486,6 @@ lib/ansible/modules/commands/command.py validate-modules:nonexistent-parameter-d
|
|||
lib/ansible/modules/commands/command.py validate-modules:parameter-list-no-elements
|
||||
lib/ansible/modules/commands/command.py validate-modules:undocumented-parameter
|
||||
lib/ansible/modules/commands/expect.py validate-modules:doc-missing-type
|
||||
lib/ansible/modules/files/acl.py validate-modules:parameter-type-not-in-doc
|
||||
lib/ansible/modules/files/assemble.py validate-modules:nonexistent-parameter-documented
|
||||
lib/ansible/modules/files/blockinfile.py validate-modules:doc-choices-do-not-match-spec
|
||||
lib/ansible/modules/files/blockinfile.py validate-modules:doc-default-does-not-match-spec
|
||||
|
@ -1510,13 +1507,6 @@ lib/ansible/modules/files/replace.py validate-modules:nonexistent-parameter-docu
|
|||
lib/ansible/modules/files/stat.py validate-modules:parameter-invalid
|
||||
lib/ansible/modules/files/stat.py validate-modules:parameter-type-not-in-doc
|
||||
lib/ansible/modules/files/stat.py validate-modules:undocumented-parameter
|
||||
lib/ansible/modules/files/synchronize.py pylint:blacklisted-name
|
||||
lib/ansible/modules/files/synchronize.py use-argspec-type-path
|
||||
lib/ansible/modules/files/synchronize.py validate-modules:doc-default-does-not-match-spec
|
||||
lib/ansible/modules/files/synchronize.py validate-modules:nonexistent-parameter-documented
|
||||
lib/ansible/modules/files/synchronize.py validate-modules:parameter-list-no-elements
|
||||
lib/ansible/modules/files/synchronize.py validate-modules:parameter-type-not-in-doc
|
||||
lib/ansible/modules/files/synchronize.py validate-modules:undocumented-parameter
|
||||
lib/ansible/modules/files/unarchive.py validate-modules:nonexistent-parameter-documented
|
||||
lib/ansible/modules/files/unarchive.py validate-modules:parameter-list-no-elements
|
||||
lib/ansible/modules/identity/cyberark/cyberark_authentication.py validate-modules:parameter-type-not-in-doc
|
||||
|
@ -3810,8 +3800,6 @@ lib/ansible/modules/storage/purestorage/purefb_info.py validate-modules:paramete
|
|||
lib/ansible/modules/storage/purestorage/purefb_info.py validate-modules:return-syntax-error
|
||||
lib/ansible/modules/storage/purestorage/purefb_s3acc.py validate-modules:doc-required-mismatch
|
||||
lib/ansible/modules/storage/purestorage/purefb_s3user.py validate-modules:doc-required-mismatch
|
||||
lib/ansible/modules/system/at.py validate-modules:doc-required-mismatch
|
||||
lib/ansible/modules/system/authorized_key.py validate-modules:parameter-type-not-in-doc
|
||||
lib/ansible/modules/system/getent.py validate-modules:parameter-type-not-in-doc
|
||||
lib/ansible/modules/system/hostname.py validate-modules:invalid-ansiblemodule-schema
|
||||
lib/ansible/modules/system/hostname.py validate-modules:parameter-type-not-in-doc
|
||||
|
@ -3820,16 +3808,11 @@ lib/ansible/modules/system/iptables.py validate-modules:parameter-list-no-elemen
|
|||
lib/ansible/modules/system/known_hosts.py validate-modules:doc-default-does-not-match-spec
|
||||
lib/ansible/modules/system/known_hosts.py validate-modules:doc-missing-type
|
||||
lib/ansible/modules/system/known_hosts.py validate-modules:parameter-type-not-in-doc
|
||||
lib/ansible/modules/system/seboolean.py validate-modules:parameter-type-not-in-doc
|
||||
lib/ansible/modules/system/selinux.py validate-modules:invalid-ansiblemodule-schema
|
||||
lib/ansible/modules/system/selinux.py validate-modules:parameter-type-not-in-doc
|
||||
lib/ansible/modules/system/service.py validate-modules:nonexistent-parameter-documented
|
||||
lib/ansible/modules/system/service.py validate-modules:use-run-command-not-popen
|
||||
lib/ansible/modules/system/setup.py validate-modules:doc-missing-type
|
||||
lib/ansible/modules/system/setup.py validate-modules:parameter-list-no-elements
|
||||
lib/ansible/modules/system/setup.py validate-modules:parameter-type-not-in-doc
|
||||
lib/ansible/modules/system/sysctl.py validate-modules:doc-missing-type
|
||||
lib/ansible/modules/system/sysctl.py validate-modules:parameter-type-not-in-doc
|
||||
lib/ansible/modules/system/systemd.py validate-modules:parameter-invalid
|
||||
lib/ansible/modules/system/systemd.py validate-modules:parameter-type-not-in-doc
|
||||
lib/ansible/modules/system/systemd.py validate-modules:return-syntax-error
|
||||
|
@ -4392,8 +4375,6 @@ test/units/modules/system/test_iptables.py metaclass-boilerplate
|
|||
test/units/modules/system/test_known_hosts.py future-import-boilerplate
|
||||
test/units/modules/system/test_known_hosts.py metaclass-boilerplate
|
||||
test/units/modules/system/test_known_hosts.py pylint:ansible-bad-function
|
||||
test/units/modules/system/test_mount.py future-import-boilerplate
|
||||
test/units/modules/system/test_mount.py metaclass-boilerplate
|
||||
test/units/modules/system/test_systemd.py future-import-boilerplate
|
||||
test/units/modules/system/test_systemd.py metaclass-boilerplate
|
||||
test/units/modules/utils.py future-import-boilerplate
|
||||
|
@ -4406,8 +4387,6 @@ test/units/playbook/test_attribute.py future-import-boilerplate
|
|||
test/units/playbook/test_attribute.py metaclass-boilerplate
|
||||
test/units/playbook/test_conditional.py future-import-boilerplate
|
||||
test/units/playbook/test_conditional.py metaclass-boilerplate
|
||||
test/units/plugins/action/test_synchronize.py future-import-boilerplate
|
||||
test/units/plugins/action/test_synchronize.py metaclass-boilerplate
|
||||
test/units/plugins/inventory/test_constructed.py future-import-boilerplate
|
||||
test/units/plugins/inventory/test_constructed.py metaclass-boilerplate
|
||||
test/units/plugins/loader_fixtures/import_fixture.py future-import-boilerplate
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
# The loopback network interface
|
||||
auto lo eth0
|
||||
iface lo inet loopback
|
||||
|
||||
# The primary network interface
|
||||
iface eth0 inet static
|
||||
address 192.168.0.1
|
||||
post-up echo configuring ipv4
|
||||
|
||||
iface eth0 inet6 static
|
||||
address fc00::1
|
||||
post-up echo configuring ipv6
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"eth0": {
|
||||
"address": "fc00::1",
|
||||
"address_family": "inet6",
|
||||
"down": [],
|
||||
"method": "static",
|
||||
"post-up": [
|
||||
"echo configuring ipv6"
|
||||
],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
},
|
||||
"lo": {
|
||||
"address_family": "inet",
|
||||
"down": [],
|
||||
"method": "loopback",
|
||||
"post-up": [],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
# The loopback network interface
|
||||
auto lo eth0
|
||||
iface lo inet loopback
|
||||
|
||||
# The primary network interface
|
||||
iface eth0 inet static
|
||||
address 192.168.0.1
|
||||
post-up echo configuring ipv4
|
||||
|
||||
iface eth0 inet6 static
|
||||
address fc00::1
|
||||
post-up echo configuring ipv6
|
|
@ -1,8 +0,0 @@
|
|||
[0] fail_json message: Error: interface aggi not found
|
||||
options:
|
||||
{
|
||||
"iface": "aggi",
|
||||
"option": "up",
|
||||
"state": "present",
|
||||
"value": "route add -net 224.0.0.0 netmask 240.0.0.0 dev aggi"
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"eth0": {
|
||||
"address": "fc00::1",
|
||||
"address_family": "inet6",
|
||||
"down": [],
|
||||
"method": "static",
|
||||
"post-up": [
|
||||
"echo configuring ipv6"
|
||||
],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
},
|
||||
"lo": {
|
||||
"address_family": "inet",
|
||||
"down": [],
|
||||
"method": "loopback",
|
||||
"post-up": [],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
# The loopback network interface
|
||||
auto lo eth0
|
||||
iface lo inet loopback
|
||||
|
||||
# The primary network interface
|
||||
iface eth0 inet static
|
||||
address 192.168.0.1
|
||||
post-up echo configuring ipv4
|
||||
|
||||
iface eth0 inet6 static
|
||||
address fc00::1
|
||||
post-up echo configuring ipv6
|
|
@ -1,17 +0,0 @@
|
|||
[0] fail_json message: Error: interface aggi not found
|
||||
options:
|
||||
{
|
||||
"iface": "aggi",
|
||||
"option": "up",
|
||||
"state": "present",
|
||||
"value": "route add -net 224.0.0.0 netmask 240.0.0.0 dev aggi"
|
||||
}
|
||||
=====
|
||||
[1] fail_json message: Error: interface aggi not found
|
||||
options:
|
||||
{
|
||||
"iface": "aggi",
|
||||
"option": "up",
|
||||
"state": "absent",
|
||||
"value": null
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"eth0": {
|
||||
"address": "fc00::1",
|
||||
"address_family": "inet6",
|
||||
"down": [],
|
||||
"method": "static",
|
||||
"post-up": [
|
||||
"echo configuring ipv6"
|
||||
],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
},
|
||||
"lo": {
|
||||
"address_family": "inet",
|
||||
"down": [],
|
||||
"method": "loopback",
|
||||
"post-up": [],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
# The loopback network interface
|
||||
auto lo eth0
|
||||
iface lo inet loopback
|
||||
|
||||
# The primary network interface
|
||||
iface eth0 inet static
|
||||
address 192.168.0.42
|
||||
post-up echo configuring ipv4
|
||||
|
||||
iface eth0 inet6 static
|
||||
address fc00::1
|
||||
post-up echo configuring ipv6
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"eth0": {
|
||||
"address": "fc00::1",
|
||||
"address_family": "inet6",
|
||||
"down": [],
|
||||
"method": "static",
|
||||
"post-up": [
|
||||
"echo configuring ipv6"
|
||||
],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
},
|
||||
"lo": {
|
||||
"address_family": "inet",
|
||||
"down": [],
|
||||
"method": "loopback",
|
||||
"post-up": [],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
# The loopback network interface
|
||||
auto lo eth0
|
||||
iface lo inet loopback
|
||||
|
||||
# The primary network interface
|
||||
iface eth0 inet static
|
||||
address 192.168.0.1
|
||||
post-up echo configuring ipv4
|
||||
post-up XXXX_ipv4
|
||||
|
||||
iface eth0 inet6 static
|
||||
address fc00::1
|
||||
post-up echo configuring ipv6
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"eth0": {
|
||||
"address": "fc00::1",
|
||||
"address_family": "inet6",
|
||||
"down": [],
|
||||
"method": "static",
|
||||
"post-up": [
|
||||
"echo configuring ipv6"
|
||||
],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
},
|
||||
"lo": {
|
||||
"address_family": "inet",
|
||||
"down": [],
|
||||
"method": "loopback",
|
||||
"post-up": [],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
# The loopback network interface
|
||||
auto lo eth0
|
||||
iface lo inet loopback
|
||||
|
||||
# The primary network interface
|
||||
iface eth0 inet static
|
||||
address 192.168.0.1
|
||||
post-up echo configuring ipv4
|
||||
pre-up XXXX_ipv4
|
||||
|
||||
iface eth0 inet6 static
|
||||
address fc00::1
|
||||
post-up echo configuring ipv6
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"eth0": {
|
||||
"address": "fc00::1",
|
||||
"address_family": "inet6",
|
||||
"down": [],
|
||||
"method": "static",
|
||||
"post-up": [
|
||||
"echo configuring ipv6"
|
||||
],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
},
|
||||
"lo": {
|
||||
"address_family": "inet",
|
||||
"down": [],
|
||||
"method": "loopback",
|
||||
"post-up": [],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
# The loopback network interface
|
||||
auto lo eth0
|
||||
iface lo inet loopback
|
||||
|
||||
# The primary network interface
|
||||
iface eth0 inet static
|
||||
address 192.168.0.1
|
||||
post-up echo configuring ipv4
|
||||
|
||||
iface eth0 inet6 static
|
||||
address fc00::42
|
||||
post-up echo configuring ipv6
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"eth0": {
|
||||
"address": "fc00::1",
|
||||
"address_family": "inet6",
|
||||
"down": [],
|
||||
"method": "static",
|
||||
"post-up": [
|
||||
"echo configuring ipv6"
|
||||
],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
},
|
||||
"lo": {
|
||||
"address_family": "inet",
|
||||
"down": [],
|
||||
"method": "loopback",
|
||||
"post-up": [],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
# The loopback network interface
|
||||
auto lo eth0
|
||||
iface lo inet loopback
|
||||
|
||||
# The primary network interface
|
||||
iface eth0 inet static
|
||||
address 192.168.0.1
|
||||
post-up echo configuring ipv4
|
||||
|
||||
iface eth0 inet6 static
|
||||
address fc00::1
|
||||
post-up echo configuring ipv6
|
||||
post-up XXXX_ipv6
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"eth0": {
|
||||
"address": "fc00::1",
|
||||
"address_family": "inet6",
|
||||
"down": [],
|
||||
"method": "static",
|
||||
"post-up": [
|
||||
"echo configuring ipv6"
|
||||
],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
},
|
||||
"lo": {
|
||||
"address_family": "inet",
|
||||
"down": [],
|
||||
"method": "loopback",
|
||||
"post-up": [],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
# The loopback network interface
|
||||
auto lo eth0
|
||||
iface lo inet loopback
|
||||
|
||||
# The primary network interface
|
||||
iface eth0 inet static
|
||||
address 192.168.0.1
|
||||
post-up echo configuring ipv4
|
||||
|
||||
iface eth0 inet6 static
|
||||
address fc00::1
|
||||
post-up echo configuring ipv6
|
||||
pre-up XXXX_ipv6
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"eth0": {
|
||||
"address": "fc00::1",
|
||||
"address_family": "inet6",
|
||||
"down": [],
|
||||
"method": "static",
|
||||
"post-up": [
|
||||
"echo configuring ipv6"
|
||||
],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
},
|
||||
"lo": {
|
||||
"address_family": "inet",
|
||||
"down": [],
|
||||
"method": "loopback",
|
||||
"post-up": [],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
# The loopback network interface
|
||||
auto lo eth0
|
||||
iface lo inet loopback
|
||||
|
||||
# The primary network interface
|
||||
iface eth0 inet static
|
||||
address 192.168.0.1
|
||||
post-up echo configuring ipv4
|
||||
|
||||
iface eth0 inet6 static
|
||||
address fc00::1
|
||||
post-up echo configuring ipv6
|
|
@ -1,8 +0,0 @@
|
|||
fail_json message: Error: interface eth1 not found
|
||||
options:
|
||||
{
|
||||
"iface": "eth1",
|
||||
"option": "method",
|
||||
"state": "present",
|
||||
"value": "dhcp"
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"eth0": {
|
||||
"address": "fc00::1",
|
||||
"address_family": "inet6",
|
||||
"down": [],
|
||||
"method": "static",
|
||||
"post-up": [
|
||||
"echo configuring ipv6"
|
||||
],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
},
|
||||
"lo": {
|
||||
"address_family": "inet",
|
||||
"down": [],
|
||||
"method": "loopback",
|
||||
"post-up": [],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
# The loopback network interface
|
||||
auto lo eth0
|
||||
iface lo inet loopback
|
||||
|
||||
# The primary network interface
|
||||
iface eth0 inet static
|
||||
address 192.168.0.1
|
||||
post-up echo configuring ipv4
|
||||
|
||||
iface eth0 inet6 static
|
||||
address fc00::1
|
||||
post-up echo configuring ipv6
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"eth0": {
|
||||
"address": "fc00::1",
|
||||
"address_family": "inet6",
|
||||
"down": [],
|
||||
"method": "static",
|
||||
"post-up": [
|
||||
"echo configuring ipv6"
|
||||
],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
},
|
||||
"lo": {
|
||||
"address_family": "inet",
|
||||
"down": [],
|
||||
"method": "loopback",
|
||||
"post-up": [],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
# The loopback network interface
|
||||
auto lo eth0
|
||||
iface lo inet loopback
|
||||
|
||||
# The primary network interface
|
||||
iface eth0 inet static
|
||||
address 192.168.0.1
|
||||
post-up echo configuring ipv4
|
||||
|
||||
iface eth0 inet6 static
|
||||
address fc00::1
|
||||
post-up echo configuring ipv6
|
||||
mtu 1350
|
|
@ -1,8 +0,0 @@
|
|||
[0] fail_json message: Error: interface aggi not found
|
||||
options:
|
||||
{
|
||||
"iface": "aggi",
|
||||
"option": "mtu",
|
||||
"state": "present",
|
||||
"value": "1350"
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"eth0": {
|
||||
"address": "fc00::1",
|
||||
"address_family": "inet6",
|
||||
"down": [],
|
||||
"method": "static",
|
||||
"post-up": [
|
||||
"echo configuring ipv6"
|
||||
],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
},
|
||||
"lo": {
|
||||
"address_family": "inet",
|
||||
"down": [],
|
||||
"method": "loopback",
|
||||
"post-up": [],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
# The loopback network interface
|
||||
auto lo eth0
|
||||
iface lo inet loopback
|
||||
|
||||
# The primary network interface
|
||||
iface eth0 inet static
|
||||
address 192.168.0.1
|
||||
post-up echo configuring ipv4
|
||||
|
||||
iface eth0 inet6 static
|
||||
address fc00::1
|
||||
post-up echo configuring ipv6
|
|
@ -1,8 +0,0 @@
|
|||
[0] fail_json message: Error: interface aggi not found
|
||||
options:
|
||||
{
|
||||
"iface": "aggi",
|
||||
"option": "slaves",
|
||||
"state": "present",
|
||||
"value": "int1 int3"
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"eth0": {
|
||||
"address": "fc00::1",
|
||||
"address_family": "inet6",
|
||||
"down": [],
|
||||
"method": "static",
|
||||
"post-up": [
|
||||
"echo configuring ipv6"
|
||||
],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
},
|
||||
"lo": {
|
||||
"address_family": "inet",
|
||||
"down": [],
|
||||
"method": "loopback",
|
||||
"post-up": [],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
# The loopback network interface
|
||||
auto lo eth0
|
||||
iface lo inet loopback
|
||||
|
||||
# The primary network interface
|
||||
iface eth0 inet dhcp
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"eth0": {
|
||||
"address_family": "inet",
|
||||
"down": [],
|
||||
"method": "dhcp",
|
||||
"post-up": [],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
},
|
||||
"lo": {
|
||||
"address_family": "inet",
|
||||
"down": [],
|
||||
"method": "loopback",
|
||||
"post-up": [],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
# The loopback network interface
|
||||
auto lo eth0
|
||||
iface lo inet loopback
|
||||
|
||||
# The primary network interface
|
||||
iface eth0 inet dhcp
|
|
@ -1,8 +0,0 @@
|
|||
[0] fail_json message: Error: interface aggi not found
|
||||
options:
|
||||
{
|
||||
"iface": "aggi",
|
||||
"option": "up",
|
||||
"state": "present",
|
||||
"value": "route add -net 224.0.0.0 netmask 240.0.0.0 dev aggi"
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"eth0": {
|
||||
"address_family": "inet",
|
||||
"down": [],
|
||||
"method": "dhcp",
|
||||
"post-up": [],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
},
|
||||
"lo": {
|
||||
"address_family": "inet",
|
||||
"down": [],
|
||||
"method": "loopback",
|
||||
"post-up": [],
|
||||
"pre-up": [],
|
||||
"up": []
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
# The loopback network interface
|
||||
auto lo eth0
|
||||
iface lo inet loopback
|
||||
|
||||
# The primary network interface
|
||||
iface eth0 inet dhcp
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue