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

     1  //go:build search
     2  // +build search
     3  
     4  package client_test
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"os"
    10  	"regexp"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  
    15  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    16  	. "github.com/smartystreets/goconvey/convey"
    17  
    18  	"zotregistry.dev/zot/pkg/api"
    19  	"zotregistry.dev/zot/pkg/api/config"
    20  	"zotregistry.dev/zot/pkg/cli/client"
    21  	extconf "zotregistry.dev/zot/pkg/extensions/config"
    22  	test "zotregistry.dev/zot/pkg/test/common"
    23  	. "zotregistry.dev/zot/pkg/test/image-utils"
    24  	ociutils "zotregistry.dev/zot/pkg/test/oci-utils"
    25  )
    26  
    27  const (
    28  	customArtTypeV1 = "application/custom.art.type.v1"
    29  	customArtTypeV2 = "application/custom.art.type.v2"
    30  	repoName        = "repo"
    31  )
    32  
    33  func TestReferrerCLI(t *testing.T) {
    34  	Convey("Test GQL", t, func() {
    35  		rootDir := t.TempDir()
    36  
    37  		port := test.GetFreePort()
    38  		baseURL := test.GetBaseURL(port)
    39  		conf := config.New()
    40  		conf.HTTP.Port = port
    41  		conf.Storage.GC = false
    42  		defaultVal := true
    43  		conf.Extensions = &extconf.ExtensionConfig{
    44  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
    45  		}
    46  		ctlr := api.NewController(conf)
    47  		ctlr.Config.Storage.RootDirectory = rootDir
    48  		cm := test.NewControllerManager(ctlr)
    49  		cm.StartAndWait(conf.HTTP.Port)
    50  		defer cm.StopServer()
    51  
    52  		repo := repoName
    53  		image := CreateRandomImage()
    54  
    55  		err := UploadImage(image, baseURL, repo, "tag")
    56  		So(err, ShouldBeNil)
    57  
    58  		ref1 := CreateImageWith().
    59  			RandomLayers(1, 10).
    60  			RandomConfig().
    61  			Subject(image.DescriptorRef()).Build()
    62  
    63  		ref2 := CreateImageWith().
    64  			RandomLayers(1, 10).
    65  			ArtifactConfig(customArtTypeV1).
    66  			Subject(image.DescriptorRef()).Build()
    67  
    68  		ref3 := CreateImageWith().
    69  			RandomLayers(1, 10).
    70  			RandomConfig().
    71  			ArtifactType(customArtTypeV2).
    72  			Subject(image.DescriptorRef()).Build()
    73  
    74  		err = UploadImage(ref1, baseURL, repo, ref1.DigestStr())
    75  		So(err, ShouldBeNil)
    76  
    77  		err = UploadImage(ref2, baseURL, repo, ref2.DigestStr())
    78  		So(err, ShouldBeNil)
    79  
    80  		err = UploadImage(ref3, baseURL, repo, ref3.DigestStr())
    81  		So(err, ShouldBeNil)
    82  
    83  		args := []string{"subject", repo + "@" + image.DigestStr(), "--config", "reftest"}
    84  
    85  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
    86  			baseURL))
    87  		defer os.Remove(configPath)
    88  
    89  		cmd := client.NewSearchCommand(client.NewSearchService())
    90  
    91  		buff := &bytes.Buffer{}
    92  		cmd.SetOut(buff)
    93  		cmd.SetErr(buff)
    94  		cmd.SetArgs(args)
    95  		err = cmd.Execute()
    96  		So(err, ShouldBeNil)
    97  		space := regexp.MustCompile(`\s+`)
    98  		str := strings.TrimSpace(space.ReplaceAllString(buff.String(), " "))
    99  		So(str, ShouldContainSubstring, "ARTIFACT TYPE SIZE DIGEST")
   100  		So(str, ShouldContainSubstring, "application/vnd.oci.image.config.v1+json 563 B "+ref1.DigestStr())
   101  		So(str, ShouldContainSubstring, "custom.art.type.v1 551 B "+ref2.DigestStr())
   102  		So(str, ShouldContainSubstring, "custom.art.type.v2 611 B "+ref3.DigestStr())
   103  
   104  		fmt.Println(buff.String())
   105  
   106  		os.Remove(configPath)
   107  
   108  		args = []string{"subject", repo + ":" + "tag", "--config", "reftest"}
   109  
   110  		configPath = makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
   111  			baseURL))
   112  		defer os.Remove(configPath)
   113  
   114  		cmd = client.NewSearchCommand(client.NewSearchService())
   115  
   116  		buff = &bytes.Buffer{}
   117  		cmd.SetOut(buff)
   118  		cmd.SetErr(buff)
   119  		cmd.SetArgs(args)
   120  		err = cmd.Execute()
   121  		So(err, ShouldBeNil)
   122  		str = strings.TrimSpace(space.ReplaceAllString(buff.String(), " "))
   123  		So(str, ShouldContainSubstring, "ARTIFACT TYPE SIZE DIGEST")
   124  		So(str, ShouldContainSubstring, "application/vnd.oci.image.config.v1+json 563 B "+ref1.DigestStr())
   125  		So(str, ShouldContainSubstring, "custom.art.type.v1 551 B "+ref2.DigestStr())
   126  		So(str, ShouldContainSubstring, "custom.art.type.v2 611 B "+ref3.DigestStr())
   127  
   128  		fmt.Println(buff.String())
   129  	})
   130  
   131  	Convey("Test REST", t, func() {
   132  		rootDir := t.TempDir()
   133  
   134  		port := test.GetFreePort()
   135  		baseURL := test.GetBaseURL(port)
   136  		conf := config.New()
   137  		conf.HTTP.Port = port
   138  		conf.Storage.GC = false
   139  		defaultVal := false
   140  		conf.Extensions = &extconf.ExtensionConfig{
   141  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
   142  		}
   143  		ctlr := api.NewController(conf)
   144  		ctlr.Config.Storage.RootDirectory = rootDir
   145  		cm := test.NewControllerManager(ctlr)
   146  		cm.StartAndWait(conf.HTTP.Port)
   147  		defer cm.StopServer()
   148  
   149  		repo := repoName
   150  		image := CreateRandomImage()
   151  
   152  		err := UploadImage(image, baseURL, repo, "tag")
   153  		So(err, ShouldBeNil)
   154  
   155  		ref1 := CreateImageWith().
   156  			RandomLayers(1, 10).
   157  			RandomConfig().
   158  			Subject(image.DescriptorRef()).Build()
   159  
   160  		ref2 := CreateImageWith().
   161  			RandomLayers(1, 10).
   162  			ArtifactConfig(customArtTypeV1).
   163  			Subject(image.DescriptorRef()).Build()
   164  
   165  		ref3 := CreateImageWith().
   166  			RandomLayers(1, 10).
   167  			RandomConfig().
   168  			ArtifactType(customArtTypeV2).
   169  			Subject(image.DescriptorRef()).Build()
   170  
   171  		err = UploadImage(ref1, baseURL, repo, ref1.DigestStr())
   172  		So(err, ShouldBeNil)
   173  
   174  		err = UploadImage(ref2, baseURL, repo, ref2.DigestStr())
   175  		So(err, ShouldBeNil)
   176  
   177  		err = UploadImage(ref3, baseURL, repo, ref3.DigestStr())
   178  		So(err, ShouldBeNil)
   179  
   180  		// get referrers by digest
   181  		args := []string{"subject", repo + "@" + image.DigestStr(), "--config", "reftest"}
   182  
   183  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
   184  			baseURL))
   185  
   186  		cmd := client.NewSearchCommand(client.NewSearchService())
   187  
   188  		buff := &bytes.Buffer{}
   189  		cmd.SetOut(buff)
   190  		cmd.SetErr(buff)
   191  		cmd.SetArgs(args)
   192  		err = cmd.Execute()
   193  		So(err, ShouldBeNil)
   194  		space := regexp.MustCompile(`\s+`)
   195  		str := strings.TrimSpace(space.ReplaceAllString(buff.String(), " "))
   196  		So(str, ShouldContainSubstring, "ARTIFACT TYPE SIZE DIGEST")
   197  		So(str, ShouldContainSubstring, "application/vnd.oci.image.config.v1+json 563 B "+ref1.DigestStr())
   198  		So(str, ShouldContainSubstring, "custom.art.type.v1 551 B "+ref2.DigestStr())
   199  		So(str, ShouldContainSubstring, "custom.art.type.v2 611 B "+ref3.DigestStr())
   200  		fmt.Println(buff.String())
   201  
   202  		os.Remove(configPath)
   203  
   204  		args = []string{"subject", repo + ":" + "tag", "--config", "reftest"}
   205  
   206  		configPath = makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
   207  			baseURL))
   208  		defer os.Remove(configPath)
   209  
   210  		buff = &bytes.Buffer{}
   211  		cmd.SetOut(buff)
   212  		cmd.SetErr(buff)
   213  		cmd.SetArgs(args)
   214  		err = cmd.Execute()
   215  		So(err, ShouldBeNil)
   216  		str = strings.TrimSpace(space.ReplaceAllString(buff.String(), " "))
   217  		So(str, ShouldContainSubstring, "ARTIFACT TYPE SIZE DIGEST")
   218  		So(str, ShouldContainSubstring, "application/vnd.oci.image.config.v1+json 563 B "+ref1.DigestStr())
   219  		So(str, ShouldContainSubstring, "custom.art.type.v1 551 B "+ref2.DigestStr())
   220  		So(str, ShouldContainSubstring, "custom.art.type.v2 611 B "+ref3.DigestStr())
   221  		fmt.Println(buff.String())
   222  	})
   223  }
   224  
   225  func TestFormatsReferrersCLI(t *testing.T) {
   226  	Convey("Create server", t, func() {
   227  		rootDir := t.TempDir()
   228  
   229  		port := test.GetFreePort()
   230  		baseURL := test.GetBaseURL(port)
   231  		conf := config.New()
   232  		conf.HTTP.Port = port
   233  		conf.Storage.GC = false
   234  		defaultVal := false
   235  		conf.Extensions = &extconf.ExtensionConfig{
   236  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
   237  		}
   238  		ctlr := api.NewController(conf)
   239  		ctlr.Config.Storage.RootDirectory = rootDir
   240  		cm := test.NewControllerManager(ctlr)
   241  		cm.StartAndWait(conf.HTTP.Port)
   242  		defer cm.StopServer()
   243  
   244  		repo := repoName
   245  		image := CreateRandomImage()
   246  
   247  		err := UploadImage(image, baseURL, repo, "tag")
   248  		So(err, ShouldBeNil)
   249  
   250  		// add referrers
   251  		ref1 := CreateImageWith().
   252  			RandomLayers(1, 10).
   253  			RandomConfig().
   254  			Subject(image.DescriptorRef()).Build()
   255  
   256  		ref2 := CreateImageWith().
   257  			RandomLayers(1, 10).
   258  			ArtifactConfig(customArtTypeV1).
   259  			Subject(image.DescriptorRef()).Build()
   260  
   261  		ref3 := CreateImageWith().
   262  			RandomLayers(1, 10).
   263  			RandomConfig().
   264  			ArtifactType(customArtTypeV2).
   265  			Subject(image.DescriptorRef()).Build()
   266  
   267  		err = UploadImage(ref1, baseURL, repo, ref1.DigestStr())
   268  		So(err, ShouldBeNil)
   269  
   270  		err = UploadImage(ref2, baseURL, repo, ref2.DigestStr())
   271  		So(err, ShouldBeNil)
   272  
   273  		err = UploadImage(ref3, baseURL, repo, ref3.DigestStr())
   274  		So(err, ShouldBeNil)
   275  
   276  		Convey("JSON format", func() {
   277  			args := []string{"subject", repo + "@" + image.DigestStr(), "--format", "json", "--config", "reftest"}
   278  
   279  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
   280  				baseURL))
   281  
   282  			defer os.Remove(configPath)
   283  
   284  			cmd := client.NewSearchCommand(client.NewSearchService())
   285  
   286  			buff := &bytes.Buffer{}
   287  			cmd.SetOut(buff)
   288  			cmd.SetErr(buff)
   289  			cmd.SetArgs(args)
   290  			err = cmd.Execute()
   291  			So(err, ShouldBeNil)
   292  			fmt.Println(buff.String())
   293  		})
   294  		Convey("YAML format", func() {
   295  			args := []string{"subject", repo + "@" + image.DigestStr(), "--format", "yaml", "--config", "reftest"}
   296  
   297  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
   298  				baseURL))
   299  
   300  			defer os.Remove(configPath)
   301  
   302  			cmd := client.NewSearchCommand(client.NewSearchService())
   303  
   304  			buff := &bytes.Buffer{}
   305  			cmd.SetOut(buff)
   306  			cmd.SetErr(buff)
   307  			cmd.SetArgs(args)
   308  			err = cmd.Execute()
   309  			So(err, ShouldBeNil)
   310  			fmt.Println(buff.String())
   311  		})
   312  		Convey("Invalid format", func() {
   313  			args := []string{"subject", repo + "@" + image.DigestStr(), "--format", "invalid_format", "--config", "reftest"}
   314  
   315  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"reftest","url":"%s","showspinner":false}]}`,
   316  				baseURL))
   317  
   318  			defer os.Remove(configPath)
   319  
   320  			cmd := client.NewSearchCommand(client.NewSearchService())
   321  
   322  			buff := &bytes.Buffer{}
   323  			cmd.SetOut(buff)
   324  			cmd.SetErr(buff)
   325  			cmd.SetArgs(args)
   326  			err = cmd.Execute()
   327  			So(err, ShouldNotBeNil)
   328  		})
   329  	})
   330  }
   331  
   332  func TestReferrersCLIErrors(t *testing.T) {
   333  	Convey("Errors", t, func() {
   334  		cmd := client.NewSearchCommand(client.NewSearchService())
   335  
   336  		Convey("no url provided", func() {
   337  			args := []string{"query", "repo/alpine", "--format", "invalid", "--config", "reftest"}
   338  
   339  			configPath := makeConfigFile(`{"configs":[{"_name":"reftest","showspinner":false}]}`)
   340  
   341  			defer os.Remove(configPath)
   342  
   343  			buff := &bytes.Buffer{}
   344  			cmd.SetOut(buff)
   345  			cmd.SetErr(buff)
   346  			cmd.SetArgs(args)
   347  			err := cmd.Execute()
   348  			So(err, ShouldNotBeNil)
   349  		})
   350  
   351  		Convey("getConfigValue", func() {
   352  			args := []string{"subject", "repo/alpine", "--config", "reftest"}
   353  
   354  			configPath := makeConfigFile(`bad-json`)
   355  
   356  			defer os.Remove(configPath)
   357  
   358  			buff := &bytes.Buffer{}
   359  			cmd.SetOut(buff)
   360  			cmd.SetErr(buff)
   361  			cmd.SetArgs(args)
   362  			err := cmd.Execute()
   363  			So(err, ShouldNotBeNil)
   364  		})
   365  
   366  		Convey("bad showspinnerConfig ", func() {
   367  			args := []string{"query", "repo", "--config", "reftest"}
   368  
   369  			configPath := makeConfigFile(`{"configs":[{"_name":"reftest", "url":"http://127.0.0.1:8080", "showspinner":"bad"}]}`)
   370  
   371  			defer os.Remove(configPath)
   372  
   373  			buff := &bytes.Buffer{}
   374  			cmd.SetOut(buff)
   375  			cmd.SetErr(buff)
   376  			cmd.SetArgs(args)
   377  			err := cmd.Execute()
   378  			So(err, ShouldNotBeNil)
   379  		})
   380  
   381  		Convey("bad verifyTLSConfig ", func() {
   382  			args := []string{"query", "repo", "reftest"}
   383  
   384  			configPath := makeConfigFile(
   385  				`{"configs":[{"_name":"reftest", "url":"http://127.0.0.1:8080", "showspinner":false, "verify-tls": "bad"}]}`)
   386  
   387  			defer os.Remove(configPath)
   388  
   389  			buff := &bytes.Buffer{}
   390  			cmd.SetOut(buff)
   391  			cmd.SetErr(buff)
   392  			cmd.SetArgs(args)
   393  			err := cmd.Execute()
   394  			So(err, ShouldNotBeNil)
   395  		})
   396  
   397  		Convey("url from config is empty", func() {
   398  			args := []string{"subject", "repo/alpine", "--config", "reftest"}
   399  
   400  			configPath := makeConfigFile(`{"configs":[{"_name":"reftest", "url":"", "showspinner":false}]}`)
   401  
   402  			defer os.Remove(configPath)
   403  
   404  			buff := &bytes.Buffer{}
   405  			cmd.SetOut(buff)
   406  			cmd.SetErr(buff)
   407  			cmd.SetArgs(args)
   408  			err := cmd.Execute()
   409  			So(err, ShouldNotBeNil)
   410  		})
   411  
   412  		Convey("bad params combination", func() {
   413  			args := []string{"query", "repo", "reftest"}
   414  
   415  			configPath := makeConfigFile(`{"configs":[{"_name":"reftest", "url":"http://127.0.0.1:8080", "showspinner":false}]}`)
   416  
   417  			defer os.Remove(configPath)
   418  
   419  			buff := &bytes.Buffer{}
   420  			cmd.SetOut(buff)
   421  			cmd.SetErr(buff)
   422  			cmd.SetArgs(args)
   423  			err := cmd.Execute()
   424  			So(err, ShouldNotBeNil)
   425  		})
   426  	})
   427  }
   428  
   429  func TestSearchCLI(t *testing.T) {
   430  	Convey("Test GQL", t, func() {
   431  		rootDir := t.TempDir()
   432  
   433  		port := test.GetFreePort()
   434  		baseURL := test.GetBaseURL(port)
   435  		conf := config.New()
   436  		conf.HTTP.Port = port
   437  		conf.Storage.GC = false
   438  		defaultVal := true
   439  		conf.Extensions = &extconf.ExtensionConfig{
   440  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
   441  		}
   442  		ctlr := api.NewController(conf)
   443  		ctlr.Config.Storage.RootDirectory = rootDir
   444  		cm := test.NewControllerManager(ctlr)
   445  		cm.StartAndWait(conf.HTTP.Port)
   446  		defer cm.StopServer()
   447  
   448  		const (
   449  			repo1  = "repo"
   450  			r1tag1 = "repo1tag1"
   451  			r1tag2 = "repo1tag2"
   452  
   453  			repo2  = "repo/alpine"
   454  			r2tag1 = "repo2tag1"
   455  			r2tag2 = "repo2tag2"
   456  
   457  			repo3  = "repo/test/alpine"
   458  			r3tag1 = "repo3tag1"
   459  			r3tag2 = "repo3tag2"
   460  		)
   461  
   462  		image1 := CreateImageWith().
   463  			RandomLayers(1, 10).
   464  			ImageConfig(ispec.Image{
   465  				Created:  DefaultTimeRef(),
   466  				Platform: ispec.Platform{OS: "Os", Architecture: "Arch"},
   467  			}).
   468  			Build()
   469  		formatterDigest1 := image1.Digest().Encoded()[:8]
   470  
   471  		image2 := CreateImageWith().
   472  			RandomLayers(1, 10).
   473  			DefaultConfig().
   474  			Build()
   475  		formatterDigest2 := image2.Digest().Encoded()[:8]
   476  
   477  		err := UploadImage(image1, baseURL, repo1, r1tag1)
   478  		So(err, ShouldBeNil)
   479  		err = UploadImage(image2, baseURL, repo1, r1tag2)
   480  		So(err, ShouldBeNil)
   481  
   482  		err = UploadImage(image1, baseURL, repo2, r2tag1)
   483  		So(err, ShouldBeNil)
   484  		err = UploadImage(image2, baseURL, repo2, r2tag2)
   485  		So(err, ShouldBeNil)
   486  
   487  		err = UploadImage(image1, baseURL, repo3, r3tag1)
   488  		So(err, ShouldBeNil)
   489  		err = UploadImage(image2, baseURL, repo3, r3tag2)
   490  		So(err, ShouldBeNil)
   491  
   492  		// search by repos
   493  
   494  		args := []string{"query", "test/alpin", "--verbose", "--config", "searchtest"}
   495  
   496  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`,
   497  			baseURL))
   498  		defer os.Remove(configPath)
   499  
   500  		cmd := client.NewSearchCommand(client.NewSearchService())
   501  
   502  		buff := &bytes.Buffer{}
   503  		cmd.SetOut(buff)
   504  		cmd.SetErr(buff)
   505  		cmd.SetArgs(args)
   506  		err = cmd.Execute()
   507  		So(err, ShouldBeNil)
   508  		space := regexp.MustCompile(`\s+`)
   509  		str := strings.TrimSpace(space.ReplaceAllString(buff.String(), " "))
   510  		So(str, ShouldContainSubstring, "NAME SIZE LAST UPDATED DOWNLOADS STARS PLATFORMS")
   511  		So(str, ShouldContainSubstring, "repo/test/alpine 1.1kB 2010-01-01 01:01:01 +0000 UTC 0 0")
   512  		So(str, ShouldContainSubstring, "Os/Arch")
   513  		So(str, ShouldContainSubstring, "linux/amd64")
   514  
   515  		fmt.Println("\n", buff.String())
   516  
   517  		os.Remove(configPath)
   518  
   519  		cmd = client.NewSearchCommand(client.NewSearchService())
   520  
   521  		args = []string{"query", "repo/alpine:", "--config", "searchtest"}
   522  
   523  		configPath = makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`,
   524  			baseURL))
   525  
   526  		defer os.Remove(configPath)
   527  
   528  		buff = &bytes.Buffer{}
   529  		cmd.SetOut(buff)
   530  		cmd.SetErr(buff)
   531  		cmd.SetArgs(args)
   532  		err = cmd.Execute()
   533  		So(err, ShouldBeNil)
   534  		str = strings.TrimSpace(space.ReplaceAllString(buff.String(), " "))
   535  		So(str, ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE")
   536  		So(str, ShouldContainSubstring, "repo/alpine repo2tag1 Os/Arch "+formatterDigest1+" false 525B")
   537  		So(str, ShouldContainSubstring, "repo/alpine repo2tag2 linux/amd64 "+formatterDigest2+" false 552B")
   538  
   539  		fmt.Println("\n", buff.String())
   540  	})
   541  }
   542  
   543  func TestFormatsSearchCLI(t *testing.T) {
   544  	Convey("", t, func() {
   545  		rootDir := t.TempDir()
   546  
   547  		port := test.GetFreePort()
   548  		baseURL := test.GetBaseURL(port)
   549  		conf := config.New()
   550  		conf.HTTP.Port = port
   551  		conf.Storage.GC = false
   552  		defaultVal := true
   553  		conf.Extensions = &extconf.ExtensionConfig{
   554  			Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
   555  		}
   556  		ctlr := api.NewController(conf)
   557  		ctlr.Config.Storage.RootDirectory = rootDir
   558  		cm := test.NewControllerManager(ctlr)
   559  		cm.StartAndWait(conf.HTTP.Port)
   560  		defer cm.StopServer()
   561  
   562  		const (
   563  			repo1  = "repo"
   564  			r1tag1 = "repo1tag1"
   565  			r1tag2 = "repo1tag2"
   566  
   567  			repo2  = "repo/alpine"
   568  			r2tag1 = "repo2tag1"
   569  			r2tag2 = "repo2tag2"
   570  
   571  			repo3  = "repo/test/alpine"
   572  			r3tag1 = "repo3tag1"
   573  			r3tag2 = "repo3tag2"
   574  		)
   575  
   576  		image1 := CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build()
   577  		image2 := CreateImageWith().RandomLayers(1, 10).DefaultConfig().Build()
   578  
   579  		err := UploadImage(image1, baseURL, repo1, r1tag1)
   580  		So(err, ShouldBeNil)
   581  		err = UploadImage(image2, baseURL, repo1, r1tag2)
   582  		So(err, ShouldBeNil)
   583  
   584  		err = UploadImage(image1, baseURL, repo2, r2tag1)
   585  		So(err, ShouldBeNil)
   586  		err = UploadImage(image2, baseURL, repo2, r2tag2)
   587  		So(err, ShouldBeNil)
   588  
   589  		err = UploadImage(image1, baseURL, repo3, r3tag1)
   590  		So(err, ShouldBeNil)
   591  		err = UploadImage(image2, baseURL, repo3, r3tag2)
   592  		So(err, ShouldBeNil)
   593  
   594  		cmd := client.NewSearchCommand(client.NewSearchService())
   595  
   596  		Convey("JSON format", func() {
   597  			args := []string{"query", "repo/alpine", "--format", "json", "--config", "searchtest"}
   598  
   599  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`,
   600  				baseURL))
   601  
   602  			defer os.Remove(configPath)
   603  
   604  			buff := &bytes.Buffer{}
   605  			cmd.SetOut(buff)
   606  			cmd.SetErr(buff)
   607  			cmd.SetArgs(args)
   608  			err = cmd.Execute()
   609  			So(err, ShouldBeNil)
   610  			fmt.Println(buff.String())
   611  		})
   612  
   613  		Convey("YAML format", func() {
   614  			args := []string{"query", "repo/alpine", "--format", "yaml", "--config", "searchtest"}
   615  
   616  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`,
   617  				baseURL))
   618  
   619  			defer os.Remove(configPath)
   620  
   621  			buff := &bytes.Buffer{}
   622  			cmd.SetOut(buff)
   623  			cmd.SetErr(buff)
   624  			cmd.SetArgs(args)
   625  			err = cmd.Execute()
   626  			So(err, ShouldBeNil)
   627  			fmt.Println(buff.String())
   628  		})
   629  
   630  		Convey("Invalid format", func() {
   631  			args := []string{"query", "repo/alpine", "--format", "invalid", "--config", "searchtest"}
   632  
   633  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"searchtest","url":"%s","showspinner":false}]}`,
   634  				baseURL))
   635  
   636  			defer os.Remove(configPath)
   637  
   638  			buff := &bytes.Buffer{}
   639  			cmd.SetOut(buff)
   640  			cmd.SetErr(buff)
   641  			cmd.SetArgs(args)
   642  			err = cmd.Execute()
   643  			So(err, ShouldNotBeNil)
   644  		})
   645  	})
   646  }
   647  
   648  func TestSearchCLIErrors(t *testing.T) {
   649  	Convey("Errors", t, func() {
   650  		cmd := client.NewSearchCommand(client.NewSearchService())
   651  
   652  		Convey("no url provided", func() {
   653  			args := []string{"query", "repo/alpine", "--format", "invalid", "--config", "searchtest"}
   654  
   655  			configPath := makeConfigFile(`{"configs":[{"_name":"searchtest","showspinner":false}]}`)
   656  
   657  			defer os.Remove(configPath)
   658  
   659  			buff := &bytes.Buffer{}
   660  			cmd.SetOut(buff)
   661  			cmd.SetErr(buff)
   662  			cmd.SetArgs(args)
   663  			err := cmd.Execute()
   664  			So(err, ShouldNotBeNil)
   665  		})
   666  
   667  		Convey("getConfigValue", func() {
   668  			args := []string{"query", "repo/alpine", "--format", "invalid", "--config", "searchtest"}
   669  
   670  			configPath := makeConfigFile(`bad-json`)
   671  
   672  			defer os.Remove(configPath)
   673  
   674  			buff := &bytes.Buffer{}
   675  			cmd.SetOut(buff)
   676  			cmd.SetErr(buff)
   677  			cmd.SetArgs(args)
   678  			err := cmd.Execute()
   679  			So(err, ShouldNotBeNil)
   680  		})
   681  
   682  		Convey("bad showspinnerConfig ", func() {
   683  			args := []string{"query", "repo/alpine", "--config", "searchtest"}
   684  
   685  			configPath := makeConfigFile(
   686  				`{"configs":[{"_name":"searchtest", "url":"http://127.0.0.1:8080", "showspinner":"bad"}]}`)
   687  
   688  			defer os.Remove(configPath)
   689  
   690  			buff := &bytes.Buffer{}
   691  			cmd.SetOut(buff)
   692  			cmd.SetErr(buff)
   693  			cmd.SetArgs(args)
   694  			err := cmd.Execute()
   695  			So(err, ShouldNotBeNil)
   696  		})
   697  
   698  		Convey("bad verifyTLSConfig ", func() {
   699  			args := []string{"query", "repo/alpine", "--config", "searchtest"}
   700  
   701  			configPath := makeConfigFile(
   702  				`{"configs":[{"_name":"searchtest", "url":"http://127.0.0.1:8080", "showspinner":false, "verify-tls": "bad"}]}`)
   703  
   704  			defer os.Remove(configPath)
   705  
   706  			buff := &bytes.Buffer{}
   707  			cmd.SetOut(buff)
   708  			cmd.SetErr(buff)
   709  			cmd.SetArgs(args)
   710  			err := cmd.Execute()
   711  			So(err, ShouldNotBeNil)
   712  		})
   713  
   714  		Convey("url from config is empty", func() {
   715  			args := []string{"query", "repo/alpine", "--format", "invalid", "--config", "searchtest"}
   716  
   717  			configPath := makeConfigFile(`{"configs":[{"_name":"searchtest", "url":"", "showspinner":false}]}`)
   718  
   719  			defer os.Remove(configPath)
   720  
   721  			buff := &bytes.Buffer{}
   722  			cmd.SetOut(buff)
   723  			cmd.SetErr(buff)
   724  			cmd.SetArgs(args)
   725  			err := cmd.Execute()
   726  			So(err, ShouldNotBeNil)
   727  		})
   728  	})
   729  }
   730  
   731  func TestSearchSort(t *testing.T) {
   732  	rootDir := t.TempDir()
   733  	port := test.GetFreePort()
   734  	baseURL := test.GetBaseURL(port)
   735  	conf := config.New()
   736  	conf.HTTP.Port = port
   737  
   738  	defaultVal := true
   739  	conf.Extensions = &extconf.ExtensionConfig{
   740  		Search: &extconf.SearchConfig{
   741  			BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
   742  			CVE:        nil,
   743  		},
   744  	}
   745  	ctlr := api.NewController(conf)
   746  	ctlr.Config.Storage.RootDirectory = rootDir
   747  
   748  	image1 := CreateImageWith().DefaultLayers().
   749  		ImageConfig(ispec.Image{Created: DateRef(2010, 1, 1, 1, 1, 1, 0, time.UTC)}).
   750  		Build()
   751  
   752  	image2 := CreateImageWith().DefaultLayers().
   753  		ImageConfig(ispec.Image{Created: DateRef(2020, 1, 1, 1, 1, 1, 0, time.UTC)}).
   754  		Build()
   755  
   756  	storeController := ociutils.GetDefaultStoreController(rootDir, ctlr.Log)
   757  
   758  	err := WriteImageToFileSystem(image1, "b-repo", "tag2", storeController)
   759  	if err != nil {
   760  		t.FailNow()
   761  	}
   762  
   763  	err = WriteImageToFileSystem(image2, "a-test-repo", "tag2", storeController)
   764  	if err != nil {
   765  		t.FailNow()
   766  	}
   767  
   768  	cm := test.NewControllerManager(ctlr)
   769  	cm.StartAndWait(conf.HTTP.Port)
   770  
   771  	defer cm.StopServer()
   772  
   773  	Convey("test sorting", t, func() {
   774  		args := []string{"query", "repo", "--sort-by", "relevance", "--url", baseURL}
   775  		cmd := client.NewSearchCommand(client.NewSearchService())
   776  		buff := bytes.NewBufferString("")
   777  		cmd.SetOut(buff)
   778  		cmd.SetErr(buff)
   779  		cmd.SetArgs(args)
   780  		err := cmd.Execute()
   781  		So(err, ShouldBeNil)
   782  		str := buff.String()
   783  		So(strings.Index(str, "b-repo"), ShouldBeLessThan, strings.Index(str, "a-test-repo"))
   784  
   785  		args = []string{"query", "repo", "--sort-by", "alpha-asc", "--url", baseURL}
   786  		cmd = client.NewSearchCommand(client.NewSearchService())
   787  		buff = bytes.NewBufferString("")
   788  		cmd.SetOut(buff)
   789  		cmd.SetErr(buff)
   790  		cmd.SetArgs(args)
   791  		err = cmd.Execute()
   792  		So(err, ShouldBeNil)
   793  		str = buff.String()
   794  		So(strings.Index(str, "a-test-repo"), ShouldBeLessThan, strings.Index(str, "b-repo"))
   795  	})
   796  }