github.com/anuvu/zot@v1.3.4/pkg/compliance/v1_0_0/check.go (about) 1 // nolint: dupl 2 package v1_0_0 // nolint:stylecheck,golint 3 4 import ( 5 "bytes" 6 "encoding/json" 7 "fmt" 8 "io" 9 "os" 10 "path" 11 "strings" 12 "testing" 13 14 "github.com/anuvu/zot/pkg/api" 15 "github.com/anuvu/zot/pkg/compliance" 16 . "github.com/anuvu/zot/test" // nolint:golint,stylecheck 17 godigest "github.com/opencontainers/go-digest" 18 ispec "github.com/opencontainers/image-spec/specs-go/v1" 19 . "github.com/smartystreets/goconvey/convey" // nolint:golint,stylecheck 20 "github.com/smartystreets/goconvey/convey/reporting" 21 "gopkg.in/resty.v1" 22 ) 23 24 func CheckWorkflows(t *testing.T, config *compliance.Config) { 25 if config == nil || config.Address == "" || config.Port == "" { 26 panic("insufficient config") 27 } 28 29 if config.OutputJSON { 30 outputJSONEnter() 31 32 defer outputJSONExit() 33 } 34 35 baseURL := fmt.Sprintf("http://%s:%s", config.Address, config.Port) 36 37 storageInfo := config.StorageInfo 38 39 fmt.Println("------------------------------") 40 fmt.Println("Checking for v1.0.0 compliance") 41 fmt.Println("------------------------------") 42 43 Convey("Make API calls to the controller", t, func(c C) { 44 Convey("Check version", func() { 45 Print("\nCheck version") 46 resp, err := resty.R().Get(baseURL + "/v2/") 47 So(err, ShouldBeNil) 48 So(resp.StatusCode(), ShouldEqual, 200) 49 }) 50 51 Convey("Get repository catalog", func() { 52 Print("\nGet repository catalog") 53 resp, err := resty.R().Get(baseURL + "/v2/_catalog") 54 So(err, ShouldBeNil) 55 So(resp.StatusCode(), ShouldEqual, 200) 56 So(resp.String(), ShouldNotBeEmpty) 57 So(resp.Header().Get("Content-Type"), ShouldEqual, api.DefaultMediaType) 58 var repoList api.RepositoryList 59 err = json.Unmarshal(resp.Body(), &repoList) 60 So(err, ShouldBeNil) 61 So(len(repoList.Repositories), ShouldEqual, 0) 62 63 // after newly created upload should succeed 64 resp, err = resty.R().Post(baseURL + "/v2/z/blobs/uploads/") 65 So(err, ShouldBeNil) 66 So(resp.StatusCode(), ShouldEqual, 202) 67 68 // after newly created upload should succeed 69 resp, err = resty.R().Post(baseURL + "/v2/a/b/c/d/blobs/uploads/") 70 So(err, ShouldBeNil) 71 So(resp.StatusCode(), ShouldEqual, 202) 72 73 resp, err = resty.R().SetResult(&api.RepositoryList{}).Get(baseURL + "/v2/_catalog") 74 So(err, ShouldBeNil) 75 So(resp.StatusCode(), ShouldEqual, 200) 76 So(resp.String(), ShouldNotBeEmpty) 77 r := resp.Result().(*api.RepositoryList) 78 if !config.Compliance { 79 // stricter check for zot ci/cd 80 So(len(r.Repositories), ShouldBeGreaterThan, 0) 81 So(r.Repositories[0], ShouldEqual, "a/b/c/d") 82 So(r.Repositories[1], ShouldEqual, "z") 83 } 84 }) 85 86 Convey("Get images in a repository", func() { 87 Print("\nGet images in a repository") 88 // non-existent repository should fail 89 resp, err := resty.R().Get(baseURL + "/v2/repo1/tags/list") 90 So(err, ShouldBeNil) 91 So(resp.StatusCode(), ShouldEqual, 404) 92 So(resp.String(), ShouldNotBeEmpty) 93 94 // after newly created upload should succeed 95 resp, err = resty.R().Post(baseURL + "/v2/repo1/blobs/uploads/") 96 So(err, ShouldBeNil) 97 So(resp.StatusCode(), ShouldEqual, 202) 98 99 resp, err = resty.R().Get(baseURL + "/v2/repo1/tags/list") 100 So(err, ShouldBeNil) 101 if !config.Compliance { 102 // stricter check for zot ci/cd 103 So(resp.StatusCode(), ShouldEqual, 200) 104 So(resp.String(), ShouldNotBeEmpty) 105 } 106 }) 107 108 Convey("Monolithic blob upload", func() { 109 Print("\nMonolithic blob upload") 110 resp, err := resty.R().Post(baseURL + "/v2/repo2/blobs/uploads/") 111 So(err, ShouldBeNil) 112 So(resp.StatusCode(), ShouldEqual, 202) 113 loc := Location(baseURL, resp) 114 So(loc, ShouldNotBeEmpty) 115 116 resp, err = resty.R().Get(loc) 117 So(err, ShouldBeNil) 118 So(resp.StatusCode(), ShouldEqual, 204) 119 120 resp, err = resty.R().Get(baseURL + "/v2/repo2/tags/list") 121 So(err, ShouldBeNil) 122 if !config.Compliance { 123 // stricter check for zot ci/cd 124 So(resp.StatusCode(), ShouldEqual, 200) 125 So(resp.String(), ShouldNotBeEmpty) 126 } 127 128 // without a "?digest=<>" should fail 129 content := []byte("this is a blob1") 130 digest := godigest.FromBytes(content) 131 So(digest, ShouldNotBeNil) 132 resp, err = resty.R().Put(loc) 133 So(err, ShouldBeNil) 134 So(resp.StatusCode(), ShouldEqual, 400) 135 // without the Content-Length should fail 136 resp, err = resty.R().SetQueryParam("digest", digest.String()).Put(loc) 137 So(err, ShouldBeNil) 138 So(resp.StatusCode(), ShouldEqual, 400) 139 // without any data to send, should fail 140 resp, err = resty.R().SetQueryParam("digest", digest.String()). 141 SetHeader("Content-Type", "application/octet-stream").Put(loc) 142 So(err, ShouldBeNil) 143 So(resp.StatusCode(), ShouldEqual, 400) 144 // monolithic blob upload: success 145 resp, err = resty.R().SetQueryParam("digest", digest.String()). 146 SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(loc) 147 So(err, ShouldBeNil) 148 So(resp.StatusCode(), ShouldEqual, 201) 149 blobLoc := Location(baseURL, resp) 150 So(blobLoc, ShouldNotBeEmpty) 151 So(resp.Header().Get("Content-Length"), ShouldEqual, "0") 152 So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) 153 // upload reference should now be removed 154 resp, err = resty.R().Get(loc) 155 So(err, ShouldBeNil) 156 So(resp.StatusCode(), ShouldEqual, 404) 157 // blob reference should be accessible 158 resp, err = resty.R().Get(blobLoc) 159 So(err, ShouldBeNil) 160 So(resp.StatusCode(), ShouldEqual, 200) 161 }) 162 163 Convey("Monolithic blob upload with body", func() { 164 Print("\nMonolithic blob upload") 165 // create content 166 content := []byte("this is a blob2") 167 digest := godigest.FromBytes(content) 168 So(digest, ShouldNotBeNil) 169 // setting invalid URL params should fail 170 resp, err := resty.R(). 171 SetQueryParam("digest", digest.String()). 172 SetQueryParam("from", digest.String()). 173 SetHeader("Content-Type", "application/octet-stream"). 174 SetBody(content). 175 Post(baseURL + "/v2/repo2/blobs/uploads/") 176 So(err, ShouldBeNil) 177 So(resp.StatusCode(), ShouldEqual, 405) 178 // setting a "?digest=<>" but without body should fail 179 resp, err = resty.R(). 180 SetQueryParam("digest", digest.String()). 181 SetHeader("Content-Type", "application/octet-stream"). 182 Post(baseURL + "/v2/repo2/blobs/uploads/") 183 So(err, ShouldBeNil) 184 So(resp.StatusCode(), ShouldEqual, 400) 185 // set a "?digest=<>" 186 resp, err = resty.R(). 187 SetQueryParam("digest", digest.String()). 188 SetHeader("Content-Type", "application/octet-stream"). 189 SetBody(content). 190 Post(baseURL + "/v2/repo2/blobs/uploads/") 191 So(err, ShouldBeNil) 192 So(resp.StatusCode(), ShouldEqual, 201) 193 loc := Location(baseURL, resp) 194 So(loc, ShouldNotBeEmpty) 195 // blob reference should be accessible 196 resp, err = resty.R().Get(loc) 197 So(err, ShouldBeNil) 198 So(resp.StatusCode(), ShouldEqual, 200) 199 }) 200 201 Convey("Monolithic blob upload with multiple name components", func() { 202 Print("\nMonolithic blob upload with multiple name components") 203 resp, err := resty.R().Post(baseURL + "/v2/repo10/repo20/repo30/blobs/uploads/") 204 So(err, ShouldBeNil) 205 So(resp.StatusCode(), ShouldEqual, 202) 206 loc := Location(baseURL, resp) 207 So(loc, ShouldNotBeEmpty) 208 209 resp, err = resty.R().Get(loc) 210 So(err, ShouldBeNil) 211 So(resp.StatusCode(), ShouldEqual, 204) 212 213 resp, err = resty.R().Get(baseURL + "/v2/repo10/repo20/repo30/tags/list") 214 So(err, ShouldBeNil) 215 if !config.Compliance { 216 // stricter check for zot ci/cd 217 So(resp.StatusCode(), ShouldEqual, 200) 218 So(resp.String(), ShouldNotBeEmpty) 219 } 220 221 // without a "?digest=<>" should fail 222 content := []byte("this is a blob3") 223 digest := godigest.FromBytes(content) 224 So(digest, ShouldNotBeNil) 225 resp, err = resty.R().Put(loc) 226 So(err, ShouldBeNil) 227 So(resp.StatusCode(), ShouldEqual, 400) 228 // without the Content-Length should fail 229 resp, err = resty.R().SetQueryParam("digest", digest.String()).Put(loc) 230 So(err, ShouldBeNil) 231 So(resp.StatusCode(), ShouldEqual, 400) 232 // without any data to send, should fail 233 resp, err = resty.R().SetQueryParam("digest", digest.String()). 234 SetHeader("Content-Type", "application/octet-stream").Put(loc) 235 So(err, ShouldBeNil) 236 So(resp.StatusCode(), ShouldEqual, 400) 237 // monolithic blob upload: success 238 resp, err = resty.R().SetQueryParam("digest", digest.String()). 239 SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(loc) 240 So(err, ShouldBeNil) 241 So(resp.StatusCode(), ShouldEqual, 201) 242 blobLoc := Location(baseURL, resp) 243 So(blobLoc, ShouldNotBeEmpty) 244 So(resp.Header().Get("Content-Length"), ShouldEqual, "0") 245 So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) 246 // upload reference should now be removed 247 resp, err = resty.R().Get(loc) 248 So(err, ShouldBeNil) 249 So(resp.StatusCode(), ShouldEqual, 404) 250 // blob reference should be accessible 251 resp, err = resty.R().Get(blobLoc) 252 So(err, ShouldBeNil) 253 So(resp.StatusCode(), ShouldEqual, 200) 254 }) 255 256 Convey("Chunked blob upload", func() { 257 Print("\nChunked blob upload") 258 resp, err := resty.R().Post(baseURL + "/v2/repo3/blobs/uploads/") 259 So(err, ShouldBeNil) 260 So(resp.StatusCode(), ShouldEqual, 202) 261 loc := Location(baseURL, resp) 262 So(loc, ShouldNotBeEmpty) 263 264 var buf bytes.Buffer 265 chunk1 := []byte("this is the first chunk1") 266 n, err := buf.Write(chunk1) 267 So(n, ShouldEqual, len(chunk1)) 268 So(err, ShouldBeNil) 269 270 // write first chunk 271 contentRange := fmt.Sprintf("%d-%d", 0, len(chunk1)-1) 272 resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream"). 273 SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(loc) 274 So(err, ShouldBeNil) 275 So(resp.StatusCode(), ShouldEqual, 202) 276 277 // check progress 278 resp, err = resty.R().Get(loc) 279 So(err, ShouldBeNil) 280 So(resp.StatusCode(), ShouldEqual, 204) 281 r := resp.Header().Get("Range") 282 So(r, ShouldNotBeEmpty) 283 So(r, ShouldEqual, "bytes="+contentRange) 284 285 // write same chunk should fail 286 contentRange = fmt.Sprintf("%d-%d", 0, len(chunk1)-1) 287 resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream"). 288 SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(loc) 289 So(err, ShouldBeNil) 290 So(resp.StatusCode(), ShouldEqual, 416) 291 So(resp.String(), ShouldNotBeEmpty) 292 293 chunk2 := []byte("this is the second chunk1") 294 n, err = buf.Write(chunk2) 295 So(n, ShouldEqual, len(chunk2)) 296 So(err, ShouldBeNil) 297 298 digest := godigest.FromBytes(buf.Bytes()) 299 So(digest, ShouldNotBeNil) 300 301 // write final chunk 302 contentRange = fmt.Sprintf("%d-%d", len(chunk1), len(buf.Bytes())-1) 303 resp, err = resty.R().SetQueryParam("digest", digest.String()). 304 SetHeader("Content-Range", contentRange). 305 SetHeader("Content-Type", "application/octet-stream").SetBody(chunk2).Put(loc) 306 So(err, ShouldBeNil) 307 So(resp.StatusCode(), ShouldEqual, 201) 308 blobLoc := Location(baseURL, resp) 309 So(err, ShouldBeNil) 310 So(resp.StatusCode(), ShouldEqual, 201) 311 So(blobLoc, ShouldNotBeEmpty) 312 So(resp.Header().Get("Content-Length"), ShouldEqual, "0") 313 So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) 314 // upload reference should now be removed 315 resp, err = resty.R().Get(loc) 316 So(err, ShouldBeNil) 317 So(resp.StatusCode(), ShouldEqual, 404) 318 // blob reference should be accessible 319 resp, err = resty.R().Get(blobLoc) 320 So(err, ShouldBeNil) 321 So(resp.StatusCode(), ShouldEqual, 200) 322 }) 323 324 Convey("Chunked blob upload with multiple name components", func() { 325 Print("\nChunked blob upload with multiple name components") 326 resp, err := resty.R().Post(baseURL + "/v2/repo40/repo50/repo60/blobs/uploads/") 327 So(err, ShouldBeNil) 328 So(resp.StatusCode(), ShouldEqual, 202) 329 loc := Location(baseURL, resp) 330 So(loc, ShouldNotBeEmpty) 331 332 var buf bytes.Buffer 333 chunk1 := []byte("this is the first chunk2") 334 n, err := buf.Write(chunk1) 335 So(n, ShouldEqual, len(chunk1)) 336 So(err, ShouldBeNil) 337 338 // write first chunk 339 contentRange := fmt.Sprintf("%d-%d", 0, len(chunk1)-1) 340 resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream"). 341 SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(loc) 342 So(err, ShouldBeNil) 343 So(resp.StatusCode(), ShouldEqual, 202) 344 345 // check progress 346 resp, err = resty.R().Get(loc) 347 So(err, ShouldBeNil) 348 So(resp.StatusCode(), ShouldEqual, 204) 349 r := resp.Header().Get("Range") 350 So(r, ShouldNotBeEmpty) 351 So(r, ShouldEqual, "bytes="+contentRange) 352 353 // write same chunk should fail 354 contentRange = fmt.Sprintf("%d-%d", 0, len(chunk1)-1) 355 resp, err = resty.R().SetHeader("Content-Type", "application/octet-stream"). 356 SetHeader("Content-Range", contentRange).SetBody(chunk1).Patch(loc) 357 So(err, ShouldBeNil) 358 So(resp.StatusCode(), ShouldEqual, 416) 359 So(resp.String(), ShouldNotBeEmpty) 360 361 chunk2 := []byte("this is the second chunk2") 362 n, err = buf.Write(chunk2) 363 So(n, ShouldEqual, len(chunk2)) 364 So(err, ShouldBeNil) 365 366 digest := godigest.FromBytes(buf.Bytes()) 367 So(digest, ShouldNotBeNil) 368 369 // write final chunk 370 contentRange = fmt.Sprintf("%d-%d", len(chunk1), len(buf.Bytes())-1) 371 resp, err = resty.R().SetQueryParam("digest", digest.String()). 372 SetHeader("Content-Range", contentRange). 373 SetHeader("Content-Type", "application/octet-stream").SetBody(chunk2).Put(loc) 374 So(err, ShouldBeNil) 375 So(resp.StatusCode(), ShouldEqual, 201) 376 blobLoc := Location(baseURL, resp) 377 So(err, ShouldBeNil) 378 So(resp.StatusCode(), ShouldEqual, 201) 379 So(blobLoc, ShouldNotBeEmpty) 380 So(resp.Header().Get("Content-Length"), ShouldEqual, "0") 381 So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) 382 // upload reference should now be removed 383 resp, err = resty.R().Get(loc) 384 So(err, ShouldBeNil) 385 So(resp.StatusCode(), ShouldEqual, 404) 386 // blob reference should be accessible 387 resp, err = resty.R().Get(blobLoc) 388 So(err, ShouldBeNil) 389 So(resp.StatusCode(), ShouldEqual, 200) 390 }) 391 392 Convey("Create and delete uploads", func() { 393 Print("\nCreate and delete uploads") 394 // create a upload 395 resp, err := resty.R().Post(baseURL + "/v2/repo4/blobs/uploads/") 396 So(err, ShouldBeNil) 397 So(resp.StatusCode(), ShouldEqual, 202) 398 loc := Location(baseURL, resp) 399 So(loc, ShouldNotBeEmpty) 400 401 // delete this upload 402 resp, err = resty.R().Delete(loc) 403 So(err, ShouldBeNil) 404 So(resp.StatusCode(), ShouldEqual, 204) 405 }) 406 407 Convey("Create and delete blobs", func() { 408 Print("\nCreate and delete blobs") 409 // create a upload 410 resp, err := resty.R().Post(baseURL + "/v2/repo5/blobs/uploads/") 411 So(err, ShouldBeNil) 412 So(resp.StatusCode(), ShouldEqual, 202) 413 loc := Location(baseURL, resp) 414 So(loc, ShouldNotBeEmpty) 415 416 content := []byte("this is a blob4") 417 digest := godigest.FromBytes(content) 418 So(digest, ShouldNotBeNil) 419 // monolithic blob upload 420 resp, err = resty.R().SetQueryParam("digest", digest.String()). 421 SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(loc) 422 So(err, ShouldBeNil) 423 So(resp.StatusCode(), ShouldEqual, 201) 424 blobLoc := Location(baseURL, resp) 425 So(blobLoc, ShouldNotBeEmpty) 426 So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) 427 428 // delete this blob 429 resp, err = resty.R().Delete(blobLoc) 430 So(err, ShouldBeNil) 431 So(resp.StatusCode(), ShouldEqual, 202) 432 So(resp.Header().Get("Content-Length"), ShouldEqual, "0") 433 }) 434 435 Convey("Mount blobs", func() { 436 Print("\nMount blobs from another repository") 437 // create a upload 438 resp, err := resty.R().Post(baseURL + "/v2/repo6/blobs/uploads/?digest=\"abc\"&&from=\"xyz\"") 439 So(err, ShouldBeNil) 440 So(resp.StatusCode(), ShouldBeIn, []int{201, 202, 405}) 441 }) 442 443 Convey("Manifests", func() { 444 Print("\nManifests") 445 // create a blob/layer 446 resp, err := resty.R().Post(baseURL + "/v2/repo7/blobs/uploads/") 447 So(err, ShouldBeNil) 448 So(resp.StatusCode(), ShouldEqual, 202) 449 loc := Location(baseURL, resp) 450 So(loc, ShouldNotBeEmpty) 451 452 // since we are not specifying any prefix i.e provided in config while starting server, 453 // so it should store repo7 to global root dir 454 _, err = os.Stat(path.Join(storageInfo[0], "repo7")) 455 So(err, ShouldBeNil) 456 457 resp, err = resty.R().Get(loc) 458 So(err, ShouldBeNil) 459 So(resp.StatusCode(), ShouldEqual, 204) 460 content := []byte("this is a blob5") 461 digest := godigest.FromBytes(content) 462 So(digest, ShouldNotBeNil) 463 // monolithic blob upload: success 464 resp, err = resty.R().SetQueryParam("digest", digest.String()). 465 SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(loc) 466 So(err, ShouldBeNil) 467 So(resp.StatusCode(), ShouldEqual, 201) 468 blobLoc := resp.Header().Get("Location") 469 So(blobLoc, ShouldNotBeEmpty) 470 So(resp.Header().Get("Content-Length"), ShouldEqual, "0") 471 So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) 472 473 // check a non-existent manifest 474 resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). 475 SetBody(content).Head(baseURL + "/v2/unknown/manifests/test:1.0") 476 So(err, ShouldBeNil) 477 So(resp.StatusCode(), ShouldEqual, 404) 478 479 // create a manifest 480 m := ispec.Manifest{ 481 Config: ispec.Descriptor{ 482 Digest: digest, 483 Size: int64(len(content)), 484 }, 485 Layers: []ispec.Descriptor{ 486 { 487 MediaType: "application/vnd.oci.image.layer.v1.tar", 488 Digest: digest, 489 Size: int64(len(content)), 490 }, 491 }, 492 } 493 m.SchemaVersion = 2 494 content, err = json.Marshal(m) 495 So(err, ShouldBeNil) 496 digest = godigest.FromBytes(content) 497 So(digest, ShouldNotBeNil) 498 resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). 499 SetBody(content).Put(baseURL + "/v2/repo7/manifests/test:1.0") 500 So(err, ShouldBeNil) 501 So(resp.StatusCode(), ShouldEqual, 201) 502 d := resp.Header().Get(api.DistContentDigestKey) 503 So(d, ShouldNotBeEmpty) 504 So(d, ShouldEqual, digest.String()) 505 506 resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). 507 SetBody(content).Put(baseURL + "/v2/repo7/manifests/test:1.0.1") 508 So(err, ShouldBeNil) 509 So(resp.StatusCode(), ShouldEqual, 201) 510 d = resp.Header().Get(api.DistContentDigestKey) 511 So(d, ShouldNotBeEmpty) 512 So(d, ShouldEqual, digest.String()) 513 514 content = []byte("this is a blob5") 515 digest = godigest.FromBytes(content) 516 So(digest, ShouldNotBeNil) 517 // create a manifest with same blob but a different tag 518 m = ispec.Manifest{ 519 Config: ispec.Descriptor{ 520 Digest: digest, 521 Size: int64(len(content)), 522 }, 523 Layers: []ispec.Descriptor{ 524 { 525 MediaType: "application/vnd.oci.image.layer.v1.tar", 526 Digest: digest, 527 Size: int64(len(content)), 528 }, 529 }, 530 } 531 m.SchemaVersion = 2 532 content, err = json.Marshal(m) 533 So(err, ShouldBeNil) 534 digest = godigest.FromBytes(content) 535 So(digest, ShouldNotBeNil) 536 resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). 537 SetBody(content).Put(baseURL + "/v2/repo7/manifests/test:2.0") 538 So(err, ShouldBeNil) 539 So(resp.StatusCode(), ShouldEqual, 201) 540 d = resp.Header().Get(api.DistContentDigestKey) 541 So(d, ShouldNotBeEmpty) 542 So(d, ShouldEqual, digest.String()) 543 544 // check/get by tag 545 resp, err = resty.R().Head(baseURL + "/v2/repo7/manifests/test:1.0") 546 So(err, ShouldBeNil) 547 So(resp.StatusCode(), ShouldEqual, 200) 548 So(resp.Header().Get("Content-Type"), ShouldNotBeEmpty) 549 resp, err = resty.R().Get(baseURL + "/v2/repo7/manifests/test:1.0") 550 So(err, ShouldBeNil) 551 So(resp.StatusCode(), ShouldEqual, 200) 552 So(resp.Body(), ShouldNotBeEmpty) 553 // check/get by reference 554 resp, err = resty.R().Head(baseURL + "/v2/repo7/manifests/" + digest.String()) 555 So(err, ShouldBeNil) 556 So(resp.StatusCode(), ShouldEqual, 200) 557 So(resp.Header().Get("Content-Type"), ShouldNotBeEmpty) 558 resp, err = resty.R().Get(baseURL + "/v2/repo7/manifests/" + digest.String()) 559 So(err, ShouldBeNil) 560 So(resp.StatusCode(), ShouldEqual, 200) 561 So(resp.Body(), ShouldNotBeEmpty) 562 563 // delete manifest by tag should pass 564 resp, err = resty.R().Delete(baseURL + "/v2/repo7/manifests/test:1.0") 565 So(err, ShouldBeNil) 566 So(resp.StatusCode(), ShouldEqual, 202) 567 // delete manifest by digest (1.0 deleted but 1.0.1 has same reference) 568 resp, err = resty.R().Delete(baseURL + "/v2/repo7/manifests/" + digest.String()) 569 So(err, ShouldBeNil) 570 So(resp.StatusCode(), ShouldEqual, 202) 571 // delete manifest by digest 572 resp, err = resty.R().Delete(baseURL + "/v2/repo7/manifests/" + digest.String()) 573 So(err, ShouldBeNil) 574 So(resp.StatusCode(), ShouldEqual, 404) 575 // delete again should fail 576 resp, err = resty.R().Delete(baseURL + "/v2/repo7/manifests/" + digest.String()) 577 So(err, ShouldBeNil) 578 So(resp.StatusCode(), ShouldEqual, 404) 579 580 // check/get by tag 581 resp, err = resty.R().Head(baseURL + "/v2/repo7/manifests/test:1.0") 582 So(err, ShouldBeNil) 583 So(resp.StatusCode(), ShouldEqual, 404) 584 resp, err = resty.R().Get(baseURL + "/v2/repo7/manifests/test:1.0") 585 So(err, ShouldBeNil) 586 So(resp.StatusCode(), ShouldEqual, 404) 587 So(resp.Body(), ShouldNotBeEmpty) 588 resp, err = resty.R().Head(baseURL + "/v2/repo7/manifests/test:2.0") 589 So(err, ShouldBeNil) 590 So(resp.StatusCode(), ShouldEqual, 404) 591 resp, err = resty.R().Get(baseURL + "/v2/repo7/manifests/test:2.0") 592 So(err, ShouldBeNil) 593 So(resp.StatusCode(), ShouldEqual, 404) 594 So(resp.Body(), ShouldNotBeEmpty) 595 // check/get by reference 596 resp, err = resty.R().Head(baseURL + "/v2/repo7/manifests/" + digest.String()) 597 So(err, ShouldBeNil) 598 So(resp.StatusCode(), ShouldEqual, 404) 599 resp, err = resty.R().Get(baseURL + "/v2/repo7/manifests/" + digest.String()) 600 So(err, ShouldBeNil) 601 So(resp.StatusCode(), ShouldEqual, 404) 602 So(resp.Body(), ShouldNotBeEmpty) 603 }) 604 605 // pagination 606 Convey("Pagination", func() { 607 Print("\nPagination") 608 609 for i := 0; i <= 4; i++ { 610 // create a blob/layer 611 resp, err := resty.R().Post(baseURL + "/v2/page0/blobs/uploads/") 612 So(err, ShouldBeNil) 613 So(resp.StatusCode(), ShouldEqual, 202) 614 loc := Location(baseURL, resp) 615 So(loc, ShouldNotBeEmpty) 616 617 resp, err = resty.R().Get(loc) 618 So(err, ShouldBeNil) 619 So(resp.StatusCode(), ShouldEqual, 204) 620 content := []byte("this is a blob7") 621 digest := godigest.FromBytes(content) 622 So(digest, ShouldNotBeNil) 623 // monolithic blob upload: success 624 resp, err = resty.R().SetQueryParam("digest", digest.String()). 625 SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(loc) 626 So(err, ShouldBeNil) 627 So(resp.StatusCode(), ShouldEqual, 201) 628 blobLoc := resp.Header().Get("Location") 629 So(blobLoc, ShouldNotBeEmpty) 630 So(resp.Header().Get("Content-Length"), ShouldEqual, "0") 631 So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) 632 633 // create a manifest 634 m := ispec.Manifest{ 635 Config: ispec.Descriptor{ 636 Digest: digest, 637 Size: int64(len(content)), 638 }, 639 Layers: []ispec.Descriptor{ 640 { 641 MediaType: "application/vnd.oci.image.layer.v1.tar", 642 Digest: digest, 643 Size: int64(len(content)), 644 }, 645 }, 646 } 647 m.SchemaVersion = 2 648 content, err = json.Marshal(m) 649 So(err, ShouldBeNil) 650 digest = godigest.FromBytes(content) 651 So(digest, ShouldNotBeNil) 652 resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). 653 SetBody(content).Put(baseURL + fmt.Sprintf("/v2/page0/manifests/test:%d.0", i)) 654 So(err, ShouldBeNil) 655 So(resp.StatusCode(), ShouldEqual, 201) 656 d := resp.Header().Get(api.DistContentDigestKey) 657 So(d, ShouldNotBeEmpty) 658 So(d, ShouldEqual, digest.String()) 659 } 660 661 resp, err := resty.R().Get(baseURL + "/v2/page0/tags/list") 662 So(err, ShouldBeNil) 663 So(resp.StatusCode(), ShouldEqual, 200) 664 665 resp, err = resty.R().Get(baseURL + "/v2/page0/tags/list?n=0") 666 So(err, ShouldBeNil) 667 So(resp.StatusCode(), ShouldEqual, 200) 668 669 resp, err = resty.R().Get(baseURL + "/v2/page0/tags/list?n=3") 670 So(err, ShouldBeNil) 671 So(resp.StatusCode(), ShouldEqual, 200) 672 next := resp.Header().Get("Link") 673 So(next, ShouldNotBeEmpty) 674 675 u := baseURL + strings.Split(next, ";")[0] 676 resp, err = resty.R().Get(u) 677 So(err, ShouldBeNil) 678 So(resp.StatusCode(), ShouldEqual, 200) 679 next = resp.Header().Get("Link") 680 So(next, ShouldBeEmpty) 681 }) 682 683 // this is an additional test for repository names (alphanumeric) 684 Convey("Repository names", func() { 685 Print("\nRepository names") 686 // create a blob/layer 687 resp, err := resty.R().Post(baseURL + "/v2/repotest/blobs/uploads/") 688 So(err, ShouldBeNil) 689 So(resp.StatusCode(), ShouldEqual, 202) 690 resp, err = resty.R().Post(baseURL + "/v2/repotest123/blobs/uploads/") 691 So(err, ShouldBeNil) 692 So(resp.StatusCode(), ShouldEqual, 202) 693 }) 694 695 Convey("Multiple Storage", func() { 696 // test APIS on subpath routes, default storage already tested above 697 // subpath route firsttest 698 resp, err := resty.R().Post(baseURL + "/v2/firsttest/first/blobs/uploads/") 699 So(err, ShouldBeNil) 700 So(resp.StatusCode(), ShouldEqual, 202) 701 firstloc := Location(baseURL, resp) 702 So(firstloc, ShouldNotBeEmpty) 703 704 resp, err = resty.R().Get(firstloc) 705 So(err, ShouldBeNil) 706 So(resp.StatusCode(), ShouldEqual, 204) 707 708 // if firsttest route is used as prefix in url that means repo should be stored in subpaths["firsttest"] rootdir 709 _, err = os.Stat(path.Join(storageInfo[1], "firsttest/first")) 710 So(err, ShouldBeNil) 711 712 // subpath route secondtest 713 resp, err = resty.R().Post(baseURL + "/v2/secondtest/second/blobs/uploads/") 714 So(err, ShouldBeNil) 715 So(resp.StatusCode(), ShouldEqual, 202) 716 secondloc := Location(baseURL, resp) 717 So(secondloc, ShouldNotBeEmpty) 718 719 resp, err = resty.R().Get(secondloc) 720 So(err, ShouldBeNil) 721 So(resp.StatusCode(), ShouldEqual, 204) 722 723 // if secondtest route is used as prefix in url that means repo should be stored in subpaths["secondtest"] rootdir 724 _, err = os.Stat(path.Join(storageInfo[2], "secondtest/second")) 725 So(err, ShouldBeNil) 726 727 content := []byte("this is a blob5") 728 digest := godigest.FromBytes(content) 729 So(digest, ShouldNotBeNil) 730 // monolithic blob upload: success 731 // first test 732 resp, err = resty.R().SetQueryParam("digest", digest.String()). 733 SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(firstloc) 734 So(err, ShouldBeNil) 735 So(resp.StatusCode(), ShouldEqual, 201) 736 firstblobLoc := resp.Header().Get("Location") 737 So(firstblobLoc, ShouldNotBeEmpty) 738 So(resp.Header().Get("Content-Length"), ShouldEqual, "0") 739 So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) 740 741 // second test 742 resp, err = resty.R().SetQueryParam("digest", digest.String()). 743 SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(secondloc) 744 So(err, ShouldBeNil) 745 So(resp.StatusCode(), ShouldEqual, 201) 746 secondblobLoc := resp.Header().Get("Location") 747 So(secondblobLoc, ShouldNotBeEmpty) 748 So(resp.Header().Get("Content-Length"), ShouldEqual, "0") 749 So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty) 750 751 // check a non-existent manifest 752 resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). 753 SetBody(content).Head(baseURL + "/v2/unknown/manifests/test:1.0") 754 So(err, ShouldBeNil) 755 So(resp.StatusCode(), ShouldEqual, 404) 756 757 resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). 758 SetBody(content).Head(baseURL + "/v2/firsttest/unknown/manifests/test:1.0") 759 So(err, ShouldBeNil) 760 So(resp.StatusCode(), ShouldEqual, 404) 761 762 resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). 763 SetBody(content).Head(baseURL + "/v2/secondtest/unknown/manifests/test:1.0") 764 So(err, ShouldBeNil) 765 So(resp.StatusCode(), ShouldEqual, 404) 766 767 // create a manifest 768 m := ispec.Manifest{ 769 Config: ispec.Descriptor{ 770 Digest: digest, 771 Size: int64(len(content)), 772 }, 773 Layers: []ispec.Descriptor{ 774 { 775 MediaType: "application/vnd.oci.image.layer.v1.tar", 776 Digest: digest, 777 Size: int64(len(content)), 778 }, 779 }, 780 } 781 m.SchemaVersion = 2 782 content, err = json.Marshal(m) 783 So(err, ShouldBeNil) 784 digest = godigest.FromBytes(content) 785 So(digest, ShouldNotBeNil) 786 // subpath firsttest 787 resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). 788 SetBody(content).Put(baseURL + "/v2/firsttest/first/manifests/test:1.0") 789 So(err, ShouldBeNil) 790 So(resp.StatusCode(), ShouldEqual, 201) 791 d := resp.Header().Get(api.DistContentDigestKey) 792 So(d, ShouldNotBeEmpty) 793 So(d, ShouldEqual, digest.String()) 794 795 // subpath secondtest 796 resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). 797 SetBody(content).Put(baseURL + "/v2/secondtest/second/manifests/test:1.0") 798 So(err, ShouldBeNil) 799 So(resp.StatusCode(), ShouldEqual, 201) 800 d = resp.Header().Get(api.DistContentDigestKey) 801 So(d, ShouldNotBeEmpty) 802 So(d, ShouldEqual, digest.String()) 803 804 content = []byte("this is a blob5") 805 digest = godigest.FromBytes(content) 806 So(digest, ShouldNotBeNil) 807 // create a manifest with same blob but a different tag 808 m = ispec.Manifest{ 809 Config: ispec.Descriptor{ 810 Digest: digest, 811 Size: int64(len(content)), 812 }, 813 Layers: []ispec.Descriptor{ 814 { 815 MediaType: "application/vnd.oci.image.layer.v1.tar", 816 Digest: digest, 817 Size: int64(len(content)), 818 }, 819 }, 820 } 821 m.SchemaVersion = 2 822 content, err = json.Marshal(m) 823 So(err, ShouldBeNil) 824 digest = godigest.FromBytes(content) 825 So(digest, ShouldNotBeNil) 826 827 // subpath firsttest 828 resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). 829 SetBody(content).Put(baseURL + "/v2/firsttest/first/manifests/test:2.0") 830 So(err, ShouldBeNil) 831 So(resp.StatusCode(), ShouldEqual, 201) 832 d = resp.Header().Get(api.DistContentDigestKey) 833 So(d, ShouldNotBeEmpty) 834 So(d, ShouldEqual, digest.String()) 835 836 // subpath secondtest 837 resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json"). 838 SetBody(content).Put(baseURL + "/v2/secondtest/second/manifests/test:2.0") 839 So(err, ShouldBeNil) 840 So(resp.StatusCode(), ShouldEqual, 201) 841 d = resp.Header().Get(api.DistContentDigestKey) 842 So(d, ShouldNotBeEmpty) 843 So(d, ShouldEqual, digest.String()) 844 845 // check/get by tag 846 resp, err = resty.R().Head(baseURL + "/v2/firsttest/first/manifests/test:1.0") 847 So(err, ShouldBeNil) 848 So(resp.StatusCode(), ShouldEqual, 200) 849 resp, err = resty.R().Get(baseURL + "/v2/firsttest/first/manifests/test:1.0") 850 So(err, ShouldBeNil) 851 So(resp.StatusCode(), ShouldEqual, 200) 852 So(resp.Body(), ShouldNotBeEmpty) 853 resp, err = resty.R().Head(baseURL + "/v2/secondtest/second/manifests/test:1.0") 854 So(err, ShouldBeNil) 855 So(resp.StatusCode(), ShouldEqual, 200) 856 resp, err = resty.R().Get(baseURL + "/v2/secondtest/second/manifests/test:1.0") 857 So(err, ShouldBeNil) 858 So(resp.StatusCode(), ShouldEqual, 200) 859 So(resp.Body(), ShouldNotBeEmpty) 860 861 // check/get by reference 862 resp, err = resty.R().Head(baseURL + "/v2/firsttest/first/manifests/" + digest.String()) 863 So(err, ShouldBeNil) 864 So(resp.StatusCode(), ShouldEqual, 200) 865 resp, err = resty.R().Get(baseURL + "/v2/firsttest/first/manifests/" + digest.String()) 866 So(err, ShouldBeNil) 867 So(resp.StatusCode(), ShouldEqual, 200) 868 So(resp.Body(), ShouldNotBeEmpty) 869 870 resp, err = resty.R().Head(baseURL + "/v2/secondtest/second/manifests/" + digest.String()) 871 So(err, ShouldBeNil) 872 So(resp.StatusCode(), ShouldEqual, 200) 873 resp, err = resty.R().Get(baseURL + "/v2/secondtest/second/manifests/" + digest.String()) 874 So(err, ShouldBeNil) 875 So(resp.StatusCode(), ShouldEqual, 200) 876 So(resp.Body(), ShouldNotBeEmpty) 877 878 // delete manifest by digest 879 resp, err = resty.R().Delete(baseURL + "/v2/firsttest/first/manifests/" + digest.String()) 880 So(err, ShouldBeNil) 881 So(resp.StatusCode(), ShouldEqual, 202) 882 883 resp, err = resty.R().Delete(baseURL + "/v2/secondtest/second/manifests/" + digest.String()) 884 So(err, ShouldBeNil) 885 So(resp.StatusCode(), ShouldEqual, 202) 886 887 // delete manifest by digest 888 resp, err = resty.R().Delete(baseURL + "/v2/firsttest/first/manifests/" + digest.String()) 889 So(err, ShouldBeNil) 890 So(resp.StatusCode(), ShouldEqual, 404) 891 892 resp, err = resty.R().Delete(baseURL + "/v2/secondtest/second/manifests/" + digest.String()) 893 So(err, ShouldBeNil) 894 So(resp.StatusCode(), ShouldEqual, 404) 895 896 // delete again should fail 897 resp, err = resty.R().Delete(baseURL + "/v2/firsttest/first/manifests/" + digest.String()) 898 So(err, ShouldBeNil) 899 So(resp.StatusCode(), ShouldEqual, 404) 900 901 resp, err = resty.R().Delete(baseURL + "/v2/secondtest/second/manifests/" + digest.String()) 902 So(err, ShouldBeNil) 903 So(resp.StatusCode(), ShouldEqual, 404) 904 905 // check/get by tag 906 resp, err = resty.R().Head(baseURL + "/v2/firsttest/first/manifests/test:1.0") 907 So(err, ShouldBeNil) 908 So(resp.StatusCode(), ShouldEqual, 404) 909 resp, err = resty.R().Get(baseURL + "/v2/firsttest/first/manifests/test:1.0") 910 So(err, ShouldBeNil) 911 So(resp.StatusCode(), ShouldEqual, 404) 912 So(resp.Body(), ShouldNotBeEmpty) 913 914 resp, err = resty.R().Head(baseURL + "/v2/secondtest/second/manifests/test:1.0") 915 So(err, ShouldBeNil) 916 So(resp.StatusCode(), ShouldEqual, 404) 917 resp, err = resty.R().Get(baseURL + "/v2/secondtest/second/manifests/test:1.0") 918 So(err, ShouldBeNil) 919 So(resp.StatusCode(), ShouldEqual, 404) 920 So(resp.Body(), ShouldNotBeEmpty) 921 922 resp, err = resty.R().Head(baseURL + "/v2/firsttest/first/repo7/manifests/test:2.0") 923 So(err, ShouldBeNil) 924 So(resp.StatusCode(), ShouldEqual, 404) 925 resp, err = resty.R().Get(baseURL + "/v2/firsttest/first/manifests/test:2.0") 926 So(err, ShouldBeNil) 927 So(resp.StatusCode(), ShouldEqual, 404) 928 So(resp.Body(), ShouldNotBeEmpty) 929 930 resp, err = resty.R().Head(baseURL + "/v2/secondtest/second/manifests/test:2.0") 931 So(err, ShouldBeNil) 932 So(resp.StatusCode(), ShouldEqual, 404) 933 resp, err = resty.R().Get(baseURL + "/v2/secondtest/second/manifests/test:2.0") 934 So(err, ShouldBeNil) 935 So(resp.StatusCode(), ShouldEqual, 404) 936 So(resp.Body(), ShouldNotBeEmpty) 937 938 // check/get by reference 939 resp, err = resty.R().Head(baseURL + "/v2/firsttest/first/manifests/" + digest.String()) 940 So(err, ShouldBeNil) 941 So(resp.StatusCode(), ShouldEqual, 404) 942 resp, err = resty.R().Get(baseURL + "/v2/firsttest/first/manifests/" + digest.String()) 943 So(err, ShouldBeNil) 944 So(resp.StatusCode(), ShouldEqual, 404) 945 So(resp.Body(), ShouldNotBeEmpty) 946 947 resp, err = resty.R().Head(baseURL + "/v2/secondtest/second/manifests/" + digest.String()) 948 So(err, ShouldBeNil) 949 So(resp.StatusCode(), ShouldEqual, 404) 950 resp, err = resty.R().Get(baseURL + "/v2/secondtest/second/manifests/" + digest.String()) 951 So(err, ShouldBeNil) 952 So(resp.StatusCode(), ShouldEqual, 404) 953 So(resp.Body(), ShouldNotBeEmpty) 954 }) 955 }) 956 } 957 958 // nolint: gochecknoglobals 959 var ( 960 old *os.File 961 r *os.File 962 w *os.File 963 outC chan string 964 ) 965 966 func outputJSONEnter() { 967 // this env var instructs goconvey to output results to JSON (stdout) 968 os.Setenv("GOCONVEY_REPORTER", "json") 969 970 // stdout capture copied from: https://stackoverflow.com/a/29339052 971 old = os.Stdout 972 // keep backup of the real stdout 973 r, w, _ = os.Pipe() 974 outC = make(chan string) 975 os.Stdout = w 976 977 // copy the output in a separate goroutine so printing can't block indefinitely 978 go func() { 979 var buf bytes.Buffer 980 981 _, err := io.Copy(&buf, r) 982 if err != nil { 983 panic(err) 984 } 985 986 outC <- buf.String() 987 }() 988 } 989 990 func outputJSONExit() { 991 // back to normal state 992 w.Close() 993 994 os.Stdout = old // restoring the real stdout 995 996 out := <-outC 997 998 // The output of JSON is combined with regular output, so we look for the 999 // first occurrence of the "{" character and take everything after that 1000 rawJSON := "[{" + strings.Join(strings.Split(out, "{")[1:], "{") 1001 rawJSON = strings.Replace(rawJSON, reporting.OpenJson, "", 1) 1002 rawJSON = strings.Replace(rawJSON, reporting.CloseJson, "", 1) 1003 tmp := strings.Split(rawJSON, ",") 1004 rawJSON = strings.Join(tmp[0:len(tmp)-1], ",") + "]" 1005 1006 rawJSONMinified := validateMinifyRawJSON(rawJSON) 1007 fmt.Println(rawJSONMinified) 1008 } 1009 1010 func validateMinifyRawJSON(rawJSON string) string { 1011 var j interface{} 1012 1013 err := json.Unmarshal([]byte(rawJSON), &j) 1014 if err != nil { 1015 panic(err) 1016 } 1017 1018 rawJSONBytesMinified, err := json.Marshal(j) 1019 if err != nil { 1020 panic(err) 1021 } 1022 1023 return string(rawJSONBytesMinified) 1024 }