Skip to content

Commit 1749fb1

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 1749fb1

File tree

4 files changed

+84
-94
lines changed

4 files changed

+84
-94
lines changed

cmd/manager/main.go

+45-29
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"
@@ -38,13 +38,12 @@ import (
3838
"sigs.k8s.io/controller-runtime/pkg/metrics/server"
3939

4040
helmclient "github.com/operator-framework/helm-operator-plugins/pkg/client"
41-
"github.com/operator-framework/rukpak/pkg/finalizer"
4241
registryv1handler "github.com/operator-framework/rukpak/pkg/handler"
4342
"github.com/operator-framework/rukpak/pkg/provisioner/registry"
4443
"github.com/operator-framework/rukpak/pkg/source"
4544
"github.com/operator-framework/rukpak/pkg/storage"
4645

47-
"github.com/operator-framework/operator-controller/api/v1alpha1"
46+
ocv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
4847
"github.com/operator-framework/operator-controller/internal/catalogmetadata/cache"
4948
catalogclient "github.com/operator-framework/operator-controller/internal/catalogmetadata/client"
5049
"github.com/operator-framework/operator-controller/internal/controllers"
@@ -74,14 +73,13 @@ func podNamespace() string {
7473

7574
func main() {
7675
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
76+
metricsAddr string
77+
enableLeaderElection bool
78+
probeAddr string
79+
cachePath string
80+
operatorControllerVersion bool
81+
systemNamespace string
82+
caCert string
8583
)
8684
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
8785
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
@@ -92,7 +90,6 @@ func main() {
9290
flag.StringVar(&cachePath, "cache-path", "/var/cache", "The local directory path used for filesystem based caching")
9391
flag.BoolVar(&operatorControllerVersion, "version", false, "Prints operator-controller version information")
9492
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.")
9693
opts := zap.Options{
9794
Development: true,
9895
}
@@ -114,7 +111,7 @@ func main() {
114111
systemNamespace = podNamespace()
115112
}
116113

117-
dependentRequirement, err := k8slabels.NewRequirement(labels.OwnerKindKey, selection.In, []string{v1alpha1.ClusterExtensionKind})
114+
dependentRequirement, err := k8slabels.NewRequirement(labels.OwnerKindKey, selection.In, []string{ocv1alpha1.ClusterExtensionKind})
118115
if err != nil {
119116
setupLog.Error(err, "unable to create dependent label selector for cache")
120117
os.Exit(1)
@@ -130,7 +127,7 @@ func main() {
130127
LeaderElectionID: "9c4404e7.operatorframework.io",
131128
Cache: crcache.Options{
132129
ByObject: map[client.Object]crcache.ByObject{
133-
&v1alpha1.ClusterExtension{}: {},
130+
&ocv1alpha1.ClusterExtension{}: {},
134131
},
135132
DefaultNamespaces: map[string]crcache.Config{
136133
systemNamespace: {},
@@ -162,9 +159,14 @@ func main() {
162159
cl := mgr.GetClient()
163160
catalogClient := catalogclient.New(cl, cache.NewFilesystemCache(cachePath, httpClient))
164161

165-
cfgGetter, err := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(), helmclient.StorageNamespaceMapper(func(o client.Object) (string, error) {
166-
return systemNamespace, nil
167-
}))
162+
installNamespaceMapper := helmclient.ObjectToStringMapper(func(obj client.Object) (string, error) {
163+
ext := obj.(*ocv1alpha1.ClusterExtension)
164+
return ext.Spec.InstallNamespace, nil
165+
})
166+
cfgGetter, err := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(),
167+
helmclient.StorageNamespaceMapper(installNamespaceMapper),
168+
helmclient.ClientNamespaceMapper(installNamespaceMapper),
169+
)
168170
if err != nil {
169171
setupLog.Error(err, "unable to config for creating helm client")
170172
os.Exit(1)
@@ -176,37 +178,45 @@ func main() {
176178
os.Exit(1)
177179
}
178180

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)
181+
clusterExtensionFinalizers := crfinalizer.NewFinalizers()
182+
unpacker := &source.ImageRegistry{
183+
BaseCachePath: filepath.Join(cachePath, "unpack"),
184+
// TODO: This needs to be derived per extension via ext.Spec.InstallNamespace
185+
AuthNamespace: systemNamespace,
184186
}
185187

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

191199
localStorage := &storage.LocalDirectory{
192-
RootDirectory: provisionerStorageDirectory,
200+
RootDirectory: filepath.Join(cachePath, "bundles"),
193201
URL: url.URL{},
194202
}
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)
203+
if err := clusterExtensionFinalizers.Register(deleteCachedBundleKey, finalizerFunc(func(ctx context.Context, obj client.Object) (crfinalizer.Result, error) {
204+
ext := obj.(*ocv1alpha1.ClusterExtension)
205+
return crfinalizer.Result{}, localStorage.Delete(ctx, ext)
206+
})); err != nil {
207+
setupLog.Error(err, "unable to register finalizer", "finalizerKey", deleteCachedBundleKey)
198208
os.Exit(1)
199209
}
200210

201211
if err = (&controllers.ClusterExtensionReconciler{
202212
Client: cl,
203-
ReleaseNamespace: systemNamespace,
204213
BundleProvider: catalogClient,
205214
ActionClientGetter: acg,
206215
Unpacker: unpacker,
207216
Storage: localStorage,
208217
InstalledBundleGetter: &controllers.DefaultInstalledBundleGetter{ActionClientGetter: acg},
209218
Handler: registryv1handler.HandlerFunc(registry.HandleBundleDeployment),
219+
Finalizers: clusterExtensionFinalizers,
210220
}).SetupWithManager(mgr); err != nil {
211221
setupLog.Error(err, "unable to create controller", "controller", "ClusterExtension")
212222
os.Exit(1)
@@ -229,3 +239,9 @@ func main() {
229239
os.Exit(1)
230240
}
231241
}
242+
243+
type finalizerFunc func(ctx context.Context, obj client.Object) (crfinalizer.Result, error)
244+
245+
func (f finalizerFunc) Finalize(ctx context.Context, obj client.Object) (crfinalizer.Result, error) {
246+
return f(ctx, obj)
247+
}

config/base/rbac/role.yaml

+4-14
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

+29-49
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

+6-2
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)