Permalink
Browse files

Merge branch 'master' into 864-int

  • Loading branch information...
bitprophet committed Mar 20, 2013
2 parents 74648eb + 3f75202 commit d0fafa0e9b58693be2b1fbaab6a8f0b9573993f4
Showing with 95 additions and 13 deletions.
  1. +10 −1 .travis.yml
  2. +4 −0 docs/changelog.rst
  3. +19 −11 fabric/contrib/files.py
  4. +4 −0 fabric/task_utils.py
  5. +51 −0 integration/test_contrib.py
  6. +1 −1 tests/test_contrib.py
  7. +6 −0 tests/test_main.py
View
@@ -12,7 +12,16 @@ install:
- "if [[ $TRAVIS_PYTHON_VERSION == '2.5' ]]; then pip install multiprocessing; fi"
# Deal with issue on Travis builders re: multiprocessing.Queue :(
- "sudo rm -rf /dev/shm && sudo ln -s /run/shm /dev/shm"
-script: fab test
+before_script:
+ # Allow us to SSH passwordless to localhost
+ - ssh-keygen -f ~/.ssh/id_rsa -N ""
+ - cp ~/.ssh/{id_rsa.pub,authorized_keys}
+ - "netstat -tan | grep LISTEN"
+script:
+ # Normal tests
+ #- fab test
+ # Integration tests
+ - fab test:"--tests\=integration"
notifications:
irc:
channels: "irc.freenode.org#fabric"
View
@@ -25,6 +25,10 @@ would have also been included in the 1.2 line.
Changelog
=========
+* :bug:`84` Fixed problem with missing -r flag in Mac OS X sed version.
+* :bug:`861` Gracefully handle situations where users give a single string
+ literal to ``env.hosts``. Thanks to Bill Tucker for catch & patch.
+* :bug:`367` Expand paths with tilde inside (``contrib.files``).
* :feature:`845` Downstream synchronization option implemented for
`~fabric.contrib.project.rsync_project`. Thanks to Antonio Barrero for the
patch.
View
@@ -25,7 +25,7 @@ def exists(path, use_sudo=False, verbose=False):
behavior.
"""
func = use_sudo and sudo or run
- cmd = 'test -e "$(echo %s)"' % path
+ cmd = 'test -e %s' % _expand_path(path)
# If verbose, run normally
if verbose:
with settings(warn_only=True):
@@ -81,7 +81,7 @@ def upload_template(filename, destination, context=None, use_jinja=False,
func = use_sudo and sudo or run
# Normalize destination to be an actual filename, due to using StringIO
with settings(hide('everything'), warn_only=True):
- if func('test -d %s' % destination).succeeded:
+ if func('test -d %s' % _expand_path(destination)).succeeded:
sep = "" if destination.endswith('/') else "/"
destination += sep + os.path.basename(filename)
@@ -105,14 +105,14 @@ def upload_template(filename, destination, context=None, use_jinja=False,
tb = traceback.format_exc()
abort(tb + "\nUnable to import Jinja2 -- see above.")
else:
- with open(filename) as inputfile:
+ with open(os.path.expanduser(filename)) as inputfile:
text = inputfile.read()
if context:
text = text % context
# Back up original file
if backup and exists(destination):
- func("cp %s{,.bak}" % destination)
+ func("cp %s{,.bak}" % _expand_path(destination))
# Upload the file.
return put(
@@ -168,6 +168,11 @@ def sed(filename, before, after, limit='', use_sudo=False, backup='.bak',
after = after.replace(char, r'\%s' % char)
if limit:
limit = r'/%s/ ' % limit
+ context = {
+ 'script': r"'%ss/%s/%s/%sg'" % (limit, before, after, flags),
+ 'filename': _expand_path(filename),
+ 'backup': backup
+ }
# Test the OS because of differences between sed versions
with hide('running', 'stdout'):
@@ -177,16 +182,16 @@ def sed(filename, before, after, limit='', use_sudo=False, backup='.bak',
hasher = hashlib.sha1()
hasher.update(env.host_string)
hasher.update(filename)
- tmp = "/tmp/%s" % hasher.hexdigest()
+ context['tmp'] = "/tmp/%s" % hasher.hexdigest()
# Use temp file to work around lack of -i
expr = r"""cp -p %(filename)s %(tmp)s \
-&& sed -r -e '%(limit)ss/%(before)s/%(after)s/%(flags)sg' %(filename)s > %(tmp)s \
+&& sed -r -e %(script)s %(filename)s > %(tmp)s \
&& cp -p %(filename)s %(filename)s%(backup)s \
&& mv %(tmp)s %(filename)s"""
- command = expr % locals()
else:
- expr = r"sed -i%s -r -e '%ss/%s/%s/%sg' %s"
- command = expr % (backup, limit, before, after, flags, filename)
+ context['extended_regex'] = '-E' if platform == 'Darwin' else '-r'
+ expr = r"sed -i%(backup)s %(extended_regex)s -e %(script)s %(filename)s"
+ command = expr % context
return func(command, shell=shell)
@@ -314,7 +319,7 @@ def contains(filename, text, exact=False, use_sudo=False, escape=True,
if exact:
text = "^%s$" % text
with settings(hide('everything'), warn_only=True):
- egrep_cmd = 'egrep "%s" "%s"' % (text, filename)
+ egrep_cmd = 'egrep "%s" %s' % (text, _expand_path(filename))
return func(egrep_cmd, shell=shell).succeeded
@@ -367,7 +372,7 @@ def append(filename, text, use_sudo=False, partial=False, escape=True,
shell=shell)):
continue
line = line.replace("'", r"'\\''") if escape else line
- func("echo '%s' >> %s" % (line, filename))
+ func("echo '%s' >> %s" % (line, _expand_path(filename)))
def _escape_for_regex(text):
"""Escape ``text`` to allow literal matching using egrep"""
@@ -379,3 +384,6 @@ def _escape_for_regex(text):
# Whereas single quotes should not be escaped
regex = regex.replace(r"\'", "'")
return regex
+
+def _expand_path(path):
+ return '"$(echo %s)"' % path
View
@@ -44,6 +44,10 @@ def merge(hosts, roles, exclude, roledefs):
indent(bad_roles)
))
+ # Coerce strings to one-item lists
+ if isinstance(hosts, basestring):
+ hosts = [hosts]
+
# Look up roles, turn into flat list of hosts
role_hosts = []
for role in roles:
@@ -0,0 +1,51 @@
+import types
+
+from fabric.api import env, run, local
+from fabric.contrib import files
+
+
+class Integration(object):
+ def setup(self):
+ env.host_string = "127.0.0.1"
+
+
+def tildify(path):
+ home = run("echo ~", quiet=True).stdout.strip()
+ return path.replace('~', home)
+
+def expect(path):
+ assert files.exists(tildify(path))
+
+def expect_contains(path, value):
+ assert files.contains(tildify(path), value)
+
+def escape(path):
+ return path.replace(' ', r'\ ')
+
+
+class TestTildeExpansion(Integration):
+ def test_append(self):
+ for target in ('~/append_test', '~/append_test with spaces'):
+ files.append(target, ['line'])
+ expect(target)
+
+ def test_exists(self):
+ for target in ('~/exists_test', '~/exists test with space'):
+ run("touch %s" % escape(target))
+ expect(target)
+
+ def test_sed(self):
+ for target in ('~/sed_test', '~/sed test with space'):
+ run("echo 'before' > %s" % escape(target))
+ files.sed(target, 'before', 'after')
+ expect_contains(target, 'after')
+
+ def test_upload_template(self):
+ for i, target in enumerate((
+ '~/upload_template_test',
+ '~/upload template test with space'
+ )):
+ src = "source%s" % i
+ local("touch %s" % src)
+ files.upload_template(src, target)
+ expect(target)
View
@@ -11,7 +11,7 @@ class TestContrib(FabricTest):
# Make sure it knows / is a directory.
# This is in lieu of starting down the "actual honest to god fake operating
# system" road...:(
- @server(responses={'test -d /': ""})
+ @server(responses={'test -d "$(echo /)"': ""})
def test_upload_template_uses_correct_remote_filename(self):
"""
upload_template() shouldn't munge final remote filename
View
@@ -300,6 +300,12 @@ def test_aborts_on_nonexistent_roles():
"""
merge([], ['badrole'], [], {})
+def test_accepts_non_list_hosts():
+ """
+ Aborts if hosts is a string, not a list
+ """
+ assert merge('badhosts', [], [], {}) == ['badhosts']
+
lazy_role = {'r1': lambda: ['a', 'b']}

0 comments on commit d0fafa0

Please sign in to comment.