mirror of
https://github.com/project-zot/zot.git
synced 2026-06-16 04:17:55 +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
+9
-2
@@ -72,11 +72,18 @@ type LogConfig struct {
|
||||
Output string
|
||||
}
|
||||
|
||||
type GlobalStorageConfig struct {
|
||||
RootDirectory string
|
||||
Dedupe bool
|
||||
GC bool
|
||||
SubPaths map[string]StorageConfig
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Version string
|
||||
Commit string
|
||||
BinaryType string
|
||||
Storage StorageConfig
|
||||
Storage GlobalStorageConfig
|
||||
HTTP HTTPConfig
|
||||
Log *LogConfig
|
||||
Extensions *ext.ExtensionConfig
|
||||
@@ -87,7 +94,7 @@ func NewConfig() *Config {
|
||||
Version: dspec.Version,
|
||||
Commit: Commit,
|
||||
BinaryType: BinaryType,
|
||||
Storage: StorageConfig{GC: true, Dedupe: true},
|
||||
Storage: GlobalStorageConfig{GC: true, Dedupe: true},
|
||||
HTTP: HTTPConfig{Address: "127.0.0.1", Port: "8080"},
|
||||
Log: &LogConfig{Level: "debug"},
|
||||
}
|
||||
|
||||
+66
-18
@@ -7,7 +7,6 @@ import (
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/anuvu/zot/errors"
|
||||
@@ -23,11 +22,11 @@ const (
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
Config *Config
|
||||
Router *mux.Router
|
||||
ImageStore *storage.ImageStore
|
||||
Log log.Logger
|
||||
Server *http.Server
|
||||
Config *Config
|
||||
Router *mux.Router
|
||||
StoreController storage.StoreController
|
||||
Log log.Logger
|
||||
Server *http.Server
|
||||
}
|
||||
|
||||
func NewController(config *Config) *Controller {
|
||||
@@ -63,20 +62,69 @@ func (c *Controller) Run() error {
|
||||
handlers.RecoveryHandler(handlers.RecoveryLogger(c.Log),
|
||||
handlers.PrintRecoveryStack(false)))
|
||||
|
||||
c.ImageStore = storage.NewImageStore(c.Config.Storage.RootDirectory, c.Config.Storage.GC,
|
||||
c.Config.Storage.Dedupe, c.Log)
|
||||
if c.ImageStore == nil {
|
||||
// we can't proceed without at least a image store
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Enable extensions if extension config is provided
|
||||
if c.Config != nil && c.Config.Extensions != nil {
|
||||
ext.EnableExtensions(c.Config.Extensions, c.Log, c.Config.Storage.RootDirectory)
|
||||
}
|
||||
|
||||
c.Router = engine
|
||||
c.Router.UseEncodedPath()
|
||||
|
||||
c.StoreController = storage.StoreController{}
|
||||
|
||||
if c.Config.Storage.RootDirectory != "" {
|
||||
if c.Config.Storage.Dedupe {
|
||||
err := storage.ValidateHardLink(c.Config.Storage.RootDirectory)
|
||||
if err != nil {
|
||||
c.Log.Warn().Msg("input storage root directory filesystem does not supports hardlinking," +
|
||||
"disabling dedupe functionality")
|
||||
|
||||
c.Config.Storage.Dedupe = false
|
||||
}
|
||||
}
|
||||
|
||||
defaultStore := storage.NewImageStore(c.Config.Storage.RootDirectory,
|
||||
c.Config.Storage.GC, c.Config.Storage.Dedupe, c.Log)
|
||||
|
||||
c.StoreController.DefaultStore = defaultStore
|
||||
|
||||
// Enable extensions if extension config is provided
|
||||
if c.Config != nil && c.Config.Extensions != nil {
|
||||
ext.EnableExtensions(c.Config.Extensions, c.Log, c.Config.Storage.RootDirectory)
|
||||
}
|
||||
} else {
|
||||
// we can't proceed without global storage
|
||||
c.Log.Error().Err(errors.ErrImgStoreNotFound).Msg("controller: no storage config provided")
|
||||
|
||||
return errors.ErrImgStoreNotFound
|
||||
}
|
||||
|
||||
if c.Config.Storage.SubPaths != nil {
|
||||
if len(c.Config.Storage.SubPaths) > 0 {
|
||||
subPaths := c.Config.Storage.SubPaths
|
||||
|
||||
subImageStore := make(map[string]*storage.ImageStore)
|
||||
|
||||
// creating image store per subpaths
|
||||
for route, storageConfig := range subPaths {
|
||||
if storageConfig.Dedupe {
|
||||
err := storage.ValidateHardLink(storageConfig.RootDirectory)
|
||||
if err != nil {
|
||||
c.Log.Warn().Msg("input storage root directory filesystem does not supports hardlinking, " +
|
||||
"disabling dedupe functionality")
|
||||
|
||||
storageConfig.Dedupe = false
|
||||
}
|
||||
}
|
||||
|
||||
subImageStore[route] = storage.NewImageStore(storageConfig.RootDirectory,
|
||||
storageConfig.GC, storageConfig.Dedupe, c.Log)
|
||||
|
||||
// Enable extensions if extension config is provided
|
||||
if c.Config != nil && c.Config.Extensions != nil {
|
||||
ext.EnableExtensions(c.Config.Extensions, c.Log, storageConfig.RootDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
c.StoreController.SubStore = subImageStore
|
||||
}
|
||||
}
|
||||
|
||||
_ = NewRouteHandler(c)
|
||||
|
||||
addr := fmt.Sprintf("%s:%s", c.Config.HTTP.Address, c.Config.HTTP.Port)
|
||||
|
||||
+228
-15
@@ -371,6 +371,136 @@ func TestBasicAuth(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestMultipleInstance(t *testing.T) {
|
||||
Convey("Negative test zot multiple instance", t, func() {
|
||||
config := api.NewConfig()
|
||||
config.HTTP.Port = SecurePort1
|
||||
htpasswdPath := makeHtpasswdFile()
|
||||
defer os.Remove(htpasswdPath)
|
||||
|
||||
config.HTTP.Auth = &api.AuthConfig{
|
||||
HTPasswd: api.AuthHTPasswd{
|
||||
Path: htpasswdPath,
|
||||
},
|
||||
}
|
||||
c := api.NewController(config)
|
||||
err := c.Run()
|
||||
So(err, ShouldEqual, errors.ErrImgStoreNotFound)
|
||||
|
||||
globalDir, err := ioutil.TempDir("", "oci-repo-test")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(globalDir)
|
||||
|
||||
subDir, err := ioutil.TempDir("/tmp", "oci-sub-test")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(subDir)
|
||||
|
||||
c.Config.Storage.RootDirectory = globalDir
|
||||
subPathMap := make(map[string]api.StorageConfig)
|
||||
|
||||
subPathMap["/a"] = api.StorageConfig{RootDirectory: subDir}
|
||||
|
||||
go func() {
|
||||
if err := c.Run(); err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// wait till ready
|
||||
for {
|
||||
_, err := resty.R().Get(BaseURL1)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
ctx := context.Background()
|
||||
_ = c.Server.Shutdown(ctx)
|
||||
}()
|
||||
|
||||
client := resty.New()
|
||||
|
||||
tagResponse, err := client.R().SetBasicAuth(username, passphrase).
|
||||
Get(BaseURL1 + "/v2/zot-test/tags/list")
|
||||
So(err, ShouldBeNil)
|
||||
So(tagResponse.StatusCode(), ShouldEqual, 404)
|
||||
})
|
||||
|
||||
Convey("Test zot multiple instance", t, func() {
|
||||
config := api.NewConfig()
|
||||
config.HTTP.Port = SecurePort1
|
||||
htpasswdPath := makeHtpasswdFile()
|
||||
defer os.Remove(htpasswdPath)
|
||||
|
||||
config.HTTP.Auth = &api.AuthConfig{
|
||||
HTPasswd: api.AuthHTPasswd{
|
||||
Path: htpasswdPath,
|
||||
},
|
||||
}
|
||||
c := api.NewController(config)
|
||||
globalDir, err := ioutil.TempDir("", "oci-repo-test")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(globalDir)
|
||||
|
||||
subDir, err := ioutil.TempDir("/tmp", "oci-sub-test")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(subDir)
|
||||
|
||||
c.Config.Storage.RootDirectory = globalDir
|
||||
subPathMap := make(map[string]api.StorageConfig)
|
||||
|
||||
subPathMap["/a"] = api.StorageConfig{RootDirectory: subDir}
|
||||
go func() {
|
||||
// this blocks
|
||||
if err := c.Run(); err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
// wait till ready
|
||||
for {
|
||||
_, err := resty.R().Get(BaseURL1)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
ctx := context.Background()
|
||||
_ = c.Server.Shutdown(ctx)
|
||||
}()
|
||||
|
||||
// without creds, should get access error
|
||||
resp, err := resty.R().Get(BaseURL1 + "/v2/")
|
||||
So(err, ShouldBeNil)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 401)
|
||||
var e api.Error
|
||||
err = json.Unmarshal(resp.Body(), &e)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// with creds, should get expected status code
|
||||
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(BaseURL1)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 404)
|
||||
|
||||
resp, _ = resty.R().SetBasicAuth(username, passphrase).Get(BaseURL1 + "/v2/")
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, 200)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTLSWithBasicAuth(t *testing.T) {
|
||||
Convey("Make a new controller", t, func() {
|
||||
caCert, err := ioutil.ReadFile(CACert)
|
||||
@@ -1820,13 +1950,13 @@ func TestParallelRequests(t *testing.T) {
|
||||
{
|
||||
srcImageName: "zot-cve-test",
|
||||
srcImageTag: "0.0.1",
|
||||
destImageName: "zot-3-test",
|
||||
destImageName: "a/zot-3-test",
|
||||
testCaseName: "Request-3",
|
||||
},
|
||||
{
|
||||
srcImageName: "zot-cve-test",
|
||||
srcImageTag: "0.0.1",
|
||||
destImageName: "zot-4-test",
|
||||
destImageName: "b/zot-4-test",
|
||||
testCaseName: "Request-4",
|
||||
},
|
||||
{
|
||||
@@ -1881,13 +2011,13 @@ func TestParallelRequests(t *testing.T) {
|
||||
{
|
||||
srcImageName: "zot-cve-test",
|
||||
srcImageTag: "0.0.1",
|
||||
destImageName: "zot-3-test",
|
||||
destImageName: "a/zot-3-test",
|
||||
testCaseName: "Request-13",
|
||||
},
|
||||
{
|
||||
srcImageName: "zot-cve-test",
|
||||
srcImageTag: "0.0.1",
|
||||
destImageName: "zot-4-test",
|
||||
destImageName: "b/zot-4-test",
|
||||
testCaseName: "Request-14",
|
||||
},
|
||||
}
|
||||
@@ -1896,8 +2026,6 @@ func TestParallelRequests(t *testing.T) {
|
||||
config.HTTP.Port = SecurePort1
|
||||
htpasswdPath := makeHtpasswdFileFromString(getCredString(username, passphrase))
|
||||
|
||||
// defer os.Remove(htpasswdPath)
|
||||
|
||||
config.HTTP.Auth = &api.AuthConfig{
|
||||
HTPasswd: api.AuthHTPasswd{
|
||||
Path: htpasswdPath,
|
||||
@@ -1911,11 +2039,23 @@ func TestParallelRequests(t *testing.T) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = copyFiles("../../test/data", dir)
|
||||
firstSubDir, err := ioutil.TempDir("", "oci-sub-dir")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//defer os.RemoveAll(dir)
|
||||
|
||||
secondSubDir, err := ioutil.TempDir("", "oci-sub-dir")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
subPaths := make(map[string]api.StorageConfig)
|
||||
|
||||
subPaths["/a"] = api.StorageConfig{RootDirectory: firstSubDir}
|
||||
subPaths["/b"] = api.StorageConfig{RootDirectory: secondSubDir}
|
||||
|
||||
c.Config.Storage.SubPaths = subPaths
|
||||
|
||||
c.Config.Storage.RootDirectory = dir
|
||||
|
||||
go func() {
|
||||
@@ -1939,7 +2079,7 @@ func TestParallelRequests(t *testing.T) {
|
||||
for i, testcase := range testCases {
|
||||
testcase := testcase
|
||||
j := i
|
||||
//println(i)
|
||||
|
||||
t.Run(testcase.testCaseName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := resty.New()
|
||||
@@ -1949,7 +2089,7 @@ func TestParallelRequests(t *testing.T) {
|
||||
assert.Equal(t, err, nil, "Error should be nil")
|
||||
assert.NotEqual(t, tagResponse.StatusCode(), 400, "bad request")
|
||||
|
||||
manifestList := getAllManifests(path.Join(c.Config.Storage.RootDirectory, testcase.srcImageName))
|
||||
manifestList := getAllManifests(path.Join("../../test/data", testcase.srcImageName))
|
||||
|
||||
for _, manifest := range manifestList {
|
||||
headResponse, err := client.R().SetBasicAuth(username, passphrase).
|
||||
@@ -1963,7 +2103,7 @@ func TestParallelRequests(t *testing.T) {
|
||||
assert.Equal(t, getResponse.StatusCode(), 404, "response status code should return 404")
|
||||
}
|
||||
|
||||
blobList := getAllBlobs(path.Join(c.Config.Storage.RootDirectory, testcase.srcImageName))
|
||||
blobList := getAllBlobs(path.Join("../../test/data", testcase.srcImageName))
|
||||
|
||||
for _, blob := range blobList {
|
||||
// Get request of blob
|
||||
@@ -1981,7 +2121,7 @@ func TestParallelRequests(t *testing.T) {
|
||||
assert.Equal(t, err, nil, "Should not be nil")
|
||||
assert.NotEqual(t, getResponse.StatusCode(), 500, "internal server error should not occurred")
|
||||
|
||||
blobPath := path.Join(c.Config.Storage.RootDirectory, testcase.srcImageName, "blobs/sha256", blob)
|
||||
blobPath := path.Join("../../test/data", testcase.srcImageName, "blobs/sha256", blob)
|
||||
|
||||
buf, err := ioutil.ReadFile(blobPath)
|
||||
if err != nil {
|
||||
@@ -2050,9 +2190,6 @@ func TestParallelRequests(t *testing.T) {
|
||||
SetHeader("Content-Range", fmt.Sprintf("%d", readContent)+"-"+fmt.Sprintf("%d", readContent+n-1)).
|
||||
SetBasicAuth(username, passphrase).
|
||||
Patch(BaseURL1 + "/v2/" + testcase.destImageName + "/blobs/uploads/" + sessionID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, err, nil, "Error should be nil")
|
||||
assert.NotEqual(t, patchResponse.StatusCode(), 500, "response status code should not return 500")
|
||||
@@ -2273,3 +2410,79 @@ func stopServer(ctrl *api.Controller) {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHardLink(t *testing.T) {
|
||||
Convey("Validate hard link", t, func() {
|
||||
config := api.NewConfig()
|
||||
config.HTTP.Port = SecurePort1
|
||||
htpasswdPath := makeHtpasswdFileFromString(getCredString(username, passphrase))
|
||||
|
||||
config.HTTP.Auth = &api.AuthConfig{
|
||||
HTPasswd: api.AuthHTPasswd{
|
||||
Path: htpasswdPath,
|
||||
},
|
||||
}
|
||||
|
||||
c := api.NewController(config)
|
||||
|
||||
dir, err := ioutil.TempDir("", "hard-link-test")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
err = os.Chmod(dir, 0400)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
subDir, err := ioutil.TempDir("", "sub-hardlink-test")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer os.RemoveAll(subDir)
|
||||
|
||||
err = os.Chmod(subDir, 0400)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Config.Storage.RootDirectory = dir
|
||||
subPaths := make(map[string]api.StorageConfig)
|
||||
|
||||
subPaths["/a"] = api.StorageConfig{RootDirectory: subDir, Dedupe: true}
|
||||
|
||||
c.Config.Storage.SubPaths = subPaths
|
||||
|
||||
go func() {
|
||||
// this blocks
|
||||
if err := c.Run(); err != nil {
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
// wait till ready
|
||||
for {
|
||||
_, err := resty.R().Get(BaseURL1)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
err = os.Chmod(dir, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = os.Chmod(subDir, 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
So(c.Config.Storage.Dedupe, ShouldEqual, false)
|
||||
})
|
||||
}
|
||||
|
||||
+85
-28
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/anuvu/zot/errors"
|
||||
ext "github.com/anuvu/zot/pkg/extensions"
|
||||
"github.com/anuvu/zot/pkg/log"
|
||||
"github.com/anuvu/zot/pkg/storage"
|
||||
"github.com/gorilla/mux"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
@@ -90,7 +91,7 @@ func (rh *RouteHandler) SetupRoutes() {
|
||||
rh.c.Router.PathPrefix("/swagger/v2/").Methods("GET").Handler(httpSwagger.WrapHandler)
|
||||
// Setup Extensions Routes
|
||||
if rh.c.Config != nil && rh.c.Config.Extensions != nil {
|
||||
ext.SetupRoutes(rh.c.Router, rh.c.Config.Storage.RootDirectory, rh.c.ImageStore, rh.c.Log)
|
||||
ext.SetupRoutes(rh.c.Router, rh.c.StoreController, rh.c.Log)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +140,7 @@ type ImageTags struct {
|
||||
// @Failure 400 {string} string "bad request".
|
||||
func (rh *RouteHandler) ListTags(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
name, ok := vars["name"]
|
||||
|
||||
if !ok || name == "" {
|
||||
@@ -146,11 +148,11 @@ func (rh *RouteHandler) ListTags(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
is := rh.getImageStore(name)
|
||||
|
||||
paginate := false
|
||||
n := -1
|
||||
|
||||
var err error
|
||||
|
||||
nQuery, ok := r.URL.Query()["n"]
|
||||
|
||||
if ok {
|
||||
@@ -161,6 +163,8 @@ func (rh *RouteHandler) ListTags(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var n1 int64
|
||||
|
||||
var err error
|
||||
|
||||
if n1, err = strconv.ParseInt(nQuery[0], 10, 0); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
@@ -182,7 +186,7 @@ func (rh *RouteHandler) ListTags(w http.ResponseWriter, r *http.Request) {
|
||||
last = lastQuery[0]
|
||||
}
|
||||
|
||||
tags, err := rh.c.ImageStore.GetImageTags(name)
|
||||
tags, err := is.GetImageTags(name)
|
||||
if err != nil {
|
||||
WriteJSON(w, http.StatusNotFound, NewErrorList(NewError(NAME_UNKNOWN, map[string]string{"name": name})))
|
||||
return
|
||||
@@ -255,13 +259,15 @@ func (rh *RouteHandler) CheckManifest(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
is := rh.getImageStore(name)
|
||||
|
||||
reference, ok := vars["reference"]
|
||||
if !ok || reference == "" {
|
||||
WriteJSON(w, http.StatusNotFound, NewErrorList(NewError(MANIFEST_INVALID, map[string]string{"reference": reference})))
|
||||
return
|
||||
}
|
||||
|
||||
_, digest, _, err := rh.c.ImageStore.GetImageManifest(name, reference)
|
||||
_, digest, _, err := is.GetImageManifest(name, reference)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case errors.ErrRepoNotFound:
|
||||
@@ -310,13 +316,15 @@ func (rh *RouteHandler) GetManifest(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
is := rh.getImageStore(name)
|
||||
|
||||
reference, ok := vars["reference"]
|
||||
if !ok || reference == "" {
|
||||
WriteJSON(w, http.StatusNotFound, NewErrorList(NewError(MANIFEST_UNKNOWN, map[string]string{"reference": reference})))
|
||||
return
|
||||
}
|
||||
|
||||
content, digest, mediaType, err := rh.c.ImageStore.GetImageManifest(name, reference)
|
||||
content, digest, mediaType, err := is.GetImageManifest(name, reference)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case errors.ErrRepoNotFound:
|
||||
@@ -362,6 +370,8 @@ func (rh *RouteHandler) UpdateManifest(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
is := rh.getImageStore(name)
|
||||
|
||||
reference, ok := vars["reference"]
|
||||
if !ok || reference == "" {
|
||||
WriteJSON(w, http.StatusNotFound, NewErrorList(NewError(MANIFEST_INVALID, map[string]string{"reference": reference})))
|
||||
@@ -382,7 +392,7 @@ func (rh *RouteHandler) UpdateManifest(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
digest, err := rh.c.ImageStore.PutImageManifest(name, reference, mediaType, body)
|
||||
digest, err := is.PutImageManifest(name, reference, mediaType, body)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case errors.ErrRepoNotFound:
|
||||
@@ -428,13 +438,16 @@ func (rh *RouteHandler) DeleteManifest(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
is := rh.getImageStore(name)
|
||||
|
||||
reference, ok := vars["reference"]
|
||||
if !ok || reference == "" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err := rh.c.ImageStore.DeleteImageManifest(name, reference)
|
||||
err := is.DeleteImageManifest(name, reference)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case errors.ErrRepoNotFound:
|
||||
@@ -476,6 +489,8 @@ func (rh *RouteHandler) CheckBlob(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
is := rh.getImageStore(name)
|
||||
|
||||
digest, ok := vars["digest"]
|
||||
if !ok || digest == "" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
@@ -484,7 +499,7 @@ func (rh *RouteHandler) CheckBlob(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
mediaType := r.Header.Get("Accept")
|
||||
|
||||
ok, blen, err := rh.c.ImageStore.CheckBlob(name, digest, mediaType)
|
||||
ok, blen, err := is.CheckBlob(name, digest, mediaType)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case errors.ErrBadBlobDigest:
|
||||
@@ -530,6 +545,8 @@ func (rh *RouteHandler) GetBlob(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
is := rh.getImageStore(name)
|
||||
|
||||
digest, ok := vars["digest"]
|
||||
if !ok || digest == "" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
@@ -538,7 +555,7 @@ func (rh *RouteHandler) GetBlob(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
mediaType := r.Header.Get("Accept")
|
||||
|
||||
br, blen, err := rh.c.ImageStore.GetBlob(name, digest, mediaType)
|
||||
br, blen, err := is.GetBlob(name, digest, mediaType)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case errors.ErrBadBlobDigest:
|
||||
@@ -576,16 +593,20 @@ func (rh *RouteHandler) DeleteBlob(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if !ok || name == "" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
digest, ok := vars["digest"]
|
||||
if !ok || digest == "" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err := rh.c.ImageStore.DeleteBlob(name, digest)
|
||||
is := rh.getImageStore(name)
|
||||
|
||||
err := is.DeleteBlob(name, digest)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case errors.ErrBadBlobDigest:
|
||||
@@ -626,6 +647,9 @@ func (rh *RouteHandler) CreateBlobUpload(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
is := rh.getImageStore(name)
|
||||
|
||||
// currently zot does not support cross-repository mounting, following dist-spec and returning 202
|
||||
if mountDigests, ok := r.URL.Query()["mount"]; ok {
|
||||
if len(mountDigests) != 1 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
@@ -639,9 +663,9 @@ func (rh *RouteHandler) CreateBlobUpload(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
// zot does not support cross mounting directly and do a workaround by copying blob using hard link
|
||||
err := rh.c.ImageStore.MountBlob(name, from[0], mountDigests[0])
|
||||
err := is.MountBlob(name, from[0], mountDigests[0])
|
||||
if err != nil {
|
||||
u, err := rh.c.ImageStore.NewBlobUpload(name)
|
||||
u, err := is.NewBlobUpload(name)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case errors.ErrRepoNotFound:
|
||||
@@ -703,7 +727,7 @@ func (rh *RouteHandler) CreateBlobUpload(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
sessionID, size, err := rh.c.ImageStore.FullBlobUpload(name, r.Body, digest)
|
||||
sessionID, size, err := is.FullBlobUpload(name, r.Body, digest)
|
||||
if err != nil {
|
||||
rh.c.Log.Error().Err(err).Int64("actual", size).Int64("expected", contentLength).Msg("failed full upload")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
@@ -725,7 +749,7 @@ func (rh *RouteHandler) CreateBlobUpload(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
u, err := rh.c.ImageStore.NewBlobUpload(name)
|
||||
u, err := is.NewBlobUpload(name)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case errors.ErrRepoNotFound:
|
||||
@@ -765,13 +789,16 @@ func (rh *RouteHandler) GetBlobUpload(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
is := rh.getImageStore(name)
|
||||
|
||||
sessionID, ok := vars["session_id"]
|
||||
if !ok || sessionID == "" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
size, err := rh.c.ImageStore.GetBlobUpload(name, sessionID)
|
||||
size, err := is.GetBlobUpload(name, sessionID)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case errors.ErrBadUploadRange:
|
||||
@@ -824,19 +851,21 @@ func (rh *RouteHandler) PatchBlobUpload(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
is := rh.getImageStore(name)
|
||||
|
||||
sessionID, ok := vars["session_id"]
|
||||
if !ok || sessionID == "" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
var clen int64
|
||||
|
||||
var err error
|
||||
|
||||
if r.Header.Get("Content-Length") == "" || r.Header.Get("Content-Range") == "" {
|
||||
// streamed blob upload
|
||||
clen, err = rh.c.ImageStore.PutBlobChunkStreamed(name, sessionID, r.Body)
|
||||
clen, err = is.PutBlobChunkStreamed(name, sessionID, r.Body)
|
||||
} else {
|
||||
// chunked blob upload
|
||||
|
||||
@@ -863,7 +892,7 @@ func (rh *RouteHandler) PatchBlobUpload(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
clen, err = rh.c.ImageStore.PutBlobChunk(name, sessionID, from, to, r.Body)
|
||||
clen, err = is.PutBlobChunk(name, sessionID, from, to, r.Body)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -916,6 +945,8 @@ func (rh *RouteHandler) UpdateBlobUpload(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
is := rh.getImageStore(name)
|
||||
|
||||
sessionID, ok := vars["session_id"]
|
||||
if !ok || sessionID == "" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
@@ -969,7 +1000,7 @@ func (rh *RouteHandler) UpdateBlobUpload(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = rh.c.ImageStore.PutBlobChunk(name, sessionID, from, to, r.Body)
|
||||
_, err = is.PutBlobChunk(name, sessionID, from, to, r.Body)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case errors.ErrBadUploadRange:
|
||||
@@ -992,7 +1023,7 @@ func (rh *RouteHandler) UpdateBlobUpload(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
finish:
|
||||
// blob chunks already transferred, just finish
|
||||
if err := rh.c.ImageStore.FinishBlobUpload(name, sessionID, r.Body, digest); err != nil {
|
||||
if err := is.FinishBlobUpload(name, sessionID, r.Body, digest); err != nil {
|
||||
switch err {
|
||||
case errors.ErrBadBlobDigest:
|
||||
WriteJSON(w, http.StatusBadRequest,
|
||||
@@ -1040,13 +1071,15 @@ func (rh *RouteHandler) DeleteBlobUpload(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
is := rh.getImageStore(name)
|
||||
|
||||
sessionID, ok := vars["session_id"]
|
||||
if !ok || sessionID == "" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if err := rh.c.ImageStore.DeleteBlobUpload(name, sessionID); err != nil {
|
||||
if err := is.DeleteBlobUpload(name, sessionID); err != nil {
|
||||
switch err {
|
||||
case errors.ErrRepoNotFound:
|
||||
WriteJSON(w, http.StatusNotFound,
|
||||
@@ -1078,13 +1111,32 @@ type RepositoryList struct {
|
||||
// @Failure 500 {string} string "internal server error"
|
||||
// @Router /v2/_catalog [get].
|
||||
func (rh *RouteHandler) ListRepositories(w http.ResponseWriter, r *http.Request) {
|
||||
repos, err := rh.c.ImageStore.GetRepositories()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
combineRepoList := make([]string, 0)
|
||||
|
||||
subStore := rh.c.StoreController.SubStore
|
||||
|
||||
for _, imgStore := range subStore {
|
||||
repos, err := imgStore.GetRepositories()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
combineRepoList = append(combineRepoList, repos...)
|
||||
}
|
||||
|
||||
is := RepositoryList{Repositories: repos}
|
||||
singleStore := rh.c.StoreController.DefaultStore
|
||||
if singleStore != nil {
|
||||
repos, err := singleStore.GetRepositories()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
combineRepoList = append(combineRepoList, repos...)
|
||||
}
|
||||
|
||||
is := RepositoryList{Repositories: combineRepoList}
|
||||
|
||||
WriteJSON(w, http.StatusOK, is)
|
||||
}
|
||||
@@ -1148,3 +1200,8 @@ func WriteDataFromReader(w http.ResponseWriter, status int, length int64, mediaT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// will return image storage corresponding to subpath provided in config.
|
||||
func (rh *RouteHandler) getImageStore(name string) *storage.ImageStore {
|
||||
return rh.c.StoreController.GetImageStore(name)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user