]> git.ipfire.org Git - pakfire.git/blame - src/libpakfire/buildservice.c
buildservice: Export upload UUID
[pakfire.git] / src / libpakfire / buildservice.c
CommitLineData
931d97a0
MT
1/*#############################################################################
2# #
3# Pakfire - The IPFire package management system #
4# Copyright (C) 2021 Pakfire development team #
5# #
6# This program is free software: you can redistribute it and/or modify #
7# it under the terms of the GNU General Public License as published by #
8# the Free Software Foundation, either version 3 of the License, or #
9# (at your option) any later version. #
10# #
11# This program is distributed in the hope that it will be useful, #
12# but WITHOUT ANY WARRANTY; without even the implied warranty of #
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14# GNU General Public License for more details. #
15# #
16# You should have received a copy of the GNU General Public License #
17# along with this program. If not, see <http://www.gnu.org/licenses/>. #
18# #
19#############################################################################*/
20
21#include <errno.h>
3d2cc9e6 22#include <limits.h>
931d97a0
MT
23#include <stdlib.h>
24
57a8b2d4
MT
25#include <json.h>
26
931d97a0 27#include <pakfire/buildservice.h>
3d2cc9e6 28#include <pakfire/config.h>
931d97a0 29#include <pakfire/ctx.h>
01840335 30#include <pakfire/digest.h>
d188b602 31#include <pakfire/downloader.h>
3d2cc9e6 32#include <pakfire/logging.h>
01840335 33#include <pakfire/path.h>
931d97a0 34#include <pakfire/private.h>
3d2cc9e6 35#include <pakfire/string.h>
57a8b2d4 36#include <pakfire/util.h>
931d97a0 37
ff5cb6ba
MT
38#include <krb5/krb5.h>
39
1e4b70d0
MT
40#define DEFAULT_KEYTAB "/etc/krb5.keytab"
41
931d97a0
MT
42struct pakfire_buildservice {
43 struct pakfire_ctx* ctx;
44 int nrefs;
3d2cc9e6 45
d188b602 46 // Configuration
3d2cc9e6 47 char url[PATH_MAX];
1e4b70d0 48 char keytab[PATH_MAX];
d188b602
MT
49
50 // A HTTP Client
51 struct pakfire_downloader* httpclient;
ff5cb6ba
MT
52
53 // Kerberos Context
54 krb5_context krb5_ctx;
931d97a0
MT
55};
56
ff5cb6ba
MT
57static int pakfire_buildservice_setup_auth(struct pakfire_buildservice* service) {
58 const char* error = NULL;
59 int r;
60
61 // Setup a Kerberos context
62 r = krb5_init_context(&service->krb5_ctx);
63 if (r) {
64 error = krb5_get_error_message(service->krb5_ctx, r);
65
66 CTX_ERROR(service->ctx, "Could not initialize Kerberos: %s\n", error);
67 krb5_free_error_message(service->krb5_ctx, error);
68
69 goto ERROR;
70 }
71
72ERROR:
73 return r;
74}
75
3d2cc9e6
MT
76static int pakfire_buildservice_setup(struct pakfire_buildservice* service) {
77 struct pakfire_config* config = NULL;
78 const char* url = NULL;
1e4b70d0 79 const char* keytab = NULL;
3d2cc9e6
MT
80 int r;
81
82 // Fetch the configuration
83 config = pakfire_ctx_get_config(service->ctx);
84 if (!config) {
85 r = 1;
86 goto ERROR;
87 }
88
89 // Fetch the URL
90 url = pakfire_config_get(config, "client", "url", NULL);
91 if (!url) {
92 CTX_ERROR(service->ctx, "Build Service URL is not configured\n");
93 r = 1;
94 goto ERROR;
95 }
96
97 // Store the URL
98 r = pakfire_string_set(service->url, url);
99 if (r)
100 goto ERROR;
101
1e4b70d0
MT
102 // Fetch the keytab
103 keytab = pakfire_config_get(config, "client", "keytab", DEFAULT_KEYTAB);
104
105 // Store the keytab
106 r = pakfire_string_set(service->keytab, keytab);
107 if (r)
108 goto ERROR;
109
d188b602
MT
110 // Setup the downloader
111 r = pakfire_downloader_create(&service->httpclient, service->ctx);
112 if (r)
113 goto ERROR;
114
ff5cb6ba
MT
115 // Setup authentication
116 r = pakfire_buildservice_setup_auth(service);
117 if (r)
118 goto ERROR;
119
3d2cc9e6
MT
120ERROR:
121 if (config)
122 pakfire_config_unref(config);
123
124 return r;
125}
126
931d97a0 127static void pakfire_buildservice_free(struct pakfire_buildservice* service) {
ff5cb6ba
MT
128 if (service->krb5_ctx)
129 krb5_free_context(service->krb5_ctx);
d188b602
MT
130 if (service->httpclient)
131 pakfire_downloader_unref(service->httpclient);
931d97a0
MT
132 if (service->ctx)
133 pakfire_ctx_unref(service->ctx);
134
135 free(service);
136}
137
138PAKFIRE_EXPORT int pakfire_buildservice_create(
139 struct pakfire_buildservice** service, struct pakfire_ctx* ctx) {
140 struct pakfire_buildservice* s = NULL;
3d2cc9e6 141 int r;
931d97a0
MT
142
143 // Allocate some memory
144 s = calloc(1, sizeof(*s));
145 if (!s)
146 return -errno;
147
148 // Store a reference to the context
149 s->ctx = pakfire_ctx_ref(ctx);
150
151 // Initialize the reference counter
152 s->nrefs = 1;
153
3d2cc9e6
MT
154 // Setup everything
155 r = pakfire_buildservice_setup(s);
156 if (r)
157 goto ERROR;
158
931d97a0
MT
159 // Return the pointer
160 *service = s;
161
162 return 0;
3d2cc9e6
MT
163
164ERROR:
165 pakfire_buildservice_free(s);
166
167 return r;
931d97a0
MT
168}
169
170PAKFIRE_EXPORT struct pakfire_buildservice* pakfire_buildservice_ref(
171 struct pakfire_buildservice* service) {
172 ++service->nrefs;
173
174 return service;
175}
176
177PAKFIRE_EXPORT struct pakfire_buildservice* pakfire_buildservice_unref(
178 struct pakfire_buildservice* service) {
179 if (--service->nrefs > 0)
180 return service;
181
182 pakfire_buildservice_free(service);
183 return NULL;
184}
57a8b2d4
MT
185
186static int pakfire_buildservice_create_transfer(struct pakfire_transfer** transfer,
187 struct pakfire_buildservice* service, const char* url) {
188 struct pakfire_transfer* t = NULL;
189 int r;
190
191 // Create a new transfer
192 r = pakfire_downloader_transfer_create(&t, service->httpclient, url);
193 if (r)
194 goto ERROR;
195
196 // Set the base URL
197 r = pakfire_downloader_transfer_set_baseurl(t, service->url);
198 if (r)
199 goto ERROR;
200
201 // Return the new transfer
202 *transfer = pakfire_downloader_transfer_ref(t);
203
204ERROR:
205 if (t)
206 pakfire_downloader_transfer_unref(t);
207
208 return r;
209}
210
33de78f2
MT
211static int pakfire_buildservice_handle_error(struct pakfire_buildservice* service,
212 struct pakfire_transfer* transfer, const struct json_object* error) {
213 struct json_object* message = NULL;
214 struct json_object* code = NULL;
215 const char* m = NULL;
216 unsigned int c = 0;
217
218 // Fetch the URL
219 const char* url = pakfire_downloader_transfer_get_effective_url(transfer);
220
221 // Fetch the code
222 if (!json_object_object_get_ex(error, "code", &code))
223 return -EBADMSG;
224
225 // Check if the code is an integer
226 if (!json_object_is_type(code, json_type_int))
227 return -EBADMSG;
228
229 // Fetch the message
230 if (!json_object_object_get_ex(error, "message", &message))
231 return -EBADMSG;
232
233 // Check if the message is a string
234 if (!json_object_is_type(message, json_type_string))
235 return -EBADMSG;
236
237 c = json_object_get_uint64(code);
238 m = json_object_get_string(message);
239
240 // Log the error
605615a2
MT
241 CTX_ERROR(service->ctx, "%s responded with error %u (%s):\n %s\n",
242 url, c, strerror(c), m);
33de78f2 243
605615a2 244 return -c;
33de78f2
MT
245}
246
247/*
248 This function parses an API response
249*/
250static int pakfire_buildservice_parse_response(struct pakfire_buildservice* service,
251 struct pakfire_transfer* transfer, const char* buffer, const size_t length,
252 struct json_object** object) {
253 struct json_object* error = NULL;
254 struct json_object* o = NULL;
255 int r;
256
cc2371c1
MT
257 // Check if we received any data
258 if (!length) {
259 CTX_ERROR(service->ctx, "Received an empty response\n");
260 r = -EBADMSG;
261 goto ERROR;
262 }
263
33de78f2
MT
264 // XXX Maybe fetch the parser's error message here?!
265
266 // Parse the buffer
267 o = pakfire_json_parse(service->ctx, buffer, length);
268 if (!o) {
269 CTX_ERROR(service->ctx, "Could not parse the response\n");
270 r = -EBADMSG;
271 goto ERROR;
272 }
273
274 // Check if the response is a dictionary
275 if (!json_object_is_type(o, json_type_object)) {
276 CTX_ERROR(service->ctx, "The received object is not a JSON dict\n");
277 r = -EBADMSG;
278 goto ERROR;
279 }
280
281 // Fetch error
282 r = json_object_object_get_ex(o, "error", &error);
283 if (r) {
284 r = pakfire_buildservice_handle_error(service, transfer, error);
285 goto ERROR;
286 }
287
288 // Return the object
289 *object = o;
290
291 return 0;
292
293ERROR:
294 if (o)
295 json_object_put(o);
296
297 return r;
298}
299
57a8b2d4
MT
300// Uploads
301
01840335
MT
302static int pakfire_buildservice_create_upload(struct pakfire_buildservice* service,
303 const char* path, const char* filename, FILE* f, char** uuid) {
304 struct pakfire_transfer* transfer = NULL;
27e7b6f6 305 struct pakfire_digests digests = {};
01840335
MT
306 struct json_object* response = NULL;
307 struct json_object* id = NULL;
01840335
MT
308 const char* __id = NULL;
309 char* hexdigest = NULL;
310 char* buffer = NULL;
311 size_t length = 0;
312 struct stat stat;
313 int r;
314
315 const int fd = fileno(f);
316
317 // Stat the file
318 r = fstat(fd, &stat);
319 if (r) {
320 CTX_ERROR(service->ctx, "Could not stat %s: %s\n", path, strerror(errno));
321 goto ERROR;
322 }
323
324 // Compute the digest
325 r = pakfire_digests_compute_from_file(service->ctx, &digests, PAKFIRE_DIGEST_BLAKE2B512, f);
326 if (r) {
327 CTX_ERROR(service->ctx, "Could not compute the digest of %s: %s\n",
328 path, strerror(-r));
329 goto ERROR;
330 }
331
332 // Convert the digest into hex format
333 hexdigest = pakfire_digest_get_hex(&digests, PAKFIRE_DIGEST_BLAKE2B512);
334 if (!hexdigest)
335 goto ERROR;
336
337 // Create a new transfer
338 r = pakfire_buildservice_create_transfer(&transfer, service, "/api/v1/uploads");
339 if (r)
340 goto ERROR;
341
342 // Enable authentication
343 r = pakfire_downloader_transfer_auth(transfer);
344 if (r)
345 goto ERROR;
346
347 // Add the filename parameter
348 r = pakfire_downloader_transfer_add_param(transfer, "filename", "%s", filename);
349 if (r)
350 goto ERROR;
351
352 // Add the size parameter
353 r = pakfire_downloader_transfer_add_param(transfer, "size", "%jd", stat.st_size);
354 if (r)
355 goto ERROR;
356
357 // Add the hexdigest algo parameter
358 r = pakfire_downloader_transfer_add_param(transfer, "hexdigest_algo", "%s", "blake2b512");
359 if (r)
360 goto ERROR;
361
362 // Add the hexdigest parameter
363 r = pakfire_downloader_transfer_add_param(transfer, "hexdigest", "%s", hexdigest);
364 if (r)
365 goto ERROR;
366
367 // Write the response to the buffer
368 r = pakfire_downloader_transfer_set_output_buffer(transfer, &buffer, &length);
369 if (r)
370 goto ERROR;
371
372 // Run the transfer
373 r = pakfire_downloader_transfer_run(transfer, PAKFIRE_TRANSFER_NO_PROGRESS);
374 if (r)
375 goto ERROR;
376
33de78f2
MT
377 // Parse the response
378 r = pakfire_buildservice_parse_response(service, transfer, buffer, length, &response);
379 if (r) {
380 CTX_ERROR(service->ctx, "Could not parse the response: %s\n", strerror(-r));
01840335
MT
381 goto ERROR;
382 }
383
384 // Fetch the ID
385 r = json_object_object_get_ex(response, "id", &id);
386 if (r == 0) {
387 CTX_ERROR(service->ctx, "Could not fetch ID from response\n");
388 r = -EBADMSG;
389 goto ERROR;
390 }
391
392 // Extract the UUID
393 __id = json_object_get_string(id);
394 if (!__id) {
395 CTX_ERROR(service->ctx, "Could not fetch ID from response\n");
396 r = -EBADMSG;
397 goto ERROR;
398 }
399
400 // Return the UUID
401 *uuid = strdup(__id);
402 if (!*uuid) {
403 r = -errno;
404 goto ERROR;
405 }
406
407 // Success
408 r = 0;
409
410ERROR:
411 if (transfer)
412 pakfire_downloader_transfer_unref(transfer);
413 if (response)
414 json_object_put(response);
415 if (hexdigest)
416 free(hexdigest);
417 if (buffer)
418 free(buffer);
419
420 return r;
421}
422
423static int pakfire_buildservice_upload_payload(struct pakfire_buildservice* service,
424 const char* filename, const char* uuid, FILE* f) {
425 struct pakfire_transfer* transfer = NULL;
33de78f2
MT
426 struct json_object* response = NULL;
427 char* buffer = NULL;
428 size_t length = 0;
01840335
MT
429 char url[PATH_MAX];
430 int r;
431
432 // Make the URL
433 r = pakfire_string_format(url, "/api/v1/uploads/%s", uuid);
434 if (r)
435 goto ERROR;
436
437 // Create a new transfer
438 r = pakfire_buildservice_create_transfer(&transfer, service, url);
439 if (r)
440 goto ERROR;
441
442 // Set the title
443 r = pakfire_downloader_transfer_set_title(transfer, filename);
444 if (r)
445 goto ERROR;
446
447 // Set source file
448 r = pakfire_downloader_transfer_set_input(transfer, f);
449 if (r)
450 goto ERROR;
451
33de78f2
MT
452 // Set the output buffer
453 r = pakfire_downloader_transfer_set_output_buffer(transfer, &buffer, &length);
454 if (r)
455 goto ERROR;
456
01840335
MT
457 // Run the transfer
458 r = pakfire_downloader_transfer_run(transfer, 0);
459 if (r)
460 goto ERROR;
461
33de78f2
MT
462 // Parse the response
463 r = pakfire_buildservice_parse_response(service, transfer, buffer, length, &response);
464 if (r) {
465 CTX_ERROR(service->ctx, "Could not parse the response: %s\n", strerror(-r));
466 goto ERROR;
467 }
468
01840335
MT
469ERROR:
470 if (transfer)
471 pakfire_downloader_transfer_unref(transfer);
33de78f2
MT
472 if (response)
473 json_object_put(response);
01840335
MT
474
475 return r;
476}
477
478PAKFIRE_EXPORT int pakfire_buildservice_upload(struct pakfire_buildservice* service,
81adcd5c 479 const char* path, const char* filename, char** uuid) {
01840335 480 char basename[NAME_MAX];
01840335
MT
481 FILE* f = NULL;
482 int r;
483
484 // Compute the basename
485 r = pakfire_path_basename(basename, path);
486 if (r)
487 goto ERROR;
488
489 // Set the basename as default filename
490 if (!filename)
491 filename = basename;
492
493 // Open the source file
494 f = fopen(path, "r");
495 if (!f) {
496 CTX_ERROR(service->ctx, "Could not open file for upload %s: %m\n", path);
497 return -errno;
498 }
499
500 // Create a new upload
81adcd5c 501 r = pakfire_buildservice_create_upload(service, path, filename, f, uuid);
01840335
MT
502 if (r)
503 goto ERROR;
504
81adcd5c 505 CTX_DEBUG(service->ctx, "Created a new download (%s)\n", *uuid);
01840335
MT
506
507 // Send the payload
81adcd5c 508 r = pakfire_buildservice_upload_payload(service, filename, *uuid, f);
01840335
MT
509 if (r)
510 goto ERROR;
511
512ERROR:
81adcd5c
MT
513 if (r) {
514 if (*uuid)
515 free(*uuid);
516
517 *uuid = NULL;
518 }
01840335
MT
519 if (f)
520 fclose(f);
521
522 return r;
523}
524
57a8b2d4
MT
525PAKFIRE_EXPORT int pakfire_buildservice_list_uploads(
526 struct pakfire_buildservice* service, struct json_object** p) {
527 struct pakfire_transfer* transfer = NULL;
528 struct json_object* response = NULL;
529 struct json_object* uploads = NULL;
530 char* buffer = NULL;
531 size_t length = 0;
532 int r;
533
534 // Create a new transfer
0144f5c7 535 r = pakfire_buildservice_create_transfer(&transfer, service, "/api/v1/uploads");
57a8b2d4
MT
536 if (r)
537 goto ERROR;
538
539 // Enable authentication
540 r = pakfire_downloader_transfer_auth(transfer);
541 if (r)
542 goto ERROR;
543
544 // Write the response to the buffer
545 r = pakfire_downloader_transfer_set_output_buffer(transfer, &buffer, &length);
546 if (r)
547 goto ERROR;
548
549 // Run the transfer
550 r = pakfire_downloader_transfer_run(transfer, PAKFIRE_TRANSFER_NO_PROGRESS);
551 if (r)
552 goto ERROR;
553
57a8b2d4 554 // Parse the response
33de78f2
MT
555 r = pakfire_buildservice_parse_response(service, transfer, buffer, length, &response);
556 if (r) {
557 CTX_ERROR(service->ctx, "Could not parse the response: %s\n", strerror(-r));
57a8b2d4
MT
558 goto ERROR;
559 }
560
561 // Fetch the uploads
562 uploads = json_object_object_get(response, "uploads");
563 if (!uploads) {
564 CTX_ERROR(service->ctx, "Malformed response\n");
565 r = -EBADMSG;
566 goto ERROR;
567 }
568
569 // Return the pointer
570 *p = json_object_get(uploads);
571
572ERROR:
573 if (transfer)
574 pakfire_downloader_transfer_unref(transfer);
575 if (response)
576 json_object_put(response);
577 if (buffer)
578 free(buffer);
579
580 return r;
581}
c0b6198a
MT
582
583PAKFIRE_EXPORT int pakfire_buildservice_delete_upload(
584 struct pakfire_buildservice* service, const char* uuid) {
585 struct pakfire_transfer* transfer = NULL;
586 struct json_object* response = NULL;
587 char* buffer = NULL;
588 size_t length = 0;
589 char url[PATH_MAX];
590 int r;
591
592 // Compose the URL
593 r = pakfire_string_format(url, "/api/v1/uploads/%s", uuid);
594 if (r)
595 goto ERROR;
596
597 // Create a new transfer
598 r = pakfire_buildservice_create_transfer(&transfer, service, url);
599 if (r)
600 goto ERROR;
601
602 // Ask to DELETE
603 r = pakfire_downloader_transfer_set_method(transfer, PAKFIRE_METHOD_DELETE);
604 if (r)
605 goto ERROR;
606
607 // Enable authentication
608 r = pakfire_downloader_transfer_auth(transfer);
609 if (r)
610 goto ERROR;
611
612 // Write the response to the buffer
613 r = pakfire_downloader_transfer_set_output_buffer(transfer, &buffer, &length);
614 if (r)
615 goto ERROR;
616
617 // Run the transfer
618 r = pakfire_downloader_transfer_run(transfer, PAKFIRE_TRANSFER_NO_PROGRESS);
619 if (r)
620 goto ERROR;
621
622 // Parse the response
623 r = pakfire_buildservice_parse_response(service, transfer, buffer, length, &response);
624 if (r) {
625 CTX_ERROR(service->ctx, "Could not parse the response: %s\n", strerror(-r));
626 goto ERROR;
627 }
628
629ERROR:
630 if (transfer)
631 pakfire_downloader_transfer_unref(transfer);
632 if (response)
633 json_object_put(response);
634 if (buffer)
635 free(buffer);
636
637 return r;
638}