chiark / gitweb /
import: print nice warning if we need btrfs but /var/lib/machines is not btrfs
[elogind.git] / src / import / import-dkr.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2014 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <curl/curl.h>
23 #include <sys/prctl.h>
24
25 #include "sd-daemon.h"
26 #include "json.h"
27 #include "strv.h"
28 #include "btrfs-util.h"
29 #include "utf8.h"
30 #include "mkdir.h"
31 #include "import-util.h"
32 #include "curl-util.h"
33 #include "aufs-util.h"
34 #include "import-job.h"
35 #include "import-common.h"
36 #include "import-dkr.h"
37
38 typedef enum DkrProgress {
39         DKR_SEARCHING,
40         DKR_RESOLVING,
41         DKR_METADATA,
42         DKR_DOWNLOADING,
43         DKR_COPYING,
44 } DkrProgress;
45
46 struct DkrImport {
47         sd_event *event;
48         CurlGlue *glue;
49
50         char *index_url;
51         char *image_root;
52
53         ImportJob *images_job;
54         ImportJob *tags_job;
55         ImportJob *ancestry_job;
56         ImportJob *json_job;
57         ImportJob *layer_job;
58
59         char *name;
60         char *tag;
61         char *id;
62
63         char *response_token;
64         char **response_registries;
65
66         char **ancestry;
67         unsigned n_ancestry;
68         unsigned current_ancestry;
69
70         DkrImportFinished on_finished;
71         void *userdata;
72
73         char *local;
74         bool force_local;
75
76         char *temp_path;
77         char *final_path;
78
79         pid_t tar_pid;
80 };
81
82 #define PROTOCOL_PREFIX "https://"
83
84 #define HEADER_TOKEN "X-Do" /* the HTTP header for the auth token */ "cker-Token:"
85 #define HEADER_REGISTRY "X-Do" /*the HTTP header for the registry */ "cker-Endpoints:"
86
87 #define LAYERS_MAX 2048
88
89 static void dkr_import_job_on_finished(ImportJob *j);
90
91 DkrImport* dkr_import_unref(DkrImport *i) {
92         if (!i)
93                 return NULL;
94
95         if (i->tar_pid > 1) {
96                 (void) kill_and_sigcont(i->tar_pid, SIGKILL);
97                 (void) wait_for_terminate(i->tar_pid, NULL);
98         }
99
100         import_job_unref(i->images_job);
101         import_job_unref(i->tags_job);
102         import_job_unref(i->ancestry_job);
103         import_job_unref(i->json_job);
104         import_job_unref(i->layer_job);
105
106         curl_glue_unref(i->glue);
107         sd_event_unref(i->event);
108
109         if (i->temp_path) {
110                 (void) btrfs_subvol_remove(i->temp_path);
111                 (void) rm_rf_dangerous(i->temp_path, false, true, false);
112                 free(i->temp_path);
113         }
114
115         free(i->name);
116         free(i->tag);
117         free(i->id);
118         free(i->response_token);
119         free(i->response_registries);
120         strv_free(i->ancestry);
121         free(i->final_path);
122         free(i->index_url);
123         free(i->image_root);
124         free(i->local);
125         free(i);
126
127         return NULL;
128 }
129
130 int dkr_import_new(
131                 DkrImport **ret,
132                 sd_event *event,
133                 const char *index_url,
134                 const char *image_root,
135                 DkrImportFinished on_finished,
136                 void *userdata) {
137
138         _cleanup_(dkr_import_unrefp) DkrImport *i = NULL;
139         char *e;
140         int r;
141
142         assert(ret);
143         assert(index_url);
144
145         if (!http_url_is_valid(index_url))
146                 return -EINVAL;
147
148         i = new0(DkrImport, 1);
149         if (!i)
150                 return -ENOMEM;
151
152         i->on_finished = on_finished;
153         i->userdata = userdata;
154
155         i->image_root = strdup(image_root ?: "/var/lib/machines");
156         if (!i->image_root)
157                 return -ENOMEM;
158
159         i->index_url = strdup(index_url);
160         if (!i->index_url)
161                 return -ENOMEM;
162
163         e = endswith(i->index_url, "/");
164         if (e)
165                 *e = 0;
166
167         if (event)
168                 i->event = sd_event_ref(event);
169         else {
170                 r = sd_event_default(&i->event);
171                 if (r < 0)
172                         return r;
173         }
174
175         r = curl_glue_new(&i->glue, i->event);
176         if (r < 0)
177                 return r;
178
179         i->glue->on_finished = import_job_curl_on_finished;
180         i->glue->userdata = i;
181
182         *ret = i;
183         i = NULL;
184
185         return 0;
186 }
187
188 static void dkr_import_report_progress(DkrImport *i, DkrProgress p) {
189         unsigned percent;
190
191         assert(i);
192
193         switch (p) {
194
195         case DKR_SEARCHING:
196                 percent = 0;
197                 if (i->images_job)
198                         percent += i->images_job->progress_percent * 5 / 100;
199                 break;
200
201         case DKR_RESOLVING:
202                 percent = 5;
203                 if (i->tags_job)
204                         percent += i->tags_job->progress_percent * 5 / 100;
205                 break;
206
207         case DKR_METADATA:
208                 percent = 10;
209                 if (i->ancestry_job)
210                         percent += i->ancestry_job->progress_percent * 5 / 100;
211                 if (i->json_job)
212                         percent += i->json_job->progress_percent * 5 / 100;
213                 break;
214
215         case DKR_DOWNLOADING:
216                 percent = 20;
217                 percent += 75 * i->current_ancestry / MAX(1U, i->n_ancestry);
218                 if (i->layer_job)
219                         percent += i->layer_job->progress_percent * 75 / MAX(1U, i->n_ancestry) / 100;
220
221                 break;
222
223         case DKR_COPYING:
224                 percent = 95;
225                 break;
226
227         default:
228                 assert_not_reached("Unknown progress state");
229         }
230
231         sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent);
232         log_debug("Combined progress %u%%", percent);
233 }
234
235 static int parse_id(const void *payload, size_t size, char **ret) {
236         _cleanup_free_ char *buf = NULL, *id = NULL, *other = NULL;
237         union json_value v = {};
238         void *json_state = NULL;
239         const char *p;
240         int t;
241
242         assert(payload);
243         assert(ret);
244
245         if (size <= 0)
246                 return -EBADMSG;
247
248         if (memchr(payload, 0, size))
249                 return -EBADMSG;
250
251         buf = strndup(payload, size);
252         if (!buf)
253                 return -ENOMEM;
254
255         p = buf;
256         t = json_tokenize(&p, &id, &v, &json_state, NULL);
257         if (t < 0)
258                 return t;
259         if (t != JSON_STRING)
260                 return -EBADMSG;
261
262         t = json_tokenize(&p, &other, &v, &json_state, NULL);
263         if (t < 0)
264                 return t;
265         if (t != JSON_END)
266                 return -EBADMSG;
267
268         if (!dkr_id_is_valid(id))
269                 return -EBADMSG;
270
271         *ret = id;
272         id = NULL;
273
274         return 0;
275 }
276
277 static int parse_ancestry(const void *payload, size_t size, char ***ret) {
278         _cleanup_free_ char *buf = NULL;
279         void *json_state = NULL;
280         const char *p;
281         enum {
282                 STATE_BEGIN,
283                 STATE_ITEM,
284                 STATE_COMMA,
285                 STATE_END,
286         } state = STATE_BEGIN;
287         _cleanup_strv_free_ char **l = NULL;
288         size_t n = 0, allocated = 0;
289
290         if (size <= 0)
291                 return -EBADMSG;
292
293         if (memchr(payload, 0, size))
294                 return -EBADMSG;
295
296         buf = strndup(payload, size);
297         if (!buf)
298                 return -ENOMEM;
299
300         p = buf;
301         for (;;) {
302                 _cleanup_free_ char *str;
303                 union json_value v = {};
304                 int t;
305
306                 t = json_tokenize(&p, &str, &v, &json_state, NULL);
307                 if (t < 0)
308                         return t;
309
310                 switch (state) {
311
312                 case STATE_BEGIN:
313                         if (t == JSON_ARRAY_OPEN)
314                                 state = STATE_ITEM;
315                         else
316                                 return -EBADMSG;
317
318                         break;
319
320                 case STATE_ITEM:
321                         if (t == JSON_STRING) {
322                                 if (!dkr_id_is_valid(str))
323                                         return -EBADMSG;
324
325                                 if (n+1 > LAYERS_MAX)
326                                         return -EFBIG;
327
328                                 if (!GREEDY_REALLOC(l, allocated, n + 2))
329                                         return -ENOMEM;
330
331                                 l[n++] = str;
332                                 str = NULL;
333                                 l[n] = NULL;
334
335                                 state = STATE_COMMA;
336
337                         } else if (t == JSON_ARRAY_CLOSE)
338                                 state = STATE_END;
339                         else
340                                 return -EBADMSG;
341
342                         break;
343
344                 case STATE_COMMA:
345                         if (t == JSON_COMMA)
346                                 state = STATE_ITEM;
347                         else if (t == JSON_ARRAY_CLOSE)
348                                 state = STATE_END;
349                         else
350                                 return -EBADMSG;
351                         break;
352
353                 case STATE_END:
354                         if (t == JSON_END) {
355
356                                 if (strv_isempty(l))
357                                         return -EBADMSG;
358
359                                 if (!strv_is_uniq(l))
360                                         return -EBADMSG;
361
362                                 l = strv_reverse(l);
363
364                                 *ret = l;
365                                 l = NULL;
366                                 return 0;
367                         } else
368                                 return -EBADMSG;
369                 }
370
371         }
372 }
373
374 static const char *dkr_import_current_layer(DkrImport *i) {
375         assert(i);
376
377         if (strv_isempty(i->ancestry))
378                 return NULL;
379
380         return i->ancestry[i->current_ancestry];
381 }
382
383 static const char *dkr_import_current_base_layer(DkrImport *i) {
384         assert(i);
385
386         if (strv_isempty(i->ancestry))
387                 return NULL;
388
389         if (i->current_ancestry <= 0)
390                 return NULL;
391
392         return i->ancestry[i->current_ancestry-1];
393 }
394
395 static int dkr_import_add_token(DkrImport *i, ImportJob *j) {
396         const char *t;
397
398         assert(i);
399         assert(j);
400
401         if (i->response_token)
402                 t = strjoina("Authorization: Token ", i->response_token);
403         else
404                 t = HEADER_TOKEN " true";
405
406         j->request_header = curl_slist_new("Accept: application/json", t, NULL);
407         if (!j->request_header)
408                 return -ENOMEM;
409
410         return 0;
411 }
412
413 static bool dkr_import_is_done(DkrImport *i) {
414         assert(i);
415         assert(i->images_job);
416
417         if (i->images_job->state != IMPORT_JOB_DONE)
418                 return false;
419
420         if (!i->tags_job || i->tags_job->state != IMPORT_JOB_DONE)
421                 return false;
422
423         if (!i->ancestry_job || i->ancestry_job->state != IMPORT_JOB_DONE)
424                 return false;
425
426         if (!i->json_job || i->json_job->state != IMPORT_JOB_DONE)
427                 return false;
428
429         if (i->layer_job && i->layer_job->state != IMPORT_JOB_DONE)
430                 return false;
431
432         if (dkr_import_current_layer(i))
433                 return false;
434
435         return true;
436 }
437
438 static int dkr_import_make_local_copy(DkrImport *i) {
439         int r;
440
441         assert(i);
442
443         if (!i->local)
444                 return 0;
445
446         if (!i->final_path) {
447                 i->final_path = strjoin(i->image_root, "/.dkr-", i->id, NULL);
448                 if (!i->final_path)
449                         return log_oom();
450         }
451
452         r = import_make_local_copy(i->final_path, i->image_root, i->local, i->force_local);
453         if (r < 0)
454                 return r;
455
456         return 0;
457 }
458
459 static int dkr_import_job_on_open_disk(ImportJob *j) {
460         const char *base;
461         DkrImport *i;
462         int r;
463
464         assert(j);
465         assert(j->userdata);
466
467         i = j->userdata;
468         assert(i->layer_job == j);
469         assert(i->final_path);
470         assert(!i->temp_path);
471         assert(i->tar_pid <= 0);
472
473         r = tempfn_random(i->final_path, &i->temp_path);
474         if (r < 0)
475                 return log_oom();
476
477         mkdir_parents_label(i->temp_path, 0700);
478
479         base = dkr_import_current_base_layer(i);
480         if (base) {
481                 const char *base_path;
482
483                 base_path = strjoina(i->image_root, "/.dkr-", base);
484                 r = btrfs_subvol_snapshot(base_path, i->temp_path, false, true);
485         } else
486                 r = btrfs_subvol_make(i->temp_path);
487         if (r < 0)
488                 return log_error_errno(r, "Failed to make btrfs subvolume %s: %m", i->temp_path);
489
490         j->disk_fd = import_fork_tar(i->temp_path, &i->tar_pid);
491         if (j->disk_fd < 0)
492                 return j->disk_fd;
493
494         return 0;
495 }
496
497 static void dkr_import_job_on_progress(ImportJob *j) {
498         DkrImport *i;
499
500         assert(j);
501         assert(j->userdata);
502
503         i = j->userdata;
504
505         dkr_import_report_progress(
506                         i,
507                         j == i->images_job                       ? DKR_SEARCHING :
508                         j == i->tags_job                         ? DKR_RESOLVING :
509                         j == i->ancestry_job || j == i->json_job ? DKR_METADATA :
510                                                                    DKR_DOWNLOADING);
511 }
512
513 static int dkr_import_pull_layer(DkrImport *i) {
514         _cleanup_free_ char *path = NULL;
515         const char *url, *layer = NULL;
516         int r;
517
518         assert(i);
519         assert(!i->layer_job);
520         assert(!i->temp_path);
521         assert(!i->final_path);
522
523         for (;;) {
524                 layer = dkr_import_current_layer(i);
525                 if (!layer)
526                         return 0; /* no more layers */
527
528                 path = strjoin(i->image_root, "/.dkr-", layer, NULL);
529                 if (!path)
530                         return log_oom();
531
532                 if (laccess(path, F_OK) < 0) {
533                         if (errno == ENOENT)
534                                 break;
535
536                         return log_error_errno(errno, "Failed to check for container: %m");
537                 }
538
539                 log_info("Layer %s already exists, skipping.", layer);
540
541                 i->current_ancestry++;
542
543                 free(path);
544                 path = NULL;
545         }
546
547         log_info("Pulling layer %s...", layer);
548
549         i->final_path = path;
550         path = NULL;
551
552         url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", layer, "/layer");
553         r = import_job_new(&i->layer_job, url, i->glue, i);
554         if (r < 0)
555                 return log_error_errno(r, "Failed to allocate layer job: %m");
556
557         r = dkr_import_add_token(i, i->layer_job);
558         if (r < 0)
559                 return log_oom();
560
561         i->layer_job->on_finished = dkr_import_job_on_finished;
562         i->layer_job->on_open_disk = dkr_import_job_on_open_disk;
563         i->layer_job->on_progress = dkr_import_job_on_progress;
564
565         r = import_job_begin(i->layer_job);
566         if (r < 0)
567                 return log_error_errno(r, "Failed to start layer job: %m");
568
569         return 0;
570 }
571
572 static void dkr_import_job_on_finished(ImportJob *j) {
573         DkrImport *i;
574         int r;
575
576         assert(j);
577         assert(j->userdata);
578
579         i = j->userdata;
580         if (j->error != 0) {
581                 if (j == i->images_job)
582                         log_error_errno(j->error, "Failed to retrieve images list. (Wrong index URL?)");
583                 else if (j == i->tags_job)
584                         log_error_errno(j->error, "Failed to retrieve tags list.");
585                 else if (j == i->ancestry_job)
586                         log_error_errno(j->error, "Failed to retrieve ancestry list.");
587                 else if (j == i->json_job)
588                         log_error_errno(j->error, "Failed to retrieve json data.");
589                 else
590                         log_error_errno(j->error, "Failed to retrieve layer data.");
591
592                 r = j->error;
593                 goto finish;
594         }
595
596         if (i->images_job == j) {
597                 const char *url;
598
599                 assert(!i->tags_job);
600                 assert(!i->ancestry_job);
601                 assert(!i->json_job);
602                 assert(!i->layer_job);
603
604                 if (strv_isempty(i->response_registries)) {
605                         r = -EBADMSG;
606                         log_error("Didn't get registry information.");
607                         goto finish;
608                 }
609
610                 log_info("Index lookup succeeded, directed to registry %s.", i->response_registries[0]);
611                 dkr_import_report_progress(i, DKR_RESOLVING);
612
613                 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/repositories/", i->name, "/tags/", i->tag);
614                 r = import_job_new(&i->tags_job, url, i->glue, i);
615                 if (r < 0) {
616                         log_error_errno(r, "Failed to allocate tags job: %m");
617                         goto finish;
618                 }
619
620                 r = dkr_import_add_token(i, i->tags_job);
621                 if (r < 0) {
622                         log_oom();
623                         goto finish;
624                 }
625
626                 i->tags_job->on_finished = dkr_import_job_on_finished;
627                 i->tags_job->on_progress = dkr_import_job_on_progress;
628
629                 r = import_job_begin(i->tags_job);
630                 if (r < 0) {
631                         log_error_errno(r, "Failed to start tags job: %m");
632                         goto finish;
633                 }
634
635         } else if (i->tags_job == j) {
636                 const char *url;
637                 char *id = NULL;
638
639                 assert(!i->ancestry_job);
640                 assert(!i->json_job);
641                 assert(!i->layer_job);
642
643                 r = parse_id(j->payload, j->payload_size, &id);
644                 if (r < 0) {
645                         log_error_errno(r, "Failed to parse JSON id.");
646                         goto finish;
647                 }
648
649                 free(i->id);
650                 i->id = id;
651
652                 log_info("Tag lookup succeeded, resolved to layer %s.", i->id);
653                 dkr_import_report_progress(i, DKR_METADATA);
654
655                 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", i->id, "/ancestry");
656                 r = import_job_new(&i->ancestry_job, url, i->glue, i);
657                 if (r < 0) {
658                         log_error_errno(r, "Failed to allocate ancestry job: %m");
659                         goto finish;
660                 }
661
662                 r = dkr_import_add_token(i, i->ancestry_job);
663                 if (r < 0) {
664                         log_oom();
665                         goto finish;
666                 }
667
668                 i->ancestry_job->on_finished = dkr_import_job_on_finished;
669                 i->ancestry_job->on_progress = dkr_import_job_on_progress;
670
671                 url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", i->id, "/json");
672                 r = import_job_new(&i->json_job, url, i->glue, i);
673                 if (r < 0) {
674                         log_error_errno(r, "Failed to allocate json job: %m");
675                         goto finish;
676                 }
677
678                 r = dkr_import_add_token(i, i->json_job);
679                 if (r < 0) {
680                         log_oom();
681                         goto finish;
682                 }
683
684                 i->json_job->on_finished = dkr_import_job_on_finished;
685                 i->json_job->on_progress = dkr_import_job_on_progress;
686
687                 r = import_job_begin(i->ancestry_job);
688                 if (r < 0) {
689                         log_error_errno(r, "Failed to start ancestry job: %m");
690                         goto finish;
691                 }
692
693                 r = import_job_begin(i->json_job);
694                 if (r < 0) {
695                         log_error_errno(r, "Failed to start json job: %m");
696                         goto finish;
697                 }
698
699         } else if (i->ancestry_job == j) {
700                 char **ancestry = NULL, **k;
701                 unsigned n;
702
703                 assert(!i->layer_job);
704
705                 r = parse_ancestry(j->payload, j->payload_size, &ancestry);
706                 if (r < 0) {
707                         log_error_errno(r, "Failed to parse JSON id.");
708                         goto finish;
709                 }
710
711                 n = strv_length(ancestry);
712                 if (n <= 0 || !streq(ancestry[n-1], i->id)) {
713                         log_error("Ancestry doesn't end in main layer.");
714                         strv_free(ancestry);
715                         r = -EBADMSG;
716                         goto finish;
717                 }
718
719                 log_info("Ancestor lookup succeeded, requires layers:\n");
720                 STRV_FOREACH(k, ancestry)
721                         log_info("\t%s", *k);
722
723                 strv_free(i->ancestry);
724                 i->ancestry = ancestry;
725                 i->n_ancestry = n;
726                 i->current_ancestry = 0;
727
728                 dkr_import_report_progress(i, DKR_DOWNLOADING);
729
730                 r = dkr_import_pull_layer(i);
731                 if (r < 0)
732                         goto finish;
733
734         } else if (i->layer_job == j) {
735                 assert(i->temp_path);
736                 assert(i->final_path);
737
738                 j->disk_fd = safe_close(j->disk_fd);
739
740                 if (i->tar_pid > 0) {
741                         r = wait_for_terminate_and_warn("tar", i->tar_pid, true);
742                         i->tar_pid = 0;
743                         if (r < 0)
744                                 goto finish;
745                 }
746
747                 r = aufs_resolve(i->temp_path);
748                 if (r < 0) {
749                         log_error_errno(r, "Failed to resolve aufs whiteouts: %m");
750                         goto finish;
751                 }
752
753                 r = btrfs_subvol_set_read_only(i->temp_path, true);
754                 if (r < 0) {
755                         log_error_errno(r, "Failed to mark snapshot read-only: %m");
756                         goto finish;
757                 }
758
759                 if (rename(i->temp_path, i->final_path) < 0) {
760                         log_error_errno(errno, "Failed to rename snaphsot: %m");
761                         goto finish;
762                 }
763
764                 log_info("Completed writing to layer %s.", i->final_path);
765
766                 i->layer_job = import_job_unref(i->layer_job);
767                 free(i->temp_path);
768                 i->temp_path = NULL;
769                 free(i->final_path);
770                 i->final_path = NULL;
771
772                 i->current_ancestry ++;
773                 r = dkr_import_pull_layer(i);
774                 if (r < 0)
775                         goto finish;
776
777         } else if (i->json_job != j)
778                 assert_not_reached("Got finished event for unknown curl object");
779
780         if (!dkr_import_is_done(i))
781                 return;
782
783         dkr_import_report_progress(i, DKR_COPYING);
784
785         r = dkr_import_make_local_copy(i);
786         if (r < 0)
787                 goto finish;
788
789         r = 0;
790
791 finish:
792         if (i->on_finished)
793                 i->on_finished(i, r, i->userdata);
794         else
795                 sd_event_exit(i->event, r);
796 }
797
798 static int dkr_import_job_on_header(ImportJob *j, const char *header, size_t sz)  {
799         _cleanup_free_ char *registry = NULL;
800         char *token;
801         DkrImport *i;
802         int r;
803
804         assert(j);
805         assert(j->userdata);
806
807         i = j->userdata;
808
809         r = curl_header_strdup(header, sz, HEADER_TOKEN, &token);
810         if (r < 0)
811                 return log_oom();
812         if (r > 0) {
813                 free(i->response_token);
814                 i->response_token = token;
815                 return 0;
816         }
817
818         r = curl_header_strdup(header, sz, HEADER_REGISTRY, &registry);
819         if (r < 0)
820                 return log_oom();
821         if (r > 0) {
822                 char **l, **k;
823
824                 l = strv_split(registry, ",");
825                 if (!l)
826                         return log_oom();
827
828                 STRV_FOREACH(k, l) {
829                         if (!hostname_is_valid(*k)) {
830                                 log_error("Registry hostname is not valid.");
831                                 strv_free(l);
832                                 return -EBADMSG;
833                         }
834                 }
835
836                 strv_free(i->response_registries);
837                 i->response_registries = l;
838         }
839
840         return 0;
841 }
842
843 int dkr_import_pull(DkrImport *i, const char *name, const char *tag, const char *local, bool force_local) {
844         const char *url;
845         int r;
846
847         assert(i);
848
849         if (!dkr_name_is_valid(name))
850                 return -EINVAL;
851
852         if (tag && !dkr_tag_is_valid(tag))
853                 return -EINVAL;
854
855         if (local && !machine_name_is_valid(local))
856                 return -EINVAL;
857
858         if (i->images_job)
859                 return -EBUSY;
860
861         if (!tag)
862                 tag = "latest";
863
864         r = free_and_strdup(&i->local, local);
865         if (r < 0)
866                 return r;
867         i->force_local = force_local;
868
869         r = free_and_strdup(&i->name, name);
870         if (r < 0)
871                 return r;
872         r = free_and_strdup(&i->tag, tag);
873         if (r < 0)
874                 return r;
875
876         url = strjoina(i->index_url, "/v1/repositories/", name, "/images");
877
878         r = import_job_new(&i->images_job, url, i->glue, i);
879         if (r < 0)
880                 return r;
881
882         r = dkr_import_add_token(i, i->images_job);
883         if (r < 0)
884                 return r;
885
886         i->images_job->on_finished = dkr_import_job_on_finished;
887         i->images_job->on_header = dkr_import_job_on_header;
888         i->images_job->on_progress = dkr_import_job_on_progress;
889
890         return import_job_begin(i->images_job);
891 }