feat(cli): updated display format for multiarch images (#1268)

Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
This commit is contained in:
LaurentiuNiculae
2023-03-21 19:16:00 +02:00
committed by GitHub
parent 0036d6dd09
commit 21b7c69fd9
12 changed files with 734 additions and 184 deletions
@@ -6544,6 +6544,8 @@ func TestImageSummary(t *testing.T) {
Image(image:"%s:%s"){
RepoName
Tag
Digest
MediaType
Manifests {
Digest
ConfigDigest
@@ -6569,6 +6571,8 @@ func TestImageSummary(t *testing.T) {
Image(image:"%s"){
RepoName,
Tag,
Digest,
MediaType,
Manifests {
Digest
ConfigDigest
@@ -6679,6 +6683,8 @@ func TestImageSummary(t *testing.T) {
imgSummary := imgSummaryResponse.SingleImageSummary.ImageSummary
So(imgSummary.RepoName, ShouldContainSubstring, repoName)
So(imgSummary.Tag, ShouldContainSubstring, tagTarget)
So(imgSummary.Digest, ShouldContainSubstring, manifestDigest.Encoded())
So(imgSummary.MediaType, ShouldContainSubstring, ispec.MediaTypeImageManifest)
So(imgSummary.Manifests[0].ConfigDigest, ShouldContainSubstring, image.Manifest.Config.Digest.Encoded())
So(imgSummary.Manifests[0].Digest, ShouldContainSubstring, manifestDigest.Encoded())
So(len(imgSummary.Manifests[0].Layers), ShouldEqual, 1)
+2
View File
@@ -22,6 +22,8 @@ type RepoSummary struct {
type ImageSummary struct {
RepoName string `json:"repoName"`
Tag string `json:"tag"`
Digest string `json:"digest"`
MediaType string `json:"mediaType"`
Manifests []ManifestSummary `json:"manifests"`
Size string `json:"size"`
DownloadCount int `json:"downloadCount"`
+50 -2
View File
@@ -132,8 +132,16 @@ func TestConvertErrors(t *testing.T) {
Convey("ImageManifest2ImageSummary", t, func() {
ctx := graphql.WithResponseContext(context.Background(),
graphql.DefaultErrorPresenter, graphql.DefaultRecover)
configBlob, err := json.Marshal(ispec.Image{
Platform: ispec.Platform{
OS: "os",
Architecture: "arch",
Variant: "var",
},
})
So(err, ShouldBeNil)
_, _, err := convert.ImageManifest2ImageSummary(
_, _, err = convert.ImageManifest2ImageSummary(
ctx,
"repo",
"tag",
@@ -142,7 +150,7 @@ func TestConvertErrors(t *testing.T) {
repodb.RepoMetadata{},
repodb.ManifestMetadata{
ManifestBlob: []byte("{}"),
ConfigBlob: []byte("{}"),
ConfigBlob: configBlob,
},
mocks.CveInfoMock{
GetCVESummaryForImageFn: func(repo, reference string,
@@ -168,6 +176,12 @@ func TestConvertErrors(t *testing.T) {
MediaType: ispec.MediaTypeImageManifest,
},
false,
repodb.RepoMetadata{
Tags: map[string]repodb.Descriptor{},
Statistics: map[string]repodb.DescriptorStatistics{},
Signatures: map[string]repodb.ManifestSignatures{},
Referrers: map[string][]repodb.ReferrerInfo{},
},
repodb.ManifestMetadata{
ManifestBlob: []byte("{}"),
ConfigBlob: []byte("bad json"),
@@ -187,6 +201,7 @@ func TestConvertErrors(t *testing.T) {
Platform: ispec.Platform{
OS: "os",
Architecture: "arch",
Variant: "var",
},
})
So(err, ShouldBeNil)
@@ -200,6 +215,12 @@ func TestConvertErrors(t *testing.T) {
MediaType: ispec.MediaTypeImageManifest,
},
false,
repodb.RepoMetadata{
Tags: map[string]repodb.Descriptor{},
Statistics: map[string]repodb.DescriptorStatistics{},
Signatures: map[string]repodb.ManifestSignatures{"dig": {"cosine": []repodb.SignatureInfo{{}}}},
Referrers: map[string][]repodb.ReferrerInfo{},
},
repodb.ManifestMetadata{
ManifestBlob: []byte("{}"),
ConfigBlob: configBlob,
@@ -245,6 +266,33 @@ func TestConvertErrors(t *testing.T) {
}, log.NewLogger("debug", ""),
)
So(len(imageSummaries), ShouldEqual, 0)
// cveInfo present no error
_, imageSummaries = convert.RepoMeta2ExpandedRepoInfo(
ctx,
repodb.RepoMetadata{
Tags: map[string]repodb.Descriptor{
"tag1": {Digest: "dig", MediaType: ispec.MediaTypeImageManifest},
},
},
map[string]repodb.ManifestMetadata{
"dig": {
ManifestBlob: []byte("{}"),
ConfigBlob: []byte("{}"),
},
},
map[string]repodb.IndexData{},
convert.SkipQGLField{
Vulnerabilities: false,
},
mocks.CveInfoMock{
GetCVESummaryForImageFn: func(repo, reference string,
) (cveinfo.ImageCVESummary, error) {
return cveinfo.ImageCVESummary{}, ErrTestError
},
}, log.NewLogger("debug", ""),
)
So(len(imageSummaries), ShouldEqual, 1)
})
}
+22 -4
View File
@@ -189,11 +189,14 @@ func ImageIndex2ImageSummary(ctx context.Context, repo, tag string, indexDigest
maxSeverity string
manifestSummaries = make([]*gql_generated.ManifestSummary, 0, len(indexContent.Manifests))
indexBlobs = make(map[string]int64, 0)
indexDigestStr = indexDigest.String()
indexMediaType = ispec.MediaTypeImageIndex
)
for _, descriptor := range indexContent.Manifests {
manifestSummary, manifestBlobs, err := ImageManifest2ManifestSummary(ctx, repo, tag, descriptor, false,
manifestMetaMap[descriptor.Digest.String()], repoMeta.Referrers[descriptor.Digest.String()], cveInfo)
repoMeta, manifestMetaMap[descriptor.Digest.String()], repoMeta.Referrers[descriptor.Digest.String()], cveInfo)
if err != nil {
return &gql_generated.ImageSummary{}, map[string]int64{}, err
}
@@ -244,6 +247,8 @@ func ImageIndex2ImageSummary(ctx context.Context, repo, tag string, indexDigest
indexSummary := gql_generated.ImageSummary{
RepoName: &repo,
Tag: &tag,
Digest: &indexDigestStr,
MediaType: &indexMediaType,
Manifests: manifestSummaries,
LastUpdated: &indexLastUpdated,
IsSigned: &isSigned,
@@ -272,6 +277,7 @@ func ImageManifest2ImageSummary(ctx context.Context, repo, tag string, digest go
var (
manifestContent ispec.Manifest
manifestDigest = digest.String()
mediaType = ispec.MediaTypeImageManifest
)
err := json.Unmarshal(manifestMeta.ManifestBlob, &manifestContent)
@@ -349,14 +355,17 @@ func ImageManifest2ImageSummary(ctx context.Context, repo, tag string, digest go
}
imageSummary := gql_generated.ImageSummary{
RepoName: &repoName,
Tag: &tag,
RepoName: &repoName,
Tag: &tag,
Digest: &manifestDigest,
MediaType: &mediaType,
Manifests: []*gql_generated.ManifestSummary{
{
Digest: &manifestDigest,
ConfigDigest: &configDigest,
LastUpdated: &imageLastUpdated,
Size: &imageSize,
IsSigned: &isSigned,
Platform: &platform,
DownloadCount: &downloadCount,
Layers: getLayersSummaries(manifestContent),
@@ -424,7 +433,8 @@ func getAnnotationsFromMap(annotationsMap map[string]string) []*gql_generated.An
}
func ImageManifest2ManifestSummary(ctx context.Context, repo, tag string, descriptor ispec.Descriptor,
skipCVE bool, manifestMeta repodb.ManifestMetadata, referrersInfo []repodb.ReferrerInfo, cveInfo cveinfo.CveInfo,
skipCVE bool, repoMeta repodb.RepoMetadata, manifestMeta repodb.ManifestMetadata, referrersInfo []repodb.ReferrerInfo,
cveInfo cveinfo.CveInfo,
) (*gql_generated.ManifestSummary, map[string]int64, error) {
var (
manifestContent ispec.Manifest
@@ -456,6 +466,7 @@ func ImageManifest2ManifestSummary(ctx context.Context, repo, tag string, descri
configSize = manifestContent.Config.Size
imageLastUpdated = common.GetImageLastUpdated(configContent)
downloadCount = manifestMeta.DownloadCount
isSigned = false
)
opSys := configContent.OS
@@ -492,6 +503,12 @@ func ImageManifest2ManifestSummary(ctx context.Context, repo, tag string, descri
}
}
for _, signatures := range repoMeta.Signatures[manifestDigestStr] {
if len(signatures) > 0 {
isSigned = true
}
}
manifestSummary := gql_generated.ManifestSummary{
Digest: &manifestDigestStr,
ConfigDigest: &configDigest,
@@ -501,6 +518,7 @@ func ImageManifest2ManifestSummary(ctx context.Context, repo, tag string, descri
DownloadCount: &downloadCount,
Layers: getLayersSummaries(manifestContent),
History: historyEntries,
IsSigned: &isSigned,
Vulnerabilities: &gql_generated.ImageVulnerabilitySummary{
MaxSeverity: &imageCveSummary.MaxSeverity,
Count: &imageCveSummary.Count,
@@ -79,6 +79,7 @@ type ComplexityRoot struct {
ImageSummary struct {
Authors func(childComplexity int) int
Description func(childComplexity int) int
Digest func(childComplexity int) int
Documentation func(childComplexity int) int
DownloadCount func(childComplexity int) int
IsSigned func(childComplexity int) int
@@ -86,6 +87,7 @@ type ComplexityRoot struct {
LastUpdated func(childComplexity int) int
Licenses func(childComplexity int) int
Manifests func(childComplexity int) int
MediaType func(childComplexity int) int
Referrers func(childComplexity int) int
RepoName func(childComplexity int) int
Score func(childComplexity int) int
@@ -118,6 +120,7 @@ type ComplexityRoot struct {
Digest func(childComplexity int) int
DownloadCount func(childComplexity int) int
History func(childComplexity int) int
IsSigned func(childComplexity int) int
LastUpdated func(childComplexity int) int
Layers func(childComplexity int) int
Platform func(childComplexity int) int
@@ -372,6 +375,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.ImageSummary.Description(childComplexity), true
case "ImageSummary.Digest":
if e.complexity.ImageSummary.Digest == nil {
break
}
return e.complexity.ImageSummary.Digest(childComplexity), true
case "ImageSummary.Documentation":
if e.complexity.ImageSummary.Documentation == nil {
break
@@ -421,6 +431,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.ImageSummary.Manifests(childComplexity), true
case "ImageSummary.MediaType":
if e.complexity.ImageSummary.MediaType == nil {
break
}
return e.complexity.ImageSummary.MediaType(childComplexity), true
case "ImageSummary.Referrers":
if e.complexity.ImageSummary.Referrers == nil {
break
@@ -561,6 +578,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.ManifestSummary.History(childComplexity), true
case "ManifestSummary.IsSigned":
if e.complexity.ManifestSummary.IsSigned == nil {
break
}
return e.complexity.ManifestSummary.IsSigned(childComplexity), true
case "ManifestSummary.LastUpdated":
if e.complexity.ManifestSummary.LastUpdated == nil {
break
@@ -1131,6 +1155,14 @@ type ImageSummary {
"""
Tag: String
"""
The digest of the descriptor of this image
"""
Digest: String
"""
The media type of the descriptor of this image
"""
MediaType: String
"""
List of manifests for all supported versions of the image for different operating systems and architectures
"""
Manifests: [ManifestSummary]
@@ -1217,6 +1249,10 @@ type ManifestSummary {
"""
Size: String
"""
True if the manifest has a signature associated with it, false otherwise
"""
IsSigned: Boolean
"""
OS and architecture supported by this image
"""
Platform: Platform
@@ -2587,6 +2623,10 @@ func (ec *executionContext) fieldContext_GlobalSearchResult_Images(ctx context.C
return ec.fieldContext_ImageSummary_RepoName(ctx, field)
case "Tag":
return ec.fieldContext_ImageSummary_Tag(ctx, field)
case "Digest":
return ec.fieldContext_ImageSummary_Digest(ctx, field)
case "MediaType":
return ec.fieldContext_ImageSummary_MediaType(ctx, field)
case "Manifests":
return ec.fieldContext_ImageSummary_Manifests(ctx, field)
case "Size":
@@ -3027,6 +3067,88 @@ func (ec *executionContext) fieldContext_ImageSummary_Tag(ctx context.Context, f
return fc, nil
}
func (ec *executionContext) _ImageSummary_Digest(ctx context.Context, field graphql.CollectedField, obj *ImageSummary) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_ImageSummary_Digest(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Digest, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_ImageSummary_Digest(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "ImageSummary",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type String does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _ImageSummary_MediaType(ctx context.Context, field graphql.CollectedField, obj *ImageSummary) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_ImageSummary_MediaType(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.MediaType, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_ImageSummary_MediaType(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "ImageSummary",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type String does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _ImageSummary_Manifests(ctx context.Context, field graphql.CollectedField, obj *ImageSummary) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_ImageSummary_Manifests(ctx, field)
if err != nil {
@@ -3071,6 +3193,8 @@ func (ec *executionContext) fieldContext_ImageSummary_Manifests(ctx context.Cont
return ec.fieldContext_ManifestSummary_LastUpdated(ctx, field)
case "Size":
return ec.fieldContext_ManifestSummary_Size(ctx, field)
case "IsSigned":
return ec.fieldContext_ManifestSummary_IsSigned(ctx, field)
case "Platform":
return ec.fieldContext_ManifestSummary_Platform(ctx, field)
case "DownloadCount":
@@ -4194,6 +4318,47 @@ func (ec *executionContext) fieldContext_ManifestSummary_Size(ctx context.Contex
return fc, nil
}
func (ec *executionContext) _ManifestSummary_IsSigned(ctx context.Context, field graphql.CollectedField, obj *ManifestSummary) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_ManifestSummary_IsSigned(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.IsSigned, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*bool)
fc.Result = res
return ec.marshalOBoolean2ᚖbool(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_ManifestSummary_IsSigned(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "ManifestSummary",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Boolean does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _ManifestSummary_Platform(ctx context.Context, field graphql.CollectedField, obj *ManifestSummary) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_ManifestSummary_Platform(ctx, field)
if err != nil {
@@ -4779,6 +4944,10 @@ func (ec *executionContext) fieldContext_PaginatedImagesResult_Results(ctx conte
return ec.fieldContext_ImageSummary_RepoName(ctx, field)
case "Tag":
return ec.fieldContext_ImageSummary_Tag(ctx, field)
case "Digest":
return ec.fieldContext_ImageSummary_Digest(ctx, field)
case "MediaType":
return ec.fieldContext_ImageSummary_MediaType(ctx, field)
case "Manifests":
return ec.fieldContext_ImageSummary_Manifests(ctx, field)
case "Size":
@@ -5663,6 +5832,10 @@ func (ec *executionContext) fieldContext_Query_Image(ctx context.Context, field
return ec.fieldContext_ImageSummary_RepoName(ctx, field)
case "Tag":
return ec.fieldContext_ImageSummary_Tag(ctx, field)
case "Digest":
return ec.fieldContext_ImageSummary_Digest(ctx, field)
case "MediaType":
return ec.fieldContext_ImageSummary_MediaType(ctx, field)
case "Manifests":
return ec.fieldContext_ImageSummary_Manifests(ctx, field)
case "Size":
@@ -6160,6 +6333,10 @@ func (ec *executionContext) fieldContext_RepoInfo_Images(ctx context.Context, fi
return ec.fieldContext_ImageSummary_RepoName(ctx, field)
case "Tag":
return ec.fieldContext_ImageSummary_Tag(ctx, field)
case "Digest":
return ec.fieldContext_ImageSummary_Digest(ctx, field)
case "MediaType":
return ec.fieldContext_ImageSummary_MediaType(ctx, field)
case "Manifests":
return ec.fieldContext_ImageSummary_Manifests(ctx, field)
case "Size":
@@ -6556,6 +6733,10 @@ func (ec *executionContext) fieldContext_RepoSummary_NewestImage(ctx context.Con
return ec.fieldContext_ImageSummary_RepoName(ctx, field)
case "Tag":
return ec.fieldContext_ImageSummary_Tag(ctx, field)
case "Digest":
return ec.fieldContext_ImageSummary_Digest(ctx, field)
case "MediaType":
return ec.fieldContext_ImageSummary_MediaType(ctx, field)
case "Manifests":
return ec.fieldContext_ImageSummary_Manifests(ctx, field)
case "Size":
@@ -8827,6 +9008,14 @@ func (ec *executionContext) _ImageSummary(ctx context.Context, sel ast.Selection
out.Values[i] = ec._ImageSummary_Tag(ctx, field, obj)
case "Digest":
out.Values[i] = ec._ImageSummary_Digest(ctx, field, obj)
case "MediaType":
out.Values[i] = ec._ImageSummary_MediaType(ctx, field, obj)
case "Manifests":
out.Values[i] = ec._ImageSummary_Manifests(ctx, field, obj)
@@ -9019,6 +9208,10 @@ func (ec *executionContext) _ManifestSummary(ctx context.Context, sel ast.Select
out.Values[i] = ec._ManifestSummary_Size(ctx, field, obj)
case "IsSigned":
out.Values[i] = ec._ManifestSummary_IsSigned(ctx, field, obj)
case "Platform":
out.Values[i] = ec._ManifestSummary_Platform(ctx, field, obj)
@@ -91,6 +91,10 @@ type ImageSummary struct {
RepoName *string `json:"RepoName"`
// Tag identifying the image within the repository
Tag *string `json:"Tag"`
// The digest of the descriptor of this image
Digest *string `json:"Digest"`
// The media type of the descriptor of this image
MediaType *string `json:"MediaType"`
// List of manifests for all supported versions of the image for different operating systems and architectures
Manifests []*ManifestSummary `json:"Manifests"`
// Total size of the files associated with all images (manifest, config, layers)
@@ -162,6 +166,8 @@ type ManifestSummary struct {
LastUpdated *time.Time `json:"LastUpdated"`
// Total size of the files associated with this manifest (manifest, config, layers)
Size *string `json:"Size"`
// True if the manifest has a signature associated with it, false otherwise
IsSigned *bool `json:"IsSigned"`
// OS and architecture supported by this image
Platform *Platform `json:"Platform"`
// Total numer of image manifest downloads from this repository
+12
View File
@@ -124,6 +124,14 @@ type ImageSummary {
"""
Tag: String
"""
The digest of the descriptor of this image
"""
Digest: String
"""
The media type of the descriptor of this image
"""
MediaType: String
"""
List of manifests for all supported versions of the image for different operating systems and architectures
"""
Manifests: [ManifestSummary]
@@ -210,6 +218,10 @@ type ManifestSummary {
"""
Size: String
"""
True if the manifest has a signature associated with it, false otherwise
"""
IsSigned: Boolean
"""
OS and architecture supported by this image
"""
Platform: Platform