Skip to content

Commit 699d670

Browse files
adiskyruiwen-zhao
authored andcommitted
Pinned image support
Signed-off-by: Aditi Sharma <[email protected]> (cherry picked from commit fe4f8bd) Signed-off-by: ruiwen-zhao <[email protected]>
1 parent a766dc5 commit 699d670

11 files changed

Lines changed: 234 additions & 31 deletions

File tree

integration/containerd_image_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,21 @@ func TestContainerdImageInOtherNamespaces(t *testing.T) {
207207
}
208208
assert.NoError(t, Consistently(checkImage, 100*time.Millisecond, time.Second))
209209
}
210+
211+
func TestContainerdSandboxImage(t *testing.T) {
212+
var pauseImage = images.Get(images.Pause)
213+
ctx := context.Background()
214+
215+
t.Log("make sure the pause image exist")
216+
pauseImg, err := containerdClient.GetImage(ctx, pauseImage)
217+
require.NoError(t, err)
218+
t.Log("ensure correct labels are set on pause image")
219+
assert.Equal(t, pauseImg.Labels()["io.cri-containerd.pinned"], "pinned")
220+
221+
t.Log("pause image should be seen by cri plugin")
222+
pimg, err := imageService.ImageStatus(&runtime.ImageSpec{Image: pauseImage})
223+
require.NoError(t, err)
224+
require.NotNil(t, pimg)
225+
t.Log("verify pinned field is set for pause image")
226+
assert.True(t, pimg.Pinned)
227+
}

pkg/cri/labels/labels.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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 labels
18+
19+
const (
20+
// criContainerdPrefix is common prefix for cri-containerd
21+
criContainerdPrefix = "io.cri-containerd"
22+
// ImageLabelKey is the label key indicating the image is managed by cri plugin.
23+
ImageLabelKey = criContainerdPrefix + ".image"
24+
// ImageLabelValue is the label value indicating the image is managed by cri plugin.
25+
ImageLabelValue = "managed"
26+
// PinnedImageLabelKey is the label value indicating the image is pinned.
27+
PinnedImageLabelKey = criContainerdPrefix + ".pinned"
28+
// PinnedImageLabelValue is the label value indicating the image is pinned.
29+
PinnedImageLabelValue = "pinned"
30+
)

