Example #1
0
 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)
Example #2
0
 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
Example #3
0
 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
Example #4
0
 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)
Example #5
0
 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)
Example #6
0
    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
Example #7
0
    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
Example #8
0
    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