github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/registry/resolver/source_registry.go (about)

     1  package resolver
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/blang/semver/v4"
    11  	operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
    12  	v1alpha1listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1"
    13  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
    14  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
    15  	"github.com/operator-framework/operator-registry/pkg/api"
    16  	"github.com/operator-framework/operator-registry/pkg/client"
    17  	opregistry "github.com/operator-framework/operator-registry/pkg/registry"
    18  	"github.com/sirupsen/logrus"
    19  	"k8s.io/apimachinery/pkg/labels"
    20  )
    21  
    22  // todo: move to pkg/controller/operators/catalog
    23  
    24  type RegistryClientProvider interface {
    25  	ClientsForNamespaces(namespaces ...string) map[registry.CatalogKey]client.Interface
    26  }
    27  
    28  type sourceInvalidator struct {
    29  	m          sync.Mutex
    30  	validChans map[cache.SourceKey]chan struct{}
    31  	ttl        time.Duration // auto-invalidate after this ttl
    32  }
    33  
    34  func (i *sourceInvalidator) Invalidate(key cache.SourceKey) {
    35  	i.m.Lock()
    36  	defer i.m.Unlock()
    37  	if c, ok := i.validChans[key]; ok {
    38  		close(c)
    39  		delete(i.validChans, key)
    40  	}
    41  }
    42  
    43  func (i *sourceInvalidator) GetValidChannel(key cache.SourceKey) <-chan struct{} {
    44  	i.m.Lock()
    45  	defer i.m.Unlock()
    46  
    47  	if c, ok := i.validChans[key]; ok {
    48  		return c
    49  	}
    50  	c := make(chan struct{})
    51  	i.validChans[key] = c
    52  
    53  	go func() {
    54  		<-time.After(i.ttl)
    55  
    56  		// be careful to avoid closing c (and panicking) after
    57  		// it has already been invalidated via Invalidate
    58  		i.m.Lock()
    59  		defer i.m.Unlock()
    60  
    61  		if saved := i.validChans[key]; saved == c {
    62  			close(c)
    63  			delete(i.validChans, key)
    64  		}
    65  	}()
    66  
    67  	return c
    68  }
    69  
    70  type RegistrySourceProvider struct {
    71  	rcp          RegistryClientProvider
    72  	catsrcLister v1alpha1listers.CatalogSourceLister
    73  	logger       logrus.StdLogger
    74  	invalidator  *sourceInvalidator
    75  }
    76  
    77  func SourceProviderFromRegistryClientProvider(rcp RegistryClientProvider, catsrcLister v1alpha1listers.CatalogSourceLister, logger logrus.StdLogger) *RegistrySourceProvider {
    78  	return &RegistrySourceProvider{
    79  		rcp:          rcp,
    80  		logger:       logger,
    81  		catsrcLister: catsrcLister,
    82  		invalidator: &sourceInvalidator{
    83  			validChans: make(map[cache.SourceKey]chan struct{}),
    84  			ttl:        5 * time.Minute,
    85  		},
    86  	}
    87  }
    88  
    89  type errorSource struct {
    90  	error
    91  }
    92  
    93  func (s errorSource) Snapshot(_ context.Context) (*cache.Snapshot, error) {
    94  	return nil, s.error
    95  }
    96  
    97  func (a *RegistrySourceProvider) Sources(namespaces ...string) map[cache.SourceKey]cache.Source {
    98  	result := make(map[cache.SourceKey]cache.Source)
    99  
   100  	cats := []*operatorsv1alpha1.CatalogSource{}
   101  	for _, ns := range namespaces {
   102  		catsInNamespace, err := a.catsrcLister.CatalogSources(ns).List(labels.Everything())
   103  		if err != nil {
   104  			result[cache.SourceKey{Name: "", Namespace: ns}] = errorSource{
   105  				error: fmt.Errorf("failed to list catalogsources for namespace %q: %w", ns, err),
   106  			}
   107  			return result
   108  		}
   109  		cats = append(cats, catsInNamespace...)
   110  	}
   111  
   112  	clients := a.rcp.ClientsForNamespaces(namespaces...)
   113  	for _, cat := range cats {
   114  		key := cache.SourceKey{Name: cat.Name, Namespace: cat.Namespace}
   115  		if client, ok := clients[registry.CatalogKey{Name: cat.Name, Namespace: cat.Namespace}]; ok {
   116  			result[key] = &registrySource{
   117  				key:         key,
   118  				client:      client,
   119  				logger:      a.logger,
   120  				invalidator: a.invalidator,
   121  			}
   122  		} else {
   123  			result[key] = errorSource{
   124  				error: fmt.Errorf("no registry client established for catalogsource %s/%s", cat.Namespace, cat.Name),
   125  			}
   126  		}
   127  	}
   128  	if len(result) == 0 {
   129  		return nil
   130  	}
   131  	return result
   132  }
   133  
   134  func (a *RegistrySourceProvider) Invalidate(key cache.SourceKey) {
   135  	a.invalidator.Invalidate(key)
   136  }
   137  
   138  type registrySource struct {
   139  	key         cache.SourceKey
   140  	client      client.Interface
   141  	logger      logrus.StdLogger
   142  	invalidator *sourceInvalidator
   143  }
   144  
   145  func (s *registrySource) Snapshot(ctx context.Context) (*cache.Snapshot, error) {
   146  	// Fetching default channels this way makes many round trips
   147  	// -- may need to either add a new API to fetch all at once,
   148  	// or embed the information into Bundle.
   149  	packages := make(map[string]*api.Package)
   150  
   151  	it, err := s.client.ListBundles(ctx)
   152  	if err != nil {
   153  		return nil, fmt.Errorf("failed to list bundles: %w", err)
   154  	}
   155  
   156  	var operators []*cache.Entry
   157  	for b := it.Next(); b != nil; b = it.Next() {
   158  		p, ok := packages[b.PackageName]
   159  		if !ok {
   160  			if p, err = s.client.GetPackage(ctx, b.PackageName); err != nil {
   161  				s.logger.Printf("failed to retrieve default channel for bundle, continuing: %v", err)
   162  				continue
   163  			} else {
   164  				packages[b.PackageName] = p
   165  			}
   166  		}
   167  		o, err := newOperatorFromBundle(b, "", s.key, p.DefaultChannelName)
   168  		if err != nil {
   169  			s.logger.Printf("failed to construct operator from bundle, continuing: %v", err)
   170  			continue
   171  		}
   172  		var deprecations *cache.Deprecations
   173  		if p.Deprecation != nil {
   174  			deprecations = &cache.Deprecations{Package: &api.Deprecation{Message: fmt.Sprintf("olm.package/%s: %s", p.Name, p.Deprecation.GetMessage())}}
   175  		}
   176  		for _, c := range p.Channels {
   177  			if c.Name == b.ChannelName && c.Deprecation != nil {
   178  				if deprecations == nil {
   179  					deprecations = &cache.Deprecations{}
   180  				}
   181  				deprecations.Channel = &api.Deprecation{Message: fmt.Sprintf("olm.channel/%s: %s", c.Name, c.Deprecation.GetMessage())}
   182  			}
   183  		}
   184  		if b.Deprecation != nil {
   185  			if deprecations == nil {
   186  				deprecations = &cache.Deprecations{}
   187  			}
   188  			deprecations.Bundle = &api.Deprecation{Message: fmt.Sprintf("olm.bundle/%s: %s", b.CsvName, b.Deprecation.GetMessage())}
   189  		}
   190  		o.SourceInfo.Deprecations = deprecations
   191  		o.ProvidedAPIs = o.ProvidedAPIs.StripPlural()
   192  		o.RequiredAPIs = o.RequiredAPIs.StripPlural()
   193  		o.Replaces = b.Replaces
   194  		EnsurePackageProperty(o, b.PackageName, b.Version)
   195  		operators = append(operators, o)
   196  	}
   197  	if err := it.Error(); err != nil {
   198  		return nil, fmt.Errorf("error encountered while listing bundles: %w", err)
   199  	}
   200  
   201  	return &cache.Snapshot{
   202  		Entries: operators,
   203  		Valid:   s.invalidator.GetValidChannel(s.key),
   204  	}, nil
   205  }
   206  
   207  func EnsurePackageProperty(o *cache.Entry, name, version string) {
   208  	for _, p := range o.Properties {
   209  		if p.Type == opregistry.PackageType {
   210  			return
   211  		}
   212  	}
   213  	prop := opregistry.PackageProperty{
   214  		PackageName: name,
   215  		Version:     version,
   216  	}
   217  	bytes, err := json.Marshal(prop)
   218  	if err != nil {
   219  		return
   220  	}
   221  	o.Properties = append(o.Properties, &api.Property{
   222  		Type:  opregistry.PackageType,
   223  		Value: string(bytes),
   224  	})
   225  }
   226  
   227  func newOperatorFromBundle(bundle *api.Bundle, startingCSV string, sourceKey cache.SourceKey, defaultChannel string) (*cache.Entry, error) {
   228  	parsedVersion, err := semver.ParseTolerant(bundle.Version)
   229  	version := &parsedVersion
   230  	if err != nil {
   231  		version = nil
   232  	}
   233  	provided := cache.APISet{}
   234  	for _, gvk := range bundle.ProvidedApis {
   235  		provided[opregistry.APIKey{Plural: gvk.Plural, Group: gvk.Group, Kind: gvk.Kind, Version: gvk.Version}] = struct{}{}
   236  	}
   237  	required := cache.APISet{}
   238  	for _, gvk := range bundle.RequiredApis {
   239  		required[opregistry.APIKey{Plural: gvk.Plural, Group: gvk.Group, Kind: gvk.Kind, Version: gvk.Version}] = struct{}{}
   240  	}
   241  	sourceInfo := &cache.OperatorSourceInfo{
   242  		Package:     bundle.PackageName,
   243  		Channel:     bundle.ChannelName,
   244  		StartingCSV: startingCSV,
   245  		Catalog:     sourceKey,
   246  	}
   247  	sourceInfo.DefaultChannel = sourceInfo.Channel == defaultChannel
   248  
   249  	// legacy support - if the api doesn't contain properties/dependencies, build them from required/provided apis
   250  	properties := bundle.Properties
   251  	if len(properties) == 0 {
   252  		properties, err = providedAPIsToProperties(provided)
   253  		if err != nil {
   254  			return nil, err
   255  		}
   256  	}
   257  	if len(bundle.Dependencies) > 0 {
   258  		ps, err := legacyDependenciesToProperties(bundle.Dependencies)
   259  		if err != nil {
   260  			return nil, fmt.Errorf("failed to translate legacy dependencies to properties: %w", err)
   261  		}
   262  		properties = append(properties, ps...)
   263  	} else {
   264  		ps, err := requiredAPIsToProperties(required)
   265  		if err != nil {
   266  			return nil, err
   267  		}
   268  		properties = append(properties, ps...)
   269  	}
   270  
   271  	o := &cache.Entry{
   272  		Name:         bundle.CsvName,
   273  		Replaces:     bundle.Replaces,
   274  		Version:      version,
   275  		ProvidedAPIs: provided,
   276  		RequiredAPIs: required,
   277  		SourceInfo:   sourceInfo,
   278  		Properties:   properties,
   279  		Skips:        bundle.Skips,
   280  		BundlePath:   bundle.BundlePath,
   281  	}
   282  
   283  	if r, err := semver.ParseRange(bundle.SkipRange); err == nil {
   284  		o.SkipRange = r
   285  	}
   286  
   287  	if o.BundlePath == "" {
   288  		// This bundle's content is embedded within the Bundle
   289  		// proto message, not specified via image reference.
   290  		o.Bundle = bundle
   291  	}
   292  
   293  	return o, nil
   294  }
   295  
   296  func legacyDependenciesToProperties(dependencies []*api.Dependency) ([]*api.Property, error) {
   297  	var result []*api.Property
   298  	for _, dependency := range dependencies {
   299  		switch dependency.Type {
   300  		case "olm.gvk":
   301  			type gvk struct {
   302  				Group   string `json:"group"`
   303  				Version string `json:"version"`
   304  				Kind    string `json:"kind"`
   305  			}
   306  			var vfrom gvk
   307  			if err := json.Unmarshal([]byte(dependency.Value), &vfrom); err != nil {
   308  				return nil, fmt.Errorf("failed to unmarshal legacy 'olm.gvk' dependency: %w", err)
   309  			}
   310  			vto := gvk{
   311  				Group:   vfrom.Group,
   312  				Version: vfrom.Version,
   313  				Kind:    vfrom.Kind,
   314  			}
   315  			vb, err := json.Marshal(&vto)
   316  			if err != nil {
   317  				return nil, fmt.Errorf("unexpected error marshaling generated 'olm.package.required' property: %w", err)
   318  			}
   319  			result = append(result, &api.Property{
   320  				Type:  "olm.gvk.required",
   321  				Value: string(vb),
   322  			})
   323  		case "olm.package":
   324  			var vfrom struct {
   325  				PackageName  string `json:"packageName"`
   326  				VersionRange string `json:"version"`
   327  			}
   328  			if err := json.Unmarshal([]byte(dependency.Value), &vfrom); err != nil {
   329  				return nil, fmt.Errorf("failed to unmarshal legacy 'olm.package' dependency: %w", err)
   330  			}
   331  			vto := struct {
   332  				PackageName  string `json:"packageName"`
   333  				VersionRange string `json:"versionRange"`
   334  			}{
   335  				PackageName:  vfrom.PackageName,
   336  				VersionRange: vfrom.VersionRange,
   337  			}
   338  			vb, err := json.Marshal(&vto)
   339  			if err != nil {
   340  				return nil, fmt.Errorf("unexpected error marshaling generated 'olm.package.required' property: %w", err)
   341  			}
   342  			result = append(result, &api.Property{
   343  				Type:  "olm.package.required",
   344  				Value: string(vb),
   345  			})
   346  		case "olm.label":
   347  			result = append(result, &api.Property{
   348  				Type:  "olm.label.required",
   349  				Value: dependency.Value,
   350  			})
   351  		case "olm.constraint":
   352  			result = append(result, &api.Property{
   353  				Type:  "olm.constraint",
   354  				Value: dependency.Value,
   355  			})
   356  		}
   357  	}
   358  	return result, nil
   359  }