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

mendersoftware / mender / 1448115011

10 Sep 2024 10:12AM UTC coverage: 76.326%. Remained the same
1448115011

push

gitlab-ci

web-flow
Merge pull request #1664 from kacf/small_fixes

Smaller fixes to support upcoming commits

9 of 12 new or added lines in 3 files covered. (75.0%)

27 existing lines in 2 files now uncovered.

7367 of 9652 relevant lines covered (76.33%)

11321.93 hits per line

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

77.52
/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
        .long_option = "stop-after",
52
        .description =
53
                "Stop after the given state has completed. "
54
                "Choices are `Download`, `ArtifactInstall` and `ArtifactCommit`. "
55
                "You can later resume the installation by using the `resume` command. "
56
                "Note that the client always stops after `ArtifactInstall` if the update module supports rollback.",
57
        .parameter = "STATE",
58
};
59

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

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

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

93
const conf::CliCommand cmd_resume {
94
        .name = "resume",
95
        .description = "Resume an interrupted installation",
96
        .options =
97
                {
98
                        conf::CliOption {
99
                                .long_option = "reboot-exit-code",
100
                                .description =
101
                                        "Return exit code 4 if a manual reboot is required after the Artifact installation.",
102
                        },
103
                        opt_stop_after,
104
                },
105
};
106

107
const conf::CliCommand cmd_rollback {
108
        .name = "rollback",
109
        .description = "Rollback current Artifact. Returns (2) if no update in progress",
110
        .options =
111
                {
112
                        opt_stop_after,
113
                },
114
};
115

116
const conf::CliCommand cmd_send_inventory {
117
        .name = "send-inventory",
118
        .description = "Force inventory update",
119
};
120

121
const conf::CliCommand cmd_show_artifact {
122
        .name = "show-artifact",
123
        .description = "Print the current artifact name to the command line and exit",
124
};
125

126
const conf::CliCommand cmd_show_provides {
127
        .name = "show-provides",
128
        .description = "Print the current provides to the command line and exit",
129
};
130

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

154
static error::Error CommonInstallFlagsHandler(
83✔
155
        conf::CmdlineOptionsIterator &iter,
156
        string *filename,
157
        bool *reboot_exit_code,
158
        vector<string> *stop_after) {
159
        while (true) {
160
                auto arg = iter.Next();
155✔
161
                if (!arg) {
155✔
162
                        return arg.error();
1✔
163
                }
164

165
                auto value = arg.value();
215✔
166
                if (reboot_exit_code != nullptr and value.option == "--reboot-exit-code") {
154✔
167
                        *reboot_exit_code = true;
1✔
168
                        continue;
11✔
169
                } else if (stop_after != nullptr and value.option == "--stop-after") {
153✔
170
                        if (value.value == "") {
10✔
UNCOV
171
                                return conf::MakeError(conf::InvalidOptionsError, "--stop-after needs an argument");
×
172
                        }
173
                        stop_after->push_back(value.value);
10✔
174
                        continue;
10✔
175
                } else if (value.option != "") {
143✔
UNCOV
176
                        return conf::MakeError(conf::InvalidOptionsError, "No such option: " + value.option);
×
177
                }
178

179
                if (value.value != "") {
143✔
180
                        if (filename == nullptr or *filename != "") {
62✔
181
                                return conf::MakeError(
182
                                        conf::InvalidOptionsError, "Too many arguments: " + value.value);
2✔
183
                        } else {
184
                                *filename = value.value;
185
                        }
186
                } else {
187
                        if (filename != nullptr and *filename == "") {
81✔
188
                                return conf::MakeError(conf::InvalidOptionsError, "Need a path to an artifact");
2✔
189
                        } else {
190
                                break;
191
                        }
192
                }
193
        }
194

195
        return error::NoError;
80✔
196
}
197

