zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/pkg/cli/server/config_reloader_test.go (about) 1 //go:build search 2 // +build search 3 4 package server_test 5 6 import ( 7 "fmt" 8 "io" 9 "os" 10 "testing" 11 "time" 12 13 . "github.com/smartystreets/goconvey/convey" 14 15 cli "zotregistry.io/zot/pkg/cli/server" 16 test "zotregistry.io/zot/pkg/test/common" 17 ) 18 19 func TestConfigReloader(t *testing.T) { 20 oldArgs := os.Args 21 22 defer func() { os.Args = oldArgs }() 23 24 Convey("reload access control config", t, func(c C) { 25 port := test.GetFreePort() 26 baseURL := test.GetBaseURL(port) 27 28 logFile, err := os.CreateTemp("", "zot-log*.txt") 29 So(err, ShouldBeNil) 30 31 username := "alice" 32 password := "alice" 33 34 htpasswdPath := test.MakeHtpasswdFileFromString(test.GetCredString(username, password)) 35 defer os.Remove(htpasswdPath) 36 37 defer os.Remove(logFile.Name()) // clean up 38 39 content := fmt.Sprintf(`{ 40 "distSpecVersion": "1.1.0-dev", 41 "storage": { 42 "rootDirectory": "%s" 43 }, 44 "http": { 45 "address": "127.0.0.1", 46 "port": "%s", 47 "realm": "zot", 48 "auth": { 49 "htpasswd": { 50 "path": "%s" 51 }, 52 "failDelay": 1 53 }, 54 "accessControl": { 55 "repositories": { 56 "**": { 57 "policies": [ 58 { 59 "users": ["charlie"], 60 "actions": ["read"] 61 } 62 ], 63 "defaultPolicy": ["read", "create"] 64 } 65 }, 66 "adminPolicy": { 67 "users": ["admin"], 68 "actions": ["read", "create", "update", "delete"] 69 } 70 } 71 }, 72 "log": { 73 "level": "debug", 74 "output": "%s" 75 } 76 }`, t.TempDir(), port, htpasswdPath, logFile.Name()) 77 78 cfgfile, err := os.CreateTemp("", "zot-test*.json") 79 So(err, ShouldBeNil) 80 81 defer os.Remove(cfgfile.Name()) // clean up 82 83 _, err = cfgfile.WriteString(content) 84 So(err, ShouldBeNil) 85 86 // err = cfgfile.Close() 87 // So(err, ShouldBeNil) 88 89 os.Args = []string{"cli_test", "serve", cfgfile.Name()} 90 go func() { 91 err = cli.NewServerRootCmd().Execute() 92 So(err, ShouldBeNil) 93 }() 94 95 test.WaitTillServerReady(baseURL) 96 97 content = fmt.Sprintf(`{ 98 "distSpecVersion": "1.1.0-dev", 99 "storage": { 100 "rootDirectory": "%s" 101 }, 102 "http": { 103 "address": "127.0.0.1", 104 "port": "%s", 105 "realm": "zot", 106 "auth": { 107 "htpasswd": { 108 "path": "%s" 109 }, 110 "failDelay": 1 111 }, 112 "accessControl": { 113 "repositories": { 114 "**": { 115 "policies": [ 116 { 117 "users": ["alice"], 118 "actions": ["read", "create", "update", "delete"] 119 } 120 ], 121 "defaultPolicy": ["read"] 122 } 123 }, 124 "adminPolicy": { 125 "users": ["admin"], 126 "actions": ["read", "create", "update", "delete"] 127 } 128 } 129 }, 130 "log": { 131 "level": "debug", 132 "output": "%s" 133 } 134 }`, t.TempDir(), port, htpasswdPath, logFile.Name()) 135 136 err = cfgfile.Truncate(0) 137 So(err, ShouldBeNil) 138 139 _, err = cfgfile.Seek(0, io.SeekStart) 140 So(err, ShouldBeNil) 141 142 _, err = cfgfile.WriteString(content) 143 So(err, ShouldBeNil) 144 145 err = cfgfile.Close() 146 So(err, ShouldBeNil) 147 148 // wait for config reload 149 time.Sleep(2 * time.Second) 150 151 data, err := os.ReadFile(logFile.Name()) 152 So(err, ShouldBeNil) 153 154 t.Logf("log file: %s", data) 155 So(string(data), ShouldContainSubstring, "reloaded params") 156 So(string(data), ShouldContainSubstring, "loaded new configuration settings") 157 So(string(data), ShouldContainSubstring, "\"Users\":[\"alice\"]") 158 So(string(data), ShouldContainSubstring, "\"Actions\":[\"read\",\"create\",\"update\",\"delete\"]") 159 }) 160 161 Convey("reload gc config", t, func(c C) { 162 port := test.GetFreePort() 163 baseURL := test.GetBaseURL(port) 164 165 logFile, err := os.CreateTemp("", "zot-log*.txt") 166 So(err, ShouldBeNil) 167 168 defer os.Remove(logFile.Name()) // clean up 169 170 content := fmt.Sprintf(`{ 171 "distSpecVersion": "1.1.0-dev", 172 "storage": { 173 "rootDirectory": "%s", 174 "gc": false, 175 "dedupe": false, 176 "subPaths": { 177 "/a": { 178 "rootDirectory": "%s", 179 "gc": false, 180 "dedupe": false 181 } 182 } 183 }, 184 "http": { 185 "address": "127.0.0.1", 186 "port": "%s" 187 }, 188 "log": { 189 "level": "debug", 190 "output": "%s" 191 } 192 }`, t.TempDir(), t.TempDir(), port, logFile.Name()) 193 194 cfgfile, err := os.CreateTemp("", "zot-test*.json") 195 So(err, ShouldBeNil) 196 197 defer os.Remove(cfgfile.Name()) // clean up 198 199 _, err = cfgfile.WriteString(content) 200 So(err, ShouldBeNil) 201 202 // err = cfgfile.Close() 203 // So(err, ShouldBeNil) 204 205 os.Args = []string{"cli_test", "serve", cfgfile.Name()} 206 go func() { 207 err = cli.NewServerRootCmd().Execute() 208 So(err, ShouldBeNil) 209 }() 210 211 test.WaitTillServerReady(baseURL) 212 213 content = fmt.Sprintf(`{ 214 "distSpecVersion": "1.1.0-dev", 215 "storage": { 216 "rootDirectory": "%s", 217 "gc": true, 218 "dedupe": true, 219 "subPaths": { 220 "/a": { 221 "rootDirectory": "%s", 222 "gc": true, 223 "dedupe": true 224 } 225 } 226 }, 227 "http": { 228 "address": "127.0.0.1", 229 "port": "%s" 230 }, 231 "log": { 232 "level": "debug", 233 "output": "%s" 234 } 235 }`, t.TempDir(), t.TempDir(), port, logFile.Name()) 236 237 err = cfgfile.Truncate(0) 238 So(err, ShouldBeNil) 239 240 _, err = cfgfile.Seek(0, io.SeekStart) 241 So(err, ShouldBeNil) 242 243 // truncate log before changing config, for the ShouldNotContainString 244 So(logFile.Truncate(0), ShouldBeNil) 245 246 _, err = cfgfile.WriteString(content) 247 So(err, ShouldBeNil) 248 249 err = cfgfile.Close() 250 So(err, ShouldBeNil) 251 252 // wait for config reload 253 time.Sleep(2 * time.Second) 254 255 data, err := os.ReadFile(logFile.Name()) 256 So(err, ShouldBeNil) 257 t.Logf("log file: %s", data) 258 259 So(string(data), ShouldContainSubstring, "reloaded params") 260 So(string(data), ShouldContainSubstring, "loaded new configuration settings") 261 So(string(data), ShouldContainSubstring, "\"GC\":true") 262 So(string(data), ShouldContainSubstring, "\"Dedupe\":true") 263 So(string(data), ShouldNotContainSubstring, "\"GC\":false") 264 So(string(data), ShouldNotContainSubstring, "\"Dedupe\":false") 265 }) 266 267 Convey("reload sync config", t, func(c C) { 268 port := test.GetFreePort() 269 baseURL := test.GetBaseURL(port) 270 271 logFile, err := os.CreateTemp("", "zot-log*.txt") 272 So(err, ShouldBeNil) 273 274 defer os.Remove(logFile.Name()) // clean up 275 276 content := fmt.Sprintf(`{ 277 "distSpecVersion": "1.1.0-dev", 278 "storage": { 279 "rootDirectory": "%s" 280 }, 281 "http": { 282 "address": "127.0.0.1", 283 "port": "%s" 284 }, 285 "log": { 286 "level": "debug", 287 "output": "%s" 288 }, 289 "extensions": { 290 "sync": { 291 "registries": [{ 292 "urls": ["http://localhost:8080"], 293 "tlsVerify": false, 294 "onDemand": true, 295 "maxRetries": 3, 296 "retryDelay": "15m", 297 "certDir": "", 298 "content":[ 299 { 300 "prefix": "zot-test", 301 "tags": { 302 "regex": ".*", 303 "semver": true 304 } 305 } 306 ] 307 }] 308 } 309 } 310 }`, t.TempDir(), port, logFile.Name()) 311 312 cfgfile, err := os.CreateTemp("", "zot-test*.json") 313 So(err, ShouldBeNil) 314 315 defer os.Remove(cfgfile.Name()) // clean up 316 317 _, err = cfgfile.WriteString(content) 318 So(err, ShouldBeNil) 319 320 // err = cfgfile.Close() 321 // So(err, ShouldBeNil) 322 323 os.Args = []string{"cli_test", "serve", cfgfile.Name()} 324 go func() { 325 err = cli.NewServerRootCmd().Execute() 326 So(err, ShouldBeNil) 327 }() 328 329 test.WaitTillServerReady(baseURL) 330 331 content = fmt.Sprintf(`{ 332 "distSpecVersion": "1.1.0-dev", 333 "storage": { 334 "rootDirectory": "%s" 335 }, 336 "http": { 337 "address": "127.0.0.1", 338 "port": "%s" 339 }, 340 "log": { 341 "level": "debug", 342 "output": "%s" 343 }, 344 "extensions": { 345 "sync": { 346 "registries": [{ 347 "urls": ["http://localhost:9999"], 348 "tlsVerify": true, 349 "onDemand": false, 350 "maxRetries": 10, 351 "retryDelay": "5m", 352 "certDir": "certs", 353 "content":[ 354 { 355 "prefix": "zot-cve-test", 356 "tags": { 357 "regex": "tag", 358 "semver": false 359 } 360 } 361 ] 362 }] 363 } 364 } 365 }`, t.TempDir(), port, logFile.Name()) 366 367 err = cfgfile.Truncate(0) 368 So(err, ShouldBeNil) 369 370 _, err = cfgfile.Seek(0, io.SeekStart) 371 So(err, ShouldBeNil) 372 373 _, err = cfgfile.WriteString(content) 374 So(err, ShouldBeNil) 375 376 err = cfgfile.Close() 377 So(err, ShouldBeNil) 378 379 // wait for config reload 380 time.Sleep(2 * time.Second) 381 382 data, err := os.ReadFile(logFile.Name()) 383 So(err, ShouldBeNil) 384 t.Logf("log file: %s", data) 385 386 So(string(data), ShouldContainSubstring, "reloaded params") 387 So(string(data), ShouldContainSubstring, "loaded new configuration settings") 388 So(string(data), ShouldContainSubstring, "\"URLs\":[\"http://localhost:9999\"]") 389 So(string(data), ShouldContainSubstring, "\"TLSVerify\":true") 390 So(string(data), ShouldContainSubstring, "\"OnDemand\":false") 391 So(string(data), ShouldContainSubstring, "\"MaxRetries\":10") 392 So(string(data), ShouldContainSubstring, "\"RetryDelay\":300000000000") 393 So(string(data), ShouldContainSubstring, "\"CertDir\":\"certs\"") 394 So(string(data), ShouldContainSubstring, "\"Prefix\":\"zot-cve-test\"") 395 So(string(data), ShouldContainSubstring, "\"Regex\":\"tag\"") 396 So(string(data), ShouldContainSubstring, "\"Semver\":false") 397 }) 398 399 Convey("reload scrub and CVE config", t, func(c C) { 400 port := test.GetFreePort() 401 baseURL := test.GetBaseURL(port) 402 403 logFile, err := os.CreateTemp("", "zot-log*.txt") 404 So(err, ShouldBeNil) 405 406 defer os.Remove(logFile.Name()) // clean up 407 408 content := fmt.Sprintf(`{ 409 "distSpecVersion": "1.1.0-dev", 410 "storage": { 411 "rootDirectory": "%s" 412 }, 413 "http": { 414 "address": "127.0.0.1", 415 "port": "%s" 416 }, 417 "log": { 418 "level": "debug", 419 "output": "%s" 420 }, 421 "extensions": { 422 "search": { 423 "cve": { 424 "updateInterval": "24h", 425 "trivy": { 426 "DBRepository": "unreachable/trivy/url1" 427 } 428 } 429 }, 430 "scrub": { 431 "enable": true, 432 "interval": "24h" 433 } 434 } 435 }`, t.TempDir(), port, logFile.Name()) 436 437 cfgfile, err := os.CreateTemp("", "zot-test*.json") 438 So(err, ShouldBeNil) 439 440 defer os.Remove(cfgfile.Name()) // clean up 441 442 _, err = cfgfile.WriteString(content) 443 So(err, ShouldBeNil) 444 445 os.Args = []string{"cli_test", "serve", cfgfile.Name()} 446 go func() { 447 err = cli.NewServerRootCmd().Execute() 448 So(err, ShouldBeNil) 449 }() 450 451 test.WaitTillServerReady(baseURL) 452 453 content = fmt.Sprintf(`{ 454 "distSpecVersion": "1.1.0-dev", 455 "storage": { 456 "rootDirectory": "%s" 457 }, 458 "http": { 459 "address": "127.0.0.1", 460 "port": "%s" 461 }, 462 "log": { 463 "level": "debug", 464 "output": "%s" 465 }, 466 "extensions": { 467 "search": { 468 "cve": { 469 "updateInterval": "5h", 470 "trivy": { 471 "DBRepository": "another/unreachable/trivy/url2" 472 } 473 } 474 } 475 } 476 }`, t.TempDir(), port, logFile.Name()) 477 478 err = cfgfile.Truncate(0) 479 So(err, ShouldBeNil) 480 481 _, err = cfgfile.Seek(0, io.SeekStart) 482 So(err, ShouldBeNil) 483 484 _, err = cfgfile.WriteString(content) 485 So(err, ShouldBeNil) 486 487 err = cfgfile.Close() 488 So(err, ShouldBeNil) 489 490 // wait for config reload 491 time.Sleep(5 * time.Second) 492 493 found, err := test.ReadLogFileAndSearchString(logFile.Name(), 494 "Error downloading Trivy DB to destination dir", 30*time.Second) 495 So(err, ShouldBeNil) 496 So(found, ShouldBeTrue) 497 498 data, err := os.ReadFile(logFile.Name()) 499 So(err, ShouldBeNil) 500 t.Logf("log file: %s", data) 501 502 So(string(data), ShouldContainSubstring, "reloaded params") 503 So(string(data), ShouldContainSubstring, "loaded new configuration settings") 504 So(string(data), ShouldContainSubstring, "\"UpdateInterval\":18000000000000") 505 So(string(data), ShouldContainSubstring, "\"Scrub\":null") 506 So(string(data), ShouldContainSubstring, "\"DBRepository\":\"another/unreachable/trivy/url2\"") 507 // matching log message when it errors out, test that indeed the download will try the second url 508 found, err = test.ReadLogFileAndSearchString(logFile.Name(), 509 "\"dbRepository\":\"another/unreachable/trivy/url2\",\"goroutine", 1*time.Minute) 510 So(err, ShouldBeNil) 511 So(found, ShouldBeTrue) 512 }) 513 514 Convey("reload bad config", t, func(c C) { 515 port := test.GetFreePort() 516 baseURL := test.GetBaseURL(port) 517 518 logFile, err := os.CreateTemp("", "zot-log*.txt") 519 So(err, ShouldBeNil) 520 521 defer os.Remove(logFile.Name()) // clean up 522 523 content := fmt.Sprintf(`{ 524 "distSpecVersion": "1.1.0-dev", 525 "storage": { 526 "rootDirectory": "%s" 527 }, 528 "http": { 529 "address": "127.0.0.1", 530 "port": "%s" 531 }, 532 "log": { 533 "level": "debug", 534 "output": "%s" 535 }, 536 "extensions": { 537 "sync": { 538 "registries": [{ 539 "urls": ["http://localhost:8080"], 540 "tlsVerify": false, 541 "onDemand": true, 542 "maxRetries": 3, 543 "retryDelay": "15m", 544 "certDir": "", 545 "content":[ 546 { 547 "prefix": "zot-test", 548 "tags": { 549 "regex": ".*", 550 "semver": true 551 } 552 } 553 ] 554 }] 555 } 556 } 557 }`, t.TempDir(), port, logFile.Name()) 558 559 cfgfile, err := os.CreateTemp("", "zot-test*.json") 560 So(err, ShouldBeNil) 561 562 defer os.Remove(cfgfile.Name()) // clean up 563 564 _, err = cfgfile.WriteString(content) 565 So(err, ShouldBeNil) 566 567 // err = cfgfile.Close() 568 // So(err, ShouldBeNil) 569 570 os.Args = []string{"cli_test", "serve", cfgfile.Name()} 571 go func() { 572 err = cli.NewServerRootCmd().Execute() 573 So(err, ShouldBeNil) 574 }() 575 576 test.WaitTillServerReady(baseURL) 577 578 content = "[]" 579 580 err = cfgfile.Truncate(0) 581 So(err, ShouldBeNil) 582 583 _, err = cfgfile.Seek(0, io.SeekStart) 584 So(err, ShouldBeNil) 585 586 _, err = cfgfile.WriteString(content) 587 So(err, ShouldBeNil) 588 589 err = cfgfile.Close() 590 So(err, ShouldBeNil) 591 592 // wait for config reload 593 time.Sleep(2 * time.Second) 594 595 data, err := os.ReadFile(logFile.Name()) 596 So(err, ShouldBeNil) 597 t.Logf("log file: %s", data) 598 599 So(string(data), ShouldNotContainSubstring, "reloaded params") 600 So(string(data), ShouldNotContainSubstring, "new configuration settings") 601 So(string(data), ShouldContainSubstring, "\"URLs\":[\"http://localhost:8080\"]") 602 So(string(data), ShouldContainSubstring, "\"TLSVerify\":false") 603 So(string(data), ShouldContainSubstring, "\"OnDemand\":true") 604 So(string(data), ShouldContainSubstring, "\"MaxRetries\":3") 605 So(string(data), ShouldContainSubstring, "\"CertDir\":\"\"") 606 So(string(data), ShouldContainSubstring, "\"Prefix\":\"zot-test\"") 607 So(string(data), ShouldContainSubstring, "\"Regex\":\".*\"") 608 So(string(data), ShouldContainSubstring, "\"Semver\":true") 609 }) 610 }