fabric / fabric
1
"""
2
CLI entrypoint & parser configuration.
3

4
Builds on top of Invoke's core functionality for same.
5
"""
6

7 2
import getpass
8

9 2
from invoke import Argument, Collection, Program
10 2
from invoke import __version__ as invoke
11 2
from paramiko import __version__ as paramiko
12

13 2
from . import __version__ as fabric
14 2
from . import Config, Executor
15

16

17 2
class Fab(Program):
18 2
    def print_version(self):
19 2
        super(Fab, self).print_version()
20 2
        print("Paramiko {}".format(paramiko))
21 2
        print("Invoke {}".format(invoke))
22

23 2
    def core_args(self):
24 2
        core_args = super(Fab, self).core_args()
25 2
        my_args = [
26
            Argument(
27
                names=("S", "ssh-config"),
28
                help="Path to runtime SSH config file.",
29
            ),
30
            Argument(
31
                names=("H", "hosts"),
32
                help="Comma-separated host name(s) to execute tasks against.",
33
            ),
34
            Argument(
35
                names=("i", "identity"),
36
                kind=list,  # Same as OpenSSH, can give >1 key
37
                # TODO: automatically add hint about iterable-ness to Invoke
38
                # help display machinery?
39
                help="Path to runtime SSH identity (key) file. May be given multiple times.",  # noqa
40
            ),
41
            # TODO: worth having short flags for these prompt args?
42
            Argument(
43
                names=("prompt-for-login-password",),
44
                kind=bool,
45
                help="Request an upfront SSH-auth password prompt.",
46
            ),
47
            Argument(
48
                names=("prompt-for-passphrase",),
49
                kind=bool,
50
                help="Request an upfront SSH key passphrase prompt.",
51
            ),
52
        ]
53 2
        return core_args + my_args
54

55 2
    @property
56
    def _remainder_only(self):
57
        # No 'unparsed' (i.e. tokens intended for task contexts), and remainder
58
        # (text after a double-dash) implies a contextless/taskless remainder
59
        # execution of the style 'fab -H host -- command'.
60
        # NOTE: must ALSO check to ensure the double dash isn't being used for
61
        # tab completion machinery...
62 2
        return (
63
            not self.core.unparsed
64
            and self.core.remainder
65
            and not self.args.complete.value
66
        )
67

68 2
    def load_collection(self):
69
        # Stick in a dummy Collection if it looks like we were invoked w/o any
70
        # tasks, and with a remainder.
71
        # This isn't super ideal, but Invoke proper has no obvious "just run my
72
        # remainder" use case, so having it be capable of running w/o any task
73
        # module, makes no sense. But we want that capability for testing &
74
        # things like 'fab -H x,y,z -- mycommand'.
75 2
        if self._remainder_only:
76
            # TODO: hm we're probably not honoring project-specific configs in
77
            # this branch; is it worth having it assume CWD==project, since
78
            # that's often what users expect? Even tho no task collection to
79
            # honor the real "lives by task coll"?
80 2
            self.collection = Collection()
81
        else:
82 2
            super(Fab, self).load_collection()
83

84 2
    def no_tasks_given(self):
85
        # As above, neuter the usual "hey you didn't give me any tasks, let me
86
        # print help for you" behavior, if necessary.
87 2
        if not self._remainder_only:
88 0
            super(Fab, self).no_tasks_given()
89

90 2
    def create_config(self):
91
        # Create config, as parent does, but with lazy=True to avoid our own
92
        # SSH config autoload. (Otherwise, we can't correctly load _just_ the
93
        # runtime file if one's being given later.)
94 2
        self.config = self.config_class(lazy=True)
95
        # However, we don't really want the parent class' lazy behavior (which
96
        # skips loading system/global invoke-type conf files) so we manually do
97
        # that here to match upstream behavior.
98 2
        self.config.load_base_conf_files()
99
        # And merge again so that data is available.
100
        # TODO: really need to either A) stop giving fucks about calling
101
        # merge() "too many times", or B) make merge() itself determine whether
102
        # it needs to run and/or just merge stuff that's changed, so log spam
103
        # isn't as bad.
104 2
        self.config.merge()
105

106 2
    def update_config(self):
107
        # Note runtime SSH path, if given, and load SSH configurations.
108
        # NOTE: must do parent before our work, in case users want to disable
109
        # SSH config loading within a runtime-level conf file/flag.
110 2
        super(Fab, self).update_config(merge=False)
111 2
        self.config.set_runtime_ssh_path(self.args["ssh-config"].value)
112 2
        self.config.load_ssh_config()
113
        # Load -i identity file, if given, into connect_kwargs, at overrides
114
        # level.
115
        # TODO: this feels a little gross, but since the parent has already
116
        # called load_overrides, this is best we can do for now w/o losing
117
        # data. Still feels correct; just might be cleaner to have even more
118
        # Config API members around this sort of thing. Shrug.
119 2
        connect_kwargs = {}
120 2
        path = self.args["identity"].value
121 2
        if path:
122 2
            connect_kwargs["key_filename"] = path
123
        # Secrets prompts that want to happen at handoff time instead of
124
        # later/at user-time.
125
        # TODO: should this become part of Invoke proper in case other
126
        # downstreams have need of it? E.g. a prompt Argument 'type'? We're
127
        # already doing a similar thing there for sudo password...
128 2
        if self.args["prompt-for-login-password"].value:
129 2
            prompt = "Enter login password for use with SSH auth: "
130 2
            connect_kwargs["password"] = getpass.getpass(prompt)
131 2
        if self.args["prompt-for-passphrase"].value:
132 2
            prompt = "Enter passphrase for use unlocking SSH keys: "
133 2
            connect_kwargs["passphrase"] = getpass.getpass(prompt)
134 2
        self.config._overrides["connect_kwargs"] = connect_kwargs
135
        # Since we gave merge=False above, we must do it ourselves here. (Also
136
        # allows us to 'compile' our overrides manipulation.)
137 2
        self.config.merge()
138

139

140
# Mostly a concession to testing.
141 2
def make_program():
142 2
    return Fab(
143
        name="Fabric",
144
        version=fabric,
145
        executor_class=Executor,
146
        config_class=Config,
147
    )
148

149

150 2
program = make_program()

Read our documentation on viewing source code .

Loading