Skip to content

Commit fe4f8bd

Browse files
committed
Pinned image support
Signed-off-by: Aditi Sharma <[email protected]>
1 parent 8a6c8a9 commit fe4f8bd

File tree

11 files changed

+235
-34
lines changed

11 files changed

+235
-34
lines changed

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/images/image_pull.go

Lines changed: 32 additions & 10 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 *CRIImageService) PullImage(ctx context.Context, r *runtime.PullImageReq
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 *CRIImageService) PullImage(ctx context.Context, r *runtime.PullImageReq
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.
@@ -283,50 +286,69 @@ func ParseAuth(auth *runtime.AuthConfig, host string) (string, string, error) {
283286
// Note that because create and update are not finished in one transaction, there could be race. E.g.
284287
// the image reference is deleted by someone else after create returns already exists, but before update
285288
// happens.
286-
func (c *CRIImageService) createImageReference(ctx context.Context, name string, desc imagespec.Descriptor) error {
289+
func (c *CRIImageService) createImageReference(ctx context.Context, name string, desc imagespec.Descriptor, labels map[string]string) error {
287290
img := containerdimages.Image{
288291
Name: name,
289292
Target: desc,
290293
// Add a label to indicate that the image is managed by the cri plugin.
291-
Labels: map[string]string{imageLabelKey: imageLabelValue},
294+
Labels: labels,
292295
}
293296
// TODO(random-liu): Figure out which is the more performant sequence create then update or
294297
// update then create.
295298
oldImg, err := c.client.ImageService().Create(ctx, img)
296299
if err == nil || !errdefs.IsAlreadyExists(err) {
297300
return err
298301
}
299-
if oldImg.Target.Digest == img.Target.Digest && oldImg.Labels[imageLabelKey] == imageLabelValue {
302+
if oldImg.Target.Digest == img.Target.Digest && oldImg.Labels[crilabels.ImageLabelKey] == labels[crilabels.ImageLabelKey] {
300303
return nil
301304
}
302-
_, err = c.client.ImageService().Update(ctx, img, "target", "labels."+imageLabelKey)
305+
_, err = c.client.ImageService().Update(ctx, img, "target", "labels."+crilabels.ImageLabelKey)
303306
return err
304307
}
305308

306-
// UpdateImage updates image store to reflect the newest state of an image reference
309+
// getLabels get image labels to be added on CRI image
310+
func (c *CRIImageService) getLabels(ctx context.Context, name string) map[string]string {
311+
labels := map[string]string{crilabels.ImageLabelKey: crilabels.ImageLabelValue}
312+
configSandboxImage := c.config.SandboxImage
313+
// parse sandbox image
314+
sandboxNamedRef, err := distribution.ParseDockerRef(configSandboxImage)
315+
if err != nil {
316+
log.G(ctx).Errorf("failed to parse sandbox image from config %s", sandboxNamedRef)
317+
return nil
318+
}
319+
sandboxRef := sandboxNamedRef.String()
320+
// Adding pinned image label to sandbox image
321+
if sandboxRef == name {
322+
labels[crilabels.PinnedImageLabelKey] = crilabels.PinnedImageLabelValue
323+
}
324+
return labels
325+
}
326+
327+
// updateImage updates image store to reflect the newest state of an image reference
307328
// in containerd. If the reference is not managed by the cri plugin, the function also
308329
// generates necessary metadata for the image and make it managed.
309330
func (c *CRIImageService) UpdateImage(ctx context.Context, r string) error {
310331
img, err := c.client.GetImage(ctx, r)
311332
if err != nil && !errdefs.IsNotFound(err) {
312333
return fmt.Errorf("get image by reference: %w", err)
313334
}
314-
if err == nil && img.Labels()[imageLabelKey] != imageLabelValue {
335+
if err == nil && img.Labels()[crilabels.ImageLabelKey] != crilabels.ImageLabelValue {
315336
// Make sure the image has the image id as its unique
316337
// identifier that references the image in its lifetime.
317338
configDesc, err := img.Config(ctx)
318339
if err != nil {
319340
return fmt.Errorf("get image id: %w", err)
320341
}
321342
id := configDesc.Digest.String()
322-
if err := c.createImageReference(ctx, id, img.Target()); err != nil {
343+
labels := c.getLabels(ctx, id)
344+
if err := c.createImageReference(ctx, id, img.Target(), labels); err != nil {
323345
return fmt.Errorf("create image id reference %q: %w", id, err)
324346
}
325347
if err := c.imageStore.Update(ctx, id); err != nil {
326348
return fmt.Errorf("update image store for %q: %w", id, err)
327349
}
328350
// The image id is ready, add the label to mark the image as managed.
329-
if err := c.createImageReference(ctx, r, img.Target()); err != nil {
351+
if err := c.createImageReference(ctx, r, img.Target(), labels); err != nil {
330352
return fmt.Errorf("create managed label: %w", err)
331353
}
332354
}

pkg/cri/sbserver/images/image_pull_test.go

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

3030
"github.com/containerd/containerd/pkg/cri/annotations"
3131
criconfig "github.com/containerd/containerd/pkg/cri/config"
32+
"github.com/containerd/containerd/pkg/cri/labels"
3233
)
3334

3435
func TestParseAuth(t *testing.T) {
@@ -481,3 +482,57 @@ func TestGetRepoDigestAndTag(t *testing.T) {
481482
})
482483
}
483484
}
485+
486+
func TestImageGetLabels(t *testing.T) {
487+
488+
criService := newTestCRIService()
489+
490+
tests := []struct {
491+
name string
492+
expectedLabel map[string]string
493+
configSandboxImage string
494+
pullImageName string
495+
}{
496+
{
497+
name: "pinned image labels should get added on sandbox image",
498+
expectedLabel: map[string]string{labels.ImageLabelKey: labels.ImageLabelValue, labels.PinnedImageLabelKey: labels.PinnedImageLabelValue},
499+
configSandboxImage: "k8s.gcr.io/pause:3.9",
500+
pullImageName: "k8s.gcr.io/pause:3.9",
501+
},
502+
{
503+
name: "pinned image labels should get added on sandbox image without tag",
504+
expectedLabel: map[string]string{labels.ImageLabelKey: labels.ImageLabelValue, labels.PinnedImageLabelKey: labels.PinnedImageLabelValue},
505+
configSandboxImage: "k8s.gcr.io/pause",
506+
pullImageName: "k8s.gcr.io/pause:latest",
507+
},
508+
{
509+
name: "pinned image labels should get added on sandbox image specified with tag and digest both",
510+
expectedLabel: map[string]string{labels.ImageLabelKey: labels.ImageLabelValue, labels.PinnedImageLabelKey: labels.PinnedImageLabelValue},
511+
configSandboxImage: "k8s.gcr.io/pause:3.9@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2",
512+
pullImageName: "k8s.gcr.io/pause@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2",
513+
},
514+
515+
{
516+
name: "pinned image labels should get added on sandbox image specified with digest",
517+
expectedLabel: map[string]string{labels.ImageLabelKey: labels.ImageLabelValue, labels.PinnedImageLabelKey: labels.PinnedImageLabelValue},
518+
configSandboxImage: "k8s.gcr.io/pause@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2",
519+
pullImageName: "k8s.gcr.io/pause@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2",
520+
},
521+
522+
{
523+
name: "pinned image labels should not get added on other image",
524+
expectedLabel: map[string]string{labels.ImageLabelKey: labels.ImageLabelValue},
525+
configSandboxImage: "k8s.gcr.io/pause:3.9",
526+
pullImageName: "k8s.gcr.io/random:latest",
527+
},
528+
}
529+
530+
for _, tt := range tests {
531+
t.Run(tt.name, func(t *testing.T) {
532+
criService.config.SandboxImage = tt.configSandboxImage
533+
labels := criService.getLabels(context.Background(), tt.pullImageName)
534+
assert.Equal(t, tt.expectedLabel, labels)
535+
536+
})
537+
}
538+
}

pkg/cri/sbserver/images/image_status.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ func toCRIImage(image imagestore.Image) *runtime.Image {
7171
RepoTags: repoTags,
7272
RepoDigests: repoDigests,
7373
Size_: uint64(image.Size),
74+
Pinned: image.Pinned,
7475
}
7576
uid, username := getUserFromImage(image.ImageSpec.Config.User)
7677
if uid != nil {

pkg/cri/sbserver/images/service.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,6 @@ import (
3232
"github.com/sirupsen/logrus"
3333
)
3434

35-
const (
36-
// imageLabelKey is the label key indicating the image is managed by cri plugin.
37-
imageLabelKey = "io.cri-containerd.image"
38-
// imageLabelValue is the label value indicating the image is managed by cri plugin.
39-
imageLabelValue = "managed"
40-
)
41-
4235
type CRIImageService struct {
4336
// config contains all configurations.
4437
config criconfig.Config

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
@@ -43,6 +43,7 @@ import (
4343
"github.com/containerd/containerd/log"
4444
"github.com/containerd/containerd/pkg/cri/annotations"
4545
criconfig "github.com/containerd/containerd/pkg/cri/config"
46+
crilabels "github.com/containerd/containerd/pkg/cri/labels"
4647
snpkg "github.com/containerd/containerd/pkg/snapshotters"
4748
distribution "github.com/containerd/containerd/reference/docker"
4849
"github.com/containerd/containerd/remotes/docker"
@@ -152,12 +153,15 @@ func (c *criService) PullImage(ctx context.Context, r *runtime.PullImageRequest)
152153
tracing.Attribute("image.ref", ref),
153154
tracing.Attribute("snapshotter.name", snapshotter),
154155
)
156+
157+
labels := c.getLabels(ctx, ref)
158+
155159
pullOpts := []containerd.RemoteOpt{
156160
containerd.WithSchema1Conversion, //nolint:staticcheck // Ignore SA1019. Need to keep deprecated package for compatibility.
157161
containerd.WithResolver(resolver),
158162
containerd.WithPullSnapshotter(snapshotter),
159163
containerd.WithPullUnpack,
160-
containerd.WithPullLabel(imageLabelKey, imageLabelValue),
164+
containerd.WithPullLabels(labels),
161165
containerd.WithMaxConcurrentDownloads(c.config.MaxConcurrentDownloads),
162166
containerd.WithImageHandler(imageHandler),
163167
containerd.WithUnpackOpts([]containerd.UnpackOpt{
@@ -196,7 +200,7 @@ func (c *criService) PullImage(ctx context.Context, r *runtime.PullImageRequest)
196200
if r == "" {
197201
continue
198202
}
199-
if err := c.createImageReference(ctx, r, image.Target()); err != nil {
203+
if err := c.createImageReference(ctx, r, image.Target(), labels); err != nil {
200204
return nil, fmt.Errorf("failed to create image reference %q: %w", r, err)
201205
}
202206
// Update image store to reflect the newest state in containerd.
@@ -265,26 +269,44 @@ func ParseAuth(auth *runtime.AuthConfig, host string) (string, string, error) {
265269
// Note that because create and update are not finished in one transaction, there could be race. E.g.
266270
// the image reference is deleted by someone else after create returns already exists, but before update
267271
// happens.
268-
func (c *criService) createImageReference(ctx context.Context, name string, desc imagespec.Descriptor) error {
272+
func (c *criService) createImageReference(ctx context.Context, name string, desc imagespec.Descriptor, labels map[string]string) error {
269273
img := containerdimages.Image{
270274
Name: name,
271275
Target: desc,
272276
// Add a label to indicate that the image is managed by the cri plugin.
273-
Labels: map[string]string{imageLabelKey: imageLabelValue},
277+
Labels: labels,
274278
}
275279
// TODO(random-liu): Figure out which is the more performant sequence create then update or
276280
// update then create.
277281
oldImg, err := c.client.ImageService().Create(ctx, img)
278282
if err == nil || !errdefs.IsAlreadyExists(err) {
279283
return err
280284
}
281-
if oldImg.Target.Digest == img.Target.Digest && oldImg.Labels[imageLabelKey] == imageLabelValue {
285+
if oldImg.Target.Digest == img.Target.Digest && oldImg.Labels[crilabels.ImageLabelKey] == labels[crilabels.ImageLabelKey] {
282286
return nil
283287
}
284-
_, err = c.client.ImageService().Update(ctx, img, "target", "labels."+imageLabelKey)
288+
_, err = c.client.ImageService().Update(ctx, img, "target", "labels."+crilabels.ImageLabelKey)
285289
return err
286290
}
287291

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

0 commit comments

Comments
 (0)