zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/cli/server/extensions_test.go (about) 1 //go:build sync && scrub && metrics && search && userprefs && mgmt && imagetrust 2 // +build sync,scrub,metrics,search,userprefs,mgmt,imagetrust 3 4 package server_test 5 6 import ( 7 "fmt" 8 "net/http" 9 "os" 10 "testing" 11 "time" 12 13 . "github.com/smartystreets/goconvey/convey" 14 "gopkg.in/resty.v1" 15 16 "zotregistry.dev/zot/pkg/api/config" 17 cli "zotregistry.dev/zot/pkg/cli/server" 18 . "zotregistry.dev/zot/pkg/test/common" 19 ) 20 21 const readLogFileTimeout = 5 * time.Second 22 23 func TestVerifyExtensionsConfig(t *testing.T) { 24 oldArgs := os.Args 25 26 defer func() { os.Args = oldArgs }() 27 28 Convey("Test verify CVE warn for remote storage", t, func(c C) { 29 tmpfile, err := os.CreateTemp("", "zot-test*.json") 30 So(err, ShouldBeNil) 31 defer os.Remove(tmpfile.Name()) // clean up 32 33 content := fmt.Sprintf(`{ 34 "storage":{ 35 "rootDirectory":"%s", 36 "dedupe":true, 37 "remoteCache":false, 38 "storageDriver":{ 39 "name":"s3", 40 "rootdirectory":"/zot", 41 "region":"us-east-2", 42 "bucket":"zot-storage", 43 "secure":true, 44 "skipverify":false 45 } 46 }, 47 "http":{ 48 "address":"127.0.0.1", 49 "port":"8080" 50 }, 51 "extensions":{ 52 "search": { 53 "enable": true, 54 "cve": { 55 "updateInterval": "24h" 56 } 57 } 58 } 59 }`, t.TempDir()) 60 61 err = os.WriteFile(tmpfile.Name(), []byte(content), 0o0600) 62 So(err, ShouldBeNil) 63 64 os.Args = []string{"cli_test", "verify", tmpfile.Name()} 65 So(cli.NewServerRootCmd().Execute(), ShouldNotBeNil) 66 67 content = fmt.Sprintf(`{ 68 "storage":{ 69 "rootDirectory":"%s", 70 "dedupe":true, 71 "remoteCache":false, 72 "subPaths":{ 73 "/a": { 74 "rootDirectory": "%s", 75 "dedupe": false, 76 "storageDriver":{ 77 "name":"s3", 78 "rootdirectory":"/zot-a", 79 "region":"us-east-2", 80 "bucket":"zot-storage", 81 "secure":true, 82 "skipverify":false 83 } 84 } 85 } 86 }, 87 "http":{ 88 "address":"127.0.0.1", 89 "port":"8080" 90 }, 91 "extensions":{ 92 "search": { 93 "enable": true, 94 "cve": { 95 "updateInterval": "24h" 96 } 97 } 98 } 99 }`, t.TempDir(), t.TempDir()) 100 err = os.WriteFile(tmpfile.Name(), []byte(content), 0o0600) 101 So(err, ShouldBeNil) 102 103 os.Args = []string{"cli_test", "verify", tmpfile.Name()} 104 So(cli.NewServerRootCmd().Execute(), ShouldNotBeNil) 105 }) 106 107 Convey("Test verify w/ sync and w/o filesystem storage", t, func(c C) { 108 tmpfile, err := os.CreateTemp("", "zot-test*.json") 109 So(err, ShouldBeNil) 110 defer os.Remove(tmpfile.Name()) // clean up 111 content := fmt.Sprintf(`{"storage":{"rootDirectory":"%s", "storageDriver": {"name": "s3"}}, 112 "http":{"address":"127.0.0.1","port":"8080","realm":"zot", 113 "auth":{"htpasswd":{"path":"test/data/htpasswd"},"failDelay":1}}, 114 "extensions":{"sync": {"registries": [{"urls":["localhost:9999"], 115 "maxRetries": 1, "retryDelay": "10s"}]}}}`, t.TempDir()) 116 _, err = tmpfile.WriteString(content) 117 So(err, ShouldBeNil) 118 err = tmpfile.Close() 119 So(err, ShouldBeNil) 120 os.Args = []string{"cli_test", "verify", tmpfile.Name()} 121 So(cli.NewServerRootCmd().Execute(), ShouldNotBeNil) 122 }) 123 124 Convey("Test verify w/ sync and w/ filesystem storage", t, func(c C) { 125 tmpfile, err := os.CreateTemp("", "zot-test*.json") 126 So(err, ShouldBeNil) 127 defer os.Remove(tmpfile.Name()) // clean up 128 content := fmt.Sprintf(`{"storage":{"rootDirectory":"%s"}, 129 "http":{"address":"127.0.0.1","port":"8080","realm":"zot", 130 "auth":{"htpasswd":{"path":"test/data/htpasswd"},"failDelay":1}}, 131 "extensions":{"sync": {"registries": [{"urls":["localhost:9999"], 132 "maxRetries": 1, "retryDelay": "10s"}]}}}`, t.TempDir()) 133 _, err = tmpfile.WriteString(content) 134 So(err, ShouldBeNil) 135 err = tmpfile.Close() 136 So(err, ShouldBeNil) 137 os.Args = []string{"cli_test", "verify", tmpfile.Name()} 138 So(cli.NewServerRootCmd().Execute(), ShouldBeNil) 139 }) 140 141 Convey("Test verify with bad sync prefixes", t, func(c C) { 142 tmpfile, err := os.CreateTemp("", "zot-test*.json") 143 So(err, ShouldBeNil) 144 defer os.Remove(tmpfile.Name()) // clean up 145 content := fmt.Sprintf(`{"storage":{"rootDirectory":"%s"}, 146 "http":{"address":"127.0.0.1","port":"8080","realm":"zot", 147 "auth":{"htpasswd":{"path":"test/data/htpasswd"},"failDelay":1}}, 148 "extensions":{"sync": {"registries": [{"urls":["localhost:9999"], 149 "maxRetries": 1, "retryDelay": "10s", 150 "content": [{"prefix":"[repo^&["}]}]}}}`, t.TempDir()) 151 _, err = tmpfile.WriteString(content) 152 So(err, ShouldBeNil) 153 err = tmpfile.Close() 154 So(err, ShouldBeNil) 155 os.Args = []string{"cli_test", "verify", tmpfile.Name()} 156 So(cli.NewServerRootCmd().Execute(), ShouldNotBeNil) 157 }) 158 159 Convey("Test verify with bad sync content config", t, func(c C) { 160 tmpfile, err := os.CreateTemp("", "zot-test*.json") 161 So(err, ShouldBeNil) 162 defer os.Remove(tmpfile.Name()) // clean up 163 content := fmt.Sprintf(`{"storage":{"rootDirectory":"%s"}, 164 "http":{"address":"127.0.0.1","port":"8080","realm":"zot", 165 "auth":{"htpasswd":{"path":"test/data/htpasswd"},"failDelay":1}}, 166 "extensions":{"sync": {"registries": [{"urls":["localhost:9999"], 167 "maxRetries": 1, "retryDelay": "10s", 168 "content": [{"prefix":"zot-repo","stripPrefix":true,"destination":"/"}]}]}}}`, t.TempDir()) 169 _, err = tmpfile.WriteString(content) 170 So(err, ShouldBeNil) 171 err = tmpfile.Close() 172 So(err, ShouldBeNil) 173 os.Args = []string{"cli_test", "verify", tmpfile.Name()} 174 So(cli.NewServerRootCmd().Execute(), ShouldNotBeNil) 175 }) 176 177 Convey("Test verify with good sync content config", t, func(c C) { 178 tmpfile, err := os.CreateTemp("", "zot-test*.json") 179 So(err, ShouldBeNil) 180 defer os.Remove(tmpfile.Name()) // clean up 181 content := fmt.Sprintf(`{"storage":{"rootDirectory":"%s"}, 182 "http":{"address":"127.0.0.1","port":"8080","realm":"zot", 183 "auth":{"htpasswd":{"path":"test/data/htpasswd"},"failDelay":1}}, 184 "extensions":{"sync": {"registries": [{"urls":["localhost:9999"], 185 "maxRetries": 1, "retryDelay": "10s", 186 "content": [{"prefix":"zot-repo/*","stripPrefix":true,"destination":"/"}]}]}}}`, t.TempDir()) 187 _, err = tmpfile.WriteString(content) 188 So(err, ShouldBeNil) 189 err = tmpfile.Close() 190 So(err, ShouldBeNil) 191 os.Args = []string{"cli_test", "verify", tmpfile.Name()} 192 err = cli.NewServerRootCmd().Execute() 193 So(err, ShouldBeNil) 194 }) 195 196 Convey("Test verify sync config default tls value", t, func(c C) { 197 tmpfile, err := os.CreateTemp("", "zot-test*.json") 198 So(err, ShouldBeNil) 199 defer os.Remove(tmpfile.Name()) // clean up 200 content := fmt.Sprintf(`{"storage":{"rootDirectory":"%s"}, 201 "http":{"address":"127.0.0.1","port":"8080","realm":"zot", 202 "auth":{"htpasswd":{"path":"test/data/htpasswd"},"failDelay":1}}, 203 "extensions":{"sync": {"registries": [{"urls":["localhost:9999"], 204 "maxRetries": 1, "retryDelay": "10s", 205 "content": [{"prefix":"repo**"}]}]}}}`, t.TempDir()) 206 _, err = tmpfile.WriteString(content) 207 So(err, ShouldBeNil) 208 err = tmpfile.Close() 209 So(err, ShouldBeNil) 210 os.Args = []string{"cli_test", "verify", tmpfile.Name()} 211 err = cli.NewServerRootCmd().Execute() 212 So(err, ShouldBeNil) 213 }) 214 215 Convey("Test verify sync without retry options", t, func(c C) { 216 tmpfile, err := os.CreateTemp("", "zot-test*.json") 217 So(err, ShouldBeNil) 218 defer os.Remove(tmpfile.Name()) // clean up 219 content := fmt.Sprintf(`{"storage":{"rootDirectory":"%s"}, 220 "http":{"address":"127.0.0.1","port":"8080","realm":"zot", 221 "auth":{"htpasswd":{"path":"test/data/htpasswd"},"failDelay":1}}, 222 "extensions":{"sync": {"registries": [{"urls":["localhost:9999"], 223 "maxRetries": 10, "content": [{"prefix":"repo**"}]}]}}}`, t.TempDir()) 224 _, err = tmpfile.WriteString(content) 225 So(err, ShouldBeNil) 226 err = tmpfile.Close() 227 So(err, ShouldBeNil) 228 os.Args = []string{"cli_test", "verify", tmpfile.Name()} 229 So(cli.NewServerRootCmd().Execute(), ShouldNotBeNil) 230 }) 231 } 232 233 func TestValidateExtensionsConfig(t *testing.T) { 234 Convey("Legacy extensions should not error", t, func(c C) { 235 config := config.New() 236 tmpfile, err := os.CreateTemp("", "zot-test*.json") 237 So(err, ShouldBeNil) 238 defer os.Remove(tmpfile.Name()) 239 content := []byte(`{ 240 "storage": { 241 "rootDirectory": "%/tmp/zot" 242 }, 243 "http": { 244 "address": "127.0.0.1", 245 "port": "8080" 246 }, 247 "log": { 248 "level": "debug" 249 }, 250 "extensions": { 251 "mgmt": { 252 "enable": "true" 253 }, 254 "apikey": { 255 "enable": "true" 256 } 257 } 258 }`) 259 err = os.WriteFile(tmpfile.Name(), content, 0o0600) 260 So(err, ShouldBeNil) 261 err = cli.LoadConfiguration(config, tmpfile.Name()) 262 So(err, ShouldBeNil) 263 }) 264 265 Convey("Test missing extensions for UI to work", t, func(c C) { 266 config := config.New() 267 tmpfile, err := os.CreateTemp("", "zot-test*.json") 268 So(err, ShouldBeNil) 269 defer os.Remove(tmpfile.Name()) 270 content := []byte(`{ 271 "storage": { 272 "rootDirectory": "%/tmp/zot" 273 }, 274 "http": { 275 "address": "127.0.0.1", 276 "port": "8080" 277 }, 278 "log": { 279 "level": "debug" 280 }, 281 "extensions": { 282 "ui": { 283 "enable": "true" 284 } 285 } 286 }`) 287 err = os.WriteFile(tmpfile.Name(), content, 0o0600) 288 So(err, ShouldBeNil) 289 err = cli.LoadConfiguration(config, tmpfile.Name()) 290 So(err, ShouldNotBeNil) 291 }) 292 293 Convey("Test enabling UI extension with all prerequisites", t, func(c C) { 294 config := config.New() 295 tmpfile, err := os.CreateTemp("", "zot-test*.json") 296 So(err, ShouldBeNil) 297 defer os.Remove(tmpfile.Name()) 298 299 content := []byte(`{ 300 "storage": { 301 "rootDirectory": "%/tmp/zot" 302 }, 303 "http": { 304 "address": "127.0.0.1", 305 "port": "8080" 306 }, 307 "log": { 308 "level": "debug" 309 }, 310 "extensions": { 311 "ui": { 312 "enable": "true" 313 }, 314 "search": { 315 "enable": "true" 316 } 317 } 318 }`) 319 err = os.WriteFile(tmpfile.Name(), content, 0o0600) 320 So(err, ShouldBeNil) 321 err = cli.LoadConfiguration(config, tmpfile.Name()) 322 So(err, ShouldBeNil) 323 }) 324 325 Convey("Test extension are implicitly enabled", t, func(c C) { 326 config := config.New() 327 tmpfile, err := os.CreateTemp("", "zot-test*.json") 328 So(err, ShouldBeNil) 329 defer os.Remove(tmpfile.Name()) 330 331 content := []byte(`{ 332 "storage": { 333 "rootDirectory": "%/tmp/zot" 334 }, 335 "http": { 336 "address": "127.0.0.1", 337 "port": "8080" 338 }, 339 "log": { 340 "level": "debug" 341 }, 342 "extensions": { 343 "ui": {}, 344 "search": {}, 345 "metrics": {}, 346 "trust": {}, 347 "scrub": {} 348 } 349 }`) 350 err = os.WriteFile(tmpfile.Name(), content, 0o0600) 351 So(err, ShouldBeNil) 352 err = cli.LoadConfiguration(config, tmpfile.Name()) 353 So(err, ShouldBeNil) 354 So(config.Extensions.UI, ShouldNotBeNil) 355 So(*config.Extensions.UI.Enable, ShouldBeTrue) 356 So(config.Extensions.Search, ShouldNotBeNil) 357 So(*config.Extensions.Search.Enable, ShouldBeTrue) 358 So(config.Extensions.Trust, ShouldNotBeNil) 359 So(*config.Extensions.Trust.Enable, ShouldBeTrue) 360 So(*config.Extensions.Metrics, ShouldNotBeNil) 361 So(*config.Extensions.Metrics.Enable, ShouldBeTrue) 362 So(config.Extensions.Scrub, ShouldNotBeNil) 363 So(*config.Extensions.Scrub.Enable, ShouldBeTrue) 364 }) 365 } 366 367 func TestServeExtensions(t *testing.T) { 368 oldArgs := os.Args 369 370 defer func() { os.Args = oldArgs }() 371 372 Convey("config file with no extensions", t, func(c C) { 373 port := GetFreePort() 374 baseURL := GetBaseURL(port) 375 logFile, err := os.CreateTemp("", "zot-log*.txt") 376 So(err, ShouldBeNil) 377 defer os.Remove(logFile.Name()) // clean up 378 tmpFile := t.TempDir() 379 380 content := fmt.Sprintf(`{ 381 "storage": { 382 "rootDirectory": "%s" 383 }, 384 "http": { 385 "address": "127.0.0.1", 386 "port": "%s" 387 }, 388 "log": { 389 "level": "debug", 390 "output": "%s" 391 } 392 }`, tmpFile, port, logFile.Name()) 393 394 cfgfile, err := os.CreateTemp("", "zot-test*.json") 395 So(err, ShouldBeNil) 396 defer os.Remove(cfgfile.Name()) // clean up 397 _, err = cfgfile.WriteString(content) 398 So(err, ShouldBeNil) 399 err = cfgfile.Close() 400 So(err, ShouldBeNil) 401 402 os.Args = []string{"cli_test", "serve", cfgfile.Name()} 403 go func() { 404 Convey("run", t, func() { 405 err = cli.NewServerRootCmd().Execute() 406 So(err, ShouldBeNil) 407 }) 408 }() 409 410 WaitTillServerReady(baseURL) 411 data, err := os.ReadFile(logFile.Name()) 412 So(err, ShouldBeNil) 413 So(string(data), ShouldContainSubstring, "\"Extensions\":null") 414 }) 415 416 Convey("config file with empty extensions", t, func(c C) { 417 port := GetFreePort() 418 baseURL := GetBaseURL(port) 419 logFile, err := os.CreateTemp("", "zot-log*.txt") 420 So(err, ShouldBeNil) 421 defer os.Remove(logFile.Name()) // clean up 422 tmpFile := t.TempDir() 423 424 content := fmt.Sprintf(`{ 425 "storage": { 426 "rootDirectory": "%s" 427 }, 428 "http": { 429 "address": "127.0.0.1", 430 "port": "%s" 431 }, 432 "log": { 433 "level": "debug", 434 "output": "%s" 435 }, 436 "extensions": { 437 } 438 }`, tmpFile, port, logFile.Name()) 439 440 cfgfile, err := os.CreateTemp("", "zot-test*.json") 441 So(err, ShouldBeNil) 442 defer os.Remove(cfgfile.Name()) // clean up 443 _, err = cfgfile.WriteString(content) 444 So(err, ShouldBeNil) 445 err = cfgfile.Close() 446 So(err, ShouldBeNil) 447 448 os.Args = []string{"cli_test", "serve", cfgfile.Name()} 449 450 go func() { 451 Convey("run", t, func() { 452 err = cli.NewServerRootCmd().Execute() 453 So(err, ShouldBeNil) 454 }) 455 }() 456 457 WaitTillServerReady(baseURL) 458 data, err := os.ReadFile(logFile.Name()) 459 So(err, ShouldBeNil) 460 So(string(data), ShouldContainSubstring, 461 "\"Extensions\":{\"Search\":null,\"Sync\":null,\"Metrics\":null,\"Scrub\":null,\"Lint\":null,\"UI\":null,\"Mgmt\":null") //nolint:lll // gofumpt conflicts with lll 462 }) 463 } 464 465 func testWithMetricsEnabled(t *testing.T, rootDir string, cfgContentFormat string) { 466 t.Helper() 467 port := GetFreePort() 468 baseURL := GetBaseURL(port) 469 logFile, err := os.CreateTemp("", "zot-log*.txt") 470 So(err, ShouldBeNil) 471 472 defer os.Remove(logFile.Name()) // clean up 473 474 content := fmt.Sprintf(cfgContentFormat, rootDir, port, logFile.Name()) 475 cfgfile, err := os.CreateTemp("", "zot-test*.json") 476 So(err, ShouldBeNil) 477 478 defer os.Remove(cfgfile.Name()) // clean up 479 _, err = cfgfile.WriteString(content) 480 So(err, ShouldBeNil) 481 err = cfgfile.Close() 482 So(err, ShouldBeNil) 483 484 os.Args = []string{"cli_test", "serve", cfgfile.Name()} 485 486 go func() { 487 Convey("run", t, func() { 488 err = cli.NewServerRootCmd().Execute() 489 So(err, ShouldBeNil) 490 }) 491 }() 492 WaitTillServerReady(baseURL) 493 494 resp, err := resty.R().Get(baseURL + "/metrics") 495 So(err, ShouldBeNil) 496 So(resp, ShouldNotBeNil) 497 So(resp.StatusCode(), ShouldEqual, http.StatusOK) 498 499 respStr := string(resp.Body()) 500 So(respStr, ShouldContainSubstring, "zot_info") 501 502 data, err := os.ReadFile(logFile.Name()) 503 So(err, ShouldBeNil) 504 So(string(data), ShouldContainSubstring, 505 "\"Metrics\":{\"Enable\":true,\"Prometheus\":{\"Path\":\"/metrics\"}}") 506 } 507 508 func TestServeMetricsExtension(t *testing.T) { 509 oldArgs := os.Args 510 511 defer func() { os.Args = oldArgs }() 512 513 Convey("no explicit enable", t, func(c C) { 514 tmpFile := t.TempDir() 515 516 content := `{ 517 "storage": { 518 "rootDirectory": "%s" 519 }, 520 "http": { 521 "address": "127.0.0.1", 522 "port": "%s" 523 }, 524 "log": { 525 "level": "debug", 526 "output": "%s" 527 }, 528 "extensions": { 529 "metrics": { 530 } 531 } 532 }` 533 testWithMetricsEnabled(t, tmpFile, content) 534 }) 535 536 Convey("no explicit enable but with prometheus parameter", t, func(c C) { 537 tmpFile := t.TempDir() 538 539 content := `{ 540 "storage": { 541 "rootDirectory": "%s" 542 }, 543 "http": { 544 "address": "127.0.0.1", 545 "port": "%s" 546 }, 547 "log": { 548 "level": "debug", 549 "output": "%s" 550 }, 551 "extensions": { 552 "metrics": { 553 "prometheus": { 554 "path": "/metrics" 555 } 556 } 557 } 558 }` 559 testWithMetricsEnabled(t, tmpFile, content) 560 }) 561 562 Convey("with explicit enable, but without prometheus parameter", t, func(c C) { 563 tmpFile := t.TempDir() 564 565 content := `{ 566 "storage": { 567 "rootDirectory": "%s" 568 }, 569 "http": { 570 "address": "127.0.0.1", 571 "port": "%s" 572 }, 573 "log": { 574 "level": "debug", 575 "output": "%s" 576 }, 577 "extensions": { 578 "metrics": { 579 "enable": true 580 } 581 } 582 }` 583 testWithMetricsEnabled(t, tmpFile, content) 584 }) 585 586 Convey("with explicit disable", t, func(c C) { 587 port := GetFreePort() 588 baseURL := GetBaseURL(port) 589 logFile, err := os.CreateTemp("", "zot-log*.txt") 590 So(err, ShouldBeNil) 591 tmpFile := t.TempDir() 592 defer os.Remove(logFile.Name()) // clean up 593 594 content := fmt.Sprintf(`{ 595 "storage": { 596 "rootDirectory": "%s" 597 }, 598 "http": { 599 "address": "127.0.0.1", 600 "port": "%s" 601 }, 602 "log": { 603 "level": "debug", 604 "output": "%s" 605 }, 606 "extensions": { 607 "metrics": { 608 "enable": false 609 } 610 } 611 }`, tmpFile, port, logFile.Name()) 612 613 cfgfile, err := os.CreateTemp("", "zot-test*.json") 614 So(err, ShouldBeNil) 615 defer os.Remove(cfgfile.Name()) // clean up 616 _, err = cfgfile.WriteString(content) 617 So(err, ShouldBeNil) 618 err = cfgfile.Close() 619 So(err, ShouldBeNil) 620 621 os.Args = []string{"cli_test", "serve", cfgfile.Name()} 622 go func() { 623 Convey("run", t, func() { 624 err = cli.NewServerRootCmd().Execute() 625 So(err, ShouldBeNil) 626 }) 627 }() 628 WaitTillServerReady(baseURL) 629 630 resp, err := resty.R().Get(baseURL + "/metrics") 631 So(err, ShouldBeNil) 632 So(resp, ShouldNotBeNil) 633 So(resp.StatusCode(), ShouldEqual, http.StatusNotFound) 634 635 data, err := os.ReadFile(logFile.Name()) 636 So(err, ShouldBeNil) 637 So(string(data), ShouldContainSubstring, 638 "\"Metrics\":{\"Enable\":false,\"Prometheus\":{\"Path\":\"/metrics\"}}") //nolint:lll // gofumpt conflicts with lll 639 }) 640 } 641 642 func TestServeSyncExtension(t *testing.T) { 643 oldArgs := os.Args 644 645 defer func() { os.Args = oldArgs }() 646 647 Convey("sync implicitly enabled", t, func(c C) { 648 content := `{ 649 "storage": { 650 "rootDirectory": "%s" 651 }, 652 "http": { 653 "address": "127.0.0.1", 654 "port": "%s" 655 }, 656 "log": { 657 "level": "debug", 658 "output": "%s" 659 }, 660 "extensions": { 661 "sync": { 662 "registries": [{ 663 "urls": ["http://localhost:8080"], 664 "tlsVerify": false, 665 "onDemand": true, 666 "maxRetries": 3, 667 "retryDelay": "15m", 668 "certDir": "", 669 "content":[ 670 { 671 "prefix": "zot-test", 672 "tags": { 673 "regex": ".*", 674 "semver": true 675 } 676 } 677 ] 678 }] 679 } 680 } 681 }` 682 683 logPath, err := runCLIWithConfig(t.TempDir(), content) 684 So(err, ShouldBeNil) 685 data, err := os.ReadFile(logPath) 686 So(err, ShouldBeNil) 687 defer os.Remove(logPath) // clean up 688 So(string(data), ShouldContainSubstring, 689 "\"Extensions\":{\"Search\":null,\"Sync\":{\"Enable\":true") 690 }) 691 692 Convey("sync explicitly enabled", t, func(c C) { 693 content := `{ 694 "storage": { 695 "rootDirectory": "%s" 696 }, 697 "http": { 698 "address": "127.0.0.1", 699 "port": "%s" 700 }, 701 "log": { 702 "level": "debug", 703 "output": "%s" 704 }, 705 "extensions": { 706 "sync": { 707 "enable": true, 708 "registries": [{ 709 "urls": ["http://localhost:8080"], 710 "tlsVerify": false, 711 "onDemand": true, 712 "maxRetries": 3, 713 "retryDelay": "15m", 714 "certDir": "", 715 "content":[ 716 { 717 "prefix": "zot-test", 718 "tags": { 719 "regex": ".*", 720 "semver": true 721 } 722 } 723 ] 724 }] 725 } 726 } 727 }` 728 729 logPath, err := runCLIWithConfig(t.TempDir(), content) 730 So(err, ShouldBeNil) 731 data, err := os.ReadFile(logPath) 732 So(err, ShouldBeNil) 733 defer os.Remove(logPath) // clean up 734 So(string(data), ShouldContainSubstring, 735 "\"Extensions\":{\"Search\":null,\"Sync\":{\"Enable\":true") 736 }) 737 738 Convey("sync explicitly disabled", t, func(c C) { 739 content := `{ 740 "storage": { 741 "rootDirectory": "%s" 742 }, 743 "http": { 744 "address": "127.0.0.1", 745 "port": "%s" 746 }, 747 "log": { 748 "level": "debug", 749 "output": "%s" 750 }, 751 "extensions": { 752 "sync": { 753 "enable": false, 754 "registries": [{ 755 "urls": ["http://127.0.0.1:8080"], 756 "tlsVerify": false, 757 "certDir": "", 758 "maxRetries": 3, 759 "retryDelay": "15m" 760 }] 761 } 762 } 763 }` 764 765 logPath, err := runCLIWithConfig(t.TempDir(), content) 766 So(err, ShouldBeNil) 767 data, err := os.ReadFile(logPath) 768 So(err, ShouldBeNil) 769 defer os.Remove(logPath) // clean up 770 So(string(data), ShouldContainSubstring, 771 "\"Extensions\":{\"Search\":null,\"Sync\":{\"Enable\":false") 772 }) 773 } 774 775 func TestServeScrubExtension(t *testing.T) { 776 oldArgs := os.Args 777 778 defer func() { os.Args = oldArgs }() 779 780 Convey("scrub implicitly enabled", t, func(c C) { 781 content := `{ 782 "storage": { 783 "rootDirectory": "%s" 784 }, 785 "http": { 786 "address": "127.0.0.1", 787 "port": "%s" 788 }, 789 "log": { 790 "level": "debug", 791 "output": "%s" 792 }, 793 "extensions": { 794 "scrub": { 795 } 796 } 797 }` 798 799 logPath, err := runCLIWithConfig(t.TempDir(), content) 800 So(err, ShouldBeNil) 801 data, err := os.ReadFile(logPath) 802 So(err, ShouldBeNil) 803 defer os.Remove(logPath) // clean up 804 dataStr := string(data) 805 So(dataStr, ShouldContainSubstring, 806 "\"Extensions\":{\"Search\":null,\"Sync\":null,\"Metrics\":null,\"Scrub\":{\"Enable\":true,\"Interval\":86400000000000},\"Lint\":null") //nolint:lll // gofumpt conflicts with lll 807 So(dataStr, ShouldNotContainSubstring, 808 "scrub interval set to too-short interval < 2h, changing scrub duration to 2 hours and continuing.") 809 }) 810 811 Convey("scrub implicitly enabled, but with scrub interval param set", t, func(c C) { 812 content := `{ 813 "storage": { 814 "rootDirectory": "%s" 815 }, 816 "http": { 817 "address": "127.0.0.1", 818 "port": "%s" 819 }, 820 "log": { 821 "level": "debug", 822 "output": "%s" 823 }, 824 "extensions": { 825 "scrub": { 826 "interval": "1h" 827 } 828 } 829 }` 830 831 logPath, err := runCLIWithConfig(t.TempDir(), content) 832 So(err, ShouldBeNil) 833 data, err := os.ReadFile(logPath) 834 So(err, ShouldBeNil) 835 defer os.Remove(logPath) // clean up 836 // Even if in config we specified scrub interval=1h, the minimum interval is 2h 837 dataStr := string(data) 838 So(dataStr, ShouldContainSubstring, "\"Scrub\":{\"Enable\":true,\"Interval\":3600000000000}") 839 So(dataStr, ShouldContainSubstring, 840 "scrub interval set to too-short interval < 2h, changing scrub duration to 2 hours and continuing.") 841 }) 842 843 Convey("scrub explicitly enabled, but without scrub interval param set", t, func(c C) { 844 content := `{ 845 "storage": { 846 "rootDirectory": "%s" 847 }, 848 "http": { 849 "address": "127.0.0.1", 850 "port": "%s" 851 }, 852 "log": { 853 "level": "debug", 854 "output": "%s" 855 }, 856 "extensions": { 857 "scrub": { 858 "enable": true 859 } 860 } 861 }` 862 863 logPath, err := runCLIWithConfig(t.TempDir(), content) 864 So(err, ShouldBeNil) 865 data, err := os.ReadFile(logPath) 866 So(err, ShouldBeNil) 867 defer os.Remove(logPath) // clean up 868 dataStr := string(data) 869 So(dataStr, ShouldContainSubstring, 870 "\"Extensions\":{\"Search\":null,\"Sync\":null,\"Metrics\":null,\"Scrub\":{\"Enable\":true,\"Interval\":86400000000000},\"Lint\":null") //nolint:lll // gofumpt conflicts with lll 871 So(dataStr, ShouldNotContainSubstring, 872 "scrub interval set to too-short interval < 2h, changing scrub duration to 2 hours and continuing.") 873 }) 874 875 Convey("scrub explicitly disabled", t, func(c C) { 876 content := `{ 877 "storage": { 878 "rootDirectory": "%s" 879 }, 880 "http": { 881 "address": "127.0.0.1", 882 "port": "%s" 883 }, 884 "log": { 885 "level": "debug", 886 "output": "%s" 887 }, 888 "extensions": { 889 "scrub": { 890 "enable": false 891 } 892 } 893 }` 894 895 logPath, err := runCLIWithConfig(t.TempDir(), content) 896 So(err, ShouldBeNil) 897 data, err := os.ReadFile(logPath) 898 So(err, ShouldBeNil) 899 defer os.Remove(logPath) // clean up 900 dataStr := string(data) 901 So(dataStr, ShouldContainSubstring, "\"Scrub\":{\"Enable\":false,\"Interval\":86400000000000}") 902 So(dataStr, ShouldContainSubstring, "scrub config not provided, skipping scrub") 903 So(dataStr, ShouldNotContainSubstring, 904 "scrub interval set to too-short interval < 2h, changing scrub duration to 2 hours and continuing.") 905 }) 906 } 907 908 func TestServeLintExtension(t *testing.T) { 909 oldArgs := os.Args 910 911 defer func() { os.Args = oldArgs }() 912 913 Convey("lint enabled", t, func(c C) { 914 content := `{ 915 "storage": { 916 "rootDirectory": "%s" 917 }, 918 "http": { 919 "address": "127.0.0.1", 920 "port": "%s" 921 }, 922 "log": { 923 "level": "debug", 924 "output": "%s" 925 }, 926 "extensions": { 927 "lint": { 928 "enable": "true", 929 "mandatoryAnnotations": ["annot1"] 930 } 931 } 932 }` 933 934 logPath, err := runCLIWithConfig(t.TempDir(), content) 935 So(err, ShouldBeNil) 936 data, err := os.ReadFile(logPath) 937 So(err, ShouldBeNil) 938 defer os.Remove(logPath) // clean up 939 So(string(data), ShouldContainSubstring, 940 "\"Extensions\":{\"Search\":null,\"Sync\":null,\"Metrics\":null,\"Scrub\":null,\"Lint\":{\"Enable\":true,\"MandatoryAnnotations\":") //nolint:lll // gofumpt conflicts with lll 941 }) 942 943 Convey("lint enabled", t, func(c C) { 944 content := `{ 945 "storage": { 946 "rootDirectory": "%s" 947 }, 948 "http": { 949 "address": "127.0.0.1", 950 "port": "%s" 951 }, 952 "log": { 953 "level": "debug", 954 "output": "%s" 955 }, 956 "extensions": { 957 "lint": { 958 "enable": "false" 959 } 960 } 961 }` 962 963 logPath, err := runCLIWithConfig(t.TempDir(), content) 964 So(err, ShouldBeNil) 965 data, err := os.ReadFile(logPath) 966 So(err, ShouldBeNil) 967 defer os.Remove(logPath) // clean up 968 So(string(data), ShouldContainSubstring, 969 "\"Extensions\":{\"Search\":null,\"Sync\":null,\"Metrics\":null,\"Scrub\":null,\"Lint\":{\"Enable\":false,\"MandatoryAnnotations\":null}") //nolint:lll // gofumpt conflicts with lll 970 }) 971 } 972 973 func TestServeSearchEnabled(t *testing.T) { 974 oldArgs := os.Args 975 976 defer func() { os.Args = oldArgs }() 977 978 Convey("search implicitly enabled", t, func(c C) { 979 content := `{ 980 "storage": { 981 "rootDirectory": "%s" 982 }, 983 "http": { 984 "address": "127.0.0.1", 985 "port": "%s" 986 }, 987 "log": { 988 "level": "debug", 989 "output": "%s" 990 }, 991 "extensions": { 992 "search": { 993 } 994 } 995 }` 996 997 tempDir := t.TempDir() 998 logPath, err := runCLIWithConfig(tempDir, content) 999 So(err, ShouldBeNil) 1000 // to avoid data race when multiple go routines write to trivy DB instance. 1001 defer os.Remove(logPath) // clean up 1002 1003 substring := `"Extensions":{"Search":{"Enable":true,"CVE":null}` 1004 1005 found, err := ReadLogFileAndSearchString(logPath, substring, readLogFileTimeout) 1006 1007 if !found { 1008 data, err := os.ReadFile(logPath) 1009 So(err, ShouldBeNil) 1010 t.Log(string(data)) 1011 } 1012 1013 So(found, ShouldBeTrue) 1014 So(err, ShouldBeNil) 1015 }) 1016 } 1017 1018 func TestServeSearchEnabledCVE(t *testing.T) { 1019 oldArgs := os.Args 1020 1021 defer func() { os.Args = oldArgs }() 1022 1023 Convey("search implicitly enabled with CVE param set", t, func(c C) { 1024 content := `{ 1025 "storage": { 1026 "rootDirectory": "%s" 1027 }, 1028 "http": { 1029 "address": "127.0.0.1", 1030 "port": "%s" 1031 }, 1032 "log": { 1033 "level": "debug", 1034 "output": "%s" 1035 }, 1036 "extensions": { 1037 "search": { 1038 "cve": { 1039 "updateInterval": "1h" 1040 } 1041 } 1042 } 1043 }` 1044 1045 tempDir := t.TempDir() 1046 logPath, err := runCLIWithConfig(tempDir, content) 1047 So(err, ShouldBeNil) 1048 defer os.Remove(logPath) // clean up 1049 // to avoid data race when multiple go routines write to trivy DB instance. 1050 WaitTillTrivyDBDownloadStarted(tempDir) 1051 1052 // The default config handling logic will convert the 1h interval to a 2h interval 1053 substring := "\"Search\":{\"Enable\":true,\"CVE\":{\"UpdateInterval\":7200000000000,\"Trivy\":" + 1054 "{\"DBRepository\":\"ghcr.io/aquasecurity/trivy-db\",\"JavaDBRepository\":\"ghcr.io/aquasecurity/trivy-java-db\"}}}" 1055 1056 found, err := ReadLogFileAndSearchString(logPath, substring, readLogFileTimeout) 1057 1058 defer func() { 1059 if !found { 1060 data, err := os.ReadFile(logPath) 1061 So(err, ShouldBeNil) 1062 t.Log(string(data)) 1063 } 1064 }() 1065 1066 So(found, ShouldBeTrue) 1067 So(err, ShouldBeNil) 1068 1069 found, err = ReadLogFileAndSearchString(logPath, "updating cve-db", readLogFileTimeout) 1070 So(found, ShouldBeTrue) 1071 So(err, ShouldBeNil) 1072 }) 1073 } 1074 1075 func TestServeSearchEnabledNoCVE(t *testing.T) { 1076 oldArgs := os.Args 1077 1078 defer func() { os.Args = oldArgs }() 1079 1080 Convey("search explicitly enabled, but CVE parameter not set", t, func(c C) { 1081 content := `{ 1082 "storage": { 1083 "rootDirectory": "%s" 1084 }, 1085 "http": { 1086 "address": "127.0.0.1", 1087 "port": "%s" 1088 }, 1089 "log": { 1090 "level": "debug", 1091 "output": "%s" 1092 }, 1093 "extensions": { 1094 "search": { 1095 "enable": true 1096 } 1097 } 1098 }` 1099 1100 tempDir := t.TempDir() 1101 logPath, err := runCLIWithConfig(tempDir, content) 1102 So(err, ShouldBeNil) 1103 defer os.Remove(logPath) // clean up 1104 1105 substring := `"Extensions":{"Search":{"Enable":true,"CVE":null}` //nolint:lll // gofumpt conflicts with lll 1106 found, err := ReadLogFileAndSearchString(logPath, substring, readLogFileTimeout) 1107 1108 if !found { 1109 data, err := os.ReadFile(logPath) 1110 So(err, ShouldBeNil) 1111 t.Log(string(data)) 1112 } 1113 1114 So(found, ShouldBeTrue) 1115 So(err, ShouldBeNil) 1116 }) 1117 } 1118 1119 func TestServeSearchDisabled(t *testing.T) { 1120 oldArgs := os.Args 1121 1122 defer func() { os.Args = oldArgs }() 1123 1124 Convey("search explicitly disabled", t, func(c C) { 1125 content := `{ 1126 "storage": { 1127 "rootDirectory": "%s" 1128 }, 1129 "http": { 1130 "address": "127.0.0.1", 1131 "port": "%s" 1132 }, 1133 "log": { 1134 "level": "debug", 1135 "output": "%s" 1136 }, 1137 "extensions": { 1138 "search": { 1139 "enable": false, 1140 "cve": { 1141 "updateInterval": "3h" 1142 } 1143 } 1144 } 1145 }` 1146 1147 logPath, err := runCLIWithConfig(t.TempDir(), content) 1148 So(err, ShouldBeNil) 1149 data, err := os.ReadFile(logPath) 1150 So(err, ShouldBeNil) 1151 defer os.Remove(logPath) // clean up 1152 dataStr := string(data) 1153 So(dataStr, ShouldContainSubstring, 1154 `"Search":{"Enable":false,"CVE":{"UpdateInterval":10800000000000,"Trivy":null}`) 1155 So(dataStr, ShouldContainSubstring, "cve config not provided, skipping cve-db update") 1156 So(dataStr, ShouldNotContainSubstring, 1157 "CVE update interval set to too-short interval < 2h, changing update duration to 2 hours and continuing.") 1158 }) 1159 } 1160 1161 func TestServeMgmtExtension(t *testing.T) { 1162 oldArgs := os.Args 1163 1164 defer func() { os.Args = oldArgs }() 1165 1166 Convey("Mgmt implicitly enabled", t, func(c C) { 1167 content := `{ 1168 "storage": { 1169 "rootDirectory": "%s" 1170 }, 1171 "http": { 1172 "address": "127.0.0.1", 1173 "port": "%s" 1174 }, 1175 "log": { 1176 "level": "debug", 1177 "output": "%s" 1178 }, 1179 "extensions": { 1180 "search": { 1181 "enable": true 1182 } 1183 } 1184 }` 1185 1186 logPath, err := runCLIWithConfig(t.TempDir(), content) 1187 So(err, ShouldBeNil) 1188 defer os.Remove(logPath) // clean up 1189 found, err := ReadLogFileAndSearchString(logPath, "setting up mgmt routes", 10*time.Second) 1190 1191 if !found { 1192 data, err := os.ReadFile(logPath) 1193 So(err, ShouldBeNil) 1194 t.Log(string(data)) 1195 } 1196 1197 So(err, ShouldBeNil) 1198 So(found, ShouldBeTrue) 1199 }) 1200 1201 Convey("Mgmt disabled - Search unconfigured", t, func(c C) { 1202 content := `{ 1203 "storage": { 1204 "rootDirectory": "%s" 1205 }, 1206 "http": { 1207 "address": "127.0.0.1", 1208 "port": "%s" 1209 }, 1210 "log": { 1211 "level": "debug", 1212 "output": "%s" 1213 }, 1214 "extensions": { 1215 } 1216 }` 1217 1218 logPath, err := runCLIWithConfig(t.TempDir(), content) 1219 So(err, ShouldBeNil) 1220 defer os.Remove(logPath) // clean up 1221 found, err := ReadLogFileAndSearchString(logPath, 1222 "skip enabling the mgmt route as the config prerequisites are not met", 10*time.Second) 1223 1224 if !found { 1225 data, err := os.ReadFile(logPath) 1226 So(err, ShouldBeNil) 1227 t.Log(string(data)) 1228 } 1229 1230 So(err, ShouldBeNil) 1231 So(found, ShouldBeTrue) 1232 }) 1233 1234 Convey("Mgmt disabled - extensions missing", t, func(c C) { 1235 content := `{ 1236 "storage": { 1237 "rootDirectory": "%s" 1238 }, 1239 "http": { 1240 "address": "127.0.0.1", 1241 "port": "%s" 1242 }, 1243 "log": { 1244 "level": "debug", 1245 "output": "%s" 1246 } 1247 }` 1248 1249 logPath, err := runCLIWithConfig(t.TempDir(), content) 1250 So(err, ShouldBeNil) 1251 defer os.Remove(logPath) // clean up 1252 found, err := ReadLogFileAndSearchString(logPath, 1253 "skip enabling the mgmt route as the config prerequisites are not met", 10*time.Second) 1254 1255 if !found { 1256 data, err := os.ReadFile(logPath) 1257 So(err, ShouldBeNil) 1258 t.Log(string(data)) 1259 } 1260 1261 So(err, ShouldBeNil) 1262 So(found, ShouldBeTrue) 1263 }) 1264 } 1265 1266 func TestServeImageTrustExtension(t *testing.T) { 1267 oldArgs := os.Args 1268 1269 defer func() { os.Args = oldArgs }() 1270 1271 Convey("Trust explicitly disabled", t, func(c C) { 1272 content := `{ 1273 "storage": { 1274 "rootDirectory": "%s" 1275 }, 1276 "http": { 1277 "address": "127.0.0.1", 1278 "port": "%s" 1279 }, 1280 "log": { 1281 "level": "debug", 1282 "output": "%s" 1283 }, 1284 "extensions": { 1285 "trust": { 1286 "enable": false 1287 } 1288 } 1289 }` 1290 1291 logPath, err := runCLIWithConfig(t.TempDir(), content) 1292 So(err, ShouldBeNil) 1293 defer os.Remove(logPath) // clean up 1294 found, err := ReadLogFileAndSearchString(logPath, 1295 "skip enabling the image trust routes as the config prerequisites are not met", 10*time.Second) 1296 1297 if !found { 1298 data, err := os.ReadFile(logPath) 1299 So(err, ShouldBeNil) 1300 t.Log(string(data)) 1301 } 1302 1303 So(err, ShouldBeNil) 1304 So(found, ShouldBeTrue) 1305 }) 1306 1307 Convey("Trust explicitly enabled - but cosign and notation disabled", t, func(c C) { 1308 content := `{ 1309 "storage": { 1310 "rootDirectory": "%s" 1311 }, 1312 "http": { 1313 "address": "127.0.0.1", 1314 "port": "%s" 1315 }, 1316 "log": { 1317 "level": "debug", 1318 "output": "%s" 1319 }, 1320 "extensions": { 1321 "trust": { 1322 "enable": true 1323 } 1324 } 1325 }` 1326 1327 logPath, err := runCLIWithConfig(t.TempDir(), content) 1328 So(err, ShouldBeNil) 1329 defer os.Remove(logPath) // clean up 1330 found, err := ReadLogFileAndSearchString(logPath, 1331 "skip enabling the image trust routes as the config prerequisites are not met", 10*time.Second) 1332 1333 if !found { 1334 data, err := os.ReadFile(logPath) 1335 So(err, ShouldBeNil) 1336 t.Log(string(data)) 1337 } 1338 1339 So(err, ShouldBeNil) 1340 So(found, ShouldBeTrue) 1341 }) 1342 1343 Convey("Trust explicitly enabled - cosign and notation enabled", t, func(c C) { 1344 content := `{ 1345 "storage": { 1346 "rootDirectory": "%s" 1347 }, 1348 "http": { 1349 "address": "127.0.0.1", 1350 "port": "%s" 1351 }, 1352 "log": { 1353 "level": "debug", 1354 "output": "%s" 1355 }, 1356 "extensions": { 1357 "trust": { 1358 "enable": true, 1359 "cosign": true, 1360 "notation": true 1361 } 1362 } 1363 }` 1364 1365 logPath, err := runCLIWithConfig(t.TempDir(), content) 1366 So(err, ShouldBeNil) 1367 defer os.Remove(logPath) // clean up 1368 found, err := ReadLogFileAndSearchString(logPath, 1369 "setting up image trust routes", 10*time.Second) 1370 1371 defer func() { 1372 if !found { 1373 data, err := os.ReadFile(logPath) 1374 So(err, ShouldBeNil) 1375 t.Log(string(data)) 1376 } 1377 }() 1378 1379 So(err, ShouldBeNil) 1380 So(found, ShouldBeTrue) 1381 1382 found, err = ReadLogFileAndSearchString(logPath, 1383 "setting up notation route", 10*time.Second) 1384 So(err, ShouldBeNil) 1385 So(found, ShouldBeTrue) 1386 1387 found, err = ReadLogFileAndSearchString(logPath, 1388 "setting up cosign route", 10*time.Second) 1389 So(err, ShouldBeNil) 1390 So(found, ShouldBeTrue) 1391 }) 1392 } 1393 1394 func TestOverlappingSyncRetentionConfig(t *testing.T) { 1395 oldArgs := os.Args 1396 1397 defer func() { os.Args = oldArgs }() 1398 1399 Convey("Test verify without overlapping sync and retention", t, func(c C) { 1400 tmpfile, err := os.CreateTemp("", "zot-test*.json") 1401 So(err, ShouldBeNil) 1402 defer os.Remove(tmpfile.Name()) // clean up 1403 content := `{ 1404 "distSpecVersion": "1.1.0", 1405 "storage": { 1406 "rootDirectory": "%s", 1407 "gc": true, 1408 "gcDelay": "2h", 1409 "gcInterval": "1h", 1410 "retention": { 1411 "policies": [ 1412 { 1413 "repositories": ["infra/*", "prod/*"], 1414 "deleteReferrers": false, 1415 "keepTags": [{ 1416 "patterns": ["v4.*", ".*-prod"] 1417 }, 1418 { 1419 "patterns": ["v3.*", ".*-prod"], 1420 "pulledWithin": "168h" 1421 }] 1422 } 1423 ] 1424 } 1425 }, 1426 "http": { 1427 "address": "127.0.0.1", 1428 "port": "%s" 1429 }, 1430 "log": { 1431 "level": "debug", 1432 "output": "%s" 1433 }, 1434 "extensions": { 1435 "sync": { 1436 "enable": true, 1437 "registries": [ 1438 { 1439 "urls": [ 1440 "https://registry1:5000" 1441 ], 1442 "content": [ 1443 { 1444 "prefix": "infra/*", 1445 "tags": { 1446 "regex": "v4.*", 1447 "semver": true 1448 } 1449 } 1450 ] 1451 } 1452 ] 1453 } 1454 } 1455 }` 1456 1457 logPath, err := runCLIWithConfig(t.TempDir(), content) 1458 So(err, ShouldBeNil) 1459 data, err := os.ReadFile(logPath) 1460 So(err, ShouldBeNil) 1461 defer os.Remove(logPath) // clean up 1462 So(string(data), ShouldNotContainSubstring, "overlapping sync content") 1463 }) 1464 1465 Convey("Test verify with overlapping sync and retention - retention would remove v4 tags", t, func(c C) { 1466 tmpfile, err := os.CreateTemp("", "zot-test*.json") 1467 So(err, ShouldBeNil) 1468 defer os.Remove(tmpfile.Name()) // clean up 1469 content := `{ 1470 "distSpecVersion": "1.1.0", 1471 "storage": { 1472 "rootDirectory": "%s", 1473 "gc": true, 1474 "gcDelay": "2h", 1475 "gcInterval": "1h", 1476 "retention": { 1477 "policies": [ 1478 { 1479 "repositories": ["infra/*", "prod/*"], 1480 "keepTags": [{ 1481 "patterns": ["v2.*", ".*-prod"] 1482 }, 1483 { 1484 "patterns": ["v3.*", ".*-prod"] 1485 }] 1486 } 1487 ] 1488 } 1489 }, 1490 "http": { 1491 "address": "127.0.0.1", 1492 "port": "%s" 1493 }, 1494 "log": { 1495 "level": "debug", 1496 "output": "%s" 1497 }, 1498 "extensions": { 1499 "sync": { 1500 "enable": true, 1501 "registries": [ 1502 { 1503 "urls": [ 1504 "https://registry1:5000" 1505 ], 1506 "content": [ 1507 { 1508 "prefix": "infra/*", 1509 "tags": { 1510 "regex": "4.*", 1511 "semver": true 1512 } 1513 } 1514 ] 1515 } 1516 ] 1517 } 1518 } 1519 }` 1520 1521 logPath, err := runCLIWithConfig(t.TempDir(), content) 1522 So(err, ShouldBeNil) 1523 data, err := os.ReadFile(logPath) 1524 So(err, ShouldBeNil) 1525 defer os.Remove(logPath) // clean up 1526 So(string(data), ShouldContainSubstring, "overlapping sync content\":{\"Prefix\":\"infra/*") 1527 }) 1528 1529 Convey("Test verify with overlapping sync and retention - retention would remove tags from repo", t, func(c C) { 1530 tmpfile, err := os.CreateTemp("", "zot-test*.json") 1531 So(err, ShouldBeNil) 1532 defer os.Remove(tmpfile.Name()) // clean up 1533 content := `{ 1534 "distSpecVersion": "1.1.0", 1535 "storage": { 1536 "rootDirectory": "%s", 1537 "gc": true, 1538 "gcDelay": "2h", 1539 "gcInterval": "1h", 1540 "retention": { 1541 "dryRun": false, 1542 "delay": "24h", 1543 "policies": [ 1544 { 1545 "repositories": ["tmp/**"], 1546 "keepTags": [{ 1547 "patterns": ["v1.*"] 1548 }] 1549 } 1550 ] 1551 } 1552 }, 1553 "http": { 1554 "address": "127.0.0.1", 1555 "port": "%s" 1556 }, 1557 "log": { 1558 "level": "debug", 1559 "output": "%s" 1560 }, 1561 "extensions": { 1562 "sync": { 1563 "enable": true, 1564 "registries": [ 1565 { 1566 "urls": [ 1567 "https://registry1:5000" 1568 ], 1569 "content": [ 1570 { 1571 "prefix": "**", 1572 "destination": "/tmp", 1573 "stripPrefix": true 1574 } 1575 ] 1576 } 1577 ] 1578 } 1579 } 1580 } 1581 ` 1582 1583 logPath, err := runCLIWithConfig(t.TempDir(), content) 1584 So(err, ShouldBeNil) 1585 data, err := os.ReadFile(logPath) 1586 So(err, ShouldBeNil) 1587 defer os.Remove(logPath) // clean up 1588 So(string(data), ShouldContainSubstring, "overlapping sync content\":{\"Prefix\":\"**") 1589 }) 1590 1591 Convey("Test verify with overlapping sync and retention - retention would remove tags from subpath", t, func(c C) { 1592 tmpfile, err := os.CreateTemp("", "zot-test*.json") 1593 So(err, ShouldBeNil) 1594 defer os.Remove(tmpfile.Name()) // clean up 1595 content := `{ 1596 "distSpecVersion": "1.1.0", 1597 "storage": { 1598 "rootDirectory": "%s", 1599 "gc": true, 1600 "gcDelay": "2h", 1601 "gcInterval": "1h", 1602 "subPaths": { 1603 "/synced": { 1604 "rootDirectory": "/tmp/zot2", 1605 "dedupe": true, 1606 "retention": { 1607 "policies": [ 1608 { 1609 "repositories": ["infra/*", "prod/*"], 1610 "deleteReferrers": false, 1611 "keepTags": [{ 1612 }] 1613 } 1614 ] 1615 } 1616 } 1617 } 1618 }, 1619 "http": { 1620 "address": "127.0.0.1", 1621 "port": "%s" 1622 }, 1623 "log": { 1624 "level": "debug", 1625 "output": "%s" 1626 }, 1627 "extensions": { 1628 "sync": { 1629 "enable": true, 1630 "registries": [ 1631 { 1632 "urls": [ 1633 "https://registry1:5000" 1634 ], 1635 "content": [ 1636 { 1637 "prefix": "prod/*", 1638 "destination": "/synced" 1639 } 1640 ] 1641 } 1642 ] 1643 } 1644 } 1645 } 1646 ` 1647 1648 logPath, err := runCLIWithConfig(t.TempDir(), content) 1649 So(err, ShouldBeNil) 1650 data, err := os.ReadFile(logPath) 1651 So(err, ShouldBeNil) 1652 defer os.Remove(logPath) // clean up 1653 So(string(data), ShouldContainSubstring, "overlapping sync content\":{\"Prefix\":\"prod/*") 1654 }) 1655 } 1656 1657 func TestSyncWithRemoteStorageConfig(t *testing.T) { 1658 oldArgs := os.Args 1659 1660 defer func() { os.Args = oldArgs }() 1661 1662 Convey("Test verify sync with remote storage works if sync.tmpdir is provided", t, func(c C) { 1663 tmpfile, err := os.CreateTemp("", "zot-test*.json") 1664 So(err, ShouldBeNil) 1665 defer os.Remove(tmpfile.Name()) // clean up 1666 1667 content := `{ 1668 "distSpecVersion": "1.1.0", 1669 "storage": { 1670 "rootDirectory": "%s", 1671 "dedupe": false, 1672 "remoteCache": false, 1673 "storageDriver": { 1674 "name": "s3", 1675 "rootdirectory": "/zot", 1676 "region": "us-east-2", 1677 "regionendpoint": "localhost:4566", 1678 "bucket": "zot-storage", 1679 "secure": false, 1680 "skipverify": false 1681 } 1682 }, 1683 "http": { 1684 "address": "0.0.0.0", 1685 "port": "%s" 1686 }, 1687 "log": { 1688 "level": "debug", 1689 "output": "%s" 1690 }, 1691 "extensions": { 1692 "sync": { 1693 "downloadDir": "/tmp/sync", 1694 "registries": [ 1695 { 1696 "urls": [ 1697 "http://localhost:9000" 1698 ], 1699 "onDemand": true, 1700 "tlsVerify": false, 1701 "content": [ 1702 { 1703 "prefix": "**" 1704 } 1705 ] 1706 } 1707 ] 1708 } 1709 } 1710 }` 1711 1712 logPath, err := runCLIWithConfig(t.TempDir(), content) 1713 So(err, ShouldBeNil) 1714 1715 data, err := os.ReadFile(logPath) 1716 So(err, ShouldBeNil) 1717 defer os.Remove(logPath) // clean up 1718 So(string(data), ShouldNotContainSubstring, 1719 "using both sync and remote storage features needs config.Extensions.Sync.DownloadDir to be specified") 1720 }) 1721 1722 Convey("Test verify sync with remote storage panics if sync.tmpdir is not provided", t, func(c C) { 1723 port := GetFreePort() 1724 logFile, err := os.CreateTemp("", "zot-log*.txt") 1725 So(err, ShouldBeNil) 1726 defer os.Remove(logFile.Name()) // clean up 1727 1728 tmpfile, err := os.CreateTemp("", "zot-test*.json") 1729 So(err, ShouldBeNil) 1730 defer os.Remove(tmpfile.Name()) // clean up 1731 content := fmt.Sprintf(`{ 1732 "distSpecVersion": "1.1.0", 1733 "storage": { 1734 "rootDirectory": "%s", 1735 "dedupe": false, 1736 "remoteCache": false, 1737 "storageDriver": { 1738 "name": "s3", 1739 "rootdirectory": "/zot", 1740 "region": "us-east-2", 1741 "regionendpoint": "localhost:4566", 1742 "bucket": "zot-storage", 1743 "secure": false, 1744 "skipverify": false 1745 } 1746 }, 1747 "http": { 1748 "address": "0.0.0.0", 1749 "port": "%s" 1750 }, 1751 "log": { 1752 "level": "debug", 1753 "output": "%s" 1754 }, 1755 "extensions": { 1756 "sync": { 1757 "registries": [ 1758 { 1759 "urls": [ 1760 "http://localhost:9000" 1761 ], 1762 "onDemand": true, 1763 "tlsVerify": false, 1764 "content": [ 1765 { 1766 "prefix": "**" 1767 } 1768 ] 1769 } 1770 ] 1771 } 1772 } 1773 }`, t.TempDir(), port, logFile.Name()) 1774 1775 err = os.WriteFile(tmpfile.Name(), []byte(content), 0o0600) 1776 So(err, ShouldBeNil) 1777 1778 os.Args = []string{"cli_test", "serve", tmpfile.Name()} 1779 err = cli.NewServerRootCmd().Execute() 1780 So(err, ShouldNotBeNil) 1781 1782 data, err := os.ReadFile(logFile.Name()) 1783 So(err, ShouldBeNil) 1784 defer os.Remove(logFile.Name()) // clean up 1785 So(string(data), ShouldContainSubstring, 1786 "using both sync and remote storage features needs config.Extensions.Sync.DownloadDir to be specified") 1787 }) 1788 1789 Convey("Test verify sync with remote storage on subpath panics if sync.tmpdir is not provided", t, func(c C) { 1790 port := GetFreePort() 1791 logFile, err := os.CreateTemp("", "zot-log*.txt") 1792 So(err, ShouldBeNil) 1793 defer os.Remove(logFile.Name()) // clean up 1794 1795 tmpfile, err := os.CreateTemp("", "zot-test*.json") 1796 So(err, ShouldBeNil) 1797 defer os.Remove(tmpfile.Name()) // clean up 1798 content := fmt.Sprintf(`{ 1799 "distSpecVersion": "1.1.0", 1800 "storage": { 1801 "rootDirectory": "%s", 1802 "subPaths":{ 1803 "/a": { 1804 "rootDirectory": "%s", 1805 "dedupe": false, 1806 "remoteCache": false, 1807 "storageDriver":{ 1808 "name":"s3", 1809 "rootdirectory":"/zot-a", 1810 "region":"us-east-2", 1811 "bucket":"zot-storage", 1812 "secure":true, 1813 "skipverify":true 1814 } 1815 } 1816 } 1817 }, 1818 "http": { 1819 "address": "0.0.0.0", 1820 "port": "%s" 1821 }, 1822 "log": { 1823 "level": "debug", 1824 "output": "%s" 1825 }, 1826 "extensions": { 1827 "sync": { 1828 "registries": [ 1829 { 1830 "urls": [ 1831 "http://localhost:9000" 1832 ], 1833 "onDemand": true, 1834 "tlsVerify": false, 1835 "content": [ 1836 { 1837 "prefix": "**" 1838 } 1839 ] 1840 } 1841 ] 1842 } 1843 } 1844 }`, t.TempDir(), t.TempDir(), port, logFile.Name()) 1845 1846 err = os.WriteFile(tmpfile.Name(), []byte(content), 0o0600) 1847 So(err, ShouldBeNil) 1848 1849 os.Args = []string{"cli_test", "serve", tmpfile.Name()} 1850 err = cli.NewServerRootCmd().Execute() 1851 So(err, ShouldNotBeNil) 1852 1853 data, err := os.ReadFile(logFile.Name()) 1854 So(err, ShouldBeNil) 1855 defer os.Remove(logFile.Name()) // clean up 1856 So(string(data), ShouldContainSubstring, 1857 "using both sync and remote storage features needs config.Extensions.Sync.DownloadDir to be specified") 1858 }) 1859 }