github.com/kyma-project/kyma/components/asset-store-controller-manager@v0.0.0-20191203152857-3792b5df17c5/internal/controllers/clusterbucket_controller.go (about) 1 package controllers 2 3 import ( 4 "context" 5 "time" 6 7 "github.com/go-logr/logr" 8 "github.com/kyma-project/kyma/components/asset-store-controller-manager/internal/finalizer" 9 "github.com/kyma-project/kyma/components/asset-store-controller-manager/internal/handler/bucket" 10 "github.com/kyma-project/kyma/components/asset-store-controller-manager/internal/store" 11 assetstorev1alpha2 "github.com/kyma-project/kyma/components/asset-store-controller-manager/pkg/apis/assetstore/v1alpha2" 12 "github.com/pkg/errors" 13 apiErrors "k8s.io/apimachinery/pkg/api/errors" 14 "k8s.io/apimachinery/pkg/types" 15 "k8s.io/client-go/tools/record" 16 "k8s.io/client-go/util/retry" 17 ctrl "sigs.k8s.io/controller-runtime" 18 "sigs.k8s.io/controller-runtime/pkg/client" 19 "sigs.k8s.io/controller-runtime/pkg/controller" 20 ) 21 22 const deleteClusterBucketFinalizerName = "deleteclusterbucket.finalizers.assetstore.kyma-project.io" 23 24 // ClusterBucketReconciler reconciles a ClusterBucket object 25 type ClusterBucketReconciler struct { 26 client.Client 27 Log logr.Logger 28 29 cacheSynchronizer func(stop <-chan struct{}) bool 30 recorder record.EventRecorder 31 relistInterval time.Duration 32 finalizer finalizer.Finalizer 33 store store.Store 34 externalEndpoint string 35 maxConcurrentReconciles int 36 } 37 38 type ClusterBucketConfig struct { 39 MaxConcurrentReconciles int `envconfig:"default=1"` 40 RelistInterval time.Duration `envconfig:"default=30s"` 41 ExternalEndpoint string `envconfig:"-"` 42 } 43 44 func NewClusterBucket(config ClusterBucketConfig, log logr.Logger, di *Container) *ClusterBucketReconciler { 45 deleteFinalizer := finalizer.New(deleteClusterBucketFinalizerName) 46 47 return &ClusterBucketReconciler{ 48 Client: di.Manager.GetClient(), 49 cacheSynchronizer: di.Manager.GetCache().WaitForCacheSync, 50 Log: log, 51 recorder: di.Manager.GetEventRecorderFor("clusterbucket-controller"), 52 relistInterval: config.RelistInterval, 53 store: di.Store, 54 finalizer: deleteFinalizer, 55 externalEndpoint: config.ExternalEndpoint, 56 maxConcurrentReconciles: config.MaxConcurrentReconciles, 57 } 58 } 59 60 // Reconcile reads that state of the cluster for a ClusterBucket object and makes changes based on the state read 61 // +kubebuilder:rbac:groups=assetstore.kyma-project.io,resources=clusterbuckets,verbs=get;list;watch;create;update;patch;delete 62 // +kubebuilder:rbac:groups=assetstore.kyma-project.io,resources=clusterbuckets/status,verbs=get;update;patch 63 // +kubebuilder:rbac:groups="",resources=events,verbs=create;patch 64 65 func (r *ClusterBucketReconciler) Reconcile(request ctrl.Request) (ctrl.Result, error) { 66 ctx, cancel := context.WithCancel(context.TODO()) 67 defer cancel() 68 69 if err := r.appendFinalizer(ctx, request.NamespacedName); err != nil { 70 return ctrl.Result{}, errors.Wrap(err, "while appending finalizer") 71 } 72 73 instance := &assetstorev1alpha2.ClusterBucket{} 74 err := r.Get(ctx, request.NamespacedName, instance) 75 if err != nil { 76 if apiErrors.IsNotFound(err) { 77 return ctrl.Result{}, nil 78 } 79 // Error reading the object - requeue the request. 80 return ctrl.Result{}, err 81 } 82 83 bucketLogger := r.Log.WithValues("kind", instance.GetObjectKind().GroupVersionKind().Kind, "name", instance.GetName()) 84 commonHandler := bucket.New(bucketLogger, r.recorder, r.store, r.externalEndpoint, r.relistInterval) 85 commonStatus, err := commonHandler.Do(ctx, time.Now(), instance, instance.Spec.CommonBucketSpec, instance.Status.CommonBucketStatus) 86 if updateErr := r.updateStatus(ctx, request.NamespacedName, commonStatus); updateErr != nil { 87 finalErr := updateErr 88 if err != nil { 89 finalErr = errors.Wrapf(err, "along with update error %s", updateErr.Error()) 90 } 91 return ctrl.Result{}, finalErr 92 } 93 if err != nil { 94 return ctrl.Result{}, err 95 } 96 97 if err := r.removeFinalizer(ctx, request.NamespacedName); err != nil { 98 return ctrl.Result{}, errors.Wrap(err, "while removing finalizer") 99 } 100 101 return ctrl.Result{ 102 RequeueAfter: r.relistInterval, 103 }, nil 104 } 105 106 func (r *ClusterBucketReconciler) appendFinalizer(ctx context.Context, namespacedName types.NamespacedName) error { 107 updateFnc := func(instance *assetstorev1alpha2.ClusterBucket) error { 108 if !instance.DeletionTimestamp.IsZero() || r.finalizer.IsDefinedIn(instance) { 109 return nil 110 } 111 112 copy := instance.DeepCopy() 113 r.finalizer.AddTo(copy) 114 return r.Update(ctx, copy) 115 } 116 117 return r.update(ctx, namespacedName, updateFnc) 118 } 119 120 func (r *ClusterBucketReconciler) removeFinalizer(ctx context.Context, namespacedName types.NamespacedName) error { 121 updateFnc := func(instance *assetstorev1alpha2.ClusterBucket) error { 122 if instance.DeletionTimestamp.IsZero() { 123 return nil 124 } 125 126 copy := instance.DeepCopy() 127 r.finalizer.DeleteFrom(copy) 128 129 return r.Update(ctx, copy) 130 } 131 132 return r.update(ctx, namespacedName, updateFnc) 133 } 134 135 func (r *ClusterBucketReconciler) updateStatus(ctx context.Context, namespacedName types.NamespacedName, commonStatus *assetstorev1alpha2.CommonBucketStatus) error { 136 updateFnc := func(instance *assetstorev1alpha2.ClusterBucket) error { 137 if r.isStatusUnchanged(instance, commonStatus) { 138 return nil 139 } 140 141 copy := instance.DeepCopy() 142 copy.Status.CommonBucketStatus = *commonStatus 143 144 return r.Status().Update(ctx, copy) 145 } 146 147 return r.update(ctx, namespacedName, updateFnc) 148 } 149 150 func (r *ClusterBucketReconciler) isStatusUnchanged(instance *assetstorev1alpha2.ClusterBucket, newStatus *assetstorev1alpha2.CommonBucketStatus) bool { 151 currentStatus := instance.Status.CommonBucketStatus 152 153 return newStatus == nil || 154 currentStatus.ObservedGeneration == newStatus.ObservedGeneration && 155 currentStatus.Phase == newStatus.Phase && 156 currentStatus.Reason == newStatus.Reason 157 } 158 159 func (r *ClusterBucketReconciler) update(ctx context.Context, namespacedName types.NamespacedName, updateFnc func(instance *assetstorev1alpha2.ClusterBucket) error) error { 160 err := retry.RetryOnConflict(retry.DefaultRetry, func() error { 161 instance := &assetstorev1alpha2.ClusterBucket{} 162 err := r.Get(ctx, namespacedName, instance) 163 if err != nil { 164 if apiErrors.IsNotFound(err) { 165 return nil 166 } 167 // Error reading the object - requeue the request. 168 return err 169 } 170 171 err = updateFnc(instance) 172 if err != nil && apiErrors.IsConflict(err) { 173 r.cacheSynchronizer(ctx.Done()) 174 } 175 176 return err 177 }) 178 179 return err 180 } 181 182 func (r *ClusterBucketReconciler) SetupWithManager(mgr ctrl.Manager) error { 183 return ctrl.NewControllerManagedBy(mgr). 184 For(&assetstorev1alpha2.ClusterBucket{}). 185 WithOptions(controller.Options{ 186 MaxConcurrentReconciles: r.maxConcurrentReconciles, 187 }). 188 Complete(r) 189 }