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

mendersoftware / mender / 2276016505

21 Jan 2026 09:06AM UTC coverage: 81.359% (-0.02%) from 81.376%
2276016505

push

gitlab-ci

web-flow
Merge pull request #1883 from vpodzime/master-key_gen_embed_auth

fix: Generate new key in mender-update if needed with embedded auth

0 of 2 new or added lines in 1 file covered. (0.0%)

1 existing line in 1 file now uncovered.

8790 of 10804 relevant lines covered (81.36%)

20316.57 hits per line

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

65.33
/src/mender-update/cli/actions.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/actions.hpp>
16

17
#include <algorithm>
18
#include <iostream>
19
#include <string>
20

21
#include <artifact/config.hpp>
22

23
#include <common/common.hpp>
24
#include <common/error.hpp>
25
#include <common/events.hpp>
26
#include <common/expected.hpp>
27
#include <common/key_value_database.hpp>
28
#include <common/log.hpp>
29
#include <common/path.hpp>
30
#include <common/processes.hpp>
31

32
#include <mender-update/cli/cli.hpp>
33
#include <mender-update/daemon.hpp>
34
#include <mender-update/standalone.hpp>
35

36
#ifdef MENDER_EMBED_MENDER_AUTH
37
#include <mender-auth/cli/actions.hpp>
38
#include <mender-auth/cli/keystore.hpp>
39
#endif
40

