Update to graphql 1.17.13

We encountered some problems with using the existing folder structure,
but it looks like running the tooling with the latest versions works after
we regenerated the project using 'gql init' and refactoring to separate
the login previously in resolvers.go.

- the autogenerated code is now under the gql_generated folder
- the file resolvers.go now contains only the code which is not
rewritten by the gqlgen framework
- the file schema.resolvers.go is rewritten when gqlgen runs,
and we'll only keep there the actual resolvers matching query names
Changes we observed to schema.resolvers.go when gqlgen runs include
reordering methods, and renaming function parameters to match the
names used in schema.graphql
- we now have a gqlgen.yaml config file which governs the behavior of
gqlgen (can be tweaked to restructure the folder structure of the
generated code in the future)

Looks like the new graphql server has better validation
1 Returns 422 instead of 200 for missing query string - had to update tests
2 Correctly uncovered an error in a test for a bad `%` in query string.

As as result of 2, a `masked` bug was found in the way we check if images are
signed with Notary, the signatures were reasched for with the media type
of the image manifest itself instead of the media type for notation.
Fixed this bug, and improved error messages.
This bug would have also been reproducible with main branch if the bad `%`
in the test would have fixed.

Updated the linter to ignore some issues with the code which is
always rewritten when running:
`go run github.com/99designs/gqlgen@v0.17.13 generate`

Add a workflow to test gqlgen works and has no uncommitted changes

