• 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

68.31
/common/conf/conf.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 <common/conf.hpp>
16

17
#include <string>
18
#include <cstdlib>
19
#include <cerrno>
20

21
#include <mender-version.h>
22

23
#include <common/error.hpp>
24
#include <common/expected.hpp>
25
#include <common/log.hpp>
26
#include <common/json.hpp>
27

28
namespace mender {
29
namespace common {
30
namespace conf {
31

32
using namespace std;
33
namespace error = mender::common::error;
34
namespace expected = mender::common::expected;
35
namespace log = mender::common::log;
36
namespace json = mender::common::json;
37

38
const string kMenderVersion = MENDER_VERSION;
39

40
const ConfigErrorCategoryClass ConfigErrorCategory;
41

42
const char *ConfigErrorCategoryClass::name() const noexcept {
×
43
        return "ConfigErrorCategory";
×
44
}
45

46
string ConfigErrorCategoryClass::message(int code) const {
8✔
47
        switch (code) {
8✔
48
        case NoError:
49
                return "Success";
×
50
        case InvalidOptionsError:
51
                return "Invalid options given";
8✔
52
        }
53
        assert(false);
54
        return "Unknown";
×
55
}
56

57
error::Error MakeError(ConfigErrorCode code, const string &msg) {
5✔
58
        return error::Error(error_condition(code, ConfigErrorCategory), msg);
66✔
59
}
60

61

62
string GetEnv(const string &var_name, const string &default_value) {
1,819✔
63
        const char *value = getenv(var_name.c_str());
1,819✔
64
        if (value == nullptr) {
1,819✔
65
                return string(default_value);
1,818✔
66
        } else {
67
                return string(value);
1✔
68
        }
69
}
70

71
ExpectedOptionValue CmdlineOptionsIterator::Next() {
484✔
72
        string option = "";
484✔
73
        string value = "";
484✔
74

75
        if (start_ + pos_ >= end_) {
484✔
76
                return ExpectedOptionValue({"", ""});
153✔
77
        }
78

79
        if (past_double_dash_) {
331✔
80
                OptionValue opt_val {"", start_[pos_]};
6✔
81
                pos_++;
3✔
82
                return ExpectedOptionValue(opt_val);
3✔
83
        }
84

85
        if (start_[pos_] == "--") {
328✔
86
                past_double_dash_ = true;
1✔
87
                pos_++;
1✔
88
                return ExpectedOptionValue({"--", ""});
1✔
89
        }
90

91
        if (start_[pos_][0] == '-') {
327✔
92
                auto eq_idx = start_[pos_].find('=');
125✔
93
                if (eq_idx != string::npos) {
125✔
94
                        option = start_[pos_].substr(0, eq_idx);
3✔
95
                        value = start_[pos_].substr(eq_idx + 1, start_[pos_].size() - eq_idx - 1);
3✔
96
                        pos_++;
3✔
97
                } else {
98
                        option = start_[pos_];
122✔
99
                        pos_++;
122✔
100
                }
101

102
                if (opts_with_value_.count(option) != 0) {
125✔
103
                        // option with value
104
                        if ((value == "") && ((start_ + pos_ >= end_) || (start_[pos_][0] == '-'))) {
111✔
105
                                // the next item is not a value
106
                                error::Error err = MakeError(
107
                                        ConfigErrorCode::InvalidOptionsError, "Option " + option + " missing value");
4✔
108
                                return ExpectedOptionValue(expected::unexpected(err));
4✔
109
                        } else if (value == "") {
109✔
110
                                // only assign the next item as value if there was no value
111
                                // specified as '--opt=value' (parsed above)
112
                                value = start_[pos_];
107✔
113
                                pos_++;
107✔
114
                        }
115
                } else if (opts_wo_value_.count(option) == 0) {
14✔
116
                        // unknown option
117
                        error::Error err = MakeError(
118
                                ConfigErrorCode::InvalidOptionsError, "Unrecognized option '" + option + "'");
16✔
119
                        return ExpectedOptionValue(expected::unexpected(err));
16✔
120
                } else if (value != "") {
6✔
121
                        // option without a value, yet, there was a value specified as '--opt=value' (parsed
122
                        // above)
123
                        error::Error err = MakeError(
124
                                ConfigErrorCode::InvalidOptionsError,
125
                                "Option " + option + " doesn't expect a value");
2✔
126
                        return ExpectedOptionValue(expected::unexpected(err));
2✔
127
                }
128
        } else {
129
                switch (mode_) {
202✔
130
                case ArgumentsMode::AcceptBareArguments:
50✔
131
                        value = start_[pos_];
132
                        pos_++;
50✔
133
                        break;
50✔
134
                case ArgumentsMode::RejectBareArguments:
50✔
135
                        return expected::unexpected(MakeError(
50✔
136
                                ConfigErrorCode::InvalidOptionsError,
137
                                "Unexpected argument '" + start_[pos_] + "'"));
150✔
138
                case ArgumentsMode::StopAtBareArguments:
139
                        return ExpectedOptionValue({"", ""});
102✔
140
                }
141
        }
142

143
        return ExpectedOptionValue({std::move(option), std::move(value)});
164✔
144
}
145

146
expected::ExpectedSize MenderConfig::ProcessCmdlineArgs(
106✔
147
        vector<string>::const_iterator start, vector<string>::const_iterator end, const CliApp &app) {
148
        bool explicit_config_path = false;
149
        bool explicit_fallback_config_path = false;
150
        string log_file = "";
106✔
151
        string log_level;
152
        string trusted_cert;
153
        bool skip_verify_arg = false;
154
        bool version_arg = false;
155
        bool help_arg = false;
156

157
        CmdlineOptionsIterator opts_iter(
158
                start,
159
                end,
160
                {
161
                        "--config",
162
                        "-c",
163
                        "--fallback-config",
164
                        "-b",
165
                        "--data",
166
                        "-d",
167
                        "--log-file",
168
                        "-L",
169
                        "--log-level",
170
                        "-l",
171
                        "--trusted-certs",
172
                        "-E",
173
                },
174
                {
175
                        "--skipverify"
176
                        "--version",
177
                        "-v",
178
                        "--help",
179
                        "-h",
180
                });
2,438✔
181
        opts_iter.SetArgumentsMode(ArgumentsMode::StopAtBareArguments);
182
        auto ex_opt_val = opts_iter.Next();
106✔
183
        int arg_count = 0;
184
        while (ex_opt_val && ((ex_opt_val.value().option != "") || (ex_opt_val.value().value != ""))) {
208✔
185
                arg_count++;
102✔
186
                auto opt_val = ex_opt_val.value();
102✔
187
                if ((opt_val.option == "--config") || (opt_val.option == "-c")) {
102✔
188
                        paths.SetConfFile(opt_val.value);
189
                        explicit_config_path = true;
190
                } else if ((opt_val.option == "--fallback-config") || (opt_val.option == "-b")) {
99✔
191
                        paths.SetFallbackConfFile(opt_val.value);
192
                        explicit_fallback_config_path = true;
193
                } else if ((opt_val.option == "--data") || (opt_val.option == "-d")) {
99✔
194
                        paths.SetDataStore(opt_val.value);
97✔
195
                } else if ((opt_val.option == "--log-file") || (opt_val.option == "-L")) {
2✔
196
                        log_file = opt_val.value;
197
                } else if ((opt_val.option == "--log-level") || (opt_val.option == "-l")) {
2✔
198
                        log_level = opt_val.value;
199
                } else if ((opt_val.option == "--trusted-certs") || (opt_val.option == "-E")) {
×
200
                        trusted_cert = opt_val.value;
201
                } else if (opt_val.option == "--skipverify") {
×
202
                        skip_verify_arg = true;
203
                } else if ((opt_val.option == "--version") || (opt_val.option == "-v")) {
×
204
                        version_arg = true;
205
                } else if ((opt_val.option == "--help") || (opt_val.option == "-h")) {
×
206
                        help_arg = true;
207
                        break;
×
208
                }
209
                ex_opt_val = opts_iter.Next();
204✔
210
        }
211
        if (!ex_opt_val) {
106✔
212
                return expected::unexpected(ex_opt_val.error());
×
213
        }
214

215
        if (version_arg) {
106✔
216
                if (arg_count > 1 || opts_iter.GetPos() < static_cast<size_t>(end - start)) {
×
217
                        return expected::unexpected(error::Error(
×
218
                                make_error_condition(errc::invalid_argument),
×
219
                                "--version can not be combined with other commands and arguments"));
×
220
                } else {
221
                        cout << kMenderVersion << endl;
×
222
                        return expected::unexpected(error::MakeError(error::ExitWithSuccessError, ""));
×
223
                }
224
        }
225

226
        if (help_arg) {
106✔
227
                PrintCliHelp(app);
×
228
                return expected::unexpected(error::MakeError(error::ExitWithSuccessError, ""));
×
229
        }
230

231
        if (log_file != "") {
106✔
232
                auto err = log::SetupFileLogging(log_file, true);
×
233
                if (error::NoError != err) {
×
234
                        return expected::unexpected(err);
×
235
                }
236
        }
237

238
        SetLevel(log::kDefaultLogLevel);
106✔
239

240
        if (log_level != "") {
106✔
241
                auto ex_log_level = log::StringToLogLevel(log_level);
2✔
242
                if (!ex_log_level) {
2✔
243
                        return expected::unexpected(ex_log_level.error());
×
244
                }
245
                SetLevel(ex_log_level.value());
2✔
246
        }
247

248
        auto err = LoadConfigFile_(paths.GetConfFile(), explicit_config_path);
106✔
249
        if (error::NoError != err) {
106✔
250
                this->Reset();
×
251
                return expected::unexpected(err);
×
252
        }
253

254
        err = LoadConfigFile_(paths.GetFallbackConfFile(), explicit_fallback_config_path);
212✔
255
        if (error::NoError != err) {
106✔
256
                this->Reset();
×
257
                return expected::unexpected(err);
×
258
        }
259

260
        if (this->update_log_path != "") {
106✔
261
                paths.SetUpdateLogPath(this->update_log_path);
262
        }
263

264
        if (log_level == "" && this->daemon_log_level != "") {
106✔
265
                auto ex_log_level = log::StringToLogLevel(this->daemon_log_level);
1✔
266
                if (!ex_log_level) {
1✔
267
                        return expected::unexpected(ex_log_level.error());
×
268
                }
269
                SetLevel(ex_log_level.value());
1✔
270
        }
271

272
        if (trusted_cert != "") {
106✔
273
                this->server_certificate = trusted_cert;
×
274
        }
275

276
        if (skip_verify_arg) {
106✔
277
                this->skip_verify = true;
×
278
        }
279

280
        return opts_iter.GetPos();
281
}
282

283
error::Error MenderConfig::LoadConfigFile_(const string &path, bool required) {
212✔
284
        auto ret = this->LoadFile(path);
212✔
285
        if (!ret) {
212✔
286
                if (required) {
209✔
287
                        // any failure when a file is required (e.g. path was given explicitly) means an error
288
                        log::Error("Failed to load config from '" + path + "': " + ret.error().message);
×
289
                        return ret.error();
×
290
                } else if (ret.error().IsErrno(ENOENT)) {
209✔
291
                        // File doesn't exist, OK for non-required
292
                        log::Debug("Failed to load config from '" + path + "': " + ret.error().message);
418✔
293
                        return error::NoError;
209✔
294
                } else {
295
                        // other errors (parsing errors,...) for default paths should produce warnings
296
                        log::Warning("Failed to load config from '" + path + "': " + ret.error().message);
×
297
                        return error::NoError;
×
298
                }
299
        }
300
        // else
301
        auto valid = this->ValidateConfig();
3✔
302
        if (!valid) {
3✔
303
                // validation error is always an error
304
                log::Error("Failed to validate config from '" + path + "': " + valid.error().message);
×
305
                return valid.error();
×
306
        }
307

308
        return error::NoError;
3✔
309
}
310

311
error::Error MenderConfig::LoadDefaults() {
×
312
        auto err = LoadConfigFile_(paths.GetFallbackConfFile(), false);
×
313
        if (error::NoError != err) {
×
314
                this->Reset();
×
315
                return err;
×
316
        }
317

318
        err = LoadConfigFile_(paths.GetConfFile(), false);
×
319
        if (error::NoError != err) {
×
320
                this->Reset();
×
321
                return err;
×
322
        }
323

324
        return error::NoError;
×
325
}
326

327
} // namespace conf
328
} // namespace common
329
} // 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