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

mendersoftware / mender / 939977254

pending completion
939977254

push

gitlab-ci

kacf
chore: Move database key values to more central location.

There will also be more keys later, further motivating this move.

Signed-off-by: Kristian Amlie <kristian.amlie@northern.tech>

9 of 9 new or added lines in 1 file covered. (100.0%)

4174 of 5691 relevant lines covered (73.34%)

154.25 hits per line

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

77.3
/mender-update/deployments.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/deployments.hpp>
16

17
#include <sstream>
18
#include <string>
19

20
#include <api/api.hpp>
21
#include <common/common.hpp>
22
#include <common/error.hpp>
23
#include <common/events.hpp>
24
#include <common/expected.hpp>
25
#include <common/http.hpp>
26
#include <common/io.hpp>
27
#include <common/json.hpp>
28
#include <common/log.hpp>
29
#include <common/optional.hpp>
30
#include <common/path.hpp>
31
#include <mender-update/context.hpp>
32

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

37
using namespace std;
38

39
namespace api = mender::api;
40
namespace common = mender::common;
41
namespace context = mender::update::context;
42
namespace error = mender::common::error;
43
namespace events = mender::common::events;
44
namespace expected = mender::common::expected;
45
namespace io = mender::common::io;
46
namespace json = mender::common::json;
47
namespace log = mender::common::log;
48
namespace optional = mender::common::optional;
49
namespace path = mender::common::path;
50

51
const DeploymentsErrorCategoryClass DeploymentsErrorCategory;
52

53
const char *DeploymentsErrorCategoryClass::name() const noexcept {
×
54
        return "DeploymentsErrorCategory";
×
55
}
56

57
string DeploymentsErrorCategoryClass::message(int code) const {
×
58
        switch (code) {
×
59
        case NoError:
×
60
                return "Success";
×
61
        case InvalidDataError:
×
62
                return "Invalid data error";
×
63
        case BadResponseError:
×
64
                return "Bad response error";
×
65
        }
66
        assert(false);
×
67
        return "Unknown";
68
}
69

70
error::Error MakeError(DeploymentsErrorCode code, const string &msg) {
3✔
71
        return error::Error(error_condition(code, DeploymentsErrorCategory), msg);
6✔
72
}
73

74
static const string check_updates_v1_uri = "/api/devices/v1/deployments/device/deployments/next";
75
static const string check_updates_v2_uri = "/api/devices/v2/deployments/device/deployments/next";
76

77
error::Error CheckNewDeployments(
6✔
78
        context::MenderContext &ctx,
79
        const string &server_url,
80
        http::Client &client,
81
        CheckUpdatesAPIResponseHandler api_handler) {
82
        auto ex_dev_type = ctx.GetDeviceType();
12✔
83
        if (!ex_dev_type) {
6✔
84
                return ex_dev_type.error();
×
85
        }
86
        string device_type = ex_dev_type.value();
12✔
87

88
        auto ex_provides = ctx.LoadProvides();
12✔
89
        if (!ex_provides) {
6✔
90
                return ex_provides.error();
×
91
        }
92
        auto provides = ex_provides.value();
12✔
93
        if (provides.find("artifact_name") == provides.end()) {
6✔
94
                return MakeError(InvalidDataError, "Missing artifact name data");
×
95
        }
96

97
        stringstream ss;
12✔
98
        ss << R"({"update_control_map":false,"device_provides":{)";
6✔
99
        ss << R"("device_type":")";
6✔
100
        ss << json::EscapeString(device_type);
6✔
101

102
        for (const auto &kv : provides) {
14✔
103
                ss << "\",\"" + json::EscapeString(kv.first) + "\":\"";
8✔
104
                ss << json::EscapeString(kv.second);
8✔
105
        }
106

107
        ss << R"("}})";
6✔
108

109
        string v2_payload = ss.str();
12✔
110
        http::BodyGenerator payload_gen = [v2_payload]() {
6✔
111
                return make_shared<io::StringReader>(v2_payload);
6✔
112
        };
12✔
113

114
        // TODO: APIRequest
115
        auto v2_req = make_shared<http::OutgoingRequest>();
12✔
116
        v2_req->SetAddress(http::JoinUrl(server_url, check_updates_v2_uri));
6✔
117
        v2_req->SetMethod(http::Method::POST);
6✔
118
        v2_req->SetHeader("Content-Type", "application/json");
6✔
119
        v2_req->SetHeader("Content-Length", to_string(v2_payload.size()));
6✔
120
        v2_req->SetHeader("Accept", "application/json");
6✔
121
        v2_req->SetBodyGenerator(payload_gen);
6✔
122

123
        string v1_args = "artifact_name=" + http::URLEncode(provides["artifact_name"])
