oras.land/oras-go/v2@v2.5.1-0.20240520045656-aef90e4d04c4/internal/manifestutil/parser_test.go (about) 1 /* 2 Copyright The ORAS Authors. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 */ 15 16 package manifestutil 17 18 import ( 19 "bytes" 20 "context" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "io" 25 "reflect" 26 "testing" 27 28 "github.com/opencontainers/go-digest" 29 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 30 "golang.org/x/sync/errgroup" 31 "oras.land/oras-go/v2/content/memory" 32 "oras.land/oras-go/v2/internal/cas" 33 "oras.land/oras-go/v2/internal/container/set" 34 "oras.land/oras-go/v2/internal/docker" 35 ) 36 37 var ErrBadFetch = errors.New("bad fetch error") 38 39 // testStorage implements Fetcher 40 type testStorage struct { 41 store *memory.Store 42 badFetch set.Set[digest.Digest] 43 } 44 45 func (s *testStorage) Push(ctx context.Context, expected ocispec.Descriptor, reader io.Reader) error { 46 return s.store.Push(ctx, expected, reader) 47 } 48 49 func (s *testStorage) Fetch(ctx context.Context, target ocispec.Descriptor) (io.ReadCloser, error) { 50 if s.badFetch.Contains(target.Digest) { 51 return nil, ErrBadFetch 52 } 53 return s.store.Fetch(ctx, target) 54 } 55 56 // func (s *testStorage) Exists(ctx context.Context, target ocispec.Descriptor) (bool, error) { 57 // return s.store.Exists(ctx, target) 58 // } 59 60 // func (s *testStorage) Predecessors(ctx context.Context, node ocispec.Descriptor) ([]ocispec.Descriptor, error) { 61 // return s.store.Predecessors(ctx, node) 62 // } 63 64 func TestConfig(t *testing.T) { 65 storage := cas.NewMemory() 66 67 // generate test content 68 var blobs [][]byte 69 var descs []ocispec.Descriptor 70 appendBlob := func(mediaType string, blob []byte) { 71 blobs = append(blobs, blob) 72 descs = append(descs, ocispec.Descriptor{ 73 MediaType: mediaType, 74 Digest: digest.FromBytes(blob), 75 Size: int64(len(blob)), 76 }) 77 } 78 generateManifest := func(mediaType string, config ocispec.Descriptor, layers ...ocispec.Descriptor) { 79 manifest := ocispec.Manifest{ 80 Config: config, 81 Layers: layers, 82 } 83 manifestJSON, err := json.Marshal(manifest) 84 if err != nil { 85 t.Fatal(err) 86 } 87 appendBlob(mediaType, manifestJSON) 88 } 89 90 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 91 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 92 generateManifest(ocispec.MediaTypeImageManifest, descs[0], descs[1]) // Blob 2 93 generateManifest(docker.MediaTypeManifest, descs[0], descs[1]) // Blob 3 94 generateManifest("whatever", descs[0], descs[1]) // Blob 4 95 96 ctx := context.Background() 97 for i := range blobs { 98 err := storage.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 99 if err != nil { 100 t.Fatalf("failed to push test content to src: %d: %v", i, err) 101 } 102 } 103 104 tests := []struct { 105 name string 106 desc ocispec.Descriptor 107 want *ocispec.Descriptor 108 wantErr bool 109 }{ 110 { 111 name: "OCI Image Manifest", 112 desc: descs[2], 113 want: &descs[0], 114 }, 115 { 116 name: "Docker Manifest", 117 desc: descs[3], 118 want: &descs[0], 119 wantErr: false, 120 }, 121 { 122 name: "Other media type", 123 desc: descs[4], 124 want: nil, 125 }, 126 } 127 for _, tt := range tests { 128 t.Run(tt.name, func(t *testing.T) { 129 got, err := Config(ctx, storage, tt.desc) 130 if (err != nil) != tt.wantErr { 131 t.Errorf("Config() error = %v, wantErr %v", err, tt.wantErr) 132 return 133 } 134 if !reflect.DeepEqual(got, tt.want) { 135 t.Errorf("Config() = %v, want %v", got, tt.want) 136 } 137 }) 138 } 139 } 140 141 func TestManifests(t *testing.T) { 142 storage := cas.NewMemory() 143 144 // generate test content 145 var blobs [][]byte 146 var descs []ocispec.Descriptor 147 appendBlob := func(mediaType string, blob []byte) { 148 blobs = append(blobs, blob) 149 descs = append(descs, ocispec.Descriptor{ 150 MediaType: mediaType, 151 Digest: digest.FromBytes(blob), 152 Size: int64(len(blob)), 153 }) 154 } 155 generateManifest := func(subject *ocispec.Descriptor, config ocispec.Descriptor, layers ...ocispec.Descriptor) { 156 manifest := ocispec.Manifest{ 157 Subject: subject, 158 Config: config, 159 Layers: layers, 160 } 161 manifestJSON, err := json.Marshal(manifest) 162 if err != nil { 163 t.Fatal(err) 164 } 165 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 166 } 167 generateIndex := func(mediaType string, subject *ocispec.Descriptor, manifests ...ocispec.Descriptor) { 168 index := ocispec.Index{ 169 Subject: subject, 170 Manifests: manifests, 171 } 172 indexJSON, err := json.Marshal(index) 173 if err != nil { 174 t.Fatal(err) 175 } 176 appendBlob(mediaType, indexJSON) 177 } 178 179 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 180 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 181 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 182 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello")) // Blob 3 183 generateManifest(nil, descs[0], descs[1:3]...) // Blob 4 184 generateManifest(nil, descs[0], descs[3]) // Blob 5 185 appendBlob(ocispec.MediaTypeImageConfig, []byte("{}")) // Blob 6 186 appendBlob("test/sig", []byte("sig")) // Blob 7 187 generateManifest(&descs[4], descs[5], descs[6]) // Blob 8 188 generateIndex(ocispec.MediaTypeImageIndex, &descs[8], descs[4:6]...) // Blob 9 189 generateIndex(docker.MediaTypeManifestList, nil, descs[4:6]...) // Blob 10 190 generateIndex("whatever", &descs[8], descs[4:6]...) // Blob 11 191 192 ctx := context.Background() 193 for i := range blobs { 194 err := storage.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 195 if err != nil { 196 t.Fatalf("failed to push test content to src: %d: %v", i, err) 197 } 198 } 199 200 tests := []struct { 201 name string 202 desc ocispec.Descriptor 203 want []ocispec.Descriptor 204 wantErr bool 205 }{ 206 { 207 name: "OCI Image Index", 208 desc: descs[9], 209 want: descs[4:6], 210 }, 211 { 212 name: "Docker Manifest List", 213 desc: descs[10], 214 want: descs[4:6], 215 wantErr: false, 216 }, 217 { 218 name: "Other media type", 219 desc: descs[11], 220 want: nil, 221 }, 222 } 223 for _, tt := range tests { 224 t.Run(tt.name, func(t *testing.T) { 225 got, err := Manifests(ctx, storage, tt.desc) 226 if (err != nil) != tt.wantErr { 227 t.Errorf("Manifests() error = %v, wantErr %v", err, tt.wantErr) 228 return 229 } 230 if !reflect.DeepEqual(got, tt.want) { 231 t.Errorf("Manifests() = %v, want %v", got, tt.want) 232 } 233 }) 234 } 235 } 236 237 func TestSubject(t *testing.T) { 238 storage := cas.NewMemory() 239 240 // generate test content 241 var blobs [][]byte 242 var descs []ocispec.Descriptor 243 appendBlob := func(mediaType string, blob []byte) { 244 blobs = append(blobs, blob) 245 descs = append(descs, ocispec.Descriptor{ 246 MediaType: mediaType, 247 Digest: digest.FromBytes(blob), 248 Size: int64(len(blob)), 249 }) 250 } 251 generateManifest := func(config ocispec.Descriptor, subject *ocispec.Descriptor, layers ...ocispec.Descriptor) { 252 manifest := ocispec.Manifest{ 253 Config: config, 254 Subject: subject, 255 Layers: layers, 256 } 257 manifestJSON, err := json.Marshal(manifest) 258 if err != nil { 259 t.Fatal(err) 260 } 261 appendBlob(ocispec.MediaTypeImageManifest, manifestJSON) 262 } 263 appendBlob(ocispec.MediaTypeImageConfig, []byte("config")) // Blob 0 264 appendBlob(ocispec.MediaTypeImageLayer, []byte("blob")) // Blob 1 265 generateManifest(descs[0], nil, descs[1]) // Blob 2, manifest 266 generateManifest(descs[0], &descs[2], descs[1]) // Blob 3, referrer of blob 2 267 268 ctx := context.Background() 269 for i := range blobs { 270 err := storage.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 271 if err != nil { 272 t.Fatalf("failed to push test content to src: %d: %v", i, err) 273 } 274 } 275 got, err := Subject(ctx, storage, descs[3]) 276 if err != nil { 277 t.Fatalf("error when getting subject: %v", err) 278 } 279 if !reflect.DeepEqual(*got, descs[2]) { 280 t.Errorf("Subject() = %v, want %v", got, descs[2]) 281 } 282 got, err = Subject(ctx, storage, descs[0]) 283 if err != nil { 284 t.Fatalf("error when getting subject: %v", err) 285 } 286 if got != nil { 287 t.Errorf("Subject() = %v, want %v", got, nil) 288 } 289 } 290 291 func TestSubject_ErrorPath(t *testing.T) { 292 s := testStorage{ 293 store: memory.New(), 294 badFetch: set.New[digest.Digest](), 295 } 296 ctx := context.Background() 297 298 // generate test content 299 var blobs [][]byte 300 var descs []ocispec.Descriptor 301 appendBlob := func(mediaType string, artifactType string, blob []byte) { 302 blobs = append(blobs, blob) 303 descs = append(descs, ocispec.Descriptor{ 304 MediaType: mediaType, 305 ArtifactType: artifactType, 306 Annotations: map[string]string{"test": "content"}, 307 Digest: digest.FromBytes(blob), 308 Size: int64(len(blob)), 309 }) 310 } 311 generateImageManifest := func(config ocispec.Descriptor, subject *ocispec.Descriptor, layers ...ocispec.Descriptor) { 312 manifest := ocispec.Manifest{ 313 MediaType: ocispec.MediaTypeImageManifest, 314 Config: config, 315 Subject: subject, 316 Layers: layers, 317 Annotations: map[string]string{"test": "content"}, 318 } 319 manifestJSON, err := json.Marshal(manifest) 320 if err != nil { 321 t.Fatal(err) 322 } 323 appendBlob(ocispec.MediaTypeImageManifest, manifest.Config.MediaType, manifestJSON) 324 } 325 appendBlob("image manifest", "image config", []byte("config")) // Blob 0 326 appendBlob(ocispec.MediaTypeImageLayer, "layer", []byte("foo")) // Blob 1 327 appendBlob(ocispec.MediaTypeImageLayer, "layer", []byte("bar")) // Blob 2 328 appendBlob(ocispec.MediaTypeImageLayer, "layer", []byte("hello")) // Blob 3 329 generateImageManifest(descs[0], nil, descs[1]) // Blob 4 330 generateImageManifest(descs[0], &descs[4], descs[2]) // Blob 5 331 s.badFetch.Add(descs[5].Digest) 332 333 eg, egCtx := errgroup.WithContext(ctx) 334 for i := range blobs { 335 eg.Go(func(i int) func() error { 336 return func() error { 337 err := s.Push(egCtx, descs[i], bytes.NewReader(blobs[i])) 338 if err != nil { 339 return fmt.Errorf("failed to push test content to src: %d: %v", i, err) 340 } 341 return nil 342 } 343 }(i)) 344 } 345 if err := eg.Wait(); err != nil { 346 t.Fatal(err) 347 } 348 349 _, err := Subject(ctx, &s, descs[5]) 350 if !errors.Is(err, ErrBadFetch) { 351 t.Errorf("Store.Referrers() error = %v, want %v", err, ErrBadFetch) 352 } 353 }