Skip to content

Instantly share code, notes, and snippets.

@davehughes
Created July 20, 2012 15:33
Show Gist options
  • Save davehughes/3151356 to your computer and use it in GitHub Desktop.
Save davehughes/3151356 to your computer and use it in GitHub Desktop.
Implementation of cuisine modes that works by monkey-patching Fabric internals.
'''
Stack-based mode managers for mode_(sudo|user|local|remote).
'''
from fabric.api import env
MODE_STACKS = {}
env.cuisine_mode_sudo = False
env.cuisine_mode_local = False
class ModeStackManager(object):
value = None
default = None
def __init__( self ):
setattr(env, self.attribute, self.value)
print 'Set env attr %s = %s' % (self.attribute, self.value)
def __enter__( self ):
stack = MODE_STACKS.setdefault(self.attribute, [])
stack.append(getattr(fabric.api.env, self.attribute))
print 'Push env attr %s = %s' % (self.attribute, self.value)
def __exit__( self, type, value, traceback ):
stack = MODE_STACKS.setdefault(self.attribute, [])
if len(stack) > 0:
stack.pop()
value = stack[-1] if len(stack) > 0 else self.default
setattr(fabric.api.env, self.attribute, value)
print 'Pop env attr %s = %s' % (self.attribute, self.value)
class mode_local(ModeStackManager):
"""Sets Cuisine into local mode, where run/sudo won't go through
Fabric's API, but directly through a popen. This allows you to
easily test your Cuisine scripts without using Fabric."""
attribute = 'cuisine_mode_local'
value = True
default = False
class mode_remote(ModeStackManager):
attribute = 'cuisine_mode_local'
value = False
default = False
class mode_sudo(ModeStackManager):
attribute = 'cuisine_mode_sudo'
value = True
default = False
class mode_user(ModeStackManager):
attribute = 'cuisine_mode_sudo'
value = False
default = False
'''
Patch for _run_command.
'''
from fabric.state import output
from fabric.operations import (_shell_wrap, _prefix_commands, _prefix_env_vars,
_sudo_prefix, _execute, _AttributeString, default_channel, error, _run_command
)
def _execute_local(command, shell=True, combine_stderr=None):
'''
Local implementation of fabric.operations._execute using subprocess.
'''
if combine_stderr is None: combine_stderr = fabric.env.combine_stderr
stderr = subprocess.STDOUT if combine_stderr else subprocess.PIPE
process = subprocess.Popen(command, shell=shell, stdout=subprocess.PIPE, stderr=stderr)
out, err = process.communicate()
# FIXME: Should stream the output, and only print it if fabric's properties allow it
return out.rstrip('\n'), err, process.returncode
def _run_command(command, shell=True, pty=True, combine_stderr=True,
sudo=False, user=None):
"""
Underpinnings of `run` and `sudo`. See their docstrings for more info.
Patched here to understand cuisine mode flags. See fabric.operations._run_command
for the original implementation.
"""
# Set up new var so original argument can be displayed verbatim later.
given_command = command
# Allow local 'sudo' param to override cuisine mode, but apply cuisine mode if nothing
# is specified.
sudo = bool(sudo or getattr(env, 'cuisine_mode_sudo', None))
# Handle context manager modifications, and shell wrapping
wrapped_command = _shell_wrap(
_prefix_commands(_prefix_env_vars(command), 'remote'),
shell,
_sudo_prefix(user) if sudo else None
)
# Execute info line
which = 'sudo' if sudo else 'run'
if output.debug:
print("[%s] %s: %s" % (env.host_string, which, wrapped_command))
elif output.running:
print("[%s] %s: %s" % (env.host_string, which, given_command))
# Actual execution, stdin/stdout/stderr handling, and termination
if getattr(env, 'cuisine_mode_local', None):
stdout, stderr, status = _execute_local(wrapped_command,
shell=shell, combine_stderr=combine_stderr)
else:
stdout, stderr, status = _execute(default_channel(), wrapped_command, pty,
combine_stderr)
# Assemble output string
out = _AttributeString(stdout)
err = _AttributeString(stderr)
# Error handling
out.failed = False
if status != 0:
out.failed = True
msg = "%s() received nonzero return code %s while executing" % (
which, status
)
if env.warn_only:
msg += " '%s'!" % given_command
else:
msg += "!\n\nRequested: %s\nExecuted: %s" % (
given_command, wrapped_command
)
error(message=msg, stdout=out, stderr=err)
# Attach return code to output string so users who have set things to
# warn only, can inspect the error code.
out.return_code = status
# Convenience mirror of .failed
out.succeeded = not out.failed
# Attach stderr for anyone interested in that.
out.stderr = err
return out
def monkey_patch():
import fabric.operations
fabric.operations._run_command = _run_command
monkey_patch()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment