mirror of
https://github.com/project-zot/zot.git
synced 2026-06-17 21:17:58 +08:00
config: support multiple storage locations
added support to point multiple storage locations in zot by running multiple instance of zot in background. see examples/config-multiple.json for more info about config. Closes #181
This commit is contained in:
committed by
Ramkumar Chinchani
parent
9ca6eea940
commit
28974e81dc
@@ -1,11 +1,12 @@
|
||||
package compliance
|
||||
|
||||
type Config struct {
|
||||
Address string
|
||||
Port string
|
||||
Version string
|
||||
OutputJSON bool
|
||||
Compliance bool
|
||||
Address string
|
||||
Port string
|
||||
Version string
|
||||
StorageInfo []string
|
||||
OutputJSON bool
|
||||
Compliance bool
|
||||
}
|
||||
|
||||
func NewConfig() *Config {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -46,6 +47,8 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
|
||||
|
||||
baseURL := fmt.Sprintf("http://%s:%s", config.Address, config.Port)
|
||||
|
||||
storageInfo := config.StorageInfo
|
||||
|
||||
fmt.Println("------------------------------")
|
||||
fmt.Println("Checking for v1.0.0 compliance")
|
||||
fmt.Println("------------------------------")
|
||||
@@ -459,6 +462,11 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
|
||||
loc := Location(baseURL, resp)
|
||||
So(loc, ShouldNotBeEmpty)
|
||||
|
||||
// since we are not specifying any prefix i.e provided in config while starting server,
|
||||
// so it should store repo7 to global root dir
|
||||
_, err = os.Stat(path.Join(storageInfo[0], "repo7"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
resp, err = resty.R().Get(loc)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 204)
|
||||
@@ -686,6 +694,276 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 202)
|
||||
})
|
||||
|
||||
Convey("Multiple Storage", func() {
|
||||
// test APIS on subpath routes, default storage already tested above
|
||||
// subpath route firsttest
|
||||
resp, err := resty.R().Post(baseURL + "/v2/firsttest/first/blobs/uploads/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 202)
|
||||
firstloc := Location(baseURL, resp)
|
||||
So(firstloc, ShouldNotBeEmpty)
|
||||
|
||||
resp, err = resty.R().Get(firstloc)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 204)
|
||||
|
||||
// if firsttest route is used as prefix in url that means repo should be stored in subpaths["firsttest"] rootdir
|
||||
_, err = os.Stat(path.Join(storageInfo[1], "firsttest/first"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// subpath route secondtest
|
||||
resp, err = resty.R().Post(baseURL + "/v2/secondtest/second/blobs/uploads/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 202)
|
||||
secondloc := Location(baseURL, resp)
|
||||
So(secondloc, ShouldNotBeEmpty)
|
||||
|
||||
resp, err = resty.R().Get(secondloc)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 204)
|
||||
|
||||
// if secondtest route is used as prefix in url that means repo should be stored in subpaths["secondtest"] rootdir
|
||||
_, err = os.Stat(path.Join(storageInfo[2], "secondtest/second"))
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
content := []byte("this is a blob5")
|
||||
digest := godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
// monolithic blob upload: success
|
||||
// first test
|
||||
resp, err = resty.R().SetQueryParam("digest", digest.String()).
|
||||
SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(firstloc)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 201)
|
||||
firstblobLoc := resp.Header().Get("Location")
|
||||
So(firstblobLoc, ShouldNotBeEmpty)
|
||||
So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
|
||||
So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty)
|
||||
|
||||
// second test
|
||||
resp, err = resty.R().SetQueryParam("digest", digest.String()).
|
||||
SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(secondloc)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 201)
|
||||
secondblobLoc := resp.Header().Get("Location")
|
||||
So(secondblobLoc, ShouldNotBeEmpty)
|
||||
So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
|
||||
So(resp.Header().Get(api.DistContentDigestKey), ShouldNotBeEmpty)
|
||||
|
||||
// check a non-existent manifest
|
||||
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
|
||||
SetBody(content).Head(baseURL + "/v2/unknown/manifests/test:1.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
|
||||
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
|
||||
SetBody(content).Head(baseURL + "/v2/firsttest/unknown/manifests/test:1.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
|
||||
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
|
||||
SetBody(content).Head(baseURL + "/v2/secondtest/unknown/manifests/test:1.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
|
||||
// create a manifest
|
||||
m := ispec.Manifest{
|
||||
Config: ispec.Descriptor{
|
||||
Digest: digest,
|
||||
Size: int64(len(content)),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||
Digest: digest,
|
||||
Size: int64(len(content)),
|
||||
},
|
||||
},
|
||||
}
|
||||
m.SchemaVersion = 2
|
||||
content, err = json.Marshal(m)
|
||||
So(err, ShouldBeNil)
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
// subpath firsttest
|
||||
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
|
||||
SetBody(content).Put(baseURL + "/v2/firsttest/first/manifests/test:1.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 201)
|
||||
d := resp.Header().Get(api.DistContentDigestKey)
|
||||
So(d, ShouldNotBeEmpty)
|
||||
So(d, ShouldEqual, digest.String())
|
||||
|
||||
// subpath secondtest
|
||||
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
|
||||
SetBody(content).Put(baseURL + "/v2/secondtest/second/manifests/test:1.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 201)
|
||||
d = resp.Header().Get(api.DistContentDigestKey)
|
||||
So(d, ShouldNotBeEmpty)
|
||||
So(d, ShouldEqual, digest.String())
|
||||
|
||||
content = []byte("this is a blob5")
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
// create a manifest with same blob but a different tag
|
||||
m = ispec.Manifest{
|
||||
Config: ispec.Descriptor{
|
||||
Digest: digest,
|
||||
Size: int64(len(content)),
|
||||
},
|
||||
Layers: []ispec.Descriptor{
|
||||
{
|
||||
MediaType: "application/vnd.oci.image.layer.v1.tar",
|
||||
Digest: digest,
|
||||
Size: int64(len(content)),
|
||||
},
|
||||
},
|
||||
}
|
||||
m.SchemaVersion = 2
|
||||
content, err = json.Marshal(m)
|
||||
So(err, ShouldBeNil)
|
||||
digest = godigest.FromBytes(content)
|
||||
So(digest, ShouldNotBeNil)
|
||||
|
||||
// subpath firsttest
|
||||
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
|
||||
SetBody(content).Put(baseURL + "/v2/firsttest/first/manifests/test:2.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 201)
|
||||
d = resp.Header().Get(api.DistContentDigestKey)
|
||||
So(d, ShouldNotBeEmpty)
|
||||
So(d, ShouldEqual, digest.String())
|
||||
|
||||
// subpath secondtest
|
||||
resp, err = resty.R().SetHeader("Content-Type", "application/vnd.oci.image.manifest.v1+json").
|
||||
SetBody(content).Put(baseURL + "/v2/secondtest/second/manifests/test:2.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 201)
|
||||
d = resp.Header().Get(api.DistContentDigestKey)
|
||||
So(d, ShouldNotBeEmpty)
|
||||
So(d, ShouldEqual, digest.String())
|
||||
|
||||
// check/get by tag
|
||||
resp, err = resty.R().Head(baseURL + "/v2/firsttest/first/manifests/test:1.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
resp, err = resty.R().Get(baseURL + "/v2/firsttest/first/manifests/test:1.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
So(resp.Body(), ShouldNotBeEmpty)
|
||||
resp, err = resty.R().Head(baseURL + "/v2/secondtest/second/manifests/test:1.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
resp, err = resty.R().Get(baseURL + "/v2/secondtest/second/manifests/test:1.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
So(resp.Body(), ShouldNotBeEmpty)
|
||||
|
||||
// check/get by reference
|
||||
resp, err = resty.R().Head(baseURL + "/v2/firsttest/first/manifests/" + digest.String())
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
resp, err = resty.R().Get(baseURL + "/v2/firsttest/first/manifests/" + digest.String())
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
So(resp.Body(), ShouldNotBeEmpty)
|
||||
|
||||
resp, err = resty.R().Head(baseURL + "/v2/secondtest/second/manifests/" + digest.String())
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
resp, err = resty.R().Get(baseURL + "/v2/secondtest/second/manifests/" + digest.String())
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
So(resp.Body(), ShouldNotBeEmpty)
|
||||
|
||||
// delete manifest by tag should fail
|
||||
resp, err = resty.R().Delete(baseURL + "/v2/firsttest/first/manifests/test:1.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 400)
|
||||
|
||||
resp, err = resty.R().Delete(baseURL + "/v2/secondtest/second/manifests/test:1.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 400)
|
||||
|
||||
// delete manifest by digest
|
||||
resp, err = resty.R().Delete(baseURL + "/v2/firsttest/first/manifests/" + digest.String())
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 202)
|
||||
|
||||
resp, err = resty.R().Delete(baseURL + "/v2/secondtest/second/manifests/" + digest.String())
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 202)
|
||||
|
||||
// delete manifest by digest
|
||||
resp, err = resty.R().Delete(baseURL + "/v2/firsttest/first/manifests/" + digest.String())
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
|
||||
resp, err = resty.R().Delete(baseURL + "/v2/secondtest/second/manifests/" + digest.String())
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
|
||||
// delete again should fail
|
||||
resp, err = resty.R().Delete(baseURL + "/v2/firsttest/first/manifests/" + digest.String())
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
|
||||
resp, err = resty.R().Delete(baseURL + "/v2/secondtest/second/manifests/" + digest.String())
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
|
||||
// check/get by tag
|
||||
resp, err = resty.R().Head(baseURL + "/v2/firsttest/first/manifests/test:1.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
resp, err = resty.R().Get(baseURL + "/v2/firsttest/first/manifests/test:1.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
So(resp.Body(), ShouldNotBeEmpty)
|
||||
|
||||
resp, err = resty.R().Head(baseURL + "/v2/secondtest/second/manifests/test:1.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
resp, err = resty.R().Get(baseURL + "/v2/secondtest/second/manifests/test:1.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
So(resp.Body(), ShouldNotBeEmpty)
|
||||
|
||||
resp, err = resty.R().Head(baseURL + "/v2/firsttest/first/repo7/manifests/test:2.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
resp, err = resty.R().Get(baseURL + "/v2/firsttest/first/manifests/test:2.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
So(resp.Body(), ShouldNotBeEmpty)
|
||||
|
||||
resp, err = resty.R().Head(baseURL + "/v2/secondtest/second/manifests/test:2.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
resp, err = resty.R().Get(baseURL + "/v2/secondtest/second/manifests/test:2.0")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
So(resp.Body(), ShouldNotBeEmpty)
|
||||
|
||||
// check/get by reference
|
||||
resp, err = resty.R().Head(baseURL + "/v2/firsttest/first/manifests/" + digest.String())
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
resp, err = resty.R().Get(baseURL + "/v2/firsttest/first/manifests/" + digest.String())
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
So(resp.Body(), ShouldNotBeEmpty)
|
||||
|
||||
resp, err = resty.R().Head(baseURL + "/v2/secondtest/second/manifests/" + digest.String())
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
resp, err = resty.R().Get(baseURL + "/v2/secondtest/second/manifests/" + digest.String())
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
So(resp.Body(), ShouldNotBeEmpty)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -18,24 +18,35 @@ import (
|
||||
// nolint: gochecknoglobals
|
||||
var (
|
||||
listenAddress = "127.0.0.1"
|
||||
defaultDir = ""
|
||||
firstDir = ""
|
||||
secondDir = ""
|
||||
)
|
||||
|
||||
func TestWorkflows(t *testing.T) {
|
||||
ctrl, randomPort := startServer()
|
||||
defer stopServer(ctrl)
|
||||
|
||||
storageInfo := []string{defaultDir, firstDir, secondDir}
|
||||
|
||||
v1_0_0.CheckWorkflows(t, &compliance.Config{
|
||||
Address: listenAddress,
|
||||
Port: randomPort,
|
||||
Address: listenAddress,
|
||||
Port: randomPort,
|
||||
StorageInfo: storageInfo,
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkflowsOutputJSON(t *testing.T) {
|
||||
ctrl, randomPort := startServer()
|
||||
defer stopServer(ctrl)
|
||||
|
||||
storageInfo := []string{defaultDir, firstDir, secondDir}
|
||||
|
||||
v1_0_0.CheckWorkflows(t, &compliance.Config{
|
||||
Address: listenAddress,
|
||||
Port: randomPort,
|
||||
OutputJSON: true,
|
||||
Address: listenAddress,
|
||||
Port: randomPort,
|
||||
OutputJSON: true,
|
||||
StorageInfo: storageInfo,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -59,8 +70,31 @@ func startServer() (*api.Controller, string) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defaultDir = dir
|
||||
|
||||
firstSubDir, err := ioutil.TempDir("", "oci-repo-test")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
firstDir = firstSubDir
|
||||
|
||||
secondSubDir, err := ioutil.TempDir("", "oci-repo-test")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
secondDir = secondSubDir
|
||||
|
||||
subPaths := make(map[string]api.StorageConfig)
|
||||
|
||||
subPaths["/firsttest"] = api.StorageConfig{RootDirectory: firstSubDir}
|
||||
subPaths["/secondtest"] = api.StorageConfig{RootDirectory: secondSubDir}
|
||||
|
||||
ctrl.Config.Storage.RootDirectory = dir
|
||||
|
||||
ctrl.Config.Storage.SubPaths = subPaths
|
||||
|
||||
go func() {
|
||||
// this blocks
|
||||
if err := ctrl.Run(); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user