pkg/cri/sbserver/helpers.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,6 @@ const (
8484
// containerKindContainer is a label value indicating container is application container
8585
containerKindContainer = "container"
8686

87-
// imageLabelKey is the label key indicating the image is managed by cri plugin.
88-
imageLabelKey = criContainerdPrefix + ".image"
89-
// imageLabelValue is the label value indicating the image is managed by cri plugin.
90-
imageLabelValue = "managed"
91-
9287
// containerMetadataExtension is an extension name that identify metadata of container in CreateContainerRequest
9388
containerMetadataExtension = criContainerdPrefix + ".container.metadata"
9489

pkg/cri/sbserver/image_pull.go

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import (
4545
"github.com/containerd/containerd/log"
4646
"github.com/containerd/containerd/pkg/cri/annotations"
4747
criconfig "github.com/containerd/containerd/pkg/cri/config"
48+
crilabels "github.com/containerd/containerd/pkg/cri/labels"
4849
snpkg "github.com/containerd/containerd/pkg/snapshotters"
4950
distribution "github.com/containerd/containerd/reference/docker"
5051
"github.com/containerd/containerd/remotes/docker"
@@ -155,12 +156,14 @@ func (c *criService) PullImage(ctx context.Context, r *runtime.PullImageRequest)
155156
tracing.Attribute("snapshotter.name", snapshotter),
156157
)
157158

159+
labels := c.getLabels(ctx, ref)
160+
158161
pullOpts := []containerd.RemoteOpt{
159162
containerd.WithSchema1Conversion, //nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
160163
containerd.WithResolver(resolver),
161164
containerd.WithPullSnapshotter(snapshotter),
162165
containerd.WithPullUnpack,
163-
containerd.WithPullLabel(imageLabelKey, imageLabelValue),
166+
containerd.WithPullLabels(labels),
164167
containerd.WithMaxConcurrentDownloads(c.config.MaxConcurrentDownloads),
165168
containerd.WithImageHandler(imageHandler),
166169
containerd.WithUnpackOpts([]containerd.UnpackOpt{
@@ -199,7 +202,7 @@ func (c *criService) PullImage(ctx context.Context, r *runtime.PullImageRequest)
199202
if r == "" {
200203
continue
201204
}
202-
if err := c.createImageReference(ctx, r, image.Target()); err != nil {
205+
if err := c.createImageReference(ctx, r, image.Target(), labels); err != nil {
203206
return nil, fmt.Errorf("failed to create image reference %q: %w", r, err)
204207
}
205208
// Update image store to reflect the newest state in containerd.
@@ -267,26 +270,44 @@ func ParseAuth(auth *runtime.AuthConfig, host string) (string, string, error) {
267270
// Note that because create and update are not finished in one transaction, there could be race. E.g.
268271
// the image reference is deleted by someone else after create returns already exists, but before update
269272
// happens.
270-
func (c *criService) createImageReference(ctx context.Context, name string, desc imagespec.Descriptor) error {
273+
func (c *criService) createImageReference(ctx context.Context, name string, desc imagespec.Descriptor, labels map[string]string) error {
271274
img := containerdimages.Image{
272275
Name: name,
273276
Target: desc,
274277
// Add a label to indicate that the image is managed by the cri plugin.
275-
Labels: map[string]string{imageLabelKey: imageLabelValue},
278+
Labels: labels,
276279
}
277280
// TODO(random-liu): Figure out which is the more performant sequence create then update or
278281
// update then create.
279282
oldImg, err := c.client.ImageService().Create(ctx, img)
280283
if err == nil || !errdefs.IsAlreadyExists(err) {
281284
return err
282285
}
283-
if oldImg.Target.Digest == img.Target.Digest && oldImg.Labels[imageLabelKey] == imageLabelValue {
286+
if oldImg.Target.Digest == img.Target.Digest && oldImg.Labels[crilabels.ImageLabelKey] == labels[crilabels.ImageLabelKey] {
284287
return nil
285288
}
286-
_, err = c.client.ImageService().Update(ctx, img, "target", "labels."+imageLabelKey)
289+
_, err = c.client.ImageService().Update(ctx, img, "target", "labels."+crilabels.ImageLabelKey)
287290
return err
288291
}
289292

293+
// getLabels get image labels to be added on CRI image
294+
func (c *criService) getLabels(ctx context.Context, name string) map[string]string {
295+
labels := map[string]string{crilabels.ImageLabelKey: crilabels.ImageLabelValue}
296+
configSandboxImage := c.config.SandboxImage
297+
// parse sandbox image
298+
sandboxNamedRef, err := distribution.ParseDockerRef(configSandboxImage)
299+
if err != nil {
300+
log.G(ctx).Errorf("failed to parse sandbox image from config %s", sandboxNamedRef)
301+
return nil
302+
}
303+
sandboxRef := sandboxNamedRef.String()
304+
// Adding pinned image label to sandbox image
305+
if sandboxRef == name {
306+
labels[crilabels.PinnedImageLabelKey] = crilabels.PinnedImageLabelValue
307+
}
308+
return labels
309+
}
310+
290311
// updateImage updates image store to reflect the newest state of an image reference
291312
// in containerd. If the reference is not managed by the cri plugin, the function also
292313
// generates necessary metadata for the image and make it managed.
@@ -295,22 +316,23 @@ func (c *criService) updateImage(ctx context.Context, r string) error {
295316
if err != nil && !errdefs.IsNotFound(err) {
296317
return fmt.Errorf("get image by reference: %w", err)
297318
}
298-
if err == nil && img.Labels()[imageLabelKey] != imageLabelValue {
319+
if err == nil && img.Labels()[crilabels.ImageLabelKey] != crilabels.ImageLabelValue {
299320
// Make sure the image has the image id as its unique
300321
// identifier that references the image in its lifetime.
301322
configDesc, err := img.Config(ctx)
302323
if err != nil {
303324
return fmt.Errorf("get image id: %w", err)
304325
}
305326
id := configDesc.Digest.String()
306-
if err := c.createImageReference(ctx, id, img.Target()); err != nil {
327+
labels := c.getLabels(ctx, id)
328+
if err := c.createImageReference(ctx, id, img.Target(), labels); err != nil {
307329
return fmt.Errorf("create image id reference %q: %w", id, err)
308330
}
309331
if err := c.imageStore.Update(ctx, id); err != nil {
310332
return fmt.Errorf("update image store for %q: %w", id, err)
311333
}
312334
// The image id is ready, add the label to mark the image as managed.
313-
if err := c.createImageReference(ctx, r, img.Target()); err != nil {
335+
if err := c.createImageReference(ctx, r, img.Target(), labels); err != nil {
314336
return fmt.Errorf("create managed label: %w", err)
315337
}
316338
}

pkg/cri/sbserver/image_pull_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727

2828
"github.com/containerd/containerd/pkg/cri/annotations"
2929
criconfig "github.com/containerd/containerd/pkg/cri/config"
30+
"github.com/containerd/containerd/pkg/cri/labels"
3031
)
3132

3233
func TestParseAuth(t *testing.T) {
@@ -395,3 +396,57 @@ func TestSnapshotterFromPodSandboxConfig(t *testing.T) {
395396
})
396397
}
397398
}
399+
400+
func TestImageGetLabels(t *testing.T) {
401+
402+
criService := newTestCRIService()
403+
404+
tests := []struct {
405+
name string
406+
expectedLabel map[string]string
407+
configSandboxImage string
408+
pullImageName string
409+
}{
410+
{
411+
name: "pinned image labels should get added on sandbox image",
412+
expectedLabel: map[string]string{labels.ImageLabelKey: labels.ImageLabelValue, labels.PinnedImageLabelKey: labels.PinnedImageLabelValue},
413+
configSandboxImage: "k8s.gcr.io/pause:3.9",
414+
pullImageName: "k8s.gcr.io/pause:3.9",
415+
},
416+
{
417+
name: "pinned image labels should get added on sandbox image without tag",
418+
expectedLabel: map[string]string{labels.ImageLabelKey: labels.ImageLabelValue, labels.PinnedImageLabelKey: labels.PinnedImageLabelValue},
419+
configSandboxImage: "k8s.gcr.io/pause",
420+
pullImageName: "k8s.gcr.io/pause:latest",
421+
},
422+
{
423+
name: "pinned image labels should get added on sandbox image specified with tag and digest both",
424+
expectedLabel: map[string]string{labels.ImageLabelKey: labels.ImageLabelValue, labels.PinnedImageLabelKey: labels.PinnedImageLabelValue},
425+
configSandboxImage: "k8s.gcr.io/pause:3.9@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2",
426+
pullImageName: "k8s.gcr.io/pause@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2",
427+
},
428+
429+
{
430+
name: "pinned image labels should get added on sandbox image specified with digest",
431+
expectedLabel: map[string]string{labels.ImageLabelKey: labels.ImageLabelValue, labels.PinnedImageLabelKey: labels.PinnedImageLabelValue},
432+
configSandboxImage: "k8s.gcr.io/pause@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2",
433+
pullImageName: "k8s.gcr.io/pause@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2",
434+
},
435+
436+
{
437+
name: "pinned image labels should not get added on other image",
438+
expectedLabel: map[string]string{labels.ImageLabelKey: labels.ImageLabelValue},
439+
configSandboxImage: "k8s.gcr.io/pause:3.9",
440+
pullImageName: "k8s.gcr.io/random:latest",
441+
},
442+
}
443+
444+
for _, tt := range tests {
445+
t.Run(tt.name, func(t *testing.T) {
446+
criService.config.SandboxImage = tt.configSandboxImage
447+
labels := criService.getLabels(context.Background(), tt.pullImageName)
448+
assert.Equal(t, tt.expectedLabel, labels)
449+
450+
})
451+
}
452+
}

pkg/cri/sbserver/image_status.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ func toCRIImage(image imagestore.Image) *runtime.Image {
6868
RepoTags: repoTags,
6969
RepoDigests: repoDigests,
7070
Size_: uint64(image.Size),
71+
Pinned: image.Pinned,
7172
}
7273
uid, username := getUserFromImage(image.ImageSpec.Config.User)
7374
if uid != nil {

pkg/cri/server/helpers.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,6 @@ const (
8080
containerKindSandbox = "sandbox"
8181
// containerKindContainer is a label value indicating container is application container
8282
containerKindContainer = "container"
83-
// imageLabelKey is the label key indicating the image is managed by cri plugin.
84-
imageLabelKey = criContainerdPrefix + ".image"
85-
// imageLabelValue is the label value indicating the image is managed by cri plugin.
86-
imageLabelValue = "managed"
8783
// sandboxMetadataExtension is an extension name that identify metadata of sandbox in CreateContainerRequest
8884
sandboxMetadataExtension = criContainerdPrefix + ".sandbox.metadata"
8985
// containerMetadataExtension is an extension name that identify metadata of container in CreateContainerRequest

pkg/cri/server/image_pull.go

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import (
4545
"github.com/containerd/containerd/log"
4646
"github.com/containerd/containerd/pkg/cri/annotations"
4747
criconfig "github.com/containerd/containerd/pkg/cri/config"
48+
crilabels "github.com/containerd/containerd/pkg/cri/labels"
4849
snpkg "github.com/containerd/containerd/pkg/snapshotters"
4950
distribution "github.com/containerd/containerd/reference/docker"
5051
"github.com/containerd/containerd/remotes/docker"
@@ -154,12 +155,15 @@ func (c *criService) PullImage(ctx context.Context, r *runtime.PullImageRequest)
154155
tracing.Attribute("image.ref", ref),
155156
tracing.Attribute("snapshotter.name", snapshotter),
156157
)
158+
159+
labels := c.getLabels(ctx, ref)
160+
157161
pullOpts := []containerd.RemoteOpt{
158162
containerd.WithSchema1Conversion, //nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
159163
containerd.WithResolver(resolver),
160164
containerd.WithPullSnapshotter(snapshotter),
161165
containerd.WithPullUnpack,
162-
containerd.WithPullLabel(imageLabelKey, imageLabelValue),
166+
containerd.WithPullLabels(labels),
163167
containerd.WithMaxConcurrentDownloads(c.config.MaxConcurrentDownloads),
164168
containerd.WithImageHandler(imageHandler),
165169
containerd.WithUnpackOpts([]containerd.UnpackOpt{
@@ -198,7 +202,7 @@ func (c *criService) PullImage(ctx context.Context, r *runtime.PullImageRequest)
198202
if r == "" {
199203
continue
200204
}
201-
if err := c.createImageReference(ctx, r, image.Target()); err != nil {
205+
if err := c.createImageReference(ctx, r, image.Target(), labels); err != nil {
202206
return nil, fmt.Errorf("failed to create image reference %q: %w", r, err)
203207
}
204208
// Update image store to reflect the newest state in containerd.
@@ -266,26 +270,44 @@ func ParseAuth(auth *runtime.AuthConfig, host string) (string, string, error) {
266270
// Note that because create and update are not finished in one transaction, there could be race. E.g.
267271
// the image reference is deleted by someone else after create returns already exists, but before update
268272
// happens.
269-
func (c *criService) createImageReference(ctx context.Context, name string, desc imagespec.Descriptor) error {
273+
func (c *criService) createImageReference(ctx context.Context, name string, desc imagespec.Descriptor, labels map[string]string) error {
270274
img := containerdimages.Image{
271275
Name: name,
272276
Target: desc,
273277
// Add a label to indicate that the image is managed by the cri plugin.
274-
Labels: map[string]string{imageLabelKey: imageLabelValue},
278+
Labels: labels,
275279
}
276280
// TODO(random-liu): Figure out which is the more performant sequence create then update or
277281
// update then create.
278282
oldImg, err := c.client.ImageService().Create(ctx, img)
279283
if err == nil || !errdefs.IsAlreadyExists(err) {
280284
return err
281285
}
282-
if oldImg.Target.Digest == img.Target.Digest && oldImg.Labels[imageLabelKey] == imageLabelValue {
286+
if oldImg.Target.Digest == img.Target.Digest && oldImg.Labels[crilabels.ImageLabelKey] == labels[crilabels.ImageLabelKey] {
283287
return nil
284288
}
285-
_, err = c.client.ImageService().Update(ctx, img, "target", "labels."+imageLabelKey)
289+
_, err = c.client.ImageService().Update(ctx, img, "target", "labels."+crilabels.ImageLabelKey)
286290
return err
287291
}
288292

293+
// getLabels get image labels to be added on CRI image
294+
func (c *criService) getLabels(ctx context.Context, name string) map[string]string {
295+
labels := map[string]string{crilabels.ImageLabelKey: crilabels.ImageLabelValue}
296+
configSandboxImage := c.config.SandboxImage
297+
// parse sandbox image
298+
sandboxNamedRef, err := distribution.ParseDockerRef(configSandboxImage)
299+
if err != nil {
300+
log.G(ctx).Errorf("failed to parse sandbox image from config %s", sandboxNamedRef)
301+
return nil
302+
}
303+
sandboxRef := sandboxNamedRef.String()
304+
// Adding pinned image label to sandbox image
305+
if sandboxRef == name {
306+
labels[crilabels.PinnedImageLabelKey] = crilabels.PinnedImageLabelValue
307+
}
308+
return labels
309+
}
310+
289311
// updateImage updates image store to reflect the newest state of an image reference
290312
// in containerd. If the reference is not managed by the cri plugin, the function also
291313
// generates necessary metadata for the image and make it managed.
@@ -294,22 +316,23 @@ func (c *criService) updateImage(ctx context.Context, r string) error {
294316
if err != nil && !errdefs.IsNotFound(err) {
295317
return fmt.Errorf("get image by reference: %w", err)
296318
}
297-
if err == nil && img.Labels()[imageLabelKey] != imageLabelValue {
319+
if err == nil && img.Labels()[crilabels.ImageLabelKey] != crilabels.ImageLabelValue {
298320
// Make sure the image has the image id as its unique
299321
// identifier that references the image in its lifetime.
300322
configDesc, err := img.Config(ctx)
301323
if err != nil {
302324
return fmt.Errorf("get image id: %w", err)
303325
}
304326
id := configDesc.Digest.String()
305-
if err := c.createImageReference(ctx, id, img.Target()); err != nil {
327+
labels := c.getLabels(ctx, id)
328+
if err := c.createImageReference(ctx, id, img.Target(), labels); err != nil {
306329
return fmt.Errorf("create image id reference %q: %w", id, err)
307330
}
308331
if err := c.imageStore.Update(ctx, id); err != nil {
309332
return fmt.Errorf("update image store for %q: %w", id, err)
310333
}
311334
// The image id is ready, add the label to mark the image as managed.
312-
if err := c.createImageReference(ctx, r, img.Target()); err != nil {
335+
if err := c.createImageReference(ctx, r, img.Target(), labels); err != nil {
313336
return fmt.Errorf("create managed label: %w", err)
314337
}
315338
}

0 commit comments

Comments
 (0)