main.rs 16.6 KB
Newer Older
1
2
3
4
//! runaway-cli/main.rs
//! Author: Alexandre Péré
//! 
//! Runaway command line tool. Allows to execute scripts and batches of scripts on remote hosts. 
Alexandre Péré's avatar
Alexandre Péré committed
5
6
//! Every subcommand of the application is implemented in a separate function of the `subcommands` 
//! module.
Alexandre Péré's avatar
Alexandre Péré committed
7
8


9
//------------------------------------------------------------------------------------------ IMPORTS
Alexandre Péré's avatar
Alexandre Péré committed
10

Alexandre Pere's avatar
Alexandre Pere committed
11
extern crate openssl_probe;
Alexandre Péré's avatar
Alexandre Péré committed
12

Alexandre Pere's avatar
Alexandre Pere committed
13
14
15
#[macro_use]
extern crate lazy_static;

Alexandre Péré's avatar
Alexandre Péré committed
16
use clap;
Alexandre Pere's avatar
Alexandre Pere committed
17
use exit::Exit;
Alexandre Pere's avatar
Alexandre Pere committed
18
use tracing::{self, error};
19
20


Alexandre Péré's avatar
Alexandre Péré committed
21
22
//------------------------------------------------------------------------------------------ MODULES

Alexandre Pere's avatar
Alexandre Pere committed
23

Alexandre Péré's avatar
Alexandre Péré committed
24
25
mod subcommands;
mod misc;
26
mod exit;
Alexandre Pere's avatar
Alexandre Pere committed
27
28
mod logger;

29

Alexandre Péré's avatar
Alexandre Péré committed
30
//---------------------------------------------------------------------------------------- CONSTANTS
PERE Alexandre's avatar
PERE Alexandre committed
31

Alexandre Pere's avatar
Alexandre Pere committed
32

33
34
35
36
const NAME: &str = env!("CARGO_PKG_NAME");
const VERSION: &str = env!("CARGO_PKG_VERSION");
const AUTHOR: &str = env!("CARGO_PKG_AUTHORS");
const DESC: &str = "Execute code on remote hosts.";
Alexandre Péré's avatar
Alexandre Péré committed
37

38
39

//--------------------------------------------------------------------------------------------- MAIN
Alexandre Péré's avatar
Alexandre Péré committed
40