198
ExpectedActionPtr ParseUpdateArguments(
130✔
199
        vector<string>::const_iterator start, vector<string>::const_iterator end) {
200
        if (start == end) {
130✔
201
                return expected::unexpected(conf::MakeError(conf::InvalidOptionsError, "Need an action"));
3✔
202
        }
203

204
        bool help_arg = conf::FindCmdlineHelpArg(start + 1, end);
129✔
205
        if (help_arg) {
129✔
UNCOV
206
                conf::PrintCliCommandHelp(cli_mender_update, start[0]);
×
UNCOV
207
                return expected::unexpected(error::MakeError(error::ExitWithSuccessError, ""));
×
208
        }
209

210
        if (start[0] == "show-artifact") {
129✔
211
                conf::CmdlineOptionsIterator iter(start + 1, end, cmd_show_artifact.options);
8✔
212
                auto arg = iter.Next();
4✔
213
                if (!arg) {
4✔
214
                        return expected::unexpected(arg.error());
4✔
215
                }
216

217
                return make_shared<ShowArtifactAction>();
4✔
218
        } else if (start[0] == "show-provides") {
125✔
219
                conf::CmdlineOptionsIterator iter(start + 1, end, cmd_show_provides.options);
84✔
220
                auto arg = iter.Next();
42✔
221
                if (!arg) {
42✔
222
                        return expected::unexpected(arg.error());
4✔
223
                }
224

225
                return make_shared<ShowProvidesAction>();
80✔
226
        } else if (start[0] == "install") {
83✔
227
                conf::CmdlineOptionsIterator iter(start + 1, end, cmd_install.options);
126✔
228
                iter.SetArgumentsMode(conf::ArgumentsMode::AcceptBareArguments);
229

230
                string filename;
231
                bool reboot_exit_code = false;
63✔
232
                vector<string> stop_after;
63✔
233
                auto err = CommonInstallFlagsHandler(iter, &filename, &reboot_exit_code, &stop_after);
63✔
234
                if (err != error::NoError) {
63✔
235
                        return expected::unexpected(err);
6✔
236
                }
237

238
                auto install_action = make_shared<InstallAction>(filename);
60✔
239
                install_action->SetRebootExitCode(reboot_exit_code);
60✔
240
                install_action->SetStopAfter(std::move(stop_after));
60✔
241
                return install_action;
60✔
242
        } else if (start[0] == "resume") {
20✔
243
                conf::CmdlineOptionsIterator iter(start + 1, end, cmd_resume.options);
12✔
244

245
                bool reboot_exit_code = false;
6✔
246
                vector<string> stop_after;
6✔
247
                auto err = CommonInstallFlagsHandler(iter, nullptr, &reboot_exit_code, &stop_after);
6✔
248
                if (err != error::NoError) {
6✔
UNCOV
249
                        return expected::unexpected(err);
×
250
                }
251

252
                auto resume_action = make_shared<ResumeAction>();
6✔
253
                resume_action->SetRebootExitCode(reboot_exit_code);
6✔
254
                resume_action->SetStopAfter(std::move(stop_after));
6✔
255
                return resume_action;
6✔
256
        } else if (start[0] == "commit") {
14✔
257
                conf::CmdlineOptionsIterator iter(start + 1, end, cmd_commit.options);
12✔
258

259
                vector<string> stop_after;
6✔
260
                auto err = CommonInstallFlagsHandler(iter, nullptr, nullptr, &stop_after);
6✔
261
                if (err != error::NoError) {
6✔
UNCOV
262
                        return expected::unexpected(err);
×
263
                }
264

265
                auto commit_action = make_shared<CommitAction>();
6✔
266
                commit_action->SetStopAfter(std::move(stop_after));
6✔
267
                return commit_action;
6✔
268
        } else if (start[0] == "rollback") {
8✔
269
                conf::CmdlineOptionsIterator iter(start + 1, end, cmd_rollback.options);
16✔
270

271
                vector<string> stop_after;
8✔
272
                auto err = CommonInstallFlagsHandler(iter, nullptr, nullptr, &stop_after);
8✔
273
                if (err != error::NoError) {
8✔
274
                        return expected::unexpected(err);
×
275
                }
276

277
                auto rollback_action = make_shared<RollbackAction>();
8✔
278
                rollback_action->SetStopAfter(std::move(stop_after));
8✔
279
                return rollback_action;
8✔
280
        } else if (start[0] == "daemon") {
×
NEW
281
                conf::CmdlineOptionsIterator iter(start + 1, end, cmd_daemon.options);
×
282
                auto arg = iter.Next();
×
UNCOV
283
                if (!arg) {
×
UNCOV
284
                        return expected::unexpected(arg.error());
×
285
                }
286

UNCOV
287
                return make_shared<DaemonAction>();
×
UNCOV
288
        } else if (start[0] == "send-inventory") {
×
NEW
289
                conf::CmdlineOptionsIterator iter(start + 1, end, cmd_send_inventory.options);
×
UNCOV
290
                auto arg = iter.Next();
×
UNCOV
291
                if (!arg) {
×
UNCOV
292
                        return expected::unexpected(arg.error());
×
293
                }
294

UNCOV
295
                return make_shared<SendInventoryAction>();
×
296
        } else if (start[0] == "check-update") {
×
NEW
297
                conf::CmdlineOptionsIterator iter(start + 1, end, cmd_check_update.options);
×
UNCOV
298
                auto arg = iter.Next();
×
UNCOV
299
                if (!arg) {
×
UNCOV
300
                        return expected::unexpected(arg.error());
×
301
                }
302

UNCOV
303
                return make_shared<CheckUpdateAction>();
×
304
        }
305
#ifdef MENDER_EMBED_MENDER_AUTH
306
        // We do not test for this here, because mender-auth has its own Main() function and
307
        // therefore it is inconvenient (it returns int, not an action). So we do it in Main()
308
        // below instead, but semantically it is the same as doing it here.
309
        //
310
        // else if (start[0] == "auth") {
311
        //         ...stuff...
312
#endif
313
        else {
UNCOV
314
                return expected::unexpected(
×
UNCOV
315
                        conf::MakeError(conf::InvalidOptionsError, "No such action: " + start[0]));
×
316
        }
317
}
318

