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

mendersoftware / mender / 1035858476

13 Oct 2023 11:49AM UTC coverage: 79.922% (+0.001%) from 79.921%
1035858476

push

gitlab-ci

lluiscampos
refac: Refactor the whole `common::cli` inside `common::conf`

The first had some problems, like the interdependency between
`common::cli` and `common:conf` and the horrible code duplication
between `mender::upate::cli` and `mender::auth::cli`.

From a different point of view, the parsing of the (common) CLI options
is done in `common::conf` for both tools, which suggest that there is
reason to have everything in `common::conf` in the first place.

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

24 of 24 new or added lines in 4 files covered. (100.0%)

6592 of 8248 relevant lines covered (79.92%)

9745.91 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

21
namespace mender {
22
namespace update {
23
namespace cli {
24

25
namespace conf = mender::common::conf;
26

27
const int NoUpdateInProgressExitStatus = 2;
28
const int RebootExitStatus = 4;
29

30
const conf::CliCommand cmd_check_update {
31
        .name = "check-update",
32
        .description = "Force update check",
33
};
34

35
const conf::CliCommand cmd_commit {
36
        .name = "commit",
37
        .description = "Commit current Artifact. Returns (2) if no update in progress",
38
};
39

40
const conf::CliCommand cmd_daemon {
41
        .name = "daemon",
42
        .description = "Start the client as a background service",
43
};
44

45
const conf::CliCommand cmd_install {
46
        .name = "install",
47
        .description = "Mender Artifact to install - local file or a URL",
48
        .options =
49
                {
50
                        conf::CliOption {
51
                                .long_option = "reboot-exit-code",
52
                                .description =
53
                                        "Return exit code 4 if a manual reboot is required after the Artifact installation",
54
                        },
55
                },
56
};
57

58
const conf::CliCommand cmd_rollback {
59
        .name = "rollback",
60
        .description = "Rollback current Artifact. Returns (2) if no update in progress",
61
};
62

63
const conf::CliCommand cmd_send_inventory {
64
        .name = "send-inventory",
65
        .description = "Force inventory update",
66
};
67

68
const conf::CliCommand cmd_show_artifact {
69
        .name = "show-artifact",
70
        .description = "Print the current artifact name to the command line and exit",
71
};
72

73
const conf::CliCommand cmd_show_provides {
74
        .name = "show-provides",
75
        .description = "Print the current provides to the command line and exit",
76
};
77

78
const conf::CliApp cli_mender_update = {
79
        .name = "mender-update",
80
        .short_description = "manage and start Mender Update",
81
        .long_description =
82
                R"(mender-update integrates both the mender-auth daemon and commands for manually
83
   performing tasks performed by the daemon (see list of COMMANDS below).)",
84
        .commands =
85
                {
86
                        cmd_check_update,
87
                        cmd_commit,
88
                        cmd_daemon,
89
                        cmd_install,
90
                        cmd_rollback,
91
                        cmd_send_inventory,
92
                        cmd_show_artifact,
93
                        cmd_show_provides,
94
                },
95
};
96

97
ExpectedActionPtr ParseUpdateArguments(
96✔
98
        vector<string>::const_iterator start, vector<string>::const_iterator end) {
99
        if (start == end) {
96✔
100
                return expected::unexpected(conf::MakeError(conf::InvalidOptionsError, "Need an action"));
3✔
101
        }
102

103
        conf::CmdlineOptionsIterator opts_iter(
104
                start + 1,
105
                end,
106
                {},
107
                {
108
                        "--help",
109
                        "-h",
110
                });
665✔
111
        auto ex_opt_val = opts_iter.Next();
95✔
112

113
        bool help_arg = false;
114
        while (ex_opt_val && ((ex_opt_val.value().option != "") || (ex_opt_val.value().value != ""))) {
95✔
115
                auto opt_val = ex_opt_val.value();
×
116
                if ((opt_val.option == "--help") || (opt_val.option == "-h")) {
×
117
                        help_arg = true;
118
                        break;
×
119
                }
120
                ex_opt_val = opts_iter.Next();
×
121
        }
122

123
        if (help_arg) {
124
                conf::PrintCliCommandHelp(cli_mender_update, start[0]);
×
125
                return expected::unexpected(error::MakeError(error::ExitWithSuccessError, ""));
×
126
        }
127

128
        if (start[0] == "show-artifact") {
95✔
129
                unordered_set<string> options {};
4✔
130
                conf::CmdlineOptionsIterator iter(start + 1, end, options, options);
8✔
131
                auto arg = iter.Next();
4✔
132
                if (!arg) {
4✔
133
                        return expected::unexpected(arg.error());
4✔
134
                }
135

136
                return make_shared<ShowArtifactAction>();
4✔
137
        } else if (start[0] == "show-provides") {
91✔
138
                unordered_set<string> options {};
34✔
139
                conf::CmdlineOptionsIterator iter(start + 1, end, options, options);
68✔
140
                auto arg = iter.Next();
34✔
141
                if (!arg) {
34✔
142
                        return expected::unexpected(arg.error());
2✔
143
                }
144

145
                return make_shared<ShowProvidesAction>();
64✔
146
        } else if (start[0] == "install") {
57✔
147
                unordered_set<string> options {};
47✔
148
                conf::CmdlineOptionsIterator iter(start + 1, end, options, {"--reboot-exit-code"});
282✔
149
                iter.SetArgumentsMode(conf::ArgumentsMode::AcceptBareArguments);
150

151
                string filename;
152
                bool reboot_exit_code = false;
47✔
153
                while (true) {
154
                        auto arg = iter.Next();
93✔
155
                        if (!arg) {
93✔
156
                                return expected::unexpected(arg.error());
2✔
157
                        }
158

159
                        auto value = arg.value();
137✔
160
                        if (value.option == "--reboot-exit-code") {
92✔
161
                                reboot_exit_code = true;
1✔
162
                                continue;
1✔
163
                        } else if (value.option != "") {
91✔
164
                                return expected::unexpected(
×
165
                                        conf::MakeError(conf::InvalidOptionsError, "No such option: " + value.option));
×
166
                        }
167

168
                        if (value.value != "") {
91✔
169
                                if (filename != "") {
46✔
170
                                        return expected::unexpected(conf::MakeError(
1✔
171
                                                conf::InvalidOptionsError, "Too many arguments: " + value.value));
3✔
172
                                } else {
173
                                        filename = value.value;
174
                                }
175
                        } else {
176
                                if (filename == "") {
45✔
177
                                        return expected::unexpected(
1✔
178
                                                conf::MakeError(conf::InvalidOptionsError, "Need a path to an artifact"));
3✔
179
                                } else {
180
                                        break;
181
                                }
182
                        }
183
                }
184

185
                return make_shared<InstallAction>(filename, reboot_exit_code);
88✔
186
        } else if (start[0] == "commit") {
10✔
187
                unordered_set<string> options {};
4✔
188
                conf::CmdlineOptionsIterator iter(start + 1, end, options, options);
8✔
189
                auto arg = iter.Next();
4✔
190
                if (!arg) {
4✔
191
                        return expected::unexpected(arg.error());
×
192
                }
193

194
                return make_shared<CommitAction>();
8✔
195
        } else if (start[0] == "rollback") {
6✔
196
                unordered_set<string> options {};
6✔
197
                conf::CmdlineOptionsIterator iter(start + 1, end, options, options);
12✔
198
                auto arg = iter.Next();
6✔
199
                if (!arg) {
6✔
200
                        return expected::unexpected(arg.error());
×
201
                }
202

203
                return make_shared<RollbackAction>();
12✔
204
        } else if (start[0] == "daemon") {
×
205
                unordered_set<string> options {};
×
206
                conf::CmdlineOptionsIterator iter(start + 1, end, options, options);
×
207
                auto arg = iter.Next();
×
208
                if (!arg) {
×
209
                        return expected::unexpected(arg.error());
×
210
                }
211

212
                return make_shared<DaemonAction>();
×
213
        } else if (start[0] == "send-inventory") {
×
214
                unordered_set<string> options {};
×
215
                conf::CmdlineOptionsIterator iter(start + 1, end, options, options);
×
216
                auto arg = iter.Next();
×
217
                if (!arg) {
×
218
                        return expected::unexpected(arg.error());
×
219
                }
220

221
                return make_shared<SendInventoryAction>();
×
222
        } else if (start[0] == "check-update") {
×
223
                unordered_set<string> options {};
×
224
                conf::CmdlineOptionsIterator iter(start + 1, end, options, options);
×
225
                auto arg = iter.Next();
×
226
                if (!arg) {
×
227
                        return expected::unexpected(arg.error());
×
228
                }
229

230
                return make_shared<CheckUpdateAction>();
×
231
        } else {
232
                return expected::unexpected(
×
233
                        conf::MakeError(conf::InvalidOptionsError, "No such action: " + start[0]));
×
234
        }
235
}
236

237
static error::Error DoMain(
96✔
238
        const vector<string> &args,
239
        function<void(mender::update::context::MenderContext &ctx)> test_hook) {
240
        mender::common::conf::MenderConfig config;
192✔
241

242
        auto args_pos = config.ProcessCmdlineArgs(args.begin(), args.end(), cli_mender_update);
96✔
243
        if (!args_pos) {
96✔
244
                if (args_pos.error().code != error::MakeError(error::ExitWithSuccessError, "").code) {
×
245
                        conf::PrintCliHelp(cli_mender_update);
×
246
                }
247
                return args_pos.error();
×
248
        }
249

250
        auto action = ParseUpdateArguments(args.begin() + args_pos.value(), args.end());
96✔
251
        if (!action) {
96✔
252
                if (action.error().code != error::MakeError(error::ExitWithSuccessError, "").code) {
8✔
253
                        if (args.size() > 0) {
8✔
254
                                conf::PrintCliCommandHelp(cli_mender_update, args[0]);
8✔
255
                        } else {
256
                                conf::PrintCliHelp(cli_mender_update);
×
257
                        }
258
                }
259
                return action.error();
8✔
260
        }
261

262
        mender::update::context::MenderContext main_context(config);
88✔
263

264
        test_hook(main_context);
88✔
265

266
        auto err = main_context.Initialize();
88✔
267
        if (error::NoError != err) {
88✔
268
                return err;
×
269
        }
270

271
        return action.value()->Execute(main_context);
88✔
272
}
273

274
int Main(
96✔
275
        const vector<string> &args,
276
        function<void(mender::update::context::MenderContext &ctx)> test_hook) {
277
        auto err = DoMain(args, test_hook);
96✔
278

279
        if (err.code == context::MakeError(context::NoUpdateInProgressError, "").code) {
96✔
280
                return NoUpdateInProgressExitStatus;
281
        } else if (err.code == context::MakeError(context::RebootRequiredError, "").code) {
94✔
282
                return RebootExitStatus;
283
        } else if (err != error::NoError) {
93✔
284
                if (err.code == error::MakeError(error::ExitWithSuccessError, "").code) {
26✔
285
                        return 0;
286
                } else if (err.code != error::MakeError(error::ExitWithFailureError, "").code) {
26✔
287
                        cerr << "Could not fulfill request: " + err.String() << endl;
40✔
288
                }
289
                return 1;
26✔
290
        }
291

292
        return 0;
293
}
294

295
} // namespace cli
296
} // namespace update
297
} // 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