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

mendersoftware / mender / 968408678

pending completion
968408678

push

gitlab-ci

oleorhagen
feat(artifact/scripts): Add support for executing state scripts

Ticket: MEN-6636
Changelog: None

Signed-off-by: Ole Petter <ole.orhagen@northern.tech>

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

5405 of 6857 relevant lines covered (78.82%)

195.75 hits per line

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

79.57
/mender-update/context/context.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/context.hpp>
16

17
#include <cctype>
18

19
#include <algorithm>
20
#include <regex>
21
#include <set>
22

23
#include <artifact/artifact.hpp>
24
#include <common/common.hpp>
25
#include <common/error.hpp>
26
#include <common/expected.hpp>
27
#include <common/io.hpp>
28
#include <common/json.hpp>
29
#include <common/key_value_database.hpp>
30
#include <common/log.hpp>
31
#include <common/path.hpp>
32

33
namespace mender {
34
namespace update {
35
namespace context {
36

37
using namespace std;
38
namespace artifact = mender::artifact;
39
namespace common = mender::common;
40
namespace error = mender::common::error;
41
namespace expected = mender::common::expected;
42
namespace io = mender::common::io;
43
namespace json = mender::common::json;
44
namespace kv_db = mender::common::key_value_database;
45
namespace log = mender::common::log;
46
namespace path = mender::common::path;
47

48
const string MenderContext::broken_artifact_name_suffix {"_INCONSISTENT"};
49

50
const string MenderContext::artifact_name_key {"artifact-name"};
51
const string MenderContext::artifact_group_key {"artifact-group"};
52
const string MenderContext::artifact_provides_key {"artifact-provides"};
53
const string MenderContext::standalone_state_key {"standalone-state"};
54
const string MenderContext::state_data_key {"state"};
55
const string MenderContext::state_data_key_uncommitted {"state-uncommitted"};
56
const string MenderContext::update_control_maps {"update-control-maps"};
57
const string MenderContext::auth_token_name {"authtoken"};
58
const string MenderContext::auth_token_cache_invalidator_name {"auth-token-cache-invalidator"};
59

60
const int MenderContext::standalone_data_version {1};
61

62
const MenderContextErrorCategoryClass MenderContextErrorCategory;
63

64
const char *MenderContextErrorCategoryClass::name() const noexcept {
×
65
        return "MenderContextErrorCategory";
×
66
}
67

68
string MenderContextErrorCategoryClass::message(int code) const {
3✔
69
        switch (code) {
3✔
70
        case NoError:
×
71
                return "Success";
×
72
        case ParseError:
×
73
                return "Parse error";
×
74
        case ValueError:
×
75
                return "Value error";
×
76
        case NoSuchUpdateModuleError:
×
77
                return "Update Module not found for given artifact type";
×
78
        case DatabaseValueError:
×
79
                return "Value in database is invalid or corrupted";
×
80
        case RebootRequiredError:
×
81
                return "Reboot required";
×
82
        case NoUpdateInProgressError:
2✔
83
                return "No update in progress";
2✔
84
        case ExitStatusOnlyError:
×
85
                return "ExitStatusOnlyError";
×
86
        case UnexpectedHttpResponse:
×
87
                return "Unexpected HTTP response";
×
88
        case StateDataStoreCountExceededError:
1✔
89
                return "State data store count exceeded";
1✔
90
        }
91
        assert(false);
×
92
        return "Unknown";
93
}
94

95
error::Error MakeError(MenderContextErrorCode code, const string &msg) {
207✔
96
        return error::Error(error_condition(code, MenderContextErrorCategory), msg);
414✔
97
}
98

99
error::Error MenderContext::Initialize() {
180✔
100
#if MENDER_USE_LMDB
101
        auto err = mender_store_.Open(
102
                path::Join(config_.default_config.GetDefaultDataStore(), "mender-store"));
540✔
103
        if (error::NoError != err) {
180✔
104
                return err;
×
105
        }
106
        err = mender_store_.Remove(auth_token_name);
180✔
107
        if (error::NoError != err) {
180✔
108
                // key not existing in the DB is not treated as an error so this must be
109
                // a real error
110
                return err;
×
111
        }
112
        err = mender_store_.Remove(auth_token_cache_invalidator_name);
180✔
113
        if (error::NoError != err) {
180✔
114
                // same as above -- a real error
115
                return err;
×
116
        }
117

118
        return error::NoError;
180✔
119
#else
120
        return error::NoError;
121
#endif
122
}
123

124
kv_db::KeyValueDatabase &MenderContext::GetMenderStoreDB() {
447✔
125
        return mender_store_;
447✔
126
}
127

128
ExpectedProvidesData MenderContext::LoadProvides() {
144✔
129
        ExpectedProvidesData data;
288✔
130
        auto err = mender_store_.ReadTransaction([this, &data](kv_db::Transaction &txn) {
434✔
131
                data = LoadProvides(txn);
144✔
132
                if (!data) {
144✔
133
                        return data.error();
2✔
134
                }
135
                return error::NoError;
142✔
136
        });
288✔
137
        if (err != error::NoError) {
144✔
138
                return expected::unexpected(err);
4✔
139
        }
140
        return data;
142✔
141
}
142

143
ExpectedProvidesData MenderContext::LoadProvides(kv_db::Transaction &txn) {
190✔
144
        string artifact_name;
380✔
145
        string artifact_group;
380✔
146
        string artifact_provides_str;
380✔
147

148
        auto err = kv_db::ReadString(txn, artifact_name_key, artifact_name, true);
380✔
149
        if (err != error::NoError) {
190✔
150
                return expected::unexpected(err);
×
151
        }
152
        err = kv_db::ReadString(txn, artifact_group_key, artifact_group, true);
190✔
153
        if (err != error::NoError) {
190✔
154
                return expected::unexpected(err);
×
155
        }
156
        err = kv_db::ReadString(txn, artifact_provides_key, artifact_provides_str, true);
190✔
157
        if (err != error::NoError) {
190✔
158
                return expected::unexpected(err);
×
159
        }
160

161
        ProvidesData ret {};
380✔
162
        if (artifact_name != "") {
190✔
163
                ret["artifact_name"] = artifact_name;
139✔
164
        }
165
        if (artifact_group != "") {
190✔
166
                ret["artifact_group"] = artifact_group;
10✔
167
        }
168
        if (artifact_provides_str == "") {
190✔
169
                // nothing more to do
170
                return ret;
126✔
171
        }
172

173
        auto ex_j = json::Load(artifact_provides_str);
128✔
174
        if (!ex_j) {
64✔
175
                return expected::unexpected(ex_j.error());
2✔
176
        }
177
        auto ex_children = ex_j.value().GetChildren();
126✔
178
        if (!ex_children) {
63✔
179
                return expected::unexpected(ex_children.error());
×
180
        }
181

182
        auto children = ex_children.value();
126✔
183
        if (!all_of(children.cbegin(), children.cend(), [](const json::ChildrenMap::value_type &it) {
63✔
184
                        return it.second.IsString();
105✔
185
                })) {
186
                auto err = json::MakeError(json::TypeError, "Unexpected non-string data in provides");
2✔
187
                return expected::unexpected(err);
2✔
188
        }
189
        for (const auto &it : ex_children.value()) {
166✔
190
                ret[it.first] = it.second.GetString().value();
104✔
191
        }
192

193
        return ret;
62✔
194
}
195

196
expected::ExpectedString MenderContext::GetDeviceType() {
76✔
197
        string device_type_fpath =
198
                path::Join(config_.default_config.GetDefaultDataStore(), "device_type");
152✔
199
        auto ex_is = io::OpenIfstream(device_type_fpath);
152✔
200
        if (!ex_is) {
76✔
201
                return expected::ExpectedString(expected::unexpected(ex_is.error()));
4✔
202
        }
203

204
        auto &is = ex_is.value();
74✔
205
        string line;
148✔
206
        errno = 0;
74✔
207
        getline(is, line);
74✔
208
        if (is.bad()) {
74✔
209
                int io_errno = errno;
×
210
                error::Error err {
211
                        generic_category().default_error_condition(io_errno),
×
212
                        "Failed to read device type from '" + device_type_fpath + "'"};
×
213
                return expected::ExpectedString(expected::unexpected(err));
×
214
        }
215

216
        const string::size_type eq_pos = 12;
74✔
217
        if (line.substr(0, eq_pos) != "device_type=") {
74✔
218
                auto err = MakeError(ParseError, "Failed to parse device_type data '" + line + "'");
6✔
219
                return expected::ExpectedString(expected::unexpected(err));
6✔
220
        }
221

222
        string ret = line.substr(eq_pos, string::npos);
142✔
223

224
        if (!is.eof()) {
71✔
225
                errno = 0;
70✔
226
                getline(is, line);
70✔
227
                if ((line != "") || (!is.eof())) {
70✔
228
                        auto err = MakeError(ValueError, "Trailing device_type data");
4✔
229
                        return expected::ExpectedString(expected::unexpected(err));
4✔
230
                }
231
        }
232

233
        return expected::ExpectedString(ret);
69✔
234
}
235

236
static error::Error FilterProvides(
40✔
237
        const ProvidesData &new_provides,
238
        const ClearsProvidesData &clears_provides,
239
        ProvidesData &to_modify) {
240
        // Use clears_provides to filter out unwanted provides.
241
        for (auto to_clear : clears_provides) {
127✔
242
                string escaped;
87✔
243
                // Potential to escape every character, though unlikely.
244
                escaped.reserve(to_clear.size() * 2);
87✔
245
                // Notable exception: '*', since it has special handling as a glob character.
246
                string meta_characters {".^$+()[]{}|?"};
87✔
247
                for (const auto chr : to_clear) {
1,639✔
248
                        if (chr == '*') {
1,552✔
249
                                // Turn every '*' glob wildcard into '.*' regex wildcard.
250
                                escaped.push_back('.');
39✔
251
                        } else if (any_of(meta_characters.begin(), meta_characters.end(), [chr](char c) {
1,513✔
252
                                                   return chr == c;
17,606✔
253
                                           })) {
254
                                // Escape every regex special character except '*'.
255
                                escaped.push_back('\\');
50✔
256
                        }
257
                        escaped.push_back(chr);
1,552✔
258
                }
259

260
                regex compiled;
87✔
261
                auto err = error::ExceptionToErrorOrAbort(
262
                        [&compiled, &escaped]() { compiled.assign(escaped, regex_constants::basic); });
174✔
263
                // Should not be possible, since the whole regex is escaped.
264
                AssertOrReturnError(err == error::NoError);
87✔
265

266
                set<string> keys;
174✔
267
                for (auto provide : to_modify) {
155✔
268
                        if (regex_match(provide.first.begin(), provide.first.end(), compiled)) {
68✔
269
                                keys.insert(provide.first);
12✔
270
                        }
271
                }
272
                for (auto key : keys) {
99✔
273
                        to_modify.erase(key);
12✔
274
                }
275
        }
276

277
        // Now add the provides from the new_provides set.
278
        for (auto provide : new_provides) {
103✔
279
                to_modify[provide.first] = provide.second;
63✔
280
        }
281

282
        return error::NoError;
40✔
283
}
284

285
error::Error MenderContext::CommitArtifactData(
46✔
286
        string artifact_name,
287
        string artifact_group,
288
        const optional::optional<ProvidesData> &new_provides,
289
        const optional::optional<ClearsProvidesData> &clears_provides,
290
        function<error::Error(kv_db::Transaction &)> txn_func) {
291
        return mender_store_.WriteTransaction([&](kv_db::Transaction &txn) {
46✔
292
                auto exp_existing = LoadProvides(txn);
92✔
293
                if (!exp_existing) {
46✔
294
                        return exp_existing.error();
×
295
                }
296
                auto modified_provides = exp_existing.value();
92✔
297

298
                error::Error err;
92✔
299
                if (!new_provides && !clears_provides) {
46✔
300
                        // Neither provides nor clear_provides came with the artifact. This means
301
                        // erase everything. `artifact_name` and `artifact_group` will still be
302
                        // preserved through special cases below.
303
                        modified_provides.clear();
3✔
304
                } else if (!new_provides) {
43✔
305
                        // No new provides came with the artifact. This means filter what we have,
306
                        // but don't add any new provides fields.
307
                        ProvidesData empty_provides;
2✔
308
                        err = FilterProvides(empty_provides, clears_provides.value(), modified_provides);
2✔
309
                } else if (!clears_provides) {
41✔
310
                        // Missing clears_provides is equivalent to `["*"]`, for historical reasons.
311
                        modified_provides = new_provides.value();
3✔
312
                } else {
313
                        // Standard case, filter existing provides using clears_provides, and then
314
                        // add new ones on top.
315
                        err = FilterProvides(new_provides.value(), clears_provides.value(), modified_provides);
38✔
316
                }
317
                if (err != error::NoError) {
46✔
318
                        return err;
×
319
                }
320

321
                if (artifact_name != "") {
46✔
322
                        modified_provides["artifact_name"] = artifact_name;
46✔
323
                }
324
                if (artifact_group != "") {
46✔
325
                        modified_provides["artifact_group"] = artifact_group;
4✔
326
                }
327

328
                string artifact_provides_str {"{"};
92✔
329
                for (const auto &it : modified_provides) {
165✔
330
                        if (it.first != "artifact_name" && it.first != "artifact_group") {
119✔
331
                                artifact_provides_str +=
332
                                        "\"" + it.first + "\":" + "\"" + json::EscapeString(it.second) + "\",";
68✔
333
                        }
334
                }
335

336
                // if some key-value pairs were added, replace the trailing comma with the
337
                // closing '}' to make a valid JSON
338
                if (artifact_provides_str != "{") {
46✔
339
                        artifact_provides_str[artifact_provides_str.length() - 1] = '}';
42✔
340
                } else {
341
                        // set to an empty value for consistency with the other two items
342
                        artifact_provides_str = "";
4✔
343
                }
344

345
                if (modified_provides["artifact_name"] != "") {
46✔
346
                        err = txn.Write(
46✔
347
                                artifact_name_key,
348
                                common::ByteVectorFromString(modified_provides["artifact_name"]));
92✔
349
                        if (err != error::NoError) {
46✔
350
                                return err;
×
351
                        }
352
                } else {
353
                        // This should not happen.
354
                        AssertOrReturnError(false);
×
355
                }
356

357
                if (modified_provides["artifact_group"] != "") {
46✔
358
                        err = txn.Write(
5✔
359
                                artifact_group_key,
360
                                common::ByteVectorFromString(modified_provides["artifact_group"]));
10✔
361
                } else {
362
                        err = txn.Remove(artifact_group_key);
41✔
363
                }
364
                if (err != error::NoError) {
46✔
365
                        return err;
×
366
                }
367

368
                if (artifact_provides_str != "") {
46✔
369
                        err = txn.Write(
42✔
370
                                artifact_provides_key, common::ByteVectorFromString(artifact_provides_str));
84✔
371
                        if (err != error::NoError) {
42✔
372
                                return err;
×
373
                        }
374
                }
375
                return txn_func(txn);
46✔
376
        });
46✔
377
}
378

379
expected::ExpectedBool MenderContext::MatchesArtifactDepends(const artifact::HeaderView &hdr_view) {
×
380
        auto ex_dev_type = GetDeviceType();
×
381
        if (!ex_dev_type) {
×
382
                return expected::unexpected(ex_dev_type.error());
×
383
        }
384
        auto ex_provides = LoadProvides();
×
385
        if (!ex_provides) {
×
386
                return expected::unexpected(ex_provides.error());
×
387
        }
388
        auto &provides = ex_provides.value();
×
389
        return ArtifactMatchesContext(
390
                provides, ex_dev_type.value(), hdr_view.header_info, hdr_view.type_info);
×
391
}
392

393
expected::ExpectedBool ArtifactMatchesContext(
12✔
394
        const ProvidesData &provides,
395
        const string &device_type,
396
        const artifact::HeaderInfo &hdr_info,
397
        const artifact::TypeInfo &type_info) {
398
        if (!common::MapContainsStringKey(provides, "artifact_name")) {
12✔
399
                return expected::unexpected(
×
400
                        MakeError(ValueError, "Missing artifact_name value in provides"));
×
401
        }
402

403
        const auto &hdr_depends = hdr_info.depends;
12✔
404
        AssertOrReturnUnexpected(hdr_depends.device_type.size() > 0);
12✔
405
        if (!common::VectorContainsString(hdr_depends.device_type, device_type)) {
12✔
406
                log::Debug("Artifact device type doesn't match");
1✔
407
                return false;
1✔
408
        }
409

410
        if (hdr_depends.artifact_name) {
11✔
411
                AssertOrReturnUnexpected(hdr_depends.artifact_name->size() > 0);
9✔
412
                if (!common::VectorContainsString(
9✔
413
                                *hdr_depends.artifact_name, provides.at("artifact_name"))) {
9✔
414
                        log::Debug("Artifact name doesn't match");
1✔
415
                        return false;
1✔
416
                }
417
        }
418

419
        if (hdr_depends.artifact_group) {
10✔
420
                AssertOrReturnUnexpected(hdr_depends.artifact_group->size() > 0);
8✔
421
                if (!common::MapContainsStringKey(provides, "artifact_group")) {
8✔
422
                        log::Debug(
1✔
423
                                "Missing artifact_group value in provides, required by artifact header info depends");
2✔
424
                        return false;
1✔
425
                }
426
                if (!common::VectorContainsString(
7✔
427
                                *hdr_depends.artifact_group, provides.at("artifact_group"))) {
7✔
428
                        log::Debug("Artifact group doesn't match");
1✔
429
                        return false;
1✔
430
                }
431
        }
432

433
        const auto &ti_depends = type_info.artifact_depends;
8✔
434
        if (!ti_depends) {
8✔
435
                // nothing more to check
436
                return true;
5✔
437
        }
438
        for (auto it : *ti_depends) {
4✔
439
                if (!common::MapContainsStringKey(provides, it.first)) {
3✔
440
                        log::Debug(
1✔
441
                                "Missing '" + it.first + "' in provides, required by artifact type info depends");
2✔
442
                        return false;
1✔
443
                }
444
                if (provides.at(it.first) != it.second) {
2✔
445
                        log::Debug(
1✔
446
                                "'" + it.first + "' artifact type info depends value '" + it.second
2✔
447
                                + "' doesn't match provides value '" + provides.at(it.first) + "'");
3✔
448
                        return false;
1✔
449
                }
450
        }
451

452
        return true;
1✔
453
}
454

455
} // namespace context
456
} // namespace update
457
} // 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