0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-30 22:34:13 -05:00

feat(repodb): Multiarch Image support (#1147)

* feat(repodb): index logic + tests

Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>

* feat(cli): printing indexes support using the rest api

Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>

---------

Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
This commit is contained in:
LaurentiuNiculae 2023-02-27 21:23:18 +02:00 committed by GitHub
parent a561d0bad5
commit d62c09e2cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 8656 additions and 2988 deletions

View file

@ -63,6 +63,7 @@ var (
ErrManifestConflict = errors.New("manifest: multiple manifests found") ErrManifestConflict = errors.New("manifest: multiple manifests found")
ErrManifestMetaNotFound = errors.New("repodb: image metadata not found for given manifest digest") ErrManifestMetaNotFound = errors.New("repodb: image metadata not found for given manifest digest")
ErrManifestDataNotFound = errors.New("repodb: image data not found for given manifest digest") ErrManifestDataNotFound = errors.New("repodb: image data not found for given manifest digest")
ErrIndexDataNotFount = errors.New("repodb: index data not found for given digest")
ErrRepoMetaNotFound = errors.New("repodb: repo metadata not found for given repo name") ErrRepoMetaNotFound = errors.New("repodb: repo metadata not found for given repo name")
ErrTagMetaNotFound = errors.New("repodb: tag metadata not found for given repo and tag names") ErrTagMetaNotFound = errors.New("repodb: tag metadata not found for given repo and tag names")
ErrTypeAssertionFailed = errors.New("storage: failed DatabaseDriver type assertion") ErrTypeAssertionFailed = errors.New("storage: failed DatabaseDriver type assertion")
@ -77,4 +78,5 @@ var (
ErrOffsetIsNegative = errors.New("pageturner: offset has negative value") ErrOffsetIsNegative = errors.New("pageturner: offset has negative value")
ErrSortCriteriaNotSupported = errors.New("pageturner: the sort criteria is not supported") ErrSortCriteriaNotSupported = errors.New("pageturner: the sort criteria is not supported")
ErrTimeout = errors.New("operation timeout") ErrTimeout = errors.New("operation timeout")
ErrNotImplemented = errors.New("not implemented")
) )

View file

@ -551,6 +551,9 @@ func getDynamoParams(cacheDriverConfig map[string]interface{}, log log.Logger) d
manifestDataTablename, ok := toStringIfOk(cacheDriverConfig, "manifestdatatablename", log) manifestDataTablename, ok := toStringIfOk(cacheDriverConfig, "manifestdatatablename", log)
allParametersOk = allParametersOk && ok allParametersOk = allParametersOk && ok
indexDataTablename, ok := toStringIfOk(cacheDriverConfig, "indexdatatablename", log)
allParametersOk = allParametersOk && ok
versionTablename, ok := toStringIfOk(cacheDriverConfig, "versiontablename", log) versionTablename, ok := toStringIfOk(cacheDriverConfig, "versiontablename", log)
allParametersOk = allParametersOk && ok allParametersOk = allParametersOk && ok
@ -563,6 +566,7 @@ func getDynamoParams(cacheDriverConfig map[string]interface{}, log log.Logger) d
Region: region, Region: region,
RepoMetaTablename: repoMetaTablename, RepoMetaTablename: repoMetaTablename,
ManifestDataTablename: manifestDataTablename, ManifestDataTablename: manifestDataTablename,
IndexDataTablename: indexDataTablename,
VersionTablename: versionTablename, VersionTablename: versionTablename,
} }
} }

View file

@ -3863,7 +3863,7 @@ func TestImageSignatures(t *testing.T) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "1.0", Reference: "1.0",
}, baseURL, repoName) }, baseURL, repoName)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -4103,7 +4103,7 @@ func TestArtifactReferences(t *testing.T) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "1.0", Reference: "1.0",
}, baseURL, repoName) }, baseURL, repoName)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -4970,7 +4970,7 @@ func TestStorageCommit(t *testing.T) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "test:1.0", Reference: "test:1.0",
}, baseURL, repoName) }, baseURL, repoName)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -5005,7 +5005,7 @@ func TestStorageCommit(t *testing.T) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "test:1.0.1", Reference: "test:1.0.1",
}, baseURL, repoName) }, baseURL, repoName)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -5017,7 +5017,7 @@ func TestStorageCommit(t *testing.T) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "test:2.0", Reference: "test:2.0",
}, baseURL, repoName) }, baseURL, repoName)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -5127,7 +5127,7 @@ func TestManifestImageIndex(t *testing.T) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "test:1.0", Reference: "test:1.0",
}, baseURL, repoName) }, baseURL, repoName)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -5555,7 +5555,7 @@ func TestManifestCollision(t *testing.T) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "test:1.0", Reference: "test:1.0",
}, baseURL, "index") }, baseURL, "index")
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -5582,7 +5582,7 @@ func TestManifestCollision(t *testing.T) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "test:2.0", Reference: "test:2.0",
}, baseURL, "index") }, baseURL, "index")
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -6218,7 +6218,7 @@ func TestGCSignaturesAndUntaggedManifests(t *testing.T) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: tag, Reference: tag,
}, baseURL, repoName) }, baseURL, repoName)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
@ -6235,7 +6235,7 @@ func TestGCSignaturesAndUntaggedManifests(t *testing.T) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: tag, Reference: tag,
}, baseURL, repoName) }, baseURL, repoName)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
@ -6256,7 +6256,7 @@ func TestGCSignaturesAndUntaggedManifests(t *testing.T) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: untaggedManifestDigest.String(), Reference: untaggedManifestDigest.String(),
}, baseURL, repoName) }, baseURL, repoName)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -6269,7 +6269,7 @@ func TestGCSignaturesAndUntaggedManifests(t *testing.T) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: tag, Reference: tag,
}, baseURL, repoName) }, baseURL, repoName)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -6345,7 +6345,7 @@ func TestGCSignaturesAndUntaggedManifests(t *testing.T) {
Manifest: manifest, Manifest: manifest,
Config: config, Config: config,
Layers: layers, Layers: layers,
Tag: manifestDigest.String(), Reference: manifestDigest.String(),
}, },
baseURL, baseURL,
repoName) repoName)
@ -6530,7 +6530,7 @@ func TestSearchRoutes(t *testing.T) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "latest", Reference: "latest",
}, baseURL, repoName) }, baseURL, repoName)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -6544,7 +6544,7 @@ func TestSearchRoutes(t *testing.T) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "latest", Reference: "latest",
}, baseURL, inaccessibleRepo) }, baseURL, inaccessibleRepo)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -6618,7 +6618,7 @@ func TestSearchRoutes(t *testing.T) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "latest", Reference: "latest",
}, baseURL, repoName, }, baseURL, repoName,
user1, password1) user1, password1)
@ -6633,7 +6633,7 @@ func TestSearchRoutes(t *testing.T) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "latest", Reference: "latest",
}, baseURL, inaccessibleRepo, }, baseURL, inaccessibleRepo,
user1, password1) user1, password1)
@ -6657,7 +6657,6 @@ func TestSearchRoutes(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp, ShouldNotBeNil) So(resp, ShouldNotBeNil)
So(resp.StatusCode(), ShouldEqual, 200) So(resp.StatusCode(), ShouldEqual, 200)
So(string(resp.Body()), ShouldContainSubstring, repoName) So(string(resp.Body()), ShouldContainSubstring, repoName)
So(string(resp.Body()), ShouldNotContainSubstring, inaccessibleRepo) So(string(resp.Body()), ShouldNotContainSubstring, inaccessibleRepo)

View file

@ -52,6 +52,19 @@ func makeGETRequest(ctx context.Context, url, username, password string,
return doHTTPRequest(req, verifyTLS, debug, resultsPtr, configWriter) return doHTTPRequest(req, verifyTLS, debug, resultsPtr, configWriter)
} }
func makeHEADRequest(ctx context.Context, url, username, password string, verifyTLS bool,
debug bool,
) (http.Header, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodHead, url, nil)
if err != nil {
return nil, err
}
req.SetBasicAuth(username, password)
return doHTTPRequest(req, verifyTLS, debug, nil, io.Discard)
}
func makeGraphQLRequest(ctx context.Context, url, query, username, func makeGraphQLRequest(ctx context.Context, url, query, username,
password string, verifyTLS bool, debug bool, resultsPtr interface{}, configWriter io.Writer, password string, verifyTLS bool, debug bool, resultsPtr interface{}, configWriter io.Writer,
) error { ) error {
@ -126,6 +139,10 @@ func doHTTPRequest(req *http.Request, verifyTLS bool, debug bool,
return nil, errors.New(string(bodyBytes)) //nolint: goerr113 return nil, errors.New(string(bodyBytes)) //nolint: goerr113
} }
if resultsPtr == nil {
return resp.Header, nil
}
if err := json.NewDecoder(resp.Body).Decode(resultsPtr); err != nil { if err := json.NewDecoder(resp.Body).Decode(resultsPtr); err != nil {
return nil, err return nil, err
} }
@ -140,26 +157,25 @@ func isURL(str string) bool {
} // from https://stackoverflow.com/a/55551215 } // from https://stackoverflow.com/a/55551215
type requestsPool struct { type requestsPool struct {
jobs chan *manifestJob jobs chan *httpJob
done chan struct{} done chan struct{}
wtgrp *sync.WaitGroup wtgrp *sync.WaitGroup
outputCh chan stringResult outputCh chan stringResult
} }
type manifestJob struct { type httpJob struct {
url string url string
username string username string
password string password string
imageName string imageName string
tagName string tagName string
config searchConfig config searchConfig
manifestResp manifestResponse
} }
const rateLimiterBuffer = 5000 const rateLimiterBuffer = 5000
func newSmoothRateLimiter(wtgrp *sync.WaitGroup, opch chan stringResult) *requestsPool { func newSmoothRateLimiter(wtgrp *sync.WaitGroup, opch chan stringResult) *requestsPool {
ch := make(chan *manifestJob, rateLimiterBuffer) ch := make(chan *httpJob, rateLimiterBuffer)
return &requestsPool{ return &requestsPool{
jobs: ch, jobs: ch,
@ -188,11 +204,12 @@ func (p *requestsPool) startRateLimiter(ctx context.Context) {
} }
} }
func (p *requestsPool) doJob(ctx context.Context, job *manifestJob) { func (p *requestsPool) doJob(ctx context.Context, job *httpJob) {
defer p.wtgrp.Done() defer p.wtgrp.Done()
header, err := makeGETRequest(ctx, job.url, job.username, job.password, // Check manifest media type
*job.config.verifyTLS, *job.config.debug, &job.manifestResp, job.config.resultWriter) header, err := makeHEADRequest(ctx, job.url, job.username, job.password, *job.config.verifyTLS,
*job.config.debug)
if err != nil { if err != nil {
if isContextDone(ctx) { if isContextDone(ctx) {
return return
@ -200,72 +217,20 @@ func (p *requestsPool) doJob(ctx context.Context, job *manifestJob) {
p.outputCh <- stringResult{"", err} p.outputCh <- stringResult{"", err}
} }
digestStr := header.Get("docker-content-digest") switch header.Get("Content-Type") {
configDigest := job.manifestResp.Config.Digest case ispec.MediaTypeImageManifest:
image, err := fetchImageManifestStruct(ctx, job)
var size uint64
layers := []layer{}
for _, entry := range job.manifestResp.Layers {
size += entry.Size
layers = append(
layers,
layer{
Size: entry.Size,
Digest: entry.Digest,
},
)
}
size += uint64(job.manifestResp.Config.Size)
manifestSize, err := strconv.Atoi(header.Get("Content-Length"))
if err != nil { if err != nil {
if isContextDone(ctx) {
return
}
p.outputCh <- stringResult{"", err} p.outputCh <- stringResult{"", err}
return
} }
platformStr := getPlatformStr(image.Manifests[0].Platform)
isSigned := false str, err := image.string(*job.config.outputFormat, len(job.imageName), len(job.tagName), len(platformStr))
cosignTag := strings.Replace(digestStr, ":", "-", 1) + "." + remote.SignatureTagSuffix
_, err = makeGETRequest(ctx, *job.config.servURL+"/v2/"+job.imageName+
"/manifests/"+cosignTag, job.username, job.password,
*job.config.verifyTLS, *job.config.debug, &job.manifestResp, job.config.resultWriter)
if err == nil {
isSigned = true
}
var referrers ispec.Index
if !isSigned {
_, err = makeGETRequest(ctx, fmt.Sprintf("%s/v2/%s/referrers/%s?artifactType=%s",
*job.config.servURL, job.imageName, digestStr, notreg.ArtifactTypeNotation), job.username, job.password,
*job.config.verifyTLS, *job.config.debug, &referrers, job.config.resultWriter)
if err == nil {
for _, reference := range referrers.Manifests {
if reference.ArtifactType == notreg.ArtifactTypeNotation {
isSigned = true
break
}
}
}
}
size += uint64(manifestSize)
image := &imageStruct{}
image.verbose = *job.config.verbose
image.RepoName = job.imageName
image.Tag = job.tagName
image.Digest = digestStr
image.Size = strconv.Itoa(int(size))
image.ConfigDigest = configDigest
image.Layers = layers
image.IsSigned = isSigned
str, err := image.string(*job.config.outputFormat, len(job.imageName), len(job.tagName))
if err != nil { if err != nil {
if isContextDone(ctx) { if isContextDone(ctx) {
return return
@ -280,8 +245,270 @@ func (p *requestsPool) doJob(ctx context.Context, job *manifestJob) {
} }
p.outputCh <- stringResult{str, nil} p.outputCh <- stringResult{str, nil}
case ispec.MediaTypeImageIndex:
image, err := fetchImageIndexStruct(ctx, job)
if err != nil {
if isContextDone(ctx) {
return
}
p.outputCh <- stringResult{"", err}
return
}
platformStr := getPlatformStr(image.Manifests[0].Platform)
str, err := image.string(*job.config.outputFormat, len(job.imageName), len(job.tagName), len(platformStr))
if err != nil {
if isContextDone(ctx) {
return
}
p.outputCh <- stringResult{"", err}
return
}
if isContextDone(ctx) {
return
}
p.outputCh <- stringResult{str, nil}
default:
return
}
} }
func (p *requestsPool) submitJob(job *manifestJob) { func fetchImageIndexStruct(ctx context.Context, job *httpJob) (*imageStruct, error) {
var indexContent ispec.Index
header, err := makeGETRequest(ctx, job.url, job.username, job.password,
*job.config.verifyTLS, *job.config.debug, &indexContent, job.config.resultWriter)
if err != nil {
if isContextDone(ctx) {
return nil, context.Canceled
}
return nil, err
}
indexDigest := header.Get("docker-content-digest")
indexSize, err := strconv.ParseInt(header.Get("Content-Length"), 10, 64)
if err != nil {
return nil, err
}
imageSize := indexSize
manifestList := make([]manifestStruct, 0, len(indexContent.Manifests))
for _, manifestDescriptor := range indexContent.Manifests {
manifest, err := fetchManifestStruct(ctx, job.imageName, manifestDescriptor.Digest.String(),
job.config, job.username, job.password)
if err != nil {
return nil, err
}
imageSize += int64(atoiWithDefault(manifest.Size, 0))
if manifestDescriptor.Platform != nil {
manifest.Platform = platform{
Os: manifestDescriptor.Platform.OS,
Arch: manifestDescriptor.Platform.Architecture,
Variant: manifestDescriptor.Platform.Variant,
}
}
manifestList = append(manifestList, manifest)
}
isIndexSigned := isCosignSigned(ctx, job.imageName, indexDigest, job.config, job.username, job.password) ||
isNotationSigned(ctx, job.imageName, indexDigest, job.config, job.username, job.password)
return &imageStruct{
verbose: *job.config.verbose,
RepoName: job.imageName,
Tag: job.tagName,
Size: strconv.FormatInt(imageSize, 10),
IsSigned: isIndexSigned,
Manifests: manifestList,
}, nil
}
func atoiWithDefault(size string, defaultVal int) int {
val, err := strconv.Atoi(size)
if err != nil {
return defaultVal
}
return val
}
func fetchImageManifestStruct(ctx context.Context, job *httpJob) (*imageStruct, error) {
manifest, err := fetchManifestStruct(ctx, job.imageName, job.tagName, job.config, job.username, job.password)
if err != nil {
return nil, err
}
return &imageStruct{
verbose: *job.config.verbose,
RepoName: job.imageName,
Tag: job.tagName,
Size: manifest.Size,
IsSigned: manifest.IsSigned,
Manifests: []manifestStruct{
manifest,
},
}, nil
}
func fetchManifestStruct(ctx context.Context, repo, manifestReference string, searchConf searchConfig,
username, password string,
) (manifestStruct, error) {
manifestResp := ispec.Manifest{}
URL := fmt.Sprintf("%s/v2/%s/manifests/%s",
*searchConf.servURL, repo, manifestReference)
header, err := makeGETRequest(ctx, URL, username, password,
*searchConf.verifyTLS, *searchConf.debug, &manifestResp, searchConf.resultWriter)
if err != nil {
if isContextDone(ctx) {
return manifestStruct{}, context.Canceled
}
return manifestStruct{}, err
}
manifestDigest := header.Get("docker-content-digest")
configDigest := manifestResp.Config.Digest.String()
configContent, err := fetchConfig(ctx, repo, configDigest, searchConf, username, password)
if err != nil {
if isContextDone(ctx) {
return manifestStruct{}, context.Canceled
}
return manifestStruct{}, err
}
opSys := ""
arch := ""
variant := ""
if manifestResp.Config.Platform != nil {
opSys = manifestResp.Config.Platform.OS
arch = manifestResp.Config.Platform.Architecture
variant = manifestResp.Config.Platform.Variant
}
if opSys == "" {
opSys = configContent.OS
}
if arch == "" {
arch = configContent.Architecture
}
if variant == "" {
variant = configContent.Variant
}
manifestSize, err := strconv.ParseInt(header.Get("Content-Length"), 10, 64)
if err != nil {
return manifestStruct{}, err
}
var imageSize int64
imageSize += manifestResp.Config.Size
imageSize += manifestSize
layers := []layer{}
for _, entry := range manifestResp.Layers {
imageSize += entry.Size
layers = append(
layers,
layer{
Size: entry.Size,
Digest: entry.Digest.String(),
},
)
}
isSigned := isCosignSigned(ctx, repo, manifestDigest, searchConf, username, password) ||
isNotationSigned(ctx, repo, manifestDigest, searchConf, username, password)
return manifestStruct{
ConfigDigest: configDigest,
Digest: manifestDigest,
Layers: layers,
Platform: platform{Os: opSys, Arch: arch, Variant: variant},
Size: strconv.FormatInt(imageSize, 10),
IsSigned: isSigned,
}, nil
}
func fetchConfig(ctx context.Context, repo, configDigest string, searchConf searchConfig,
username, password string,
) (ispec.Image, error) {
configContent := ispec.Image{}
URL := fmt.Sprintf("%s/v2/%s/blobs/%s",
*searchConf.servURL, repo, configDigest)
_, err := makeGETRequest(ctx, URL, username, password,
*searchConf.verifyTLS, *searchConf.debug, &configContent, searchConf.resultWriter)
if err != nil {
if isContextDone(ctx) {
return ispec.Image{}, context.Canceled
}
return ispec.Image{}, err
}
return configContent, nil
}
func isNotationSigned(ctx context.Context, repo, digestStr string, searchConf searchConfig,
username, password string,
) bool {
var referrers ispec.Index
URL := fmt.Sprintf("%s/v2/%s/referrers/%s?artifactType=%s",
*searchConf.servURL, repo, digestStr, notreg.ArtifactTypeNotation)
_, err := makeGETRequest(ctx, URL, username, password,
*searchConf.verifyTLS, *searchConf.debug, &referrers, searchConf.resultWriter)
if err != nil {
return false
}
for _, reference := range referrers.Manifests {
if reference.ArtifactType == notreg.ArtifactTypeNotation {
return true
}
}
return false
}
func isCosignSigned(ctx context.Context, repo, digestStr string, searchConf searchConfig,
username, password string,
) bool {
var result interface{}
cosignTag := strings.Replace(digestStr, ":", "-", 1) + "." + remote.SignatureTagSuffix
URL := fmt.Sprintf("%s/v2/%s/manifests/%s", *searchConf.servURL, repo, cosignTag)
_, err := makeGETRequest(ctx, URL, username, password, *searchConf.verifyTLS,
*searchConf.debug, &result, searchConf.resultWriter)
return err == nil
}
func (p *requestsPool) submitJob(job *httpJob) {
p.jobs <- job p.jobs <- job
} }

View file

@ -0,0 +1,652 @@
//go:build search
// +build search
package cli //nolint:testpackage
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"sync"
"testing"
"github.com/gorilla/mux"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
. "github.com/smartystreets/goconvey/convey"
"zotregistry.io/zot/pkg/test"
)
type RouteHandler struct {
Route string
// HandlerFunc is the HTTP handler function that receives a writer for output and an HTTP request as input.
HandlerFunc http.HandlerFunc
// AllowedMethods specifies the HTTP methods allowed for the current route.
AllowedMethods []string
}
// Routes is a map that associates HTTP paths to their corresponding HTTP handlers.
type HTTPRoutes []RouteHandler
func StartTestHTTPServer(routes HTTPRoutes, port string) *http.Server {
baseURL := test.GetBaseURL(port)
mux := mux.NewRouter()
mux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte("{}"))
if err != nil {
return
}
}).Methods(http.MethodGet)
for _, routeHandler := range routes {
mux.HandleFunc(routeHandler.Route, routeHandler.HandlerFunc).Methods(routeHandler.AllowedMethods...)
}
server := &http.Server{ //nolint:gosec
Addr: fmt.Sprintf(":%s", port),
Handler: mux,
}
go func() {
if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) {
return
}
}()
test.WaitTillServerReady(baseURL + "/test")
return server
}
func getDefaultSearchConf(baseURL string) searchConfig {
verifyTLS := false
debug := false
verbose := true
outputFormat := "text"
return searchConfig{
servURL: &baseURL,
resultWriter: io.Discard,
verifyTLS: &verifyTLS,
debug: &debug,
verbose: &verbose,
outputFormat: &outputFormat,
}
}
func TestDoHTTPRequest(t *testing.T) {
Convey("doHTTPRequest nil result pointer", t, func() {
port := test.GetFreePort()
server := StartTestHTTPServer(nil, port)
defer server.Close()
url := fmt.Sprintf("http://127.0.0.1:%s/asd", port)
req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, url, nil)
So(err, ShouldBeNil)
So(func() { _, _ = doHTTPRequest(req, false, false, nil, io.Discard) }, ShouldNotPanic)
})
Convey("doHTTPRequest bad return json", t, func() {
port := test.GetFreePort()
server := StartTestHTTPServer(HTTPRoutes{
{
Route: "/test",
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte("bad json"))
if err != nil {
return
}
},
AllowedMethods: []string{http.MethodGet},
},
}, port)
defer server.Close()
url := fmt.Sprintf("http://127.0.0.1:%s/test", port)
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
So(err, ShouldBeNil)
So(func() { _, _ = doHTTPRequest(req, false, false, &ispec.Manifest{}, io.Discard) }, ShouldNotPanic)
})
Convey("makeGraphQLRequest bad request context", t, func() {
err := makeGraphQLRequest(nil, "", "", "", "", false, false, nil, io.Discard) //nolint:staticcheck
So(err, ShouldNotBeNil)
})
Convey("makeHEADRequest bad request context", t, func() {
_, err := makeHEADRequest(nil, "", "", "", false, false) //nolint:staticcheck
So(err, ShouldNotBeNil)
})
Convey("makeGETRequest bad request context", t, func() {
_, err := makeGETRequest(nil, "", "", "", false, false, nil, io.Discard) //nolint:staticcheck
So(err, ShouldNotBeNil)
})
Convey("fetchImageManifestStruct errors", t, func() {
port := test.GetFreePort()
baseURL := test.GetBaseURL(port)
searchConf := getDefaultSearchConf(baseURL)
// 404 erorr will appear
server := StartTestHTTPServer(HTTPRoutes{}, port)
defer server.Close()
URL := baseURL + "/v2/repo/manifests/tag"
_, err := fetchImageManifestStruct(context.Background(), &httpJob{
url: URL,
username: "",
password: "",
imageName: "repo",
tagName: "tag",
config: searchConf,
})
So(err, ShouldNotBeNil)
})
Convey("fetchManifestStruct errors", t, func() {
port := test.GetFreePort()
baseURL := test.GetBaseURL(port)
searchConf := getDefaultSearchConf(baseURL)
Convey("makeGETRequest manifest error, context is done", func() {
server := StartTestHTTPServer(HTTPRoutes{}, port)
defer server.Close()
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err := fetchManifestStruct(ctx, "repo", "tag", searchConf,
"", "")
So(err, ShouldNotBeNil)
})
Convey("makeGETRequest manifest error, context is not done", func() {
server := StartTestHTTPServer(HTTPRoutes{}, port)
defer server.Close()
_, err := fetchManifestStruct(context.Background(), "repo", "tag", searchConf,
"", "")
So(err, ShouldNotBeNil)
})
Convey("makeGETRequest config error, context is not done", func() {
server := StartTestHTTPServer(HTTPRoutes{
{
Route: "/v2/{name}/manifests/{reference}",
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte(`{"config":{"digest":"digest","size":0}}`))
if err != nil {
return
}
},
AllowedMethods: []string{http.MethodGet},
},
}, port)
defer server.Close()
_, err := fetchManifestStruct(context.Background(), "repo", "tag", searchConf,
"", "")
So(err, ShouldNotBeNil)
})
Convey("Platforms on config", func() {
server := StartTestHTTPServer(HTTPRoutes{
{
Route: "/v2/{name}/manifests/{reference}",
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte(`
{
"config":{
"digest":"digest",
"size":0,
"platform" : {
"os": "",
"architecture": "",
"variant": ""
}
}
}
`))
if err != nil {
return
}
},
AllowedMethods: []string{http.MethodGet},
},
{
Route: "/v2/{name}/blobs/{digest}",
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte(`
{
"architecture": "arch",
"os": "os",
"variant": "var"
}
`))
if err != nil {
return
}
},
AllowedMethods: []string{http.MethodGet},
},
}, port)
defer server.Close()
_, err := fetchManifestStruct(context.Background(), "repo", "tag", searchConf,
"", "")
So(err, ShouldBeNil)
})
Convey("isNotationSigned error", func() {
isSigned := isNotationSigned(context.Background(), "repo", "digest", searchConf,
"", "")
So(isSigned, ShouldBeFalse)
})
Convey("fetchImageIndexStruct no errors", func() {
server := StartTestHTTPServer(HTTPRoutes{
{
Route: "/v2/{name}/manifests/{reference}",
HandlerFunc: func(writer http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
if vars["reference"] == "indexRef" {
_, err := writer.Write([]byte(`
{
"manifests": [
{
"digest": "manifestRef",
"platform": {
"architecture": "arch",
"os": "os",
"variant": "var"
}
}
]
}
`))
if err != nil {
return
}
} else if vars["reference"] == "manifestRef" {
_, err := writer.Write([]byte(`
{
"config":{
"digest":"digest",
"size":0
}
}
`))
if err != nil {
return
}
}
},
AllowedMethods: []string{http.MethodGet},
},
{
Route: "/v2/{name}/blobs/{digest}",
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte(`{}`))
if err != nil {
return
}
},
AllowedMethods: []string{http.MethodGet},
},
}, port)
defer server.Close()
URL := baseURL + "/v2/repo/manifests/indexRef"
imageStruct, err := fetchImageIndexStruct(context.Background(), &httpJob{
url: URL,
username: "",
password: "",
imageName: "repo",
tagName: "tag",
config: searchConf,
})
So(err, ShouldBeNil)
So(imageStruct, ShouldNotBeNil)
})
Convey("fetchImageIndexStruct makeGETRequest errors context done", func() {
server := StartTestHTTPServer(HTTPRoutes{}, port)
defer server.Close()
ctx, cancel := context.WithCancel(context.Background())
cancel()
URL := baseURL + "/v2/repo/manifests/indexRef"
imageStruct, err := fetchImageIndexStruct(ctx, &httpJob{
url: URL,
username: "",
password: "",
imageName: "repo",
tagName: "tag",
config: searchConf,
})
So(err, ShouldNotBeNil)
So(imageStruct, ShouldBeNil)
})
Convey("fetchImageIndexStruct makeGETRequest errors context not done", func() {
server := StartTestHTTPServer(HTTPRoutes{}, port)
defer server.Close()
URL := baseURL + "/v2/repo/manifests/indexRef"
imageStruct, err := fetchImageIndexStruct(context.Background(), &httpJob{
url: URL,
username: "",
password: "",
imageName: "repo",
tagName: "tag",
config: searchConf,
})
So(err, ShouldNotBeNil)
So(imageStruct, ShouldBeNil)
})
})
}
func TestDoJobErrors(t *testing.T) {
port := test.GetFreePort()
baseURL := test.GetBaseURL(port)
searchConf := getDefaultSearchConf(baseURL)
reqPool := &requestsPool{
jobs: make(chan *httpJob),
done: make(chan struct{}),
wtgrp: &sync.WaitGroup{},
outputCh: make(chan stringResult),
}
Convey("Do Job errors", t, func() {
reqPool.wtgrp.Add(1)
Convey("Do Job makeHEADRequest error context done", func() {
server := StartTestHTTPServer(HTTPRoutes{}, port)
defer server.Close()
URL := baseURL + "/v2/repo/manifests/manifestRef"
ctx, cancel := context.WithCancel(context.Background())
cancel()
reqPool.doJob(ctx, &httpJob{
url: URL,
username: "",
password: "",
imageName: "",
tagName: "",
config: searchConf,
})
})
Convey("Do Job makeHEADRequest error context not done", func() {
server := StartTestHTTPServer(HTTPRoutes{}, port)
defer server.Close()
URL := baseURL + "/v2/repo/manifests/manifestRef"
ctx := context.Background()
go reqPool.doJob(ctx, &httpJob{
url: URL,
username: "",
password: "",
imageName: "",
tagName: "",
config: searchConf,
})
result := <-reqPool.outputCh
So(result.Err, ShouldNotBeNil)
So(result.StrValue, ShouldResemble, "")
})
Convey("Do Job fetchManifestStruct errors context canceled", func() {
server := StartTestHTTPServer(HTTPRoutes{
{
Route: "/v2/{name}/manifests/{reference}",
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", ispec.MediaTypeImageManifest)
_, err := w.Write([]byte(""))
if err != nil {
return
}
},
AllowedMethods: []string{http.MethodHead},
},
}, port)
defer server.Close()
URL := baseURL + "/v2/repo/manifests/manifestRef"
ctx, cancel := context.WithCancel(context.Background())
cancel()
// context not canceled
reqPool.doJob(ctx, &httpJob{
url: URL,
username: "",
password: "",
imageName: "",
tagName: "",
config: searchConf,
})
})
Convey("Do Job fetchManifestStruct errors context not canceled", func() {
server := StartTestHTTPServer(HTTPRoutes{
{
Route: "/v2/{name}/manifests/{reference}",
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", ispec.MediaTypeImageManifest)
_, err := w.Write([]byte(""))
if err != nil {
return
}
},
AllowedMethods: []string{http.MethodHead},
},
}, port)
defer server.Close()
URL := baseURL + "/v2/repo/manifests/manifestRef"
ctx := context.Background()
go reqPool.doJob(ctx, &httpJob{
url: URL,
username: "",
password: "",
imageName: "",
tagName: "",
config: searchConf,
})
result := <-reqPool.outputCh
So(result.Err, ShouldNotBeNil)
So(result.StrValue, ShouldResemble, "")
})
Convey("Do Job fetchIndexStruct errors context canceled", func() {
server := StartTestHTTPServer(HTTPRoutes{
{
Route: "/v2/{name}/manifests/{reference}",
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", ispec.MediaTypeImageIndex)
_, err := w.Write([]byte(""))
if err != nil {
return
}
},
AllowedMethods: []string{http.MethodHead},
},
}, port)
defer server.Close()
URL := baseURL + "/v2/repo/manifests/indexRef"
ctx, cancel := context.WithCancel(context.Background())
cancel()
// context not canceled
reqPool.doJob(ctx, &httpJob{
url: URL,
username: "",
password: "",
imageName: "",
tagName: "",
config: searchConf,
})
})
Convey("Do Job fetchIndexStruct errors context not canceled", func() {
server := StartTestHTTPServer(HTTPRoutes{
{
Route: "/v2/{name}/manifests/{reference}",
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", ispec.MediaTypeImageIndex)
_, err := w.Write([]byte(""))
if err != nil {
return
}
},
AllowedMethods: []string{http.MethodHead},
},
}, port)
defer server.Close()
URL := baseURL + "/v2/repo/manifests/indexRef"
ctx := context.Background()
go reqPool.doJob(ctx, &httpJob{
url: URL,
username: "",
password: "",
imageName: "",
tagName: "",
config: searchConf,
})
result := <-reqPool.outputCh
So(result.Err, ShouldNotBeNil)
So(result.StrValue, ShouldResemble, "")
})
Convey("Do Job fetchIndexStruct not supported content type", func() {
server := StartTestHTTPServer(HTTPRoutes{
{
Route: "/v2/{name}/manifests/{reference}",
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "some-media-type")
_, err := w.Write([]byte(""))
if err != nil {
return
}
},
AllowedMethods: []string{http.MethodHead},
},
}, port)
defer server.Close()
URL := baseURL + "/v2/repo/manifests/indexRef"
ctx := context.Background()
reqPool.doJob(ctx, &httpJob{
url: URL,
username: "",
password: "",
imageName: "",
tagName: "",
config: searchConf,
})
})
Convey("Media type is MediaTypeImageIndex image.string erorrs", func() {
server := StartTestHTTPServer(HTTPRoutes{
{
Route: "/v2/{name}/manifests/{reference}",
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", ispec.MediaTypeImageIndex)
_, err := w.Write([]byte(""))
if err != nil {
return
}
},
AllowedMethods: []string{http.MethodHead},
},
{
Route: "/v2/{name}/manifests/{reference}",
HandlerFunc: func(writer http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
if vars["reference"] == "indexRef" {
_, err := writer.Write([]byte(`{"manifests": [{"digest": "manifestRef"}]}`))
if err != nil {
return
}
}
if vars["reference"] == "manifestRef" {
_, err := writer.Write([]byte(`{"config": {"digest": "confDigest"}}`))
if err != nil {
return
}
}
},
AllowedMethods: []string{http.MethodGet},
},
{
Route: "/v2/{name}/blobs/{digest}",
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte(`{}`))
if err != nil {
return
}
},
AllowedMethods: []string{http.MethodGet},
},
}, port)
defer server.Close()
URL := baseURL + "/v2/repo/manifests/indexRef"
go reqPool.doJob(context.Background(), &httpJob{
url: URL,
username: "",
password: "",
imageName: "repo",
tagName: "indexRef",
config: searchConf,
})
result := <-reqPool.outputCh
So(result.Err, ShouldNotBeNil)
So(result.StrValue, ShouldResemble, "")
})
})
}

View file

@ -179,7 +179,8 @@ func TestSearchCVECmd(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIGNED SIZE dummyImageName tag 6e2f80bf false 123kB") So(strings.TrimSpace(str), ShouldEqual,
"IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE dummyImageName tag 6e2f80bf os/arch false 123kB")
}) })
Convey("Test CVE by name and CVE ID - using shorthand", t, func() { Convey("Test CVE by name and CVE ID - using shorthand", t, func() {
@ -195,7 +196,8 @@ func TestSearchCVECmd(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIGNED SIZE dummyImageName tag 6e2f80bf false 123kB") So(strings.TrimSpace(str), ShouldEqual,
"IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE dummyImageName tag 6e2f80bf os/arch false 123kB")
}) })
Convey("Test CVE by image name - in text format", t, func() { Convey("Test CVE by image name - in text format", t, func() {
@ -278,7 +280,7 @@ func TestSearchCVECmd(t *testing.T) {
err := cveCmd.Execute() err := cveCmd.Execute()
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIGNED SIZE anImage tag 6e2f80bf false 123kB") So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE anImage tag 6e2f80bf os/arch false 123kB") //nolint:lll
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })
@ -323,7 +325,7 @@ func TestSearchCVECmd(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIGNED SIZE fixedImage tag 6e2f80bf false 123kB") So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE fixedImage tag 6e2f80bf os/arch false 123kB") //nolint:lll
}) })
Convey("Test fixed tags by and image name CVE ID - invalid image name", t, func() { Convey("Test fixed tags by and image name CVE ID - invalid image name", t, func() {
@ -625,8 +627,8 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(str, ShouldEqual, "IMAGE NAME TAG DIGEST SIGNED SIZE zot-cve-test 0.0.1 "+ So(str, ShouldEqual, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE zot-cve-test 0.0.1 "+
test.GetTestBlobDigest("zot-cve-test", "manifest").Encoded()[:8]+" false 75MB") test.GetTestBlobDigest("zot-cve-test", "manifest").Encoded()[:8]+" N/A false 75MB")
}) })
Convey("Test images by CVE ID - GQL - invalid CVE ID", t, func() { Convey("Test images by CVE ID - GQL - invalid CVE ID", t, func() {
@ -643,7 +645,7 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(str, ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(str, ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
}) })
Convey("Test images by CVE ID - GQL - invalid output format", t, func() { Convey("Test images by CVE ID - GQL - invalid output format", t, func() {
@ -691,7 +693,7 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(strings.TrimSpace(str), ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(strings.TrimSpace(str), ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
}) })
Convey("Test fixed tags by image name and CVE ID - GQL - random image", t, func() { Convey("Test fixed tags by image name and CVE ID - GQL - random image", t, func() {
@ -708,7 +710,7 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(strings.TrimSpace(str), ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(strings.TrimSpace(str), ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
}) })
Convey("Test fixed tags by image name and CVE ID - GQL - invalid image", t, func() { Convey("Test fixed tags by image name and CVE ID - GQL - invalid image", t, func() {
@ -725,7 +727,7 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(strings.TrimSpace(str), ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(strings.TrimSpace(str), ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
}) })
Convey("Test CVE by name and CVE ID - GQL - positive", t, func() { Convey("Test CVE by name and CVE ID - GQL - positive", t, func() {
@ -741,8 +743,8 @@ func TestServerCVEResponse(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIGNED SIZE zot-cve-test 0.0.1 "+ So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE zot-cve-test 0.0.1 "+
test.GetTestBlobDigest("zot-cve-test", "manifest").Encoded()[:8]+" false 75MB") test.GetTestBlobDigest("zot-cve-test", "manifest").Encoded()[:8]+" N/A false 75MB")
}) })
Convey("Test CVE by name and CVE ID - GQL - invalid name and CVE ID", t, func() { Convey("Test CVE by name and CVE ID - GQL - invalid name and CVE ID", t, func() {
@ -820,8 +822,8 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(str, ShouldEqual, "IMAGE NAME TAG DIGEST SIGNED SIZE zot-cve-test 0.0.1 "+ So(str, ShouldEqual, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE zot-cve-test 0.0.1 "+
test.GetTestBlobDigest("zot-cve-test", "manifest").Encoded()[:8]+" false 75MB") test.GetTestBlobDigest("zot-cve-test", "manifest").Encoded()[:8]+" linux/amd64 false 75MB")
}) })
Convey("Test images by CVE ID - invalid CVE ID", t, func() { Convey("Test images by CVE ID - invalid CVE ID", t, func() {
@ -838,7 +840,7 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(str, ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(str, ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
}) })
Convey("Test fixed tags by and image name CVE ID - positive", t, func() { Convey("Test fixed tags by and image name CVE ID - positive", t, func() {
@ -872,7 +874,7 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(strings.TrimSpace(str), ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(strings.TrimSpace(str), ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
}) })
Convey("Test fixed tags by and image name CVE ID - invalid image", t, func() { Convey("Test fixed tags by and image name CVE ID - invalid image", t, func() {
@ -889,7 +891,7 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(strings.TrimSpace(str), ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(strings.TrimSpace(str), ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
}) })
Convey("Test CVE by name and CVE ID - positive", t, func() { Convey("Test CVE by name and CVE ID - positive", t, func() {
@ -905,8 +907,8 @@ func TestServerCVEResponse(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIGNED SIZE zot-cve-test 0.0.1 "+ So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE zot-cve-test 0.0.1 "+
test.GetTestBlobDigest("zot-cve-test", "manifest").Encoded()[:8]+" false 75MB") test.GetTestBlobDigest("zot-cve-test", "manifest").Encoded()[:8]+" linux/amd64 false 75MB")
}) })
Convey("Test CVE by name and CVE ID - invalid name and CVE ID", t, func() { Convey("Test CVE by name and CVE ID - invalid name and CVE ID", t, func() {
@ -922,7 +924,7 @@ func TestServerCVEResponse(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(strings.TrimSpace(str), ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(strings.TrimSpace(str), ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
}) })
} }
@ -1082,17 +1084,10 @@ func getMockCveInfo(repoDB repodb.RepoDB, log log.Logger) cveinfo.CveInfo {
CompareSeveritiesFn: func(severity1, severity2 string) int { CompareSeveritiesFn: func(severity1, severity2 string) int {
return severities[severity2] - severities[severity1] return severities[severity2] - severities[severity1]
}, },
IsImageFormatScannableFn: func(image string) (bool, error) { IsImageFormatScannableFn: func(repo string, reference string) (bool, error) {
// Almost same logic compared to actual Trivy specific implementation // Almost same logic compared to actual Trivy specific implementation
var imageDir string imageDir := repo
inputTag := reference
var inputTag string
if strings.Contains(image, ":") {
imageDir, inputTag, _ = strings.Cut(image, ":")
} else {
imageDir = image
}
repoMeta, err := repoDB.GetRepoMeta(imageDir) repoMeta, err := repoDB.GetRepoMeta(imageDir)
if err != nil { if err != nil {

View file

@ -185,7 +185,8 @@ func TestSearchImageCmd(t *testing.T) {
err := cmd.Execute() err := cmd.Execute()
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIGNED SIZE dummyImageName tag 6e2f80bf false 123kB") So(strings.TrimSpace(str), ShouldEqual,
"IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE dummyImageName tag 6e2f80bf os/arch false 123kB")
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })
@ -201,7 +202,8 @@ func TestSearchImageCmd(t *testing.T) {
err := imageCmd.Execute() err := imageCmd.Execute()
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIGNED SIZE dummyImageName tag 6e2f80bf false 123kB") So(strings.TrimSpace(str), ShouldEqual,
"IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE dummyImageName tag 6e2f80bf os/arch false 123kB")
So(err, ShouldBeNil) So(err, ShouldBeNil)
Convey("using shorthand", func() { Convey("using shorthand", func() {
args := []string{"imagetest", "-n", "dummyImageName", "--url", "someUrlImage"} args := []string{"imagetest", "-n", "dummyImageName", "--url", "someUrlImage"}
@ -216,7 +218,8 @@ func TestSearchImageCmd(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIGNED SIZE dummyImageName tag 6e2f80bf false 123kB") So(strings.TrimSpace(str), ShouldEqual,
"IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE dummyImageName tag 6e2f80bf os/arch false 123kB")
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })
}) })
@ -233,7 +236,8 @@ func TestSearchImageCmd(t *testing.T) {
err := imageCmd.Execute() err := imageCmd.Execute()
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIGNED SIZE anImage tag 6e2f80bf false 123kB") So(strings.TrimSpace(str), ShouldEqual,
"IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE anImage tag 6e2f80bf os/arch false 123kB")
So(err, ShouldBeNil) So(err, ShouldBeNil)
Convey("invalid URL format", func() { Convey("invalid URL format", func() {
@ -285,7 +289,7 @@ func TestSignature(t *testing.T) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "test:1.0", Reference: "test:1.0",
}, url, repoName) }, url, repoName)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -326,8 +330,8 @@ func TestSignature(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:1.0 6742241d true 447B") So(actual, ShouldContainSubstring, "repo7 test:1.0 6742241d linux/amd64 true 447B")
t.Log("Test getting all images using rest calls to get catalog and individual manifests") t.Log("Test getting all images using rest calls to get catalog and individual manifests")
cmd = MockNewImageCommand(new(searchService)) cmd = MockNewImageCommand(new(searchService))
@ -339,8 +343,8 @@ func TestSignature(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
str = space.ReplaceAllString(buff.String(), " ") str = space.ReplaceAllString(buff.String(), " ")
actual = strings.TrimSpace(str) actual = strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:1.0 6742241d true 447B") So(actual, ShouldContainSubstring, "repo7 test:1.0 6742241d linux/amd64 true 447B")
err = os.Chdir(currentWorkingDir) err = os.Chdir(currentWorkingDir)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -377,7 +381,7 @@ func TestSignature(t *testing.T) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "0.0.1", Reference: "0.0.1",
}, url, repoName) }, url, repoName)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -403,8 +407,8 @@ func TestSignature(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 0.0.1 6742241d true 447B") So(actual, ShouldContainSubstring, "repo7 0.0.1 6742241d linux/amd64 true 447B")
t.Log("Test getting all images using rest calls to get catalog and individual manifests") t.Log("Test getting all images using rest calls to get catalog and individual manifests")
cmd = MockNewImageCommand(new(searchService)) cmd = MockNewImageCommand(new(searchService))
@ -416,8 +420,8 @@ func TestSignature(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
str = space.ReplaceAllString(buff.String(), " ") str = space.ReplaceAllString(buff.String(), " ")
actual = strings.TrimSpace(str) actual = strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 0.0.1 6742241d true 447B") So(actual, ShouldContainSubstring, "repo7 0.0.1 6742241d linux/amd64 true 447B")
err = os.Chdir(currentWorkingDir) err = os.Chdir(currentWorkingDir)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -465,8 +469,8 @@ func TestDerivedImageList(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:1.0 2694fdb0 false 824B") So(actual, ShouldContainSubstring, "repo7 test:1.0 2694fdb0 N/A false 824B")
}) })
Convey("Test derived images list fails", func() { Convey("Test derived images list fails", func() {
@ -538,8 +542,8 @@ func TestBaseImageList(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 3fc80493 false 494B") So(actual, ShouldContainSubstring, "repo7 test:2.0 3fc80493 N/A false 494B")
}) })
Convey("Test base images list fail", func() { Convey("Test base images list fail", func() {
@ -727,7 +731,8 @@ func TestOutputFormat(t *testing.T) {
err := cmd.Execute() err := cmd.Execute()
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST SIGNED SIZE dummyImageName tag 6e2f80bf false 123kB") So(strings.TrimSpace(str), ShouldEqual,
"IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE dummyImageName tag 6e2f80bf os/arch false 123kB")
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })
@ -746,10 +751,11 @@ func TestOutputFormat(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(strings.TrimSpace(str), ShouldEqual, `{ "repoName": "dummyImageName", "tag": "tag", `+ So(strings.TrimSpace(str), ShouldEqual, `{ "repoName": "dummyImageName", "tag": "tag", `+
`"configDigest": "sha256:4c10985c40365538426f2ba8cf0c21384a7769be502a550dcc0601b3736625e0", `+ `"Manifests": [ { "configDigest": "sha256:4c10985c40365538426f2ba8cf0c21384a7769be502a550dcc0601b3736625e0", `+
`"digest": "sha256:6e2f80bf9cfaabad474fbaf8ad68fdb652f776ea80b63492ecca404e5f6446a6", `+ `"digest": "sha256:6e2f80bf9cfaabad474fbaf8ad68fdb652f776ea80b63492ecca404e5f6446a6", `+
`"layers": [ { "size": "0", `+ `"layers": [ { "size": "0", "digest": "sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6" } ], `+ //nolint:lll
`"digest": "sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6" } ], `+ `"platform": { "os": "os", "arch": "arch", "variant": "" }, `+
`"size": "123445", "isSigned": false } ], `+
`"size": "123445", "isSigned": false }`) `"size": "123445", "isSigned": false }`)
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })
@ -770,9 +776,12 @@ func TestOutputFormat(t *testing.T) {
strings.TrimSpace(str), strings.TrimSpace(str),
ShouldEqual, ShouldEqual,
`reponame: dummyImageName tag: tag `+ `reponame: dummyImageName tag: tag `+
`manifests: - `+
`configdigest: sha256:4c10985c40365538426f2ba8cf0c21384a7769be502a550dcc0601b3736625e0 `+ `configdigest: sha256:4c10985c40365538426f2ba8cf0c21384a7769be502a550dcc0601b3736625e0 `+
`digest: sha256:6e2f80bf9cfaabad474fbaf8ad68fdb652f776ea80b63492ecca404e5f6446a6 `+ `digest: sha256:6e2f80bf9cfaabad474fbaf8ad68fdb652f776ea80b63492ecca404e5f6446a6 `+
`layers: - size: 0 digest: sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6 `+ `layers: - size: 0 digest: sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6 `+
`platform: os: os arch: arch variant: "" `+
`size: "123445" issigned: false `+
`size: "123445" issigned: false`, `size: "123445" issigned: false`,
) )
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -796,9 +805,12 @@ func TestOutputFormat(t *testing.T) {
strings.TrimSpace(str), strings.TrimSpace(str),
ShouldEqual, ShouldEqual,
`reponame: dummyImageName tag: tag `+ `reponame: dummyImageName tag: tag `+
`manifests: - `+
`configdigest: sha256:4c10985c40365538426f2ba8cf0c21384a7769be502a550dcc0601b3736625e0 `+ `configdigest: sha256:4c10985c40365538426f2ba8cf0c21384a7769be502a550dcc0601b3736625e0 `+
`digest: sha256:6e2f80bf9cfaabad474fbaf8ad68fdb652f776ea80b63492ecca404e5f6446a6 `+ `digest: sha256:6e2f80bf9cfaabad474fbaf8ad68fdb652f776ea80b63492ecca404e5f6446a6 `+
`layers: - size: 0 digest: sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6 `+ `layers: - size: 0 digest: sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6 `+
`platform: os: os arch: arch variant: "" `+
`size: "123445" issigned: false `+
`size: "123445" issigned: false`, `size: "123445" issigned: false`,
) )
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -855,9 +867,9 @@ func TestServerResponseGQL(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 false 492B") So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 linux/amd64 false 492B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 false 492B") So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 linux/amd64 false 492B")
Convey("Test all images invalid output format", func() { Convey("Test all images invalid output format", func() {
args := []string{"imagetest", "-o", "random"} args := []string{"imagetest", "-o", "random"}
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url))
@ -888,14 +900,14 @@ func TestServerResponseGQL(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
// Actual cli output should be something similar to (order of images may differ): // Actual cli output should be something similar to (order of images may differ):
// IMAGE NAME TAG DIGEST CONFIG SIGNED LAYERS SIZE // IMAGE NAME TAG DIGEST CONFIG OS/ARCH SIGNED LAYERS SIZE
// repo7 test:2.0 a0ca253b b8781e88 false 492B // repo7 test:2.0 a0ca253b b8781e88 linux/amd64 false 492B
// b8781e88 15B // b8781e88 15B
// repo7 test:1.0 a0ca253b b8781e88 false 492B // repo7 test:1.0 a0ca253b b8781e88 linux/amd64 false 492B
// b8781e88 15B // b8781e88 15B
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST CONFIG SIGNED LAYERS SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST CONFIG OS/ARCH SIGNED LAYERS SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 3a1d2d0c false 492B b8781e88 15B") So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 3a1d2d0c linux/amd64 false 492B b8781e88 15B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 3a1d2d0c false 492B b8781e88 15B") So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 3a1d2d0c linux/amd64 false 492B b8781e88 15B")
}) })
Convey("Test all images with debug flag", func() { Convey("Test all images with debug flag", func() {
@ -913,9 +925,9 @@ func TestServerResponseGQL(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "GET") So(actual, ShouldContainSubstring, "GET")
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 false 492B") So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 linux/amd64 false 492B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 false 492B") So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 linux/amd64 false 492B")
}) })
Convey("Test image by name config url", func() { Convey("Test image by name config url", func() {
@ -932,9 +944,9 @@ func TestServerResponseGQL(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 false 492B") So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 linux/amd64 false 492B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 false 492B") So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 linux/amd64 false 492B")
Convey("with shorthand", func() { Convey("with shorthand", func() {
args := []string{"imagetest", "-n", "repo7"} args := []string{"imagetest", "-n", "repo7"}
@ -950,9 +962,9 @@ func TestServerResponseGQL(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 false 492B") So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 linux/amd64 false 492B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 false 492B") So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 linux/amd64 false 492B")
}) })
Convey("invalid output format", func() { Convey("invalid output format", func() {
@ -985,12 +997,12 @@ func TestServerResponseGQL(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
// Actual cli output should be something similar to (order of images may differ): // Actual cli output should be something similar to (order of images may differ):
// IMAGE NAME TAG DIGEST SIZE // IMAGE NAME TAG DIGEST OS/ARCH SIZE
// repo7 test:2.0 a0ca253b 15B // repo7 test:2.0 a0ca253b N/A 15B
// repo7 test:1.0 a0ca253b 15B // repo7 test:1.0 a0ca253b N/A 15B
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 false 492B") So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 N/A false 492B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 false 492B") So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 N/A false 492B")
Convey("with shorthand", func() { Convey("with shorthand", func() {
args := []string{"imagetest", "-d", "883fc0c5"} args := []string{"imagetest", "-d", "883fc0c5"}
@ -1006,9 +1018,9 @@ func TestServerResponseGQL(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 false 492B") So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 N/A false 492B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 false 492B") So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 N/A false 492B")
}) })
Convey("nonexistent digest", func() { Convey("nonexistent digest", func() {
@ -1116,9 +1128,9 @@ func TestServerResponse(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 false 492B") So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 linux/amd64 false 492B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 false 492B") So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 linux/amd64 false 492B")
}) })
Convey("Test all images verbose", func() { Convey("Test all images verbose", func() {
@ -1136,14 +1148,14 @@ func TestServerResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
// Actual cli output should be something similar to (order of images may differ): // Actual cli output should be something similar to (order of images may differ):
// IMAGE NAME TAG DIGEST CONFIG SIGNED LAYERS SIZE // IMAGE NAME TAG DIGEST CONFIG OS/ARCH SIGNED LAYERS SIZE
// repo7 test:2.0 a0ca253b b8781e88 false 492B // repo7 test:2.0 a0ca253b b8781e88 linux/amd64 false 492B
// b8781e88 15B // linux/amd64 b8781e88 15B
// repo7 test:1.0 a0ca253b b8781e88 false 492B // repo7 test:1.0 a0ca253b b8781e88 linux/amd64 false 492B
// b8781e88 15B // linux/amd64 b8781e88 15B
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST CONFIG SIGNED LAYERS SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST CONFIG OS/ARCH SIGNED LAYERS SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 3a1d2d0c false 492B b8781e88 15B") So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 3a1d2d0c linux/amd64 false 492B b8781e88 15B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 3a1d2d0c false 492B b8781e88 15B") So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 3a1d2d0c linux/amd64 false 492B b8781e88 15B")
}) })
Convey("Test image by name", func() { Convey("Test image by name", func() {
@ -1160,9 +1172,9 @@ func TestServerResponse(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 false 492B") So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 linux/amd64 false 492B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 false 492B") So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 linux/amd64 false 492B")
}) })
Convey("Test image by digest", func() { Convey("Test image by digest", func() {
@ -1180,12 +1192,12 @@ func TestServerResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
// Actual cli output should be something similar to (order of images may differ): // Actual cli output should be something similar to (order of images may differ):
// IMAGE NAME TAG DIGEST SIZE // IMAGE NAME TAG DIGEST OS/ARCH SIZE
// repo7 test:2.0 a0ca253b 492B // repo7 test:2.0 a0ca253b linux/amd64 492B
// repo7 test:1.0 a0ca253b 492B // repo7 test:1.0 a0ca253b linux/amd64 492B
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 false 492B") So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 linux/amd64 false 492B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 false 492B") So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 linux/amd64 false 492B")
Convey("nonexistent digest", func() { Convey("nonexistent digest", func() {
args := []string{"imagetest", "--digest", "d1g35t"} args := []string{"imagetest", "--digest", "d1g35t"}
@ -1540,11 +1552,16 @@ func (service mockService) getDerivedImageListGQL(ctx context.Context, config se
{ {
RepoName: "dummyImageName", RepoName: "dummyImageName",
Tag: "tag", Tag: "tag",
Manifests: []manifestStruct{
{
Digest: godigest.FromString("Digest").String(), Digest: godigest.FromString("Digest").String(),
ConfigDigest: godigest.FromString("ConfigDigest").String(), ConfigDigest: godigest.FromString("ConfigDigest").String(),
Size: "123445", Size: "123445",
Layers: []layer{{Digest: godigest.FromString("LayerDigest").String()}}, Layers: []layer{{Digest: godigest.FromString("LayerDigest").String()}},
}, },
},
Size: "123445",
},
} }
return imageListGQLResponse, nil return imageListGQLResponse, nil
@ -1558,11 +1575,16 @@ func (service mockService) getBaseImageListGQL(ctx context.Context, config searc
{ {
RepoName: "dummyImageName", RepoName: "dummyImageName",
Tag: "tag", Tag: "tag",
Manifests: []manifestStruct{
{
Digest: godigest.FromString("Digest").String(), Digest: godigest.FromString("Digest").String(),
ConfigDigest: godigest.FromString("ConfigDigest").String(), ConfigDigest: godigest.FromString("ConfigDigest").String(),
Size: "123445", Size: "123445",
Layers: []layer{{Digest: godigest.FromString("LayerDigest").String()}}, Layers: []layer{{Digest: godigest.FromString("LayerDigest").String()}},
}, },
},
Size: "123445",
},
} }
return imageListGQLResponse, nil return imageListGQLResponse, nil
@ -1576,11 +1598,16 @@ func (service mockService) getImagesGQL(ctx context.Context, config searchConfig
{ {
RepoName: "dummyImageName", RepoName: "dummyImageName",
Tag: "tag", Tag: "tag",
Manifests: []manifestStruct{
{
Digest: godigest.FromString("Digest").String(), Digest: godigest.FromString("Digest").String(),
ConfigDigest: godigest.FromString("ConfigDigest").String(), ConfigDigest: godigest.FromString("ConfigDigest").String(),
Size: "123445", Size: "123445",
Layers: []layer{{Digest: godigest.FromString("LayerDigest").String()}}, Layers: []layer{{Digest: godigest.FromString("LayerDigest").String()}},
}, },
},
Size: "123445",
},
} }
return imageListGQLResponse, nil return imageListGQLResponse, nil
@ -1594,10 +1621,15 @@ func (service mockService) getImagesByDigestGQL(ctx context.Context, config sear
{ {
RepoName: "randomimageName", RepoName: "randomimageName",
Tag: "tag", Tag: "tag",
Manifests: []manifestStruct{
{
Digest: godigest.FromString("Digest").String(), Digest: godigest.FromString("Digest").String(),
ConfigDigest: godigest.FromString("ConfigDigest").String(), ConfigDigest: godigest.FromString("ConfigDigest").String(),
Size: "123445",
Layers: []layer{{Digest: godigest.FromString("LayerDigest").String()}}, Layers: []layer{{Digest: godigest.FromString("LayerDigest").String()}},
Size: "123445",
},
},
Size: "123445",
}, },
} }
@ -1691,10 +1723,15 @@ func (service mockService) getMockedImageByName(imageName string) imageStruct {
image := imageStruct{} image := imageStruct{}
image.RepoName = imageName image.RepoName = imageName
image.Tag = "tag" image.Tag = "tag"
image.Digest = godigest.FromString("Digest").String() image.Manifests = []manifestStruct{
image.ConfigDigest = godigest.FromString("ConfigDigest").String() {
Digest: godigest.FromString("Digest").String(),
ConfigDigest: godigest.FromString("ConfigDigest").String(),
Layers: []layer{{Digest: godigest.FromString("LayerDigest").String()}},
Size: "123445",
},
}
image.Size = "123445" image.Size = "123445"
image.Layers = []layer{{Digest: godigest.FromString("LayerDigest").String()}}
return image return image
} }
@ -1708,12 +1745,18 @@ func (service mockService) getAllImages(ctx context.Context, config searchConfig
image := &imageStruct{} image := &imageStruct{}
image.RepoName = "randomimageName" image.RepoName = "randomimageName"
image.Tag = "tag" image.Tag = "tag"
image.Digest = godigest.FromString("Digest").String() image.Manifests = []manifestStruct{
image.ConfigDigest = godigest.FromString("ConfigDigest").String() {
Digest: godigest.FromString("Digest").String(),
ConfigDigest: godigest.FromString("ConfigDigest").String(),
Layers: []layer{{Digest: godigest.FromString("LayerDigest").String()}},
Size: "123445",
Platform: platform{Os: "os", Arch: "arch"},
},
}
image.Size = "123445" image.Size = "123445"
image.Layers = []layer{{Digest: godigest.FromString("LayerDigest").String()}}
str, err := image.string(*config.outputFormat, len(image.RepoName), len(image.Tag)) str, err := image.string(*config.outputFormat, len(image.RepoName), len(image.Tag), len("os/Arch"))
if err != nil { if err != nil {
channel <- stringResult{"", err} channel <- stringResult{"", err}
@ -1732,12 +1775,18 @@ func (service mockService) getImageByName(ctx context.Context, config searchConf
image := &imageStruct{} image := &imageStruct{}
image.RepoName = imageName image.RepoName = imageName
image.Tag = "tag" image.Tag = "tag"
image.Digest = godigest.FromString("Digest").String() image.Manifests = []manifestStruct{
image.ConfigDigest = godigest.FromString("ConfigDigest").String() {
Digest: godigest.FromString("Digest").String(),
ConfigDigest: godigest.FromString("ConfigDigest").String(),
Layers: []layer{{Digest: godigest.FromString("LayerDigest").String()}},
Size: "123445",
Platform: platform{Os: "os", Arch: "arch"},
},
}
image.Size = "123445" image.Size = "123445"
image.Layers = []layer{{Digest: godigest.FromString("LayerDigest").String()}}
str, err := image.string(*config.outputFormat, len(image.RepoName), len(image.Tag)) str, err := image.string(*config.outputFormat, len(image.RepoName), len(image.Tag), len("os/Arch"))
if err != nil { if err != nil {
channel <- stringResult{"", err} channel <- stringResult{"", err}

View file

@ -359,7 +359,7 @@ func (search cveByImageSearcherGQL) search(config searchConfig) (bool, error) {
if len(cveList.Data.CVEListForImage.CVEList) > 0 && if len(cveList.Data.CVEListForImage.CVEList) > 0 &&
(*config.outputFormat == defaultOutoutFormat || *config.outputFormat == "") { (*config.outputFormat == defaultOutoutFormat || *config.outputFormat == "") {
printCVETableHeader(&builder, *config.verbose, 0, 0) printCVETableHeader(&builder, *config.verbose, 0, 0, 0)
fmt.Fprint(config.resultWriter, builder.String()) fmt.Fprint(config.resultWriter, builder.String())
} }
@ -596,7 +596,7 @@ func collectResults(config searchConfig, wg *sync.WaitGroup, imageErr chan strin
if !foundResult && (*config.outputFormat == defaultOutoutFormat || *config.outputFormat == "") { if !foundResult && (*config.outputFormat == defaultOutoutFormat || *config.outputFormat == "") {
var builder strings.Builder var builder strings.Builder
printHeader(&builder, *config.verbose, 0, 0) printHeader(&builder, *config.verbose, 0, 0, 0)
fmt.Fprint(config.resultWriter, builder.String()) fmt.Fprint(config.resultWriter, builder.String())
} }
@ -696,13 +696,14 @@ type stringResult struct {
Err error Err error
} }
type printHeader func(writer io.Writer, verbose bool, maxImageNameLen, maxTagLen int) type printHeader func(writer io.Writer, verbose bool, maxImageNameLen, maxTagLen, maxPlatformLen int)
func printImageTableHeader(writer io.Writer, verbose bool, maxImageNameLen, maxTagLen int) { func printImageTableHeader(writer io.Writer, verbose bool, maxImageNameLen, maxTagLen, maxPlatformLen int) {
table := getImageTableWriter(writer) table := getImageTableWriter(writer)
table.SetColMinWidth(colImageNameIndex, imageNameWidth) table.SetColMinWidth(colImageNameIndex, imageNameWidth)
table.SetColMinWidth(colTagIndex, tagWidth) table.SetColMinWidth(colTagIndex, tagWidth)
table.SetColMinWidth(colPlatformIndex, platformWidth)
table.SetColMinWidth(colDigestIndex, digestWidth) table.SetColMinWidth(colDigestIndex, digestWidth)
table.SetColMinWidth(colSizeIndex, sizeWidth) table.SetColMinWidth(colSizeIndex, sizeWidth)
table.SetColMinWidth(colIsSignedIndex, isSignedWidth) table.SetColMinWidth(colIsSignedIndex, isSignedWidth)
@ -712,7 +713,7 @@ func printImageTableHeader(writer io.Writer, verbose bool, maxImageNameLen, maxT
table.SetColMinWidth(colLayersIndex, layersWidth) table.SetColMinWidth(colLayersIndex, layersWidth)
} }
row := make([]string, 7) //nolint:gomnd row := make([]string, 8) //nolint:gomnd
// adding spaces so that image name and tag columns are aligned // adding spaces so that image name and tag columns are aligned
// in case the name/tag are fully shown and too long // in case the name/tag are fully shown and too long
@ -731,6 +732,13 @@ func printImageTableHeader(writer io.Writer, verbose bool, maxImageNameLen, maxT
row[colTagIndex] = "TAG" row[colTagIndex] = "TAG"
} }
if maxPlatformLen > len("OS/ARCH") {
offset = strings.Repeat(" ", maxPlatformLen-len("OS/ARCH"))
row[colPlatformIndex] = "OS/ARCH" + offset
} else {
row[colPlatformIndex] = "OS/ARCH"
}
row[colDigestIndex] = "DIGEST" row[colDigestIndex] = "DIGEST"
row[colSizeIndex] = "SIZE" row[colSizeIndex] = "SIZE"
row[colIsSignedIndex] = "SIGNED" row[colIsSignedIndex] = "SIGNED"
@ -744,7 +752,7 @@ func printImageTableHeader(writer io.Writer, verbose bool, maxImageNameLen, maxT
table.Render() table.Render()
} }
func printCVETableHeader(writer io.Writer, verbose bool, maxImgLen, maxTagLen int) { func printCVETableHeader(writer io.Writer, verbose bool, maxImgLen, maxTagLen, maxPlatformLen int) {
table := getCVETableWriter(writer) table := getCVETableWriter(writer)
row := make([]string, 3) //nolint:gomnd row := make([]string, 3) //nolint:gomnd
row[colCVEIDIndex] = "ID" row[colCVEIDIndex] = "ID"
@ -759,6 +767,7 @@ func printResult(config searchConfig, imageList []imageStruct) error {
var builder strings.Builder var builder strings.Builder
maxImgNameLen := 0 maxImgNameLen := 0
maxTagLen := 0 maxTagLen := 0
maxPlatformLen := 0
if len(imageList) > 0 { if len(imageList) > 0 {
for i := range imageList { for i := range imageList {
@ -769,9 +778,17 @@ func printResult(config searchConfig, imageList []imageStruct) error {
if maxTagLen < len(imageList[i].Tag) { if maxTagLen < len(imageList[i].Tag) {
maxTagLen = len(imageList[i].Tag) maxTagLen = len(imageList[i].Tag)
} }
for j := range imageList[i].Manifests {
platform := imageList[i].Manifests[j].Platform.Os + "/" + imageList[i].Manifests[j].Platform.Arch
if maxPlatformLen < len(platform) {
maxPlatformLen = len(platform)
}
}
} }
printImageTableHeader(&builder, *config.verbose, maxImgNameLen, maxTagLen) printImageTableHeader(&builder, *config.verbose, maxImgNameLen, maxTagLen, maxPlatformLen)
fmt.Fprint(config.resultWriter, builder.String()) fmt.Fprint(config.resultWriter, builder.String())
} }
@ -779,7 +796,7 @@ func printResult(config searchConfig, imageList []imageStruct) error {
img := imageList[i] img := imageList[i]
img.verbose = *config.verbose img.verbose = *config.verbose
out, err := img.string(*config.outputFormat, maxImgNameLen, maxTagLen) out, err := img.string(*config.outputFormat, maxImgNameLen, maxTagLen, maxPlatformLen)
if err != nil { if err != nil {
return err return err
} }

View file

@ -74,10 +74,14 @@ func (service searchService) getDerivedImageListGQL(ctx context.Context, config
Results{ Results{
RepoName, RepoName,
Tag, Tag,
Manifests {
Digest, Digest,
ConfigDigest, ConfigDigest,
Layers {Size Digest}, Layers {Size Digest},
LastUpdated, LastUpdated,
Size
},
LastUpdated,
IsSigned, IsSigned,
Size Size
} }
@ -103,10 +107,14 @@ func (service searchService) getBaseImageListGQL(ctx context.Context, config sea
Results{ Results{
RepoName, RepoName,
Tag, Tag,
Manifests {
Digest, Digest,
ConfigDigest, ConfigDigest,
Layers {Size Digest}, Layers {Size Digest},
LastUpdated, LastUpdated,
Size
},
LastUpdated,
IsSigned, IsSigned,
Size Size
} }
@ -126,8 +134,22 @@ func (service searchService) getBaseImageListGQL(ctx context.Context, config sea
func (service searchService) getImagesGQL(ctx context.Context, config searchConfig, username, password string, func (service searchService) getImagesGQL(ctx context.Context, config searchConfig, username, password string,
imageName string, imageName string,
) (*imageListStructGQL, error) { ) (*imageListStructGQL, error) {
query := fmt.Sprintf(`{ImageList(repo: "%s") { Results {`+` query := fmt.Sprintf(`
RepoName Tag Digest ConfigDigest Size Layers {Size Digest} IsSigned}} {
ImageList(repo: "%s") {
Results {
RepoName Tag
Manifests {
Digest
ConfigDigest
Size
Platform {Os Arch}
Layers {Size Digest}
}
Size
IsSigned
}
}
}`, }`,
imageName) imageName)
result := &imageListStructGQL{} result := &imageListStructGQL{}
@ -144,8 +166,21 @@ func (service searchService) getImagesGQL(ctx context.Context, config searchConf
func (service searchService) getImagesByDigestGQL(ctx context.Context, config searchConfig, username, password string, func (service searchService) getImagesByDigestGQL(ctx context.Context, config searchConfig, username, password string,
digest string, digest string,
) (*imageListStructForDigestGQL, error) { ) (*imageListStructForDigestGQL, error) {
query := fmt.Sprintf(`{ImageListForDigest(id: "%s") { Results{`+` query := fmt.Sprintf(`
RepoName Tag Digest ConfigDigest Size Layers {Size Digest}}} {
ImageListForDigest(id: "%s") {
Results {
RepoName Tag
Manifests {
Digest
ConfigDigest
Size
Layers {Size Digest}
}
Size
IsSigned
}
}
}`, }`,
digest) digest)
result := &imageListStructForDigestGQL{} result := &imageListStructForDigestGQL{}
@ -162,8 +197,21 @@ func (service searchService) getImagesByDigestGQL(ctx context.Context, config se
func (service searchService) getImagesByCveIDGQL(ctx context.Context, config searchConfig, username, func (service searchService) getImagesByCveIDGQL(ctx context.Context, config searchConfig, username,
password, cveID string, password, cveID string,
) (*imagesForCve, error) { ) (*imagesForCve, error) {
query := fmt.Sprintf(`{ImageListForCVE(id: "%s") { Results {`+` query := fmt.Sprintf(`
RepoName Tag Digest ConfigDigest Layers {Size Digest} Size}} {
ImageListForCVE(id: "%s") {
Results {
RepoName Tag
Manifests {
Digest
ConfigDigest
Size
Layers {Size Digest}
}
Size
IsSigned
}
}
}`, }`,
cveID) cveID)
result := &imagesForCve{} result := &imagesForCve{}
@ -199,8 +247,20 @@ func (service searchService) getCveByImageGQL(ctx context.Context, config search
func (service searchService) getTagsForCVEGQL(ctx context.Context, config searchConfig, func (service searchService) getTagsForCVEGQL(ctx context.Context, config searchConfig,
username, password, imageName, cveID string, username, password, imageName, cveID string,
) (*imagesForCve, error) { ) (*imagesForCve, error) {
query := fmt.Sprintf(`{ImageListForCVE(id: "%s") { Results {`+` query := fmt.Sprintf(`
RepoName Tag Digest ConfigDigest Layers {Size Digest} Size}} {
ImageListForCVE(id: "%s") {
Results {
RepoName Tag
Manifests {
Digest
ConfigDigest
Size
Layers {Size Digest}
}
Size
}
}
}`, }`,
cveID) cveID)
result := &imagesForCve{} result := &imagesForCve{}
@ -217,8 +277,20 @@ func (service searchService) getTagsForCVEGQL(ctx context.Context, config search
func (service searchService) getFixedTagsForCVEGQL(ctx context.Context, config searchConfig, func (service searchService) getFixedTagsForCVEGQL(ctx context.Context, config searchConfig,
username, password, imageName, cveID string, username, password, imageName, cveID string,
) (*fixedTags, error) { ) (*fixedTags, error) {
query := fmt.Sprintf(`{ImageListWithCVEFixed(id: "%s", image: "%s") { Results {`+` query := fmt.Sprintf(`
RepoName Tag Digest ConfigDigest Layers {Size Digest} Size}} {
ImageListWithCVEFixed(id: "%s", image: "%s") {
Results {
RepoName Tag
Manifests {
Digest
ConfigDigest
Size
Layers {Size Digest}
}
Size
}
}
}`, }`,
cveID, imageName) cveID, imageName)
@ -349,10 +421,23 @@ func (service searchService) getImagesByCveID(ctx context.Context, config search
defer wtgrp.Done() defer wtgrp.Done()
defer close(rch) defer close(rch)
query := fmt.Sprintf(`{ImageListForCVE(id: "%s") { Results {`+` query := fmt.Sprintf(
RepoName Tag Digest ConfigDigest Layers {Size Digest} Size}} `{
ImageListForCVE(id: "%s") {
Results {
RepoName Tag
Manifests {
Digest
ConfigDigest
Size
Layers {Size Digest}
}
Size
}
}
}`, }`,
cvid) cvid)
result := &imagesForCve{} result := &imagesForCve{}
err := service.makeGraphQLQuery(ctx, config, username, password, query, result) err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
@ -402,10 +487,23 @@ func (service searchService) getImagesByDigest(ctx context.Context, config searc
defer wtgrp.Done() defer wtgrp.Done()
defer close(rch) defer close(rch)
query := fmt.Sprintf(`{ImageListForDigest(id: "%s") { Results {`+` query := fmt.Sprintf(
RepoName Tag Digest ConfigDigest Size Layers {Size Digest}}} `{
ImageListForDigest(id: "%s") {
Results {
RepoName Tag
Manifests {
Digest
ConfigDigest
Size
Layers {Size Digest}
}
Size
}
}
}`, }`,
digest) digest)
result := &imagesForDigest{} result := &imagesForDigest{}
err := service.makeGraphQLQuery(ctx, config, username, password, query, result) err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
@ -455,10 +553,23 @@ func (service searchService) getImageByNameAndCVEID(ctx context.Context, config
defer wtgrp.Done() defer wtgrp.Done()
defer close(rch) defer close(rch)
query := fmt.Sprintf(`{ImageListForCVE(id: "%s") { Results {`+` query := fmt.Sprintf(
RepoName Tag Digest ConfigDigest Size Layers {Size Digest}}} `{
ImageListForCVE(id: "%s") {
Results {
RepoName Tag
Manifests {
Digest
ConfigDigest
Size
Layers {Size Digest}
}
Size
}
}
}`, }`,
cvid) cvid)
result := &imagesForCve{} result := &imagesForCve{}
err := service.makeGraphQLQuery(ctx, config, username, password, query, result) err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
@ -566,10 +677,22 @@ func (service searchService) getFixedTagsForCVE(ctx context.Context, config sear
defer wtgrp.Done() defer wtgrp.Done()
defer close(rch) defer close(rch)
query := fmt.Sprintf(`{ImageListWithCVEFixed (id: "%s", image: "%s") { Results {`+` query := fmt.Sprintf(`
RepoName Tag Digest ConfigDigest Layers {Size Digest} Size}} {
}`, ImageListWithCVEFixed (id: "%s", image: "%s") {
cvid, imageName) Results {
RepoName Tag
Manifests {
Digest
ConfigDigest
Size
Layers {Size Digest}
}
Size
}
}
}`, cvid, imageName)
result := &fixedTags{} result := &fixedTags{}
err := service.makeGraphQLQuery(ctx, config, username, password, query, result) err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
@ -719,8 +842,6 @@ func addManifestCallToPool(ctx context.Context, config searchConfig, pool *reque
) { ) {
defer wtgrp.Done() defer wtgrp.Done()
resultManifest := manifestResponse{}
manifestEndpoint, err := combineServerAndEndpointURL(*config.servURL, manifestEndpoint, err := combineServerAndEndpointURL(*config.servURL,
fmt.Sprintf("/v2/%s/manifests/%s", imageName, tagName)) fmt.Sprintf("/v2/%s/manifests/%s", imageName, tagName))
if err != nil { if err != nil {
@ -730,13 +851,12 @@ func addManifestCallToPool(ctx context.Context, config searchConfig, pool *reque
rch <- stringResult{"", err} rch <- stringResult{"", err}
} }
job := manifestJob{ job := httpJob{
url: manifestEndpoint, url: manifestEndpoint,
username: username, username: username,
imageName: imageName, imageName: imageName,
password: password, password: password,
tagName: tagName, tagName: tagName,
manifestResp: resultManifest,
config: config, config: config,
} }
@ -862,14 +982,27 @@ type PaginatedImagesResult struct {
type imageStruct struct { type imageStruct struct {
RepoName string `json:"repoName"` RepoName string `json:"repoName"`
Tag string `json:"tag"` Tag string `json:"tag"`
ConfigDigest string `json:"configDigest"` Manifests []manifestStruct
Digest string `json:"digest"`
Layers []layer `json:"layers"`
Size string `json:"size"` Size string `json:"size"`
verbose bool verbose bool
IsSigned bool `json:"isSigned"` IsSigned bool `json:"isSigned"`
} }
type manifestStruct struct {
ConfigDigest string `json:"configDigest"`
Digest string `json:"digest"`
Layers []layer `json:"layers"`
Platform platform `json:"platform"`
Size string `json:"size"`
IsSigned bool `json:"isSigned"`
}
type platform struct {
Os string `json:"os"`
Arch string `json:"arch"`
Variant string `json:"variant"`
}
type DerivedImageList struct { type DerivedImageList struct {
Results []imageStruct `json:"results"` Results []imageStruct `json:"results"`
} }
@ -913,14 +1046,14 @@ type imagesForDigest struct {
} }
type layer struct { type layer struct {
Size uint64 `json:"size,string"` Size int64 `json:"size,string"`
Digest string `json:"digest"` Digest string `json:"digest"`
} }
func (img imageStruct) string(format string, maxImgNameLen, maxTagLen int) (string, error) { func (img imageStruct) string(format string, maxImgNameLen, maxTagLen, maxPlatformLen int) (string, error) {
switch strings.ToLower(format) { switch strings.ToLower(format) {
case "", defaultOutoutFormat: case "", defaultOutoutFormat:
return img.stringPlainText(maxImgNameLen, maxTagLen) return img.stringPlainText(maxImgNameLen, maxTagLen, maxPlatformLen)
case "json": case "json":
return img.stringJSON() return img.stringJSON()
case "yml", "yaml": case "yml", "yaml":
@ -930,14 +1063,14 @@ func (img imageStruct) string(format string, maxImgNameLen, maxTagLen int) (stri
} }
} }
func (img imageStruct) stringPlainText(maxImgNameLen, maxTagLen int) (string, error) { func (img imageStruct) stringPlainText(maxImgNameLen, maxTagLen, maxPlatformLen int) (string, error) {
var builder strings.Builder var builder strings.Builder
table := getImageTableWriter(&builder) table := getImageTableWriter(&builder)
table.SetColMinWidth(colImageNameIndex, maxImgNameLen) table.SetColMinWidth(colImageNameIndex, maxImgNameLen)
table.SetColMinWidth(colTagIndex, maxTagLen) table.SetColMinWidth(colTagIndex, maxTagLen)
table.SetColMinWidth(colPlatformIndex, platformWidth)
table.SetColMinWidth(colDigestIndex, digestWidth) table.SetColMinWidth(colDigestIndex, digestWidth)
table.SetColMinWidth(colSizeIndex, sizeWidth) table.SetColMinWidth(colSizeIndex, sizeWidth)
table.SetColMinWidth(colIsSignedIndex, isSignedWidth) table.SetColMinWidth(colIsSignedIndex, isSignedWidth)
@ -952,26 +1085,56 @@ func (img imageStruct) stringPlainText(maxImgNameLen, maxTagLen int) (string, er
imageName = img.RepoName imageName = img.RepoName
tagName = img.Tag tagName = img.Tag
manifestDigest, err := godigest.Parse(img.Digest) if imageNameWidth > maxImgNameLen {
if err != nil { maxImgNameLen = imageNameWidth
return "", fmt.Errorf("error parsing manifest digest %s: %w", img.Digest, err)
} }
configDigest, err := godigest.Parse(img.ConfigDigest) if tagWidth > maxTagLen {
maxTagLen = tagWidth
}
// adding spaces so that image name and tag columns are aligned
// in case the name/tag are fully shown and too long
var offset string
if maxImgNameLen > len(imageName) {
offset = strings.Repeat(" ", maxImgNameLen-len(imageName))
imageName += offset
}
if maxTagLen > len(tagName) {
offset = strings.Repeat(" ", maxTagLen-len(tagName))
tagName += offset
}
for i := range img.Manifests {
manifestDigest, err := godigest.Parse(img.Manifests[i].Digest)
if err != nil { if err != nil {
return "", fmt.Errorf("error parsing config digest %s: %w", img.ConfigDigest, err) return "", fmt.Errorf("error parsing manifest digest %s: %w", img.Manifests[i].Digest, err)
}
configDigest, err := godigest.Parse(img.Manifests[i].ConfigDigest)
if err != nil {
return "", fmt.Errorf("error parsing config digest %s: %w", img.Manifests[i].ConfigDigest, err)
}
platform := getPlatformStr(img.Manifests[i].Platform)
if maxPlatformLen > len(platform) {
offset = strings.Repeat(" ", maxPlatformLen-len(platform))
platform += offset
} }
minifestDigestStr := ellipsize(manifestDigest.Encoded(), digestWidth, "") minifestDigestStr := ellipsize(manifestDigest.Encoded(), digestWidth, "")
configDigestStr := ellipsize(configDigest.Encoded(), configWidth, "") configDigestStr := ellipsize(configDigest.Encoded(), configWidth, "")
imgSize, _ := strconv.ParseUint(img.Size, 10, 64) imgSize, _ := strconv.ParseUint(img.Manifests[i].Size, 10, 64)
size := ellipsize(strings.ReplaceAll(humanize.Bytes(imgSize), " ", ""), sizeWidth, ellipsis) size := ellipsize(strings.ReplaceAll(humanize.Bytes(imgSize), " ", ""), sizeWidth, ellipsis)
isSigned := img.IsSigned isSigned := img.IsSigned
row := make([]string, 7) //nolint:gomnd row := make([]string, 8) //nolint:gomnd
row[colImageNameIndex] = imageName row[colImageNameIndex] = imageName
row[colTagIndex] = tagName row[colTagIndex] = tagName
row[colDigestIndex] = minifestDigestStr row[colDigestIndex] = minifestDigestStr
row[colPlatformIndex] = platform
row[colSizeIndex] = size row[colSizeIndex] = size
row[colIsSignedIndex] = strconv.FormatBool(isSigned) row[colIsSignedIndex] = strconv.FormatBool(isSigned)
@ -983,9 +1146,9 @@ func (img imageStruct) stringPlainText(maxImgNameLen, maxTagLen int) (string, er
table.Append(row) table.Append(row)
if img.verbose { if img.verbose {
for _, entry := range img.Layers { for _, entry := range img.Manifests[i].Layers {
layerSize := entry.Size layerSize := entry.Size
size := ellipsize(strings.ReplaceAll(humanize.Bytes(layerSize), " ", ""), sizeWidth, ellipsis) size := ellipsize(strings.ReplaceAll(humanize.Bytes(uint64(layerSize)), " ", ""), sizeWidth, ellipsis)
layerDigest, err := godigest.Parse(entry.Digest) layerDigest, err := godigest.Parse(entry.Digest)
if err != nil { if err != nil {
@ -994,10 +1157,11 @@ func (img imageStruct) stringPlainText(maxImgNameLen, maxTagLen int) (string, er
layerDigestStr := ellipsize(layerDigest.Encoded(), digestWidth, "") layerDigestStr := ellipsize(layerDigest.Encoded(), digestWidth, "")
layerRow := make([]string, 7) //nolint:gomnd layerRow := make([]string, 8) //nolint:gomnd
layerRow[colImageNameIndex] = "" layerRow[colImageNameIndex] = ""
layerRow[colTagIndex] = "" layerRow[colTagIndex] = ""
layerRow[colDigestIndex] = "" layerRow[colDigestIndex] = ""
layerRow[colPlatformIndex] = ""
layerRow[colSizeIndex] = size layerRow[colSizeIndex] = size
layerRow[colConfigIndex] = "" layerRow[colConfigIndex] = ""
layerRow[colLayersIndex] = layerDigestStr layerRow[colLayersIndex] = layerDigestStr
@ -1005,12 +1169,32 @@ func (img imageStruct) stringPlainText(maxImgNameLen, maxTagLen int) (string, er
table.Append(layerRow) table.Append(layerRow)
} }
} }
}
table.Render() table.Render()
return builder.String(), nil return builder.String(), nil
} }
func getPlatformStr(platf platform) string {
if platf.Arch == "" && platf.Os == "" {
return "N/A"
}
platform := platf.Os
if platf.Arch != "" {
platform = platform + "/" + platf.Arch
platform = strings.Trim(platform, "/")
if platf.Variant != "" {
platform = platform + "/" + platf.Variant
}
}
return platform
}
func (img imageStruct) stringJSON() (string, error) { func (img imageStruct) stringJSON() (string, error) {
json := jsoniter.ConfigCompatibleWithStandardLibrary json := jsoniter.ConfigCompatibleWithStandardLibrary
@ -1035,25 +1219,6 @@ type catalogResponse struct {
Repositories []string `json:"repositories"` Repositories []string `json:"repositories"`
} }
//nolint:tagliatelle
type manifestResponse struct {
Layers []struct {
MediaType string `json:"mediaType"`
Digest string `json:"digest"`
Size uint64 `json:"size"`
} `json:"layers"`
Annotations struct {
WsTychoStackerStackerYaml string `json:"ws.tycho.stacker.stacker_yaml"`
WsTychoStackerGitVersion string `json:"ws.tycho.stacker.git_version"`
} `json:"annotations"`
Config struct {
Size int `json:"size"`
Digest string `json:"digest"`
MediaType string `json:"mediaType"`
} `json:"config"`
SchemaVersion int `json:"schemaVersion"`
}
func combineServerAndEndpointURL(serverURL, endPoint string) (string, error) { func combineServerAndEndpointURL(serverURL, endPoint string) (string, error) {
if !isURL(serverURL) { if !isURL(serverURL) {
return "", zotErrors.ErrInvalidURL return "", zotErrors.ErrInvalidURL
@ -1157,9 +1322,10 @@ func (service searchService) getRepos(ctx context.Context, config searchConfig,
} }
const ( const (
imageNameWidth = 32 imageNameWidth = 10
tagWidth = 24 tagWidth = 8
digestWidth = 8 digestWidth = 8
platformWidth = 14
sizeWidth = 8 sizeWidth = 8
isSignedWidth = 8 isSignedWidth = 8
configWidth = 8 configWidth = 8
@ -1170,9 +1336,10 @@ const (
colTagIndex = 1 colTagIndex = 1
colDigestIndex = 2 colDigestIndex = 2
colConfigIndex = 3 colConfigIndex = 3
colIsSignedIndex = 4 colPlatformIndex = 4
colLayersIndex = 5 colIsSignedIndex = 5
colSizeIndex = 6 colLayersIndex = 6
colSizeIndex = 7
cveIDWidth = 16 cveIDWidth = 16
cveSeverityWidth = 8 cveSeverityWidth = 8

View file

@ -493,7 +493,7 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "test:1.0", Reference: "test:1.0",
}, baseURL, repoName) }, baseURL, repoName)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -507,7 +507,7 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "test:1.0.1", Reference: "test:1.0.1",
}, baseURL, repoName) }, baseURL, repoName)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -525,7 +525,7 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "test:2.0", Reference: "test:2.0",
}, baseURL, repoName) }, baseURL, repoName)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -604,7 +604,7 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: fmt.Sprintf("test:%d.0", index), Reference: fmt.Sprintf("test:%d.0", index),
}, baseURL, repoName) }, baseURL, repoName)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -745,7 +745,7 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "test:1.0", Reference: "test:1.0",
}, baseURL, "firsttest/first") }, baseURL, "firsttest/first")
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -760,7 +760,7 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "test:1.0", Reference: "test:1.0",
}, baseURL, "secondtest/second") }, baseURL, "secondtest/second")
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -779,7 +779,7 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "test:2.0", Reference: "test:2.0",
}, baseURL, "firsttest/first") }, baseURL, "firsttest/first")
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -794,7 +794,7 @@ func CheckWorkflows(t *testing.T, config *compliance.Config) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "test:2.0", Reference: "test:2.0",
}, baseURL, "secondtest/second") }, baseURL, "secondtest/second")
So(err, ShouldBeNil) So(err, ShouldBeNil)

View file

@ -69,7 +69,7 @@ func TestUIExtension(t *testing.T) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: tagName, Reference: tagName,
}, baseURL, repoName) }, baseURL, repoName)
So(err, ShouldBeNil) So(err, ShouldBeNil)

View file

@ -418,6 +418,11 @@ func TestVerifyMandatoryAnnotations(t *testing.T) {
test.CopyTestFiles("../../../test/data", dir) test.CopyTestFiles("../../../test/data", dir)
files, err := os.ReadDir(dir)
So(err, ShouldBeNil)
t.Log("Files in dir:", dir, ": ", files)
ctlr.Config.Storage.RootDirectory = dir ctlr.Config.Storage.RootDirectory = dir
cm := test.NewControllerManager(ctlr) cm := test.NewControllerManager(ctlr)

View file

@ -24,9 +24,14 @@ const (
LabelAnnotationSource = "org.label-schema.vcs-url" LabelAnnotationSource = "org.label-schema.vcs-url"
) )
type Descriptor struct {
Digest godigest.Digest
MediaType string
}
type TagInfo struct { type TagInfo struct {
Name string Name string
Digest godigest.Digest Descriptor Descriptor
Timestamp time.Time Timestamp time.Time
} }
@ -78,6 +83,33 @@ func GetImageDirAndTag(imageName string) (string, string) {
return imageDir, imageTag return imageDir, imageTag
} }
func GetImageDirAndDigest(imageName string) (string, string) {
var imageDir string
var imageDigest string
if strings.Contains(imageName, "@") {
imageDir, imageDigest, _ = strings.Cut(imageName, "@")
} else {
imageDir = imageName
}
return imageDir, imageDigest
}
// GetImageDirAndReference returns the repo, digest and isTag.
func GetImageDirAndReference(imageName string) (string, string, bool) {
if strings.Contains(imageName, "@") {
repo, digest := GetImageDirAndDigest(imageName)
return repo, digest, false
}
repo, tag := GetImageDirAndTag(imageName)
return repo, tag, true
}
// GetImageLastUpdated This method will return last updated timestamp. // GetImageLastUpdated This method will return last updated timestamp.
// The Created timestamp is used, but if it is missing, look at the // The Created timestamp is used, but if it is missing, look at the
// history field and, if provided, return the timestamp of last entry in history. // history field and, if provided, return the timestamp of last entry in history.
@ -277,3 +309,9 @@ func GetAnnotations(annotations, labels map[string]string) ImageAnnotations {
Authors: authors, Authors: authors,
} }
} }
func ReferenceIsDigest(reference string) bool {
_, err := godigest.Parse(reference)
return err == nil
}

File diff suppressed because it is too large Load diff

View file

@ -13,7 +13,7 @@ type RepoSummary struct {
Name string `json:"name"` Name string `json:"name"`
LastUpdated time.Time `json:"lastUpdated"` LastUpdated time.Time `json:"lastUpdated"`
Size string `json:"size"` Size string `json:"size"`
Platforms []OsArch `json:"platforms"` Platforms []Platform `json:"platforms"`
Vendors []string `json:"vendors"` Vendors []string `json:"vendors"`
Score int `json:"score"` Score int `json:"score"`
NewestImage ImageSummary `json:"newestImage"` NewestImage ImageSummary `json:"newestImage"`
@ -22,28 +22,36 @@ type RepoSummary struct {
type ImageSummary struct { type ImageSummary struct {
RepoName string `json:"repoName"` RepoName string `json:"repoName"`
Tag string `json:"tag"` Tag string `json:"tag"`
Digest string `json:"digest"` Manifests []ManifestSummary `json:"manifests"`
ConfigDigest string `json:"configDigest"`
LastUpdated time.Time `json:"lastUpdated"`
IsSigned bool `json:"isSigned"`
Size string `json:"size"` Size string `json:"size"`
Platform OsArch `json:"platform"`
Vendor string `json:"vendor"`
Score int `json:"score"`
DownloadCount int `json:"downloadCount"` DownloadCount int `json:"downloadCount"`
LastUpdated time.Time `json:"lastUpdated"`
Description string `json:"description"` Description string `json:"description"`
IsSigned bool `json:"isSigned"`
Licenses string `json:"licenses"` Licenses string `json:"licenses"`
Labels string `json:"labels"` Labels string `json:"labels"`
Title string `json:"title"` Title string `json:"title"`
Score int `json:"score"`
Source string `json:"source"` Source string `json:"source"`
Documentation string `json:"documentation"` Documentation string `json:"documentation"`
History []LayerHistory `json:"history"`
Layers []LayerSummary `json:"layers"`
Vulnerabilities ImageVulnerabilitySummary `json:"vulnerabilities"`
Authors string `json:"authors"` Authors string `json:"authors"`
Vendor string `json:"vendor"`
Vulnerabilities ImageVulnerabilitySummary `json:"vulnerabilities"`
} }
type OsArch struct { type ManifestSummary struct {
Digest string `json:"digest"`
ConfigDigest string `json:"configDigest"`
LastUpdated time.Time `json:"lastUpdated"`
Size string `json:"size"`
Platform Platform `json:"platform"`
DownloadCount int `json:"downloadCount"`
Layers []LayerSummary `json:"layers"`
History []LayerHistory `json:"history"`
Vulnerabilities ImageVulnerabilitySummary `json:"vulnerabilities"`
}
type Platform struct {
Os string `json:"os"` Os string `json:"os"`
Arch string `json:"arch"` Arch string `json:"arch"`
} }

View file

@ -206,7 +206,16 @@ func (olu BaseOciLayoutUtils) GetImageTagsWithTimestamp(repo string) ([]TagInfo,
timeStamp := GetImageLastUpdated(imageInfo) timeStamp := GetImageLastUpdated(imageInfo)
tagsInfo = append(tagsInfo, TagInfo{Name: val, Timestamp: timeStamp, Digest: digest}) tagsInfo = append(tagsInfo,
TagInfo{
Name: val,
Timestamp: timeStamp,
Descriptor: Descriptor{
Digest: digest,
MediaType: manifest.MediaType,
},
},
)
} }
} }
@ -327,9 +336,8 @@ func (olu BaseOciLayoutUtils) GetRepoLastUpdated(repo string) (TagInfo, error) {
return latestTag, nil return latestTag, nil
} }
func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error) { func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(repoName string) (RepoInfo, error) {
repo := RepoInfo{} repo := RepoInfo{}
repoBlob2Size := make(map[string]int64, 10) repoBlob2Size := make(map[string]int64, 10)
// made up of all manifests, configs and image layers // made up of all manifests, configs and image layers
@ -337,22 +345,22 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error)
imageSummaries := make([]ImageSummary, 0) imageSummaries := make([]ImageSummary, 0)
manifestList, err := olu.GetImageManifests(name) manifestList, err := olu.GetImageManifests(repoName)
if err != nil { if err != nil {
olu.Log.Error().Err(err).Msg("error getting image manifests") olu.Log.Error().Err(err).Msg("error getting image manifests")
return RepoInfo{}, err return RepoInfo{}, err
} }
lastUpdatedTag, err := olu.GetRepoLastUpdated(name) lastUpdatedTag, err := olu.GetRepoLastUpdated(repoName)
if err != nil { if err != nil {
olu.Log.Error().Err(err).Msgf("can't get last updated manifest for repo: %s", name) olu.Log.Error().Err(err).Msgf("can't get last updated manifest for repo: %s", repoName)
return RepoInfo{}, err return RepoInfo{}, err
} }
repoVendorsSet := make(map[string]bool, len(manifestList)) repoVendorsSet := make(map[string]bool, len(manifestList))
repoPlatformsSet := make(map[string]OsArch, len(manifestList)) repoPlatformsSet := make(map[string]Platform, len(manifestList))
var lastUpdatedImageSummary ImageSummary var lastUpdatedImageSummary ImageSummary
@ -367,38 +375,38 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error)
continue continue
} }
manifest, err := olu.GetImageBlobManifest(name, man.Digest) manifest, err := olu.GetImageBlobManifest(repoName, man.Digest)
if err != nil { if err != nil {
olu.Log.Error().Err(err).Msg("error getting image manifest blob") olu.Log.Error().Err(err).Msg("error getting image manifest blob")
return RepoInfo{}, err return RepoInfo{}, err
} }
isSigned := olu.CheckManifestSignature(name, man.Digest) isSigned := olu.CheckManifestSignature(repoName, man.Digest)
manifestSize := olu.GetImageManifestSize(name, man.Digest) manifestSize := olu.GetImageManifestSize(repoName, man.Digest)
olu.Log.Debug().Msg(fmt.Sprintf("%v", man.Digest.String())) olu.Log.Debug().Msg(fmt.Sprintf("%v", man.Digest.String()))
configSize := manifest.Config.Size configSize := manifest.Config.Size
repoBlob2Size[man.Digest.String()] = manifestSize repoBlob2Size[man.Digest.String()] = manifestSize
repoBlob2Size[manifest.Config.Digest.String()] = configSize repoBlob2Size[manifest.Config.Digest.String()] = configSize
imageConfigInfo, err := olu.GetImageConfigInfo(name, man.Digest) imageConfigInfo, err := olu.GetImageConfigInfo(repoName, man.Digest)
if err != nil { if err != nil {
olu.Log.Error().Err(err).Msgf("can't retrieve config info for the image %s %s", name, man.Digest) olu.Log.Error().Err(err).Msgf("can't retrieve config info for the image %s %s", repoName, man.Digest)
continue continue
} }
opSys, arch := olu.GetImagePlatform(imageConfigInfo) opSys, arch := olu.GetImagePlatform(imageConfigInfo)
osArch := OsArch{ platform := Platform{
Os: opSys, Os: opSys,
Arch: arch, Arch: arch,
} }
if opSys != "" || arch != "" { if opSys != "" || arch != "" {
osArchString := strings.TrimSpace(fmt.Sprintf("%s %s", opSys, arch)) platformString := strings.TrimSpace(fmt.Sprintf("%s %s", opSys, arch))
repoPlatformsSet[osArchString] = osArch repoPlatformsSet[platformString] = platform
} }
layers := make([]LayerSummary, 0) layers := make([]LayerSummary, 0)
@ -457,7 +465,7 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error)
if layersIterator+1 > len(layers) { if layersIterator+1 > len(layers) {
olu.Log.Error().Err(errors.ErrBadLayerCount). olu.Log.Error().Err(errors.ErrBadLayerCount).
Msgf("error on creating layer history for imaeg %s %s", name, man.Digest) Msgf("error on creating layer history for image %s %s", repoName, man.Digest)
break break
} }
@ -477,29 +485,35 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error)
score := 0 score := 0
imageSummary := ImageSummary{ imageSummary := ImageSummary{
RepoName: name, RepoName: repoName,
Tag: tag, Tag: tag,
LastUpdated: lastUpdated, Manifests: []ManifestSummary{
{
Digest: manifestDigest, Digest: manifestDigest,
ConfigDigest: configDigest, ConfigDigest: configDigest,
LastUpdated: lastUpdated,
Size: size,
Platform: platform,
Layers: layers,
History: allHistory,
},
},
LastUpdated: lastUpdated,
IsSigned: isSigned, IsSigned: isSigned,
Size: size, Size: size,
Platform: osArch,
Vendor: annotations.Vendor,
Score: score, Score: score,
Description: annotations.Description, Description: annotations.Description,
Title: annotations.Title, Title: annotations.Title,
Documentation: annotations.Documentation, Documentation: annotations.Documentation,
Licenses: annotations.Licenses, Licenses: annotations.Licenses,
Labels: annotations.Labels, Labels: annotations.Labels,
Vendor: annotations.Vendor,
Source: annotations.Source, Source: annotations.Source,
Layers: layers,
History: allHistory,
} }
imageSummaries = append(imageSummaries, imageSummary) imageSummaries = append(imageSummaries, imageSummary)
if man.Digest.String() == lastUpdatedTag.Digest.String() { if man.Digest.String() == lastUpdatedTag.Descriptor.Digest.String() {
lastUpdatedImageSummary = imageSummary lastUpdatedImageSummary = imageSummary
} }
} }
@ -512,10 +526,10 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error)
size := strconv.FormatInt(repoSize, 10) size := strconv.FormatInt(repoSize, 10)
repoPlatforms := make([]OsArch, 0, len(repoPlatformsSet)) repoPlatforms := make([]Platform, 0, len(repoPlatformsSet))
for _, osArch := range repoPlatformsSet { for _, platform := range repoPlatformsSet {
repoPlatforms = append(repoPlatforms, osArch) repoPlatforms = append(repoPlatforms, platform)
} }
repoVendors := make([]string, 0, len(repoVendorsSet)) repoVendors := make([]string, 0, len(repoVendorsSet))
@ -526,7 +540,7 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(name string) (RepoInfo, error)
} }
summary := RepoSummary{ summary := RepoSummary{
Name: name, Name: repoName,
LastUpdated: lastUpdatedTag.Timestamp, LastUpdated: lastUpdatedTag.Timestamp,
Size: size, Size: size,
Platforms: repoPlatforms, Platforms: repoPlatforms,

View file

@ -19,6 +19,7 @@ import (
"zotregistry.io/zot/pkg/extensions/search/common" "zotregistry.io/zot/pkg/extensions/search/common"
"zotregistry.io/zot/pkg/extensions/search/convert" "zotregistry.io/zot/pkg/extensions/search/convert"
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve" cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
"zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/meta/repodb" "zotregistry.io/zot/pkg/meta/repodb"
bolt "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper" bolt "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper"
. "zotregistry.io/zot/pkg/test" . "zotregistry.io/zot/pkg/test"
@ -28,7 +29,7 @@ import (
var ErrTestError = errors.New("TestError") var ErrTestError = errors.New("TestError")
func TestConvertErrors(t *testing.T) { func TestConvertErrors(t *testing.T) {
Convey("", t, func() { Convey("Convert Errors", t, func() {
repoDB, err := bolt.NewBoltDBWrapper(bolt.DBParameters{ repoDB, err := bolt.NewBoltDBWrapper(bolt.DBParameters{
RootDir: t.TempDir(), RootDir: t.TempDir(),
}) })
@ -59,7 +60,7 @@ func TestConvertErrors(t *testing.T) {
err = repoDB.SetRepoTag("repo1", "0.1.0", digest11, ispec.MediaTypeImageManifest) err = repoDB.SetRepoTag("repo1", "0.1.0", digest11, ispec.MediaTypeImageManifest)
So(err, ShouldBeNil) So(err, ShouldBeNil)
repoMetas, manifestMetaMap, _, err := repoDB.SearchRepos(context.Background(), "", repodb.Filter{}, repoMetas, manifestMetaMap, _, _, err := repoDB.SearchRepos(context.Background(), "", repodb.Filter{},
repodb.PageInput{}) repodb.PageInput{})
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -70,9 +71,11 @@ func TestConvertErrors(t *testing.T) {
ctx, ctx,
repoMetas[0], repoMetas[0],
manifestMetaMap, manifestMetaMap,
map[string]repodb.IndexData{},
convert.SkipQGLField{}, convert.SkipQGLField{},
mocks.CveInfoMock{ mocks.CveInfoMock{
GetCVESummaryForImageFn: func(image string) (cveinfo.ImageCVESummary, error) { GetCVESummaryForImageFn: func(repo string, reference string,
) (cveinfo.ImageCVESummary, error) {
return cveinfo.ImageCVESummary{}, ErrTestError return cveinfo.ImageCVESummary{}, ErrTestError
}, },
}, },
@ -80,6 +83,167 @@ func TestConvertErrors(t *testing.T) {
So(graphql.GetErrors(ctx).Error(), ShouldContainSubstring, "unable to run vulnerability scan on tag") So(graphql.GetErrors(ctx).Error(), ShouldContainSubstring, "unable to run vulnerability scan on tag")
}) })
Convey("ImageIndex2ImageSummary errors", t, func() {
ctx := graphql.WithResponseContext(context.Background(),
graphql.DefaultErrorPresenter, graphql.DefaultRecover)
_, _, err := convert.ImageIndex2ImageSummary(
ctx,
"repo",
"tag",
godigest.FromString("indexDigest"),
true,
repodb.RepoMetadata{},
repodb.IndexData{
IndexBlob: []byte("bad json"),
},
map[string]repodb.ManifestMetadata{},
mocks.CveInfoMock{},
)
So(err, ShouldNotBeNil)
})
Convey("ImageIndex2ImageSummary cve scanning", t, func() {
ctx := graphql.WithResponseContext(context.Background(),
graphql.DefaultErrorPresenter, graphql.DefaultRecover)
_, _, err := convert.ImageIndex2ImageSummary(
ctx,
"repo",
"tag",
godigest.FromString("indexDigest"),
false,
repodb.RepoMetadata{},
repodb.IndexData{
IndexBlob: []byte("{}"),
},
map[string]repodb.ManifestMetadata{},
mocks.CveInfoMock{
GetCVESummaryForImageFn: func(repo, reference string,
) (cveinfo.ImageCVESummary, error) {
return cveinfo.ImageCVESummary{}, ErrTestError
},
},
)
So(err, ShouldBeNil)
})
Convey("ImageManifest2ImageSummary", t, func() {
ctx := graphql.WithResponseContext(context.Background(),
graphql.DefaultErrorPresenter, graphql.DefaultRecover)
_, _, err := convert.ImageManifest2ImageSummary(
ctx,
"repo",
"tag",
godigest.FromString("manifestDigest"),
false,
repodb.RepoMetadata{},
repodb.ManifestMetadata{
ManifestBlob: []byte("{}"),
ConfigBlob: []byte("{}"),
},
mocks.CveInfoMock{
GetCVESummaryForImageFn: func(repo, reference string,
) (cveinfo.ImageCVESummary, error) {
return cveinfo.ImageCVESummary{}, ErrTestError
},
},
)
So(err, ShouldBeNil)
})
Convey("ImageManifest2ManifestSummary", t, func() {
ctx := graphql.WithResponseContext(context.Background(),
graphql.DefaultErrorPresenter, graphql.DefaultRecover)
// with bad config json, error while unmarshaling
_, _, err := convert.ImageManifest2ManifestSummary(
ctx,
"repo",
"tag",
ispec.Descriptor{
Digest: "dig",
MediaType: ispec.MediaTypeImageManifest,
},
false,
repodb.ManifestMetadata{
ManifestBlob: []byte("{}"),
ConfigBlob: []byte("bad json"),
},
mocks.CveInfoMock{
GetCVESummaryForImageFn: func(repo, reference string,
) (cveinfo.ImageCVESummary, error) {
return cveinfo.ImageCVESummary{}, ErrTestError
},
},
)
So(err, ShouldNotBeNil)
// CVE scan using platform
configBlob, err := json.Marshal(ispec.Image{
Platform: ispec.Platform{
OS: "os",
Architecture: "arch",
},
})
So(err, ShouldBeNil)
_, _, err = convert.ImageManifest2ManifestSummary(
ctx,
"repo",
"tag",
ispec.Descriptor{
Digest: "dig",
MediaType: ispec.MediaTypeImageManifest,
},
false,
repodb.ManifestMetadata{
ManifestBlob: []byte("{}"),
ConfigBlob: configBlob,
},
mocks.CveInfoMock{
GetCVESummaryForImageFn: func(repo, reference string,
) (cveinfo.ImageCVESummary, error) {
return cveinfo.ImageCVESummary{}, ErrTestError
},
},
)
So(err, ShouldBeNil)
})
Convey("RepoMeta2ExpandedRepoInfo", t, func() {
ctx := graphql.WithResponseContext(context.Background(),
graphql.DefaultErrorPresenter, graphql.DefaultRecover)
// with bad config json, error while unmarshaling
_, imageSummaries := convert.RepoMeta2ExpandedRepoInfo(
ctx,
repodb.RepoMetadata{
Tags: map[string]repodb.Descriptor{
"tag1": {Digest: "dig", MediaType: ispec.MediaTypeImageManifest},
},
},
map[string]repodb.ManifestMetadata{
"dig": {
ManifestBlob: []byte("{}"),
ConfigBlob: []byte("bad json"),
},
},
map[string]repodb.IndexData{},
convert.SkipQGLField{
Vulnerabilities: false,
},
mocks.CveInfoMock{
GetCVESummaryForImageFn: func(repo, reference string,
) (cveinfo.ImageCVESummary, error) {
return cveinfo.ImageCVESummary{}, ErrTestError
},
}, log.NewLogger("debug", ""),
)
So(len(imageSummaries), ShouldEqual, 0)
})
} }
func TestBuildImageInfo(t *testing.T) { func TestBuildImageInfo(t *testing.T) {
@ -159,7 +323,7 @@ func TestBuildImageInfo(t *testing.T) {
Layers: [][]byte{ Layers: [][]byte{
layerblob, layerblob,
}, },
Tag: "0.0.1", Reference: "0.0.1",
}, },
baseURL, baseURL,
imageName, imageName,
@ -174,7 +338,7 @@ func TestBuildImageInfo(t *testing.T) {
imageSummary := convert.BuildImageInfo(imageName, imageName, manifestDigest, ispecManifest, imageSummary := convert.BuildImageInfo(imageName, imageName, manifestDigest, ispecManifest,
imageConfig, isSigned) imageConfig, isSigned)
So(len(imageSummary.Layers), ShouldEqual, len(ispecManifest.Layers)) So(len(imageSummary.Manifests[0].Layers), ShouldEqual, len(ispecManifest.Layers))
imageSummaryLayerSize, err := strconv.Atoi(*imageSummary.Size) imageSummaryLayerSize, err := strconv.Atoi(*imageSummary.Size)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(imageSummaryLayerSize, ShouldEqual, manifestLayersSize) So(imageSummaryLayerSize, ShouldEqual, manifestLayersSize)
@ -249,7 +413,7 @@ func TestBuildImageInfo(t *testing.T) {
layerblob, layerblob,
layerblob2, layerblob2,
}, },
Tag: "0.0.1", Reference: "0.0.1",
}, },
baseURL, baseURL,
imageName, imageName,
@ -264,7 +428,7 @@ func TestBuildImageInfo(t *testing.T) {
imageSummary := convert.BuildImageInfo(imageName, imageName, manifestDigest, ispecManifest, imageSummary := convert.BuildImageInfo(imageName, imageName, manifestDigest, ispecManifest,
imageConfig, isSigned) imageConfig, isSigned)
So(len(imageSummary.Layers), ShouldEqual, len(ispecManifest.Layers)) So(len(imageSummary.Manifests[0].Layers), ShouldEqual, len(ispecManifest.Layers))
imageSummaryLayerSize, err := strconv.Atoi(*imageSummary.Size) imageSummaryLayerSize, err := strconv.Atoi(*imageSummary.Size)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(imageSummaryLayerSize, ShouldEqual, manifestLayersSize) So(imageSummaryLayerSize, ShouldEqual, manifestLayersSize)
@ -331,7 +495,7 @@ func TestBuildImageInfo(t *testing.T) {
Layers: [][]byte{ Layers: [][]byte{
layerblob, layerblob,
}, },
Tag: "0.0.1", Reference: "0.0.1",
}, },
baseURL, baseURL,
imageName, imageName,
@ -346,7 +510,7 @@ func TestBuildImageInfo(t *testing.T) {
imageSummary := convert.BuildImageInfo(imageName, imageName, manifestDigest, ispecManifest, imageSummary := convert.BuildImageInfo(imageName, imageName, manifestDigest, ispecManifest,
imageConfig, isSigned) imageConfig, isSigned)
So(len(imageSummary.Layers), ShouldEqual, len(ispecManifest.Layers)) So(len(imageSummary.Manifests[0].Layers), ShouldEqual, len(ispecManifest.Layers))
imageSummaryLayerSize, err := strconv.Atoi(*imageSummary.Size) imageSummaryLayerSize, err := strconv.Atoi(*imageSummary.Size)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(imageSummaryLayerSize, ShouldEqual, manifestLayersSize) So(imageSummaryLayerSize, ShouldEqual, manifestLayersSize)

View file

@ -10,7 +10,6 @@ import (
"zotregistry.io/zot/pkg/extensions/search/common" "zotregistry.io/zot/pkg/extensions/search/common"
"zotregistry.io/zot/pkg/extensions/search/gql_generated" "zotregistry.io/zot/pkg/extensions/search/gql_generated"
"zotregistry.io/zot/pkg/log" "zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/meta/repodb"
) )
func BuildImageInfo(repo string, tag string, manifestDigest godigest.Digest, func BuildImageInfo(repo string, tag string, manifestDigest godigest.Digest,
@ -58,12 +57,21 @@ func BuildImageInfo(repo string, tag string, manifestDigest godigest.Digest,
imageInfo := &gql_generated.ImageSummary{ imageInfo := &gql_generated.ImageSummary{
RepoName: &repo, RepoName: &repo,
Tag: &tag, Tag: &tag,
Manifests: []*gql_generated.ManifestSummary{
{
Digest: &formattedManifestDigest, Digest: &formattedManifestDigest,
ConfigDigest: &configDigest, ConfigDigest: &configDigest,
Size: &formattedSize,
Layers: layers, Layers: layers,
Size: &formattedSize,
History: allHistory, History: allHistory,
Vendor: &annotations.Vendor, Platform: &gql_generated.Platform{
Os: &imageConfig.OS,
Arch: &imageConfig.Architecture,
},
LastUpdated: &lastUpdated,
},
},
Size: &formattedSize,
Description: &annotations.Description, Description: &annotations.Description,
Title: &annotations.Title, Title: &annotations.Title,
Documentation: &annotations.Documentation, Documentation: &annotations.Documentation,
@ -71,12 +79,9 @@ func BuildImageInfo(repo string, tag string, manifestDigest godigest.Digest,
Labels: &annotations.Labels, Labels: &annotations.Labels,
Source: &annotations.Source, Source: &annotations.Source,
Authors: &authors, Authors: &authors,
Vendor: &annotations.Vendor,
LastUpdated: &lastUpdated, LastUpdated: &lastUpdated,
IsSigned: &isSigned, IsSigned: &isSigned,
Platform: &gql_generated.OsArch{
Os: &imageConfig.OS,
Arch: &imageConfig.Architecture,
},
} }
return imageInfo return imageInfo
@ -108,13 +113,23 @@ func BuildImageInfo(repo string, tag string, manifestDigest godigest.Digest,
return &gql_generated.ImageSummary{ return &gql_generated.ImageSummary{
RepoName: &repo, RepoName: &repo,
Tag: &tag, Tag: &tag,
Manifests: []*gql_generated.ManifestSummary{
{
Digest: &formattedManifestDigest, Digest: &formattedManifestDigest,
ConfigDigest: &configDigest, ConfigDigest: &configDigest,
Size: &formattedSize,
Layers: layers, Layers: layers,
Size: &formattedSize,
History: allHistory, History: allHistory,
Vendor: &annotations.Vendor, Platform: &gql_generated.Platform{
Os: &imageConfig.OS,
Arch: &imageConfig.Architecture,
},
LastUpdated: &lastUpdated,
},
},
Size: &formattedSize,
Description: &annotations.Description, Description: &annotations.Description,
Vendor: &annotations.Vendor,
Title: &annotations.Title, Title: &annotations.Title,
Documentation: &annotations.Documentation, Documentation: &annotations.Documentation,
Licenses: &annotations.Licenses, Licenses: &annotations.Licenses,
@ -123,10 +138,6 @@ func BuildImageInfo(repo string, tag string, manifestDigest godigest.Digest,
Authors: &authors, Authors: &authors,
LastUpdated: &lastUpdated, LastUpdated: &lastUpdated,
IsSigned: &isSigned, IsSigned: &isSigned,
Platform: &gql_generated.OsArch{
Os: &imageConfig.OS,
Arch: &imageConfig.Architecture,
},
} }
} }
@ -154,25 +165,31 @@ func BuildImageInfo(repo string, tag string, manifestDigest godigest.Digest,
imageInfo := &gql_generated.ImageSummary{ imageInfo := &gql_generated.ImageSummary{
RepoName: &repo, RepoName: &repo,
Tag: &tag, Tag: &tag,
Manifests: []*gql_generated.ManifestSummary{
{
Digest: &formattedManifestDigest, Digest: &formattedManifestDigest,
ConfigDigest: &configDigest, ConfigDigest: &configDigest,
Size: &formattedSize,
Layers: layers, Layers: layers,
History: allHistory, History: allHistory,
Vendor: &annotations.Vendor, Platform: &gql_generated.Platform{
Os: &imageConfig.OS,
Arch: &imageConfig.Architecture,
},
Size: &formattedSize,
LastUpdated: &lastUpdated,
},
},
Size: &formattedSize,
Description: &annotations.Description, Description: &annotations.Description,
Title: &annotations.Title, Title: &annotations.Title,
Documentation: &annotations.Documentation, Documentation: &annotations.Documentation,
Licenses: &annotations.Licenses, Licenses: &annotations.Licenses,
Labels: &annotations.Labels, Labels: &annotations.Labels,
Source: &annotations.Source, Source: &annotations.Source,
Vendor: &annotations.Vendor,
Authors: &authors, Authors: &authors,
LastUpdated: &lastUpdated, LastUpdated: &lastUpdated,
IsSigned: &isSigned, IsSigned: &isSigned,
Platform: &gql_generated.OsArch{
Os: &imageConfig.OS,
Arch: &imageConfig.Architecture,
},
} }
return imageInfo return imageInfo
@ -180,23 +197,12 @@ func BuildImageInfo(repo string, tag string, manifestDigest godigest.Digest,
// updateRepoBlobsMap adds all the image blobs and their respective size to the repo blobs map // updateRepoBlobsMap adds all the image blobs and their respective size to the repo blobs map
// and returnes the total size of the image. // and returnes the total size of the image.
func updateRepoBlobsMap(manifestDigest string, manifestSize int64, configDigest string, configSize int64, func updateRepoBlobsMap(imageBlobs map[string]int64, repoBlob2Size map[string]int64) int64 {
layers []ispec.Descriptor, repoBlob2Size map[string]int64,
) int64 {
imgSize := int64(0) imgSize := int64(0)
// add config size for digest, size := range imageBlobs {
imgSize += configSize repoBlob2Size[digest] = size
repoBlob2Size[configDigest] = configSize imgSize += size
// add manifest size
imgSize += manifestSize
repoBlob2Size[manifestDigest] = manifestSize
// add layers size
for _, layer := range layers {
repoBlob2Size[layer.Digest.String()] = layer.Size
imgSize += layer.Size
} }
return imgSize return imgSize
@ -267,14 +273,3 @@ func getAllHistory(manifestContent ispec.Manifest, configContent ispec.Image) (
return allHistory, nil return allHistory, nil
} }
func imageHasSignatures(signatures repodb.ManifestSignatures) bool {
// (sigType, signatures)
for _, sigs := range signatures {
if len(sigs) > 0 {
return true
}
}
return false
}

View file

@ -9,12 +9,15 @@ import (
"time" "time"
"github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql"
godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1" ispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/vektah/gqlparser/v2/gqlerror" "github.com/vektah/gqlparser/v2/gqlerror"
"zotregistry.io/zot/pkg/extensions/search/common" "zotregistry.io/zot/pkg/extensions/search/common"
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve" cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
"zotregistry.io/zot/pkg/extensions/search/gql_generated" "zotregistry.io/zot/pkg/extensions/search/gql_generated"
"zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/meta/repodb" "zotregistry.io/zot/pkg/meta/repodb"
) )
@ -23,11 +26,12 @@ type SkipQGLField struct {
} }
func RepoMeta2RepoSummary(ctx context.Context, repoMeta repodb.RepoMetadata, func RepoMeta2RepoSummary(ctx context.Context, repoMeta repodb.RepoMetadata,
manifestMetaMap map[string]repodb.ManifestMetadata, skip SkipQGLField, cveInfo cveinfo.CveInfo, manifestMetaMap map[string]repodb.ManifestMetadata, indexDataMap map[string]repodb.IndexData,
skip SkipQGLField, cveInfo cveinfo.CveInfo,
) *gql_generated.RepoSummary { ) *gql_generated.RepoSummary {
var ( var (
repoLastUpdatedTimestamp = time.Time{} repoLastUpdatedTimestamp = time.Time{}
repoPlatformsSet = map[string]*gql_generated.OsArch{} repoPlatformsSet = map[string]*gql_generated.Platform{}
repoVendorsSet = map[string]bool{} repoVendorsSet = map[string]bool{}
lastUpdatedImageSummary *gql_generated.ImageSummary lastUpdatedImageSummary *gql_generated.ImageSummary
repoStarCount = repoMeta.Stars repoStarCount = repoMeta.Stars
@ -45,107 +49,32 @@ func RepoMeta2RepoSummary(ctx context.Context, repoMeta repodb.RepoMetadata,
) )
for tag, descriptor := range repoMeta.Tags { for tag, descriptor := range repoMeta.Tags {
var ( imageSummary, imageBlobsMap, err := Descriptor2ImageSummary(ctx, descriptor, repoMeta.Name, tag, true, repoMeta,
manifestContent ispec.Manifest manifestMetaMap, indexDataMap, cveInfo)
manifestDigest = descriptor.Digest
imageSignatures = repoMeta.Signatures[descriptor.Digest]
)
err := json.Unmarshal(manifestMetaMap[manifestDigest].ManifestBlob, &manifestContent)
if err != nil { if err != nil {
graphql.AddError(ctx, gqlerror.Errorf("can't unmarshal manifest blob for image: %s:%s, manifest digest: %s, "+
"error: %s", repoMeta.Name, tag, manifestDigest, err.Error()))
continue continue
} }
var configContent ispec.Image for blobDigest, blobSize := range imageBlobsMap {
repoBlob2Size[blobDigest] = blobSize
err = json.Unmarshal(manifestMetaMap[manifestDigest].ConfigBlob, &configContent)
if err != nil {
graphql.AddError(ctx, gqlerror.Errorf("can't unmarshal config blob for image: %s:%s, manifest digest: %s, error: %s",
repoMeta.Name, tag, manifestDigest, err.Error()))
continue
} }
var ( for _, manifestSummary := range imageSummary.Manifests {
tag = tag if *manifestSummary.Platform.Os != "" || *manifestSummary.Platform.Arch != "" {
isSigned = imageHasSignatures(imageSignatures) opSys, arch := *manifestSummary.Platform.Os, *manifestSummary.Platform.Arch
configDigest = manifestContent.Config.Digest.String()
configSize = manifestContent.Config.Size
opSys = configContent.OS
arch = configContent.Architecture
osArch = gql_generated.OsArch{Os: &opSys, Arch: &arch}
imageLastUpdated = common.GetImageLastUpdated(configContent)
downloadCount = repoMeta.Statistics[descriptor.Digest].DownloadCount
size = updateRepoBlobsMap( platformString := strings.TrimSpace(fmt.Sprintf("%s %s", opSys, arch))
manifestDigest, int64(len(manifestMetaMap[manifestDigest].ManifestBlob)), repoPlatformsSet[platformString] = &gql_generated.Platform{Os: &opSys, Arch: &arch}
configDigest, configSize,
manifestContent.Layers,
repoBlob2Size)
imageSize = strconv.FormatInt(size, 10)
)
annotations := common.GetAnnotations(manifestContent.Annotations, configContent.Config.Labels)
authors := annotations.Authors
if authors == "" {
authors = configContent.Author
} }
historyEntries, err := getAllHistory(manifestContent, configContent) repoDownloadCount += manifestMetaMap[*manifestSummary.Digest].DownloadCount
if err != nil {
graphql.AddError(ctx, gqlerror.Errorf("error generating history on tag %s in repo %s: "+
"manifest digest: %s, error: %s", tag, repoMeta.Name, manifestDigest, err.Error()))
} }
imageCveSummary := cveinfo.ImageCVESummary{} if *imageSummary.Vendor != "" {
repoVendorsSet[*imageSummary.Vendor] = true
imageSummary := gql_generated.ImageSummary{
RepoName: &repoName,
Tag: &tag,
Digest: &manifestDigest,
ConfigDigest: &configDigest,
LastUpdated: &imageLastUpdated,
IsSigned: &isSigned,
Size: &imageSize,
Platform: &osArch,
Vendor: &annotations.Vendor,
DownloadCount: &downloadCount,
Layers: getLayersSummaries(manifestContent),
Description: &annotations.Description,
Title: &annotations.Title,
Documentation: &annotations.Documentation,
Licenses: &annotations.Licenses,
Labels: &annotations.Labels,
Source: &annotations.Source,
Authors: &authors,
History: historyEntries,
Vulnerabilities: &gql_generated.ImageVulnerabilitySummary{
MaxSeverity: &imageCveSummary.MaxSeverity,
Count: &imageCveSummary.Count,
},
} }
if annotations.Vendor != "" { lastUpdatedImageSummary = UpdateLastUpdatedTimestam(&repoLastUpdatedTimestamp, lastUpdatedImageSummary, imageSummary)
repoVendorsSet[annotations.Vendor] = true
}
if opSys != "" || arch != "" {
osArchString := strings.TrimSpace(fmt.Sprintf("%s %s", opSys, arch))
repoPlatformsSet[osArchString] = &gql_generated.OsArch{Os: &opSys, Arch: &arch}
}
if repoLastUpdatedTimestamp.Equal(time.Time{}) {
// initialize with first time value
repoLastUpdatedTimestamp = imageLastUpdated
lastUpdatedImageSummary = &imageSummary
} else if repoLastUpdatedTimestamp.Before(imageLastUpdated) {
repoLastUpdatedTimestamp = imageLastUpdated
lastUpdatedImageSummary = &imageSummary
}
repoDownloadCount += repoMeta.Statistics[descriptor.Digest].DownloadCount repoDownloadCount += repoMeta.Statistics[descriptor.Digest].DownloadCount
} }
@ -158,9 +87,9 @@ func RepoMeta2RepoSummary(ctx context.Context, repoMeta repodb.RepoMetadata,
repoSize := strconv.FormatInt(size, 10) repoSize := strconv.FormatInt(size, 10)
score := 0 score := 0
repoPlatforms := make([]*gql_generated.OsArch, 0, len(repoPlatformsSet)) repoPlatforms := make([]*gql_generated.Platform, 0, len(repoPlatformsSet))
for _, osArch := range repoPlatformsSet { for _, platform := range repoPlatformsSet {
repoPlatforms = append(repoPlatforms, osArch) repoPlatforms = append(repoPlatforms, platform)
} }
repoVendors := make([]*string, 0, len(repoVendorsSet)) repoVendors := make([]*string, 0, len(repoVendorsSet))
@ -173,9 +102,7 @@ func RepoMeta2RepoSummary(ctx context.Context, repoMeta repodb.RepoMetadata,
// We only scan the latest image on the repo for performance reasons // We only scan the latest image on the repo for performance reasons
// Check if vulnerability scanning is disabled // Check if vulnerability scanning is disabled
if cveInfo != nil && lastUpdatedImageSummary != nil && !skip.Vulnerabilities { if cveInfo != nil && lastUpdatedImageSummary != nil && !skip.Vulnerabilities {
imageName := fmt.Sprintf("%s:%s", repoMeta.Name, *lastUpdatedImageSummary.Tag) imageCveSummary, err := cveInfo.GetCVESummaryForImage(repoMeta.Name, *lastUpdatedImageSummary.Tag)
imageCveSummary, err := cveInfo.GetCVESummaryForImage(imageName)
if err != nil { if err != nil {
// Log the error, but we should still include the image in results // Log the error, but we should still include the image in results
graphql.AddError( graphql.AddError(
@ -208,70 +135,192 @@ func RepoMeta2RepoSummary(ctx context.Context, repoMeta repodb.RepoMetadata,
} }
} }
func RepoMeta2ImageSummaries(ctx context.Context, repoMeta repodb.RepoMetadata, func UpdateLastUpdatedTimestam(repoLastUpdatedTimestamp *time.Time, lastUpdatedImageSummary *gql_generated.ImageSummary,
manifestMetaMap map[string]repodb.ManifestMetadata, skip SkipQGLField, cveInfo cveinfo.CveInfo, imageSummary *gql_generated.ImageSummary,
) []*gql_generated.ImageSummary { ) *gql_generated.ImageSummary {
imageSummaries := make([]*gql_generated.ImageSummary, 0, len(repoMeta.Tags)) newLastUpdatedImageSummary := lastUpdatedImageSummary
for tag, descriptor := range repoMeta.Tags { if repoLastUpdatedTimestamp.Equal(time.Time{}) {
var ( // initialize with first time value
manifestContent ispec.Manifest *repoLastUpdatedTimestamp = *imageSummary.LastUpdated
manifestDigest = descriptor.Digest newLastUpdatedImageSummary = imageSummary
imageSignatures = repoMeta.Signatures[descriptor.Digest] } else if repoLastUpdatedTimestamp.Before(*imageSummary.LastUpdated) {
) *repoLastUpdatedTimestamp = *imageSummary.LastUpdated
newLastUpdatedImageSummary = imageSummary
err := json.Unmarshal(manifestMetaMap[manifestDigest].ManifestBlob, &manifestContent)
if err != nil {
graphql.AddError(ctx, gqlerror.Errorf("can't unmarshal manifest blob for image: %s:%s, "+
"manifest digest: %s, error: %s", repoMeta.Name, tag, manifestDigest, err.Error()))
continue
} }
var configContent ispec.Image return newLastUpdatedImageSummary
}
err = json.Unmarshal(manifestMetaMap[manifestDigest].ConfigBlob, &configContent) func Descriptor2ImageSummary(ctx context.Context, descriptor repodb.Descriptor, repo, tag string, skipCVE bool,
repoMeta repodb.RepoMetadata, manifestMetaMap map[string]repodb.ManifestMetadata,
indexDataMap map[string]repodb.IndexData, cveInfo cveinfo.CveInfo,
) (*gql_generated.ImageSummary, map[string]int64, error) {
switch descriptor.MediaType {
case ispec.MediaTypeImageManifest:
return ImageManifest2ImageSummary(ctx, repo, tag, godigest.Digest(descriptor.Digest), skipCVE,
repoMeta, manifestMetaMap[descriptor.Digest], cveInfo)
case ispec.MediaTypeImageIndex:
return ImageIndex2ImageSummary(ctx, repo, tag, godigest.Digest(descriptor.Digest), skipCVE,
repoMeta, indexDataMap[descriptor.Digest], manifestMetaMap, cveInfo)
default:
return &gql_generated.ImageSummary{}, map[string]int64{}, nil
}
}
func ImageIndex2ImageSummary(ctx context.Context, repo, tag string, indexDigest godigest.Digest, skipCVE bool,
repoMeta repodb.RepoMetadata, indexData repodb.IndexData, manifestMetaMap map[string]repodb.ManifestMetadata,
cveInfo cveinfo.CveInfo,
) (*gql_generated.ImageSummary, map[string]int64, error) {
var indexContent ispec.Index
err := json.Unmarshal(indexData.IndexBlob, &indexContent)
if err != nil { if err != nil {
graphql.AddError(ctx, gqlerror.Errorf("can't unmarshal config blob for image: %s:%s, "+ return &gql_generated.ImageSummary{}, map[string]int64{}, err
"manifest digest: %s, error: %s", repoMeta.Name, tag, manifestDigest, err.Error())) }
continue var (
indexLastUpdated time.Time
isSigned bool
totalIndexSize int64
indexSize string
totalDownloadCount int
maxSeverity string
manifestSummaries = make([]*gql_generated.ManifestSummary, 0, len(indexContent.Manifests))
indexBlobs = make(map[string]int64, 0)
)
for _, descriptor := range indexContent.Manifests {
manifestSummary, manifestBlobs, err := ImageManifest2ManifestSummary(ctx, repo, tag, descriptor, false,
manifestMetaMap[descriptor.Digest.String()], cveInfo)
if err != nil {
return &gql_generated.ImageSummary{}, map[string]int64{}, err
}
manifestSize := int64(0)
for digest, size := range manifestBlobs {
indexBlobs[digest] = size
manifestSize += size
}
if indexLastUpdated.Before(*manifestSummary.LastUpdated) {
indexLastUpdated = *manifestSummary.LastUpdated
}
totalIndexSize += manifestSize
if cvemodel.SeverityValue(*manifestSummary.Vulnerabilities.MaxSeverity) >
cvemodel.SeverityValue(maxSeverity) {
maxSeverity = *manifestSummary.Vulnerabilities.MaxSeverity
}
manifestSummaries = append(manifestSummaries, manifestSummary)
}
for _, signatures := range repoMeta.Signatures[indexDigest.String()] {
if len(signatures) > 0 {
isSigned = true
}
} }
imageCveSummary := cveinfo.ImageCVESummary{} imageCveSummary := cveinfo.ImageCVESummary{}
// Check if vulnerability scanning is disabled
if cveInfo != nil && !skip.Vulnerabilities { if cveInfo != nil && !skipCVE {
imageName := fmt.Sprintf("%s:%s", repoMeta.Name, tag) imageCveSummary, err = cveInfo.GetCVESummaryForImage(repo, tag)
imageCveSummary, err = cveInfo.GetCVESummaryForImage(imageName)
if err != nil { if err != nil {
// Log the error, but we should still include the manifest in results // Log the error, but we should still include the manifest in results
graphql.AddError(ctx, gqlerror.Errorf("unable to run vulnerability scan on tag %s in repo %s: "+ graphql.AddError(ctx, gqlerror.Errorf("unable to run vulnerability scan on tag %s in repo %s: "+
"manifest digest: %s, error: %s", tag, repoMeta.Name, manifestDigest, err.Error())) "manifest digest: %s, error: %s", tag, repo, indexDigest, err.Error()))
} }
} }
imgSize := int64(0) indexSize = strconv.FormatInt(totalIndexSize, 10)
imgSize += manifestContent.Config.Size
imgSize += int64(len(manifestMetaMap[manifestDigest].ManifestBlob))
for _, layer := range manifestContent.Layers { annotations := common.GetAnnotations(indexContent.Annotations, map[string]string{})
imgSize += layer.Size
indexSummary := gql_generated.ImageSummary{
RepoName: &repo,
Tag: &tag,
Manifests: manifestSummaries,
LastUpdated: &indexLastUpdated,
IsSigned: &isSigned,
Size: &indexSize,
DownloadCount: &totalDownloadCount,
Description: &annotations.Description,
Title: &annotations.Title,
Documentation: &annotations.Documentation,
Licenses: &annotations.Licenses,
Labels: &annotations.Labels,
Source: &annotations.Source,
Vendor: &annotations.Vendor,
Vulnerabilities: &gql_generated.ImageVulnerabilitySummary{
MaxSeverity: &imageCveSummary.MaxSeverity,
Count: &imageCveSummary.Count,
},
}
return &indexSummary, indexBlobs, nil
}
func ImageManifest2ImageSummary(ctx context.Context, repo, tag string, digest godigest.Digest, skipCVE bool,
repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata, cveInfo cveinfo.CveInfo,
) (*gql_generated.ImageSummary, map[string]int64, error) {
var (
manifestContent ispec.Manifest
manifestDigest = digest.String()
)
err := json.Unmarshal(manifestMeta.ManifestBlob, &manifestContent)
if err != nil {
graphql.AddError(ctx, gqlerror.Errorf("can't unmarshal manifest blob for image: %s:%s, manifest digest: %s, "+
"error: %s", repo, tag, manifestDigest, err.Error()))
return &gql_generated.ImageSummary{}, map[string]int64{}, err
}
var configContent ispec.Image
err = json.Unmarshal(manifestMeta.ConfigBlob, &configContent)
if err != nil {
graphql.AddError(ctx, gqlerror.Errorf("can't unmarshal config blob for image: %s:%s, manifest digest: %s, error: %s",
repo, tag, manifestDigest, err.Error()))
return &gql_generated.ImageSummary{}, map[string]int64{}, err
} }
var ( var (
repoName = repoMeta.Name repoName = repo
tag = tag
configDigest = manifestContent.Config.Digest.String() configDigest = manifestContent.Config.Digest.String()
configSize = manifestContent.Config.Size
imageLastUpdated = common.GetImageLastUpdated(configContent) imageLastUpdated = common.GetImageLastUpdated(configContent)
isSigned = imageHasSignatures(imageSignatures) downloadCount = repoMeta.Statistics[digest.String()].DownloadCount
imageSize = strconv.FormatInt(imgSize, 10) isSigned = false
os = configContent.OS
arch = configContent.Architecture
osArch = gql_generated.OsArch{Os: &os, Arch: &arch}
downloadCount = repoMeta.Statistics[descriptor.Digest].DownloadCount
) )
opSys := configContent.OS
arch := configContent.Architecture
variant := configContent.Variant
if variant != "" {
arch = arch + "/" + variant
}
platform := gql_generated.Platform{Os: &opSys, Arch: &arch}
for _, signatures := range repoMeta.Signatures[digest.String()] {
if len(signatures) > 0 {
isSigned = true
}
}
size, imageBlobsMap := getImageBlobsInfo(
manifestDigest, int64(len(manifestMeta.ManifestBlob)),
configDigest, configSize,
manifestContent.Layers)
imageSize := strconv.FormatInt(size, 10)
annotations := common.GetAnnotations(manifestContent.Annotations, configContent.Config.Labels) annotations := common.GetAnnotations(manifestContent.Annotations, configContent.Config.Labels)
authors := annotations.Authors authors := annotations.Authors
@ -282,28 +331,138 @@ func RepoMeta2ImageSummaries(ctx context.Context, repoMeta repodb.RepoMetadata,
historyEntries, err := getAllHistory(manifestContent, configContent) historyEntries, err := getAllHistory(manifestContent, configContent)
if err != nil { if err != nil {
graphql.AddError(ctx, gqlerror.Errorf("error generating history on tag %s in repo %s: "+ graphql.AddError(ctx, gqlerror.Errorf("error generating history on tag %s in repo %s: "+
"manifest digest: %s, error: %s", tag, repoMeta.Name, manifestDigest, err.Error())) "manifest digest: %s, error: %s", tag, repo, manifestDigest, err.Error()))
}
imageCveSummary := cveinfo.ImageCVESummary{}
if cveInfo != nil && !skipCVE {
imageCveSummary, err = cveInfo.GetCVESummaryForImage(repo, tag)
if err != nil {
// Log the error, but we should still include the manifest in results
graphql.AddError(ctx, gqlerror.Errorf("unable to run vulnerability scan on tag %s in repo %s: "+
"manifest digest: %s, error: %s", tag, repo, manifestDigest, err.Error()))
}
} }
imageSummary := gql_generated.ImageSummary{ imageSummary := gql_generated.ImageSummary{
RepoName: &repoName, RepoName: &repoName,
Tag: &tag, Tag: &tag,
Manifests: []*gql_generated.ManifestSummary{
{
Digest: &manifestDigest, Digest: &manifestDigest,
ConfigDigest: &configDigest, ConfigDigest: &configDigest,
LastUpdated: &imageLastUpdated, LastUpdated: &imageLastUpdated,
IsSigned: &isSigned,
Size: &imageSize, Size: &imageSize,
Platform: &osArch, Platform: &platform,
Vendor: &annotations.Vendor,
DownloadCount: &downloadCount, DownloadCount: &downloadCount,
Layers: getLayersSummaries(manifestContent), Layers: getLayersSummaries(manifestContent),
History: historyEntries,
Vulnerabilities: &gql_generated.ImageVulnerabilitySummary{
MaxSeverity: &imageCveSummary.MaxSeverity,
Count: &imageCveSummary.Count,
},
},
},
LastUpdated: &imageLastUpdated,
IsSigned: &isSigned,
Size: &imageSize,
DownloadCount: &downloadCount,
Description: &annotations.Description, Description: &annotations.Description,
Title: &annotations.Title, Title: &annotations.Title,
Documentation: &annotations.Documentation, Documentation: &annotations.Documentation,
Licenses: &annotations.Licenses, Licenses: &annotations.Licenses,
Labels: &annotations.Labels, Labels: &annotations.Labels,
Source: &annotations.Source, Source: &annotations.Source,
Vendor: &annotations.Vendor,
Authors: &authors, Authors: &authors,
Vulnerabilities: &gql_generated.ImageVulnerabilitySummary{
MaxSeverity: &imageCveSummary.MaxSeverity,
Count: &imageCveSummary.Count,
},
}
return &imageSummary, imageBlobsMap, nil
}
func ImageManifest2ManifestSummary(ctx context.Context, repo, tag string, descriptor ispec.Descriptor,
skipCVE bool, manifestMeta repodb.ManifestMetadata, cveInfo cveinfo.CveInfo,
) (*gql_generated.ManifestSummary, map[string]int64, error) {
var (
manifestContent ispec.Manifest
digest = descriptor.Digest
)
err := json.Unmarshal(manifestMeta.ManifestBlob, &manifestContent)
if err != nil {
graphql.AddError(ctx, gqlerror.Errorf("can't unmarshal manifest blob for image: %s:%s, manifest digest: %s, "+
"error: %s", repo, tag, digest, err.Error()))
return &gql_generated.ManifestSummary{}, map[string]int64{}, err
}
var configContent ispec.Image
err = json.Unmarshal(manifestMeta.ConfigBlob, &configContent)
if err != nil {
graphql.AddError(ctx, gqlerror.Errorf("can't unmarshal config blob for image: %s:%s, manifest digest: %s, error: %s",
repo, tag, digest, err.Error()))
return &gql_generated.ManifestSummary{}, map[string]int64{}, err
}
var (
manifestDigestStr = digest.String()
configDigest = manifestContent.Config.Digest.String()
configSize = manifestContent.Config.Size
imageLastUpdated = common.GetImageLastUpdated(configContent)
downloadCount = manifestMeta.DownloadCount
)
opSys := configContent.OS
arch := configContent.Architecture
variant := configContent.Variant
if variant != "" {
arch = arch + "/" + variant
}
platform := gql_generated.Platform{Os: &opSys, Arch: &arch}
size, imageBlobsMap := getImageBlobsInfo(
manifestDigestStr, int64(len(manifestMeta.ManifestBlob)),
configDigest, configSize,
manifestContent.Layers)
imageSize := strconv.FormatInt(size, 10)
historyEntries, err := getAllHistory(manifestContent, configContent)
if err != nil {
graphql.AddError(ctx, gqlerror.Errorf("error generating history on tag %s in repo %s: "+
"manifest digest: %s, error: %s", tag, repo, manifestDigestStr, err.Error()))
}
imageCveSummary := cveinfo.ImageCVESummary{}
if cveInfo != nil && !skipCVE {
imageCveSummary, err = cveInfo.GetCVESummaryForImage(repo, tag)
if err != nil {
// Log the error, but we should still include the manifest in results
graphql.AddError(ctx, gqlerror.Errorf("unable to run vulnerability scan on tag %s in repo %s: "+
"manifest digest: %s, error: %s", tag, repo, manifestDigestStr, err.Error()))
}
}
manifestSummary := gql_generated.ManifestSummary{
Digest: &manifestDigestStr,
ConfigDigest: &configDigest,
LastUpdated: &imageLastUpdated,
Size: &imageSize,
Platform: &platform,
DownloadCount: &downloadCount,
Layers: getLayersSummaries(manifestContent),
History: historyEntries, History: historyEntries,
Vulnerabilities: &gql_generated.ImageVulnerabilitySummary{ Vulnerabilities: &gql_generated.ImageVulnerabilitySummary{
MaxSeverity: &imageCveSummary.MaxSeverity, MaxSeverity: &imageCveSummary.MaxSeverity,
@ -311,18 +470,58 @@ func RepoMeta2ImageSummaries(ctx context.Context, repoMeta repodb.RepoMetadata,
}, },
} }
imageSummaries = append(imageSummaries, &imageSummary) return &manifestSummary, imageBlobsMap, nil
}
func getImageBlobsInfo(manifestDigest string, manifestSize int64, configDigest string, configSize int64,
layers []ispec.Descriptor,
) (int64, map[string]int64) {
imageBlobsMap := map[string]int64{}
imageSize := int64(0)
// add config size
imageSize += configSize
imageBlobsMap[configDigest] = configSize
// add manifest size
imageSize += manifestSize
imageBlobsMap[manifestDigest] = manifestSize
// add layers size
for _, layer := range layers {
imageBlobsMap[layer.Digest.String()] = layer.Size
imageSize += layer.Size
}
return imageSize, imageBlobsMap
}
func RepoMeta2ImageSummaries(ctx context.Context, repoMeta repodb.RepoMetadata,
manifestMetaMap map[string]repodb.ManifestMetadata, indexDataMap map[string]repodb.IndexData,
skip SkipQGLField, cveInfo cveinfo.CveInfo,
) []*gql_generated.ImageSummary {
imageSummaries := make([]*gql_generated.ImageSummary, 0, len(repoMeta.Tags))
for tag, descriptor := range repoMeta.Tags {
imageSummary, _, err := Descriptor2ImageSummary(ctx, descriptor, repoMeta.Name, tag, skip.Vulnerabilities,
repoMeta, manifestMetaMap, indexDataMap, cveInfo)
if err != nil {
continue
}
imageSummaries = append(imageSummaries, imageSummary)
} }
return imageSummaries return imageSummaries
} }
func RepoMeta2ExpandedRepoInfo(ctx context.Context, repoMeta repodb.RepoMetadata, func RepoMeta2ExpandedRepoInfo(ctx context.Context, repoMeta repodb.RepoMetadata,
manifestMetaMap map[string]repodb.ManifestMetadata, skip SkipQGLField, cveInfo cveinfo.CveInfo, manifestMetaMap map[string]repodb.ManifestMetadata, indexDataMap map[string]repodb.IndexData,
skip SkipQGLField, cveInfo cveinfo.CveInfo, log log.Logger,
) (*gql_generated.RepoSummary, []*gql_generated.ImageSummary) { ) (*gql_generated.RepoSummary, []*gql_generated.ImageSummary) {
var ( var (
repoLastUpdatedTimestamp = time.Time{} repoLastUpdatedTimestamp = time.Time{}
repoPlatformsSet = map[string]*gql_generated.OsArch{} repoPlatformsSet = map[string]*gql_generated.Platform{}
repoVendorsSet = map[string]bool{} repoVendorsSet = map[string]bool{}
lastUpdatedImageSummary *gql_generated.ImageSummary lastUpdatedImageSummary *gql_generated.ImageSummary
repoStarCount = repoMeta.Stars repoStarCount = repoMeta.Stars
@ -342,104 +541,33 @@ func RepoMeta2ExpandedRepoInfo(ctx context.Context, repoMeta repodb.RepoMetadata
) )
for tag, descriptor := range repoMeta.Tags { for tag, descriptor := range repoMeta.Tags {
var ( imageSummary, imageBlobs, err := Descriptor2ImageSummary(ctx, descriptor, repoName, tag,
manifestContent ispec.Manifest skip.Vulnerabilities, repoMeta, manifestMetaMap, indexDataMap, cveInfo)
manifestDigest = descriptor.Digest
imageSignatures = repoMeta.Signatures[descriptor.Digest]
)
err := json.Unmarshal(manifestMetaMap[manifestDigest].ManifestBlob, &manifestContent)
if err != nil { if err != nil {
graphql.AddError(ctx, gqlerror.Errorf("can't unmarshal manifest blob for image: %s:%s, manifest digest: %s, "+ log.Error().Msgf("repodb: erorr while converting descriptor for image '%s:%s'", repoName, tag)
"error: %s", repoMeta.Name, tag, manifestDigest, err.Error()))
continue continue
} }
var configContent ispec.Image for _, manifestSummary := range imageSummary.Manifests {
opSys, arch := *manifestSummary.Platform.Os, *manifestSummary.Platform.Arch
err = json.Unmarshal(manifestMetaMap[manifestDigest].ConfigBlob, &configContent)
if err != nil {
graphql.AddError(ctx, gqlerror.Errorf("can't unmarshal config blob for image: %s:%s, manifest digest: %s, error: %s",
repoMeta.Name, tag, manifestDigest, err.Error()))
continue
}
var (
tag = tag
isSigned = imageHasSignatures(imageSignatures)
configDigest = manifestContent.Config.Digest.String()
configSize = manifestContent.Config.Size
opSys = configContent.OS
arch = configContent.Architecture
osArch = gql_generated.OsArch{Os: &opSys, Arch: &arch}
imageLastUpdated = common.GetImageLastUpdated(configContent)
downloadCount = repoMeta.Statistics[descriptor.Digest].DownloadCount
size = updateRepoBlobsMap(
manifestDigest, int64(len(manifestMetaMap[manifestDigest].ManifestBlob)),
configDigest, configSize,
manifestContent.Layers,
repoBlob2Size)
imageSize = strconv.FormatInt(size, 10)
)
annotations := common.GetAnnotations(manifestContent.Annotations, configContent.Config.Labels)
authors := annotations.Authors
if authors == "" {
authors = configContent.Author
}
imageCveSummary := cveinfo.ImageCVESummary{}
imageSummary := gql_generated.ImageSummary{
RepoName: &repoName,
Tag: &tag,
Digest: &manifestDigest,
ConfigDigest: &configDigest,
LastUpdated: &imageLastUpdated,
IsSigned: &isSigned,
Size: &imageSize,
Platform: &osArch,
Vendor: &annotations.Vendor,
DownloadCount: &downloadCount,
Layers: getLayersSummaries(manifestContent),
Description: &annotations.Description,
Title: &annotations.Title,
Documentation: &annotations.Documentation,
Licenses: &annotations.Licenses,
Labels: &annotations.Labels,
Source: &annotations.Source,
Authors: &authors,
Vulnerabilities: &gql_generated.ImageVulnerabilitySummary{
MaxSeverity: &imageCveSummary.MaxSeverity,
Count: &imageCveSummary.Count,
},
}
imageSummaries = append(imageSummaries, &imageSummary)
if annotations.Vendor != "" {
repoVendorsSet[annotations.Vendor] = true
}
if opSys != "" || arch != "" { if opSys != "" || arch != "" {
osArchString := strings.TrimSpace(fmt.Sprintf("%s %s", opSys, arch)) platformString := strings.TrimSpace(fmt.Sprintf("%s %s", opSys, arch))
repoPlatformsSet[osArchString] = &gql_generated.OsArch{Os: &opSys, Arch: &arch} repoPlatformsSet[platformString] = &gql_generated.Platform{Os: &opSys, Arch: &arch}
} }
if repoLastUpdatedTimestamp.Equal(time.Time{}) { updateRepoBlobsMap(imageBlobs, repoBlob2Size)
// initialize with first time value
repoLastUpdatedTimestamp = imageLastUpdated
lastUpdatedImageSummary = &imageSummary
} else if repoLastUpdatedTimestamp.Before(imageLastUpdated) {
repoLastUpdatedTimestamp = imageLastUpdated
lastUpdatedImageSummary = &imageSummary
} }
repoDownloadCount += repoMeta.Statistics[descriptor.Digest].DownloadCount if *imageSummary.Vendor != "" {
repoVendorsSet[*imageSummary.Vendor] = true
}
lastUpdatedImageSummary = UpdateLastUpdatedTimestam(&repoLastUpdatedTimestamp, lastUpdatedImageSummary, imageSummary)
repoDownloadCount += *imageSummary.DownloadCount
imageSummaries = append(imageSummaries, imageSummary)
} }
// calculate repo size = sum all manifest, config and layer blobs sizes // calculate repo size = sum all manifest, config and layer blobs sizes
@ -450,9 +578,9 @@ func RepoMeta2ExpandedRepoInfo(ctx context.Context, repoMeta repodb.RepoMetadata
repoSize := strconv.FormatInt(size, 10) repoSize := strconv.FormatInt(size, 10)
score := 0 score := 0
repoPlatforms := make([]*gql_generated.OsArch, 0, len(repoPlatformsSet)) repoPlatforms := make([]*gql_generated.Platform, 0, len(repoPlatformsSet))
for _, osArch := range repoPlatformsSet { for _, platform := range repoPlatformsSet {
repoPlatforms = append(repoPlatforms, osArch) repoPlatforms = append(repoPlatforms, platform)
} }
repoVendors := make([]*string, 0, len(repoVendorsSet)) repoVendors := make([]*string, 0, len(repoVendorsSet))
@ -461,13 +589,10 @@ func RepoMeta2ExpandedRepoInfo(ctx context.Context, repoMeta repodb.RepoMetadata
vendor := vendor vendor := vendor
repoVendors = append(repoVendors, &vendor) repoVendors = append(repoVendors, &vendor)
} }
// We only scan the latest image on the repo for performance reasons // We only scan the latest image on the repo for performance reasons
// Check if vulnerability scanning is disabled // Check if vulnerability scanning is disabled
if cveInfo != nil && lastUpdatedImageSummary != nil && !skip.Vulnerabilities { if cveInfo != nil && lastUpdatedImageSummary != nil && !skip.Vulnerabilities {
imageName := fmt.Sprintf("%s:%s", repoMeta.Name, *lastUpdatedImageSummary.Tag) imageCveSummary, err := cveInfo.GetCVESummaryForImage(repoMeta.Name, *lastUpdatedImageSummary.Tag)
imageCveSummary, err := cveInfo.GetCVESummaryForImage(imageName)
if err != nil { if err != nil {
// Log the error, but we should still include the image in results // Log the error, but we should still include the image in results
graphql.AddError( graphql.AddError(

View file

@ -7,6 +7,7 @@ import (
godigest "github.com/opencontainers/go-digest" godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1" ispec "github.com/opencontainers/image-spec/specs-go/v1"
"zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/extensions/search/common" "zotregistry.io/zot/pkg/extensions/search/common"
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model" cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
"zotregistry.io/zot/pkg/extensions/search/cve/trivy" "zotregistry.io/zot/pkg/extensions/search/cve/trivy"
@ -18,15 +19,15 @@ import (
type CveInfo interface { type CveInfo interface {
GetImageListForCVE(repo, cveID string) ([]common.TagInfo, error) GetImageListForCVE(repo, cveID string) ([]common.TagInfo, error)
GetImageListWithCVEFixed(repo, cveID string) ([]common.TagInfo, error) GetImageListWithCVEFixed(repo, cveID string) ([]common.TagInfo, error)
GetCVEListForImage(image string, pageinput PageInput) ([]cvemodel.CVE, PageInfo, error) GetCVEListForImage(repo, tag string, pageinput PageInput) ([]cvemodel.CVE, PageInfo, error)
GetCVESummaryForImage(image string) (ImageCVESummary, error) GetCVESummaryForImage(repo, tag string) (ImageCVESummary, error)
CompareSeverities(severity1, severity2 string) int CompareSeverities(severity1, severity2 string) int
UpdateDB() error UpdateDB() error
} }
type Scanner interface { type Scanner interface {
ScanImage(image string) (map[string]cvemodel.CVE, error) ScanImage(image string) (map[string]cvemodel.CVE, error)
IsImageFormatScannable(image string) (bool, error) IsImageFormatScannable(repo, tag string) (bool, error)
CompareSeverities(severity1, severity2 string) int CompareSeverities(severity1, severity2 string) int
UpdateDB() error UpdateDB() error
} }
@ -66,49 +67,38 @@ func (cveinfo BaseCveInfo) GetImageListForCVE(repo, cveID string) ([]common.TagI
} }
for tag, descriptor := range repoMeta.Tags { for tag, descriptor := range repoMeta.Tags {
switch descriptor.MediaType {
case ispec.MediaTypeImageManifest:
manifestDigestStr := descriptor.Digest manifestDigestStr := descriptor.Digest
manifestDigest, err := godigest.Parse(manifestDigestStr) manifestDigest := godigest.Digest(manifestDigestStr)
if err != nil {
cveinfo.Log.Error().Err(err).Str("repo", repo).Str("tag", tag).
Str("cve-id", cveID).Str("digest", manifestDigestStr).Msg("unable to parse digest")
return nil, err isScanableImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, tag)
} if !isScanableImage || err != nil {
cveinfo.Log.Info().Str("image", repo+":"+tag).Err(err).Msg("image is not scanable")
manifestMeta, err := cveinfo.RepoDB.GetManifestMeta(repo, manifestDigest)
if err != nil {
return nil, err
}
var manifestContent ispec.Manifest
err = json.Unmarshal(manifestMeta.ManifestBlob, &manifestContent)
if err != nil {
cveinfo.Log.Error().Err(err).Str("repo", repo).Str("tag", tag).
Str("cve-id", cveID).Msg("unable to unmashal manifest blob")
continue continue
} }
image := fmt.Sprintf("%s:%s", repo, tag) cveMap, err := cveinfo.Scanner.ScanImage(getImageString(repo, tag))
isValidImage, _ := cveinfo.Scanner.IsImageFormatScannable(image)
if !isValidImage {
continue
}
cveMap, err := cveinfo.Scanner.ScanImage(image)
if err != nil { if err != nil {
cveinfo.Log.Info().Str("image", repo+":"+tag).Err(err).Msg("image scan failed")
continue continue
} }
if _, hasCVE := cveMap[cveID]; hasCVE { if _, hasCVE := cveMap[cveID]; hasCVE {
imgList = append(imgList, common.TagInfo{ imgList = append(imgList, common.TagInfo{
Name: tag, Name: tag,
Descriptor: common.Descriptor{
Digest: manifestDigest, Digest: manifestDigest,
MediaType: descriptor.MediaType,
},
}) })
} }
default:
cveinfo.Log.Error().Msgf("type '%s' not supported for scanning", descriptor.MediaType)
}
} }
return imgList, nil return imgList, nil
@ -126,9 +116,13 @@ func (cveinfo BaseCveInfo) GetImageListWithCVEFixed(repo, cveID string) ([]commo
vulnerableTags := make([]common.TagInfo, 0) vulnerableTags := make([]common.TagInfo, 0)
allTags := make([]common.TagInfo, 0) allTags := make([]common.TagInfo, 0)
var hasCVE bool
for tag, descriptor := range repoMeta.Tags { for tag, descriptor := range repoMeta.Tags {
manifestDigestStr := descriptor.Digest manifestDigestStr := descriptor.Digest
switch descriptor.MediaType {
case ispec.MediaTypeImageManifest:
manifestDigest, err := godigest.Parse(manifestDigestStr) manifestDigest, err := godigest.Parse(manifestDigestStr)
if err != nil { if err != nil {
cveinfo.Log.Error().Err(err).Str("repo", repo).Str("tag", tag). cveinfo.Log.Error().Err(err).Str("repo", repo).Str("tag", tag).
@ -158,15 +152,15 @@ func (cveinfo BaseCveInfo) GetImageListWithCVEFixed(repo, cveID string) ([]commo
tagInfo := common.TagInfo{ tagInfo := common.TagInfo{
Name: tag, Name: tag,
Timestamp: common.GetImageLastUpdated(configContent), Timestamp: common.GetImageLastUpdated(configContent),
Digest: manifestDigest, Descriptor: common.Descriptor{Digest: manifestDigest, MediaType: descriptor.MediaType},
} }
allTags = append(allTags, tagInfo) allTags = append(allTags, tagInfo)
image := fmt.Sprintf("%s:%s", repo, tag) image := fmt.Sprintf("%s:%s", repo, tag)
isValidImage, _ := cveinfo.Scanner.IsImageFormatScannable(image) isValidImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, tag)
if !isValidImage { if !isValidImage || err != nil {
cveinfo.Log.Debug().Str("image", image).Str("cve-id", cveID). cveinfo.Log.Debug().Str("image", image).Str("cve-id", cveID).
Msg("image media type not supported for scanning, adding as a vulnerable image") Msg("image media type not supported for scanning, adding as a vulnerable image")
@ -175,7 +169,7 @@ func (cveinfo BaseCveInfo) GetImageListWithCVEFixed(repo, cveID string) ([]commo
continue continue
} }
cveMap, err := cveinfo.Scanner.ScanImage(image) cveMap, err := cveinfo.Scanner.ScanImage(getImageString(repo, tag))
if err != nil { if err != nil {
cveinfo.Log.Debug().Str("image", image).Str("cve-id", cveID). cveinfo.Log.Debug().Str("image", image).Str("cve-id", cveID).
Msg("scanning failed, adding as a vulnerable image") Msg("scanning failed, adding as a vulnerable image")
@ -185,9 +179,25 @@ func (cveinfo BaseCveInfo) GetImageListWithCVEFixed(repo, cveID string) ([]commo
continue continue
} }
if _, hasCVE := cveMap[cveID]; hasCVE { hasCVE = false
for id := range cveMap {
if id == cveID {
hasCVE = true
break
}
}
if hasCVE {
vulnerableTags = append(vulnerableTags, tagInfo) vulnerableTags = append(vulnerableTags, tagInfo)
} }
default:
cveinfo.Log.Error().Msgf("media type not supported '%s'", descriptor.MediaType)
return []common.TagInfo{},
fmt.Errorf("media type '%s' is not supported: %w", descriptor.MediaType, errors.ErrNotImplemented)
}
} }
var fixedTags []common.TagInfo var fixedTags []common.TagInfo
@ -205,16 +215,18 @@ func (cveinfo BaseCveInfo) GetImageListWithCVEFixed(repo, cveID string) ([]commo
return fixedTags, nil return fixedTags, nil
} }
func (cveinfo BaseCveInfo) GetCVEListForImage(image string, pageInput PageInput) ( func (cveinfo BaseCveInfo) GetCVEListForImage(repo, tag string, pageInput PageInput) (
[]cvemodel.CVE, []cvemodel.CVE,
PageInfo, PageInfo,
error, error,
) { ) {
isValidImage, err := cveinfo.Scanner.IsImageFormatScannable(image) isValidImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, tag)
if !isValidImage { if !isValidImage {
return []cvemodel.CVE{}, PageInfo{}, err return []cvemodel.CVE{}, PageInfo{}, err
} }
image := getImageString(repo, tag)
cveMap, err := cveinfo.Scanner.ScanImage(image) cveMap, err := cveinfo.Scanner.ScanImage(image)
if err != nil { if err != nil {
return []cvemodel.CVE{}, PageInfo{}, err return []cvemodel.CVE{}, PageInfo{}, err
@ -234,7 +246,8 @@ func (cveinfo BaseCveInfo) GetCVEListForImage(image string, pageInput PageInput)
return cveList, pageInfo, nil return cveList, pageInfo, nil
} }
func (cveinfo BaseCveInfo) GetCVESummaryForImage(image string) (ImageCVESummary, error) { func (cveinfo BaseCveInfo) GetCVESummaryForImage(repo, tag string,
) (ImageCVESummary, error) {
// There are several cases, expected returned values below: // There are several cases, expected returned values below:
// not scannable / error during scan - max severity "" - cve count 0 - Errors // not scannable / error during scan - max severity "" - cve count 0 - Errors
// scannable no issues found - max severity "NONE" - cve count 0 - no Errors // scannable no issues found - max severity "NONE" - cve count 0 - no Errors
@ -244,11 +257,13 @@ func (cveinfo BaseCveInfo) GetCVESummaryForImage(image string) (ImageCVESummary,
MaxSeverity: "", MaxSeverity: "",
} }
isValidImage, err := cveinfo.Scanner.IsImageFormatScannable(image) isValidImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, tag)
if !isValidImage { if !isValidImage {
return imageCVESummary, err return imageCVESummary, err
} }
image := getImageString(repo, tag)
cveMap, err := cveinfo.Scanner.ScanImage(image) cveMap, err := cveinfo.Scanner.ScanImage(image)
if err != nil { if err != nil {
return imageCVESummary, err return imageCVESummary, err
@ -272,6 +287,16 @@ func (cveinfo BaseCveInfo) GetCVESummaryForImage(image string) (ImageCVESummary,
return imageCVESummary, nil return imageCVESummary, nil
} }
func getImageString(repo, reference string) string {
image := repo + ":" + reference
if common.ReferenceIsDigest(reference) {
image = repo + "@" + reference
}
return image
}
func (cveinfo BaseCveInfo) UpdateDB() error { func (cveinfo BaseCveInfo) UpdateDB() error {
return cveinfo.Scanner.UpdateDB() return cveinfo.Scanner.UpdateDB()
} }

View file

@ -27,7 +27,6 @@ import (
"zotregistry.io/zot/pkg/api/constants" "zotregistry.io/zot/pkg/api/constants"
extconf "zotregistry.io/zot/pkg/extensions/config" extconf "zotregistry.io/zot/pkg/extensions/config"
"zotregistry.io/zot/pkg/extensions/monitoring" "zotregistry.io/zot/pkg/extensions/monitoring"
"zotregistry.io/zot/pkg/extensions/search/common"
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve" cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model" cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
"zotregistry.io/zot/pkg/log" "zotregistry.io/zot/pkg/log"
@ -148,7 +147,7 @@ func generateTestData(dbDir string) error { //nolint: gocyclo
return err return err
} }
content = fmt.Sprintf(`{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:2a9b097b4e4c613dd8185eba55163201a221909f3d430f8df87cd3639afc5929","size":1240,"annotations":{"org.opencontainers.image.ref.name":"commit-aaa7c6e7-squashfs"},"platform":{"architecture":"amd64","os":"linux"}}]} content = fmt.Sprint(`{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:2a9b097b4e4c613dd8185eba55163201a221909f3d430f8df87cd3639afc5929","size":1240,"annotations":{"org.opencontainers.image.ref.name":"commit-aaa7c6e7-squashfs"},"platform":{"architecture":"amd64","os":"linux"}}]}
`) `)
err = makeTestFile(path.Join(dbDir, "zot-squashfs-invalid-blob", "index.json"), content) err = makeTestFile(path.Join(dbDir, "zot-squashfs-invalid-blob", "index.json"), content)
@ -156,7 +155,7 @@ func generateTestData(dbDir string) error { //nolint: gocyclo
return err return err
} }
content = fmt.Sprintf(`{"schemaVersion":2,"config"{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:4b37d4133908ac9a3032ba996020f2ad41354a616b071ca7e726a1df18a0f354","size":1691},"layers":[{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:a01a66356aace53222e92fb6fd990b23eb44ab0e58dff6f853fa9f771ecf3ac5","size":54996992},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:91c26d6934ef2b5c5c4d8458af9bfc4ca46cf90c22380193154964abc8298a7a","size":52330496},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:f281a550ca49746cfc6b8f1ac52f8086b3d5845db2ca18fde980dab62ae3bf7d","size":23343104},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:7ee02568717acdda336c9d56d4dc6ea7f3b1c553e43bb0c0ecc6fd3bbd059d1a","size":5910528},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:8fb33b130588b239235dedd560cdf49d29bbf6f2db5419ac68e4592a85c1f416","size":123269120},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:1b49f0b33d4a696bb94d84c9acab3623e2c195bfb446d446a583a2f9f27b04c3","size":113901568}],"annotations":{"com.cisco.stacker.git_version":"7-dev19-63-gaaa7c6e7","ws.tycho.stacker.git_version":"0.3.26"}} content = fmt.Sprint(`{"schemaVersion":2,"config"{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:4b37d4133908ac9a3032ba996020f2ad41354a616b071ca7e726a1df18a0f354","size":1691},"layers":[{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:a01a66356aace53222e92fb6fd990b23eb44ab0e58dff6f853fa9f771ecf3ac5","size":54996992},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:91c26d6934ef2b5c5c4d8458af9bfc4ca46cf90c22380193154964abc8298a7a","size":52330496},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:f281a550ca49746cfc6b8f1ac52f8086b3d5845db2ca18fde980dab62ae3bf7d","size":23343104},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:7ee02568717acdda336c9d56d4dc6ea7f3b1c553e43bb0c0ecc6fd3bbd059d1a","size":5910528},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:8fb33b130588b239235dedd560cdf49d29bbf6f2db5419ac68e4592a85c1f416","size":123269120},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:1b49f0b33d4a696bb94d84c9acab3623e2c195bfb446d446a583a2f9f27b04c3","size":113901568}],"annotations":{"com.cisco.stacker.git_version":"7-dev19-63-gaaa7c6e7","ws.tycho.stacker.git_version":"0.3.26"}}
`) `)
err = makeTestFile(path.Join(dbDir, "zot-squashfs-invalid-blob", "blobs/sha256", "2a9b097b4e4c613dd8185eba55163201a221909f3d430f8df87cd3639afc5929"), content) err = makeTestFile(path.Join(dbDir, "zot-squashfs-invalid-blob", "blobs/sha256", "2a9b097b4e4c613dd8185eba55163201a221909f3d430f8df87cd3639afc5929"), content)
@ -187,49 +186,49 @@ func generateTestData(dbDir string) error { //nolint: gocyclo
return err return err
} }
content = fmt.Sprintf(`{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.25"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:45df53588e59759a12bd3eca553cdc9063939baac9a28d7ebb6101e4ec230b76","size":873,"annotations":{"org.opencontainers.image.ref.name":"0.3.22-squashfs"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:71448405a4b89539fcfa581afb4dc7d257f98857686b8138b08a1c539f313099","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.19"},"platform":{"architecture":"amd64","os":"linux"}}]}`) content = fmt.Sprint(`{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.25"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:45df53588e59759a12bd3eca553cdc9063939baac9a28d7ebb6101e4ec230b76","size":873,"annotations":{"org.opencontainers.image.ref.name":"0.3.22-squashfs"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:71448405a4b89539fcfa581afb4dc7d257f98857686b8138b08a1c539f313099","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.19"},"platform":{"architecture":"amd64","os":"linux"}}]}`)
err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "index.json"), content) err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "index.json"), content)
if err != nil { if err != nil {
return err return err
} }
content = fmt.Sprintf(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:c5c2fd2b07ad4cb025cf20936d6bce6085584b8377780599be4da8a91739f0e8","size":1738},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:3414b5ef0ad2f0390daaf55b63c422eeedef6191d47036a69d8ee396fabdce72","size":58993484},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:a3b04fff744c13dfa4883e01fa35e01af8daa7f72d9e9b6b7fad1f28843846b6","size":55631733},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:754f517f58f302190aa94e025c25890c18e1e811127aed1ef25c189278ec4ab0","size":113612795},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:ec004cd43488b803d6e232599e83a3164394d44fcd9f44755fed7b5791087ede","size":108889651}],"annotations":{"ws.tycho.stacker.git_version":"0.3.19"}}`) content = fmt.Sprint(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:c5c2fd2b07ad4cb025cf20936d6bce6085584b8377780599be4da8a91739f0e8","size":1738},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:3414b5ef0ad2f0390daaf55b63c422eeedef6191d47036a69d8ee396fabdce72","size":58993484},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:a3b04fff744c13dfa4883e01fa35e01af8daa7f72d9e9b6b7fad1f28843846b6","size":55631733},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:754f517f58f302190aa94e025c25890c18e1e811127aed1ef25c189278ec4ab0","size":113612795},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:ec004cd43488b803d6e232599e83a3164394d44fcd9f44755fed7b5791087ede","size":108889651}],"annotations":{"ws.tycho.stacker.git_version":"0.3.19"}}`)
err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "71448405a4b89539fcfa581afb4dc7d257f98857686b8138b08a1c539f313099"), content) err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "71448405a4b89539fcfa581afb4dc7d257f98857686b8138b08a1c539f313099"), content)
if err != nil { if err != nil {
return err return err
} }
content = fmt.Sprintf(`{"created": "2020-04-08T05:32:49.805795564Z","author": "","architecture": "amd64","os": "linux","config": {"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs": {"type": "layers","diff_ids": []},"history": [{"created": "2020-04-08T05:08:43.590117872Z","created_by": "stacker umoci repack"}, {"created": "2020-04-08T05:08:53.213437118Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:12:15.999154739Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:12:31.0513552Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:20:38.068800557Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:21:01.956154957Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:32:24.582132274Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:32:49.805795564Z","created_by": "stacker build","author": "","empty_layer": true}]}`) content = fmt.Sprint(`{"created": "2020-04-08T05:32:49.805795564Z","author": "","architecture": "amd64","os": "linux","config": {"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs": {"type": "layers","diff_ids": []},"history": [{"created": "2020-04-08T05:08:43.590117872Z","created_by": "stacker umoci repack"}, {"created": "2020-04-08T05:08:53.213437118Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:12:15.999154739Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:12:31.0513552Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:20:38.068800557Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:21:01.956154957Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:32:24.582132274Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:32:49.805795564Z","created_by": "stacker build","author": "","empty_layer": true}]}`)
err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "c5c2fd2b07ad4cb025cf20936d6bce6085584b8377780599be4da8a91739f0e8"), content) err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "c5c2fd2b07ad4cb025cf20936d6bce6085584b8377780599be4da8a91739f0e8"), content)
if err != nil { if err != nil {
return err return err
} }
content = fmt.Sprintf(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a6","size":1740},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:f8b7e41ce10d9a0f614f068326c43431c2777e6fc346f729c2a643bfab24af83","size":59451113},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:9ca9274f196b56a708a7a672d3de88184c0158a30744d355dd0411f3a6850fa6","size":55685756},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:6c1ca50788f93937e9ce9341b564f86cbbcd28e367ed6a57cfc776aee4a9d050","size":113726186},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:d1a92139df86bdf00c818db75bf1ecc860857d142b426e9971a62f5f90e2aa57","size":108755643}],"annotations":{"ws.tycho.stacker.git_version":"0.3.25"}}`) content = fmt.Sprint(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a6","size":1740},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:f8b7e41ce10d9a0f614f068326c43431c2777e6fc346f729c2a643bfab24af83","size":59451113},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:9ca9274f196b56a708a7a672d3de88184c0158a30744d355dd0411f3a6850fa6","size":55685756},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:6c1ca50788f93937e9ce9341b564f86cbbcd28e367ed6a57cfc776aee4a9d050","size":113726186},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:d1a92139df86bdf00c818db75bf1ecc860857d142b426e9971a62f5f90e2aa57","size":108755643}],"annotations":{"ws.tycho.stacker.git_version":"0.3.25"}}`)
err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb"), content) err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb"), content)
if err != nil { if err != nil {
return err return err
} }
content = fmt.Sprintf(`{"created": "2020-04-08T05:32:49.805795564Z","author": "","architecture": "amd64","os": "linux","config": {"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs": {"type": "layers","diff_ids": []},"history": [{"created": "2020-05-11T18:17:24.516727354Z","created_by": "stacker umoci repack"}, {"created": "2020-04-08T05:08:53.213437118Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:12:15.999154739Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:12:31.0513552Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:20:38.068800557Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:21:01.956154957Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:32:24.582132274Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:32:49.805795564Z","created_by": "stacker build","author": "","empty_layer": true}]}`) content = fmt.Sprint(`{"created": "2020-04-08T05:32:49.805795564Z","author": "","architecture": "amd64","os": "linux","config": {"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs": {"type": "layers","diff_ids": []},"history": [{"created": "2020-05-11T18:17:24.516727354Z","created_by": "stacker umoci repack"}, {"created": "2020-04-08T05:08:53.213437118Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:12:15.999154739Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:12:31.0513552Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:20:38.068800557Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:21:01.956154957Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:32:24.582132274Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:32:49.805795564Z","created_by": "stacker build","author": "","empty_layer": true}]}`)
err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a6"), content) err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a6"), content)
if err != nil { if err != nil {
return err return err
} }
content = fmt.Sprintf(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:1fc1d045b241b04fea54333d76d4f57eb1961f9a314413f02a956b76e77a99f0","size":1218},"layers":[{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:c40d72b1556293c00a3e4b6c64c46ef4c7ae4d876dc18bad942b7d1903e8e5b7","size":54745420},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:4115890e3e2563e545e03f264bfecb0097e24e02306ae3e7668dea52e00c6cc2","size":52213357},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:91859e13e0cf704d5405199d73a2d1a0718391dbb183a77c4cb85d99e923ff57","size":109479329},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:20aef84d8098d47a0643a2f99ce05f0deed957b3a259fb708c538f23ed97cc82","size":103996238}],"annotations":{"ws.tycho.stacker.git_version":"0.3.25"}}`) content = fmt.Sprint(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:1fc1d045b241b04fea54333d76d4f57eb1961f9a314413f02a956b76e77a99f0","size":1218},"layers":[{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:c40d72b1556293c00a3e4b6c64c46ef4c7ae4d876dc18bad942b7d1903e8e5b7","size":54745420},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:4115890e3e2563e545e03f264bfecb0097e24e02306ae3e7668dea52e00c6cc2","size":52213357},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:91859e13e0cf704d5405199d73a2d1a0718391dbb183a77c4cb85d99e923ff57","size":109479329},{"mediaType":"application/vnd.oci.image.layer.squashfs","digest":"sha256:20aef84d8098d47a0643a2f99ce05f0deed957b3a259fb708c538f23ed97cc82","size":103996238}],"annotations":{"ws.tycho.stacker.git_version":"0.3.25"}}`)
err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "45df53588e59759a12bd3eca553cdc9063939baac9a28d7ebb6101e4ec230b76"), content) err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "45df53588e59759a12bd3eca553cdc9063939baac9a28d7ebb6101e4ec230b76"), content)
if err != nil { if err != nil {
return err return err
} }
content = fmt.Sprintf(`{"created": "2020-04-08T05:32:49.805795564Z","author": "","architecture": "amd64","os": "linux","config": {"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs": {"type": "layers","diff_ids": []},"history": [{"created": "2020-05-11T18:17:24.516727354Z","created_by": "stacker umoci repack"}, {"created": "2020-04-08T05:08:53.213437118Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:12:15.999154739Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-05-11T19:30:02.467956112Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:20:38.068800557Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:21:01.956154957Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:32:24.582132274Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:32:49.805795564Z","created_by": "stacker build","author": "","empty_layer": true}]}`) content = fmt.Sprint(`{"created": "2020-04-08T05:32:49.805795564Z","author": "","architecture": "amd64","os": "linux","config": {"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs": {"type": "layers","diff_ids": []},"history": [{"created": "2020-05-11T18:17:24.516727354Z","created_by": "stacker umoci repack"}, {"created": "2020-04-08T05:08:53.213437118Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:12:15.999154739Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-05-11T19:30:02.467956112Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:20:38.068800557Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:21:01.956154957Z","created_by": "stacker build","author": "","empty_layer": true}, {"created": "2020-04-08T05:32:24.582132274Z","created_by": "stacker umoci repack","author": ""}, {"created": "2020-04-08T05:32:49.805795564Z","created_by": "stacker build","author": "","empty_layer": true}]}`)
err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "1fc1d045b241b04fea54333d76d4f57eb1961f9a314413f02a956b76e77a99f0"), content) err = makeTestFile(path.Join(dbDir, "zot-squashfs-test", "blobs/sha256", "1fc1d045b241b04fea54333d76d4f57eb1961f9a314413f02a956b76e77a99f0"), content)
if err != nil { if err != nil {
@ -243,21 +242,21 @@ func generateTestData(dbDir string) error { //nolint: gocyclo
return err return err
} }
content = fmt.Sprintf(`{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.25"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:45df53588e59759a12bd3eca553cdc9063939baac9a28d7ebb6101e4ec230b76","size":873,"annotations":{"org.opencontainers.image.ref.name":"0.3.22-squashfs"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:71448405a4b89539fcfa581afb4dc7d257f98857686b8138b08a1c539f313099","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.19"},"platform":{"architecture":"amd64","os":"linux"}}]}`) content = fmt.Sprint(`{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.25"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:45df53588e59759a12bd3eca553cdc9063939baac9a28d7ebb6101e4ec230b76","size":873,"annotations":{"org.opencontainers.image.ref.name":"0.3.22-squashfs"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:71448405a4b89539fcfa581afb4dc7d257f98857686b8138b08a1c539f313099","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.19"},"platform":{"architecture":"amd64","os":"linux"}}]}`)
err = makeTestFile(path.Join(dbDir, "zot-invalid-layer", "index.json"), content) err = makeTestFile(path.Join(dbDir, "zot-invalid-layer", "index.json"), content)
if err != nil { if err != nil {
return err return err
} }
content = fmt.Sprintf(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a6","size":1740},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:f8b7e41ce10d9a0f614f068326c43431c2777e6fc346f729c2a643bfab24af83","size":59451113},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:9ca9274f196b56a708a7a672d3de88184c0158a30744d355dd0411f3a6850fa6","size":55685756},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:6c1ca50788f93937e9ce9341b564f86cbbcd28e367ed6a57cfc776aee4a9d050","size":113726186},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:d1a92139df86bdf00c818db75bf1ecc860857d142b426e9971a62f5f90e2aa57","size":108755643}],"annotations":{"ws.tycho.stacker.git_version":"0.3.25"}}`) content = fmt.Sprint(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a6","size":1740},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:f8b7e41ce10d9a0f614f068326c43431c2777e6fc346f729c2a643bfab24af83","size":59451113},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:9ca9274f196b56a708a7a672d3de88184c0158a30744d355dd0411f3a6850fa6","size":55685756},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:6c1ca50788f93937e9ce9341b564f86cbbcd28e367ed6a57cfc776aee4a9d050","size":113726186},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:d1a92139df86bdf00c818db75bf1ecc860857d142b426e9971a62f5f90e2aa57","size":108755643}],"annotations":{"ws.tycho.stacker.git_version":"0.3.25"}}`)
err = makeTestFile(path.Join(dbDir, "zot-invalid-layer", "blobs/sha256", "eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb"), content) err = makeTestFile(path.Join(dbDir, "zot-invalid-layer", "blobs/sha256", "eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb"), content)
if err != nil { if err != nil {
return err return err
} }
content = fmt.Sprintf(`{"created":"2020-05-11T19:12:23.239785708Z","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs":{"type":"layers","diff_ids":["sha256:8817d297aa60796f41f559ba688d29b31830854014091233575d474f3a6e808e","sha256:dd5a09481ae1f5caf8d1dbc87bc7f86a01af030796467ba25851ad69964d226d","sha256:a8bce2aaf5ce6f1a5459b72de64927a1e507a911453789bf60df06752222cacd","sha256:dc0b750a934e8f376af23de6dcab1af282967498844a6510aed2c61277f20c11"]},"history":[{"created":"2020-05-11T18:17:24.516727354Z","created_by":"stacker umoci repack"},{"created":"2020-05-11T18:17:33.111086359Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T18:18:43.147035914Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T18:19:03.346279546Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T18:27:01.623678656Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T18:27:23.420280147Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T19:11:54.886053615Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T19:12:23.239785708Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true}]`) content = fmt.Sprint(`{"created":"2020-05-11T19:12:23.239785708Z","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs":{"type":"layers","diff_ids":["sha256:8817d297aa60796f41f559ba688d29b31830854014091233575d474f3a6e808e","sha256:dd5a09481ae1f5caf8d1dbc87bc7f86a01af030796467ba25851ad69964d226d","sha256:a8bce2aaf5ce6f1a5459b72de64927a1e507a911453789bf60df06752222cacd","sha256:dc0b750a934e8f376af23de6dcab1af282967498844a6510aed2c61277f20c11"]},"history":[{"created":"2020-05-11T18:17:24.516727354Z","created_by":"stacker umoci repack"},{"created":"2020-05-11T18:17:33.111086359Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T18:18:43.147035914Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T18:19:03.346279546Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T18:27:01.623678656Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T18:27:23.420280147Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T19:11:54.886053615Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T19:12:23.239785708Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true}]`)
err = makeTestFile(path.Join(dbDir, "zot-invalid-layer", "blobs/sha256", "5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a6"), content) err = makeTestFile(path.Join(dbDir, "zot-invalid-layer", "blobs/sha256", "5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a6"), content)
if err != nil { if err != nil {
@ -271,21 +270,21 @@ func generateTestData(dbDir string) error { //nolint: gocyclo
return err return err
} }
content = fmt.Sprintf(`{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.25"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:45df53588e59759a12bd3eca553cdc9063939baac9a28d7ebb6101e4ec230b76","size":873,"annotations":{"org.opencontainers.image.ref.name":"0.3.22-squashfs"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:71448405a4b89539fcfa581afb4dc7d257f98857686b8138b08a1c539f313099","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.19"},"platform":{"architecture":"amd64","os":"linux"}}]}`) content = fmt.Sprint(`{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.25"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:45df53588e59759a12bd3eca553cdc9063939baac9a28d7ebb6101e4ec230b76","size":873,"annotations":{"org.opencontainers.image.ref.name":"0.3.22-squashfs"},"platform":{"architecture":"amd64","os":"linux"}},{"mediaType":"application/vnd.oci.image.manifest.v1+json","digest":"sha256:71448405a4b89539fcfa581afb4dc7d257f98857686b8138b08a1c539f313099","size":886,"annotations":{"org.opencontainers.image.ref.name":"0.3.19"},"platform":{"architecture":"amd64","os":"linux"}}]}`)
err = makeTestFile(path.Join(dbDir, "zot-no-layer", "index.json"), content) err = makeTestFile(path.Join(dbDir, "zot-no-layer", "index.json"), content)
if err != nil { if err != nil {
return err return err
} }
content = fmt.Sprintf(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a6","size":1740},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:f8b7e41ce10d9a0f614f068326c43431c2777e6fc346f729c2a643bfab24af83","size":59451113},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:9ca9274f196b56a708a7a672d3de88184c0158a30744d355dd0411f3a6850fa6","size":55685756},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:6c1ca50788f93937e9ce9341b564f86cbbcd28e367ed6a57cfc776aee4a9d050","size":113726186},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:d1a92139df86bdf00c818db75bf1ecc860857d142b426e9971a62f5f90e2aa57","size":108755643}],"annotations":{"ws.tycho.stacker.git_version":"0.3.25"}}`) content = fmt.Sprint(`{"schemaVersion":2,"config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a6","size":1740},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:f8b7e41ce10d9a0f614f068326c43431c2777e6fc346f729c2a643bfab24af83","size":59451113},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:9ca9274f196b56a708a7a672d3de88184c0158a30744d355dd0411f3a6850fa6","size":55685756},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:6c1ca50788f93937e9ce9341b564f86cbbcd28e367ed6a57cfc776aee4a9d050","size":113726186},{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:d1a92139df86bdf00c818db75bf1ecc860857d142b426e9971a62f5f90e2aa57","size":108755643}],"annotations":{"ws.tycho.stacker.git_version":"0.3.25"}}`)
err = makeTestFile(path.Join(dbDir, "zot-no-layer", "blobs/sha256", "eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb"), content) err = makeTestFile(path.Join(dbDir, "zot-no-layer", "blobs/sha256", "eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb"), content)
if err != nil { if err != nil {
return err return err
} }
content = fmt.Sprintf(`{"created":"2020-05-11T19:12:23.239785708Z","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs":{"type":"layers","diff_ids":["sha256:8817d297aa60796f41f559ba688d29b31830854014091233575d474f3a6e808e","sha256:dd5a09481ae1f5caf8d1dbc87bc7f86a01af030796467ba25851ad69964d226d","sha256:a8bce2aaf5ce6f1a5459b72de64927a1e507a911453789bf60df06752222cacd","sha256:dc0b750a934e8f376af23de6dcab1af282967498844a6510aed2c61277f20c11"]},"history":[{"created":"2020-05-11T18:17:24.516727354Z","created_by":"stacker umoci repack"},{"created":"2020-05-11T18:17:33.111086359Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T18:18:43.147035914Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T18:19:03.346279546Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T18:27:01.623678656Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T18:27:23.420280147Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T19:11:54.886053615Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T19:12:23.239785708Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true}]`) content = fmt.Sprint(`{"created":"2020-05-11T19:12:23.239785708Z","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","architecture":"amd64","os":"linux","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]},"rootfs":{"type":"layers","diff_ids":["sha256:8817d297aa60796f41f559ba688d29b31830854014091233575d474f3a6e808e","sha256:dd5a09481ae1f5caf8d1dbc87bc7f86a01af030796467ba25851ad69964d226d","sha256:a8bce2aaf5ce6f1a5459b72de64927a1e507a911453789bf60df06752222cacd","sha256:dc0b750a934e8f376af23de6dcab1af282967498844a6510aed2c61277f20c11"]},"history":[{"created":"2020-05-11T18:17:24.516727354Z","created_by":"stacker umoci repack"},{"created":"2020-05-11T18:17:33.111086359Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T18:18:43.147035914Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T18:19:03.346279546Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T18:27:01.623678656Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T18:27:23.420280147Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true},{"created":"2020-05-11T19:11:54.886053615Z","created_by":"stacker umoci repack","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI"},{"created":"2020-05-11T19:12:23.239785708Z","created_by":"stacker build","author":"root@jenkinsProduction-Atom-Full-Build-c3-master-159CI","empty_layer":true}]`)
err = makeTestFile(path.Join(dbDir, "zot-no-layer", "blobs/sha256", "5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a"), content) err = makeTestFile(path.Join(dbDir, "zot-no-layer", "blobs/sha256", "5f00b5570a5561a6f9b7e66e4f26e2e30c4d09b43a8d3f993f3c1c99be6137a"), content)
if err != nil { if err != nil {
@ -324,50 +323,73 @@ func TestImageFormat(t *testing.T) {
cveInfo := cveinfo.NewCVEInfo(storeController, repoDB, "", log) cveInfo := cveinfo.NewCVEInfo(storeController, repoDB, "", log)
isValidImage, err := cveInfo.Scanner.IsImageFormatScannable("zot-test") isValidImage, err := cveInfo.Scanner.IsImageFormatScannable("zot-test", "")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(isValidImage, ShouldEqual, false) So(isValidImage, ShouldEqual, false)
isValidImage, err = cveInfo.Scanner.IsImageFormatScannable("zot-test:0.0.1") isValidImage, err = cveInfo.Scanner.IsImageFormatScannable("zot-test", "0.0.1")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(isValidImage, ShouldEqual, true) So(isValidImage, ShouldEqual, true)
isValidImage, err = cveInfo.Scanner.IsImageFormatScannable("zot-test:0.0.") isValidImage, err = cveInfo.Scanner.IsImageFormatScannable("zot-test", "0.0.")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(isValidImage, ShouldEqual, false) So(isValidImage, ShouldEqual, false)
isValidImage, err = cveInfo.Scanner.IsImageFormatScannable("zot-noindex-test") isValidImage, err = cveInfo.Scanner.IsImageFormatScannable("zot-noindex-test", "")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(isValidImage, ShouldEqual, false) So(isValidImage, ShouldEqual, false)
isValidImage, err = cveInfo.Scanner.IsImageFormatScannable("zot--tet") isValidImage, err = cveInfo.Scanner.IsImageFormatScannable("zot--tet", "")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(isValidImage, ShouldEqual, false) So(isValidImage, ShouldEqual, false)
isValidImage, err = cveInfo.Scanner.IsImageFormatScannable("zot-noindex-test") isValidImage, err = cveInfo.Scanner.IsImageFormatScannable("zot-noindex-test", "")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(isValidImage, ShouldEqual, false) So(isValidImage, ShouldEqual, false)
isValidImage, err = cveInfo.Scanner.IsImageFormatScannable("zot-squashfs-noblobs") isValidImage, err = cveInfo.Scanner.IsImageFormatScannable("zot-squashfs-noblobs", "")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(isValidImage, ShouldEqual, false) So(isValidImage, ShouldEqual, false)
isValidImage, err = cveInfo.Scanner.IsImageFormatScannable("zot-squashfs-invalid-index") isValidImage, err = cveInfo.Scanner.IsImageFormatScannable("zot-squashfs-invalid-index", "")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(isValidImage, ShouldEqual, false) So(isValidImage, ShouldEqual, false)
isValidImage, err = cveInfo.Scanner.IsImageFormatScannable("zot-squashfs-invalid-blob") isValidImage, err = cveInfo.Scanner.IsImageFormatScannable("zot-squashfs-invalid-blob", "")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(isValidImage, ShouldEqual, false) So(isValidImage, ShouldEqual, false)
isValidImage, err = cveInfo.Scanner.IsImageFormatScannable("zot-squashfs-test:0.3.22-squashfs") isValidImage, err = cveInfo.Scanner.IsImageFormatScannable("zot-squashfs-test:0.3.22-squashfs", "")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(isValidImage, ShouldEqual, false) So(isValidImage, ShouldEqual, false)
isValidImage, err = cveInfo.Scanner.IsImageFormatScannable("zot-nonreadable-test") isValidImage, err = cveInfo.Scanner.IsImageFormatScannable("zot-nonreadable-test", "")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(isValidImage, ShouldEqual, false) So(isValidImage, ShouldEqual, false)
}) })
Convey("isIndexScanable", t, func() {
log := log.NewLogger("debug", "")
repoDB := &mocks.RepoDBMock{
GetRepoMetaFn: func(repo string) (repodb.RepoMetadata, error) {
return repodb.RepoMetadata{
Tags: map[string]repodb.Descriptor{
"tag": {MediaType: ispec.MediaTypeImageIndex},
},
}, nil
},
}
storeController := storage.StoreController{
DefaultStore: mocks.MockedImageStore{},
}
cveInfo := cveinfo.NewCVEInfo(storeController, repoDB, "", log)
isScanable, err := cveInfo.Scanner.IsImageFormatScannable("repo", "tag")
So(err, ShouldBeNil)
So(isScanable, ShouldBeFalse)
})
} }
func TestCVESearchDisabled(t *testing.T) { func TestCVESearchDisabled(t *testing.T) {
@ -1023,6 +1045,43 @@ func TestCVEStruct(t *testing.T) {
err = repoDB.SetRepoTag("repo5", "nonexitent-manifest", digest51, ispec.MediaTypeImageManifest) err = repoDB.SetRepoTag("repo5", "nonexitent-manifest", digest51, ispec.MediaTypeImageManifest)
So(err, ShouldBeNil) So(err, ShouldBeNil)
// ------ Multiarch image
_, _, manifestContent1, err := GetRandomImageComponents(100)
So(err, ShouldBeNil)
manifestContent1Blob, err := json.Marshal(manifestContent1)
So(err, ShouldBeNil)
diestManifestFromIndex1 := godigest.FromBytes(manifestContent1Blob)
err = repoDB.SetManifestData(diestManifestFromIndex1, repodb.ManifestData{
ManifestBlob: manifestContent1Blob,
ConfigBlob: []byte("{}"),
})
So(err, ShouldBeNil)
_, _, manifestContent2, err := GetRandomImageComponents(100)
So(err, ShouldBeNil)
manifestContent2Blob, err := json.Marshal(manifestContent2)
So(err, ShouldBeNil)
diestManifestFromIndex2 := godigest.FromBytes(manifestContent2Blob)
err = repoDB.SetManifestData(diestManifestFromIndex1, repodb.ManifestData{
ManifestBlob: manifestContent2Blob,
ConfigBlob: []byte("{}"),
})
So(err, ShouldBeNil)
indexBlob, err := GetIndexBlobWithManifests(
[]godigest.Digest{diestManifestFromIndex1, diestManifestFromIndex2},
)
So(err, ShouldBeNil)
indexDigest := godigest.FromBytes(indexBlob)
err = repoDB.SetIndexData(indexDigest, repodb.IndexData{
IndexBlob: indexBlob,
})
So(err, ShouldBeNil)
err = repoDB.SetRepoTag("repoIndex", "tagIndex", indexDigest, ispec.MediaTypeImageIndex)
So(err, ShouldBeNil)
// RepoDB loaded with initial data, mock the scanner // RepoDB loaded with initial data, mock the scanner
severities := map[string]int{ severities := map[string]int{
"UNKNOWN": 0, "UNKNOWN": 0,
@ -1100,15 +1159,30 @@ func TestCVEStruct(t *testing.T) {
}, nil }, nil
} }
if image == "repoIndex:tagIndex" {
return map[string]cvemodel.CVE{
"CVE1": {
ID: "CVE1",
Severity: "MEDIUM",
Title: "Title CVE1",
Description: "Description CVE1",
},
}, nil
}
// By default the image has no vulnerabilities // By default the image has no vulnerabilities
return map[string]cvemodel.CVE{}, nil return map[string]cvemodel.CVE{}, nil
}, },
CompareSeveritiesFn: func(severity1, severity2 string) int { CompareSeveritiesFn: func(severity1, severity2 string) int {
return severities[severity2] - severities[severity1] return severities[severity2] - severities[severity1]
}, },
IsImageFormatScannableFn: func(image string) (bool, error) { IsImageFormatScannableFn: func(repo string, reference string) (bool, error) {
if repo == "repoIndex" {
return true, nil
}
// Almost same logic compared to actual Trivy specific implementation // Almost same logic compared to actual Trivy specific implementation
imageDir, inputTag := common.GetImageDirAndTag(image) imageDir, inputTag := repo, reference
repoMeta, err := repoDB.GetRepoMeta(imageDir) repoMeta, err := repoDB.GetRepoMeta(imageDir)
if err != nil { if err != nil {
@ -1158,51 +1232,51 @@ func TestCVEStruct(t *testing.T) {
t.Log("Test GetCVESummaryForImage") t.Log("Test GetCVESummaryForImage")
// Image is found // Image is found
cveSummary, err := cveInfo.GetCVESummaryForImage("repo1:0.1.0") cveSummary, err := cveInfo.GetCVESummaryForImage("repo1", "0.1.0")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(cveSummary.Count, ShouldEqual, 1) So(cveSummary.Count, ShouldEqual, 1)
So(cveSummary.MaxSeverity, ShouldEqual, "MEDIUM") So(cveSummary.MaxSeverity, ShouldEqual, "MEDIUM")
cveSummary, err = cveInfo.GetCVESummaryForImage("repo1:1.0.0") cveSummary, err = cveInfo.GetCVESummaryForImage("repo1", "1.0.0")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(cveSummary.Count, ShouldEqual, 3) So(cveSummary.Count, ShouldEqual, 3)
So(cveSummary.MaxSeverity, ShouldEqual, "HIGH") So(cveSummary.MaxSeverity, ShouldEqual, "HIGH")
cveSummary, err = cveInfo.GetCVESummaryForImage("repo1:1.0.1") cveSummary, err = cveInfo.GetCVESummaryForImage("repo1", "1.0.1")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(cveSummary.Count, ShouldEqual, 2) So(cveSummary.Count, ShouldEqual, 2)
So(cveSummary.MaxSeverity, ShouldEqual, "MEDIUM") So(cveSummary.MaxSeverity, ShouldEqual, "MEDIUM")
cveSummary, err = cveInfo.GetCVESummaryForImage("repo1:1.1.0") cveSummary, err = cveInfo.GetCVESummaryForImage("repo1", "1.1.0")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(cveSummary.Count, ShouldEqual, 1) So(cveSummary.Count, ShouldEqual, 1)
So(cveSummary.MaxSeverity, ShouldEqual, "LOW") So(cveSummary.MaxSeverity, ShouldEqual, "LOW")
cveSummary, err = cveInfo.GetCVESummaryForImage("repo6:1.0.0") cveSummary, err = cveInfo.GetCVESummaryForImage("repo6", "1.0.0")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(cveSummary.Count, ShouldEqual, 0) So(cveSummary.Count, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "NONE") So(cveSummary.MaxSeverity, ShouldEqual, "NONE")
// Image is not scannable // Image is not scannable
cveSummary, err = cveInfo.GetCVESummaryForImage("repo2:1.0.0") cveSummary, err = cveInfo.GetCVESummaryForImage("repo2", "1.0.0")
So(err, ShouldEqual, zerr.ErrScanNotSupported) So(err, ShouldEqual, zerr.ErrScanNotSupported)
So(cveSummary.Count, ShouldEqual, 0) So(cveSummary.Count, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "") So(cveSummary.MaxSeverity, ShouldEqual, "")
// Tag is not found // Tag is not found
cveSummary, err = cveInfo.GetCVESummaryForImage("repo3:1.0.0") cveSummary, err = cveInfo.GetCVESummaryForImage("repo3", "1.0.0")
So(err, ShouldEqual, zerr.ErrTagMetaNotFound) So(err, ShouldEqual, zerr.ErrTagMetaNotFound)
So(cveSummary.Count, ShouldEqual, 0) So(cveSummary.Count, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "") So(cveSummary.MaxSeverity, ShouldEqual, "")
// Manifest is not found // Manifest is not found
cveSummary, err = cveInfo.GetCVESummaryForImage("repo5:nonexitent-manifest") cveSummary, err = cveInfo.GetCVESummaryForImage("repo5", "nonexitent-manifest")
So(err, ShouldEqual, zerr.ErrManifestDataNotFound) So(err, ShouldEqual, zerr.ErrManifestDataNotFound)
So(cveSummary.Count, ShouldEqual, 0) So(cveSummary.Count, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "") So(cveSummary.MaxSeverity, ShouldEqual, "")
// Repo is not found // Repo is not found
cveSummary, err = cveInfo.GetCVESummaryForImage("repo100:1.0.0") cveSummary, err = cveInfo.GetCVESummaryForImage("repo100", "1.0.0")
So(err, ShouldEqual, zerr.ErrRepoMetaNotFound) So(err, ShouldEqual, zerr.ErrRepoMetaNotFound)
So(cveSummary.Count, ShouldEqual, 0) So(cveSummary.Count, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "") So(cveSummary.MaxSeverity, ShouldEqual, "")
@ -1214,14 +1288,14 @@ func TestCVEStruct(t *testing.T) {
} }
// Image is found // Image is found
cveList, pageInfo, err := cveInfo.GetCVEListForImage("repo1:0.1.0", pageInput) cveList, pageInfo, err := cveInfo.GetCVEListForImage("repo1", "0.1.0", pageInput)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 1) So(len(cveList), ShouldEqual, 1)
So(cveList[0].ID, ShouldEqual, "CVE1") So(cveList[0].ID, ShouldEqual, "CVE1")
So(pageInfo.ItemCount, ShouldEqual, 1) So(pageInfo.ItemCount, ShouldEqual, 1)
So(pageInfo.TotalCount, ShouldEqual, 1) So(pageInfo.TotalCount, ShouldEqual, 1)
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo1:1.0.0", pageInput) cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", pageInput)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 3) So(len(cveList), ShouldEqual, 3)
So(cveList[0].ID, ShouldEqual, "CVE2") So(cveList[0].ID, ShouldEqual, "CVE2")
@ -1230,7 +1304,7 @@ func TestCVEStruct(t *testing.T) {
So(pageInfo.ItemCount, ShouldEqual, 3) So(pageInfo.ItemCount, ShouldEqual, 3)
So(pageInfo.TotalCount, ShouldEqual, 3) So(pageInfo.TotalCount, ShouldEqual, 3)
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo1:1.0.1", pageInput) cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.1", pageInput)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 2) So(len(cveList), ShouldEqual, 2)
So(cveList[0].ID, ShouldEqual, "CVE1") So(cveList[0].ID, ShouldEqual, "CVE1")
@ -1238,42 +1312,42 @@ func TestCVEStruct(t *testing.T) {
So(pageInfo.ItemCount, ShouldEqual, 2) So(pageInfo.ItemCount, ShouldEqual, 2)
So(pageInfo.TotalCount, ShouldEqual, 2) So(pageInfo.TotalCount, ShouldEqual, 2)
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo1:1.1.0", pageInput) cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.1.0", pageInput)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 1) So(len(cveList), ShouldEqual, 1)
So(cveList[0].ID, ShouldEqual, "CVE3") So(cveList[0].ID, ShouldEqual, "CVE3")
So(pageInfo.ItemCount, ShouldEqual, 1) So(pageInfo.ItemCount, ShouldEqual, 1)
So(pageInfo.TotalCount, ShouldEqual, 1) So(pageInfo.TotalCount, ShouldEqual, 1)
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo6:1.0.0", pageInput) cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo6", "1.0.0", pageInput)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 0) So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0) So(pageInfo.ItemCount, ShouldEqual, 0)
So(pageInfo.TotalCount, ShouldEqual, 0) So(pageInfo.TotalCount, ShouldEqual, 0)
// Image is not scannable // Image is not scannable
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo2:1.0.0", pageInput) cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo2", "1.0.0", pageInput)
So(err, ShouldEqual, zerr.ErrScanNotSupported) So(err, ShouldEqual, zerr.ErrScanNotSupported)
So(len(cveList), ShouldEqual, 0) So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0) So(pageInfo.ItemCount, ShouldEqual, 0)
So(pageInfo.TotalCount, ShouldEqual, 0) So(pageInfo.TotalCount, ShouldEqual, 0)
// Tag is not found // Tag is not found
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo3:1.0.0", pageInput) cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo3", "1.0.0", pageInput)
So(err, ShouldEqual, zerr.ErrTagMetaNotFound) So(err, ShouldEqual, zerr.ErrTagMetaNotFound)
So(len(cveList), ShouldEqual, 0) So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0) So(pageInfo.ItemCount, ShouldEqual, 0)
So(pageInfo.TotalCount, ShouldEqual, 0) So(pageInfo.TotalCount, ShouldEqual, 0)
// Manifest is not found // Manifest is not found
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo5:nonexitent-manifest", pageInput) cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo5", "nonexitent-manifest", pageInput)
So(err, ShouldEqual, zerr.ErrManifestDataNotFound) So(err, ShouldEqual, zerr.ErrManifestDataNotFound)
So(len(cveList), ShouldEqual, 0) So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0) So(pageInfo.ItemCount, ShouldEqual, 0)
So(pageInfo.TotalCount, ShouldEqual, 0) So(pageInfo.TotalCount, ShouldEqual, 0)
// Repo is not found // Repo is not found
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo100:1.0.0", pageInput) cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo100", "1.0.0", pageInput)
So(err, ShouldEqual, zerr.ErrRepoMetaNotFound) So(err, ShouldEqual, zerr.ErrRepoMetaNotFound)
So(len(cveList), ShouldEqual, 0) So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0) So(pageInfo.ItemCount, ShouldEqual, 0)
@ -1388,12 +1462,12 @@ func TestCVEStruct(t *testing.T) {
cveInfo = cveinfo.BaseCveInfo{Log: log, Scanner: faultyScanner, RepoDB: repoDB} cveInfo = cveinfo.BaseCveInfo{Log: log, Scanner: faultyScanner, RepoDB: repoDB}
cveSummary, err = cveInfo.GetCVESummaryForImage("repo1:0.1.0") cveSummary, err = cveInfo.GetCVESummaryForImage("repo1", "0.1.0")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(cveSummary.Count, ShouldEqual, 0) So(cveSummary.Count, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "") So(cveSummary.MaxSeverity, ShouldEqual, "")
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo1:0.1.0", pageInput) cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "0.1.0", pageInput)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(cveList, ShouldBeEmpty) So(cveList, ShouldBeEmpty)
So(pageInfo.ItemCount, ShouldEqual, 0) So(pageInfo.ItemCount, ShouldEqual, 0)
@ -1410,5 +1484,32 @@ func TestCVEStruct(t *testing.T) {
// but do not return an error // but do not return an error
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(tagList), ShouldEqual, 0) So(len(tagList), ShouldEqual, 0)
cveInfo = cveinfo.BaseCveInfo{Log: log, Scanner: scanner, RepoDB: repoDB}
tagList, err = cveInfo.GetImageListForCVE("repoIndex", "CVE1")
So(err, ShouldBeNil)
So(len(tagList), ShouldEqual, 0)
cveInfo = cveinfo.BaseCveInfo{Log: log, Scanner: mocks.CveScannerMock{
IsImageFormatScannableFn: func(repo, reference string) (bool, error) {
return false, nil
},
}, RepoDB: repoDB}
_, err = cveInfo.GetImageListForCVE("repoIndex", "CVE1")
So(err, ShouldBeNil)
cveInfo = cveinfo.BaseCveInfo{Log: log, Scanner: mocks.CveScannerMock{
IsImageFormatScannableFn: func(repo, reference string) (bool, error) {
return true, nil
},
ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) {
return nil, zerr.ErrTypeAssertionFailed
},
}, RepoDB: repoDB}
_, err = cveInfo.GetImageListForCVE("repoIndex", "CVE1")
So(err, ShouldBeNil)
}) })
} }

View file

@ -15,3 +15,23 @@ type Package struct {
InstalledVersion string `json:"InstalledVersion"` InstalledVersion string `json:"InstalledVersion"`
FixedVersion string `json:"FixedVersion"` FixedVersion string `json:"FixedVersion"`
} }
const (
None = iota
Low
Medium
High
Critical
)
func SeverityValue(severity string) int {
sevMap := map[string]int{
"NONE": None,
"LOW": Low,
"MEDIUM": Medium,
"HIGH": High,
"CRITICAL": Critical,
}
return sevMap[severity]
}

View file

@ -182,7 +182,7 @@ func TestCVEPagination(t *testing.T) {
Convey("Page", func() { Convey("Page", func() {
Convey("defaults", func() { Convey("defaults", func() {
// By default expect unlimitted results sorted by severity // By default expect unlimitted results sorted by severity
cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1:0.1.0", cveinfo.PageInput{}) cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1", "0.1.0", cveinfo.PageInput{})
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 5) So(len(cves), ShouldEqual, 5)
So(pageInfo.ItemCount, ShouldEqual, 5) So(pageInfo.ItemCount, ShouldEqual, 5)
@ -193,7 +193,7 @@ func TestCVEPagination(t *testing.T) {
previousSeverity = severityToInt[cve.Severity] previousSeverity = severityToInt[cve.Severity]
} }
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1:1.0.0", cveinfo.PageInput{}) cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", cveinfo.PageInput{})
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 30) So(len(cves), ShouldEqual, 30)
So(pageInfo.ItemCount, ShouldEqual, 30) So(pageInfo.ItemCount, ShouldEqual, 30)
@ -211,7 +211,8 @@ func TestCVEPagination(t *testing.T) {
cveIds = append(cveIds, fmt.Sprintf("CVE%d", i)) cveIds = append(cveIds, fmt.Sprintf("CVE%d", i))
} }
cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1:0.1.0", cveinfo.PageInput{SortBy: cveinfo.AlphabeticAsc}) cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1", "0.1.0",
cveinfo.PageInput{SortBy: cveinfo.AlphabeticAsc})
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 5) So(len(cves), ShouldEqual, 5)
So(pageInfo.ItemCount, ShouldEqual, 5) So(pageInfo.ItemCount, ShouldEqual, 5)
@ -221,7 +222,7 @@ func TestCVEPagination(t *testing.T) {
} }
sort.Strings(cveIds) sort.Strings(cveIds)
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1:1.0.0", cveinfo.PageInput{SortBy: cveinfo.AlphabeticAsc}) cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", cveinfo.PageInput{SortBy: cveinfo.AlphabeticAsc})
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 30) So(len(cves), ShouldEqual, 30)
So(pageInfo.ItemCount, ShouldEqual, 30) So(pageInfo.ItemCount, ShouldEqual, 30)
@ -231,7 +232,7 @@ func TestCVEPagination(t *testing.T) {
} }
sort.Sort(sort.Reverse(sort.StringSlice(cveIds))) sort.Sort(sort.Reverse(sort.StringSlice(cveIds)))
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1:1.0.0", cveinfo.PageInput{SortBy: cveinfo.AlphabeticDsc}) cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", cveinfo.PageInput{SortBy: cveinfo.AlphabeticDsc})
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 30) So(len(cves), ShouldEqual, 30)
So(pageInfo.ItemCount, ShouldEqual, 30) So(pageInfo.ItemCount, ShouldEqual, 30)
@ -240,7 +241,7 @@ func TestCVEPagination(t *testing.T) {
So(cve.ID, ShouldEqual, cveIds[i]) So(cve.ID, ShouldEqual, cveIds[i])
} }
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1:1.0.0", cveinfo.PageInput{SortBy: cveinfo.SeverityDsc}) cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", cveinfo.PageInput{SortBy: cveinfo.SeverityDsc})
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 30) So(len(cves), ShouldEqual, 30)
So(pageInfo.ItemCount, ShouldEqual, 30) So(pageInfo.ItemCount, ShouldEqual, 30)
@ -258,7 +259,7 @@ func TestCVEPagination(t *testing.T) {
cveIds = append(cveIds, fmt.Sprintf("CVE%d", i)) cveIds = append(cveIds, fmt.Sprintf("CVE%d", i))
} }
cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1:0.1.0", cveinfo.PageInput{ cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1", "0.1.0", cveinfo.PageInput{
Limit: 3, Limit: 3,
Offset: 1, Offset: 1,
SortBy: cveinfo.AlphabeticAsc, SortBy: cveinfo.AlphabeticAsc,
@ -271,7 +272,7 @@ func TestCVEPagination(t *testing.T) {
So(cves[1].ID, ShouldEqual, "CVE2") So(cves[1].ID, ShouldEqual, "CVE2")
So(cves[2].ID, ShouldEqual, "CVE3") So(cves[2].ID, ShouldEqual, "CVE3")
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1:0.1.0", cveinfo.PageInput{ cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "0.1.0", cveinfo.PageInput{
Limit: 2, Limit: 2,
Offset: 1, Offset: 1,
SortBy: cveinfo.AlphabeticDsc, SortBy: cveinfo.AlphabeticDsc,
@ -283,7 +284,7 @@ func TestCVEPagination(t *testing.T) {
So(cves[0].ID, ShouldEqual, "CVE3") So(cves[0].ID, ShouldEqual, "CVE3")
So(cves[1].ID, ShouldEqual, "CVE2") So(cves[1].ID, ShouldEqual, "CVE2")
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1:0.1.0", cveinfo.PageInput{ cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "0.1.0", cveinfo.PageInput{
Limit: 3, Limit: 3,
Offset: 1, Offset: 1,
SortBy: cveinfo.SeverityDsc, SortBy: cveinfo.SeverityDsc,
@ -299,7 +300,7 @@ func TestCVEPagination(t *testing.T) {
} }
sort.Strings(cveIds) sort.Strings(cveIds)
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1:1.0.0", cveinfo.PageInput{ cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", cveinfo.PageInput{
Limit: 5, Limit: 5,
Offset: 20, Offset: 20,
SortBy: cveinfo.AlphabeticAsc, SortBy: cveinfo.AlphabeticAsc,
@ -314,7 +315,7 @@ func TestCVEPagination(t *testing.T) {
}) })
Convey("limit > len(cves)", func() { Convey("limit > len(cves)", func() {
cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1:0.1.0", cveinfo.PageInput{ cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1", "0.1.0", cveinfo.PageInput{
Limit: 6, Limit: 6,
Offset: 3, Offset: 3,
SortBy: cveinfo.AlphabeticAsc, SortBy: cveinfo.AlphabeticAsc,
@ -326,7 +327,7 @@ func TestCVEPagination(t *testing.T) {
So(cves[0].ID, ShouldEqual, "CVE3") So(cves[0].ID, ShouldEqual, "CVE3")
So(cves[1].ID, ShouldEqual, "CVE4") So(cves[1].ID, ShouldEqual, "CVE4")
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1:0.1.0", cveinfo.PageInput{ cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "0.1.0", cveinfo.PageInput{
Limit: 6, Limit: 6,
Offset: 3, Offset: 3,
SortBy: cveinfo.AlphabeticDsc, SortBy: cveinfo.AlphabeticDsc,
@ -338,7 +339,7 @@ func TestCVEPagination(t *testing.T) {
So(cves[0].ID, ShouldEqual, "CVE1") So(cves[0].ID, ShouldEqual, "CVE1")
So(cves[1].ID, ShouldEqual, "CVE0") So(cves[1].ID, ShouldEqual, "CVE0")
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1:0.1.0", cveinfo.PageInput{ cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "0.1.0", cveinfo.PageInput{
Limit: 6, Limit: 6,
Offset: 3, Offset: 3,
SortBy: cveinfo.SeverityDsc, SortBy: cveinfo.SeverityDsc,

View file

@ -14,6 +14,7 @@ import (
regTypes "github.com/google/go-containerregistry/pkg/v1/types" regTypes "github.com/google/go-containerregistry/pkg/v1/types"
godigest "github.com/opencontainers/go-digest" godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1" ispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
zerr "zotregistry.io/zot/errors" zerr "zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/extensions/search/common" "zotregistry.io/zot/pkg/extensions/search/common"
@ -173,24 +174,49 @@ func (scanner Scanner) runTrivy(opts flag.Options) (types.Report, error) {
return report, nil return report, nil
} }
func (scanner Scanner) IsImageFormatScannable(image string) (bool, error) { func (scanner Scanner) IsImageFormatScannable(repo, tag string) (bool, error) {
image := repo + ":" + tag
if scanner.cache.Get(image) != nil { if scanner.cache.Get(image) != nil {
return true, nil return true, nil
} }
imageDir, inputTag := common.GetImageDirAndTag(image) repoMeta, err := scanner.repoDB.GetRepoMeta(repo)
repoMeta, err := scanner.repoDB.GetRepoMeta(imageDir)
if err != nil { if err != nil {
return false, err return false, err
} }
manifestDigestStr, ok := repoMeta.Tags[inputTag] var ok bool
imageDescriptor, ok := repoMeta.Tags[tag]
if !ok { if !ok {
return false, zerr.ErrTagMetaNotFound return false, zerr.ErrTagMetaNotFound
} }
manifestDigest, err := godigest.Parse(manifestDigestStr.Digest) switch imageDescriptor.MediaType {
case ispec.MediaTypeImageManifest:
ok, err := scanner.isManifestScanable(imageDescriptor)
if err != nil {
return ok, errors.Wrapf(err, "image '%s'", image)
}
return ok, nil
case ispec.MediaTypeImageIndex:
ok, err := scanner.isIndexScanable(imageDescriptor)
if err != nil {
return ok, errors.Wrapf(err, "image '%s'", image)
}
return ok, nil
}
return false, nil
}
func (scanner Scanner) isManifestScanable(descriptor repodb.Descriptor) (bool, error) {
manifestDigestStr := descriptor.Digest
manifestDigest, err := godigest.Parse(manifestDigestStr)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -204,7 +230,7 @@ func (scanner Scanner) IsImageFormatScannable(image string) (bool, error) {
err = json.Unmarshal(manifestData.ManifestBlob, &manifestContent) err = json.Unmarshal(manifestData.ManifestBlob, &manifestContent)
if err != nil { if err != nil {
scanner.log.Error().Err(err).Str("image", image).Msg("unable to unmashal manifest blob") scanner.log.Error().Err(err).Msg("unable to unmashal manifest blob")
return false, zerr.ErrScanNotSupported return false, zerr.ErrScanNotSupported
} }
@ -214,7 +240,7 @@ func (scanner Scanner) IsImageFormatScannable(image string) (bool, error) {
case ispec.MediaTypeImageLayerGzip, ispec.MediaTypeImageLayer, string(regTypes.DockerLayer): case ispec.MediaTypeImageLayerGzip, ispec.MediaTypeImageLayer, string(regTypes.DockerLayer):
continue continue
default: default:
scanner.log.Debug().Str("image", image). scanner.log.Debug().
Msgf("image media type %s not supported for scanning", imageLayer.MediaType) Msgf("image media type %s not supported for scanning", imageLayer.MediaType)
return false, zerr.ErrScanNotSupported return false, zerr.ErrScanNotSupported
@ -224,6 +250,10 @@ func (scanner Scanner) IsImageFormatScannable(image string) (bool, error) {
return true, nil return true, nil
} }
func (scanner Scanner) isIndexScanable(descriptor repodb.Descriptor) (bool, error) {
return false, nil
}
func (scanner Scanner) ScanImage(image string) (map[string]cvemodel.CVE, error) { func (scanner Scanner) ScanImage(image string) (map[string]cvemodel.CVE, error) {
if scanner.cache.Get(image) != nil { if scanner.cache.Get(image) != nil {
return scanner.cache.Get(image), nil return scanner.cache.Get(image), nil

View file

@ -119,6 +119,7 @@ func TestMultipleStoragePath(t *testing.T) {
// Scanning image in default store // Scanning image in default store
cveMap, err := scanner.ScanImage(img0) cveMap, err := scanner.ScanImage(img0)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(len(cveMap), ShouldEqual, 0) So(len(cveMap), ShouldEqual, 0)
@ -200,7 +201,7 @@ func TestTrivyLibraryErrors(t *testing.T) {
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
// Scanning image with invalid input to trigger a scanner error // Scanning image with invalid input to trigger a scanner error
opts = scanner.getTrivyOptions("nonexisting_image:0.0.1") opts = scanner.getTrivyOptions("nilnonexisting_image:0.0.1")
_, err = scanner.runTrivy(opts) _, err = scanner.runTrivy(opts)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
@ -358,43 +359,43 @@ func TestImageScannable(t *testing.T) {
scanner := NewScanner(storeController, repoDB, "ghcr.io/project-zot/trivy-db", log) scanner := NewScanner(storeController, repoDB, "ghcr.io/project-zot/trivy-db", log)
Convey("Valid image should be scannable", t, func() { Convey("Valid image should be scannable", t, func() {
result, err := scanner.IsImageFormatScannable("repo1:valid") result, err := scanner.IsImageFormatScannable("repo1", "valid")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(result, ShouldBeTrue) So(result, ShouldBeTrue)
}) })
Convey("Image with layers of unsupported types should be unscannable", t, func() { Convey("Image with layers of unsupported types should be unscannable", t, func() {
result, err := scanner.IsImageFormatScannable("repo1:unscannable-layer") result, err := scanner.IsImageFormatScannable("repo1", "unscannable-layer")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(result, ShouldBeFalse) So(result, ShouldBeFalse)
}) })
Convey("Image with unmarshable manifests should be unscannable", t, func() { Convey("Image with unmarshable manifests should be unscannable", t, func() {
result, err := scanner.IsImageFormatScannable("repo1:unmarshable") result, err := scanner.IsImageFormatScannable("repo1", "unmarshable")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(result, ShouldBeFalse) So(result, ShouldBeFalse)
}) })
Convey("Image with missing manifest meta should be unscannable", t, func() { Convey("Image with missing manifest meta should be unscannable", t, func() {
result, err := scanner.IsImageFormatScannable("repo1:missing") result, err := scanner.IsImageFormatScannable("repo1", "missing")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(result, ShouldBeFalse) So(result, ShouldBeFalse)
}) })
Convey("Image with invalid manifest digest should be unscannable", t, func() { Convey("Image with invalid manifest digest should be unscannable", t, func() {
result, err := scanner.IsImageFormatScannable("repo1:invalid-digest") result, err := scanner.IsImageFormatScannable("repo1", "invalid-digest")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(result, ShouldBeFalse) So(result, ShouldBeFalse)
}) })
Convey("Image with unknown tag should be unscannable", t, func() { Convey("Image with unknown tag should be unscannable", t, func() {
result, err := scanner.IsImageFormatScannable("repo1:unknown-tag") result, err := scanner.IsImageFormatScannable("repo1", "unknown-tag")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(result, ShouldBeFalse) So(result, ShouldBeFalse)
}) })
Convey("Image with unknown repo should be unscannable", t, func() { Convey("Image with unknown repo should be unscannable", t, func() {
result, err := scanner.IsImageFormatScannable("unknown-repo:sometag") result, err := scanner.IsImageFormatScannable("unknown-repo", "sometag")
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(result, ShouldBeFalse) So(result, ShouldBeFalse)
}) })

View file

@ -55,7 +55,7 @@ func (digestinfo DigestInfo) GetImageTagsByDigest(repo, digest string) ([]ImageI
tags := []*string{} tags := []*string{}
// Check the image manigest in index.json matches the search digest // Check the image manifest in index.json matches the search digest
// This is a blob with mediaType application/vnd.oci.image.manifest.v1+json // This is a blob with mediaType application/vnd.oci.image.manifest.v1+json
if strings.Contains(manifest.Digest.String(), digest) { if strings.Contains(manifest.Digest.String(), digest) {
tags = append(tags, &val) tags = append(tags, &val)

View file

@ -65,10 +65,10 @@ func testSetup(t *testing.T) (string, string, *digestinfo.DigestInfo) {
subRootDir := subDir subRootDir := subDir
// Test images used/copied: // Test images used/copied:
// IMAGE NAME TAG DIGEST CONFIG LAYERS SIZE // IMAGE NAME TAG DIGEST OS/ARCH CONFIG LAYERS SIZE
// zot-test 0.0.1 2bacca16 adf3bb6c 76MB // zot-test 0.0.1 2bacca16 linux/amd64 adf3bb6c 76MB
// 2d473b07 76MB // 2d473b07 76MB
// zot-cve-test 0.0.1 63a795ca 8dd57e17 75MB // zot-cve-test 0.0.1 63a795ca linux/amd64 8dd57e17 75MB
// 7a0437f0 75MB // 7a0437f0 75MB
err := os.Mkdir(subDir+"/a", 0o700) err := os.Mkdir(subDir+"/a", 0o700)
@ -159,9 +159,20 @@ func TestDigestSearchHTTP(t *testing.T) {
So(resp.StatusCode(), ShouldEqual, 422) So(resp.StatusCode(), ShouldEqual, 422)
// "sha" should match all digests in all images // "sha" should match all digests in all images
query := `{
ImageListForDigest(id:"sha") {
Results {
RepoName Tag
Manifests {
Digest ConfigDigest Size
Layers { Digest }
}
Size
}
}
}`
resp, err = resty.R().Get( resp, err = resty.R().Get(
baseURL + constants.FullSearchPrefix + `?query={ImageListForDigest(id:"sha")` + baseURL + constants.FullSearchPrefix + "?query=" + url.QueryEscape(query),
`{Results{RepoName%20Tag%20Digest%20ConfigDigest%20Size%20Layers%20{%20Digest}}}}`,
) )
So(resp, ShouldNotBeNil) So(resp, ShouldNotBeNil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -178,7 +189,7 @@ func TestDigestSearchHTTP(t *testing.T) {
// GetTestBlobDigest("zot-test", "manifest").Encoded() should match the manifest of 1 image // GetTestBlobDigest("zot-test", "manifest").Encoded() should match the manifest of 1 image
gqlQuery := url.QueryEscape(`{ImageListForDigest(id:"` + GetTestBlobDigest("zot-test", "manifest").Encoded() + `") gqlQuery := url.QueryEscape(`{ImageListForDigest(id:"` + GetTestBlobDigest("zot-test", "manifest").Encoded() + `")
{Results{RepoName Tag Digest ConfigDigest Size Layers { Digest }}}}`) {Results{RepoName Tag Manifests {Digest ConfigDigest Size Layers { Digest }}}}}`)
targetURL := baseURL + constants.FullSearchPrefix + `?query=` + gqlQuery targetURL := baseURL + constants.FullSearchPrefix + `?query=` + gqlQuery
resp, err = resty.R().Get(targetURL) resp, err = resty.R().Get(targetURL)
@ -196,7 +207,7 @@ func TestDigestSearchHTTP(t *testing.T) {
// GetTestBlobDigest("zot-test", "config").Encoded() should match the config of 1 image. // GetTestBlobDigest("zot-test", "config").Encoded() should match the config of 1 image.
gqlQuery = url.QueryEscape(`{ImageListForDigest(id:"` + GetTestBlobDigest("zot-test", "config").Encoded() + `") gqlQuery = url.QueryEscape(`{ImageListForDigest(id:"` + GetTestBlobDigest("zot-test", "config").Encoded() + `")
{Results{RepoName Tag Digest ConfigDigest Size Layers { Digest }}}}`) {Results{RepoName Tag Manifests {Digest ConfigDigest Size Layers { Digest }}}}}`)
targetURL = baseURL + constants.FullSearchPrefix + `?query=` + gqlQuery targetURL = baseURL + constants.FullSearchPrefix + `?query=` + gqlQuery
resp, err = resty.R().Get(targetURL) resp, err = resty.R().Get(targetURL)
@ -215,7 +226,7 @@ func TestDigestSearchHTTP(t *testing.T) {
// Call should return {"data":{"ImageListForDigest":[{"Name":"zot-cve-test","Tags":["0.0.1"]}]}} // Call should return {"data":{"ImageListForDigest":[{"Name":"zot-cve-test","Tags":["0.0.1"]}]}}
// GetTestBlobDigest("zot-cve-test", "layer").Encoded() should match the layer of 1 image // GetTestBlobDigest("zot-cve-test", "layer").Encoded() should match the layer of 1 image
gqlQuery = url.QueryEscape(`{ImageListForDigest(id:"` + GetTestBlobDigest("zot-cve-test", "layer").Encoded() + `") gqlQuery = url.QueryEscape(`{ImageListForDigest(id:"` + GetTestBlobDigest("zot-cve-test", "layer").Encoded() + `")
{Results{RepoName Tag Digest ConfigDigest Size Layers { Digest }}}}`) {Results{RepoName Tag Manifests {Digest ConfigDigest Size Layers { Digest }}}}}`)
targetURL = baseURL + constants.FullSearchPrefix + `?query=` + gqlQuery targetURL = baseURL + constants.FullSearchPrefix + `?query=` + gqlQuery
resp, err = resty.R().Get( resp, err = resty.R().Get(
@ -236,9 +247,20 @@ func TestDigestSearchHTTP(t *testing.T) {
// Call should return {"data":{"ImageListForDigest":[]}} // Call should return {"data":{"ImageListForDigest":[]}}
// "1111111" should match 0 images // "1111111" should match 0 images
query = `
{
ImageListForDigest(id:"1111111") {
Results {
RepoName Tag
Manifests {
Digest ConfigDigest Size
Layers { Digest }
}
}
}
}`
resp, err = resty.R().Get( resp, err = resty.R().Get(
baseURL + constants.FullSearchPrefix + `?query={ImageListForDigest(id:"1111111")` + baseURL + constants.FullSearchPrefix + "?query=" + url.QueryEscape(query),
`{Results{RepoName%20Tag%20Digest%20ConfigDigest%20Size%20Layers%20{%20Digest}}}}`,
) )
So(resp, ShouldNotBeNil) So(resp, ShouldNotBeNil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -250,9 +272,14 @@ func TestDigestSearchHTTP(t *testing.T) {
So(len(responseStruct.ImgListForDigest.Results), ShouldEqual, 0) So(len(responseStruct.ImgListForDigest.Results), ShouldEqual, 0)
// Call should return {"errors": [{....}]", data":null}} // Call should return {"errors": [{....}]", data":null}}
query = `{
ImageListForDigest(id:"1111111") {
Results {
RepoName Tag343s
}
}`
resp, err = resty.R().Get( resp, err = resty.R().Get(
baseURL + constants.FullSearchPrefix + `?query={ImageListForDigest(id:"1111111")` + baseURL + constants.FullSearchPrefix + "?query=" + url.QueryEscape(query),
`{Results{RepoName%20Tag343s}}}`,
) )
So(resp, ShouldNotBeNil) So(resp, ShouldNotBeNil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -306,9 +333,19 @@ func TestDigestSearchHTTPSubPaths(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(resp.StatusCode(), ShouldEqual, 422) So(resp.StatusCode(), ShouldEqual, 422)
query := `{
ImageListForDigest(id:"sha") {
Results {
RepoName Tag
Manifests {
Digest ConfigDigest Size
Layers { Digest }
}
}
}
}`
resp, err = resty.R().Get( resp, err = resty.R().Get(
baseURL + constants.FullSearchPrefix + `?query={ImageListForDigest(id:"sha")` + baseURL + constants.FullSearchPrefix + "?query=" + url.QueryEscape(query),
`{Results{RepoName%20Tag%20Digest%20ConfigDigest%20Size%20Layers%20{%20Digest}}}}`,
) )
So(resp, ShouldNotBeNil) So(resp, ShouldNotBeNil)
So(err, ShouldBeNil) So(err, ShouldBeNil)

File diff suppressed because it is too large Load diff

View file

@ -91,28 +91,18 @@ type ImageSummary struct {
RepoName *string `json:"RepoName"` RepoName *string `json:"RepoName"`
// Tag identifying the image within the repository // Tag identifying the image within the repository
Tag *string `json:"Tag"` Tag *string `json:"Tag"`
// Digest of the manifest file associated with this image // List of manifests for all supported versions of the image for different operating systems and architectures
Digest *string `json:"Digest"` Manifests []*ManifestSummary `json:"Manifests"`
// Digest of the config file associated with this image // Total size of the files associated with all images (manifest, config, layers)
ConfigDigest *string `json:"ConfigDigest"`
// Timestamp of the last modification done to the image (from config or the last updated layer)
LastUpdated *time.Time `json:"LastUpdated"`
// True if the image has a signature associated with it, false otherwise
IsSigned *bool `json:"IsSigned"`
// Total size of the files associated with this image (manigest, config, layers)
Size *string `json:"Size"` Size *string `json:"Size"`
// OS and architecture supported by this image
Platform *OsArch `json:"Platform"`
// Vendor associated with this image, the distributing entity, organization or individual
Vendor *string `json:"Vendor"`
// Integer used to rank search results by relevance
Score *int `json:"Score"`
// Number of downloads of the manifest of this image // Number of downloads of the manifest of this image
DownloadCount *int `json:"DownloadCount"` DownloadCount *int `json:"DownloadCount"`
// Information on the layers of this image // Timestamp of the last modification done to the image (from config or the last updated layer)
Layers []*LayerSummary `json:"Layers"` LastUpdated *time.Time `json:"LastUpdated"`
// Human-readable description of the software packaged in the image // Human-readable description of the software packaged in the image
Description *string `json:"Description"` Description *string `json:"Description"`
// True if the image has a signature associated with it, false otherwise
IsSigned *bool `json:"IsSigned"`
// License(s) under which contained software is distributed as an SPDX License Expression // License(s) under which contained software is distributed as an SPDX License Expression
Licenses *string `json:"Licenses"` Licenses *string `json:"Licenses"`
// Labels associated with this image // Labels associated with this image
@ -120,16 +110,18 @@ type ImageSummary struct {
Labels *string `json:"Labels"` Labels *string `json:"Labels"`
// Human-readable title of the image // Human-readable title of the image
Title *string `json:"Title"` Title *string `json:"Title"`
// Integer used to rank search results by relevance
Score *int `json:"Score"`
// URL to get source code for building the image // URL to get source code for building the image
Source *string `json:"Source"` Source *string `json:"Source"`
// URL to get documentation on the image // URL to get documentation on the image
Documentation *string `json:"Documentation"` Documentation *string `json:"Documentation"`
// Information about the history of the specific image, see LayerHistory // Vendor associated with this image, the distributing entity, organization or individual
History []*LayerHistory `json:"History"` Vendor *string `json:"Vendor"`
// Short summary of the identified CVEs
Vulnerabilities *ImageVulnerabilitySummary `json:"Vulnerabilities"`
// Contact details of the people or organization responsible for the image // Contact details of the people or organization responsible for the image
Authors *string `json:"Authors"` Authors *string `json:"Authors"`
// Short summary of the identified CVEs
Vulnerabilities *ImageVulnerabilitySummary `json:"Vulnerabilities"`
} }
// Contains summary of vulnerabilities found in a specific image // Contains summary of vulnerabilities found in a specific image
@ -158,14 +150,27 @@ type LayerSummary struct {
Score *int `json:"Score"` Score *int `json:"Score"`
} }
// Contains details about the OS and architecture of the image // Details about a specific version of an image for a certain operating system and architecture.
type OsArch struct { type ManifestSummary struct {
// The name of the operating system which the image is built to run on, // Digest of the manifest file associated with this image
// Should be values listed in the Go Language document https://go.dev/doc/install/source#environment Digest *string `json:"Digest"`
Os *string `json:"Os"` // Digest of the config file associated with this image
// The name of the compilation architecture which the image is built to run on, ConfigDigest *string `json:"ConfigDigest"`
// Should be values listed in the Go Language document https://go.dev/doc/install/source#environment // Timestamp of the last update to an image inside this repository
Arch *string `json:"Arch"` LastUpdated *time.Time `json:"LastUpdated"`
// Total size of the files associated with this manifest (manifest, config, layers)
Size *string `json:"Size"`
// OS and architecture supported by this image
Platform *Platform `json:"Platform"`
// Total numer of image manifest downloads from this repository
DownloadCount *int `json:"DownloadCount"`
// List of layers matching the search criteria
// NOTE: the actual search logic for layers is not implemented at the moment
Layers []*LayerSummary `json:"Layers"`
// Information about the history of the specific image, see LayerHistory
History []*LayerHistory `json:"History"`
// Short summary of the identified CVEs
Vulnerabilities *ImageVulnerabilitySummary `json:"Vulnerabilities"`
} }
// Contains the name of the package, the current installed version and the version where the CVE was fixed // Contains the name of the package, the current installed version and the version where the CVE was fixed
@ -215,6 +220,16 @@ type PaginatedReposResult struct {
Results []*RepoSummary `json:"Results"` Results []*RepoSummary `json:"Results"`
} }
// Contains details about the OS and architecture of the image
type Platform struct {
// The name of the operating system which the image is built to run on,
// Should be values listed in the Go Language document https://go.dev/doc/install/source#environment
Os *string `json:"Os"`
// The name of the compilation architecture which the image is built to run on,
// Should be values listed in the Go Language document https://go.dev/doc/install/source#environment
Arch *string `json:"Arch"`
}
// A referrer is an object which has a reference to a another object // A referrer is an object which has a reference to a another object
type Referrer struct { type Referrer struct {
// Referrer MediaType // Referrer MediaType
@ -248,7 +263,7 @@ type RepoSummary struct {
// Total size of the files within this repository // Total size of the files within this repository
Size *string `json:"Size"` Size *string `json:"Size"`
// List of platforms supported by this repository // List of platforms supported by this repository
Platforms []*OsArch `json:"Platforms"` Platforms []*Platform `json:"Platforms"`
// Vendors associated with this image, the distributing entities, organizations or individuals // Vendors associated with this image, the distributing entities, organizations or individuals
Vendors []*string `json:"Vendors"` Vendors []*string `json:"Vendors"`
// Integer used to rank search results by relevance // Integer used to rank search results by relevance

View file

@ -7,6 +7,7 @@ package search
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"sort" "sort"
"strings" "strings"
@ -137,13 +138,14 @@ func getImageListForDigest(ctx context.Context, digest string, repoDB repodb.Rep
} }
// get all repos // get all repos
reposMeta, manifestMetaMap, pageInfo, err := repoDB.FilterTags(ctx, FilterByDigest(digest), pageInput) reposMeta, manifestMetaMap, indexDataMap, pageInfo, err := repoDB.FilterTags(ctx, FilterByDigest(digest), pageInput)
if err != nil { if err != nil {
return &gql_generated.PaginatedImagesResult{}, err return &gql_generated.PaginatedImagesResult{}, err
} }
for _, repoMeta := range reposMeta { for _, repoMeta := range reposMeta {
imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, skip, cveInfo) imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, indexDataMap,
skip, cveInfo)
imageList = append(imageList, imageSummaries...) imageList = append(imageList, imageSummaries...)
} }
@ -157,7 +159,7 @@ func getImageListForDigest(ctx context.Context, digest string, repoDB repodb.Rep
}, nil }, nil
} }
func getImageSummary(ctx context.Context, repo, tag string, repoDB repodb.RepoDB, func getImageSummary(ctx context.Context, repo, tag string, digest *string, repoDB repodb.RepoDB,
cveInfo cveinfo.CveInfo, log log.Logger, //nolint:unparam cveInfo cveinfo.CveInfo, log log.Logger, //nolint:unparam
) ( ) (
*gql_generated.ImageSummary, error, *gql_generated.ImageSummary, error,
@ -172,28 +174,115 @@ func getImageSummary(ctx context.Context, repo, tag string, repoDB repodb.RepoDB
return nil, gqlerror.Errorf("can't find image: %s:%s", repo, tag) return nil, gqlerror.Errorf("can't find image: %s:%s", repo, tag)
} }
manifestDigest := manifestDescriptor.Digest
for t := range repoMeta.Tags { for t := range repoMeta.Tags {
if t != tag { if t != tag {
delete(repoMeta.Tags, t) delete(repoMeta.Tags, t)
} }
} }
manifestMeta, err := repoDB.GetManifestMeta(repo, godigest.Digest(manifestDigest)) var (
manifestMetaMap = map[string]repodb.ManifestMetadata{}
indexDataMap = map[string]repodb.IndexData{}
)
switch manifestDescriptor.MediaType {
case ispec.MediaTypeImageManifest:
manifestDigest := manifestDescriptor.Digest
if digest != nil && *digest != manifestDigest {
return nil, fmt.Errorf("resolver: can't get ManifestData for digest %s for image '%s:%s' %w",
manifestDigest, repo, tag, zerr.ErrManifestDataNotFound)
}
manifestData, err := repoDB.GetManifestData(godigest.Digest(manifestDigest))
if err != nil { if err != nil {
return nil, err return nil, err
} }
manifestMetaMap := map[string]repodb.ManifestMetadata{ manifestMetaMap[manifestDigest] = repodb.ManifestMetadata{
manifestDigest: manifestMeta, ManifestBlob: manifestData.ManifestBlob,
ConfigBlob: manifestData.ConfigBlob,
}
case ispec.MediaTypeImageIndex:
indexDigest := manifestDescriptor.Digest
indexData, err := repoDB.GetIndexData(godigest.Digest(indexDigest))
if err != nil {
return nil, err
}
var indexContent ispec.Index
err = json.Unmarshal(indexData.IndexBlob, &indexContent)
if err != nil {
return nil, err
}
if digest != nil {
manifestDigest := *digest
digestFound := false
for _, manifest := range indexContent.Manifests {
if manifest.Digest.String() == manifestDigest {
digestFound = true
break
}
}
if !digestFound {
return nil, fmt.Errorf("resolver: can't get ManifestData for digest %s for image '%s:%s' %w",
manifestDigest, repo, tag, zerr.ErrManifestDataNotFound)
}
manifestData, err := repoDB.GetManifestData(godigest.Digest(manifestDigest))
if err != nil {
return nil, fmt.Errorf("resolver: can't get ManifestData for digest %s for image '%s:%s' %w",
manifestDigest, repo, tag, err)
}
manifestMetaMap[manifestDigest] = repodb.ManifestMetadata{
ManifestBlob: manifestData.ManifestBlob,
ConfigBlob: manifestData.ConfigBlob,
}
// We update the tag descriptor to be the manifest descriptor with digest specified in the
// 'digest' parameter. We treat it as a standalone image.
repoMeta.Tags[tag] = repodb.Descriptor{
Digest: manifestDigest,
MediaType: ispec.MediaTypeImageManifest,
}
break
}
for _, manifest := range indexContent.Manifests {
manifestData, err := repoDB.GetManifestData(manifest.Digest)
if err != nil {
return nil, fmt.Errorf("resolver: can't get ManifestData for digest %s for image '%s:%s' %w",
manifest.Digest, repo, tag, err)
}
manifestMetaMap[manifest.Digest.String()] = repodb.ManifestMetadata{
ManifestBlob: manifestData.ManifestBlob,
ConfigBlob: manifestData.ConfigBlob,
}
}
indexDataMap[indexDigest] = indexData
default:
log.Error().Msgf("resolver: media type '%s' not supported", manifestDescriptor.MediaType)
} }
skip := convert.SkipQGLField{ skip := convert.SkipQGLField{
Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Vulnerabilities"), Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Vulnerabilities"),
} }
imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, indexDataMap, skip, cveInfo)
imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, skip, cveInfo) if len(imageSummaries) == 0 {
return &gql_generated.ImageSummary{}, nil
}
return imageSummaries[0], nil return imageSummaries[0], nil
} }
@ -217,13 +306,17 @@ func getCVEListForImage(
), ),
} }
_, copyImgTag := common.GetImageDirAndTag(image) repo, ref, isTag := common.GetImageDirAndReference(image)
if copyImgTag == "" { if ref == "" {
return &gql_generated.CVEResultForImage{}, gqlerror.Errorf("no reference provided") return &gql_generated.CVEResultForImage{}, gqlerror.Errorf("no reference provided")
} }
cveList, pageInfo, err := cveInfo.GetCVEListForImage(image, pageInput) if !isTag {
return &gql_generated.CVEResultForImage{}, gqlerror.Errorf("reference by digest not supported")
}
cveList, pageInfo, err := cveInfo.GetCVEListForImage(repo, ref, pageInput)
if err != nil { if err != nil {
return &gql_generated.CVEResultForImage{}, err return &gql_generated.CVEResultForImage{}, err
} }
@ -262,7 +355,7 @@ func getCVEListForImage(
} }
return &gql_generated.CVEResultForImage{ return &gql_generated.CVEResultForImage{
Tag: &copyImgTag, Tag: &ref,
CVEList: cveids, CVEList: cveids,
Page: &gql_generated.PageInfo{ Page: &gql_generated.PageInfo{
TotalCount: pageInfo.TotalCount, TotalCount: pageInfo.TotalCount,
@ -276,7 +369,7 @@ func FilterByTagInfo(tagsInfo []common.TagInfo) repodb.FilterFunc {
manifestDigest := godigest.FromBytes(manifestMeta.ManifestBlob).String() manifestDigest := godigest.FromBytes(manifestMeta.ManifestBlob).String()
for _, tagInfo := range tagsInfo { for _, tagInfo := range tagsInfo {
if tagInfo.Digest.String() == manifestDigest { if tagInfo.Descriptor.Digest.String() == manifestDigest {
return true return true
} }
} }
@ -342,13 +435,14 @@ func getImageListForCVE(
} }
// get all repos // get all repos
reposMeta, manifestMetaMap, pageInfo, err := repoDB.FilterTags(ctx, FilterByTagInfo(affectedImages), pageInput) reposMeta, manifestMetaMap, indexDataMap, pageInfo, err := repoDB.FilterTags(ctx,
FilterByTagInfo(affectedImages), pageInput)
if err != nil { if err != nil {
return &gql_generated.PaginatedImagesResult{}, err return &gql_generated.PaginatedImagesResult{}, err
} }
for _, repoMeta := range reposMeta { for _, repoMeta := range reposMeta {
imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, skip, cveInfo) imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, indexDataMap, skip, cveInfo)
imageList = append(imageList, imageSummaries...) imageList = append(imageList, imageSummaries...)
} }
@ -403,7 +497,7 @@ func getImageListWithCVEFixed(
} }
// get all repos // get all repos
reposMeta, manifestMetaMap, pageInfo, err := repoDB.FilterTags(ctx, FilterByTagInfo(tagsInfo), pageInput) reposMeta, manifestMetaMap, indexDataMap, pageInfo, err := repoDB.FilterTags(ctx, FilterByTagInfo(tagsInfo), pageInput)
if err != nil { if err != nil {
return &gql_generated.PaginatedImagesResult{}, err return &gql_generated.PaginatedImagesResult{}, err
} }
@ -413,7 +507,7 @@ func getImageListWithCVEFixed(
continue continue
} }
imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, skip, cveInfo) imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, indexDataMap, skip, cveInfo)
imageList = append(imageList, imageSummaries...) imageList = append(imageList, imageSummaries...)
} }
@ -452,13 +546,14 @@ func repoListWithNewestImage(
), ),
} }
reposMeta, manifestMetaMap, pageInfo, err := repoDB.SearchRepos(ctx, "", repodb.Filter{}, pageInput) reposMeta, manifestMetaMap, indexDataMap, pageInfo, err := repoDB.SearchRepos(ctx, "", repodb.Filter{}, pageInput)
if err != nil { if err != nil {
return &gql_generated.PaginatedReposResult{}, err return &gql_generated.PaginatedReposResult{}, err
} }
for _, repoMeta := range reposMeta { for _, repoMeta := range reposMeta {
repoSummary := convert.RepoMeta2RepoSummary(ctx, repoMeta, manifestMetaMap, skip, cveInfo) repoSummary := convert.RepoMeta2RepoSummary(ctx, repoMeta, manifestMetaMap, indexDataMap,
skip, cveInfo)
repos = append(repos, repoSummary) repos = append(repos, repoSummary)
} }
@ -507,13 +602,14 @@ func globalSearch(ctx context.Context, query string, repoDB repodb.RepoDB, filte
), ),
} }
reposMeta, manifestMetaMap, pageInfo, err := repoDB.SearchRepos(ctx, query, localFilter, pageInput) reposMeta, manifestMetaMap, indexDataMap, pageInfo, err := repoDB.SearchRepos(ctx, query, localFilter, pageInput)
if err != nil { if err != nil {
return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err
} }
for _, repoMeta := range reposMeta { for _, repoMeta := range reposMeta {
repoSummary := convert.RepoMeta2RepoSummary(ctx, repoMeta, manifestMetaMap, skip, cveInfo) repoSummary := convert.RepoMeta2RepoSummary(ctx, repoMeta, manifestMetaMap, indexDataMap,
skip, cveInfo)
repos = append(repos, repoSummary) repos = append(repos, repoSummary)
} }
@ -537,13 +633,13 @@ func globalSearch(ctx context.Context, query string, repoDB repodb.RepoDB, filte
), ),
} }
reposMeta, manifestMetaMap, pageInfo, err := repoDB.SearchTags(ctx, query, localFilter, pageInput) reposMeta, manifestMetaMap, indexDataMap, pageInfo, err := repoDB.SearchTags(ctx, query, localFilter, pageInput)
if err != nil { if err != nil {
return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err return &gql_generated.PaginatedReposResult{}, []*gql_generated.ImageSummary{}, []*gql_generated.LayerSummary{}, err
} }
for _, repoMeta := range reposMeta { for _, repoMeta := range reposMeta {
imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, skip, cveInfo) imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, indexDataMap, skip, cveInfo)
images = append(images, imageSummaries...) images = append(images, imageSummaries...)
} }
@ -563,7 +659,7 @@ func canSkipField(preloads map[string]bool, s string) bool {
return !fieldIsPresent return !fieldIsPresent
} }
func derivedImageList(ctx context.Context, image string, repoDB repodb.RepoDB, func derivedImageList(ctx context.Context, image string, digest *string, repoDB repodb.RepoDB,
requestedPage *gql_generated.PageInput, requestedPage *gql_generated.PageInput,
cveInfo cveinfo.CveInfo, log log.Logger, cveInfo cveinfo.CveInfo, log log.Logger,
) (*gql_generated.PaginatedImagesResult, error) { ) (*gql_generated.PaginatedImagesResult, error) {
@ -590,7 +686,7 @@ func derivedImageList(ctx context.Context, image string, repoDB repodb.RepoDB,
return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("no reference provided") return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("no reference provided")
} }
searchedImage, err := getImageSummary(ctx, imageRepo, imageTag, repoDB, cveInfo, log) searchedImage, err := getImageSummary(ctx, imageRepo, imageTag, digest, repoDB, cveInfo, log)
if err != nil { if err != nil {
if errors.Is(err, zerr.ErrRepoMetaNotFound) { if errors.Is(err, zerr.ErrRepoMetaNotFound) {
return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("repository: not found") return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("repository: not found")
@ -600,7 +696,7 @@ func derivedImageList(ctx context.Context, image string, repoDB repodb.RepoDB,
} }
// we need all available tags // we need all available tags
reposMeta, manifestMetaMap, pageInfo, err := repoDB.FilterTags(ctx, reposMeta, manifestMetaMap, indexDataMap, pageInfo, err := repoDB.FilterTags(ctx,
filterDerivedImages(searchedImage), filterDerivedImages(searchedImage),
pageInput) pageInput)
if err != nil { if err != nil {
@ -608,7 +704,7 @@ func derivedImageList(ctx context.Context, image string, repoDB repodb.RepoDB,
} }
for _, repoMeta := range reposMeta { for _, repoMeta := range reposMeta {
summary := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, skip, cveInfo) summary := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, indexDataMap, skip, cveInfo)
derivedList = append(derivedList, summary...) derivedList = append(derivedList, summary...)
} }
@ -641,12 +737,12 @@ func filterDerivedImages(image *gql_generated.ImageSummary) repodb.FilterFunc {
return false return false
} }
for i := range image.Manifests {
manifestDigest := godigest.FromBytes(manifestMeta.ManifestBlob).String() manifestDigest := godigest.FromBytes(manifestMeta.ManifestBlob).String()
if manifestDigest == *image.Digest { if manifestDigest == *image.Manifests[i].Digest {
return false return false
} }
imageLayers := image.Manifests[i].Layers
imageLayers := image.Layers
addImageToList = false addImageToList = false
layers := imageManifest.Layers layers := imageManifest.Layers
@ -667,11 +763,16 @@ func filterDerivedImages(image *gql_generated.ImageSummary) repodb.FilterFunc {
addImageToList = true addImageToList = true
} }
return addImageToList if addImageToList {
return true
}
}
return false
} }
} }
func baseImageList(ctx context.Context, image string, repoDB repodb.RepoDB, func baseImageList(ctx context.Context, image string, digest *string, repoDB repodb.RepoDB,
requestedPage *gql_generated.PageInput, requestedPage *gql_generated.PageInput,
cveInfo cveinfo.CveInfo, log log.Logger, cveInfo cveinfo.CveInfo, log log.Logger,
) (*gql_generated.PaginatedImagesResult, error) { ) (*gql_generated.PaginatedImagesResult, error) {
@ -699,7 +800,7 @@ func baseImageList(ctx context.Context, image string, repoDB repodb.RepoDB,
return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("no reference provided") return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("no reference provided")
} }
searchedImage, err := getImageSummary(ctx, imageRepo, imageTag, repoDB, cveInfo, log) searchedImage, err := getImageSummary(ctx, imageRepo, imageTag, digest, repoDB, cveInfo, log)
if err != nil { if err != nil {
if errors.Is(err, zerr.ErrRepoMetaNotFound) { if errors.Is(err, zerr.ErrRepoMetaNotFound) {
return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("repository: not found") return &gql_generated.PaginatedImagesResult{}, gqlerror.Errorf("repository: not found")
@ -709,7 +810,7 @@ func baseImageList(ctx context.Context, image string, repoDB repodb.RepoDB,
} }
// we need all available tags // we need all available tags
reposMeta, manifestMetaMap, pageInfo, err := repoDB.FilterTags(ctx, reposMeta, manifestMetaMap, indexDataMap, pageInfo, err := repoDB.FilterTags(ctx,
filterBaseImages(searchedImage), filterBaseImages(searchedImage),
pageInput) pageInput)
if err != nil { if err != nil {
@ -717,7 +818,7 @@ func baseImageList(ctx context.Context, image string, repoDB repodb.RepoDB,
} }
for _, repoMeta := range reposMeta { for _, repoMeta := range reposMeta {
summary := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, skip, cveInfo) summary := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, indexDataMap, skip, cveInfo)
imageSummaries = append(imageSummaries, summary...) imageSummaries = append(imageSummaries, summary...)
} }
@ -743,27 +844,25 @@ func filterBaseImages(image *gql_generated.ImageSummary) repodb.FilterFunc {
return func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool { return func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
var addImageToList bool var addImageToList bool
var imageManifest ispec.Manifest var manifestContent ispec.Manifest
err := json.Unmarshal(manifestMeta.ManifestBlob, &imageManifest) err := json.Unmarshal(manifestMeta.ManifestBlob, &manifestContent)
if err != nil { if err != nil {
return false return false
} }
for i := range image.Manifests {
manifestDigest := godigest.FromBytes(manifestMeta.ManifestBlob).String() manifestDigest := godigest.FromBytes(manifestMeta.ManifestBlob).String()
if manifestDigest == *image.Digest { if manifestDigest == *image.Manifests[i].Digest {
return false return false
} }
imageLayers := image.Layers
addImageToList = true addImageToList = true
layers := imageManifest.Layers
for _, l := range layers { for _, l := range manifestContent.Layers {
foundLayer := false foundLayer := false
for _, k := range imageLayers { for _, k := range image.Manifests[i].Layers {
if l.Digest.String() == *k.Digest { if l.Digest.String() == *k.Digest {
foundLayer = true foundLayer = true
@ -778,7 +877,12 @@ func filterBaseImages(image *gql_generated.ImageSummary) repodb.FilterFunc {
} }
} }
return addImageToList if addImageToList {
return true
}
}
return false
} }
} }
@ -921,9 +1025,14 @@ func expandedRepoInfo(ctx context.Context, repo string, repoDB repodb.RepoDB, cv
return &gql_generated.RepoInfo{}, err return &gql_generated.RepoInfo{}, err
} }
manifestMetaMap := map[string]repodb.ManifestMetadata{} var (
manifestMetaMap = map[string]repodb.ManifestMetadata{}
indexDataMap = map[string]repodb.IndexData{}
)
for tag, descriptor := range repoMeta.Tags { for tag, descriptor := range repoMeta.Tags {
switch descriptor.MediaType {
case ispec.MediaTypeImageManifest:
digest := descriptor.Digest digest := descriptor.Digest
if _, alreadyDownloaded := manifestMetaMap[digest]; alreadyDownloaded { if _, alreadyDownloaded := manifestMetaMap[digest]; alreadyDownloaded {
@ -939,13 +1048,65 @@ func expandedRepoInfo(ctx context.Context, repo string, repoDB repodb.RepoDB, cv
} }
manifestMetaMap[digest] = manifestMeta manifestMetaMap[digest] = manifestMeta
case ispec.MediaTypeImageIndex:
digest := descriptor.Digest
if _, alreadyDownloaded := indexDataMap[digest]; alreadyDownloaded {
continue
}
indexData, err := repoDB.GetIndexData(godigest.Digest(digest))
if err != nil {
graphql.AddError(ctx, errors.Wrapf(err,
"resolver: failed to get manifest meta for image %s:%s with manifest digest %s", repo, tag, digest))
continue
}
var indexContent ispec.Index
err = json.Unmarshal(indexData.IndexBlob, &indexContent)
if err != nil {
graphql.AddError(ctx, errors.Wrapf(err,
"resolver: failed to unmarshal index content for image %s:%s with digest %s", repo, tag, digest))
continue
}
var errorOccured bool
for _, descriptor := range indexContent.Manifests {
manifestMeta, err := repoDB.GetManifestMeta(repo, descriptor.Digest)
if err != nil {
graphql.AddError(ctx, errors.Wrapf(err,
"resolver: failed to get manifest meta with digest '%s' for multiarch image %s:%s",
digest, repo, tag),
)
errorOccured = true
break
}
manifestMetaMap[descriptor.Digest.String()] = manifestMeta
}
if errorOccured {
continue
}
indexDataMap[digest] = indexData
default:
}
} }
skip := convert.SkipQGLField{ skip := convert.SkipQGLField{
Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Summary.NewestImage.Vulnerabilities"), Vulnerabilities: canSkipField(convert.GetPreloads(ctx), "Summary.NewestImage.Vulnerabilities") &&
canSkipField(convert.GetPreloads(ctx), "Images.Vulnerabilities"),
} }
repoSummary, imageSummaries := convert.RepoMeta2ExpandedRepoInfo(ctx, repoMeta, manifestMetaMap, skip, cveInfo) repoSummary, imageSummaries := convert.RepoMeta2ExpandedRepoInfo(ctx, repoMeta, manifestMetaMap, indexDataMap,
skip, cveInfo, log)
dateSortedImages := make(timeSlice, 0, len(imageSummaries)) dateSortedImages := make(timeSlice, 0, len(imageSummaries))
for _, imgSummary := range imageSummaries { for _, imgSummary := range imageSummaries {
@ -1005,7 +1166,7 @@ func getImageList(ctx context.Context, repo string, repoDB repodb.RepoDB, cveInf
} }
// reposMeta, manifestMetaMap, err := repoDB.SearchRepos(ctx, repo, repodb.Filter{}, pageInput) // reposMeta, manifestMetaMap, err := repoDB.SearchRepos(ctx, repo, repodb.Filter{}, pageInput)
reposMeta, manifestMetaMap, pageInfo, err := repoDB.FilterTags(ctx, reposMeta, manifestMetaMap, indexDataMap, pageInfo, err := repoDB.FilterTags(ctx,
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool { func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
return true return true
}, },
@ -1018,7 +1179,7 @@ func getImageList(ctx context.Context, repo string, repoDB repodb.RepoDB, cveInf
if repoMeta.Name != repo && repo != "" { if repoMeta.Name != repo && repo != "" {
continue continue
} }
imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, skip, cveInfo) imageSummaries := convert.RepoMeta2ImageSummaries(ctx, repoMeta, manifestMetaMap, indexDataMap, skip, cveInfo)
imageList = append(imageList, imageSummaries...) imageList = append(imageList, imageSummaries...)
} }

File diff suppressed because it is too large Load diff

View file

@ -124,53 +124,33 @@ type ImageSummary {
""" """
Tag: String Tag: String
""" """
Digest of the manifest file associated with this image List of manifests for all supported versions of the image for different operating systems and architectures
""" """
Digest: String Manifests: [ManifestSummary]
""" """
Digest of the config file associated with this image Total size of the files associated with all images (manifest, config, layers)
"""
ConfigDigest: String
"""
Timestamp of the last modification done to the image (from config or the last updated layer)
"""
LastUpdated: Time
"""
True if the image has a signature associated with it, false otherwise
"""
IsSigned: Boolean
"""
Total size of the files associated with this image (manigest, config, layers)
""" """
Size: String Size: String
""" """
OS and architecture supported by this image
"""
Platform: OsArch
"""
Vendor associated with this image, the distributing entity, organization or individual
"""
Vendor: String
"""
Integer used to rank search results by relevance
"""
Score: Int
"""
Number of downloads of the manifest of this image Number of downloads of the manifest of this image
""" """
DownloadCount: Int DownloadCount: Int
""" """
Information on the layers of this image Timestamp of the last modification done to the image (from config or the last updated layer)
""" """
Layers: [LayerSummary] LastUpdated: Time
""" """
Human-readable description of the software packaged in the image Human-readable description of the software packaged in the image
""" """
Description: String Description: String
""" """
True if the image has a signature associated with it, false otherwise
"""
IsSigned: Boolean
"""
License(s) under which contained software is distributed as an SPDX License Expression License(s) under which contained software is distributed as an SPDX License Expression
""" """
Licenses: String Licenses: String # The value of the annotation if present, 'unknown' otherwise).
""" """
Labels associated with this image Labels associated with this image
NOTE: currently this field is unused NOTE: currently this field is unused
@ -181,6 +161,10 @@ type ImageSummary {
""" """
Title: String Title: String
""" """
Integer used to rank search results by relevance
"""
Score: Int
"""
URL to get source code for building the image URL to get source code for building the image
""" """
Source: String Source: String
@ -189,6 +173,52 @@ type ImageSummary {
""" """
Documentation: String Documentation: String
""" """
Vendor associated with this image, the distributing entity, organization or individual
"""
Vendor: String
"""
Contact details of the people or organization responsible for the image
"""
Authors: String
"""
Short summary of the identified CVEs
"""
Vulnerabilities: ImageVulnerabilitySummary
}
"""
Details about a specific version of an image for a certain operating system and architecture.
"""
type ManifestSummary {
"""
Digest of the manifest file associated with this image
"""
Digest: String
"""
Digest of the config file associated with this image
"""
ConfigDigest: String
"""
Timestamp of the last update to an image inside this repository
"""
LastUpdated: Time
"""
Total size of the files associated with this manifest (manifest, config, layers)
"""
Size: String
"""
OS and architecture supported by this image
"""
Platform: Platform
"""
Total numer of image manifest downloads from this repository
"""
DownloadCount: Int
"""
List of layers matching the search criteria
NOTE: the actual search logic for layers is not implemented at the moment
"""
Layers: [LayerSummary]
"""
Information about the history of the specific image, see LayerHistory Information about the history of the specific image, see LayerHistory
""" """
History: [LayerHistory] History: [LayerHistory]
@ -196,10 +226,6 @@ type ImageSummary {
Short summary of the identified CVEs Short summary of the identified CVEs
""" """
Vulnerabilities: ImageVulnerabilitySummary Vulnerabilities: ImageVulnerabilitySummary
"""
Contact details of the people or organization responsible for the image
"""
Authors: String
} }
""" """
@ -235,7 +261,7 @@ type RepoSummary {
""" """
List of platforms supported by this repository List of platforms supported by this repository
""" """
Platforms: [OsArch] Platforms: [Platform]
""" """
Vendors associated with this image, the distributing entities, organizations or individuals Vendors associated with this image, the distributing entities, organizations or individuals
""" """
@ -371,7 +397,7 @@ type Referrer {
""" """
Contains details about the OS and architecture of the image Contains details about the OS and architecture of the image
""" """
type OsArch { type Platform {
""" """
The name of the operating system which the image is built to run on, The name of the operating system which the image is built to run on,
Should be values listed in the Go Language document https://go.dev/doc/install/source#environment Should be values listed in the Go Language document https://go.dev/doc/install/source#environment
@ -606,6 +632,8 @@ type Query {
DerivedImageList( DerivedImageList(
"Image name in the format `repository:tag`" "Image name in the format `repository:tag`"
image: String!, image: String!,
"Digest of a specific manifest inside the image. When null whole image is considered"
digest: String,
"Sets the parameters of the requested page" "Sets the parameters of the requested page"
requestedPage: PageInput requestedPage: PageInput
): PaginatedImagesResult! ): PaginatedImagesResult!
@ -616,6 +644,8 @@ type Query {
BaseImageList( BaseImageList(
"Image name in the format `repository:tag`" "Image name in the format `repository:tag`"
image: String!, image: String!,
"Digest of a specific manifest inside the image. When null whole image is considered"
digest: String,
"Sets the parameters of the requested page" "Sets the parameters of the requested page"
requestedPage: PageInput requestedPage: PageInput
): PaginatedImagesResult! ): PaginatedImagesResult!

View file

@ -104,15 +104,15 @@ func (r *queryResolver) GlobalSearch(ctx context.Context, query string, filter *
} }
// DependencyListForImage is the resolver for the DependencyListForImage field. // DependencyListForImage is the resolver for the DependencyListForImage field.
func (r *queryResolver) DerivedImageList(ctx context.Context, image string, requestedPage *gql_generated.PageInput) (*gql_generated.PaginatedImagesResult, error) { func (r *queryResolver) DerivedImageList(ctx context.Context, image string, digest *string, requestedPage *gql_generated.PageInput) (*gql_generated.PaginatedImagesResult, error) {
derivedList, err := derivedImageList(ctx, image, r.repoDB, requestedPage, r.cveInfo, r.log) derivedList, err := derivedImageList(ctx, image, digest, r.repoDB, requestedPage, r.cveInfo, r.log)
return derivedList, err return derivedList, err
} }
// BaseImageList is the resolver for the BaseImageList field. // BaseImageList is the resolver for the BaseImageList field.
func (r *queryResolver) BaseImageList(ctx context.Context, image string, requestedPage *gql_generated.PageInput) (*gql_generated.PaginatedImagesResult, error) { func (r *queryResolver) BaseImageList(ctx context.Context, image string, digest *string, requestedPage *gql_generated.PageInput) (*gql_generated.PaginatedImagesResult, error) {
imageList, err := baseImageList(ctx, image, r.repoDB, requestedPage, r.cveInfo, r.log) imageList, err := baseImageList(ctx, image, digest, r.repoDB, requestedPage, r.cveInfo, r.log)
return imageList, err return imageList, err
} }
@ -125,7 +125,7 @@ func (r *queryResolver) Image(ctx context.Context, image string) (*gql_generated
return &gql_generated.ImageSummary{}, gqlerror.Errorf("no reference provided") return &gql_generated.ImageSummary{}, gqlerror.Errorf("no reference provided")
} }
return getImageSummary(ctx, repo, tag, r.repoDB, r.cveInfo, r.log) return getImageSummary(ctx, repo, tag, nil, r.repoDB, r.cveInfo, r.log)
} }
// Referrers is the resolver for the Referrers field. // Referrers is the resolver for the Referrers field.

View file

@ -4627,7 +4627,7 @@ func TestSyncImageIndex(t *testing.T) {
Manifest: manifest, Manifest: manifest,
Config: config, Config: config,
Layers: layers, Layers: layers,
Tag: manifestDigest.String(), Reference: manifestDigest.String(),
}, },
srcBaseURL, srcBaseURL,
"index") "index")

View file

@ -56,6 +56,11 @@ func NewBoltDBWrapper(params DBParameters) (*DBWrapper, error) {
return err return err
} }
_, err = transaction.CreateBucketIfNotExists([]byte(repodb.IndexDataBucket))
if err != nil {
return err
}
_, err = transaction.CreateBucketIfNotExists([]byte(repodb.RepoMetadataBucket)) _, err = transaction.CreateBucketIfNotExists([]byte(repodb.RepoMetadataBucket))
if err != nil { if err != nil {
return err return err
@ -209,6 +214,51 @@ func (bdw DBWrapper) GetManifestMeta(repo string, manifestDigest godigest.Digest
return manifestMetadata, err return manifestMetadata, err
} }
func (bdw DBWrapper) SetIndexData(indexDigest godigest.Digest, indexMetadata repodb.IndexData) error {
// we make the assumption that the oci layout is consistent and all manifests refferenced inside the
// index are present
err := bdw.DB.Update(func(tx *bolt.Tx) error {
buck := tx.Bucket([]byte(repodb.IndexDataBucket))
imBlob, err := json.Marshal(indexMetadata)
if err != nil {
return errors.Wrapf(err, "repodb: error while calculating blob for manifest with digest %s", indexDigest)
}
err = buck.Put([]byte(indexDigest), imBlob)
if err != nil {
return errors.Wrapf(err, "repodb: error while setting manifest meta with for digest %s", indexDigest)
}
return nil
})
return err
}
func (bdw DBWrapper) GetIndexData(indexDigest godigest.Digest) (repodb.IndexData, error) {
var indexMetadata repodb.IndexData
err := bdw.DB.View(func(tx *bolt.Tx) error {
buck := tx.Bucket([]byte(repodb.IndexDataBucket))
mmBlob := buck.Get([]byte(indexDigest))
if len(mmBlob) == 0 {
return zerr.ErrManifestMetaNotFound
}
err := json.Unmarshal(mmBlob, &indexMetadata)
if err != nil {
return errors.Wrapf(err, "repodb: error while unmashaling manifest meta for digest %s", indexDigest)
}
return nil
})
return indexMetadata, err
}
func (bdw DBWrapper) SetRepoTag(repo string, tag string, manifestDigest godigest.Digest, func (bdw DBWrapper) SetRepoTag(repo string, tag string, manifestDigest godigest.Digest,
mediaType string, mediaType string,
) error { ) error {
@ -622,24 +672,30 @@ func (bdw DBWrapper) DeleteSignature(repo string, signedManifestDigest godigest.
func (bdw DBWrapper) SearchRepos(ctx context.Context, searchText string, filter repodb.Filter, func (bdw DBWrapper) SearchRepos(ctx context.Context, searchText string, filter repodb.Filter,
requestedPage repodb.PageInput, requestedPage repodb.PageInput,
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) { ) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, map[string]repodb.IndexData, repodb.PageInfo,
error,
) {
var ( var (
foundRepos = make([]repodb.RepoMetadata, 0) foundRepos = make([]repodb.RepoMetadata, 0)
foundManifestMetadataMap = make(map[string]repodb.ManifestMetadata) foundManifestMetadataMap = make(map[string]repodb.ManifestMetadata)
foundindexDataMap = make(map[string]repodb.IndexData)
pageFinder repodb.PageFinder pageFinder repodb.PageFinder
pageInfo repodb.PageInfo pageInfo repodb.PageInfo
) )
pageFinder, err := repodb.NewBaseRepoPageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy) pageFinder, err := repodb.NewBaseRepoPageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy)
if err != nil { if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, repodb.PageInfo{}, err return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
repodb.PageInfo{}, err
} }
err = bdw.DB.View(func(tx *bolt.Tx) error { err = bdw.DB.View(func(transaction *bolt.Tx) error {
var ( var (
manifestMetadataMap = make(map[string]repodb.ManifestMetadata) manifestMetadataMap = make(map[string]repodb.ManifestMetadata)
repoBuck = tx.Bucket([]byte(repodb.RepoMetadataBucket)) indexDataMap = make(map[string]repodb.IndexData)
dataBuck = tx.Bucket([]byte(repodb.ManifestDataBucket)) repoBuck = transaction.Bucket([]byte(repodb.RepoMetadataBucket))
indexBuck = transaction.Bucket([]byte(repodb.IndexDataBucket))
manifestBuck = transaction.Bucket([]byte(repodb.ManifestDataBucket))
) )
cursor := repoBuck.Cursor() cursor := repoBuck.Cursor()
@ -667,47 +723,87 @@ func (bdw DBWrapper) SearchRepos(ctx context.Context, searchText string, filter
isSigned = false isSigned = false
) )
for _, descriptor := range repoMeta.Tags { for tag, descriptor := range repoMeta.Tags {
var manifestMeta repodb.ManifestMetadata switch descriptor.MediaType {
case ispec.MediaTypeImageManifest:
manifestDigest := descriptor.Digest
manifestMeta, manifestDownloaded := manifestMetadataMap[descriptor.Digest] manifestMeta, err := fetchManifestMetaWithCheck(repoMeta, manifestDigest,
manifestMetadataMap, manifestBuck)
if !manifestDownloaded {
manifestMetaBlob := dataBuck.Get([]byte(descriptor.Digest))
if manifestMetaBlob == nil {
return zerr.ErrManifestMetaNotFound
}
err := json.Unmarshal(manifestMetaBlob, &manifestMeta)
if err != nil { if err != nil {
return errors.Wrapf(err, "repodb: error while unmarshaling manifest metadata for digest %s", descriptor.Digest) return errors.Wrapf(err, "repodb: error fetching manifest meta for manifest with digest %s",
} manifestDigest)
} }
// get fields related to filtering manifestFilterData, err := collectImageManifestFilterData(manifestDigest, repoMeta, manifestMeta)
var configContent ispec.Image
err = json.Unmarshal(manifestMeta.ConfigBlob, &configContent)
if err != nil { if err != nil {
return errors.Wrapf(err, "repodb: error while unmarshaling config content for digest %s", descriptor.Digest) return errors.Wrapf(err, "repodb: error collecting filter data for manifest with digest %s",
manifestDigest)
} }
osSet[configContent.OS] = true repoDownloads += manifestFilterData.DownloadCount
archSet[configContent.Architecture] = true
// get fields related to sorting for _, os := range manifestFilterData.OsList {
repoDownloads += repoMeta.Statistics[descriptor.Digest].DownloadCount osSet[os] = true
}
for _, arch := range manifestFilterData.ArchList {
archSet[arch] = true
}
imageLastUpdated := common.GetImageLastUpdatedTimestamp(configContent) if firstImageChecked || repoLastUpdated.Before(manifestFilterData.LastUpdated) {
repoLastUpdated = manifestFilterData.LastUpdated
if firstImageChecked || repoLastUpdated.Before(imageLastUpdated) {
repoLastUpdated = imageLastUpdated
firstImageChecked = false firstImageChecked = false
isSigned = common.CheckIsSigned(repoMeta.Signatures[descriptor.Digest]) isSigned = manifestFilterData.IsSigned
} }
manifestMetadataMap[descriptor.Digest] = manifestMeta manifestMetadataMap[descriptor.Digest] = manifestMeta
case ispec.MediaTypeImageIndex:
var indexLastUpdated time.Time
indexDigest := descriptor.Digest
indexData, err := fetchIndexDataWithCheck(indexDigest, indexDataMap, indexBuck)
if err != nil {
return errors.Wrapf(err, "repodb: error fetching index data for index with digest %s",
indexDigest)
}
var indexContent ispec.Index
err = json.Unmarshal(indexData.IndexBlob, &indexContent)
if err != nil {
return errors.Wrapf(err, "repodb: error while unmashaling index content for %s:%s", repoName, tag)
}
// this also updates manifestMetadataMap
imageFilterData, err := collectImageIndexFilterInfo(indexDigest, repoMeta, indexData, manifestMetadataMap,
manifestBuck)
if err != nil {
return errors.Wrapf(err, "repodb: error collecting filter data for index with digest %s",
indexDigest)
}
for _, arch := range imageFilterData.ArchList {
archSet[arch] = true
}
for _, os := range imageFilterData.OsList {
osSet[os] = true
}
repoDownloads += imageFilterData.DownloadCount
if repoLastUpdated.Before(imageFilterData.LastUpdated) {
repoLastUpdated = indexLastUpdated
isSigned = imageFilterData.IsSigned
}
indexDataMap[indexDigest] = indexData
default:
bdw.Log.Error().Msgf("Unsupported type: %s", descriptor.MediaType)
}
} }
repoFilterData := repodb.FilterData{ repoFilterData := repodb.FilterData{
@ -731,39 +827,225 @@ func (bdw DBWrapper) SearchRepos(ctx context.Context, searchText string, filter
foundRepos, pageInfo = pageFinder.Page() foundRepos, pageInfo = pageFinder.Page()
// keep just the manifestMeta we need // keep just the manifestMeta and indexData we need
for _, repoMeta := range foundRepos { for _, repoMeta := range foundRepos {
for _, manifestDigest := range repoMeta.Tags { for _, descriptor := range repoMeta.Tags {
foundManifestMetadataMap[manifestDigest.Digest] = manifestMetadataMap[manifestDigest.Digest] switch descriptor.MediaType {
case ispec.MediaTypeImageManifest:
foundManifestMetadataMap[descriptor.Digest] = manifestMetadataMap[descriptor.Digest]
case ispec.MediaTypeImageIndex:
indexData := indexDataMap[descriptor.Digest]
var indexContent ispec.Index
err := json.Unmarshal(indexData.IndexBlob, &indexContent)
if err != nil {
return err
}
for _, manifestDescriptor := range indexContent.Manifests {
manifestDigest := manifestDescriptor.Digest.String()
foundManifestMetadataMap[manifestDigest] = manifestMetadataMap[manifestDigest]
}
foundindexDataMap[descriptor.Digest] = indexData
default:
bdw.Log.Error().Msgf("Unsupported type: %s", descriptor.MediaType)
}
} }
} }
return nil return nil
}) })
return foundRepos, foundManifestMetadataMap, pageInfo, err return foundRepos, foundManifestMetadataMap, foundindexDataMap, pageInfo, err
}
func fetchManifestMetaWithCheck(repoMeta repodb.RepoMetadata, manifestDigest string,
manifestMetadataMap map[string]repodb.ManifestMetadata, manifestBuck *bolt.Bucket,
) (repodb.ManifestMetadata, error) {
manifestMeta, manifestDownloaded := manifestMetadataMap[manifestDigest]
if !manifestDownloaded {
var manifestData repodb.ManifestData
manifestDataBlob := manifestBuck.Get([]byte(manifestDigest))
if manifestDataBlob == nil {
return repodb.ManifestMetadata{}, zerr.ErrManifestMetaNotFound
}
err := json.Unmarshal(manifestDataBlob, &manifestData)
if err != nil {
return repodb.ManifestMetadata{}, errors.Wrapf(err,
"repodb: error while unmarshaling manifest metadata for digest %s", manifestDigest)
}
manifestMeta = NewManifestMetadata(manifestDigest, repoMeta, manifestData)
}
return manifestMeta, nil
}
func fetchIndexDataWithCheck(indexDigest string, indexDataMap map[string]repodb.IndexData,
indexBuck *bolt.Bucket,
) (repodb.IndexData, error) {
var (
indexData repodb.IndexData
err error
)
indexData, indexExists := indexDataMap[indexDigest]
if !indexExists {
indexDataBlob := indexBuck.Get([]byte(indexDigest))
if indexDataBlob == nil {
return repodb.IndexData{}, zerr.ErrIndexDataNotFount
}
err := json.Unmarshal(indexDataBlob, &indexData)
if err != nil {
return repodb.IndexData{},
errors.Wrapf(err, "repodb: error while unmashaling index data for digest %s", indexDigest)
}
}
return indexData, err
}
func collectImageManifestFilterData(digest string, repoMeta repodb.RepoMetadata,
manifestMeta repodb.ManifestMetadata,
) (repodb.FilterData, error) {
// get fields related to filtering
var (
configContent ispec.Image
osList []string
archList []string
)
err := json.Unmarshal(manifestMeta.ConfigBlob, &configContent)
if err != nil {
return repodb.FilterData{},
errors.Wrapf(err, "repodb: error while unmarshaling config content")
}
if configContent.OS != "" {
osList = append(osList, configContent.OS)
}
if configContent.Architecture != "" {
archList = append(archList, configContent.Architecture)
}
return repodb.FilterData{
DownloadCount: repoMeta.Statistics[digest].DownloadCount,
OsList: osList,
ArchList: archList,
LastUpdated: common.GetImageLastUpdatedTimestamp(configContent),
IsSigned: common.CheckIsSigned(repoMeta.Signatures[digest]),
}, nil
}
func collectImageIndexFilterInfo(indexDigest string, repoMeta repodb.RepoMetadata,
indexData repodb.IndexData, manifestMetadataMap map[string]repodb.ManifestMetadata,
manifestBuck *bolt.Bucket,
) (repodb.FilterData, error) {
var indexContent ispec.Index
err := json.Unmarshal(indexData.IndexBlob, &indexContent)
if err != nil {
return repodb.FilterData{},
errors.Wrapf(err, "repodb: error while unmarshaling index content for digest %s", indexDigest)
}
var (
indexLastUpdated time.Time
firstManifestChecked = false
indexOsList = []string{}
indexArchList = []string{}
)
for _, manifest := range indexContent.Manifests {
manifestDigest := manifest.Digest
manifestMeta, err := fetchManifestMetaWithCheck(repoMeta, manifestDigest.String(),
manifestMetadataMap, manifestBuck)
if err != nil {
return repodb.FilterData{},
errors.Wrapf(err, "")
}
manifestFilterData, err := collectImageManifestFilterData(manifestDigest.String(), repoMeta,
manifestMeta)
if err != nil {
return repodb.FilterData{},
errors.Wrapf(err, "")
}
indexOsList = append(indexOsList, manifestFilterData.OsList...)
indexArchList = append(indexArchList, manifestFilterData.ArchList...)
if !firstManifestChecked || indexLastUpdated.Before(manifestFilterData.LastUpdated) {
indexLastUpdated = manifestFilterData.LastUpdated
firstManifestChecked = true
}
manifestMetadataMap[manifest.Digest.String()] = manifestMeta
}
return repodb.FilterData{
DownloadCount: repoMeta.Statistics[indexDigest].DownloadCount,
LastUpdated: indexLastUpdated,
OsList: indexOsList,
ArchList: indexArchList,
IsSigned: common.CheckIsSigned(repoMeta.Signatures[indexDigest]),
}, nil
}
func NewManifestMetadata(manifestDigest string, repoMeta repodb.RepoMetadata,
manifestData repodb.ManifestData,
) repodb.ManifestMetadata {
manifestMeta := repodb.ManifestMetadata{
ManifestBlob: manifestData.ManifestBlob,
ConfigBlob: manifestData.ConfigBlob,
}
manifestMeta.DownloadCount = repoMeta.Statistics[manifestDigest].DownloadCount
manifestMeta.Signatures = repodb.ManifestSignatures{}
if repoMeta.Signatures[manifestDigest] != nil {
manifestMeta.Signatures = repoMeta.Signatures[manifestDigest]
}
return manifestMeta
} }
func (bdw DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc, func (bdw DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
requestedPage repodb.PageInput, requestedPage repodb.PageInput,
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) { ) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, map[string]repodb.IndexData,
repodb.PageInfo, error,
) {
var ( var (
foundRepos = make([]repodb.RepoMetadata, 0) foundRepos = make([]repodb.RepoMetadata, 0)
manifestMetadataMap = make(map[string]repodb.ManifestMetadata)
indexDataMap = make(map[string]repodb.IndexData)
foundManifestMetadataMap = make(map[string]repodb.ManifestMetadata) foundManifestMetadataMap = make(map[string]repodb.ManifestMetadata)
foundindexDataMap = make(map[string]repodb.IndexData)
pageFinder repodb.PageFinder pageFinder repodb.PageFinder
pageInfo repodb.PageInfo pageInfo repodb.PageInfo
) )
pageFinder, err := repodb.NewBaseImagePageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy) pageFinder, err := repodb.NewBaseImagePageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy)
if err != nil { if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, repodb.PageInfo{}, err return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
repodb.PageInfo{}, err
} }
err = bdw.DB.View(func(tx *bolt.Tx) error { err = bdw.DB.View(func(tx *bolt.Tx) error {
var ( var (
manifestMetadataMap = make(map[string]repodb.ManifestMetadata)
repoBuck = tx.Bucket([]byte(repodb.RepoMetadataBucket)) repoBuck = tx.Bucket([]byte(repodb.RepoMetadataBucket))
dataBuck = tx.Bucket([]byte(repodb.ManifestDataBucket)) indexBuck = tx.Bucket([]byte(repodb.IndexDataBucket))
manifestBuck = tx.Bucket([]byte(repodb.ManifestDataBucket))
cursor = repoBuck.Cursor() cursor = repoBuck.Cursor()
) )
@ -784,39 +1066,16 @@ func (bdw DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
matchedTags := make(map[string]repodb.Descriptor) matchedTags := make(map[string]repodb.Descriptor)
// take all manifestMetas // take all manifestMetas
for tag, descriptor := range repoMeta.Tags { for tag, descriptor := range repoMeta.Tags {
matchedTags[tag] = descriptor
switch descriptor.MediaType {
case ispec.MediaTypeImageManifest:
manifestDigest := descriptor.Digest manifestDigest := descriptor.Digest
matchedTags[tag] = descriptor manifestMeta, err := fetchManifestMetaWithCheck(repoMeta, manifestDigest, manifestMetadataMap, manifestBuck)
// in case tags reference the same manifest we don't download from DB multiple times
manifestMeta, manifestExists := manifestMetadataMap[manifestDigest]
if !manifestExists {
manifestDataBlob := dataBuck.Get([]byte(manifestDigest))
if manifestDataBlob == nil {
return zerr.ErrManifestMetaNotFound
}
var manifestData repodb.ManifestData
err := json.Unmarshal(manifestDataBlob, &manifestData)
if err != nil { if err != nil {
return errors.Wrapf(err, "repodb: error while unmashaling manifest metadata for digest %s", manifestDigest) return errors.Wrapf(err, "repodb: error while unmashaling manifest metadata for digest %s", manifestDigest)
} }
var configContent ispec.Image
err = json.Unmarshal(manifestData.ConfigBlob, &configContent)
if err != nil {
return errors.Wrapf(err, "repodb: error while unmashaling config for manifest with digest %s", manifestDigest)
}
manifestMeta = repodb.ManifestMetadata{
ConfigBlob: manifestData.ConfigBlob,
ManifestBlob: manifestData.ManifestBlob,
}
}
if !filter(repoMeta, manifestMeta) { if !filter(repoMeta, manifestMeta) {
delete(matchedTags, tag) delete(matchedTags, tag)
@ -824,6 +1083,52 @@ func (bdw DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
} }
manifestMetadataMap[manifestDigest] = manifestMeta manifestMetadataMap[manifestDigest] = manifestMeta
case ispec.MediaTypeImageIndex:
indexDigest := descriptor.Digest
indexData, err := fetchIndexDataWithCheck(indexDigest, indexDataMap, indexBuck)
if err != nil {
return errors.Wrapf(err, "repodb: error while getting index data for digest %s", indexDigest)
}
var indexContent ispec.Index
err = json.Unmarshal(indexData.IndexBlob, &indexContent)
if err != nil {
return errors.Wrapf(err, "repodb: error while unmashaling index content for digest %s", indexDigest)
}
manifestHasBeenMatched := false
for _, manifest := range indexContent.Manifests {
manifestDigest := manifest.Digest.String()
manifestMeta, err := fetchManifestMetaWithCheck(repoMeta, manifestDigest, manifestMetadataMap, manifestBuck)
if err != nil {
return errors.Wrapf(err, "repodb: error while getting manifest data for digest %s", manifestDigest)
}
manifestMetadataMap[manifestDigest] = manifestMeta
if filter(repoMeta, manifestMeta) {
manifestHasBeenMatched = true
}
}
if !manifestHasBeenMatched {
delete(matchedTags, tag)
for _, manifest := range indexContent.Manifests {
delete(manifestMetadataMap, manifest.Digest.String())
}
continue
}
indexDataMap[indexDigest] = indexData
default:
bdw.Log.Error().Msgf("Unsupported type: %s", descriptor.MediaType)
}
} }
if len(matchedTags) == 0 { if len(matchedTags) == 0 {
@ -839,25 +1144,50 @@ func (bdw DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
foundRepos, pageInfo = pageFinder.Page() foundRepos, pageInfo = pageFinder.Page()
// keep just the manifestMeta we need // keep just the manifestMeta and indexData we need
for _, repoMeta := range foundRepos { for _, repoMeta := range foundRepos {
for _, descriptor := range repoMeta.Tags { for _, descriptor := range repoMeta.Tags {
switch descriptor.MediaType {
case ispec.MediaTypeImageManifest:
foundManifestMetadataMap[descriptor.Digest] = manifestMetadataMap[descriptor.Digest] foundManifestMetadataMap[descriptor.Digest] = manifestMetadataMap[descriptor.Digest]
case ispec.MediaTypeImageIndex:
indexData := indexDataMap[descriptor.Digest]
var indexContent ispec.Index
err := json.Unmarshal(indexData.IndexBlob, &indexContent)
if err != nil {
return err
}
for _, manifestDescriptor := range indexContent.Manifests {
manifestDigest := manifestDescriptor.Digest.String()
foundManifestMetadataMap[manifestDigest] = manifestMetadataMap[manifestDigest]
}
foundindexDataMap[descriptor.Digest] = indexData
default:
bdw.Log.Error().Msgf("Unsupported type: %s", descriptor.MediaType)
}
} }
} }
return nil return nil
}) })
return foundRepos, foundManifestMetadataMap, pageInfo, err return foundRepos, foundManifestMetadataMap, foundindexDataMap, pageInfo, err
} }
func (bdw DBWrapper) SearchTags(ctx context.Context, searchText string, filter repodb.Filter, func (bdw DBWrapper) SearchTags(ctx context.Context, searchText string, filter repodb.Filter,
requestedPage repodb.PageInput, requestedPage repodb.PageInput,
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) { ) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, map[string]repodb.IndexData, repodb.PageInfo, error) {
var ( var (
foundRepos = make([]repodb.RepoMetadata, 0) foundRepos = make([]repodb.RepoMetadata, 0)
manifestMetadataMap = make(map[string]repodb.ManifestMetadata)
indexDataMap = make(map[string]repodb.IndexData)
foundManifestMetadataMap = make(map[string]repodb.ManifestMetadata) foundManifestMetadataMap = make(map[string]repodb.ManifestMetadata)
foundindexDataMap = make(map[string]repodb.IndexData)
pageInfo repodb.PageInfo pageInfo repodb.PageInfo
pageFinder repodb.PageFinder pageFinder repodb.PageFinder
@ -865,20 +1195,22 @@ func (bdw DBWrapper) SearchTags(ctx context.Context, searchText string, filter r
pageFinder, err := repodb.NewBaseImagePageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy) pageFinder, err := repodb.NewBaseImagePageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy)
if err != nil { if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, repodb.PageInfo{}, err return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
repodb.PageInfo{}, err
} }
searchedRepo, searchedTag, err := common.GetRepoTag(searchText) searchedRepo, searchedTag, err := common.GetRepoTag(searchText)
if err != nil { if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, repodb.PageInfo{}, return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
repodb.PageInfo{},
errors.Wrap(err, "repodb: error while parsing search text, invalid format") errors.Wrap(err, "repodb: error while parsing search text, invalid format")
} }
err = bdw.DB.View(func(tx *bolt.Tx) error { err = bdw.DB.View(func(tx *bolt.Tx) error {
var ( var (
manifestMetadataMap = make(map[string]repodb.ManifestMetadata)
repoBuck = tx.Bucket([]byte(repodb.RepoMetadataBucket)) repoBuck = tx.Bucket([]byte(repodb.RepoMetadataBucket))
dataBuck = tx.Bucket([]byte(repodb.ManifestDataBucket)) indexBuck = tx.Bucket([]byte(repodb.IndexDataBucket))
manifestBuck = tx.Bucket([]byte(repodb.ManifestDataBucket))
cursor = repoBuck.Cursor() cursor = repoBuck.Cursor()
) )
@ -906,46 +1238,84 @@ func (bdw DBWrapper) SearchTags(ctx context.Context, searchText string, filter r
matchedTags[tag] = descriptor matchedTags[tag] = descriptor
// in case tags reference the same manifest we don't download from DB multiple times switch descriptor.MediaType {
if manifestMeta, manifestExists := manifestMetadataMap[descriptor.Digest]; manifestExists { case ispec.MediaTypeImageManifest:
manifestMetadataMap[descriptor.Digest] = manifestMeta manifestDigest := descriptor.Digest
continue manifestMeta, err := fetchManifestMetaWithCheck(repoMeta, manifestDigest, manifestMetadataMap, manifestBuck)
}
manifestMetaBlob := dataBuck.Get([]byte(descriptor.Digest))
if manifestMetaBlob == nil {
return zerr.ErrManifestMetaNotFound
}
var manifestMeta repodb.ManifestMetadata
err := json.Unmarshal(manifestMetaBlob, &manifestMeta)
if err != nil { if err != nil {
return errors.Wrapf(err, "repodb: error while unmashaling manifest metadata for digest %s", descriptor.Digest) return errors.Wrapf(err, "repodb: error fetching manifest meta for manifest with digest %s",
manifestDigest)
} }
var configContent ispec.Image imageFilterData, err := collectImageManifestFilterData(manifestDigest, repoMeta, manifestMeta)
err = json.Unmarshal(manifestMeta.ConfigBlob, &configContent)
if err != nil { if err != nil {
return errors.Wrapf(err, "repodb: error while unmashaling config for manifest with digest %s", descriptor.Digest) return errors.Wrapf(err, "repodb: error collecting filter data for manifest with digest %s",
} manifestDigest)
imageFilterData := repodb.FilterData{
OsList: []string{configContent.OS},
ArchList: []string{configContent.Architecture},
IsSigned: false,
} }
if !common.AcceptedByFilter(filter, imageFilterData) { if !common.AcceptedByFilter(filter, imageFilterData) {
delete(matchedTags, tag) delete(matchedTags, tag)
delete(manifestMetadataMap, descriptor.Digest)
continue continue
} }
manifestMetadataMap[descriptor.Digest] = manifestMeta manifestMetadataMap[descriptor.Digest] = manifestMeta
case ispec.MediaTypeImageIndex:
indexDigest := descriptor.Digest
indexData, err := fetchIndexDataWithCheck(indexDigest, indexDataMap, indexBuck)
if err != nil {
return errors.Wrapf(err, "repodb: error fetching index data for index with digest %s",
indexDigest)
}
var indexContent ispec.Index
err = json.Unmarshal(indexData.IndexBlob, &indexContent)
if err != nil {
return errors.Wrapf(err, "repodb: error collecting filter data for index with digest %s",
indexDigest)
}
manifestHasBeenMatched := false
for _, manifest := range indexContent.Manifests {
manifestDigest := manifest.Digest.String()
manifestMeta, err := fetchManifestMetaWithCheck(repoMeta, manifestDigest, manifestMetadataMap, manifestBuck)
if err != nil {
return errors.Wrapf(err, "repodb: error fetching from db manifest meta for manifest with digest %s",
manifestDigest)
}
manifestFilterData, err := collectImageManifestFilterData(manifestDigest, repoMeta, manifestMeta)
if err != nil {
return errors.Wrapf(err, "repodb: error collecting filter data for manifest with digest %s",
manifestDigest)
}
manifestMetadataMap[manifestDigest] = manifestMeta
if common.AcceptedByFilter(filter, manifestFilterData) {
manifestHasBeenMatched = true
}
}
if !manifestHasBeenMatched {
delete(matchedTags, tag)
for _, manifest := range indexContent.Manifests {
delete(manifestMetadataMap, manifest.Digest.String())
}
continue
}
indexDataMap[indexDigest] = indexData
default:
bdw.Log.Error().Msgf("Unsupported type: %s", descriptor.MediaType)
}
} }
if len(matchedTags) == 0 { if len(matchedTags) == 0 {
@ -962,17 +1332,39 @@ func (bdw DBWrapper) SearchTags(ctx context.Context, searchText string, filter r
foundRepos, pageInfo = pageFinder.Page() foundRepos, pageInfo = pageFinder.Page()
// keep just the manifestMeta we need // keep just the manifestMeta and indexData we need
for _, repoMeta := range foundRepos { for _, repoMeta := range foundRepos {
for _, descriptor := range repoMeta.Tags { for _, descriptor := range repoMeta.Tags {
switch descriptor.MediaType {
case ispec.MediaTypeImageManifest:
foundManifestMetadataMap[descriptor.Digest] = manifestMetadataMap[descriptor.Digest] foundManifestMetadataMap[descriptor.Digest] = manifestMetadataMap[descriptor.Digest]
case ispec.MediaTypeImageIndex:
indexData := indexDataMap[descriptor.Digest]
var indexContent ispec.Index
err := json.Unmarshal(indexData.IndexBlob, &indexContent)
if err != nil {
return err
}
for _, manifestDescriptor := range indexContent.Manifests {
manifestDigest := manifestDescriptor.Digest.String()
foundManifestMetadataMap[manifestDigest] = manifestMetadataMap[manifestDigest]
}
foundindexDataMap[descriptor.Digest] = indexData
default:
bdw.Log.Error().Msgf("Unsupported type: %s", descriptor.MediaType)
}
} }
} }
return nil return nil
}) })
return foundRepos, foundManifestMetadataMap, pageInfo, err return foundRepos, foundManifestMetadataMap, foundindexDataMap, pageInfo, err
} }
func (bdw *DBWrapper) PatchDB() error { func (bdw *DBWrapper) PatchDB() error {

View file

@ -13,10 +13,12 @@ import (
"zotregistry.io/zot/pkg/meta/repodb" "zotregistry.io/zot/pkg/meta/repodb"
bolt "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper" bolt "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper"
"zotregistry.io/zot/pkg/test"
) )
func TestWrapperErrors(t *testing.T) { func TestWrapperErrors(t *testing.T) {
Convey("Errors", t, func() { Convey("Errors", t, func() {
ctx := context.Background()
tmpDir := t.TempDir() tmpDir := t.TempDir()
boltDBParams := bolt.DBParameters{RootDir: tmpDir} boltDBParams := bolt.DBParameters{RootDir: tmpDir}
boltdbWrapper, err := bolt.NewBoltDBWrapper(boltDBParams) boltdbWrapper, err := bolt.NewBoltDBWrapper(boltDBParams)
@ -300,7 +302,7 @@ func TestWrapperErrors(t *testing.T) {
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
_, _, _, err = boltdbWrapper.SearchRepos(context.Background(), "", repodb.Filter{}, repodb.PageInput{}) _, _, _, _, err = boltdbWrapper.SearchRepos(context.Background(), "", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
err = boltdbWrapper.DB.Update(func(tx *bbolt.Tx) error { err = boltdbWrapper.DB.Update(func(tx *bbolt.Tx) error {
@ -339,10 +341,10 @@ func TestWrapperErrors(t *testing.T) {
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
_, _, _, err = boltdbWrapper.SearchRepos(context.Background(), "repo1", repodb.Filter{}, repodb.PageInput{}) _, _, _, _, err = boltdbWrapper.SearchRepos(context.Background(), "repo1", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
_, _, _, err = boltdbWrapper.SearchRepos(context.Background(), "repo2", repodb.Filter{}, repodb.PageInput{}) _, _, _, _, err = boltdbWrapper.SearchRepos(context.Background(), "repo2", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
err = boltdbWrapper.DB.Update(func(tx *bbolt.Tx) error { err = boltdbWrapper.DB.Update(func(tx *bbolt.Tx) error {
@ -378,10 +380,85 @@ func TestWrapperErrors(t *testing.T) {
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
_, _, _, err = boltdbWrapper.SearchRepos(context.Background(), "repo1", repodb.Filter{}, repodb.PageInput{}) _, _, _, _, err = boltdbWrapper.SearchRepos(context.Background(), "repo1", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
Convey("Index Errors", func() {
Convey("Bad index data", func() {
indexDigest := digest.FromString("indexDigest")
err := boltdbWrapper.SetRepoTag("repo", "tag1", indexDigest, ispec.MediaTypeImageIndex) //nolint:contextcheck
So(err, ShouldBeNil)
err = setBadIndexData(boltdbWrapper.DB, indexDigest.String())
So(err, ShouldBeNil)
_, _, _, _, err = boltdbWrapper.SearchRepos(ctx, "", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil)
_, _, _, _, err = boltdbWrapper.SearchTags(ctx, "repo:", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil)
})
Convey("Bad indexBlob in IndexData", func() {
indexDigest := digest.FromString("indexDigest")
err := boltdbWrapper.SetRepoTag("repo", "tag1", indexDigest, ispec.MediaTypeImageIndex) //nolint:contextcheck
So(err, ShouldBeNil)
err = boltdbWrapper.SetIndexData(indexDigest, repodb.IndexData{
IndexBlob: []byte("bad json"),
})
So(err, ShouldBeNil)
_, _, _, _, err = boltdbWrapper.SearchRepos(ctx, "", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil)
_, _, _, _, err = boltdbWrapper.SearchTags(ctx, "repo:", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil)
})
Convey("Good index data, bad manifest inside index", func() {
var (
indexDigest = digest.FromString("indexDigest")
manifestDigestFromIndex1 = digest.FromString("manifestDigestFromIndex1")
manifestDigestFromIndex2 = digest.FromString("manifestDigestFromIndex2")
)
err := boltdbWrapper.SetRepoTag("repo", "tag1", indexDigest, ispec.MediaTypeImageIndex) //nolint:contextcheck
So(err, ShouldBeNil)
indexBlob, err := test.GetIndexBlobWithManifests([]digest.Digest{
manifestDigestFromIndex1, manifestDigestFromIndex2,
})
So(err, ShouldBeNil)
err = boltdbWrapper.SetIndexData(indexDigest, repodb.IndexData{
IndexBlob: indexBlob,
})
So(err, ShouldBeNil)
err = boltdbWrapper.SetManifestData(manifestDigestFromIndex1, repodb.ManifestData{
ManifestBlob: []byte("Bad Manifest"),
ConfigBlob: []byte("Bad Manifest"),
})
So(err, ShouldBeNil)
err = boltdbWrapper.SetManifestData(manifestDigestFromIndex2, repodb.ManifestData{
ManifestBlob: []byte("Bad Manifest"),
ConfigBlob: []byte("Bad Manifest"),
})
So(err, ShouldBeNil)
_, _, _, _, err = boltdbWrapper.SearchRepos(ctx, "", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil)
_, _, _, _, err = boltdbWrapper.SearchTags(ctx, "repo:", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil)
})
})
Convey("SearchTags", func() { Convey("SearchTags", func() {
ctx := context.Background() ctx := context.Background()
@ -392,10 +469,10 @@ func TestWrapperErrors(t *testing.T) {
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
_, _, _, err = boltdbWrapper.SearchTags(ctx, "", repodb.Filter{}, repodb.PageInput{}) _, _, _, _, err = boltdbWrapper.SearchTags(ctx, "", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
_, _, _, err = boltdbWrapper.SearchTags(ctx, "repo1:", repodb.Filter{}, repodb.PageInput{}) _, _, _, _, err = boltdbWrapper.SearchTags(ctx, "repo1:", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
err = boltdbWrapper.DB.Update(func(tx *bbolt.Tx) error { err = boltdbWrapper.DB.Update(func(tx *bbolt.Tx) error {
@ -466,14 +543,117 @@ func TestWrapperErrors(t *testing.T) {
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
_, _, _, err = boltdbWrapper.SearchTags(ctx, "repo1:", repodb.Filter{}, repodb.PageInput{}) _, _, _, _, err = boltdbWrapper.SearchTags(ctx, "repo1:", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
_, _, _, err = boltdbWrapper.SearchTags(ctx, "repo2:", repodb.Filter{}, repodb.PageInput{}) _, _, _, _, err = boltdbWrapper.SearchTags(ctx, "repo2:", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
_, _, _, err = boltdbWrapper.SearchTags(ctx, "repo3:", repodb.Filter{}, repodb.PageInput{}) _, _, _, _, err = boltdbWrapper.SearchTags(ctx, "repo3:", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
Convey("FilterTags Index errors", func() {
Convey("FilterTags bad IndexData", func() {
indexDigest := digest.FromString("indexDigest")
err := boltdbWrapper.SetRepoTag("repo", "tag1", indexDigest, ispec.MediaTypeImageIndex) //nolint:contextcheck
So(err, ShouldBeNil)
err = setBadIndexData(boltdbWrapper.DB, indexDigest.String())
So(err, ShouldBeNil)
_, _, _, _, err = boltdbWrapper.FilterTags(ctx,
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool { return true },
repodb.PageInput{},
)
So(err, ShouldNotBeNil)
})
Convey("FilterTags bad indexBlob in IndexData", func() {
indexDigest := digest.FromString("indexDigest")
err := boltdbWrapper.SetRepoTag("repo", "tag1", indexDigest, ispec.MediaTypeImageIndex) //nolint:contextcheck
So(err, ShouldBeNil)
err = boltdbWrapper.SetIndexData(indexDigest, repodb.IndexData{
IndexBlob: []byte("bad json"),
})
So(err, ShouldBeNil)
_, _, _, _, err = boltdbWrapper.FilterTags(ctx,
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool { return true },
repodb.PageInput{},
)
So(err, ShouldNotBeNil)
})
Convey("FilterTags didn't match any index manifest", func() {
var (
indexDigest = digest.FromString("indexDigest")
manifestDigestFromIndex1 = digest.FromString("manifestDigestFromIndex1")
manifestDigestFromIndex2 = digest.FromString("manifestDigestFromIndex2")
)
err := boltdbWrapper.SetRepoTag("repo", "tag1", indexDigest, ispec.MediaTypeImageIndex) //nolint:contextcheck
So(err, ShouldBeNil)
indexBlob, err := test.GetIndexBlobWithManifests([]digest.Digest{
manifestDigestFromIndex1, manifestDigestFromIndex2,
})
So(err, ShouldBeNil)
err = boltdbWrapper.SetIndexData(indexDigest, repodb.IndexData{
IndexBlob: indexBlob,
})
So(err, ShouldBeNil)
err = boltdbWrapper.SetManifestData(manifestDigestFromIndex1, repodb.ManifestData{
ManifestBlob: []byte("{}"),
ConfigBlob: []byte("{}"),
})
So(err, ShouldBeNil)
err = boltdbWrapper.SetManifestData(manifestDigestFromIndex2, repodb.ManifestData{
ManifestBlob: []byte("{}"),
ConfigBlob: []byte("{}"),
})
So(err, ShouldBeNil)
_, _, _, _, err = boltdbWrapper.FilterTags(ctx,
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool { return false },
repodb.PageInput{},
)
So(err, ShouldBeNil)
})
})
Convey("Unsuported type", func() {
digest := digest.FromString("digest")
err := boltdbWrapper.SetRepoTag("repo", "tag1", digest, "invalid type") //nolint:contextcheck
So(err, ShouldBeNil)
_, _, _, _, err = boltdbWrapper.SearchRepos(ctx, "", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldBeNil)
_, _, _, _, err = boltdbWrapper.SearchTags(ctx, "repo:", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldBeNil)
_, _, _, _, err = boltdbWrapper.FilterTags(
ctx,
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool { return true },
repodb.PageInput{},
)
So(err, ShouldBeNil)
})
})
}
func setBadIndexData(dB *bbolt.DB, digest string) error {
return dB.Update(func(tx *bbolt.Tx) error {
indexDataBuck := tx.Bucket([]byte(repodb.IndexDataBucket))
return indexDataBuck.Put([]byte(digest), []byte("bad json"))
}) })
} }

View file

@ -64,6 +64,9 @@ func TestWrapperErrors(t *testing.T) {
err = dynamoWrapper.createManifestDataTable() err = dynamoWrapper.createManifestDataTable()
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
err = dynamoWrapper.createIndexDataTable()
So(err, ShouldNotBeNil)
err = dynamoWrapper.createVersionTable() err = dynamoWrapper.createVersionTable()
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })

View file

@ -12,6 +12,8 @@ import (
"github.com/aws/aws-sdk-go-v2/service/dynamodb" "github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types" "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
guuid "github.com/gofrs/uuid" guuid "github.com/gofrs/uuid"
"github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/rs/zerolog" "github.com/rs/zerolog"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
@ -20,6 +22,7 @@ import (
dynamo "zotregistry.io/zot/pkg/meta/repodb/dynamodb-wrapper" dynamo "zotregistry.io/zot/pkg/meta/repodb/dynamodb-wrapper"
"zotregistry.io/zot/pkg/meta/repodb/dynamodb-wrapper/iterator" "zotregistry.io/zot/pkg/meta/repodb/dynamodb-wrapper/iterator"
dynamoParams "zotregistry.io/zot/pkg/meta/repodb/dynamodb-wrapper/params" dynamoParams "zotregistry.io/zot/pkg/meta/repodb/dynamodb-wrapper/params"
"zotregistry.io/zot/pkg/test"
) )
func TestIterator(t *testing.T) { func TestIterator(t *testing.T) {
@ -36,6 +39,7 @@ func TestIterator(t *testing.T) {
repoMetaTablename := "RepoMetadataTable" + uuid.String() repoMetaTablename := "RepoMetadataTable" + uuid.String()
manifestDataTablename := "ManifestDataTable" + uuid.String() manifestDataTablename := "ManifestDataTable" + uuid.String()
versionTablename := "Version" + uuid.String() versionTablename := "Version" + uuid.String()
indexDataTablename := "IndexDataTable" + uuid.String()
Convey("TestIterator", t, func() { Convey("TestIterator", t, func() {
dynamoWrapper, err := dynamo.NewDynamoDBWrapper(dynamoParams.DBDriverParameters{ dynamoWrapper, err := dynamo.NewDynamoDBWrapper(dynamoParams.DBDriverParameters{
@ -43,6 +47,7 @@ func TestIterator(t *testing.T) {
Region: region, Region: region,
RepoMetaTablename: repoMetaTablename, RepoMetaTablename: repoMetaTablename,
ManifestDataTablename: manifestDataTablename, ManifestDataTablename: manifestDataTablename,
IndexDataTablename: indexDataTablename,
VersionTablename: versionTablename, VersionTablename: versionTablename,
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -127,6 +132,7 @@ func TestWrapperErrors(t *testing.T) {
repoMetaTablename := "RepoMetadataTable" + uuid.String() repoMetaTablename := "RepoMetadataTable" + uuid.String()
manifestDataTablename := "ManifestDataTable" + uuid.String() manifestDataTablename := "ManifestDataTable" + uuid.String()
versionTablename := "Version" + uuid.String() versionTablename := "Version" + uuid.String()
indexDataTablename := "IndexDataTable" + uuid.String()
ctx := context.Background() ctx := context.Background()
@ -136,6 +142,7 @@ func TestWrapperErrors(t *testing.T) {
Region: region, Region: region,
RepoMetaTablename: repoMetaTablename, RepoMetaTablename: repoMetaTablename,
ManifestDataTablename: manifestDataTablename, ManifestDataTablename: manifestDataTablename,
IndexDataTablename: indexDataTablename,
VersionTablename: versionTablename, VersionTablename: versionTablename,
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -144,7 +151,7 @@ func TestWrapperErrors(t *testing.T) {
So(dynamoWrapper.ResetRepoMetaTable(), ShouldBeNil) //nolint:contextcheck So(dynamoWrapper.ResetRepoMetaTable(), ShouldBeNil) //nolint:contextcheck
Convey("SetManifestData", func() { Convey("SetManifestData", func() {
dynamoWrapper.ManifestDataTablename = "WRONG table" dynamoWrapper.ManifestDataTablename = "WRONG tables"
err := dynamoWrapper.SetManifestData("dig", repodb.ManifestData{}) err := dynamoWrapper.SetManifestData("dig", repodb.ManifestData{})
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
@ -165,6 +172,21 @@ func TestWrapperErrors(t *testing.T) {
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
Convey("GetIndexData", func() {
dynamoWrapper.IndexDataTablename = "WRONG table"
_, err := dynamoWrapper.GetIndexData("dig")
So(err, ShouldNotBeNil)
})
Convey("GetIndexData unmarshal error", func() {
err := setBadIndexData(dynamoWrapper.Client, indexDataTablename, "dig")
So(err, ShouldBeNil)
_, err = dynamoWrapper.GetManifestData("dig")
So(err, ShouldNotBeNil)
})
Convey("SetManifestMeta GetRepoMeta error", func() { Convey("SetManifestMeta GetRepoMeta error", func() {
err := setBadRepoMeta(dynamoWrapper.Client, repoMetaTablename, "repo1") err := setBadRepoMeta(dynamoWrapper.Client, repoMetaTablename, "repo1")
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -255,14 +277,6 @@ func TestWrapperErrors(t *testing.T) {
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
Convey("IncrementImageDownloads GetManifestMeta error", func() {
err := dynamoWrapper.SetRepoTag("repo", "tag", "dig", "")
So(err, ShouldBeNil)
err = dynamoWrapper.IncrementImageDownloads("repo", "tag")
So(err, ShouldNotBeNil)
})
Convey("AddManifestSignature GetRepoMeta error", func() { Convey("AddManifestSignature GetRepoMeta error", func() {
err := dynamoWrapper.SetRepoTag("repo", "tag", "dig", "") err := dynamoWrapper.SetRepoTag("repo", "tag", "dig", "")
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -329,22 +343,22 @@ func TestWrapperErrors(t *testing.T) {
err = setBadRepoMeta(dynamoWrapper.Client, repoMetaTablename, "repo") //nolint:contextcheck err = setBadRepoMeta(dynamoWrapper.Client, repoMetaTablename, "repo") //nolint:contextcheck
So(err, ShouldBeNil) So(err, ShouldBeNil)
_, _, _, err = dynamoWrapper.SearchRepos(ctx, "", repodb.Filter{}, repodb.PageInput{}) _, _, _, _, err = dynamoWrapper.SearchRepos(ctx, "", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
Convey("SearchRepos GetManifestMeta error", func() { Convey("SearchRepos GetManifestMeta error", func() {
err := dynamoWrapper.SetRepoTag("repo", "tag1", "notFoundDigest", "") //nolint:contextcheck err := dynamoWrapper.SetRepoTag("repo", "tag1", "notFoundDigest", ispec.MediaTypeImageManifest) //nolint:contextcheck
So(err, ShouldBeNil) So(err, ShouldBeNil)
_, _, _, err = dynamoWrapper.SearchRepos(ctx, "", repodb.Filter{}, repodb.PageInput{}) _, _, _, _, err = dynamoWrapper.SearchRepos(ctx, "", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
Convey("SearchRepos config unmarshal error", func() { Convey("SearchRepos config unmarshal error", func() {
err := dynamoWrapper.SetRepoTag("repo", "tag1", "dig1", "") //nolint:contextcheck err := dynamoWrapper.SetRepoTag("repo", "tag1", "dig1", ispec.MediaTypeImageManifest) //nolint:contextcheck
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = dynamoWrapper.SetManifestData("dig1", repodb.ManifestData{ //nolint:contextcheck err = dynamoWrapper.SetManifestData("dig1", repodb.ManifestData{ //nolint:contextcheck
@ -353,31 +367,116 @@ func TestWrapperErrors(t *testing.T) {
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
_, _, _, err = dynamoWrapper.SearchRepos(ctx, "", repodb.Filter{}, repodb.PageInput{}) _, _, _, _, err = dynamoWrapper.SearchRepos(ctx, "", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
Convey("Unsuported type", func() {
digest := digest.FromString("digest")
err := dynamoWrapper.SetRepoTag("repo", "tag1", digest, "invalid type") //nolint:contextcheck
So(err, ShouldBeNil)
_, _, _, _, err = dynamoWrapper.SearchRepos(ctx, "", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldBeNil)
_, _, _, _, err = dynamoWrapper.SearchTags(ctx, "repo:", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldBeNil)
_, _, _, _, err = dynamoWrapper.FilterTags(
ctx,
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool { return true },
repodb.PageInput{},
)
So(err, ShouldBeNil)
})
Convey("SearchRepos bad index data", func() {
indexDigest := digest.FromString("indexDigest")
err := dynamoWrapper.SetRepoTag("repo", "tag1", indexDigest, ispec.MediaTypeImageIndex) //nolint:contextcheck
So(err, ShouldBeNil)
err = setBadIndexData(dynamoWrapper.Client, indexDataTablename, indexDigest.String()) //nolint:contextcheck
So(err, ShouldBeNil)
_, _, _, _, err = dynamoWrapper.SearchRepos(ctx, "", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil)
})
Convey("SearchRepos bad indexBlob in IndexData", func() {
indexDigest := digest.FromString("indexDigest")
err := dynamoWrapper.SetRepoTag("repo", "tag1", indexDigest, ispec.MediaTypeImageIndex) //nolint:contextcheck
So(err, ShouldBeNil)
err = dynamoWrapper.SetIndexData(indexDigest, repodb.IndexData{ //nolint:contextcheck
IndexBlob: []byte("bad json"),
})
So(err, ShouldBeNil)
_, _, _, _, err = dynamoWrapper.SearchRepos(ctx, "", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil)
})
Convey("SearchRepos good index data, bad manifest inside index", func() {
var (
indexDigest = digest.FromString("indexDigest")
manifestDigestFromIndex1 = digest.FromString("manifestDigestFromIndex1")
manifestDigestFromIndex2 = digest.FromString("manifestDigestFromIndex2")
)
err := dynamoWrapper.SetRepoTag("repo", "tag1", indexDigest, ispec.MediaTypeImageIndex) //nolint:contextcheck
So(err, ShouldBeNil)
indexBlob, err := test.GetIndexBlobWithManifests([]digest.Digest{
manifestDigestFromIndex1, manifestDigestFromIndex2,
})
So(err, ShouldBeNil)
err = dynamoWrapper.SetIndexData(indexDigest, repodb.IndexData{ //nolint:contextcheck
IndexBlob: indexBlob,
})
So(err, ShouldBeNil)
err = dynamoWrapper.SetManifestData(manifestDigestFromIndex1, repodb.ManifestData{ //nolint:contextcheck
ManifestBlob: []byte("Bad Manifest"),
ConfigBlob: []byte("Bad Manifest"),
})
So(err, ShouldBeNil)
err = dynamoWrapper.SetManifestData(manifestDigestFromIndex2, repodb.ManifestData{ //nolint:contextcheck
ManifestBlob: []byte("Bad Manifest"),
ConfigBlob: []byte("Bad Manifest"),
})
So(err, ShouldBeNil)
_, _, _, _, err = dynamoWrapper.SearchRepos(ctx, "", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil)
})
Convey("SearchTags repoMeta unmarshal error", func() { Convey("SearchTags repoMeta unmarshal error", func() {
err = setBadRepoMeta(dynamoWrapper.Client, repoMetaTablename, "repo") //nolint:contextcheck err = setBadRepoMeta(dynamoWrapper.Client, repoMetaTablename, "repo") //nolint:contextcheck
So(err, ShouldBeNil) So(err, ShouldBeNil)
_, _, _, err = dynamoWrapper.SearchTags(ctx, "repo:", repodb.Filter{}, repodb.PageInput{}) _, _, _, _, err = dynamoWrapper.SearchTags(ctx, "repo:", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
Convey("SearchTags GetManifestMeta error", func() { Convey("SearchTags GetManifestMeta error", func() {
err := dynamoWrapper.SetRepoTag("repo", "tag1", "manifestNotFound", "") //nolint:contextcheck err := dynamoWrapper.SetRepoTag("repo", "tag1", "manifestNotFound", //nolint:contextcheck
ispec.MediaTypeImageManifest)
So(err, ShouldBeNil) So(err, ShouldBeNil)
_, _, _, err = dynamoWrapper.SearchTags(ctx, "repo:", repodb.Filter{}, repodb.PageInput{}) _, _, _, _, err = dynamoWrapper.SearchTags(ctx, "repo:", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
Convey("SearchTags config unmarshal error", func() { Convey("SearchTags config unmarshal error", func() {
err := dynamoWrapper.SetRepoTag("repo", "tag1", "dig1", "") //nolint:contextcheck err := dynamoWrapper.SetRepoTag("repo", "tag1", "dig1", ispec.MediaTypeImageManifest) //nolint:contextcheck
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = dynamoWrapper.SetManifestData( //nolint:contextcheck err = dynamoWrapper.SetManifestData( //nolint:contextcheck
@ -389,16 +488,80 @@ func TestWrapperErrors(t *testing.T) {
) )
So(err, ShouldBeNil) So(err, ShouldBeNil)
_, _, _, err = dynamoWrapper.SearchTags(ctx, "repo:", repodb.Filter{}, repodb.PageInput{}) _, _, _, _, err = dynamoWrapper.SearchTags(ctx, "repo:", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
Convey("SearchTags bad index data", func() {
indexDigest := digest.FromString("indexDigest")
err := dynamoWrapper.SetRepoTag("repo", "tag1", indexDigest, ispec.MediaTypeImageIndex) //nolint:contextcheck
So(err, ShouldBeNil)
err = setBadIndexData(dynamoWrapper.Client, indexDataTablename, indexDigest.String()) //nolint:contextcheck
So(err, ShouldBeNil)
_, _, _, _, err = dynamoWrapper.SearchTags(ctx, "repo:", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil)
})
Convey("SearchTags bad indexBlob in IndexData", func() {
indexDigest := digest.FromString("indexDigest")
err := dynamoWrapper.SetRepoTag("repo", "tag1", indexDigest, ispec.MediaTypeImageIndex) //nolint:contextcheck
So(err, ShouldBeNil)
err = dynamoWrapper.SetIndexData(indexDigest, repodb.IndexData{ //nolint:contextcheck
IndexBlob: []byte("bad json"),
})
So(err, ShouldBeNil)
_, _, _, _, err = dynamoWrapper.SearchTags(ctx, "repo:", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil)
})
Convey("SearchTags good index data, bad manifest inside index", func() {
var (
indexDigest = digest.FromString("indexDigest")
manifestDigestFromIndex1 = digest.FromString("manifestDigestFromIndex1")
manifestDigestFromIndex2 = digest.FromString("manifestDigestFromIndex2")
)
err := dynamoWrapper.SetRepoTag("repo", "tag1", indexDigest, ispec.MediaTypeImageIndex) //nolint:contextcheck
So(err, ShouldBeNil)
indexBlob, err := test.GetIndexBlobWithManifests([]digest.Digest{
manifestDigestFromIndex1, manifestDigestFromIndex2,
})
So(err, ShouldBeNil)
err = dynamoWrapper.SetIndexData(indexDigest, repodb.IndexData{ //nolint:contextcheck
IndexBlob: indexBlob,
})
So(err, ShouldBeNil)
err = dynamoWrapper.SetManifestData(manifestDigestFromIndex1, repodb.ManifestData{ //nolint:contextcheck
ManifestBlob: []byte("Bad Manifest"),
ConfigBlob: []byte("Bad Manifest"),
})
So(err, ShouldBeNil)
err = dynamoWrapper.SetManifestData(manifestDigestFromIndex2, repodb.ManifestData{ //nolint:contextcheck
ManifestBlob: []byte("Bad Manifest"),
ConfigBlob: []byte("Bad Manifest"),
})
So(err, ShouldBeNil)
_, _, _, _, err = dynamoWrapper.SearchTags(ctx, "repo:", repodb.Filter{}, repodb.PageInput{})
So(err, ShouldNotBeNil)
})
Convey("FilterTags repoMeta unmarshal error", func() { Convey("FilterTags repoMeta unmarshal error", func() {
err = setBadRepoMeta(dynamoWrapper.Client, repoMetaTablename, "repo") //nolint:contextcheck err = setBadRepoMeta(dynamoWrapper.Client, repoMetaTablename, "repo") //nolint:contextcheck
So(err, ShouldBeNil) So(err, ShouldBeNil)
_, _, _, err = dynamoWrapper.FilterTags( _, _, _, _, err = dynamoWrapper.FilterTags(
ctx, ctx,
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool { func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
return true return true
@ -410,10 +573,11 @@ func TestWrapperErrors(t *testing.T) {
}) })
Convey("FilterTags manifestMeta not found", func() { Convey("FilterTags manifestMeta not found", func() {
err := dynamoWrapper.SetRepoTag("repo", "tag1", "manifestNotFound", "") //nolint:contextcheck err := dynamoWrapper.SetRepoTag("repo", "tag1", "manifestNotFound", //nolint:contextcheck
ispec.MediaTypeImageManifest)
So(err, ShouldBeNil) So(err, ShouldBeNil)
_, _, _, err = dynamoWrapper.FilterTags( _, _, _, _, err = dynamoWrapper.FilterTags(
ctx, ctx,
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool { func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
return true return true
@ -425,13 +589,13 @@ func TestWrapperErrors(t *testing.T) {
}) })
Convey("FilterTags manifestMeta unmarshal error", func() { Convey("FilterTags manifestMeta unmarshal error", func() {
err := dynamoWrapper.SetRepoTag("repo", "tag1", "dig", "") //nolint:contextcheck err := dynamoWrapper.SetRepoTag("repo", "tag1", "dig", ispec.MediaTypeImageManifest) //nolint:contextcheck
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = setBadManifestData(dynamoWrapper.Client, manifestDataTablename, "dig") //nolint:contextcheck err = setBadManifestData(dynamoWrapper.Client, manifestDataTablename, "dig") //nolint:contextcheck
So(err, ShouldBeNil) So(err, ShouldBeNil)
_, _, _, err = dynamoWrapper.FilterTags( _, _, _, _, err = dynamoWrapper.FilterTags(
ctx, ctx,
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool { func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
return true return true
@ -442,27 +606,131 @@ func TestWrapperErrors(t *testing.T) {
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
Convey("FilterTags config unmarshal error", func() { Convey("FilterTags bad IndexData", func() {
err := dynamoWrapper.SetRepoTag("repo", "tag1", "dig1", "") //nolint:contextcheck indexDigest := digest.FromString("indexDigest")
err := dynamoWrapper.SetRepoTag("repo", "tag1", indexDigest, ispec.MediaTypeImageIndex) //nolint:contextcheck
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = dynamoWrapper.SetManifestData("dig1", repodb.ManifestData{ //nolint:contextcheck err = setBadIndexData(dynamoWrapper.Client, indexDataTablename, indexDigest.String()) //nolint:contextcheck
So(err, ShouldBeNil)
_, _, _, _, err = dynamoWrapper.FilterTags(ctx,
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool { return true },
repodb.PageInput{},
)
So(err, ShouldNotBeNil)
})
Convey("FilterTags bad indexBlob in IndexData", func() {
indexDigest := digest.FromString("indexDigest")
err := dynamoWrapper.SetRepoTag("repo", "tag1", indexDigest, ispec.MediaTypeImageIndex) //nolint:contextcheck
So(err, ShouldBeNil)
err = dynamoWrapper.SetIndexData(indexDigest, repodb.IndexData{ //nolint:contextcheck
IndexBlob: []byte("bad json"),
})
So(err, ShouldBeNil)
_, _, _, _, err = dynamoWrapper.FilterTags(ctx,
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool { return true },
repodb.PageInput{},
)
So(err, ShouldNotBeNil)
})
Convey("FilterTags didn't match any index manifest", func() {
var (
indexDigest = digest.FromString("indexDigest")
manifestDigestFromIndex1 = digest.FromString("manifestDigestFromIndex1")
manifestDigestFromIndex2 = digest.FromString("manifestDigestFromIndex2")
)
err := dynamoWrapper.SetRepoTag("repo", "tag1", indexDigest, ispec.MediaTypeImageIndex) //nolint:contextcheck
So(err, ShouldBeNil)
indexBlob, err := test.GetIndexBlobWithManifests([]digest.Digest{
manifestDigestFromIndex1, manifestDigestFromIndex2,
})
So(err, ShouldBeNil)
err = dynamoWrapper.SetIndexData(indexDigest, repodb.IndexData{ //nolint:contextcheck
IndexBlob: indexBlob,
})
So(err, ShouldBeNil)
err = dynamoWrapper.SetManifestData(manifestDigestFromIndex1, repodb.ManifestData{ //nolint:contextcheck
ManifestBlob: []byte("{}"), ManifestBlob: []byte("{}"),
ConfigBlob: []byte("bad json"), ConfigBlob: []byte("{}"),
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
_, _, _, err = dynamoWrapper.FilterTags( err = dynamoWrapper.SetManifestData(manifestDigestFromIndex2, repodb.ManifestData{ //nolint:contextcheck
ctx, ManifestBlob: []byte("{}"),
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool { ConfigBlob: []byte("{}"),
return true })
}, So(err, ShouldBeNil)
_, _, _, _, err = dynamoWrapper.FilterTags(ctx,
func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool { return false },
repodb.PageInput{}, repodb.PageInput{},
) )
So(err, ShouldBeNil)
So(err, ShouldNotBeNil)
}) })
}) })
Convey("NewDynamoDBWrapper errors", t, func() {
_, err := dynamo.NewDynamoDBWrapper(dynamoParams.DBDriverParameters{ //nolint:contextcheck
Endpoint: endpoint,
Region: region,
RepoMetaTablename: "",
ManifestDataTablename: manifestDataTablename,
IndexDataTablename: indexDataTablename,
VersionTablename: versionTablename,
})
So(err, ShouldNotBeNil)
_, err = dynamo.NewDynamoDBWrapper(dynamoParams.DBDriverParameters{ //nolint:contextcheck
Endpoint: endpoint,
Region: region,
RepoMetaTablename: repoMetaTablename,
ManifestDataTablename: "",
IndexDataTablename: indexDataTablename,
VersionTablename: versionTablename,
})
So(err, ShouldNotBeNil)
_, err = dynamo.NewDynamoDBWrapper(dynamoParams.DBDriverParameters{ //nolint:contextcheck
Endpoint: endpoint,
Region: region,
RepoMetaTablename: repoMetaTablename,
ManifestDataTablename: manifestDataTablename,
IndexDataTablename: "",
VersionTablename: versionTablename,
})
So(err, ShouldNotBeNil)
_, err = dynamo.NewDynamoDBWrapper(dynamoParams.DBDriverParameters{ //nolint:contextcheck
Endpoint: endpoint,
Region: region,
RepoMetaTablename: repoMetaTablename,
ManifestDataTablename: manifestDataTablename,
IndexDataTablename: indexDataTablename,
VersionTablename: "",
})
So(err, ShouldNotBeNil)
_, err = dynamo.NewDynamoDBWrapper(dynamoParams.DBDriverParameters{ //nolint:contextcheck
Endpoint: endpoint,
Region: region,
RepoMetaTablename: repoMetaTablename,
ManifestDataTablename: manifestDataTablename,
IndexDataTablename: indexDataTablename,
VersionTablename: versionTablename,
})
So(err, ShouldBeNil)
})
} }
func setBadManifestData(client *dynamodb.Client, manifestDataTableName, digest string) error { func setBadManifestData(client *dynamodb.Client, manifestDataTableName, digest string) error {
@ -490,6 +758,31 @@ func setBadManifestData(client *dynamodb.Client, manifestDataTableName, digest s
return err return err
} }
func setBadIndexData(client *dynamodb.Client, indexDataTableName, digest string) error {
mdAttributeValue, err := attributevalue.Marshal("string")
if err != nil {
return err
}
_, err = client.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
ExpressionAttributeNames: map[string]string{
"#ID": "IndexData",
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":IndexData": mdAttributeValue,
},
Key: map[string]types.AttributeValue{
"IndexDigest": &types.AttributeValueMemberS{
Value: digest,
},
},
TableName: aws.String(indexDataTableName),
UpdateExpression: aws.String("SET #ID = :IndexData"),
})
return err
}
func setBadRepoMeta(client *dynamodb.Client, repoMetadataTableName, repoName string) error { func setBadRepoMeta(client *dynamodb.Client, repoMetadataTableName, repoName string) error {
repoAttributeValue, err := attributevalue.Marshal("string") repoAttributeValue, err := attributevalue.Marshal("string")
if err != nil { if err != nil {

View file

@ -30,6 +30,7 @@ import (
type DBWrapper struct { type DBWrapper struct {
Client *dynamodb.Client Client *dynamodb.Client
RepoMetaTablename string RepoMetaTablename string
IndexDataTablename string
ManifestDataTablename string ManifestDataTablename string
VersionTablename string VersionTablename string
Patches []func(client *dynamodb.Client, tableNames map[string]string) error Patches []func(client *dynamodb.Client, tableNames map[string]string) error
@ -60,6 +61,7 @@ func NewDynamoDBWrapper(params dynamoParams.DBDriverParameters) (*DBWrapper, err
Client: dynamodb.NewFromConfig(cfg), Client: dynamodb.NewFromConfig(cfg),
RepoMetaTablename: params.RepoMetaTablename, RepoMetaTablename: params.RepoMetaTablename,
ManifestDataTablename: params.ManifestDataTablename, ManifestDataTablename: params.ManifestDataTablename,
IndexDataTablename: params.IndexDataTablename,
VersionTablename: params.VersionTablename, VersionTablename: params.VersionTablename,
Patches: version.GetDynamoDBPatches(), Patches: version.GetDynamoDBPatches(),
Log: log.Logger{Logger: zerolog.New(os.Stdout)}, Log: log.Logger{Logger: zerolog.New(os.Stdout)},
@ -80,6 +82,11 @@ func NewDynamoDBWrapper(params dynamoParams.DBDriverParameters) (*DBWrapper, err
return nil, err return nil, err
} }
err = dynamoWrapper.createIndexDataTable()
if err != nil {
return nil, err
}
// Using the Config value, create the DynamoDB client // Using the Config value, create the DynamoDB client
return &dynamoWrapper, nil return &dynamoWrapper, nil
} }
@ -248,6 +255,58 @@ func (dwr DBWrapper) GetRepoStars(repo string) (int, error) {
return repoMeta.Stars, nil return repoMeta.Stars, nil
} }
func (dwr DBWrapper) SetIndexData(indexDigest godigest.Digest, indexData repodb.IndexData) error {
indexAttributeValue, err := attributevalue.Marshal(indexData)
if err != nil {
return err
}
_, err = dwr.Client.UpdateItem(context.TODO(), &dynamodb.UpdateItemInput{
ExpressionAttributeNames: map[string]string{
"#ID": "IndexData",
},
ExpressionAttributeValues: map[string]types.AttributeValue{
":IndexData": indexAttributeValue,
},
Key: map[string]types.AttributeValue{
"IndexDigest": &types.AttributeValueMemberS{
Value: indexDigest.String(),
},
},
TableName: aws.String(dwr.IndexDataTablename),
UpdateExpression: aws.String("SET #ID = :IndexData"),
})
return err
}
func (dwr DBWrapper) GetIndexData(indexDigest godigest.Digest) (repodb.IndexData, error) {
resp, err := dwr.Client.GetItem(context.TODO(), &dynamodb.GetItemInput{
TableName: aws.String(dwr.IndexDataTablename),
Key: map[string]types.AttributeValue{
"IndexDigest": &types.AttributeValueMemberS{
Value: indexDigest.String(),
},
},
})
if err != nil {
return repodb.IndexData{}, err
}
if resp.Item == nil {
return repodb.IndexData{}, zerr.ErrRepoMetaNotFound
}
var indexData repodb.IndexData
err = attributevalue.Unmarshal(resp.Item["IndexData"], &indexData)
if err != nil {
return repodb.IndexData{}, err
}
return indexData, nil
}
func (dwr DBWrapper) SetRepoTag(repo string, tag string, manifestDigest godigest.Digest, mediaType string) error { func (dwr DBWrapper) SetRepoTag(repo string, tag string, manifestDigest godigest.Digest, mediaType string) error {
if err := common.ValidateRepoTagInput(repo, tag, manifestDigest); err != nil { if err := common.ValidateRepoTagInput(repo, tag, manifestDigest); err != nil {
return err return err
@ -377,7 +436,7 @@ func (dwr DBWrapper) IncrementImageDownloads(repo string, reference string) erro
return err return err
} }
manifestDigest := reference descriptorDigest := reference
if !common.ReferenceIsDigest(reference) { if !common.ReferenceIsDigest(reference) {
// search digest for tag // search digest for tag
@ -387,19 +446,14 @@ func (dwr DBWrapper) IncrementImageDownloads(repo string, reference string) erro
return zerr.ErrManifestMetaNotFound return zerr.ErrManifestMetaNotFound
} }
manifestDigest = descriptor.Digest descriptorDigest = descriptor.Digest
} }
manifestMeta, err := dwr.GetManifestMeta(repo, godigest.Digest(manifestDigest)) manifestStatistics := repoMeta.Statistics[descriptorDigest]
if err != nil { manifestStatistics.DownloadCount++
return err repoMeta.Statistics[descriptorDigest] = manifestStatistics
}
manifestMeta.DownloadCount++ return dwr.setRepoMeta(repo, repoMeta)
err = dwr.SetManifestMeta(repo, godigest.Digest(manifestDigest), manifestMeta)
return err
} }
func (dwr DBWrapper) AddManifestSignature(repo string, signedManifestDigest godigest.Digest, func (dwr DBWrapper) AddManifestSignature(repo string, signedManifestDigest godigest.Digest,
@ -531,11 +585,10 @@ func (dwr DBWrapper) GetMultipleRepoMeta(ctx context.Context,
func (dwr DBWrapper) SearchRepos(ctx context.Context, searchText string, filter repodb.Filter, func (dwr DBWrapper) SearchRepos(ctx context.Context, searchText string, filter repodb.Filter,
requestedPage repodb.PageInput, requestedPage repodb.PageInput,
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) { ) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, map[string]repodb.IndexData, repodb.PageInfo, error) {
var ( var (
foundManifestMetadataMap = make(map[string]repodb.ManifestMetadata)
manifestMetadataMap = make(map[string]repodb.ManifestMetadata) manifestMetadataMap = make(map[string]repodb.ManifestMetadata)
indexDataMap = make(map[string]repodb.IndexData)
repoMetaAttributeIterator iterator.AttributesIterator repoMetaAttributeIterator iterator.AttributesIterator
pageFinder repodb.PageFinder pageFinder repodb.PageFinder
pageInfo repodb.PageInfo pageInfo repodb.PageInfo
@ -547,7 +600,8 @@ func (dwr DBWrapper) SearchRepos(ctx context.Context, searchText string, filter
pageFinder, err := repodb.NewBaseRepoPageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy) pageFinder, err := repodb.NewBaseRepoPageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy)
if err != nil { if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo, err return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo, err
} }
repoMetaAttribute, err := repoMetaAttributeIterator.First(ctx) repoMetaAttribute, err := repoMetaAttributeIterator.First(ctx)
@ -555,14 +609,16 @@ func (dwr DBWrapper) SearchRepos(ctx context.Context, searchText string, filter
for ; repoMetaAttribute != nil; repoMetaAttribute, err = repoMetaAttributeIterator.Next(ctx) { for ; repoMetaAttribute != nil; repoMetaAttribute, err = repoMetaAttributeIterator.Next(ctx) {
if err != nil { if err != nil {
// log // log
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo, err return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo, err
} }
var repoMeta repodb.RepoMetadata var repoMeta repodb.RepoMetadata
err := attributevalue.Unmarshal(repoMetaAttribute, &repoMeta) err := attributevalue.Unmarshal(repoMetaAttribute, &repoMeta)
if err != nil { if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo, err return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo, err
} }
if ok, err := localCtx.RepoIsUserAvailable(ctx, repoMeta.Name); !ok || err != nil { if ok, err := localCtx.RepoIsUserAvailable(ctx, repoMeta.Name); !ok || err != nil {
@ -581,43 +637,84 @@ func (dwr DBWrapper) SearchRepos(ctx context.Context, searchText string, filter
) )
for _, descriptor := range repoMeta.Tags { for _, descriptor := range repoMeta.Tags {
var manifestMeta repodb.ManifestMetadata switch descriptor.MediaType {
case ispec.MediaTypeImageManifest:
manifestDigest := descriptor.Digest
manifestMeta, manifestDownloaded := manifestMetadataMap[descriptor.Digest] manifestMeta, err := dwr.fetchManifestMetaWithCheck(repoMeta.Name, manifestDigest, //nolint:contextcheck
manifestMetadataMap)
if !manifestDownloaded {
manifestMeta, err = dwr.GetManifestMeta(repoMeta.Name, godigest.Digest(descriptor.Digest)) //nolint:contextcheck
if err != nil { if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo, return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
errors.Wrapf(err, "repodb: error while unmarshaling manifest metadata for digest %s", descriptor.Digest) pageInfo,
} errors.Wrapf(err, "")
} }
// get fields related to filtering manifestFilterData, err := collectImageManifestFilterData(manifestDigest, repoMeta, manifestMeta)
var configContent ispec.Image
err = json.Unmarshal(manifestMeta.ConfigBlob, &configContent)
if err != nil { if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo, return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
errors.Wrapf(err, "repodb: error while unmarshaling config content for digest %s", descriptor.Digest) pageInfo,
errors.Wrapf(err, "")
} }
osSet[configContent.OS] = true repoDownloads += manifestFilterData.DownloadCount
archSet[configContent.Architecture] = true
// get fields related to sorting for _, os := range manifestFilterData.OsList {
repoDownloads += repoMeta.Statistics[descriptor.Digest].DownloadCount osSet[os] = true
}
imageLastUpdated := common.GetImageLastUpdatedTimestamp(configContent) for _, arch := range manifestFilterData.ArchList {
archSet[arch] = true
}
if firstImageChecked || repoLastUpdated.Before(imageLastUpdated) { if firstImageChecked || repoLastUpdated.Before(manifestFilterData.LastUpdated) {
repoLastUpdated = imageLastUpdated repoLastUpdated = manifestFilterData.LastUpdated
firstImageChecked = false firstImageChecked = false
isSigned = common.CheckIsSigned(manifestMeta.Signatures) isSigned = manifestFilterData.IsSigned
} }
manifestMetadataMap[descriptor.Digest] = manifestMeta manifestMetadataMap[descriptor.Digest] = manifestMeta
case ispec.MediaTypeImageIndex:
var indexLastUpdated time.Time
indexDigest := descriptor.Digest
indexData, err := dwr.fetchIndexDataWithCheck(indexDigest, indexDataMap) //nolint:contextcheck
if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo,
errors.Wrapf(err, "")
}
// this also updates manifestMetadataMap
imageFilterData, err := dwr.collectImageIndexFilterInfo(indexDigest, repoMeta, indexData, //nolint:contextcheck
manifestMetadataMap)
if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo,
errors.Wrapf(err, "")
}
for _, arch := range imageFilterData.ArchList {
archSet[arch] = true
}
for _, os := range imageFilterData.OsList {
osSet[os] = true
}
repoDownloads += imageFilterData.DownloadCount
if repoLastUpdated.Before(imageFilterData.LastUpdated) {
repoLastUpdated = indexLastUpdated
isSigned = imageFilterData.IsSigned
}
indexDataMap[indexDigest] = indexData
default:
dwr.Log.Error().Msgf("Unsupported type: %s", descriptor.MediaType)
}
} }
repoFilterData := repodb.FilterData{ repoFilterData := repodb.FilterData{
@ -641,22 +738,145 @@ func (dwr DBWrapper) SearchRepos(ctx context.Context, searchText string, filter
foundRepos, pageInfo := pageFinder.Page() foundRepos, pageInfo := pageFinder.Page()
// keep just the manifestMeta we need foundManifestMetadataMap, foundindexDataMap, err := filterFoundData(foundRepos, manifestMetadataMap, indexDataMap)
for _, repoMeta := range foundRepos {
for _, descriptor := range repoMeta.Tags { return foundRepos, foundManifestMetadataMap, foundindexDataMap, pageInfo, err
foundManifestMetadataMap[descriptor.Digest] = manifestMetadataMap[descriptor.Digest] }
func (dwr DBWrapper) fetchManifestMetaWithCheck(repoName string, manifestDigest string,
manifestMetadataMap map[string]repodb.ManifestMetadata,
) (repodb.ManifestMetadata, error) {
var (
manifestMeta repodb.ManifestMetadata
err error
)
manifestMeta, manifestDownloaded := manifestMetadataMap[manifestDigest]
if !manifestDownloaded {
manifestMeta, err = dwr.GetManifestMeta(repoName, godigest.Digest(manifestDigest)) //nolint:contextcheck
if err != nil {
return repodb.ManifestMetadata{}, err
} }
} }
return foundRepos, foundManifestMetadataMap, pageInfo, err return manifestMeta, nil
}
func collectImageManifestFilterData(digest string, repoMeta repodb.RepoMetadata,
manifestMeta repodb.ManifestMetadata,
) (repodb.FilterData, error) {
// get fields related to filtering
var (
configContent ispec.Image
osList []string
archList []string
)
err := json.Unmarshal(manifestMeta.ConfigBlob, &configContent)
if err != nil {
return repodb.FilterData{},
errors.Wrapf(err, "repodb: error while unmarshaling config content")
}
if configContent.OS != "" {
osList = append(osList, configContent.OS)
}
if configContent.Architecture != "" {
archList = append(archList, configContent.Architecture)
}
return repodb.FilterData{
DownloadCount: repoMeta.Statistics[digest].DownloadCount,
OsList: osList,
ArchList: archList,
LastUpdated: common.GetImageLastUpdatedTimestamp(configContent),
IsSigned: common.CheckIsSigned(repoMeta.Signatures[digest]),
}, nil
}
func (dwr DBWrapper) fetchIndexDataWithCheck(indexDigest string, indexDataMap map[string]repodb.IndexData,
) (repodb.IndexData, error) {
var (
indexData repodb.IndexData
err error
)
indexData, indexExists := indexDataMap[indexDigest]
if !indexExists {
indexData, err = dwr.GetIndexData(godigest.Digest(indexDigest)) //nolint:contextcheck
if err != nil {
return repodb.IndexData{},
errors.Wrapf(err, "repodb: error while unmarshaling index data for digest %s", indexDigest)
}
}
return indexData, err
}
func (dwr DBWrapper) collectImageIndexFilterInfo(indexDigest string, repoMeta repodb.RepoMetadata,
indexData repodb.IndexData, manifestMetadataMap map[string]repodb.ManifestMetadata,
) (repodb.FilterData, error) {
var indexContent ispec.Index
err := json.Unmarshal(indexData.IndexBlob, &indexContent)
if err != nil {
return repodb.FilterData{},
errors.Wrapf(err, "repodb: error while unmarshaling index content for digest %s", indexDigest)
}
var (
indexLastUpdated time.Time
firstManifestChecked = false
indexOsList = []string{}
indexArchList = []string{}
)
for _, manifest := range indexContent.Manifests {
manifestDigest := manifest.Digest
manifestMeta, err := dwr.fetchManifestMetaWithCheck(repoMeta.Name, manifestDigest.String(),
manifestMetadataMap)
if err != nil {
return repodb.FilterData{},
errors.Wrapf(err, "")
}
manifestFilterData, err := collectImageManifestFilterData(manifestDigest.String(), repoMeta,
manifestMeta)
if err != nil {
return repodb.FilterData{},
errors.Wrapf(err, "")
}
indexOsList = append(indexOsList, manifestFilterData.OsList...)
indexArchList = append(indexArchList, manifestFilterData.ArchList...)
if !firstManifestChecked || indexLastUpdated.Before(manifestFilterData.LastUpdated) {
indexLastUpdated = manifestFilterData.LastUpdated
firstManifestChecked = true
}
manifestMetadataMap[manifest.Digest.String()] = manifestMeta
}
return repodb.FilterData{
DownloadCount: repoMeta.Statistics[indexDigest].DownloadCount,
LastUpdated: indexLastUpdated,
OsList: indexOsList,
ArchList: indexArchList,
IsSigned: common.CheckIsSigned(repoMeta.Signatures[indexDigest]),
}, nil
} }
func (dwr DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc, func (dwr DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
requestedPage repodb.PageInput, requestedPage repodb.PageInput,
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) { ) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, map[string]repodb.IndexData, repodb.PageInfo, error) {
var ( var (
foundManifestMetadataMap = make(map[string]repodb.ManifestMetadata)
manifestMetadataMap = make(map[string]repodb.ManifestMetadata) manifestMetadataMap = make(map[string]repodb.ManifestMetadata)
indexDataMap = make(map[string]repodb.IndexData)
pageFinder repodb.PageFinder pageFinder repodb.PageFinder
repoMetaAttributeIterator iterator.AttributesIterator repoMetaAttributeIterator iterator.AttributesIterator
pageInfo repodb.PageInfo pageInfo repodb.PageInfo
@ -668,7 +888,8 @@ func (dwr DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
pageFinder, err := repodb.NewBaseImagePageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy) pageFinder, err := repodb.NewBaseImagePageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy)
if err != nil { if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo, err return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo, err
} }
repoMetaAttribute, err := repoMetaAttributeIterator.First(ctx) repoMetaAttribute, err := repoMetaAttributeIterator.First(ctx)
@ -676,14 +897,16 @@ func (dwr DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
for ; repoMetaAttribute != nil; repoMetaAttribute, err = repoMetaAttributeIterator.Next(ctx) { for ; repoMetaAttribute != nil; repoMetaAttribute, err = repoMetaAttributeIterator.Next(ctx) {
if err != nil { if err != nil {
// log // log
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo, err return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo, err
} }
var repoMeta repodb.RepoMetadata var repoMeta repodb.RepoMetadata
err := attributevalue.Unmarshal(repoMetaAttribute, &repoMeta) err := attributevalue.Unmarshal(repoMetaAttribute, &repoMeta)
if err != nil { if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo, err return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo, err
} }
if ok, err := localCtx.RepoIsUserAvailable(ctx, repoMeta.Name); !ok || err != nil { if ok, err := localCtx.RepoIsUserAvailable(ctx, repoMeta.Name); !ok || err != nil {
@ -692,29 +915,20 @@ func (dwr DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
matchedTags := make(map[string]repodb.Descriptor) matchedTags := make(map[string]repodb.Descriptor)
// take all manifestMetas // take all manifestMetas
for tag, descriptor := range repoMeta.Tags { for tag, descriptor := range repoMeta.Tags {
manifestDigest := descriptor.Digest
matchedTags[tag] = descriptor matchedTags[tag] = descriptor
// in case tags reference the same manifest we don't download from DB multiple times switch descriptor.MediaType {
manifestMeta, manifestExists := manifestMetadataMap[manifestDigest] case ispec.MediaTypeImageManifest:
manifestDigest := descriptor.Digest
if !manifestExists { manifestMeta, err := dwr.fetchManifestMetaWithCheck(repoMeta.Name, manifestDigest, //nolint:contextcheck
manifestMeta, err := dwr.GetManifestMeta(repoMeta.Name, godigest.Digest(manifestDigest)) //nolint:contextcheck manifestMetadataMap)
if err != nil { if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo, return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo,
errors.Wrapf(err, "repodb: error while unmashaling manifest metadata for digest %s", manifestDigest) errors.Wrapf(err, "repodb: error while unmashaling manifest metadata for digest %s", manifestDigest)
} }
var configContent ispec.Image
err = json.Unmarshal(manifestMeta.ConfigBlob, &configContent)
if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo,
errors.Wrapf(err, "repodb: error while unmashaling config for manifest with digest %s", manifestDigest)
}
}
if !filter(repoMeta, manifestMeta) { if !filter(repoMeta, manifestMeta) {
delete(matchedTags, tag) delete(matchedTags, tag)
@ -722,6 +936,59 @@ func (dwr DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
} }
manifestMetadataMap[manifestDigest] = manifestMeta manifestMetadataMap[manifestDigest] = manifestMeta
case ispec.MediaTypeImageIndex:
indexDigest := descriptor.Digest
indexData, err := dwr.fetchIndexDataWithCheck(indexDigest, indexDataMap) //nolint:contextcheck
if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo,
errors.Wrapf(err, "repodb: error while getting index data for digest %s", indexDigest)
}
var indexContent ispec.Index
err = json.Unmarshal(indexData.IndexBlob, &indexContent)
if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo,
errors.Wrapf(err, "repodb: error while unmashaling index content for digest %s", indexDigest)
}
manifestHasBeenMatched := false
for _, manifest := range indexContent.Manifests {
manifestDigest := manifest.Digest.String()
manifestMeta, err := dwr.fetchManifestMetaWithCheck(repoMeta.Name, manifestDigest, //nolint:contextcheck
manifestMetadataMap)
if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo,
errors.Wrapf(err, "repodb: error while getting manifest data for digest %s", manifestDigest)
}
manifestMetadataMap[manifestDigest] = manifestMeta
if filter(repoMeta, manifestMeta) {
manifestHasBeenMatched = true
}
}
if !manifestHasBeenMatched {
delete(matchedTags, tag)
for _, manifest := range indexContent.Manifests {
delete(manifestMetadataMap, manifest.Digest.String())
}
continue
}
indexDataMap[indexDigest] = indexData
default:
dwr.Log.Error().Msgf("Unsupported type: %s", descriptor.MediaType)
}
} }
if len(matchedTags) == 0 { if len(matchedTags) == 0 {
@ -737,22 +1004,17 @@ func (dwr DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
foundRepos, pageInfo := pageFinder.Page() foundRepos, pageInfo := pageFinder.Page()
// keep just the manifestMeta we need foundManifestMetadataMap, foundindexDataMap, err := filterFoundData(foundRepos, manifestMetadataMap, indexDataMap)
for _, repoMeta := range foundRepos {
for _, descriptor := range repoMeta.Tags {
foundManifestMetadataMap[descriptor.Digest] = manifestMetadataMap[descriptor.Digest]
}
}
return foundRepos, foundManifestMetadataMap, pageInfo, err return foundRepos, foundManifestMetadataMap, foundindexDataMap, pageInfo, err
} }
func (dwr DBWrapper) SearchTags(ctx context.Context, searchText string, filter repodb.Filter, func (dwr DBWrapper) SearchTags(ctx context.Context, searchText string, filter repodb.Filter,
requestedPage repodb.PageInput, requestedPage repodb.PageInput,
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) { ) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, map[string]repodb.IndexData, repodb.PageInfo, error) {
var ( var (
foundManifestMetadataMap = make(map[string]repodb.ManifestMetadata)
manifestMetadataMap = make(map[string]repodb.ManifestMetadata) manifestMetadataMap = make(map[string]repodb.ManifestMetadata)
indexDataMap = make(map[string]repodb.IndexData)
repoMetaAttributeIterator = iterator.NewBaseDynamoAttributesIterator( repoMetaAttributeIterator = iterator.NewBaseDynamoAttributesIterator(
dwr.Client, dwr.RepoMetaTablename, "RepoMetadata", 0, dwr.Log, dwr.Client, dwr.RepoMetaTablename, "RepoMetadata", 0, dwr.Log,
) )
@ -763,12 +1025,14 @@ func (dwr DBWrapper) SearchTags(ctx context.Context, searchText string, filter r
pageFinder, err := repodb.NewBaseImagePageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy) pageFinder, err := repodb.NewBaseImagePageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy)
if err != nil { if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo, err return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo, err
} }
searchedRepo, searchedTag, err := common.GetRepoTag(searchText) searchedRepo, searchedTag, err := common.GetRepoTag(searchText)
if err != nil { if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo, return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo,
errors.Wrap(err, "repodb: error while parsing search text, invalid format") errors.Wrap(err, "repodb: error while parsing search text, invalid format")
} }
@ -777,14 +1041,16 @@ func (dwr DBWrapper) SearchTags(ctx context.Context, searchText string, filter r
for ; repoMetaAttribute != nil; repoMetaAttribute, err = repoMetaAttributeIterator.Next(ctx) { for ; repoMetaAttribute != nil; repoMetaAttribute, err = repoMetaAttributeIterator.Next(ctx) {
if err != nil { if err != nil {
// log // log
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo, err return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo, err
} }
var repoMeta repodb.RepoMetadata var repoMeta repodb.RepoMetadata
err := attributevalue.Unmarshal(repoMetaAttribute, &repoMeta) err := attributevalue.Unmarshal(repoMetaAttribute, &repoMeta)
if err != nil { if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo, err return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo, err
} }
if ok, err := localCtx.RepoIsUserAvailable(ctx, repoMeta.Name); !ok || err != nil { if ok, err := localCtx.RepoIsUserAvailable(ctx, repoMeta.Name); !ok || err != nil {
@ -801,41 +1067,92 @@ func (dwr DBWrapper) SearchTags(ctx context.Context, searchText string, filter r
matchedTags[tag] = descriptor matchedTags[tag] = descriptor
// in case tags reference the same manifest we don't download from DB multiple times switch descriptor.MediaType {
if manifestMeta, manifestExists := manifestMetadataMap[descriptor.Digest]; manifestExists { case ispec.MediaTypeImageManifest:
manifestMetadataMap[descriptor.Digest] = manifestMeta manifestDigest := descriptor.Digest
continue manifestMeta, err := dwr.fetchManifestMetaWithCheck(repoMeta.Name, manifestDigest, //nolint:contextcheck
} manifestMetadataMap)
manifestMeta, err := dwr.GetManifestMeta(repoMeta.Name, godigest.Digest(descriptor.Digest)) //nolint:contextcheck
if err != nil { if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo, return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo,
errors.Wrapf(err, "repodb: error while unmashaling manifest metadata for digest %s", descriptor.Digest) errors.Wrapf(err, "repodb: error while unmashaling manifest metadata for digest %s", descriptor.Digest)
} }
var configContent ispec.Image imageFilterData, err := collectImageManifestFilterData(manifestDigest, repoMeta, manifestMeta)
err = json.Unmarshal(manifestMeta.ConfigBlob, &configContent)
if err != nil { if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, pageInfo, return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
errors.Wrapf(err, "repodb: error while unmashaling config for manifest with digest %s", descriptor.Digest) pageInfo,
} errors.Wrapf(err, "")
imageFilterData := repodb.FilterData{
OsList: []string{configContent.OS},
ArchList: []string{configContent.Architecture},
IsSigned: false,
} }
if !common.AcceptedByFilter(filter, imageFilterData) { if !common.AcceptedByFilter(filter, imageFilterData) {
delete(matchedTags, tag) delete(matchedTags, tag)
delete(manifestMetadataMap, descriptor.Digest)
continue continue
} }
manifestMetadataMap[descriptor.Digest] = manifestMeta manifestMetadataMap[descriptor.Digest] = manifestMeta
case ispec.MediaTypeImageIndex:
indexDigest := descriptor.Digest
indexData, err := dwr.fetchIndexDataWithCheck(indexDigest, indexDataMap) //nolint:contextcheck
if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo,
errors.Wrapf(err, "")
}
var indexContent ispec.Index
err = json.Unmarshal(indexData.IndexBlob, &indexContent)
if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo,
errors.Wrapf(err, "repodb: error while unmashaling index content for digest %s", indexDigest)
}
manifestHasBeenMatched := false
for _, manifest := range indexContent.Manifests {
manifestDigest := manifest.Digest.String()
manifestMeta, err := dwr.fetchManifestMetaWithCheck(repoMeta.Name, manifestDigest, //nolint:contextcheck
manifestMetadataMap)
if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo,
errors.Wrapf(err, "")
}
manifestFilterData, err := collectImageManifestFilterData(manifestDigest, repoMeta, manifestMeta)
if err != nil {
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
pageInfo,
errors.Wrapf(err, "")
}
manifestMetadataMap[manifestDigest] = manifestMeta
if common.AcceptedByFilter(filter, manifestFilterData) {
manifestHasBeenMatched = true
}
}
if !manifestHasBeenMatched {
delete(matchedTags, tag)
for _, manifest := range indexContent.Manifests {
delete(manifestMetadataMap, manifest.Digest.String())
}
continue
}
indexDataMap[indexDigest] = indexData
default:
dwr.Log.Error().Msgf("Unsupported type: %s", descriptor.MediaType)
}
} }
if len(matchedTags) == 0 { if len(matchedTags) == 0 {
@ -852,14 +1169,49 @@ func (dwr DBWrapper) SearchTags(ctx context.Context, searchText string, filter r
foundRepos, pageInfo := pageFinder.Page() foundRepos, pageInfo := pageFinder.Page()
foundManifestMetadataMap, foundindexDataMap, err := filterFoundData(foundRepos, manifestMetadataMap, indexDataMap)
return foundRepos, foundManifestMetadataMap, foundindexDataMap, pageInfo, err
}
func filterFoundData(foundRepos []repodb.RepoMetadata, manifestMetadataMap map[string]repodb.ManifestMetadata,
indexDataMap map[string]repodb.IndexData,
) (map[string]repodb.ManifestMetadata, map[string]repodb.IndexData, error) {
var (
foundManifestMetadataMap = make(map[string]repodb.ManifestMetadata)
foundindexDataMap = make(map[string]repodb.IndexData)
)
// keep just the manifestMeta we need // keep just the manifestMeta we need
for _, repoMeta := range foundRepos { for _, repoMeta := range foundRepos {
for _, descriptor := range repoMeta.Tags { for _, descriptor := range repoMeta.Tags {
switch descriptor.MediaType {
case ispec.MediaTypeImageManifest:
foundManifestMetadataMap[descriptor.Digest] = manifestMetadataMap[descriptor.Digest] foundManifestMetadataMap[descriptor.Digest] = manifestMetadataMap[descriptor.Digest]
case ispec.MediaTypeImageIndex:
indexData := indexDataMap[descriptor.Digest]
var indexContent ispec.Index
err := json.Unmarshal(indexData.IndexBlob, &indexContent)
if err != nil {
return map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
errors.Wrapf(err, "repodb: error while getting manifest data for digest %s", descriptor.Digest)
}
for _, manifestDescriptor := range indexContent.Manifests {
manifestDigest := manifestDescriptor.Digest.String()
foundManifestMetadataMap[manifestDigest] = manifestMetadataMap[manifestDigest]
}
foundindexDataMap[descriptor.Digest] = indexData
default:
}
} }
} }
return foundRepos, foundManifestMetadataMap, pageInfo, err return foundManifestMetadataMap, foundindexDataMap, nil
} }
func (dwr *DBWrapper) PatchDB() error { func (dwr *DBWrapper) PatchDB() error {
@ -1008,6 +1360,31 @@ func (dwr DBWrapper) createManifestDataTable() error {
return dwr.waitTableToBeCreated(dwr.ManifestDataTablename) return dwr.waitTableToBeCreated(dwr.ManifestDataTablename)
} }
func (dwr DBWrapper) createIndexDataTable() error {
_, err := dwr.Client.CreateTable(context.Background(), &dynamodb.CreateTableInput{
TableName: aws.String(dwr.IndexDataTablename),
AttributeDefinitions: []types.AttributeDefinition{
{
AttributeName: aws.String("IndexDigest"),
AttributeType: types.ScalarAttributeTypeS,
},
},
KeySchema: []types.KeySchemaElement{
{
AttributeName: aws.String("IndexDigest"),
KeyType: types.KeyTypeHash,
},
},
BillingMode: types.BillingModePayPerRequest,
})
if err != nil && strings.Contains(err.Error(), "Table already exists") {
return nil
}
return dwr.waitTableToBeCreated(dwr.IndexDataTablename)
}
func (dwr *DBWrapper) createVersionTable() error { func (dwr *DBWrapper) createVersionTable() error {
_, err := dwr.Client.CreateTable(context.Background(), &dynamodb.CreateTableInput{ _, err := dwr.Client.CreateTable(context.Background(), &dynamodb.CreateTableInput{
TableName: aws.String(dwr.VersionTablename), TableName: aws.String(dwr.VersionTablename),

View file

@ -1,5 +1,6 @@
package params package params
type DBDriverParameters struct { type DBDriverParameters struct {
Endpoint, Region, RepoMetaTablename, ManifestDataTablename, VersionTablename string Endpoint, Region, RepoMetaTablename, ManifestDataTablename, IndexDataTablename,
VersionTablename string
} }

View file

@ -2,6 +2,7 @@ package repodb
import ( import (
"context" "context"
"time"
godigest "github.com/opencontainers/go-digest" godigest "github.com/opencontainers/go-digest"
) )
@ -9,6 +10,7 @@ import (
// MetadataDB. // MetadataDB.
const ( const (
ManifestDataBucket = "ManifestData" ManifestDataBucket = "ManifestData"
IndexDataBucket = "IndexData"
UserMetadataBucket = "UserMeta" UserMetadataBucket = "UserMeta"
RepoMetadataBucket = "RepoMetadata" RepoMetadataBucket = "RepoMetadata"
VersionBucket = "Version" VersionBucket = "Version"
@ -59,6 +61,12 @@ type RepoDB interface { //nolint:interfacebloat
// GetManifestMeta sets ManifestMetadata for a given manifest in the database // GetManifestMeta sets ManifestMetadata for a given manifest in the database
SetManifestMeta(repo string, manifestDigest godigest.Digest, mm ManifestMetadata) error SetManifestMeta(repo string, manifestDigest godigest.Digest, mm ManifestMetadata) error
// SetIndexData sets indexData for a given index in the database
SetIndexData(digest godigest.Digest, indexData IndexData) error
// GetIndexData returns indexData for a given Index from the database
GetIndexData(indexDigest godigest.Digest) (IndexData, error)
// IncrementManifestDownloads adds 1 to the download count of a manifest // IncrementManifestDownloads adds 1 to the download count of a manifest
IncrementImageDownloads(repo string, reference string) error IncrementImageDownloads(repo string, reference string) error
@ -70,15 +78,15 @@ type RepoDB interface { //nolint:interfacebloat
// SearchRepos searches for repos given a search string // SearchRepos searches for repos given a search string
SearchRepos(ctx context.Context, searchText string, filter Filter, requestedPage PageInput) ( SearchRepos(ctx context.Context, searchText string, filter Filter, requestedPage PageInput) (
[]RepoMetadata, map[string]ManifestMetadata, PageInfo, error) []RepoMetadata, map[string]ManifestMetadata, map[string]IndexData, PageInfo, error)
// SearchTags searches for images(repo:tag) given a search string // SearchTags searches for images(repo:tag) given a search string
SearchTags(ctx context.Context, searchText string, filter Filter, requestedPage PageInput) ( SearchTags(ctx context.Context, searchText string, filter Filter, requestedPage PageInput) (
[]RepoMetadata, map[string]ManifestMetadata, PageInfo, error) []RepoMetadata, map[string]ManifestMetadata, map[string]IndexData, PageInfo, error)
// FilterTags filters for images given a filter function // FilterTags filters for images given a filter function
FilterTags(ctx context.Context, filter FilterFunc, FilterTags(ctx context.Context, filter FilterFunc,
requestedPage PageInput) ([]RepoMetadata, map[string]ManifestMetadata, PageInfo, error) requestedPage PageInput) ([]RepoMetadata, map[string]ManifestMetadata, map[string]IndexData, PageInfo, error)
PatchDB() error PatchDB() error
} }
@ -90,6 +98,10 @@ type ManifestMetadata struct {
Signatures ManifestSignatures Signatures ManifestSignatures
} }
type IndexData struct {
IndexBlob []byte
}
type ManifestData struct { type ManifestData struct {
ManifestBlob []byte ManifestBlob []byte
ConfigBlob []byte ConfigBlob []byte
@ -163,6 +175,8 @@ type Filter struct {
} }
type FilterData struct { type FilterData struct {
DownloadCount int
LastUpdated time.Time
OsList []string OsList []string
ArchList []string ArchList []string
IsSigned bool IsSigned bool

File diff suppressed because it is too large Load diff

View file

@ -19,6 +19,7 @@ func TestCreateDynamo(t *testing.T) {
Endpoint: os.Getenv("DYNAMODBMOCK_ENDPOINT"), Endpoint: os.Getenv("DYNAMODBMOCK_ENDPOINT"),
RepoMetaTablename: "RepoMetadataTable", RepoMetaTablename: "RepoMetadataTable",
ManifestDataTablename: "ManifestDataTable", ManifestDataTablename: "ManifestDataTable",
IndexDataTablename: "IndexDataTable",
VersionTablename: "Version", VersionTablename: "Version",
Region: "us-east-2", Region: "us-east-2",
} }

View file

@ -74,12 +74,6 @@ func SyncRepo(repo string, repoDB RepoDB, storeController storage.StoreControlle
for _, manifest := range indexContent.Manifests { for _, manifest := range indexContent.Manifests {
tag, hasTag := manifest.Annotations[ispec.AnnotationRefName] tag, hasTag := manifest.Annotations[ispec.AnnotationRefName]
if !hasTag {
log.Warn().Msgf("sync-repo: image without tag found, will not be synced into RepoDB")
continue
}
manifestMetaIsPresent, err := isManifestMetaPresent(repo, manifest, repoDB) manifestMetaIsPresent, err := isManifestMetaPresent(repo, manifest, repoDB)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("sync-repo: error checking manifestMeta in RepoDB") log.Error().Err(err).Msgf("sync-repo: error checking manifestMeta in RepoDB")
@ -87,7 +81,7 @@ func SyncRepo(repo string, repoDB RepoDB, storeController storage.StoreControlle
return err return err
} }
if manifestMetaIsPresent { if manifestMetaIsPresent && hasTag {
err = repoDB.SetRepoTag(repo, tag, manifest.Digest, manifest.MediaType) err = repoDB.SetRepoTag(repo, tag, manifest.Digest, manifest.MediaType)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("sync-repo: failed to set repo tag for %s:%s", repo, tag) log.Error().Err(err).Msgf("sync-repo: failed to set repo tag for %s:%s", repo, tag)
@ -131,31 +125,16 @@ func SyncRepo(repo string, repoDB RepoDB, storeController storage.StoreControlle
continue continue
} }
manifestData, err := NewManifestData(repo, manifestBlob, storeController) reference := tag
if err != nil {
log.Error().Err(err).Msgf("sync-repo: failed to create manifest data for image %s:%s manifest digest %s ",
repo, tag, manifest.Digest.String())
return err if tag == "" {
reference = manifest.Digest.String()
} }
err = repoDB.SetManifestMeta(repo, manifest.Digest, ManifestMetadata{ err = SetMetadataFromInput(repo, reference, manifest.MediaType, manifest.Digest, manifestBlob,
ManifestBlob: manifestData.ManifestBlob, storeController, repoDB, log)
ConfigBlob: manifestData.ConfigBlob,
DownloadCount: 0,
Signatures: ManifestSignatures{},
})
if err != nil { if err != nil {
log.Error().Err(err).Msgf("sync-repo: failed to set manifest meta for image %s:%s manifest digest %s ", log.Error().Err(err).Msgf("sync-repo: failed to set metadata for %s:%s", repo, tag)
repo, tag, manifest.Digest.String())
return err
}
err = repoDB.SetRepoTag(repo, tag, manifest.Digest, manifest.MediaType)
if err != nil {
log.Error().Err(err).Msgf("sync-repo: failed to repo tag for repo %s and tag %s",
repo, tag)
return err return err
} }
@ -271,3 +250,61 @@ func NewManifestData(repoName string, manifestBlob []byte, storeController stora
return manifestData, nil return manifestData, nil
} }
func NewIndexData(repoName string, indexBlob []byte, storeController storage.StoreController,
) IndexData {
indexData := IndexData{}
indexData.IndexBlob = indexBlob
return indexData
}
// SetMetadataFromInput tries to set manifest metadata and update repo metadata by adding the current tag
// (in case the reference is a tag). The function expects image manifests and indexes (multi arch images).
func SetMetadataFromInput(repo, reference, mediaType string, digest godigest.Digest, descriptorBlob []byte,
storeController storage.StoreController, repoDB RepoDB, log log.Logger,
) error {
switch mediaType {
case ispec.MediaTypeImageManifest:
imageData, err := NewManifestData(repo, descriptorBlob, storeController)
if err != nil {
return err
}
err = repoDB.SetManifestData(digest, imageData)
if err != nil {
log.Error().Err(err).Msg("repodb: error while putting manifest meta")
return err
}
case ispec.MediaTypeImageIndex:
indexData := NewIndexData(repo, descriptorBlob, storeController)
err := repoDB.SetIndexData(digest, indexData)
if err != nil {
log.Error().Err(err).Msg("repodb: error while putting index data")
return err
}
}
if refferenceIsDigest(reference) {
return nil
}
err := repoDB.SetRepoTag(repo, reference, digest, mediaType)
if err != nil {
log.Error().Err(err).Msg("repodb: error while putting repo meta")
return err
}
return nil
}
func refferenceIsDigest(reference string) bool {
_, err := godigest.Parse(reference)
return err == nil
}

View file

@ -301,7 +301,7 @@ func TestSyncRepoDBWithStorage(t *testing.T) {
Config: config, Config: config,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: fmt.Sprintf("tag%d", i), Reference: fmt.Sprintf("tag%d", i),
}, },
repo, repo,
storeController) storeController)
@ -325,7 +325,7 @@ func TestSyncRepoDBWithStorage(t *testing.T) {
Config: config, Config: config,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: signatureTag, Reference: signatureTag,
}, },
repo, repo,
storeController) storeController)
@ -401,7 +401,7 @@ func TestSyncRepoDBWithStorage(t *testing.T) {
Config: config, Config: config,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "tag1", Reference: "tag1",
}, },
repo, repo,
storeController) storeController)
@ -423,7 +423,7 @@ func TestSyncRepoDBWithStorage(t *testing.T) {
Config: config, Config: config,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: signatureTag, Reference: signatureTag,
}, },
repo, repo,
storeController) storeController)
@ -473,7 +473,7 @@ func TestSyncRepoDBDynamoWrapper(t *testing.T) {
Config: config, Config: config,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: fmt.Sprintf("tag%d", i), Reference: fmt.Sprintf("tag%d", i),
}, },
repo, repo,
storeController) storeController)
@ -497,7 +497,7 @@ func TestSyncRepoDBDynamoWrapper(t *testing.T) {
Config: config, Config: config,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: signatureTag, Reference: signatureTag,
}, },
repo, repo,
storeController) storeController)
@ -531,6 +531,7 @@ func TestSyncRepoDBDynamoWrapper(t *testing.T) {
Region: "us-east-2", Region: "us-east-2",
RepoMetaTablename: "RepoMetadataTable", RepoMetaTablename: "RepoMetadataTable",
ManifestDataTablename: "ManifestDataTable", ManifestDataTablename: "ManifestDataTable",
IndexDataTablename: "IndexDataTable",
VersionTablename: "Version", VersionTablename: "Version",
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -583,7 +584,7 @@ func TestSyncRepoDBDynamoWrapper(t *testing.T) {
Config: config, Config: config,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: "tag1", Reference: "tag1",
}, },
repo, repo,
storeController) storeController)
@ -605,7 +606,7 @@ func TestSyncRepoDBDynamoWrapper(t *testing.T) {
Config: config, Config: config,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: signatureTag, Reference: signatureTag,
}, },
repo, repo,
storeController) storeController)
@ -617,6 +618,7 @@ func TestSyncRepoDBDynamoWrapper(t *testing.T) {
Region: "us-east-2", Region: "us-east-2",
RepoMetaTablename: "RepoMetadataTable", RepoMetaTablename: "RepoMetadataTable",
ManifestDataTablename: "ManifestDataTable", ManifestDataTablename: "ManifestDataTable",
IndexDataTablename: "IndexDataTable",
VersionTablename: "Version", VersionTablename: "Version",
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)

View file

@ -51,7 +51,7 @@ func OnUpdateManifest(name, reference, mediaType string, digest godigest.Digest,
metadataSuccessfullySet = false metadataSuccessfullySet = false
} }
} else { } else {
err := SetMetadataFromInput(name, reference, mediaType, digest, body, err := repodb.SetMetadataFromInput(name, reference, mediaType, digest, body,
storeController, repoDB, log) storeController, repoDB, log)
if err != nil { if err != nil {
metadataSuccessfullySet = false metadataSuccessfullySet = false
@ -160,46 +160,3 @@ func OnGetManifest(name, reference string, digest godigest.Digest, body []byte,
return nil return nil
} }
// SetMetadataFromInput receives raw information about the manifest pushed and tries to set manifest metadata
// and update repo metadata by adding the current tag (in case the reference is a tag).
// The function expects image manifest.
func SetMetadataFromInput(repo, reference, mediaType string, digest godigest.Digest, manifestBlob []byte,
storeController storage.StoreController, repoDB repodb.RepoDB, log log.Logger,
) error {
imageMetadata, err := repodb.NewManifestData(repo, manifestBlob, storeController)
if err != nil {
return err
}
err = repoDB.SetManifestMeta(repo, digest, repodb.ManifestMetadata{
ManifestBlob: imageMetadata.ManifestBlob,
ConfigBlob: imageMetadata.ConfigBlob,
DownloadCount: 0,
Signatures: repodb.ManifestSignatures{},
})
if err != nil {
log.Error().Err(err).Msg("repodb: error while putting image meta")
return err
}
if refferenceIsDigest(reference) {
return nil
}
err = repoDB.SetRepoTag(repo, reference, digest, mediaType)
if err != nil {
log.Error().Err(err).Msg("repodb: error while putting repo meta")
return err
}
return nil
}
func refferenceIsDigest(reference string) bool {
_, err := godigest.Parse(reference)
return err == nil
}

View file

@ -14,6 +14,7 @@ import (
zerr "zotregistry.io/zot/errors" zerr "zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/extensions/monitoring" "zotregistry.io/zot/pkg/extensions/monitoring"
"zotregistry.io/zot/pkg/log" "zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/meta/repodb"
bolt_wrapper "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper" bolt_wrapper "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper"
repoDBUpdate "zotregistry.io/zot/pkg/meta/repodb/update" repoDBUpdate "zotregistry.io/zot/pkg/meta/repodb/update"
"zotregistry.io/zot/pkg/storage" "zotregistry.io/zot/pkg/storage"
@ -42,8 +43,12 @@ func TestOnUpdateManifest(t *testing.T) {
config, layers, manifest, err := test.GetRandomImageComponents(100) config, layers, manifest, err := test.GetRandomImageComponents(100)
So(err, ShouldBeNil) So(err, ShouldBeNil)
err = test.WriteImageToFileSystem(test.Image{Config: config, Manifest: manifest, Layers: layers, Tag: "tag1"}, err = test.WriteImageToFileSystem(
"repo", storeController) test.Image{
Config: config, Manifest: manifest, Layers: layers, Reference: "tag1",
},
"repo",
storeController)
So(err, ShouldBeNil) So(err, ShouldBeNil)
manifestBlob, err := json.Marshal(manifest) manifestBlob, err := json.Marshal(manifest)
@ -59,6 +64,26 @@ func TestOnUpdateManifest(t *testing.T) {
So(repoMeta.Tags, ShouldContainKey, "tag1") So(repoMeta.Tags, ShouldContainKey, "tag1")
}) })
Convey("metadataSuccessfullySet is false", t, func() {
rootDir := t.TempDir()
storeController := storage.StoreController{}
log := log.NewLogger("debug", "")
metrics := monitoring.NewMetricsServer(false, log)
storeController.DefaultStore = local.NewImageStore(rootDir, true, 1*time.Second,
true, true, log, metrics, nil, nil,
)
repoDB := mocks.RepoDBMock{
SetManifestDataFn: func(manifestDigest godigest.Digest, mm repodb.ManifestData) error {
return ErrTestError
},
}
err := repoDBUpdate.OnUpdateManifest("repo", "tag1", ispec.MediaTypeImageManifest, "digest",
[]byte("{}"), storeController, repoDB, log)
So(err, ShouldNotBeNil)
})
} }
func TestUpdateErrors(t *testing.T) { func TestUpdateErrors(t *testing.T) {
@ -160,8 +185,8 @@ func TestUpdateErrors(t *testing.T) {
repoDB := mocks.RepoDBMock{} repoDB := mocks.RepoDBMock{}
log := log.NewLogger("debug", "") log := log.NewLogger("debug", "")
err := repoDBUpdate.SetMetadataFromInput("repo", "ref", "digest", "", []byte("BadManifestBlob"), err := repodb.SetMetadataFromInput("repo", "ref", ispec.MediaTypeImageManifest, "digest",
storeController, repoDB, log) []byte("BadManifestBlob"), storeController, repoDB, log)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
// reference is digest // reference is digest
@ -177,7 +202,7 @@ func TestUpdateErrors(t *testing.T) {
return []byte("{}"), nil return []byte("{}"), nil
} }
err = repoDBUpdate.SetMetadataFromInput("repo", string(godigest.FromString("reference")), "", "digest", err = repodb.SetMetadataFromInput("repo", string(godigest.FromString("reference")), "", "digest",
manifestBlob, storeController, repoDB, log) manifestBlob, storeController, repoDB, log)
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })

View file

@ -119,6 +119,7 @@ func TestVersioningDynamoDB(t *testing.T) {
Region: region, Region: region,
RepoMetaTablename: "RepoMetadataTable", RepoMetaTablename: "RepoMetadataTable",
ManifestDataTablename: "ManifestDataTable", ManifestDataTablename: "ManifestDataTable",
IndexDataTablename: "IndexDataTable",
VersionTablename: "Version", VersionTablename: "Version",
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)

View file

@ -45,6 +45,7 @@ import (
"oras.land/oras-go/v2/registry/remote" "oras.land/oras-go/v2/registry/remote"
"oras.land/oras-go/v2/registry/remote/auth" "oras.land/oras-go/v2/registry/remote/auth"
"zotregistry.io/zot/pkg/meta/repodb"
"zotregistry.io/zot/pkg/storage" "zotregistry.io/zot/pkg/storage"
) )
@ -85,13 +86,47 @@ var (
ErrAlreadyExists = errors.New("already exists") ErrAlreadyExists = errors.New("already exists")
ErrKeyNotFound = errors.New("key not found") ErrKeyNotFound = errors.New("key not found")
ErrSignatureVerification = errors.New("signature verification failed") ErrSignatureVerification = errors.New("signature verification failed")
ErrPutIndex = errors.New("can't put index")
) )
type Image struct { type Image struct {
Manifest ispec.Manifest Manifest ispec.Manifest
Config ispec.Image Config ispec.Image
Layers [][]byte Layers [][]byte
Tag string Reference string
}
func (img Image) Digest() (godigest.Digest, error) {
blob, err := json.Marshal(img.Manifest)
if err != nil {
return "", err
}
return godigest.FromBytes(blob), nil
}
type MultiarchImage struct {
Index ispec.Index
Images []Image
Reference string
}
func (mi *MultiarchImage) Digest() (godigest.Digest, error) {
indexBlob, err := json.Marshal(mi.Index)
if err != nil {
return "", err
}
return godigest.FromBytes(indexBlob), nil
}
func (mi *MultiarchImage) IndexData() (repodb.IndexData, error) {
indexBlob, err := json.Marshal(mi.Index)
if err != nil {
return repodb.IndexData{}, err
}
return repodb.IndexData{IndexBlob: indexBlob}, nil
} }
func GetFreePort() string { func GetFreePort() string {
@ -298,7 +333,7 @@ func WriteImageToFileSystem(image Image, repoName string, storeController storag
return err return err
} }
_, err = store.PutImageManifest(repoName, image.Tag, ispec.MediaTypeImageManifest, manifestBlob) _, err = store.PutImageManifest(repoName, image.Reference, ispec.MediaTypeImageManifest, manifestBlob)
if err != nil { if err != nil {
return err return err
} }
@ -306,6 +341,34 @@ func WriteImageToFileSystem(image Image, repoName string, storeController storag
return nil return nil
} }
func WriteMultiArchImageToFileSystem(multiarchImage MultiarchImage, repoName string,
storeController storage.StoreController,
) error {
store := storeController.GetImageStore(repoName)
err := store.InitRepo(repoName)
if err != nil {
return err
}
for _, image := range multiarchImage.Images {
err := WriteImageToFileSystem(image, repoName, storeController)
if err != nil {
return err
}
}
indexBlob, err := json.Marshal(multiarchImage.Index)
if err != nil {
return err
}
_, err = store.PutImageManifest(repoName, multiarchImage.Reference, ispec.MediaTypeImageIndex,
indexBlob)
return err
}
func WaitTillServerReady(url string) { func WaitTillServerReady(url string) {
for { for {
_, err := resty.R().Get(url) _, err := resty.R().Get(url)
@ -417,7 +480,7 @@ func GetOciLayoutDigests(imagePath string) (godigest.Digest, godigest.Digest, go
oci, err := umoci.OpenLayout(imagePath) oci, err := umoci.OpenLayout(imagePath)
if err != nil { if err != nil {
panic(err) panic(fmt.Errorf("error opening layout at '%s' : %w", imagePath, err))
} }
defer oci.Close() defer oci.Close()
@ -560,7 +623,7 @@ func GetRandomImageComponents(layerSize int) (ispec.Image, [][]byte, ispec.Manif
return config, layers, manifest, nil return config, layers, manifest, nil
} }
func GetImageWithConfig(conf ispec.Image) (ispec.Image, [][]byte, ispec.Manifest, error) { func GetImageComponentsWithConfig(conf ispec.Image) (ispec.Image, [][]byte, ispec.Manifest, error) {
configBlob, err := json.Marshal(conf) configBlob, err := json.Marshal(conf)
if err = Error(err); err != nil { if err = Error(err); err != nil {
return ispec.Image{}, [][]byte{}, ispec.Manifest{}, err return ispec.Image{}, [][]byte{}, ispec.Manifest{}, err
@ -603,6 +666,68 @@ func GetImageWithConfig(conf ispec.Image) (ispec.Image, [][]byte, ispec.Manifest
return conf, layers, manifest, nil return conf, layers, manifest, nil
} }
func GetImageWithConfig(conf ispec.Image) (Image, error) {
config, layers, manifest, err := GetImageComponentsWithConfig(conf)
if err != nil {
return Image{}, err
}
blob, err := json.Marshal(manifest)
if err != nil {
return Image{}, err
}
return Image{
Manifest: manifest,
Config: config,
Layers: layers,
Reference: godigest.FromBytes(blob).String(),
}, nil
}
func GetImageWithComponents(config ispec.Image, layers [][]byte) (Image, error) {
configBlob, err := json.Marshal(config)
if err != nil {
return Image{}, err
}
manifestLayers := make([]ispec.Descriptor, 0, len(layers))
for _, layer := range layers {
manifestLayers = append(manifestLayers, ispec.Descriptor{
MediaType: "application/vnd.oci.image.layer.v1.tar",
Digest: godigest.FromBytes(layer),
Size: int64(len(layer)),
})
}
const schemaVersion = 2
manifest := ispec.Manifest{
Versioned: specs.Versioned{
SchemaVersion: schemaVersion,
},
Config: ispec.Descriptor{
MediaType: "application/vnd.oci.image.config.v1+json",
Digest: godigest.FromBytes(configBlob),
Size: int64(len(configBlob)),
},
Layers: manifestLayers,
}
manifestBlob, err := json.Marshal(manifest)
if err != nil {
return Image{}, err
}
return Image{
Manifest: manifest,
Config: config,
Layers: layers,
Reference: godigest.FromBytes(manifestBlob).String(),
}, nil
}
func GetCosignSignatureTagForManifest(manifest ispec.Manifest) (string, error) { func GetCosignSignatureTagForManifest(manifest ispec.Manifest) (string, error) {
manifestBlob, err := json.Marshal(manifest) manifestBlob, err := json.Marshal(manifest)
if err != nil { if err != nil {
@ -692,7 +817,11 @@ func UploadImage(img Image, baseURL, repo string) error {
resp, err = resty.R(). resp, err = resty.R().
SetHeader("Content-type", "application/vnd.oci.image.manifest.v1+json"). SetHeader("Content-type", "application/vnd.oci.image.manifest.v1+json").
SetBody(manifestBlob). SetBody(manifestBlob).
Put(baseURL + "/v2/" + repo + "/manifests/" + img.Tag) Put(baseURL + "/v2/" + repo + "/manifests/" + img.Reference)
if ErrStatusCode(resp.StatusCode()) != http.StatusCreated {
return ErrPutBlob
}
if ErrStatusCode(resp.StatusCode()) != http.StatusCreated { if ErrStatusCode(resp.StatusCode()) != http.StatusCreated {
return ErrPutBlob return ErrPutBlob
@ -759,7 +888,7 @@ func PushTestImage(repoName string, tag string, //nolint:unparam
Manifest: manifest, Manifest: manifest,
Config: config, Config: config,
Layers: layers, Layers: layers,
Tag: tag, Reference: tag,
}, },
baseURL, baseURL,
repoName, repoName,
@ -1332,7 +1461,7 @@ func UploadImageWithBasicAuth(img Image, baseURL, repo, user, password string) e
SetBasicAuth(user, password). SetBasicAuth(user, password).
SetHeader("Content-type", "application/vnd.oci.image.manifest.v1+json"). SetHeader("Content-type", "application/vnd.oci.image.manifest.v1+json").
SetBody(manifestBlob). SetBody(manifestBlob).
Put(baseURL + "/v2/" + repo + "/manifests/" + img.Tag) Put(baseURL + "/v2/" + repo + "/manifests/" + img.Reference)
return err return err
} }
@ -1364,8 +1493,10 @@ func SignImageUsingCosign(repoTag, port string) error {
imageURL := fmt.Sprintf("localhost:%s/%s", port, repoTag) imageURL := fmt.Sprintf("localhost:%s/%s", port, repoTag)
const timeoutPeriod = 5
// sign the image // sign the image
return sign.SignCmd(&options.RootOptions{Verbose: true, Timeout: 1 * time.Minute}, return sign.SignCmd(&options.RootOptions{Verbose: true, Timeout: timeoutPeriod * time.Minute},
options.KeyOpts{KeyRef: path.Join(tdir, "cosign.key"), PassFunc: generate.GetPass}, options.KeyOpts{KeyRef: path.Join(tdir, "cosign.key"), PassFunc: generate.GetPass},
options.RegistryOptions{AllowInsecure: true}, options.RegistryOptions{AllowInsecure: true},
map[string]interface{}{"tag": "1.0"}, map[string]interface{}{"tag": "1.0"},
@ -1408,3 +1539,189 @@ func SignImageUsingNotary(repoTag, port string) error {
return err return err
} }
func GetRandomMultiarchImageComponents() (ispec.Index, []Image, error) {
const layerSize = 100
randomLayer1 := make([]byte, layerSize)
_, err := rand.Read(randomLayer1)
if err != nil {
return ispec.Index{}, []Image{}, err
}
image1, err := GetImageWithComponents(
ispec.Image{
Platform: ispec.Platform{
OS: "linux",
Architecture: "amd64",
},
},
[][]byte{
randomLayer1,
})
if err != nil {
return ispec.Index{}, []Image{}, err
}
image1.Reference = getManifestDigest(image1.Manifest).String()
randomLayer2 := make([]byte, layerSize)
_, err = rand.Read(randomLayer2)
if err != nil {
return ispec.Index{}, []Image{}, err
}
image2, err := GetImageWithComponents(
ispec.Image{
Platform: ispec.Platform{
OS: "linux",
Architecture: "386",
},
},
[][]byte{
randomLayer2,
})
if err != nil {
return ispec.Index{}, []Image{}, err
}
image2.Reference = getManifestDigest(image2.Manifest).String()
randomLayer3 := make([]byte, layerSize)
_, err = rand.Read(randomLayer3)
if err != nil {
return ispec.Index{}, []Image{}, err
}
image3, err := GetImageWithComponents(
ispec.Image{
Platform: ispec.Platform{
OS: "windows",
Architecture: "amd64",
},
},
[][]byte{
randomLayer3,
})
if err != nil {
return ispec.Index{}, []Image{}, err
}
image3.Reference = getManifestDigest(image3.Manifest).String()
index := ispec.Index{
MediaType: ispec.MediaTypeImageIndex,
Manifests: []ispec.Descriptor{
{
MediaType: ispec.MediaTypeImageManifest,
Digest: getManifestDigest(image1.Manifest),
Size: getManifestSize(image1.Manifest),
},
{
MediaType: ispec.MediaTypeImageManifest,
Digest: getManifestDigest(image2.Manifest),
Size: getManifestSize(image2.Manifest),
},
{
MediaType: ispec.MediaTypeImageManifest,
Digest: getManifestDigest(image3.Manifest),
Size: getManifestSize(image3.Manifest),
},
},
}
return index, []Image{image1, image2, image3}, nil
}
func GetRandomMultiarchImage(reference string) (MultiarchImage, error) {
index, images, err := GetRandomMultiarchImageComponents()
if err != nil {
return MultiarchImage{}, err
}
return MultiarchImage{
Index: index, Images: images, Reference: reference,
}, err
}
func GetMultiarchImageForImages(reference string, images []Image) MultiarchImage {
var index ispec.Index
for i, image := range images {
index.Manifests = append(index.Manifests, ispec.Descriptor{
MediaType: ispec.MediaTypeImageManifest,
Digest: getManifestDigest(image.Manifest),
Size: getManifestSize(image.Manifest),
})
// update the reference with the digest of the manifest
images[i].Reference = getManifestDigest(image.Manifest).String()
}
return MultiarchImage{Index: index, Images: images, Reference: reference}
}
func getManifestSize(manifest ispec.Manifest) int64 {
manifestBlob, err := json.Marshal(manifest)
if err != nil {
return 0
}
return int64(len(manifestBlob))
}
func getManifestDigest(manifest ispec.Manifest) godigest.Digest {
manifestBlob, err := json.Marshal(manifest)
if err != nil {
return ""
}
return godigest.FromBytes(manifestBlob)
}
func UploadMultiarchImage(multiImage MultiarchImage, baseURL string, repo string) error {
for _, image := range multiImage.Images {
err := UploadImage(image, baseURL, repo)
if err != nil {
return err
}
}
// put manifest
indexBlob, err := json.Marshal(multiImage.Index)
if err = Error(err); err != nil {
return err
}
resp, err := resty.R().
SetHeader("Content-type", ispec.MediaTypeImageIndex).
SetBody(indexBlob).
Put(baseURL + "/v2/" + repo + "/manifests/" + multiImage.Reference)
if resp.StatusCode() != http.StatusCreated {
return ErrPutIndex
}
return err
}
func GetIndexBlobWithManifests(manifestDigests []godigest.Digest) ([]byte, error) {
manifests := make([]ispec.Descriptor, 0, len(manifestDigests))
for _, manifestDigest := range manifestDigests {
manifests = append(manifests, ispec.Descriptor{
Digest: manifestDigest,
MediaType: ispec.MediaTypeImageManifest,
})
}
indexContent := ispec.Index{
MediaType: ispec.MediaTypeImageIndex,
Manifests: manifests,
}
return json.Marshal(indexContent)
}

View file

@ -1064,7 +1064,7 @@ func TestVerifyWithNotation(t *testing.T) {
Config: cfg, Config: cfg,
Layers: layers, Layers: layers,
Manifest: manifest, Manifest: manifest,
Tag: tag, Reference: tag,
}, baseURL, repoName) }, baseURL, repoName)
So(err, ShouldBeNil) So(err, ShouldBeNil)

View file

@ -9,8 +9,10 @@ import (
type CveInfoMock struct { type CveInfoMock struct {
GetImageListForCVEFn func(repo, cveID string) ([]common.TagInfo, error) GetImageListForCVEFn func(repo, cveID string) ([]common.TagInfo, error)
GetImageListWithCVEFixedFn func(repo, cveID string) ([]common.TagInfo, error) GetImageListWithCVEFixedFn func(repo, cveID string) ([]common.TagInfo, error)
GetCVEListForImageFn func(image string, pageInput cveinfo.PageInput) ([]cvemodel.CVE, cveinfo.PageInfo, error) GetCVEListForImageFn func(repo string, reference string, pageInput cveinfo.PageInput,
GetCVESummaryForImageFn func(image string) (cveinfo.ImageCVESummary, error) ) ([]cvemodel.CVE, cveinfo.PageInfo, error)
GetCVESummaryForImageFn func(repo string, reference string,
) (cveinfo.ImageCVESummary, error)
CompareSeveritiesFn func(severity1, severity2 string) int CompareSeveritiesFn func(severity1, severity2 string) int
UpdateDBFn func() error UpdateDBFn func() error
} }
@ -31,21 +33,22 @@ func (cveInfo CveInfoMock) GetImageListWithCVEFixed(repo, cveID string) ([]commo
return []common.TagInfo{}, nil return []common.TagInfo{}, nil
} }
func (cveInfo CveInfoMock) GetCVEListForImage(image string, pageInput cveinfo.PageInput) ( func (cveInfo CveInfoMock) GetCVEListForImage(repo string, reference string, pageInput cveinfo.PageInput) (
[]cvemodel.CVE, []cvemodel.CVE,
cveinfo.PageInfo, cveinfo.PageInfo,
error, error,
) { ) {
if cveInfo.GetCVEListForImageFn != nil { if cveInfo.GetCVEListForImageFn != nil {
return cveInfo.GetCVEListForImageFn(image, pageInput) return cveInfo.GetCVEListForImageFn(repo, reference, pageInput)
} }
return []cvemodel.CVE{}, cveinfo.PageInfo{}, nil return []cvemodel.CVE{}, cveinfo.PageInfo{}, nil
} }
func (cveInfo CveInfoMock) GetCVESummaryForImage(image string) (cveinfo.ImageCVESummary, error) { func (cveInfo CveInfoMock) GetCVESummaryForImage(repo string, reference string,
) (cveinfo.ImageCVESummary, error) {
if cveInfo.GetCVESummaryForImageFn != nil { if cveInfo.GetCVESummaryForImageFn != nil {
return cveInfo.GetCVESummaryForImageFn(image) return cveInfo.GetCVESummaryForImageFn(repo, reference)
} }
return cveinfo.ImageCVESummary{}, nil return cveinfo.ImageCVESummary{}, nil
@ -68,15 +71,15 @@ func (cveInfo CveInfoMock) UpdateDB() error {
} }
type CveScannerMock struct { type CveScannerMock struct {
IsImageFormatScannableFn func(image string) (bool, error) IsImageFormatScannableFn func(repo string, reference string) (bool, error)
ScanImageFn func(image string) (map[string]cvemodel.CVE, error) ScanImageFn func(image string) (map[string]cvemodel.CVE, error)
CompareSeveritiesFn func(severity1, severity2 string) int CompareSeveritiesFn func(severity1, severity2 string) int
UpdateDBFn func() error UpdateDBFn func() error
} }
func (scanner CveScannerMock) IsImageFormatScannable(image string) (bool, error) { func (scanner CveScannerMock) IsImageFormatScannable(repo string, reference string) (bool, error) {
if scanner.IsImageFormatScannableFn != nil { if scanner.IsImageFormatScannableFn != nil {
return scanner.IsImageFormatScannableFn(image) return scanner.IsImageFormatScannableFn(repo, reference)
} }
return true, nil return true, nil

View file

@ -36,6 +36,10 @@ type RepoDBMock struct {
SetManifestMetaFn func(repo string, manifestDigest godigest.Digest, mm repodb.ManifestMetadata) error SetManifestMetaFn func(repo string, manifestDigest godigest.Digest, mm repodb.ManifestMetadata) error
SetIndexDataFn func(digest godigest.Digest, indexData repodb.IndexData) error
GetIndexDataFn func(indexDigest godigest.Digest) (repodb.IndexData, error)
IncrementImageDownloadsFn func(repo string, reference string) error IncrementImageDownloadsFn func(repo string, reference string) error
AddManifestSignatureFn func(repo string, signedManifestDigest godigest.Digest, sm repodb.SignatureMetadata) error AddManifestSignatureFn func(repo string, signedManifestDigest godigest.Digest, sm repodb.SignatureMetadata) error
@ -43,14 +47,14 @@ type RepoDBMock struct {
DeleteSignatureFn func(repo string, signedManifestDigest godigest.Digest, sm repodb.SignatureMetadata) error DeleteSignatureFn func(repo string, signedManifestDigest godigest.Digest, sm repodb.SignatureMetadata) error
SearchReposFn func(ctx context.Context, searchText string, filter repodb.Filter, requestedPage repodb.PageInput) ( SearchReposFn func(ctx context.Context, searchText string, filter repodb.Filter, requestedPage repodb.PageInput) (
[]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) []repodb.RepoMetadata, map[string]repodb.ManifestMetadata, map[string]repodb.IndexData, repodb.PageInfo, error)
SearchTagsFn func(ctx context.Context, searchText string, filter repodb.Filter, requestedPage repodb.PageInput) ( SearchTagsFn func(ctx context.Context, searchText string, filter repodb.Filter, requestedPage repodb.PageInput) (
[]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) []repodb.RepoMetadata, map[string]repodb.ManifestMetadata, map[string]repodb.IndexData, repodb.PageInfo, error)
FilterTagsFn func(ctx context.Context, filter repodb.FilterFunc, FilterTagsFn func(ctx context.Context, filter repodb.FilterFunc,
requestedPage repodb.PageInput, requestedPage repodb.PageInput,
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) ) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, map[string]repodb.IndexData, repodb.PageInfo, error)
SearchDigestsFn func(ctx context.Context, searchText string, requestedPage repodb.PageInput) ( SearchDigestsFn func(ctx context.Context, searchText string, requestedPage repodb.PageInput) (
[]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error) []repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error)
@ -143,7 +147,7 @@ func (sdm RepoDBMock) GetMultipleRepoMeta(ctx context.Context, filter func(repoM
func (sdm RepoDBMock) GetManifestData(manifestDigest godigest.Digest) (repodb.ManifestData, error) { func (sdm RepoDBMock) GetManifestData(manifestDigest godigest.Digest) (repodb.ManifestData, error) {
if sdm.GetManifestDataFn != nil { if sdm.GetManifestDataFn != nil {
return sdm.GetManifestData(manifestDigest) return sdm.GetManifestDataFn(manifestDigest)
} }
return repodb.ManifestData{}, nil return repodb.ManifestData{}, nil
@ -151,7 +155,7 @@ func (sdm RepoDBMock) GetManifestData(manifestDigest godigest.Digest) (repodb.Ma
func (sdm RepoDBMock) SetManifestData(manifestDigest godigest.Digest, md repodb.ManifestData) error { func (sdm RepoDBMock) SetManifestData(manifestDigest godigest.Digest, md repodb.ManifestData) error {
if sdm.SetManifestDataFn != nil { if sdm.SetManifestDataFn != nil {
return sdm.SetManifestData(manifestDigest, md) return sdm.SetManifestDataFn(manifestDigest, md)
} }
return nil return nil
@ -203,32 +207,35 @@ func (sdm RepoDBMock) DeleteSignature(repo string, signedManifestDigest godigest
func (sdm RepoDBMock) SearchRepos(ctx context.Context, searchText string, filter repodb.Filter, func (sdm RepoDBMock) SearchRepos(ctx context.Context, searchText string, filter repodb.Filter,
requestedPage repodb.PageInput, requestedPage repodb.PageInput,
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) { ) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, map[string]repodb.IndexData, repodb.PageInfo, error) {
if sdm.SearchReposFn != nil { if sdm.SearchReposFn != nil {
return sdm.SearchReposFn(ctx, searchText, filter, requestedPage) return sdm.SearchReposFn(ctx, searchText, filter, requestedPage)
} }
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, repodb.PageInfo{}, nil return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{},
map[string]repodb.IndexData{}, repodb.PageInfo{}, nil
} }
func (sdm RepoDBMock) SearchTags(ctx context.Context, searchText string, filter repodb.Filter, func (sdm RepoDBMock) SearchTags(ctx context.Context, searchText string, filter repodb.Filter,
requestedPage repodb.PageInput, requestedPage repodb.PageInput,
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) { ) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, map[string]repodb.IndexData, repodb.PageInfo, error) {
if sdm.SearchTagsFn != nil { if sdm.SearchTagsFn != nil {
return sdm.SearchTagsFn(ctx, searchText, filter, requestedPage) return sdm.SearchTagsFn(ctx, searchText, filter, requestedPage)
} }
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, repodb.PageInfo{}, nil return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{},
map[string]repodb.IndexData{}, repodb.PageInfo{}, nil
} }
func (sdm RepoDBMock) FilterTags(ctx context.Context, filter repodb.FilterFunc, func (sdm RepoDBMock) FilterTags(ctx context.Context, filter repodb.FilterFunc,
requestedPage repodb.PageInput, requestedPage repodb.PageInput,
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, repodb.PageInfo, error) { ) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, map[string]repodb.IndexData, repodb.PageInfo, error) {
if sdm.FilterTagsFn != nil { if sdm.FilterTagsFn != nil {
return sdm.FilterTagsFn(ctx, filter, requestedPage) return sdm.FilterTagsFn(ctx, filter, requestedPage)
} }
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, repodb.PageInfo{}, nil return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{},
map[string]repodb.IndexData{}, repodb.PageInfo{}, nil
} }
func (sdm RepoDBMock) SearchDigests(ctx context.Context, searchText string, requestedPage repodb.PageInput, func (sdm RepoDBMock) SearchDigests(ctx context.Context, searchText string, requestedPage repodb.PageInput,
@ -268,6 +275,22 @@ func (sdm RepoDBMock) SearchForDescendantImages(ctx context.Context, searchText
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, nil return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, nil
} }
func (sdm RepoDBMock) SetIndexData(digest godigest.Digest, indexData repodb.IndexData) error {
if sdm.SetIndexDataFn != nil {
return sdm.SetIndexDataFn(digest, indexData)
}
return nil
}
func (sdm RepoDBMock) GetIndexData(indexDigest godigest.Digest) (repodb.IndexData, error) {
if sdm.GetIndexDataFn != nil {
return sdm.GetIndexDataFn(indexDigest)
}
return repodb.IndexData{}, nil
}
func (sdm RepoDBMock) PatchDB() error { func (sdm RepoDBMock) PatchDB() error {
if sdm.PatchDBFn != nil { if sdm.PatchDBFn != nil {
return sdm.PatchDBFn() return sdm.PatchDBFn()

View file

@ -74,7 +74,8 @@ function teardown_file() {
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
run podman push 127.0.0.1:8080/annotations:latest --tls-verify=false --format=oci run podman push 127.0.0.1:8080/annotations:latest --tls-verify=false --format=oci
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
run curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ ImageList(repo: \"annotations\") { Results { RepoName Tag Digest ConfigDigest Size Layers {Size Digest } Vendor Licenses }}}"}' http://localhost:8080/v2/_zot/ext/search run curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ ImageList(repo: \"annotations\") { Results { RepoName Tag Manifests {Digest ConfigDigest Size Layers { Size Digest }} Vendor Licenses }}}"}' http://localhost:8080/v2/_zot/ext/search
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
# [ $(echo "${lines[-1]}" | jq '.data.ImageList') ] # [ $(echo "${lines[-1]}" | jq '.data.ImageList') ]
[ $(echo "${lines[-1]}" | jq '.data.ImageList.Results[0].RepoName') = '"annotations"' ] [ $(echo "${lines[-1]}" | jq '.data.ImageList.Results[0].RepoName') = '"annotations"' ]
@ -87,7 +88,7 @@ function teardown_file() {
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
run stacker --oci-dir ${BATS_FILE_TMPDIR}/stackeroci --stacker-dir ${BATS_FILE_TMPDIR}/.stacker --roots-dir ${BATS_FILE_TMPDIR}/roots publish -f ${BATS_FILE_TMPDIR}/stacker.yaml --substitute IMAGE_NAME="ghcr.io/project-zot/golang" --substitute IMAGE_TAG="1.20" --substitute DESCRIPTION="mydesc" --substitute VENDOR="CentOs" --substitute LICENSES="GPLv2" --url docker://127.0.0.1:8080 --tag 1.20 --skip-tls run stacker --oci-dir ${BATS_FILE_TMPDIR}/stackeroci --stacker-dir ${BATS_FILE_TMPDIR}/.stacker --roots-dir ${BATS_FILE_TMPDIR}/roots publish -f ${BATS_FILE_TMPDIR}/stacker.yaml --substitute IMAGE_NAME="ghcr.io/project-zot/golang" --substitute IMAGE_TAG="1.20" --substitute DESCRIPTION="mydesc" --substitute VENDOR="CentOs" --substitute LICENSES="GPLv2" --url docker://127.0.0.1:8080 --tag 1.20 --skip-tls
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
run curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ ImageList(repo: \"ghcr.io/project-zot/golang\") { Results { RepoName Tag Digest ConfigDigest Size Layers {Size Digest } Description Vendor Licenses }}}"}' http://localhost:8080/v2/_zot/ext/search run curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ ImageList(repo: \"ghcr.io/project-zot/golang\") { Results { RepoName Tag Manifests {Digest ConfigDigest Size Layers { Size Digest }} Vendor Licenses Description }}}"}' http://localhost:8080/v2/_zot/ext/search
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.data.ImageList.Results[0].RepoName') = '"ghcr.io/project-zot/golang"' ] [ $(echo "${lines[-1]}" | jq '.data.ImageList.Results[0].RepoName') = '"ghcr.io/project-zot/golang"' ]
[ $(echo "${lines[-1]}" | jq '.data.ImageList.Results[0].Description') = '"mydesc"' ] [ $(echo "${lines[-1]}" | jq '.data.ImageList.Results[0].Description') = '"mydesc"' ]
@ -96,10 +97,10 @@ function teardown_file() {
} }
@test "sign/verify with cosign" { @test "sign/verify with cosign" {
run curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ ImageList(repo: \"annotations\") { Results { RepoName Tag Digest ConfigDigest Size Layers {Size Digest } Vendor Licenses }}}"}' http://localhost:8080/v2/_zot/ext/search run curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ ImageList(repo: \"annotations\") { Results { RepoName Tag Manifests {Digest ConfigDigest Size Layers { Size Digest }} Vendor Licenses }}}"}' http://localhost:8080/v2/_zot/ext/search
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.data.ImageList.Results[0].RepoName') = '"annotations"' ] [ $(echo "${lines[-1]}" | jq '.data.ImageList.Results[0].RepoName') = '"annotations"' ]
local digest=$(echo "${lines[-1]}" | jq -r '.data.ImageList.Results[0].Digest') local digest=$(echo "${lines[-1]}" | jq -r '.data.ImageList.Results[0].Manifests[0].Digest')
run cosign initialize run cosign initialize
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
@ -115,7 +116,7 @@ function teardown_file() {
} }
@test "sign/verify with notation" { @test "sign/verify with notation" {
run curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ ImageList(repo: \"annotations\") { Results { RepoName Tag Digest ConfigDigest Size Layers {Size Digest } }}}"}' http://localhost:8080/v2/_zot/ext/search run curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ ImageList(repo: \"annotations\") { Results { RepoName Tag Manifests {Digest ConfigDigest Size Layers { Size Digest }} Vendor Licenses }}}"}' http://localhost:8080/v2/_zot/ext/search
[ "$status" -eq 0 ] [ "$status" -eq 0 ]
[ $(echo "${lines[-1]}" | jq '.data.ImageList.Results[0].RepoName') = '"annotations"' ] [ $(echo "${lines[-1]}" | jq '.data.ImageList.Results[0].RepoName') = '"annotations"' ]
[ "$status" -eq 0 ] [ "$status" -eq 0 ]

View file

@ -37,6 +37,7 @@ function setup() {
"cacheTablename": "BlobTable", "cacheTablename": "BlobTable",
"repoMetaTablename": "RepoMetadataTable", "repoMetaTablename": "RepoMetadataTable",
"manifestDataTablename": "ManifestDataTable", "manifestDataTablename": "ManifestDataTable",
"indexDataTablename": "IndexDataTable",
"versionTablename": "Version" "versionTablename": "Version"
} }
}, },
@ -66,8 +67,6 @@ function setup() {
EOF EOF
awslocal s3 --region "us-east-2" mb s3://zot-storage awslocal s3 --region "us-east-2" mb s3://zot-storage
awslocal dynamodb --region "us-east-2" create-table --table-name "BlobTable" --attribute-definitions AttributeName=Digest,AttributeType=S --key-schema AttributeName=Digest,KeyType=HASH --provisioned-throughput ReadCapacityUnits=10,WriteCapacityUnits=5 awslocal dynamodb --region "us-east-2" create-table --table-name "BlobTable" --attribute-definitions AttributeName=Digest,AttributeType=S --key-schema AttributeName=Digest,KeyType=HASH --provisioned-throughput ReadCapacityUnits=10,WriteCapacityUnits=5
awslocal dynamodb --region "us-east-2" create-table --table-name "RepoMetadataTable" --attribute-definitions AttributeName=RepoName,AttributeType=S --key-schema AttributeName=RepoName,KeyType=HASH --provisioned-throughput ReadCapacityUnits=10,WriteCapacityUnits=5
awslocal dynamodb --region "us-east-2" create-table --table-name "ManifestDataTable" --attribute-definitions AttributeName=Digest,AttributeType=S --key-schema AttributeName=Digest,KeyType=HASH --provisioned-throughput ReadCapacityUnits=10,WriteCapacityUnits=5
zot_serve_strace ${zot_config_file} zot_serve_strace ${zot_config_file}
wait_zot_reachable "http://127.0.0.1:8080/v2/_catalog" wait_zot_reachable "http://127.0.0.1:8080/v2/_catalog"
} }