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

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

7 6
import getpass
8

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

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

16

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

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

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

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

89 6
    def no_tasks_given(self):
90
        # As above, neuter the usual "hey you didn't give me any tasks, let me
91
        # print help for you" behavior, if necessary.
92 6
        if not self._remainder_only:
93 0
            super(Fab, self).no_tasks_given()
94

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

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

148

149
# Mostly a concession to testing.
150 6
def make_program():
151 6
    return Fab(
152
        name="Fabric",
153
        version=fabric,
154
        executor_class=Executor,
155
        config_class=Config,
156
    )
157

158

159 6
program = make_program()

Read our documentation on viewing source code .

Loading