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

     1  //go:build search
     2  // +build search
     3  
     4  package client
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"math"
    10  	"strings"
    11  	"sync"
    12  	"time"
    13  
    14  	zerr "zotregistry.dev/zot/errors"
    15  	zcommon "zotregistry.dev/zot/pkg/common"
    16  )
    17  
    18  const CveDBRetryInterval = 3
    19  
    20  func SearchAllImages(config SearchConfig) error {
    21  	username, password := getUsernameAndPassword(config.User)
    22  	imageErr := make(chan stringResult)
    23  	ctx, cancel := context.WithCancel(context.Background())
    24  
    25  	var wg sync.WaitGroup
    26  
    27  	wg.Add(1)
    28  
    29  	go config.SearchService.getAllImages(ctx, config, username, password, imageErr, &wg)
    30  	wg.Add(1)
    31  
    32  	errCh := make(chan error, 1)
    33  
    34  	go collectResults(config, &wg, imageErr, cancel, printImageTableHeader, errCh)
    35  	wg.Wait()
    36  	select {
    37  	case err := <-errCh:
    38  		return err
    39  	default:
    40  		return nil
    41  	}
    42  }
    43  
    44  func SearchAllImagesGQL(config SearchConfig) error {
    45  	username, password := getUsernameAndPassword(config.User)
    46  	ctx, cancel := context.WithCancel(context.Background())
    47  
    48  	defer cancel()
    49  
    50  	imageList, err := config.SearchService.getImagesGQL(ctx, config, username, password, "")
    51  	if err != nil {
    52  		return err
    53  	}
    54  
    55  	imageListData := []imageStruct{}
    56  
    57  	for _, image := range imageList.Results {
    58  		imageListData = append(imageListData, imageStruct(image))
    59  	}
    60  
    61  	return printImageResult(config, imageListData)
    62  }
    63  
    64  func SearchImageByName(config SearchConfig, image string) error {
    65  	username, password := getUsernameAndPassword(config.User)
    66  	imageErr := make(chan stringResult)
    67  	ctx, cancel := context.WithCancel(context.Background())
    68  
    69  	var wg sync.WaitGroup
    70  
    71  	wg.Add(1)
    72  
    73  	go config.SearchService.getImageByName(ctx, config, username, password,
    74  		image, imageErr, &wg)
    75  	wg.Add(1)
    76  
    77  	errCh := make(chan error, 1)
    78  	go collectResults(config, &wg, imageErr, cancel, printImageTableHeader, errCh)
    79  
    80  	wg.Wait()
    81  
    82  	select {
    83  	case err := <-errCh:
    84  		if strings.Contains(err.Error(), "NAME_UNKNOWN") {
    85  			return zerr.ErrEmptyRepoList
    86  		}
    87  
    88  		return err
    89  	default:
    90  		return nil
    91  	}
    92  }
    93  
    94  func SearchImageByNameGQL(config SearchConfig, imageName string) error {
    95  	username, password := getUsernameAndPassword(config.User)
    96  	ctx, cancel := context.WithCancel(context.Background())
    97  
    98  	defer cancel()
    99  
   100  	repo, tag := zcommon.GetImageDirAndTag(imageName)
   101  
   102  	imageList, err := config.SearchService.getImagesGQL(ctx, config, username, password, repo)
   103  	if err != nil {
   104  		return err
   105  	}
   106  
   107  	imageListData := []imageStruct{}
   108  
   109  	for _, image := range imageList.Results {
   110  		if tag == "" || image.Tag == tag {
   111  			imageListData = append(imageListData, imageStruct(image))
   112  		}
   113  	}
   114  
   115  	return printImageResult(config, imageListData)
   116  }
   117  
   118  func SearchImagesByDigest(config SearchConfig, digest string) error {
   119  	username, password := getUsernameAndPassword(config.User)
   120  	imageErr := make(chan stringResult)
   121  	ctx, cancel := context.WithCancel(context.Background())
   122  
   123  	var wg sync.WaitGroup
   124  
   125  	wg.Add(1)
   126  
   127  	go config.SearchService.getImagesByDigest(ctx, config, username, password,
   128  		digest, imageErr, &wg)
   129  	wg.Add(1)
   130  
   131  	errCh := make(chan error, 1)
   132  	go collectResults(config, &wg, imageErr, cancel, printImageTableHeader, errCh)
   133  
   134  	wg.Wait()
   135  
   136  	select {
   137  	case err := <-errCh:
   138  		return err
   139  	default:
   140  		return nil
   141  	}
   142  }
   143  
   144  func SearchDerivedImageListGQL(config SearchConfig, derivedImage string) error {
   145  	username, password := getUsernameAndPassword(config.User)
   146  	ctx, cancel := context.WithCancel(context.Background())
   147  
   148  	defer cancel()
   149  
   150  	imageList, err := config.SearchService.getDerivedImageListGQL(ctx, config, username,
   151  		password, derivedImage)
   152  	if err != nil {
   153  		return err
   154  	}
   155  
   156  	imageListData := []imageStruct{}
   157  
   158  	for _, image := range imageList.DerivedImageList.Results {
   159  		imageListData = append(imageListData, imageStruct(image))
   160  	}
   161  
   162  	return printImageResult(config, imageListData)
   163  }
   164  
   165  func SearchBaseImageListGQL(config SearchConfig, baseImage string) error {
   166  	username, password := getUsernameAndPassword(config.User)
   167  	ctx, cancel := context.WithCancel(context.Background())
   168  
   169  	defer cancel()
   170  
   171  	imageList, err := config.SearchService.getBaseImageListGQL(ctx, config, username,
   172  		password, baseImage)
   173  	if err != nil {
   174  		return err
   175  	}
   176  
   177  	imageListData := []imageStruct{}
   178  
   179  	for _, image := range imageList.BaseImageList.Results {
   180  		imageListData = append(imageListData, imageStruct(image))
   181  	}
   182  
   183  	return printImageResult(config, imageListData)
   184  }
   185  
   186  func SearchImagesForDigestGQL(config SearchConfig, digest string) error {
   187  	username, password := getUsernameAndPassword(config.User)
   188  	ctx, cancel := context.WithCancel(context.Background())
   189  
   190  	defer cancel()
   191  
   192  	imageList, err := config.SearchService.getImagesForDigestGQL(ctx, config, username, password, digest)
   193  	if err != nil {
   194  		return err
   195  	}
   196  
   197  	imageListData := []imageStruct{}
   198  
   199  	for _, image := range imageList.Results {
   200  		imageListData = append(imageListData, imageStruct(image))
   201  	}
   202  
   203  	if err := printImageResult(config, imageListData); err != nil {
   204  		return err
   205  	}
   206  
   207  	return nil
   208  }
   209  
   210  func SearchCVEForImageGQL(config SearchConfig, image, searchedCveID string) error {
   211  	username, password := getUsernameAndPassword(config.User)
   212  	ctx, cancel := context.WithCancel(context.Background())
   213  
   214  	defer cancel()
   215  
   216  	var cveList *cveResult
   217  
   218  	err := zcommon.RetryWithContext(ctx, func(attempt int, retryIn time.Duration) error {
   219  		var err error
   220  
   221  		cveList, err = config.SearchService.getCveByImageGQL(ctx, config, username, password, image, searchedCveID)
   222  		if err != nil {
   223  			if !strings.Contains(err.Error(), zerr.ErrCVEDBNotFound.Error()) {
   224  				cancel()
   225  
   226  				return err
   227  			}
   228  
   229  			fmt.Fprintf(config.ResultWriter,
   230  				"[warning] CVE DB is not ready [%d] - retry in %d seconds\n", attempt, int(retryIn.Seconds()))
   231  		}
   232  
   233  		return err
   234  	}, maxRetries, CveDBRetryInterval*time.Second)
   235  	if err != nil {
   236  		return err
   237  	}
   238  
   239  	if len(cveList.Data.CVEListForImage.CVEList) == 0 {
   240  		fmt.Fprint(config.ResultWriter, "No CVEs found for image\n")
   241  
   242  		return nil
   243  	}
   244  
   245  	var builder strings.Builder
   246  
   247  	if config.OutputFormat == defaultOutputFormat || config.OutputFormat == "" {
   248  		imageCVESummary := cveList.Data.CVEListForImage.Summary
   249  
   250  		statsStr := fmt.Sprintf("CRITICAL %d, HIGH %d, MEDIUM %d, LOW %d, UNKNOWN %d, TOTAL %d\n\n",
   251  			imageCVESummary.CriticalCount, imageCVESummary.HighCount, imageCVESummary.MediumCount,
   252  			imageCVESummary.LowCount, imageCVESummary.UnknownCount, imageCVESummary.Count)
   253  
   254  		fmt.Fprint(config.ResultWriter, statsStr)
   255  
   256  		if !config.Verbose {
   257  			printCVETableHeader(&builder)
   258  			fmt.Fprint(config.ResultWriter, builder.String())
   259  		}
   260  	}
   261  
   262  	out, err := cveList.string(config.OutputFormat, config.Verbose)
   263  	if err != nil {
   264  		return err
   265  	}
   266  
   267  	fmt.Fprint(config.ResultWriter, out)
   268  
   269  	return nil
   270  }
   271  
   272  func SearchCVEDiffList(config SearchConfig, minuend, subtrahend ImageIdentifier) error {
   273  	username, password := getUsernameAndPassword(config.User)
   274  
   275  	response, err := config.SearchService.getCVEDiffListGQL(context.Background(), config, username, password,
   276  		minuend, subtrahend)
   277  	if err != nil {
   278  		return err
   279  	}
   280  
   281  	cveDiffResult := response.Data.CveDiffResult
   282  
   283  	result := cveResult{
   284  		Data: cveData{
   285  			CVEListForImage: cveListForImage{
   286  				Tag:     cveDiffResult.Minuend.Tag,
   287  				CVEList: cveDiffResult.CVEList,
   288  				Summary: cveDiffResult.Summary,
   289  			},
   290  		},
   291  	}
   292  
   293  	var builder strings.Builder
   294  
   295  	if config.OutputFormat == defaultOutputFormat || config.OutputFormat == "" {
   296  		imageCVESummary := result.Data.CVEListForImage.Summary
   297  
   298  		statsStr := fmt.Sprintf("CRITICAL %d, HIGH %d, MEDIUM %d, LOW %d, UNKNOWN %d, TOTAL %d\n\n",
   299  			imageCVESummary.CriticalCount, imageCVESummary.HighCount, imageCVESummary.MediumCount,
   300  			imageCVESummary.LowCount, imageCVESummary.UnknownCount, imageCVESummary.Count)
   301  
   302  		fmt.Fprint(config.ResultWriter, statsStr)
   303  
   304  		printCVETableHeader(&builder)
   305  		fmt.Fprint(config.ResultWriter, builder.String())
   306  	}
   307  
   308  	out, err := result.string(config.OutputFormat, config.Verbose)
   309  	if err != nil {
   310  		return err
   311  	}
   312  
   313  	fmt.Fprint(config.ResultWriter, out)
   314  
   315  	return nil
   316  }
   317  
   318  func SearchImagesByCVEIDGQL(config SearchConfig, repo, cveid string) error {
   319  	username, password := getUsernameAndPassword(config.User)
   320  	ctx, cancel := context.WithCancel(context.Background())
   321  
   322  	defer cancel()
   323  
   324  	var imageList *zcommon.ImagesForCve
   325  
   326  	err := zcommon.RetryWithContext(ctx, func(attempt int, retryIn time.Duration) error {
   327  		var err error
   328  
   329  		imageList, err = config.SearchService.getTagsForCVEGQL(ctx, config, username, password,
   330  			repo, cveid)
   331  		if err != nil {
   332  			if !strings.Contains(err.Error(), zerr.ErrCVEDBNotFound.Error()) {
   333  				cancel()
   334  
   335  				return err
   336  			}
   337  
   338  			fmt.Fprintf(config.ResultWriter,
   339  				"[warning] CVE DB is not ready [%d] - retry in %d seconds\n", attempt, int(retryIn.Seconds()))
   340  		}
   341  
   342  		return err
   343  	}, maxRetries, CveDBRetryInterval*time.Second)
   344  	if err != nil {
   345  		return err
   346  	}
   347  
   348  	imageListData := []imageStruct{}
   349  
   350  	for _, image := range imageList.Results {
   351  		imageListData = append(imageListData, imageStruct(image))
   352  	}
   353  
   354  	return printImageResult(config, imageListData)
   355  }
   356  
   357  func SearchFixedTagsGQL(config SearchConfig, repo, cveid string) error {
   358  	username, password := getUsernameAndPassword(config.User)
   359  	ctx, cancel := context.WithCancel(context.Background())
   360  
   361  	defer cancel()
   362  
   363  	var fixedTags *zcommon.ImageListWithCVEFixedResponse
   364  
   365  	err := zcommon.RetryWithContext(ctx, func(attempt int, retryIn time.Duration) error {
   366  		var err error
   367  
   368  		fixedTags, err = config.SearchService.getFixedTagsForCVEGQL(ctx, config, username, password,
   369  			repo, cveid)
   370  		if err != nil {
   371  			if !strings.Contains(err.Error(), zerr.ErrCVEDBNotFound.Error()) {
   372  				cancel()
   373  
   374  				return err
   375  			}
   376  
   377  			fmt.Fprintf(config.ResultWriter,
   378  				"[warning] CVE DB is not ready [%d] - retry in %d seconds\n", attempt, int(retryIn.Seconds()))
   379  		}
   380  
   381  		return err
   382  	}, maxRetries, CveDBRetryInterval*time.Second)
   383  	if err != nil {
   384  		return err
   385  	}
   386  
   387  	imageList := make([]imageStruct, 0, len(fixedTags.Results))
   388  
   389  	for _, image := range fixedTags.Results {
   390  		imageList = append(imageList, imageStruct(image))
   391  	}
   392  
   393  	return printImageResult(config, imageList)
   394  }
   395  
   396  func GlobalSearchGQL(config SearchConfig, query string) error {
   397  	username, password := getUsernameAndPassword(config.User)
   398  	ctx, cancel := context.WithCancel(context.Background())
   399  
   400  	defer cancel()
   401  
   402  	globalSearchResult, err := config.SearchService.globalSearchGQL(ctx, config, username, password, query)
   403  	if err != nil {
   404  		return err
   405  	}
   406  
   407  	imagesList := []imageStruct{}
   408  
   409  	for _, image := range globalSearchResult.Images {
   410  		imagesList = append(imagesList, imageStruct(image))
   411  	}
   412  
   413  	reposList := []repoStruct{}
   414  
   415  	for _, repo := range globalSearchResult.Repos {
   416  		reposList = append(reposList, repoStruct(repo))
   417  	}
   418  
   419  	if err := printImageResult(config, imagesList); err != nil {
   420  		return err
   421  	}
   422  
   423  	return printRepoResults(config, reposList)
   424  }
   425  
   426  func SearchReferrersGQL(config SearchConfig, subject string) error {
   427  	username, password := getUsernameAndPassword(config.User)
   428  
   429  	repo, ref, refIsTag, err := zcommon.GetRepoReference(subject)
   430  	if err != nil {
   431  		return err
   432  	}
   433  
   434  	digest := ref
   435  
   436  	if refIsTag {
   437  		digest, err = fetchImageDigest(repo, ref, username, password, config)
   438  		if err != nil {
   439  			return err
   440  		}
   441  	}
   442  
   443  	response, err := config.SearchService.getReferrersGQL(context.Background(), config, username, password, repo, digest)
   444  	if err != nil {
   445  		return err
   446  	}
   447  
   448  	referrersList := referrersResult(response.Referrers)
   449  
   450  	maxArtifactTypeLen := math.MinInt
   451  
   452  	for _, referrer := range referrersList {
   453  		if maxArtifactTypeLen < len(referrer.ArtifactType) {
   454  			maxArtifactTypeLen = len(referrer.ArtifactType)
   455  		}
   456  	}
   457  
   458  	printReferrersTableHeader(config, config.ResultWriter, maxArtifactTypeLen)
   459  
   460  	return printReferrersResult(config, referrersList, maxArtifactTypeLen)
   461  }
   462  
   463  func SearchReferrers(config SearchConfig, subject string) error {
   464  	username, password := getUsernameAndPassword(config.User)
   465  
   466  	repo, ref, refIsTag, err := zcommon.GetRepoReference(subject)
   467  	if err != nil {
   468  		return err
   469  	}
   470  
   471  	digest := ref
   472  
   473  	if refIsTag {
   474  		digest, err = fetchImageDigest(repo, ref, username, password, config)
   475  		if err != nil {
   476  			return err
   477  		}
   478  	}
   479  
   480  	referrersList, err := config.SearchService.getReferrers(context.Background(), config, username, password,
   481  		repo, digest)
   482  	if err != nil {
   483  		return err
   484  	}
   485  
   486  	maxArtifactTypeLen := math.MinInt
   487  
   488  	for _, referrer := range referrersList {
   489  		if maxArtifactTypeLen < len(referrer.ArtifactType) {
   490  			maxArtifactTypeLen = len(referrer.ArtifactType)
   491  		}
   492  	}
   493  
   494  	printReferrersTableHeader(config, config.ResultWriter, maxArtifactTypeLen)
   495  
   496  	return printReferrersResult(config, referrersList, maxArtifactTypeLen)
   497  }
   498  
   499  func SearchRepos(config SearchConfig) error {
   500  	username, password := getUsernameAndPassword(config.User)
   501  	repoErr := make(chan stringResult)
   502  	ctx, cancel := context.WithCancel(context.Background())
   503  
   504  	var wg sync.WaitGroup
   505  
   506  	wg.Add(1)
   507  
   508  	go config.SearchService.getRepos(ctx, config, username, password, repoErr, &wg)
   509  	wg.Add(1)
   510  
   511  	errCh := make(chan error, 1)
   512  
   513  	go collectResults(config, &wg, repoErr, cancel, printImageTableHeader, errCh)
   514  	wg.Wait()
   515  	select {
   516  	case err := <-errCh:
   517  		return err
   518  	default:
   519  		return nil
   520  	}
   521  }