zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/cli/client/image_cmd_internal_test.go (about)

     1  //go:build search
     2  // +build search
     3  
     4  package client
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"errors"
    10  	"fmt"
    11  	"log"
    12  	"os"
    13  	"path"
    14  	"path/filepath"
    15  	"regexp"
    16  	"strings"
    17  	"sync"
    18  	"testing"
    19  	"time"
    20  
    21  	godigest "github.com/opencontainers/go-digest"
    22  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    23  	. "github.com/smartystreets/goconvey/convey"
    24  
    25  	zerr "zotregistry.dev/zot/errors"
    26  	"zotregistry.dev/zot/pkg/api"
    27  	"zotregistry.dev/zot/pkg/api/config"
    28  	"zotregistry.dev/zot/pkg/common"
    29  	extconf "zotregistry.dev/zot/pkg/extensions/config"
    30  	stypes "zotregistry.dev/zot/pkg/storage/types"
    31  	test "zotregistry.dev/zot/pkg/test/common"
    32  	. "zotregistry.dev/zot/pkg/test/image-utils"
    33  )
    34  
    35  func TestSearchImageCmd(t *testing.T) {
    36  	Convey("Test image help", t, func() {
    37  		args := []string{"--help"}
    38  		configPath := makeConfigFile("")
    39  		defer os.Remove(configPath)
    40  		cmd := NewImageCommand(new(mockService))
    41  		buff := bytes.NewBufferString("")
    42  		cmd.SetOut(buff)
    43  		cmd.SetErr(buff)
    44  		cmd.SetArgs(args)
    45  		err := cmd.Execute()
    46  		So(buff.String(), ShouldContainSubstring, "Usage")
    47  		So(err, ShouldBeNil)
    48  		Convey("with the shorthand", func() {
    49  			args[0] = "-h"
    50  			configPath := makeConfigFile("")
    51  			defer os.Remove(configPath)
    52  			cmd := NewImageCommand(new(mockService))
    53  			buff := bytes.NewBufferString("")
    54  			cmd.SetOut(buff)
    55  			cmd.SetErr(buff)
    56  			cmd.SetArgs(args)
    57  			err := cmd.Execute()
    58  			So(buff.String(), ShouldContainSubstring, "Usage")
    59  			So(err, ShouldBeNil)
    60  		})
    61  	})
    62  
    63  	Convey("Test image no url", t, func() {
    64  		args := []string{"name", "dummyIdRandom", "--config", "imagetest"}
    65  		configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`)
    66  		defer os.Remove(configPath)
    67  		cmd := NewImageCommand(new(mockService))
    68  		buff := bytes.NewBufferString("")
    69  		cmd.SetOut(buff)
    70  		cmd.SetErr(buff)
    71  		cmd.SetArgs(args)
    72  		err := cmd.Execute()
    73  		So(err, ShouldNotBeNil)
    74  		So(errors.Is(err, zerr.ErrNoURLProvided), ShouldBeTrue)
    75  	})
    76  
    77  	Convey("Test image invalid home directory", t, func() {
    78  		args := []string{"name", "dummyImageName", "--config", "imagetest"}
    79  
    80  		configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`)
    81  		defer os.Remove(configPath)
    82  
    83  		err := os.Setenv("HOME", "nonExistentDirectory")
    84  		if err != nil {
    85  			panic(err)
    86  		}
    87  
    88  		cmd := NewImageCommand(new(mockService))
    89  		buff := bytes.NewBufferString("")
    90  		cmd.SetOut(buff)
    91  		cmd.SetErr(buff)
    92  		cmd.SetArgs(args)
    93  		err = cmd.Execute()
    94  		So(err, ShouldNotBeNil)
    95  
    96  		home, err := os.UserHomeDir()
    97  		if err != nil {
    98  			panic(err)
    99  		}
   100  		err = os.Setenv("HOME", home)
   101  		if err != nil {
   102  			log.Fatal(err)
   103  		}
   104  	})
   105  
   106  	Convey("Test image no params", t, func() {
   107  		args := []string{"--url", "someUrl"}
   108  		configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`)
   109  		defer os.Remove(configPath)
   110  		cmd := NewImageCommand(new(mockService))
   111  		buff := bytes.NewBufferString("")
   112  		cmd.SetOut(buff)
   113  		cmd.SetErr(buff)
   114  		cmd.SetArgs(args)
   115  		err := cmd.Execute()
   116  		So(err, ShouldBeNil)
   117  	})
   118  
   119  	Convey("Test image invalid url", t, func() {
   120  		args := []string{"name", "dummyImageName", "--url", "invalidUrl"}
   121  		configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`)
   122  		defer os.Remove(configPath)
   123  		cmd := NewImageCommand(new(searchService))
   124  		buff := bytes.NewBufferString("")
   125  		cmd.SetOut(buff)
   126  		cmd.SetErr(buff)
   127  		cmd.SetArgs(args)
   128  		err := cmd.Execute()
   129  		So(err, ShouldNotBeNil)
   130  		So(strings.Contains(err.Error(), zerr.ErrInvalidURL.Error()), ShouldBeTrue)
   131  		So(buff.String(), ShouldContainSubstring, "invalid URL format")
   132  	})
   133  
   134  	Convey("Test image invalid url port", t, func() {
   135  		args := []string{"name", "dummyImageName", "--url", "http://localhost:99999"}
   136  		configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`)
   137  		defer os.Remove(configPath)
   138  		cmd := NewImageCommand(new(searchService))
   139  		buff := bytes.NewBufferString("")
   140  		cmd.SetOut(buff)
   141  		cmd.SetErr(buff)
   142  		cmd.SetArgs(args)
   143  		err := cmd.Execute()
   144  		So(err, ShouldNotBeNil)
   145  		So(buff.String(), ShouldContainSubstring, "invalid port")
   146  
   147  		Convey("without flags", func() {
   148  			args := []string{"list", "--url", "http://localhost:99999"}
   149  			configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`)
   150  			defer os.Remove(configPath)
   151  			cmd := NewImageCommand(new(searchService))
   152  			buff := bytes.NewBufferString("")
   153  			cmd.SetOut(buff)
   154  			cmd.SetErr(buff)
   155  			cmd.SetArgs(args)
   156  			err = cmd.Execute()
   157  			So(err, ShouldNotBeNil)
   158  			So(buff.String(), ShouldContainSubstring, "invalid port")
   159  		})
   160  	})
   161  
   162  	Convey("Test image unreachable", t, func() {
   163  		args := []string{"name", "dummyImageName", "--url", "http://localhost:9999"}
   164  		configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`)
   165  		defer os.Remove(configPath)
   166  		cmd := NewImageCommand(new(searchService))
   167  		buff := bytes.NewBufferString("")
   168  		cmd.SetOut(buff)
   169  		cmd.SetErr(buff)
   170  		cmd.SetArgs(args)
   171  		err := cmd.Execute()
   172  		So(err, ShouldNotBeNil)
   173  	})
   174  
   175  	Convey("Test image url from config", t, func() {
   176  		args := []string{"name", "dummyImageName", "--config", "imagetest"}
   177  		configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`)
   178  		defer os.Remove(configPath)
   179  		cmd := NewImageCommand(new(mockService))
   180  		buff := bytes.NewBufferString("")
   181  		cmd.SetOut(buff)
   182  		cmd.SetErr(buff)
   183  		cmd.SetArgs(args)
   184  		err := cmd.Execute()
   185  		space := regexp.MustCompile(`\s+`)
   186  		str := space.ReplaceAllString(buff.String(), " ")
   187  		So(strings.TrimSpace(str), ShouldEqual,
   188  			"REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE dummyImageName tag os/arch 6e2f80bf false 123kB")
   189  		So(err, ShouldBeNil)
   190  	})
   191  
   192  	Convey("Test image by name", t, func() {
   193  		args := []string{"name", "dummyImageName", "--url", "http://127.0.0.1:8080"}
   194  		configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","showspinner":false}]}`)
   195  		defer os.Remove(configPath)
   196  		imageCmd := NewImageCommand(new(mockService))
   197  		buff := &bytes.Buffer{}
   198  		imageCmd.SetOut(buff)
   199  		imageCmd.SetErr(buff)
   200  		imageCmd.SetArgs(args)
   201  		err := imageCmd.Execute()
   202  		space := regexp.MustCompile(`\s+`)
   203  		str := space.ReplaceAllString(buff.String(), " ")
   204  		So(strings.TrimSpace(str), ShouldEqual,
   205  			"REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE dummyImageName tag os/arch 6e2f80bf false 123kB")
   206  		So(err, ShouldBeNil)
   207  	})
   208  
   209  	Convey("Test image by digest", t, func() {
   210  		searchConfig := getTestSearchConfig("http://127.0.0.1:8080", new(mockService))
   211  		buff := &bytes.Buffer{}
   212  		searchConfig.ResultWriter = buff
   213  		err := SearchImagesByDigest(searchConfig, "6e2f80bf")
   214  		So(err, ShouldBeNil)
   215  
   216  		space := regexp.MustCompile(`\s+`)
   217  		str := space.ReplaceAllString(buff.String(), " ")
   218  		So(strings.TrimSpace(str), ShouldEqual,
   219  			"REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE anImage tag os/arch 6e2f80bf false 123kB")
   220  		So(err, ShouldBeNil)
   221  	})
   222  }
   223  
   224  func TestListRepos(t *testing.T) {
   225  	searchConfig := getTestSearchConfig("https://test-url.com", new(mockService))
   226  
   227  	Convey("Test listing repositories", t, func() {
   228  		buff := &bytes.Buffer{}
   229  		searchConfig.ResultWriter = buff
   230  		err := SearchRepos(searchConfig)
   231  		So(err, ShouldBeNil)
   232  	})
   233  
   234  	Convey("Test listing repositories with debug flag", t, func() {
   235  		args := []string{"list", "--config", "config-test", "--debug"}
   236  		configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"https://test-url.com","showspinner":false}]}`)
   237  		defer os.Remove(configPath)
   238  		cmd := NewRepoCommand(new(searchService))
   239  
   240  		buff := bytes.NewBufferString("")
   241  		cmd.SetOut(buff)
   242  		cmd.SetErr(buff)
   243  		cmd.SetArgs(args)
   244  		err := cmd.Execute()
   245  		So(err, ShouldNotBeNil)
   246  		space := regexp.MustCompile(`\s+`)
   247  		str := space.ReplaceAllString(buff.String(), " ")
   248  		actual := strings.TrimSpace(str)
   249  		So(actual, ShouldContainSubstring, "GET")
   250  	})
   251  
   252  	Convey("Test error on home directory", t, func() {
   253  		args := []string{"list", "--config", "config-test"}
   254  
   255  		configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"https://test-url.com","showspinner":false}]}`)
   256  		defer os.Remove(configPath)
   257  
   258  		err := os.Setenv("HOME", "nonExistentDirectory")
   259  		if err != nil {
   260  			panic(err)
   261  		}
   262  
   263  		cmd := NewRepoCommand(new(mockService))
   264  		buff := bytes.NewBufferString("")
   265  		cmd.SetOut(buff)
   266  		cmd.SetErr(buff)
   267  		cmd.SetArgs(args)
   268  		err = cmd.Execute()
   269  		So(err, ShouldNotBeNil)
   270  
   271  		home, err := os.UserHomeDir()
   272  		if err != nil {
   273  			panic(err)
   274  		}
   275  		err = os.Setenv("HOME", home)
   276  		if err != nil {
   277  			log.Fatal(err)
   278  		}
   279  	})
   280  
   281  	Convey("Test listing repositories error", t, func() {
   282  		args := []string{"list", "--config", "config-test"}
   283  		configPath := makeConfigFile(`{"configs":[{"_name":"config-test",
   284          	"url":"https://invalid.invalid","showspinner":false}]}`)
   285  		defer os.Remove(configPath)
   286  		cmd := NewRepoCommand(new(searchService))
   287  		buff := bytes.NewBufferString("")
   288  		cmd.SetOut(buff)
   289  		cmd.SetErr(buff)
   290  		cmd.SetArgs(args)
   291  		err := cmd.Execute()
   292  		So(err, ShouldNotBeNil)
   293  	})
   294  
   295  	Convey("Test unable to get config value", t, func() {
   296  		args := []string{"list", "--config", "config-test-nonexistent"}
   297  		configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"https://test-url.com","showspinner":false}]}`)
   298  		defer os.Remove(configPath)
   299  		cmd := NewRepoCommand(new(mockService))
   300  		buff := bytes.NewBufferString("")
   301  		cmd.SetOut(buff)
   302  		cmd.SetErr(buff)
   303  		cmd.SetArgs(args)
   304  		err := cmd.Execute()
   305  		So(err, ShouldNotBeNil)
   306  	})
   307  
   308  	Convey("Test error - no url provided", t, func() {
   309  		args := []string{"list", "--config", "config-test"}
   310  		configPath := makeConfigFile(`{"configs":[{"_name":"config-test","url":"","showspinner":false}]}`)
   311  		defer os.Remove(configPath)
   312  		cmd := NewRepoCommand(new(mockService))
   313  		buff := bytes.NewBufferString("")
   314  		cmd.SetOut(buff)
   315  		cmd.SetErr(buff)
   316  		cmd.SetArgs(args)
   317  		err := cmd.Execute()
   318  		So(err, ShouldNotBeNil)
   319  	})
   320  
   321  	Convey("Test error - spinner config invalid", t, func() {
   322  		args := []string{"list", "--config", "config-test"}
   323  		configPath := makeConfigFile(`{"configs":[{"_name":"config-test",
   324         		"url":"https://test-url.com","showspinner":invalid}]}`)
   325  		defer os.Remove(configPath)
   326  		cmd := NewRepoCommand(new(mockService))
   327  		buff := bytes.NewBufferString("")
   328  		cmd.SetOut(buff)
   329  		cmd.SetErr(buff)
   330  		cmd.SetArgs(args)
   331  		err := cmd.Execute()
   332  		So(err, ShouldNotBeNil)
   333  	})
   334  
   335  	Convey("Test error - verifyTLSConfig fails", t, func() {
   336  		args := []string{"list", "--config", "config-test"}
   337  		configPath := makeConfigFile(`{"configs":[{"_name":"config-test",
   338          	"verify-tls":"invalid", "url":"https://test-url.com","showspinner":false}]}`)
   339  		defer os.Remove(configPath)
   340  		cmd := NewRepoCommand(new(mockService))
   341  		buff := bytes.NewBufferString("")
   342  		cmd.SetOut(buff)
   343  		cmd.SetErr(buff)
   344  		cmd.SetArgs(args)
   345  		err := cmd.Execute()
   346  		So(err, ShouldNotBeNil)
   347  	})
   348  }
   349  
   350  func TestOutputFormat(t *testing.T) {
   351  	Convey("Test text", t, func() {
   352  		args := []string{"name", "dummyImageName", "--config", "imagetest", "-f", "text"}
   353  		configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`)
   354  		defer os.Remove(configPath)
   355  		cmd := NewImageCommand(new(mockService))
   356  		buff := bytes.NewBufferString("")
   357  		cmd.SetOut(buff)
   358  		cmd.SetErr(buff)
   359  		cmd.SetArgs(args)
   360  		err := cmd.Execute()
   361  		space := regexp.MustCompile(`\s+`)
   362  		str := space.ReplaceAllString(buff.String(), " ")
   363  		So(strings.TrimSpace(str), ShouldEqual,
   364  			"REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE dummyImageName tag os/arch 6e2f80bf false 123kB")
   365  		So(err, ShouldBeNil)
   366  	})
   367  
   368  	Convey("Test json", t, func() {
   369  		args := []string{"name", "dummyImageName", "--config", "imagetest", "-f", "json"}
   370  		configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`)
   371  		defer os.Remove(configPath)
   372  		cmd := NewImageCommand(new(mockService))
   373  		buff := bytes.NewBufferString("")
   374  		cmd.SetOut(buff)
   375  		cmd.SetErr(buff)
   376  		cmd.SetArgs(args)
   377  		err := cmd.Execute()
   378  		// Output is supposed to be in json lines format, keep all spaces as is for verification
   379  		So(buff.String(), ShouldEqual, `{"repoName":"dummyImageName","tag":"tag",`+
   380  			`"digest":"sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",`+
   381  			`"mediaType":"application/vnd.oci.image.manifest.v1+json",`+
   382  			`"manifests":[{"digest":"sha256:6e2f80bf9cfaabad474fbaf8ad68fdb652f776ea80b63492ecca404e5f6446a6",`+
   383  			`"configDigest":"sha256:4c10985c40365538426f2ba8cf0c21384a7769be502a550dcc0601b3736625e0",`+
   384  			`"lastUpdated":"0001-01-01T00:00:00Z","size":"123445","platform":{"os":"os","arch":"arch",`+
   385  			`"variant":""},"isSigned":false,"downloadCount":0,`+
   386  			`"layers":[{"size":"","digest":"sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6",`+
   387  			`"score":0}],"history":null,"vulnerabilities":{"maxSeverity":"","unknownCount":0,"lowCount":0,`+
   388  			`"mediumCount":0,"highCount":0,"criticalCount":0,"count":0},`+
   389  			`"referrers":null,"artifactType":"","signatureInfo":null}],"size":"123445",`+
   390  			`"downloadCount":0,"lastUpdated":"0001-01-01T00:00:00Z","description":"","isSigned":false,"licenses":"",`+
   391  			`"labels":"","title":"","source":"","documentation":"","authors":"","vendor":"",`+
   392  			`"vulnerabilities":{"maxSeverity":"","unknownCount":0,"lowCount":0,"mediumCount":0,"highCount":0,`+
   393  			`"criticalCount":0,"count":0},"referrers":null,"signatureInfo":null}`+"\n")
   394  		So(err, ShouldBeNil)
   395  	})
   396  
   397  	Convey("Test yaml", t, func() {
   398  		args := []string{"name", "dummyImageName", "--config", "imagetest", "-f", "yaml"}
   399  		configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`)
   400  		defer os.Remove(configPath)
   401  		cmd := NewImageCommand(new(mockService))
   402  		buff := bytes.NewBufferString("")
   403  		cmd.SetOut(buff)
   404  		cmd.SetErr(buff)
   405  		cmd.SetArgs(args)
   406  		err := cmd.Execute()
   407  		space := regexp.MustCompile(`\s+`)
   408  		str := space.ReplaceAllString(buff.String(), " ")
   409  		So(
   410  			strings.TrimSpace(str),
   411  			ShouldEqual,
   412  			`--- reponame: dummyImageName tag: tag `+
   413  				`digest: sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 `+
   414  				`mediatype: application/vnd.oci.image.manifest.v1+json manifests: - `+
   415  				`digest: sha256:6e2f80bf9cfaabad474fbaf8ad68fdb652f776ea80b63492ecca404e5f6446a6 `+
   416  				`configdigest: sha256:4c10985c40365538426f2ba8cf0c21384a7769be502a550dcc0601b3736625e0 `+
   417  				`lastupdated: 0001-01-01T00:00:00Z size: "123445" platform: os: os arch: arch variant: "" `+
   418  				`issigned: false downloadcount: 0 layers: - size: "" `+
   419  				`digest: sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6 score: 0 `+
   420  				`history: [] vulnerabilities: maxseverity: "" `+
   421  				`unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 0 criticalcount: 0 count: 0 `+
   422  				`referrers: [] artifacttype: "" `+
   423  				`signatureinfo: [] size: "123445" downloadcount: 0 `+
   424  				`lastupdated: 0001-01-01T00:00:00Z description: "" issigned: false licenses: "" labels: "" `+
   425  				`title: "" source: "" documentation: "" authors: "" vendor: "" vulnerabilities: maxseverity: "" `+
   426  				`unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 0 criticalcount: 0 `+
   427  				`count: 0 referrers: [] signatureinfo: []`,
   428  		)
   429  		So(err, ShouldBeNil)
   430  
   431  		Convey("Test yml", func() {
   432  			args := []string{"name", "dummyImageName", "--config", "imagetest", "-f", "yml"}
   433  			configPath := makeConfigFile(
   434  				`{"configs":[{"_name":"imagetest",` +
   435  					`"url":"https://test-url.com","showspinner":false}]}`,
   436  			)
   437  			defer os.Remove(configPath)
   438  			cmd := NewImageCommand(new(mockService))
   439  			buff := bytes.NewBufferString("")
   440  			cmd.SetOut(buff)
   441  			cmd.SetErr(buff)
   442  			cmd.SetArgs(args)
   443  			err := cmd.Execute()
   444  			space := regexp.MustCompile(`\s+`)
   445  			str := space.ReplaceAllString(buff.String(), " ")
   446  			So(
   447  				strings.TrimSpace(str),
   448  				ShouldEqual,
   449  				`--- reponame: dummyImageName tag: tag `+
   450  					`digest: sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 `+
   451  					`mediatype: application/vnd.oci.image.manifest.v1+json `+
   452  					`manifests: - digest: sha256:6e2f80bf9cfaabad474fbaf8ad68fdb652f776ea80b63492ecca404e5f6446a6 `+
   453  					`configdigest: sha256:4c10985c40365538426f2ba8cf0c21384a7769be502a550dcc0601b3736625e0 `+
   454  					`lastupdated: 0001-01-01T00:00:00Z size: "123445" platform: os: os arch: arch variant: "" `+
   455  					`issigned: false downloadcount: 0 layers: - size: "" `+
   456  					`digest: sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6 score: 0 `+
   457  					`history: [] vulnerabilities: maxseverity: "" unknowncount: 0 lowcount: 0 mediumcount: 0 `+
   458  					`highcount: 0 criticalcount: 0 count: 0 referrers: [] artifacttype: "" `+
   459  					`signatureinfo: [] size: "123445" downloadcount: 0 `+
   460  					`lastupdated: 0001-01-01T00:00:00Z description: "" issigned: false licenses: "" labels: "" `+
   461  					`title: "" source: "" documentation: "" authors: "" vendor: "" vulnerabilities: maxseverity: "" `+
   462  					`unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 0 criticalcount: 0 `+
   463  					`count: 0 referrers: [] signatureinfo: []`,
   464  			)
   465  			So(err, ShouldBeNil)
   466  		})
   467  	})
   468  
   469  	Convey("Test invalid", t, func() {
   470  		args := []string{"name", "dummyImageName", "--config", "imagetest", "-f", "random"}
   471  		configPath := makeConfigFile(`{"configs":[{"_name":"imagetest","url":"https://test-url.com","showspinner":false}]}`)
   472  		defer os.Remove(configPath)
   473  		cmd := NewImageCommand(new(mockService))
   474  		buff := bytes.NewBufferString("")
   475  		cmd.SetOut(buff)
   476  		cmd.SetErr(buff)
   477  		cmd.SetArgs(args)
   478  		err := cmd.Execute()
   479  		So(err, ShouldNotBeNil)
   480  		So(buff.String(), ShouldContainSubstring, "invalid cli output format")
   481  	})
   482  }
   483  
   484  func TestImagesCommandGQL(t *testing.T) {
   485  	port := test.GetFreePort()
   486  	baseURL := test.GetBaseURL(port)
   487  	conf := config.New()
   488  	conf.HTTP.Port = port
   489  
   490  	defaultVal := true
   491  	conf.Extensions = &extconf.ExtensionConfig{
   492  		Search: &extconf.SearchConfig{
   493  			BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
   494  		},
   495  	}
   496  	ctlr := api.NewController(conf)
   497  	ctlr.Config.Storage.RootDirectory = t.TempDir()
   498  	cm := test.NewControllerManager(ctlr)
   499  
   500  	cm.StartAndWait(conf.HTTP.Port)
   501  	defer cm.StopServer()
   502  
   503  	Convey("commands with gql", t, func() {
   504  		err := removeLocalStorageContents(ctlr.StoreController.DefaultStore)
   505  		So(err, ShouldBeNil)
   506  
   507  		Convey("base and derived command", func() {
   508  			baseImage := CreateImageWith().LayerBlobs(
   509  				[][]byte{{1, 2, 3}, {11, 22, 33}},
   510  			).DefaultConfig().Build()
   511  
   512  			derivedImage := CreateImageWith().LayerBlobs(
   513  				[][]byte{{1, 2, 3}, {11, 22, 33}, {44, 55, 66}},
   514  			).DefaultConfig().Build()
   515  
   516  			err := UploadImage(baseImage, baseURL, "repo", "base")
   517  			So(err, ShouldBeNil)
   518  
   519  			err = UploadImage(derivedImage, baseURL, "repo", "derived")
   520  			So(err, ShouldBeNil)
   521  
   522  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
   523  				baseURL))
   524  			defer os.Remove(configPath)
   525  			args := []string{"base", "repo:derived", "--config", "imagetest"}
   526  
   527  			cmd := NewImageCommand(NewSearchService())
   528  			buff := bytes.NewBufferString("")
   529  			cmd.SetOut(buff)
   530  			cmd.SetErr(buff)
   531  			cmd.SetArgs(args)
   532  			err = cmd.Execute()
   533  			So(err, ShouldBeNil)
   534  			space := regexp.MustCompile(`\s+`)
   535  			str := space.ReplaceAllString(buff.String(), " ")
   536  			actual := strings.TrimSpace(str)
   537  			So(actual, ShouldContainSubstring, "repo base linux/amd64 df554ddd false 699B")
   538  			args = []string{"derived", "repo:base", "--config", "imagetest"}
   539  
   540  			cmd = NewImageCommand(NewSearchService())
   541  			buff = bytes.NewBufferString("")
   542  			cmd.SetOut(buff)
   543  			cmd.SetErr(buff)
   544  			cmd.SetArgs(args)
   545  			err = cmd.Execute()
   546  			So(err, ShouldBeNil)
   547  			str = space.ReplaceAllString(buff.String(), " ")
   548  			actual = strings.TrimSpace(str)
   549  			So(actual, ShouldContainSubstring, "repo derived linux/amd64 79f4b82e false 854B")
   550  		})
   551  
   552  		Convey("base and derived command errors", func() {
   553  			// too many parameters
   554  			buff := bytes.NewBufferString("")
   555  			args := []string{"too", "many", "args", "--config", "imagetest"}
   556  			cmd := NewImageBaseCommand(NewSearchService())
   557  			cmd.SetOut(buff)
   558  			cmd.SetErr(buff)
   559  			cmd.SetArgs(args)
   560  			err := cmd.Execute()
   561  			So(err, ShouldNotBeNil)
   562  
   563  			cmd = NewImageDerivedCommand(NewSearchService())
   564  			cmd.SetOut(buff)
   565  			cmd.SetErr(buff)
   566  			cmd.SetArgs(args)
   567  			err = cmd.Execute()
   568  			So(err, ShouldNotBeNil)
   569  
   570  			// bad input
   571  			buff = bytes.NewBufferString("")
   572  			args = []string{"only-repo"}
   573  			cmd = NewImageBaseCommand(NewSearchService())
   574  			cmd.SetOut(buff)
   575  			cmd.SetErr(buff)
   576  			cmd.SetArgs(args)
   577  			err = cmd.Execute()
   578  			So(err, ShouldNotBeNil)
   579  
   580  			cmd = NewImageDerivedCommand(NewSearchService())
   581  			cmd.SetOut(buff)
   582  			cmd.SetErr(buff)
   583  			cmd.SetArgs(args)
   584  			err = cmd.Execute()
   585  			So(err, ShouldNotBeNil)
   586  
   587  			// no url
   588  			buff = bytes.NewBufferString("")
   589  			args = []string{"repo:tag"}
   590  			cmd = NewImageBaseCommand(NewSearchService())
   591  			cmd.SetOut(buff)
   592  			cmd.SetErr(buff)
   593  			cmd.SetArgs(args)
   594  			err = cmd.Execute()
   595  			So(err, ShouldNotBeNil)
   596  
   597  			cmd = NewImageDerivedCommand(NewSearchService())
   598  			cmd.SetOut(buff)
   599  			cmd.SetErr(buff)
   600  			cmd.SetArgs(args)
   601  			err = cmd.Execute()
   602  			So(err, ShouldNotBeNil)
   603  		})
   604  
   605  		Convey("digest command", func() {
   606  			image := CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build()
   607  
   608  			err := UploadImage(image, baseURL, "repo", "img")
   609  			So(err, ShouldBeNil)
   610  
   611  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
   612  				baseURL))
   613  			defer os.Remove(configPath)
   614  			args := []string{"digest", image.DigestStr(), "--config", "imagetest"}
   615  
   616  			cmd := NewImageCommand(NewSearchService())
   617  			buff := bytes.NewBufferString("")
   618  			cmd.SetOut(buff)
   619  			cmd.SetErr(buff)
   620  			cmd.SetArgs(args)
   621  			err = cmd.Execute()
   622  			So(err, ShouldBeNil)
   623  			space := regexp.MustCompile(`\s+`)
   624  			str := space.ReplaceAllString(buff.String(), " ")
   625  			actual := strings.TrimSpace(str)
   626  			So(actual, ShouldContainSubstring, fmt.Sprintf("repo img linux/amd64 %s false 552B",
   627  				image.DigestStr()[7:7+8]))
   628  		})
   629  
   630  		Convey("digest command errors", func() {
   631  			// too many parameters
   632  			buff := bytes.NewBufferString("")
   633  			args := []string{"too", "many", "args", "--config", "imagetest"}
   634  			cmd := NewImageDigestCommand(NewSearchService())
   635  			cmd.SetOut(buff)
   636  			cmd.SetErr(buff)
   637  			cmd.SetArgs(args)
   638  			err := cmd.Execute()
   639  			So(err, ShouldNotBeNil)
   640  
   641  			// bad input
   642  			buff = bytes.NewBufferString("")
   643  			args = []string{"bad-digest"}
   644  			cmd = NewImageDigestCommand(NewSearchService())
   645  			cmd.SetOut(buff)
   646  			cmd.SetErr(buff)
   647  			cmd.SetArgs(args)
   648  			err = cmd.Execute()
   649  			So(err, ShouldNotBeNil)
   650  
   651  			// no url
   652  			buff = bytes.NewBufferString("")
   653  			args = []string{godigest.FromString("str").String()}
   654  			cmd = NewImageDigestCommand(NewSearchService())
   655  			cmd.SetOut(buff)
   656  			cmd.SetErr(buff)
   657  			cmd.SetArgs(args)
   658  			err = cmd.Execute()
   659  			So(err, ShouldNotBeNil)
   660  		})
   661  
   662  		Convey("list command", func() {
   663  			image := CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build()
   664  
   665  			err := UploadImage(image, baseURL, "repo", "img")
   666  			So(err, ShouldBeNil)
   667  
   668  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
   669  				baseURL))
   670  			defer os.Remove(configPath)
   671  			args := []string{"list", "--config", "imagetest"}
   672  
   673  			cmd := NewImageCommand(NewSearchService())
   674  			buff := bytes.NewBufferString("")
   675  			cmd.SetOut(buff)
   676  			cmd.SetErr(buff)
   677  			cmd.SetArgs(args)
   678  			err = cmd.Execute()
   679  			So(err, ShouldBeNil)
   680  			space := regexp.MustCompile(`\s+`)
   681  			str := space.ReplaceAllString(buff.String(), " ")
   682  			actual := strings.TrimSpace(str)
   683  			fmt.Println(actual)
   684  			So(actual, ShouldContainSubstring, fmt.Sprintf("repo img linux/amd64 %s false 552B",
   685  				image.DigestStr()[7:7+8]))
   686  			fmt.Println(actual)
   687  		})
   688  
   689  		Convey("list command errors", func() {
   690  			// too many parameters
   691  			buff := bytes.NewBufferString("")
   692  			args := []string{"repo:img", "arg", "--config", "imagetest"}
   693  			cmd := NewImageListCommand(NewSearchService())
   694  			cmd.SetOut(buff)
   695  			cmd.SetErr(buff)
   696  			cmd.SetArgs(args)
   697  			err := cmd.Execute()
   698  			So(err, ShouldNotBeNil)
   699  
   700  			// no url
   701  			buff = bytes.NewBufferString("")
   702  			args = []string{}
   703  			cmd = NewImageListCommand(NewSearchService())
   704  			cmd.SetOut(buff)
   705  			cmd.SetErr(buff)
   706  			cmd.SetArgs(args)
   707  			err = cmd.Execute()
   708  			So(err, ShouldNotBeNil)
   709  		})
   710  
   711  		Convey("name command", func() {
   712  			image := CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build()
   713  
   714  			err := UploadImage(image, baseURL, "repo", "img")
   715  			So(err, ShouldBeNil)
   716  
   717  			err = UploadImage(CreateRandomImage(), baseURL, "repo", "img2")
   718  			So(err, ShouldBeNil)
   719  
   720  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
   721  				baseURL))
   722  			defer os.Remove(configPath)
   723  			args := []string{"name", "repo:img", "--config", "imagetest"}
   724  
   725  			cmd := NewImageCommand(NewSearchService())
   726  			buff := bytes.NewBufferString("")
   727  			cmd.SetOut(buff)
   728  			cmd.SetErr(buff)
   729  			cmd.SetArgs(args)
   730  			err = cmd.Execute()
   731  			So(err, ShouldBeNil)
   732  			space := regexp.MustCompile(`\s+`)
   733  			str := space.ReplaceAllString(buff.String(), " ")
   734  			actual := strings.TrimSpace(str)
   735  			fmt.Println(actual)
   736  			So(actual, ShouldContainSubstring, fmt.Sprintf("repo img linux/amd64 %s false 552B",
   737  				image.DigestStr()[7:7+8]))
   738  			fmt.Println(actual)
   739  		})
   740  
   741  		Convey("name command errors", func() {
   742  			// too many parameters
   743  			buff := bytes.NewBufferString("")
   744  			args := []string{"repo:img", "arg", "--config", "imagetest"}
   745  			cmd := NewImageNameCommand(NewSearchService())
   746  			cmd.SetOut(buff)
   747  			cmd.SetErr(buff)
   748  			cmd.SetArgs(args)
   749  			err := cmd.Execute()
   750  			So(err, ShouldNotBeNil)
   751  
   752  			// bad input
   753  			buff = bytes.NewBufferString("")
   754  			args = []string{":tag"}
   755  			cmd = NewImageNameCommand(NewSearchService())
   756  			cmd.SetOut(buff)
   757  			cmd.SetErr(buff)
   758  			cmd.SetArgs(args)
   759  			err = cmd.Execute()
   760  			So(err, ShouldNotBeNil)
   761  
   762  			// no url
   763  			buff = bytes.NewBufferString("")
   764  			args = []string{"repo:tag"}
   765  			cmd = NewImageNameCommand(NewSearchService())
   766  			cmd.SetOut(buff)
   767  			cmd.SetErr(buff)
   768  			cmd.SetArgs(args)
   769  			err = cmd.Execute()
   770  			So(err, ShouldNotBeNil)
   771  		})
   772  
   773  		Convey("CVE", func() {
   774  			vulnImage := CreateDefaultVulnerableImage()
   775  			err := UploadImage(vulnImage, baseURL, "repo", "vuln")
   776  			So(err, ShouldBeNil)
   777  
   778  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
   779  				baseURL))
   780  			defer os.Remove(configPath)
   781  
   782  			args := []string{"cve", "repo:vuln", "--config", "imagetest"}
   783  			cmd := NewImageCommand(mockService{})
   784  			buff := bytes.NewBufferString("")
   785  			cmd.SetOut(buff)
   786  			cmd.SetErr(buff)
   787  			cmd.SetArgs(args)
   788  			err = cmd.Execute()
   789  			So(err, ShouldBeNil)
   790  			space := regexp.MustCompile(`\s+`)
   791  			str := space.ReplaceAllString(buff.String(), " ")
   792  			actual := strings.TrimSpace(str)
   793  			So(actual, ShouldContainSubstring, "CRITICAL 0, HIGH 1, MEDIUM 0, LOW 0, UNKNOWN 0, TOTAL 1")
   794  			So(actual, ShouldContainSubstring, "dummyCVEID HIGH Title of that CVE")
   795  		})
   796  
   797  		Convey("CVE errors", func() {
   798  			count := 0
   799  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
   800  				baseURL))
   801  			defer os.Remove(configPath)
   802  			args := []string{"cve", "repo:vuln", "--config", "imagetest"}
   803  			cmd := NewImageCommand(mockService{
   804  				getCveByImageGQLFn: func(ctx context.Context, config SearchConfig, username, password,
   805  					imageName, searchedCVE string) (*cveResult, error,
   806  				) {
   807  					if count == 0 {
   808  						count++
   809  						fmt.Println("Count:", count)
   810  
   811  						return &cveResult{}, zerr.ErrCVEDBNotFound
   812  					}
   813  
   814  					return &cveResult{}, zerr.ErrInjected
   815  				},
   816  			})
   817  			buff := bytes.NewBufferString("")
   818  			cmd.SetOut(buff)
   819  			cmd.SetErr(buff)
   820  			cmd.SetArgs(args)
   821  			err = cmd.Execute()
   822  			So(err, ShouldNotBeNil)
   823  			space := regexp.MustCompile(`\s+`)
   824  			str := space.ReplaceAllString(buff.String(), " ")
   825  			actual := strings.TrimSpace(str)
   826  			So(actual, ShouldContainSubstring, "[warning] CVE DB is not ready")
   827  		})
   828  	})
   829  
   830  	Convey("Config error", t, func() {
   831  		args := []string{"base", "repo:derived", "--config", "imagetest"}
   832  		cmd := NewImageCommand(NewSearchService())
   833  		buff := bytes.NewBufferString("")
   834  		cmd.SetOut(buff)
   835  		cmd.SetErr(buff)
   836  		cmd.SetArgs(args)
   837  		err := cmd.Execute()
   838  		So(err, ShouldNotBeNil)
   839  		So(err, ShouldNotBeNil)
   840  
   841  		args = []string{"derived", "repo:base"}
   842  		cmd = NewImageCommand(NewSearchService())
   843  		buff = bytes.NewBufferString("")
   844  		cmd.SetOut(buff)
   845  		cmd.SetErr(buff)
   846  		cmd.SetArgs(args)
   847  		err = cmd.Execute()
   848  		So(err, ShouldNotBeNil)
   849  
   850  		args = []string{"digest", ispec.DescriptorEmptyJSON.Digest.String()}
   851  		cmd = NewImageCommand(NewSearchService())
   852  		buff = bytes.NewBufferString("")
   853  		cmd.SetOut(buff)
   854  		cmd.SetErr(buff)
   855  		cmd.SetArgs(args)
   856  		err = cmd.Execute()
   857  		So(err, ShouldNotBeNil)
   858  
   859  		args = []string{"list"}
   860  		cmd = NewImageCommand(NewSearchService())
   861  		buff = bytes.NewBufferString("")
   862  		cmd.SetOut(buff)
   863  		cmd.SetErr(buff)
   864  		cmd.SetArgs(args)
   865  		err = cmd.Execute()
   866  		So(err, ShouldNotBeNil)
   867  
   868  		args = []string{"name", "repo:img"}
   869  		cmd = NewImageCommand(NewSearchService())
   870  		buff = bytes.NewBufferString("")
   871  		cmd.SetOut(buff)
   872  		cmd.SetErr(buff)
   873  		cmd.SetArgs(args)
   874  		err = cmd.Execute()
   875  		So(err, ShouldNotBeNil)
   876  
   877  		args = []string{"cve", "repo:vuln"}
   878  		cmd = NewImageCommand(mockService{})
   879  		buff = bytes.NewBufferString("")
   880  		cmd.SetOut(buff)
   881  		cmd.SetErr(buff)
   882  		cmd.SetArgs(args)
   883  		err = cmd.Execute()
   884  		So(err, ShouldNotBeNil)
   885  	})
   886  }
   887  
   888  func TestImageCommandREST(t *testing.T) {
   889  	port := test.GetFreePort()
   890  	baseURL := test.GetBaseURL(port)
   891  	conf := config.New()
   892  	conf.HTTP.Port = port
   893  
   894  	ctlr := api.NewController(conf)
   895  	ctlr.Config.Storage.RootDirectory = t.TempDir()
   896  	cm := test.NewControllerManager(ctlr)
   897  
   898  	cm.StartAndWait(conf.HTTP.Port)
   899  	defer cm.StopServer()
   900  
   901  	Convey("commands without gql", t, func() {
   902  		err := removeLocalStorageContents(ctlr.StoreController.DefaultStore)
   903  		So(err, ShouldBeNil)
   904  
   905  		Convey("base and derived command", func() {
   906  			baseImage := CreateImageWith().LayerBlobs(
   907  				[][]byte{{1, 2, 3}, {11, 22, 33}},
   908  			).DefaultConfig().Build()
   909  
   910  			derivedImage := CreateImageWith().LayerBlobs(
   911  				[][]byte{{1, 2, 3}, {11, 22, 33}, {44, 55, 66}},
   912  			).DefaultConfig().Build()
   913  
   914  			err := UploadImage(baseImage, baseURL, "repo", "base")
   915  			So(err, ShouldBeNil)
   916  
   917  			err = UploadImage(derivedImage, baseURL, "repo", "derived")
   918  			So(err, ShouldBeNil)
   919  
   920  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
   921  				baseURL))
   922  			defer os.Remove(configPath)
   923  
   924  			args := []string{"base", "repo:derived", "--config", "imagetest"}
   925  			cmd := NewImageCommand(NewSearchService())
   926  			buff := bytes.NewBufferString("")
   927  			cmd.SetOut(buff)
   928  			cmd.SetErr(buff)
   929  			cmd.SetArgs(args)
   930  			err = cmd.Execute()
   931  			So(err, ShouldNotBeNil)
   932  
   933  			args = []string{"derived", "repo:base"}
   934  			cmd = NewImageCommand(NewSearchService())
   935  			buff = bytes.NewBufferString("")
   936  			cmd.SetOut(buff)
   937  			cmd.SetErr(buff)
   938  			cmd.SetArgs(args)
   939  			err = cmd.Execute()
   940  			So(err, ShouldNotBeNil)
   941  		})
   942  
   943  		Convey("digest command", func() {
   944  			image := CreateRandomImage()
   945  
   946  			err := UploadImage(image, baseURL, "repo", "img")
   947  			So(err, ShouldBeNil)
   948  
   949  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
   950  				baseURL))
   951  			defer os.Remove(configPath)
   952  
   953  			args := []string{"digest", image.DigestStr(), "--config", "imagetest"}
   954  			cmd := NewImageCommand(NewSearchService())
   955  			buff := bytes.NewBufferString("")
   956  			cmd.SetOut(buff)
   957  			cmd.SetErr(buff)
   958  			cmd.SetArgs(args)
   959  			err = cmd.Execute()
   960  			So(err, ShouldNotBeNil)
   961  		})
   962  
   963  		Convey("list command", func() {
   964  			image := CreateRandomImage()
   965  
   966  			err := UploadImage(image, baseURL, "repo", "img")
   967  			So(err, ShouldBeNil)
   968  
   969  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
   970  				baseURL))
   971  			defer os.Remove(configPath)
   972  
   973  			args := []string{"list", "--config", "imagetest"}
   974  			cmd := NewImageCommand(NewSearchService())
   975  			buff := bytes.NewBufferString("")
   976  			cmd.SetOut(buff)
   977  			cmd.SetErr(buff)
   978  			cmd.SetArgs(args)
   979  			err = cmd.Execute()
   980  			So(err, ShouldBeNil)
   981  			fmt.Println(buff.String())
   982  			fmt.Println()
   983  		})
   984  
   985  		Convey("name command", func() {
   986  			image := CreateRandomImage()
   987  
   988  			err := UploadImage(image, baseURL, "repo", "img")
   989  			So(err, ShouldBeNil)
   990  
   991  			err = UploadImage(CreateRandomImage(), baseURL, "repo", "img2")
   992  			So(err, ShouldBeNil)
   993  
   994  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
   995  				baseURL))
   996  			defer os.Remove(configPath)
   997  
   998  			args := []string{"name", "repo:img", "--config", "imagetest"}
   999  			cmd := NewImageCommand(NewSearchService())
  1000  			buff := bytes.NewBufferString("")
  1001  			cmd.SetOut(buff)
  1002  			cmd.SetErr(buff)
  1003  			cmd.SetArgs(args)
  1004  			err = cmd.Execute()
  1005  			So(err, ShouldBeNil)
  1006  			fmt.Println(buff.String())
  1007  			fmt.Println()
  1008  		})
  1009  
  1010  		Convey("CVE", func() {
  1011  			vulnImage := CreateDefaultVulnerableImage()
  1012  			err := UploadImage(vulnImage, baseURL, "repo", "vuln")
  1013  			So(err, ShouldBeNil)
  1014  
  1015  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"imagetest","url":"%s","showspinner":false}]}`,
  1016  				baseURL))
  1017  			args := []string{"cve", "repo:vuln", "--config", "imagetest"}
  1018  			defer os.Remove(configPath)
  1019  			cmd := NewImageCommand(mockService{})
  1020  			buff := bytes.NewBufferString("")
  1021  			cmd.SetOut(buff)
  1022  			cmd.SetErr(buff)
  1023  			cmd.SetArgs(args)
  1024  			err = cmd.Execute()
  1025  			So(err, ShouldNotBeNil)
  1026  		})
  1027  	})
  1028  }
  1029  
  1030  type mockService struct {
  1031  	getAllImagesFn func(ctx context.Context, config SearchConfig, username, password string,
  1032  		channel chan stringResult, wtgrp *sync.WaitGroup)
  1033  
  1034  	getImagesGQLFn func(ctx context.Context, config SearchConfig, username, password string,
  1035  		imageName string) (*common.ImageListResponse, error)
  1036  
  1037  	getImageByNameFn func(ctx context.Context, config SearchConfig,
  1038  		username, password, imageName string, channel chan stringResult, wtgrp *sync.WaitGroup,
  1039  	)
  1040  
  1041  	getImagesByDigestFn func(ctx context.Context, config SearchConfig, username,
  1042  		password, digest string, rch chan stringResult, wtgrp *sync.WaitGroup,
  1043  	)
  1044  
  1045  	getReferrersFn func(ctx context.Context, config SearchConfig, username, password string,
  1046  		repo, digest string,
  1047  	) (referrersResult, error)
  1048  
  1049  	globalSearchGQLFn func(ctx context.Context, config SearchConfig, username, password string,
  1050  		query string,
  1051  	) (*common.GlobalSearch, error)
  1052  
  1053  	getReferrersGQLFn func(ctx context.Context, config SearchConfig, username, password string,
  1054  		repo, digest string,
  1055  	) (*common.ReferrersResp, error)
  1056  
  1057  	getDerivedImageListGQLFn func(ctx context.Context, config SearchConfig, username, password string,
  1058  		derivedImage string,
  1059  	) (*common.DerivedImageListResponse, error)
  1060  
  1061  	getBaseImageListGQLFn func(ctx context.Context, config SearchConfig, username, password string,
  1062  		derivedImage string,
  1063  	) (*common.BaseImageListResponse, error)
  1064  
  1065  	getImagesForDigestGQLFn func(ctx context.Context, config SearchConfig, username, password string,
  1066  		digest string,
  1067  	) (*common.ImagesForDigest, error)
  1068  
  1069  	getCveByImageGQLFn func(ctx context.Context, config SearchConfig, username, password,
  1070  		imageName, searchedCVE string,
  1071  	) (*cveResult, error)
  1072  
  1073  	getTagsForCVEGQLFn func(ctx context.Context, config SearchConfig, username, password,
  1074  		imageName, cveID string,
  1075  	) (*common.ImagesForCve, error)
  1076  
  1077  	getFixedTagsForCVEGQLFn func(ctx context.Context, config SearchConfig, username, password,
  1078  		imageName, cveID string,
  1079  	) (*common.ImageListWithCVEFixedResponse, error)
  1080  
  1081  	getCVEDiffListGQLFn func(ctx context.Context, config SearchConfig, username, password string,
  1082  		minuend, subtrahend ImageIdentifier,
  1083  	) (*cveDiffListResp, error)
  1084  }
  1085  
  1086  func (service mockService) getCVEDiffListGQL(ctx context.Context, config SearchConfig, username, password string,
  1087  	minuend, subtrahend ImageIdentifier,
  1088  ) (*cveDiffListResp, error) {
  1089  	if service.getCVEDiffListGQLFn != nil {
  1090  		return service.getCVEDiffListGQLFn(ctx, config, username, password, minuend, subtrahend)
  1091  	}
  1092  
  1093  	return &cveDiffListResp{}, nil
  1094  }
  1095  
  1096  func (service mockService) getRepos(ctx context.Context, config SearchConfig, username,
  1097  	password string, channel chan stringResult, wtgrp *sync.WaitGroup,
  1098  ) {
  1099  	defer wtgrp.Done()
  1100  	defer close(channel)
  1101  
  1102  	fmt.Fprintln(config.ResultWriter, "\n\nREPOSITORY NAME")
  1103  
  1104  	fmt.Fprintln(config.ResultWriter, "repo1")
  1105  	fmt.Fprintln(config.ResultWriter, "repo2")
  1106  }
  1107  
  1108  func (service mockService) getReferrers(ctx context.Context, config SearchConfig, username, password string,
  1109  	repo, digest string,
  1110  ) (referrersResult, error) {
  1111  	if service.getReferrersFn != nil {
  1112  		return service.getReferrersFn(ctx, config, username, password, repo, digest)
  1113  	}
  1114  
  1115  	return referrersResult{
  1116  		common.Referrer{
  1117  			ArtifactType: "art.type",
  1118  			Digest:       ispec.DescriptorEmptyJSON.Digest.String(),
  1119  			MediaType:    ispec.MediaTypeImageManifest,
  1120  			Size:         100,
  1121  		},
  1122  	}, nil
  1123  }
  1124  
  1125  func (service mockService) globalSearchGQL(ctx context.Context, config SearchConfig, username, password string,
  1126  	query string,
  1127  ) (*common.GlobalSearch, error) {
  1128  	if service.globalSearchGQLFn != nil {
  1129  		return service.globalSearchGQLFn(ctx, config, username, password, query)
  1130  	}
  1131  
  1132  	return &common.GlobalSearch{
  1133  		Images: []common.ImageSummary{
  1134  			{
  1135  				RepoName:  "repo",
  1136  				MediaType: ispec.MediaTypeImageManifest,
  1137  				Size:      "100",
  1138  				Manifests: []common.ManifestSummary{
  1139  					{
  1140  						Digest:       godigest.FromString("str").String(),
  1141  						Size:         "100",
  1142  						ConfigDigest: ispec.DescriptorEmptyJSON.Digest.String(),
  1143  					},
  1144  				},
  1145  			},
  1146  		},
  1147  		Repos: []common.RepoSummary{
  1148  			{
  1149  				Name:        "repo",
  1150  				Size:        "100",
  1151  				LastUpdated: time.Date(2010, 1, 1, 1, 1, 1, 0, time.UTC),
  1152  			},
  1153  		},
  1154  	}, nil
  1155  }
  1156  
  1157  func (service mockService) getReferrersGQL(ctx context.Context, config SearchConfig, username, password string,
  1158  	repo, digest string,
  1159  ) (*common.ReferrersResp, error) {
  1160  	if service.getReferrersGQLFn != nil {
  1161  		return service.getReferrersGQLFn(ctx, config, username, password, repo, digest)
  1162  	}
  1163  
  1164  	return &common.ReferrersResp{
  1165  		ReferrersResult: common.ReferrersResult{
  1166  			Referrers: []common.Referrer{
  1167  				{
  1168  					MediaType:    "MediaType",
  1169  					ArtifactType: "ArtifactType",
  1170  					Size:         100,
  1171  					Digest:       "Digest",
  1172  				},
  1173  			},
  1174  		},
  1175  	}, nil
  1176  }
  1177  
  1178  func (service mockService) getDerivedImageListGQL(ctx context.Context, config SearchConfig, username, password string,
  1179  	derivedImage string,
  1180  ) (*common.DerivedImageListResponse, error) {
  1181  	if service.getDerivedImageListGQLFn != nil {
  1182  		return service.getDerivedImageListGQLFn(ctx, config, username, password, derivedImage)
  1183  	}
  1184  
  1185  	imageListGQLResponse := &common.DerivedImageListResponse{}
  1186  	imageListGQLResponse.DerivedImageList.Results = []common.ImageSummary{
  1187  		{
  1188  			RepoName: "dummyImageName",
  1189  			Tag:      "tag",
  1190  			Manifests: []common.ManifestSummary{
  1191  				{
  1192  					Digest:       godigest.FromString("Digest").String(),
  1193  					ConfigDigest: godigest.FromString("ConfigDigest").String(),
  1194  					Size:         "123445",
  1195  					Layers:       []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}},
  1196  					Platform:     common.Platform{Os: "os", Arch: "arch"},
  1197  				},
  1198  			},
  1199  			Size: "123445",
  1200  		},
  1201  	}
  1202  
  1203  	return imageListGQLResponse, nil
  1204  }
  1205  
  1206  func (service mockService) getBaseImageListGQL(ctx context.Context, config SearchConfig, username, password string,
  1207  	baseImage string,
  1208  ) (*common.BaseImageListResponse, error) {
  1209  	if service.getBaseImageListGQLFn != nil {
  1210  		return service.getBaseImageListGQLFn(ctx, config, username, password, baseImage)
  1211  	}
  1212  
  1213  	imageListGQLResponse := &common.BaseImageListResponse{}
  1214  	imageListGQLResponse.BaseImageList.Results = []common.ImageSummary{
  1215  		{
  1216  			RepoName: "dummyImageName",
  1217  			Tag:      "tag",
  1218  			Manifests: []common.ManifestSummary{
  1219  				{
  1220  					Digest:       godigest.FromString("Digest").String(),
  1221  					ConfigDigest: godigest.FromString("ConfigDigest").String(),
  1222  					Size:         "123445",
  1223  					Layers:       []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}},
  1224  					Platform:     common.Platform{Os: "os", Arch: "arch"},
  1225  				},
  1226  			},
  1227  			Size: "123445",
  1228  		},
  1229  	}
  1230  
  1231  	return imageListGQLResponse, nil
  1232  }
  1233  
  1234  func (service mockService) getImagesGQL(ctx context.Context, config SearchConfig, username, password string,
  1235  	imageName string,
  1236  ) (*common.ImageListResponse, error) {
  1237  	if service.getImagesGQLFn != nil {
  1238  		return service.getImagesGQLFn(ctx, config, username, password, imageName)
  1239  	}
  1240  
  1241  	imageListGQLResponse := &common.ImageListResponse{}
  1242  	imageListGQLResponse.PaginatedImagesResult.Results = []common.ImageSummary{
  1243  		{
  1244  			RepoName:  "dummyImageName",
  1245  			Tag:       "tag",
  1246  			MediaType: ispec.MediaTypeImageManifest,
  1247  			Digest:    godigest.FromString("test").String(),
  1248  			Manifests: []common.ManifestSummary{
  1249  				{
  1250  					Digest:       godigest.FromString("Digest").String(),
  1251  					ConfigDigest: godigest.FromString("ConfigDigest").String(),
  1252  					Size:         "123445",
  1253  					Layers:       []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}},
  1254  					Platform:     common.Platform{Os: "os", Arch: "arch"},
  1255  				},
  1256  			},
  1257  			Size: "123445",
  1258  		},
  1259  	}
  1260  
  1261  	return imageListGQLResponse, nil
  1262  }
  1263  
  1264  func (service mockService) getImagesForDigestGQL(ctx context.Context, config SearchConfig, username, password string,
  1265  	digest string,
  1266  ) (*common.ImagesForDigest, error) {
  1267  	if service.getImagesForDigestGQLFn != nil {
  1268  		return service.getImagesForDigestGQLFn(ctx, config, username, password, digest)
  1269  	}
  1270  
  1271  	imageListGQLResponse := &common.ImagesForDigest{}
  1272  	imageListGQLResponse.Results = []common.ImageSummary{
  1273  		{
  1274  			RepoName:  "randomimageName",
  1275  			Tag:       "tag",
  1276  			MediaType: ispec.MediaTypeImageManifest,
  1277  			Digest:    godigest.FromString("test").String(),
  1278  			Manifests: []common.ManifestSummary{
  1279  				{
  1280  					Digest:       godigest.FromString("Digest").String(),
  1281  					ConfigDigest: godigest.FromString("ConfigDigest").String(),
  1282  					Layers:       []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}},
  1283  					Size:         "123445",
  1284  					Platform:     common.Platform{Os: "os", Arch: "arch"},
  1285  				},
  1286  			},
  1287  			Size: "123445",
  1288  		},
  1289  	}
  1290  
  1291  	return imageListGQLResponse, nil
  1292  }
  1293  
  1294  func (service mockService) getTagsForCVEGQL(ctx context.Context, config SearchConfig, username, password,
  1295  	imageName, cveID string,
  1296  ) (*common.ImagesForCve, error) {
  1297  	if service.getTagsForCVEGQLFn != nil {
  1298  		return service.getTagsForCVEGQLFn(ctx, config, username, password, imageName, cveID)
  1299  	}
  1300  
  1301  	images := &common.ImagesForCve{
  1302  		Errors: nil,
  1303  		ImagesForCVEList: struct {
  1304  			common.PaginatedImagesResult `json:"ImageListForCVE"` //nolint:tagliatelle // graphQL schema
  1305  		}{},
  1306  	}
  1307  
  1308  	if imageName == "" {
  1309  		imageName = "image-name"
  1310  	}
  1311  
  1312  	images.Errors = nil
  1313  
  1314  	mockedImage := service.getMockedImageByName(imageName)
  1315  	images.Results = []common.ImageSummary{common.ImageSummary(mockedImage)}
  1316  
  1317  	return images, nil
  1318  }
  1319  
  1320  func (service mockService) getFixedTagsForCVEGQL(ctx context.Context, config SearchConfig, username, password,
  1321  	imageName, cveID string,
  1322  ) (*common.ImageListWithCVEFixedResponse, error) {
  1323  	if service.getFixedTagsForCVEGQLFn != nil {
  1324  		return service.getFixedTagsForCVEGQLFn(ctx, config, username, password, imageName, cveID)
  1325  	}
  1326  
  1327  	fixedTags := &common.ImageListWithCVEFixedResponse{
  1328  		Errors: nil,
  1329  		ImageListWithCVEFixed: struct {
  1330  			common.PaginatedImagesResult `json:"ImageListWithCVEFixed"` //nolint:tagliatelle // graphQL schema
  1331  		}{},
  1332  	}
  1333  
  1334  	fixedTags.Errors = nil
  1335  
  1336  	mockedImage := service.getMockedImageByName(imageName)
  1337  	fixedTags.Results = []common.ImageSummary{common.ImageSummary(mockedImage)}
  1338  
  1339  	return fixedTags, nil
  1340  }
  1341  
  1342  func (service mockService) getCveByImageGQL(ctx context.Context, config SearchConfig, username, password,
  1343  	imageName, searchedCVE string,
  1344  ) (*cveResult, error) {
  1345  	if service.getCveByImageGQLFn != nil {
  1346  		return service.getCveByImageGQLFn(ctx, config, username, password, imageName, searchedCVE)
  1347  	}
  1348  	cveRes := &cveResult{}
  1349  	cveRes.Data = cveData{
  1350  		CVEListForImage: cveListForImage{
  1351  			Tag: imageName,
  1352  			CVEList: []cve{
  1353  				{
  1354  					ID:          "dummyCVEID",
  1355  					Description: "Description of the CVE",
  1356  					Title:       "Title of that CVE",
  1357  					Severity:    "HIGH",
  1358  					PackageList: []packageList{
  1359  						{
  1360  							Name:             "packagename",
  1361  							FixedVersion:     "fixedver",
  1362  							InstalledVersion: "installedver",
  1363  						},
  1364  					},
  1365  				},
  1366  			},
  1367  			Summary: common.ImageVulnerabilitySummary{
  1368  				Count:         1,
  1369  				UnknownCount:  0,
  1370  				LowCount:      0,
  1371  				MediumCount:   0,
  1372  				HighCount:     1,
  1373  				CriticalCount: 0,
  1374  				MaxSeverity:   "HIGH",
  1375  			},
  1376  		},
  1377  	}
  1378  
  1379  	return cveRes, nil
  1380  }
  1381  
  1382  //nolint:goconst
  1383  func (service mockService) getMockedImageByName(imageName string) imageStruct {
  1384  	image := imageStruct{}
  1385  	image.RepoName = imageName
  1386  	image.Tag = "tag"
  1387  	image.MediaType = ispec.MediaTypeImageManifest
  1388  	image.Manifests = []common.ManifestSummary{
  1389  		{
  1390  			Digest:       godigest.FromString("Digest").String(),
  1391  			ConfigDigest: godigest.FromString("ConfigDigest").String(),
  1392  			Layers:       []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}},
  1393  			Size:         "123445",
  1394  			Platform:     common.Platform{Os: "os", Arch: "arch"},
  1395  		},
  1396  	}
  1397  	image.Size = "123445"
  1398  
  1399  	return image
  1400  }
  1401  
  1402  func (service mockService) getAllImages(ctx context.Context, config SearchConfig, username, password string,
  1403  	channel chan stringResult, wtgrp *sync.WaitGroup,
  1404  ) {
  1405  	defer wtgrp.Done()
  1406  	defer close(channel)
  1407  
  1408  	if service.getAllImagesFn != nil {
  1409  		service.getAllImagesFn(ctx, config, username, password, channel, wtgrp)
  1410  
  1411  		return
  1412  	}
  1413  
  1414  	image := &imageStruct{}
  1415  	image.RepoName = "randomimageName"
  1416  	image.Tag = "tag"
  1417  	image.Digest = godigest.FromString("test").String()
  1418  	image.MediaType = ispec.MediaTypeImageManifest
  1419  	image.Manifests = []common.ManifestSummary{
  1420  		{
  1421  			Digest:       godigest.FromString("Digest").String(),
  1422  			ConfigDigest: godigest.FromString("ConfigDigest").String(),
  1423  			Layers:       []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}},
  1424  			Size:         "123445",
  1425  			Platform:     common.Platform{Os: "os", Arch: "arch"},
  1426  		},
  1427  	}
  1428  	image.Size = "123445"
  1429  
  1430  	str, err := image.string(config.OutputFormat, len(image.RepoName), len(image.Tag), len("os/Arch"), config.Verbose)
  1431  	if err != nil {
  1432  		channel <- stringResult{"", err}
  1433  
  1434  		return
  1435  	}
  1436  
  1437  	channel <- stringResult{str, nil}
  1438  }
  1439  
  1440  func (service mockService) getImageByName(ctx context.Context, config SearchConfig,
  1441  	username, password, imageName string, channel chan stringResult, wtgrp *sync.WaitGroup,
  1442  ) {
  1443  	defer wtgrp.Done()
  1444  	defer close(channel)
  1445  
  1446  	if service.getImageByNameFn != nil {
  1447  		service.getImageByNameFn(ctx, config, username, password, imageName, channel, wtgrp)
  1448  
  1449  		return
  1450  	}
  1451  
  1452  	image := &imageStruct{}
  1453  	image.RepoName = imageName
  1454  	image.Tag = "tag"
  1455  	image.Digest = godigest.FromString("test").String()
  1456  	image.MediaType = ispec.MediaTypeImageManifest
  1457  	image.Manifests = []common.ManifestSummary{
  1458  		{
  1459  			Digest:       godigest.FromString("Digest").String(),
  1460  			ConfigDigest: godigest.FromString("ConfigDigest").String(),
  1461  			Layers:       []common.LayerSummary{{Digest: godigest.FromString("LayerDigest").String()}},
  1462  			Size:         "123445",
  1463  			Platform:     common.Platform{Os: "os", Arch: "arch"},
  1464  		},
  1465  	}
  1466  	image.Size = "123445"
  1467  
  1468  	str, err := image.string(config.OutputFormat, len(image.RepoName), len(image.Tag), len("os/Arch"), config.Verbose)
  1469  	if err != nil {
  1470  		channel <- stringResult{"", err}
  1471  
  1472  		return
  1473  	}
  1474  
  1475  	channel <- stringResult{str, nil}
  1476  }
  1477  
  1478  func (service mockService) getImagesByDigest(ctx context.Context, config SearchConfig, username,
  1479  	password, digest string, rch chan stringResult, wtgrp *sync.WaitGroup,
  1480  ) {
  1481  	if service.getImagesByDigestFn != nil {
  1482  		defer wtgrp.Done()
  1483  		defer close(rch)
  1484  
  1485  		service.getImagesByDigestFn(ctx, config, username, password, digest, rch, wtgrp)
  1486  
  1487  		return
  1488  	}
  1489  
  1490  	service.getImageByName(ctx, config, username, password, "anImage", rch, wtgrp)
  1491  }
  1492  
  1493  func makeConfigFile(content string) string {
  1494  	os.Setenv("HOME", os.TempDir())
  1495  
  1496  	home, err := os.UserHomeDir()
  1497  	if err != nil {
  1498  		panic(err)
  1499  	}
  1500  
  1501  	configPath := path.Join(home, "/.zot")
  1502  
  1503  	if err := os.WriteFile(configPath, []byte(content), 0o600); err != nil {
  1504  		panic(err)
  1505  	}
  1506  
  1507  	return configPath
  1508  }
  1509  
  1510  func getTestSearchConfig(url string, searchService SearchService) SearchConfig {
  1511  	var (
  1512  		user         string
  1513  		outputFormat string
  1514  		verbose      bool
  1515  		debug        bool
  1516  		verifyTLS    bool
  1517  	)
  1518  
  1519  	return SearchConfig{
  1520  		SearchService: searchService,
  1521  		SortBy:        "alpha-asc",
  1522  		ServURL:       url,
  1523  		User:          user,
  1524  		OutputFormat:  outputFormat,
  1525  		Verbose:       verbose,
  1526  		Debug:         debug,
  1527  		VerifyTLS:     verifyTLS,
  1528  		ResultWriter:  nil,
  1529  	}
  1530  }
  1531  
  1532  func removeLocalStorageContents(imageStore stypes.ImageStore) error {
  1533  	repos, err := imageStore.GetRepositories()
  1534  	if err != nil {
  1535  		return err
  1536  	}
  1537  
  1538  	for _, repo := range repos {
  1539  		// take just the first path
  1540  		err = os.RemoveAll(filepath.Join(imageStore.RootDir(), filepath.SplitList(repo)[0]))
  1541  		if err != nil {
  1542  			return err
  1543  		}
  1544  	}
  1545  
  1546  	return nil
  1547  }