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:
parent
a561d0bad5
commit
d62c09e2cc
54 changed files with 8656 additions and 2988 deletions
|
@ -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")
|
||||||
)
|
)
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
652
pkg/cli/client_utils_test.go
Normal file
652
pkg/cli/client_utils_test.go
Normal 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, "")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
@ -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
|
||||||
|
|
|
@ -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: ©ImgTag,
|
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
|
@ -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!
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
@ -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",
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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 ]
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue