|
21 | 21 | "context"
|
22 | 22 | "errors"
|
23 | 23 | "fmt"
|
24 |
| - "github.com/operator-framework/operator-controller/internal/httputil" |
25 | 24 | "io"
|
26 |
| - "sigs.k8s.io/controller-runtime/pkg/builder" |
27 |
| - "sigs.k8s.io/controller-runtime/pkg/event" |
28 |
| - "sigs.k8s.io/controller-runtime/pkg/predicate" |
29 |
| - "slices" |
30 |
| - "sort" |
31 | 25 | "strings"
|
32 | 26 | "sync"
|
33 | 27 | "time"
|
34 | 28 |
|
35 |
| - mmsemver "github.com/Masterminds/semver/v3" |
36 |
| - bsemver "github.com/blang/semver/v4" |
37 | 29 | "github.com/go-logr/logr"
|
38 | 30 | "helm.sh/helm/v3/pkg/action"
|
39 | 31 | "helm.sh/helm/v3/pkg/chart"
|
|
50 | 42 | "k8s.io/apimachinery/pkg/util/sets"
|
51 | 43 | apimachyaml "k8s.io/apimachinery/pkg/util/yaml"
|
52 | 44 | ctrl "sigs.k8s.io/controller-runtime"
|
| 45 | + "sigs.k8s.io/controller-runtime/pkg/builder" |
53 | 46 | "sigs.k8s.io/controller-runtime/pkg/cache"
|
54 | 47 | "sigs.k8s.io/controller-runtime/pkg/client"
|
55 | 48 | crcontroller "sigs.k8s.io/controller-runtime/pkg/controller"
|
| 49 | + "sigs.k8s.io/controller-runtime/pkg/event" |
56 | 50 | crfinalizer "sigs.k8s.io/controller-runtime/pkg/finalizer"
|
57 | 51 | crhandler "sigs.k8s.io/controller-runtime/pkg/handler"
|
58 | 52 | "sigs.k8s.io/controller-runtime/pkg/log"
|
| 53 | + "sigs.k8s.io/controller-runtime/pkg/predicate" |
59 | 54 | "sigs.k8s.io/controller-runtime/pkg/reconcile"
|
60 | 55 | "sigs.k8s.io/controller-runtime/pkg/source"
|
61 | 56 |
|
|
64 | 59 | helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client"
|
65 | 60 | "github.com/operator-framework/operator-registry/alpha/declcfg"
|
66 | 61 | "github.com/operator-framework/operator-registry/alpha/property"
|
67 | 62 | rukpakv1alpha2 "github.com/operator-framework/rukpak/api/v1alpha2"
|
68 | 63 | registryv1handler "github.com/operator-framework/rukpak/pkg/handler"
|
69 | 64 | crdupgradesafety "github.com/operator-framework/rukpak/pkg/preflights/crdupgradesafety"
|
70 | 65 | rukpaksource "github.com/operator-framework/rukpak/pkg/source"
|
71 | 66 | "github.com/operator-framework/rukpak/pkg/storage"
|
72 | 67 | "github.com/operator-framework/rukpak/pkg/util"
|
73 | 68 |
|
74 | 69 | ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
|
75 | 70 | "github.com/operator-framework/operator-controller/internal/bundleutil"
|
76 |
| - "github.com/operator-framework/operator-controller/internal/catalogmetadata/filter" |
77 |
| - catsort "github.com/operator-framework/operator-controller/internal/catalogmetadata/sort" |
78 | 71 | "github.com/operator-framework/operator-controller/internal/conditionsets"
|
| 72 | + "github.com/operator-framework/operator-controller/internal/httputil" |
79 | 73 | "github.com/operator-framework/operator-controller/internal/labels"
|
| 74 | + "github.com/operator-framework/operator-controller/internal/resolve" |
80 | 75 | )
|
81 | 76 |
|
82 | 77 | const (
|
83 | 78 | maxHelmReleaseHistory = 10
|
84 | 79 | )
|
85 | 80 |
|
86 |
| -// CatalogPackageProvider provides the way to retrieve package metadata from a catalog. |
87 |
| -type CatalogPackageProvider interface { |
88 |
| - GetPackage(context.Context, *catalogd.ClusterCatalog, string) (*declcfg.DeclarativeConfig, error) |
89 |
| -} |
90 |
| - |
91 | 81 | // ClusterExtensionReconciler reconciles a ClusterExtension object
|
92 | 82 | type ClusterExtensionReconciler struct {
|
93 | 83 | client.Client
|
94 |
| - CatalogPackageProvider CatalogPackageProvider |
95 |
| - Unpacker rukpaksource.Unpacker |
96 |
| - ActionClientGetter helmclient.ActionClientGetter |
97 |
| - Storage storage.Storage |
98 |
| - Handler registryv1handler.Handler |
99 |
| - dynamicWatchMutex sync.RWMutex |
100 |
| - dynamicWatchGVKs sets.Set[schema.GroupVersionKind] |
101 |
| - controller crcontroller.Controller |
102 |
| - cache cache.Cache |
103 |
| - InstalledBundleGetter InstalledBundleGetter |
104 |
| - Finalizers crfinalizer.Finalizers |
105 |
| - CaCertDir string |
106 |
| - Preflights []Preflight |
107 |
| -} |
108 |
| - |
109 |
| -type InstalledBundleGetter interface { |
110 |
| - GetInstalledBundle(ctx context.Context, ext *ocv1alpha1.ClusterExtension) (*ocv1alpha1.BundleMetadata, error) |
| 84 | + Resolver resolve.Resolver |
| 85 | + Unpacker rukpaksource.Unpacker |
| 86 | + ActionClientGetter helmclient.ActionClientGetter |
| 87 | + Storage storage.Storage |
| 88 | + Handler registryv1handler.Handler |
| 89 | + dynamicWatchMutex sync.RWMutex |
| 90 | + dynamicWatchGVKs sets.Set[schema.GroupVersionKind] |
| 91 | + controller crcontroller.Controller |
| 92 | + cache cache.Cache |
| 93 | + Finalizers crfinalizer.Finalizers |
| 94 | + CaCertDir string |
| 95 | + Preflights []Preflight |
111 | 96 | }
|
112 | 97 |
|
113 | 98 | // Preflight is a check that should be run before making any changes to the cluster
|
|
145 | 130 | if err := r.Client.Get(ctx, req.NamespacedName, existingExt); err != nil {
|
146 | 131 | return ctrl.Result{}, client.IgnoreNotFound(err)
|
147 | 132 | }
|
148 |
| - |
149 |
| - var updateError error |
| 133 | + l.V(1).Info("reconciling", "clusterextension", existingExt.Name, "resourceVersion", existingExt.ResourceVersion) |
150 | 134 |
|
151 | 135 | reconciledExt := existingExt.DeepCopy()
|
152 | 136 | res, err := r.reconcile(ctx, reconciledExt)
|
153 |
| - updateError = errors.Join(updateError, err) |
| 137 | + updateError := err |
154 | 138 |
|
155 | 139 | // Do checks before any Update()s, as Update() may modify the resource structure!
|
156 | 140 | updateStatus := !equality.Semantic.DeepEqual(existingExt.Status, reconciledExt.Status)
|
157 | 141 | updateFinalizers := !equality.Semantic.DeepEqual(existingExt.Finalizers, reconciledExt.Finalizers)
|
158 | 142 | unexpectedFieldsChanged := checkForUnexpectedFieldChange(*existingExt, *reconciledExt)
|
159 | 143 |
|
160 | 144 | if updateStatus {
|
161 |
| - err = r.Client.Status().Update(ctx, reconciledExt) |
162 |
| - updateError = errors.Join(updateError, err) |
| 145 | + if err := r.Client.Status().Update(ctx, reconciledExt); err != nil { |
| 146 | + updateError = errors.Join(updateError, fmt.Errorf("error updating status: %v", err)) |
| 147 | + } |
163 | 148 | }
|
164 | 149 |
|
165 | 150 | if unexpectedFieldsChanged {
|
166 | 151 | panic("spec or metadata changed by reconciler")
|
167 | 152 | }
|
168 | 153 |
|
169 | 154 | if updateFinalizers {
|
170 |
| - err = r.Client.Update(ctx, reconciledExt) |
171 |
| - updateError = errors.Join(updateError, err) |
| 155 | + if err := r.Client.Update(ctx, reconciledExt); err != nil { |
| 156 | + updateError = errors.Join(updateError, fmt.Errorf("error updating finalizers: %v", err)) |
| 157 | + } |
172 | 158 | }
|
173 | 159 |
|
174 | 160 | return res, updateError
|
|
250 | 236 | }
|
251 | 237 |
|
252 | 238 | // run resolution
|
253 |
| - resolvedBundle, resolvedBundleVersion, resolvedDeprecation, err := r.resolve(ctx, *ext) |
| 239 | + resolvedBundle, resolvedBundleVersion, resolvedDeprecation, err := r.Resolver.Resolve(ctx, *ext) |
254 | 240 | if err != nil {
|
255 | 241 | // Note: We don't distinguish between resolution-specific errors and generic errors
|
256 | 242 | ext.Status.ResolvedBundle = nil
|
|
423 | 409 | source.Kind(r.cache,
|
424 | 410 | obj,
|
425 | 411 | crhandler.EnqueueRequestForOwner(r.Scheme(), r.RESTMapper(), ext, crhandler.OnlyControllerOwner()),
|
| 412 | + predicate.Funcs{ |
| 413 | + CreateFunc: func(ce event.CreateEvent) bool { return false }, |
| 414 | + UpdateFunc: func(ue event.UpdateEvent) bool { return true }, |
| 415 | + DeleteFunc: func(de event.DeleteEvent) bool { return true }, |
| 416 | + GenericFunc: func(ge event.GenericEvent) bool { return true }, |
| 417 | + }, |
426 | 418 | ),
|
427 | 419 | ); err != nil {
|
428 | 420 | return err
|
|
442 | 434 | return ctrl.Result{}, nil
|
443 | 435 | }
|
444 | 436 |
|
445 |
| -// resolve returns a Bundle from the catalog that needs to get installed on the cluster. |
446 |
| -func (r *ClusterExtensionReconciler) resolve(ctx context.Context, ext ocv1alpha1.ClusterExtension) (*declcfg.Bundle, *bsemver.Version, *declcfg.Deprecation, error) { |
447 |
| - clusterCatalogs := &catalogd.ClusterCatalogList{} |
448 |
| - if err := r.Client.List(ctx, clusterCatalogs); err != nil { |
449 |
| - return nil, nil, nil, fmt.Errorf("error listing cluster catalogs: %w", err) |
450 |
| - } |
451 |
| - |
452 |
| - installedBundle, err := r.InstalledBundleGetter.GetInstalledBundle(ctx, &ext) |
453 |
| - if err != nil { |
454 |
| - return nil, nil, nil, err |
455 |
| - } |
456 |
| - |
457 |
| - packageName := ext.Spec.PackageName |
458 |
| - versionRange := ext.Spec.Version |
459 |
| - channelName := ext.Spec.Channel |
460 |
| - |
461 |
| - var ( |
462 |
| - resolvedBundle *declcfg.Bundle |
463 |
| - resolvedDeprecation *declcfg.Deprecation |
464 |
| - ) |
465 |
| - |
466 |
| - for _, cat := range clusterCatalogs.Items { |
467 |
| - packageFBC, err := r.CatalogPackageProvider.GetPackage(ctx, &cat, ext.Spec.PackageName) |
468 |
| - if err != nil { |
469 |
| - return nil, nil, nil, fmt.Errorf("error catalog metadata: %w", err) |
470 |
| - } |
471 |
| - |
472 |
| - var predicates []filter.Predicate[declcfg.Bundle] |
473 |
| - if channelName != "" { |
474 |
| - channels := slices.DeleteFunc(packageFBC.Channels, func(c declcfg.Channel) bool { |
475 |
| - return channelName != "" && c.Name != channelName |
476 |
| - }) |
477 |
| - predicates = append(predicates, filter.InAnyChannel(channels...)) |
478 |
| - } |
479 |
| - |
480 |
| - if versionRange != "" { |
481 |
| - vRange, err := mmsemver.NewConstraint(versionRange) |
482 |
| - if err != nil { |
483 |
| - return nil, nil, nil, fmt.Errorf("invalid version range %q: %w", versionRange, err) |
484 |
| - } |
485 |
| - predicates = append(predicates, filter.InMastermindsSemverRange(vRange)) |
486 |
| - } |
487 |
| - |
488 |
| - if ext.Spec.UpgradeConstraintPolicy != ocv1alpha1.UpgradeConstraintPolicyIgnore && installedBundle != nil { |
489 |
| - successorPredicate, err := filter.SuccessorsOf(installedBundle, packageFBC.Channels...) |
490 |
| - if err != nil { |
491 |
| - return nil, nil, nil, fmt.Errorf("error finding upgrade edges: %w", err) |
492 |
| - } |
493 |
| - predicates = append(predicates, successorPredicate) |
494 |
| - } |
495 |
| - |
496 |
| - // Apply the predicates to get the candidate bundles |
497 |
| - packageFBC.Bundles = filter.Filter(packageFBC.Bundles, filter.And(predicates...)) |
498 |
| - if len(packageFBC.Bundles) == 0 { |
499 |
| - continue |
500 |
| - } |
501 |
| - |
502 |
| - // If this package has a deprecation, we: |
503 |
| - // 1. Want to sort deprecated bundles to the end of the list |
504 |
| - // 2. Want to keep track of it so that we can return it if we end |
505 |
| - // up resolving a bundle from this package. |
506 |
| - byDeprecation := func(a, b *declcfg.Bundle) int { return 0 } |
507 |
| - var thisDeprecation *declcfg.Deprecation |
508 |
| - if len(packageFBC.Deprecations) > 0 { |
509 |
| - thisDeprecation = &packageFBC.Deprecations[0] |
510 |
| - byDeprecation = catsort.ByDeprecationFunc(*thisDeprecation) |
511 |
| - } |
512 |
| - |
513 |
| - // Sort the bundles by deprecation and then by version |
514 |
| - |
515 |
| - sort.SliceStable(packageFBC.Bundles, func(a, b int) bool { |
516 |
| - if lessDep := byDeprecation(&packageFBC.Bundles[a], &packageFBC.Bundles[b]); lessDep != 0 { |
517 |
| - return lessDep < 0 |
518 |
| - } |
519 |
| - return catsort.ByVersion(&packageFBC.Bundles[a], &packageFBC.Bundles[b]) < 0 |
520 |
| - }) |
521 |
| - |
522 |
| - thisBundle := &packageFBC.Bundles[0] |
523 |
| - if resolvedBundle != nil { |
524 |
| - // Cases where we stick with `resolvedBundle`: |
525 |
| - // 1. If `thisBundle` is deprecated and `resolvedBundle` is not |
526 |
| - // 2. If `thisBundle` and `resolvedBundle` have the same deprecation status AND `resolvedBundle` is a higher version |
527 |
| - cmpDeprecation := byDeprecation(resolvedBundle, thisBundle) |
528 |
| - cmpVersion := catsort.ByVersion(resolvedBundle, thisBundle) |
529 |
| - if cmpDeprecation < 0 || (cmpDeprecation == 0 && cmpVersion < 0) { |
530 |
| - continue |
531 |
| - } |
532 |
| - } |
533 |
| - resolvedBundle = thisBundle |
534 |
| - resolvedDeprecation = thisDeprecation |
535 |
| - } |
536 |
| - |
537 |
| - if resolvedBundle == nil { |
538 |
| - switch { |
539 |
| - case versionRange != "" && channelName != "": |
540 |
| - return nil, nil, nil, fmt.Errorf("no package %q matching version %q in channel %q found", packageName, versionRange, channelName) |
541 |
| - case versionRange != "": |
542 |
| - return nil, nil, nil, fmt.Errorf("no package %q matching version %q found", packageName, versionRange) |
543 |
| - case channelName != "": |
544 |
| - return nil, nil, nil, fmt.Errorf("no package %q in channel %q found", packageName, channelName) |
545 |
| - default: |
546 |
| - return nil, nil, nil, fmt.Errorf("no package %q found", packageName) |
547 |
| - } |
548 |
| - } |
549 |
| - |
550 |
| - resolvedBundleVersion, err := bundleutil.VersionGetter.GetVersion(resolvedBundle) |
551 |
| - if err != nil { |
552 |
| - return nil, nil, nil, fmt.Errorf("error getting bundle version: %w", err) |
553 |
| - } |
554 |
| - return resolvedBundle, resolvedBundleVersion, resolvedDeprecation, nil |
555 |
| -} |
556 |
| - |
557 | 437 | // SetDeprecationStatus will set the appropriate deprecation statuses for a ClusterExtension
|
558 | 438 | // based on the provided bundle
|
559 | 439 | func SetDeprecationStatus(ext *ocv1alpha1.ClusterExtension, bundleName string, deprecation *declcfg.Deprecation) {
|
|
577 | 457 | }
|
578 | 458 | }
|
579 | 459 |
|
| 460 | + // first get ordered deprecation messages that we'll join in the Deprecated condition message |
580 | 461 | var deprecationMessages []string
|
581 | 462 | for _, conditionType := range []string{
|
582 | 463 | ocv1alpha1.TypePackageDeprecated,
|
583 | 464 | ocv1alpha1.TypeChannelDeprecated,
|
584 | 465 | ocv1alpha1.TypeBundleDeprecated,
|
585 | 466 | } {
|
586 |
| - entry, ok := deprecations[conditionType] |
587 |
| - status, reason, message := metav1.ConditionFalse, "", "" |
588 |
| - if ok { |
589 |
| - status, reason, message = metav1.ConditionTrue, ocv1alpha1.ReasonDeprecated, entry.Message |
590 |
| - deprecationMessages = append(deprecationMessages, message) |
| 467 | + if entry, ok := deprecations[conditionType]; ok { |
| 468 | + deprecationMessages = append(deprecationMessages, entry.Message) |
591 | 469 | }
|
592 |
| - apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ |
593 |
| - Type: conditionType, |
594 |
| - Reason: reason, |
595 |
| - Status: status, |
596 |
| - Message: message, |
597 |
| - ObservedGeneration: ext.Generation, |
598 |
| - }) |
599 | 470 | }
|
600 | 471 |
|
601 |
| - status, reason, message := metav1.ConditionFalse, "", "" |
| 472 | + // next, set the Deprecated condition |
| 473 | + status, reason, message := metav1.ConditionFalse, ocv1alpha1.ReasonDeprecated, "" |
602 | 474 | if len(deprecationMessages) > 0 {
|
603 | 475 | status, reason, message = metav1.ConditionTrue, ocv1alpha1.ReasonDeprecated, strings.Join(deprecationMessages, ";")
|
604 | 476 | }
|
|
609 | 481 | Message: message,
|
610 | 482 | ObservedGeneration: ext.Generation,
|
611 | 483 | })
|
| 484 | + |
| 485 | + // finally, set the individual deprecation conditions for package, channel, and bundle |
| 486 | + for _, conditionType := range []string{ |
| 487 | + ocv1alpha1.TypePackageDeprecated, |
| 488 | + ocv1alpha1.TypeChannelDeprecated, |
| 489 | + ocv1alpha1.TypeBundleDeprecated, |
| 490 | + } { |
| 491 | + entry, ok := deprecations[conditionType] |
| 492 | + status, reason, message := metav1.ConditionFalse, ocv1alpha1.ReasonDeprecated, "" |
| 493 | + if ok { |
| 494 | + status, reason, message = metav1.ConditionTrue, ocv1alpha1.ReasonDeprecated, entry.Message |
| 495 | + deprecationMessages = append(deprecationMessages, message) |
| 496 | + } |
| 497 | + apimeta.SetStatusCondition(&ext.Status.Conditions, metav1.Condition{ |
| 498 | + Type: conditionType, |
| 499 | + Reason: reason, |
| 500 | + Status: status, |
| 501 | + Message: message, |
| 502 | + ObservedGeneration: ext.Generation, |
| 503 | + }) |
| 504 | + } |
612 | 505 | }
|
613 | 506 |
|
614 | 507 | func (r *ClusterExtensionReconciler) generateBundleDeploymentForUnpack(ctx context.Context, bundlePath string, ce *ocv1alpha1.ClusterExtension) *rukpakv1alpha2.BundleDeployment {
|
|
0 commit comments