Signed-off-by: Andrei Aaron <andaaron@cisco.com>
This commit is contained in:
Andrei Aaron
2022-07-15 11:10:51 +00:00
committed by Ramkumar Chinchani
parent 76b811b029
commit 43160dcc43
17 changed files with 3230 additions and 1601 deletions
+21 -373
View File
@@ -1,12 +1,11 @@
package search
//go:generate go run github.com/99designs/gqlgen
// This file will not be regenerated automatically.
//
// It serves as dependency injection for your app, add any dependencies you require here.
import (
"context"
"fmt"
"strconv"
"strings"
godigest "github.com/opencontainers/go-digest"
"zotregistry.io/zot/pkg/log" // nolint: gci
@@ -14,6 +13,7 @@ import (
"zotregistry.io/zot/pkg/extensions/search/common"
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
digestinfo "zotregistry.io/zot/pkg/extensions/search/digest"
"zotregistry.io/zot/pkg/extensions/search/gql_generated"
"zotregistry.io/zot/pkg/storage"
) // THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.
@@ -25,22 +25,15 @@ type Resolver struct {
log log.Logger
}
// Query ...
func (r *Resolver) Query() QueryResolver {
return &queryResolver{r}
}
type queryResolver struct{ *Resolver }
type cveDetail struct {
Title string
Description string
Severity string
PackageList []*PackageInfo
PackageList []*gql_generated.PackageInfo
}
// GetResolverConfig ...
func GetResolverConfig(log log.Logger, storeController storage.StoreController, enableCVE bool) Config {
func GetResolverConfig(log log.Logger, storeController storage.StoreController, enableCVE bool) gql_generated.Config {
var cveInfo *cveinfo.CveInfo
var err error
@@ -56,200 +49,16 @@ func GetResolverConfig(log log.Logger, storeController storage.StoreController,
resConfig := &Resolver{cveInfo: cveInfo, storeController: storeController, digestInfo: digestInfo, log: log}
return Config{
Resolvers: resConfig, Directives: DirectiveRoot{},
Complexity: ComplexityRoot{},
return gql_generated.Config{
Resolvers: resConfig, Directives: gql_generated.DirectiveRoot{},
Complexity: gql_generated.ComplexityRoot{},
}
}
func (r *queryResolver) ExpandedRepoInfo(ctx context.Context, name string) (*RepoInfo, error) {
olu := common.NewOciLayoutUtils(r.storeController, r.log)
repo, err := olu.GetExpandedRepoInfo(name)
if err != nil {
r.log.Error().Err(err).Msg("error getting repos")
return &RepoInfo{}, err
}
// repos type is of common deep copy this to search
repoInfo := &RepoInfo{}
manifests := make([]*ManifestInfo, 0)
for _, manifest := range repo.Manifests {
tag := manifest.Tag
digest := manifest.Digest
isSigned := manifest.IsSigned
manifestInfo := &ManifestInfo{Tag: &tag, Digest: &digest, IsSigned: &isSigned}
layers := make([]*LayerInfo, 0)
for _, l := range manifest.Layers {
size := l.Size
digest := l.Digest
layerInfo := &LayerInfo{Digest: &digest, Size: &size}
layers = append(layers, layerInfo)
}
manifestInfo.Layers = layers
manifests = append(manifests, manifestInfo)
}
repoInfo.Manifests = manifests
return repoInfo, nil
}
func (r *queryResolver) CVEListForImage(ctx context.Context, image string) (*CVEResultForImage, error) {
trivyCtx := r.cveInfo.GetTrivyContext(image)
r.log.Info().Str("image", image).Msg("scanning image")
isValidImage, err := r.cveInfo.LayoutUtils.IsValidImageFormat(image)
if !isValidImage {
r.log.Debug().Str("image", image).Msg("image media type not supported for scanning")
return &CVEResultForImage{}, err
}
report, err := cveinfo.ScanImage(trivyCtx.Ctx)
if err != nil {
r.log.Error().Err(err).Msg("unable to scan image repository")
return &CVEResultForImage{}, err
}
var copyImgTag string
if strings.Contains(image, ":") {
copyImgTag = strings.Split(image, ":")[1]
}
cveidMap := make(map[string]cveDetail)
for _, result := range report.Results {
for _, vulnerability := range result.Vulnerabilities {
pkgName := vulnerability.PkgName
installedVersion := vulnerability.InstalledVersion
var fixedVersion string
if vulnerability.FixedVersion != "" {
fixedVersion = vulnerability.FixedVersion
} else {
fixedVersion = "Not Specified"
}
_, ok := cveidMap[vulnerability.VulnerabilityID]
if ok {
cveDetailStruct := cveidMap[vulnerability.VulnerabilityID]
pkgList := cveDetailStruct.PackageList
pkgList = append(pkgList,
&PackageInfo{Name: &pkgName, InstalledVersion: &installedVersion, FixedVersion: &fixedVersion})
cveDetailStruct.PackageList = pkgList
cveidMap[vulnerability.VulnerabilityID] = cveDetailStruct
} else {
newPkgList := make([]*PackageInfo, 0)
newPkgList = append(newPkgList,
&PackageInfo{Name: &pkgName, InstalledVersion: &installedVersion, FixedVersion: &fixedVersion})
cveidMap[vulnerability.VulnerabilityID] = cveDetail{
Title: vulnerability.Title,
Description: vulnerability.Description, Severity: vulnerability.Severity, PackageList: newPkgList,
}
}
}
}
cveids := []*Cve{}
for id, cveDetail := range cveidMap {
vulID := id
desc := cveDetail.Description
title := cveDetail.Title
severity := cveDetail.Severity
pkgList := cveDetail.PackageList
cveids = append(cveids,
&Cve{ID: &vulID, Title: &title, Description: &desc, Severity: &severity, PackageList: pkgList})
}
return &CVEResultForImage{Tag: &copyImgTag, CVEList: cveids}, nil
}
func (r *queryResolver) ImageListForCve(ctx context.Context, cvid string) ([]*ImgResultForCve, error) {
finalCveResult := []*ImgResultForCve{}
r.log.Info().Msg("extracting repositories")
defaultStore := r.storeController.DefaultStore
defaultTrivyCtx := r.cveInfo.CveTrivyController.DefaultCveConfig
repoList, err := defaultStore.GetRepositories()
if err != nil {
r.log.Error().Err(err).Msg("unable to search repositories")
return finalCveResult, err
}
r.cveInfo.Log.Info().Msg("scanning each global repository")
cveResult, err := r.getImageListForCVE(repoList, cvid, defaultStore, defaultTrivyCtx)
if err != nil {
r.log.Error().Err(err).Msg("error getting cve list for global repositories")
return finalCveResult, err
}
finalCveResult = append(finalCveResult, cveResult...)
subStore := r.storeController.SubStore
for route, store := range subStore {
subRepoList, err := store.GetRepositories()
if err != nil {
r.log.Error().Err(err).Msg("unable to search repositories")
return cveResult, err
}
subTrivyCtx := r.cveInfo.CveTrivyController.SubCveConfig[route]
subCveResult, err := r.getImageListForCVE(subRepoList, cvid, store, subTrivyCtx)
if err != nil {
r.log.Error().Err(err).Msg("unable to get cve result for sub repositories")
return finalCveResult, err
}
finalCveResult = append(finalCveResult, subCveResult...)
}
return finalCveResult, nil
}
func (r *queryResolver) getImageListForCVE(repoList []string, cvid string, imgStore storage.ImageStore,
trivyCtx *cveinfo.TrivyCtx,
) ([]*ImgResultForCve, error) {
cveResult := []*ImgResultForCve{}
) ([]*gql_generated.ImgResultForCve, error) {
cveResult := []*gql_generated.ImgResultForCve{}
for _, repo := range repoList {
r.log.Info().Str("repo", repo).Msg("extracting list of tags available in image repo")
@@ -264,142 +73,17 @@ func (r *queryResolver) getImageListForCVE(repoList []string, cvid string, imgSt
}
if len(tags) != 0 {
cveResult = append(cveResult, &ImgResultForCve{Name: &name, Tags: tags})
cveResult = append(cveResult, &gql_generated.ImgResultForCve{Name: &name, Tags: tags})
}
}
return cveResult, nil
}
func (r *queryResolver) ImageListWithCVEFixed(ctx context.Context, cvid, image string) (*ImgResultForFixedCve, error) { // nolint: lll
imgResultForFixedCVE := &ImgResultForFixedCve{}
r.log.Info().Str("image", image).Msg("extracting list of tags available in image")
tagsInfo, err := r.cveInfo.LayoutUtils.GetImageTagsWithTimestamp(image)
if err != nil {
r.log.Error().Err(err).Msg("unable to read image tags")
return imgResultForFixedCVE, err
}
infectedTags := make([]common.TagInfo, 0)
var hasCVE bool
for _, tag := range tagsInfo {
image := fmt.Sprintf("%s:%s", image, tag.Name)
isValidImage, _ := r.cveInfo.LayoutUtils.IsValidImageFormat(image)
if !isValidImage {
r.log.Debug().Str("image",
fmt.Sprintf("%s:%s", image, tag.Name)).
Msg("image media type not supported for scanning, adding as an infected image")
infectedTags = append(infectedTags, common.TagInfo{Name: tag.Name, Timestamp: tag.Timestamp})
continue
}
trivyCtx := r.cveInfo.GetTrivyContext(image)
r.cveInfo.Log.Info().Str("image", fmt.Sprintf("%s:%s", image, tag.Name)).Msg("scanning image")
report, err := cveinfo.ScanImage(trivyCtx.Ctx)
if err != nil {
r.log.Error().Err(err).
Str("image", fmt.Sprintf("%s:%s", image, tag.Name)).Msg("unable to scan image")
continue
}
hasCVE = false
for _, result := range report.Results {
for _, vulnerability := range result.Vulnerabilities {
if vulnerability.VulnerabilityID == cvid {
hasCVE = true
break
}
}
}
if hasCVE {
infectedTags = append(infectedTags, common.TagInfo{Name: tag.Name, Timestamp: tag.Timestamp, Digest: tag.Digest})
}
}
var finalTagList []*TagInfo
if len(infectedTags) != 0 {
r.log.Info().Msg("comparing fixed tags timestamp")
fixedTags := common.GetFixedTags(tagsInfo, infectedTags)
finalTagList = getGraphqlCompatibleTags(fixedTags)
} else {
r.log.Info().Str("image", image).Str("cve-id", cvid).Msg("image does not contain any tag that have given cve")
finalTagList = getGraphqlCompatibleTags(tagsInfo)
}
imgResultForFixedCVE = &ImgResultForFixedCve{Tags: finalTagList}
return imgResultForFixedCVE, nil
}
func (r *queryResolver) ImageListForDigest(ctx context.Context, digestID string) ([]*ImgResultForDigest, error) {
imgResultForDigest := []*ImgResultForDigest{}
r.log.Info().Msg("extracting repositories")
defaultStore := r.storeController.DefaultStore
repoList, err := defaultStore.GetRepositories()
if err != nil {
r.log.Error().Err(err).Msg("unable to search repositories")
return imgResultForDigest, err
}
r.log.Info().Msg("scanning each global repository")
partialImgResultForDigest, err := r.getImageListForDigest(repoList, digestID)
if err != nil {
r.log.Error().Err(err).Msg("unable to get image and tag list for global repositories")
return imgResultForDigest, err
}
imgResultForDigest = append(imgResultForDigest, partialImgResultForDigest...)
subStore := r.storeController.SubStore
for _, store := range subStore {
subRepoList, err := store.GetRepositories()
if err != nil {
r.log.Error().Err(err).Msg("unable to search sub-repositories")
return imgResultForDigest, err
}
partialImgResultForDigest, err = r.getImageListForDigest(subRepoList, digestID)
if err != nil {
r.log.Error().Err(err).Msg("unable to get image and tag list for sub-repositories")
return imgResultForDigest, err
}
imgResultForDigest = append(imgResultForDigest, partialImgResultForDigest...)
}
return imgResultForDigest, nil
}
func (r *queryResolver) getImageListForDigest(repoList []string,
digest string,
) ([]*ImgResultForDigest, error) {
imgResultForDigest := []*ImgResultForDigest{}
) ([]*gql_generated.ImgResultForDigest, error) {
imgResultForDigest := []*gql_generated.ImgResultForDigest{}
var errResult error
@@ -418,51 +102,15 @@ func (r *queryResolver) getImageListForDigest(repoList []string,
if len(tags) != 0 {
name := repo
imgResultForDigest = append(imgResultForDigest, &ImgResultForDigest{Name: &name, Tags: tags})
imgResultForDigest = append(imgResultForDigest, &gql_generated.ImgResultForDigest{Name: &name, Tags: tags})
}
}
return imgResultForDigest, errResult
}
func (r *queryResolver) ImageListWithLatestTag(ctx context.Context) ([]*ImageInfo, error) {
r.log.Info().Msg("extension api: finding image list")
imageList := make([]*ImageInfo, 0)
defaultStore := r.storeController.DefaultStore
dsImageList, err := r.getImageListWithLatestTag(defaultStore)
if err != nil {
r.log.Error().Err(err).Msg("extension api: error extracting default store image list")
return imageList, err
}
if len(dsImageList) != 0 {
imageList = append(imageList, dsImageList...)
}
subStore := r.storeController.SubStore
for _, store := range subStore {
ssImageList, err := r.getImageListWithLatestTag(store)
if err != nil {
r.log.Error().Err(err).Msg("extension api: error extracting default store image list")
return imageList, err
}
if len(ssImageList) != 0 {
imageList = append(imageList, ssImageList...)
}
}
return imageList, nil
}
func (r *queryResolver) getImageListWithLatestTag(store storage.ImageStore) ([]*ImageInfo, error) {
results := make([]*ImageInfo, 0)
func (r *queryResolver) getImageListWithLatestTag(store storage.ImageStore) ([]*gql_generated.ImageInfo, error) {
results := make([]*gql_generated.ImageInfo, 0)
repoList, err := store.GetRepositories()
if err != nil {
@@ -528,7 +176,7 @@ func (r *queryResolver) getImageListWithLatestTag(store storage.ImageStore) ([]*
// Read categories
categories := common.GetCategories(labels)
results = append(results, &ImageInfo{
results = append(results, &gql_generated.ImageInfo{
Name: &name, Latest: &latestTag.Name,
Description: &desc, Licenses: &license, Vendor: &vendor,
Labels: &categories, Size: &size, LastUpdated: &latestTag.Timestamp,
@@ -538,14 +186,14 @@ func (r *queryResolver) getImageListWithLatestTag(store storage.ImageStore) ([]*
return results, nil
}
func getGraphqlCompatibleTags(fixedTags []common.TagInfo) []*TagInfo {
finalTagList := make([]*TagInfo, 0)
func getGraphqlCompatibleTags(fixedTags []common.TagInfo) []*gql_generated.TagInfo {
finalTagList := make([]*gql_generated.TagInfo, 0)
for _, tag := range fixedTags {
fixTag := tag
finalTagList = append(finalTagList,
&TagInfo{Name: &fixTag.Name, Digest: &fixTag.Digest, Timestamp: &fixTag.Timestamp})
&gql_generated.TagInfo{Name: &fixTag.Name, Digest: &fixTag.Digest, Timestamp: &fixTag.Timestamp})
}
return finalTagList