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

mendersoftware / mender / 1492015618

11 Oct 2024 01:45PM UTC coverage: 76.332% (-0.04%) from 76.372%
1492015618

push

gitlab-ci

web-flow
Merge pull request #1675 from aduskett/shellcheck-fixups

fix: cleanup shellcheck warnings for rootfs-image module

7308 of 9574 relevant lines covered (76.33%)

11298.87 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_before {
51
        .long_option = "stop-before",
52
        .description =
53
                "Stop before entering the given state. "
54
                "Choices are "
55
                "`ArtifactInstall_Enter`, "
56
                "`ArtifactCommit_Enter`, "
57
                "`ArtifactCommit_Leave`, "
58
                "`ArtifactRollback_Enter`, "
59
                "`ArtifactFailure_Enter`, "
60
                "and `Cleanup`. "
61
                "You can later resume the installation by using the `resume` command. "
62
                "Note that the client always stops after `ArtifactInstall` if the update module supports rollback.",
63
        .parameter = "STATE",
64
};
65

66
const conf::CliCommand cmd_commit {
67
        .name = "commit",
68
        .description = "Commit current Artifact. Returns (2) if no update in progress",
69
        .options =
70
                {
71
                        opt_stop_before,
72
                },
73
};
74

75
const conf::CliCommand cmd_daemon {
76
        .name = "daemon",
77
        .description = "Start the client as a background service",
78
};
79

80
const conf::CliCommand cmd_install {
81
        .name = "install",
82
        .description = "Mender Artifact to install - local file or a URL",
83
        .argument =
84
                conf::CliArgument {
85
                        .name = "artifact",
86
                        .mandatory = true,
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
                        opt_stop_before,
96
                },
97
};
98

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

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

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

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

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

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

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

171
                auto value = arg.value();
228✔
172
                if (reboot_exit_code != nullptr and value.option == "--reboot-exit-code") {
167✔
173
                        *reboot_exit_code = true;
1✔
174
                        continue;
17✔
175
                } else if (stop_before != nullptr and value.option == "--stop-before") {
166✔
176
                        if (value.value == "") {
16✔
177
                                return conf::MakeError(
178
                                        conf::InvalidOptionsError, "--stop-before needs an argument");
×
179
                        }
180
                        stop_before->push_back(value.value);
16✔
181
                        continue;
16✔
182
                } else if (value.option != "") {
150✔
183
                        return conf::MakeError(conf::InvalidOptionsError, "No such option: " + value.option);
×
184
                }
185

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

202
        return error::NoError;
87✔
203
}
204

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

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

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

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

232
                return make_shared<ShowProvidesAction>();
