Browse code

c8d/commit-builder: Extract common code

Extract duplicated image creation code to a function.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>

Paweł Gronowski authored on 2024/01/11 20:45:10
Showing 2 changed files
... ...
@@ -1,7 +1,9 @@
1 1
 package containerd
2 2
 
3 3
 import (
4
+	"bytes"
4 5
 	"context"
6
+	"encoding/json"
5 7
 	"errors"
6 8
 	"fmt"
7 9
 	"io"
... ...
@@ -25,6 +27,7 @@ import (
25 25
 	"github.com/docker/docker/builder"
26 26
 	"github.com/docker/docker/errdefs"
27 27
 	dimage "github.com/docker/docker/image"
28
+	imagespec "github.com/docker/docker/image/spec/specs-go/v1"
28 29
 	"github.com/docker/docker/internal/compatcontext"
29 30
 	"github.com/docker/docker/layer"
30 31
 	"github.com/docker/docker/pkg/archive"
... ...
@@ -34,6 +37,7 @@ import (
34 34
 	registrypkg "github.com/docker/docker/registry"
35 35
 	"github.com/opencontainers/go-digest"
36 36
 	"github.com/opencontainers/image-spec/identity"
37
+	"github.com/opencontainers/image-spec/specs-go"
37 38
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
38 39
 )
39 40
 
... ...
@@ -442,11 +446,22 @@ func (i *ImageService) CreateImage(ctx context.Context, config []byte, parent st
442 442
 		})
443 443
 	}
444 444
 
