zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/cli/client/cve_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  	"os"
    12  	"regexp"
    13  	"strconv"
    14  	"strings"
    15  	"testing"
    16  
    17  	. "github.com/smartystreets/goconvey/convey"
    18  
    19  	zerr "zotregistry.dev/zot/errors"
    20  	"zotregistry.dev/zot/pkg/api"
    21  	"zotregistry.dev/zot/pkg/api/config"
    22  	zcommon "zotregistry.dev/zot/pkg/common"
    23  	extconf "zotregistry.dev/zot/pkg/extensions/config"
    24  	test "zotregistry.dev/zot/pkg/test/common"
    25  )
    26  
    27  func TestSearchCVECmd(t *testing.T) {
    28  	port := test.GetFreePort()
    29  	baseURL := test.GetBaseURL(port)
    30  	conf := config.New()
    31  	conf.HTTP.Port = port
    32  	rootDir := t.TempDir()
    33  	conf.Storage.RootDirectory = rootDir
    34  
    35  	defaultVal := true
    36  	conf.Extensions = &extconf.ExtensionConfig{
    37  		Search: &extconf.SearchConfig{
    38  			BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
    39  		},
    40  	}
    41  
    42  	ctlr := api.NewController(conf)
    43  	cm := test.NewControllerManager(ctlr)
    44  
    45  	cm.StartAndWait(port)
    46  	defer cm.StopServer()
    47  
    48  	Convey("Test CVE help", t, func() {
    49  		args := []string{"--help"}
    50  		configPath := makeConfigFile("")
    51  		defer os.Remove(configPath)
    52  		cmd := NewCVECommand(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  	Convey("Test CVE help - with the shorthand", t, func() {
    63  		args := []string{"-h"}
    64  		configPath := makeConfigFile("")
    65  		defer os.Remove(configPath)
    66  		cmd := NewCVECommand(new(mockService))
    67  		buff := bytes.NewBufferString("")
    68  		cmd.SetOut(buff)
    69  		cmd.SetErr(buff)
    70  		cmd.SetArgs(args)
    71  		err := cmd.Execute()
    72  		So(buff.String(), ShouldContainSubstring, "Usage")
    73  		So(err, ShouldBeNil)
    74  	})
    75  
    76  	Convey("Test CVE no url", t, func() {
    77  		args := []string{"affected", "CVE-cveIdRandom", "--config", "cvetest"}
    78  		configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
    79  		defer os.Remove(configPath)
    80  		cmd := NewCVECommand(new(mockService))
    81  		buff := bytes.NewBufferString("")
    82  		cmd.SetOut(buff)
    83  		cmd.SetErr(buff)
    84  		cmd.SetArgs(args)
    85  		err := cmd.Execute()
    86  		So(err, ShouldNotBeNil)
    87  		So(errors.Is(err, zerr.ErrNoURLProvided), ShouldBeTrue)
    88  	})
    89  
    90  	Convey("Test CVE invalid url", t, func() {
    91  		args := []string{"list", "dummyImageName:tag", "--url", "invalidUrl"}
    92  		configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
    93  		defer os.Remove(configPath)
    94  		cmd := NewCVECommand(new(searchService))
    95  		buff := bytes.NewBufferString("")
    96  		cmd.SetOut(buff)
    97  		cmd.SetErr(buff)
    98  		cmd.SetArgs(args)
    99  		err := cmd.Execute()
   100  		So(err, ShouldNotBeNil)
   101  		So(errors.Is(err, zerr.ErrInvalidURL), ShouldBeTrue)
   102  		So(buff.String(), ShouldContainSubstring, "invalid URL format")
   103  	})
   104  
   105  	Convey("Test CVE invalid url port", t, func() {
   106  		args := []string{"list", "dummyImageName:tag", "--url", "http://localhost:99999"}
   107  		configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
   108  		defer os.Remove(configPath)
   109  		cmd := NewCVECommand(new(searchService))
   110  		buff := bytes.NewBufferString("")
   111  		cmd.SetOut(buff)
   112  		cmd.SetErr(buff)
   113  		cmd.SetArgs(args)
   114  		err := cmd.Execute()
   115  		So(err, ShouldNotBeNil)
   116  		So(buff.String(), ShouldContainSubstring, "invalid port")
   117  	})
   118  
   119  	Convey("Test CVE unreachable", t, func() {
   120  		args := []string{"list", "dummyImageName:tag", "--url", "http://localhost:9999"}
   121  		configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
   122  		defer os.Remove(configPath)
   123  		cmd := NewCVECommand(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  	})
   131  
   132  	Convey("Test CVE url from config", t, func() {
   133  		args := []string{"list", "dummyImageName:tag", "--config", "cvetest"}
   134  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, baseURL))
   135  		defer os.Remove(configPath)
   136  		cmd := NewCVECommand(new(mockService))
   137  		buff := bytes.NewBufferString("")
   138  		cmd.SetOut(buff)
   139  		cmd.SetErr(buff)
   140  		cmd.SetArgs(args)
   141  		err := cmd.Execute()
   142  		space := regexp.MustCompile(`\s+`)
   143  		str := space.ReplaceAllString(buff.String(), " ")
   144  		So(strings.TrimSpace(str), ShouldEqual, "CRITICAL 0, HIGH 1, MEDIUM 0, LOW 0, UNKNOWN 0, TOTAL 1 "+
   145  			"ID SEVERITY TITLE dummyCVEID HIGH Title of that CVE")
   146  		So(err, ShouldBeNil)
   147  	})
   148  
   149  	Convey("Test debug flag", t, func() {
   150  		args := []string{"list", "dummyImageName:tag", "--debug", "--config", "cvetest"}
   151  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, baseURL))
   152  		defer os.Remove(configPath)
   153  		cmd := NewCVECommand(new(searchService))
   154  		buff := bytes.NewBufferString("")
   155  		cmd.SetOut(buff)
   156  		cmd.SetErr(buff)
   157  		cmd.SetArgs(args)
   158  		err := cmd.Execute()
   159  		space := regexp.MustCompile(`\s+`)
   160  		str := space.ReplaceAllString(buff.String(), " ")
   161  		So(strings.TrimSpace(str), ShouldContainSubstring, "GET")
   162  		So(err, ShouldNotBeNil)
   163  	})
   164  
   165  	Convey("Test CVE by name and CVE ID - long option", t, func() {
   166  		args := []string{"affected", "CVE-CVEID", "--repo", "dummyImageName", "--url", baseURL}
   167  		configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
   168  		defer os.Remove(configPath)
   169  		cveCmd := NewCVECommand(new(mockService))
   170  		buff := bytes.NewBufferString("")
   171  		cveCmd.SetOut(buff)
   172  		cveCmd.SetErr(buff)
   173  		cveCmd.SetArgs(args)
   174  		err := cveCmd.Execute()
   175  		So(err, ShouldBeNil)
   176  		space := regexp.MustCompile(`\s+`)
   177  		str := space.ReplaceAllString(buff.String(), " ")
   178  		So(strings.TrimSpace(str), ShouldEqual,
   179  			"REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE dummyImageName tag os/arch 6e2f80bf false 123kB")
   180  	})
   181  
   182  	Convey("Test CVE by name and CVE ID - using shorthand", t, func() {
   183  		args := []string{"affected", "CVE-CVEID", "--repo", "dummyImageName", "--url", baseURL}
   184  		buff := bytes.NewBufferString("")
   185  		configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
   186  		defer os.Remove(configPath)
   187  		cveCmd := NewCVECommand(new(mockService))
   188  		cveCmd.SetOut(buff)
   189  		cveCmd.SetErr(buff)
   190  		cveCmd.SetArgs(args)
   191  		err := cveCmd.Execute()
   192  		So(err, ShouldBeNil)
   193  		space := regexp.MustCompile(`\s+`)
   194  		str := space.ReplaceAllString(buff.String(), " ")
   195  		So(strings.TrimSpace(str), ShouldEqual,
   196  			"REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE dummyImageName tag os/arch 6e2f80bf false 123kB")
   197  	})
   198  
   199  	Convey("Test CVE by image name - in text format", t, func() {
   200  		args := []string{"list", "dummyImageName:tag", "--url", baseURL}
   201  		configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
   202  		defer os.Remove(configPath)
   203  		cveCmd := NewCVECommand(new(mockService))
   204  		buff := bytes.NewBufferString("")
   205  		cveCmd.SetOut(buff)
   206  		cveCmd.SetErr(buff)
   207  		cveCmd.SetArgs(args)
   208  		err := cveCmd.Execute()
   209  		space := regexp.MustCompile(`\s+`)
   210  		str := space.ReplaceAllString(buff.String(), " ")
   211  		So(strings.TrimSpace(str), ShouldEqual, "CRITICAL 0, HIGH 1, MEDIUM 0, LOW 0, UNKNOWN 0, TOTAL 1 "+
   212  			"ID SEVERITY TITLE dummyCVEID HIGH Title of that CVE")
   213  		So(err, ShouldBeNil)
   214  	})
   215  
   216  	Convey("Test CVE by image name - in text format - in verbose mode", t, func() {
   217  		args := []string{"list", "dummyImageName:tag", "--url", baseURL, "--verbose"}
   218  		configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
   219  		defer os.Remove(configPath)
   220  		cveCmd := NewCVECommand(new(mockService))
   221  		buff := bytes.NewBufferString("")
   222  		cveCmd.SetOut(buff)
   223  		cveCmd.SetErr(buff)
   224  		cveCmd.SetArgs(args)
   225  		err := cveCmd.Execute()
   226  
   227  		outputLines := strings.Split(buff.String(), "\n")
   228  		expected := []string{
   229  			"CRITICAL 0, HIGH 1, MEDIUM 0, LOW 0, UNKNOWN 0, TOTAL 1",
   230  			"",
   231  			"dummyCVEID",
   232  			"Severity: HIGH",
   233  			"Title: Title of that CVE",
   234  			"Description:",
   235  			"Description of the CVE",
   236  			"",
   237  			"Vulnerable Packages:",
   238  			" Package Name: packagename",
   239  			" Package Path: ",
   240  			" Installed Version: installedver",
   241  			" Fixed Version: fixedver",
   242  			"",
   243  			"",
   244  		}
   245  
   246  		for index, expectedLine := range expected {
   247  			So(outputLines[index], ShouldEqual, expectedLine)
   248  		}
   249  
   250  		So(err, ShouldBeNil)
   251  	})
   252  
   253  	Convey("Test CVE by image name - in json format", t, func() {
   254  		args := []string{"list", "dummyImageName:tag", "--url", baseURL, "-f", "json"}
   255  		configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
   256  		defer os.Remove(configPath)
   257  		cveCmd := NewCVECommand(new(mockService))
   258  		buff := bytes.NewBufferString("")
   259  		cveCmd.SetOut(buff)
   260  		cveCmd.SetErr(buff)
   261  		cveCmd.SetArgs(args)
   262  		err := cveCmd.Execute()
   263  		// Output is supposed to be in json lines format, keep all spaces as is for verification
   264  		So(buff.String(), ShouldEqual, `{"Tag":"dummyImageName:tag","CVEList":`+
   265  			`[{"Id":"dummyCVEID","Severity":"HIGH","Title":"Title of that CVE",`+
   266  			`"Description":"Description of the CVE","PackageList":[{"Name":"packagename",`+
   267  			`"PackagePath":"","InstalledVersion":"installedver","FixedVersion":"fixedver"}]}],"Summary":`+
   268  			`{"maxSeverity":"HIGH","unknownCount":0,"lowCount":0,"mediumCount":0,"highCount":1,`+
   269  			`"criticalCount":0,"count":1}}`+"\n")
   270  		So(err, ShouldBeNil)
   271  	})
   272  
   273  	Convey("Test CVE by image name - in yaml format", t, func() {
   274  		args := []string{"list", "dummyImageName:tag", "--url", baseURL, "-f", "yaml"}
   275  		configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
   276  		defer os.Remove(configPath)
   277  		cveCmd := NewCVECommand(new(mockService))
   278  		buff := bytes.NewBufferString("")
   279  		cveCmd.SetOut(buff)
   280  		cveCmd.SetErr(buff)
   281  		cveCmd.SetArgs(args)
   282  		err := cveCmd.Execute()
   283  		space := regexp.MustCompile(`\s+`)
   284  		str := space.ReplaceAllString(buff.String(), " ")
   285  		So(strings.TrimSpace(str), ShouldEqual, `--- tag: dummyImageName:tag cvelist: - id: dummyCVEID`+
   286  			` severity: HIGH title: Title of that CVE description: Description of the CVE packagelist: `+
   287  			`- name: packagename packagepath: "" installedversion: installedver fixedversion: fixedver `+
   288  			`summary: maxseverity: HIGH unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 1 criticalcount: 0 count: 1`)
   289  		So(err, ShouldBeNil)
   290  	})
   291  	Convey("Test CVE by image name - invalid format", t, func() {
   292  		args := []string{"list", "dummyImageName:tag", "--url", baseURL, "-f", "random"}
   293  		configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
   294  		defer os.Remove(configPath)
   295  		cveCmd := NewCVECommand(new(mockService))
   296  		buff := bytes.NewBufferString("")
   297  		cveCmd.SetOut(buff)
   298  		cveCmd.SetErr(buff)
   299  		cveCmd.SetArgs(args)
   300  		err := cveCmd.Execute()
   301  		space := regexp.MustCompile(`\s+`)
   302  		str := space.ReplaceAllString(buff.String(), " ")
   303  		So(err, ShouldNotBeNil)
   304  		So(strings.TrimSpace(str), ShouldContainSubstring, zerr.ErrInvalidOutputFormat.Error())
   305  	})
   306  
   307  	Convey("Test images by CVE ID - positive", t, func() {
   308  		args := []string{"affected", "CVE-CVEID", "--repo", "anImage", "--url", baseURL}
   309  		configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
   310  		defer os.Remove(configPath)
   311  		cveCmd := NewCVECommand(new(mockService))
   312  		buff := bytes.NewBufferString("")
   313  		cveCmd.SetOut(buff)
   314  		cveCmd.SetErr(buff)
   315  		cveCmd.SetArgs(args)
   316  		err := cveCmd.Execute()
   317  		space := regexp.MustCompile(`\s+`)
   318  		str := space.ReplaceAllString(buff.String(), " ")
   319  		So(strings.TrimSpace(str), ShouldContainSubstring, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE anImage tag os/arch 6e2f80bf false 123kB") //nolint:lll
   320  		So(err, ShouldBeNil)
   321  	})
   322  
   323  	Convey("Test images by CVE ID - positive with retries", t, func() {
   324  		args := []string{"affected", "CVE-CVEID", "--repo", "anImage", "--url", baseURL}
   325  		configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
   326  		defer os.Remove(configPath)
   327  		mockService := mockServiceForRetry{succeedOn: 2} // CVE info will be provided in 2nd attempt
   328  		cveCmd := NewCVECommand(&mockService)
   329  		buff := bytes.NewBufferString("")
   330  		cveCmd.SetOut(buff)
   331  		cveCmd.SetErr(buff)
   332  		cveCmd.SetArgs(args)
   333  		err := cveCmd.Execute()
   334  		space := regexp.MustCompile(`\s+`)
   335  		str := space.ReplaceAllString(buff.String(), " ")
   336  		t.Logf("Output: %s", str)
   337  		So(strings.TrimSpace(str), ShouldContainSubstring,
   338  			"[warning] CVE DB is not ready [1] - retry in "+strconv.Itoa(CveDBRetryInterval)+" seconds")
   339  		So(strings.TrimSpace(str), ShouldContainSubstring,
   340  			"REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE anImage tag os/arch 6e2f80bf false 123kB")
   341  		So(err, ShouldBeNil)
   342  	})
   343  
   344  	Convey("Test images by CVE ID - failed after retries", t, func() {
   345  		args := []string{"affected", "CVE-CVEID", "--url", baseURL}
   346  		configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
   347  		defer os.Remove(configPath)
   348  		mockService := mockServiceForRetry{succeedOn: -1} // CVE info will be unavailable on all retries
   349  		cveCmd := NewCVECommand(&mockService)
   350  		buff := bytes.NewBufferString("")
   351  		cveCmd.SetOut(buff)
   352  		cveCmd.SetErr(buff)
   353  		cveCmd.SetArgs(args)
   354  		err := cveCmd.Execute()
   355  		space := regexp.MustCompile(`\s+`)
   356  		str := space.ReplaceAllString(buff.String(), " ")
   357  		t.Logf("Output: %s", str)
   358  		So(strings.TrimSpace(str), ShouldContainSubstring,
   359  			"[warning] CVE DB is not ready [1] - retry in "+strconv.Itoa(CveDBRetryInterval)+" seconds")
   360  		So(strings.TrimSpace(str), ShouldNotContainSubstring,
   361  			"REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE anImage tag os/arch 6e2f80bf false 123kB")
   362  		So(err, ShouldNotBeNil)
   363  	})
   364  
   365  	Convey("Test images by CVE ID - invalid CVE ID", t, func() {
   366  		args := []string{"affected", "CVE-invalidCVEID", "--config", "cvetest"}
   367  		configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
   368  		defer os.Remove(configPath)
   369  		cveCmd := NewCVECommand(new(mockService))
   370  		buff := bytes.NewBufferString("")
   371  		cveCmd.SetOut(buff)
   372  		cveCmd.SetErr(buff)
   373  		cveCmd.SetArgs(args)
   374  		err := cveCmd.Execute()
   375  		So(err, ShouldNotBeNil)
   376  	})
   377  
   378  	Convey("Test images by CVE ID - invalid url", t, func() {
   379  		args := []string{"affected", "CVE-CVEID", "--url", "invalidURL"}
   380  		configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
   381  		defer os.Remove(configPath)
   382  		cveCmd := NewCVECommand(NewSearchService())
   383  		buff := bytes.NewBufferString("")
   384  		cveCmd.SetOut(buff)
   385  		cveCmd.SetErr(buff)
   386  		cveCmd.SetArgs(args)
   387  		err := cveCmd.Execute()
   388  		So(err, ShouldNotBeNil)
   389  		So(errors.Is(err, zerr.ErrInvalidURL), ShouldBeTrue)
   390  		So(buff.String(), ShouldContainSubstring, "invalid URL format")
   391  	})
   392  
   393  	Convey("Test fixed tags by and image name CVE ID - positive", t, func() {
   394  		args := []string{"fixed", "fixedImage", "CVE-CVEID", "--url", baseURL}
   395  		configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
   396  		defer os.Remove(configPath)
   397  		cveCmd := NewCVECommand(new(mockService))
   398  		buff := bytes.NewBufferString("")
   399  		cveCmd.SetOut(buff)
   400  		cveCmd.SetErr(buff)
   401  		cveCmd.SetArgs(args)
   402  		err := cveCmd.Execute()
   403  		space := regexp.MustCompile(`\s+`)
   404  		str := space.ReplaceAllString(buff.String(), " ")
   405  		So(err, ShouldBeNil)
   406  		So(strings.TrimSpace(str), ShouldEqual, "REPOSITORY TAG OS/ARCH DIGEST SIGNED SIZE fixedImage tag os/arch 6e2f80bf false 123kB") //nolint:lll
   407  	})
   408  
   409  	Convey("Test fixed tags by and image name CVE ID - invalid image name", t, func() {
   410  		args := []string{"affected", "CVE-CVEID", "--image", "invalidImageName", "--config", "cvetest"}
   411  		configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
   412  		defer os.Remove(configPath)
   413  		cveCmd := NewCVECommand(NewSearchService())
   414  		buff := bytes.NewBufferString("")
   415  		cveCmd.SetOut(buff)
   416  		cveCmd.SetErr(buff)
   417  		cveCmd.SetArgs(args)
   418  		err := cveCmd.Execute()
   419  		So(err, ShouldNotBeNil)
   420  	})
   421  }
   422  
   423  func TestCVECommandGQL(t *testing.T) {
   424  	port := test.GetFreePort()
   425  	baseURL := test.GetBaseURL(port)
   426  	conf := config.New()
   427  	conf.HTTP.Port = port
   428  
   429  	defaultVal := true
   430  	conf.Extensions = &extconf.ExtensionConfig{
   431  		Search: &extconf.SearchConfig{
   432  			BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
   433  		},
   434  	}
   435  
   436  	ctlr := api.NewController(conf)
   437  	ctlr.Config.Storage.RootDirectory = t.TempDir()
   438  	cm := test.NewControllerManager(ctlr)
   439  
   440  	cm.StartAndWait(conf.HTTP.Port)
   441  	defer cm.StopServer()
   442  
   443  	Convey("commands without gql", t, func() {
   444  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, baseURL))
   445  		defer os.Remove(configPath)
   446  
   447  		Convey("cveid", func() {
   448  			args := []string{"affected", "CVE-1942", "--config", "cvetest"}
   449  			cmd := NewCVECommand(mockService{})
   450  			buff := bytes.NewBufferString("")
   451  			cmd.SetOut(buff)
   452  			cmd.SetErr(buff)
   453  			cmd.SetArgs(args)
   454  			err := cmd.Execute()
   455  			So(err, ShouldBeNil)
   456  			space := regexp.MustCompile(`\s+`)
   457  			str := space.ReplaceAllString(buff.String(), " ")
   458  			actual := strings.TrimSpace(str)
   459  			So(actual, ShouldContainSubstring, "image-name tag os/arch 6e2f80bf false 123kB")
   460  		})
   461  
   462  		Convey("cveid db download wait", func() {
   463  			count := 0
   464  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`,
   465  				baseURL))
   466  			args := []string{"affected", "CVE-12345", "--config", "cvetest"}
   467  			defer os.Remove(configPath)
   468  			cmd := NewCVECommand(mockService{
   469  				getTagsForCVEGQLFn: func(ctx context.Context, config SearchConfig, username, password,
   470  					imageName, cveID string) (*zcommon.ImagesForCve, error,
   471  				) {
   472  					if count == 0 {
   473  						count++
   474  						fmt.Println("Count:", count)
   475  
   476  						return &zcommon.ImagesForCve{}, zerr.ErrCVEDBNotFound
   477  					}
   478  
   479  					return &zcommon.ImagesForCve{}, zerr.ErrInjected
   480  				},
   481  			})
   482  			buff := bytes.NewBufferString("")
   483  			cmd.SetOut(buff)
   484  			cmd.SetErr(buff)
   485  			cmd.SetArgs(args)
   486  			err := cmd.Execute()
   487  			So(err, ShouldNotBeNil)
   488  			space := regexp.MustCompile(`\s+`)
   489  			str := space.ReplaceAllString(buff.String(), " ")
   490  			actual := strings.TrimSpace(str)
   491  			So(actual, ShouldContainSubstring, "[warning] CVE DB is not ready")
   492  		})
   493  
   494  		Convey("fixed", func() {
   495  			args := []string{"fixed", "image-name", "CVE-123", "--config", "cvetest"}
   496  			cmd := NewCVECommand(mockService{})
   497  			buff := bytes.NewBufferString("")
   498  			cmd.SetOut(buff)
   499  			cmd.SetErr(buff)
   500  			cmd.SetArgs(args)
   501  			err := cmd.Execute()
   502  			So(err, ShouldBeNil)
   503  			space := regexp.MustCompile(`\s+`)
   504  			str := space.ReplaceAllString(buff.String(), " ")
   505  			actual := strings.TrimSpace(str)
   506  			So(actual, ShouldContainSubstring, "image-name tag os/arch 6e2f80bf false 123kB")
   507  		})
   508  
   509  		Convey("fixed db download wait", func() {
   510  			count := 0
   511  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`,
   512  				baseURL))
   513  			args := []string{"fixed", "repo", "CVE-2222", "--config", "cvetest"}
   514  			defer os.Remove(configPath)
   515  			cmd := NewCVECommand(mockService{
   516  				getFixedTagsForCVEGQLFn: func(ctx context.Context, config SearchConfig, username, password,
   517  					imageName, cveID string) (*zcommon.ImageListWithCVEFixedResponse, error,
   518  				) {
   519  					if count == 0 {
   520  						count++
   521  						fmt.Println("Count:", count)
   522  
   523  						return &zcommon.ImageListWithCVEFixedResponse{}, zerr.ErrCVEDBNotFound
   524  					}
   525  
   526  					return &zcommon.ImageListWithCVEFixedResponse{}, zerr.ErrInjected
   527  				},
   528  			})
   529  			buff := bytes.NewBufferString("")
   530  			cmd.SetOut(buff)
   531  			cmd.SetErr(buff)
   532  			cmd.SetArgs(args)
   533  			err := cmd.Execute()
   534  			So(err, ShouldNotBeNil)
   535  			space := regexp.MustCompile(`\s+`)
   536  			str := space.ReplaceAllString(buff.String(), " ")
   537  			actual := strings.TrimSpace(str)
   538  			So(actual, ShouldContainSubstring, "[warning] CVE DB is not ready")
   539  		})
   540  
   541  		Convey("image", func() {
   542  			args := []string{"list", "repo:tag", "--config", "cvetest"}
   543  			cmd := NewCVECommand(mockService{})
   544  			buff := bytes.NewBufferString("")
   545  			cmd.SetOut(buff)
   546  			cmd.SetErr(buff)
   547  			cmd.SetArgs(args)
   548  			err := cmd.Execute()
   549  			So(err, ShouldBeNil)
   550  			space := regexp.MustCompile(`\s+`)
   551  			str := space.ReplaceAllString(buff.String(), " ")
   552  			actual := strings.TrimSpace(str)
   553  			So(actual, ShouldContainSubstring, "CRITICAL 0, HIGH 1, MEDIUM 0, LOW 0, UNKNOWN 0, TOTAL 1")
   554  			So(actual, ShouldContainSubstring, "dummyCVEID HIGH Title of that CVE")
   555  		})
   556  
   557  		Convey("image db download wait", func() {
   558  			count := 0
   559  			configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`,
   560  				baseURL))
   561  			args := []string{"list", "repo:vuln", "--config", "cvetest"}
   562  			defer os.Remove(configPath)
   563  			cmd := NewCVECommand(mockService{
   564  				getCveByImageGQLFn: func(ctx context.Context, config SearchConfig, username, password,
   565  					imageName, searchedCVE string) (*cveResult, error,
   566  				) {
   567  					if count == 0 {
   568  						count++
   569  						fmt.Println("Count:", count)
   570  
   571  						return &cveResult{}, zerr.ErrCVEDBNotFound
   572  					}
   573  
   574  					return &cveResult{}, zerr.ErrInjected
   575  				},
   576  			})
   577  			buff := bytes.NewBufferString("")
   578  			cmd.SetOut(buff)
   579  			cmd.SetErr(buff)
   580  			cmd.SetArgs(args)
   581  			err := cmd.Execute()
   582  			So(err, ShouldNotBeNil)
   583  			space := regexp.MustCompile(`\s+`)
   584  			str := space.ReplaceAllString(buff.String(), " ")
   585  			actual := strings.TrimSpace(str)
   586  			So(actual, ShouldContainSubstring, "[warning] CVE DB is not ready")
   587  		})
   588  	})
   589  }
   590  
   591  func TestCVECommandErrors(t *testing.T) {
   592  	port := test.GetFreePort()
   593  	baseURL := test.GetBaseURL(port)
   594  	conf := config.New()
   595  	conf.HTTP.Port = port
   596  
   597  	conf.Extensions = &extconf.ExtensionConfig{
   598  		Search: &extconf.SearchConfig{
   599  			BaseConfig: extconf.BaseConfig{Enable: ref(true)},
   600  		},
   601  	}
   602  
   603  	ctlr := api.NewController(conf)
   604  	ctlr.Config.Storage.RootDirectory = t.TempDir()
   605  	cm := test.NewControllerManager(ctlr)
   606  
   607  	cm.StartAndWait(conf.HTTP.Port)
   608  	defer cm.StopServer()
   609  
   610  	Convey("commands without gql", t, func() {
   611  		configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, baseURL))
   612  		defer os.Remove(configPath)
   613  
   614  		Convey("cveid", func() {
   615  			args := []string{"affected", "CVE-1942"}
   616  			cmd := NewCVECommand(mockService{})
   617  			buff := bytes.NewBufferString("")
   618  			cmd.SetOut(buff)
   619  			cmd.SetErr(buff)
   620  			cmd.SetArgs(args)
   621  			err := cmd.Execute()
   622  			So(err, ShouldNotBeNil)
   623  		})
   624  
   625  		Convey("cveid error", func() {
   626  			// too many args
   627  			args := []string{"too", "many", "args"}
   628  			cmd := NewImagesByCVEIDCommand(mockService{})
   629  			buff := bytes.NewBufferString("")
   630  			cmd.SetOut(buff)
   631  			cmd.SetErr(buff)
   632  			cmd.SetArgs(args)
   633  			err := cmd.Execute()
   634  			So(err, ShouldNotBeNil)
   635  
   636  			// bad args
   637  			args = []string{"not-a-cve-id"}
   638  			cmd = NewImagesByCVEIDCommand(mockService{})
   639  			buff = bytes.NewBufferString("")
   640  			cmd.SetOut(buff)
   641  			cmd.SetErr(buff)
   642  			cmd.SetArgs(args)
   643  			err = cmd.Execute()
   644  			So(err, ShouldNotBeNil)
   645  
   646  			// no URL
   647  			args = []string{"CVE-1942"}
   648  			cmd = NewImagesByCVEIDCommand(mockService{})
   649  			buff = bytes.NewBufferString("")
   650  			cmd.SetOut(buff)
   651  			cmd.SetErr(buff)
   652  			cmd.SetArgs(args)
   653  			err = cmd.Execute()
   654  			So(err, ShouldNotBeNil)
   655  		})
   656  
   657  		Convey("fixed command", func() {
   658  			args := []string{"fixed", "image-name", "CVE-123"}
   659  			cmd := NewCVECommand(mockService{})
   660  			buff := bytes.NewBufferString("")
   661  			cmd.SetOut(buff)
   662  			cmd.SetErr(buff)
   663  			cmd.SetArgs(args)
   664  			err := cmd.Execute()
   665  			So(err, ShouldNotBeNil)
   666  		})
   667  
   668  		Convey("fixed command error", func() {
   669  			// too many args
   670  			args := []string{"too", "many", "args", "args"}
   671  			cmd := NewFixedTagsCommand(mockService{})
   672  			buff := bytes.NewBufferString("")
   673  			cmd.SetOut(buff)
   674  			cmd.SetErr(buff)
   675  			cmd.SetArgs(args)
   676  			err := cmd.Execute()
   677  			So(err, ShouldNotBeNil)
   678  
   679  			// bad args
   680  			args = []string{"repo-tag-instead-of-just-repo:fail-here", "CVE-123"}
   681  			cmd = NewFixedTagsCommand(mockService{})
   682  			buff = bytes.NewBufferString("")
   683  			cmd.SetOut(buff)
   684  			cmd.SetErr(buff)
   685  			cmd.SetArgs(args)
   686  			err = cmd.Execute()
   687  			So(err, ShouldNotBeNil)
   688  
   689  			// no URL
   690  			args = []string{"CVE-1942"}
   691  			cmd = NewFixedTagsCommand(mockService{})
   692  			buff = bytes.NewBufferString("")
   693  			cmd.SetOut(buff)
   694  			cmd.SetErr(buff)
   695  			cmd.SetArgs(args)
   696  			err = cmd.Execute()
   697  			So(err, ShouldNotBeNil)
   698  		})
   699  
   700  		Convey("image", func() {
   701  			args := []string{"list", "repo:tag"}
   702  			cmd := NewCVECommand(mockService{})
   703  			buff := bytes.NewBufferString("")
   704  			cmd.SetOut(buff)
   705  			cmd.SetErr(buff)
   706  			cmd.SetArgs(args)
   707  			err := cmd.Execute()
   708  			So(err, ShouldNotBeNil)
   709  		})
   710  
   711  		Convey("image command error", func() {
   712  			// too many args
   713  			args := []string{"too", "many", "args", "args"}
   714  			cmd := NewCveForImageCommand(mockService{})
   715  			buff := bytes.NewBufferString("")
   716  			cmd.SetOut(buff)
   717  			cmd.SetErr(buff)
   718  			cmd.SetArgs(args)
   719  			err := cmd.Execute()
   720  			So(err, ShouldNotBeNil)
   721  
   722  			// bad args
   723  			args = []string{"repo-tag-instead-of-just-repo:fail-here", "CVE-123"}
   724  			cmd = NewCveForImageCommand(mockService{})
   725  			buff = bytes.NewBufferString("")
   726  			cmd.SetOut(buff)
   727  			cmd.SetErr(buff)
   728  			cmd.SetArgs(args)
   729  			err = cmd.Execute()
   730  			So(err, ShouldNotBeNil)
   731  
   732  			// no URL
   733  			args = []string{"CVE-1942"}
   734  			cmd = NewCveForImageCommand(mockService{})
   735  			buff = bytes.NewBufferString("")
   736  			cmd.SetOut(buff)
   737  			cmd.SetErr(buff)
   738  			cmd.SetArgs(args)
   739  			err = cmd.Execute()
   740  			So(err, ShouldNotBeNil)
   741  		})
   742  	})
   743  }
   744  
   745  type mockServiceForRetry struct {
   746  	mockService
   747  	retryCounter int
   748  	succeedOn    int
   749  }
   750  
   751  func (service *mockServiceForRetry) getTagsForCVEGQL(ctx context.Context, config SearchConfig, username, password, repo,
   752  	cveID string,
   753  ) (*zcommon.ImagesForCve, error) {
   754  	service.retryCounter += 1
   755  
   756  	if service.retryCounter < service.succeedOn || service.succeedOn < 0 {
   757  		return &zcommon.ImagesForCve{}, zerr.ErrCVEDBNotFound
   758  	}
   759  
   760  	return service.mockService.getTagsForCVEGQL(ctx, config, username, password, repo, cveID)
   761  }