github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/package-server/provider/registry.go (about) 1 package provider 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io" 8 "sort" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/blang/semver/v4" 14 operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" 15 "github.com/operator-framework/operator-registry/pkg/api" 16 orregistry "github.com/operator-framework/operator-registry/pkg/registry" 17 "github.com/sirupsen/logrus" 18 "google.golang.org/grpc" 19 "google.golang.org/grpc/connectivity" 20 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 "k8s.io/apimachinery/pkg/labels" 22 utilerrors "k8s.io/apimachinery/pkg/util/errors" 23 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 24 "k8s.io/client-go/tools/cache" 25 26 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" 27 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/informers/externalversions" 28 operatorslisters "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1" 29 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry" 30 registrygrpc "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/grpc" 31 utillabels "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubernetes/pkg/util/labels" 32 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer" 33 "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators" 34 pkglisters "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/client/listers/operators/internalversion" 35 ) 36 37 const ( 38 catalogIndex = "catalog" 39 cacheTimeout = 5 * time.Minute 40 readyTimeout = 10 * time.Minute 41 stateTimeout = 20 * time.Second 42 ) 43 44 func getSourceKey(pkg *operators.PackageManifest) (key *registry.CatalogKey) { 45 if pkg != nil { 46 key = ®istry.CatalogKey{ 47 Namespace: pkg.Status.CatalogSourceNamespace, 48 Name: pkg.Status.CatalogSource, 49 } 50 } 51 52 return 53 } 54 55 func catalogIndexFunc(obj interface{}) ([]string, error) { 56 pkg, ok := obj.(*operators.PackageManifest) 57 if !ok { 58 return []string{""}, fmt.Errorf("obj is not a packagemanifest %v", obj) 59 } 60 61 return []string{getSourceKey(pkg).String()}, nil 62 } 63 64 func PackageManifestKeyFunc(obj interface{}) (string, error) { 65 if key, ok := obj.(string); ok { 66 return key, nil 67 } 68 69 pkg, ok := obj.(*operators.PackageManifest) 70 if !ok { 71 return "", fmt.Errorf("obj is not a packagemanifest %v", obj) 72 } 73 74 return pkg.Status.CatalogSource + "/" + pkg.GetNamespace() + "/" + pkg.GetName(), nil 75 } 76 77 func SplitPackageManifestKey(key string) (catsrcname, namespace, name string, err error) { 78 parts := strings.Split(key, "/") 79 switch len(parts) { 80 case 3: 81 // catalogsource name, namespace and packagemanifest name 82 return parts[0], parts[1], parts[2], nil 83 } 84 85 return "", "", "", fmt.Errorf("unexpected key format: %q", key) 86 } 87 88 type registryClient struct { 89 api.RegistryClient 90 catsrc *operatorsv1alpha1.CatalogSource 91 conn *grpc.ClientConn 92 } 93 94 func newRegistryClient(catsrc *operatorsv1alpha1.CatalogSource, conn *grpc.ClientConn) *registryClient { 95 return ®istryClient{ 96 RegistryClient: api.NewRegistryClient(conn), 97 catsrc: catsrc, 98 conn: conn, 99 } 100 } 101 102 func (r *registryClient) key() (key registry.CatalogKey, err error) { 103 if r.catsrc == nil { 104 err = fmt.Errorf("cannot get key, nil catalog") 105 return 106 } 107 108 key = registry.CatalogKey{ 109 Namespace: r.catsrc.GetNamespace(), 110 Name: r.catsrc.GetName(), 111 } 112 113 return 114 } 115 116 // RegistryProvider aggregates several `CatalogSources` and establishes gRPC connections to their registry servers. 117 type RegistryProvider struct { 118 queueinformer.Operator 119 runOnce sync.Once 120 121 globalNamespace string 122 sources *registrygrpc.SourceStore 123 cache cache.Indexer 124 pkgLister pkglisters.PackageManifestLister 125 catsrcLister operatorslisters.CatalogSourceLister 126 } 127 128 var _ PackageManifestProvider = &RegistryProvider{} 129 130 func NewRegistryProvider(ctx context.Context, crClient versioned.Interface, operator queueinformer.Operator, wakeupInterval time.Duration, globalNamespace string) (*RegistryProvider, error) { 131 p := &RegistryProvider{ 132 Operator: operator, 133 134 globalNamespace: globalNamespace, 135 cache: cache.NewIndexer(PackageManifestKeyFunc, cache.Indexers{ 136 cache.NamespaceIndex: cache.MetaNamespaceIndexFunc, 137 catalogIndex: catalogIndexFunc, 138 }), 139 } 140 p.sources = registrygrpc.NewSourceStore(logrus.New(), stateTimeout, readyTimeout, p.syncSourceState) 141 p.pkgLister = pkglisters.NewPackageManifestLister(p.cache) 142 143 // Register queue and QueueInformer 144 informerFactory := externalversions.NewSharedInformerFactoryWithOptions(crClient, wakeupInterval, externalversions.WithNamespace(metav1.NamespaceAll)) 145 catsrcInformer := informerFactory.Operators().V1alpha1().CatalogSources() 146 catsrcQueueInformer, err := queueinformer.NewQueueInformer( 147 ctx, 148 queueinformer.WithInformer(catsrcInformer.Informer()), 149 queueinformer.WithSyncer(queueinformer.LegacySyncHandler(p.syncCatalogSource).ToSyncerWithDelete(p.catalogSourceDeleted)), 150 ) 151 if err != nil { 152 return nil, err 153 } 154 if err := p.RegisterQueueInformer(catsrcQueueInformer); err != nil { 155 return nil, err 156 } 157 p.catsrcLister = catsrcInformer.Lister() 158 159 return p, nil 160 } 161 162 // Run starts the provider's source connection management and catalog informers without blocking. 163 func (p *RegistryProvider) Run(ctx context.Context) { 164 p.runOnce.Do(func() { 165 // Both are non-blocking 166 p.sources.Start(ctx) 167 p.Operator.Run(ctx) 168 }) 169 } 170 171 func (p *RegistryProvider) syncCatalogSource(obj interface{}) (syncError error) { 172 source, ok := obj.(*operatorsv1alpha1.CatalogSource) 173 if !ok { 174 logrus.Errorf("catalogsource type assertion failed: wrong type: %#v", obj) 175 } 176 177 logger := logrus.WithFields(logrus.Fields{ 178 "action": "sync catalogsource", 179 "name": source.GetName(), 180 "namespace": source.GetNamespace(), 181 }) 182 183 if source.Status.RegistryServiceStatus == nil { 184 logger.Debug("registry service is not ready for grpc connection") 185 return 186 } 187 188 address := source.Address() 189 logger = logger.WithField("address", address) 190 191 key := registry.CatalogKey{ 192 Namespace: source.GetNamespace(), 193 Name: source.GetName(), 194 } 195 196 if sourceMeta := p.sources.GetMeta(key); sourceMeta != nil && sourceMeta.Address == address { 197 logger.Infof("updating PackageManifest based on CatalogSource changes: %v", key) 198 timeout, cancel := context.WithTimeout(context.Background(), cacheTimeout) 199 defer cancel() 200 var client *registryClient 201 client, syncError = p.registryClient(key) 202 if syncError != nil { 203 return 204 } 205 syncError = p.refreshCache(timeout, client) 206 return 207 } 208 209 logger.Info("connecting to source") 210 if _, syncError = p.sources.Add(key, address); syncError != nil { 211 logger.Warn("failed to create a new source") 212 } 213 214 return 215 } 216 217 func (p *RegistryProvider) syncSourceState(state registrygrpc.SourceState) { 218 key := state.Key 219 logger := logrus.WithFields(logrus.Fields{ 220 "action": "sync source", 221 "source": key, 222 "state": state.State, 223 }) 224 logger.Debug("source state changed") 225 226 timeout, cancel := context.WithTimeout(context.Background(), cacheTimeout) 227 defer cancel() 228 229 var err error 230 switch state.State { 231 case connectivity.Ready: 232 var client *registryClient 233 client, err = p.registryClient(key) 234 if err == nil { 235 err = p.refreshCache(timeout, client) 236 } 237 case connectivity.TransientFailure, connectivity.Shutdown: 238 err = p.gcPackages(key, nil) 239 default: 240 logger.Debug("inert source state, skipping cache update") 241 } 242 243 if err != nil { 244 logger.WithError(err).Warn("failed to update cache") 245 } 246 } 247 248 func (p *RegistryProvider) registryClient(key registry.CatalogKey) (client *registryClient, err error) { 249 source := p.sources.Get(key) 250 if source == nil { 251 err = fmt.Errorf("missing source for catalog %s", key) 252 return 253 } 254 255 conn := source.Conn 256 if conn == nil { 257 err = fmt.Errorf("missing grpc connection for source %s", key) 258 return 259 } 260 261 var catsrc *operatorsv1alpha1.CatalogSource 262 catsrc, err = p.catsrcLister.CatalogSources(key.Namespace).Get(key.Name) 263 if err != nil { 264 return 265 } 266 267 client = newRegistryClient(catsrc, conn) 268 return 269 } 270 271 func getOperatorDeprecation(in *api.Deprecation) *operators.Deprecation { 272 if in == nil { 273 return nil 274 } 275 return &operators.Deprecation{ 276 Message: in.Message, 277 } 278 } 279 280 func (p *RegistryProvider) refreshCache(ctx context.Context, client *registryClient) error { 281 key, err := client.key() 282 if err != nil { 283 return err 284 } 285 286 logger := logrus.WithFields(logrus.Fields{ 287 "action": "refresh cache", 288 "source": key, 289 }) 290 291 bundleStream, err := client.ListBundles(ctx, &api.ListBundlesRequest{}) 292 if err != nil { 293 logger.WithField("err", err.Error()).Warnf("error getting bundle stream") 294 return nil 295 } 296 297 bundles := map[string]map[string][]operators.ChannelEntry{} 298 299 for { 300 bundle, err := bundleStream.Recv() 301 if err == io.EOF { 302 break 303 } 304 if err != nil { 305 logger.WithField("err", err.Error()).Warnf("error getting bundle data") 306 break 307 } 308 if isDeprecated(bundle) { 309 continue 310 } 311 if _, ok := bundles[bundle.PackageName]; !ok { 312 bundles[bundle.PackageName] = map[string][]operators.ChannelEntry{} 313 } 314 315 bundles[bundle.PackageName][bundle.ChannelName] = append(bundles[bundle.PackageName][bundle.ChannelName], operators.ChannelEntry{ 316 Name: bundle.CsvName, 317 Version: bundle.Version, 318 Deprecation: getOperatorDeprecation(bundle.Deprecation), 319 }) 320 } 321 322 stream, err := client.ListPackages(ctx, &api.ListPackageRequest{}) 323 if err != nil { 324 logger.WithField("err", err.Error()).Warnf("error getting package stream") 325 return nil 326 } 327 328 for pkgName := range bundles { 329 for chName := range bundles[pkgName] { 330 sort.Slice(bundles[pkgName][chName], func(i, j int) bool { 331 iV, err := semver.Parse(bundles[pkgName][chName][i].Version) 332 if err != nil { 333 iV = semver.Version{} 334 } 335 jV, err := semver.Parse(bundles[pkgName][chName][j].Version) 336 if err != nil { 337 jV = semver.Version{} 338 } 339 return iV.GT(jV) 340 }) 341 } 342 } 343 344 var ( 345 added = map[string]struct{}{} 346 mu sync.Mutex 347 wg sync.WaitGroup 348 ) 349 for { 350 pkgName, err := stream.Recv() 351 if err == io.EOF { 352 break 353 } 354 if err != nil { 355 logger.WithField("err", err.Error()).Warnf("error getting package name data") 356 break 357 } 358 359 wg.Add(1) 360 go func() { 361 defer wg.Done() 362 pkg, err := client.GetPackage(ctx, &api.GetPackageRequest{Name: pkgName.GetName()}) 363 if err != nil { 364 logger.WithField("err", err.Error()).Warnf("eliding package: error getting package") 365 return 366 } 367 368 newPkg, err := newPackageManifest(ctx, logger, pkg, client, bundles[pkg.GetName()]) 369 if err != nil { 370 logger.WithField("err", err.Error()).Warnf("eliding package: error converting to packagemanifest") 371 return 372 } 373 374 if err := p.cache.Add(newPkg); err != nil { 375 logger.WithField("err", err.Error()).Warnf("eliding package: failed to add to cache") 376 return 377 } 378 379 mu.Lock() 380 defer mu.Unlock() 381 added[newPkg.GetName()] = struct{}{} 382 }() 383 } 384 385 logger.Debug("caching new packages...") 386 wg.Wait() 387 logger.Debug("new packages cached") 388 389 // Garbage collect orphaned packagemanifests from the cache 390 return p.gcPackages(key, added) 391 } 392 393 func isDeprecated(b *api.Bundle) bool { 394 for _, p := range b.Properties { 395 if p.Type == orregistry.DeprecatedType { 396 return true 397 } 398 } 399 return false 400 } 401 402 func (p *RegistryProvider) gcPackages(key registry.CatalogKey, keep map[string]struct{}) error { 403 logger := logrus.WithFields(logrus.Fields{ 404 "action": "gc cache", 405 "source": key.String(), 406 }) 407 408 storedPkgKeys, err := p.cache.IndexKeys(catalogIndex, key.String()) 409 if err != nil { 410 return err 411 } 412 413 var errs []error 414 for _, storedPkgKey := range storedPkgKeys { 415 _, _, name, _ := SplitPackageManifestKey(storedPkgKey) 416 if keep != nil { 417 if _, ok := keep[name]; ok { 418 continue 419 } 420 } 421 if err := p.cache.Delete(storedPkgKey); err != nil { 422 logger.WithField("pkg", name).WithError(err).Warn("failed to delete cache entry") 423 errs = append(errs, err) 424 } 425 } 426 427 return utilerrors.NewAggregate(errs) 428 } 429 430 func (p *RegistryProvider) catalogSourceDeleted(obj interface{}) { 431 catsrc, ok := obj.(metav1.Object) 432 if !ok { 433 tombstone, ok := obj.(cache.DeletedFinalStateUnknown) 434 if !ok { 435 utilruntime.HandleError(fmt.Errorf("couldn't get object from tombstone %#v", obj)) 436 return 437 } 438 439 catsrc, ok = tombstone.Obj.(metav1.Object) 440 if !ok { 441 utilruntime.HandleError(fmt.Errorf("tombstone contained object that is not a Namespace %#v", obj)) 442 return 443 } 444 } 445 446 key := registry.CatalogKey{ 447 Namespace: catsrc.GetNamespace(), 448 Name: catsrc.GetName(), 449 } 450 logger := logrus.WithFields(logrus.Fields{ 451 "action": "CatalogSource Deleted", 452 "source": key.String(), 453 }) 454 455 if err := p.sources.Remove(key); err != nil { 456 logger.WithError(err).Warn("failed to remove source") 457 } 458 459 if err := p.gcPackages(key, nil); err != nil { 460 logger.WithError(err).Warn("failed to gc orphaned packages in cache") 461 } 462 } 463 464 func (p *RegistryProvider) Get(namespace, name string) (*operators.PackageManifest, error) { 465 logger := logrus.WithFields(logrus.Fields{ 466 "action": "Get PackageManifest", 467 "name": name, 468 "namespace": namespace, 469 }) 470 471 pkgs, err := p.List(namespace, labels.Everything()) 472 if err != nil { 473 return nil, fmt.Errorf("could not list packages in namespace %s", namespace) 474 } 475 476 for _, pkg := range pkgs.Items { 477 if pkg.GetName() == name { 478 return &pkg, nil 479 } 480 } 481 482 logger.Info("package not found") 483 return nil, nil 484 } 485 486 func (p *RegistryProvider) List(namespace string, selector labels.Selector) (*operators.PackageManifestList, error) { 487 var pkgs []*operators.PackageManifest 488 if namespace == metav1.NamespaceAll { 489 all, err := p.pkgLister.List(selector) 490 if err != nil { 491 return nil, err 492 } 493 pkgs = append(pkgs, all...) 494 } else { 495 nsPkgs, err := p.pkgLister.PackageManifests(namespace).List(selector) 496 if err != nil { 497 return nil, err 498 } 499 pkgs = append(pkgs, nsPkgs...) 500 501 if namespace != p.globalNamespace { 502 globalPkgs, err := p.pkgLister.PackageManifests(p.globalNamespace).List(selector) 503 if err != nil { 504 return nil, err 505 } 506 507 pkgs = append(pkgs, globalPkgs...) 508 } 509 } 510 511 pkgList := &operators.PackageManifestList{} 512 for _, pkg := range pkgs { 513 out := pkg.DeepCopy() 514 // Set request namespace to stop k8s clients from complaining about namespace mismatch. 515 if namespace != metav1.NamespaceAll { 516 out.SetNamespace(namespace) 517 } 518 pkgList.Items = append(pkgList.Items, *out) 519 } 520 521 return pkgList, nil 522 } 523 524 func newPackageManifest(ctx context.Context, logger *logrus.Entry, pkg *api.Package, client *registryClient, entriesByChannel map[string][]operators.ChannelEntry) (*operators.PackageManifest, error) { 525 pkgChannels := pkg.GetChannels() 526 sort.Slice(pkgChannels, func(i, j int) bool { 527 return pkgChannels[i].Name < pkgChannels[j].Name 528 }) 529 catsrc := client.catsrc 530 manifest := &operators.PackageManifest{ 531 ObjectMeta: metav1.ObjectMeta{ 532 Name: pkg.GetName(), 533 Namespace: catsrc.GetNamespace(), 534 Labels: utillabels.CloneAndAddLabel( 535 utillabels.CloneAndAddLabel(catsrc.GetLabels(), 536 "catalog", catsrc.GetName()), "catalog-namespace", catsrc.GetNamespace()), 537 CreationTimestamp: catsrc.GetCreationTimestamp(), 538 }, 539 Status: operators.PackageManifestStatus{ 540 CatalogSource: catsrc.GetName(), 541 CatalogSourceDisplayName: catsrc.Spec.DisplayName, 542 CatalogSourcePublisher: catsrc.Spec.Publisher, 543 CatalogSourceNamespace: catsrc.GetNamespace(), 544 PackageName: pkg.Name, 545 DefaultChannel: pkg.GetDefaultChannelName(), 546 Deprecation: getOperatorDeprecation(pkg.Deprecation), 547 }, 548 } 549 550 var ( 551 providerSet bool 552 defaultElided bool 553 defaultCsv *operatorsv1alpha1.ClusterServiceVersion 554 ) 555 for _, pkgChannel := range pkgChannels { 556 // TODO: replace with non-deprecated API (e.g. client.GetBundle()) 557 // nolint:staticcheck 558 bundle, err := client.GetBundleForChannel(ctx, &api.GetBundleInChannelRequest{PkgName: pkg.GetName(), ChannelName: pkgChannel.GetName()}) 559 if err != nil { 560 logger.WithError(err).WithField("channel", pkgChannel.GetName()).Warn("error getting bundle, eliding channel") 561 defaultElided = defaultElided || pkgChannel.Name == manifest.Status.DefaultChannel 562 continue 563 } 564 565 csv := operatorsv1alpha1.ClusterServiceVersion{} 566 err = json.Unmarshal([]byte(bundle.GetCsvJson()), &csv) 567 if err != nil { 568 logger.WithError(err).WithField("channel", pkgChannel.GetName()).Warn("error unmarshaling csv, eliding channel") 569 defaultElided = defaultElided || pkgChannel.Name == manifest.Status.DefaultChannel 570 continue 571 } 572 if defaultCsv == nil || pkgChannel.GetName() == manifest.Status.DefaultChannel { 573 defaultCsv = &csv 574 } 575 manifest.Status.Channels = append(manifest.Status.Channels, operators.PackageChannel{ 576 Name: pkgChannel.GetName(), 577 CurrentCSV: csv.GetName(), 578 CurrentCSVDesc: operators.CreateCSVDescription(&csv, bundle.GetCsvJson()), 579 Entries: entriesByChannel[pkgChannel.GetName()], 580 Deprecation: getOperatorDeprecation(pkgChannel.Deprecation), 581 }) 582 583 if manifest.Status.DefaultChannel != "" && pkgChannel.GetName() == manifest.Status.DefaultChannel || !providerSet { 584 manifest.Status.Provider = operators.AppLink{ 585 Name: csv.Spec.Provider.Name, 586 URL: csv.Spec.Provider.URL, 587 } 588 manifest.ObjectMeta.Labels["provider"] = manifest.Status.Provider.Name 589 manifest.ObjectMeta.Labels["provider-url"] = manifest.Status.Provider.URL 590 providerSet = true 591 } 592 } 593 594 if len(manifest.Status.Channels) == 0 { 595 return nil, fmt.Errorf("packagemanifest has no valid channels") 596 } 597 598 if defaultElided { 599 logger.Warn("default channel elided, setting as first in packagemanifest") 600 manifest.Status.DefaultChannel = manifest.Status.Channels[0].Name 601 } 602 manifestLabels := manifest.GetLabels() 603 for k, v := range defaultCsv.GetLabels() { 604 manifestLabels[k] = v 605 } 606 setDefaultOsArchLabels(manifestLabels) 607 manifest.SetLabels(manifestLabels) 608 return manifest, nil 609 }