Skip to content

Commit a6544ed

Browse files
ktockthaJeztah
authored andcommitted
Export remote snapshotter label handler
Signed-off-by: Kohei Tokunaga <[email protected]> (cherry picked from commit dbf384a) Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent 4335c65 commit a6544ed

File tree

4 files changed

+173
-122
lines changed

4 files changed

+173
-122
lines changed

pkg/cri/server/image_pull.go

Lines changed: 2 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ import (
3232
"github.com/containerd/containerd"
3333
"github.com/containerd/containerd/errdefs"
3434
containerdimages "github.com/containerd/containerd/images"
35-
"github.com/containerd/containerd/labels"
3635
"github.com/containerd/containerd/log"
36+
snpkg "github.com/containerd/containerd/pkg/snapshotters"
3737
distribution "github.com/containerd/containerd/reference/docker"
3838
"github.com/containerd/containerd/remotes/docker"
3939
"github.com/containerd/containerd/remotes/docker/config"
@@ -129,7 +129,7 @@ func (c *criService) PullImage(ctx context.Context, r *runtime.PullImageRequest)
129129
pullOpts = append(pullOpts, c.encryptedImagesPullOpts()...)
130130
if !c.config.ContainerdConfig.DisableSnapshotAnnotations {
131131
pullOpts = append(pullOpts,
132-
containerd.WithImageHandlerWrapper(appendInfoHandlerWrapper(ref)))
132+
containerd.WithImageHandlerWrapper(snpkg.AppendInfoHandlerWrapper(ref)))
133133
}
134134

135135
if c.config.ContainerdConfig.DiscardUnpackedLayers {
@@ -495,73 +495,3 @@ func (c *criService) encryptedImagesPullOpts() []containerd.RemoteOpt {
495495
}
496496
return nil
497497
}
498-
499-
const (
500-
// targetRefLabel is a label which contains image reference and will be passed
501-
// to snapshotters.
502-
targetRefLabel = "containerd.io/snapshot/cri.image-ref"
503-
// targetManifestDigestLabel is a label which contains manifest digest and will be passed
504-
// to snapshotters.
505-
targetManifestDigestLabel = "containerd.io/snapshot/cri.manifest-digest"
506-
// targetLayerDigestLabel is a label which contains layer digest and will be passed
507-
// to snapshotters.
508-
targetLayerDigestLabel = "containerd.io/snapshot/cri.layer-digest"
509-
// targetImageLayersLabel is a label which contains layer digests contained in
510-
// the target image and will be passed to snapshotters for preparing layers in
511-
// parallel. Skipping some layers is allowed and only affects performance.
512-
targetImageLayersLabel = "containerd.io/snapshot/cri.image-layers"
513-
)
514-
515-
// appendInfoHandlerWrapper makes a handler which appends some basic information
516-
// of images like digests for manifest and their child layers as annotations during unpack.
517-
// These annotations will be passed to snapshotters as labels. These labels will be
518-
// used mainly by stargz-based snapshotters for querying image contents from the
519-
// registry.
520-
func appendInfoHandlerWrapper(ref string) func(f containerdimages.Handler) containerdimages.Handler {
521-
return func(f containerdimages.Handler) containerdimages.Handler {
522-
return containerdimages.HandlerFunc(func(ctx context.Context, desc imagespec.Descriptor) ([]imagespec.Descriptor, error) {
523-
children, err := f.Handle(ctx, desc)
524-
if err != nil {
525-
return nil, err
526-
}
527-
switch desc.MediaType {
528-
case imagespec.MediaTypeImageManifest, containerdimages.MediaTypeDockerSchema2Manifest:
529-
for i := range children {
530-
c := &children[i]
531-
if containerdimages.IsLayerType(c.MediaType) {
532-
if c.Annotations == nil {
533-
c.Annotations = make(map[string]string)
534-
}
535-
c.Annotations[targetRefLabel] = ref
536-
c.Annotations[targetLayerDigestLabel] = c.Digest.String()
537-
c.Annotations[targetImageLayersLabel] = getLayers(ctx, targetImageLayersLabel, children[i:], labels.Validate)
538-
c.Annotations[targetManifestDigestLabel] = desc.Digest.String()
539-
}
540-
}
541-
}
542-
return children, nil
543-
})
544-
}
545-
}
546-
547-
// getLayers returns comma-separated digests based on the passed list of
548-
// descriptors. The returned list contains as many digests as possible as well
549-
// as meets the label validation.
550-
func getLayers(ctx context.Context, key string, descs []imagespec.Descriptor, validate func(k, v string) error) (layers string) {
551-
var item string
552-
for _, l := range descs {
553-
if containerdimages.IsLayerType(l.MediaType) {
554-
item = l.Digest.String()
555-
if layers != "" {
556-
item = "," + item
557-
}
558-
// This avoids the label hits the size limitation.
559-
if err := validate(key, layers+item); err != nil {
560-
log.G(ctx).WithError(err).WithField("label", key).Debugf("%q is omitted in the layers list", l.Digest.String())
561-
break
562-
}
563-
layers += item
564-
}
565-
}
566-
return
567-
}

