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

mendersoftware / mender / 1034821089

12 Oct 2023 02:46PM UTC coverage: 79.922% (+0.3%) from 79.579%
1034821089

push

gitlab-ci

lluiscampos
fix: Implement `--trusted-certs` and `--skip-verify` cli options

Changelog: None
Ticket: MEN-6679

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

6 of 6 new or added lines in 1 file covered. (100.0%)

6592 of 8248 relevant lines covered (79.92%)

9744.95 hits per line

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

66.67
/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
#include <iostream>
18

19
#include <common/conf.hpp>
20
#include <common/cli.hpp>
21
#include <mender-version.h>
22

23
namespace mender {
24
namespace update {
25
namespace cli {
26

27
namespace conf = mender::common::conf;
28
namespace cli = mender::common::cli;
29

30
const int NoUpdateInProgressExitStatus = 2;
31
const int RebootExitStatus = 4;
32

33
const cli::Command cmd_check_update {
34
        .name = "check-update",
35
        .description = "Force update check",
36
};
37

38
const cli::Command cmd_commit {
39
        .name = "commit",
40
        .description = "Commit current Artifact. Returns (2) if no update in progress",
41
};
42

43
const cli::Command cmd_daemon {
44
        .name = "daemon",
45
        .description = "Start the client as a background service",
46
};
47

48
const cli::Command cmd_install {
49
        .name = "install",
50
        .description = "Mender Artifact to install - local file or a URL",
51
        .options =
52
                {
53
                        cli::Option {
54
                                .long_option = "reboot-exit-code",
55
                                .description =
56
                                        "Return exit code 4 if a manual reboot is required after the Artifact installation",
57
                        },
58
                        // TODO: not implemented
59
                        cli::Option {
60
                                .long_option = "passphrase-file",
61
                                .description =
62
                                        "Passphrase file for decrypting an encrypted private key. '-' loads passphrase from stdin",
63
                        },
64
                },
65
};
66

67
const cli::Command cmd_rollback {
68
        .name = "rollback",
69
        .description = "Rollback current Artifact. Returns (2) if no update in progress",
70
};
71

72
const cli::Command cmd_send_inventory {
73
        .name = "send-inventory",
74
        .description = "Force inventory update",
75
};
76

77
const cli::Command cmd_show_artifact {
78
        .name = "show-artifact",
79
        .description = "Print the current artifact name to the command line and exit",
80
};
81

82
const cli::Command cmd_show_provides {
83
        .name = "show-provides",
84
        .description = "Print the current provides to the command line and exit",
85
};
86

87
const conf::Paths default_paths {};
88

89
const cli::App cli_mender_update = {
90
        .name = "mender-update",
91
        .short_description = "manage and start Mender Update",
92
        .long_description =
93
                R"(mender-update integrates both the mender-auth daemon and commands for manually
94
   performing tasks performed by the daemon (see list of COMMANDS below).
95

96
Global flag remarks:
97
   - Supported log levels incudes: 'debug', 'info', 'warning', 'error', 'panic' and
98
     'fatal'.
99

100
Environment variables:
101
   - MENDER_CONF_DIR - configuration (default: )"
102
                + default_paths.GetPathConfDir() + R"().
103
   - MENDER_DATA_DIR - identity, inventory and update modules (default: )"
104
                + default_paths.GetPathDataDir() + R"().
105
   - MENDER_DATASTORE_DIR - runtime datastore (default: )"
106
                + default_paths.GetDataStore() + R"().)",
107
        .version = string {MENDER_VERSION},
108
        .commands =
109
                {
110
                        cmd_check_update,
111
                        cmd_commit,
112
                        cmd_daemon,
113
                        cmd_install,
114
                        cmd_rollback,
115
                        cmd_send_inventory,
116
                        cmd_show_artifact,
117
                        cmd_show_provides,
118
                },
119
        .global_options =
120
                {
121
                        cli::Option {
122
                                .long_option = "config",
123
                                .short_option = "c",
124
                                .description = "Configuration FILE path",
125
                                .default_value = default_paths.GetConfFile(),
126
                                .parameter = "FILE"},
127
                        cli::Option {
128
                                .long_option = "fallback-config",
129
                                .short_option = "b",
130
                                .description = "Fallback configuration FILE path",
131
                                .default_value = default_paths.GetFallbackConfFile(),
132
                                .parameter = "FILE"},
133
                        cli::Option {
134
                                .long_option = "data",
135
                                .short_option = "d",
136
                                .description = "Mender state data DIRECTORY path",
137
                                .default_value = default_paths.GetPathDataDir(),
138
                                .parameter = "DIR"},
139
                        cli::Option {
140
                                .long_option = "log-file",
141
                                .short_option = "L",
142
                                .description = "FILE to log to",
143
                                .parameter = "FILE"},
144
                        cli::Option {
145
                                .long_option = "log-level",
146
                                .short_option = "l",
147
                                .description = "Set logging level",
148
                                .default_value = "info",
149
                        },
150
                        cli::Option {
151
                                .long_option = "trusted-certs",
152
                                .short_option = "E",
153
                                .description = "Trusted server certificates FILE path",
154
                                .parameter = "FILE"},
155
                        // TODO: not implemented
156
                        cli::Option {
157
                                .long_option = "no-syslog",
158
                                .description = "Disable logging to syslog",
159
                        },
160
                        cli::Option {
161
                                .long_option = "skipverify",
162
                                .description = "Skip certificate verification",
163
                        },
164
                },
165
};
166

167
ExpectedActionPtr ParseUpdateArguments(
96✔
168
        vector<string>::const_iterator start, vector<string>::const_iterator end) {
169
        if (start == end) {
96✔
170
                return expected::unexpected(conf::MakeError(conf::InvalidOptionsError, "Need an action"));
3✔
171
        }
172

173
        conf::CmdlineOptionsIterator opts_iter(
174
                start + 1,
175
                end,
176
                {},
177
                {
178
                        "--help",
179
                        "-h",
180
                });
665✔
181
        auto ex_opt_val = opts_iter.Next();
95✔
182

183
        bool help_arg = false;
184
        while (ex_opt_val && ((ex_opt_val.value().option != "") || (ex_opt_val.value().value != ""))) {
95✔
185
                auto opt_val = ex_opt_val.value();
×
186
                if ((opt_val.option == "--help") || (opt_val.option == "-h")) {
×
187
                        help_arg = true;
188
                        break;
×
189
                }
190
                ex_opt_val = opts_iter.Next();
×
191
        }
192

193
        if (help_arg) {
194
                cli::PrintCliCommandHelp(cli_mender_update, start[0]);
×
195
                return expected::unexpected(error::MakeError(error::ExitWithSuccessError, ""));
×
196
        }
197

198
        if (start[0] == "show-artifact") {
95✔
199
                unordered_set<string> options {};
4✔
200
                conf::CmdlineOptionsIterator iter(start + 1, end, options, options);
8✔
201
                auto arg = iter.Next();
4✔
202
                if (!arg) {
4✔
203
                        return expected::unexpected(arg.error());
4✔
204
                }
205

206
                return make_shared<ShowArtifactAction>();
4✔
207
        } else if (start[0] == "show-provides") {
91✔
208
                unordered_set<string> options {};
34✔
209
                conf::CmdlineOptionsIterator iter(start + 1, end, options, options);
68✔
210
                auto arg = iter.Next();
34✔
211
                if (!arg) {
34✔
212
                        return expected::unexpected(arg.error());
4✔
213
                }
214

215
                return make_shared<ShowProvidesAction>();
64✔
216
        } else if (start[0] == "install") {
57✔
217
                unordered_set<string> options {};
47✔
218
                conf::CmdlineOptionsIterator iter(start + 1, end, options, {"--reboot-exit-code"});
282✔
219
                iter.SetArgumentsMode(conf::ArgumentsMode::AcceptBareArguments);
220

221
                string filename;
222
                bool reboot_exit_code = false;
47✔
223
                while (true) {
224
                        auto arg = iter.Next();
93✔
225
                        if (!arg) {
93✔
226
                                return expected::unexpected(arg.error());
2✔
227
                        }
228

229
                        auto value = arg.value();
137✔
230
                        if (value.option == "--reboot-exit-code") {
92✔
231
                                reboot_exit_code = true;
1✔
232
                                continue;
1✔
233
                        } else if (value.option != "") {
91✔
234
                                return expected::unexpected(
×
235
                                        conf::MakeError(conf::InvalidOptionsError, "No such option: " + value.option));
×
236
                        }
237

238
                        if (value.value != "") {
91✔
239
                                if (filename != "") {
46✔
240
                                        return expected::unexpected(conf::MakeError(
1✔
241
                                                conf::InvalidOptionsError, "Too many arguments: " + value.value));
3✔
242
                                } else {
243
                                        filename = value.value;
244
                                }
245
                        } else {
246
                                if (filename == "") {
45✔
247
                                        return expected::unexpected(
1✔
248
                                                conf::MakeError(conf::InvalidOptionsError, "Need a path to an artifact"));
3✔
249
                                } else {
250
                                        break;
251
                                }
252
                        }
253
                }
254

255
                return make_shared<InstallAction>(filename, reboot_exit_code);
88✔
256
        } else if (start[0] == "commit") {
10✔
257
                unordered_set<string> options {};
4✔
258
                conf::CmdlineOptionsIterator iter(start + 1, end, options, options);
8✔
259
                auto arg = iter.Next();
4✔
260
                if (!arg) {
4✔
261
                        return expected::unexpected(arg.error());
×
262
                }
263

264
                return make_shared<CommitAction>();
8✔
265
        } else if (start[0] == "rollback") {
6✔
266
                unordered_set<string> options {};
6✔
267
                conf::CmdlineOptionsIterator iter(start + 1, end, options, options);
12✔
268
                auto arg = iter.Next();
6✔
269
                if (!arg) {
6✔
270
                        return expected::unexpected(arg.error());
×
271
                }
272

273
                return make_shared<RollbackAction>();
12✔
274
        } else if (start[0] == "daemon") {
×
275
                unordered_set<string> options {};
×
276
                conf::CmdlineOptionsIterator iter(start + 1, end, options, options);
×
277
                auto arg = iter.Next();
×
278
                if (!arg) {
×
279
                        return expected::unexpected(arg.error());
×
280
                }
281

282
                return make_shared<DaemonAction>();
×
283
        } else if (start[0] == "send-inventory") {
×
284
                unordered_set<string> options {};
×
285
                conf::CmdlineOptionsIterator iter(start + 1, end, options, options);
×
286
                auto arg = iter.Next();
×
287
                if (!arg) {
×
288
                        return expected::unexpected(arg.error());
×
289
                }
290

291
                return make_shared<SendInventoryAction>();
×
292
        } else if (start[0] == "check-update") {
×
293
                unordered_set<string> options {};
×
294
                conf::CmdlineOptionsIterator iter(start + 1, end, options, options);
×
295
                auto arg = iter.Next();
×
296
                if (!arg) {
×
297
                        return expected::unexpected(arg.error());
×
298
                }
299

300
                return make_shared<CheckUpdateAction>();
×
301
        } else {
302
                return expected::unexpected(
×
303
                        conf::MakeError(conf::InvalidOptionsError, "No such action: " + start[0]));
×
304
        }
305
}
306

307
static error::Error DoMain(
96✔
308
        const vector<string> &args,
309
        function<void(mender::update::context::MenderContext &ctx)> test_hook) {
310
        mender::common::conf::MenderConfig config;
192✔
311

312
        auto args_pos = config.ProcessCmdlineArgs(args.begin(), args.end(), cli_mender_update);
96✔
313
        if (!args_pos) {
96✔
314
                if (args_pos.error().code != error::MakeError(error::ExitWithSuccessError, "").code) {
×
315
                        cli::PrintCliHelp(cli_mender_update);
×
316
                }
317
                return args_pos.error();
×
318
        }
319

320
        auto action = ParseUpdateArguments(args.begin() + args_pos.value(), args.end());
96✔
321
        if (!action) {
96✔
322
                if (action.error().code != error::MakeError(error::ExitWithSuccessError, "").code) {
8✔
323
                        if (args.size() > 0) {
8✔
324
                                cli::PrintCliCommandHelp(cli_mender_update, args[0]);
8✔
325
                        } else {
326
                                cli::PrintCliHelp(cli_mender_update);
×
327
                        }
328
                }
329
                return action.error();
8✔
330
        }
331

332
        mender::update::context::MenderContext main_context(config);
88✔
333

334
        test_hook(main_context);
88✔
335

336
        auto err = main_context.Initialize();
88✔
337
        if (error::NoError != err) {
88✔
338
                return err;
×
339
        }
340

341
        return action.value()->Execute(main_context);
88✔
342
}
343

344
int Main(
96✔
345
        const vector<string> &args,
346
        function<void(mender::update::context::MenderContext &ctx)> test_hook) {
347
        auto err = DoMain(args, test_hook);
96✔
348

349
        if (err.code == context::MakeError(context::NoUpdateInProgressError, "").code) {
96✔
350
                return NoUpdateInProgressExitStatus;
351
        } else if (err.code == context::MakeError(context::RebootRequiredError, "").code) {
94✔
352
                return RebootExitStatus;
353
        } else if (err != error::NoError) {
93✔
354
                if (err.code == error::MakeError(error::ExitWithSuccessError, "").code) {
26✔
355
                        return 0;
356
                } else if (err.code != error::MakeError(error::ExitWithFailureError, "").code) {
26✔
357
                        cerr << "Could not fulfill request: " + err.String() << endl;
40✔
358
                }
359
                return 1;
26✔
360
        }
361

362
        return 0;
363
}
364

365
} // namespace cli
366
} // namespace update
367
} // 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