zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/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.io/zot/errors"
    15  	zcommon "zotregistry.io/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  		printCVETableHeader(&builder)
   249  		fmt.Fprint(config.ResultWriter, builder.String())
   250  	}
   251  
   252  	out, err := cveList.string(config.OutputFormat)
   253  	if err != nil {
   254  		return err
   255  	}
   256  
   257  	fmt.Fprint(config.ResultWriter, out)
   258  
   259  	return nil
   260  }
   261  
   262  func SearchImagesByCVEIDGQL(config SearchConfig, repo, cveid string) error {
   263  	username, password := getUsernameAndPassword(config.User)
   264  	ctx, cancel := context.WithCancel(context.Background())
   265  
   266  	defer cancel()
   267  
   268  	var imageList *zcommon.ImagesForCve
   269  
   270  	err := zcommon.RetryWithContext(ctx, func(attempt int, retryIn time.Duration) error {
   271  		var err error
   272  
   273  		imageList, err = config.SearchService.getTagsForCVEGQL(ctx, config, username, password,
   274  			repo, cveid)
   275  		if err != nil {
   276  			if !strings.Contains(err.Error(), zerr.ErrCVEDBNotFound.Error()) {
   277  				cancel()
   278  
   279  				return err
   280  			}
   281  
   282  			fmt.Fprintf(config.ResultWriter,
   283  				"[warning] CVE DB is not ready [%d] - retry in %d seconds\n", attempt, int(retryIn.Seconds()))
   284  		}
   285  
   286  		return err
   287  	}, maxRetries, CveDBRetryInterval*time.Second)
   288  	if err != nil {
   289  		return err
   290  	}
   291  
   292  	imageListData := []imageStruct{}
   293  
   294  	for _, image := range imageList.Results {
   295  		imageListData = append(imageListData, imageStruct(image))
   296  	}
   297  
   298  	return printImageResult(config, imageListData)
   299  }
   300  
   301  func SearchFixedTagsGQL(config SearchConfig, repo, cveid string) error {
   302  	username, password := getUsernameAndPassword(config.User)
   303  	ctx, cancel := context.WithCancel(context.Background())
   304  
   305  	defer cancel()
   306  
   307  	var fixedTags *zcommon.ImageListWithCVEFixedResponse
   308  
   309  	err := zcommon.RetryWithContext(ctx, func(attempt int, retryIn time.Duration) error {
   310  		var err error
   311  
   312  		fixedTags, err = config.SearchService.getFixedTagsForCVEGQL(ctx, config, username, password,
   313  			repo, cveid)
   314  		if err != nil {
   315  			if !strings.Contains(err.Error(), zerr.ErrCVEDBNotFound.Error()) {
   316  				cancel()
   317  
   318  				return err
   319  			}
   320  
   321  			fmt.Fprintf(config.ResultWriter,
   322  				"[warning] CVE DB is not ready [%d] - retry in %d seconds\n", attempt, int(retryIn.Seconds()))
   323  		}
   324  
   325  		return err
   326  	}, maxRetries, CveDBRetryInterval*time.Second)
   327  	if err != nil {
   328  		return err
   329  	}
   330  
   331  	imageList := make([]imageStruct, 0, len(fixedTags.Results))
   332  
   333  	for _, image := range fixedTags.Results {
   334  		imageList = append(imageList, imageStruct(image))
   335  	}
   336  
   337  	return printImageResult(config, imageList)
   338  }
   339  
   340  func GlobalSearchGQL(config SearchConfig, query string) error {
   341  	username, password := getUsernameAndPassword(config.User)
   342  	ctx, cancel := context.WithCancel(context.Background())
   343  
   344  	defer cancel()
   345  
   346  	globalSearchResult, err := config.SearchService.globalSearchGQL(ctx, config, username, password, query)
   347  	if err != nil {
   348  		return err
   349  	}
   350  
   351  	imagesList := []imageStruct{}
   352  
   353  	for _, image := range globalSearchResult.Images {
   354  		imagesList = append(imagesList, imageStruct(image))
   355  	}
   356  
   357  	reposList := []repoStruct{}
   358  
   359  	for _, repo := range globalSearchResult.Repos {
   360  		reposList = append(reposList, repoStruct(repo))
   361  	}
   362  
   363  	if err := printImageResult(config, imagesList); err != nil {
   364  		return err
   365  	}
   366  
   367  	return printRepoResults(config, reposList)
   368  }
   369  
   370  func SearchReferrersGQL(config SearchConfig, subject string) error {
   371  	username, password := getUsernameAndPassword(config.User)
   372  
   373  	repo, ref, refIsTag, err := zcommon.GetRepoReference(subject)
   374  	if err != nil {
   375  		return err
   376  	}
   377  
   378  	digest := ref
   379  
   380  	if refIsTag {
   381  		digest, err = fetchImageDigest(repo, ref, username, password, config)
   382  		if err != nil {
   383  			return err
   384  		}
   385  	}
   386  
   387  	response, err := config.SearchService.getReferrersGQL(context.Background(), config, username, password, repo, digest)
   388  	if err != nil {
   389  		return err
   390  	}
   391  
   392  	referrersList := referrersResult(response.Referrers)
   393  
   394  	maxArtifactTypeLen := math.MinInt
   395  
   396  	for _, referrer := range referrersList {
   397  		if maxArtifactTypeLen < len(referrer.ArtifactType) {
   398  			maxArtifactTypeLen = len(referrer.ArtifactType)
   399  		}
   400  	}
   401  
   402  	printReferrersTableHeader(config, config.ResultWriter, maxArtifactTypeLen)
   403  
   404  	return printReferrersResult(config, referrersList, maxArtifactTypeLen)
   405  }
   406  
   407  func SearchReferrers(config SearchConfig, subject string) error {
   408  	username, password := getUsernameAndPassword(config.User)
   409  
   410  	repo, ref, refIsTag, err := zcommon.GetRepoReference(subject)
   411  	if err != nil {
   412  		return err
   413  	}
   414  
   415  	digest := ref
   416  
   417  	if refIsTag {
   418  		digest, err = fetchImageDigest(repo, ref, username, password, config)
   419  		if err != nil {
   420  			return err
   421  		}
   422  	}
   423  
   424  	referrersList, err := config.SearchService.getReferrers(context.Background(), config, username, password,
   425  		repo, digest)
   426  	if err != nil {
   427  		return err
   428  	}
   429  
   430  	maxArtifactTypeLen := math.MinInt
   431  
   432  	for _, referrer := range referrersList {
   433  		if maxArtifactTypeLen < len(referrer.ArtifactType) {
   434  			maxArtifactTypeLen = len(referrer.ArtifactType)
   435  		}
   436  	}
   437  
   438  	printReferrersTableHeader(config, config.ResultWriter, maxArtifactTypeLen)
   439  
   440  	return printReferrersResult(config, referrersList, maxArtifactTypeLen)
   441  }
   442  
   443  func SearchRepos(config SearchConfig) error {
   444  	username, password := getUsernameAndPassword(config.User)
   445  	repoErr := make(chan stringResult)
   446  	ctx, cancel := context.WithCancel(context.Background())
   447  
   448  	var wg sync.WaitGroup
   449  
   450  	wg.Add(1)
   451  
   452  	go config.SearchService.getRepos(ctx, config, username, password, repoErr, &wg)
   453  	wg.Add(1)
   454  
   455  	errCh := make(chan error, 1)
   456  
   457  	go collectResults(config, &wg, repoErr, cancel, printImageTableHeader, errCh)
   458  	wg.Wait()
   459  	select {
   460  	case err := <-errCh:
   461  		return err
   462  	default:
   463  		return nil
   464  	}
   465  }