Alexandre Péré's avatar
Alexandre Péré committed
41
// The application entrypoint.
Alexandre Péré's avatar
Alexandre Péré committed
42
fn main(){
43

Alexandre Pere's avatar
Alexandre Pere committed
44
45
46
    // We get openssl certificates
    openssl_probe::init_ssl_cert_env_vars();

47
    // We get available profiles
Alexandre Péré's avatar
Alexandre Péré committed
48
    let profiles = misc::get_available_profiles();
49
50
51

    // We define the arguments parser
    let application = clap::App::new(NAME)
Alexandre Péré's avatar
Alexandre Péré committed
52
53
54
        .version(VERSION)
        .about(DESC)
        .author(AUTHOR)
Alexandre Péré's avatar
Alexandre Péré committed
55
        .setting(clap::AppSettings::ArgRequiredElseHelp) 
Alexandre Péré's avatar
Alexandre Péré committed
56
        .about("Execute code on remote hosts")
57
58
        .subcommand(clap::SubCommand::with_name("install-completion")
            .about("Install bash completion script"))
Alexandre Péré's avatar
Alexandre Péré committed
59
60
61
62
        .subcommand(clap::SubCommand::with_name("exec")
            .about("Runs a single execution on a remote host")
            .arg(clap::Arg::with_name("REMOTE")
                .help("Name of remote profile to execute script with")
63
                .possible_values(&profiles.iter().map(|a| a.as_str()).collect::<Vec<_>>()[..])
Alexandre Péré's avatar
Alexandre Péré committed
64
                .required(true))
Alexandre Péré's avatar
Alexandre Péré committed
65
66
67
            .arg(clap::Arg::with_name("SCRIPT")
                .help("File name of the script to be executed")
                .required(true))
Alexandre Péré's avatar
Alexandre Péré committed
68
            .arg(clap::Arg::with_name("verbose")
Alexandre Pere's avatar
Alexandre Pere committed
69
                .short("V")
Alexandre Péré's avatar
Alexandre Péré committed
70
                .long("verbose")
Alexandre Pere's avatar
Alexandre Pere committed
71
                .help("Print more messages"))
72
73
74
75
76
77
78
            .arg(clap::Arg::with_name("debug-ssh")
                .long("debug-ssh")
                .help("Print ssh messages"))
            .arg(clap::Arg::with_name("trace")
                .takes_value(true)
                .long("trace")
                .help("Print trace message of the mentionned module"))
Alexandre Pere's avatar
Alexandre Pere committed
79
80
81
82
            .arg(clap::Arg::with_name("silent")
                .short("S")
                .long("silent")
                .help("Print no messages from runaway"))
83
84
85
86
87
88
89
90
91
92
            .arg(clap::Arg::with_name("send-ignore")
                .short("s")
                .long("send-ignore")
                .default_value(".sendignore")
                .help("File containing glob patterns used to ignore files when sending data."))
            .arg(clap::Arg::with_name("fetch-ignore")
                .short("f")
                .long("fetch-ignore")
                .default_value(".fetchignore")
                .help("File containing glob patterns used to ignore files when fetching data."))
Alexandre Péré's avatar
Alexandre Péré committed
93
94
95
96
97
98
99
            .arg(clap::Arg::with_name("leave")
                .short("l")
                .long("leave")
                .takes_value(true)
                .possible_value("nothing")
                .possible_value("code")
                .possible_value("everything")
Alexandre Péré's avatar
Alexandre Péré committed
100
                .default_value("nothing")
Alexandre Péré's avatar
Alexandre Péré committed
101
                .help("What to leave on the remote host after execution"))
Alexandre Pere's avatar
Alexandre Pere committed
102
            .arg(clap::Arg::with_name("remote-folder")
Alexandre Pere's avatar
Alexandre Pere committed
103
                .short("r")
Alexandre Pere's avatar
Alexandre Pere committed
104
105
                .long("remote-folder")
                .default_value("$RUNAWAY_PATH/$RUNAWAY_UUID")
Alexandre Pere's avatar
Alexandre Pere committed
106
                .help("Folder to deflate data in, on the remote."))
Alexandre Pere's avatar
Alexandre Pere committed
107
108
109
110
111
112
113
114
115
116
117
            .arg(clap::Arg::with_name("output-folder")
                .short("o")
                .long("output-folder")
                .default_value(".")
                .help("Folder to deflate data in, on local."))
            .arg(clap::Arg::with_name("no-ecode")
                .long("no-ecode")
                .help("Do not copy the remote exit code to the local command. Returns 0 whatever the script exit code."))
            .arg(clap::Arg::with_name("no-env-read")
                .long("no-env-read")
                .help("Do not read the local environment variables to apply it to the remote context."))
Alexandre Pere's avatar
Alexandre Pere committed
118
119
120
121
            .arg(clap::Arg::with_name("on-local")
                .short("L")
                .long("on-local")
                .help("This allows to reduce the transfers when the profile executes locally."))
Alexandre Pere's avatar
Alexandre Pere committed
122
123
            .arg(clap::Arg::with_name("ARGUMENTS")
                .help("Script arguments")
Alexandre Péré's avatar
Alexandre Péré committed
124
125
                .multiple(true)
                .allow_hyphen_values(true)
Alexandre Pere's avatar
Alexandre Pere committed
126
                .last(true)))
Alexandre Péré's avatar
Alexandre Péré committed
127
128
129
130
        .subcommand(clap::SubCommand::with_name("batch")
            .about("Runs a batch of executions on a remote host")
            .arg(clap::Arg::with_name("REMOTE")
                .help("Name of remote profile to execute script with")
131
                .possible_values(&profiles.iter().map(|a| a.as_str()).collect::<Vec<_>>()[..])
Alexandre Péré's avatar
Alexandre Péré committed
132
                .required(true))
Alexandre Péré's avatar
Alexandre Péré committed
133
134
135
            .arg(clap::Arg::with_name("SCRIPT")
                .help("File name of the script to be executed")
                .required(true)) 
Alexandre Péré's avatar
Alexandre Péré committed
136
            .arg(clap::Arg::with_name("verbose")
Alexandre Pere's avatar
Alexandre Pere committed
137
                .short("V")
Alexandre Péré's avatar
Alexandre Péré committed
138
                .long("verbose")
Alexandre Pere's avatar
Alexandre Pere committed
139
                .help("Print more messages"))
140
141
142
143
144
145
146
            .arg(clap::Arg::with_name("debug-ssh")
                .long("debug-ssh")
                .help("Print ssh messages"))
            .arg(clap::Arg::with_name("trace")
                .takes_value(true)
                .long("trace")
                .help("Print trace message of the mentionned module"))
Alexandre Pere's avatar
Alexandre Pere committed
147
148
149
150
            .arg(clap::Arg::with_name("silent")
                .short("S")
                .long("silent")
                .help("Print no messages from runaway"))
Alexandre Péré's avatar
Alexandre Péré committed
151
            .arg(clap::Arg::with_name("repeats")
Alexandre Pere's avatar
Alexandre Pere committed
152
                .short("x")
Alexandre Péré's avatar
Alexandre Péré committed
153
154
155
156
                .long("repeats")
                .takes_value(true)
                .default_value("1")
                .help("The number of time every parameter must be repeated. Used with product string."))
Alexandre Péré's avatar
Alexandre Péré committed
157
158
159
160
161
162
163
            .arg(clap::Arg::with_name("leave")
                .short("l")
                .long("leave")
                .takes_value(true)
                .possible_value("nothing")
                .possible_value("code")
                .possible_value("everything")
Alexandre Péré's avatar
Alexandre Péré committed
164
                .default_value("nothing")
Alexandre Péré's avatar
Alexandre Péré committed
165
                .help("What to leave on the remote host after execution"))
Alexandre Pere's avatar
Alexandre Pere committed
166
167
168
            .arg(clap::Arg::with_name("arguments-file")
                .short("A")
                .long("arguments-file")
Alexandre Péré's avatar
Alexandre Péré committed
169
                .takes_value(true)
Alexandre Pere's avatar
Alexandre Pere committed
170
171
                .help("A file specifying a list of newline-separated arguments. Template strings can also be used."))
            .arg(clap::Arg::with_name("outputs-file")
Alexandre Péré's avatar
Alexandre Péré committed
172
                .short("O")
Alexandre Pere's avatar
Alexandre Pere committed
173
                .long("outputs-file")
Alexandre Péré's avatar
Alexandre Péré committed
174
175
                .takes_value(true)
                .help("A file specifying a list of newline-separated output directories."))
Alexandre Pere's avatar
Alexandre Pere committed
176
177
            .arg(clap::Arg::with_name("remotes-file")
                .short("R")
178
179
                .long("remotes-file")
                .takes_value(true)
Alexandre Pere's avatar
Alexandre Pere committed
180
181
                .help("Folder to deflate data in, on local."))
            .arg(clap::Arg::with_name("output-folders")
Alexandre Péré's avatar
Alexandre Péré committed
182
                .short("o")
Alexandre Pere's avatar
Alexandre Pere committed
183
                .long("output-folders")
Alexandre Péré's avatar
Alexandre Péré committed
184
                .takes_value(true)
185
                .default_value("./batch/$RUNAWAY_UUID")
Alexandre Pere's avatar
Alexandre Pere committed
186
187
188
189
190
191
                .help("The output folders to put the results in. Template strings can be used."))
            .arg(clap::Arg::with_name("remote-folders")
                .short("r")
                .long("remote-folders")
                .default_value("$RUNAWAY_PATH/$RUNAWAY_UUID")
                .help("The folders to put the code in, on the remote. Template strings can be used."))
192
193
194
195
196
197
198
199
200
201
            .arg(clap::Arg::with_name("send-ignore")
                .short("s")
                .long("send-ignore")
                .default_value(".sendignore")
                .help("File containing glob patterns used to ignore files when sending data."))
            .arg(clap::Arg::with_name("fetch-ignore")
                .short("f")
                .long("fetch-ignore")
                .default_value(".fetchignore")
                .help("File containing glob patterns used to ignore files when fetching data."))
Alexandre Pere's avatar
Alexandre Pere committed
202
203
204
            .arg(clap::Arg::with_name("no-env-read")
                .long("no-env-read")
                .help("Do not read the local environment variables to apply it to the remote context."))
Alexandre Pere's avatar
Alexandre Pere committed
205
206
207
208
209
            .arg(clap::Arg::with_name("on-local")
                .short("L")
                .long("on-local")
                .help("This allows to reduce the transfers when the profile executes locally. \
                       Remote folders are overriden to follow the output folders."))
210
            .arg(clap::Arg::with_name("post-command")
Alexandre Pere's avatar
Alexandre Pere committed
211
                .short("p")
212
                .long("post-command")
Alexandre Pere's avatar
Alexandre Pere committed
213
                .default_value("cd $RUNAWAY_OUTPUT_FOLDER && \
214
215
216
                                echo \"$RUNAWAY_ECODE\" > ecode && \
                                echo \"$RUNAWAY_STDOUT\" > stdout && \
                                echo \"$RUNAWAY_STDERR\" > stderr ")
Alexandre Pere's avatar
Alexandre Pere committed
217
218
219
220
                .help("Bash command executed after the data were fetched to the local end. \
                       Runaway environment variables from the execution can be used. In particular \
                       we set $RUNAWAY_OUTPUT_FOLDER, $RUNAWAY_ECODE, $RUNAWAY_STDOUT and \
                       $RUNAWAY_STDERR."))
221
           .arg(clap::Arg::with_name("post-script")
Alexandre Pere's avatar
Alexandre Pere committed
222
                .short("P")
223
224
                .long("post-script")
                .takes_value(true)
Alexandre Pere's avatar
Alexandre Pere committed
225
226
227
228
                .help("Bash script to execute instead of post-proc-command, after the data were \
                       fetched to the local end."))
           .arg(clap::Arg::with_name("ARGUMENTS")
                .help("Script argument string. Template strings can be used.")
Alexandre Péré's avatar
Alexandre Péré committed
229
230
                .multiple(true)
                .allow_hyphen_values(true)
Alexandre Pere's avatar
Alexandre Pere committed
231
                .last(true)))
Alexandre Péré's avatar
Alexandre Péré committed
232
233
234
235
236
237
238
239
240
241
        .subcommand(clap::SubCommand::with_name("sched")
            .about("Use an online scheduler to optimize / explore experiment results.")
            .arg(clap::Arg::with_name("REMOTE")
                .help("Name of remote profile to execute script with")
                .possible_values(&profiles.iter().map(|a| a.as_str()).collect::<Vec<_>>()[..])
                .required(true))
            .arg(clap::Arg::with_name("SCRIPT")
                .help("File name of the script to be executed")
                .required(true)) 
            .arg(clap::Arg::with_name("SCHEDULER")
242
243
                .help("Search command to use to schedule experiment parameters.")
                .required(true))
Alexandre Péré's avatar
Alexandre Péré committed
244
            .arg(clap::Arg::with_name("verbose")
Alexandre Pere's avatar
Alexandre Pere committed
245
                .short("V")
Alexandre Péré's avatar
Alexandre Péré committed
246
                .long("verbose")
Alexandre Pere's avatar
Alexandre Pere committed
247
                .help("Print more messages"))
248
249
250
251
252
253
254
            .arg(clap::Arg::with_name("debug-ssh")
                .long("debug-ssh")
                .help("Print ssh messages"))
            .arg(clap::Arg::with_name("trace")
                .takes_value(true)
                .long("trace")
                .help("Print trace message of the mentionned module"))
Alexandre Pere's avatar
Alexandre Pere committed
255
256
257
258
            .arg(clap::Arg::with_name("silent")
                .short("S")
                .long("silent")
                .help("Print no messages from runaway"))
Alexandre Péré's avatar
Alexandre Péré committed
259
260
261
262
263
264
265
266
            .arg(clap::Arg::with_name("leave")
                .long("leave")
                .takes_value(true)
                .possible_value("nothing")
                .possible_value("code")
                .possible_value("everything")
                .default_value("nothing")
                .help("What to leave on the remote host after execution"))
267
268
269
270
271
272
273
274
275
276
277
            .arg(clap::Arg::with_name("output-folders")
                .short("o")
                .long("output-folders")
                .takes_value(true)
                .default_value("./batch/$RUNAWAY_UUID")
                .help("The output folders to put the results in. Template strings can be used."))
            .arg(clap::Arg::with_name("remote-folders")
                .short("r")
                .long("remote-folders")
                .default_value("$RUNAWAY_PATH/$RUNAWAY_UUID")
                .help("The folders to put the code in, on the remote. Template strings can be used."))
278
279
280
281
282
283
284
285
286
287
            .arg(clap::Arg::with_name("send-ignore")
                .short("s")
                .long("send-ignore")
                .default_value(".sendignore")
                .help("File containing glob patterns used to ignore files when sending data."))
            .arg(clap::Arg::with_name("fetch-ignore")
                .short("f")
                .long("fetch-ignore")
                .default_value(".fetchignore")
                .help("File containing glob patterns used to ignore files when fetching data."))
288
289
290
            .arg(clap::Arg::with_name("no-env-read")
                .long("no-env-read")
                .help("Do not read the local environment variables to apply it to the remote context."))
Alexandre Pere's avatar
Alexandre Pere committed
291
292
293
294
295
            .arg(clap::Arg::with_name("on-local")
                .short("L")
                .long("on-local")
                .help("This allows to reduce the transfers when the profile executes locally. \
                       Remote folders are overriden to follow the output folders."))
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
            .arg(clap::Arg::with_name("post-command")
                .short("p")
                .long("post-command")
                .default_value("cd $RUNAWAY_OUTPUT_FOLDER && \
                                echo \"$RUNAWAY_ECODE\" > ecode && \
                                echo \"$RUNAWAY_STDOUT\" > stdout && \
                                echo \"$RUNAWAY_STDERR\" > stderr && \
                                echo \"$RUNAWAY_FEATURES\" > features ")
                .help("Bash command executed after the data were fetched to the local end. \
                       Runaway environment variables from the execution can be used. In particular \
                       we set $RUNAWAY_OUTPUT_FOLDER, $RUNAWAY_ECODE, $RUNAWAY_STDOUT, \
                       $RUNAWAY_STDERR and $RUNAWAY_FEATURES."))
           .arg(clap::Arg::with_name("post-script")
                .short("P")
                .long("post-script")
Alexandre Péré's avatar
Alexandre Péré committed
311
                .takes_value(true)
312
313
314
315
316
                .help("Bash script to execute instead of post-proc-command, after the data were \
                       fetched to the local end."))
            .arg(clap::Arg::with_name("stop-on-fail")
                .long("stop-on-fail")
                .help(""))
Alexandre Pere's avatar
Alexandre Pere committed
317
        );
318
319

    // If the completion_file already exists, we update it to account for the new available profiles
Alexandre Péré's avatar
Alexandre Péré committed
320
    match misc::which_shell(){
321
        Ok(clap::Shell::Zsh) => {
Alexandre Péré's avatar
Alexandre Péré committed
322
            misc::generate_zsh_completion(application.clone());
323
324
        }
        Ok(clap::Shell::Bash) => {
Alexandre Péré's avatar
Alexandre Péré committed
325
            misc::generate_bash_completion(application.clone());
326
        }
Alexandre Péré's avatar
Alexandre Péré committed
327
        Err(_) => {},
328
329
        _ => unreachable!()
    }
Alexandre Péré's avatar
Alexandre Péré committed
330

Alexandre Péré's avatar
Alexandre Péré committed
331
332
    // We parse the arguments, keeping the application untouched if we want to generate the 
    // completion files. 
333
    let matches = application.clone().get_matches();
Alexandre Péré's avatar
Alexandre Péré committed
334

Alexandre Pere's avatar
Alexandre Pere committed
335
336
    // We dispatch to subcommands and exit;
    let output;
Alexandre Pere's avatar
Alexandre Pere committed
337
    if let Some(matches) = matches.subcommand_matches("exec"){
Alexandre Pere's avatar
Alexandre Pere committed
338
        output = subcommands::exec(matches.clone());
Alexandre Péré's avatar
Alexandre Péré committed
339
    } else if let Some(matches) = matches.subcommand_matches("batch"){
Alexandre Pere's avatar
Alexandre Pere committed
340
        output = subcommands::batch(matches.clone());
Alexandre Péré's avatar
Alexandre Péré committed
341
    } else if let Some(_) = matches.subcommand_matches("install-completion"){
Alexandre Pere's avatar
Alexandre Pere committed
342
        output = subcommands::install_completion(application);
Alexandre Péré's avatar
Alexandre Péré committed
343
    } else if let Some(matches) = matches.subcommand_matches("sched"){
344
        output = subcommands::sched(matches.clone());
Alexandre Pere's avatar
Alexandre Pere committed
345
346
    } else {
        output = Ok(Exit::AllGood);
Alexandre Péré's avatar
Alexandre Péré committed
347
    }
Alexandre Pere's avatar
Alexandre Pere committed
348
349
350
351
352

    // Depending on the output, we return a different message
    let exit = match output{
        Ok(Exit::AllGood) => Exit::AllGood.into(),
        Ok(Exit::ScriptFailedWithCode(e)) => {
Alexandre Pere's avatar
Alexandre Pere committed
353
            error!("Script execution failed with exit code {}", e);
Alexandre Pere's avatar
Alexandre Pere committed
354
355
356
            e
        }
        Ok(Exit::ScriptFailedWithoutCode) => {
Alexandre Pere's avatar
Alexandre Pere committed
357
            error!("Script execution failed without returning exit code");
Alexandre Pere's avatar
Alexandre Pere committed
358
359
            Exit::ScriptFailedWithoutCode.into()
        }
360
        Ok(Exit::SomeExecutionFailed(nb)) => {
Alexandre Pere's avatar
Alexandre Pere committed
361
            error!("{} executions failed.", nb);
362
363
            Exit::SomeExecutionFailed(nb).into()
        }
Alexandre Pere's avatar
Alexandre Pere committed
364
365
        Ok(_) => unreachable!(),
        Err(e) => {
Alexandre Pere's avatar
Alexandre Pere committed
366
            error!("Runaway experienced an error: {}", e);
Alexandre Pere's avatar
Alexandre Pere committed
367
368
369
370
            e.into()
        }
    };
    std::process::exit(exit);
Alexandre Péré's avatar
Alexandre Péré committed
371
}