12✔
124
                                         + "&device_type=" + http::URLEncode(device_type);
24✔
125
        auto v1_req = make_shared<http::OutgoingRequest>();
12✔
126
        v1_req->SetAddress(http::JoinUrl(server_url, check_updates_v1_uri) + "?" + v1_args);
6✔
127
        v1_req->SetMethod(http::Method::GET);
6✔
128
        v1_req->SetHeader("Accept", "application/json");
6✔
129

130
        auto received_body = make_shared<vector<uint8_t>>();
12✔
131
        auto handle_data = [received_body, api_handler](unsigned status) {
4✔
132
                if (status == http::StatusOK) {
4✔
133
                        auto ex_j = json::Load(common::StringFromByteVector(*received_body));
4✔
134
                        if (ex_j) {
2✔
135
                                CheckUpdatesAPIResponse response {optional::optional<json::Json> {ex_j.value()}};
2✔
136
                                api_handler(response);
2✔
137
                        } else {
138
                                api_handler(expected::unexpected(ex_j.error()));
×
139
                        }
140
                } else if (status == http::StatusNoContent) {
2✔
141
                        api_handler(CheckUpdatesAPIResponse {optional::nullopt});
2✔
142
                }
143
        };
16✔
144

145
        http::ResponseHandler header_handler =
146
                [received_body, api_handler](http::ExpectedIncomingResponsePtr exp_resp) {
9✔
147
                        if (!exp_resp) {
9✔
148
                                log::Error("Request to check new deployments failed: " + exp_resp.error().message);
×
149
                                CheckUpdatesAPIResponse response = expected::unexpected(exp_resp.error());
×
150
                                api_handler(response);
×
151
                                return;
×
152
                        }
153

154
                        auto resp = exp_resp.value();
18✔
155
                        received_body->clear();
9✔
156
                        auto body_writer = make_shared<io::ByteWriter>(received_body);
9✔
157
                        body_writer->SetUnlimited(true);
9✔
158
                        resp->SetBodyWriter(body_writer);
9✔
159
                };
12✔
160

161
        http::ResponseHandler v1_body_handler =
162
                [received_body, api_handler, handle_data](http::ExpectedIncomingResponsePtr exp_resp) {
3✔
163
                        if (!exp_resp) {
3✔
164
                                log::Error("Request to check new deployments failed: " + exp_resp.error().message);
×
165
                                CheckUpdatesAPIResponse response = expected::unexpected(exp_resp.error());
×
166
                                api_handler(response);
×
167
                                return;
×
168
                        }
169
                        auto resp = exp_resp.value();
6✔
170
                        auto status = resp->GetStatusCode();
3✔
171
                        if ((status == http::StatusOK) || (status == http::StatusNoContent)) {
3✔
172
                                handle_data(status);
2✔
173
                        } else {
174
                                auto ex_err_msg = api::ErrorMsgFromErrorResponse(*received_body);
2✔
175
                                string err_str;
1✔
176
                                if (ex_err_msg) {
1✔
177
                                        err_str = ex_err_msg.value();
×
178
                                } else {
179
                                        err_str = resp->GetStatusMessage();
1✔
180
                                }
181
                                api_handler(expected::unexpected(MakeError(
2✔
182
                                        BadResponseError,
183
                                        "Got unexpected response " + to_string(status) + ": " + err_str)));
3✔
184
                        }
185
                };
12✔
186

187
        http::ResponseHandler v2_body_handler = [received_body,
6✔
188
                                                                                         v1_req,
189
                                                                                         header_handler,
190
                                                                                         v1_body_handler,
191
                                                                                         api_handler,
192
                                                                                         handle_data,
193
                                                                                         &client](http::ExpectedIncomingResponsePtr exp_resp) {
3✔
194
                if (!exp_resp) {
6✔
195
                        log::Error("Request to check new deployments failed: " + exp_resp.error().message);
×
196
                        CheckUpdatesAPIResponse response = expected::unexpected(exp_resp.error());
×
197
                        api_handler(response);
×
198
                        return;
×
199
                }
200
                auto resp = exp_resp.value();
12✔
201
                auto status = resp->GetStatusCode();
6✔
202
                if ((status == http::StatusOK) || (status == http::StatusNoContent)) {
6✔
203
                        handle_data(status);
2✔
204
                } else if (status == http::StatusNotFound) {
4✔
205
                        log::Info(
3✔
206
                                "POST request to v2 version of the deployments API failed, falling back to v1 version and GET");
6✔
207
                        auto err = client.AsyncCall(v1_req, header_handler, v1_body_handler);
9✔
208
                        if (err != error::NoError) {
3✔
209
                                api_handler(expected::unexpected(err.WithContext("While calling v1 endpoint")));
×
210
                        }
211
                } else {
212
                        auto ex_err_msg = api::ErrorMsgFromErrorResponse(*received_body);
2✔
213
                        string err_str;
1✔
214
                        if (ex_err_msg) {
1✔
215
                                err_str = ex_err_msg.value();
1✔
216
                        } else {
217
                                err_str = resp->GetStatusMessage();
×
218
                        }
219
                        api_handler(expected::unexpected(MakeError(
2✔
220
                                BadResponseError,
221
                                "Got unexpected response " + to_string(status) + ": " + err_str)));
3✔
222
                }
223
        };
