Created
July 20, 2012 16:00
-
-
Save davehughes/3151551 to your computer and use it in GitHub Desktop.
Implementation of cuisine modes that works by monkey-patching Fabric internals.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' | |
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 _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. | |
""" | |
# 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 _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 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