Implement a way to search for an image by manifest, config or layer digest

```
Usage:
  zot images [config-name] [flags]

Flags:
  -d, --digest string   List images containing a specific manifest, config, or layer digest
[...]
```
This commit is contained in:
Andrei Aaron
2021-05-26 20:22:31 +03:00
committed by Ramkumar Chinchani
parent 97628e69c9
commit 519ea75d9a
14 changed files with 1043 additions and 175 deletions
+87 -42
View File
@@ -29,6 +29,8 @@ type SearchService interface {
channel chan stringResult, wg *sync.WaitGroup)
getImagesByCveID(ctx context.Context, config searchConfig, username, password, cveID string,
channel chan stringResult, wg *sync.WaitGroup)
getImagesByDigest(ctx context.Context, config searchConfig, username, password, digest string,
channel chan stringResult, wg *sync.WaitGroup)
getImageByNameAndCVEID(ctx context.Context, config searchConfig, username, password, imageName, cveID string,
channel chan stringResult, wg *sync.WaitGroup)
getFixedTagsForCVE(ctx context.Context, config searchConfig, username, password, imageName, cveID string,
@@ -147,7 +149,8 @@ func (service searchService) getImagesByCveID(ctx context.Context, config search
cveID)
result := &imagesForCve{}
endPoint, err := combineServerAndEndpointURL(*config.servURL, "/query")
err := service.makeGraphQLQuery(config, username, password, query, result)
if err != nil {
if isContextDone(ctx) {
return
@@ -157,17 +160,7 @@ func (service searchService) getImagesByCveID(ctx context.Context, config search
return
}
err = makeGraphQLRequest(endPoint, query, username, password, *config.verifyTLS, result)
if err != nil {
if isContextDone(ctx) {
return
}
c <- stringResult{"", err}
return
}
if result.Errors != nil {
if result.Errors != nil || err != nil {
var errBuilder strings.Builder
for _, err := range result.Errors {
@@ -200,6 +193,61 @@ func (service searchService) getImagesByCveID(ctx context.Context, config search
localWg.Wait()
}
func (service searchService) getImagesByDigest(ctx context.Context, config searchConfig, username,
password string, digest string, c chan stringResult, wg *sync.WaitGroup) {
defer wg.Done()
defer close(c)
query := fmt.Sprintf(`{ImageListForDigest(id: "%s") {`+`
Name Tags }
}`,
digest)
result := &imagesForDigest{}
err := service.makeGraphQLQuery(config, username, password, query, result)
if err != nil {
if isContextDone(ctx) {
return
}
c <- 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
}
c <- stringResult{"", errors.New(errBuilder.String())} //nolint: goerr113
return
}
var localWg sync.WaitGroup
p := newSmoothRateLimiter(ctx, &localWg, c)
localWg.Add(1)
go p.startRateLimiter()
for _, image := range result.Data.ImageListForDigest {
for _, tag := range image.Tags {
localWg.Add(1)
go addManifestCallToPool(ctx, config, p, username, password, image.Name, tag, c, &localWg)
}
}
localWg.Wait()
}
func (service searchService) getImageByNameAndCVEID(ctx context.Context, config searchConfig, username,
password, imageName, cveID string, c chan stringResult, wg *sync.WaitGroup) {
defer wg.Done()
@@ -211,17 +259,8 @@ func (service searchService) getImageByNameAndCVEID(ctx context.Context, config
cveID)
result := &imagesForCve{}
endPoint, err := combineServerAndEndpointURL(*config.servURL, "/query")
if err != nil {
if isContextDone(ctx) {
return
}
c <- stringResult{"", err}
err := service.makeGraphQLQuery(config, username, password, query, result)
return
}
err = makeGraphQLRequest(endPoint, query, username, password, *config.verifyTLS, result)
if err != nil {
if isContextDone(ctx) {
return
@@ -278,17 +317,8 @@ func (service searchService) getCveByImage(ctx context.Context, config searchCon
`PackageList {Name InstalledVersion FixedVersion}} } }`, imageName)
result := &cveResult{}
endPoint, err := combineServerAndEndpointURL(*config.servURL, "/query")
if err != nil {
if isContextDone(ctx) {
return
}
c <- stringResult{"", err}
err := service.makeGraphQLQuery(config, username, password, query, result)
return
}
err = makeGraphQLRequest(endPoint, query, username, password, *config.verifyTLS, result)
if err != nil {
if isContextDone(ctx) {
return
@@ -372,17 +402,8 @@ func (service searchService) getFixedTagsForCVE(ctx context.Context, config sear
cveID, imageName)
result := &fixedTags{}
endPoint, err := combineServerAndEndpointURL(*config.servURL, "/query")
if err != nil {
if isContextDone(ctx) {
return
}
c <- stringResult{"", err}
err := service.makeGraphQLQuery(config, username, password, query, result)
return
}
err = makeGraphQLRequest(endPoint, query, username, password, *config.verifyTLS, result)
if err != nil {
if isContextDone(ctx) {
return
@@ -423,6 +444,23 @@ func (service searchService) getFixedTagsForCVE(ctx context.Context, config sear
localWg.Wait()
}
// 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(config searchConfig, username, password, query string,
resultPtr interface{}) error {
endPoint, err := combineServerAndEndpointURL(*config.servURL, "/query")
if err != nil {
return err
}
err = makeGraphQLRequest(endPoint, query, username, password, *config.verifyTLS, resultPtr)
if err != nil {
return err
}
return nil
}
func addManifestCallToPool(ctx context.Context, config searchConfig, p *requestsPool, username, password, imageName,
tagName string, c chan stringResult, wg *sync.WaitGroup) {
defer wg.Done()
@@ -555,6 +593,13 @@ type imagesForCve struct {
} `json:"data"`
}
type imagesForDigest struct {
Errors []errorGraphQL `json:"errors"`
Data struct {
ImageListForDigest []tagListResp `json:"ImageListForDigest"`
} `json:"data"`
}
type tagListResp struct {
Name string `json:"name"`
Tags []string `json:"tags"`