zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/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.io/zot/pkg/api/config"
    17  	cli "zotregistry.io/zot/pkg/cli/server"
    18  	. "zotregistry.io/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 the CVE database", 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 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-dev",
  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-dev",
  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-dev",
  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-dev",
  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  }