80✔
233
        } else if (start[0] == "install") {
90✔
234
                conf::CmdlineOptionsIterator iter(start + 1, end, cmd_install.options);
126✔
235
                iter.SetArgumentsMode(conf::ArgumentsMode::AcceptBareArguments);
236

237
                string filename;
238
                bool reboot_exit_code = false;
63✔
239
                vector<string> stop_before;
63✔
240
                auto err = CommonInstallFlagsHandler(iter, &filename, &reboot_exit_code, &stop_before);
63✔
241
                if (err != error::NoError) {
63✔
242
                        return expected::unexpected(err);
6✔
243
                }
244

245
                auto install_action = make_shared<InstallAction>(filename);
60✔
246
                install_action->SetRebootExitCode(reboot_exit_code);
60✔
247
                install_action->SetStopBefore(std::move(stop_before));
60✔
248
                return install_action;
60✔
249
        } else if (start[0] == "resume") {
27✔
250
                conf::CmdlineOptionsIterator iter(start + 1, end, cmd_resume.options);
26✔
251

252
                bool reboot_exit_code = false;
13✔
253
                vector<string> stop_before;
13✔
254
                auto err = CommonInstallFlagsHandler(iter, nullptr, &reboot_exit_code, &stop_before);
13✔
255
                if (err != error::NoError) {
13✔
256
                        return expected::unexpected(err);
×
257
                }
258

259
                auto resume_action = make_shared<ResumeAction>();
13✔
260
                resume_action->SetRebootExitCode(reboot_exit_code);
13✔
261
                resume_action->SetStopBefore(std::move(stop_before));
13✔
262
                return resume_action;
13✔
263
        } else if (start[0] == "commit") {
14✔
264
                conf::CmdlineOptionsIterator iter(start + 1, end, cmd_commit.options);
12✔
265

266
                vector<string> stop_before;
6✔
267
                auto err = CommonInstallFlagsHandler(iter, nullptr, nullptr, &stop_before);
6✔
268
                if (err != error::NoError) {
6✔
269
                        return expected::unexpected(err);
×
270
                }
271

272
                auto commit_action = make_shared<CommitAction>();
6✔
273
                commit_action->SetStopBefore(std::move(stop_before));
6✔
274
                return commit_action;
6✔
275
        } else if (start[0] == "rollback") {
8✔
276
                conf::CmdlineOptionsIterator iter(start + 1, end, cmd_rollback.options);
16✔
277

278
                vector<string> stop_before;
8✔
279
                auto err = CommonInstallFlagsHandler(iter, nullptr, nullptr, &stop_before);
8✔
280
                if (err != error::NoError) {
8✔
281
                        return expected::unexpected(err);
×
282
                }
283

284
                auto rollback_action = make_shared<RollbackAction>();
8✔
285
                rollback_action->SetStopBefore(std::move(stop_before));
8✔
286
                return rollback_action;
8✔
287
        } else if (start[0] == "daemon") {
×
288
                conf::CmdlineOptionsIterator iter(start + 1, end, cmd_daemon.options);
×
289
                auto arg = iter.Next();
×
290
                if (!arg) {
×
291
                        return expected::unexpected(arg.error());
×
292
                }
293

294
                return make_shared<DaemonAction>();
×
295
        } else if (start[0] == "send-inventory") {
×
296
                conf::CmdlineOptionsIterator iter(start + 1, end, cmd_send_inventory.options);
×
297
                auto arg = iter.Next();
×
298
                if (!arg) {
×
299
                        return expected::unexpected(arg.error());
×
300
                }
301

302
                return make_shared<SendInventoryAction>();
×
303
        } else if (start[0] == "check-update") {
×
304
                conf::CmdlineOptionsIterator iter(start + 1, end, cmd_check_update.options);
×
305
                auto arg = iter.Next();
×
306
                if (!arg) {
×
307
                        return expected::unexpected(arg.error());
×
308
                }
309

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

326
static error::Error DoMain(
141✔
327
        const vector<string> &args,
328
        function<void(mender::update::context::MenderContext &ctx)> test_hook) {
329
        mender::client_shared::conf::MenderConfig config;
282✔
330

331
        auto args_pos = config.ProcessCmdlineArgs(args.begin(), args.end(), cli_mender_update);
141✔
332
        if (!args_pos) {
141✔
333
                if (args_pos.error().code != error::MakeError(error::ExitWithSuccessError, "").code) {
4✔
334
                        conf::PrintCliHelp(cli_mender_update);
1✔
335
                }
336
                return args_pos.error();
4✔
337
        }
338

339
        auto action = ParseUpdateArguments(args.begin() + args_pos.value(), args.end());
137✔
340
        if (!action) {
137✔
341
                if (action.error().code != error::MakeError(error::ExitWithSuccessError, "").code) {
8✔
342
                        if (args.size() > 0) {
8✔
343
                                conf::PrintCliCommandHelp(cli_mender_update, args[0]);
8✔
344
                        } else {
345
                                conf::PrintCliHelp(cli_mender_update);
×
346
                        }
347
                }
348
                return action.error();
8✔
349
        }
350

351
        mender::update::context::MenderContext main_context(config);
129✔
352

353
        test_hook(main_context);
129✔
354

355
        auto err = main_context.Initialize();
129✔
356
        if (error::NoError != err) {
129✔
357
                return err;
×
358
        }
359

360
        return action.value()->Execute(main_context);
129✔
361
}
362

363
int Main(
141✔
364
        const vector<string> &args,
365
        function<void(mender::update::context::MenderContext &ctx)> test_hook) {
366
#ifdef MENDER_EMBED_MENDER_AUTH
367
        // Early special treatment for "auth" argument.
368
        if (args.size() > 0 and args[0] == "auth") {
369
                return mender::auth::cli::Main({args.begin() + 1, args.end()});
370
        }
371
#endif
372

373
        auto err = DoMain(args, test_hook);
141✔
374

375
        if (err.code == context::MakeError(context::NoUpdateInProgressError, "").code) {
141✔
376
                return NoUpdateInProgressExitStatus;
377
        } else if (err.code == context::MakeError(context::RebootRequiredError, "").code) {
139✔
378
                return RebootExitStatus;
379
        } else if (err != error::NoError) {
138✔
380
                if (err.code == error::MakeError(error::ExitWithSuccessError, "").code) {
36✔
381
                        return 0;
382
                } else if (err.code != error::MakeError(error::ExitWithFailureError, "").code) {
33✔
383
                        cerr << "Could not fulfill request: " + err.String() << endl;
62✔
384
                }
385
                return 1;
33✔
386
        }
387

388
        return 0;
389
}
390

391
} // namespace cli
392
} // namespace update
393
} // 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