mirror of
https://github.com/project-zot/zot.git
synced 2026-06-18 05:28:07 +08:00
dfb5d1df54
* fix: make config read/write thread safe and fix some other similar issues 1. The config config has a lock, and safe methods to update and read the attributes 2. The config has methods to retrieve copies of specific attributes, such as the extyensions config, the auth config, and the authz config. These are needed, as the config object may mutate in the middle of an auth/authz requests, and we avoid partial configuration being applied for that request. 3. Fix an issue with the monitoring server not stopping when the controller is shut down. 4. Fix an issue with the HTPasswdWatcher not stopping when the background tasks are supposed to finish. 5. Fix some tests using hardcoded ports. Moved some of the methods which were on the main config to the auth, access control and extension configs Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
2026 lines
46 KiB
Go
2026 lines
46 KiB
Go
//go:build sync && scrub && metrics && search && userprefs && mgmt && imagetrust && events
|
|
// +build sync,scrub,metrics,search,userprefs,mgmt,imagetrust,events
|
|
|
|
package server_test
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
. "github.com/smartystreets/goconvey/convey"
|
|
"gopkg.in/resty.v1"
|
|
|
|
zerr "zotregistry.dev/zot/v2/errors"
|
|
"zotregistry.dev/zot/v2/pkg/api/config"
|
|
cli "zotregistry.dev/zot/v2/pkg/cli/server"
|
|
. "zotregistry.dev/zot/v2/pkg/test/common"
|
|
)
|
|
|
|
const readLogFileTimeout = 5 * time.Second
|
|
|
|
func TestVerifyExtensionsConfig(t *testing.T) {
|
|
oldArgs := os.Args
|
|
|
|
defer func() { os.Args = oldArgs }()
|
|
|
|
Convey("Test verify CVE warn for remote storage", t, func(c C) {
|
|
tmpfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(tmpfile.Name()) // clean up
|
|
|
|
content := fmt.Sprintf(`{
|
|
"storage":{
|
|
"rootDirectory":"%s",
|
|
"dedupe":true,
|
|
"remoteCache":false,
|
|
"storageDriver":{
|
|
"name":"s3",
|
|
"rootdirectory":"/zot",
|
|
"region":"us-east-2",
|
|
"bucket":"zot-storage",
|
|
"secure":true,
|
|
"skipverify":false
|
|
}
|
|
},
|
|
"http":{
|
|
"address":"127.0.0.1",
|
|
"port":"8080"
|
|
},
|
|
"extensions":{
|
|
"search": {
|
|
"enable": true,
|
|
"cve": {
|
|
"updateInterval": "24h"
|
|
}
|
|
}
|
|
}
|
|
}`, t.TempDir())
|
|
|
|
err = os.WriteFile(tmpfile.Name(), []byte(content), 0o0600)
|
|
So(err, ShouldBeNil)
|
|
|
|
os.Args = []string{"cli_test", "verify", tmpfile.Name()}
|
|
|
|
So(cli.NewServerRootCmd().Execute(), ShouldNotBeNil)
|
|
|
|
content = fmt.Sprintf(`{
|
|
"storage":{
|
|
"rootDirectory":"%s",
|
|
"dedupe":true,
|
|
"remoteCache":false,
|
|
"subPaths":{
|
|
"/a": {
|
|
"rootDirectory": "%s",
|
|
"dedupe": false,
|
|
"storageDriver":{
|
|
"name":"s3",
|
|
"rootdirectory":"/zot-a",
|
|
"region":"us-east-2",
|
|
"bucket":"zot-storage",
|
|
"secure":true,
|
|
"skipverify":false
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"http":{
|
|
"address":"127.0.0.1",
|
|
"port":"8080"
|
|
},
|
|
"extensions":{
|
|
"search": {
|
|
"enable": true,
|
|
"cve": {
|
|
"updateInterval": "24h"
|
|
}
|
|
}
|
|
}
|
|
}`, t.TempDir(), t.TempDir())
|
|
err = os.WriteFile(tmpfile.Name(), []byte(content), 0o0600)
|
|
So(err, ShouldBeNil)
|
|
|
|
os.Args = []string{"cli_test", "verify", tmpfile.Name()}
|
|
|
|
So(cli.NewServerRootCmd().Execute(), ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Test verify w/ sync and w/o filesystem storage", t, func(c C) {
|
|
tmpfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(tmpfile.Name()) // clean up
|
|
|
|
content := fmt.Sprintf(`{"storage":{"rootDirectory":"%s", "storageDriver": {"name": "s3"}},
|
|
"http":{"address":"127.0.0.1","port":"8080","realm":"zot",
|
|
"auth":{"htpasswd":{"path":"test/data/htpasswd"},"failDelay":1}},
|
|
"extensions":{"sync": {"registries": [{"urls":["localhost:9999"],
|
|
"maxRetries": 1, "retryDelay": "10s"}]}}}`, t.TempDir())
|
|
_, err = tmpfile.WriteString(content)
|
|
So(err, ShouldBeNil)
|
|
err = tmpfile.Close()
|
|
So(err, ShouldBeNil)
|
|
os.Args = []string{"cli_test", "verify", tmpfile.Name()}
|
|
|
|
So(cli.NewServerRootCmd().Execute(), ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Test verify w/ sync and w/ filesystem storage", t, func(c C) {
|
|
tmpfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(tmpfile.Name()) // clean up
|
|
|
|
content := fmt.Sprintf(`{"storage":{"rootDirectory":"%s"},
|
|
"http":{"address":"127.0.0.1","port":"8080","realm":"zot",
|
|
"auth":{"htpasswd":{"path":"test/data/htpasswd"},"failDelay":1}},
|
|
"extensions":{"sync": {"registries": [{"urls":["localhost:9999"],
|
|
"maxRetries": 1, "retryDelay": "10s"}]}}}`, t.TempDir())
|
|
_, err = tmpfile.WriteString(content)
|
|
So(err, ShouldBeNil)
|
|
err = tmpfile.Close()
|
|
So(err, ShouldBeNil)
|
|
|
|
os.Args = []string{"cli_test", "verify", tmpfile.Name()}
|
|
|
|
So(cli.NewServerRootCmd().Execute(), ShouldBeNil)
|
|
})
|
|
|
|
Convey("Test verify with bad sync prefixes", t, func(c C) {
|
|
tmpfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(tmpfile.Name()) // clean up
|
|
|
|
content := fmt.Sprintf(`{"storage":{"rootDirectory":"%s"},
|
|
"http":{"address":"127.0.0.1","port":"8080","realm":"zot",
|
|
"auth":{"htpasswd":{"path":"test/data/htpasswd"},"failDelay":1}},
|
|
"extensions":{"sync": {"registries": [{"urls":["localhost:9999"],
|
|
"maxRetries": 1, "retryDelay": "10s",
|
|
"content": [{"prefix":"[repo^&["}]}]}}}`, t.TempDir())
|
|
_, err = tmpfile.WriteString(content)
|
|
So(err, ShouldBeNil)
|
|
err = tmpfile.Close()
|
|
So(err, ShouldBeNil)
|
|
|
|
os.Args = []string{"cli_test", "verify", tmpfile.Name()}
|
|
|
|
So(cli.NewServerRootCmd().Execute(), ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Test verify with bad sync content config", t, func(c C) {
|
|
tmpfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(tmpfile.Name()) // clean up
|
|
|
|
content := fmt.Sprintf(`{"storage":{"rootDirectory":"%s"},
|
|
"http":{"address":"127.0.0.1","port":"8080","realm":"zot",
|
|
"auth":{"htpasswd":{"path":"test/data/htpasswd"},"failDelay":1}},
|
|
"extensions":{"sync": {"registries": [{"urls":["localhost:9999"],
|
|
"maxRetries": 1, "retryDelay": "10s",
|
|
"content": [{"prefix":"zot-repo","stripPrefix":true,"destination":"/"}]}]}}}`, t.TempDir())
|
|
_, err = tmpfile.WriteString(content)
|
|
So(err, ShouldBeNil)
|
|
err = tmpfile.Close()
|
|
So(err, ShouldBeNil)
|
|
os.Args = []string{"cli_test", "verify", tmpfile.Name()}
|
|
|
|
So(cli.NewServerRootCmd().Execute(), ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Test verify with good sync content config", t, func(c C) {
|
|
tmpfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(tmpfile.Name()) // clean up
|
|
|
|
content := fmt.Sprintf(`{"storage":{"rootDirectory":"%s"},
|
|
"http":{"address":"127.0.0.1","port":"8080","realm":"zot",
|
|
"auth":{"htpasswd":{"path":"test/data/htpasswd"},"failDelay":1}},
|
|
"extensions":{"sync": {"registries": [{"urls":["localhost:9999"],
|
|
"maxRetries": 1, "retryDelay": "10s",
|
|
"content": [{"prefix":"zot-repo/*","stripPrefix":true,"destination":"/"}]}]}}}`, t.TempDir())
|
|
_, err = tmpfile.WriteString(content)
|
|
So(err, ShouldBeNil)
|
|
err = tmpfile.Close()
|
|
So(err, ShouldBeNil)
|
|
os.Args = []string{"cli_test", "verify", tmpfile.Name()}
|
|
err = cli.NewServerRootCmd().Execute()
|
|
So(err, ShouldBeNil)
|
|
})
|
|
|
|
Convey("Test verify sync config default tls value", t, func(c C) {
|
|
tmpfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(tmpfile.Name()) // clean up
|
|
|
|
content := fmt.Sprintf(`{"storage":{"rootDirectory":"%s"},
|
|
"http":{"address":"127.0.0.1","port":"8080","realm":"zot",
|
|
"auth":{"htpasswd":{"path":"test/data/htpasswd"},"failDelay":1}},
|
|
"extensions":{"sync": {"registries": [{"urls":["localhost:9999"],
|
|
"maxRetries": 1, "retryDelay": "10s",
|
|
"content": [{"prefix":"repo**"}]}]}}}`, t.TempDir())
|
|
_, err = tmpfile.WriteString(content)
|
|
So(err, ShouldBeNil)
|
|
err = tmpfile.Close()
|
|
So(err, ShouldBeNil)
|
|
os.Args = []string{"cli_test", "verify", tmpfile.Name()}
|
|
err = cli.NewServerRootCmd().Execute()
|
|
So(err, ShouldBeNil)
|
|
})
|
|
|
|
Convey("Test verify sync without retry options", t, func(c C) {
|
|
tmpfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(tmpfile.Name()) // clean up
|
|
|
|
content := fmt.Sprintf(`{"storage":{"rootDirectory":"%s"},
|
|
"http":{"address":"127.0.0.1","port":"8080","realm":"zot",
|
|
"auth":{"htpasswd":{"path":"test/data/htpasswd"},"failDelay":1}},
|
|
"extensions":{"sync": {"registries": [{"urls":["localhost:9999"],
|
|
"maxRetries": 10, "content": [{"prefix":"repo**"}]}]}}}`, t.TempDir())
|
|
_, err = tmpfile.WriteString(content)
|
|
So(err, ShouldBeNil)
|
|
err = tmpfile.Close()
|
|
So(err, ShouldBeNil)
|
|
os.Args = []string{"cli_test", "verify", tmpfile.Name()}
|
|
|
|
So(cli.NewServerRootCmd().Execute(), ShouldNotBeNil)
|
|
})
|
|
}
|
|
|
|
func TestValidateExtensionsConfig(t *testing.T) {
|
|
Convey("Legacy extensions should not error", t, func(c C) {
|
|
config := config.New()
|
|
tmpfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(tmpfile.Name())
|
|
|
|
content := []byte(`{
|
|
"storage": {
|
|
"rootDirectory": "%/tmp/zot"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "8080"
|
|
},
|
|
"log": {
|
|
"level": "debug"
|
|
},
|
|
"extensions": {
|
|
"mgmt": {
|
|
"enable": "true"
|
|
},
|
|
"apikey": {
|
|
"enable": "true"
|
|
}
|
|
}
|
|
}`)
|
|
err = os.WriteFile(tmpfile.Name(), content, 0o0600)
|
|
So(err, ShouldBeNil)
|
|
err = cli.LoadConfiguration(config, tmpfile.Name())
|
|
So(err, ShouldBeNil)
|
|
})
|
|
|
|
Convey("Test missing extensions for UI to work", t, func(c C) {
|
|
config := config.New()
|
|
|
|
tmpfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(tmpfile.Name())
|
|
|
|
content := []byte(`{
|
|
"storage": {
|
|
"rootDirectory": "%/tmp/zot"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "8080"
|
|
},
|
|
"log": {
|
|
"level": "debug"
|
|
},
|
|
"extensions": {
|
|
"ui": {
|
|
"enable": "true"
|
|
}
|
|
}
|
|
}`)
|
|
err = os.WriteFile(tmpfile.Name(), content, 0o0600)
|
|
So(err, ShouldBeNil)
|
|
err = cli.LoadConfiguration(config, tmpfile.Name())
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("Test enabling UI extension with all prerequisites", t, func(c C) {
|
|
config := config.New()
|
|
tmpfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(tmpfile.Name())
|
|
|
|
content := []byte(`{
|
|
"storage": {
|
|
"rootDirectory": "%/tmp/zot"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "8080"
|
|
},
|
|
"log": {
|
|
"level": "debug"
|
|
},
|
|
"extensions": {
|
|
"ui": {
|
|
"enable": "true"
|
|
},
|
|
"search": {
|
|
"enable": "true"
|
|
}
|
|
}
|
|
}`)
|
|
err = os.WriteFile(tmpfile.Name(), content, 0o0600)
|
|
So(err, ShouldBeNil)
|
|
err = cli.LoadConfiguration(config, tmpfile.Name())
|
|
So(err, ShouldBeNil)
|
|
})
|
|
|
|
Convey("Test extension are implicitly enabled", t, func(c C) {
|
|
config := config.New()
|
|
tmpfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(tmpfile.Name())
|
|
|
|
content := []byte(`{
|
|
"storage": {
|
|
"rootDirectory": "%/tmp/zot"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "8080"
|
|
},
|
|
"log": {
|
|
"level": "debug"
|
|
},
|
|
"extensions": {
|
|
"ui": {},
|
|
"search": {},
|
|
"metrics": {},
|
|
"trust": {},
|
|
"scrub": {}
|
|
}
|
|
}`)
|
|
err = os.WriteFile(tmpfile.Name(), content, 0o0600)
|
|
So(err, ShouldBeNil)
|
|
err = cli.LoadConfiguration(config, tmpfile.Name())
|
|
So(err, ShouldBeNil)
|
|
So(config.Extensions.UI, ShouldNotBeNil)
|
|
So(*config.Extensions.UI.Enable, ShouldBeTrue)
|
|
So(config.Extensions.Search, ShouldNotBeNil)
|
|
So(*config.Extensions.Search.Enable, ShouldBeTrue)
|
|
So(config.Extensions.Trust, ShouldNotBeNil)
|
|
So(*config.Extensions.Trust.Enable, ShouldBeTrue)
|
|
So(*config.Extensions.Metrics, ShouldNotBeNil)
|
|
So(*config.Extensions.Metrics.Enable, ShouldBeTrue)
|
|
So(config.Extensions.Scrub, ShouldNotBeNil)
|
|
So(*config.Extensions.Scrub.Enable, ShouldBeTrue)
|
|
})
|
|
}
|
|
|
|
func TestServeExtensions(t *testing.T) {
|
|
oldArgs := os.Args
|
|
|
|
defer func() { os.Args = oldArgs }()
|
|
|
|
Convey("config file with no extensions", t, func(c C) {
|
|
port := GetFreePort()
|
|
baseURL := GetBaseURL(port)
|
|
logFile, err := os.CreateTemp("", "zot-log*.txt")
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logFile.Name()) // clean up
|
|
|
|
tmpFile := t.TempDir()
|
|
|
|
content := fmt.Sprintf(`{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
}
|
|
}`, tmpFile, port, logFile.Name())
|
|
|
|
cfgfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(cfgfile.Name()) // clean up
|
|
|
|
_, err = cfgfile.WriteString(content)
|
|
So(err, ShouldBeNil)
|
|
err = cfgfile.Close()
|
|
So(err, ShouldBeNil)
|
|
|
|
os.Args = []string{"cli_test", "serve", cfgfile.Name()}
|
|
|
|
go func() {
|
|
Convey("run", t, func() {
|
|
err = cli.NewServerRootCmd().Execute()
|
|
So(err, ShouldBeNil)
|
|
})
|
|
}()
|
|
|
|
WaitTillServerReady(baseURL)
|
|
|
|
data, err := os.ReadFile(logFile.Name())
|
|
|
|
So(err, ShouldBeNil)
|
|
So(string(data), ShouldContainSubstring, "\"Extensions\":null")
|
|
})
|
|
|
|
Convey("config file with empty extensions", t, func(c C) {
|
|
port := GetFreePort()
|
|
baseURL := GetBaseURL(port)
|
|
logFile, err := os.CreateTemp("", "zot-log*.txt")
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logFile.Name()) // clean up
|
|
|
|
tmpFile := t.TempDir()
|
|
|
|
content := fmt.Sprintf(`{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
}
|
|
}`, tmpFile, port, logFile.Name())
|
|
|
|
cfgfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(cfgfile.Name()) // clean up
|
|
|
|
_, err = cfgfile.WriteString(content)
|
|
So(err, ShouldBeNil)
|
|
err = cfgfile.Close()
|
|
So(err, ShouldBeNil)
|
|
|
|
os.Args = []string{"cli_test", "serve", cfgfile.Name()}
|
|
|
|
go func() {
|
|
Convey("run", t, func() {
|
|
err = cli.NewServerRootCmd().Execute()
|
|
So(err, ShouldBeNil)
|
|
})
|
|
}()
|
|
|
|
WaitTillServerReady(baseURL)
|
|
data, err := os.ReadFile(logFile.Name())
|
|
So(err, ShouldBeNil)
|
|
So(string(data), ShouldContainSubstring,
|
|
"\"Extensions\":{\"Search\":null,\"Sync\":null,\"Metrics\":null,\"Scrub\":null,\"Lint\":null,\"UI\":null,\"Mgmt\":null") //nolint:lll // gofumpt conflicts with lll
|
|
})
|
|
}
|
|
|
|
func testWithMetricsEnabled(t *testing.T, rootDir string, cfgContentFormat string) {
|
|
t.Helper()
|
|
port := GetFreePort()
|
|
baseURL := GetBaseURL(port)
|
|
logFile, err := os.CreateTemp("", "zot-log*.txt")
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logFile.Name()) // clean up
|
|
|
|
content := fmt.Sprintf(cfgContentFormat, rootDir, port, logFile.Name())
|
|
cfgfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(cfgfile.Name()) // clean up
|
|
|
|
_, err = cfgfile.WriteString(content)
|
|
So(err, ShouldBeNil)
|
|
err = cfgfile.Close()
|
|
So(err, ShouldBeNil)
|
|
|
|
os.Args = []string{"cli_test", "serve", cfgfile.Name()}
|
|
|
|
go func() {
|
|
Convey("run", t, func() {
|
|
err = cli.NewServerRootCmd().Execute()
|
|
So(err, ShouldBeNil)
|
|
})
|
|
}()
|
|
WaitTillServerReady(baseURL)
|
|
|
|
resp, err := resty.R().Get(baseURL + "/metrics")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
|
|
|
respStr := string(resp.Body())
|
|
So(respStr, ShouldContainSubstring, "zot_info")
|
|
|
|
data, err := os.ReadFile(logFile.Name())
|
|
So(err, ShouldBeNil)
|
|
So(string(data), ShouldContainSubstring,
|
|
"\"Metrics\":{\"Enable\":true,\"Prometheus\":{\"Path\":\"/metrics\"}}")
|
|
}
|
|
|
|
func TestServeMetricsExtension(t *testing.T) {
|
|
oldArgs := os.Args
|
|
|
|
defer func() { os.Args = oldArgs }()
|
|
|
|
Convey("no explicit enable", t, func(c C) {
|
|
tmpFile := t.TempDir()
|
|
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"metrics": {
|
|
}
|
|
}
|
|
}`
|
|
testWithMetricsEnabled(t, tmpFile, content)
|
|
})
|
|
|
|
Convey("no explicit enable but with prometheus parameter", t, func(c C) {
|
|
tmpFile := t.TempDir()
|
|
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"metrics": {
|
|
"prometheus": {
|
|
"path": "/metrics"
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
testWithMetricsEnabled(t, tmpFile, content)
|
|
})
|
|
|
|
Convey("with explicit enable, but without prometheus parameter", t, func(c C) {
|
|
tmpFile := t.TempDir()
|
|
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"metrics": {
|
|
"enable": true
|
|
}
|
|
}
|
|
}`
|
|
testWithMetricsEnabled(t, tmpFile, content)
|
|
})
|
|
|
|
Convey("with explicit disable", t, func(c C) {
|
|
port := GetFreePort()
|
|
baseURL := GetBaseURL(port)
|
|
logFile, err := os.CreateTemp("", "zot-log*.txt")
|
|
So(err, ShouldBeNil)
|
|
tmpFile := t.TempDir()
|
|
|
|
defer os.Remove(logFile.Name()) // clean up
|
|
|
|
content := fmt.Sprintf(`{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"metrics": {
|
|
"enable": false
|
|
}
|
|
}
|
|
}`, tmpFile, port, logFile.Name())
|
|
|
|
cfgfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(cfgfile.Name()) // clean up
|
|
|
|
_, err = cfgfile.WriteString(content)
|
|
So(err, ShouldBeNil)
|
|
err = cfgfile.Close()
|
|
So(err, ShouldBeNil)
|
|
|
|
os.Args = []string{"cli_test", "serve", cfgfile.Name()}
|
|
|
|
go func() {
|
|
Convey("run", t, func() {
|
|
err = cli.NewServerRootCmd().Execute()
|
|
So(err, ShouldBeNil)
|
|
})
|
|
}()
|
|
|
|
WaitTillServerReady(baseURL)
|
|
|
|
resp, err := resty.R().Get(baseURL + "/metrics")
|
|
So(err, ShouldBeNil)
|
|
So(resp, ShouldNotBeNil)
|
|
So(resp.StatusCode(), ShouldEqual, http.StatusNotFound)
|
|
|
|
data, err := os.ReadFile(logFile.Name())
|
|
So(err, ShouldBeNil)
|
|
So(string(data), ShouldContainSubstring,
|
|
"\"Metrics\":{\"Enable\":false,\"Prometheus\":{\"Path\":\"/metrics\"}}") //nolint:lll // gofumpt conflicts with lll
|
|
})
|
|
}
|
|
|
|
func TestServeSyncExtension(t *testing.T) {
|
|
oldArgs := os.Args
|
|
|
|
defer func() { os.Args = oldArgs }()
|
|
|
|
Convey("sync implicitly enabled", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"sync": {
|
|
"registries": [{
|
|
"urls": ["http://localhost:8080"],
|
|
"tlsVerify": false,
|
|
"onDemand": true,
|
|
"maxRetries": 3,
|
|
"retryDelay": "15m",
|
|
"certDir": "",
|
|
"content":[
|
|
{
|
|
"prefix": "zot-test",
|
|
"tags": {
|
|
"regex": ".*",
|
|
"semver": true
|
|
}
|
|
}
|
|
]
|
|
}]
|
|
}
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
So(string(data), ShouldContainSubstring,
|
|
"\"Extensions\":{\"Search\":null,\"Sync\":{\"Enable\":true")
|
|
})
|
|
|
|
Convey("sync explicitly enabled", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"sync": {
|
|
"enable": true,
|
|
"registries": [{
|
|
"urls": ["http://localhost:8080"],
|
|
"tlsVerify": false,
|
|
"onDemand": true,
|
|
"maxRetries": 3,
|
|
"retryDelay": "15m",
|
|
"certDir": "",
|
|
"content":[
|
|
{
|
|
"prefix": "zot-test",
|
|
"tags": {
|
|
"regex": ".*",
|
|
"semver": true
|
|
}
|
|
}
|
|
]
|
|
}]
|
|
}
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
So(string(data), ShouldContainSubstring,
|
|
"\"Extensions\":{\"Search\":null,\"Sync\":{\"Enable\":true")
|
|
})
|
|
|
|
Convey("sync explicitly disabled", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"sync": {
|
|
"enable": false,
|
|
"registries": [{
|
|
"urls": ["http://127.0.0.1:8080"],
|
|
"tlsVerify": false,
|
|
"certDir": "",
|
|
"maxRetries": 3,
|
|
"retryDelay": "15m"
|
|
}]
|
|
}
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
So(string(data), ShouldContainSubstring,
|
|
"\"Extensions\":{\"Search\":null,\"Sync\":{\"Enable\":false")
|
|
})
|
|
}
|
|
|
|
func TestServeScrubExtension(t *testing.T) {
|
|
oldArgs := os.Args
|
|
|
|
defer func() { os.Args = oldArgs }()
|
|
|
|
Convey("scrub implicitly enabled", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"scrub": {
|
|
}
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
dataStr := string(data)
|
|
So(dataStr, ShouldContainSubstring,
|
|
"\"Extensions\":{\"Search\":null,\"Sync\":null,\"Metrics\":null,\"Scrub\":{\"Enable\":true,\"Interval\":86400000000000},\"Lint\":null") //nolint:lll // gofumpt conflicts with lll
|
|
So(dataStr, ShouldNotContainSubstring,
|
|
"scrub interval set to too-short interval < 2h, changing scrub duration to 2 hours and continuing.")
|
|
})
|
|
|
|
Convey("scrub implicitly enabled, but with scrub interval param set", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"scrub": {
|
|
"interval": "1h"
|
|
}
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
// Even if in config we specified scrub interval=1h, the minimum interval is 2h
|
|
dataStr := string(data)
|
|
So(dataStr, ShouldContainSubstring, "\"Scrub\":{\"Enable\":true,\"Interval\":7200000000000}")
|
|
So(dataStr, ShouldContainSubstring,
|
|
"scrub interval set to too-short interval < 2h, changing scrub duration to 2 hours and continuing.")
|
|
})
|
|
|
|
Convey("scrub explicitly enabled, but without scrub interval param set", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"scrub": {
|
|
"enable": true
|
|
}
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
dataStr := string(data)
|
|
So(dataStr, ShouldContainSubstring,
|
|
"\"Extensions\":{\"Search\":null,\"Sync\":null,\"Metrics\":null,\"Scrub\":{\"Enable\":true,\"Interval\":86400000000000},\"Lint\":null") //nolint:lll // gofumpt conflicts with lll
|
|
So(dataStr, ShouldNotContainSubstring,
|
|
"scrub interval set to too-short interval < 2h, changing scrub duration to 2 hours and continuing.")
|
|
})
|
|
|
|
Convey("scrub explicitly disabled", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"scrub": {
|
|
"enable": false
|
|
}
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
dataStr := string(data)
|
|
So(dataStr, ShouldContainSubstring, "\"Scrub\":{\"Enable\":false,\"Interval\":86400000000000}")
|
|
So(dataStr, ShouldContainSubstring, "scrub config not provided, skipping scrub")
|
|
So(dataStr, ShouldNotContainSubstring,
|
|
"scrub interval set to too-short interval < 2h, changing scrub duration to 2 hours and continuing.")
|
|
})
|
|
}
|
|
|
|
func TestServeLintExtension(t *testing.T) {
|
|
oldArgs := os.Args
|
|
|
|
defer func() { os.Args = oldArgs }()
|
|
|
|
Convey("lint enabled", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"lint": {
|
|
"enable": "true",
|
|
"mandatoryAnnotations": ["annot1"]
|
|
}
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
So(string(data), ShouldContainSubstring,
|
|
"\"Extensions\":{\"Search\":null,\"Sync\":null,\"Metrics\":null,\"Scrub\":null,\"Lint\":{\"Enable\":true,\"MandatoryAnnotations\":") //nolint:lll // gofumpt conflicts with lll
|
|
})
|
|
|
|
Convey("lint enabled", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"lint": {
|
|
"enable": "false"
|
|
}
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
So(string(data), ShouldContainSubstring,
|
|
"\"Extensions\":{\"Search\":null,\"Sync\":null,\"Metrics\":null,\"Scrub\":null,\"Lint\":{\"Enable\":false,\"MandatoryAnnotations\":null}") //nolint:lll // gofumpt conflicts with lll
|
|
})
|
|
}
|
|
|
|
func TestServeSearchEnabled(t *testing.T) {
|
|
oldArgs := os.Args
|
|
|
|
defer func() { os.Args = oldArgs }()
|
|
|
|
Convey("search implicitly enabled", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"search": {
|
|
}
|
|
}
|
|
}`
|
|
|
|
tempDir := t.TempDir()
|
|
logPath, err := runCLIWithConfig(tempDir, content)
|
|
So(err, ShouldBeNil)
|
|
// to avoid data race when multiple go routines write to trivy DB instance.
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
substring := `"Extensions":{"Search":{"Enable":true,"CVE":null}`
|
|
|
|
found, err := ReadLogFileAndSearchString(logPath, substring, readLogFileTimeout)
|
|
|
|
if !found {
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
t.Log(string(data))
|
|
}
|
|
|
|
So(found, ShouldBeTrue)
|
|
So(err, ShouldBeNil)
|
|
})
|
|
}
|
|
|
|
func TestServeSearchEnabledDefaultCVEDB(t *testing.T) {
|
|
oldArgs := os.Args
|
|
|
|
defer func() { os.Args = oldArgs }()
|
|
|
|
Convey("search implicitly enabled with CVE param set", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"search": {
|
|
"cve": {
|
|
"updateInterval": "1h"
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
tempDir := t.TempDir()
|
|
logPath, err := runCLIWithConfig(tempDir, content)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
// to avoid data race when multiple go routines write to trivy DB instance.
|
|
WaitTillTrivyDBDownloadStarted(tempDir)
|
|
|
|
// The default config handling logic will convert the 1h interval to a 2h interval
|
|
substring := "\"Search\":{\"Enable\":true,\"CVE\":{\"UpdateInterval\":7200000000000,\"Trivy\":" +
|
|
"{\"DBRepository\":\"ghcr.io/aquasecurity/trivy-db\",\"JavaDBRepository\":\"ghcr.io/aquasecurity/trivy-java-db\"}}}"
|
|
|
|
found, err := ReadLogFileAndSearchString(logPath, substring, readLogFileTimeout)
|
|
|
|
defer func() {
|
|
if !found {
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
t.Log(string(data))
|
|
}
|
|
}()
|
|
|
|
So(found, ShouldBeTrue)
|
|
So(err, ShouldBeNil)
|
|
|
|
found, err = ReadLogFileAndSearchString(logPath, "updating cve-db", readLogFileTimeout)
|
|
So(found, ShouldBeTrue)
|
|
So(err, ShouldBeNil)
|
|
})
|
|
}
|
|
|
|
func TestServeSearchEnabledNoCVE(t *testing.T) {
|
|
oldArgs := os.Args
|
|
|
|
defer func() { os.Args = oldArgs }()
|
|
|
|
Convey("search explicitly enabled, but CVE parameter not set", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"search": {
|
|
"enable": true
|
|
}
|
|
}
|
|
}`
|
|
|
|
tempDir := t.TempDir()
|
|
logPath, err := runCLIWithConfig(tempDir, content)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
substring := `"Extensions":{"Search":{"Enable":true,"CVE":null}` //nolint:lll // gofumpt conflicts with lll
|
|
found, err := ReadLogFileAndSearchString(logPath, substring, readLogFileTimeout)
|
|
|
|
if !found {
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
t.Log(string(data))
|
|
}
|
|
|
|
So(found, ShouldBeTrue)
|
|
So(err, ShouldBeNil)
|
|
})
|
|
}
|
|
|
|
func TestServeSearchDisabled(t *testing.T) {
|
|
oldArgs := os.Args
|
|
|
|
defer func() { os.Args = oldArgs }()
|
|
|
|
Convey("search explicitly disabled", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"search": {
|
|
"enable": false,
|
|
"cve": {
|
|
"updateInterval": "3h"
|
|
}
|
|
}
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
dataStr := string(data)
|
|
So(dataStr, ShouldContainSubstring,
|
|
`"Search":{"Enable":false,"CVE":{"UpdateInterval":10800000000000,"Trivy":null}`)
|
|
So(dataStr, ShouldContainSubstring, "cve config not provided, skipping cve-db update")
|
|
So(dataStr, ShouldNotContainSubstring,
|
|
"CVE update interval set to too-short interval < 2h, changing update duration to 2 hours and continuing.")
|
|
})
|
|
}
|
|
|
|
func TestServeMgmtExtension(t *testing.T) {
|
|
oldArgs := os.Args
|
|
|
|
defer func() { os.Args = oldArgs }()
|
|
|
|
Convey("Mgmt implicitly enabled", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"search": {
|
|
"enable": true
|
|
}
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
found, err := ReadLogFileAndSearchString(logPath, "setting up mgmt routes", 10*time.Second)
|
|
|
|
if !found {
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
t.Log(string(data))
|
|
}
|
|
|
|
So(err, ShouldBeNil)
|
|
So(found, ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Mgmt disabled - Search unconfigured", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
found, err := ReadLogFileAndSearchString(logPath,
|
|
"skip enabling the mgmt route as the config prerequisites are not met", 10*time.Second)
|
|
|
|
if !found {
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
t.Log(string(data))
|
|
}
|
|
|
|
So(err, ShouldBeNil)
|
|
So(found, ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Mgmt disabled - extensions missing", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
found, err := ReadLogFileAndSearchString(logPath,
|
|
"skip enabling the mgmt route as the config prerequisites are not met", 10*time.Second)
|
|
|
|
if !found {
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
t.Log(string(data))
|
|
}
|
|
|
|
So(err, ShouldBeNil)
|
|
So(found, ShouldBeTrue)
|
|
})
|
|
}
|
|
|
|
func TestServeImageTrustExtension(t *testing.T) {
|
|
oldArgs := os.Args
|
|
|
|
defer func() { os.Args = oldArgs }()
|
|
|
|
Convey("Trust explicitly disabled", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"trust": {
|
|
"enable": false
|
|
}
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
found, err := ReadLogFileAndSearchString(logPath,
|
|
"skip enabling the image trust routes as the config prerequisites are not met", 10*time.Second)
|
|
|
|
if !found {
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
t.Log(string(data))
|
|
}
|
|
|
|
So(err, ShouldBeNil)
|
|
So(found, ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Trust explicitly enabled - but cosign and notation disabled", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"trust": {
|
|
"enable": true
|
|
}
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
found, err := ReadLogFileAndSearchString(logPath,
|
|
"skip enabling the image trust routes as the config prerequisites are not met", 10*time.Second)
|
|
|
|
if !found {
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
t.Log(string(data))
|
|
}
|
|
|
|
So(err, ShouldBeNil)
|
|
So(found, ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Trust explicitly enabled - cosign and notation enabled", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"trust": {
|
|
"enable": true,
|
|
"cosign": true,
|
|
"notation": true
|
|
}
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
found, err := ReadLogFileAndSearchString(logPath,
|
|
"setting up image trust routes", 10*time.Second)
|
|
|
|
defer func() {
|
|
if !found {
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
t.Log(string(data))
|
|
}
|
|
}()
|
|
|
|
So(err, ShouldBeNil)
|
|
So(found, ShouldBeTrue)
|
|
|
|
found, err = ReadLogFileAndSearchString(logPath,
|
|
"setting up notation route", 10*time.Second)
|
|
So(err, ShouldBeNil)
|
|
So(found, ShouldBeTrue)
|
|
|
|
found, err = ReadLogFileAndSearchString(logPath,
|
|
"setting up cosign route", 10*time.Second)
|
|
So(err, ShouldBeNil)
|
|
So(found, ShouldBeTrue)
|
|
})
|
|
}
|
|
|
|
func TestOverlappingSyncRetentionConfig(t *testing.T) {
|
|
oldArgs := os.Args
|
|
|
|
defer func() { os.Args = oldArgs }()
|
|
|
|
Convey("Test verify without overlapping sync and retention", t, func(c C) {
|
|
tmpfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(tmpfile.Name()) // clean up
|
|
|
|
content := `{
|
|
"distSpecVersion": "1.1.1",
|
|
"storage": {
|
|
"rootDirectory": "%s",
|
|
"gc": true,
|
|
"gcDelay": "2h",
|
|
"gcInterval": "1h",
|
|
"retention": {
|
|
"policies": [
|
|
{
|
|
"repositories": ["infra/*", "prod/*"],
|
|
"deleteReferrers": false,
|
|
"keepTags": [{
|
|
"patterns": ["v4.*", ".*-prod"]
|
|
},
|
|
{
|
|
"patterns": ["v3.*", ".*-prod"],
|
|
"pulledWithin": "168h"
|
|
}]
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"sync": {
|
|
"enable": true,
|
|
"registries": [
|
|
{
|
|
"urls": [
|
|
"https://registry1:5000"
|
|
],
|
|
"content": [
|
|
{
|
|
"prefix": "infra/*",
|
|
"tags": {
|
|
"regex": "v4.*",
|
|
"semver": true
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
So(string(data), ShouldNotContainSubstring, "overlapping sync content")
|
|
})
|
|
|
|
Convey("Test verify with overlapping sync and retention - retention would remove v4 tags", t, func(c C) {
|
|
tmpfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(tmpfile.Name()) // clean up
|
|
|
|
content := `{
|
|
"distSpecVersion": "1.1.1",
|
|
"storage": {
|
|
"rootDirectory": "%s",
|
|
"gc": true,
|
|
"gcDelay": "2h",
|
|
"gcInterval": "1h",
|
|
"retention": {
|
|
"policies": [
|
|
{
|
|
"repositories": ["infra/*", "prod/*"],
|
|
"keepTags": [{
|
|
"patterns": ["v2.*", ".*-prod"]
|
|
},
|
|
{
|
|
"patterns": ["v3.*", ".*-prod"]
|
|
}]
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"sync": {
|
|
"enable": true,
|
|
"registries": [
|
|
{
|
|
"urls": [
|
|
"https://registry1:5000"
|
|
],
|
|
"content": [
|
|
{
|
|
"prefix": "infra/*",
|
|
"tags": {
|
|
"regex": "4.*",
|
|
"semver": true
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
So(string(data), ShouldContainSubstring, "overlapping sync content\":{\"Prefix\":\"infra/*")
|
|
})
|
|
|
|
Convey("Test verify with overlapping sync and retention - retention would remove tags from repo", t, func(c C) {
|
|
tmpfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(tmpfile.Name()) // clean up
|
|
|
|
content := `{
|
|
"distSpecVersion": "1.1.1",
|
|
"storage": {
|
|
"rootDirectory": "%s",
|
|
"gc": true,
|
|
"gcDelay": "2h",
|
|
"gcInterval": "1h",
|
|
"retention": {
|
|
"dryRun": false,
|
|
"delay": "24h",
|
|
"policies": [
|
|
{
|
|
"repositories": ["tmp/**"],
|
|
"keepTags": [{
|
|
"patterns": ["v1.*"]
|
|
}]
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"sync": {
|
|
"enable": true,
|
|
"registries": [
|
|
{
|
|
"urls": [
|
|
"https://registry1:5000"
|
|
],
|
|
"content": [
|
|
{
|
|
"prefix": "**",
|
|
"destination": "/tmp",
|
|
"stripPrefix": true
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
So(string(data), ShouldContainSubstring, "overlapping sync content\":{\"Prefix\":\"**")
|
|
})
|
|
|
|
Convey("Test verify with overlapping sync and retention - retention would remove tags from subpath", t, func(c C) {
|
|
tmpfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(tmpfile.Name()) // clean up
|
|
|
|
content := `{
|
|
"distSpecVersion": "1.1.1",
|
|
"storage": {
|
|
"rootDirectory": "%s",
|
|
"gc": true,
|
|
"gcDelay": "2h",
|
|
"gcInterval": "1h",
|
|
"subPaths": {
|
|
"/synced": {
|
|
"rootDirectory": "/tmp/zot2",
|
|
"dedupe": true,
|
|
"retention": {
|
|
"policies": [
|
|
{
|
|
"repositories": ["infra/*", "prod/*"],
|
|
"deleteReferrers": false,
|
|
"keepTags": [{
|
|
}]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"sync": {
|
|
"enable": true,
|
|
"registries": [
|
|
{
|
|
"urls": [
|
|
"https://registry1:5000"
|
|
],
|
|
"content": [
|
|
{
|
|
"prefix": "prod/*",
|
|
"destination": "/synced"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
So(string(data), ShouldContainSubstring, "overlapping sync content\":{\"Prefix\":\"prod/*")
|
|
})
|
|
}
|
|
|
|
func TestSyncWithRemoteStorageConfig(t *testing.T) {
|
|
oldArgs := os.Args
|
|
|
|
defer func() { os.Args = oldArgs }()
|
|
|
|
Convey("Test verify sync with remote storage works if sync.tmpdir is provided", t, func(c C) {
|
|
tmpfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(tmpfile.Name()) // clean up
|
|
|
|
content := `{
|
|
"distSpecVersion": "1.1.1",
|
|
"storage": {
|
|
"rootDirectory": "%s",
|
|
"dedupe": false,
|
|
"remoteCache": false,
|
|
"storageDriver": {
|
|
"name": "s3",
|
|
"rootdirectory": "/zot",
|
|
"region": "us-east-2",
|
|
"regionendpoint": "localhost:4566",
|
|
"bucket": "zot-storage",
|
|
"secure": false,
|
|
"skipverify": false
|
|
}
|
|
},
|
|
"http": {
|
|
"address": "0.0.0.0",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"sync": {
|
|
"downloadDir": "/tmp/sync",
|
|
"registries": [
|
|
{
|
|
"urls": [
|
|
"http://localhost:9000"
|
|
],
|
|
"onDemand": true,
|
|
"tlsVerify": false,
|
|
"content": [
|
|
{
|
|
"prefix": "**"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
So(string(data), ShouldNotContainSubstring,
|
|
"using both sync and remote storage features needs config.Extensions.Sync.DownloadDir to be specified")
|
|
})
|
|
|
|
Convey("Test verify sync with remote storage panics if sync.tmpdir is not provided", t, func(c C) {
|
|
port := GetFreePort()
|
|
logFile, err := os.CreateTemp("", "zot-log*.txt")
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logFile.Name()) // clean up
|
|
|
|
tmpfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(tmpfile.Name()) // clean up
|
|
|
|
content := fmt.Sprintf(`{
|
|
"distSpecVersion": "1.1.1",
|
|
"storage": {
|
|
"rootDirectory": "%s",
|
|
"dedupe": false,
|
|
"remoteCache": false,
|
|
"storageDriver": {
|
|
"name": "s3",
|
|
"rootdirectory": "/zot",
|
|
"region": "us-east-2",
|
|
"regionendpoint": "localhost:4566",
|
|
"bucket": "zot-storage",
|
|
"secure": false,
|
|
"skipverify": false
|
|
}
|
|
},
|
|
"http": {
|
|
"address": "0.0.0.0",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"sync": {
|
|
"registries": [
|
|
{
|
|
"urls": [
|
|
"http://localhost:9000"
|
|
],
|
|
"onDemand": true,
|
|
"tlsVerify": false,
|
|
"content": [
|
|
{
|
|
"prefix": "**"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}`, t.TempDir(), port, logFile.Name())
|
|
|
|
err = os.WriteFile(tmpfile.Name(), []byte(content), 0o0600)
|
|
So(err, ShouldBeNil)
|
|
|
|
os.Args = []string{"cli_test", "serve", tmpfile.Name()}
|
|
err = cli.NewServerRootCmd().Execute()
|
|
So(err, ShouldNotBeNil)
|
|
|
|
data, err := os.ReadFile(logFile.Name())
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logFile.Name()) // clean up
|
|
|
|
So(string(data), ShouldContainSubstring,
|
|
"using both sync and remote storage features needs config.Extensions.Sync.DownloadDir to be specified")
|
|
})
|
|
|
|
Convey("Test verify sync with remote storage on subpath panics if sync.tmpdir is not provided", t, func(c C) {
|
|
port := GetFreePort()
|
|
logFile, err := os.CreateTemp("", "zot-log*.txt")
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(logFile.Name()) // clean up
|
|
|
|
tmpfile, err := os.CreateTemp("", "zot-test*.json")
|
|
So(err, ShouldBeNil)
|
|
|
|
defer os.Remove(tmpfile.Name()) // clean up
|
|
|
|
content := fmt.Sprintf(`{
|
|
"distSpecVersion": "1.1.1",
|
|
"storage": {
|
|
"rootDirectory": "%s",
|
|
"subPaths":{
|
|
"/a": {
|
|
"rootDirectory": "%s",
|
|
"dedupe": false,
|
|
"remoteCache": false,
|
|
"storageDriver":{
|
|
"name":"s3",
|
|
"rootdirectory":"/zot-a",
|
|
"region":"us-east-2",
|
|
"bucket":"zot-storage",
|
|
"secure":true,
|
|
"skipverify":true
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"http": {
|
|
"address": "0.0.0.0",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"sync": {
|
|
"registries": [
|
|
{
|
|
"urls": [
|
|
"http://localhost:9000"
|
|
],
|
|
"onDemand": true,
|
|
"tlsVerify": false,
|
|
"content": [
|
|
{
|
|
"prefix": "**"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}`, t.TempDir(), t.TempDir(), port, logFile.Name())
|
|
|
|
err = os.WriteFile(tmpfile.Name(), []byte(content), 0o0600)
|
|
So(err, ShouldBeNil)
|
|
|
|
os.Args = []string{"cli_test", "serve", tmpfile.Name()}
|
|
err = cli.NewServerRootCmd().Execute()
|
|
So(err, ShouldNotBeNil)
|
|
|
|
data, err := os.ReadFile(logFile.Name())
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(logFile.Name()) // clean up
|
|
|
|
So(string(data), ShouldContainSubstring,
|
|
"using both sync and remote storage features needs config.Extensions.Sync.DownloadDir to be specified")
|
|
})
|
|
}
|
|
|
|
func TestEventsExtension(t *testing.T) {
|
|
oldArgs := os.Args
|
|
|
|
defer func() { os.Args = oldArgs }()
|
|
|
|
Convey("Events explicitly disabled", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"events": {
|
|
"enable": false
|
|
}
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
So(err, ShouldBeNil)
|
|
defer os.Remove(logPath) // clean up
|
|
|
|
found, err := ReadLogFileAndSearchString(logPath,
|
|
"events disabled in configuration", 10*time.Second)
|
|
|
|
if !found {
|
|
data, err := os.ReadFile(logPath)
|
|
So(err, ShouldBeNil)
|
|
t.Log(string(data))
|
|
}
|
|
|
|
So(err, ShouldBeNil)
|
|
So(found, ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Unsupported event sink", t, func(c C) {
|
|
content := `{
|
|
"storage": {
|
|
"rootDirectory": "%s"
|
|
},
|
|
"http": {
|
|
"address": "127.0.0.1",
|
|
"port": "%s"
|
|
},
|
|
"log": {
|
|
"level": "debug",
|
|
"output": "%s"
|
|
},
|
|
"extensions": {
|
|
"events": {
|
|
"enable": true,
|
|
"sinks": [{
|
|
"type": "unsupported"
|
|
}]
|
|
}
|
|
}
|
|
}`
|
|
|
|
logPath, err := runCLIWithConfig(t.TempDir(), content)
|
|
defer func(p string) {
|
|
if p != "" {
|
|
os.Remove(p)
|
|
}
|
|
}(logPath) // clean up
|
|
So(err, ShouldNotBeNil)
|
|
So(err.Error(), ShouldContainSubstring, zerr.ErrUnsupportedEventSink.Error())
|
|
})
|
|
}
|