Source code for nuka.tasks.file

# -*- coding: utf-8 -*-
"""
file related tasks
"""
from nuka.task import Task
from nuka import utils
import logging
import codecs
import stat
import glob
import re
import os


[docs]class exists(Task): """return True if a path exists""" ignore_errors = True def __init__(self, dst=None, **kwargs): kwargs.setdefault('name', dst) kwargs.setdefault('dst', dst) super(exists, self).__init__(**kwargs) def do(self): dst = self.args['dst'] if os.path.exists(dst): return dict(rc=0, dst=dst, changed=False) return dict(rc=1, dst=dst) def diff(self): dst = self.args['dst'] if os.path.exists(dst): return dict(rc=0, dst=dst, diff='', changed=False) diff = self.texts_diff('', dst) return dict(rc=1, dst=dst, diff=diff)
[docs]class mkdir(Task): """create a directory""" def __init__(self, dst=None, mod=None, own=None, **kwargs): kwargs.setdefault('name', dst) kwargs.update(dst=dst, mod=mod, own=None) super(mkdir, self).__init__(**kwargs) def do(self): dst = self.args['dst'] mod = self.args['mod'] own = self.args['mod'] res = utils.makedirs(dst, mod=mod, own=own) res.update(rc=0, dst=dst) return res def diff(self): dst = self.args['dst'] diff = '' if not os.path.exists(dst): res = self.lists_diff([], [dst + '\n'], fromfile=dst) diff = u''.join(res) + u'\n' return dict(rc=0, diff=diff, dst=dst)
[docs]class mkdirs(Task): """create directories""" def __init__(self, directories=None, **kwargs): kwargs.setdefault('name', ', '.join([d['dst'] for d in directories])) super(mkdirs, self).__init__(directories=directories, **kwargs) def do(self): res = dict(rc=0, changed=[]) for d in self.args['directories']: dst = d.pop('dst') r = utils.makedirs(dst, **d) if r['changed']: res['changed'].append(dst) return res def diff(self): res = dict(rc=0, diff='', changed=[]) for d in self.args['directories']: dst = d.pop('dst') r = mkdir(dst, **d).diff() if r['diff']: res['diff'] += r['diff'] res['changed'].append(dst) return res
[docs]class rm(Task): """rm a file or directory""" def __init__(self, dst=None, **kwargs): kwargs.setdefault('name', dst) kwargs.setdefault('dst', dst) super(rm, self).__init__(**kwargs) def do(self): changed = False for dst in glob.glob(self.args['dst']): if os.path.exists(dst): try: if os.path.isdir(dst): import shutil shutil.rmtree(dst) else: os.remove(dst) changed = True except OSError: exc = self.format_exception() return dict(rc=1, exc=exc) return dict(rc=0, changed=changed) def diff(self): diff = '' filenames = [] for dst in glob.glob(self.args['dst']): if os.path.exists(dst): filenames.append(dst + '\n') if filenames: res = self.lists_diff(filenames, [], fromfile=dst) diff = u''.join(res) + u'\n' return dict(rc=0, diff=diff)
[docs]class mv(Task): """rename/move a file or a directory""" def __init__(self, src=None, dst=None, **kwargs): kwargs.setdefault('name', src) super(mv, self).__init__(src=src, dst=dst, **kwargs) def do(self): src = self.args['src'] dst = self.args['dst'] if not os.path.exists(src) and os.path.exists(dst): # Consider the task already done return dict(rc=0, changed=False) try: os.rename(src, dst) except OSError: exc = self.format_exception() return dict(rc=1, exc=exc) return dict(rc=0, changed=True) def diff(self): src = self.args['src'] dst = self.args['dst'] diff = '' if os.path.exists(src): diff += "%s -> %s\n" % (src, dst) return dict(rc=0, diff=diff)
[docs]class chmod(Task): """apply chmod to dst""" diff = False def __init__(self, dst=None, mod='644', recursive=False, **kwargs): kwargs.setdefault('name', dst) super(chmod, self).__init__(dst=dst, mod=mod, recursive=recursive, **kwargs) def do(self): dst = self.args['dst'] mod = self.args['mod'] recursive = self.args['recursive'] utils.chmod(dst, mod, recursive=recursive) return dict(rc=0, dst=dst, mod=mod)
[docs]class chown(Task): """apply chown to dst""" diff = False def __init__(self, dst=None, own='root:root', recursive=False, **kwargs): kwargs.setdefault('name', dst) super(chown, self).__init__(dst=dst, own=own, recursive=recursive, **kwargs) def do(self): dst = self.args['dst'] own = self.args['own'] recursive = self.args['recursive'] utils.chown(dst, own, recursive=recursive) return dict(rc=0, dst=dst, own=own)
[docs]class put(Task): """put files on the remote host""" def __init__(self, files=None, **kwargs): kwargs.setdefault('name', [f['dst'] for f in files or []]) super(put, self).__init__(files=files, **kwargs) def pre_process(self): for fd in self.args['files']: if 'linkto' in fd: fd['data'] = None elif 'src' in fd: src = fd['src'] if src.startswith('~/'): fd['src'] = os.path.expanduser(src) if src.endswith(('.j2', '.j2.gpg')): self.render_template(fd) else: self.render_file(fd) elif 'tpl' in fd: src = fd['src'] = fd['tpl'] if src.startswith('~/'): fd['src'] = os.path.expanduser(src) self.render_template(fd) if 'data' not in fd: raise RuntimeError('cant get content for fd {0}'.format(fd)) def do(self): files = self.args['files'] or [] files_changed = [] for fd in files: dst = fd['dst'] if dst.startswith('~/'): dst = fd['dst'] = os.path.expanduser(dst) if 'linkto' in fd: link = fd['linkto'] if os.path.exists(dst): if os.path.islink(dst): if os.path.realpath(dst) != os.path.realpath(link): os.unlink(dst) else: os.remove(dst) if not os.path.exists(link): self.send_log(( 'Invalid link destination for ' '{0[dst]} -> no such file {0[linkto]}' ).format(fd), level=logging.ERROR) if not os.path.islink(dst): os.symlink(link, dst) files_changed.append(dst) else: data = fd['data'] if os.path.exists(dst): with codecs.open(dst, 'rb', 'utf8') as fd_: if data != fd_.read(): files_changed.append(dst) else: files_changed.append(dst) with codecs.open(dst, 'wb', 'utf8') as fd_: fd_.write(data) mod = fd.get('mod') if mod is None and fd.get('executable'): st = os.stat(dst) mod = ( st.st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH ) if mod is not None: utils.chmod(dst, mod) return dict(rc=0, changed=files_changed) def diff(self): diff = '' files = self.args['files'] or [] files_changed = [] for fd in files: dst = fd['dst'] if 'linkto' in fd: if os.path.exists(fd['linkto']): new_text = '{0[dst]} -> {0[linkto]}\n'.format(fd) else: new_text = ( '{0[dst]} -> no such file {0[linkto]}\n' ).format(fd) self.send_log(( 'Invalid link destination for {0}' ).format(new_text.strip()), level=logging.ERROR) if not os.path.isfile(dst): old_text = '' else: old_text = new_text else: new_text = fd['data'] if not os.path.isfile(dst): old_text = '' else: with codecs.open(dst, 'rb', 'utf8') as fd_: old_text = fd_.read() if old_text != new_text: files_changed.append(dst) diff += self.texts_diff(old_text, new_text, fromfile=dst) return dict(rc=0, diff=diff, changed=files_changed)
[docs]class scripts(put): """put and execute scripts on the remote host""" def do(self): files = self.args['files'] or [] for f in files: f['executable'] = True super(scripts, self).do() for f in files: res = self.sh(f['dst']) return res
[docs]class cat(Task): """cat a file""" def __init__(self, src=None, **kwargs): kwargs.setdefault('name', src) super(cat, self).__init__(src=src, **kwargs) def do(self): src = self.args['src'] with codecs.open(src, 'rb', 'utf8') as fd: res = dict(rc=0, content=fd.read()) return res
[docs]class update(Task): """update a file""" def __init__(self, dst=None, replaces=None, appends=None, **kwargs): kwargs.setdefault('name', dst) super(update, self).__init__( dst=dst, replaces=replaces, appends=appends, **kwargs) def pre_process(self): for regex, value in self.args['replaces']: re.compile(regex) def update(self, data): for regex, value in self.args['replaces'] or []: data = re.sub(regex, value, data, flags=re.MULTILINE) lines = data.splitlines(1) for after, value in self.args['appends'] or []: value += '\n' if value not in lines: if after == 'EOF': lines.append(value) else: for i, line in enumerate(lines): if re.match(after, line): lines[i + 1:i + 1] = [value] return ''.join(lines) def do(self): dst = self.args['dst'] with codecs.open(dst, 'r', 'utf8') as fd: old_data = data = fd.read() data = self.update(data) changed = old_data != data if changed: with codecs.open(dst, 'w', 'utf8') as fd: fd.write(data) return dict(rc=0, changed=changed) def diff(self, dst=None, **kwargs): dst = self.args['dst'] with codecs.open(dst, 'r', 'utf8') as fd: old_data = data = fd.read() data = self.update(data) if old_data != data: diff = self.texts_diff(old_data, data, fromfile=dst) else: diff = u'' return dict(rc=0, diff=diff)