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