zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/pkg/cli/server/root_test.go (about)

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