319
static error::Error DoMain(
134✔
320
        const vector<string> &args,
321
        function<void(mender::update::context::MenderContext &ctx)> test_hook) {
322
        mender::client_shared::conf::MenderConfig config;
268✔
323

324
        auto args_pos = config.ProcessCmdlineArgs(args.begin(), args.end(), cli_mender_update);
134✔
325
        if (!args_pos) {
134✔
326
                if (args_pos.error().code != error::MakeError(error::ExitWithSuccessError, "").code) {
4✔
327
                        conf::PrintCliHelp(cli_mender_update);
1✔
328
                }
329
                return args_pos.error();
4✔
330
        }
331

332
        auto action = ParseUpdateArguments(args.begin() + args_pos.value(), args.end());
130✔
333
        if (!action) {
130✔
334
                if (action.error().code != error::MakeError(error::ExitWithSuccessError, "").code) {
8✔
335
                        if (args.size() > 0) {
8✔
336
                                conf::PrintCliCommandHelp(cli_mender_update, args[0]);
8✔
337
                        } else {
UNCOV
338
                                conf::PrintCliHelp(cli_mender_update);
×
339
                        }
340
                }
341
                return action.error();
8✔
342
        }
343

344
        mender::update::context::MenderContext main_context(config);
122✔
345

346
        test_hook(main_context);
122✔
347

348
        auto err = main_context.Initialize();
122✔
349
        if (error::NoError != err) {
122✔
UNCOV
350
                return err;
×
351
        }
352

353
        return action.value()->Execute(main_context);
122✔
354
}
355

356
int Main(
134✔
357
        const vector<string> &args,
358
        function<void(mender::update::context::MenderContext &ctx)> test_hook) {
359
#ifdef MENDER_EMBED_MENDER_AUTH
360
        // Early special treatment for "auth" argument.
361
        if (args.size() > 0 and args[0] == "auth") {
362
                return mender::auth::cli::Main({args.begin() + 1, args.end()});
363
        }
364
#endif
365

366
        auto err = DoMain(args, test_hook);
134✔
367

368
        if (err.code == context::MakeError(context::NoUpdateInProgressError, "").code) {
134✔
369
                return NoUpdateInProgressExitStatus;
370
        } else if (err.code == context::MakeError(context::RebootRequiredError, "").code) {
132✔
371
                return RebootExitStatus;
372
        } else if (err != error::NoError) {
131✔
373
                if (err.code == error::MakeError(error::ExitWithSuccessError, "").code) {
36✔
374
                        return 0;
375
                } else if (err.code != error::MakeError(error::ExitWithFailureError, "").code) {
33✔
376
                        cerr << "Could not fulfill request: " + err.String() << endl;
62✔
377
                }
378
                return 1;
33✔
379
        }
380

381
        return 0;
382
}
383

384
} // namespace cli
385
} // namespace update
386
} // 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