pkg/cri/server/image_pull_test.go

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,9 @@
1717
package server
1818

1919
import (
20-
"context"
2120
"encoding/base64"
22-
"fmt"
23-
"strings"
2421
"testing"
2522

26-
digest "github.com/opencontainers/go-digest"
27-
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
2823
"github.com/stretchr/testify/assert"
2924
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
3025

@@ -332,48 +327,3 @@ func TestEncryptedImagePullOpts(t *testing.T) {
332327
assert.Equal(t, test.expectedOpts, got)
333328
}
334329
}
335-
336-
func TestImageLayersLabel(t *testing.T) {
337-
sampleKey := "sampleKey"
338-
sampleDigest, err := digest.Parse("sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
339-
assert.NoError(t, err)
340-
sampleMaxSize := 300
341-
sampleValidate := func(k, v string) error {
342-
if (len(k) + len(v)) > sampleMaxSize {
343-
return fmt.Errorf("invalid: %q: %q", k, v)
344-
}
345-
return nil
346-
}
347-
348-
tests := []struct {
349-
name string
350-
layersNum int
351-
wantNum int
352-
}{
353-
{
354-
name: "valid number of layers",
355-
layersNum: 2,
356-
wantNum: 2,
357-
},
358-
{
359-
name: "many layers",
360-
layersNum: 5, // hits sampleMaxSize (300 chars).
361-
wantNum: 4, // layers should be omitted for avoiding invalid label.
362-
},
363-
}
364-
365-
for _, tt := range tests {
366-
t.Run(tt.name, func(t *testing.T) {
367-
var sampleLayers []imagespec.Descriptor
368-
for i := 0; i < tt.layersNum; i++ {
369-
sampleLayers = append(sampleLayers, imagespec.Descriptor{
370-
MediaType: imagespec.MediaTypeImageLayerGzip,
371-
Digest: sampleDigest,
372-
})
373-
}
374-
gotS := getLayers(context.Background(), sampleKey, sampleLayers, sampleValidate)
375-
got := len(strings.Split(gotS, ","))
376-
assert.Equal(t, tt.wantNum, got)
377-
})
378-
}
379-
}

pkg/snapshotters/annotations.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package snapshotters
18+
19+
import (
20+
"context"
21+
22+
"github.com/containerd/containerd/images"
23+
"github.com/containerd/containerd/labels"
24+
"github.com/containerd/containerd/log"
25+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
26+
)
27+
28+
// NOTE: The following labels contain "cri" prefix but they are not specific to CRI and
29+
// can be used by non-CRI clients as well for enabling remote snapshotters. We need to
30+
// retain that string for keeping compatibility with snapshotter implementations.
31+
const (
32+
// TargetRefLabel is a label which contains image reference and will be passed
33+
// to snapshotters.
34+
TargetRefLabel = "containerd.io/snapshot/cri.image-ref"
35+
// TargetManifestDigestLabel is a label which contains manifest digest and will be passed
36+
// to snapshotters.
37+
TargetManifestDigestLabel = "containerd.io/snapshot/cri.manifest-digest"
38+
// TargetLayerDigestLabel is a label which contains layer digest and will be passed
39+
// to snapshotters.
40+
TargetLayerDigestLabel = "containerd.io/snapshot/cri.layer-digest"
41+
// TargetImageLayersLabel is a label which contains layer digests contained in
42+
// the target image and will be passed to snapshotters for preparing layers in
43+
// parallel. Skipping some layers is allowed and only affects performance.
44+
TargetImageLayersLabel = "containerd.io/snapshot/cri.image-layers"
45+
)
46+
47+
// AppendInfoHandlerWrapper makes a handler which appends some basic information
48+
// of images like digests for manifest and their child layers as annotations during unpack.
49+
// These annotations will be passed to snapshotters as labels. These labels will be
50+
// used mainly by remote snapshotters for querying image contents from the remote location.
51+
func AppendInfoHandlerWrapper(ref string) func(f images.Handler) images.Handler {
52+
return func(f images.Handler) images.Handler {
53+
return images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
54+
children, err := f.Handle(ctx, desc)
55+
if err != nil {
56+
return nil, err
57+
}
58+
switch desc.MediaType {
59+
case ocispec.MediaTypeImageManifest, images.MediaTypeDockerSchema2Manifest:
60+
for i := range children {
61+
c := &children[i]
62+
if images.IsLayerType(c.MediaType) {
63+
if c.Annotations == nil {
64+
c.Annotations = make(map[string]string)
65+
}
66+
c.Annotations[TargetRefLabel] = ref
67+
c.Annotations[TargetLayerDigestLabel] = c.Digest.String()
68+
c.Annotations[TargetImageLayersLabel] = getLayers(ctx, TargetImageLayersLabel, children[i:], labels.Validate)
69+
c.Annotations[TargetManifestDigestLabel] = desc.Digest.String()
70+
}
71+
}
72+
}
73+
return children, nil
74+
})
75+
}
76+
}
77+
78+
// getLayers returns comma-separated digests based on the passed list of
79+
// descriptors. The returned list contains as many digests as possible as well
80+
// as meets the label validation.
81+
func getLayers(ctx context.Context, key string, descs []ocispec.Descriptor, validate func(k, v string) error) (layers string) {
82+
for _, l := range descs {
83+
if images.IsLayerType(l.MediaType) {
84+
item := l.Digest.String()
85+
if layers != "" {
86+
item = "," + item
87+
}
88+
// This avoids the label hits the size limitation.
89+
if err := validate(key, layers+item); err != nil {
90+
log.G(ctx).WithError(err).WithField("label", key).WithField("digest", l.Digest.String()).Debug("omitting digest in the layers list")
91+
break
92+
}
93+
layers += item
94+
}
95+
}
96+
return
97+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package snapshotters
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"strings"
23+
"testing"
24+
25+
digest "github.com/opencontainers/go-digest"
26+
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
27+
"github.com/stretchr/testify/assert"
28+
)
29+
30+
func TestImageLayersLabel(t *testing.T) {
31+
sampleKey := "sampleKey"
32+
sampleDigest, err := digest.Parse("sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
33+
assert.NoError(t, err)
34+
sampleMaxSize := 300
35+
sampleValidate := func(k, v string) error {
36+
if (len(k) + len(v)) > sampleMaxSize {
37+
return fmt.Errorf("invalid: %q: %q", k, v)
38+
}
39+
return nil
40+
}
41+
42+
tests := []struct {
43+
name string
44+
layersNum int
45+
wantNum int
46+
}{
47+
{
48+
name: "valid number of layers",
49+
layersNum: 2,
50+
wantNum: 2,
51+
},
52+
{
53+
name: "many layers",
54+
layersNum: 5, // hits sampleMaxSize (300 chars).
55+
wantNum: 4, // layers should be omitted for avoiding invalid label.
56+
},
57+
}
58+
59+
for _, tt := range tests {
60+
tt := tt
61+
t.Run(tt.name, func(t *testing.T) {
62+
sampleLayers := make([]imagespec.Descriptor, 0, tt.layersNum)
63+
for i := 0; i < tt.layersNum; i++ {
64+
sampleLayers = append(sampleLayers, imagespec.Descriptor{
65+
MediaType: imagespec.MediaTypeImageLayerGzip,
66+
Digest: sampleDigest,
67+
})
68+
}
69+
gotS := getLayers(context.Background(), sampleKey, sampleLayers, sampleValidate)
70+
got := len(strings.Split(gotS, ","))
71+
assert.Equal(t, tt.wantNum, got)
72+
})
73+
}
74+
}

0 commit comments

Comments
 (0)