6✔
224

225
        return client.AsyncCall(v2_req, header_handler, v2_body_handler);
6✔
226
}
227

228
static const string deployment_status_strings[static_cast<int>(DeploymentStatus::End_) + 1] = {
229
        "installing",
230
        "pause_before_installing",
231
        "downloading",
232
        "pause_before_rebooting",
233
        "rebooting",
234
        "pause_before_committing",
235
        "success",
236
        "failure",
237
        "already-installed"};
238

239
static const string status_uri_prefix = "/api/devices/v1/deployments/device/deployments";
240
static const string status_uri_suffix = "/status";
241

242
error::Error PushStatus(
3✔
243
        const string &deployment_id,
244
        DeploymentStatus status,
245
        const string &substate,
246
        const string &server_url,
247
        http::Client &client,
248
        StatusAPIResponseHandler api_handler) {
249
        string payload = R"({"status":")" + deployment_status_strings[static_cast<int>(status)] + "\"";
6✔
250
        if (substate != "") {
3✔
251
                payload += R"(,"substate":")" + json::EscapeString(substate) + "\"}";
2✔
252
        } else {
253
                payload += "}";
1✔
254
        }
255
        http::BodyGenerator payload_gen = [payload]() {
3✔
256
                return make_shared<io::StringReader>(payload);
3✔
257
        };
6✔
258

259
        auto req = make_shared<http::OutgoingRequest>();
6✔
260
        req->SetAddress(http::JoinUrl(server_url, status_uri_prefix, deployment_id, status_uri_suffix));
3✔
261
        req->SetMethod(http::Method::PUT);
3✔
262
        req->SetHeader("Content-Type", "application/json");
3✔
263
        req->SetHeader("Content-Length", to_string(payload.size()));
3✔
264
        req->SetHeader("Accept", "application/json");
3✔
265
        req->SetBodyGenerator(payload_gen);
3✔
266

267
        auto received_body = make_shared<vector<uint8_t>>();
3✔
268
        return client.AsyncCall(
269
                req,
270
                [received_body, api_handler](http::ExpectedIncomingResponsePtr exp_resp) {
3✔
271
                        if (!exp_resp) {
3✔
272
                                log::Error("Request to push status data failed: " + exp_resp.error().message);
×
273
                                api_handler(exp_resp.error());
×
274
                        }
275

276
                        auto body_writer = make_shared<io::ByteWriter>(received_body);
6✔
277
                        auto resp = exp_resp.value();
6✔
278
                        auto content_length = resp->GetHeader("Content-Length");
9✔
279
                        auto ex_len = common::StringToLongLong(content_length.value());
3✔
280
                        if (!ex_len) {
3✔
281
                                log::Error("Failed to get content length from the status API response headers");
×
282
                                body_writer->SetUnlimited(true);
×
283
                        } else {
284
                                received_body->resize(ex_len.value());
3✔
285
                        }
286
                        resp->SetBodyWriter(body_writer);
3✔
287
                },
3✔
288
                [received_body, api_handler](http::ExpectedIncomingResponsePtr exp_resp) {
3✔
289
                        if (!exp_resp) {
3✔
290
                                log::Error("Request to push status data failed: " + exp_resp.error().message);
×
291
                                api_handler(exp_resp.error());
×
292
                        }
293

294
                        auto resp = exp_resp.value();
6✔
295
                        auto status = resp->GetStatusCode();
3✔
296
                        if (status == http::StatusOK) {
3✔
297
                                api_handler(error::NoError);
2✔
298
                        } else {
299
                                auto ex_err_msg = api::ErrorMsgFromErrorResponse(*received_body);
2✔
300
                                string err_str;
1✔
301
                                if (ex_err_msg) {
1✔
302
                                        err_str = ex_err_msg.value();
1✔
303
                                } else {
304
                                        err_str = resp->GetStatusMessage();
×
305
                                }
306
                                api_handler(MakeError(
1✔
307
                                        BadResponseError,
308
                                        "Got unexpected response " + to_string(status)
2✔
309
                                                + " from status API: " + err_str));
2✔
310
                        }
311
                });
9✔
312
}
313

314
} // namespace deployments
315
} // namespace update
316
} // 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