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

mendersoftware / mender / 2504081154

06 May 2026 10:55AM UTC coverage: 81.407% (-0.04%) from 81.442%
2504081154

push

gitlab-ci

elkoniu
fix: Fail when device tier is invalid

DeviceTier is one of the configuration options but it is critical
for how device is recognized by the server. When we will fail
parsing it out of the configuration we should fail instead to
switching to the `standard` device tier.

Ticket: ME-636

Signed-off-by: Paweł Poławski <pawel.polawski@northern.tech>

1 of 6 new or added lines in 2 files covered. (16.67%)

1 existing line in 1 file now uncovered.

9186 of 11284 relevant lines covered (81.41%)

20212.03 hits per line

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

82.91
/src/client_shared/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 <client_shared/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 client_shared {
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
namespace config_parser = mender::client_shared::config_parser;
38

39
const string kMenderVersion = MENDER_VERSION;
40

41
const DefaultPathsType DefaultPaths;
42

43
const ConfigErrorCategoryClass ConfigErrorCategory;
44

45
const char *ConfigErrorCategoryClass::name() const noexcept {
×
46
        return "ConfigErrorCategory";
×
47
}
48

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

60
error::Error MakeError(ConfigErrorCode code, const string &msg) {
5✔
61
        return error::Error(error_condition(code, ConfigErrorCategory), msg);
93✔
62
}
63

64

65
string GetEnv(const string &var_name, const string &default_value) {
1,625✔
66
        const char *value = getenv(var_name.c_str());
1,625✔
67
        if (value == nullptr) {
1,625✔
68
                return string(default_value);
1,620✔
69
        } else {
70
                return string(value);
5✔
71
        }
72
}
73

74
ExpectedOptionValue CmdlineOptionsIterator::Next() {
700✔
75
        string option = "";
700✔
76
        string value = "";
700✔
77

78
        if (start_ + pos_ >= end_) {
700✔
79
                return ExpectedOptionValue({"", ""});
216✔
80
        }
81

82
        if (past_double_dash_) {
484✔
83
                OptionValue opt_val {"", start_[pos_]};
3✔
84
                pos_++;
3✔
85
                return ExpectedOptionValue(opt_val);
3✔
86
        }
3✔
87

88
        if (start_[pos_] == "--") {
481✔
89
                past_double_dash_ = true;
1✔
90
                pos_++;
1✔
91
                return ExpectedOptionValue({"--", ""});
1✔
92
        }
93

94
        if (start_[pos_][0] == '-') {
480✔
95
                auto eq_idx = start_[pos_].find('=');
208✔
96
                if (eq_idx != string::npos) {
208✔
97
                        option = start_[pos_].substr(0, eq_idx);
3✔
98
                        value = start_[pos_].substr(eq_idx + 1, start_[pos_].size() - eq_idx - 1);
3✔
99
                        pos_++;
3✔
100
                } else {
101
                        option = start_[pos_];
205✔
102
                        pos_++;
205✔
103
                }
104

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

146
        return ExpectedOptionValue({std::move(option), std::move(value)});
247✔
147
}
609✔
148

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

160
        CmdlineOptionsIterator opts_iter(
161
                start, end, GlobalOptsSetWithValue(), GlobalOptsSetWithoutValue());
322✔
162
        opts_iter.SetArgumentsMode(ArgumentsMode::StopAtBareArguments);
163
        auto ex_opt_val = opts_iter.Next();
161✔
164
        int arg_count = 0;
165
        while (ex_opt_val && ((ex_opt_val.value().option != "") || (ex_opt_val.value().value != ""))) {
310✔
166
                arg_count++;
153✔
167
                auto opt_val = ex_opt_val.value();
153✔
168
                if ((opt_val.option == "--config") || (opt_val.option == "-c")) {
153✔
169
                        paths.SetConfFile(opt_val.value);
170
                        explicit_config_path = true;
171
                } else if ((opt_val.option == "--fallback-config") || (opt_val.option == "-b")) {
149✔
172
                        paths.SetFallbackConfFile(opt_val.value);
173
                        explicit_fallback_config_path = true;
174
                } else if (
175
                        (opt_val.option == "--data") || (opt_val.option == "--datastore")
133✔
176
                        || (opt_val.option == "-d")) {
158✔
177
                        paths.SetDataStore(opt_val.value);
138✔
178
                } else if ((opt_val.option == "--log-file") || (opt_val.option == "-L")) {
10✔
179
                        log_file = opt_val.value;
180
                } else if ((opt_val.option == "--log-level") || (opt_val.option == "-l")) {
10✔
181
                        log_level = opt_val.value;
182
                } else if ((opt_val.option == "--trusted-certs") || (opt_val.option == "-E")) {
8✔
183
                        trusted_cert = opt_val.value;
184
                } else if (opt_val.option == "--skipverify") {
8✔
185
                        skip_verify_arg = true;
186
                } else if ((opt_val.option == "--version") || (opt_val.option == "-v")) {
8✔
187
                        version_arg = true;
188
                } else if ((opt_val.option == "--help") || (opt_val.option == "-h")) {
4✔
189
                        help_arg = true;
190
                        break;
191
                } else {
192
                        assert(false);
193
                }
194
                ex_opt_val = opts_iter.Next();
149✔
195
        }
153✔
196
        if (!ex_opt_val) {
161✔
197
                return expected::unexpected(ex_opt_val.error());
×
198
        }
199

200
        if (version_arg) {
161✔
201
                if (arg_count > 1 || opts_iter.GetPos() < static_cast<size_t>(end - start)) {
4✔
202
                        return expected::unexpected(error::Error(
2✔
203
                                make_error_condition(errc::invalid_argument),
4✔
204
                                "--version can not be combined with other commands and arguments"));
6✔
205
                } else {
206
                        cout << kMenderVersion << endl;
2✔
207
                        return expected::unexpected(error::MakeError(error::ExitWithSuccessError, ""));
6✔
208
                }
209
        }
210

211
        if (help_arg) {
157✔
212
                PrintCliHelp(app);
4✔
213
                return expected::unexpected(error::MakeError(error::ExitWithSuccessError, ""));
12✔
214
        }
215

216
        if (log_file != "") {
153✔
217
                auto err = log::SetupFileLogging(log_file, true);
×
218
                if (error::NoError != err) {
×
219
                        return expected::unexpected(err);
×
220
                }
221
        }
222

223
        SetLevel(log::kDefaultLogLevel);
153✔
224

225
        if (log_level != "") {
153✔
226
                auto ex_log_level = log::StringToLogLevel(log_level);
2✔
227
                if (!ex_log_level) {
2✔
228
                        return expected::unexpected(ex_log_level.error());
×
229
                }
230
                SetLevel(ex_log_level.value());
2✔
231
        }
232

233
        auto err = LoadConfigFile_(paths.GetFallbackConfFile(), explicit_fallback_config_path);
153✔
234
        if (error::NoError != err) {
153✔
235
                this->Reset();
×
236
                return expected::unexpected(err);
×
237
        }
238

239
        err = LoadConfigFile_(paths.GetConfFile(), explicit_config_path);
153✔
240
        if (error::NoError != err) {
153✔
241
                this->Reset();
×
242
                return expected::unexpected(err);
×
243
        }
244

245
        if (this->update_log_path != "") {
153✔
246
                paths.SetUpdateLogPath(this->update_log_path);
247
        }
248

249
        if (log_level == "" && this->daemon_log_level != "") {
153✔
250
                auto ex_log_level = log::StringToLogLevel(this->daemon_log_level);
1✔
251
                if (!ex_log_level) {
1✔
252
                        return expected::unexpected(ex_log_level.error());
×
253
                }
254
                SetLevel(ex_log_level.value());
1✔
255
        }
256

257
        if (trusted_cert != "") {
153✔
258
                this->server_certificate = trusted_cert;
×
259
        }
260

261
        if (skip_verify_arg) {
153✔
262
                this->skip_verify = true;
×
263
        }
264

265
        http_client_config_.server_cert_path = server_certificate;
153✔
266
        http_client_config_.client_cert_path = https_client.certificate;
153✔
267
        http_client_config_.client_cert_key_path = https_client.key;
153✔
268
        http_client_config_.ssl_engine = https_client.ssl_engine;
153✔
269
        http_client_config_.skip_verify = skip_verify;
153✔
270
        http_client_config_.retry_download_count = retry_download_count;
153✔
271

272
        auto proxy = http::GetHttpProxyStringFromEnvironment();
153✔
273
        if (proxy) {
153✔
274
                http_client_config_.http_proxy = proxy.value();
152✔
275
        } else {
276
                return expected::unexpected(proxy.error());
2✔
277
        }
278

279
        proxy = http::GetHttpsProxyStringFromEnvironment();
304✔
280
        if (proxy) {
152✔
281
                http_client_config_.https_proxy = proxy.value();
151✔
282
        } else {
283
                return expected::unexpected(proxy.error());
2✔
284
        }
285

286
        proxy = http::GetNoProxyStringFromEnvironment();
302✔
287
        if (proxy) {
151✔
288
                http_client_config_.no_proxy = proxy.value();
150✔
289
        } else {
290
                return expected::unexpected(proxy.error());
2✔
291
        }
292

293
        if (http_client_config_.no_proxy.find(',') != string::npos) {
150✔
294
                return expected::unexpected(MakeError(
×
295
                        ConfigErrorCode::InvalidOptionsError,
296
                        "Unsupported syntax for NO_PROXY environment variable. Use space-separated values instead of comma-separated."));
×
297
        }
298

299
        return opts_iter.GetPos();
300
}
161✔
301

302
error::Error MenderConfig::LoadConfigFile_(const string &path, bool required) {
306✔
303
        auto ret = this->LoadFile(path);
306✔
304
        if (!ret) {
306✔
305
                if (required) {
301✔
306
                        // any failure when a file is required (e.g. path was given explicitly) means an error
307
                        log::Error("Failed to load config from '" + path + "': " + ret.error().message);
×
308
                        return ret.error();
×
309
                } else if (ret.error().IsErrno(ENOENT)) {
301✔
310
                        // File doesn't exist, OK for non-required
311
                        log::Debug("Failed to load config from '" + path + "': " + ret.error().message);
301✔
312
                        return error::NoError;
301✔
313
                } else {
314
                        // Incorrect DeviceTier shall lead to the daemon failure
NEW
315
                        auto device_tier_error = config_parser::MakeError(
×
NEW
316
                                config_parser::ConfigParserErrorCode::DeviceTierError, "Invalid DeviceTier");
×
NEW
317
                        if (ret.error().code == device_tier_error.code) {
×
NEW
318
                                log::Error("Failed to get DeviceTier from '" + path + "': " + ret.error().message);
×
NEW
319
                                return ret.error();
×
320
                        }
321
                        // other errors (parsing errors,...) for default paths should produce warnings
322
                        log::Warning("Failed to load config from '" + path + "': " + ret.error().message);
×
323
                        return error::NoError;
×
324
                }
325
        }
326
        // else
327

328
        return error::NoError;
5✔
329
}
330

331
} // namespace conf
332
} // namespace client_shared
333
} // 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