Skip to content

Convenient sudo() wrapper #294

@bitprophet

Description

@bitprophet

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.

Given those user-exposed features, there's no technical necessity to implement it in core, because it should just turn into something like this:

@task
def identity(c):
    assert c.run("whoami").stdout.strip() == "myuser"
    sudo_prompt = "Sudo password plz:"
    with c.prefix("sudo -S -p '{0}' ".format(sudo_prompt):
        result = c.run("whoami", responses={sudo_prompt: "mysudopassword\n"})
        assert result.stdout.strip() == "root"

But clearly, even if you remove the asserts, that's annoying boilerplate we can erase by implementing a commonly used pattern as a convenience method.


MUCH 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:

  • The most-obvious answer is "you should store it in your config up front so no prompting is necessary".
  • But storing that data on-disk is insecure; a runtime prompt is better.
    • A runtime "pulled from a secure store over the network, flash key, etc" is best. No prompt, no on-disk.
  • But there's nothing saying we can't explicitly prompt up-front in sudo.
  • 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.
  • 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.
    • 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.
    • Counterpoint: users with large fleets will usually want "raise/store an exception and keep trucking" instead...
  • 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.
    • Counterpoint, if you have that setup, you could be using Fabric 2's per-host/group configs
    • But then you're back to storing the data on-disk
    • So you really do need one prompt per remote password, no matter when it pops up.
    • 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}})
    • 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.
    • 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.
  • 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"
    • This isn't exactly the same as Fabric 1's behavior, because it involves running the command literally twice in a row.
    • But it feels cleaner because it doesn't require the truly-inline prompting.
    • 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.
  • 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.
    • Though there's the added wrinkle of "when to use getpass.getpass vs (raw_)input"...
  • 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).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions