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

mendersoftware / mender / 1430702423

28 Aug 2024 02:32PM UTC coverage: 76.323% (+0.6%) from 75.74%
1430702423

push

gitlab-ci

web-flow
Merge pull request #1642 from kacf/standalone_state_machine

refac: Add --stop-after flag and make state machine for standalone.

547 of 664 new or added lines in 9 files covered. (82.38%)

1 existing line in 1 file now uncovered.

7369 of 9655 relevant lines covered (76.32%)

11318.18 hits per line

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

77.86
/src/mender-update/cli/cli.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 <mender-update/cli/cli.hpp>
16

17
#ifdef MENDER_EMBED_MENDER_AUTH
18
#include <mender-auth/cli/cli.hpp>
19
#endif
20

21
#include <iostream>
22

23
#include <client_shared/conf.hpp>
24
#include <common/error.hpp>
25
#include <common/expected.hpp>
26

27
namespace mender {
28
namespace update {
29
namespace cli {
30

31
namespace conf = mender::client_shared::conf;
32
namespace error = mender::common::error;
33
namespace expected = mender::common::expected;
34

35
const int NoUpdateInProgressExitStatus = 2;
36
const int RebootExitStatus = 4;
37

38
#ifdef MENDER_EMBED_MENDER_AUTH
39
const conf::CliCommand cmd_auth {
40
        .name = "auth",
41
        .description = "Access built-in mender-auth commands (experimental).",
42
};
43
#endif
44

45
const conf::CliCommand cmd_check_update {
46
        .name = "check-update",
47
        .description = "Force update check",
48
};
49

50
const conf::CliOption opt_stop_after {
51
        conf::CliOption {
52
                .long_option = "stop-after",
53
                .description =
54
                        "Stop after the given state has completed. "
55
                        "Choices are `Download`, `ArtifactInstall` and `ArtifactCommit`. "
56
                        "You can later resume the installation by using the `resume` command. "
57
                        "Note that the client always stops after `ArtifactInstall` if the update module supports rollback.",
58
                .parameter = "STATE",
59
        },
60
};
61

62
const conf::CliCommand cmd_commit {
63
        .name = "commit",
64
        .description = "Commit current Artifact. Returns (2) if no update in progress",
65
        .options_w_values =
66
                {
67
                        opt_stop_after,
68
                },
69
};
70

71
const conf::CliCommand cmd_daemon {
72
        .name = "daemon",
73
        .description = "Start the client as a background service",
74
};
75

76
const conf::CliCommand cmd_install {
77
        .name = "install",
78
        .description = "Mender Artifact to install - local file or a URL",
79
        .argument =
80
                conf::CliArgument {
81
                        .name = "artifact",
82
                        .mandatory = true,
83
                },
84
        .options_w_values =
85
                {
86
                        opt_stop_after,
87
                },
88
        .options =
89
                {
90
                        conf::CliOption {
91
                                .long_option = "reboot-exit-code",
92
                                .description =
93
                                        "Return exit code 4 if a manual reboot is required after the Artifact installation.",
94
                        },
95
                },
96
};
97

98
const conf::CliCommand cmd_resume {
99
        .name = "install",
100
        .description = "Resume an interrupted installation",
101
        .options_w_values =
102
                {
103
                        opt_stop_after,
104
                },
105
        .options =
106
                {
107
                        conf::CliOption {
108
                                .long_option = "reboot-exit-code",
109
                                .description =
110
                                        "Return exit code 4 if a manual reboot is required after the Artifact installation.",
111
                        },
112
                },
113
};
114

115
const conf::CliCommand cmd_rollback {
116
        .name = "rollback",
117
        .description = "Rollback current Artifact. Returns (2) if no update in progress",
118
        .options_w_values =
119
                {
120
                        opt_stop_after,
121
                },
122
};
123

124
const conf::CliCommand cmd_send_inventory {
125
        .name = "send-inventory",
126
        .description = "Force inventory update",
127
};
128

129
const conf::CliCommand cmd_show_artifact {
130
        .name = "show-artifact",
131
        .description = "Print the current artifact name to the command line and exit",
132
};
133

134
const conf::CliCommand cmd_show_provides {
135
        .name = "show-provides",
136
        .description = "Print the current provides to the command line and exit",
137
};
138

139
const conf::CliApp cli_mender_update = {
140
        .name = "mender-update",
141
        .short_description = "manage and start Mender Update",
142
        .long_description =
143
                R"(mender-update integrates both the mender-auth daemon and commands for manually
144
   performing tasks performed by the daemon (see list of COMMANDS below).)",
145
        .commands =
146
                {
147
#ifdef MENDER_EMBED_MENDER_AUTH
148
                        cmd_auth,
149
#endif
150
                        cmd_check_update,
151
                        cmd_commit,
152
                        cmd_daemon,
153
                        cmd_install,
154
                        cmd_resume,
155
                        cmd_rollback,
156
                        cmd_send_inventory,
157
                        cmd_show_artifact,
158
                        cmd_show_provides,
159
                },
160
};
161

162
static error::Error CommonInstallFlagsHandler(
83✔
163
        conf::CmdlineOptionsIterator &iter,
164
        string *filename,
165
        bool *reboot_exit_code,
166
        vector<string> *stop_after) {
167
        while (true) {
168
                auto arg = iter.Next();
155✔
169
                if (!arg) {
155✔
170
                        return arg.error();
1✔
171
                }
172

173
                auto value = arg.value();
215✔
174
                if (reboot_exit_code != nullptr and value.option == "--reboot-exit-code") {
154✔
175
                        *reboot_exit_code = true;
1✔
176
                        continue;
11✔
177
                } else if (stop_after != nullptr and value.option == "--stop-after") {
153✔
178
                        if (value.value == "") {
10✔
NEW
179
                                return conf::MakeError(conf::InvalidOptionsError, "--stop-after needs an argument");
×
180
                        }
181
                        stop_after->push_back(value.value);
10✔
182
                        continue;
10✔
183
                } else if (value.option != "") {
143✔
NEW
184
                        return conf::MakeError(conf::InvalidOptionsError, "No such option: " + value.option);
×
185
                }
186

187
                if (value.value != "") {
143✔
188
                        if (filename == nullptr or *filename != "") {
62✔
189
                                return conf::MakeError(
190
                                        conf::InvalidOptionsError, "Too many arguments: " + value.value);
2✔
191
                        } else {
192
                                *filename = value.value;
193
                        }
194
                } else {
195
                        if (filename != nullptr and *filename == "") {
81✔
196
                                return conf::MakeError(conf::InvalidOptionsError, "Need a path to an artifact");
2✔
197
                        } else {
198
                                break;
199
                        }
200
                }
201
        }
202

203
        return error::NoError;
80✔
204
}
205

206
ExpectedActionPtr ParseUpdateArguments(
130✔
207
        vector<string>::const_iterator start, vector<string>::const_iterator end) {
208
        if (start == end) {
130✔
209
                return expected::unexpected(conf::MakeError(conf::InvalidOptionsError, "Need an action"));
3✔
210
        }
211

212
        bool help_arg = conf::FindCmdlineHelpArg(start + 1, end);
129✔
213
        if (help_arg) {
129✔
214
                conf::PrintCliCommandHelp(cli_mender_update, start[0]);
×
215
                return expected::unexpected(error::MakeError(error::ExitWithSuccessError, ""));
×
216
        }
217

218
        if (start[0] == "show-artifact") {
129✔
219
                conf::CmdlineOptionsIterator iter(start + 1, end, {}, {});
12✔
220
                auto arg = iter.Next();
4✔
221
                if (!arg) {
4✔
222
                        return expected::unexpected(arg.error());
4✔
223
                }
224

225
                return make_shared<ShowArtifactAction>();
4✔
226
        } else if (start[0] == "show-provides") {
125✔
227
                conf::CmdlineOptionsIterator iter(start + 1, end, {}, {});
126✔
228
                auto arg = iter.Next();
42✔
229
                if (!arg) {
42✔
230
                        return expected::unexpected(arg.error());
4✔
231
                }
232

233
                return make_shared<ShowProvidesAction>();
80✔
234
        } else if (start[0] == "install") {
83✔
235
                conf::CmdlineOptionsIterator iter(
236
                        start + 1,
237
                        end,
238
                        conf::CommandOptsSetWithValue(cmd_install.options_w_values),
126✔
239
                        conf::CommandOptsSetWithoutValue(cmd_install.options));
252✔
240
                iter.SetArgumentsMode(conf::ArgumentsMode::AcceptBareArguments);
241

242
                string filename;
243
                bool reboot_exit_code = false;
63✔
244
                vector<string> stop_after;
63✔
245
                auto err = CommonInstallFlagsHandler(iter, &filename, &reboot_exit_code, &stop_after);
63✔
246
                if (err != error::NoError) {
63✔
247
                        return expected::unexpected(err);
6✔
248
                }
249

250
                auto install_action = make_shared<InstallAction>(filename);
60✔
251
                install_action->SetRebootExitCode(reboot_exit_code);
60✔
252
                install_action->SetStopAfter(std::move(stop_after));
60✔
253
                return install_action;
60✔
254
        } else if (start[0] == "resume") {
20✔
255
                conf::CmdlineOptionsIterator iter(
256
                        start + 1,
257
                        end,
258
                        conf::CommandOptsSetWithValue(cmd_resume.options_w_values),
12✔
259
                        conf::CommandOptsSetWithoutValue(cmd_resume.options));
18✔
260

261
                bool reboot_exit_code = false;
6✔
262
                vector<string> stop_after;
6✔
263
                auto err = CommonInstallFlagsHandler(iter, nullptr, &reboot_exit_code, &stop_after);
6✔
264
                if (err != error::NoError) {
6✔
NEW
265
                        return expected::unexpected(err);
×
266
                }
267

268
                auto resume_action = make_shared<ResumeAction>();
6✔
269
                resume_action->SetRebootExitCode(reboot_exit_code);
6✔
270
                resume_action->SetStopAfter(std::move(stop_after));
6✔
271
                return resume_action;
6✔
272
        } else if (start[0] == "commit") {
14✔
273
                conf::CmdlineOptionsIterator iter(
274
                        start + 1, end, conf::CommandOptsSetWithValue(cmd_commit.options_w_values), {});
18✔
275

276
                vector<string> stop_after;
6✔
277
                auto err = CommonInstallFlagsHandler(iter, nullptr, nullptr, &stop_after);
6✔
278
                if (err != error::NoError) {
6✔
NEW
279
                        return expected::unexpected(err);
×
280
                }
281

282
                auto commit_action = make_shared<CommitAction>();
6✔
283
                commit_action->SetStopAfter(std::move(stop_after));
6✔
284
                return commit_action;
6✔
285
        } else if (start[0] == "rollback") {
8✔
286
                conf::CmdlineOptionsIterator iter(
287
                        start + 1, end, conf::CommandOptsSetWithValue(cmd_rollback.options_w_values), {});
24✔
288

289
                vector<string> stop_after;
8✔
290
                auto err = CommonInstallFlagsHandler(iter, nullptr, nullptr, &stop_after);
8✔
291
                if (err != error::NoError) {
8✔
NEW
292
                        return expected::unexpected(err);
×
293
                }
294

295
                auto rollback_action = make_shared<RollbackAction>();
8✔
296
                rollback_action->SetStopAfter(std::move(stop_after));
8✔
297
                return rollback_action;
8✔
298
        } else if (start[0] == "daemon") {
×
299
                conf::CmdlineOptionsIterator iter(start + 1, end, {}, {});
×
300
                auto arg = iter.Next();
×
301
                if (!arg) {
×
302
                        return expected::unexpected(arg.error());
×
303
                }
304

305
                return make_shared<DaemonAction>();
×
306
        } else if (start[0] == "send-inventory") {
×
307
                conf::CmdlineOptionsIterator iter(start + 1, end, {}, {});
×
308
                auto arg = iter.Next();
×
309
                if (!arg) {
×
310
                        return expected::unexpected(arg.error());
×
311
                }
312

313
                return make_shared<SendInventoryAction>();
×
314
        } else if (start[0] == "check-update") {
×
315
                conf::CmdlineOptionsIterator iter(start + 1, end, {}, {});
×
316
                auto arg = iter.Next();
×
317
                if (!arg) {
×
318
                        return expected::unexpected(arg.error());
×
319
                }
320

321
                return make_shared<CheckUpdateAction>();
×
322
        }
323
#ifdef MENDER_EMBED_MENDER_AUTH
324
        // We do not test for this here, because mender-auth has its own Main() function and
325
        // therefore it is inconvenient (it returns int, not an action). So we do it in Main()
326
        // below instead, but semantically it is the same as doing it here.
327
        //
328
        // else if (start[0] == "auth") {
329
        //         ...stuff...
330
#endif
331
        else {
332
                return expected::unexpected(
×
333
                        conf::MakeError(conf::InvalidOptionsError, "No such action: " + start[0]));
×
334
        }
335
}
336

337
static error::Error DoMain(
134✔
338
        const vector<string> &args,
339
        function<void(mender::update::context::MenderContext &ctx)> test_hook) {
340
        mender::client_shared::conf::MenderConfig config;
268✔
341

342
        auto args_pos = config.ProcessCmdlineArgs(args.begin(), args.end(), cli_mender_update);
134✔
343
        if (!args_pos) {
134✔
344
                if (args_pos.error().code != error::MakeError(error::ExitWithSuccessError, "").code) {
4✔
345
                        conf::PrintCliHelp(cli_mender_update);
1✔
346
                }
347
                return args_pos.error();
4✔
348
        }
349

350
        auto action = ParseUpdateArguments(args.begin() + args_pos.value(), args.end());
130✔
351
        if (!action) {
130✔
352
                if (action.error().code != error::MakeError(error::ExitWithSuccessError, "").code) {
8✔
353
                        if (args.size() > 0) {
8✔
354
                                conf::PrintCliCommandHelp(cli_mender_update, args[0]);
8✔
355
                        } else {
356
                                conf::PrintCliHelp(cli_mender_update);
×
357
                        }
358
                }
359
                return action.error();
8✔
360
        }
361

362
        mender::update::context::MenderContext main_context(config);
122✔
363

364
        test_hook(main_context);
122✔
365

366
        auto err = main_context.Initialize();
122✔
367
        if (error::NoError != err) {
122✔
368
                return err;
×
369
        }
370

371
        return action.value()->Execute(main_context);
122✔
372
}
373

374
int Main(
134✔
375
        const vector<string> &args,
376
        function<void(mender::update::context::MenderContext &ctx)> test_hook) {
377
#ifdef MENDER_EMBED_MENDER_AUTH
378
        // Early special treatment for "auth" argument.
379
        if (args.size() > 0 and args[0] == "auth") {
380
                return mender::auth::cli::Main({args.begin() + 1, args.end()});
381
        }
382
#endif
383

384
        auto err = DoMain(args, test_hook);
134✔
385

386
        if (err.code == context::MakeError(context::NoUpdateInProgressError, "").code) {
134✔
387
                return NoUpdateInProgressExitStatus;
388
        } else if (err.code == context::MakeError(context::RebootRequiredError, "").code) {
132✔
389
                return RebootExitStatus;
390
        } else if (err != error::NoError) {
131✔
391
                if (err.code == error::MakeError(error::ExitWithSuccessError, "").code) {
36✔
392
                        return 0;
393
                } else if (err.code != error::MakeError(error::ExitWithFailureError, "").code) {
33✔
394
                        cerr << "Could not fulfill request: " + err.String() << endl;
62✔
395
                }
396
                return 1;
33✔
397
        }
398

399
        return 0;
400
}
401

402
} // namespace cli
403
} // namespace update
404
} // 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

© 2026 Coveralls, Inc