0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00
zot/pkg/log/log_test.go
Alex Stan ada21ed842 Manage builds with different combinations of extensions
Files were added to be built whether an extension is on or off.
New build tags were added for each extension, while minimal and extended disappeared.

added custom binary naming depending on extensions used and changed references from binary to binary-extended

added automated blackbox tests for sync, search, scrub, metrics

added contributor guidelines

Signed-off-by: Alex Stan <alexandrustan96@yahoo.ro>
2022-06-30 09:53:52 -07:00

342 lines
9.2 KiB
Go

//go:build sync && scrub && metrics && search && ui_base
// +build sync,scrub,metrics,search,ui_base
package log_test
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"path"
"strings"
"testing"
"time"
godigest "github.com/opencontainers/go-digest"
"github.com/rs/zerolog"
. "github.com/smartystreets/goconvey/convey"
"gopkg.in/resty.v1"
"zotregistry.io/zot/pkg/api"
"zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/api/constants"
"zotregistry.io/zot/pkg/log"
. "zotregistry.io/zot/pkg/test"
)
const (
username = "test"
passphrase = "test"
AuthorizedNamespace = "everyone/isallowed"
UnauthorizedNamespace = "fortknox/notallowed"
)
type AuditLog struct {
Level string `json:"level"`
ClientIP string `json:"clientIP"` //nolint:tagliatelle // keep IP
Subject string `json:"subject"`
Action string `json:"action"`
Object string `json:"object"`
Status int `json:"status"`
Time string `json:"time"`
Message string `json:"message"`
}
func TestAuditLogMessages(t *testing.T) {
Convey("Make a new controller", t, func() {
dir := t.TempDir()
err := CopyFiles("../../test/data", dir)
if err != nil {
panic(err)
}
port := GetFreePort()
baseURL := GetBaseURL(port)
conf := config.New()
outputPath := dir + "/zot.log"
auditPath := dir + "/zot-audit.log"
conf.Log = &config.LogConfig{Level: "debug", Output: outputPath, Audit: auditPath}
conf.HTTP.Port = port
htpasswdPath := MakeHtpasswdFile()
defer os.Remove(htpasswdPath)
conf.HTTP.Auth = &config.AuthConfig{
HTPasswd: config.AuthHTPasswd{
Path: htpasswdPath,
},
}
ctlr := api.NewController(conf)
ctlr.Config.Storage.RootDirectory = dir
go func() {
// this blocks
if err := ctlr.Run(context.Background()); err != nil {
return
}
}()
// wait till ready
for {
_, err := resty.R().Get(baseURL)
if err == nil {
break
}
time.Sleep(100 * time.Millisecond)
}
defer func() {
ctx := context.Background()
_ = ctlr.Server.Shutdown(ctx)
}()
Convey("Open auditLog file", func() {
auditFile, err := os.Open(auditPath)
if err != nil {
t.Log("Cannot open file")
panic(err)
}
defer auditFile.Close()
Convey("Test GET request", func() {
resp, err := resty.R().SetBasicAuth(username, passphrase).Get(baseURL + "/v2/")
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
byteValue, _ := ioutil.ReadAll(auditFile)
So(len(byteValue), ShouldEqual, 0)
})
Convey("Test POST request", func() {
path := "/v2/" + AuthorizedNamespace + "/blobs/uploads/"
resp, err := resty.R().SetBasicAuth(username, passphrase).Post(baseURL + path)
So(err, ShouldBeNil)
So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
// wait until the file is populated
byteValue, _ := ioutil.ReadAll(auditFile)
for {
if len(byteValue) != 0 {
break
}
time.Sleep(100 * time.Millisecond)
byteValue, _ = ioutil.ReadAll(auditFile)
}
var auditLog AuditLog
err = json.Unmarshal(byteValue, &auditLog)
if err != nil {
panic(err)
}
So(auditLog.Subject, ShouldEqual, username)
So(auditLog.Action, ShouldEqual, http.MethodPost)
So(auditLog.Status, ShouldEqual, http.StatusAccepted)
So(auditLog.Object, ShouldEqual, path)
})
Convey("Test PUT and DELETE request", func() {
// create upload
path := "/v2/repo/blobs/uploads/"
resp, err := resty.R().SetBasicAuth(username, passphrase).Post(baseURL + path)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
loc := Location(baseURL, resp)
So(loc, ShouldNotBeEmpty)
location := resp.Header().Get("Location")
So(location, ShouldNotBeEmpty)
// wait until the file is populated
byteValue, _ := ioutil.ReadAll(auditFile)
for {
if len(byteValue) != 0 {
break
}
time.Sleep(100 * time.Millisecond)
byteValue, _ = ioutil.ReadAll(auditFile)
}
var auditLog AuditLog
err = json.Unmarshal(byteValue, &auditLog)
if err != nil {
panic(err)
}
So(auditLog.Subject, ShouldEqual, username)
So(auditLog.Action, ShouldEqual, http.MethodPost)
So(auditLog.Status, ShouldEqual, http.StatusAccepted)
So(auditLog.Object, ShouldEqual, path)
content := []byte("this is a blob")
digest := godigest.FromBytes(content)
So(digest, ShouldNotBeNil)
// blob upload
resp, err = resty.R().SetQueryParam("digest", digest.String()).
SetBasicAuth(username, passphrase).
SetHeader("Content-Type", "application/octet-stream").SetBody(content).Put(loc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusCreated)
blobLoc := Location(baseURL, resp)
So(blobLoc, ShouldNotBeEmpty)
So(resp.Header().Get(constants.DistContentDigestKey), ShouldNotBeEmpty)
// wait until the file is populated
byteValue, _ = ioutil.ReadAll(auditFile)
for {
if len(byteValue) != 0 {
break
}
time.Sleep(100 * time.Millisecond)
byteValue, _ = ioutil.ReadAll(auditFile)
}
err = json.Unmarshal(byteValue, &auditLog)
if err != nil {
panic(err)
}
So(auditLog.Subject, ShouldEqual, username)
So(auditLog.Action, ShouldEqual, http.MethodPut)
So(auditLog.Status, ShouldEqual, http.StatusCreated)
putPath := location + "?digest=" + strings.ReplaceAll(digest.String(), ":", "%3A")
So(auditLog.Object, ShouldEqual, putPath)
// delete this blob
resp, err = resty.R().SetBasicAuth(username, passphrase).Delete(blobLoc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
So(resp.Header().Get("Content-Length"), ShouldEqual, "0")
// wait until the file is populated
byteValue, _ = ioutil.ReadAll(auditFile)
for {
if len(byteValue) != 0 {
break
}
time.Sleep(100 * time.Millisecond)
byteValue, _ = ioutil.ReadAll(auditFile)
}
err = json.Unmarshal(byteValue, &auditLog)
if err != nil {
panic(err)
}
So(auditLog.Subject, ShouldEqual, username)
So(auditLog.Action, ShouldEqual, http.MethodDelete)
So(auditLog.Status, ShouldEqual, http.StatusAccepted)
deletePath := strings.ReplaceAll(path, "uploads/", digest.String())
So(auditLog.Object, ShouldEqual, deletePath)
})
Convey("Test PATCH request", func() {
path := "/v2/repo/blobs/uploads/"
resp, err := resty.R().SetBasicAuth(username, passphrase).Post(baseURL + path)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
loc := Location(baseURL, resp)
So(loc, ShouldNotBeEmpty)
location := resp.Header().Get("Location")
So(location, ShouldNotBeEmpty)
// wait until the file is populated
byteValue, _ := ioutil.ReadAll(auditFile)
for {
if len(byteValue) != 0 {
break
}
time.Sleep(100 * time.Millisecond)
byteValue, _ = ioutil.ReadAll(auditFile)
}
var auditLog AuditLog
err = json.Unmarshal(byteValue, &auditLog)
if err != nil {
panic(err)
}
So(auditLog.Subject, ShouldEqual, username)
So(auditLog.Action, ShouldEqual, http.MethodPost)
So(auditLog.Status, ShouldEqual, http.StatusAccepted)
So(auditLog.Object, ShouldEqual, path)
var buf bytes.Buffer
chunk := []byte("this is a chunk")
n, err := buf.Write(chunk)
So(n, ShouldEqual, len(chunk))
So(err, ShouldBeNil)
// write a chunk
contentRange := fmt.Sprintf("%d-%d", 0, len(chunk)-1)
resp, err = resty.R().SetBasicAuth(username, passphrase).
SetHeader("Content-Type", "application/octet-stream").
SetHeader("Content-Range", contentRange).SetBody(chunk).Patch(loc)
So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, http.StatusAccepted)
// wait until the file is populated
byteValue, _ = ioutil.ReadAll(auditFile)
for {
if len(byteValue) != 0 {
break
}
time.Sleep(100 * time.Millisecond)
byteValue, _ = ioutil.ReadAll(auditFile)
}
err = json.Unmarshal(byteValue, &auditLog)
if err != nil {
panic(err)
}
So(auditLog.Subject, ShouldEqual, username)
So(auditLog.Action, ShouldEqual, http.MethodPatch)
So(auditLog.Status, ShouldEqual, http.StatusAccepted)
patchPath := location
So(auditLog.Object, ShouldEqual, patchPath)
})
})
})
}
func TestLogErrors(t *testing.T) {
Convey("Get error with unknown log level", t, func() {
So(func() { _ = log.NewLogger("invalid", "test.out") }, ShouldPanic)
})
Convey("Get error when opening log file", t, func() {
dir := t.TempDir()
logPath := path.Join(dir, "logFile")
err := ioutil.WriteFile(logPath, []byte{}, 0o000)
So(err, ShouldBeNil)
So(func() {
_ = log.NewLogger(zerolog.DebugLevel.String(), logPath)
}, ShouldPanic)
})
}
func TestNewAuditLogger(t *testing.T) {
Convey("Get error with unknown audit log level", t, func() {
So(func() { _ = log.NewAuditLogger("invalid", "test.out") }, ShouldPanic)
})
Convey("Get error when opening audit file", t, func() {
dir := t.TempDir()
logPath := path.Join(dir, "logFile")
err := ioutil.WriteFile(logPath, []byte{}, 0o000)
So(err, ShouldBeNil)
So(func() {
_ = log.NewAuditLogger(zerolog.DebugLevel.String(), logPath)
}, ShouldPanic)
})
}