Skip to content

Commit e437d99

Browse files
committed
A mish-mash of improvements and fixes
- remove unnecessary flag - optimize catalog watch handler - fix finalizers (also stop using rukpak-based finalizers and keys) - Add some reminder TODO comments about more improvements (need to convert to issues) Signed-off-by: Joe Lanford <[email protected]>
1 parent f301f55 commit e437d99

File tree

4 files changed

+86
-93
lines changed

4 files changed

+86
-93
lines changed

cmd/manager/main.go

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ limitations under the License.
1717
package main
1818

1919
import (
20-
"crypto/x509"
20+
"context"
2121
"flag"
2222
"fmt"
2323
"net/url"
@@ -44,7 +44,7 @@ import (
4444
"github.com/operator-framework/rukpak/pkg/source"
4545
"github.com/operator-framework/rukpak/pkg/storage"
4646

47-
"github.com/operator-framework/operator-controller/api/v1alpha1"
47+
ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
4848
"github.com/operator-framework/operator-controller/internal/catalogmetadata/cache"
4949
catalogclient "github.com/operator-framework/operator-controller/internal/catalogmetadata/client"
5050
"github.com/operator-framework/operator-controller/internal/controllers"
@@ -74,14 +74,13 @@ func podNamespace() string {
7474

7575
func main() {
7676
var (
77-
metricsAddr string
78-
enableLeaderElection bool
79-
probeAddr string
80-
cachePath string
81-
operatorControllerVersion bool
82-
systemNamespace string
83-
provisionerStorageDirectory string
84-
caCert string
77+
metricsAddr string
78+
enableLeaderElection bool
79+
probeAddr string
80+
cachePath string
81+
operatorControllerVersion bool
82+
systemNamespace string
83+
caCert string
8584
)
8685
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
8786
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
@@ -92,7 +91,6 @@ func main() {
9291
flag.StringVar(&cachePath, "cache-path", "/var/cache", "The local directory path used for filesystem based caching")
9392
flag.BoolVar(&operatorControllerVersion, "version", false, "Prints operator-controller version information")
9493
flag.StringVar(&systemNamespace, "system-namespace", "", "Configures the namespace that gets used to deploy system resources.")
95-
flag.StringVar(&provisionerStorageDirectory, "provisioner-storage-dir", storage.DefaultBundleCacheDir, "The directory that is used to store bundle contents.")
9694
opts := zap.Options{
9795
Development: true,
9896
}
@@ -114,7 +112,7 @@ func main() {
114112
systemNamespace = podNamespace()
115113
}
116114

117-
dependentRequirement, err := k8slabels.NewRequirement(labels.OwnerKindKey, selection.In, []string{v1alpha1.ClusterExtensionKind})
115+
dependentRequirement, err := k8slabels.NewRequirement(labels.OwnerKindKey, selection.In, []string{ocv1alpha1.ClusterExtensionKind})
118116
if err != nil {
119117
setupLog.Error(err, "unable to create dependent label selector for cache")
120118
os.Exit(1)
@@ -130,7 +128,7 @@ func main() {
130128
LeaderElectionID: "9c4404e7.operatorframework.io",
131129
Cache: crcache.Options{
132130
ByObject: map[client.Object]crcache.ByObject{
133-
&v1alpha1.ClusterExtension{}: {},
131+
&ocv1alpha1.ClusterExtension{}: {},
134132
},
135133
DefaultNamespaces: map[string]crcache.Config{
136134
systemNamespace: {},
@@ -162,9 +160,14 @@ func main() {
162160
cl := mgr.GetClient()
163161
catalogClient := catalogclient.New(cl, cache.NewFilesystemCache(cachePath, httpClient))
164162

165-
cfgGetter, err := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(), helmclient.StorageNamespaceMapper(func(o client.Object) (string, error) {
166-
return systemNamespace, nil
167-
}))
163+
installNamespaceMapper := helmclient.ObjectToStringMapper(func(obj client.Object) (string, error) {
164+
ext := obj.(*ocv1alpha1.ClusterExtension)
165+
return ext.Spec.InstallNamespace, nil
166+
})
167+
cfgGetter, err := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(),
168+
helmclient.StorageNamespaceMapper(installNamespaceMapper),
169+
helmclient.ClientNamespaceMapper(installNamespaceMapper),
170+
)
168171
if err != nil {
169172
setupLog.Error(err, "unable to config for creating helm client")
170173
os.Exit(1)
@@ -176,37 +179,47 @@ func main() {
176179
os.Exit(1)
177180
}
178181

179-
bundleFinalizers := crfinalizer.NewFinalizers()
180-
unpacker, err := source.NewDefaultUnpacker(mgr, systemNamespace, filepath.Join(cachePath, "unpack"), (*x509.CertPool)(nil))
181-
if err != nil {
182-
setupLog.Error(err, "unable to create unpacker")
183-
os.Exit(1)
182+
clusterExtensionFinalizers := crfinalizer.NewFinalizers()
183+
unpacker := &source.ImageRegistry{
184+
BaseCachePath: filepath.Join(cachePath, "unpack"),
185+
// TODO: This needs to be derived per extension via ext.Spec.InstallNamespace
186+
AuthNamespace: systemNamespace,
184187
}
185188

186-
if err := bundleFinalizers.Register(finalizer.CleanupUnpackCacheKey, &finalizer.CleanupUnpackCache{Unpacker: unpacker}); err != nil {
187-
setupLog.Error(err, "unable to register finalizer", "finalizerKey", finalizer.CleanupUnpackCacheKey)
189+
domain := ocv1alpha1.GroupVersion.Group
190+
cleanupUnpackCacheKey := fmt.Sprintf("%s/cleanup-unpack-cache", domain)
191+
deleteCachedBundleKey := fmt.Sprintf("%s/delete-cached-bundle", domain)
192+
if err := clusterExtensionFinalizers.Register(cleanupUnpackCacheKey, finalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) {
193+
ext := obj.(*ocv1alpha1.ClusterExtension)
194+
return crfinalizer.Result{}, os.RemoveAll(filepath.Join(unpacker.BaseCachePath, ext.GetName()))
195+
})); err != nil {
196+
setupLog.Error(err, "unable to register finalizer", "finalizerKey", cleanupUnpackCacheKey)
188197
os.Exit(1)
189198
}
190199

191200
localStorage := &storage.LocalDirectory{
192-
RootDirectory: provisionerStorageDirectory,
201+
RootDirectory: filepath.Join(cachePath, "bundles"),
193202
URL: url.URL{},
194203
}
195-
196-
if err := bundleFinalizers.Register(finalizer.DeleteCachedBundleKey, &finalizer.DeleteCachedBundle{Storage: localStorage}); err != nil {
197-
setupLog.Error(err, "unable to register finalizer", "finalizerKey", finalizer.DeleteCachedBundleKey)
204+
if err := clusterExtensionFinalizers.Register(deleteCachedBundleKey, finalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) {
205+
ext := obj.(*ocv1alpha1.ClusterExtension)
206+
return crfinalizer.Result{}, localStorage.Delete(ctx, ext)
207+
})); err != nil {
208+
setupLog.Error(err, "unable to register finalizer", "finalizerKey", deleteCachedBundleKey)
198209
os.Exit(1)
199210
}
200211

212+
if err := clusterExtensionFinalizers.Register(
213+
201214
if err = (&controllers.ClusterExtensionReconciler{
202215
Client: cl,
203-
ReleaseNamespace: systemNamespace,
204216
BundleProvider: catalogClient,
205217
ActionClientGetter: acg,
206218
Unpacker: unpacker,
207219
Storage: localStorage,
208220
InstalledBundleGetter: &controllers.DefaultInstalledBundleGetter{ActionClientGetter: acg},
209221
Handler: registryv1handler.HandlerFunc(registry.HandleBundleDeployment),
222+
Finalizers: clusterExtensionFinalizers,
210223
}).SetupWithManager(mgr); err != nil {
211224
setupLog.Error(err, "unable to create controller", "controller", "ClusterExtension")
212225
os.Exit(1)
@@ -229,3 +242,9 @@ func main() {
229242
os.Exit(1)
230243
}
231244
}
245+
246+
type finalizerFunc func(ctx context.Context, obj client.Object) (crfinalizer.Result, error)
247+
248+
func (f finalizerFunc) Finalize(ctx context.Context, obj client.Object) (crfinalizer.Result, error) {
249+
return f(ctx, obj)
250+
}

config/base/rbac/role.yaml

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,15 @@ rules:
2727
- apiGroups:
2828
- ""
2929
resources:
30-
- configmaps
31-
verbs:
32-
- list
33-
- watch
34-
- apiGroups:
35-
- ""
36-
resources:
37-
- pods
30+
- secrets
3831
verbs:
3932
- create
4033
- delete
34+
- get
4135
- list
36+
- patch
37+
- update
4238
- watch
43-
- apiGroups:
44-
- ""
45-
resources:
46-
- pods/log
47-
verbs:
48-
- get
4939
- apiGroups:
5040
- olm.operatorframework.io
5141
resources:

internal/controllers/clusterextension_controller.go

Lines changed: 29 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ import (
3636
"helm.sh/helm/v3/pkg/postrender"
3737
"helm.sh/helm/v3/pkg/release"
3838
"helm.sh/helm/v3/pkg/storage/driver"
39-
corev1 "k8s.io/api/core/v1"
4039
"k8s.io/apimachinery/pkg/api/equality"
4140
apimeta "k8s.io/apimachinery/pkg/api/meta"
4241
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -48,9 +47,9 @@ import (
4847
ctrl "sigs.k8s.io/controller-runtime"
4948
"sigs.k8s.io/controller-runtime/pkg/cache"
5049
"sigs.k8s.io/controller-runtime/pkg/client"
51-
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
5250
crcontroller "sigs.k8s.io/controller-runtime/pkg/controller"
5351
"sigs.k8s.io/controller-runtime/pkg/event"
52+
crfinalizer "sigs.k8s.io/controller-runtime/pkg/finalizer"
5453
crhandler "sigs.k8s.io/controller-runtime/pkg/handler"
5554
"sigs.k8s.io/controller-runtime/pkg/log"
5655
"sigs.k8s.io/controller-runtime/pkg/predicate"
@@ -80,7 +79,6 @@ import (
8079
// ClusterExtensionReconciler reconciles a ClusterExtension object
8180
type ClusterExtensionReconciler struct {
8281
client.Client
83-
ReleaseNamespace string
8482
BundleProvider BundleProvider
8583
Unpacker rukpaksource.Unpacker
8684
ActionClientGetter helmclient.ActionClientGetter
@@ -91,6 +89,7 @@ type ClusterExtensionReconciler struct {
9189
controller crcontroller.Controller
9290
cache cache.Cache
9391
InstalledBundleGetter InstalledBundleGetter
92+
Finalizers crfinalizer.Finalizers
9493
}
9594

9695
type InstalledBundleGetter interface {
@@ -104,9 +103,7 @@ const (
104103
//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clusterextensions,verbs=get;list;watch
105104
//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clusterextensions/status,verbs=update;patch
106105
//+kubebuilder:rbac:groups=olm.operatorframework.io,resources=clusterextensions/finalizers,verbs=update
107-
//+kubebuilder:rbac:groups=core,resources=pods,verbs=list;watch;create;delete
108-
//+kubebuilder:rbac:groups=core,resources=configmaps,verbs=list;watch
109-
//+kubebuilder:rbac:groups=core,resources=pods/log,verbs=get
106+
//+kubebuilder:rbac:groups=core,resources=secrets,verbs=create;update;patch;delete;get;list;watch
110107
//+kubebuilder:rbac:groups=*,resources=*,verbs=*
111108

112109
//+kubebuilder:rbac:groups=catalogd.operatorframework.io,resources=clustercatalogs,verbs=list;watch
@@ -196,6 +193,25 @@ func checkForUnexpectedFieldChange(a, b ocv1alpha1.ClusterExtension) bool {
196193
*/
197194
//nolint:unparam
198195
func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alpha1.ClusterExtension) (ctrl.Result, error) {
196+
finalizeResult, err := r.Finalizers.Finalize(ctx, ext)
197+
if err != nil {
198+
// TODO: For now, this error handling follows the pattern of other error handling.
199+
// Namely: zero just about everything out, throw our hands up, and return an error.
200+
// This is not ideal, and we should consider a more nuanced approach that resolves
201+
// as much status as possible before returning, or at least keeps previous state if
202+
// it is properly labeled with its observed generation.
203+
ext.Status.ResolvedBundle = nil
204+
ext.Status.InstalledBundle = nil
205+
setResolvedStatusConditionFailed(ext, err.Error())
206+
ensureAllConditionsWithReason(ext, ocv1alpha1.ReasonResolutionFailed, err.Error())
207+
return ctrl.Result{}, err
208+
}
209+
if finalizeResult.Updated || finalizeResult.StatusUpdated {
210+
// On create: make sure the finalizer is applied before we do anything
211+
// On delete: make sure we do nothing after the finalizer is removed
212+
return ctrl.Result{}, nil
213+
}
214+
199215
// run resolution
200216
bundle, err := r.resolve(ctx, *ext)
201217
if err != nil {
@@ -300,7 +316,7 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp
300316

301317
switch state {
302318
case stateNeedsInstall:
303-
rel, err = ac.Install(ext.GetName(), r.ReleaseNamespace, chrt, values, func(install *action.Install) error {
319+
rel, err = ac.Install(ext.GetName(), ext.Spec.InstallNamespace, chrt, values, func(install *action.Install) error {
304320
install.CreateNamespace = false
305321
install.Labels = map[string]string{labels.BundleNameKey: bundle.Name, labels.PackageNameKey: bundle.Package, labels.BundleVersionKey: bundleVersion.String()}
306322
return nil
@@ -310,7 +326,7 @@ func (r *ClusterExtensionReconciler) reconcile(ctx context.Context, ext *ocv1alp
310326
return ctrl.Result{}, err
311327
}
312328
case stateNeedsUpgrade:
313-
rel, err = ac.Upgrade(ext.GetName(), r.ReleaseNamespace, chrt, values, helmclient.AppendUpgradePostRenderer(post))
329+
rel, err = ac.Upgrade(ext.GetName(), ext.Spec.InstallNamespace, chrt, values, helmclient.AppendUpgradePostRenderer(post))
314330
if err != nil {
315331
setInstalledStatusConditionFailed(ext, fmt.Sprintf("%s:%v", ocv1alpha1.ReasonUpgradeFailed, err))
316332
return ctrl.Result{}, err
@@ -574,7 +590,6 @@ func (r *ClusterExtensionReconciler) SetupWithManager(mgr ctrl.Manager) error {
574590
return true
575591
},
576592
}).
577-
Watches(&corev1.Pod{}, mapOwneeToOwnerHandler(mgr.GetClient(), mgr.GetLogger(), &ocv1alpha1.ClusterExtension{})).
578593
Build(r)
579594

580595
if err != nil {
@@ -587,47 +602,12 @@ func (r *ClusterExtensionReconciler) SetupWithManager(mgr ctrl.Manager) error {
587602
return nil
588603
}
589604

590-
func mapOwneeToOwnerHandler(cl client.Client, log logr.Logger, owner client.Object) crhandler.EventHandler {
591-
return crhandler.EnqueueRequestsFromMapFunc(func(ctx context.Context, obj client.Object) []reconcile.Request {
592-
ownerGVK, err := apiutil.GVKForObject(owner, cl.Scheme())
593-
if err != nil {
594-
log.Error(err, "map ownee to owner: lookup GVK for owner")
595-
return nil
596-
}
597-
598-
type ownerInfo struct {
599-
key types.NamespacedName
600-
gvk schema.GroupVersionKind
601-
}
602-
var oi *ownerInfo
603-
604-
for _, ref := range obj.GetOwnerReferences() {
605-
gv, err := schema.ParseGroupVersion(ref.APIVersion)
606-
if err != nil {
607-
log.Error(err, fmt.Sprintf("map ownee to owner: parse ownee's owner reference group version %q", ref.APIVersion))
608-
return nil
609-
}
610-
refGVK := gv.WithKind(ref.Kind)
611-
if refGVK == ownerGVK && ref.Controller != nil && *ref.Controller {
612-
oi = &ownerInfo{
613-
key: types.NamespacedName{Name: ref.Name},
614-
gvk: ownerGVK,
615-
}
616-
break
617-
}
618-
}
619-
if oi == nil {
620-
return nil
621-
}
622-
return []reconcile.Request{{NamespacedName: oi.key}}
623-
})
624-
}
625-
626605
// Generate reconcile requests for all cluster extensions affected by a catalog change
627606
func clusterExtensionRequestsForCatalog(c client.Reader, logger logr.Logger) crhandler.MapFunc {
628607
return func(ctx context.Context, _ client.Object) []reconcile.Request {
629608
// no way of associating an extension to a catalog so create reconcile requests for everything
630-
clusterExtensions := ocv1alpha1.ClusterExtensionList{}
609+
clusterExtensions := metav1.PartialObjectMetadataList{}
610+
clusterExtensions.SetGroupVersionKind(ocv1alpha1.GroupVersion.WithKind("ClusterExtensionList"))
631611
err := c.List(ctx, &clusterExtensions)
632612
if err != nil {
633613
logger.Error(err, "unable to enqueue cluster extensions for catalog reconcile")
@@ -655,16 +635,16 @@ const (
655635
stateError releaseState = "Error"
656636
)
657637

658-
func (r *ClusterExtensionReconciler) getReleaseState(cl helmclient.ActionInterface, obj metav1.Object, chrt *chart.Chart, values chartutil.Values, post *postrenderer) (*release.Release, releaseState, error) {
659-
currentRelease, err := cl.Get(obj.GetName())
638+
func (r *ClusterExtensionReconciler) getReleaseState(cl helmclient.ActionInterface, ext *ocv1alpha1.ClusterExtension, chrt *chart.Chart, values chartutil.Values, post *postrenderer) (*release.Release, releaseState, error) {
639+
currentRelease, err := cl.Get(ext.GetName())
660640
if err != nil && !errors.Is(err, driver.ErrReleaseNotFound) {
661641
return nil, stateError, err
662642
}
663643
if errors.Is(err, driver.ErrReleaseNotFound) {
664644
return nil, stateNeedsInstall, nil
665645
}
666646

667-
desiredRelease, err := cl.Upgrade(obj.GetName(), r.ReleaseNamespace, chrt, values, func(upgrade *action.Upgrade) error {
647+
desiredRelease, err := cl.Upgrade(ext.GetName(), ext.Spec.InstallNamespace, chrt, values, func(upgrade *action.Upgrade) error {
668648
upgrade.DryRun = true
669649
return nil
670650
}, helmclient.AppendUpgradePostRenderer(post))

test/e2e/cluster_extension_install_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ func TestClusterExtensionInstallReResolvesWhenNewCatalog(t *testing.T) {
146146
t.Log("By creating the ClusterExtension resource")
147147
require.NoError(t, c.Create(context.Background(), clusterExtension))
148148

149+
// TODO: this isn't a good precondition because a missing package results in
150+
// exponential backoff retries. So we can't be sure that the re-reconcile is a result of
151+
// the catalog becoming available because it could also be a retry of the initial failed
152+
// resolution.
149153
t.Log("By failing to find ClusterExtension during resolution")
150154
require.EventuallyWithT(t, func(ct *assert.CollectT) {
151155
assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension))
@@ -257,13 +261,13 @@ func TestClusterExtensionForceInstallNonSuccessorVersion(t *testing.T) {
257261
assert.Equal(ct, &ocv1alpha1.BundleMetadata{Name: "prometheus-operator.1.0.0", Version: "1.0.0"}, clusterExtension.Status.ResolvedBundle)
258262
}, pollDuration, pollInterval)
259263

260-
t.Log("It does not allow to upgrade the ClusterExtension to a non-successor version")
264+
t.Log("It allows to upgrade the ClusterExtension to a non-successor version")
261265
t.Log("By updating the ClusterExtension resource to a non-successor version")
262266
// 1.2.0 does not replace/skip/skipRange 1.0.0.
263267
clusterExtension.Spec.Version = "1.2.0"
264268
clusterExtension.Spec.UpgradeConstraintPolicy = ocv1alpha1.UpgradeConstraintPolicyIgnore
265269
require.NoError(t, c.Update(context.Background(), clusterExtension))
266-
t.Log("By eventually reporting an unsatisfiable resolution")
270+
t.Log("By eventually reporting a satisfiable resolution")
267271
require.EventuallyWithT(t, func(ct *assert.CollectT) {
268272
assert.NoError(ct, c.Get(context.Background(), types.NamespacedName{Name: clusterExtension.Name}, clusterExtension))
269273
cond := apimeta.FindStatusCondition(clusterExtension.Status.Conditions, ocv1alpha1.TypeResolved)

0 commit comments

Comments
 (0)