github.com/opcr-io/oras-go/v2@v2.0.0-20231122155130-eb4260d8a0ae/internal/platform/platform_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 platform 17 18 import ( 19 "bytes" 20 "context" 21 _ "crypto/sha256" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "reflect" 26 "testing" 27 28 "github.com/opcr-io/oras-go/v2/errdef" 29 "github.com/opcr-io/oras-go/v2/internal/cas" 30 "github.com/opcr-io/oras-go/v2/internal/docker" 31 "github.com/opencontainers/go-digest" 32 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 33 ) 34 35 func TestMatch(t *testing.T) { 36 tests := []struct { 37 got ocispec.Platform 38 want ocispec.Platform 39 isMatched bool 40 }{{ 41 ocispec.Platform{Architecture: "amd64", OS: "linux"}, 42 ocispec.Platform{Architecture: "amd64", OS: "linux"}, 43 true, 44 }, { 45 ocispec.Platform{Architecture: "amd64", OS: "linux"}, 46 ocispec.Platform{Architecture: "amd64", OS: "LINUX"}, 47 false, 48 }, { 49 ocispec.Platform{Architecture: "amd64", OS: "linux"}, 50 ocispec.Platform{Architecture: "arm64", OS: "linux"}, 51 false, 52 }, { 53 ocispec.Platform{Architecture: "arm", OS: "linux"}, 54 ocispec.Platform{Architecture: "arm", OS: "linux", Variant: "v7"}, 55 false, 56 }, { 57 ocispec.Platform{Architecture: "arm", OS: "linux", Variant: "v7"}, 58 ocispec.Platform{Architecture: "arm", OS: "linux"}, 59 true, 60 }, { 61 ocispec.Platform{Architecture: "arm", OS: "linux", Variant: "v7"}, 62 ocispec.Platform{Architecture: "arm", OS: "linux", Variant: "v7"}, 63 true, 64 }, { 65 ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "10.0.20348.768"}, 66 ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "10.0.20348.700"}, 67 false, 68 }, { 69 ocispec.Platform{Architecture: "amd64", OS: "windows"}, 70 ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "10.0.20348.768"}, 71 false, 72 }, { 73 ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "10.0.20348.768"}, 74 ocispec.Platform{Architecture: "amd64", OS: "windows"}, 75 true, 76 }, { 77 ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "10.0.20348.768"}, 78 ocispec.Platform{Architecture: "amd64", OS: "windows", OSVersion: "10.0.20348.768"}, 79 true, 80 }, { 81 ocispec.Platform{Architecture: "arm", OS: "linux", OSFeatures: []string{"a", "d"}}, 82 ocispec.Platform{Architecture: "arm", OS: "linux", OSFeatures: []string{"a", "c"}}, 83 false, 84 }, { 85 ocispec.Platform{Architecture: "arm", OS: "linux"}, 86 ocispec.Platform{Architecture: "arm", OS: "linux", OSFeatures: []string{"a"}}, 87 false, 88 }, { 89 ocispec.Platform{Architecture: "arm", OS: "linux", OSFeatures: []string{"a"}}, 90 ocispec.Platform{Architecture: "arm", OS: "linux"}, 91 true, 92 }, { 93 ocispec.Platform{Architecture: "arm", OS: "linux", OSFeatures: []string{"a", "b"}}, 94 ocispec.Platform{Architecture: "arm", OS: "linux", OSFeatures: []string{"a", "b"}}, 95 true, 96 }, { 97 ocispec.Platform{Architecture: "arm", OS: "linux", OSFeatures: []string{"a", "d", "c", "b"}}, 98 ocispec.Platform{Architecture: "arm", OS: "linux", OSFeatures: []string{"d", "c", "a", "b"}}, 99 true, 100 }} 101 102 for _, tt := range tests { 103 gotPlatformJSON, _ := json.Marshal(tt.got) 104 wantPlatformJSON, _ := json.Marshal(tt.want) 105 name := string(gotPlatformJSON) + string(wantPlatformJSON) 106 t.Run(name, func(t *testing.T) { 107 if actual := Match(&tt.got, &tt.want); actual != tt.isMatched { 108 t.Errorf("Match() = %v, want %v", actual, tt.isMatched) 109 } 110 }) 111 } 112 } 113 114 func TestSelectManifest(t *testing.T) { 115 storage := cas.NewMemory() 116 arc_1 := "test-arc-1" 117 os_1 := "test-os-1" 118 variant_1 := "v1" 119 arc_2 := "test-arc-2" 120 os_2 := "test-os-2" 121 variant_2 := "v2" 122 123 // generate test content 124 var blobs [][]byte 125 var descs []ocispec.Descriptor 126 appendBlob := func(mediaType string, blob []byte) { 127 blobs = append(blobs, blob) 128 descs = append(descs, ocispec.Descriptor{ 129 MediaType: mediaType, 130 Digest: digest.FromBytes(blob), 131 Size: int64(len(blob)), 132 }) 133 } 134 appendManifest := func(arc, os, variant string, mediaType string, blob []byte) { 135 blobs = append(blobs, blob) 136 descs = append(descs, ocispec.Descriptor{ 137 MediaType: mediaType, 138 Digest: digest.FromBytes(blob), 139 Size: int64(len(blob)), 140 Platform: &ocispec.Platform{ 141 Architecture: arc, 142 OS: os, 143 Variant: variant, 144 }, 145 }) 146 } 147 generateManifest := func(arc, os, variant string, config ocispec.Descriptor, layers ...ocispec.Descriptor) { 148 manifest := ocispec.Manifest{ 149 Config: config, 150 Layers: layers, 151 } 152 manifestJSON, err := json.Marshal(manifest) 153 if err != nil { 154 t.Fatal(err) 155 } 156 appendManifest(arc, os, variant, ocispec.MediaTypeImageManifest, manifestJSON) 157 } 158 generateIndex := func(manifests ...ocispec.Descriptor) { 159 index := ocispec.Index{ 160 Manifests: manifests, 161 } 162 indexJSON, err := json.Marshal(index) 163 if err != nil { 164 t.Fatal(err) 165 } 166 appendBlob(ocispec.MediaTypeImageIndex, indexJSON) 167 } 168 169 appendBlob(ocispec.MediaTypeImageConfig, []byte(`{"mediaType":"application/vnd.oci.image.config.v1+json", 170 "created":"2022-07-29T08:13:55Z", 171 "author":"test author", 172 "architecture":"test-arc-1", 173 "os":"test-os-1", 174 "variant":"v1"}`)) // Blob 0 175 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo")) // Blob 1 176 appendBlob(ocispec.MediaTypeImageLayer, []byte("bar")) // Blob 2 177 generateManifest(arc_1, os_1, variant_1, descs[0], descs[1:3]...) // Blob 3 178 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello1")) // Blob 4 179 generateManifest(arc_2, os_2, variant_1, descs[0], descs[4]) // Blob 5 180 appendBlob(ocispec.MediaTypeImageLayer, []byte("hello2")) // Blob 6 181 generateManifest(arc_1, os_1, variant_2, descs[0], descs[6]) // Blob 7 182 generateIndex(descs[3], descs[5], descs[7]) // Blob 8 183 184 ctx := context.Background() 185 for i := range blobs { 186 err := storage.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 187 if err != nil { 188 t.Fatalf("failed to push test content to src: %d: %v", i, err) 189 } 190 } 191 192 // test SelectManifest on image index, only one matching manifest found 193 root := descs[8] 194 targetPlatform := ocispec.Platform{ 195 Architecture: arc_2, 196 OS: os_2, 197 } 198 wantDesc := descs[5] 199 gotDesc, err := SelectManifest(ctx, storage, root, &targetPlatform) 200 if err != nil { 201 t.Fatalf("SelectManifest() error = %v, wantErr %v", err, false) 202 } 203 if !reflect.DeepEqual(gotDesc, wantDesc) { 204 t.Errorf("SelectManifest() = %v, want %v", gotDesc, wantDesc) 205 } 206 207 // test SelectManifest on image index, 208 // and multiple manifests match the required platform. 209 // Should return the first matching entry. 210 targetPlatform = ocispec.Platform{ 211 Architecture: arc_1, 212 OS: os_1, 213 } 214 wantDesc = descs[3] 215 gotDesc, err = SelectManifest(ctx, storage, root, &targetPlatform) 216 if err != nil { 217 t.Fatalf("SelectManifest() error = %v, wantErr %v", err, false) 218 } 219 if !reflect.DeepEqual(gotDesc, wantDesc) { 220 t.Errorf("SelectManifest() = %v, want %v", gotDesc, wantDesc) 221 } 222 223 // test SelectManifest on manifest 224 root = descs[7] 225 targetPlatform = ocispec.Platform{ 226 Architecture: arc_1, 227 OS: os_1, 228 } 229 wantDesc = descs[7] 230 gotDesc, err = SelectManifest(ctx, storage, root, &targetPlatform) 231 if err != nil { 232 t.Fatalf("SelectManifest() error = %v, wantErr %v", err, false) 233 } 234 if !reflect.DeepEqual(gotDesc, wantDesc) { 235 t.Errorf("SelectManifest() = %v, want %v", gotDesc, wantDesc) 236 } 237 238 // test SelectManifest on manifest, but there is no matching node. 239 // Should return not found error. 240 root = descs[7] 241 targetPlatform = ocispec.Platform{ 242 Architecture: arc_1, 243 OS: os_1, 244 Variant: variant_2, 245 } 246 _, err = SelectManifest(ctx, storage, root, &targetPlatform) 247 expected := fmt.Sprintf("%s: %v: platform in manifest does not match target platform", root.Digest, errdef.ErrNotFound) 248 if err.Error() != expected { 249 t.Fatalf("SelectManifest() error = %v, wantErr %v", err, expected) 250 } 251 252 // test SelectManifest on manifest, but the node's media type is not 253 // supported. Should return unsupported error 254 targetPlatform = ocispec.Platform{ 255 Architecture: arc_1, 256 OS: os_1, 257 } 258 root = descs[1] 259 _, err = SelectManifest(ctx, storage, root, &targetPlatform) 260 if !errors.Is(err, errdef.ErrUnsupported) { 261 t.Fatalf("SelectManifest() error = %v, wantErr %v", err, errdef.ErrUnsupported) 262 } 263 264 // generate incorrect test content 265 blobs = nil 266 descs = nil 267 appendBlob(docker.MediaTypeConfig, []byte(`{"mediaType":"application/vnd.oci.image.config.v1+json", 268 "created":"2022-07-29T08:13:55Z", 269 "author":"test author 1", 270 "architecture":"test-arc-1", 271 "os":"test-os-1", 272 "variant":"v1"}`)) // Blob 0 273 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo1")) // Blob 1 274 generateManifest(arc_1, os_1, variant_1, descs[0], descs[1]) // Blob 2 275 generateIndex(descs[2]) // Blob 3 276 277 ctx = context.Background() 278 for i := range blobs { 279 err := storage.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 280 if err != nil { 281 t.Fatalf("failed to push test content to src: %d: %v", i, err) 282 } 283 } 284 285 // test SelectManifest on manifest, but the manifest is 286 // invalid by having docker mediaType config in the manifest and oci 287 // mediaType in the image config. Should return error. 288 root = descs[2] 289 targetPlatform = ocispec.Platform{ 290 Architecture: arc_1, 291 OS: os_1, 292 } 293 _, err = SelectManifest(ctx, storage, root, &targetPlatform) 294 expected = fmt.Sprintf("fail to recognize platform from unknown config %s: expect %s", docker.MediaTypeConfig, ocispec.MediaTypeImageConfig) 295 if err.Error() != expected { 296 t.Fatalf("SelectManifest() error = %v, wantErr %v", err, expected) 297 } 298 299 // generate test content with null config blob 300 blobs = nil 301 descs = nil 302 appendBlob(ocispec.MediaTypeImageConfig, []byte("null")) // Blob 0 303 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo2")) // Blob 1 304 generateManifest(arc_1, os_1, variant_1, descs[0], descs[1]) // Blob 2 305 generateIndex(descs[2]) // Blob 3 306 307 ctx = context.Background() 308 for i := range blobs { 309 err := storage.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 310 if err != nil { 311 t.Fatalf("failed to push test content to src: %d: %v", i, err) 312 } 313 } 314 315 // test SelectManifest on manifest with null config blob, 316 // should return not found error. 317 root = descs[2] 318 targetPlatform = ocispec.Platform{ 319 Architecture: arc_1, 320 OS: os_1, 321 } 322 _, err = SelectManifest(ctx, storage, root, &targetPlatform) 323 expected = fmt.Sprintf("%s: %v: platform in manifest does not match target platform", root.Digest, errdef.ErrNotFound) 324 if err.Error() != expected { 325 t.Fatalf("SelectManifest() error = %v, wantErr %v", err, expected) 326 } 327 328 // generate test content with empty config blob 329 blobs = nil 330 descs = nil 331 appendBlob(ocispec.MediaTypeImageConfig, []byte("")) // Blob 0 332 appendBlob(ocispec.MediaTypeImageLayer, []byte("foo3")) // Blob 1 333 generateManifest(arc_1, os_1, variant_1, descs[0], descs[1]) // Blob 2 334 generateIndex(descs[2]) // Blob 3 335 336 ctx = context.Background() 337 for i := range blobs { 338 err := storage.Push(ctx, descs[i], bytes.NewReader(blobs[i])) 339 if err != nil { 340 t.Fatalf("failed to push test content to src: %d: %v", i, err) 341 } 342 } 343 344 // test SelectManifest on manifest with empty config blob 345 // should return not found error 346 root = descs[2] 347 targetPlatform = ocispec.Platform{ 348 Architecture: arc_1, 349 OS: os_1, 350 } 351 _, err = SelectManifest(ctx, storage, root, &targetPlatform) 352 expected = fmt.Sprintf("%s: %v: platform in manifest does not match target platform", root.Digest, errdef.ErrNotFound) 353 if err.Error() != expected { 354 t.Fatalf("SelectManifest() error = %v, wantErr %v", err, expected) 355 } 356 }