445
-	// necessary to prevent the contents from being GC'd
445
+	createdImageId, err := i.createImageOCI(ctx, ociImgToCreate, parentDigest, layers)
446
+	if err != nil {
447
+		return nil, err
448
+	}
449
+
450
+	return dimage.Clone(imgToCreate, createdImageId), nil
451
+}
452
+
453
+func (i *ImageService) createImageOCI(ctx context.Context, imgToCreate imagespec.DockerOCIImage,
454
+	parentDigest digest.Digest, layers []ocispec.Descriptor,
455
+) (dimage.ID, error) {
456
+	// Necessary to prevent the contents from being GC'd
446 457
 	// between writing them here and creating an image
447 458
 	ctx, release, err := i.client.WithLease(ctx, leases.WithRandomID(), leases.WithExpiration(1*time.Hour))
448 459
 	if err != nil {
449
-		return nil, err
460
+		return "", err
450 461
 	}
451 462
 	defer func() {
452 463
 		if err := release(compatcontext.WithoutCancel(ctx)); err != nil {
... ...
@@ -454,15 +469,14 @@ func (i *ImageService) CreateImage(ctx context.Context, config []byte, parent st
454 454
 		}
455 455
 	}()
456 456
 
457
-	commitManifestDesc, err := writeContentsForImage(ctx, i.snapshotter, i.client.ContentStore(), ociImgToCreate, layers)
457
+	manifestDesc, err := writeContentsForImage(ctx, i.snapshotter, i.client.ContentStore(), imgToCreate, layers)
458 458
 	if err != nil {
459
-		return nil, err
459
+		return "", err
460 460
 	}
461 461
 
462
-	// image create
463 462
 	img := containerdimages.Image{
464
-		Name:      danglingImageName(commitManifestDesc.Digest),
465
-		Target:    commitManifestDesc,
463
+		Name:      danglingImageName(manifestDesc.Digest),
464
+		Target:    manifestDesc,
466 465
 		CreatedAt: time.Now(),
467 466
 		Labels: map[string]string{
468 467
 			imageLabelClassicBuilderParent: parentDigest.String(),
... ...
@@ -472,18 +486,80 @@ func (i *ImageService) CreateImage(ctx context.Context, config []byte, parent st
472 472
 	createdImage, err := i.client.ImageService().Update(ctx, img)
473 473
 	if err != nil {
474 474
 		if !cerrdefs.IsNotFound(err) {
475
-			return nil, err
475
+			return "", err
476 476
 		}
477 477
 
478 478
 		if createdImage, err = i.client.ImageService().Create(ctx, img); err != nil {
479
-			return nil, fmt.Errorf("failed to create new image: %w", err)
479
+			return "", fmt.Errorf("failed to create new image: %w", err)
480 480
 		}
481 481
 	}
482 482
 
483
-	if err := i.unpackImage(ctx, i.StorageDriver(), img, commitManifestDesc); err != nil {
484
-		return nil, err
483
+	if err := i.unpackImage(ctx, i.StorageDriver(), img, manifestDesc); err != nil {
484
+		return "", err
485
+	}
486
+
487
+	return dimage.ID(createdImage.Target.Digest), nil
488
+}
489
+
490
+// writeContentsForImage will commit oci image config and manifest into containerd's content store.
491
+func writeContentsForImage(ctx context.Context, snName string, cs content.Store, newConfig imagespec.DockerOCIImage, layers []ocispec.Descriptor) (ocispec.Descriptor, error) {
492
+	newConfigJSON, err := json.Marshal(newConfig)
493
+	if err != nil {
494
+		return ocispec.Descriptor{}, err
495
+	}
496
+
497
+	configDesc := ocispec.Descriptor{
498
+		MediaType: ocispec.MediaTypeImageConfig,
499
+		Digest:    digest.FromBytes(newConfigJSON),
500
+		Size:      int64(len(newConfigJSON)),
501
+	}
502
+
503
+	newMfst := struct {
504
+		MediaType string `json:"mediaType,omitempty"`
505
+		ocispec.Manifest
506
+	}{
507
+		MediaType: ocispec.MediaTypeImageManifest,
508
+		Manifest: ocispec.Manifest{
509
+			Versioned: specs.Versioned{
510
+				SchemaVersion: 2,
511
+			},
512
+			Config: configDesc,
513
+			Layers: layers,
514
+		},
515
+	}
516
+
517
+	newMfstJSON, err := json.MarshalIndent(newMfst, "", "    ")
518
+	if err != nil {
519
+		return ocispec.Descriptor{}, err
520
+	}
521
+
522
+	newMfstDesc := ocispec.Descriptor{
523
+		MediaType: ocispec.MediaTypeImageManifest,
524
+		Digest:    digest.FromBytes(newMfstJSON),
525
+		Size:      int64(len(newMfstJSON)),
526
+	}
527
+
528
+	// new manifest should reference the layers and config content
529
+	labels := map[string]string{
530
+		"containerd.io/gc.ref.content.0": configDesc.Digest.String(),
531
+	}
532
+	for i, l := range layers {
533
+		labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i+1)] = l.Digest.String()
534
+	}
535
+
536
+	err = content.WriteBlob(ctx, cs, newMfstDesc.Digest.String(), bytes.NewReader(newMfstJSON), newMfstDesc, content.WithLabels(labels))
537
+	if err != nil {
538
+		return ocispec.Descriptor{}, err
539
+	}
540
+
541
+	// config should reference to snapshotter
542
+	labelOpt := content.WithLabels(map[string]string{
543
+		fmt.Sprintf("containerd.io/gc.ref.snapshot.%s", snName): identity.ChainID(newConfig.RootFS.DiffIDs).String(),
544
+	})
545
+	err = content.WriteBlob(ctx, cs, configDesc.Digest.String(), bytes.NewReader(newConfigJSON), configDesc, labelOpt)
546
+	if err != nil {
547
+		return ocispec.Descriptor{}, err
485 548
 	}
486 549
 
487
-	newImage := dimage.Clone(imgToCreate, dimage.ID(createdImage.Target.Digest))
488
-	return newImage, nil
550
+	return newMfstDesc, nil
489 551
 }
... ...
@@ -1,7 +1,6 @@
1 1
 package containerd
2 2
 
3 3
 import (
4
-	"bytes"
5 4
 	"context"
6 5
 	"crypto/rand"
7 6
 	"encoding/base64"
... ...
@@ -14,7 +13,6 @@ import (
14 14
 	"github.com/containerd/containerd/content"
15 15
 	"github.com/containerd/containerd/diff"
16 16
 	cerrdefs "github.com/containerd/containerd/errdefs"
17
-	"github.com/containerd/containerd/images"
18 17
 	"github.com/containerd/containerd/leases"
19 18
 	"github.com/containerd/containerd/mount"
20 19
 	"github.com/containerd/containerd/pkg/cleanup"
... ...
@@ -27,7 +25,6 @@ import (
27 27
 	"github.com/docker/docker/pkg/archive"
28 28
 	"github.com/opencontainers/go-digest"
29 29
 	"github.com/opencontainers/image-spec/identity"
30
-	"github.com/opencontainers/image-spec/specs-go"
31 30
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
32 31
 	"github.com/pkg/errors"
33 32
 )
... ...
@@ -99,41 +96,7 @@ func (i *ImageService) CommitImage(ctx context.Context, cc backend.CommitConfig)
99 99
 		layers = append(layers, *diffLayerDesc)
100 100
 	}
101 101
 
102
-	commitManifestDesc, err := writeContentsForImage(ctx, container.Driver, cs, imageConfig, layers)
103
-	if err != nil {
104
-		return "", err
105
-	}
106
-
107
-	// image create
108
-	img := images.Image{
109
-		Name:      danglingImageName(commitManifestDesc.Digest),
110
-		Target:    commitManifestDesc,
111
-		CreatedAt: time.Now(),
112
-		Labels: map[string]string{
113
-			imageLabelClassicBuilderParent: cc.ParentImageID,
114
-		},
115
-	}
116
-
117
-	if _, err := i.client.ImageService().Update(ctx, img); err != nil {
118
-		if !cerrdefs.IsNotFound(err) {
119
-			return "", err
120
-		}
121
-
122
-		if _, err := i.client.ImageService().Create(ctx, img); err != nil {
123
-			return "", fmt.Errorf("failed to create new image: %w", err)
124
-		}
125
-	}
126
-	id := image.ID(img.Target.Digest)
127
-
128
-	c8dImg, err := i.NewImageManifest(ctx, img, commitManifestDesc)
129
-	if err != nil {
130
-		return id, err
131
-	}
132
-	if err := c8dImg.Unpack(ctx, container.Driver); err != nil && !cerrdefs.IsAlreadyExists(err) {
133
-		return id, fmt.Errorf("failed to unpack image: %w", err)
134
-	}
135
-
136
-	return id, nil
102
+	return i.createImageOCI(ctx, imageConfig, digest.Digest(cc.ParentImageID), layers)
137 103
 }
138 104
 
139 105
 // generateCommitImageConfig generates an OCI Image config based on the
... ...
@@ -185,69 +148,6 @@ func generateCommitImageConfig(baseConfig imagespec.DockerOCIImage, diffID diges
185 185
 	}
186 186
 }
187 187
 
188
-// writeContentsForImage will commit oci image config and manifest into containerd's content store.
189
-func writeContentsForImage(ctx context.Context, snName string, cs content.Store, newConfig imagespec.DockerOCIImage, layers []ocispec.Descriptor) (ocispec.Descriptor, error) {
190
-	newConfigJSON, err := json.Marshal(newConfig)
191
-	if err != nil {
192
-		return ocispec.Descriptor{}, err
193
-	}
194
-
195
-	configDesc := ocispec.Descriptor{
196
-		MediaType: ocispec.MediaTypeImageConfig,
197
-		Digest:    digest.FromBytes(newConfigJSON),
198
-		Size:      int64(len(newConfigJSON)),
199
-	}
200
-
201
-	newMfst := struct {
202
-		MediaType string `json:"mediaType,omitempty"`
203
-		ocispec.Manifest
204
-	}{
205
-		MediaType: ocispec.MediaTypeImageManifest,
206
-		Manifest: ocispec.Manifest{
207
-			Versioned: specs.Versioned{
208
-				SchemaVersion: 2,
209
-			},
210
-			Config: configDesc,
211
-			Layers: layers,
212
-		},
213
-	}
214
-
215
-	newMfstJSON, err := json.MarshalIndent(newMfst, "", "    ")
216
-	if err != nil {
217
-		return ocispec.Descriptor{}, err
218
-	}
219
-
220
-	newMfstDesc := ocispec.Descriptor{
221
-		MediaType: ocispec.MediaTypeImageManifest,
222
-		Digest:    digest.FromBytes(newMfstJSON),
223
-		Size:      int64(len(newMfstJSON)),
224
-	}
225
-
226
-	// new manifest should reference the layers and config content
227
-	labels := map[string]string{
228
-		"containerd.io/gc.ref.content.0": configDesc.Digest.String(),
229
-	}
230
-	for i, l := range layers {
231
-		labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i+1)] = l.Digest.String()
232
-	}
233
-
234
-	err = content.WriteBlob(ctx, cs, newMfstDesc.Digest.String(), bytes.NewReader(newMfstJSON), newMfstDesc, content.WithLabels(labels))
235
-	if err != nil {
236
-		return ocispec.Descriptor{}, err
237
-	}
238
-
239
-	// config should reference to snapshotter
240
-	labelOpt := content.WithLabels(map[string]string{
241
-		fmt.Sprintf("containerd.io/gc.ref.snapshot.%s", snName): identity.ChainID(newConfig.RootFS.DiffIDs).String(),
242
-	})
243
-	err = content.WriteBlob(ctx, cs, configDesc.Digest.String(), bytes.NewReader(newConfigJSON), configDesc, labelOpt)
244
-	if err != nil {
245
-		return ocispec.Descriptor{}, err
246
-	}
247
-
248
-	return newMfstDesc, nil
249
-}
250
-
251 188
 // createDiff creates a layer diff into containerd's content store.
252 189
 // If the diff is empty it returns nil empty digest and no error.
253 190
 func (i *ImageService) createDiff(ctx context.Context, name string, sn snapshots.Snapshotter, cs content.Store, comparer diff.Comparer) (*ocispec.Descriptor, digest.Digest, error) {