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 }