diff --git a/errors/errors.go b/errors/errors.go index 74b523c8..dc72ea40 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -35,4 +35,5 @@ var ( ErrNoURLProvided = errors.New("cli: no URL provided in argument or via config. see 'zot config -h'") ErrIllegalConfigKey = errors.New("cli: given config key is not allowed") ErrScanNotSupported = errors.New("search: scanning of image media type not supported") + ErrFixedTagNotFound = errors.New("search: no fixed tag found") ) diff --git a/pkg/extensions/search/cve/cve.go b/pkg/extensions/search/cve/cve.go index 95618be7..fba1cae3 100644 --- a/pkg/extensions/search/cve/cve.go +++ b/pkg/extensions/search/cve/cve.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" "path" + "sort" "strings" "github.com/anuvu/zot/errors" @@ -130,3 +131,96 @@ func getImageDir(imageName string) string { return imageDir } + +// GetImageTagsWithTimestamp returns a list of image tags with timestamp available in the specified repository. +func (cveinfo CveInfo) GetImageTagsWithTimestamp(rootDir string, repo string) ([]TagInfo, error) { + dir := path.Join(rootDir, repo) + if !dirExists(dir) { + return nil, errors.ErrRepoNotFound + } + + var digest godigest.Digest + + buf, err := ioutil.ReadFile(path.Join(dir, "index.json")) + if err != nil { + cveinfo.Log.Error().Err(err).Str("dir", dir).Msg("failed to read index.json") + return nil, errors.ErrRepoNotFound + } + + var index ispec.Index + if err := json.Unmarshal(buf, &index); err != nil { + cveinfo.Log.Error().Err(err).Str("dir", dir).Msg("invalid JSON") + return nil, errors.ErrRepoNotFound + } + + tagsInfo := make([]TagInfo, 0) + + var blobIndex ispec.Manifest + + var layerIndex ispec.Image + + for _, manifest := range index.Manifests { + digest = manifest.Digest + v, ok := manifest.Annotations[ispec.AnnotationRefName] + + blobBuf, err := ioutil.ReadFile(path.Join(dir, "blobs", digest.Algorithm().String(), digest.Encoded())) + if err != nil { + cveinfo.Log.Error().Err(err).Msg("Unable to open Image Metadata file") + + return nil, err + } + + if err := json.Unmarshal(blobBuf, &blobIndex); err != nil { + cveinfo.Log.Error().Err(err).Msg("Unable to marshal blob index") + + return nil, err + } + + digest = blobIndex.Config.Digest + + blobBuf, err = ioutil.ReadFile(path.Join(dir, "blobs", digest.Algorithm().String(), digest.Encoded())) + if err != nil { + cveinfo.Log.Error().Err(err).Msg("Unable to open Image Layers file") + + return nil, err + } + + if err := json.Unmarshal(blobBuf, &layerIndex); err != nil { + cveinfo.Log.Error().Err(err).Msg("Unable to marshal blob index") + + return nil, err + } + + timeStamp := *layerIndex.History[0].Created + + if ok { + tagsInfo = append(tagsInfo, TagInfo{Name: v, Timestamp: timeStamp}) + } + } + + return tagsInfo, nil +} + +func GetFixedTags(allTags []TagInfo, infectedTags []TagInfo) []TagInfo { + sort.Slice(allTags, func(i, j int) bool { + return allTags[i].Timestamp.Before(allTags[j].Timestamp) + }) + + latestInfected := TagInfo{} + + for _, tag := range infectedTags { + if !tag.Timestamp.Before(latestInfected.Timestamp) { + latestInfected = tag + } + } + + var fixedTags []TagInfo + + for _, tag := range allTags { + if tag.Timestamp.After(latestInfected.Timestamp) { + fixedTags = append(fixedTags, tag) + } + } + + return fixedTags +} diff --git a/pkg/extensions/search/cve/models.go b/pkg/extensions/search/cve/models.go index ec507e13..a75b6dbe 100644 --- a/pkg/extensions/search/cve/models.go +++ b/pkg/extensions/search/cve/models.go @@ -2,6 +2,8 @@ package cveinfo import ( + "time" + "github.com/anuvu/zot/pkg/log" config "github.com/aquasecurity/trivy/integration/config" ) @@ -11,3 +13,8 @@ type CveInfo struct { Log log.Logger CveTrivyConfig *config.Config } + +type TagInfo struct { + Name string + Timestamp time.Time +} diff --git a/pkg/extensions/search/generated.go b/pkg/extensions/search/generated.go index 3047e98a..fae6ebc8 100644 --- a/pkg/extensions/search/generated.go +++ b/pkg/extensions/search/generated.go @@ -8,6 +8,7 @@ import ( "errors" "strconv" "sync" + "time" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/introspection" @@ -61,6 +62,10 @@ type ComplexityRoot struct { Tags func(childComplexity int) int } + ImgResultForFixedCve struct { + Tags func(childComplexity int) int + } + PackageInfo struct { FixedVersion func(childComplexity int) int InstalledVersion func(childComplexity int) int @@ -68,14 +73,21 @@ type ComplexityRoot struct { } Query struct { - CVEListForImage func(childComplexity int, image string) int - ImageListForCve func(childComplexity int, id string) int + CVEListForImage func(childComplexity int, image string) int + ImageListForCve func(childComplexity int, id string) int + ImageListWithCVEFixed func(childComplexity int, id string, image string) int + } + + TagInfo struct { + Name func(childComplexity int) int + Timestamp func(childComplexity int) int } } type QueryResolver interface { CVEListForImage(ctx context.Context, image string) (*CVEResultForImage, error) ImageListForCve(ctx context.Context, id string) ([]*ImgResultForCve, error) + ImageListWithCVEFixed(ctx context.Context, id string, image string) (*ImgResultForFixedCve, error) } type executableSchema struct { @@ -156,6 +168,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ImgResultForCve.Tags(childComplexity), true + case "ImgResultForFixedCVE.Tags": + if e.complexity.ImgResultForFixedCve.Tags == nil { + break + } + + return e.complexity.ImgResultForFixedCve.Tags(childComplexity), true + case "PackageInfo.FixedVersion": if e.complexity.PackageInfo.FixedVersion == nil { break @@ -201,6 +220,32 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.ImageListForCve(childComplexity, args["id"].(string)), true + case "Query.ImageListWithCVEFixed": + if e.complexity.Query.ImageListWithCVEFixed == nil { + break + } + + args, err := ec.field_Query_ImageListWithCVEFixed_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.ImageListWithCVEFixed(childComplexity, args["id"].(string), args["image"].(string)), true + + case "TagInfo.Name": + if e.complexity.TagInfo.Name == nil { + break + } + + return e.complexity.TagInfo.Name(childComplexity), true + + case "TagInfo.Timestamp": + if e.complexity.TagInfo.Timestamp == nil { + break + } + + return e.complexity.TagInfo.Timestamp(childComplexity), true + } return 0, false } @@ -251,7 +296,9 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er } var sources = []*ast.Source{ - {Name: "schema.graphql", Input: `type CVEResultForImage { + {Name: "schema.graphql", Input: `scalar Time + +type CVEResultForImage { Tag: String CVEList: [CVE] } @@ -275,9 +322,19 @@ type ImgResultForCVE { Tags: [String] } +type ImgResultForFixedCVE { + Tags: [TagInfo] +} + +type TagInfo { + Name: String + Timestamp: Time +} + type Query { CVEListForImage(image: String!) :CVEResultForImage ImageListForCVE(id: String!) :[ImgResultForCVE] + ImageListWithCVEFixed(id: String!, image: String!) :ImgResultForFixedCVE }`, BuiltIn: false}, } var parsedSchema = gqlparser.MustLoadSchema(sources...) @@ -361,6 +418,30 @@ func (ec *executionContext) field_Query_ImageListForCVE_args(ctx context.Context return args, nil } +func (ec *executionContext) field_Query_ImageListWithCVEFixed_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["id"]; ok { + ctx := graphql.WithFieldInputContext(ctx, graphql.NewFieldInputWithField("id")) + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["id"] = arg0 + var arg1 string + if tmp, ok := rawArgs["image"]; ok { + ctx := graphql.WithFieldInputContext(ctx, graphql.NewFieldInputWithField("image")) + arg1, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["image"] = arg1 + return args, nil +} + func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -708,6 +789,34 @@ func (ec *executionContext) _ImgResultForCVE_Tags(ctx context.Context, field gra return ec.marshalOString2ᚕᚖstring(ctx, field.Selections, res) } +func (ec *executionContext) _ImgResultForFixedCVE_Tags(ctx context.Context, field graphql.CollectedField, obj *ImgResultForFixedCve) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "ImgResultForFixedCVE", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Tags, nil + }) + + if resTmp == nil { + return graphql.Null + } + res := resTmp.([]*TagInfo) + fc.Result = res + return ec.marshalOTagInfo2ᚕᚖgithubᚗcomᚋanuvuᚋzotᚋpkgᚋextensionsᚋsearchᚐTagInfo(ctx, field.Selections, res) +} + func (ec *executionContext) _PackageInfo_Name(ctx context.Context, field graphql.CollectedField, obj *PackageInfo) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -862,6 +971,41 @@ func (ec *executionContext) _Query_ImageListForCVE(ctx context.Context, field gr return ec.marshalOImgResultForCVE2ᚕᚖgithubᚗcomᚋanuvuᚋzotᚋpkgᚋextensionsᚋsearchᚐImgResultForCve(ctx, field.Selections, res) } +func (ec *executionContext) _Query_ImageListWithCVEFixed(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_ImageListWithCVEFixed_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp := ec._fieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().ImageListWithCVEFixed(rctx, args["id"].(string), args["image"].(string)) + }) + + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*ImgResultForFixedCve) + fc.Result = res + return ec.marshalOImgResultForFixedCVE2ᚖgithubᚗcomᚋanuvuᚋzotᚋpkgᚋextensionsᚋsearchᚐImgResultForFixedCve(ctx, field.Selections, res) +} + func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -925,6 +1069,62 @@ func (ec *executionContext) _Query___schema(ctx context.Context, field graphql.C return ec.marshalO__Schema2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐSchema(ctx, field.Selections, res) } +func (ec *executionContext) _TagInfo_Name(ctx context.Context, field graphql.CollectedField, obj *TagInfo) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "TagInfo", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Name, nil + }) + + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) _TagInfo_Timestamp(ctx context.Context, field graphql.CollectedField, obj *TagInfo) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "TagInfo", + Field: field, + Args: nil, + IsMethod: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Timestamp, nil + }) + + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*time.Time) + fc.Result = res + return ec.marshalOTime2ᚖtimeᚐTime(ctx, field.Selections, res) +} + func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -1976,6 +2176,30 @@ func (ec *executionContext) _ImgResultForCVE(ctx context.Context, sel ast.Select return out } +var imgResultForFixedCVEImplementors = []string{"ImgResultForFixedCVE"} + +func (ec *executionContext) _ImgResultForFixedCVE(ctx context.Context, sel ast.SelectionSet, obj *ImgResultForFixedCve) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, imgResultForFixedCVEImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("ImgResultForFixedCVE") + case "Tags": + out.Values[i] = ec._ImgResultForFixedCVE_Tags(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var packageInfoImplementors = []string{"PackageInfo"} func (ec *executionContext) _PackageInfo(ctx context.Context, sel ast.SelectionSet, obj *PackageInfo) graphql.Marshaler { @@ -2041,6 +2265,17 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr res = ec._Query_ImageListForCVE(ctx, field) return res }) + case "ImageListWithCVEFixed": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_ImageListWithCVEFixed(ctx, field) + return res + }) case "__type": out.Values[i] = ec._Query___type(ctx, field) case "__schema": @@ -2056,6 +2291,32 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr return out } +var tagInfoImplementors = []string{"TagInfo"} + +func (ec *executionContext) _TagInfo(ctx context.Context, sel ast.SelectionSet, obj *TagInfo) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, tagInfoImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("TagInfo") + case "Name": + out.Values[i] = ec._TagInfo_Name(ctx, field, obj) + case "Timestamp": + out.Values[i] = ec._TagInfo_Timestamp(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var __DirectiveImplementors = []string{"__Directive"} func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionSet, obj *introspection.Directive) graphql.Marshaler { @@ -2685,6 +2946,13 @@ func (ec *executionContext) marshalOImgResultForCVE2ᚖgithubᚗcomᚋanuvuᚋzo return ec._ImgResultForCVE(ctx, sel, v) } +func (ec *executionContext) marshalOImgResultForFixedCVE2ᚖgithubᚗcomᚋanuvuᚋzotᚋpkgᚋextensionsᚋsearchᚐImgResultForFixedCve(ctx context.Context, sel ast.SelectionSet, v *ImgResultForFixedCve) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._ImgResultForFixedCVE(ctx, sel, v) +} + func (ec *executionContext) marshalOPackageInfo2ᚕᚖgithubᚗcomᚋanuvuᚋzotᚋpkgᚋextensionsᚋsearchᚐPackageInfo(ctx context.Context, sel ast.SelectionSet, v []*PackageInfo) graphql.Marshaler { if v == nil { return graphql.Null @@ -2792,6 +3060,68 @@ func (ec *executionContext) marshalOString2ᚖstring(ctx context.Context, sel as return graphql.MarshalString(*v) } +func (ec *executionContext) marshalOTagInfo2ᚕᚖgithubᚗcomᚋanuvuᚋzotᚋpkgᚋextensionsᚋsearchᚐTagInfo(ctx context.Context, sel ast.SelectionSet, v []*TagInfo) graphql.Marshaler { + if v == nil { + return graphql.Null + } + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalOTagInfo2ᚖgithubᚗcomᚋanuvuᚋzotᚋpkgᚋextensionsᚋsearchᚐTagInfo(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + return ret +} + +func (ec *executionContext) marshalOTagInfo2ᚖgithubᚗcomᚋanuvuᚋzotᚋpkgᚋextensionsᚋsearchᚐTagInfo(ctx context.Context, sel ast.SelectionSet, v *TagInfo) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._TagInfo(ctx, sel, v) +} + +func (ec *executionContext) unmarshalOTime2ᚖtimeᚐTime(ctx context.Context, v interface{}) (*time.Time, error) { + if v == nil { + return nil, nil + } + res, err := graphql.UnmarshalTime(v) + return &res, graphql.WrapErrorWithInputPath(ctx, err) +} + +func (ec *executionContext) marshalOTime2ᚖtimeᚐTime(ctx context.Context, sel ast.SelectionSet, v *time.Time) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return graphql.MarshalTime(*v) +} + func (ec *executionContext) marshalO__EnumValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValueᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.EnumValue) graphql.Marshaler { if v == nil { return graphql.Null diff --git a/pkg/extensions/search/models_gen.go b/pkg/extensions/search/models_gen.go index 63810926..4dd079a6 100644 --- a/pkg/extensions/search/models_gen.go +++ b/pkg/extensions/search/models_gen.go @@ -2,6 +2,10 @@ package search +import ( + "time" +) + type Cve struct { ID *string `json:"Id"` Title *string `json:"Title"` @@ -20,8 +24,17 @@ type ImgResultForCve struct { Tags []*string `json:"Tags"` } +type ImgResultForFixedCve struct { + Tags []*TagInfo `json:"Tags"` +} + type PackageInfo struct { Name *string `json:"Name"` InstalledVersion *string `json:"InstalledVersion"` FixedVersion *string `json:"FixedVersion"` } + +type TagInfo struct { + Name *string `json:"Name"` + Timestamp *time.Time `json:"Timestamp"` +} diff --git a/pkg/extensions/search/resolver.go b/pkg/extensions/search/resolver.go index 80d4322f..2a07165d 100644 --- a/pkg/extensions/search/resolver.go +++ b/pkg/extensions/search/resolver.go @@ -218,3 +218,86 @@ func (r *queryResolver) ImageListForCve(ctx context.Context, id string) ([]*ImgR return cveResult, nil } + +func (r *queryResolver) ImageListWithCVEFixed(ctx context.Context, id string, image string) (*ImgResultForFixedCve, error) { // nolint: lll + imgResultForFixedCVE := &ImgResultForFixedCve{} + + r.cveInfo.Log.Info().Str("Extracting list of tags available in image", image).Msg("") + + isValidImage, err := r.cveInfo.IsValidImageFormat(path.Join(r.dir, image)) + if !isValidImage { + r.cveInfo.Log.Debug().Msg("Image media type not supported for scanning") + + return imgResultForFixedCVE, errors.ErrScanNotSupported + } + + if err != nil { + return imgResultForFixedCVE, err + } + + tagsInfo, err := r.cveInfo.GetImageTagsWithTimestamp(r.dir, image) + if err != nil { + r.cveInfo.Log.Error().Err(err).Msg("Error while readling image media type") + + return imgResultForFixedCVE, err + } + + infectedTags := make([]cveinfo.TagInfo, 0) + + var hasCVE bool + + for _, tag := range tagsInfo { + r.cveInfo.CveTrivyConfig.TrivyConfig.Input = path.Join(r.dir, image+":"+tag.Name) + + r.cveInfo.Log.Info().Str("Scanning image", path.Join(r.dir, image+":"+tag.Name)).Msg("") + + results, err := cveinfo.ScanImage(r.cveInfo.CveTrivyConfig) + if err != nil { + r.cveInfo.Log.Error().Err(err).Str("Error scanning image", image+":"+tag.Name).Msg("") + + continue + } + + hasCVE = false + + for _, result := range results { + for _, vulnerability := range result.Vulnerabilities { + if vulnerability.VulnerabilityID == id { + hasCVE = true + + break + } + } + } + + if hasCVE { + infectedTags = append(infectedTags, cveinfo.TagInfo{Name: tag.Name, Timestamp: tag.Timestamp}) + } + } + + if len(infectedTags) != 0 { + r.cveInfo.Log.Info().Msg("Comparing fixed tags timestamp") + + fixedTags := cveinfo.GetFixedTags(tagsInfo, infectedTags) + + finalTagList := make([]*TagInfo, 0) + + for _, tag := range fixedTags { + copyTag := tag.Name + + copyTimeStamp := tag.Timestamp + + finalTagList = append(finalTagList, &TagInfo{Name: ©Tag, Timestamp: ©TimeStamp}) + } + + imgResultForFixedCVE = &ImgResultForFixedCve{Tags: finalTagList} + + return imgResultForFixedCVE, nil + } + + r.cveInfo.Log.Info().Msg("Input image does not contain any tag that does not have given cve") + + imgResultForFixedCVE = &ImgResultForFixedCve{} + + return imgResultForFixedCVE, errors.ErrFixedTagNotFound +} diff --git a/pkg/extensions/search/schema.graphql b/pkg/extensions/search/schema.graphql index b2d03b3e..71e1eccc 100644 --- a/pkg/extensions/search/schema.graphql +++ b/pkg/extensions/search/schema.graphql @@ -1,3 +1,5 @@ +scalar Time + type CVEResultForImage { Tag: String CVEList: [CVE] @@ -22,7 +24,17 @@ type ImgResultForCVE { Tags: [String] } +type ImgResultForFixedCVE { + Tags: [TagInfo] +} + +type TagInfo { + Name: String + Timestamp: Time +} + type Query { CVEListForImage(image: String!) :CVEResultForImage ImageListForCVE(id: String!) :[ImgResultForCVE] + ImageListWithCVEFixed(id: String!, image: String!) :ImgResultForFixedCVE } \ No newline at end of file