1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#![allow(dead_code)]

extern crate serde_yaml;

pub mod output_extensions;
mod wait;

pub use wait::{Error as WaitError, Wait, WaitBuilder};

use self::output_extensions::ProcessOutput;
use std::{
    process::{Command, Output, Stdio},
    thread, time,
};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum ProcessError {
    #[error("could not start process '{message}'")]
    ProcessExited { message: String },
}

/// Runs command, wait for output and returns it output
///
/// # Arguments
///
/// * `command` - Command which will be invoked
///
pub fn run_process_and_get_output(mut command: Command) -> Output {
    // FIXME: switch to something like assert_cmd to wait with a timeout
    println!("Running command: {:?}", &command);
    let content = command
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()
        .unwrap()
        .wait_with_output()
        .expect("failed to execute process");

    if content.as_lossy_string() != "" {
        println!("Output: {}", content.as_lossy_string());
    }
    if content.err_as_lossy_string() != "" {
        println!("Error: {}", content.err_as_lossy_string());
    }
    println!();
    content
}

/// Runs command for n times with m second interval.
///
/// # Panics
///
/// Panics if max_attempts is exceeded and none of attempts return successful exit code.
///
/// # Arguments
///
/// * `command` - Command which will be invoked
/// * `timeout` - Timeout after unsuccesful attempt (in seconds)
/// * `max_attempts` - Maximum number of attempts
/// * `command_description` - User description of command
/// * `error_description` - User description of error
///
/// # Example
///
/// use process_utils::run_process_until_exited_successfully;
///
///    process_utils::run_process_until_exited_successfully(
///         jcli_wrapper::run_rest_stats_command_default(),
///         2,
///         5,
///         "get stats from jormungandr node",
///         "jormungandr node is not up"
///    );
///
pub fn run_process_until_exited_successfully(
    mut command: Command,
    timeout: u64,
    max_attempts: i32,
    command_description: &str,
    error_description: &str,
) {
    let mut attempts = max_attempts;

    loop {
        let status = command.status().unwrap_or_else(|_| {
            panic!(
                "failed to get exit status of command: {}",
                &command_description
            )
        });

        if status.success() {
            break;
        }

        if attempts <= 0 {
            break;
        }

        println!(
            "non-zero status with message(). {}. waiting {} s and trying again ({} of {})",
            command_description,
            &timeout,
            max_attempts - attempts + 1,
            max_attempts
        );

        attempts -= 1;
        self::sleep(timeout);
    }

    if attempts <= 0 {
        panic!(
            "{} (tried to connect {} times with {} s interval)",
            &error_description, &max_attempts, &timeout
        );
    }
    println!("Success: {}", &command_description);
}

pub fn run_process_until_response_matches<F: Fn(Output) -> bool>(
    mut command: Command,
    is_output_ok: F,
    sleep_between_attempt: u64,
    max_attempts: u64,
    command_description: &str,
    error_description: &str,
) -> Result<(), ProcessError> {
    let sleep_between_attempt_duration = time::Duration::from_millis(&sleep_between_attempt * 1000);
    let mut attempts = 1;

    println!("Running command {:?} in loop", command);

    loop {
        let output = command
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()
            .unwrap()
            .wait_with_output()
            .unwrap_or_else(|_| panic!("cannot get output from command {:?}", &command));

        println!("Standard Output: {}", output.as_lossy_string());
        println!("Standard Error: {}", output.err_as_lossy_string());

        if output.status.success() && is_output_ok(output) {
            println!("Success: {}", &command_description);
            return Ok(());
        }

        if attempts >= max_attempts {
            return Err(ProcessError::ProcessExited {
                message: format!(
                    "{} (tried to connect {} times with {} s interval)",
                    &error_description, &max_attempts, &sleep_between_attempt
                ),
            });
        }

        println!(
            "non-zero status with message(). waiting {} s and trying again ({} of {})",
            &sleep_between_attempt, &attempts, &max_attempts
        );

        attempts += 1;
        thread::sleep(sleep_between_attempt_duration);
    }
}

pub fn sleep(seconds: u64) {
    let duration = time::Duration::from_secs(seconds);
    thread::sleep(duration);
}