0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2025-03-25 02:32:57 -05:00

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
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 734 additions and 184 deletions

View file

@ -326,12 +326,14 @@ func fetchImageIndexStruct(ctx context.Context, job *httpJob) (*imageStruct, err
isNotationSigned(ctx, job.imageName, indexDigest, job.config, job.username, job.password) isNotationSigned(ctx, job.imageName, indexDigest, job.config, job.username, job.password)
return &imageStruct{ return &imageStruct{
verbose: *job.config.verbose,
RepoName: job.imageName, RepoName: job.imageName,
Tag: job.tagName, Tag: job.tagName,
Digest: indexDigest,
MediaType: ispec.MediaTypeImageIndex,
Manifests: manifestList,
Size: strconv.FormatInt(imageSize, 10), Size: strconv.FormatInt(imageSize, 10),
IsSigned: isIndexSigned, IsSigned: isIndexSigned,
Manifests: manifestList, verbose: *job.config.verbose,
}, nil }, nil
} }
@ -351,14 +353,16 @@ func fetchImageManifestStruct(ctx context.Context, job *httpJob) (*imageStruct,
} }
return &imageStruct{ return &imageStruct{
verbose: *job.config.verbose, RepoName: job.imageName,
RepoName: job.imageName, Tag: job.tagName,
Tag: job.tagName, Digest: manifest.Digest,
Size: manifest.Size, MediaType: ispec.MediaTypeImageManifest,
IsSigned: manifest.IsSigned,
Manifests: []manifestStruct{ Manifests: []manifestStruct{
manifest, manifest,
}, },
Size: manifest.Size,
IsSigned: manifest.IsSigned,
verbose: *job.config.verbose,
}, nil }, nil
} }

View file

