github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/registry/resolver/step_resolver.go (about) 1 //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -o ../../../fakes/fake_resolver.go . StepResolver 2 package resolver 3 4 import ( 5 "context" 6 "fmt" 7 8 "github.com/operator-framework/api/pkg/operators/v1alpha1" 9 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned" 10 v1listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1" 11 v1alpha1listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1" 12 controllerbundle "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/bundle" 13 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache" 14 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/projection" 15 "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister" 16 "github.com/sirupsen/logrus" 17 corev1 "k8s.io/api/core/v1" 18 "k8s.io/apimachinery/pkg/api/errors" 19 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 "k8s.io/apimachinery/pkg/labels" 21 ) 22 23 const ( 24 BundleLookupConditionPacked v1alpha1.BundleLookupConditionType = "BundleLookupNotPersisted" 25 exclusionAnnotation string = "olm.operatorframework.io/exclude-global-namespace-resolution" 26 ) 27 28 // init hooks provides the downstream a way to modify the upstream behavior 29 var initHooks []stepResolverInitHook 30 31 type StepResolver interface { 32 ResolveSteps(namespace string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) 33 } 34 35 type OperatorStepResolver struct { 36 subLister v1alpha1listers.SubscriptionLister 37 csvLister v1alpha1listers.ClusterServiceVersionLister 38 ogLister v1listers.OperatorGroupLister 39 client versioned.Interface 40 globalCatalogNamespace string 41 resolver *Resolver 42 log logrus.FieldLogger 43 } 44 45 var _ StepResolver = &OperatorStepResolver{} 46 47 type catsrcPriorityProvider struct { 48 lister v1alpha1listers.CatalogSourceLister 49 } 50 51 func (pp catsrcPriorityProvider) Priority(key cache.SourceKey) int { 52 catsrc, err := pp.lister.CatalogSources(key.Namespace).Get(key.Name) 53 if err != nil { 54 return 0 55 } 56 return catsrc.Spec.Priority 57 } 58 59 func NewOperatorStepResolver(lister operatorlister.OperatorLister, client versioned.Interface, globalCatalogNamespace string, sourceProvider cache.SourceProvider, log logrus.FieldLogger) *OperatorStepResolver { 60 cacheSourceProvider := &mergedSourceProvider{ 61 sps: []cache.SourceProvider{ 62 sourceProvider, 63 //SourceProviderFromRegistryClientProvider(provider, log), 64 &csvSourceProvider{ 65 csvLister: lister.OperatorsV1alpha1().ClusterServiceVersionLister(), 66 subLister: lister.OperatorsV1alpha1().SubscriptionLister(), 67 ogLister: lister.OperatorsV1().OperatorGroupLister(), 68 logger: log, 69 client: client, 70 }, 71 }, 72 } 73 stepResolver := &OperatorStepResolver{ 74 subLister: lister.OperatorsV1alpha1().SubscriptionLister(), 75 csvLister: lister.OperatorsV1alpha1().ClusterServiceVersionLister(), 76 ogLister: lister.OperatorsV1().OperatorGroupLister(), 77 client: client, 78 globalCatalogNamespace: globalCatalogNamespace, 79 resolver: NewDefaultResolver(cacheSourceProvider, catsrcPriorityProvider{lister: lister.OperatorsV1alpha1().CatalogSourceLister()}, log), 80 log: log, 81 } 82 83 // init hooks can be added to the downstream to 84 // modify resolver behaviour 85 for _, initHook := range initHooks { 86 if err := initHook(stepResolver); err != nil { 87 panic(err) 88 } 89 } 90 return stepResolver 91 } 92 93 func (r *OperatorStepResolver) ResolveSteps(namespace string) ([]*v1alpha1.Step, []v1alpha1.BundleLookup, []*v1alpha1.Subscription, error) { 94 subs, err := r.listSubscriptions(namespace) 95 if err != nil { 96 return nil, nil, nil, err 97 } 98 99 namespaces := []string{namespace} 100 ogs, err := r.ogLister.OperatorGroups(namespace).List(labels.Everything()) 101 if err != nil { 102 return nil, nil, nil, fmt.Errorf("listing operatorgroups in namespace %s: %s", namespace, err) 103 } 104 if len(ogs) != 1 { 105 return nil, nil, nil, fmt.Errorf("expected 1 OperatorGroup in the namespace, found %d", len(ogs)) 106 } 107 og := ogs[0] 108 if val, ok := og.Annotations[exclusionAnnotation]; ok && val == "true" { 109 // Exclusion specified 110 // Ignore the globalNamespace for the purposes of resolution in this namespace 111 r.log.Printf("excluding global catalogs from resolution in namespace %s", namespace) 112 } else { 113 namespaces = append(namespaces, r.globalCatalogNamespace) 114 } 115 operators, err := r.resolver.Resolve(namespaces, subs) 116 if err != nil { 117 return nil, nil, nil, err 118 } 119 120 // if there's no error, we were able to satisfy all constraints in the subscription set, so we calculate what 121 // changes to persist to the cluster and write them out as `steps` 122 steps := []*v1alpha1.Step{} 123 updatedSubs := []*v1alpha1.Subscription{} 124 bundleLookups := []v1alpha1.BundleLookup{} 125 for _, op := range operators { 126 // Find any existing subscriptions that resolve to this operator. 127 existingSubscriptions := make(map[*v1alpha1.Subscription]bool) 128 sourceInfo := *op.SourceInfo 129 for _, sub := range subs { 130 if sub.Spec.Package != sourceInfo.Package { 131 continue 132 } 133 if sub.Spec.Channel != "" && sub.Spec.Channel != sourceInfo.Channel { 134 continue 135 } 136 subCatalogKey := cache.SourceKey{ 137 Name: sub.Spec.CatalogSource, 138 Namespace: sub.Spec.CatalogSourceNamespace, 139 } 140 if !subCatalogKey.Empty() && !subCatalogKey.Equal(sourceInfo.Catalog) { 141 continue 142 } 143 alreadyExists, err := r.hasExistingCurrentCSV(sub) 144 if err != nil { 145 return nil, nil, nil, fmt.Errorf("unable to determine whether subscription %s has a preexisting CSV", sub.GetName()) 146 } 147 existingSubscriptions[sub] = alreadyExists 148 } 149 150 if len(existingSubscriptions) > 0 { 151 upToDate := true 152 for sub, exists := range existingSubscriptions { 153 if !exists || sub.Status.CurrentCSV != op.Name { 154 upToDate = false 155 } 156 } 157 // all matching subscriptions are up to date 158 if upToDate { 159 continue 160 } 161 } 162 163 // add steps for any new bundle 164 if op.Bundle != nil { 165 bundleSteps, err := NewStepsFromBundle(op.Bundle, namespace, op.Replaces, op.SourceInfo.Catalog.Name, op.SourceInfo.Catalog.Namespace) 166 if err != nil { 167 return nil, nil, nil, fmt.Errorf("failed to turn bundle into steps: %s", err.Error()) 168 } 169 steps = append(steps, bundleSteps...) 170 } else { 171 lookup := v1alpha1.BundleLookup{ 172 Path: op.BundlePath, 173 Identifier: op.Name, 174 Replaces: op.Replaces, 175 CatalogSourceRef: &corev1.ObjectReference{ 176 Namespace: op.SourceInfo.Catalog.Namespace, 177 Name: op.SourceInfo.Catalog.Name, 178 }, 179 Conditions: []v1alpha1.BundleLookupCondition{ 180 { 181 Type: BundleLookupConditionPacked, 182 Status: corev1.ConditionTrue, 183 Reason: controllerbundle.NotUnpackedReason, 184 Message: controllerbundle.NotUnpackedMessage, 185 }, 186 { 187 Type: v1alpha1.BundleLookupPending, 188 Status: corev1.ConditionTrue, 189 Reason: controllerbundle.JobNotStartedReason, 190 Message: controllerbundle.JobNotStartedMessage, 191 }, 192 }, 193 } 194 anno, err := projection.PropertiesAnnotationFromPropertyList(op.Properties) 195 if err != nil { 196 return nil, nil, nil, fmt.Errorf("failed to serialize operator properties for %q: %w", op.Name, err) 197 } 198 lookup.Properties = anno 199 bundleLookups = append(bundleLookups, lookup) 200 } 201 202 if len(existingSubscriptions) == 0 { 203 // explicitly track the resolved CSV as the starting CSV on the resolved subscriptions 204 op.SourceInfo.StartingCSV = op.Name 205 subStep, err := NewSubscriptionStepResource(namespace, *op.SourceInfo) 206 if err != nil { 207 return nil, nil, nil, err 208 } 209 steps = append(steps, &v1alpha1.Step{ 210 Resolving: op.Name, 211 Resource: subStep, 212 Status: v1alpha1.StepStatusUnknown, 213 }) 214 } 215 216 // add steps for subscriptions for bundles that were added through resolution 217 for sub := range existingSubscriptions { 218 if sub.Status.CurrentCSV == op.Name { 219 continue 220 } 221 // update existing subscription status 222 sub.Status.CurrentCSV = op.Name 223 updatedSubs = append(updatedSubs, sub) 224 } 225 } 226 227 // Order Steps 228 steps = v1alpha1.OrderSteps(steps) 229 return steps, bundleLookups, updatedSubs, nil 230 } 231 232 func (r *OperatorStepResolver) hasExistingCurrentCSV(sub *v1alpha1.Subscription) (bool, error) { 233 if sub.Status.CurrentCSV == "" { 234 return false, nil 235 } 236 _, err := r.csvLister.ClusterServiceVersions(sub.GetNamespace()).Get(sub.Status.CurrentCSV) 237 if err == nil { 238 return true, nil 239 } 240 if errors.IsNotFound(err) { 241 return false, nil 242 } 243 return false, err // Can't answer this question right now. 244 } 245 246 func (r *OperatorStepResolver) listSubscriptions(namespace string) ([]*v1alpha1.Subscription, error) { 247 list, err := r.client.OperatorsV1alpha1().Subscriptions(namespace).List(context.TODO(), metav1.ListOptions{}) 248 if err != nil { 249 return nil, err 250 } 251 252 var subs []*v1alpha1.Subscription 253 for i := range list.Items { 254 subs = append(subs, &list.Items[i]) 255 } 256 257 return subs, nil 258 } 259 260 type mergedSourceProvider struct { 261 sps []cache.SourceProvider 262 logger logrus.StdLogger 263 } 264 265 func (msp *mergedSourceProvider) Sources(namespaces ...string) map[cache.SourceKey]cache.Source { 266 result := make(map[cache.SourceKey]cache.Source) 267 for _, sp := range msp.sps { 268 for key, source := range sp.Sources(namespaces...) { 269 if _, ok := result[key]; ok { 270 msp.logger.Printf("warning: duplicate sourcekey: %q\n", key) 271 } 272 result[key] = source 273 } 274 } 275 return result 276 }