github.com/moby/docker@v26.1.3+incompatible/daemon/containerd/image_test.go (about) 1 package containerd 2 3 import ( 4 "context" 5 "io" 6 "math/rand" 7 "path/filepath" 8 "testing" 9 10 "github.com/containerd/containerd/images" 11 "github.com/containerd/containerd/metadata" 12 "github.com/containerd/containerd/namespaces" 13 "github.com/containerd/containerd/snapshots" 14 "github.com/containerd/log/logtest" 15 "github.com/distribution/reference" 16 dockerimages "github.com/docker/docker/daemon/images" 17 "github.com/opencontainers/go-digest" 18 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 19 20 "go.etcd.io/bbolt" 21 22 "gotest.tools/v3/assert" 23 is "gotest.tools/v3/assert/cmp" 24 ) 25 26 func TestLookup(t *testing.T) { 27 ctx := namespaces.WithNamespace(context.TODO(), "testing") 28 ctx = logtest.WithT(ctx, t) 29 mdb := newTestDB(ctx, t) 30 service := &ImageService{ 31 images: metadata.NewImageStore(mdb), 32 } 33 34 ubuntuLatest := images.Image{ 35 Name: "docker.io/library/ubuntu:latest", 36 Target: desc(10), 37 } 38 ubuntuLatestWithDigest := images.Image{ 39 Name: "docker.io/library/ubuntu:latest@" + digestFor(10).String(), 40 Target: desc(10), 41 } 42 ubuntuLatestWithOldDigest := images.Image{ 43 Name: "docker.io/library/ubuntu:latest@" + digestFor(11).String(), 44 Target: desc(11), 45 } 46 ambiguousShortName := images.Image{ 47 Name: "docker.io/library/abcdef:latest", 48 Target: desc(12), 49 } 50 ambiguousShortNameWithDigest := images.Image{ 51 Name: "docker.io/library/abcdef:latest@" + digestFor(12).String(), 52 Target: desc(12), 53 } 54 shortNameIsHashAlgorithm := images.Image{ 55 Name: "docker.io/library/sha256:defcab", 56 Target: desc(13), 57 } 58 59 testImages := []images.Image{ 60 ubuntuLatest, 61 ubuntuLatestWithDigest, 62 ubuntuLatestWithOldDigest, 63 ambiguousShortName, 64 ambiguousShortNameWithDigest, 65 shortNameIsHashAlgorithm, 66 { 67 Name: "docker.io/test/volatile:retried", 68 Target: desc(14), 69 }, 70 { 71 Name: "docker.io/test/volatile:inconsistent", 72 Target: desc(15), 73 }, 74 } 75 for _, img := range testImages { 76 if _, err := service.images.Create(ctx, img); err != nil { 77 t.Fatalf("failed to create image %q: %v", img.Name, err) 78 } 79 } 80 81 for _, tc := range []struct { 82 lookup string 83 img *images.Image 84 all []images.Image 85 err error 86 }{ 87 { 88 // Get ubuntu images with default "latest" tag 89 lookup: "ubuntu", 90 img: &ubuntuLatest, 91 all: []images.Image{ubuntuLatest, ubuntuLatestWithDigest}, 92 }, 93 { 94 // Get all images by image id 95 lookup: ubuntuLatest.Target.Digest.String(), 96 img: nil, 97 all: []images.Image{ubuntuLatest, ubuntuLatestWithDigest}, 98 }, 99 { 100 // Fail to lookup reference with no tag, reference has both tag and digest 101 lookup: "ubuntu@" + ubuntuLatestWithOldDigest.Target.Digest.String(), 102 img: nil, 103 all: []images.Image{ubuntuLatestWithOldDigest}, 104 }, 105 { 106 // Get all image with both tag and digest 107 lookup: "ubuntu:latest@" + ubuntuLatestWithOldDigest.Target.Digest.String(), 108 img: &ubuntuLatestWithOldDigest, 109 all: []images.Image{ubuntuLatestWithOldDigest}, 110 }, 111 { 112 // Fail to lookup reference with no tag for digest that doesn't exist 113 lookup: "ubuntu@" + digestFor(20).String(), 114 err: dockerimages.ErrImageDoesNotExist{Ref: nameDigest("ubuntu", digestFor(20))}, 115 }, 116 { 117 // Fail to lookup reference with nonexistent tag 118 lookup: "ubuntu:nonexistent", 119 err: dockerimages.ErrImageDoesNotExist{Ref: nameTag("ubuntu", "nonexistent")}, 120 }, 121 { 122 // Get abcdef image which also matches short image id 123 lookup: "abcdef", 124 img: &ambiguousShortName, 125 all: []images.Image{ambiguousShortName, ambiguousShortNameWithDigest}, 126 }, 127 { 128 // Fail to lookup image named "sha256" with tag that doesn't exist 129 lookup: "sha256:abcdef", 130 err: dockerimages.ErrImageDoesNotExist{Ref: nameTag("sha256", "abcdef")}, 131 }, 132 { 133 // Lookup with shortened image id 134 lookup: ambiguousShortName.Target.Digest.Encoded()[:8], 135 img: nil, 136 all: []images.Image{ambiguousShortName, ambiguousShortNameWithDigest}, 137 }, 138 { 139 // Lookup an actual image named "sha256" in the default namespace 140 lookup: "sha256:defcab", 141 img: &shortNameIsHashAlgorithm, 142 all: []images.Image{shortNameIsHashAlgorithm}, 143 }, 144 } { 145 tc := tc 146 t.Run(tc.lookup, func(t *testing.T) { 147 t.Parallel() 148 img, all, err := service.resolveAllReferences(ctx, tc.lookup) 149 if tc.err == nil { 150 assert.NilError(t, err) 151 } else { 152 assert.Error(t, err, tc.err.Error()) 153 } 154 if tc.img == nil { 155 assert.Assert(t, is.Nil(img)) 156 } else { 157 assert.Assert(t, img != nil) 158 assert.Check(t, is.Equal(img.Name, tc.img.Name)) 159 assert.Check(t, is.Equal(img.Target.Digest, tc.img.Target.Digest)) 160 } 161 162 assert.Assert(t, is.Len(tc.all, len(all))) 163 164 // Order should match 165 for i := range all { 166 assert.Check(t, is.Equal(all[i].Name, tc.all[i].Name), "image[%d]", i) 167 assert.Check(t, is.Equal(all[i].Target.Digest, tc.all[i].Target.Digest), "image[%d]", i) 168 } 169 }) 170 } 171 172 t.Run("fail-inconsistency", func(t *testing.T) { 173 service := &ImageService{ 174 images: &mutateOnGetImageStore{ 175 Store: service.images, 176 getMutations: []images.Image{ 177 { 178 Name: "docker.io/test/volatile:inconsistent", 179 Target: desc(18), 180 }, 181 { 182 Name: "docker.io/test/volatile:inconsistent", 183 Target: desc(19), 184 }, 185 { 186 Name: "docker.io/test/volatile:inconsistent", 187 Target: desc(20), 188 }, 189 { 190 Name: "docker.io/test/volatile:inconsistent", 191 Target: desc(21), 192 }, 193 { 194 Name: "docker.io/test/volatile:inconsistent", 195 Target: desc(22), 196 }, 197 }, 198 t: t, 199 }, 200 } 201 202 _, _, err := service.resolveAllReferences(ctx, "test/volatile:inconsistent") 203 assert.ErrorIs(t, err, errInconsistentData) 204 }) 205 206 t.Run("retry-inconsistency", func(t *testing.T) { 207 service := &ImageService{ 208 images: &mutateOnGetImageStore{ 209 Store: service.images, 210 getMutations: []images.Image{ 211 { 212 Name: "docker.io/test/volatile:retried", 213 Target: desc(16), 214 }, 215 { 216 Name: "docker.io/test/volatile:retried", 217 Target: desc(17), 218 }, 219 }, 220 t: t, 221 }, 222 } 223 224 img, all, err := service.resolveAllReferences(ctx, "test/volatile:retried") 225 assert.NilError(t, err) 226 227 assert.Assert(t, img != nil) 228 assert.Check(t, is.Equal(img.Name, "docker.io/test/volatile:retried")) 229 assert.Check(t, is.Equal(img.Target.Digest, digestFor(17))) 230 assert.Assert(t, is.Len(all, 1)) 231 assert.Check(t, is.Equal(all[0].Name, "docker.io/test/volatile:retried")) 232 assert.Check(t, is.Equal(all[0].Target.Digest, digestFor(17))) 233 }) 234 } 235 236 type mutateOnGetImageStore struct { 237 images.Store 238 getMutations []images.Image 239 t *testing.T 240 } 241 242 func (m *mutateOnGetImageStore) Get(ctx context.Context, name string) (images.Image, error) { 243 img, err := m.Store.Get(ctx, name) 244 if len(m.getMutations) > 0 { 245 m.Store.Update(ctx, m.getMutations[0]) 246 m.getMutations = m.getMutations[1:] 247 m.t.Logf("Get %s", name) 248 } 249 return img, err 250 } 251 252 func nameDigest(name string, dgst digest.Digest) reference.Reference { 253 named, _ := reference.WithName(name) 254 digested, _ := reference.WithDigest(named, dgst) 255 return digested 256 } 257 258 func nameTag(name, tag string) reference.Reference { 259 named, _ := reference.WithName(name) 260 tagged, _ := reference.WithTag(named, tag) 261 return tagged 262 } 263 264 func desc(size int64) ocispec.Descriptor { 265 return ocispec.Descriptor{ 266 Digest: digestFor(size), 267 Size: size, 268 MediaType: ocispec.MediaTypeImageIndex, 269 } 270 271 } 272 273 func digestFor(i int64) digest.Digest { 274 r := rand.New(rand.NewSource(i)) 275 dgstr := digest.SHA256.Digester() 276 _, err := io.Copy(dgstr.Hash(), io.LimitReader(r, i)) 277 if err != nil { 278 panic(err) 279 } 280 return dgstr.Digest() 281 } 282 283 func newTestDB(ctx context.Context, t testing.TB) *metadata.DB { 284 t.Helper() 285 286 p := filepath.Join(t.TempDir(), "metadata") 287 bdb, err := bbolt.Open(p, 0600, &bbolt.Options{}) 288 if err != nil { 289 t.Fatal(err) 290 } 291 t.Cleanup(func() { bdb.Close() }) 292 293 mdb := metadata.NewDB(bdb, nil, nil) 294 if err := mdb.Init(ctx); err != nil { 295 t.Fatal(err) 296 } 297 298 return mdb 299 } 300 301 type testSnapshotterService struct { 302 snapshots.Snapshotter 303 } 304 305 func (s *testSnapshotterService) Stat(ctx context.Context, key string) (snapshots.Info, error) { 306 return snapshots.Info{}, nil 307 } 308 309 func (s *testSnapshotterService) Usage(ctx context.Context, key string) (snapshots.Usage, error) { 310 return snapshots.Usage{}, nil 311 }