| /* |
| Copyright The containerd Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package client |
| |
| import ( |
| "context" |
| "fmt" |
| "runtime" |
| "strings" |
| "testing" |
| |
| . "github.com/containerd/containerd/v2/client" |
| "github.com/containerd/containerd/v2/core/images" |
| "github.com/containerd/containerd/v2/defaults" |
| imagelist "github.com/containerd/containerd/v2/integration/images" |
| "github.com/containerd/containerd/v2/pkg/labels" |
| "github.com/containerd/errdefs" |
| "github.com/containerd/platforms" |
| ocispec "github.com/opencontainers/image-spec/specs-go/v1" |
| ) |
| |
| func TestImageIsUnpacked(t *testing.T) { |
| imageName := imagelist.Get(imagelist.Pause) |
| ctx, cancel := testContext(t) |
| defer cancel() |
| |
| client, err := newClient(t, address) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer client.Close() |
| |
| // Cleanup |
| opts := []images.DeleteOpt{images.SynchronousDelete()} |
| err = client.ImageService().Delete(ctx, imageName, opts...) |
| if err != nil && !errdefs.IsNotFound(err) { |
| t.Fatal(err) |
| } |
| |
| // By default pull does not unpack an image |
| image, err := client.Pull(ctx, imageName, WithPlatformMatcher(platforms.Default())) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Check that image is not unpacked |
| unpacked, err := image.IsUnpacked(ctx, defaults.DefaultSnapshotter) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if unpacked { |
| t.Fatalf("image should not be unpacked") |
| } |
| |
| // Check that image is unpacked |
| err = image.Unpack(ctx, defaults.DefaultSnapshotter) |
| if err != nil { |
| t.Fatal(err) |
| } |
| unpacked, err = image.IsUnpacked(ctx, defaults.DefaultSnapshotter) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !unpacked { |
| t.Fatalf("image should be unpacked") |
| } |
| } |
| |
| func TestImagePullWithDistSourceLabel(t *testing.T) { |
| var ( |
| source = "registry.k8s.io" |
| repoName = "pause" |
| tag = "3.6" |
| ) |
| |
| ctx, cancel := testContext(t) |
| defer cancel() |
| |
| client, err := newClient(t, address) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer client.Close() |
| |
| imageName := fmt.Sprintf("%s/%s:%s", source, repoName, tag) |
| pMatcher := platforms.Default() |
| |
| // pull content without unpack and add distribution source label |
| image, err := client.Pull(ctx, imageName, WithPlatformMatcher(pMatcher)) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer client.ImageService().Delete(ctx, imageName) |
| |
| cs := client.ContentStore() |
| key := labels.LabelDistributionSource + "." + source |
| |
| // only check the target platform |
| childrenHandler := images.LimitManifests(images.ChildrenHandler(cs), pMatcher, 1) |
| |
| checkLabelHandler := func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { |
| children, err := childrenHandler(ctx, desc) |
| if err != nil { |
| return nil, err |
| } |
| |
| info, err := cs.Info(ctx, desc.Digest) |
| if err != nil { |
| return nil, err |
| } |
| |
| // check the label |
| if got := info.Labels[key]; !strings.Contains(got, repoName) { |
| return nil, fmt.Errorf("expected to have %s repo name in label, but got %s", repoName, got) |
| } |
| return children, nil |
| } |
| |
| if err := images.Dispatch(ctx, images.HandlerFunc(checkLabelHandler), nil, image.Target()); err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| func TestImageUsage(t *testing.T) { |
| if testing.Short() { |
| t.Skip() |
| } |
| |
| imageName := imagelist.Get(imagelist.Pause) |
| ctx, cancel := testContext(t) |
| defer cancel() |
| |
| client, err := newClient(t, address) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer client.Close() |
| |
| // Cleanup |
| err = client.ImageService().Delete(ctx, imageName, images.SynchronousDelete()) |
| if err != nil && !errdefs.IsNotFound(err) { |
| t.Fatal(err) |
| } |
| |
| pMatcher := platforms.Default() |
| |
| // Pull single platform, do not unpack |
| image, err := client.Pull(ctx, imageName, WithPlatformMatcher(pMatcher)) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| s1, err := image.Usage(ctx, WithUsageManifestLimit(1)) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if _, err := image.Usage(ctx, WithUsageManifestLimit(0), WithManifestUsage()); err == nil { |
| t.Fatal("expected NotFound with missing manifests") |
| } else if !errdefs.IsNotFound(err) { |
| t.Fatalf("unexpected error: %+v", err) |
| } |
| |
| // Pin image name to specific version for future fetches |
| imageName = imageName + "@" + image.Target().Digest.String() |
| defer client.ImageService().Delete(ctx, imageName, images.SynchronousDelete()) |
| |
| // Fetch single platforms, but all manifests pulled |
| if _, err := client.Fetch(ctx, imageName, WithPlatformMatcher(pMatcher), WithAllMetadata()); err != nil { |
| t.Fatal(err) |
| } |
| |
| if s, err := image.Usage(ctx, WithUsageManifestLimit(1)); err != nil { |
| t.Fatal(err) |
| } else if s != s1 { |
| t.Fatalf("unexpected usage %d, expected %d", s, s1) |
| } |
| |
| s2, err := image.Usage(ctx, WithUsageManifestLimit(0)) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if s2 <= s1 { |
| t.Fatalf("Expected larger usage counting all manifests: %d <= %d", s2, s1) |
| } |
| |
| s3, err := image.Usage(ctx, WithUsageManifestLimit(0), WithManifestUsage()) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if s3 <= s2 { |
| t.Fatalf("Expected larger usage counting all manifest reported sizes: %d <= %d", s3, s2) |
| } |
| |
| // Fetch everything |
| if _, err = client.Fetch(ctx, imageName); err != nil { |
| t.Fatal(err) |
| } |
| |
| if s, err := image.Usage(ctx); err != nil { |
| t.Fatal(err) |
| } else if s != s3 { |
| t.Fatalf("Expected actual usage to equal manifest reported usage of %d: got %d", s3, s) |
| } |
| |
| err = image.Unpack(ctx, defaults.DefaultSnapshotter) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if s, err := image.Usage(ctx, WithSnapshotUsage()); err != nil { |
| t.Fatal(err) |
| } else if s <= s3 { |
| t.Fatalf("Expected actual usage with snapshots to be greater: %d <= %d", s, s3) |
| } |
| } |
| |
| func TestImageSupportedBySnapshotter_Error(t *testing.T) { |
| var unsupportedImage string |
| if runtime.GOOS == "windows" { |
| unsupportedImage = "registry.k8s.io/pause-amd64:3.2" |
| } else { |
| unsupportedImage = "ghcr.io/containerd/windows/nanoserver:1809" |
| } |
| |
| ctx, cancel := testContext(t) |
| defer cancel() |
| |
| client, err := newClient(t, address) |
| if err != nil { |
| t.Fatal(err) |
| } |
| defer client.Close() |
| |
| // Cleanup |
| err = client.ImageService().Delete(ctx, unsupportedImage) |
| if err != nil && !errdefs.IsNotFound(err) { |
| t.Fatal(err) |
| } |
| |
| _, err = client.Pull(ctx, unsupportedImage, |
| WithPlatform(platforms.DefaultString()), |
| WithPullSnapshotter(defaults.DefaultSnapshotter), |
| WithPullUnpack, |
| WithUnpackOpts([]UnpackOpt{WithSnapshotterPlatformCheck()}), |
| ) |
| |
| if err == nil { |
| t.Fatalf("expected unpacking %s for snapshotter %s to fail", unsupportedImage, defaults.DefaultSnapshotter) |
| } |
| } |