2015-02-09 00:00:06 +00:00
|
|
|
#!/usr/bin/python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2017-08-03 03:25:57 +00:00
|
|
|
# Copyright: (c) 2012, Luis Alberto Perez Lazaro <luisperlazaro@gmail.com>
|
|
|
|
# Copyright: (c) 2015, Jakub Jirutka <jakub@jirutka.cz>
|
|
|
|
# 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
|
2015-02-09 00:00:06 +00:00
|
|
|
|
2017-08-16 03:16:38 +00:00
|
|
|
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
2017-03-14 16:07:22 +00:00
|
|
|
'status': ['stableinterface'],
|
|
|
|
'supported_by': 'community'}
|
|
|
|
|
2017-06-02 07:42:40 +00:00
|
|
|
DOCUMENTATION = r'''
|
2015-02-09 00:00:06 +00:00
|
|
|
---
|
|
|
|
module: patch
|
2015-05-14 14:45:21 +00:00
|
|
|
author:
|
2017-06-02 07:42:40 +00:00
|
|
|
- Jakub Jirutka (@jirutka)
|
|
|
|
- Luis Alberto Perez Lazaro (@luisperlaz)
|
|
|
|
version_added: '1.9'
|
2015-02-09 21:27:03 +00:00
|
|
|
description:
|
|
|
|
- Apply patch files using the GNU patch tool.
|
2017-06-02 07:42:40 +00:00
|
|
|
short_description: Apply patch files using the GNU patch tool
|
2015-02-09 00:00:06 +00:00
|
|
|
options:
|
|
|
|
basedir:
|
|
|
|
description:
|
|
|
|
- Path of a base directory in which the patch file will be applied.
|
2016-12-11 02:50:09 +00:00
|
|
|
May be omitted when C(dest) option is specified, otherwise required.
|
2015-02-09 00:00:06 +00:00
|
|
|
dest:
|
|
|
|
description:
|
|
|
|
- Path of the file on the remote machine to be patched.
|
|
|
|
- The names of the files to be patched are usually taken from the patch
|
|
|
|
file, but if there's just one file to be patched it can specified with
|
|
|
|
this option.
|
2017-06-02 07:42:40 +00:00
|
|
|
aliases: [ originalfile ]
|
2015-02-09 00:00:06 +00:00
|
|
|
src:
|
|
|
|
description:
|
2015-03-20 16:36:33 +00:00
|
|
|
- Path of the patch file as accepted by the GNU patch tool. If
|
Update 'patch' documentation for src, remote_src, backup and binary.
The patch module has a few missing items, and inconsistencies, in its
documentation. A few of which are addressed here.
Within Ansible documentation, the choices for boolean values are
commonly 'yes', and 'no'. We standardise the options on that.
'remote_src' documentation uses 'False' and 'True' for its documentation,
so these have been updated in both the choices and default.
'src' documentation refers to 'remote_src', so is updated to use
the 'no' choice.
'backup' did not describe its options and default at all, so we add
them.
'binary' default used 'False', but specified the type as 'bool' which is
implicitly documented as 'yes'/'no', so we make that 'no' as well.
2015-11-27 00:24:08 +00:00
|
|
|
C(remote_src) is 'no', the patch source file is looked up from the
|
2017-06-02 07:42:40 +00:00
|
|
|
module's I(files) directory.
|
2015-02-09 00:00:06 +00:00
|
|
|
required: true
|
2017-06-02 07:42:40 +00:00
|
|
|
aliases: [ patchfile ]
|
2015-02-09 21:27:03 +00:00
|
|
|
remote_src:
|
|
|
|
description:
|
Update 'patch' documentation for src, remote_src, backup and binary.
The patch module has a few missing items, and inconsistencies, in its
documentation. A few of which are addressed here.
Within Ansible documentation, the choices for boolean values are
commonly 'yes', and 'no'. We standardise the options on that.
'remote_src' documentation uses 'False' and 'True' for its documentation,
so these have been updated in both the choices and default.
'src' documentation refers to 'remote_src', so is updated to use
the 'no' choice.
'backup' did not describe its options and default at all, so we add
them.
'binary' default used 'False', but specified the type as 'bool' which is
implicitly documented as 'yes'/'no', so we make that 'no' as well.
2015-11-27 00:24:08 +00:00
|
|
|
- If C(no), it will search for src at originating/master machine, if C(yes) it will
|
2017-06-02 07:42:40 +00:00
|
|
|
go to the remote/target machine for the C(src).
|
|
|
|
choices: [ 'no', 'yes' ]
|
|
|
|
default: 'no'
|
2015-02-09 00:00:06 +00:00
|
|
|
strip:
|
|
|
|
description:
|
|
|
|
- Number that indicates the smallest prefix containing leading slashes
|
|
|
|
that will be stripped from each file name found in the patch file.
|
|
|
|
For more information see the strip parameter of the GNU patch tool.
|
2017-06-02 07:42:40 +00:00
|
|
|
default: 0
|
2015-06-02 03:32:22 +00:00
|
|
|
backup:
|
2015-06-02 12:48:20 +00:00
|
|
|
version_added: "2.0"
|
2015-05-30 13:01:52 +00:00
|
|
|
description:
|
2017-06-02 07:42:40 +00:00
|
|
|
- Passes C(--backup --version-control=numbered) to patch,
|
|
|
|
producing numbered backup copies.
|
|
|
|
choices: [ 'no', 'yes' ]
|
Update 'patch' documentation for src, remote_src, backup and binary.
The patch module has a few missing items, and inconsistencies, in its
documentation. A few of which are addressed here.
Within Ansible documentation, the choices for boolean values are
commonly 'yes', and 'no'. We standardise the options on that.
'remote_src' documentation uses 'False' and 'True' for its documentation,
so these have been updated in both the choices and default.
'src' documentation refers to 'remote_src', so is updated to use
the 'no' choice.
'backup' did not describe its options and default at all, so we add
them.
'binary' default used 'False', but specified the type as 'bool' which is
implicitly documented as 'yes'/'no', so we make that 'no' as well.
2015-11-27 00:24:08 +00:00
|
|
|
default: 'no'
|
2015-03-25 15:14:13 +00:00
|
|
|
binary:
|
|
|
|
version_added: "2.0"
|
|
|
|
description:
|
Update 'patch' documentation for src, remote_src, backup and binary.
The patch module has a few missing items, and inconsistencies, in its
documentation. A few of which are addressed here.
Within Ansible documentation, the choices for boolean values are
commonly 'yes', and 'no'. We standardise the options on that.
'remote_src' documentation uses 'False' and 'True' for its documentation,
so these have been updated in both the choices and default.
'src' documentation refers to 'remote_src', so is updated to use
the 'no' choice.
'backup' did not describe its options and default at all, so we add
them.
'binary' default used 'False', but specified the type as 'bool' which is
implicitly documented as 'yes'/'no', so we make that 'no' as well.
2015-11-27 00:24:08 +00:00
|
|
|
- Setting to C(yes) will disable patch's heuristic for transforming CRLF
|
2015-03-25 15:14:13 +00:00
|
|
|
line endings into LF. Line endings of src and dest must match. If set to
|
2017-06-02 07:42:40 +00:00
|
|
|
C(no), C(patch) will replace CRLF in C(src) files on POSIX.
|
|
|
|
choices: [ 'no', 'yes' ]
|
|
|
|
default: 'no'
|
2017-03-09 16:20:25 +00:00
|
|
|
notes:
|
2015-02-09 00:00:06 +00:00
|
|
|
- This module requires GNU I(patch) utility to be installed on the remote host.
|
|
|
|
'''
|
|
|
|
|
2017-06-02 07:42:40 +00:00
|
|
|
EXAMPLES = r'''
|
|
|
|
- name: Apply patch to one file
|
2017-01-19 17:13:09 +00:00
|
|
|
patch:
|
|
|
|
src: /tmp/index.html.patch
|
|
|
|
dest: /var/www/index.html
|
2015-02-09 00:00:06 +00:00
|
|
|
|
2017-06-02 07:42:40 +00:00
|
|
|
- name: Apply patch to multiple files under basedir
|
2017-01-19 17:13:09 +00:00
|
|
|
patch:
|
|
|
|
src: /tmp/customize.patch
|
|
|
|
basedir: /var/www
|
|
|
|
strip: 1
|
2015-02-09 00:00:06 +00:00
|
|
|
'''
|
|
|
|
|
|
|
|
import os
|
2017-08-03 03:25:57 +00:00
|
|
|
from traceback import format_exc
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
|
|
from ansible.module_utils._text import to_native
|
2015-02-09 00:00:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
class PatchError(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2015-03-25 15:14:13 +00:00
|
|
|
def is_already_applied(patch_func, patch_file, basedir, dest_file=None, binary=False, strip=0):
|
2015-02-09 00:00:06 +00:00
|
|
|
opts = ['--quiet', '--reverse', '--forward', '--dry-run',
|
|
|
|
"--strip=%s" % strip, "--directory='%s'" % basedir,
|
|
|
|
"--input='%s'" % patch_file]
|
2015-03-25 15:14:13 +00:00
|
|
|
if binary:
|
|
|
|
opts.append('--binary')
|
2015-02-09 00:00:06 +00:00
|
|
|
if dest_file:
|
|
|
|
opts.append("'%s'" % dest_file)
|
|
|
|
|
|
|
|
(rc, _, _) = patch_func(opts)
|
|
|
|
return rc == 0
|
|
|
|
|
|
|
|
|
2015-03-25 15:14:13 +00:00
|
|
|
def apply_patch(patch_func, patch_file, basedir, dest_file=None, binary=False, strip=0, dry_run=False, backup=False):
|
2015-02-09 00:00:06 +00:00
|
|
|
opts = ['--quiet', '--forward', '--batch', '--reject-file=-',
|
|
|
|
"--strip=%s" % strip, "--directory='%s'" % basedir,
|
|
|
|
"--input='%s'" % patch_file]
|
|
|
|
if dry_run:
|
|
|
|
opts.append('--dry-run')
|
2015-03-25 15:14:13 +00:00
|
|
|
if binary:
|
|
|
|
opts.append('--binary')
|
2015-02-09 00:00:06 +00:00
|
|
|
if dest_file:
|
|
|
|
opts.append("'%s'" % dest_file)
|
2015-05-30 13:01:52 +00:00
|
|
|
if backup:
|
|
|
|
opts.append('--backup --version-control=numbered')
|
2015-02-09 00:00:06 +00:00
|
|
|
|
|
|
|
(rc, out, err) = patch_func(opts)
|
|
|
|
if rc != 0:
|
2015-05-08 21:36:15 +00:00
|
|
|
msg = err or out
|
2015-02-09 00:00:06 +00:00
|
|
|
raise PatchError(msg)
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
module = AnsibleModule(
|
2017-06-02 07:42:40 +00:00
|
|
|
argument_spec=dict(
|
|
|
|
src=dict(type='path', required=True, aliases=['patchfile']),
|
|
|
|
dest=dict(type='path', aliases=['originalfile']),
|
|
|
|
basedir=dict(type='path'),
|
|
|
|
strip=dict(type='int', default=0),
|
|
|
|
remote_src=dict(type='bool', default=False),
|
2015-06-02 03:32:22 +00:00
|
|
|
# NB: for 'backup' parameter, semantics is slightly different from standard
|
|
|
|
# since patch will create numbered copies, not strftime("%Y-%m-%d@%H:%M:%S~")
|
2017-06-02 07:42:40 +00:00
|
|
|
backup=dict(type='bool', default=False),
|
|
|
|
binary=dict(type='bool', default=False),
|
|
|
|
),
|
2015-02-09 00:00:06 +00:00
|
|
|
required_one_of=[['dest', 'basedir']],
|
2017-06-02 07:42:40 +00:00
|
|
|
supports_check_mode=True,
|
2015-02-09 00:00:06 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
# Create type object as namespace for module params
|
|
|
|
p = type('Params', (), module.params)
|
|
|
|
|
2017-06-02 07:42:40 +00:00
|
|
|
if not os.access(p.src, os.R_OK):
|
2015-02-09 00:00:06 +00:00
|
|
|
module.fail_json(msg="src %s doesn't exist or not readable" % (p.src))
|
|
|
|
|
2017-06-02 07:42:40 +00:00
|
|
|
if p.dest and not os.access(p.dest, os.W_OK):
|
2015-02-28 03:07:47 +00:00
|
|
|
module.fail_json(msg="dest %s doesn't exist or not writable" % (p.dest))
|
2015-02-09 00:00:06 +00:00
|
|
|
|
2017-06-02 07:42:40 +00:00
|
|
|
if p.basedir and not os.path.exists(p.basedir):
|
2015-02-09 00:00:06 +00:00
|
|
|
module.fail_json(msg="basedir %s doesn't exist" % (p.basedir))
|
|
|
|
|
|
|
|
if not p.basedir:
|
2017-06-02 07:42:40 +00:00
|
|
|
p.basedir = os.path.dirname(p.dest)
|
2015-02-09 00:00:06 +00:00
|
|
|
|
|
|
|
patch_bin = module.get_bin_path('patch')
|
2015-03-20 16:17:16 +00:00
|
|
|
if patch_bin is None:
|
|
|
|
module.fail_json(msg="patch command not found")
|
2017-06-02 07:42:40 +00:00
|
|
|
|
|
|
|
def patch_func(opts):
|
|
|
|
return module.run_command('%s %s' % (patch_bin, ' '.join(opts)))
|
2015-02-09 00:00:06 +00:00
|
|
|
|
2015-03-20 16:19:15 +00:00
|
|
|
# patch need an absolute file name
|
|
|
|
p.src = os.path.abspath(p.src)
|
2017-01-27 23:45:23 +00:00
|
|
|
|
2015-02-09 00:00:06 +00:00
|
|
|
changed = False
|
2015-03-25 15:14:13 +00:00
|
|
|
if not is_already_applied(patch_func, p.src, p.basedir, dest_file=p.dest, binary=p.binary, strip=p.strip):
|
2015-02-09 00:00:06 +00:00
|
|
|
try:
|
2017-06-02 07:42:40 +00:00
|
|
|
apply_patch(patch_func, p.src, p.basedir, dest_file=p.dest, binary=p.binary, strip=p.strip,
|
|
|
|
dry_run=module.check_mode, backup=p.backup)
|
2015-02-09 00:00:06 +00:00
|
|
|
changed = True
|
2017-08-03 03:25:57 +00:00
|
|
|
except PatchError as e:
|
|
|
|
module.fail_json(msg=to_native(e), exception=format_exc())
|
2015-02-09 00:00:06 +00:00
|
|
|
|
|
|
|
module.exit_json(changed=changed)
|
|
|
|
|
2016-12-05 16:20:10 +00:00
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|