0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00
zot/pkg/cli/client/search_functions.go
Vishwas R c7472a2dda
feat: add verbose mode for cves for image listing (#2308)
Signed-off-by: Vishwas Rajashekar <vrajashe@cisco.com>
2024-03-12 13:38:48 -07:00

521 lines
13 KiB
Go

//go:build search
// +build search
package client
import (
"context"
"fmt"
"math"
"strings"
"sync"
"time"
zerr "zotregistry.dev/zot/errors"
zcommon "zotregistry.dev/zot/pkg/common"
)
const CveDBRetryInterval = 3
func SearchAllImages(config SearchConfig) error {
username, password := getUsernameAndPassword(config.User)
imageErr := make(chan stringResult)
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
wg.Add(1)
go config.SearchService.getAllImages(ctx, config, username, password, imageErr, &wg)
wg.Add(1)
errCh := make(chan error, 1)
go collectResults(config, &wg, imageErr, cancel, printImageTableHeader, errCh)
wg.Wait()
select {
case err := <-errCh:
return err
default:
return nil
}
}
func SearchAllImagesGQL(config SearchConfig) error {
username, password := getUsernameAndPassword(config.User)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
imageList, err := config.SearchService.getImagesGQL(ctx, config, username, password, "")
if err != nil {
return err
}
imageListData := []imageStruct{}
for _, image := range imageList.Results {
imageListData = append(imageListData, imageStruct(image))
}
return printImageResult(config, imageListData)
}
func SearchImageByName(config SearchConfig, image string) error {
username, password := getUsernameAndPassword(config.User)
imageErr := make(chan stringResult)
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
wg.Add(1)
go config.SearchService.getImageByName(ctx, config, username, password,
image, imageErr, &wg)
wg.Add(1)
errCh := make(chan error, 1)
go collectResults(config, &wg, imageErr, cancel, printImageTableHeader, errCh)
wg.Wait()
select {
case err := <-errCh:
if strings.Contains(err.Error(), "NAME_UNKNOWN") {
return zerr.ErrEmptyRepoList
}
return err
default:
return nil
}
}
func SearchImageByNameGQL(config SearchConfig, imageName string) error {
username, password := getUsernameAndPassword(config.User)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
repo, tag := zcommon.GetImageDirAndTag(imageName)
imageList, err := config.SearchService.getImagesGQL(ctx, config, username, password, repo)
if err != nil {
return err
}
imageListData := []imageStruct{}
for _, image := range imageList.Results {
if tag == "" || image.Tag == tag {
imageListData = append(imageListData, imageStruct(image))
}
}
return printImageResult(config, imageListData)
}
func SearchImagesByDigest(config SearchConfig, digest string) error {
username, password := getUsernameAndPassword(config.User)
imageErr := make(chan stringResult)
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
wg.Add(1)
go config.SearchService.getImagesByDigest(ctx, config, username, password,
digest, imageErr, &wg)
wg.Add(1)
errCh := make(chan error, 1)
go collectResults(config, &wg, imageErr, cancel, printImageTableHeader, errCh)
wg.Wait()
select {
case err := <-errCh:
return err
default:
return nil
}
}
func SearchDerivedImageListGQL(config SearchConfig, derivedImage string) error {
username, password := getUsernameAndPassword(config.User)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
imageList, err := config.SearchService.getDerivedImageListGQL(ctx, config, username,
password, derivedImage)
if err != nil {
return err
}
imageListData := []imageStruct{}
for _, image := range imageList.DerivedImageList.Results {
imageListData = append(imageListData, imageStruct(image))
}
return printImageResult(config, imageListData)
}
func SearchBaseImageListGQL(config SearchConfig, baseImage string) error {
username, password := getUsernameAndPassword(config.User)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
imageList, err := config.SearchService.getBaseImageListGQL(ctx, config, username,
password, baseImage)
if err != nil {
return err
}
imageListData := []imageStruct{}
for _, image := range imageList.BaseImageList.Results {
imageListData = append(imageListData, imageStruct(image))
}
return printImageResult(config, imageListData)
}
func SearchImagesForDigestGQL(config SearchConfig, digest string) error {
username, password := getUsernameAndPassword(config.User)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
imageList, err := config.SearchService.getImagesForDigestGQL(ctx, config, username, password, digest)
if err != nil {
return err
}
imageListData := []imageStruct{}
for _, image := range imageList.Results {
imageListData = append(imageListData, imageStruct(image))
}
if err := printImageResult(config, imageListData); err != nil {
return err
}
return nil
}
func SearchCVEForImageGQL(config SearchConfig, image, searchedCveID string) error {
username, password := getUsernameAndPassword(config.User)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var cveList *cveResult
err := zcommon.RetryWithContext(ctx, func(attempt int, retryIn time.Duration) error {
var err error
cveList, err = config.SearchService.getCveByImageGQL(ctx, config, username, password, image, searchedCveID)
if err != nil {
if !strings.Contains(err.Error(), zerr.ErrCVEDBNotFound.Error()) {
cancel()
return err
}
fmt.Fprintf(config.ResultWriter,
"[warning] CVE DB is not ready [%d] - retry in %d seconds\n", attempt, int(retryIn.Seconds()))
}
return err
}, maxRetries, CveDBRetryInterval*time.Second)
if err != nil {
return err
}
if len(cveList.Data.CVEListForImage.CVEList) == 0 {
fmt.Fprint(config.ResultWriter, "No CVEs found for image\n")
return nil
}
var builder strings.Builder
if config.OutputFormat == defaultOutputFormat || config.OutputFormat == "" {
imageCVESummary := cveList.Data.CVEListForImage.Summary
statsStr := fmt.Sprintf("CRITICAL %d, HIGH %d, MEDIUM %d, LOW %d, UNKNOWN %d, TOTAL %d\n\n",
imageCVESummary.CriticalCount, imageCVESummary.HighCount, imageCVESummary.MediumCount,
imageCVESummary.LowCount, imageCVESummary.UnknownCount, imageCVESummary.Count)
fmt.Fprint(config.ResultWriter, statsStr)
if !config.Verbose {
printCVETableHeader(&builder)
fmt.Fprint(config.ResultWriter, builder.String())
}
}
out, err := cveList.string(config.OutputFormat, config.Verbose)
if err != nil {
return err
}
fmt.Fprint(config.ResultWriter, out)
return nil
}
func SearchCVEDiffList(config SearchConfig, minuend, subtrahend ImageIdentifier) error {
username, password := getUsernameAndPassword(config.User)
response, err := config.SearchService.getCVEDiffListGQL(context.Background(), config, username, password,
minuend, subtrahend)
if err != nil {
return err
}
cveDiffResult := response.Data.CveDiffResult
result := cveResult{
Data: cveData{
CVEListForImage: cveListForImage{
Tag: cveDiffResult.Minuend.Tag,
CVEList: cveDiffResult.CVEList,
Summary: cveDiffResult.Summary,
},
},
}
var builder strings.Builder
if config.OutputFormat == defaultOutputFormat || config.OutputFormat == "" {
imageCVESummary := result.Data.CVEListForImage.Summary
statsStr := fmt.Sprintf("CRITICAL %d, HIGH %d, MEDIUM %d, LOW %d, UNKNOWN %d, TOTAL %d\n\n",
imageCVESummary.CriticalCount, imageCVESummary.HighCount, imageCVESummary.MediumCount,
imageCVESummary.LowCount, imageCVESummary.UnknownCount, imageCVESummary.Count)
fmt.Fprint(config.ResultWriter, statsStr)
printCVETableHeader(&builder)
fmt.Fprint(config.ResultWriter, builder.String())
}
out, err := result.string(config.OutputFormat, config.Verbose)
if err != nil {
return err
}
fmt.Fprint(config.ResultWriter, out)
return nil
}
func SearchImagesByCVEIDGQL(config SearchConfig, repo, cveid string) error {
username, password := getUsernameAndPassword(config.User)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var imageList *zcommon.ImagesForCve
err := zcommon.RetryWithContext(ctx, func(attempt int, retryIn time.Duration) error {
var err error
imageList, err = config.SearchService.getTagsForCVEGQL(ctx, config, username, password,
repo, cveid)
if err != nil {
if !strings.Contains(err.Error(), zerr.ErrCVEDBNotFound.Error()) {
cancel()
return err
}
fmt.Fprintf(config.ResultWriter,
"[warning] CVE DB is not ready [%d] - retry in %d seconds\n", attempt, int(retryIn.Seconds()))
}
return err
}, maxRetries, CveDBRetryInterval*time.Second)
if err != nil {
return err
}
imageListData := []imageStruct{}
for _, image := range imageList.Results {
imageListData = append(imageListData, imageStruct(image))
}
return printImageResult(config, imageListData)
}
func SearchFixedTagsGQL(config SearchConfig, repo, cveid string) error {
username, password := getUsernameAndPassword(config.User)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var fixedTags *zcommon.ImageListWithCVEFixedResponse
err := zcommon.RetryWithContext(ctx, func(attempt int, retryIn time.Duration) error {
var err error
fixedTags, err = config.SearchService.getFixedTagsForCVEGQL(ctx, config, username, password,
repo, cveid)
if err != nil {
if !strings.Contains(err.Error(), zerr.ErrCVEDBNotFound.Error()) {
cancel()
return err
}
fmt.Fprintf(config.ResultWriter,
"[warning] CVE DB is not ready [%d] - retry in %d seconds\n", attempt, int(retryIn.Seconds()))
}
return err
}, maxRetries, CveDBRetryInterval*time.Second)
if err != nil {
return err
}
imageList := make([]imageStruct, 0, len(fixedTags.Results))
for _, image := range fixedTags.Results {
imageList = append(imageList, imageStruct(image))
}
return printImageResult(config, imageList)
}
func GlobalSearchGQL(config SearchConfig, query string) error {
username, password := getUsernameAndPassword(config.User)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
globalSearchResult, err := config.SearchService.globalSearchGQL(ctx, config, username, password, query)
if err != nil {
return err
}
imagesList := []imageStruct{}
for _, image := range globalSearchResult.Images {
imagesList = append(imagesList, imageStruct(image))
}
reposList := []repoStruct{}
for _, repo := range globalSearchResult.Repos {
reposList = append(reposList, repoStruct(repo))
}
if err := printImageResult(config, imagesList); err != nil {
return err
}
return printRepoResults(config, reposList)
}
func SearchReferrersGQL(config SearchConfig, subject string) error {
username, password := getUsernameAndPassword(config.User)
repo, ref, refIsTag, err := zcommon.GetRepoReference(subject)
if err != nil {
return err
}
digest := ref
if refIsTag {
digest, err = fetchImageDigest(repo, ref, username, password, config)
if err != nil {
return err
}
}
response, err := config.SearchService.getReferrersGQL(context.Background(), config, username, password, repo, digest)
if err != nil {
return err
}
referrersList := referrersResult(response.Referrers)
maxArtifactTypeLen := math.MinInt
for _, referrer := range referrersList {
if maxArtifactTypeLen < len(referrer.ArtifactType) {
maxArtifactTypeLen = len(referrer.ArtifactType)
}
}
printReferrersTableHeader(config, config.ResultWriter, maxArtifactTypeLen)
return printReferrersResult(config, referrersList, maxArtifactTypeLen)
}
func SearchReferrers(config SearchConfig, subject string) error {
username, password := getUsernameAndPassword(config.User)
repo, ref, refIsTag, err := zcommon.GetRepoReference(subject)
if err != nil {
return err
}
digest := ref
if refIsTag {
digest, err = fetchImageDigest(repo, ref, username, password, config)
if err != nil {
return err
}
}
referrersList, err := config.SearchService.getReferrers(context.Background(), config, username, password,
repo, digest)
if err != nil {
return err
}
maxArtifactTypeLen := math.MinInt
for _, referrer := range referrersList {
if maxArtifactTypeLen < len(referrer.ArtifactType) {
maxArtifactTypeLen = len(referrer.ArtifactType)
}
}
printReferrersTableHeader(config, config.ResultWriter, maxArtifactTypeLen)
return printReferrersResult(config, referrersList, maxArtifactTypeLen)
}
func SearchRepos(config SearchConfig) error {
username, password := getUsernameAndPassword(config.User)
repoErr := make(chan stringResult)
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
wg.Add(1)
go config.SearchService.getRepos(ctx, config, username, password, repoErr, &wg)
wg.Add(1)
errCh := make(chan error, 1)
go collectResults(config, &wg, repoErr, cancel, printImageTableHeader, errCh)
wg.Wait()
select {
case err := <-errCh:
return err
default:
return nil
}
}