• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender / 992981781

05 Sep 2023 08:55AM UTC coverage: 79.541% (+0.3%) from 79.264%
992981781

push

gitlab-ci

lluiscampos
chore: Clean-up `gmock` use in tests

Many tests had the header and include path while only using GTest.

Completely unnecessary clean-up...

Signed-off-by: Lluis Campos <lluis.campos@northern.tech>

5719 of 7190 relevant lines covered (79.54%)

290.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.48
/artifact/v3/scripts/executor.cpp
1
// Copyright 2023 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14

15
#include <artifact/v3/scripts/executor.hpp>
16

17
#include <algorithm>
18
#include <chrono>
19
#include <regex>
20
#include <string>
21

22
#include <common/common.hpp>
23
#include <common/expected.hpp>
24
#include <common/path.hpp>
25

26

27
namespace mender {
28
namespace artifact {
29
namespace scripts {
30
namespace executor {
31

32
namespace expected = mender::common::expected;
33

34

35
using expected::ExpectedBool;
36

37
namespace common = mender::common;
38
namespace error = mender::common::error;
39
namespace path = mender::common::path;
40

41

42
const string expected_state_script_version {"3"};
43
const int state_script_retry_exit_code {21};
44

45
unordered_map<const State, string> state_map {
46
        {State::Idle, "Idle"},
47
        {State::Sync, "Sync"},
48
        {State::Download, "Download"},
49
        {State::ArtifactInstall, "ArtifactInstall"},
50
        {State::ArtifactReboot, "ArtifactReboot"},
51
        {State::ArtifactCommit, "ArtifactCommit"},
52
        {State::ArtifactRollback, "ArtifactRollback"},
53
        {State::ArtifactRollbackReboot, "ArtifactRollbackReboot"},
54
        {State::ArtifactFailure, "ArtifactFailure"},
55
};
56

57
unordered_map<const Action, string> action_map {
58
        {Action::Enter, "Enter"},
59
        {Action::Leave, "Leave"},
60
        {Action::Error, "Error"},
61
};
62

63
error::Error CorrectVersionFile(const string &path) {
155✔
64
        // Missing file is OK
65
        // This is because previous versions of the client wrote no
66
        // version file, so no-file=v3
67
        if (!path::FileExists(path)) {
155✔
68
                return error::NoError;
153✔
69
        }
70

71
        ifstream vf {path};
4✔
72

73
        if (!vf) {
2✔
74
                auto errnum {errno};
×
75
                return error::Error(
76
                        generic_category().default_error_condition(errnum), "Failed to open the version file");
×
77
        }
78

79
        string version;
4✔
80
        vf >> version;
2✔
81
        if (!vf) {
2✔
82
                auto errnum {errno};
×
83
                return error::Error(
84
                        generic_category().default_error_condition(errnum),
×
85
                        "Error reading the version number from the version file");
×
86
        }
87

88
        if (version != expected_state_script_version) {
2✔
89
                return executor::MakeError(
90
                        executor::VersionFileError, "Unexpected Artifact script version found: " + version);
2✔
91
        }
92
        return error::NoError;
1✔
93
}
94

95
bool isValidStateScript(const string &file, State state, Action action) {
901✔
96
        string expression {
97
                "(" + state_map.at(state) + ")" + "_(" + action_map.at(action) + ")_[0-9][0-9](_\\S+)?"};
2,703✔
98
        log::Trace(
901✔
99
                "verifying the State script format of the file: " + file
901✔
100
                + " using the regular expression: " + expression);
1,802✔
101
        const regex artifact_script_regexp {expression, std::regex_constants::ECMAScript};
901✔
102
        return regex_match(path::BaseName(file), artifact_script_regexp);
1,802✔
103
}
104

105
function<bool(const string &)> Matcher(State state, Action action) {
236✔
106
        return [state, action](const string &file) {
3,012✔
107
                auto exp_executable = path::IsExecutable(file, false);
4,222✔
108
                if (!exp_executable) {
2,111✔
109
                        log::Debug("Issue figuring the executable bits of: " + exp_executable.error().String());
×
110
                        return false;
×
111
                }
112
                return exp_executable.value() and isValidStateScript(file, state, action);
2,111✔
113
        };
236✔
114
}
115

116
bool IsArtifactScript(State state) {
473✔
117
        switch (state) {
473✔
118
        case State::Idle:
164✔
119
        case State::Sync:
120
        case State::Download:
121
                return false;
164✔
122
        case State::ArtifactInstall:
309✔
123
        case State::ArtifactReboot:
124
        case State::ArtifactCommit:
125
        case State::ArtifactRollback:
126
        case State::ArtifactRollbackReboot:
127
        case State::ArtifactFailure:
128
                return true;
309✔
129
        }
130
        assert(false);
×
131
        return false;
132
}
133

134
string ScriptRunner::ScriptPath(State state) {
236✔
135
        if (IsArtifactScript(state)) {
236✔
136
                return this->artifact_script_path_;
154✔
137
        }
138
        return this->rootfs_script_path_;
82✔
139
}
140

141
string ScriptRunner::Name() {
×
142
        return state_map.at(state_) + action_map.at(action_);
×
143
}
144

145
ScriptRunner::ScriptRunner(
89✔
146
        events::EventLoop &loop,
147
        chrono::seconds state_script_timeout,
148
        const string &artifact_script_path,
149
        const string &rootfs_script_path,
150
        mender::common::processes::OutputCallback stdout_callback,
151
        mender::common::processes::OutputCallback stderr_callback) :
89✔
152
        loop_ {loop},
153
        state_script_timeout_ {state_script_timeout},
154
        artifact_script_path_ {artifact_script_path},
155
        rootfs_script_path_ {rootfs_script_path},
156
        stdout_callback_ {stdout_callback},
157
        stderr_callback_ {stderr_callback},
158
        error_script_error_ {error::NoError} {};
89✔
159

160
void ScriptRunner::LogErrAndExecuteNext(
10✔
161
        Error err,
162
        vector<string>::iterator current_script,
163
        vector<string>::iterator end,
164
        bool ignore_error,
165
        HandlerFunction handler) {
166
        // Collect the error and carry on
167
        if (err.code
10✔
168
                == common::processes::MakeError(common::processes::NonZeroExitStatusError, "").code) {
10✔
169
                this->error_script_error_ = this->error_script_error_.FollowedBy(executor::MakeError(
10✔
170
                        executor::NonZeroExitStatusError,
171
                        "Got non zero exit code from script: " + *current_script));
20✔
172
        } else {
173
                this->error_script_error_ = this->error_script_error_.FollowedBy(err);
×
174
        }
175

176
        // Schedule the next script execution
177
        auto local_err = Execute(std::next(current_script), end, ignore_error, handler);
10✔
178
        if (local_err != error::NoError) {
10✔
179
                return handler(local_err);
×
180
        }
181
}
182

183
void ScriptRunner::HandleScriptError(Error err, HandlerFunction handler) {
10✔
184
        if (err.code
10✔
185
                == common::processes::MakeError(common::processes::NonZeroExitStatusError, "").code) {
10✔
186
                if (this->script_.get()->GetExitStatus() == state_script_retry_exit_code) {
10✔
187
                        return handler(executor::MakeError(
×
188
                                executor::RetryExitCodeError,
189
                                "Received exit code: " + to_string(state_script_retry_exit_code)));
×
190
                }
191
                return handler(executor::MakeError(
10✔
192
                        executor::NonZeroExitStatusError,
193
                        "Received error code: " + to_string(this->script_.get()->GetExitStatus())));
20✔
194
        }
195
        return handler(err);
×
196
}
197

198
Error ScriptRunner::Execute(
303✔
199
        vector<string>::iterator current_script,
200
        vector<string>::iterator end,
201
        bool ignore_error,
202
        HandlerFunction handler) {
203
        // No more scripts to execute
204
        if (current_script == end) {
303✔
205
                handler(this->error_script_error_); // Success
214✔
206
                return error::NoError;
214✔
207
        }
208

209
        log::Info("Running State Script: " + *current_script);
89✔
210

211
        this->script_.reset(new mender::common::processes::Process({*current_script}));
178✔
212
        auto err {this->script_->Start(stdout_callback_, stderr_callback_)};
267✔
213
        if (err != error::NoError) {
89✔
214
                return err;
×
215
        }
216

217
        return this->script_.get()->AsyncWait(
218
                this->loop_,
219
                [this, current_script, end, ignore_error, handler](Error err) {
293✔
220
                        if (err != error::NoError) {
89✔
221
                                if (ignore_error || this->action_ == Action::Error) {
20✔
222
                                        return LogErrAndExecuteNext(err, current_script, end, ignore_error, handler);
20✔
223
                                }
224
                                return HandleScriptError(err, handler);
10✔
225
                        }
226
                        // Schedule the next script execution
227
                        auto local_err = Execute(std::next(current_script), end, ignore_error, handler);
69✔
228
                        if (local_err != error::NoError) {
69✔
229
                                return handler(local_err);
×
230
                        }
231
                },
232
                this->state_script_timeout_);
89✔
233
}
234

235
Error ScriptRunner::AsyncRunScripts(
237✔
236
        State state, Action action, HandlerFunction handler, RunError on_error) {
237
        this->action_ = action;
237✔
238
        this->state_ = state;
237✔
239
        if (IsArtifactScript(state)) {
237✔
240
                // Verify the version in the version file (OK if no version file present)
241
                auto version_file_error {
242
                        CorrectVersionFile(path::Join(this->artifact_script_path_, "version"))};
155✔
243
                if (version_file_error != error::NoError) {
155✔
244
                        return version_file_error;
1✔
245
                }
246
        }
247

248
        // Collect
249
        auto exp_scripts {path::ListFiles(ScriptPath(state_), Matcher(state_, action_))};
708✔
250
        if (!exp_scripts) {
236✔
251
                // Missing directory is OK
252
                if (exp_scripts.error().IsErrno(ENOENT)) {
12✔
253
                        handler(error::NoError);
12✔
254
                        return error::NoError;
12✔
255
                }
256
                return executor::MakeError(
257
                        executor::Code::CollectionError,
258
                        "Failed to get the scripts, error: " + exp_scripts.error().String());
×
259
        }
260

261
        // Sort
262
        {
263
                auto &unsorted_scripts {exp_scripts.value()};
224✔
264

265
                vector<string> sorted_scripts(unsorted_scripts.begin(), unsorted_scripts.end());
448✔
266

267
                sort(sorted_scripts.begin(), sorted_scripts.end());
224✔
268
                this->collected_scripts_ = std::move(sorted_scripts);
224✔
269
        }
270

271
        bool ignore_error = on_error == RunError::Ignore;
224✔
272

273
        // Execute
274
        auto scripts_iterator {this->collected_scripts_.begin()};
224✔
275
        auto scripts_iterator_end {this->collected_scripts_.end()};
224✔
276
        return Execute(scripts_iterator, scripts_iterator_end, ignore_error, handler);
224✔
277
}
278

279

280
Error ScriptRunner::RunScripts(State state, Action action, RunError on_error) {
229✔
281
        auto run_err {error::NoError};
458✔
282
        auto err = AsyncRunScripts(
283
                state,
284
                action,
285
                [this, &run_err](Error error) {
458✔
286
                        run_err = error;
229✔
287
                        this->loop_.Stop();
229✔
288
                },
229✔
289
                on_error);
458✔
290
        if (err != error::NoError) {
229✔
291
                return err;
×
292
        }
293
        this->loop_.Run();
229✔
294
        return run_err;
229✔
295
}
296

297
} // namespace executor
298
} // namespace scripts
299
} // namespace artifact
300
} // namespace mender
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc