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

     1  package resolver
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strings"
     8  
     9  	"github.com/blang/semver/v4"
    10  	"github.com/operator-framework/api/pkg/operators/v1alpha1"
    11  	operatorv1clientset "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
    12  	v1listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1"
    13  	v1alpha1listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1"
    14  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
    15  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/projection"
    16  	"github.com/operator-framework/operator-registry/pkg/api"
    17  	opregistry "github.com/operator-framework/operator-registry/pkg/registry"
    18  	"github.com/sirupsen/logrus"
    19  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    20  	"k8s.io/apimachinery/pkg/labels"
    21  )
    22  
    23  type csvSourceProvider struct {
    24  	csvLister v1alpha1listers.ClusterServiceVersionLister
    25  	subLister v1alpha1listers.SubscriptionLister
    26  	ogLister  v1listers.OperatorGroupLister
    27  	logger    logrus.StdLogger
    28  	client    operatorv1clientset.Interface
    29  }
    30  
    31  func (csp *csvSourceProvider) Sources(namespaces ...string) map[cache.SourceKey]cache.Source {
    32  	result := make(map[cache.SourceKey]cache.Source)
    33  	for _, namespace := range namespaces {
    34  		result[cache.NewVirtualSourceKey(namespace)] = &csvSource{
    35  			key:       cache.NewVirtualSourceKey(namespace),
    36  			csvLister: csp.csvLister.ClusterServiceVersions(namespace),
    37  			subLister: csp.subLister.Subscriptions(namespace),
    38  			ogLister:  csp.ogLister.OperatorGroups(namespace),
    39  			logger:    csp.logger,
    40  			listSubscriptions: func(ctx context.Context) (*v1alpha1.SubscriptionList, error) {
    41  				return csp.client.OperatorsV1alpha1().Subscriptions(namespace).List(ctx, metav1.ListOptions{})
    42  			},
    43  		}
    44  		break // first ns is assumed to be the target ns, todo: make explicit
    45  	}
    46  	return result
    47  }
    48  
    49  type csvSource struct {
    50  	key       cache.SourceKey
    51  	csvLister v1alpha1listers.ClusterServiceVersionNamespaceLister
    52  	subLister v1alpha1listers.SubscriptionNamespaceLister
    53  	ogLister  v1listers.OperatorGroupNamespaceLister
    54  	logger    logrus.StdLogger
    55  
    56  	listSubscriptions func(context.Context) (*v1alpha1.SubscriptionList, error)
    57  }
    58  
    59  func (s *csvSource) Snapshot(ctx context.Context) (*cache.Snapshot, error) {
    60  	csvs, err := s.csvLister.List(labels.Everything())
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  
    65  	subs, err := s.subLister.List(labels.Everything())
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	failForwardEnabled, err := IsFailForwardEnabled(s.ogLister)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	// build a catalog snapshot of CSVs without subscriptions
    76  	csvSubscriptions := make(map[*v1alpha1.ClusterServiceVersion]*v1alpha1.Subscription)
    77  	for _, sub := range subs {
    78  		for _, csv := range csvs {
    79  			if csv.IsCopied() {
    80  				continue
    81  			}
    82  			if csv.Name == sub.Status.InstalledCSV {
    83  				csvSubscriptions[csv] = sub
    84  				break
    85  			}
    86  		}
    87  	}
    88  
    89  	var csvsMissingProperties []*v1alpha1.ClusterServiceVersion
    90  	var entries []*cache.Entry
    91  	for _, csv := range csvs {
    92  		if csv.IsCopied() {
    93  			continue
    94  		}
    95  
    96  		if cachedSubscription, ok := csvSubscriptions[csv]; !ok || cachedSubscription == nil {
    97  			// we might be in an incoherent state, so let's check with live clients to make sure
    98  			realSubscriptions, err := s.listSubscriptions(ctx)
    99  			if err != nil {
   100  				return nil, fmt.Errorf("failed to list subscriptions: %w", err)
   101  			}
   102  			for _, realSubscription := range realSubscriptions.Items {
   103  				if realSubscription.Status.InstalledCSV == csv.Name {
   104  					// oops, live cluster state is coherent
   105  					return nil, fmt.Errorf("lister caches incoherent for CSV %s/%s - found owning Subscription %s/%s", csv.Namespace, csv.Name, realSubscription.Namespace, realSubscription.Name)
   106  				}
   107  			}
   108  		}
   109  
   110  		if failForwardEnabled {
   111  			replacementChainEndsInFailure, err := isReplacementChainThatEndsInFailure(csv, ReplacementMapping(csvs))
   112  			if err != nil {
   113  				return nil, err
   114  			}
   115  			if csv.Status.Phase == v1alpha1.CSVPhaseReplacing && replacementChainEndsInFailure {
   116  				continue
   117  			}
   118  		}
   119  
   120  		entry, err := newEntryFromV1Alpha1CSV(csv)
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  		entry.SourceInfo = &cache.OperatorSourceInfo{
   125  			Catalog:      s.key,
   126  			Subscription: csvSubscriptions[csv],
   127  		}
   128  
   129  		entries = append(entries, entry)
   130  
   131  		if anno, ok := csv.GetAnnotations()[projection.PropertiesAnnotationKey]; !ok {
   132  			csvsMissingProperties = append(csvsMissingProperties, csv)
   133  			if inferred, err := s.inferProperties(csv, subs); err != nil {
   134  				s.logger.Printf("unable to infer properties for csv %q: %w", csv.Name, err)
   135  			} else {
   136  				entry.Properties = append(entry.Properties, inferred...)
   137  			}
   138  		} else if props, err := projection.PropertyListFromPropertiesAnnotation(anno); err != nil {
   139  			return nil, fmt.Errorf("failed to retrieve properties of csv %q: %w", csv.GetName(), err)
   140  		} else {
   141  			entry.Properties = props
   142  		}
   143  
   144  		// Try to determine source package name from properties and add to SourceInfo.
   145  		for _, p := range entry.Properties {
   146  			if p.Type != opregistry.PackageType {
   147  				continue
   148  			}
   149  			var pp opregistry.PackageProperty
   150  			err := json.Unmarshal([]byte(p.Value), &pp)
   151  			if err != nil {
   152  				s.logger.Printf("failed to unmarshal package property of csv %q: %w", csv.Name, err)
   153  				continue
   154  			}
   155  			entry.SourceInfo.Package = pp.PackageName
   156  		}
   157  	}
   158  
   159  	if len(csvsMissingProperties) > 0 {
   160  		names := make([]string, len(csvsMissingProperties))
   161  		for i, csv := range csvsMissingProperties {
   162  			names[i] = csv.GetName()
   163  		}
   164  		s.logger.Printf("considered csvs without properties annotation during resolution: %v", names)
   165  	}
   166  
   167  	return &cache.Snapshot{
   168  		Entries: entries,
   169  		Valid:   cache.ValidOnce(),
   170  	}, nil
   171  }
   172  
   173  func (s *csvSource) inferProperties(csv *v1alpha1.ClusterServiceVersion, subs []*v1alpha1.Subscription) ([]*api.Property, error) {
   174  	var properties []*api.Property
   175  
   176  	packages := make(map[string]struct{})
   177  	for _, sub := range subs {
   178  		if sub.Status.InstalledCSV != csv.Name {
   179  			continue
   180  		}
   181  		if pkg := sub.Spec.Package; pkg != "" {
   182  			packages[pkg] = struct{}{}
   183  		}
   184  		// An erroneous package inference is possible if a user edits spec.package in a
   185  		// Subscription that already references a ClusterServiceVersion via
   186  		// status.installedCSV, but all recent versions of the catalog operator project
   187  		// properties onto all ClusterServiceVersions they create.
   188  	}
   189  	if l := len(packages); l != 1 {
   190  		s.logger.Printf("could not unambiguously infer package name for %q (found %d distinct package names)", csv.Name, l)
   191  		return properties, nil
   192  	}
   193  	var pkg string
   194  	for pkg = range packages {
   195  		// Assign the single key to pkg.
   196  	}
   197  	var version string // Emit empty string rather than "0.0.0" if .spec.version is zero-valued.
   198  	if !csv.Spec.Version.Version.Equals(semver.Version{}) {
   199  		version = csv.Spec.Version.String()
   200  	}
   201  	pp, err := json.Marshal(opregistry.PackageProperty{
   202  		PackageName: pkg,
   203  		Version:     version,
   204  	})
   205  	if err != nil {
   206  		return nil, fmt.Errorf("failed to marshal inferred package property: %w", err)
   207  	}
   208  	properties = append(properties, &api.Property{
   209  		Type:  opregistry.PackageType,
   210  		Value: string(pp),
   211  	})
   212  
   213  	return properties, nil
   214  }
   215  
   216  func newEntryFromV1Alpha1CSV(csv *v1alpha1.ClusterServiceVersion) (*cache.Entry, error) {
   217  	providedAPIs := cache.EmptyAPISet()
   218  	for _, crdDef := range csv.Spec.CustomResourceDefinitions.Owned {
   219  		parts := strings.SplitN(crdDef.Name, ".", 2)
   220  		if len(parts) < 2 {
   221  			return nil, fmt.Errorf("error parsing crd name: %s", crdDef.Name)
   222  		}
   223  		providedAPIs[opregistry.APIKey{Plural: parts[0], Group: parts[1], Version: crdDef.Version, Kind: crdDef.Kind}] = struct{}{}
   224  	}
   225  	for _, api := range csv.Spec.APIServiceDefinitions.Owned {
   226  		providedAPIs[opregistry.APIKey{Group: api.Group, Version: api.Version, Kind: api.Kind, Plural: api.Name}] = struct{}{}
   227  	}
   228  
   229  	requiredAPIs := cache.EmptyAPISet()
   230  	for _, crdDef := range csv.Spec.CustomResourceDefinitions.Required {
   231  		parts := strings.SplitN(crdDef.Name, ".", 2)
   232  		if len(parts) < 2 {
   233  			return nil, fmt.Errorf("error parsing crd name: %s", crdDef.Name)
   234  		}
   235  		requiredAPIs[opregistry.APIKey{Plural: parts[0], Group: parts[1], Version: crdDef.Version, Kind: crdDef.Kind}] = struct{}{}
   236  	}
   237  	for _, api := range csv.Spec.APIServiceDefinitions.Required {
   238  		requiredAPIs[opregistry.APIKey{Group: api.Group, Version: api.Version, Kind: api.Kind, Plural: api.Name}] = struct{}{}
   239  	}
   240  
   241  	properties, err := providedAPIsToProperties(providedAPIs)
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  	dependencies, err := requiredAPIsToProperties(requiredAPIs)
   246  	if err != nil {
   247  		return nil, err
   248  	}
   249  	properties = append(properties, dependencies...)
   250  
   251  	return &cache.Entry{
   252  		Name:         csv.GetName(),
   253  		Version:      &csv.Spec.Version.Version,
   254  		ProvidedAPIs: providedAPIs,
   255  		RequiredAPIs: requiredAPIs,
   256  		SourceInfo:   &cache.OperatorSourceInfo{},
   257  		Properties:   properties,
   258  	}, nil
   259  }