diff --git a/fabric/contrib/files.py b/fabric/contrib/files.py index a9285c9..0192347 100644 --- a/fabric/contrib/files.py +++ b/fabric/contrib/files.py @@ -32,6 +32,20 @@ def exists(path, use_sudo=False, verbose=False): with settings(hide('everything'), warn_only=True): return not func(cmd).failed +def is_dir(path, use_sudo=False): + """ + Return True if given path exists and is a directory + on the current remote host + + If ``use_sudo`` is True, will use `sudo` instead of `run`. + """ + func = use_sudo and sudo or run + if exists(path, use_sudo): + with settings(hide('everything'), warn_only=True): + # Is destination a directory? + if func('test -f %s' % path).failed: + return True + return False def first(*args, **kwargs): """ @@ -48,7 +62,7 @@ def first(*args, **kwargs): def upload_template(filename, destination, context=None, use_jinja=False, - template_dir=None, use_sudo=False): + template_dir=None, use_sudo=False, backup='.bak'): """ Render and upload a template text file to a remote host. @@ -70,38 +84,56 @@ def upload_template(filename, destination, context=None, use_jinja=False, user; specify ``use_sudo=True`` to use `sudo` instead. """ basename = os.path.basename(filename) - temp_destination = '/tmp/' + basename + text = None + if use_jinja: + try: + from jinja2 import Environment, FileSystemLoader + jenv = Environment(loader=FileSystemLoader(template_dir or '.')) + text = jenv.get_template(filename).render(**context or {}) + except ImportError, e: + abort("tried to use Jinja2 but was unable to import: %s" % e) + else: + with open(filename) as inputfile: + text = inputfile.read() + if context: + text = text % context + put_from_text(text, destination, use_sudo, backup=True) + +def backup(remote_path, use_sudo=False, backup='.bak'): + """ + Return True if given path exists and is a directory + on the current remote host + + If ``use_sudo`` is True, will use `sudo` instead of `run`. + """ + func = use_sudo and sudo or run + if exists(remote_path): + # cp -R is recursive for directory, no differences for file + func("cp -R %s %s%s" % (remote_path, remote_path, backup)) + +def backup_and_put(local_path, remote_path, mode=None, use_sudo=False, + backup='.bak'): + """ + same as put() but backup the content at the other end + + If ``use_sudo`` is True, will use `sudo` instead of `run`. + """ + if os.path.isdir(local_path) and is_dir(remote_path, use_sudo): + to_backup = remote_path + '/' + os.path.basename(local_path) + else: + to_backup = remote_path + backup(to_backup, use_sudo, backup) + put(local_path, remote_path, mode) + +def put_from_text(text, remote_path, mode=None, backup=False): + """ + same as put() but first argument is a string of the content of the file + """ + func = backup and backup_and_put or put with tempfile.NamedTemporaryFile() as output: - # Init - text = None - if use_jinja: - try: - from jinja2 import Environment, FileSystemLoader - jenv = Environment(loader=FileSystemLoader(template_dir or '.')) - text = jenv.get_template(filename).render(**context or {}) - except ImportError, e: - abort("tried to use Jinja2 but was unable to import: %s" % e) - else: - with open(filename) as inputfile: - text = inputfile.read() - if context: - text = text % context output.write(text) output.flush() - put(output.name, temp_destination) - func = use_sudo and sudo or run - # Back up any original file (need to do figure out ultimate destination) - to_backup = destination - with settings(hide('everything'), warn_only=True): - # Is destination a directory? - if func('test -f %s' % to_backup).failed: - # If so, tack on the filename to get "real" destination - to_backup = destination + '/' + basename - if exists(to_backup): - func("cp %s %s.bak" % (to_backup, to_backup)) - # Actually move uploaded template to destination - func("mv %s %s" % (temp_destination, destination)) - + func(output.name, remote_path, mode) def sed(filename, before, after, limit='', use_sudo=False, backup='.bak'): """