41
namespace mender {
42
namespace update {
43
namespace cli {
44

45
namespace processes = mender::common::processes;
46
namespace conf = mender::client_shared::conf;
47
namespace daemon = mender::update::daemon;
48
namespace database = mender::common::key_value_database;
49
namespace error = mender::common::error;
50
namespace events = mender::common::events;
51
namespace expected = mender::common::expected;
52
namespace http = mender::common::http;
53
namespace kv_db = mender::common::key_value_database;
54
namespace log = mender::common::log;
55
namespace path = mender::common::path;
56
namespace standalone = mender::update::standalone;
57

58
static error::Error DoMaybeInstallBootstrapArtifact(context::MenderContext &main_context) {
107✔
59
        const string bootstrap_artifact_path {
60
                main_context.GetConfig().paths.GetBootstrapArtifactFile()};
61
        // Check if the DB is populated - then install conditionally
62
        auto &db = main_context.GetMenderStoreDB();
107✔
63
        auto exp_key = db.Read(main_context.artifact_name_key);
107✔
64
        if (exp_key) {
107✔
65
                // Key exists. Do nothing
66
                return error::NoError;
58✔
67
        }
68
        error::Error err = exp_key.error();
49✔
69
        if (err.code != kv_db::MakeError(kv_db::KeyError, "Key Not found").code) {
98✔
70
                return err;
×
71
        }
72

73
        // Key does not exist, install the bootstrap artifact if it exists
74
        if (!path::FileExists(bootstrap_artifact_path)) {
49✔
75
                log::Debug("No Bootstrap Artifact found at: " + bootstrap_artifact_path);
46✔
76
                error::Error err =
77
                        db.Write(main_context.artifact_name_key, common::ByteVectorFromString("unknown"));
46✔
78
                if (err != error::NoError) {
46✔
79
                        return err;
×
80
                }
81
                return error::NoError;
46✔
82
        }
83
        log::Info("Installing the bootstrap Artifact");
6✔
84
        events::EventLoop loop;
85
        standalone::Context ctx {main_context, loop};
3✔
86
        auto result = standalone::Install(
87
                ctx,
88
                bootstrap_artifact_path,
89
                artifact::config::Signature::Skip,
90
                standalone::InstallOptions::NoStdout);
3✔
91

92
        if (result.err != error::NoError) {
3✔
93
                error::Error err =
94
                        db.Write(main_context.artifact_name_key, common::ByteVectorFromString("unknown"));
×
95
                return result.err.FollowedBy(err).WithContext("Failed to install the bootstrap Artifact");
×
96
        }
97
        return error::NoError;
3✔
98
}
3✔
99

100
error::Error MaybeInstallBootstrapArtifact(context::MenderContext &main_context) {
107✔
101
        const string bootstrap_artifact_path {
102
                main_context.GetConfig().paths.GetBootstrapArtifactFile()};
103
        error::Error err = DoMaybeInstallBootstrapArtifact(main_context);
107✔
104

105
        // Unconditionally delete the bootstrap Artifact
106
        if (path::FileExists(bootstrap_artifact_path)) {
107✔
107
                error::Error delete_err = path::FileDelete(bootstrap_artifact_path);
3✔
108
                if (delete_err != error::NoError) {
3✔
109
                        return err.FollowedBy(
110
                                delete_err.WithContext("Failed to delete the bootstrap Artifact"));
×
111
                }
112
        }
113
        return err;
107✔
114
}
115

116
error::Error ShowArtifactAction::Execute(context::MenderContext &main_context) {
2✔
117
        error::Error err = MaybeInstallBootstrapArtifact(main_context);
2✔
118
        if (err != error::NoError) {
2✔
119
                return err;
×
120
        }
121

122
        auto exp_provides = main_context.LoadProvides();
2✔
123
        if (!exp_provides) {
2✔
124
                return exp_provides.error();
×
125
        }
126

127
        auto &provides = exp_provides.value();
2✔
128
        if (provides.count("artifact_name") == 0 || provides["artifact_name"] == "") {
8✔
129
                cout << "unknown" << endl;
×
130
        } else {
131
                cout << provides["artifact_name"] << endl;
6✔
132
        }
133
        return error::NoError;
2✔
134
}
135

136
error::Error ShowProvidesAction::Execute(context::MenderContext &main_context) {
40✔
137
        error::Error err = MaybeInstallBootstrapArtifact(main_context);
40✔
138
        if (err != error::NoError) {
40✔
139
                return err;
×
140
        }
141

142
        auto exp_provides = main_context.LoadProvides();
40✔
143
        if (!exp_provides) {
40✔
144
                return exp_provides.error();
×
145
        }
146

147
        auto &provides = exp_provides.value();
40✔
148
        for (const auto &elem : provides) {
137✔
149
                cout << elem.first << "=" << elem.second << endl;
97✔
150
        }
151

152
        return error::NoError;
40✔
153
}
154

155
static error::Error ResultHandler(standalone::ResultAndError result) {
87✔
156
        using Result = standalone::Result;
157

158
        if (result.err != error::NoError) {
87✔
159
                log::Error(result.err.String());
48✔
160
        } else if (ResultContains(result.result, Result::Failed)) {
63✔
161
                // All error states, make sure they have an error.
162
                result.err = error::MakeError(error::ExitWithFailureError, "");
4✔
163
        }
164

165
        using r = Result;
166
        auto contains = [&result](r val) { return ResultContains(result.result, val); };
438✔
167
        auto none_of = [&result](r val) { return ResultNoneOf(result.result, val); };
168
        auto add = [](string &str, const string &content) {
5✔
169
                if (str.size() > 0) {
5✔
170
                        str += " ";
×
171
                }
172
                str += content;
5✔
173
        };
5✔
174

175
        string operation_done;
176
        string operation_failure;
177

178
        // For failure case, include which attempted operation failed.
179
        if (contains(r::DownloadFailed)) {
87✔
180
                operation_failure = "Streaming failed.";
6✔
181
        } else if (contains(r::InstallFailed)) {
81✔
182
                operation_failure = "Installation failed.";
8✔
183
        } else if (contains(r::CommitFailed)) {
73✔
184
                operation_failure = "Committing failed.";
6✔
185
        }
186

187
        // For done case, include which operation succeded.
188
        if (contains(r::Committed) and none_of(r::Installed)) {
87✔
189
                operation_done = "Committed.";
6✔
190
        } else if (contains(r::Installed) and none_of(r::Committed)) {
81✔
191
                operation_done =
192
                        "Installed, but not committed.\n"
193
                        "Use 'commit' to update, or 'rollback' to roll back the update.";
17✔
194
        } else if (contains(r::Downloaded) and none_of(r::Installed)) {
64✔
195
                operation_done = "Streamed to storage, but not installed/enabled.";
9✔
196
        } else if (contains(r::Installed | r::Committed)) {
55✔
197
                operation_done = "Installed and committed.";
29✔
198
        } else if (contains(r::NoUpdateInProgress)) {
26✔
199
                operation_done = "No update in progress.";
2✔
200
        } else if (contains(r::Cleaned) and none_of(~r::Cleaned)) {
24✔
201
                // Only include this message if it was the only thing done.
202
                operation_done = "Cleaned up.";
2✔
203
        }
204

205
        // Pick which one of the done/failure cases to use. If the failure happened after the
206
        // commit, we pick the done case, since the operation was still completed.
207
        string &operation = (contains(r::Failed) and none_of(r::FailedInPostCommit | r::CleanupFailed))
24✔
208
                                                        ? operation_failure
209
                                                        : operation_done;
87✔
210

211
        string prefix;
212
        string additional;
213

214
        if (contains(r::AutoCommitWanted) and contains(r::Installed)
33✔
215
                and (contains(r::Committed) or contains(r::Failed))) {
120✔
216
                prefix = "Update Module doesn't support rollback. Committing immediately.";
32✔
217
        }
218

219
        if (contains(r::RollbackFailed)) {
87✔
220
                additional =
221
                        "Rollback failed. "
222
                        "System may be in an inconsistent state.";
7✔
223
        } else if (contains(r::NoRollback)) {
80✔
224
                additional =
225
                        "Update Module does not support rollback. "
226
                        "System may be in an inconsistent state.";
5✔
227
        } else if (contains(r::NoRollbackNecessary)) {
75✔
228
                additional = "System not modified.";
7✔
229
        } else if (contains(r::RolledBack)) {
68✔
230
                additional = "Rolled back.";
10✔
231
        }
232

233
        if (contains(r::FailedInPostCommit)) {
87✔
234
                add(additional, "One or more post-commit steps failed.");
4✔
235
        }
236
        if (contains(r::CleanupFailed)) {
87✔
237
                add(additional, "Cleanup failed.");
2✔
238
        }
239

240
        if (contains(r::RebootRequired)) {
87✔
241
                add(additional, "At least one payload requested a reboot of the device it updated.");
2✔
242
                if (result.err == error::NoError) {
2✔
243
                        result.err = context::MakeError(context::RebootRequiredError, "Reboot required");
4✔
244
                }
245
        }
246

247
        if (prefix.size() > 0) {
87✔
248
                cout << prefix << endl;
32✔
249
        }
250
        if (operation.size() > 0) {
87✔
251
                cout << operation << endl;
70✔
252
        }
253
        if (additional.size() > 0) {
87✔
254
                cout << additional << endl;
34✔
255
        }
256

257
        return result.err;
174✔
258
}
259

260
error::Error InstallAction::Execute(context::MenderContext &main_context) {
60✔
261
        error::Error err = MaybeInstallBootstrapArtifact(main_context);
60✔
262
        if (err != error::NoError) {
60✔
263
                return err;
264
        }
265
        events::EventLoop loop;
266
        standalone::Context ctx {main_context, loop};
60✔
267
        ctx.stop_before = std::move(stop_before_);
60✔
268
        auto result = standalone::Install(ctx, src_);
60✔
269
        err = ResultHandler(result);
60✔
270
        if (!reboot_exit_code_
120✔
271
                && err.code == context::MakeError(context::RebootRequiredError, "").code) {
178✔
272
                // If reboot exit code isn't requested, then this type of error should be treated as
273
                // plain success.
274
                err = error::NoError;
1✔
275
        }
276
        return err;
277
}
60✔
278

279
error::Error ResumeAction::Execute(context::MenderContext &main_context) {
13✔
280
        events::EventLoop loop;
281
        standalone::Context ctx {main_context, loop};
13✔
282
        ctx.stop_before = std::move(stop_before_);
13✔
283

284
        auto result = standalone::Resume(ctx);
13✔
285
        auto err = ResultHandler(result);
13✔
286

287
        if (!reboot_exit_code_
26✔
288
                && err.code == context::MakeError(context::RebootRequiredError, "").code) {
39✔
289
                // If reboot exit code isn't requested, then this type of error should be treated as
290
                // plain success.
291
                err = error::NoError;
×
292
        }
293
        return err;
13✔
294
}
13✔
295

296
error::Error CommitAction::Execute(context::MenderContext &main_context) {
6✔
297
        events::EventLoop loop;
298
        standalone::Context ctx {main_context, loop};
6✔
299
        ctx.stop_before = std::move(stop_before_);
6✔
300
        auto result = standalone::Commit(ctx);
6✔
301
        return ResultHandler(result);
18✔
302
}
6✔
303

304
error::Error RollbackAction::Execute(context::MenderContext &main_context) {
8✔
305
        events::EventLoop loop;
306
        standalone::Context ctx {main_context, loop};
8✔
307
        ctx.stop_before = std::move(stop_before_);
8✔
308
        auto result = standalone::Rollback(ctx);
8✔
309
        return ResultHandler(result);
24✔
310
}
8✔
311

312
error::Error DaemonAction::Execute(context::MenderContext &main_context) {
×
313
        events::EventLoop event_loop;
314
        daemon::Context ctx(main_context, event_loop);
×
NEW
315
        error::Error err;
×
316

317
#if not defined(MENDER_USE_DBUS) and defined(MENDER_EMBED_MENDER_AUTH)
318
        // Passphrase is not currently supported when launching from mender-update cli.
319
        auto key_store = mender::auth::cli::KeystoreFromConfig(ctx.mender_context.GetConfig(), "");
320
        err = key_store->Load();
321
        if (err != error::NoError
322
                && err.code != mender::auth::cli::MakeError(mender::auth::cli::NoKeysError, "").code) {
323
                return err;
324
        }
325
        if (err != error::NoError) {
326
                log::Error("Got error loading the private key from the keystore: " + err.String());
327
        }
328
        if (err.code == mender::auth::cli::MakeError(mender::auth::cli::NoKeysError, "").code) {
329
                log::Info("Generating new ED25519 key");
330
                err = key_store->Generate();
331
                if (err != error::NoError) {
332
                        log::Error("Failed to generate new key: " + err.String());
333
                        return err;
334
                }
335
                err = key_store->Save();
336
                if (err != error::NoError) {
337
                        log::Error("Failed to save new key: " + err.String());
338
                        return err;
339
                }
340
        }
341
        ctx.authenticator.SetCryptoArgs(
342
                {key_store->KeyName(), key_store->PassPhrase(), key_store->SSLEngine()});
343
#endif
344

345
        daemon::StateMachine state_machine(ctx, event_loop);
×
346
        state_machine.LoadStateFromDb();
×
NEW
347
        err = MaybeInstallBootstrapArtifact(main_context);
×
348
        if (err != error::NoError) {
×
349
                return err;
×
350
        }
351

352
        event_loop.Post([]() {
×
353
                log::Info("The update client daemon is now ready to handle incoming deployments");
×
354
        });
×
355

356
        return state_machine.Run();
×
357
}
×
358

359
static expected::ExpectedString GetPID() {
×
360
        processes::Process proc({"systemctl", "show", "--property=MainPID", "mender-updated"});
×
361
        auto exp_line_data = proc.GenerateLineData();
×
362
        if (!exp_line_data) {
×
363
                return expected::unexpected(
×
364
                        exp_line_data.error().WithContext("Failed to get the MainPID from systemctl"));
×
365
        }
366
        if (exp_line_data.value().size() < 1) {
×
367
                return expected::unexpected(error::Error(
×
368
                        make_error_condition(errc::message_size), "No output received from systemctl"));
×
369
        }
370
        const string prefix {"MainPID="};
×
371
        const string line = exp_line_data.value().at(0);
×
372
        auto split_index = line.find(prefix);
×
373
        if (split_index == string::npos) {
×
374
                return expected::unexpected(error::Error(
×
375
                        make_error_condition(errc::no_message), "No output received from systemctl"));
×
376
        }
377
        if (split_index != 0) {
×
378
                return expected::unexpected(error::Error(
×
379
                        make_error_condition(errc::bad_message), "Unexpected output from systemctl"));
×
380
        }
381
        const string PID = line.substr(split_index + prefix.size(), line.size());
×
382
        if (PID == "" or PID == "0") {
×
383
                return expected::unexpected(error::Error(
×
384
                        make_error_condition(errc::no_message),
×
385
                        "No PID found for mender-updated. The service is not running"));
×
386
        }
387
        return PID;
×
388
}
×
389

390
static error::Error SendSignal(const string &signal, const string &pid) {
×
391
        const vector<string> command {"kill", "-" + signal, pid};
×
392
        const string command_string = common::JoinStrings(command, " ");
×
393
        processes::Process proc(command);
×
394
        auto err = proc.Start();
×
395
        if (err != error::NoError) {
×
396
                return err.WithContext("Command '" + command_string + "'");
×
397
        }
398
        return proc.Wait().WithContext("Command '" + command_string + "'");
×
399
}
×
400

401
error::Error SendInventoryAction::Execute(context::MenderContext &main_context) {
×
402
        auto pid = GetPID();
×
403
        if (!pid) {
×
404
                return pid.error().WithContext("Failed to force an inventory update");
×
405
        }
406

407
        return SendSignal("SIGUSR2", pid.value()).WithContext("Failed to force an inventory update");
×
408
}
409

410
error::Error CheckUpdateAction::Execute(context::MenderContext &main_context) {
×
411
        auto pid = GetPID();
×
412
        if (!pid) {
×
413
                return pid.error().WithContext("Failed to force an update check");
×
414
        }
415
        return SendSignal("SIGUSR1", pid.value()).WithContext("Failed to force an update check");
×
416
}
417

418
} // namespace cli
419
} // namespace update
420
} // 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