@ -13,6 +13,7 @@ import (
"testing" "testing"
"github.com/gorilla/mux" "github.com/gorilla/mux"
godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1" ispec "github.com/opencontainers/image-spec/specs-go/v1"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
@ -264,6 +265,7 @@ func TestDoHTTPRequest(t *testing.T) {
vars := mux.Vars(req) vars := mux.Vars(req)
if vars["reference"] == "indexRef" { if vars["reference"] == "indexRef" {
writer.Header().Add("docker-content-digest", godigest.FromString("t").String())
_, err := writer.Write([]byte(` _, err := writer.Write([]byte(`
{ {
"manifests": [ "manifests": [
@ -592,6 +594,7 @@ func TestDoJobErrors(t *testing.T) {
Route: "/v2/{name}/manifests/{reference}", Route: "/v2/{name}/manifests/{reference}",
HandlerFunc: func(w http.ResponseWriter, r *http.Request) { HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", ispec.MediaTypeImageIndex) w.Header().Add("Content-Type", ispec.MediaTypeImageIndex)
w.Header().Add("docker-content-digest", godigest.FromString("t").String())
_, err := w.Write([]byte("")) _, err := w.Write([]byte(""))
if err != nil { if err != nil {
@ -606,6 +609,8 @@ func TestDoJobErrors(t *testing.T) {
vars := mux.Vars(req) vars := mux.Vars(req)
if vars["reference"] == "indexRef" { if vars["reference"] == "indexRef" {
writer.Header().Add("docker-content-digest", godigest.FromString("t").String())
_, err := writer.Write([]byte(`{"manifests": [{"digest": "manifestRef"}]}`)) _, err := writer.Write([]byte(`{"manifests": [{"digest": "manifestRef"}]}`))
if err != nil { if err != nil {
return return
@ -613,6 +618,8 @@ func TestDoJobErrors(t *testing.T) {
} }
if vars["reference"] == "manifestRef" { if vars["reference"] == "manifestRef" {
writer.Header().Add("docker-content-digest", godigest.FromString("t").String())
_, err := writer.Write([]byte(`{"config": {"digest": "confDigest"}}`)) _, err := writer.Write([]byte(`{"config": {"digest": "confDigest"}}`))
if err != nil { if err != nil {
return return

View file

@ -183,7 +183,7 @@ func TestSearchCVECmd(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(strings.TrimSpace(str), ShouldEqual, So(strings.TrimSpace(str), ShouldEqual,
"IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE dummyImageName tag 6e2f80bf os/arch false 123kB") "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE dummyImageName tag os/arch 6e2f80bf false 123kB")
}) })
Convey("Test CVE by name and CVE ID - using shorthand", t, func() { Convey("Test CVE by name and CVE ID - using shorthand", t, func() {
@ -200,7 +200,7 @@ func TestSearchCVECmd(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(strings.TrimSpace(str), ShouldEqual, So(strings.TrimSpace(str), ShouldEqual,
"IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE dummyImageName tag 6e2f80bf os/arch false 123kB") "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE dummyImageName tag os/arch 6e2f80bf false 123kB")
}) })
Convey("Test CVE by image name - in text format", t, func() { Convey("Test CVE by image name - in text format", t, func() {
@ -283,7 +283,7 @@ func TestSearchCVECmd(t *testing.T) {
err := cveCmd.Execute() err := cveCmd.Execute()
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE anImage tag 6e2f80bf os/arch false 123kB") //nolint:lll So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE anImage tag os/arch 6e2f80bf false 123kB") //nolint:lll
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })
@ -328,7 +328,7 @@ func TestSearchCVECmd(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE fixedImage tag 6e2f80bf os/arch false 123kB") //nolint:lll So(strings.TrimSpace(str), ShouldEqual, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE fixedImage tag os/arch 6e2f80bf false 123kB") //nolint:lll
}) })
Convey("Test fixed tags by and image name CVE ID - invalid image name", t, func() { Convey("Test fixed tags by and image name CVE ID - invalid image name", t, func() {
@ -713,7 +713,7 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(str, ShouldEqual, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE zot-cve-test 0.0.1 82836dd7 N/A false 548B") So(str, ShouldEqual, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE zot-cve-test 0.0.1 82836dd7 false 548B")
}) })
Convey("Test images by CVE ID - GQL - invalid CVE ID", t, func() { Convey("Test images by CVE ID - GQL - invalid CVE ID", t, func() {
@ -730,7 +730,7 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(str, ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(str, ShouldNotContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
}) })
Convey("Test images by CVE ID - GQL - invalid output format", t, func() { Convey("Test images by CVE ID - GQL - invalid output format", t, func() {
@ -778,7 +778,7 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(strings.TrimSpace(str), ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(strings.TrimSpace(str), ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
}) })
Convey("Test fixed tags by image name and CVE ID - GQL - random image", t, func() { Convey("Test fixed tags by image name and CVE ID - GQL - random image", t, func() {
@ -795,7 +795,7 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(strings.TrimSpace(str), ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(strings.TrimSpace(str), ShouldNotContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
}) })
Convey("Test fixed tags by image name and CVE ID - GQL - invalid image", t, func() { Convey("Test fixed tags by image name and CVE ID - GQL - invalid image", t, func() {
@ -812,7 +812,7 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(strings.TrimSpace(str), ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(strings.TrimSpace(str), ShouldNotContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
}) })
Convey("Test CVE by name and CVE ID - GQL - positive", t, func() { Convey("Test CVE by name and CVE ID - GQL - positive", t, func() {
@ -829,7 +829,7 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(strings.TrimSpace(str), ShouldEqual, So(strings.TrimSpace(str), ShouldEqual,
"IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE zot-cve-test 0.0.1 82836dd7 N/A false 548B") "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE zot-cve-test 0.0.1 82836dd7 false 548B")
}) })
Convey("Test CVE by name and CVE ID - GQL - invalid name and CVE ID", t, func() { Convey("Test CVE by name and CVE ID - GQL - invalid name and CVE ID", t, func() {
@ -845,7 +845,7 @@ func TestServerCVEResponse(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(strings.TrimSpace(str), ShouldNotContainSubstring, "IMAGE NAME TAG SIGNED SIZE") So(strings.TrimSpace(str), ShouldNotContainSubstring, "IMAGE NAME TAG OS/ARCH SIGNED SIZE")
}) })
Convey("Test CVE by name and CVE ID - GQL - invalid output format", t, func() { Convey("Test CVE by name and CVE ID - GQL - invalid output format", t, func() {
@ -907,7 +907,7 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(str, ShouldEqual, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE zot-cve-test 0.0.1 82836dd7 linux/amd64 false 548B") So(str, ShouldEqual, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE zot-cve-test 0.0.1 linux/amd64 82836dd7 false 548B")
}) })
Convey("Test images by CVE ID - invalid CVE ID", t, func() { Convey("Test images by CVE ID - invalid CVE ID", t, func() {
@ -924,7 +924,7 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(str, ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(str, ShouldNotContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
}) })
Convey("Test fixed tags by and image name CVE ID - positive", t, func() { Convey("Test fixed tags by and image name CVE ID - positive", t, func() {
@ -958,7 +958,7 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(strings.TrimSpace(str), ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(strings.TrimSpace(str), ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
}) })
Convey("Test fixed tags by and image name CVE ID - invalid image", t, func() { Convey("Test fixed tags by and image name CVE ID - invalid image", t, func() {
@ -975,7 +975,7 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str) str = strings.TrimSpace(str)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
So(strings.TrimSpace(str), ShouldNotContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(strings.TrimSpace(str), ShouldNotContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
}) })
Convey("Test CVE by name and CVE ID - positive", t, func() { Convey("Test CVE by name and CVE ID - positive", t, func() {
@ -992,7 +992,7 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(strings.TrimSpace(str), ShouldEqual, So(strings.TrimSpace(str), ShouldEqual,
"IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE zot-cve-test 0.0.1 82836dd7 linux/amd64 false 548B") "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE zot-cve-test 0.0.1 linux/amd64 82836dd7 false 548B")
}) })
Convey("Test CVE by name and CVE ID - invalid name and CVE ID", t, func() { Convey("Test CVE by name and CVE ID - invalid name and CVE ID", t, func() {
@ -1009,7 +1009,7 @@ func TestServerCVEResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(strings.TrimSpace(str), ShouldNotContainSubstring, So(strings.TrimSpace(str), ShouldNotContainSubstring,
"IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
}) })
} }

View file

@ -186,7 +186,7 @@ func TestSearchImageCmd(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(strings.TrimSpace(str), ShouldEqual, So(strings.TrimSpace(str), ShouldEqual,
"IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE dummyImageName tag 6e2f80bf os/arch false 123kB") "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE dummyImageName tag os/arch 6e2f80bf false 123kB")
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })
@ -203,7 +203,7 @@ func TestSearchImageCmd(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(strings.TrimSpace(str), ShouldEqual, So(strings.TrimSpace(str), ShouldEqual,
"IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE dummyImageName tag 6e2f80bf os/arch false 123kB") "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE dummyImageName tag os/arch 6e2f80bf false 123kB")
So(err, ShouldBeNil) So(err, ShouldBeNil)
Convey("using shorthand", func() { Convey("using shorthand", func() {
args := []string{"imagetest", "-n", "dummyImageName", "--url", "someUrlImage"} args := []string{"imagetest", "-n", "dummyImageName", "--url", "someUrlImage"}
@ -219,7 +219,7 @@ func TestSearchImageCmd(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(strings.TrimSpace(str), ShouldEqual, So(strings.TrimSpace(str), ShouldEqual,
"IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE dummyImageName tag 6e2f80bf os/arch false 123kB") "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE dummyImageName tag os/arch 6e2f80bf false 123kB")
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })
}) })
@ -237,7 +237,7 @@ func TestSearchImageCmd(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(strings.TrimSpace(str), ShouldEqual, So(strings.TrimSpace(str), ShouldEqual,
"IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE anImage tag 6e2f80bf os/arch false 123kB") "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE anImage tag os/arch 6e2f80bf false 123kB")
So(err, ShouldBeNil) So(err, ShouldBeNil)
Convey("invalid URL format", func() { Convey("invalid URL format", func() {
@ -330,8 +330,8 @@ func TestSignature(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:1.0 6742241d linux/amd64 true 447B") So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 6742241d true 447B")
t.Log("Test getting all images using rest calls to get catalog and individual manifests") t.Log("Test getting all images using rest calls to get catalog and individual manifests")
cmd = MockNewImageCommand(new(searchService)) cmd = MockNewImageCommand(new(searchService))
@ -343,8 +343,8 @@ func TestSignature(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
str = space.ReplaceAllString(buff.String(), " ") str = space.ReplaceAllString(buff.String(), " ")
actual = strings.TrimSpace(str) actual = strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:1.0 6742241d linux/amd64 true 447B") So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 6742241d true 447B")
err = os.Chdir(currentWorkingDir) err = os.Chdir(currentWorkingDir)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -407,8 +407,8 @@ func TestSignature(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 0.0.1 6742241d linux/amd64 true 447B") So(actual, ShouldContainSubstring, "repo7 0.0.1 linux/amd64 6742241d true 447B")
t.Log("Test getting all images using rest calls to get catalog and individual manifests") t.Log("Test getting all images using rest calls to get catalog and individual manifests")
cmd = MockNewImageCommand(new(searchService)) cmd = MockNewImageCommand(new(searchService))
@ -420,8 +420,8 @@ func TestSignature(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
str = space.ReplaceAllString(buff.String(), " ") str = space.ReplaceAllString(buff.String(), " ")
actual = strings.TrimSpace(str) actual = strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 0.0.1 6742241d linux/amd64 true 447B") So(actual, ShouldContainSubstring, "repo7 0.0.1 linux/amd64 6742241d true 447B")
err = os.Chdir(currentWorkingDir) err = os.Chdir(currentWorkingDir)
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -469,8 +469,8 @@ func TestDerivedImageList(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:1.0 2694fdb0 N/A false 824B") So(actual, ShouldContainSubstring, "repo7 test:1.0 2694fdb0 false 824B")
}) })
Convey("Test derived images list fails", func() { Convey("Test derived images list fails", func() {
@ -542,8 +542,8 @@ func TestBaseImageList(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 3fc80493 N/A false 494B") So(actual, ShouldContainSubstring, "repo7 test:2.0 3fc80493 false 494B")
}) })
Convey("Test base images list fail", func() { Convey("Test base images list fail", func() {
@ -732,7 +732,7 @@ func TestOutputFormat(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
So(strings.TrimSpace(str), ShouldEqual, So(strings.TrimSpace(str), ShouldEqual,
"IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE dummyImageName tag 6e2f80bf os/arch false 123kB") "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE dummyImageName tag os/arch 6e2f80bf false 123kB")
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })
@ -756,7 +756,8 @@ func TestOutputFormat(t *testing.T) {
`"layers": [ { "size": "0", "digest": "sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6" } ], `+ //nolint:lll `"layers": [ { "size": "0", "digest": "sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6" } ], `+ //nolint:lll
`"platform": { "os": "os", "arch": "arch", "variant": "" }, `+ `"platform": { "os": "os", "arch": "arch", "variant": "" }, `+
`"size": "123445", "isSigned": false } ], `+ `"size": "123445", "isSigned": false } ], `+
`"size": "123445", "isSigned": false }`) `"size": "123445", "digest": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", `+
`"mediaType": "application/vnd.oci.image.manifest.v1+json", "isSigned": false }`)
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })
@ -782,7 +783,8 @@ func TestOutputFormat(t *testing.T) {
`layers: - size: 0 digest: sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6 `+ `layers: - size: 0 digest: sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6 `+
`platform: os: os arch: arch variant: "" `+ `platform: os: os arch: arch variant: "" `+
`size: "123445" issigned: false `+ `size: "123445" issigned: false `+
`size: "123445" issigned: false`, `size: "123445" digest: sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 `+
`mediatype: application/vnd.oci.image.manifest.v1+json issigned: false`,
) )
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -811,7 +813,8 @@ func TestOutputFormat(t *testing.T) {
`layers: - size: 0 digest: sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6 `+ `layers: - size: 0 digest: sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6 `+
`platform: os: os arch: arch variant: "" `+ `platform: os: os arch: arch variant: "" `+
`size: "123445" issigned: false `+ `size: "123445" issigned: false `+
`size: "123445" issigned: false`, `size: "123445" digest: sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 `+
`mediatype: application/vnd.oci.image.manifest.v1+json issigned: false`,
) )
So(err, ShouldBeNil) So(err, ShouldBeNil)
}) })
@ -867,9 +870,9 @@ func TestServerResponseGQL(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 linux/amd64 false 492B") So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 883fc0c5 false 492B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 linux/amd64 false 492B") So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 883fc0c5 false 492B")
Convey("Test all images invalid output format", func() { Convey("Test all images invalid output format", func() {
args := []string{"imagetest", "-o", "random"} args := []string{"imagetest", "-o", "random"}
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url)) configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`, url))
@ -900,14 +903,14 @@ func TestServerResponseGQL(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
// Actual cli output should be something similar to (order of images may differ): // Actual cli output should be something similar to (order of images may differ):
// IMAGE NAME TAG DIGEST CONFIG OS/ARCH SIGNED LAYERS SIZE // IMAGE NAME TAG OS/ARCH DIGEST CONFIG SIGNED LAYERS SIZE
// repo7 test:2.0 a0ca253b b8781e88 linux/amd64 false 492B // repo7 test:2.0 linux/amd64 a0ca253b b8781e88 false 492B
// b8781e88 15B // b8781e88 15B
// repo7 test:1.0 a0ca253b b8781e88 linux/amd64 false 492B // repo7 test:1.0 linux/amd64 a0ca253b b8781e88 false 492B
// b8781e88 15B // b8781e88 15B
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST CONFIG OS/ARCH SIGNED LAYERS SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST CONFIG SIGNED LAYERS SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 3a1d2d0c linux/amd64 false 492B b8781e88 15B") So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 883fc0c5 3a1d2d0c false 492B b8781e88 15B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 3a1d2d0c linux/amd64 false 492B b8781e88 15B") So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 883fc0c5 3a1d2d0c false 492B b8781e88 15B")
}) })
Convey("Test all images with debug flag", func() { Convey("Test all images with debug flag", func() {
@ -925,9 +928,9 @@ func TestServerResponseGQL(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "GET") So(actual, ShouldContainSubstring, "GET")
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 linux/amd64 false 492B") So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 883fc0c5 false 492B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 linux/amd64 false 492B") So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 883fc0c5 false 492B")
}) })
Convey("Test image by name config url", func() { Convey("Test image by name config url", func() {
@ -944,9 +947,9 @@ func TestServerResponseGQL(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 linux/amd64 false 492B") So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 883fc0c5 false 492B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 linux/amd64 false 492B") So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 883fc0c5 false 492B")
Convey("with shorthand", func() { Convey("with shorthand", func() {
args := []string{"imagetest", "-n", "repo7"} args := []string{"imagetest", "-n", "repo7"}
@ -962,9 +965,9 @@ func TestServerResponseGQL(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 linux/amd64 false 492B") So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 883fc0c5 false 492B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 linux/amd64 false 492B") So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 883fc0c5 false 492B")
}) })
Convey("invalid output format", func() { Convey("invalid output format", func() {
@ -997,12 +1000,12 @@ func TestServerResponseGQL(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
// Actual cli output should be something similar to (order of images may differ): // Actual cli output should be something similar to (order of images may differ):
// IMAGE NAME TAG DIGEST OS/ARCH SIZE // IMAGE NAME TAG OS/ARCH DIGEST SIZE
// repo7 test:2.0 a0ca253b N/A 15B // repo7 test:2.0 a0ca253b 15B
// repo7 test:1.0 a0ca253b N/A 15B // repo7 test:1.0 a0ca253b 15B
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 N/A false 492B") So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 false 492B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 N/A false 492B") So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 false 492B")
Convey("with shorthand", func() { Convey("with shorthand", func() {
args := []string{"imagetest", "-d", "883fc0c5"} args := []string{"imagetest", "-d", "883fc0c5"}
@ -1018,9 +1021,9 @@ func TestServerResponseGQL(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 N/A false 492B") So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 false 492B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 N/A false 492B") So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 false 492B")
}) })
Convey("nonexistent digest", func() { Convey("nonexistent digest", func() {
@ -1128,9 +1131,9 @@ func TestServerResponse(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 linux/amd64 false 492B") So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 883fc0c5 false 492B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 linux/amd64 false 492B") So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 883fc0c5 false 492B")
}) })
Convey("Test all images verbose", func() { Convey("Test all images verbose", func() {
@ -1148,14 +1151,14 @@ func TestServerResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
// Actual cli output should be something similar to (order of images may differ): // Actual cli output should be something similar to (order of images may differ):
// IMAGE NAME TAG DIGEST CONFIG OS/ARCH SIGNED LAYERS SIZE // IMAGE NAME TAG OS/ARCH DIGEST CONFIG SIGNED LAYERS SIZE
// repo7 test:2.0 a0ca253b b8781e88 linux/amd64 false 492B // repo7 test:2.0 linux/amd64 a0ca253b b8781e88 false 492B
// linux/amd64 b8781e88 15B // b8781e88 15B
// repo7 test:1.0 a0ca253b b8781e88 linux/amd64 false 492B // repo7 test:1.0 linux/amd64 a0ca253b b8781e88 false 492B
// linux/amd64 b8781e88 15B // b8781e88 15B
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST CONFIG OS/ARCH SIGNED LAYERS SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST CONFIG SIGNED LAYERS SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 3a1d2d0c linux/amd64 false 492B b8781e88 15B") So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 883fc0c5 3a1d2d0c false 492B b8781e88 15B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 3a1d2d0c linux/amd64 false 492B b8781e88 15B") So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 883fc0c5 3a1d2d0c false 492B b8781e88 15B")
}) })
Convey("Test image by name", func() { Convey("Test image by name", func() {
@ -1172,9 +1175,9 @@ func TestServerResponse(t *testing.T) {
space := regexp.MustCompile(`\s+`) space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 linux/amd64 false 492B") So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 883fc0c5 false 492B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 linux/amd64 false 492B") So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 883fc0c5 false 492B")
}) })
Convey("Test image by digest", func() { Convey("Test image by digest", func() {
@ -1192,12 +1195,12 @@ func TestServerResponse(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ") str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str) actual := strings.TrimSpace(str)
// Actual cli output should be something similar to (order of images may differ): // Actual cli output should be something similar to (order of images may differ):
// IMAGE NAME TAG DIGEST OS/ARCH SIZE // IMAGE NAME TAG OS/ARCH DIGEST SIZE
// repo7 test:2.0 a0ca253b linux/amd64 492B // repo7 test:2.0 linux/amd64 a0ca253b 492B
// repo7 test:1.0 a0ca253b linux/amd64 492B // repo7 test:1.0 linux/amd64 a0ca253b 492B
So(actual, ShouldContainSubstring, "IMAGE NAME TAG DIGEST OS/ARCH SIGNED SIZE") So(actual, ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo7 test:2.0 883fc0c5 linux/amd64 false 492B") So(actual, ShouldContainSubstring, "repo7 test:2.0 linux/amd64 883fc0c5 false 492B")
So(actual, ShouldContainSubstring, "repo7 test:1.0 883fc0c5 linux/amd64 false 492B") So(actual, ShouldContainSubstring, "repo7 test:1.0 linux/amd64 883fc0c5 false 492B")
Convey("nonexistent digest", func() { Convey("nonexistent digest", func() {
args := []string{"imagetest", "--digest", "d1g35t"} args := []string{"imagetest", "--digest", "d1g35t"}
@ -1269,6 +1272,153 @@ func TestServerResponseGQLWithoutPermissions(t *testing.T) {
}) })
} }
func TestDisplayIndex(t *testing.T) {
Convey("Init Basic Server, No GQL", t, func() {
port := test.GetFreePort()
baseURL := test.GetBaseURL(port)
conf := config.New()
conf.HTTP.Port = port
Convey("No GQL", func() {
defaultVal := false
conf.Extensions = &extconf.ExtensionConfig{
Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
}
ctlr := api.NewController(conf)
ctlr.Config.Storage.RootDirectory = t.TempDir()
cm := test.NewControllerManager(ctlr)
cm.StartAndWait(conf.HTTP.Port)
defer cm.StopServer()
runDisplayIndexTests(baseURL)
})
Convey("With GQL", func() {
defaultVal := true
conf.Extensions = &extconf.ExtensionConfig{
Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
}
ctlr := api.NewController(conf)
ctlr.Config.Storage.RootDirectory = t.TempDir()
cm := test.NewControllerManager(ctlr)
cm.StartAndWait(conf.HTTP.Port)
defer cm.StopServer()
runDisplayIndexTests(baseURL)
})
})
}
func runDisplayIndexTests(baseURL string) {
Convey("Test Image Index", func() {
uploadTestMultiarch(baseURL)
args := []string{"imagetest"}
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
baseURL))
defer os.Remove(configPath)
cmd := MockNewImageCommand(new(searchService))
buff := bytes.NewBufferString("")
cmd.SetOut(buff)
cmd.SetErr(buff)
cmd.SetArgs(args)
err := cmd.Execute()
So(err, ShouldBeNil)
space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str)
// Actual cli output should be something similar to (order of images may differ):
// IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE
// repo multi-arch * 46b78b06 false 1.4kB
// linux/amd64 97b0d65c false 577B
// windows/arm64/v6 dcfa3a9c false 444B
So(actual, ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE")
So(actual, ShouldContainSubstring, "repo multi-arch * 46b78b06 false 1.4kB ")
So(actual, ShouldContainSubstring, "linux/amd64 97b0d65c false 577B ")
So(actual, ShouldContainSubstring, "windows/arm64/v6 dcfa3a9c false 444B")
})
Convey("Test Image Index Verbose", func() {
uploadTestMultiarch(baseURL)
args := []string{"imagetest", "--verbose"}
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
baseURL))
defer os.Remove(configPath)
cmd := MockNewImageCommand(new(searchService))
buff := bytes.NewBufferString("")
cmd.SetOut(buff)
cmd.SetErr(buff)
cmd.SetArgs(args)
err := cmd.Execute()
So(err, ShouldBeNil)
space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str)
// Actual cli output should be something similar to (order of images may differ):
// IMAGE NAME TAG OS/ARCH DIGEST CONFIG SIGNED LAYERS SIZE
// repo multi-arch * 46b78b06 false 1.4kB
// linux/amd64 97b0d65c 58cc9abe false 577B
// cbb5b121 4B
// a00291e8 4B
// windows/arm64/v6 dcfa3a9c 5132a1cd false 444B
// 7d08ce29 4B
So(actual, ShouldContainSubstring, "IMAGE NAME TAG OS/ARCH DIGEST CONFIG SIGNED LAYERS SIZE")
So(actual, ShouldContainSubstring, "repo multi-arch * 46b78b06 false 1.4kB")
So(actual, ShouldContainSubstring, "linux/amd64 97b0d65c 58cc9abe false 577B")
So(actual, ShouldContainSubstring, "cbb5b121 4B")
So(actual, ShouldContainSubstring, "a00291e8 4B")
So(actual, ShouldContainSubstring, "windows/arm64/v6 dcfa3a9c 5132a1cd false 444B")
So(actual, ShouldContainSubstring, "7d08ce29 4B")
})
}
func uploadTestMultiarch(baseURL string) {
// ------- Define Image1
layer11 := []byte{11, 12, 13, 14}
layer12 := []byte{16, 17, 18, 19}
image1, err := test.GetImageWithComponents(
ispec.Image{
Platform: ispec.Platform{
OS: "linux",
Architecture: "amd64",
},
},
[][]byte{
layer11,
layer12,
},
)
So(err, ShouldBeNil)
// ------ Define Image2
layer21 := []byte{21, 22, 23, 24}
image2, err := test.GetImageWithComponents(
ispec.Image{
Platform: ispec.Platform{
OS: "windows",
Architecture: "arm64",
Variant: "v6",
},
},
[][]byte{
layer21,
},
)
So(err, ShouldBeNil)
// ------- Upload The multiarch image
multiarch := test.GetMultiarchImageForImages("multi-arch", []test.Image{image1, image2})
err = test.UploadMultiarchImage(multiarch, baseURL, "repo")
So(err, ShouldBeNil)
}
func MockNewImageCommand(searchService SearchService) *cobra.Command { func MockNewImageCommand(searchService SearchService) *cobra.Command {
searchImageParams := make(map[string]*string) searchImageParams := make(map[string]*string)
@ -1596,8 +1746,10 @@ func (service mockService) getImagesGQL(ctx context.Context, config searchConfig
imageListGQLResponse := &imageListStructGQL{} imageListGQLResponse := &imageListStructGQL{}
imageListGQLResponse.Data.Results = []imageStruct{ imageListGQLResponse.Data.Results = []imageStruct{
{ {
RepoName: "dummyImageName", RepoName: "dummyImageName",
Tag: "tag", Tag: "tag",
MediaType: ispec.MediaTypeImageManifest,
Digest: godigest.FromString("test").String(),
Manifests: []manifestStruct{ Manifests: []manifestStruct{
{ {
Digest: godigest.FromString("Digest").String(), Digest: godigest.FromString("Digest").String(),
@ -1619,8 +1771,10 @@ func (service mockService) getImagesByDigestGQL(ctx context.Context, config sear
imageListGQLResponse := &imageListStructForDigestGQL{} imageListGQLResponse := &imageListStructForDigestGQL{}
imageListGQLResponse.Data.Results = []imageStruct{ imageListGQLResponse.Data.Results = []imageStruct{
{ {
RepoName: "randomimageName", RepoName: "randomimageName",
Tag: "tag", Tag: "tag",
MediaType: ispec.MediaTypeImageManifest,
Digest: godigest.FromString("test").String(),
Manifests: []manifestStruct{ Manifests: []manifestStruct{
{ {
Digest: godigest.FromString("Digest").String(), Digest: godigest.FromString("Digest").String(),
@ -1745,6 +1899,8 @@ func (service mockService) getAllImages(ctx context.Context, config searchConfig
image := &imageStruct{} image := &imageStruct{}
image.RepoName = "randomimageName" image.RepoName = "randomimageName"
image.Tag = "tag" image.Tag = "tag"
image.Digest = godigest.FromString("test").String()
image.MediaType = ispec.MediaTypeImageManifest
image.Manifests = []manifestStruct{ image.Manifests = []manifestStruct{
{ {
Digest: godigest.FromString("Digest").String(), Digest: godigest.FromString("Digest").String(),
@ -1775,6 +1931,8 @@ func (service mockService) getImageByName(ctx context.Context, config searchConf
image := &imageStruct{} image := &imageStruct{}
image.RepoName = imageName image.RepoName = imageName
image.Tag = "tag" image.Tag = "tag"
image.Digest = godigest.FromString("test").String()
image.MediaType = ispec.MediaTypeImageManifest
image.Manifests = []manifestStruct{ image.Manifests = []manifestStruct{
{ {
Digest: godigest.FromString("Digest").String(), Digest: godigest.FromString("Digest").String(),

View file

@ -17,6 +17,7 @@ import (
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter"
godigest "github.com/opencontainers/go-digest" godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
zotErrors "zotregistry.io/zot/errors" zotErrors "zotregistry.io/zot/errors"
@ -74,11 +75,14 @@ func (service searchService) getDerivedImageListGQL(ctx context.Context, config
Results{ Results{
RepoName, RepoName,
Tag, Tag,
Digest,
MediaType,
Manifests { Manifests {
Digest, Digest,
ConfigDigest, ConfigDigest,
Layers {Size Digest}, Layers {Size Digest},
LastUpdated, LastUpdated,
IsSigned,
Size Size
}, },
LastUpdated, LastUpdated,
@ -107,11 +111,14 @@ func (service searchService) getBaseImageListGQL(ctx context.Context, config sea
Results{ Results{
RepoName, RepoName,
Tag, Tag,
Digest,
MediaType,
Manifests { Manifests {
Digest, Digest,
ConfigDigest, ConfigDigest,
Layers {Size Digest}, Layers {Size Digest},
LastUpdated, LastUpdated,
IsSigned,
Size Size
}, },
LastUpdated, LastUpdated,
@ -139,11 +146,14 @@ func (service searchService) getImagesGQL(ctx context.Context, config searchConf
ImageList(repo: "%s") { ImageList(repo: "%s") {
Results { Results {
RepoName Tag RepoName Tag
Digest
MediaType
Manifests { Manifests {
Digest Digest
ConfigDigest ConfigDigest
Size Size
Platform {Os Arch} Platform {Os Arch}
IsSigned
Layers {Size Digest} Layers {Size Digest}
} }
Size Size
@ -171,12 +181,15 @@ func (service searchService) getImagesByDigestGQL(ctx context.Context, config se
ImageListForDigest(id: "%s") { ImageListForDigest(id: "%s") {
Results { Results {
RepoName Tag RepoName Tag
Digest
MediaType
Manifests { Manifests {
Digest Digest
ConfigDigest ConfigDigest
Size Size
IsSigned
Layers {Size Digest} Layers {Size Digest}
} }
Size Size
IsSigned IsSigned
} }
@ -202,12 +215,15 @@ func (service searchService) getImagesByCveIDGQL(ctx context.Context, config sea
ImageListForCVE(id: "%s") { ImageListForCVE(id: "%s") {
Results { Results {
RepoName Tag RepoName Tag
Digest
MediaType
Manifests { Manifests {
Digest Digest
ConfigDigest ConfigDigest
Size Size
IsSigned
Layers {Size Digest} Layers {Size Digest}
} }
Size Size
IsSigned IsSigned
} }
@ -252,10 +268,13 @@ func (service searchService) getTagsForCVEGQL(ctx context.Context, config search
ImageListForCVE(id: "%s") { ImageListForCVE(id: "%s") {
Results { Results {
RepoName Tag RepoName Tag
Digest
MediaType
Manifests { Manifests {
Digest Digest
ConfigDigest ConfigDigest
Size Size
IsSigned
Layers {Size Digest} Layers {Size Digest}
} }
Size Size
@ -282,12 +301,15 @@ func (service searchService) getFixedTagsForCVEGQL(ctx context.Context, config s
ImageListWithCVEFixed(id: "%s", image: "%s") { ImageListWithCVEFixed(id: "%s", image: "%s") {
Results { Results {
RepoName Tag RepoName Tag
Digest
MediaType
Manifests { Manifests {
Digest Digest
ConfigDigest ConfigDigest
Size Size
IsSigned
Layers {Size Digest} Layers {Size Digest}
} }
Size Size
} }
} }
@ -426,12 +448,15 @@ func (service searchService) getImagesByCveID(ctx context.Context, config search
ImageListForCVE(id: "%s") { ImageListForCVE(id: "%s") {
Results { Results {
RepoName Tag RepoName Tag
Digest
MediaType
Manifests { Manifests {
Digest Digest
ConfigDigest ConfigDigest
Size Size
IsSigned
Layers {Size Digest} Layers {Size Digest}
} }
Size Size
} }
} }
@ -492,12 +517,15 @@ func (service searchService) getImagesByDigest(ctx context.Context, config searc
ImageListForDigest(id: "%s") { ImageListForDigest(id: "%s") {
Results { Results {
RepoName Tag RepoName Tag
Digest
MediaType
Manifests { Manifests {
Digest Digest
ConfigDigest ConfigDigest
Size Size
IsSigned
Layers {Size Digest} Layers {Size Digest}
} }
Size Size
} }
} }
@ -558,12 +586,15 @@ func (service searchService) getImageByNameAndCVEID(ctx context.Context, config
ImageListForCVE(id: "%s") { ImageListForCVE(id: "%s") {
Results { Results {
RepoName Tag RepoName Tag
Digest
MediaType
Manifests { Manifests {
Digest Digest
ConfigDigest ConfigDigest
Size Size
IsSigned
Layers {Size Digest} Layers {Size Digest}
} }
Size Size
} }
} }
@ -682,12 +713,15 @@ func (service searchService) getFixedTagsForCVE(ctx context.Context, config sear
ImageListWithCVEFixed (id: "%s", image: "%s") { ImageListWithCVEFixed (id: "%s", image: "%s") {
Results { Results {
RepoName Tag RepoName Tag
Digest
MediaType
Manifests { Manifests {
Digest Digest
ConfigDigest ConfigDigest
Size Size
IsSigned
Layers {Size Digest} Layers {Size Digest}
} }
Size Size
} }
} }
@ -984,8 +1018,10 @@ type imageStruct struct {
Tag string `json:"tag"` Tag string `json:"tag"`
Manifests []manifestStruct Manifests []manifestStruct
Size string `json:"size"` Size string `json:"size"`
Digest string `json:"digest"`
MediaType string `json:"mediaType"`
IsSigned bool `json:"isSigned"`
verbose bool verbose bool
IsSigned bool `json:"isSigned"`
} }
type manifestStruct struct { type manifestStruct struct {
@ -1106,69 +1142,9 @@ func (img imageStruct) stringPlainText(maxImgNameLen, maxTagLen, maxPlatformLen
tagName += offset tagName += offset
} }
for i := range img.Manifests { err := addImageToTable(table, &img, maxPlatformLen, imageName, tagName)
manifestDigest, err := godigest.Parse(img.Manifests[i].Digest) if err != nil {
if err != nil { return "", err
return "", fmt.Errorf("error parsing manifest digest %s: %w", img.Manifests[i].Digest, err)
}
configDigest, err := godigest.Parse(img.Manifests[i].ConfigDigest)
if err != nil {
return "", fmt.Errorf("error parsing config digest %s: %w", img.Manifests[i].ConfigDigest, err)
}
platform := getPlatformStr(img.Manifests[i].Platform)
if maxPlatformLen > len(platform) {
offset = strings.Repeat(" ", maxPlatformLen-len(platform))
platform += offset
}
minifestDigestStr := ellipsize(manifestDigest.Encoded(), digestWidth, "")
configDigestStr := ellipsize(configDigest.Encoded(), configWidth, "")
imgSize, _ := strconv.ParseUint(img.Manifests[i].Size, 10, 64)
size := ellipsize(strings.ReplaceAll(humanize.Bytes(imgSize), " ", ""), sizeWidth, ellipsis)
isSigned := img.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 img.verbose {
row[colConfigIndex] = configDigestStr
row[colLayersIndex] = ""
}
table.Append(row)
if img.verbose {
for _, entry := range img.Manifests[i].Layers {
layerSize := entry.Size
size := ellipsize(strings.ReplaceAll(humanize.Bytes(uint64(layerSize)), " ", ""), sizeWidth, ellipsis)
layerDigest, err := godigest.Parse(entry.Digest)
if err != nil {
return "", fmt.Errorf("error parsing layer digest %s: %w", entry.Digest, err)
}
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)
}
}
} }
table.Render() table.Render()
@ -1176,9 +1152,125 @@ func (img imageStruct) stringPlainText(maxImgNameLen, maxTagLen, maxPlatformLen
return builder.String(), nil return builder.String(), nil
} }
func addImageToTable(table *tablewriter.Table, img *imageStruct, maxPlatformLen int,
imageName, tagName string,
) error {
switch img.MediaType {
case ispec.MediaTypeImageManifest:
return addManifestToTable(table, imageName, tagName, &img.Manifests[0], maxPlatformLen, img.verbose)
case ispec.MediaTypeImageIndex:
return addImageIndexToTable(table, img, maxPlatformLen, imageName, tagName)
}
return nil
}
func addImageIndexToTable(table *tablewriter.Table, img *imageStruct, maxPlatformLen int,
imageName, tagName string,
) 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)
if img.verbose {
row[colConfigIndex] = ""
row[colLayersIndex] = ""
}
table.Append(row)
for i := range img.Manifests {
err := addManifestToTable(table, "", "", &img.Manifests[i], maxPlatformLen, img.verbose)
if err != nil {
return err
}
}
return nil
}
func addManifestToTable(table *tablewriter.Table, imageName, tagName string, manifest *manifestStruct,
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)
}
configDigest, err := godigest.Parse(manifest.ConfigDigest)
if err != nil {
return fmt.Errorf("error parsing config digest %s: %w", manifest.ConfigDigest, err)
}
platform := getPlatformStr(manifest.Platform)
if maxPlatformLen > len(platform) {
offset := strings.Repeat(" ", maxPlatformLen-len(platform))
platform += offset
}
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] = ""
}
table.Append(row)
if verbose {
for _, entry := range manifest.Layers {
layerSize := entry.Size
size := ellipsize(strings.ReplaceAll(humanize.Bytes(uint64(layerSize)), " ", ""), sizeWidth, ellipsis)
layerDigest, err := godigest.Parse(entry.Digest)
if err != nil {
return fmt.Errorf("error parsing layer digest %s: %w", entry.Digest, err)
}
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)
}
}
return nil
}
func getPlatformStr(platf platform) string { func getPlatformStr(platf platform) string {
if platf.Arch == "" && platf.Os == "" { if platf.Arch == "" && platf.Os == "" {
return "N/A" return ""
} }
platform := platf.Os platform := platf.Os
@ -1332,15 +1424,6 @@ const (
layersWidth = 8 layersWidth = 8
ellipsis = "..." ellipsis = "..."
colImageNameIndex = 0
colTagIndex = 1
colDigestIndex = 2
colConfigIndex = 3
colPlatformIndex = 4
colIsSignedIndex = 5
colLayersIndex = 6
colSizeIndex = 7
cveIDWidth = 16 cveIDWidth = 16
cveSeverityWidth = 8 cveSeverityWidth = 8
cveTitleWidth = 48 cveTitleWidth = 48
@ -1351,3 +1434,16 @@ const (
defaultOutoutFormat = "text" defaultOutoutFormat = "text"
) )
const (
colImageNameIndex = iota
colTagIndex
colPlatformIndex
colDigestIndex
colConfigIndex
colIsSignedIndex
colLayersIndex
colSizeIndex
rowWidth
)

View file

@ -6544,6 +6544,8 @@ func TestImageSummary(t *testing.T) {
Image(image:"%s:%s"){ Image(image:"%s:%s"){
RepoName RepoName
Tag Tag
Digest
MediaType
Manifests { Manifests {
Digest Digest
ConfigDigest ConfigDigest
@ -6569,6 +6571,8 @@ func TestImageSummary(t *testing.T) {
Image(image:"%s"){ Image(image:"%s"){
RepoName, RepoName,
Tag, Tag,
Digest,
MediaType,
Manifests { Manifests {
Digest Digest
ConfigDigest ConfigDigest
@ -6679,6 +6683,8 @@ func TestImageSummary(t *testing.T) {
imgSummary := imgSummaryResponse.SingleImageSummary.ImageSummary imgSummary := imgSummaryResponse.SingleImageSummary.ImageSummary
So(imgSummary.RepoName, ShouldContainSubstring, repoName) So(imgSummary.RepoName, ShouldContainSubstring, repoName)
So(imgSummary.Tag, ShouldContainSubstring, tagTarget) 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].ConfigDigest, ShouldContainSubstring, image.Manifest.Config.Digest.Encoded())
So(imgSummary.Manifests[0].Digest, ShouldContainSubstring, manifestDigest.Encoded()) So(imgSummary.Manifests[0].Digest, ShouldContainSubstring, manifestDigest.Encoded())
So(len(imgSummary.Manifests[0].Layers), ShouldEqual, 1) So(len(imgSummary.Manifests[0].Layers), ShouldEqual, 1)

View file

@ -22,6 +22,8 @@ type RepoSummary struct {
type ImageSummary struct { type ImageSummary struct {
RepoName string `json:"repoName"` RepoName string `json:"repoName"`
Tag string `json:"tag"` Tag string `json:"tag"`
Digest string `json:"digest"`
MediaType string `json:"mediaType"`
Manifests []ManifestSummary `json:"manifests"` Manifests []ManifestSummary `json:"manifests"`
Size string `json:"size"` Size string `json:"size"`
DownloadCount int `json:"downloadCount"` DownloadCount int `json:"downloadCount"`

View file

@ -132,8 +132,16 @@ func TestConvertErrors(t *testing.T) {
Convey("ImageManifest2ImageSummary", t, func() { Convey("ImageManifest2ImageSummary", t, func() {
ctx := graphql.WithResponseContext(context.Background(), ctx := graphql.WithResponseContext(context.Background(),
graphql.DefaultErrorPresenter, graphql.DefaultRecover) 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, ctx,
"repo", "repo",
"tag", "tag",
@ -142,7 +150,7 @@ func TestConvertErrors(t *testing.T) {
repodb.RepoMetadata{}, repodb.RepoMetadata{},
repodb.ManifestMetadata{ repodb.ManifestMetadata{
ManifestBlob: []byte("{}"), ManifestBlob: []byte("{}"),
ConfigBlob: []byte("{}"), ConfigBlob: configBlob,
}, },
mocks.CveInfoMock{ mocks.CveInfoMock{
GetCVESummaryForImageFn: func(repo, reference string, GetCVESummaryForImageFn: func(repo, reference string,
@ -168,6 +176,12 @@ func TestConvertErrors(t *testing.T) {
MediaType: ispec.MediaTypeImageManifest, MediaType: ispec.MediaTypeImageManifest,
}, },
false, 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{ repodb.ManifestMetadata{
ManifestBlob: []byte("{}"), ManifestBlob: []byte("{}"),
ConfigBlob: []byte("bad json"), ConfigBlob: []byte("bad json"),
@ -187,6 +201,7 @@ func TestConvertErrors(t *testing.T) {
Platform: ispec.Platform{ Platform: ispec.Platform{
OS: "os", OS: "os",
Architecture: "arch", Architecture: "arch",
Variant: "var",
}, },
}) })
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -200,6 +215,12 @@ func TestConvertErrors(t *testing.T) {
MediaType: ispec.MediaTypeImageManifest, MediaType: ispec.MediaTypeImageManifest,
}, },
false, 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{ repodb.ManifestMetadata{
ManifestBlob: []byte("{}"), ManifestBlob: []byte("{}"),
ConfigBlob: configBlob, ConfigBlob: configBlob,
@ -245,6 +266,33 @@ func TestConvertErrors(t *testing.T) {
}, log.NewLogger("debug", ""), }, log.NewLogger("debug", ""),
) )
So(len(imageSummaries), ShouldEqual, 0) 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)
}) })
} }

View file

@ -189,11 +189,14 @@ func ImageIndex2ImageSummary(ctx context.Context, repo, tag string, indexDigest
maxSeverity string maxSeverity string
manifestSummaries = make([]*gql_generated.ManifestSummary, 0, len(indexContent.Manifests)) manifestSummaries = make([]*gql_generated.ManifestSummary, 0, len(indexContent.Manifests))
indexBlobs = make(map[string]int64, 0) indexBlobs = make(map[string]int64, 0)
indexDigestStr = indexDigest.String()
indexMediaType = ispec.MediaTypeImageIndex
) )
for _, descriptor := range indexContent.Manifests { for _, descriptor := range indexContent.Manifests {
manifestSummary, manifestBlobs, err := ImageManifest2ManifestSummary(ctx, repo, tag, descriptor, false, 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 { if err != nil {
return &gql_generated.ImageSummary{}, map[string]int64{}, err 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{ indexSummary := gql_generated.ImageSummary{
RepoName: &repo, RepoName: &repo,
Tag: &tag, Tag: &tag,
Digest: &indexDigestStr,
MediaType: &indexMediaType,
Manifests: manifestSummaries, Manifests: manifestSummaries,
LastUpdated: &indexLastUpdated, LastUpdated: &indexLastUpdated,
IsSigned: &isSigned, IsSigned: &isSigned,
@ -272,6 +277,7 @@ func ImageManifest2ImageSummary(ctx context.Context, repo, tag string, digest go
var ( var (
manifestContent ispec.Manifest manifestContent ispec.Manifest
manifestDigest = digest.String() manifestDigest = digest.String()
mediaType = ispec.MediaTypeImageManifest
) )
err := json.Unmarshal(manifestMeta.ManifestBlob, &manifestContent) err := json.Unmarshal(manifestMeta.ManifestBlob, &manifestContent)
@ -349,14 +355,17 @@ func ImageManifest2ImageSummary(ctx context.Context, repo, tag string, digest go
} }
imageSummary := gql_generated.ImageSummary{ imageSummary := gql_generated.ImageSummary{
RepoName: &repoName, RepoName: &repoName,
Tag: &tag, Tag: &tag,
Digest: &manifestDigest,
MediaType: &mediaType,
Manifests: []*gql_generated.ManifestSummary{ Manifests: []*gql_generated.ManifestSummary{
{ {
Digest: &manifestDigest, Digest: &manifestDigest,
ConfigDigest: &configDigest, ConfigDigest: &configDigest,
LastUpdated: &imageLastUpdated, LastUpdated: &imageLastUpdated,
Size: &imageSize, Size: &imageSize,
IsSigned: &isSigned,
Platform: &platform, Platform: &platform,
DownloadCount: &downloadCount, DownloadCount: &downloadCount,
Layers: getLayersSummaries(manifestContent), 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, 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) { ) (*gql_generated.ManifestSummary, map[string]int64, error) {
var ( var (
manifestContent ispec.Manifest manifestContent ispec.Manifest
@ -456,6 +466,7 @@ func ImageManifest2ManifestSummary(ctx context.Context, repo, tag string, descri
configSize = manifestContent.Config.Size configSize = manifestContent.Config.Size
imageLastUpdated = common.GetImageLastUpdated(configContent) imageLastUpdated = common.GetImageLastUpdated(configContent)
downloadCount = manifestMeta.DownloadCount downloadCount = manifestMeta.DownloadCount
isSigned = false
) )
opSys := configContent.OS 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{ manifestSummary := gql_generated.ManifestSummary{
Digest: &manifestDigestStr, Digest: &manifestDigestStr,
ConfigDigest: &configDigest, ConfigDigest: &configDigest,
@ -501,6 +518,7 @@ func ImageManifest2ManifestSummary(ctx context.Context, repo, tag string, descri
DownloadCount: &downloadCount, DownloadCount: &downloadCount,
Layers: getLayersSummaries(manifestContent), Layers: getLayersSummaries(manifestContent),
History: historyEntries, History: historyEntries,
IsSigned: &isSigned,
Vulnerabilities: &gql_generated.ImageVulnerabilitySummary{ Vulnerabilities: &gql_generated.ImageVulnerabilitySummary{
MaxSeverity: &imageCveSummary.MaxSeverity, MaxSeverity: &imageCveSummary.MaxSeverity,
Count: &imageCveSummary.Count, Count: &imageCveSummary.Count,

View file

@ -79,6 +79,7 @@ type ComplexityRoot struct {
ImageSummary struct { ImageSummary struct {
Authors func(childComplexity int) int Authors func(childComplexity int) int
Description func(childComplexity int) int Description func(childComplexity int) int
Digest func(childComplexity int) int
Documentation func(childComplexity int) int Documentation func(childComplexity int) int
DownloadCount func(childComplexity int) int DownloadCount func(childComplexity int) int
IsSigned func(childComplexity int) int IsSigned func(childComplexity int) int
@ -86,6 +87,7 @@ type ComplexityRoot struct {
LastUpdated func(childComplexity int) int LastUpdated func(childComplexity int) int
Licenses func(childComplexity int) int Licenses func(childComplexity int) int
Manifests func(childComplexity int) int Manifests func(childComplexity int) int
MediaType func(childComplexity int) int
Referrers func(childComplexity int) int Referrers func(childComplexity int) int
RepoName func(childComplexity int) int RepoName func(childComplexity int) int
Score func(childComplexity int) int Score func(childComplexity int) int
@ -118,6 +120,7 @@ type ComplexityRoot struct {
Digest func(childComplexity int) int Digest func(childComplexity int) int
DownloadCount func(childComplexity int) int DownloadCount func(childComplexity int) int
History func(childComplexity int) int History func(childComplexity int) int
IsSigned func(childComplexity int) int
LastUpdated func(childComplexity int) int LastUpdated func(childComplexity int) int
Layers func(childComplexity int) int Layers func(childComplexity int) int
Platform 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 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": case "ImageSummary.Documentation":
if e.complexity.ImageSummary.Documentation == nil { if e.complexity.ImageSummary.Documentation == nil {
break break
@ -421,6 +431,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.ImageSummary.Manifests(childComplexity), true 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": case "ImageSummary.Referrers":
if e.complexity.ImageSummary.Referrers == nil { if e.complexity.ImageSummary.Referrers == nil {
break break
@ -561,6 +578,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.ManifestSummary.History(childComplexity), true 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": case "ManifestSummary.LastUpdated":
if e.complexity.ManifestSummary.LastUpdated == nil { if e.complexity.ManifestSummary.LastUpdated == nil {
break break
@ -1131,6 +1155,14 @@ type ImageSummary {
""" """
Tag: String 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 List of manifests for all supported versions of the image for different operating systems and architectures
""" """
Manifests: [ManifestSummary] Manifests: [ManifestSummary]
@ -1217,6 +1249,10 @@ type ManifestSummary {
""" """
Size: String Size: String
""" """
True if the manifest has a signature associated with it, false otherwise
"""
IsSigned: Boolean
"""
OS and architecture supported by this image OS and architecture supported by this image
""" """
Platform: Platform Platform: Platform
@ -2587,6 +2623,10 @@ func (ec *executionContext) fieldContext_GlobalSearchResult_Images(ctx context.C
return ec.fieldContext_ImageSummary_RepoName(ctx, field) return ec.fieldContext_ImageSummary_RepoName(ctx, field)
case "Tag": case "Tag":
return ec.fieldContext_ImageSummary_Tag(ctx, field) 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": case "Manifests":
return ec.fieldContext_ImageSummary_Manifests(ctx, field) return ec.fieldContext_ImageSummary_Manifests(ctx, field)
case "Size": case "Size":
@ -3027,6 +3067,88 @@ func (ec *executionContext) fieldContext_ImageSummary_Tag(ctx context.Context, f
return fc, nil 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) { func (ec *executionContext) _ImageSummary_Manifests(ctx context.Context, field graphql.CollectedField, obj *ImageSummary) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_ImageSummary_Manifests(ctx, field) fc, err := ec.fieldContext_ImageSummary_Manifests(ctx, field)
if err != nil { if err != nil {
@ -3071,6 +3193,8 @@ func (ec *executionContext) fieldContext_ImageSummary_Manifests(ctx context.Cont
return ec.fieldContext_ManifestSummary_LastUpdated(ctx, field) return ec.fieldContext_ManifestSummary_LastUpdated(ctx, field)
case "Size": case "Size":
return ec.fieldContext_ManifestSummary_Size(ctx, field) return ec.fieldContext_ManifestSummary_Size(ctx, field)
case "IsSigned":
return ec.fieldContext_ManifestSummary_IsSigned(ctx, field)
case "Platform": case "Platform":
return ec.fieldContext_ManifestSummary_Platform(ctx, field) return ec.fieldContext_ManifestSummary_Platform(ctx, field)
case "DownloadCount": case "DownloadCount":
@ -4194,6 +4318,47 @@ func (ec *executionContext) fieldContext_ManifestSummary_Size(ctx context.Contex
return fc, nil 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) { func (ec *executionContext) _ManifestSummary_Platform(ctx context.Context, field graphql.CollectedField, obj *ManifestSummary) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_ManifestSummary_Platform(ctx, field) fc, err := ec.fieldContext_ManifestSummary_Platform(ctx, field)
if err != nil { if err != nil {
@ -4779,6 +4944,10 @@ func (ec *executionContext) fieldContext_PaginatedImagesResult_Results(ctx conte
return ec.fieldContext_ImageSummary_RepoName(ctx, field) return ec.fieldContext_ImageSummary_RepoName(ctx, field)
case "Tag": case "Tag":
return ec.fieldContext_ImageSummary_Tag(ctx, field) 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": case "Manifests":
return ec.fieldContext_ImageSummary_Manifests(ctx, field) return ec.fieldContext_ImageSummary_Manifests(ctx, field)
case "Size": case "Size":
@ -5663,6 +5832,10 @@ func (ec *executionContext) fieldContext_Query_Image(ctx context.Context, field
return ec.fieldContext_ImageSummary_RepoName(ctx, field) return ec.fieldContext_ImageSummary_RepoName(ctx, field)
case "Tag": case "Tag":
return ec.fieldContext_ImageSummary_Tag(ctx, field) 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": case "Manifests":
return ec.fieldContext_ImageSummary_Manifests(ctx, field) return ec.fieldContext_ImageSummary_Manifests(ctx, field)
case "Size": case "Size":
@ -6160,6 +6333,10 @@ func (ec *executionContext) fieldContext_RepoInfo_Images(ctx context.Context, fi
return ec.fieldContext_ImageSummary_RepoName(ctx, field) return ec.fieldContext_ImageSummary_RepoName(ctx, field)
case "Tag": case "Tag":
return ec.fieldContext_ImageSummary_Tag(ctx, field) 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": case "Manifests":
return ec.fieldContext_ImageSummary_Manifests(ctx, field) return ec.fieldContext_ImageSummary_Manifests(ctx, field)
case "Size": case "Size":
@ -6556,6 +6733,10 @@ func (ec *executionContext) fieldContext_RepoSummary_NewestImage(ctx context.Con
return ec.fieldContext_ImageSummary_RepoName(ctx, field) return ec.fieldContext_ImageSummary_RepoName(ctx, field)
case "Tag": case "Tag":
return ec.fieldContext_ImageSummary_Tag(ctx, field) 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": case "Manifests":
return ec.fieldContext_ImageSummary_Manifests(ctx, field) return ec.fieldContext_ImageSummary_Manifests(ctx, field)
case "Size": 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) 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": case "Manifests":
out.Values[i] = ec._ImageSummary_Manifests(ctx, field, obj) 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) out.Values[i] = ec._ManifestSummary_Size(ctx, field, obj)
case "IsSigned":
out.Values[i] = ec._ManifestSummary_IsSigned(ctx, field, obj)
case "Platform": case "Platform":
out.Values[i] = ec._ManifestSummary_Platform(ctx, field, obj) out.Values[i] = ec._ManifestSummary_Platform(ctx, field, obj)

View file

@ -91,6 +91,10 @@ type ImageSummary struct {
RepoName *string `json:"RepoName"` RepoName *string `json:"RepoName"`
// Tag identifying the image within the repository // Tag identifying the image within the repository
Tag *string `json:"Tag"` 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 // List of manifests for all supported versions of the image for different operating systems and architectures
Manifests []*ManifestSummary `json:"Manifests"` Manifests []*ManifestSummary `json:"Manifests"`
// Total size of the files associated with all images (manifest, config, layers) // Total size of the files associated with all images (manifest, config, layers)
@ -162,6 +166,8 @@ type ManifestSummary struct {
LastUpdated *time.Time `json:"LastUpdated"` LastUpdated *time.Time `json:"LastUpdated"`
// Total size of the files associated with this manifest (manifest, config, layers) // Total size of the files associated with this manifest (manifest, config, layers)
Size *string `json:"Size"` 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 // OS and architecture supported by this image
Platform *Platform `json:"Platform"` Platform *Platform `json:"Platform"`
// Total numer of image manifest downloads from this repository // Total numer of image manifest downloads from this repository

View file

@ -124,6 +124,14 @@ type ImageSummary {
""" """
Tag: String 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 List of manifests for all supported versions of the image for different operating systems and architectures
""" """
Manifests: [ManifestSummary] Manifests: [ManifestSummary]
@ -210,6 +218,10 @@ type ManifestSummary {
""" """
Size: String Size: String
""" """
True if the manifest has a signature associated with it, false otherwise
"""
IsSigned: Boolean
"""
OS and architecture supported by this image OS and architecture supported by this image
""" """
Platform: Platform Platform: Platform