{"action":"created","issue":{"url":"https://api.github.com/repos/pyinvoke/invoke/issues/294","repository_url":"https://api.github.com/repos/pyinvoke/invoke","labels_url":"https://api.github.com/repos/pyinvoke/invoke/issues/294/labels{/name}","comments_url":"https://api.github.com/repos/pyinvoke/invoke/issues/294/comments","events_url":"https://api.github.com/repos/pyinvoke/invoke/issues/294/events","html_url":"https://github.com/pyinvoke/invoke/issues/294","id":123341405,"number":294,"title":"Convenient sudo() wrapper","user":{"login":"bitprophet","id":6088,"avatar_url":"https://avatars.githubusercontent.com/u/6088?v=3","gravatar_id":"","url":"https://api.github.com/users/bitprophet","html_url":"https://github.com/bitprophet","followers_url":"https://api.github.com/users/bitprophet/followers","following_url":"https://api.github.com/users/bitprophet/following{/other_user}","gists_url":"https://api.github.com/users/bitprophet/gists{/gist_id}","starred_url":"https://api.github.com/users/bitprophet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bitprophet/subscriptions","organizations_url":"https://api.github.com/users/bitprophet/orgs","repos_url":"https://api.github.com/users/bitprophet/repos","events_url":"https://api.github.com/users/bitprophet/events{/privacy}","received_events_url":"https://api.github.com/users/bitprophet/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/pyinvoke/invoke/labels/Feature","name":"Feature","color":"40a056"},{"url":"https://api.github.com/repos/pyinvoke/invoke/labels/run()","name":"run()","color":"444444"}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":12,"created_at":"2015-12-21T19:21:13Z","updated_at":"2016-09-09T00:28:49Z","closed_at":null,"body":"tl;dr Fabric 1's `sudo()` except implemented on top of generic, user-exposed features (#289, #293) and available at Invoke-on-up, since it's not actually specific to remote execution.\r\n\r\nGiven those user-exposed features, there's no technical necessity to implement it in core, because it *should* just turn into something like this:\r\n\r\n``` python\r\n@task\r\ndef identity(c):\r\n    assert c.run(\"whoami\").stdout.strip() == \"myuser\"\r\n    sudo_prompt = \"Sudo password plz:\"\r\n    with c.prefix(\"sudo -S -p '{0}' \".format(sudo_prompt):\r\n        result = c.run(\"whoami\", responses={sudo_prompt: \"mysudopassword\\n\"})\r\n        assert result.stdout.strip() == \"root\"\r\n```\r\n\r\nBut clearly, even if you remove the asserts, that's annoying boilerplate we can erase by implementing a commonly used pattern as a convenience method.\r\n\r\n----\r\n\r\nMUCH LATER EDIT: There *is* a wrinkle here - in Fabric 1, this functionality includes \"prompt the user ourselves when the sudo-prompt response value isn't pre-set, then cache the result from then on\". That prompt+cache happens \"inline\" within the `sudo()` call. Brainstorm:\r\n\r\n* The most-obvious answer is \"you should store it in your config up front so no prompting is necessary\".\r\n* But storing that data on-disk is insecure; a runtime prompt is better.\r\n    * A runtime \"pulled from a secure store over the network, flash key, etc\" is best. No prompt, no on-disk.\r\n* But there's nothing saying we can't explicitly prompt up-front in `sudo`.\r\n* Prompting \"lazily\" / as late as possible, is \"nice\" insofar as it means users with passwordless sudo never have to deal with a prompt at all - no inner prompt seen, no outer prompt displayed.\r\n* In *most* cases, that is still just \"nice\" and not *required* - users who know up front that they will need the password, can simply enter it up front.\r\n    * But the Fabric-specific use case of remote sudo is more complex - users with large fleets may not know  whether the prompt will ever appear.\r\n    * Counterpoint: users with large fleets will usually want \"raise/store an exception and keep trucking\" instead...\r\n* Users may also have heterogenous sudo passwords across their fleets, so a single up-front prompt is no longer enough - it'd need to be one prompt per host/group/whatever.\r\n    * Counterpoint, if you have that setup, you could be using Fabric 2's per-host/group configs\r\n    * But then you're back to storing the data on-disk\r\n    * So you really *do* need one prompt per remote password, no matter when it pops up.\r\n    * If you know ahead of time *where* the passwords differ, we could then say \"ok, you just need to prompt for each group\" (perhaps with a config setting like e.g. `{'hostname': {'prompt_for_password': True}}`)\r\n    * If you *don't*, that is when runtime inline prompting becomes truly \"necessary\". Whether that's enough of a use case to be worth implementing this ourselves, hard to say.\r\n    * **NOTE** that all of these concerns pop up with Fabric connection/login passwords too - with the exception that the point of prompting is at connection time and not anywhere in `Runner`.\r\n* One way to enable \"late but not inline\" prompting is to have the prompt machinery raise an exception when it encounters a prompt key whose value is `None`, then `sudo()` can treat this as \"prompt, fill config, try that same command again\"\r\n    * This isn't **exactly** the same as Fabric 1's behavior, because it involves running the command literally twice in a row.\r\n    * But it feels cleaner because it doesn't require the truly-inline prompting.\r\n    * It also arguably lets us push the entirety of \"dealing with missing password caches\" on the user, which is better for 'training' them to do the right thing, but worse from a usability standpoint.\r\n* If we *did* go with fully inline prompting again, at least we can make it more generic - again, anything whose value is `None` could trigger the pause-and-prompt, not just sudo specific passwords.\r\n    * Though there's the added wrinkle of \"when to use `getpass.getpass` vs `(raw_)input`\"...\r\n* Yet another reason *not* to do inline prompting is that it adds implementation complexity - e.g. in Fabric 1 we need the prompt machinery to talk to the stdin handler so the stdin handler pauses itself while prompting is happening (otherwise `getpass` or `raw_input` never see the stdin, as it's being intercepted)."},"comment":{"url":"https://api.github.com/repos/pyinvoke/invoke/issues/comments/245784216","html_url":"https://github.com/pyinvoke/invoke/issues/294#issuecomment-245784216","issue_url":"https://api.github.com/repos/pyinvoke/invoke/issues/294","id":245784216,"user":{"login":"bitprophet","id":6088,"avatar_url":"https://avatars.githubusercontent.com/u/6088?v=3","gravatar_id":"","url":"https://api.github.com/users/bitprophet","html_url":"https://github.com/bitprophet","followers_url":"https://api.github.com/users/bitprophet/followers","following_url":"https://api.github.com/users/bitprophet/following{/other_user}","gists_url":"https://api.github.com/users/bitprophet/gists{/gist_id}","starred_url":"https://api.github.com/users/bitprophet/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bitprophet/subscriptions","organizations_url":"https://api.github.com/users/bitprophet/orgs","repos_url":"https://api.github.com/users/bitprophet/repos","events_url":"https://api.github.com/users/bitprophet/events{/privacy}","received_events_url":"https://api.github.com/users/bitprophet/received_events","type":"User","site_admin":false},"created_at":"2016-09-09T00:28:49Z","updated_at":"2016-09-09T00:28:49Z","body":"Have been mostly taking notes in TODO comments...bad habit.\r\n\r\nAt this point I have a working `FailureResponder` which takes a failure sentinel and raises `ResponseFailure` if it is seen. Still need to determine what `sudo` does when it sees one of these, as above.\r\n\r\nOn top of that: in the case(s) where we continue bubbling up the exception instead of retrying, it'd be nice for the exception to gain info about the `sudo` call it is bubbling up through...i.e. to turn into something like `Failure`/`Result`. Except that right now `Result` assumes it's a \"finished\" command, with attrs like `exit_code`, `ok`, etc - things which do not apply in a \"sudo authentication failed partway\" scenario.\r\n\r\nSo feels like what needs doing is:\r\n\r\n- [ ] Rip out the \"this is a regular command that failed during execution\" language/assumptions from `Failure` to a new subclass called e.g. `CommandFailure`\r\n- [ ] Create e.g. `AuthFailure`, subclassing `Failure`, for use by `sudo`\r\n- [ ] Update `Context.sudo` so it transmutes `ResponseFailure` into `AuthFailure`\r\n    - [ ] clients of `sudo` probably don't need to care about the details of the response failure itself, so it can just be discarded. Maybe someday we'll attach it to the new exception object, but that seems pretty pointless right now.\r\n    - [ ] This *actually* becomes \"update `run` so it lets callers such as `sudo` tell it how to deal with unexpected errors\" - because we need the innards of `run`, which generate `Failure`/`Response`, to still do that, even when our `ResponseFailure` occurs. Unclear how best to handle this:\r\n        - callbacks/injection, e.g. \"here, call this with your `Response` when you see a `FooException`\", hand it something that reraises the `AuthException` around the `Response`\"\r\n        - `run` learns to attach *any* inner exceptions to the raised `Failure` and then the caller is responsible for deciding whether to open and raise the inner exception, continue raising `Failure`, or etc\r\n        - refactor `run` some more so the response-generating bits are torn out of the actually-running bits. (Not ideal since `run` is already split up into a few bits for various reasons.)\r\n- [ ] Document clearly how auth failure interacts, or doesn't, with `warn=True` - rather, that `warn=True` only affects command failures, and not auth failures."}}
{"actor":{"display_login":"bitprophet"}}