def _yield_result(self, attname, command): try: obj = getattr(self, attname) # Dicts need to try direct lookup or regex matching if isinstance(obj, dict): try: obj = obj[command] except KeyError: # TODO: could optimize by skipping this if not any regex # objects in keys()? for key, value in iteritems(obj): if hasattr(key, "match") and key.match(command): obj = value break else: # Nope, nothing did match. raise KeyError # Here, the value was either never a dict or has been extracted # from one, so we can assume it's an iterable of Result objects due # to work done by __init__. result = next(obj) # Populate Result's command string with what matched unless # explicitly given if not result.command: result.command = command return result except (AttributeError, IndexError, KeyError, StopIteration): raise_from(NotImplementedError(command), None)
def _sudo(self, runner, command, **kwargs): prompt = self.config.sudo.prompt password = kwargs.pop("password", self.config.sudo.password) user = kwargs.pop("user", self.config.sudo.user) # TODO: allow subclassing for 'get the password' so users who REALLY # want lazy runtime prompting can have it easily implemented. # TODO: want to print a "cleaner" echo with just 'sudo <command>'; but # hard to do as-is, obtaining config data from outside a Runner one # holds is currently messy (could fix that), if instead we manually # inspect the config ourselves that duplicates logic. NOTE: once we # figure that out, there is an existing, would-fail-if-not-skipped test # for this behavior in test/context.py. # TODO: once that is done, though: how to handle "full debug" output # exactly (display of actual, real full sudo command w/ -S and -p), in # terms of API/config? Impl is easy, just go back to passing echo # through to 'run'... user_flags = "" if user is not None: user_flags = "-H -u {} ".format(user) command = self._prefix_commands(command) cmd_str = "sudo -S -p '{}' {}{}".format(prompt, user_flags, command) watcher = FailingResponder( pattern=re.escape(prompt), response="{}\n".format(password), sentinel="Sorry, try again.\n", ) # Ensure we merge any user-specified watchers with our own. # NOTE: If there are config-driven watchers, we pull those up to the # kwarg level; that lets us merge cleanly without needing complex # config-driven "override vs merge" semantics. # TODO: if/when those semantics are implemented, use them instead. # NOTE: config value for watchers defaults to an empty list; and we # want to clone it to avoid actually mutating the config. watchers = kwargs.pop("watchers", list(self.config.run.watchers)) watchers.append(watcher) try: return runner.run(cmd_str, watchers=watchers, **kwargs) except Failure as failure: # Transmute failures driven by our FailingResponder, into auth # failures - the command never even ran. # TODO: wants to be a hook here for users that desire "override a # bad config value for sudo.password" manual input # NOTE: as noted in #294 comments, we MAY in future want to update # this so run() is given ability to raise AuthFailure on its own. # For now that has been judged unnecessary complexity. if isinstance(failure.reason, ResponseNotAccepted): # NOTE: not bothering with 'reason' here, it's pointless. # NOTE: using raise_from(..., None) to suppress Python 3's # "helpful" multi-exception output. It's confusing here. error = AuthFailure(result=failure.result, prompt=prompt) raise_from(error, None) # Reraise for any other error so it bubbles up normally. else: raise
def _sudo(self, runner, command, **kwargs): prompt = self.config.sudo.prompt password = kwargs.pop('password', self.config.sudo.password) user = kwargs.pop('user', self.config.sudo.user) # TODO: allow subclassing for 'get the password' so users who REALLY # want lazy runtime prompting can have it easily implemented. # TODO: want to print a "cleaner" echo with just 'sudo <command>'; but # hard to do as-is, obtaining config data from outside a Runner one # holds is currently messy (could fix that), if instead we manually # inspect the config ourselves that duplicates logic. NOTE: once we # figure that out, there is an existing, would-fail-if-not-skipped test # for this behavior in test/context.py. # TODO: once that is done, though: how to handle "full debug" output # exactly (display of actual, real full sudo command w/ -S and -p), in # terms of API/config? Impl is easy, just go back to passing echo # through to 'run'... user_flags = "" if user is not None: user_flags = "-H -u {} ".format(user) command = self._prefix_commands(command) cmd_str = "sudo -S -p '{}' {}{}".format(prompt, user_flags, command) watcher = FailingResponder( pattern=re.escape(prompt), response="{}\n".format(password), sentinel="Sorry, try again.\n", ) # Ensure we merge any user-specified watchers with our own. # NOTE: If there are config-driven watchers, we pull those up to the # kwarg level; that lets us merge cleanly without needing complex # config-driven "override vs merge" semantics. # TODO: if/when those semantics are implemented, use them instead. # NOTE: config value for watchers defaults to an empty list; and we # want to clone it to avoid actually mutating the config. watchers = kwargs.pop('watchers', list(self.config.run.watchers)) watchers.append(watcher) try: return runner.run(cmd_str, watchers=watchers, **kwargs) except Failure as failure: # Transmute failures driven by our FailingResponder, into auth # failures - the command never even ran. # TODO: wants to be a hook here for users that desire "override a # bad config value for sudo.password" manual input # NOTE: as noted in #294 comments, we MAY in future want to update # this so run() is given ability to raise AuthFailure on its own. # For now that has been judged unnecessary complexity. if isinstance(failure.reason, ResponseNotAccepted): # NOTE: not bothering with 'reason' here, it's pointless. # NOTE: using raise_from(..., None) to suppress Python 3's # "helpful" multi-exception output. It's confusing here. error = AuthFailure(result=failure.result, prompt=prompt) raise_from(error, None) # Reraise for any other error so it bubbles up normally. else: raise
def _yield_result(self, attname, command): # NOTE: originally had this with a bunch of explicit # NotImplementedErrors, but it doubled method size, and chance of # unexpected index/etc errors seems low here. try: value = getattr(self, attname) # TODO: thought there's a 'better' 2x3 DictType or w/e, but can't # find one offhand if isinstance(value, dict): if hasattr(value[command], "__iter__"): result = value[command].pop(0) elif isinstance(value[command], Result): result = value.pop(command) elif hasattr(value, "__iter__"): result = value.pop(0) elif isinstance(value, Result): result = value delattr(self, attname) return result except (AttributeError, IndexError, KeyError): raise_from(NotImplementedError, None)
def sudo(self, command, **kwargs): """ Execute a shell command, via ``sudo``. In general, this method is identical to `run`, but adds a handful of convenient behaviors around invoking the ``sudo`` program. It doesn't do anything users could not do themselves by wrapping `run`, but the use case is too common to make users reinvent these wheels themselves. Specifically, `sudo`: * Places a `.FailingResponder` into the ``watchers`` kwarg (see :doc:`/concepts/watchers`) which: * searches for the configured ``sudo`` password prompt; * responds with the configured sudo password (``sudo.password`` from the :doc:`configuration </concepts/configuration>`, or a runtime `getpass <getpass.getpass>` input); * can tell when that response causes an authentication failure, and raises an exception if so. * Builds a ``sudo`` command string using the supplied ``command`` argument prefixed by the ``sudo.prefix`` configuration setting; * Executes that command via a call to `run`, returning the result. As with `run`, these additional behaviors may be configured both via the ``run`` tree of configuration settings (like ``run.echo``) or via keyword arguments, which will override the configuration system. :param str password: Runtime override for ``sudo.password``. :param str prefix: Runtime override for ``sudo.prefix``. """ prompt = self.config.sudo.prompt password = kwargs.pop('password', self.config.sudo.password) if password is None: msg = "No stored sudo password found, please enter it now: " # TODO: use something generic/overrideable that uses getpass by # default. May mean we pop this out as its own class-as-a-method or # something? password = getpass.getpass(msg) # TODO: want to print a "cleaner" echo with just 'sudo <command>'; but # hard to do as-is, obtaining config data from outside a Runner one # holds is currently messy (could fix that), if instead we manually # inspect the config ourselves that duplicates logic. NOTE: once we # figure that out, there is an existing, would-fail-if-not-skipped test # for this behavior in test/context.py. # TODO: once that is done, though: how to handle "full debug" output # exactly (display of actual, real full sudo command w/ -S and -p), in # terms of API/config? Impl is easy, just go back to passing echo # through to 'run'... cmd_str = "sudo -S -p '{0}' {1}".format(prompt, command) watcher = FailingResponder( pattern=re.escape(prompt), response="{0}\n".format(password), sentinel="Sorry, try again.\n", ) # Ensure we merge any user-specified watchers with our own. # NOTE: If there are config-driven watchers, we pull those up to the # kwarg level; that lets us merge cleanly without needing complex # config-driven "override vs merge" semantics. # TODO: if/when those semantics are implemented, use them instead. # NOTE: config value for watchers defaults to an empty list; and we # want to clone it to avoid actually mutating the config. watchers = kwargs.pop('watchers', list(self.config.run.watchers)) watchers.append(watcher) try: return self.run(cmd_str, watchers=watchers, **kwargs) except Failure as failure: # Transmute failures driven by our FailingResponder, into auth # failures - the command never even ran. # TODO: wants to be a hook here for users that desire "override a # bad config value for sudo.password" manual input # NOTE: as noted in #294 comments, we MAY in future want to update # this so run() is given ability to raise AuthFailure on its own. # For now that has been judged unnecessary complexity. if isinstance(failure.reason, ResponseNotAccepted): # NOTE: not bothering with 'reason' here, it's pointless. # NOTE: using raise_from(..., None) to suppress Python 3's # "helpful" multi-exception output. It's confusing here. error = AuthFailure(result=failure.result, prompt=prompt) raise_from(error, None) # Reraise for any other error so it bubbles up normally. else: raise
def sudo(self, command, **kwargs): """ Execute a shell command, via ``sudo``. In general, this method is identical to `run`, but adds a handful of convenient behaviors around invoking the ``sudo`` program. It doesn't do anything users could not do themselves by wrapping `run`, but the use case is too common to make users reinvent these wheels themselves. Specifically, `sudo`: * Places a `.FailingResponder` into the ``watchers`` kwarg (see :doc:`/concepts/watchers`) which: * searches for the configured ``sudo`` password prompt; * responds with the configured sudo password (``sudo.password`` from the :doc:`configuration </concepts/configuration>`, or a runtime `getpass <getpass.getpass>` input); * can tell when that response causes an authentication failure, and raises an exception if so. * Builds a ``sudo`` command string using the supplied ``command`` argument prefixed by the ``sudo.prefix`` configuration setting; * Executes that command via a call to `run`, returning the result. As with `run`, these additional behaviors may be configured both via the ``run`` tree of configuration settings (like ``run.echo``) or via keyword arguments, which will override the configuration system. :param str password: Runtime override for ``sudo.password``. :param str prefix: Runtime override for ``sudo.prefix``. """ prompt = self.config.sudo.prompt password = kwargs.pop("password", self.config.sudo.password) if password is None: msg = "No stored sudo password found, please enter it now: " # TODO: use something generic/overrideable that uses getpass by # default. May mean we pop this out as its own class-as-a-method or # something? password = getpass.getpass(msg) # TODO: want to print a "cleaner" echo with just 'sudo <command>'; but # hard to do as-is, obtaining config data from outside a Runner one # holds is currently messy (could fix that), if instead we manually # inspect the config ourselves that duplicates logic. NOTE: once we # figure that out, there is an existing, would-fail-if-not-skipped test # for this behavior in test/context.py. # TODO: once that is done, though: how to handle "full debug" output # exactly (display of actual, real full sudo command w/ -S and -p), in # terms of API/config? Impl is easy, just go back to passing echo # through to 'run'... cmd_str = "sudo -S -p '{0}' {1}".format(prompt, command) watcher = FailingResponder( pattern=re.escape(prompt), response="{0}\n".format(password), sentinel="Sorry, try again.\n" ) # Ensure we merge any user-specified watchers with our own. # NOTE: If there are config-driven watchers, we pull those up to the # kwarg level; that lets us merge cleanly without needing complex # config-driven "override vs merge" semantics. # TODO: if/when those semantics are implemented, use them instead. # NOTE: config value for watchers defaults to an empty list; and we # want to clone it to avoid actually mutating the config. watchers = kwargs.pop("watchers", list(self.config.run.watchers)) watchers.append(watcher) try: return self.run(cmd_str, watchers=watchers, **kwargs) except Failure as failure: # Transmute failures driven by our FailingResponder, into auth # failures - the command never even ran. # TODO: wants to be a hook here for users that desire "override a # bad config value for sudo.password" manual input # NOTE: as noted in #294 comments, we MAY in future want to update # this so run() is given ability to raise AuthFailure on its own. # For now that has been judged unnecessary complexity. if isinstance(failure.reason, ResponseNotAccepted): # NOTE: not bothering with 'reason' here, it's pointless. # NOTE: using raise_from(..., None) to suppress Python 3's # "helpful" multi-exception output. It's confusing here. error = AuthFailure(result=failure.result, prompt=prompt) raise_from(error, None) # Reraise for any other error so it bubbles up normally. else: raise
def sudo(self, command, **kwargs): """ Execute a shell command, via ``sudo``. **Basics** In general, this method is identical to `run`, but adds a handful of convenient behaviors around invoking the ``sudo`` program. It doesn't do anything users could not do themselves by wrapping `run`, but the use case is too common to make users reinvent these wheels themselves. Specifically, `sudo`: * Places a `.FailingResponder` into the ``watchers`` kwarg (see :doc:`/concepts/watchers`) which: * searches for the configured ``sudo`` password prompt; * responds with the configured sudo password (``sudo.password`` from the :doc:`configuration </concepts/configuration>`); * can tell when that response causes an authentication failure (e.g. if the system requires a password and one was not configured), and raises `.AuthFailure` if so. * Builds a ``sudo`` command string using the supplied ``command`` argument, prefixed by various flags (see below); * Executes that command via a call to `run`, returning the result. **Flags used** ``sudo`` flags used under the hood include: - ``-S`` to allow auto-responding of password via stdin; - ``-p <prompt>`` to explicitly state the prompt to use, so we can be sure our auto-responder knows what to look for; - ``-u <user>`` if ``user`` is not ``None``, to execute the command as a user other than ``root``; - When ``-u`` is present, ``-H`` is also added, to ensure the subprocess has the requested user's ``$HOME`` set properly. **Configuring behavior** There are a couple of ways to change how this method behaves: - Because it wraps `run`, it honors all `run` config parameters and keyword arguments, in the same way that `run` does. - Thus, invocations such as ``c.sudo('command', echo=True)`` are possible, and if a config layer (such as a config file or env var) specifies that e.g. ``run.warn = True``, that too will take effect under `sudo`. - `sudo` has its own set of keyword arguments (see below) and they are also all controllable via the configuration system, under the ``sudo.*`` tree. - Thus you could, for example, pre-set a sudo user in a config file; such as an ``invoke.json`` containing ``{"sudo": {"user": "******"}}``. :param str password: Runtime override for ``sudo.password``. :param str user: Runtime override for ``sudo.user``. """ prompt = self.config.sudo.prompt password = kwargs.pop('password', self.config.sudo.password) user = kwargs.pop('user', self.config.sudo.user) # TODO: allow subclassing for 'get the password' so users who REALLY # want lazy runtime prompting can have it easily implemented. # TODO: want to print a "cleaner" echo with just 'sudo <command>'; but # hard to do as-is, obtaining config data from outside a Runner one # holds is currently messy (could fix that), if instead we manually # inspect the config ourselves that duplicates logic. NOTE: once we # figure that out, there is an existing, would-fail-if-not-skipped test # for this behavior in test/context.py. # TODO: once that is done, though: how to handle "full debug" output # exactly (display of actual, real full sudo command w/ -S and -p), in # terms of API/config? Impl is easy, just go back to passing echo # through to 'run'... user_flags = "" if user is not None: user_flags = "-H -u {0} ".format(user) cmd_str = "sudo -S -p '{0}' {1}{2}".format(prompt, user_flags, command) watcher = FailingResponder( pattern=re.escape(prompt), response="{0}\n".format(password), sentinel="Sorry, try again.\n", ) # Ensure we merge any user-specified watchers with our own. # NOTE: If there are config-driven watchers, we pull those up to the # kwarg level; that lets us merge cleanly without needing complex # config-driven "override vs merge" semantics. # TODO: if/when those semantics are implemented, use them instead. # NOTE: config value for watchers defaults to an empty list; and we # want to clone it to avoid actually mutating the config. watchers = kwargs.pop('watchers', list(self.config.run.watchers)) watchers.append(watcher) try: return self.run(cmd_str, watchers=watchers, **kwargs) except Failure as failure: # Transmute failures driven by our FailingResponder, into auth # failures - the command never even ran. # TODO: wants to be a hook here for users that desire "override a # bad config value for sudo.password" manual input # NOTE: as noted in #294 comments, we MAY in future want to update # this so run() is given ability to raise AuthFailure on its own. # For now that has been judged unnecessary complexity. if isinstance(failure.reason, ResponseNotAccepted): # NOTE: not bothering with 'reason' here, it's pointless. # NOTE: using raise_from(..., None) to suppress Python 3's # "helpful" multi-exception output. It's confusing here. error = AuthFailure(result=failure.result, prompt=prompt) raise_from(error, None) # Reraise for any other error so it bubbles up normally. else: raise