0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00

refactor: remove pkg/extensions/search/common and move the code to the appropriate packages (#1358)

Signed-off-by: Nicol Draghici <idraghic@cisco.com>
This commit is contained in:
Nicol 2023-04-18 21:07:47 +03:00 committed by GitHub
parent e63faa8898
commit 0586c6227e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 561 additions and 595 deletions

View file

@ -10,6 +10,7 @@ import (
distext "github.com/opencontainers/distribution-spec/specs-go/v1/extensions"
"zotregistry.io/zot/pkg/api/constants"
"zotregistry.io/zot/pkg/common"
)
type field struct {
@ -24,7 +25,7 @@ type schemaList struct {
} `json:"queryType"` //nolint:tagliatelle // graphQL schema
} `json:"__schema"` //nolint:tagliatelle // graphQL schema
} `json:"data"`
Errors []errorGraphQL `json:"errors"`
Errors []common.ErrorGraphQL `json:"errors"`
}
func containsGQLQuery(queryList []field, query string) bool {

View file

@ -22,6 +22,7 @@ import (
zotErrors "zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/api/constants"
"zotregistry.io/zot/pkg/common"
)
type SearchService interface { //nolint:interfacebloat
@ -843,7 +844,7 @@ func (service searchService) makeGraphQLQuery(ctx context.Context,
return nil
}
func checkResultGraphQLQuery(ctx context.Context, err error, resultErrors []errorGraphQL,
func checkResultGraphQLQuery(ctx context.Context, err error, resultErrors []common.ErrorGraphQL,
) error {
if err != nil {
if isContextDone(ctx) {
@ -899,13 +900,8 @@ func addManifestCallToPool(ctx context.Context, config searchConfig, pool *reque
}
type cveResult struct {
Errors []errorGraphQL `json:"errors"`
Data cveData `json:"data"`
}
type errorGraphQL struct {
Message string `json:"message"`
Path []string `json:"path"`
Errors []common.ErrorGraphQL `json:"errors"`
Data cveData `json:"data"`
}
type tagListResp struct {
@ -996,14 +992,14 @@ func (cve cveResult) stringYAML() (string, error) {
}
type fixedTags struct {
Errors []errorGraphQL `json:"errors"`
Errors []common.ErrorGraphQL `json:"errors"`
Data struct {
PaginatedImagesResult `json:"ImageListWithCVEFixed"` //nolint:tagliatelle // graphQL schema
} `json:"data"`
}
type imagesForCve struct {
Errors []errorGraphQL `json:"errors"`
Errors []common.ErrorGraphQL `json:"errors"`
Data struct {
PaginatedImagesResult `json:"ImageListForCVE"` //nolint:tagliatelle // graphQL schema
} `json:"data"`
@ -1047,35 +1043,35 @@ type BaseImageList struct {
}
type imageListStructGQL struct {
Errors []errorGraphQL `json:"errors"`
Errors []common.ErrorGraphQL `json:"errors"`
Data struct {
PaginatedImagesResult `json:"ImageList"` //nolint:tagliatelle
} `json:"data"`
}
type imageListStructForDigestGQL struct {
Errors []errorGraphQL `json:"errors"`
Errors []common.ErrorGraphQL `json:"errors"`
Data struct {
PaginatedImagesResult `json:"ImageListForDigest"` //nolint:tagliatelle
} `json:"data"`
}
type imageListStructForDerivedImagesGQL struct {
Errors []errorGraphQL `json:"errors"`
Errors []common.ErrorGraphQL `json:"errors"`
Data struct {
PaginatedImagesResult `json:"DerivedImageList"` //nolint:tagliatelle
} `json:"data"`
}
type imageListStructForBaseImagesGQL struct {
Errors []errorGraphQL `json:"errors"`
Errors []common.ErrorGraphQL `json:"errors"`
Data struct {
PaginatedImagesResult `json:"BaseImageList"` //nolint:tagliatelle
} `json:"data"`
}
type imagesForDigest struct {
Errors []errorGraphQL `json:"errors"`
Errors []common.ErrorGraphQL `json:"errors"`
Data struct {
PaginatedImagesResult `json:"ImageListForDigest"` //nolint:tagliatelle // graphQL schema
} `json:"data"`

View file

@ -119,4 +119,9 @@ func TestCommon(t *testing.T) {
resultPtr, baseURL+"/v2/", ispec.MediaTypeImageManifest, log.NewLogger("", ""))
So(err, ShouldNotBeNil)
})
Convey("Test image dir and digest", t, func() {
repo, digest := common.GetImageDirAndDigest("image")
So(repo, ShouldResemble, "image")
So(digest, ShouldResemble, "")
})
}

View file

@ -57,6 +57,11 @@ type Platform struct {
Arch string `json:"arch"`
}
type ErrorGraphQL struct {
Message string `json:"message"`
Path []string `json:"path"`
}
type ImageVulnerabilitySummary struct {
MaxSeverity string `json:"maxSeverity"`
Count int `json:"count"`

70
pkg/common/utils.go Normal file
View file

@ -0,0 +1,70 @@
package common
import (
"strings"
"time"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
)
func GetImageDirAndTag(imageName string) (string, string) {
var imageDir string
var imageTag string
if strings.Contains(imageName, ":") {
imageDir, imageTag, _ = strings.Cut(imageName, ":")
} else {
imageDir = imageName
}
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.
// 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.
func GetImageLastUpdated(imageInfo ispec.Image) time.Time {
timeStamp := imageInfo.Created
if timeStamp != nil && !timeStamp.IsZero() {
return *timeStamp
}
if len(imageInfo.History) > 0 {
timeStamp = imageInfo.History[len(imageInfo.History)-1].Created
}
if timeStamp == nil {
timeStamp = &time.Time{}
}
return *timeStamp
}

View file

@ -1,317 +0,0 @@
package common
import (
"fmt"
"sort"
"strings"
"time"
godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
"zotregistry.io/zot/pkg/storage"
)
const (
// See https://github.com/opencontainers/image-spec/blob/main/annotations.md#back-compatibility-with-label-schema
AnnotationLabels = "org.label-schema.labels"
LabelAnnotationCreated = "org.label-schema.build-date"
LabelAnnotationVendor = "org.label-schema.vendor"
LabelAnnotationDescription = "org.label-schema.description"
LabelAnnotationLicenses = "org.label-schema.license"
LabelAnnotationTitle = "org.label-schema.name"
LabelAnnotationDocumentation = "org.label-schema.usage"
LabelAnnotationSource = "org.label-schema.vcs-url"
)
type Descriptor struct {
Digest godigest.Digest
MediaType string
}
type TagInfo struct {
Name string
Descriptor Descriptor
Timestamp time.Time
}
func GetRootDir(image string, storeController storage.StoreController) string {
var rootDir string
prefixName := GetRoutePrefix(image)
subStore := storeController.SubStore
if subStore != nil {
imgStore, ok := storeController.SubStore[prefixName]
if ok {
rootDir = imgStore.RootDir()
} else {
rootDir = storeController.DefaultStore.RootDir()
}
} else {
rootDir = storeController.DefaultStore.RootDir()
}
return rootDir
}
func GetRepo(image string) string {
if strings.Contains(image, ":") {
splitString := strings.SplitN(image, ":", 2) //nolint:gomnd
if len(splitString) != 2 { //nolint:gomnd
return image
}
return splitString[0]
}
return image
}
func GetImageDirAndTag(imageName string) (string, string) {
var imageDir string
var imageTag string
if strings.Contains(imageName, ":") {
imageDir, imageTag, _ = strings.Cut(imageName, ":")
} else {
imageDir = imageName
}
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.
// 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.
func GetImageLastUpdated(imageInfo ispec.Image) time.Time {
timeStamp := imageInfo.Created
if timeStamp != nil && !timeStamp.IsZero() {
return *timeStamp
}
if len(imageInfo.History) > 0 {
timeStamp = imageInfo.History[len(imageInfo.History)-1].Created
}
if timeStamp == nil {
timeStamp = &time.Time{}
}
return *timeStamp
}
func GetFixedTags(allTags, vulnerableTags []TagInfo) []TagInfo {
sort.Slice(allTags, func(i, j int) bool {
return allTags[i].Timestamp.Before(allTags[j].Timestamp)
})
earliestVulnerable := vulnerableTags[0]
vulnerableTagMap := make(map[string]TagInfo, len(vulnerableTags))
for _, tag := range vulnerableTags {
vulnerableTagMap[tag.Name] = tag
if tag.Timestamp.Before(earliestVulnerable.Timestamp) {
earliestVulnerable = tag
}
}
var fixedTags []TagInfo
// There are some downsides to this logic
// We assume there can't be multiple "branches" of the same
// image built at different times containing different fixes
// There may be older images which have a fix or
// newer images which don't
for _, tag := range allTags {
if tag.Timestamp.Before(earliestVulnerable.Timestamp) {
// The vulnerability did not exist at the time this
// image was built
continue
}
// If the image is old enough for the vulnerability to
// exist, but it was not detected, it means it contains
// the fix
if _, ok := vulnerableTagMap[tag.Name]; !ok {
fixedTags = append(fixedTags, tag)
}
}
return fixedTags
}
func GetLatestTag(allTags []TagInfo) TagInfo {
sort.Slice(allTags, func(i, j int) bool {
return allTags[i].Timestamp.Before(allTags[j].Timestamp)
})
return allTags[len(allTags)-1]
}
func GetRoutePrefix(name string) string {
names := strings.SplitN(name, "/", 2) //nolint:gomnd
if len(names) != 2 { //nolint:gomnd
// it means route is of global storage e.g "centos:latest"
if len(names) == 1 {
return "/"
}
}
return fmt.Sprintf("/%s", names[0])
}
type ImageAnnotations struct {
Description string
Licenses string
Title string
Documentation string
Source string
Labels string
Vendor string
Authors string
}
/*
OCI annotation/label with backwards compatibility
arg can be either lables or annotations
https://github.com/opencontainers/image-spec/blob/main/annotations.md.
*/
func GetAnnotationValue(annotations map[string]string, annotationKey, labelKey string) string {
value, ok := annotations[annotationKey]
if !ok || value == "" {
value, ok = annotations[labelKey]
if !ok {
value = ""
}
}
return value
}
func GetDescription(annotations map[string]string) string {
return GetAnnotationValue(annotations, ispec.AnnotationDescription, LabelAnnotationDescription)
}
func GetLicenses(annotations map[string]string) string {
return GetAnnotationValue(annotations, ispec.AnnotationLicenses, LabelAnnotationLicenses)
}
func GetVendor(annotations map[string]string) string {
return GetAnnotationValue(annotations, ispec.AnnotationVendor, LabelAnnotationVendor)
}
func GetAuthors(annotations map[string]string) string {
authors := annotations[ispec.AnnotationAuthors]
return authors
}
func GetTitle(annotations map[string]string) string {
return GetAnnotationValue(annotations, ispec.AnnotationTitle, LabelAnnotationTitle)
}
func GetDocumentation(annotations map[string]string) string {
return GetAnnotationValue(annotations, ispec.AnnotationDocumentation, LabelAnnotationDocumentation)
}
func GetSource(annotations map[string]string) string {
return GetAnnotationValue(annotations, ispec.AnnotationSource, LabelAnnotationSource)
}
func GetCategories(labels map[string]string) string {
categories := labels[AnnotationLabels]
return categories
}
func GetAnnotations(annotations, labels map[string]string) ImageAnnotations {
description := GetDescription(annotations)
if description == "" {
description = GetDescription(labels)
}
title := GetTitle(annotations)
if title == "" {
title = GetTitle(labels)
}
documentation := GetDocumentation(annotations)
if documentation == "" {
documentation = GetDocumentation(labels)
}
source := GetSource(annotations)
if source == "" {
source = GetSource(labels)
}
licenses := GetLicenses(annotations)
if licenses == "" {
licenses = GetLicenses(labels)
}
categories := GetCategories(annotations)
if categories == "" {
categories = GetCategories(labels)
}
vendor := GetVendor(annotations)
if vendor == "" {
vendor = GetVendor(labels)
}
authors := GetAuthors(annotations)
if authors == "" {
authors = GetAuthors(labels)
}
return ImageAnnotations{
Description: description,
Title: title,
Documentation: documentation,
Source: source,
Licenses: licenses,
Labels: categories,
Vendor: vendor,
Authors: authors,
}
}
func ReferenceIsDigest(reference string) bool {
_, err := godigest.Parse(reference)
return err == nil
}

View file

@ -0,0 +1,135 @@
package convert
import (
ispec "github.com/opencontainers/image-spec/specs-go/v1"
)
const (
// See https://github.com/opencontainers/image-spec/blob/main/annotations.md#back-compatibility-with-label-schema
AnnotationLabels = "org.label-schema.labels"
LabelAnnotationCreated = "org.label-schema.build-date"
LabelAnnotationVendor = "org.label-schema.vendor"
LabelAnnotationDescription = "org.label-schema.description"
LabelAnnotationLicenses = "org.label-schema.license"
LabelAnnotationTitle = "org.label-schema.name"
LabelAnnotationDocumentation = "org.label-schema.usage"
LabelAnnotationSource = "org.label-schema.vcs-url"
)
type ImageAnnotations struct {
Description string
Licenses string
Title string
Documentation string
Source string
Labels string
Vendor string
Authors string
}
/*
OCI annotation/label with backwards compatibility
arg can be either lables or annotations
https://github.com/opencontainers/image-spec/blob/main/annotations.md.
*/
func GetAnnotationValue(annotations map[string]string, annotationKey, labelKey string) string {
value, ok := annotations[annotationKey]
if !ok || value == "" {
value, ok = annotations[labelKey]
if !ok {
value = ""
}
}
return value
}
func GetDescription(annotations map[string]string) string {
return GetAnnotationValue(annotations, ispec.AnnotationDescription, LabelAnnotationDescription)
}
func GetLicenses(annotations map[string]string) string {
return GetAnnotationValue(annotations, ispec.AnnotationLicenses, LabelAnnotationLicenses)
}
func GetVendor(annotations map[string]string) string {
return GetAnnotationValue(annotations, ispec.AnnotationVendor, LabelAnnotationVendor)
}
func GetAuthors(annotations map[string]string) string {
authors := annotations[ispec.AnnotationAuthors]
return authors
}
func GetTitle(annotations map[string]string) string {
return GetAnnotationValue(annotations, ispec.AnnotationTitle, LabelAnnotationTitle)
}
func GetDocumentation(annotations map[string]string) string {
return GetAnnotationValue(annotations, ispec.AnnotationDocumentation, LabelAnnotationDocumentation)
}
func GetSource(annotations map[string]string) string {
return GetAnnotationValue(annotations, ispec.AnnotationSource, LabelAnnotationSource)
}
func GetCategories(labels map[string]string) string {
categories := labels[AnnotationLabels]
return categories
}
func GetAnnotations(annotations, labels map[string]string) ImageAnnotations {
description := GetDescription(annotations)
if description == "" {
description = GetDescription(labels)
}
title := GetTitle(annotations)
if title == "" {
title = GetTitle(labels)
}
documentation := GetDocumentation(annotations)
if documentation == "" {
documentation = GetDocumentation(labels)
}
source := GetSource(annotations)
if source == "" {
source = GetSource(labels)
}
licenses := GetLicenses(annotations)
if licenses == "" {
licenses = GetLicenses(labels)
}
categories := GetCategories(annotations)
if categories == "" {
categories = GetCategories(labels)
}
vendor := GetVendor(annotations)
if vendor == "" {
vendor = GetVendor(labels)
}
authors := GetAuthors(annotations)
if authors == "" {
authors = GetAuthors(labels)
}
return ImageAnnotations{
Description: description,
Title: title,
Documentation: documentation,
Source: source,
Licenses: licenses,
Labels: categories,
Vendor: vendor,
Authors: authors,
}
}

View file

@ -333,3 +333,55 @@ func TestUpdateLastUpdatedTimestam(t *testing.T) {
So(*img.LastUpdated, ShouldResemble, after)
})
}
func TestLabels(t *testing.T) {
Convey("Test labels", t, func() {
// Test various labels
labels := make(map[string]string)
desc := convert.GetDescription(labels)
So(desc, ShouldEqual, "")
license := convert.GetLicenses(labels)
So(license, ShouldEqual, "")
vendor := convert.GetVendor(labels)
So(vendor, ShouldEqual, "")
categories := convert.GetCategories(labels)
So(categories, ShouldEqual, "")
labels[ispec.AnnotationVendor] = "zot"
labels[ispec.AnnotationDescription] = "zot-desc"
labels[ispec.AnnotationLicenses] = "zot-license"
labels[convert.AnnotationLabels] = "zot-labels"
desc = convert.GetDescription(labels)
So(desc, ShouldEqual, "zot-desc")
license = convert.GetLicenses(labels)
So(license, ShouldEqual, "zot-license")
vendor = convert.GetVendor(labels)
So(vendor, ShouldEqual, "zot")
categories = convert.GetCategories(labels)
So(categories, ShouldEqual, "zot-labels")
labels = make(map[string]string)
// Use diff key
labels[convert.LabelAnnotationVendor] = "zot-vendor"
labels[convert.LabelAnnotationDescription] = "zot-label-desc"
labels[ispec.AnnotationLicenses] = "zot-label-license"
desc = convert.GetDescription(labels)
So(desc, ShouldEqual, "zot-label-desc")
license = convert.GetLicenses(labels)
So(license, ShouldEqual, "zot-label-license")
vendor = convert.GetVendor(labels)
So(vendor, ShouldEqual, "zot-vendor")
})
}

View file

@ -14,7 +14,7 @@ import (
"github.com/vektah/gqlparser/v2/gqlerror"
zerr "zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/extensions/search/common"
"zotregistry.io/zot/pkg/common"
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"
@ -240,7 +240,7 @@ func ImageIndex2ImageSummary(ctx context.Context, repo, tag string, indexDigest
indexSize = strconv.FormatInt(totalIndexSize, 10)
annotations := common.GetAnnotations(indexContent.Annotations, map[string]string{})
annotations := GetAnnotations(indexContent.Annotations, map[string]string{})
indexSummary := gql_generated.ImageSummary{
RepoName: &repo,
@ -327,7 +327,7 @@ func ImageManifest2ImageSummary(ctx context.Context, repo, tag string, digest go
manifestContent.Layers)
imageSize := strconv.FormatInt(size, 10)
annotations := common.GetAnnotations(manifestContent.Annotations, configContent.Config.Labels)
annotations := GetAnnotations(manifestContent.Annotations, configContent.Config.Labels)
authors := annotations.Authors
if authors == "" {

View file

@ -3,13 +3,14 @@ package cveinfo
import (
"encoding/json"
"fmt"
"sort"
"strings"
godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
"zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/extensions/search/common"
"zotregistry.io/zot/pkg/common"
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
"zotregistry.io/zot/pkg/extensions/search/cve/trivy"
"zotregistry.io/zot/pkg/log"
@ -18,8 +19,8 @@ import (
)
type CveInfo interface {
GetImageListForCVE(repo, cveID string) ([]common.TagInfo, error)
GetImageListWithCVEFixed(repo, cveID string) ([]common.TagInfo, error)
GetImageListForCVE(repo, cveID string) ([]cvemodel.TagInfo, error)
GetImageListWithCVEFixed(repo, cveID string) ([]cvemodel.TagInfo, error)
GetCVEListForImage(repo, tag string, searchedCVE string, pageinput PageInput) ([]cvemodel.CVE, PageInfo, error)
GetCVESummaryForImage(repo, tag string) (ImageCVESummary, error)
CompareSeverities(severity1, severity2 string) int
@ -56,8 +57,8 @@ func NewCVEInfo(storeController storage.StoreController, repoDB repodb.RepoDB,
}
}
func (cveinfo BaseCveInfo) GetImageListForCVE(repo, cveID string) ([]common.TagInfo, error) {
imgList := make([]common.TagInfo, 0)
func (cveinfo BaseCveInfo) GetImageListForCVE(repo, cveID string) ([]cvemodel.TagInfo, error) {
imgList := make([]cvemodel.TagInfo, 0)
repoMeta, err := cveinfo.RepoDB.GetRepoMeta(repo)
if err != nil {
@ -89,9 +90,9 @@ func (cveinfo BaseCveInfo) GetImageListForCVE(repo, cveID string) ([]common.TagI
}
if _, hasCVE := cveMap[cveID]; hasCVE {
imgList = append(imgList, common.TagInfo{
imgList = append(imgList, cvemodel.TagInfo{
Name: tag,
Descriptor: common.Descriptor{
Descriptor: cvemodel.Descriptor{
Digest: manifestDigest,
MediaType: descriptor.MediaType,
},
@ -105,17 +106,17 @@ func (cveinfo BaseCveInfo) GetImageListForCVE(repo, cveID string) ([]common.TagI
return imgList, nil
}
func (cveinfo BaseCveInfo) GetImageListWithCVEFixed(repo, cveID string) ([]common.TagInfo, error) {
func (cveinfo BaseCveInfo) GetImageListWithCVEFixed(repo, cveID string) ([]cvemodel.TagInfo, error) {
repoMeta, err := cveinfo.RepoDB.GetRepoMeta(repo)
if err != nil {
cveinfo.Log.Error().Err(err).Str("repo", repo).Str("cve-id", cveID).
Msg("unable to get list of tags from repo")
return []common.TagInfo{}, err
return []cvemodel.TagInfo{}, err
}
vulnerableTags := make([]common.TagInfo, 0)
allTags := make([]common.TagInfo, 0)
vulnerableTags := make([]cvemodel.TagInfo, 0)
allTags := make([]cvemodel.TagInfo, 0)
var hasCVE bool
@ -150,10 +151,10 @@ func (cveinfo BaseCveInfo) GetImageListWithCVEFixed(repo, cveID string) ([]commo
continue
}
tagInfo := common.TagInfo{
tagInfo := cvemodel.TagInfo{
Name: tag,
Timestamp: common.GetImageLastUpdated(configContent),
Descriptor: common.Descriptor{Digest: manifestDigest, MediaType: descriptor.MediaType},
Descriptor: cvemodel.Descriptor{Digest: manifestDigest, MediaType: descriptor.MediaType},
}
allTags = append(allTags, tagInfo)
@ -196,16 +197,16 @@ func (cveinfo BaseCveInfo) GetImageListWithCVEFixed(repo, cveID string) ([]commo
default:
cveinfo.Log.Error().Msgf("media type not supported '%s'", descriptor.MediaType)
return []common.TagInfo{},
return []cvemodel.TagInfo{},
fmt.Errorf("media type '%s' is not supported: %w", descriptor.MediaType, errors.ErrNotImplemented)
}
}
var fixedTags []common.TagInfo
var fixedTags []cvemodel.TagInfo
if len(vulnerableTags) != 0 {
cveinfo.Log.Info().Str("repo", repo).Str("cve-id", cveID).Msgf("Vulnerable tags: %v", vulnerableTags)
fixedTags = common.GetFixedTags(allTags, vulnerableTags)
fixedTags = GetFixedTags(allTags, vulnerableTags)
cveinfo.Log.Info().Str("repo", repo).Str("cve-id", cveID).Msgf("Fixed tags: %v", fixedTags)
} else {
cveinfo.Log.Info().Str("repo", repo).Str("cve-id", cveID).
@ -297,10 +298,16 @@ func (cveinfo BaseCveInfo) GetCVESummaryForImage(repo, tag string,
return imageCVESummary, nil
}
func referenceIsDigest(reference string) bool {
_, err := godigest.Parse(reference)
return err == nil
}
func getImageString(repo, reference string) string {
image := repo + ":" + reference
if common.ReferenceIsDigest(reference) {
if referenceIsDigest(reference) {
image = repo + "@" + reference
}
@ -314,3 +321,43 @@ func (cveinfo BaseCveInfo) UpdateDB() error {
func (cveinfo BaseCveInfo) CompareSeverities(severity1, severity2 string) int {
return cveinfo.Scanner.CompareSeverities(severity1, severity2)
}
func GetFixedTags(allTags, vulnerableTags []cvemodel.TagInfo) []cvemodel.TagInfo {
sort.Slice(allTags, func(i, j int) bool {
return allTags[i].Timestamp.Before(allTags[j].Timestamp)
})
earliestVulnerable := vulnerableTags[0]
vulnerableTagMap := make(map[string]cvemodel.TagInfo, len(vulnerableTags))
for _, tag := range vulnerableTags {
vulnerableTagMap[tag.Name] = tag
if tag.Timestamp.Before(earliestVulnerable.Timestamp) {
earliestVulnerable = tag
}
}
var fixedTags []cvemodel.TagInfo
// There are some downsides to this logic
// We assume there can't be multiple "branches" of the same
// image built at different times containing different fixes
// There may be older images which have a fix or
// newer images which don't
for _, tag := range allTags {
if tag.Timestamp.Before(earliestVulnerable.Timestamp) {
// The vulnerability did not exist at the time this
// image was built
continue
}
// If the image is old enough for the vulnerability to
// exist, but it was not detected, it means it contains
// the fix
if _, ok := vulnerableTagMap[tag.Name]; !ok {
fixedTags = append(fixedTags, tag)
}
}
return fixedTags
}

View file

@ -1440,3 +1440,63 @@ func TestCVEStruct(t *testing.T) {
So(err, ShouldBeNil)
})
}
func getTags() ([]cvemodel.TagInfo, []cvemodel.TagInfo) {
tags := make([]cvemodel.TagInfo, 0)
firstTag := cvemodel.TagInfo{
Name: "1.0.0",
Descriptor: cvemodel.Descriptor{
Digest: "sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb",
MediaType: ispec.MediaTypeImageManifest,
},
Timestamp: time.Now(),
}
secondTag := cvemodel.TagInfo{
Name: "1.0.1",
Descriptor: cvemodel.Descriptor{
Digest: "sha256:eca04f027f414362596f2632746d8a179362170b9ac9af772011fedcc3877ebb",
MediaType: ispec.MediaTypeImageManifest,
},
Timestamp: time.Now(),
}
thirdTag := cvemodel.TagInfo{
Name: "1.0.2",
Descriptor: cvemodel.Descriptor{
Digest: "sha256:eca04f027f414362596f2632746d8a170362170b9ac9af772011fedcc3877ebb",
MediaType: ispec.MediaTypeImageManifest,
},
Timestamp: time.Now(),
}
fourthTag := cvemodel.TagInfo{
Name: "1.0.3",
Descriptor: cvemodel.Descriptor{
Digest: "sha256:eca04f027f414362596f2632746d8a171362170b9ac9af772011fedcc3877ebb",
MediaType: ispec.MediaTypeImageManifest,
},
Timestamp: time.Now(),
}
tags = append(tags, firstTag, secondTag, thirdTag, fourthTag)
vulnerableTags := make([]cvemodel.TagInfo, 0)
vulnerableTags = append(vulnerableTags, secondTag)
return tags, vulnerableTags
}
func TestFixedTags(t *testing.T) {
Convey("Test fixed tags", t, func() {
allTags, vulnerableTags := getTags()
fixedTags := cveinfo.GetFixedTags(allTags, vulnerableTags)
So(len(fixedTags), ShouldEqual, 2)
fixedTags = cveinfo.GetFixedTags(allTags, append(vulnerableTags, cvemodel.TagInfo{
Name: "taginfo",
Descriptor: cvemodel.Descriptor{},
Timestamp: time.Date(2000, time.July, 20, 10, 10, 10, 10, time.UTC),
}))
So(len(fixedTags), ShouldEqual, 3)
})
}

View file

@ -1,5 +1,11 @@
package model
import (
"time"
godigest "github.com/opencontainers/go-digest"
)
//nolint:tagliatelle // graphQL schema
type CVE struct {
ID string `json:"Id"`
@ -35,3 +41,14 @@ func SeverityValue(severity string) int {
return sevMap[severity]
}
type Descriptor struct {
Digest godigest.Digest
MediaType string
}
type TagInfo struct {
Name string
Descriptor Descriptor
Timestamp time.Time
}

View file

@ -17,7 +17,6 @@ import (
ispec "github.com/opencontainers/image-spec/specs-go/v1"
zerr "zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/extensions/search/common"
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
"zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/meta/repodb"
@ -122,7 +121,7 @@ func NewScanner(storeController storage.StoreController,
func (scanner Scanner) getTrivyOptions(image string) flag.Options {
// Split image to get route prefix
prefixName := common.GetRoutePrefix(image)
prefixName := storage.GetRoutePrefix(image)
var opts flag.Options

View file

@ -15,8 +15,8 @@ import (
ispec "github.com/opencontainers/image-spec/specs-go/v1"
. "github.com/smartystreets/goconvey/convey"
"zotregistry.io/zot/pkg/common"
"zotregistry.io/zot/pkg/extensions/monitoring"
"zotregistry.io/zot/pkg/extensions/search/common"
"zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/meta/bolt"
"zotregistry.io/zot/pkg/meta/repodb"

View file

@ -30,7 +30,7 @@ type ImgResponseForDigest struct {
//nolint:tagliatelle // graphQL schema
type ImgListForDigest struct {
PaginatedImagesResult `json:"ImageListForDigest"`
PaginatedImagesResultForDigest `json:"ImageListForDigest"`
}
//nolint:tagliatelle // graphQL schema
@ -42,12 +42,7 @@ type ImgInfo struct {
Size string `json:"Size"`
}
type ErrorGQL struct {
Message string `json:"message"`
Path []string `json:"path"`
}
type PaginatedImagesResult struct {
type PaginatedImagesResultForDigest struct {
Results []ImgInfo `json:"results"`
Page repodb.PageInfo `json:"page"`
}

View file

@ -18,9 +18,10 @@ import (
"github.com/vektah/gqlparser/v2/gqlerror"
zerr "zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/extensions/search/common"
"zotregistry.io/zot/pkg/common"
"zotregistry.io/zot/pkg/extensions/search/convert"
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/log"
"zotregistry.io/zot/pkg/meta/repodb"
@ -357,7 +358,7 @@ func getCVEListForImage(
}, nil
}
func FilterByTagInfo(tagsInfo []common.TagInfo) repodb.FilterFunc {
func FilterByTagInfo(tagsInfo []cvemodel.TagInfo) repodb.FilterFunc {
return func(repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata) bool {
manifestDigest := godigest.FromBytes(manifestMeta.ManifestBlob).String()
@ -391,7 +392,7 @@ func getImageListForCVE(
return &gql_generated.PaginatedImagesResult{}, err
}
affectedImages := []common.TagInfo{}
affectedImages := []cvemodel.TagInfo{}
for _, repoMeta := range reposMeta {
repo := repoMeta.Name

View file

@ -14,7 +14,7 @@ import (
ispec "github.com/opencontainers/image-spec/specs-go/v1"
. "github.com/smartystreets/goconvey/convey"
"zotregistry.io/zot/pkg/extensions/search/common"
"zotregistry.io/zot/pkg/common"
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"

View file

@ -9,7 +9,7 @@ import (
"github.com/vektah/gqlparser/v2/gqlerror"
zerr "zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/extensions/search/common"
"zotregistry.io/zot/pkg/common"
"zotregistry.io/zot/pkg/extensions/search/gql_generated"
)

View file

@ -1,7 +1,7 @@
//go:build search
// +build search
package common_test
package search_test
import (
"context"
@ -31,9 +31,9 @@ import (
"zotregistry.io/zot/pkg/api"
"zotregistry.io/zot/pkg/api/config"
"zotregistry.io/zot/pkg/api/constants"
"zotregistry.io/zot/pkg/common"
extconf "zotregistry.io/zot/pkg/extensions/config"
"zotregistry.io/zot/pkg/extensions/monitoring"
"zotregistry.io/zot/pkg/extensions/search/common"
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
"zotregistry.io/zot/pkg/log"
@ -42,6 +42,7 @@ import (
"zotregistry.io/zot/pkg/storage/local"
. "zotregistry.io/zot/pkg/test"
"zotregistry.io/zot/pkg/test/mocks"
ocilayout "zotregistry.io/zot/pkg/test/oci-layout"
)
const (
@ -146,50 +147,6 @@ type ImageSummaryResult struct {
Errors []ErrorGQL `json:"errors"`
}
func getTags() ([]common.TagInfo, []common.TagInfo) {
tags := make([]common.TagInfo, 0)
firstTag := common.TagInfo{
Name: "1.0.0",
Descriptor: common.Descriptor{
Digest: "sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb",
MediaType: ispec.MediaTypeImageManifest,
},
Timestamp: time.Now(),
}
secondTag := common.TagInfo{
Name: "1.0.1",
Descriptor: common.Descriptor{
Digest: "sha256:eca04f027f414362596f2632746d8a179362170b9ac9af772011fedcc3877ebb",
MediaType: ispec.MediaTypeImageManifest,
},
Timestamp: time.Now(),
}
thirdTag := common.TagInfo{
Name: "1.0.2",
Descriptor: common.Descriptor{
Digest: "sha256:eca04f027f414362596f2632746d8a170362170b9ac9af772011fedcc3877ebb",
MediaType: ispec.MediaTypeImageManifest,
},
Timestamp: time.Now(),
}
fourthTag := common.TagInfo{
Name: "1.0.3",
Descriptor: common.Descriptor{
Digest: "sha256:eca04f027f414362596f2632746d8a171362170b9ac9af772011fedcc3877ebb",
MediaType: ispec.MediaTypeImageManifest,
},
Timestamp: time.Now(),
}
tags = append(tags, firstTag, secondTag, thirdTag, fourthTag)
vulnerableTags := make([]common.TagInfo, 0)
vulnerableTags = append(vulnerableTags, secondTag)
return tags, vulnerableTags
}
func readFileAndSearchString(filePath string, stringToMatch string, timeout time.Duration) (bool, error) {
ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
defer cancelFunc()
@ -1796,136 +1753,6 @@ func TestExpandedRepoInfo(t *testing.T) {
})
}
func TestUtilsMethod(t *testing.T) {
Convey("Test utils", t, func() {
// Test GetRepo method
repo := common.GetRepo("test")
So(repo, ShouldEqual, "test")
repo = common.GetRepo(":")
So(repo, ShouldEqual, "")
repo = common.GetRepo("")
So(repo, ShouldEqual, "")
repo = common.GetRepo("test:123")
So(repo, ShouldEqual, "test")
repo = common.GetRepo("a/test:123")
So(repo, ShouldEqual, "a/test")
repo = common.GetRepo("a/test:123:456")
So(repo, ShouldEqual, "a/test")
// Test various labels
labels := make(map[string]string)
desc := common.GetDescription(labels)
So(desc, ShouldEqual, "")
license := common.GetLicenses(labels)
So(license, ShouldEqual, "")
vendor := common.GetVendor(labels)
So(vendor, ShouldEqual, "")
categories := common.GetCategories(labels)
So(categories, ShouldEqual, "")
labels[ispec.AnnotationVendor] = "zot"
labels[ispec.AnnotationDescription] = "zot-desc"
labels[ispec.AnnotationLicenses] = "zot-license"
labels[common.AnnotationLabels] = "zot-labels"
desc = common.GetDescription(labels)
So(desc, ShouldEqual, "zot-desc")
license = common.GetLicenses(labels)
So(license, ShouldEqual, "zot-license")
vendor = common.GetVendor(labels)
So(vendor, ShouldEqual, "zot")
categories = common.GetCategories(labels)
So(categories, ShouldEqual, "zot-labels")
labels = make(map[string]string)
// Use diff key
labels[common.LabelAnnotationVendor] = "zot-vendor"
labels[common.LabelAnnotationDescription] = "zot-label-desc"
labels[ispec.AnnotationLicenses] = "zot-label-license"
desc = common.GetDescription(labels)
So(desc, ShouldEqual, "zot-label-desc")
license = common.GetLicenses(labels)
So(license, ShouldEqual, "zot-label-license")
vendor = common.GetVendor(labels)
So(vendor, ShouldEqual, "zot-vendor")
routePrefix := common.GetRoutePrefix("test:latest")
So(routePrefix, ShouldEqual, "/")
routePrefix = common.GetRoutePrefix("a/test:latest")
So(routePrefix, ShouldEqual, "/a")
routePrefix = common.GetRoutePrefix("a/b/test:latest")
So(routePrefix, ShouldEqual, "/a")
allTags, vulnerableTags := getTags()
latestTag := common.GetLatestTag(allTags)
So(latestTag.Name, ShouldEqual, "1.0.3")
fixedTags := common.GetFixedTags(allTags, vulnerableTags)
So(len(fixedTags), ShouldEqual, 2)
fixedTags = common.GetFixedTags(allTags, append(vulnerableTags, common.TagInfo{
Name: "taginfo",
Descriptor: common.Descriptor{},
Timestamp: time.Date(2000, time.July, 20, 10, 10, 10, 10, time.UTC),
}))
So(len(fixedTags), ShouldEqual, 3)
log := log.NewLogger("debug", "")
rootDir := t.TempDir()
subRootDir := t.TempDir()
conf := config.New()
conf.Extensions = &extconf.ExtensionConfig{}
conf.Extensions.Lint = &extconf.LintConfig{}
metrics := monitoring.NewMetricsServer(false, log)
defaultStore := local.NewImageStore(rootDir, false,
storage.DefaultGCDelay, false, false, log, metrics, nil, nil)
subStore := local.NewImageStore(subRootDir, false,
storage.DefaultGCDelay, false, false, log, metrics, nil, nil)
subStoreMap := make(map[string]storage.ImageStore)
subStoreMap["/b"] = subStore
storeController := storage.StoreController{DefaultStore: defaultStore, SubStore: subStoreMap}
dir := common.GetRootDir("a/zot-cve-test", storeController)
So(dir, ShouldEqual, rootDir)
dir = common.GetRootDir("b/zot-cve-test", storeController)
So(dir, ShouldEqual, subRootDir)
repo, digest := common.GetImageDirAndDigest("image")
So(repo, ShouldResemble, "image")
So(digest, ShouldResemble, "")
})
}
func TestDerivedImageList(t *testing.T) {
rootDir := t.TempDir()
@ -2390,7 +2217,7 @@ func TestGetImageManifest(t *testing.T) {
storeController := storage.StoreController{
DefaultStore: mockImageStore,
}
olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
_, _, err := olu.GetImageManifest("nonexistent-repo", "latest")
So(err, ShouldNotBeNil)
@ -2406,7 +2233,7 @@ func TestGetImageManifest(t *testing.T) {
storeController := storage.StoreController{
DefaultStore: mockImageStore,
}
olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
_, _, err := olu.GetImageManifest("test-repo", "latest") //nolint:goconst
So(err, ShouldNotBeNil)
@ -3068,7 +2895,7 @@ func TestGetRepositories(t *testing.T) {
DefaultStore: mockImageStore,
SubStore: map[string]storage.ImageStore{"test": mockImageStore},
}
olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
repoList, err := olu.GetRepositories()
So(repoList, ShouldBeEmpty)
@ -3078,7 +2905,7 @@ func TestGetRepositories(t *testing.T) {
DefaultStore: mocks.MockedImageStore{},
SubStore: map[string]storage.ImageStore{"test": mockImageStore},
}
olu = NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
olu = ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
repoList, err = olu.GetRepositories()
So(repoList, ShouldBeEmpty)
@ -3388,7 +3215,7 @@ func TestGlobalSearch(t *testing.T) {
)
So(err, ShouldBeNil)
olu := NewBaseOciLayoutUtils(ctlr.StoreController, log.NewLogger("debug", ""))
olu := ocilayout.NewBaseOciLayoutUtils(ctlr.StoreController, log.NewLogger("debug", ""))
// Initialize the objects containing the expected data
repos, err := olu.GetRepositories()
@ -3717,7 +3544,7 @@ func TestGlobalSearch(t *testing.T) {
)
So(err, ShouldBeNil)
olu := NewBaseOciLayoutUtils(ctlr.StoreController, log.NewLogger("debug", ""))
olu := ocilayout.NewBaseOciLayoutUtils(ctlr.StoreController, log.NewLogger("debug", ""))
// Initialize the objects containing the expected data
repos, err := olu.GetRepositories()

View file

@ -16,7 +16,7 @@ type BlobUpload struct {
ID string
}
func getRoutePrefix(name string) string {
func GetRoutePrefix(name string) string {
names := strings.SplitN(name, "/", 2) //nolint:gomnd
if len(names) != 2 { //nolint:gomnd
@ -32,7 +32,7 @@ func getRoutePrefix(name string) string {
func (sc StoreController) GetImageStore(name string) ImageStore {
if sc.SubStore != nil {
// SubStore is being provided, now we need to find equivalent image store and this will be found by splitting name
prefixName := getRoutePrefix(name)
prefixName := GetRoutePrefix(name)
imgStore, ok := sc.SubStore[prefixName]
if !ok {

View file

@ -902,3 +902,16 @@ func TestStorageHandler(t *testing.T) {
})
}
}
func TestRoutePrefix(t *testing.T) {
Convey("Test route prefix", t, func() {
routePrefix := storage.GetRoutePrefix("test:latest")
So(routePrefix, ShouldEqual, "/")
routePrefix = storage.GetRoutePrefix("a/test:latest")
So(routePrefix, ShouldEqual, "/a")
routePrefix = storage.GetRoutePrefix("a/b/test:latest")
So(routePrefix, ShouldEqual, "/a")
})
}

View file

@ -1,14 +1,13 @@
package mocks
import (
"zotregistry.io/zot/pkg/extensions/search/common"
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
)
type CveInfoMock struct {
GetImageListForCVEFn func(repo, cveID string) ([]common.TagInfo, error)
GetImageListWithCVEFixedFn func(repo, cveID string) ([]common.TagInfo, error)
GetImageListForCVEFn func(repo, cveID string) ([]cvemodel.TagInfo, error)
GetImageListWithCVEFixedFn func(repo, cveID string) ([]cvemodel.TagInfo, error)
GetCVEListForImageFn func(repo string, reference string, searchedCVE string, pageInput cveinfo.PageInput,
) ([]cvemodel.CVE, cveinfo.PageInfo, error)
GetCVESummaryForImageFn func(repo string, reference string,
@ -17,20 +16,20 @@ type CveInfoMock struct {
UpdateDBFn func() error
}
func (cveInfo CveInfoMock) GetImageListForCVE(repo, cveID string) ([]common.TagInfo, error) {
func (cveInfo CveInfoMock) GetImageListForCVE(repo, cveID string) ([]cvemodel.TagInfo, error) {
if cveInfo.GetImageListForCVEFn != nil {
return cveInfo.GetImageListForCVEFn(repo, cveID)
}
return []common.TagInfo{}, nil
return []cvemodel.TagInfo{}, nil
}
func (cveInfo CveInfoMock) GetImageListWithCVEFixed(repo, cveID string) ([]common.TagInfo, error) {
func (cveInfo CveInfoMock) GetImageListWithCVEFixed(repo, cveID string) ([]cvemodel.TagInfo, error) {
if cveInfo.GetImageListWithCVEFixedFn != nil {
return cveInfo.GetImageListWithCVEFixedFn(repo, cveID)
}
return []common.TagInfo{}, nil
return []cvemodel.TagInfo{}, nil
}
func (cveInfo CveInfoMock) GetCVEListForImage(repo string, reference string,

View file

@ -4,7 +4,8 @@ import (
godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
"zotregistry.io/zot/pkg/extensions/search/common"
"zotregistry.io/zot/pkg/common"
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
)
type OciLayoutUtilsMock struct {
@ -12,11 +13,11 @@ type OciLayoutUtilsMock struct {
GetImageManifestsFn func(repo string) ([]ispec.Descriptor, error)
GetImageBlobManifestFn func(repo string, digest godigest.Digest) (ispec.Manifest, error)
GetImageInfoFn func(repo string, digest godigest.Digest) (ispec.Image, error)
GetImageTagsWithTimestampFn func(repo string) ([]common.TagInfo, error)
GetImageTagsWithTimestampFn func(repo string) ([]cvemodel.TagInfo, error)
GetImagePlatformFn func(imageInfo ispec.Image) (string, string)
GetImageManifestSizeFn func(repo string, manifestDigest godigest.Digest) int64
GetImageConfigSizeFn func(repo string, manifestDigest godigest.Digest) int64
GetRepoLastUpdatedFn func(repo string) (common.TagInfo, error)
GetRepoLastUpdatedFn func(repo string) (cvemodel.TagInfo, error)
GetExpandedRepoInfoFn func(name string) (common.RepoInfo, error)
GetImageConfigInfoFn func(repo string, manifestDigest godigest.Digest) (ispec.Image, error)
CheckManifestSignatureFn func(name string, digest godigest.Digest) bool
@ -64,12 +65,12 @@ func (olum OciLayoutUtilsMock) GetImageInfo(repo string, digest godigest.Digest)
return ispec.Image{}, nil
}
func (olum OciLayoutUtilsMock) GetImageTagsWithTimestamp(repo string) ([]common.TagInfo, error) {
func (olum OciLayoutUtilsMock) GetImageTagsWithTimestamp(repo string) ([]cvemodel.TagInfo, error) {
if olum.GetImageTagsWithTimestampFn != nil {
return olum.GetImageTagsWithTimestampFn(repo)
}
return []common.TagInfo{}, nil
return []cvemodel.TagInfo{}, nil
}
func (olum OciLayoutUtilsMock) GetImagePlatform(imageInfo ispec.Image) (string, string) {
@ -96,12 +97,12 @@ func (olum OciLayoutUtilsMock) GetImageConfigSize(repo string, manifestDigest go
return 0
}
func (olum OciLayoutUtilsMock) GetRepoLastUpdated(repo string) (common.TagInfo, error) {
func (olum OciLayoutUtilsMock) GetRepoLastUpdated(repo string) (cvemodel.TagInfo, error) {
if olum.GetRepoLastUpdatedFn != nil {
return olum.GetRepoLastUpdatedFn(repo)
}
return common.TagInfo{}, nil
return cvemodel.TagInfo{}, nil
}
func (olum OciLayoutUtilsMock) GetExpandedRepoInfo(name string) (common.RepoInfo, error) {

View file

@ -1,13 +1,14 @@
//go:build sync && scrub && metrics && search
// +build sync,scrub,metrics,search
package test
package ocilayout
import (
"encoding/json"
goerrors "errors"
"fmt"
"path"
"sort"
"strconv"
"strings"
"time"
@ -17,20 +18,22 @@ import (
ispec "github.com/opencontainers/image-spec/specs-go/v1"
zerr "zotregistry.io/zot/errors"
"zotregistry.io/zot/pkg/extensions/search/common"
"zotregistry.io/zot/pkg/common"
"zotregistry.io/zot/pkg/extensions/search/convert"
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
"zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/storage"
)
type OciLayoutUtils interface { //nolint: interfacebloat
type OciUtils interface { //nolint: interfacebloat
GetImageManifest(repo string, reference string) (ispec.Manifest, godigest.Digest, error)
GetImageManifests(repo string) ([]ispec.Descriptor, error)
GetImageBlobManifest(repo string, digest godigest.Digest) (ispec.Manifest, error)
GetImageInfo(repo string, configDigest godigest.Digest) (ispec.Image, error)
GetImageTagsWithTimestamp(repo string) ([]common.TagInfo, error)
GetImageTagsWithTimestamp(repo string) ([]cvemodel.TagInfo, error)
GetImagePlatform(imageInfo ispec.Image) (string, string)
GetImageManifestSize(repo string, manifestDigest godigest.Digest) int64
GetRepoLastUpdated(repo string) (common.TagInfo, error)
GetRepoLastUpdated(repo string) (cvemodel.TagInfo, error)
GetExpandedRepoInfo(name string) (common.RepoInfo, error)
GetImageConfigInfo(repo string, manifestDigest godigest.Digest) (ispec.Image, error)
CheckManifestSignature(name string, digest godigest.Digest) bool
@ -180,8 +183,8 @@ func (olu BaseOciLayoutUtils) GetImageInfo(repo string, configDigest godigest.Di
}
// GetImageTagsWithTimestamp returns a list of image tags with timestamp available in the specified repository.
func (olu BaseOciLayoutUtils) GetImageTagsWithTimestamp(repo string) ([]common.TagInfo, error) {
tagsInfo := make([]common.TagInfo, 0)
func (olu BaseOciLayoutUtils) GetImageTagsWithTimestamp(repo string) ([]cvemodel.TagInfo, error) {
tagsInfo := make([]cvemodel.TagInfo, 0)
manifests, err := olu.GetImageManifests(repo)
if err != nil {
@ -212,10 +215,10 @@ func (olu BaseOciLayoutUtils) GetImageTagsWithTimestamp(repo string) ([]common.T
timeStamp := common.GetImageLastUpdated(imageInfo)
tagsInfo = append(tagsInfo,
common.TagInfo{
cvemodel.TagInfo{
Name: val,
Timestamp: timeStamp,
Descriptor: common.Descriptor{
Descriptor: cvemodel.Descriptor{
Digest: digest,
MediaType: manifest.MediaType,
},
@ -330,13 +333,13 @@ func (olu BaseOciLayoutUtils) GetImageConfigSize(repo string, manifestDigest god
return imageBlobManifest.Config.Size
}
func (olu BaseOciLayoutUtils) GetRepoLastUpdated(repo string) (common.TagInfo, error) {
func (olu BaseOciLayoutUtils) GetRepoLastUpdated(repo string) (cvemodel.TagInfo, error) {
tagsInfo, err := olu.GetImageTagsWithTimestamp(repo)
if err != nil || len(tagsInfo) == 0 {
return common.TagInfo{}, err
return cvemodel.TagInfo{}, err
}
latestTag := common.GetLatestTag(tagsInfo)
latestTag := GetLatestTag(tagsInfo)
return latestTag, nil
}
@ -433,7 +436,7 @@ func (olu BaseOciLayoutUtils) GetExpandedRepoInfo(repoName string) (common.RepoI
imageSize := imageLayersSize + manifestSize + configSize
// get image info from manifest annotation, if not found get from image config labels.
annotations := common.GetAnnotations(manifest.Annotations, imageConfigInfo.Config.Labels)
annotations := convert.GetAnnotations(manifest.Annotations, imageConfigInfo.Config.Labels)
if annotations.Vendor != "" {
repoVendorsSet[annotations.Vendor] = true
@ -579,3 +582,11 @@ func (olu BaseOciLayoutUtils) ExtractImageDetails(
return digest, &manifest, &imageConfig, nil
}
func GetLatestTag(allTags []cvemodel.TagInfo) cvemodel.TagInfo {
sort.Slice(allTags, func(i, j int) bool {
return allTags[i].Timestamp.Before(allTags[j].Timestamp)
})
return allTags[len(allTags)-1]
}

View file

@ -1,7 +1,7 @@
//go:build sync && scrub && metrics && search
// +build sync,scrub,metrics,search
package test_test
package ocilayout_test
import (
"encoding/json"
@ -9,6 +9,7 @@ import (
"os"
"path"
"testing"
"time"
godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
@ -19,13 +20,17 @@ import (
"zotregistry.io/zot/pkg/api/config"
extconf "zotregistry.io/zot/pkg/extensions/config"
"zotregistry.io/zot/pkg/extensions/monitoring"
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
"zotregistry.io/zot/pkg/log"
"zotregistry.io/zot/pkg/storage"
"zotregistry.io/zot/pkg/storage/local"
. "zotregistry.io/zot/pkg/test"
"zotregistry.io/zot/pkg/test/mocks"
ocilayout "zotregistry.io/zot/pkg/test/oci-layout"
)
var ErrTestError = fmt.Errorf("testError")
func TestBaseOciLayoutUtils(t *testing.T) {
manifestDigest := GetTestBlobDigest("zot-test", "config").String()
@ -37,7 +42,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
}
storeController := storage.StoreController{DefaultStore: mockStoreController}
olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
size := olu.GetImageManifestSize("", "")
So(size, ShouldBeZeroValue)
@ -51,7 +56,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
}
storeController := storage.StoreController{DefaultStore: mockStoreController}
olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
size := olu.GetImageConfigSize("", "")
So(size, ShouldBeZeroValue)
@ -86,7 +91,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
}
storeController := storage.StoreController{DefaultStore: mockStoreController}
olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
size := olu.GetImageConfigSize("", "")
So(size, ShouldBeZeroValue)
@ -100,7 +105,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
}
storeController := storage.StoreController{DefaultStore: mockStoreController}
olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
_, err := olu.GetRepoLastUpdated("")
So(err, ShouldNotBeNil)
@ -126,7 +131,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
}
storeController := storage.StoreController{DefaultStore: mockStoreController}
olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
_, err = olu.GetImageTagsWithTimestamp("rep")
So(err, ShouldNotBeNil)
@ -170,7 +175,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
}
storeController := storage.StoreController{DefaultStore: mockStoreController}
olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
_, err = olu.GetImageTagsWithTimestamp("repo")
So(err, ShouldNotBeNil)
@ -213,7 +218,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
}
storeController := storage.StoreController{DefaultStore: mockStoreController}
olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
_, err = olu.GetExpandedRepoInfo("rep")
So(err, ShouldNotBeNil)
@ -226,7 +231,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
}
storeController = storage.StoreController{DefaultStore: mockStoreController}
olu = NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
olu = ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
_, err = olu.GetExpandedRepoInfo("rep")
So(err, ShouldNotBeNil)
@ -243,7 +248,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
}
storeController = storage.StoreController{DefaultStore: mockStoreController}
olu = NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
olu = ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
_, err = olu.GetExpandedRepoInfo("rep")
So(err, ShouldBeNil)
@ -257,7 +262,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
}
storeController := storage.StoreController{DefaultStore: mockStoreController}
olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
_, err := olu.GetImageInfo("", "")
So(err, ShouldNotBeNil)
@ -275,7 +280,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
}
storeController := storage.StoreController{DefaultStore: mockStoreController}
olu := NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
olu := ocilayout.NewBaseOciLayoutUtils(storeController, log.NewLogger("debug", ""))
check := olu.CheckManifestSignature("rep", godigest.FromString(""))
So(check, ShouldBeFalse)
@ -324,7 +329,7 @@ func TestBaseOciLayoutUtils(t *testing.T) {
)
So(err, ShouldBeNil)
olu = NewBaseOciLayoutUtils(ctlr.StoreController, log.NewLogger("debug", ""))
olu = ocilayout.NewBaseOciLayoutUtils(ctlr.StoreController, log.NewLogger("debug", ""))
manifestList, err := olu.GetImageManifests(repo)
So(err, ShouldBeNil)
So(len(manifestList), ShouldEqual, 1)
@ -369,7 +374,7 @@ func TestExtractImageDetails(t *testing.T) {
So(err, ShouldBeNil)
configDigest := godigest.FromBytes(configBlob)
olu := NewBaseOciLayoutUtils(storeController, testLogger)
olu := ocilayout.NewBaseOciLayoutUtils(storeController, testLogger)
resDigest, resManifest, resIspecImage, resErr := olu.ExtractImageDetails("zot-test", "latest", testLogger)
So(string(resDigest), ShouldContainSubstring, "sha256:c52f15d2d4")
So(resManifest.Config.Digest.String(), ShouldContainSubstring, configDigest.Encoded())
@ -388,7 +393,7 @@ func TestExtractImageDetails(t *testing.T) {
DefaultStore: imageStore,
}
olu := NewBaseOciLayoutUtils(storeController, testLogger)
olu := ocilayout.NewBaseOciLayoutUtils(storeController, testLogger)
resDigest, resManifest, resIspecImage, resErr := olu.ExtractImageDetails("zot-test",
"latest", testLogger)
So(resErr, ShouldEqual, zerr.ErrRepoNotFound)
@ -431,7 +436,7 @@ func TestExtractImageDetails(t *testing.T) {
panic(err)
}
olu := NewBaseOciLayoutUtils(storeController, testLogger)
olu := ocilayout.NewBaseOciLayoutUtils(storeController, testLogger)
resDigest, resManifest, resIspecImage, resErr := olu.ExtractImageDetails("zot-test", "latest", testLogger)
So(resErr, ShouldEqual, zerr.ErrBlobNotFound)
So(string(resDigest), ShouldEqual, "")
@ -439,3 +444,47 @@ func TestExtractImageDetails(t *testing.T) {
So(resIspecImage, ShouldBeNil)
})
}
func TestTagsInfo(t *testing.T) {
Convey("Test tags info", t, func() {
allTags := make([]cvemodel.TagInfo, 0)
firstTag := cvemodel.TagInfo{
Name: "1.0.0",
Descriptor: cvemodel.Descriptor{
Digest: "sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb",
MediaType: ispec.MediaTypeImageManifest,
},
Timestamp: time.Now(),
}
secondTag := cvemodel.TagInfo{
Name: "1.0.1",
Descriptor: cvemodel.Descriptor{
Digest: "sha256:eca04f027f414362596f2632746d8a179362170b9ac9af772011fedcc3877ebb",
MediaType: ispec.MediaTypeImageManifest,
},
Timestamp: time.Now(),
}
thirdTag := cvemodel.TagInfo{
Name: "1.0.2",
Descriptor: cvemodel.Descriptor{
Digest: "sha256:eca04f027f414362596f2632746d8a170362170b9ac9af772011fedcc3877ebb",
MediaType: ispec.MediaTypeImageManifest,
},
Timestamp: time.Now(),
}
fourthTag := cvemodel.TagInfo{
Name: "1.0.3",
Descriptor: cvemodel.Descriptor{
Digest: "sha256:eca04f027f414362596f2632746d8a171362170b9ac9af772011fedcc3877ebb",
MediaType: ispec.MediaTypeImageManifest,
},
Timestamp: time.Now(),
}
allTags = append(allTags, firstTag, secondTag, thirdTag, fourthTag)
latestTag := ocilayout.GetLatestTag(allTags)
So(latestTag.Name, ShouldEqual, "1.0.3")
})
}