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 }