Files
zot/pkg/test/image-utils/upload_test.go
Andrei Aaron 9dfa7c3ae6 refactor(test): new apis for creating temporary files (#3605)
Replace MakeTempFile usage with MakeTempFilePath and MakeTempFileWithContent
helpers that automatically handle file lifecycle. This prevents resource
leaks by ensuring temporary files are properly closed.

Shoudld also make the tests easier to read.

Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
2025-12-05 09:54:38 +02:00

559 lines
13 KiB
Go

package image_test
import (
"encoding/json"
"os"
"path"
"testing"
godigest "github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
. "github.com/smartystreets/goconvey/convey"
"zotregistry.dev/zot/v2/pkg/api"
"zotregistry.dev/zot/v2/pkg/api/config"
tcommon "zotregistry.dev/zot/v2/pkg/test/common"
. "zotregistry.dev/zot/v2/pkg/test/image-utils"
"zotregistry.dev/zot/v2/pkg/test/inject"
)
func TestUploadImage(t *testing.T) {
Convey("Manifest without schemaVersion should fail validation", t, func() {
port := tcommon.GetFreePort()
baseURL := tcommon.GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
conf.Storage.RootDirectory = t.TempDir()
ctlr := api.NewController(conf)
ctlrManager := tcommon.NewControllerManager(ctlr)
ctlrManager.StartAndWait(port)
defer ctlrManager.StopServer()
layerBlob := []byte("test")
img := Image{
Layers: [][]byte{
layerBlob,
},
Manifest: ispec.Manifest{
Layers: []ispec.Descriptor{
{
Digest: godigest.FromBytes(layerBlob),
Size: int64(len(layerBlob)),
MediaType: ispec.MediaTypeImageLayerGzip,
},
},
Config: ispec.DescriptorEmptyJSON,
},
Config: ispec.Image{},
}
err := UploadImage(img, baseURL, "test", img.DigestStr())
So(err, ShouldNotBeNil)
})
Convey("Post request results in an error", t, func() {
port := tcommon.GetFreePort()
baseURL := tcommon.GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
conf.Storage.RootDirectory = t.TempDir()
img := Image{
Layers: make([][]byte, 10),
}
err := UploadImage(img, baseURL, "test", "")
So(err, ShouldNotBeNil)
})
Convey("Post request status differs from accepted", t, func() {
port := tcommon.GetFreePort()
baseURL := tcommon.GetBaseURL(port)
tempDir := t.TempDir()
conf := config.New()
conf.HTTP.Port = port
conf.Storage.RootDirectory = tempDir
ctlr := api.NewController(conf)
ctlrManager := tcommon.NewControllerManager(ctlr)
ctlrManager.StartAndWait(port)
defer ctlrManager.StopServer()
err := os.Chmod(tempDir, 0o400)
if err != nil {
t.Fatal(err)
}
defer func() {
err := os.Chmod(tempDir, 0o700)
if err != nil {
t.Fatal(err)
}
}()
img := Image{
Layers: make([][]byte, 10),
}
err = UploadImage(img, baseURL, "test", "")
So(err, ShouldNotBeNil)
})
Convey("Put request results in an error", t, func() {
port := tcommon.GetFreePort()
baseURL := tcommon.GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
conf.Storage.RootDirectory = t.TempDir()
ctlr := api.NewController(conf)
ctlrManager := tcommon.NewControllerManager(ctlr)
ctlrManager.StartAndWait(port)
defer ctlrManager.StopServer()
img := Image{
Layers: make([][]byte, 10), // invalid format that will result in an error
Config: ispec.Image{},
}
err := UploadImage(img, baseURL, "test", "")
So(err, ShouldNotBeNil)
})
Convey("Image uploaded successfully", t, func() {
port := tcommon.GetFreePort()
baseURL := tcommon.GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
conf.Storage.RootDirectory = t.TempDir()
ctlr := api.NewController(conf)
ctlrManager := tcommon.NewControllerManager(ctlr)
ctlrManager.StartAndWait(port)
defer ctlrManager.StopServer()
layerBlob := []byte("test")
img := Image{
Layers: [][]byte{
layerBlob,
},
Manifest: ispec.Manifest{
Versioned: specs.Versioned{
SchemaVersion: 2,
},
Layers: []ispec.Descriptor{
{
Digest: godigest.FromBytes(layerBlob),
Size: int64(len(layerBlob)),
MediaType: ispec.MediaTypeImageLayerGzip,
},
},
Config: ispec.DescriptorEmptyJSON,
},
Config: ispec.Image{},
}
err := UploadImage(img, baseURL, "test", img.DigestStr())
So(err, ShouldBeNil)
})
Convey("Upload image with authentification", t, func() {
tempDir := t.TempDir()
conf := config.New()
port := tcommon.GetFreePort()
baseURL := tcommon.GetBaseURL(port)
user1 := "test"
password1 := "test"
testString1 := tcommon.GetBcryptCredString(user1, password1)
htpasswdPath := tcommon.MakeHtpasswdFileFromString(t, testString1)
conf.HTTP.Auth = &config.AuthConfig{
HTPasswd: config.AuthHTPasswd{
Path: htpasswdPath,
},
}
conf.HTTP.Port = port
conf.HTTP.AccessControl = &config.AccessControlConfig{
Repositories: config.Repositories{
"repo": config.PolicyGroup{
Policies: []config.Policy{
{
Users: []string{user1},
Actions: []string{"read", "create"},
},
},
DefaultPolicy: []string{},
},
"inaccessibleRepo": config.PolicyGroup{
Policies: []config.Policy{
{
Users: []string{user1},
Actions: []string{"create"},
},
},
DefaultPolicy: []string{},
},
},
AdminPolicy: config.Policy{
Users: []string{},
Actions: []string{},
},
}
ctlr := api.NewController(conf)
ctlr.Config.Storage.RootDirectory = tempDir
ctlrManager := tcommon.NewControllerManager(ctlr)
ctlrManager.StartAndWait(port)
defer ctlrManager.StopServer()
Convey("Request fail while pushing layer", func() {
err := UploadImageWithBasicAuth(Image{Layers: [][]byte{{1, 2, 3}}}, "badURL", "", "", "", "")
So(err, ShouldNotBeNil)
})
Convey("Request status is not StatusOk while pushing layer", func() {
err := UploadImageWithBasicAuth(Image{Layers: [][]byte{{1, 2, 3}}}, baseURL, "", "repo", "", "")
So(err, ShouldNotBeNil)
})
Convey("Request fail while pushing config", func() {
err := UploadImageWithBasicAuth(Image{}, "badURL", "", "", "", "")
So(err, ShouldNotBeNil)
})
Convey("Request status is not StatusOk while pushing config", func() {
err := UploadImageWithBasicAuth(Image{}, baseURL, "", "repo", "", "")
So(err, ShouldNotBeNil)
})
})
Convey("Blob upload wrong response status code", t, func() {
port := tcommon.GetFreePort()
baseURL := tcommon.GetBaseURL(port)
tempDir := t.TempDir()
conf := config.New()
conf.HTTP.Port = port
conf.Storage.RootDirectory = tempDir
ctlr := api.NewController(conf)
ctlrManager := tcommon.NewControllerManager(ctlr)
ctlrManager.StartAndWait(port)
defer ctlrManager.StopServer()
layerBlob := []byte("test")
layerBlobDigest := godigest.FromBytes(layerBlob)
layerPath := path.Join(tempDir, "test", "blobs", "sha256")
if _, err := os.Stat(layerPath); os.IsNotExist(err) {
err = os.MkdirAll(layerPath, 0o700)
if err != nil {
t.Fatal(err)
}
file, err := os.Create(path.Join(layerPath, layerBlobDigest.Encoded()))
if err != nil {
t.Fatal(err)
}
err = os.Chmod(layerPath, 0o000)
if err != nil {
t.Fatal(err)
}
defer func() {
err = os.Chmod(layerPath, 0o700)
if err != nil {
t.Fatal(err)
}
os.RemoveAll(file.Name())
}()
}
img := Image{
Layers: [][]byte{
layerBlob,
}, // invalid format that will result in an error
Config: ispec.Image{},
}
err := UploadImage(img, baseURL, "test", "")
So(err, ShouldNotBeNil)
})
Convey("CreateBlobUpload wrong response status code", t, func() {
port := tcommon.GetFreePort()
baseURL := tcommon.GetBaseURL(port)
tempDir := t.TempDir()
conf := config.New()
conf.HTTP.Port = port
conf.Storage.RootDirectory = tempDir
ctlr := api.NewController(conf)
ctlrManager := tcommon.NewControllerManager(ctlr)
ctlrManager.StartAndWait(port)
defer ctlrManager.StopServer()
layerBlob := []byte("test")
img := Image{
Layers: [][]byte{
layerBlob,
}, // invalid format that will result in an error
Config: ispec.Image{},
}
Convey("CreateBlobUpload", func() {
injected := inject.InjectFailure(2)
if injected {
err := UploadImage(img, baseURL, "test", img.DigestStr())
So(err, ShouldNotBeNil)
}
})
Convey("UpdateBlobUpload", func() {
injected := inject.InjectFailure(4)
if injected {
err := UploadImage(img, baseURL, "test", img.DigestStr())
So(err, ShouldNotBeNil)
}
})
})
}
func TestInjectUploadImage(t *testing.T) {
Convey("Inject failures for unreachable lines", t, func() {
port := tcommon.GetFreePort()
baseURL := tcommon.GetBaseURL(port)
tempDir := t.TempDir()
conf := config.New()
conf.HTTP.Port = port
conf.Storage.RootDirectory = tempDir
ctlr := api.NewController(conf)
ctlrManager := tcommon.NewControllerManager(ctlr)
ctlrManager.StartAndWait(port)
defer ctlrManager.StopServer()
layerBlob := []byte("test")
layerPath := path.Join(tempDir, "test", ".uploads")
if _, err := os.Stat(layerPath); os.IsNotExist(err) {
err = os.MkdirAll(layerPath, 0o700)
if err != nil {
t.Fatal(err)
}
}
img := Image{
Layers: [][]byte{
layerBlob,
}, // invalid format that will result in an error
Config: ispec.Image{},
}
Convey("first marshal", func() {
injected := inject.InjectFailure(0)
if injected {
err := UploadImage(img, baseURL, "test", img.DigestStr())
So(err, ShouldNotBeNil)
}
})
Convey("CreateBlobUpload POST call", func() {
injected := inject.InjectFailure(1)
if injected {
err := UploadImage(img, baseURL, "test", img.DigestStr())
So(err, ShouldNotBeNil)
}
})
Convey("UpdateBlobUpload PUT call", func() {
injected := inject.InjectFailure(3)
if injected {
err := UploadImage(img, baseURL, "test", img.DigestStr())
So(err, ShouldNotBeNil)
}
})
Convey("second marshal", func() {
injected := inject.InjectFailure(5)
if injected {
err := UploadImage(img, baseURL, "test", img.DigestStr())
So(err, ShouldNotBeNil)
}
})
})
}
func TestUploadMultiarchImage(t *testing.T) {
Convey("make controller", t, func() {
port := tcommon.GetFreePort()
baseURL := tcommon.GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
conf.Storage.RootDirectory = t.TempDir()
ctlr := api.NewController(conf)
ctlrManager := tcommon.NewControllerManager(ctlr)
ctlrManager.StartAndWait(port)
defer ctlrManager.StopServer()
layerBlob := []byte("test")
img := Image{
Layers: [][]byte{
layerBlob,
},
Manifest: ispec.Manifest{
Versioned: specs.Versioned{
SchemaVersion: 2,
},
Layers: []ispec.Descriptor{
{
Digest: godigest.FromBytes(layerBlob),
Size: int64(len(layerBlob)),
MediaType: ispec.MediaTypeImageLayerGzip,
},
},
Config: ispec.DescriptorEmptyJSON,
},
Config: ispec.Image{},
}
manifestBuf, err := json.Marshal(img.Manifest)
So(err, ShouldBeNil)
Convey("Multiarch image uploaded successfully", func() {
err = UploadMultiarchImage(MultiarchImage{
Index: ispec.Index{
Versioned: specs.Versioned{
SchemaVersion: 2,
},
MediaType: ispec.MediaTypeImageIndex,
Manifests: []ispec.Descriptor{
{
MediaType: ispec.MediaTypeImageManifest,
Digest: godigest.FromBytes(manifestBuf),
Size: int64(len(manifestBuf)),
},
},
},
Images: []Image{img},
}, baseURL, "test", "index")
So(err, ShouldBeNil)
})
Convey("Multiarch image without schemaVersion should fail validation", func() {
err = UploadMultiarchImage(MultiarchImage{
Index: ispec.Index{
MediaType: ispec.MediaTypeImageIndex,
Manifests: []ispec.Descriptor{
{
MediaType: ispec.MediaTypeImageManifest,
Digest: godigest.FromBytes(manifestBuf),
Size: int64(len(manifestBuf)),
},
},
},
Images: []Image{img},
}, baseURL, "test", "index")
So(err, ShouldNotBeNil)
})
})
}
func TestInjectUploadImageWithBasicAuth(t *testing.T) {
Convey("Inject failures for unreachable lines", t, func() {
port := tcommon.GetFreePort()
baseURL := tcommon.GetBaseURL(port)
tempDir := t.TempDir()
conf := config.New()
conf.HTTP.Port = port
conf.Storage.RootDirectory = tempDir
user := "user"
password := "password"
testString := tcommon.GetBcryptCredString(user, password)
htpasswdPath := tcommon.MakeHtpasswdFileFromString(t, testString)
conf.HTTP.Auth = &config.AuthConfig{
HTPasswd: config.AuthHTPasswd{
Path: htpasswdPath,
},
}
ctlr := api.NewController(conf)
ctlrManager := tcommon.NewControllerManager(ctlr)
ctlrManager.StartAndWait(port)
defer ctlrManager.StopServer()
layerBlob := []byte("test")
layerPath := path.Join(tempDir, "test", ".uploads")
if _, err := os.Stat(layerPath); os.IsNotExist(err) {
err = os.MkdirAll(layerPath, 0o700)
if err != nil {
t.Fatal(err)
}
}
img := Image{
Layers: [][]byte{
layerBlob,
}, // invalid format that will result in an error
Config: ispec.Image{},
}
Convey("first marshal", func() {
injected := inject.InjectFailure(0)
if injected {
err := UploadImageWithBasicAuth(img, baseURL, "test", img.DigestStr(), "user", "password")
So(err, ShouldNotBeNil)
}
})
Convey("CreateBlobUpload POST call", func() {
injected := inject.InjectFailure(1)
if injected {
err := UploadImageWithBasicAuth(img, baseURL, "test", img.DigestStr(), "user", "password")
So(err, ShouldNotBeNil)
}
})
Convey("UpdateBlobUpload PUT call", func() {
injected := inject.InjectFailure(3)
if injected {
err := UploadImageWithBasicAuth(img, baseURL, "test", img.DigestStr(), "user", "password")
So(err, ShouldNotBeNil)
}
})
Convey("second marshal", func() {
injected := inject.InjectFailure(5)
if injected {
err := UploadImageWithBasicAuth(img, baseURL, "test", img.DigestStr(), "user", "password")
So(err, ShouldNotBeNil)
}
})
})
}