2022-10-10 07:05:55 -05:00
|
|
|
//go:build search
|
|
|
|
// +build search
|
2020-10-14 16:47:20 -05:00
|
|
|
|
2020-06-16 20:52:40 -05:00
|
|
|
package cli
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-07-06 17:44:32 -05:00
|
|
|
"errors"
|
2020-06-16 20:52:40 -05:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/url"
|
2022-01-19 10:57:10 -05:00
|
|
|
"strconv"
|
2020-06-16 20:52:40 -05:00
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/dustin/go-humanize"
|
|
|
|
jsoniter "github.com/json-iterator/go"
|
|
|
|
"github.com/olekukonko/tablewriter"
|
2022-10-22 15:46:13 -05:00
|
|
|
godigest "github.com/opencontainers/go-digest"
|
2023-03-21 12:16:00 -05:00
|
|
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
2020-06-16 20:52:40 -05:00
|
|
|
"gopkg.in/yaml.v2"
|
2022-10-20 11:39:20 -05:00
|
|
|
|
2021-12-03 22:50:58 -05:00
|
|
|
zotErrors "zotregistry.io/zot/errors"
|
2022-02-24 15:31:36 -05:00
|
|
|
"zotregistry.io/zot/pkg/api/constants"
|
2023-04-18 13:07:47 -05:00
|
|
|
"zotregistry.io/zot/pkg/common"
|
2020-06-16 20:52:40 -05:00
|
|
|
)
|
|
|
|
|
2022-10-05 05:21:14 -05:00
|
|
|
type SearchService interface { //nolint:interfacebloat
|
2022-01-19 10:57:10 -05:00
|
|
|
getImagesGQL(ctx context.Context, config searchConfig, username, password string,
|
2023-05-25 13:27:49 -05:00
|
|
|
imageName string) (*common.ImageListResponse, error)
|
2022-01-19 10:57:10 -05:00
|
|
|
getImagesByDigestGQL(ctx context.Context, config searchConfig, username, password string,
|
2023-05-25 13:27:49 -05:00
|
|
|
digest string) (*common.ImagesForDigest, error)
|
2022-01-19 10:57:10 -05:00
|
|
|
getCveByImageGQL(ctx context.Context, config searchConfig, username, password,
|
2023-03-16 14:13:07 -05:00
|
|
|
imageName string, searchedCVE string) (*cveResult, error)
|
2022-01-19 10:57:10 -05:00
|
|
|
getImagesByCveIDGQL(ctx context.Context, config searchConfig, username, password string,
|
2023-05-25 13:27:49 -05:00
|
|
|
digest string) (*common.ImagesForCve, error)
|
2022-01-19 10:57:10 -05:00
|
|
|
getTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, imageName,
|
2023-05-25 13:27:49 -05:00
|
|
|
cveID string) (*common.ImagesForCve, error)
|
2022-01-19 10:57:10 -05:00
|
|
|
getFixedTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, imageName,
|
2023-05-25 13:27:49 -05:00
|
|
|
cveID string) (*common.FixedTags, error)
|
2022-09-23 11:23:31 -05:00
|
|
|
getDerivedImageListGQL(ctx context.Context, config searchConfig, username, password string,
|
2023-05-25 13:27:49 -05:00
|
|
|
derivedImage string) (*common.DerivedImageListResponse, error)
|
2022-09-22 14:08:58 -05:00
|
|
|
getBaseImageListGQL(ctx context.Context, config searchConfig, username, password string,
|
2023-05-25 13:27:49 -05:00
|
|
|
baseImage string) (*common.BaseImageListResponse, error)
|
2022-01-19 10:57:10 -05:00
|
|
|
|
2020-07-17 14:42:22 -05:00
|
|
|
getAllImages(ctx context.Context, config searchConfig, username, password string,
|
2021-12-13 14:23:31 -05:00
|
|
|
channel chan stringResult, wtgrp *sync.WaitGroup)
|
2023-03-16 14:13:07 -05:00
|
|
|
getCveByImage(ctx context.Context, config searchConfig, username, password, imageName, searchedCVE string,
|
2021-12-13 14:23:31 -05:00
|
|
|
channel chan stringResult, wtgrp *sync.WaitGroup)
|
|
|
|
getImagesByCveID(ctx context.Context, config searchConfig, username, password, cvid string,
|
|
|
|
channel chan stringResult, wtgrp *sync.WaitGroup)
|
2021-05-26 12:22:31 -05:00
|
|
|
getImagesByDigest(ctx context.Context, config searchConfig, username, password, digest string,
|
2021-12-13 14:23:31 -05:00
|
|
|
channel chan stringResult, wtgrp *sync.WaitGroup)
|
|
|
|
getFixedTagsForCVE(ctx context.Context, config searchConfig, username, password, imageName, cvid string,
|
|
|
|
channel chan stringResult, wtgrp *sync.WaitGroup)
|
2022-06-02 09:14:21 -05:00
|
|
|
getRepos(ctx context.Context, config searchConfig, username, password string,
|
|
|
|
channel chan stringResult, wtgrp *sync.WaitGroup)
|
2022-01-19 10:57:10 -05:00
|
|
|
getImageByName(ctx context.Context, config searchConfig, username, password, imageName string,
|
|
|
|
channel chan stringResult, wtgrp *sync.WaitGroup)
|
|
|
|
getImageByNameAndCVEID(ctx context.Context, config searchConfig, username, password, imageName, cvid string,
|
|
|
|
channel chan stringResult, wtgrp *sync.WaitGroup)
|
2020-06-16 20:52:40 -05:00
|
|
|
}
|
2020-07-06 17:44:32 -05:00
|
|
|
|
2020-06-16 20:52:40 -05:00
|
|
|
type searchService struct{}
|
|
|
|
|
2020-07-06 17:44:32 -05:00
|
|
|
func NewSearchService() SearchService {
|
2020-06-16 20:52:40 -05:00
|
|
|
return searchService{}
|
|
|
|
}
|
|
|
|
|
2022-09-23 11:23:31 -05:00
|
|
|
func (service searchService) getDerivedImageListGQL(ctx context.Context, config searchConfig, username, password string,
|
|
|
|
derivedImage string,
|
2023-05-25 13:27:49 -05:00
|
|
|
) (*common.DerivedImageListResponse, error) {
|
2022-09-23 11:23:31 -05:00
|
|
|
query := fmt.Sprintf(`
|
|
|
|
{
|
|
|
|
DerivedImageList(image:"%s"){
|
2023-01-25 17:06:02 -05:00
|
|
|
Results{
|
|
|
|
RepoName,
|
|
|
|
Tag,
|
2023-03-21 12:16:00 -05:00
|
|
|
Digest,
|
|
|
|
MediaType,
|
2023-02-27 14:23:18 -05:00
|
|
|
Manifests {
|
|
|
|
Digest,
|
|
|
|
ConfigDigest,
|
|
|
|
Layers {Size Digest},
|
|
|
|
LastUpdated,
|
2023-03-21 12:16:00 -05:00
|
|
|
IsSigned,
|
2023-02-27 14:23:18 -05:00
|
|
|
Size
|
|
|
|
},
|
2023-01-25 17:06:02 -05:00
|
|
|
LastUpdated,
|
|
|
|
IsSigned,
|
|
|
|
Size
|
|
|
|
}
|
2022-09-23 11:23:31 -05:00
|
|
|
}
|
|
|
|
}`, derivedImage)
|
|
|
|
|
2023-05-25 13:27:49 -05:00
|
|
|
result := &common.DerivedImageListResponse{}
|
2022-09-23 11:23:31 -05:00
|
|
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
|
|
|
|
|
|
|
if errResult := checkResultGraphQLQuery(ctx, err, result.Errors); errResult != nil {
|
|
|
|
return nil, errResult
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2022-09-22 14:08:58 -05:00
|
|
|
func (service searchService) getBaseImageListGQL(ctx context.Context, config searchConfig, username, password string,
|
|
|
|
baseImage string,
|
2023-05-25 13:27:49 -05:00
|
|
|
) (*common.BaseImageListResponse, error) {
|
2022-09-22 14:08:58 -05:00
|
|
|
query := fmt.Sprintf(`
|
|
|
|
{
|
|
|
|
BaseImageList(image:"%s"){
|
2023-01-25 17:06:02 -05:00
|
|
|
Results{
|
|
|
|
RepoName,
|
|
|
|
Tag,
|
2023-03-21 12:16:00 -05:00
|
|
|
Digest,
|
|
|
|
MediaType,
|
2023-02-27 14:23:18 -05:00
|
|
|
Manifests {
|
|
|
|
Digest,
|
|
|
|
ConfigDigest,
|
|
|
|
Layers {Size Digest},
|
|
|
|
LastUpdated,
|
2023-03-21 12:16:00 -05:00
|
|
|
IsSigned,
|
2023-02-27 14:23:18 -05:00
|
|
|
Size
|
|
|
|
},
|
2023-01-25 17:06:02 -05:00
|
|
|
LastUpdated,
|
|
|
|
IsSigned,
|
|
|
|
Size
|
|
|
|
}
|
2022-09-22 14:08:58 -05:00
|
|
|
}
|
|
|
|
}`, baseImage)
|
|
|
|
|
2023-05-25 13:27:49 -05:00
|
|
|
result := &common.BaseImageListResponse{}
|
2022-09-22 14:08:58 -05:00
|
|
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
|
|
|
|
|
|
|
if errResult := checkResultGraphQLQuery(ctx, err, result.Errors); errResult != nil {
|
|
|
|
return nil, errResult
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2022-01-19 10:57:10 -05:00
|
|
|
func (service searchService) getImagesGQL(ctx context.Context, config searchConfig, username, password string,
|
|
|
|
imageName string,
|
2023-05-25 13:27:49 -05:00
|
|
|
) (*common.ImageListResponse, error) {
|
2023-02-27 14:23:18 -05:00
|
|
|
query := fmt.Sprintf(`
|
|
|
|
{
|
|
|
|
ImageList(repo: "%s") {
|
|
|
|
Results {
|
|
|
|
RepoName Tag
|
2023-03-21 12:16:00 -05:00
|
|
|
Digest
|
|
|
|
MediaType
|
2023-02-27 14:23:18 -05:00
|
|
|
Manifests {
|
|
|
|
Digest
|
|
|
|
ConfigDigest
|
|
|
|
Size
|
|
|
|
Platform {Os Arch}
|
2023-03-21 12:16:00 -05:00
|
|
|
IsSigned
|
2023-02-27 14:23:18 -05:00
|
|
|
Layers {Size Digest}
|
|
|
|
}
|
|
|
|
Size
|
|
|
|
IsSigned
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}`,
|
2022-01-19 10:57:10 -05:00
|
|
|
imageName)
|
2023-05-25 13:27:49 -05:00
|
|
|
result := &common.ImageListResponse{}
|
2022-01-19 10:57:10 -05:00
|
|
|
|
|
|
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
|
|
|
|
|
|
|
if errResult := checkResultGraphQLQuery(ctx, err, result.Errors); errResult != nil {
|
|
|
|
return nil, errResult
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (service searchService) getImagesByDigestGQL(ctx context.Context, config searchConfig, username, password string,
|
|
|
|
digest string,
|
2023-05-25 13:27:49 -05:00
|
|
|
) (*common.ImagesForDigest, error) {
|
2023-02-27 14:23:18 -05:00
|
|
|
query := fmt.Sprintf(`
|
|
|
|
{
|
|
|
|
ImageListForDigest(id: "%s") {
|
|
|
|
Results {
|
|
|
|
RepoName Tag
|
2023-03-21 12:16:00 -05:00
|
|
|
Digest
|
|
|
|
MediaType
|
2023-02-27 14:23:18 -05:00
|
|
|
Manifests {
|
|
|
|
Digest
|
|
|
|
ConfigDigest
|
|
|
|
Size
|
2023-03-21 12:16:00 -05:00
|
|
|
IsSigned
|
2023-02-27 14:23:18 -05:00
|
|
|
Layers {Size Digest}
|
2023-03-21 12:16:00 -05:00
|
|
|
}
|
2023-02-27 14:23:18 -05:00
|
|
|
Size
|
|
|
|
IsSigned
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}`,
|
2022-01-19 10:57:10 -05:00
|
|
|
digest)
|
2023-05-25 13:27:49 -05:00
|
|
|
result := &common.ImagesForDigest{}
|
2022-01-19 10:57:10 -05:00
|
|
|
|
|
|
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
|
|
|
|
|
|
|
if errResult := checkResultGraphQLQuery(ctx, err, result.Errors); errResult != nil {
|
|
|
|
return nil, errResult
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (service searchService) getImagesByCveIDGQL(ctx context.Context, config searchConfig, username,
|
|
|
|
password, cveID string,
|
2023-05-25 13:27:49 -05:00
|
|
|
) (*common.ImagesForCve, error) {
|
2023-02-27 14:23:18 -05:00
|
|
|
query := fmt.Sprintf(`
|
|
|
|
{
|
|
|
|
ImageListForCVE(id: "%s") {
|
|
|
|
Results {
|
|
|
|
RepoName Tag
|
2023-03-21 12:16:00 -05:00
|
|
|
Digest
|
|
|
|
MediaType
|
2023-02-27 14:23:18 -05:00
|
|
|
Manifests {
|
|
|
|
Digest
|
|
|
|
ConfigDigest
|
|
|
|
Size
|
2023-03-21 12:16:00 -05:00
|
|
|
IsSigned
|
2023-02-27 14:23:18 -05:00
|
|
|
Layers {Size Digest}
|
2023-03-21 12:16:00 -05:00
|
|
|
}
|
2023-02-27 14:23:18 -05:00
|
|
|
Size
|
|
|
|
IsSigned
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}`,
|
2022-01-19 10:57:10 -05:00
|
|
|
cveID)
|
2023-05-25 13:27:49 -05:00
|
|
|
result := &common.ImagesForCve{}
|
2022-01-19 10:57:10 -05:00
|
|
|
|
|
|
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
|
|
|
|
|
|
|
if errResult := checkResultGraphQLQuery(ctx, err, result.Errors); errResult != nil {
|
|
|
|
return nil, errResult
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (service searchService) getCveByImageGQL(ctx context.Context, config searchConfig, username, password,
|
2023-03-16 14:13:07 -05:00
|
|
|
imageName, searchedCVE string,
|
2022-01-19 10:57:10 -05:00
|
|
|
) (*cveResult, error) {
|
2023-03-16 14:13:07 -05:00
|
|
|
query := fmt.Sprintf(`{ CVEListForImage (image:"%s", searchedCVE:"%s")`+
|
2022-01-19 10:57:10 -05:00
|
|
|
` { Tag CVEList { Id Title Severity Description `+
|
2023-03-16 14:13:07 -05:00
|
|
|
`PackageList {Name InstalledVersion FixedVersion}} } }`, imageName, searchedCVE)
|
2022-01-19 10:57:10 -05:00
|
|
|
result := &cveResult{}
|
|
|
|
|
|
|
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
|
|
|
|
|
|
|
if errResult := checkResultGraphQLQuery(ctx, err, result.Errors); errResult != nil {
|
|
|
|
return nil, errResult
|
|
|
|
}
|
|
|
|
|
|
|
|
result.Data.CVEListForImage.CVEList = groupCVEsBySeverity(result.Data.CVEListForImage.CVEList)
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (service searchService) getTagsForCVEGQL(ctx context.Context, config searchConfig,
|
|
|
|
username, password, imageName, cveID string,
|
2023-05-25 13:27:49 -05:00
|
|
|
) (*common.ImagesForCve, error) {
|
2023-02-27 14:23:18 -05:00
|
|
|
query := fmt.Sprintf(`
|
|
|
|
{
|
|
|
|
ImageListForCVE(id: "%s") {
|
|
|
|
Results {
|
|
|
|
RepoName Tag
|
2023-03-21 12:16:00 -05:00
|
|
|
Digest
|
|
|
|
MediaType
|
2023-02-27 14:23:18 -05:00
|
|
|
Manifests {
|
|
|
|
Digest
|
|
|
|
ConfigDigest
|
|
|
|
Size
|
2023-03-21 12:16:00 -05:00
|
|
|
IsSigned
|
2023-02-27 14:23:18 -05:00
|
|
|
Layers {Size Digest}
|
|
|
|
}
|
|
|
|
Size
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}`,
|
2022-01-19 10:57:10 -05:00
|
|
|
cveID)
|
2023-05-25 13:27:49 -05:00
|
|
|
result := &common.ImagesForCve{}
|
2022-01-19 10:57:10 -05:00
|
|
|
|
|
|
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
|
|
|
|
|
|
|
if errResult := checkResultGraphQLQuery(ctx, err, result.Errors); errResult != nil {
|
|
|
|
return nil, errResult
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (service searchService) getFixedTagsForCVEGQL(ctx context.Context, config searchConfig,
|
|
|
|
username, password, imageName, cveID string,
|
2023-05-25 13:27:49 -05:00
|
|
|
) (*common.FixedTags, error) {
|
2023-02-27 14:23:18 -05:00
|
|
|
query := fmt.Sprintf(`
|
|
|
|
{
|
|
|
|
ImageListWithCVEFixed(id: "%s", image: "%s") {
|
|
|
|
Results {
|
|
|
|
RepoName Tag
|
2023-03-21 12:16:00 -05:00
|
|
|
Digest
|
|
|
|
MediaType
|
2023-02-27 14:23:18 -05:00
|
|
|
Manifests {
|
|
|
|
Digest
|
|
|
|
ConfigDigest
|
|
|
|
Size
|
2023-03-21 12:16:00 -05:00
|
|
|
IsSigned
|
2023-02-27 14:23:18 -05:00
|
|
|
Layers {Size Digest}
|
2023-03-21 12:16:00 -05:00
|
|
|
}
|
2023-02-27 14:23:18 -05:00
|
|
|
Size
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}`,
|
2022-01-19 10:57:10 -05:00
|
|
|
cveID, imageName)
|
|
|
|
|
2023-05-25 13:27:49 -05:00
|
|
|
result := &common.FixedTags{}
|
2022-01-19 10:57:10 -05:00
|
|
|
|
|
|
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
|
|
|
|
|
|
|
if errResult := checkResultGraphQLQuery(ctx, err, result.Errors); errResult != nil {
|
|
|
|
return nil, errResult
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2020-07-17 14:42:22 -05:00
|
|
|
func (service searchService) getImageByName(ctx context.Context, config searchConfig,
|
2022-03-21 12:37:23 -05:00
|
|
|
username, password, imageName string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
|
|
|
) {
|
2021-12-13 14:23:31 -05:00
|
|
|
defer wtgrp.Done()
|
|
|
|
defer close(rch)
|
2020-06-16 20:52:40 -05:00
|
|
|
|
2020-07-17 14:42:22 -05:00
|
|
|
var localWg sync.WaitGroup
|
2022-03-21 12:37:23 -05:00
|
|
|
rlim := newSmoothRateLimiter(&localWg, rch)
|
2020-06-16 20:52:40 -05:00
|
|
|
|
2020-07-17 14:42:22 -05:00
|
|
|
localWg.Add(1)
|
2020-06-16 20:52:40 -05:00
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
go rlim.startRateLimiter(ctx)
|
2020-07-17 14:42:22 -05:00
|
|
|
localWg.Add(1)
|
2020-06-16 20:52:40 -05:00
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
go getImage(ctx, config, username, password, imageName, rch, &localWg, rlim)
|
2020-07-17 14:42:22 -05:00
|
|
|
|
|
|
|
localWg.Wait()
|
2020-06-16 20:52:40 -05:00
|
|
|
}
|
|
|
|
|
2020-07-17 14:42:22 -05:00
|
|
|
func (service searchService) getAllImages(ctx context.Context, config searchConfig, username, password string,
|
2022-03-21 12:37:23 -05:00
|
|
|
rch chan stringResult, wtgrp *sync.WaitGroup,
|
|
|
|
) {
|
2021-12-13 14:23:31 -05:00
|
|
|
defer wtgrp.Done()
|
|
|
|
defer close(rch)
|
2020-06-16 20:52:40 -05:00
|
|
|
|
|
|
|
catalog := &catalogResponse{}
|
|
|
|
|
2022-02-24 15:31:36 -05:00
|
|
|
catalogEndPoint, err := combineServerAndEndpointURL(*config.servURL, fmt.Sprintf("%s%s",
|
|
|
|
constants.RoutePrefix, constants.ExtCatalogPrefix))
|
2020-06-16 20:52:40 -05:00
|
|
|
if err != nil {
|
|
|
|
if isContextDone(ctx) {
|
|
|
|
return
|
|
|
|
}
|
2021-12-13 14:23:31 -05:00
|
|
|
rch <- stringResult{"", err}
|
2020-06-16 20:52:40 -05:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-09-23 11:24:01 -05:00
|
|
|
_, err = makeGETRequest(ctx, catalogEndPoint, username, password, *config.verifyTLS,
|
|
|
|
*config.debug, catalog, config.resultWriter)
|
2020-06-16 20:52:40 -05:00
|
|
|
if err != nil {
|
|
|
|
if isContextDone(ctx) {
|
|
|
|
return
|
|
|
|
}
|
2021-12-13 14:23:31 -05:00
|
|
|
rch <- stringResult{"", err}
|
2020-06-16 20:52:40 -05:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-07-17 14:42:22 -05:00
|
|
|
var localWg sync.WaitGroup
|
2020-06-16 20:52:40 -05:00
|
|
|
|
2022-03-21 12:37:23 -05:00
|
|
|
rlim := newSmoothRateLimiter(&localWg, rch)
|
2020-07-17 14:42:22 -05:00
|
|
|
|
|
|
|
localWg.Add(1)
|
2020-06-16 20:52:40 -05:00
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
go rlim.startRateLimiter(ctx)
|
2020-06-16 20:52:40 -05:00
|
|
|
|
|
|
|
for _, repo := range catalog.Repositories {
|
2020-07-17 14:42:22 -05:00
|
|
|
localWg.Add(1)
|
2020-06-16 20:52:40 -05:00
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
go getImage(ctx, config, username, password, repo, rch, &localWg, rlim)
|
2020-06-16 20:52:40 -05:00
|
|
|
}
|
2020-07-17 14:42:22 -05:00
|
|
|
|
|
|
|
localWg.Wait()
|
2020-06-16 20:52:40 -05:00
|
|
|
}
|
2020-07-17 14:42:22 -05:00
|
|
|
|
|
|
|
func getImage(ctx context.Context, config searchConfig, username, password, imageName string,
|
2022-03-21 12:37:23 -05:00
|
|
|
rch chan stringResult, wtgrp *sync.WaitGroup, pool *requestsPool,
|
|
|
|
) {
|
2021-12-13 14:23:31 -05:00
|
|
|
defer wtgrp.Done()
|
2020-06-16 20:52:40 -05:00
|
|
|
|
2020-07-17 14:42:22 -05:00
|
|
|
tagListEndpoint, err := combineServerAndEndpointURL(*config.servURL, fmt.Sprintf("/v2/%s/tags/list", imageName))
|
2020-06-16 20:52:40 -05:00
|
|
|
if err != nil {
|
|
|
|
if isContextDone(ctx) {
|
|
|
|
return
|
|
|
|
}
|
2021-12-13 14:23:31 -05:00
|
|
|
rch <- stringResult{"", err}
|
2020-06-16 20:52:40 -05:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-19 10:57:10 -05:00
|
|
|
tagList := &tagListResp{}
|
2022-09-23 11:24:01 -05:00
|
|
|
_, err = makeGETRequest(ctx, tagListEndpoint, username, password, *config.verifyTLS,
|
|
|
|
*config.debug, &tagList, config.resultWriter)
|
2020-06-16 20:52:40 -05:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
if isContextDone(ctx) {
|
|
|
|
return
|
|
|
|
}
|
2021-12-13 14:23:31 -05:00
|
|
|
rch <- stringResult{"", err}
|
2020-06-16 20:52:40 -05:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-01-19 10:57:10 -05:00
|
|
|
for _, tag := range tagList.Tags {
|
2022-10-23 01:44:20 -05:00
|
|
|
hasTagPrefix := strings.HasPrefix(tag, "sha256-")
|
|
|
|
hasTagSuffix := strings.HasSuffix(tag, ".sig")
|
|
|
|
|
|
|
|
// check if it's an image or a signature
|
|
|
|
// we don't want to show signatures in cli responses
|
|
|
|
if hasTagPrefix && hasTagSuffix {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
wtgrp.Add(1)
|
2020-06-16 20:52:40 -05:00
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
go addManifestCallToPool(ctx, config, pool, username, password, imageName, tag, rch, wtgrp)
|
2020-06-16 20:52:40 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-06 17:44:32 -05:00
|
|
|
func (service searchService) getImagesByCveID(ctx context.Context, config searchConfig, username,
|
2022-03-21 12:37:23 -05:00
|
|
|
password, cvid string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
|
|
|
) {
|
2021-12-13 14:23:31 -05:00
|
|
|
defer wtgrp.Done()
|
|
|
|
defer close(rch)
|
2020-07-06 17:44:32 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
query := fmt.Sprintf(
|
|
|
|
`{
|
|
|
|
ImageListForCVE(id: "%s") {
|
|
|
|
Results {
|
|
|
|
RepoName Tag
|
2023-03-21 12:16:00 -05:00
|
|
|
Digest
|
|
|
|
MediaType
|
2023-02-27 14:23:18 -05:00
|
|
|
Manifests {
|
|
|
|
Digest
|
|
|
|
ConfigDigest
|
|
|
|
Size
|
2023-03-21 12:16:00 -05:00
|
|
|
IsSigned
|
2023-02-27 14:23:18 -05:00
|
|
|
Layers {Size Digest}
|
2023-03-21 12:16:00 -05:00
|
|
|
}
|
2023-02-27 14:23:18 -05:00
|
|
|
Size
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}`,
|
2021-12-13 14:23:31 -05:00
|
|
|
cvid)
|
2023-02-27 14:23:18 -05:00
|
|
|
|
2023-05-25 13:27:49 -05:00
|
|
|
result := &common.ImagesForCve{}
|
2020-07-06 17:44:32 -05:00
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
2020-07-06 17:44:32 -05:00
|
|
|
if err != nil {
|
|
|
|
if isContextDone(ctx) {
|
|
|
|
return
|
|
|
|
}
|
2021-12-13 14:23:31 -05:00
|
|
|
rch <- stringResult{"", err}
|
2020-07-06 17:44:32 -05:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-26 12:22:31 -05:00
|
|
|
if result.Errors != nil || err != nil {
|
|
|
|
var errBuilder strings.Builder
|
|
|
|
|
|
|
|
for _, err := range result.Errors {
|
|
|
|
fmt.Fprintln(&errBuilder, err.Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
if isContextDone(ctx) {
|
|
|
|
return
|
|
|
|
}
|
2021-12-13 14:23:31 -05:00
|
|
|
rch <- stringResult{"", errors.New(errBuilder.String())} //nolint: goerr113
|
2021-05-26 12:22:31 -05:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var localWg sync.WaitGroup
|
|
|
|
|
2022-03-21 12:37:23 -05:00
|
|
|
rlim := newSmoothRateLimiter(&localWg, rch)
|
2021-05-26 12:22:31 -05:00
|
|
|
localWg.Add(1)
|
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
go rlim.startRateLimiter(ctx)
|
2021-05-26 12:22:31 -05:00
|
|
|
|
2023-05-25 13:27:49 -05:00
|
|
|
for _, image := range result.Results {
|
2022-01-19 10:57:10 -05:00
|
|
|
localWg.Add(1)
|
2021-05-26 12:22:31 -05:00
|
|
|
|
2022-01-19 10:57:10 -05:00
|
|
|
go addManifestCallToPool(ctx, config, rlim, username, password, image.RepoName, image.Tag, rch, &localWg)
|
2021-05-26 12:22:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
localWg.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (service searchService) getImagesByDigest(ctx context.Context, config searchConfig, username,
|
2022-03-21 12:37:23 -05:00
|
|
|
password string, digest string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
|
|
|
) {
|
2021-12-13 14:23:31 -05:00
|
|
|
defer wtgrp.Done()
|
|
|
|
defer close(rch)
|
2021-05-26 12:22:31 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
query := fmt.Sprintf(
|
|
|
|
`{
|
|
|
|
ImageListForDigest(id: "%s") {
|
|
|
|
Results {
|
|
|
|
RepoName Tag
|
2023-03-21 12:16:00 -05:00
|
|
|
Digest
|
|
|
|
MediaType
|
2023-02-27 14:23:18 -05:00
|
|
|
Manifests {
|
|
|
|
Digest
|
|
|
|
ConfigDigest
|
|
|
|
Size
|
2023-03-21 12:16:00 -05:00
|
|
|
IsSigned
|
2023-02-27 14:23:18 -05:00
|
|
|
Layers {Size Digest}
|
2023-03-21 12:16:00 -05:00
|
|
|
}
|
2023-02-27 14:23:18 -05:00
|
|
|
Size
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}`,
|
2021-05-26 12:22:31 -05:00
|
|
|
digest)
|
2023-02-27 14:23:18 -05:00
|
|
|
|
2023-05-25 13:27:49 -05:00
|
|
|
result := &common.ImagesForDigest{}
|
2021-05-26 12:22:31 -05:00
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
2020-07-06 17:44:32 -05:00
|
|
|
if err != nil {
|
|
|
|
if isContextDone(ctx) {
|
|
|
|
return
|
|
|
|
}
|
2021-12-13 14:23:31 -05:00
|
|
|
rch <- stringResult{"", err}
|
2020-07-06 17:44:32 -05:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if result.Errors != nil {
|
|
|
|
var errBuilder strings.Builder
|
|
|
|
|
|
|
|
for _, err := range result.Errors {
|
|
|
|
fmt.Fprintln(&errBuilder, err.Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
if isContextDone(ctx) {
|
|
|
|
return
|
|
|
|
}
|
2021-12-13 14:23:31 -05:00
|
|
|
rch <- stringResult{"", errors.New(errBuilder.String())} //nolint: goerr113
|
2020-07-06 17:44:32 -05:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var localWg sync.WaitGroup
|
|
|
|
|
2022-03-21 12:37:23 -05:00
|
|
|
rlim := newSmoothRateLimiter(&localWg, rch)
|
2020-07-06 17:44:32 -05:00
|
|
|
localWg.Add(1)
|
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
go rlim.startRateLimiter(ctx)
|
2020-07-06 17:44:32 -05:00
|
|
|
|
2023-05-25 13:27:49 -05:00
|
|
|
for _, image := range result.Results {
|
2022-01-19 10:57:10 -05:00
|
|
|
localWg.Add(1)
|
2020-07-06 17:44:32 -05:00
|
|
|
|
2022-01-19 10:57:10 -05:00
|
|
|
go addManifestCallToPool(ctx, config, rlim, username, password, image.RepoName, image.Tag, rch, &localWg)
|
2020-07-06 17:44:32 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
localWg.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (service searchService) getImageByNameAndCVEID(ctx context.Context, config searchConfig, username,
|
2022-03-21 12:37:23 -05:00
|
|
|
password, imageName, cvid string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
|
|
|
) {
|
2021-12-13 14:23:31 -05:00
|
|
|
defer wtgrp.Done()
|
|
|
|
defer close(rch)
|
2020-07-06 17:44:32 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
query := fmt.Sprintf(
|
|
|
|
`{
|
|
|
|
ImageListForCVE(id: "%s") {
|
|
|
|
Results {
|
|
|
|
RepoName Tag
|
2023-03-21 12:16:00 -05:00
|
|
|
Digest
|
|
|
|
MediaType
|
2023-02-27 14:23:18 -05:00
|
|
|
Manifests {
|
|
|
|
Digest
|
|
|
|
ConfigDigest
|
|
|
|
Size
|
2023-03-21 12:16:00 -05:00
|
|
|
IsSigned
|
2023-02-27 14:23:18 -05:00
|
|
|
Layers {Size Digest}
|
2023-03-21 12:16:00 -05:00
|
|
|
}
|
2023-02-27 14:23:18 -05:00
|
|
|
Size
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}`,
|
2021-12-13 14:23:31 -05:00
|
|
|
cvid)
|
2023-02-27 14:23:18 -05:00
|
|
|
|
2023-05-25 13:27:49 -05:00
|
|
|
result := &common.ImagesForCve{}
|
2020-07-06 17:44:32 -05:00
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
2020-07-06 17:44:32 -05:00
|
|
|
if err != nil {
|
|
|
|
if isContextDone(ctx) {
|
|
|
|
return
|
|
|
|
}
|
2021-12-13 14:23:31 -05:00
|
|
|
rch <- stringResult{"", err}
|
2020-07-06 17:44:32 -05:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if result.Errors != nil {
|
|
|
|
var errBuilder strings.Builder
|
|
|
|
|
|
|
|
for _, err := range result.Errors {
|
|
|
|
fmt.Fprintln(&errBuilder, err.Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
if isContextDone(ctx) {
|
|
|
|
return
|
|
|
|
}
|
2021-12-13 14:23:31 -05:00
|
|
|
rch <- stringResult{"", errors.New(errBuilder.String())} //nolint: goerr113
|
2020-07-06 17:44:32 -05:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var localWg sync.WaitGroup
|
|
|
|
|
2022-03-21 12:37:23 -05:00
|
|
|
rlim := newSmoothRateLimiter(&localWg, rch)
|
2020-07-06 17:44:32 -05:00
|
|
|
localWg.Add(1)
|
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
go rlim.startRateLimiter(ctx)
|
2020-07-06 17:44:32 -05:00
|
|
|
|
2023-05-25 13:27:49 -05:00
|
|
|
for _, image := range result.Results {
|
2022-01-19 10:57:10 -05:00
|
|
|
if !strings.EqualFold(imageName, image.RepoName) {
|
2020-07-06 17:44:32 -05:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-01-19 10:57:10 -05:00
|
|
|
localWg.Add(1)
|
2020-07-06 17:44:32 -05:00
|
|
|
|
2022-01-19 10:57:10 -05:00
|
|
|
go addManifestCallToPool(ctx, config, rlim, username, password, image.RepoName, image.Tag, rch, &localWg)
|
2020-07-06 17:44:32 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
localWg.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (service searchService) getCveByImage(ctx context.Context, config searchConfig, username, password,
|
2023-03-16 14:13:07 -05:00
|
|
|
imageName, searchedCVE string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
2022-03-21 12:37:23 -05:00
|
|
|
) {
|
2021-12-13 14:23:31 -05:00
|
|
|
defer wtgrp.Done()
|
|
|
|
defer close(rch)
|
2020-07-06 17:44:32 -05:00
|
|
|
|
2023-03-16 14:13:07 -05:00
|
|
|
query := fmt.Sprintf(`{ CVEListForImage (image:"%s", searchedCVE:"%s")`+
|
2020-07-06 17:44:32 -05:00
|
|
|
` { Tag CVEList { Id Title Severity Description `+
|
2023-03-16 14:13:07 -05:00
|
|
|
`PackageList {Name InstalledVersion FixedVersion}} } }`, imageName, searchedCVE)
|
2020-07-06 17:44:32 -05:00
|
|
|
result := &cveResult{}
|
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
2020-07-06 17:44:32 -05:00
|
|
|
if err != nil {
|
|
|
|
if isContextDone(ctx) {
|
|
|
|
return
|
|
|
|
}
|
2021-12-13 14:23:31 -05:00
|
|
|
rch <- stringResult{"", err}
|
2020-07-06 17:44:32 -05:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if result.Errors != nil {
|
|
|
|
var errBuilder strings.Builder
|
|
|
|
|
|
|
|
for _, err := range result.Errors {
|
|
|
|
fmt.Fprintln(&errBuilder, err.Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
if isContextDone(ctx) {
|
|
|
|
return
|
|
|
|
}
|
2021-12-13 14:23:31 -05:00
|
|
|
rch <- stringResult{"", errors.New(errBuilder.String())} //nolint: goerr113
|
2020-07-06 17:44:32 -05:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-09-04 12:56:47 -05:00
|
|
|
result.Data.CVEListForImage.CVEList = groupCVEsBySeverity(result.Data.CVEListForImage.CVEList)
|
|
|
|
|
2020-07-06 17:44:32 -05:00
|
|
|
str, err := result.string(*config.outputFormat)
|
|
|
|
if err != nil {
|
|
|
|
if isContextDone(ctx) {
|
|
|
|
return
|
|
|
|
}
|
2021-12-13 14:23:31 -05:00
|
|
|
rch <- stringResult{"", err}
|
2020-07-06 17:44:32 -05:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if isContextDone(ctx) {
|
|
|
|
return
|
|
|
|
}
|
2021-12-13 14:23:31 -05:00
|
|
|
rch <- stringResult{str, nil}
|
2020-07-06 17:44:32 -05:00
|
|
|
}
|
|
|
|
|
2022-01-19 10:57:10 -05:00
|
|
|
func (service searchService) getFixedTagsForCVE(ctx context.Context, config searchConfig,
|
|
|
|
username, password, imageName, cvid string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
|
|
|
) {
|
|
|
|
defer wtgrp.Done()
|
|
|
|
defer close(rch)
|
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
query := fmt.Sprintf(`
|
|
|
|
{
|
|
|
|
ImageListWithCVEFixed (id: "%s", image: "%s") {
|
|
|
|
Results {
|
|
|
|
RepoName Tag
|
2023-03-21 12:16:00 -05:00
|
|
|
Digest
|
|
|
|
MediaType
|
2023-02-27 14:23:18 -05:00
|
|
|
Manifests {
|
|
|
|
Digest
|
|
|
|
ConfigDigest
|
|
|
|
Size
|
2023-03-21 12:16:00 -05:00
|
|
|
IsSigned
|
2023-02-27 14:23:18 -05:00
|
|
|
Layers {Size Digest}
|
2023-03-21 12:16:00 -05:00
|
|
|
}
|
2023-02-27 14:23:18 -05:00
|
|
|
Size
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}`, cvid, imageName)
|
|
|
|
|
2023-05-25 13:27:49 -05:00
|
|
|
result := &common.FixedTags{}
|
2022-01-19 10:57:10 -05:00
|
|
|
|
|
|
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
|
|
|
if err != nil {
|
|
|
|
if isContextDone(ctx) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
rch <- stringResult{"", err}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if result.Errors != nil {
|
|
|
|
var errBuilder strings.Builder
|
|
|
|
|
|
|
|
for _, err := range result.Errors {
|
|
|
|
fmt.Fprintln(&errBuilder, err.Message)
|
|
|
|
}
|
|
|
|
|
|
|
|
if isContextDone(ctx) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
rch <- stringResult{"", errors.New(errBuilder.String())} //nolint: goerr113
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var localWg sync.WaitGroup
|
|
|
|
|
|
|
|
rlim := newSmoothRateLimiter(&localWg, rch)
|
|
|
|
localWg.Add(1)
|
|
|
|
|
|
|
|
go rlim.startRateLimiter(ctx)
|
|
|
|
|
2023-05-25 13:27:49 -05:00
|
|
|
for _, img := range result.Results {
|
2022-01-19 10:57:10 -05:00
|
|
|
localWg.Add(1)
|
|
|
|
|
|
|
|
go addManifestCallToPool(ctx, config, rlim, username, password, imageName, img.Tag, rch, &localWg)
|
|
|
|
}
|
|
|
|
|
|
|
|
localWg.Wait()
|
|
|
|
}
|
|
|
|
|
2020-09-04 12:56:47 -05:00
|
|
|
func groupCVEsBySeverity(cveList []cve) []cve {
|
2022-05-25 09:17:54 -05:00
|
|
|
var (
|
|
|
|
unknown = make([]cve, 0)
|
|
|
|
none = make([]cve, 0)
|
|
|
|
high = make([]cve, 0)
|
|
|
|
med = make([]cve, 0)
|
|
|
|
low = make([]cve, 0)
|
|
|
|
critical = make([]cve, 0)
|
|
|
|
)
|
2020-09-04 12:56:47 -05:00
|
|
|
|
|
|
|
for _, cve := range cveList {
|
|
|
|
switch cve.Severity {
|
2022-05-25 09:17:54 -05:00
|
|
|
case "NONE":
|
|
|
|
none = append(none, cve)
|
|
|
|
|
2020-09-04 12:56:47 -05:00
|
|
|
case "LOW":
|
|
|
|
low = append(low, cve)
|
|
|
|
|
|
|
|
case "MEDIUM":
|
|
|
|
med = append(med, cve)
|
|
|
|
|
|
|
|
case "HIGH":
|
|
|
|
high = append(high, cve)
|
2022-05-25 09:17:54 -05:00
|
|
|
|
|
|
|
case "CRITICAL":
|
|
|
|
critical = append(critical, cve)
|
|
|
|
|
|
|
|
default:
|
|
|
|
unknown = append(unknown, cve)
|
2020-09-04 12:56:47 -05:00
|
|
|
}
|
|
|
|
}
|
2022-05-25 09:17:54 -05:00
|
|
|
vulnsCount := len(unknown) + len(none) + len(high) + len(med) + len(low) + len(critical)
|
|
|
|
vulns := make([]cve, 0, vulnsCount)
|
|
|
|
|
|
|
|
vulns = append(vulns, critical...)
|
|
|
|
vulns = append(vulns, high...)
|
|
|
|
vulns = append(vulns, med...)
|
|
|
|
vulns = append(vulns, low...)
|
|
|
|
vulns = append(vulns, none...)
|
|
|
|
vulns = append(vulns, unknown...)
|
2020-09-04 12:56:47 -05:00
|
|
|
|
2022-05-25 09:17:54 -05:00
|
|
|
return vulns
|
2020-09-04 12:56:47 -05:00
|
|
|
}
|
|
|
|
|
2020-06-16 20:52:40 -05:00
|
|
|
func isContextDone(ctx context.Context) bool {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-19 10:57:10 -05:00
|
|
|
// Query using JQL, the query string is passed as a parameter
|
|
|
|
// errors are returned in the stringResult channel, the unmarshalled payload is in resultPtr.
|
|
|
|
func (service searchService) makeGraphQLQuery(ctx context.Context,
|
|
|
|
config searchConfig, username, password, query string,
|
|
|
|
resultPtr interface{},
|
|
|
|
) error {
|
2022-10-18 22:46:06 -05:00
|
|
|
endPoint, err := combineServerAndEndpointURL(*config.servURL, constants.FullSearchPrefix)
|
2022-01-19 10:57:10 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-07-06 17:44:32 -05:00
|
|
|
|
2022-09-23 11:24:01 -05:00
|
|
|
err = makeGraphQLRequest(ctx, endPoint, query, username, password, *config.verifyTLS,
|
|
|
|
*config.debug, resultPtr, config.resultWriter)
|
2022-01-19 10:57:10 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-07-06 17:44:32 -05:00
|
|
|
|
2022-01-19 10:57:10 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-05-25 13:27:49 -05:00
|
|
|
func checkResultGraphQLQuery(ctx context.Context, err error, resultErrors []common.ErrorGQL,
|
2022-01-19 10:57:10 -05:00
|
|
|
) error {
|
2020-07-06 17:44:32 -05:00
|
|
|
if err != nil {
|
|
|
|
if isContextDone(ctx) {
|
2022-10-05 05:21:14 -05:00
|
|
|
return nil //nolint:nilnil
|
2020-07-06 17:44:32 -05:00
|
|
|
}
|
|
|
|
|
2022-01-19 10:57:10 -05:00
|
|
|
return err
|
2020-07-06 17:44:32 -05:00
|
|
|
}
|
|
|
|
|
2022-01-19 10:57:10 -05:00
|
|
|
if resultErrors != nil {
|
2020-07-06 17:44:32 -05:00
|
|
|
var errBuilder strings.Builder
|
|
|
|
|
2022-01-19 10:57:10 -05:00
|
|
|
for _, error := range resultErrors {
|
|
|
|
fmt.Fprintln(&errBuilder, error.Message)
|
2020-07-06 17:44:32 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if isContextDone(ctx) {
|
2022-01-19 10:57:10 -05:00
|
|
|
return nil
|
2020-07-06 17:44:32 -05:00
|
|
|
}
|
|
|
|
|
2022-10-05 05:21:14 -05:00
|
|
|
//nolint: goerr113
|
2022-01-19 10:57:10 -05:00
|
|
|
return errors.New(errBuilder.String())
|
2021-05-26 12:22:31 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
func addManifestCallToPool(ctx context.Context, config searchConfig, pool *requestsPool,
|
2022-03-21 12:37:23 -05:00
|
|
|
username, password, imageName, tagName string, rch chan stringResult, wtgrp *sync.WaitGroup,
|
|
|
|
) {
|
2021-12-13 14:23:31 -05:00
|
|
|
defer wtgrp.Done()
|
2020-06-16 20:52:40 -05:00
|
|
|
|
2020-07-17 14:42:22 -05:00
|
|
|
manifestEndpoint, err := combineServerAndEndpointURL(*config.servURL,
|
|
|
|
fmt.Sprintf("/v2/%s/manifests/%s", imageName, tagName))
|
2020-06-16 20:52:40 -05:00
|
|
|
if err != nil {
|
|
|
|
if isContextDone(ctx) {
|
|
|
|
return
|
|
|
|
}
|
2021-12-13 14:23:31 -05:00
|
|
|
rch <- stringResult{"", err}
|
2020-06-16 20:52:40 -05:00
|
|
|
}
|
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
job := httpJob{
|
|
|
|
url: manifestEndpoint,
|
|
|
|
username: username,
|
|
|
|
imageName: imageName,
|
|
|
|
password: password,
|
|
|
|
tagName: tagName,
|
|
|
|
config: config,
|
2020-06-16 20:52:40 -05:00
|
|
|
}
|
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
wtgrp.Add(1)
|
|
|
|
pool.submitJob(&job)
|
2020-06-16 20:52:40 -05:00
|
|
|
}
|
|
|
|
|
2020-07-06 17:44:32 -05:00
|
|
|
type cveResult struct {
|
2023-05-25 13:27:49 -05:00
|
|
|
Errors []common.ErrorGQL `json:"errors"`
|
|
|
|
Data cveData `json:"data"`
|
2020-07-06 17:44:32 -05:00
|
|
|
}
|
2021-12-13 14:23:31 -05:00
|
|
|
|
2022-01-19 10:57:10 -05:00
|
|
|
type tagListResp struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
Tags []string `json:"tags"`
|
|
|
|
}
|
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
//nolint:tagliatelle // graphQL schema
|
2020-07-06 17:44:32 -05:00
|
|
|
type packageList struct {
|
|
|
|
Name string `json:"Name"`
|
|
|
|
InstalledVersion string `json:"InstalledVersion"`
|
|
|
|
FixedVersion string `json:"FixedVersion"`
|
|
|
|
}
|
2021-12-13 14:23:31 -05:00
|
|
|
|
|
|
|
//nolint:tagliatelle // graphQL schema
|
2020-07-06 17:44:32 -05:00
|
|
|
type cve struct {
|
|
|
|
ID string `json:"Id"`
|
|
|
|
Severity string `json:"Severity"`
|
|
|
|
Title string `json:"Title"`
|
|
|
|
Description string `json:"Description"`
|
|
|
|
PackageList []packageList `json:"PackageList"`
|
|
|
|
}
|
2021-12-13 14:23:31 -05:00
|
|
|
|
|
|
|
//nolint:tagliatelle // graphQL schema
|
2020-07-06 17:44:32 -05:00
|
|
|
type cveListForImage struct {
|
|
|
|
Tag string `json:"Tag"`
|
|
|
|
CVEList []cve `json:"CVEList"`
|
|
|
|
}
|
2021-12-13 14:23:31 -05:00
|
|
|
|
|
|
|
//nolint:tagliatelle // graphQL schema
|
2020-07-06 17:44:32 -05:00
|
|
|
type cveData struct {
|
|
|
|
CVEListForImage cveListForImage `json:"CVEListForImage"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cve cveResult) string(format string) (string, error) {
|
|
|
|
switch strings.ToLower(format) {
|
|
|
|
case "", defaultOutoutFormat:
|
|
|
|
return cve.stringPlainText()
|
|
|
|
case "json":
|
|
|
|
return cve.stringJSON()
|
|
|
|
case "yml", "yaml":
|
|
|
|
return cve.stringYAML()
|
|
|
|
default:
|
|
|
|
return "", ErrInvalidOutputFormat
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cve cveResult) stringPlainText() (string, error) {
|
|
|
|
var builder strings.Builder
|
|
|
|
|
|
|
|
table := getCVETableWriter(&builder)
|
|
|
|
|
|
|
|
for _, c := range cve.Data.CVEListForImage.CVEList {
|
2022-01-19 10:57:10 -05:00
|
|
|
id := ellipsize(c.ID, cveIDWidth, ellipsis)
|
2020-07-06 17:44:32 -05:00
|
|
|
title := ellipsize(c.Title, cveTitleWidth, ellipsis)
|
|
|
|
severity := ellipsize(c.Severity, cveSeverityWidth, ellipsis)
|
2021-12-13 14:23:31 -05:00
|
|
|
row := make([]string, 3) //nolint:gomnd
|
2020-07-06 17:44:32 -05:00
|
|
|
row[colCVEIDIndex] = id
|
|
|
|
row[colCVESeverityIndex] = severity
|
|
|
|
row[colCVETitleIndex] = title
|
|
|
|
|
|
|
|
table.Append(row)
|
|
|
|
}
|
|
|
|
|
|
|
|
table.Render()
|
|
|
|
|
|
|
|
return builder.String(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cve cveResult) stringJSON() (string, error) {
|
2021-12-13 14:23:31 -05:00
|
|
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
2020-07-06 17:44:32 -05:00
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
body, err := json.MarshalIndent(cve.Data.CVEListForImage, "", " ")
|
2020-07-06 17:44:32 -05:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(body), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cve cveResult) stringYAML() (string, error) {
|
|
|
|
body, err := yaml.Marshal(&cve.Data.CVEListForImage)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(body), nil
|
|
|
|
}
|
|
|
|
|
2023-05-25 13:27:49 -05:00
|
|
|
type imageStruct common.ImageSummary
|
2023-01-25 17:06:02 -05:00
|
|
|
|
2023-05-25 13:27:49 -05:00
|
|
|
func (img imageStruct) string(format string, maxImgNameLen, maxTagLen, maxPlatformLen int, verbose bool) (string, error) { //nolint: lll
|
2020-06-16 20:52:40 -05:00
|
|
|
switch strings.ToLower(format) {
|
2020-07-06 17:44:32 -05:00
|
|
|
case "", defaultOutoutFormat:
|
2023-05-25 13:27:49 -05:00
|
|
|
return img.stringPlainText(maxImgNameLen, maxTagLen, maxPlatformLen, verbose)
|
2020-06-16 20:52:40 -05:00
|
|
|
case "json":
|
|
|
|
return img.stringJSON()
|
|
|
|
case "yml", "yaml":
|
|
|
|
return img.stringYAML()
|
|
|
|
default:
|
|
|
|
return "", ErrInvalidOutputFormat
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-25 13:27:49 -05:00
|
|
|
func (img imageStruct) stringPlainText(maxImgNameLen, maxTagLen, maxPlatformLen int, verbose bool) (string, error) {
|
2020-06-16 20:52:40 -05:00
|
|
|
var builder strings.Builder
|
|
|
|
|
2020-07-06 17:44:32 -05:00
|
|
|
table := getImageTableWriter(&builder)
|
2022-10-11 10:56:03 -05:00
|
|
|
|
|
|
|
table.SetColMinWidth(colImageNameIndex, maxImgNameLen)
|
|
|
|
table.SetColMinWidth(colTagIndex, maxTagLen)
|
2023-02-27 14:23:18 -05:00
|
|
|
table.SetColMinWidth(colPlatformIndex, platformWidth)
|
2021-05-28 11:27:17 -05:00
|
|
|
table.SetColMinWidth(colDigestIndex, digestWidth)
|
|
|
|
table.SetColMinWidth(colSizeIndex, sizeWidth)
|
2022-10-20 11:35:24 -05:00
|
|
|
table.SetColMinWidth(colIsSignedIndex, isSignedWidth)
|
2021-05-28 11:27:17 -05:00
|
|
|
|
2023-05-25 13:27:49 -05:00
|
|
|
if verbose {
|
2021-05-28 11:27:17 -05:00
|
|
|
table.SetColMinWidth(colConfigIndex, configWidth)
|
|
|
|
table.SetColMinWidth(colLayersIndex, layersWidth)
|
|
|
|
}
|
2020-06-16 20:52:40 -05:00
|
|
|
|
2022-10-11 10:56:03 -05:00
|
|
|
var imageName, tagName string
|
|
|
|
|
|
|
|
imageName = img.RepoName
|
|
|
|
tagName = img.Tag
|
2022-10-22 15:46:13 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
if imageNameWidth > maxImgNameLen {
|
|
|
|
maxImgNameLen = imageNameWidth
|
2022-10-22 15:46:13 -05:00
|
|
|
}
|
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
if tagWidth > maxTagLen {
|
|
|
|
maxTagLen = tagWidth
|
2022-10-22 15:46:13 -05:00
|
|
|
}
|
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
// adding spaces so that image name and tag columns are aligned
|
|
|
|
// in case the name/tag are fully shown and too long
|
|
|
|
var offset string
|
|
|
|
if maxImgNameLen > len(imageName) {
|
|
|
|
offset = strings.Repeat(" ", maxImgNameLen-len(imageName))
|
|
|
|
imageName += offset
|
|
|
|
}
|
2020-06-16 20:52:40 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
if maxTagLen > len(tagName) {
|
|
|
|
offset = strings.Repeat(" ", maxTagLen-len(tagName))
|
|
|
|
tagName += offset
|
2022-01-19 10:57:10 -05:00
|
|
|
}
|
2021-05-28 11:27:17 -05:00
|
|
|
|
2023-05-25 13:27:49 -05:00
|
|
|
err := addImageToTable(table, &img, maxPlatformLen, imageName, tagName, verbose)
|
2023-03-21 12:16:00 -05:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
table.Render()
|
|
|
|
|
|
|
|
return builder.String(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func addImageToTable(table *tablewriter.Table, img *imageStruct, maxPlatformLen int,
|
2023-05-25 13:27:49 -05:00
|
|
|
imageName, tagName string, verbose bool,
|
2023-03-21 12:16:00 -05:00
|
|
|
) error {
|
|
|
|
switch img.MediaType {
|
|
|
|
case ispec.MediaTypeImageManifest:
|
2023-05-25 13:27:49 -05:00
|
|
|
return addManifestToTable(table, imageName, tagName, &img.Manifests[0], maxPlatformLen, verbose)
|
2023-03-21 12:16:00 -05:00
|
|
|
case ispec.MediaTypeImageIndex:
|
2023-05-25 13:27:49 -05:00
|
|
|
return addImageIndexToTable(table, img, maxPlatformLen, imageName, tagName, verbose)
|
2023-03-21 12:16:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func addImageIndexToTable(table *tablewriter.Table, img *imageStruct, maxPlatformLen int,
|
2023-05-25 13:27:49 -05:00
|
|
|
imageName, tagName string, verbose bool,
|
2023-03-21 12:16:00 -05:00
|
|
|
) error {
|
|
|
|
indexDigest, err := godigest.Parse(img.Digest)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error parsing index digest %s: %w", indexDigest, err)
|
|
|
|
}
|
|
|
|
row := make([]string, rowWidth)
|
|
|
|
row[colImageNameIndex] = imageName
|
|
|
|
row[colTagIndex] = tagName
|
|
|
|
row[colDigestIndex] = ellipsize(indexDigest.Encoded(), digestWidth, "")
|
|
|
|
row[colPlatformIndex] = "*"
|
|
|
|
|
|
|
|
imgSize, _ := strconv.ParseUint(img.Size, 10, 64)
|
|
|
|
row[colSizeIndex] = ellipsize(strings.ReplaceAll(humanize.Bytes(imgSize), " ", ""), sizeWidth, ellipsis)
|
|
|
|
row[colIsSignedIndex] = strconv.FormatBool(img.IsSigned)
|
2021-05-28 11:27:17 -05:00
|
|
|
|
2023-05-25 13:27:49 -05:00
|
|
|
if verbose {
|
2023-03-21 12:16:00 -05:00
|
|
|
row[colConfigIndex] = ""
|
|
|
|
row[colLayersIndex] = ""
|
|
|
|
}
|
|
|
|
|
|
|
|
table.Append(row)
|
|
|
|
|
|
|
|
for i := range img.Manifests {
|
2023-05-25 13:27:49 -05:00
|
|
|
err := addManifestToTable(table, "", "", &img.Manifests[i], maxPlatformLen, verbose)
|
2023-02-27 14:23:18 -05:00
|
|
|
if err != nil {
|
2023-03-21 12:16:00 -05:00
|
|
|
return err
|
2023-02-27 14:23:18 -05:00
|
|
|
}
|
2023-03-21 12:16:00 -05:00
|
|
|
}
|
2022-10-22 15:46:13 -05:00
|
|
|
|
2023-03-21 12:16:00 -05:00
|
|
|
return nil
|
|
|
|
}
|
2022-10-22 15:46:13 -05:00
|
|
|
|
2023-05-25 13:27:49 -05:00
|
|
|
func addManifestToTable(table *tablewriter.Table, imageName, tagName string, manifest *common.ManifestSummary,
|
2023-03-21 12:16:00 -05:00
|
|
|
maxPlatformLen int, verbose bool,
|
|
|
|
) error {
|
|
|
|
manifestDigest, err := godigest.Parse(manifest.Digest)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error parsing manifest digest %s: %w", manifest.Digest, err)
|
|
|
|
}
|
2023-02-27 14:23:18 -05:00
|
|
|
|
2023-03-21 12:16:00 -05:00
|
|
|
configDigest, err := godigest.Parse(manifest.ConfigDigest)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error parsing config digest %s: %w", manifest.ConfigDigest, err)
|
|
|
|
}
|
2022-01-19 10:57:10 -05:00
|
|
|
|
2023-03-21 12:16:00 -05:00
|
|
|
platform := getPlatformStr(manifest.Platform)
|
2022-01-19 10:57:10 -05:00
|
|
|
|
2023-03-21 12:16:00 -05:00
|
|
|
if maxPlatformLen > len(platform) {
|
|
|
|
offset := strings.Repeat(" ", maxPlatformLen-len(platform))
|
|
|
|
platform += offset
|
|
|
|
}
|
2023-02-27 14:23:18 -05:00
|
|
|
|
2023-03-21 12:16:00 -05:00
|
|
|
minifestDigestStr := ellipsize(manifestDigest.Encoded(), digestWidth, "")
|
|
|
|
configDigestStr := ellipsize(configDigest.Encoded(), configWidth, "")
|
|
|
|
imgSize, _ := strconv.ParseUint(manifest.Size, 10, 64)
|
|
|
|
size := ellipsize(strings.ReplaceAll(humanize.Bytes(imgSize), " ", ""), sizeWidth, ellipsis)
|
|
|
|
isSigned := manifest.IsSigned
|
|
|
|
row := make([]string, 8) //nolint:gomnd
|
|
|
|
|
|
|
|
row[colImageNameIndex] = imageName
|
|
|
|
row[colTagIndex] = tagName
|
|
|
|
row[colDigestIndex] = minifestDigestStr
|
|
|
|
row[colPlatformIndex] = platform
|
|
|
|
row[colSizeIndex] = size
|
|
|
|
row[colIsSignedIndex] = strconv.FormatBool(isSigned)
|
|
|
|
|
|
|
|
if verbose {
|
|
|
|
row[colConfigIndex] = configDigestStr
|
|
|
|
row[colLayersIndex] = ""
|
|
|
|
}
|
2023-02-27 14:23:18 -05:00
|
|
|
|
2023-03-21 12:16:00 -05:00
|
|
|
table.Append(row)
|
2023-02-27 14:23:18 -05:00
|
|
|
|
2023-03-21 12:16:00 -05:00
|
|
|
if verbose {
|
|
|
|
for _, entry := range manifest.Layers {
|
2023-05-25 13:27:49 -05:00
|
|
|
layerSize, _ := strconv.ParseUint(entry.Size, 10, 64)
|
|
|
|
size := ellipsize(strings.ReplaceAll(humanize.Bytes(layerSize), " ", ""), sizeWidth, ellipsis)
|
2023-02-27 14:23:18 -05:00
|
|
|
|
2023-03-21 12:16:00 -05:00
|
|
|
layerDigest, err := godigest.Parse(entry.Digest)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error parsing layer digest %s: %w", entry.Digest, err)
|
2023-02-27 14:23:18 -05:00
|
|
|
}
|
2023-03-21 12:16:00 -05:00
|
|
|
|
|
|
|
layerDigestStr := ellipsize(layerDigest.Encoded(), digestWidth, "")
|
|
|
|
|
|
|
|
layerRow := make([]string, 8) //nolint:gomnd
|
|
|
|
layerRow[colImageNameIndex] = ""
|
|
|
|
layerRow[colTagIndex] = ""
|
|
|
|
layerRow[colDigestIndex] = ""
|
|
|
|
layerRow[colPlatformIndex] = ""
|
|
|
|
layerRow[colSizeIndex] = size
|
|
|
|
layerRow[colConfigIndex] = ""
|
|
|
|
layerRow[colLayersIndex] = layerDigestStr
|
|
|
|
|
|
|
|
table.Append(layerRow)
|
2021-05-28 11:27:17 -05:00
|
|
|
}
|
2020-06-16 20:52:40 -05:00
|
|
|
}
|
|
|
|
|
2023-03-21 12:16:00 -05:00
|
|
|
return nil
|
2020-06-16 20:52:40 -05:00
|
|
|
}
|
|
|
|
|
2023-05-25 13:27:49 -05:00
|
|
|
func getPlatformStr(platf common.Platform) string {
|
2023-02-27 14:23:18 -05:00
|
|
|
if platf.Arch == "" && platf.Os == "" {
|
2023-03-21 12:16:00 -05:00
|
|
|
return ""
|
2023-02-27 14:23:18 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
platform := platf.Os
|
|
|
|
|
|
|
|
if platf.Arch != "" {
|
|
|
|
platform = platform + "/" + platf.Arch
|
|
|
|
platform = strings.Trim(platform, "/")
|
|
|
|
|
|
|
|
if platf.Variant != "" {
|
|
|
|
platform = platform + "/" + platf.Variant
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return platform
|
|
|
|
}
|
|
|
|
|
2020-06-16 20:52:40 -05:00
|
|
|
func (img imageStruct) stringJSON() (string, error) {
|
2021-12-13 14:23:31 -05:00
|
|
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
2020-06-16 20:52:40 -05:00
|
|
|
|
2021-12-13 14:23:31 -05:00
|
|
|
body, err := json.MarshalIndent(img, "", " ")
|
2020-06-16 20:52:40 -05:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(body), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (img imageStruct) stringYAML() (string, error) {
|
|
|
|
body, err := yaml.Marshal(&img)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(body), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type catalogResponse struct {
|
|
|
|
Repositories []string `json:"repositories"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func combineServerAndEndpointURL(serverURL, endPoint string) (string, error) {
|
|
|
|
if !isURL(serverURL) {
|
|
|
|
return "", zotErrors.ErrInvalidURL
|
|
|
|
}
|
|
|
|
|
|
|
|
newURL, err := url.Parse(serverURL)
|
|
|
|
if err != nil {
|
|
|
|
return "", zotErrors.ErrInvalidURL
|
|
|
|
}
|
|
|
|
|
|
|
|
newURL, _ = newURL.Parse(endPoint)
|
|
|
|
|
|
|
|
return newURL.String(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func ellipsize(text string, max int, trailing string) string {
|
2020-07-06 17:44:32 -05:00
|
|
|
text = strings.TrimSpace(text)
|
2020-06-16 20:52:40 -05:00
|
|
|
if len(text) <= max {
|
|
|
|
return text
|
|
|
|
}
|
|
|
|
|
|
|
|
chopLength := len(trailing)
|
|
|
|
|
|
|
|
return text[:max-chopLength] + trailing
|
|
|
|
}
|
|
|
|
|
2020-07-06 17:44:32 -05:00
|
|
|
func getImageTableWriter(writer io.Writer) *tablewriter.Table {
|
2020-06-16 20:52:40 -05:00
|
|
|
table := tablewriter.NewWriter(writer)
|
|
|
|
|
|
|
|
table.SetAutoWrapText(false)
|
|
|
|
table.SetAutoFormatHeaders(true)
|
|
|
|
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
|
|
|
|
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
|
|
table.SetCenterSeparator("")
|
|
|
|
table.SetColumnSeparator("")
|
|
|
|
table.SetRowSeparator("")
|
|
|
|
table.SetHeaderLine(false)
|
|
|
|
table.SetBorder(false)
|
|
|
|
table.SetTablePadding(" ")
|
|
|
|
table.SetNoWhiteSpace(true)
|
|
|
|
|
|
|
|
return table
|
|
|
|
}
|
|
|
|
|
2020-07-06 17:44:32 -05:00
|
|
|
func getCVETableWriter(writer io.Writer) *tablewriter.Table {
|
|
|
|
table := tablewriter.NewWriter(writer)
|
|
|
|
|
|
|
|
table.SetAutoWrapText(false)
|
|
|
|
table.SetAutoFormatHeaders(true)
|
|
|
|
table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
|
|
|
|
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
|
|
|
table.SetCenterSeparator("")
|
|
|
|
table.SetColumnSeparator("")
|
|
|
|
table.SetRowSeparator("")
|
|
|
|
table.SetHeaderLine(false)
|
|
|
|
table.SetBorder(false)
|
|
|
|
table.SetTablePadding(" ")
|
|
|
|
table.SetNoWhiteSpace(true)
|
2022-01-19 10:57:10 -05:00
|
|
|
table.SetColMinWidth(colCVEIDIndex, cveIDWidth)
|
2020-07-06 17:44:32 -05:00
|
|
|
table.SetColMinWidth(colCVESeverityIndex, cveSeverityWidth)
|
|
|
|
table.SetColMinWidth(colCVETitleIndex, cveTitleWidth)
|
|
|
|
|
|
|
|
return table
|
|
|
|
}
|
|
|
|
|
2022-06-02 09:14:21 -05:00
|
|
|
func (service searchService) getRepos(ctx context.Context, config searchConfig, username, password string,
|
|
|
|
rch chan stringResult, wtgrp *sync.WaitGroup,
|
|
|
|
) {
|
|
|
|
defer wtgrp.Done()
|
|
|
|
defer close(rch)
|
|
|
|
|
|
|
|
catalog := &catalogResponse{}
|
|
|
|
|
|
|
|
catalogEndPoint, err := combineServerAndEndpointURL(*config.servURL, fmt.Sprintf("%s%s",
|
|
|
|
constants.RoutePrefix, constants.ExtCatalogPrefix))
|
|
|
|
if err != nil {
|
|
|
|
if isContextDone(ctx) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
rch <- stringResult{"", err}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-09-23 11:24:01 -05:00
|
|
|
_, err = makeGETRequest(ctx, catalogEndPoint, username, password, *config.verifyTLS,
|
|
|
|
*config.debug, catalog, config.resultWriter)
|
2022-06-02 09:14:21 -05:00
|
|
|
if err != nil {
|
|
|
|
if isContextDone(ctx) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
rch <- stringResult{"", err}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Fprintln(config.resultWriter, "\n\nREPOSITORY NAME")
|
|
|
|
|
|
|
|
for _, repo := range catalog.Repositories {
|
|
|
|
fmt.Fprintln(config.resultWriter, repo)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-16 20:52:40 -05:00
|
|
|
const (
|
2023-02-27 14:23:18 -05:00
|
|
|
imageNameWidth = 10
|
|
|
|
tagWidth = 8
|
2020-06-16 20:52:40 -05:00
|
|
|
digestWidth = 8
|
2023-02-27 14:23:18 -05:00
|
|
|
platformWidth = 14
|
2020-06-16 20:52:40 -05:00
|
|
|
sizeWidth = 8
|
2022-10-20 11:35:24 -05:00
|
|
|
isSignedWidth = 8
|
2021-05-28 11:27:17 -05:00
|
|
|
configWidth = 8
|
|
|
|
layersWidth = 8
|
2020-06-16 20:52:40 -05:00
|
|
|
ellipsis = "..."
|
|
|
|
|
2022-01-19 10:57:10 -05:00
|
|
|
cveIDWidth = 16
|
2020-07-06 17:44:32 -05:00
|
|
|
cveSeverityWidth = 8
|
|
|
|
cveTitleWidth = 48
|
|
|
|
|
|
|
|
colCVEIDIndex = 0
|
|
|
|
colCVESeverityIndex = 1
|
|
|
|
colCVETitleIndex = 2
|
|
|
|
|
|
|
|
defaultOutoutFormat = "text"
|
2020-06-16 20:52:40 -05:00
|
|
|
)
|
2023-03-21 12:16:00 -05:00
|
|
|
|
|
|
|
const (
|
|
|
|
colImageNameIndex = iota
|
|
|
|
colTagIndex
|
|
|
|
colPlatformIndex
|
|
|
|
colDigestIndex
|
|
|
|
colConfigIndex
|
|
|
|
colIsSignedIndex
|
|
|
|
colLayersIndex
|
|
|
|
colSizeIndex
|
|
|
|
|
|
|
|
rowWidth
|
|
|
|
)
|