Skip to content

Commit dbf384a

Browse files
committed
Export remote snapshotter label handler
Signed-off-by: Kohei Tokunaga <[email protected]>
1 parent 753bfd6 commit dbf384a

File tree

4 files changed

+173
-120
lines changed

4 files changed

+173
-120
lines changed

pkg/cri/server/image_pull.go

Lines changed: 2 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ import (
4242
"github.com/containerd/containerd"
4343
"github.com/containerd/containerd/errdefs"
4444
containerdimages "github.com/containerd/containerd/images"
45-
"github.com/containerd/containerd/labels"
4645
"github.com/containerd/containerd/log"
4746
"github.com/containerd/containerd/pkg/cri/annotations"
4847
criconfig "github.com/containerd/containerd/pkg/cri/config"
48+
snpkg "github.com/containerd/containerd/pkg/snapshotters"
4949
distribution "github.com/containerd/containerd/reference/docker"
5050
"github.com/containerd/containerd/remotes/docker"
5151
"github.com/containerd/containerd/remotes/docker/config"
@@ -170,7 +170,7 @@ func (c *criService) PullImage(ctx context.Context, r *runtime.PullImageRequest)
170170
pullOpts = append(pullOpts, c.encryptedImagesPullOpts()...)
171171
if !c.config.ContainerdConfig.DisableSnapshotAnnotations {
172172
pullOpts = append(pullOpts,
173-
containerd.WithImageHandlerWrapper(appendInfoHandlerWrapper(ref)))
173+
containerd.WithImageHandlerWrapper(snpkg.AppendInfoHandlerWrapper(ref)))
174174
}
175175

176176
if c.config.ContainerdConfig.DiscardUnpackedLayers {
@@ -552,76 +552,6 @@ func (c *criService) encryptedImagesPullOpts() []containerd.RemoteOpt {
552552
return nil
553553
}
554554

555-
const (
556-
// targetRefLabel is a label which contains image reference and will be passed
557-
// to snapshotters.
558-
targetRefLabel = "containerd.io/snapshot/cri.image-ref"
559-
// targetManifestDigestLabel is a label which contains manifest digest and will be passed
560-
// to snapshotters.
561-
targetManifestDigestLabel = "containerd.io/snapshot/cri.manifest-digest"
562-
// targetLayerDigestLabel is a label which contains layer digest and will be passed
563-
// to snapshotters.
564-
targetLayerDigestLabel = "containerd.io/snapshot/cri.layer-digest"
565-
// targetImageLayersLabel is a label which contains layer digests contained in
566-
// the target image and will be passed to snapshotters for preparing layers in
567-
// parallel. Skipping some layers is allowed and only affects performance.
568-
targetImageLayersLabel = "containerd.io/snapshot/cri.image-layers"
569-
)
570-
571-
// appendInfoHandlerWrapper makes a handler which appends some basic information
572-
// of images like digests for manifest and their child layers as annotations during unpack.
573-
// These annotations will be passed to snapshotters as labels. These labels will be
574-
// used mainly by stargz-based snapshotters for querying image contents from the
575-
// registry.
576-
func appendInfoHandlerWrapper(ref string) func(f containerdimages.Handler) containerdimages.Handler {
577-
return func(f containerdimages.Handler) containerdimages.Handler {
578-
return containerdimages.HandlerFunc(func(ctx context.Context, desc imagespec.Descriptor) ([]imagespec.Descriptor, error) {
579-
children, err := f.Handle(ctx, desc)
580-
if err != nil {
581-
return nil, err
582-
}
583-
switch desc.MediaType {
584-
case imagespec.MediaTypeImageManifest, containerdimages.MediaTypeDockerSchema2Manifest:
585-
for i := range children {
586-
c := &children[i]
587-
if containerdimages.IsLayerType(c.MediaType) {
588-
if c.Annotations == nil {
589-
c.Annotations = make(map[string]string)
590-
}
591-
c.Annotations[targetRefLabel] = ref
592-
c.Annotations[targetLayerDigestLabel] = c.Digest.String()
593-
c.Annotations[targetImageLayersLabel] = getLayers(ctx, targetImageLayersLabel, children[i:], labels.Validate)
594-
c.Annotations[targetManifestDigestLabel] = desc.Digest.String()
595-
}
596-
}
597-
}
598-
return children, nil
599-
})
600-
}
601-
}
602-
603-
// getLayers returns comma-separated digests based on the passed list of
604-
// descriptors. The returned list contains as many digests as possible as well
605-
// as meets the label validation.
606-
func getLayers(ctx context.Context, key string, descs []imagespec.Descriptor, validate func(k, v string) error) (layers string) {
607-
var item string
608-
for _, l := range descs {
609-
if containerdimages.IsLayerType(l.MediaType) {
610-
item = l.Digest.String()
611-
if layers != "" {
612-
item = "," + item
613-
}
614-
// This avoids the label hits the size limitation.
615-
if err := validate(key, layers+item); err != nil {
616-
log.G(ctx).WithError(err).WithField("label", key).Debugf("%q is omitted in the layers list", l.Digest.String())
617-
break
618-
}
619-
layers += item
620-
}
621-
}
622-
return
623-
}
624-
625555
const (
626556
// minPullProgressReportInternal is used to prevent the reporter from
627557
// eating more CPU resources

pkg/cri/server/image_pull_test.go

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,8 @@ import (
2020
"context"
2121
"encoding/base64"
2222
"fmt"
23-
"strings"
2423
"testing"
2524

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

@@ -338,51 +335,6 @@ func TestEncryptedImagePullOpts(t *testing.T) {
338335
}
339336
}
340337

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

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)