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  }