summaryrefslogtreecommitdiff
path: root/files
diff options
context:
space:
mode:
authorJakub Jirutka <jakub@jirutka.cz>2015-02-09 01:00:06 +0100
committerJakub Jirutka <jakub@jirutka.cz>2015-02-09 01:00:06 +0100
commitdf0130935198b03bc340b4e193b1485a55a4601a (patch)
tree0d94dfe0922fa6e7ce2d85c3db49dd13c9f127d1 /files
parent85dab364c98cddc74d518967cc1841864668196c (diff)
downloadansible-modules-extras-df0130935198b03bc340b4e193b1485a55a4601a.tar.gz
Add new module "patch"
Diffstat (limited to 'files')
-rw-r--r--files/__init__.py0
-rw-r--r--files/patch.py150
2 files changed, 150 insertions, 0 deletions
diff --git a/files/__init__.py b/files/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/files/__init__.py
diff --git a/files/patch.py b/files/patch.py
new file mode 100644
index 00000000..e2e5467f
--- /dev/null
+++ b/files/patch.py
@@ -0,0 +1,150 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# (c) 2012, Luis Alberto Perez Lazaro <luisperlazaro@gmail.com>
+# (c) 2015, Jakub Jirutka <jakub@jirutka.cz>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: patch
+author: Luis Alberto Perez Lazaro, Jakub Jirutka
+version_added: 1.8
+short_description: Apply patch files using the GNU patch tool.
+options:
+ basedir:
+ description:
+ - Path of a base directory in which the patch file will be applied.
+ May be ommitted when C(dest) option is specified, otherwise required.
+ required: false
+ 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.
+ required: false
+ aliases: [ "originalfile" ]
+ src:
+ description:
+ - Path of the patch file on the remote machine as accepted by the GNU
+ patch tool.
+ required: true
+ aliases: [ "patchfile" ]
+ 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.
+ required: false
+ type: "int"
+ default: "0"
+note:
+ - This module requires GNU I(patch) utility to be installed on the remote host.
+'''
+
+EXAMPLES = '''
+- name: apply patch to one file
+ patch: >
+ src=/tmp/index.html.patch
+ dest=/var/www/index.html
+
+- name: apply patch to multiple files under basedir
+ patch: >
+ src=/tmp/customize.patch
+ basedir=/var/www
+ strip=1
+'''
+
+import os
+from os import path, R_OK, W_OK
+
+
+class PatchError(Exception):
+ pass
+
+
+def is_already_applied(patch_func, patch_file, basedir, dest_file=None, strip=0):
+ opts = ['--quiet', '--reverse', '--forward', '--dry-run',
+ "--strip=%s" % strip, "--directory='%s'" % basedir,
+ "--input='%s'" % patch_file]
+ if dest_file:
+ opts.append("'%s'" % dest_file)
+
+ (rc, _, _) = patch_func(opts)
+ return rc == 0
+
+
+def apply_patch(patch_func, patch_file, basedir, dest_file=None, strip=0, dry_run=False):
+ opts = ['--quiet', '--forward', '--batch', '--reject-file=-',
+ "--strip=%s" % strip, "--directory='%s'" % basedir,
+ "--input='%s'" % patch_file]
+ if dry_run:
+ opts.append('--dry-run')
+ if dest_file:
+ opts.append("'%s'" % dest_file)
+
+ (rc, out, err) = patch_func(opts)
+ if rc != 0:
+ msg = out if not err else err
+ raise PatchError(msg)
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec={
+ 'src': {'required': True, 'aliases': ['patchfile']},
+ 'dest': {'aliases': ['originalfile']},
+ 'basedir': {},
+ 'strip': {'default': 0, 'type': 'int'}
+ },
+ required_one_of=[['dest', 'basedir']],
+ supports_check_mode=True
+ )
+
+ # Create type object as namespace for module params
+ p = type('Params', (), module.params)
+
+ if not os.access(p.src, R_OK):
+ module.fail_json(msg="src %s doesn't exist or not readable" % (p.src))
+
+ if p.dest and not os.access(p.dest, W_OK):
+ module.fail_json(msg="dest %s doesn't exist or not writable" % (d.dest))
+
+ if p.basedir and not path.exists(p.basedir):
+ module.fail_json(msg="basedir %s doesn't exist" % (p.basedir))
+
+ if not p.basedir:
+ p.basedir = path.dirname(p.dest)
+
+ patch_bin = module.get_bin_path('patch')
+ patch_func = lambda opts: module.run_command("%s %s" % (patch_bin, ' '.join(opts)))
+
+ changed = False
+ if not is_already_applied(patch_func, p.src, p.basedir, dest_file=p.dest, strip=p.strip):
+ try:
+ apply_patch(patch_func, p.src, p.basedir, dest_file=p.dest, strip=p.strip,
+ dry_run=module.check_mode)
+ changed = True
+ except PatchError, e:
+ module.fail_json(msg=str(e))
+
+ module.exit_json(changed=changed)
+
+# import module snippets
+from ansible.module_utils.basic import *
+main()