github.com/kyma-project/kyma/components/asset-store-controller-manager@v0.0.0-20191203152857-3792b5df17c5/internal/controllers/asset_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 deleteAssetFinalizerName = "deleteasset.finalizers.assetstore.kyma-project.io" 25 26 // AssetReconciler reconciles a Asset object 27 type AssetReconciler 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 AssetConfig struct { 44 MaxConcurrentReconciles int `envconfig:"default=1"` 45 RelistInterval time.Duration `envconfig:"default=30s"` 46 } 47 48 func NewAsset(config AssetConfig, log logr.Logger, di *Container) *AssetReconciler { 49 deleteFinalizer := finalizer.New(deleteAssetFinalizerName) 50 51 return &AssetReconciler{ 52 Client: di.Manager.GetClient(), 53 cacheSynchronizer: di.Manager.GetCache().WaitForCacheSync, 54 Log: log, 55 recorder: di.Manager.GetEventRecorderFor("asset-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 Asset object and makes changes based on the state read 67 // +kubebuilder:rbac:groups=assetstore.kyma-project.io,resources=assets,verbs=get;list;watch;create;update;patch;delete 68 // +kubebuilder:rbac:groups=assetstore.kyma-project.io,resources=assets/status,verbs=get;update;patch 69 // +kubebuilder:rbac:groups=assetstore.kyma-project.io,resources=buckets,verbs=get;list;watch 70 // +kubebuilder:rbac:groups=assetstore.kyma-project.io,resources=buckets/status,verbs=get;list 71 // +kubebuilder:rbac:groups="",resources=events,verbs=create;patch 72 73 func (r *AssetReconciler) 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.Asset{} 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(), "namespace", instance.GetNamespace()) 92 commonHandler := asset.New(assetLogger, r.recorder, r.store, r.loader, r.findBucket, 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 *AssetReconciler) appendFinalizer(ctx context.Context, namespacedName types.NamespacedName) error { 115 updateFnc := func(instance *assetstorev1alpha2.Asset) 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 *AssetReconciler) removeFinalizer(ctx context.Context, namespacedName types.NamespacedName) error { 129 updateFnc := func(instance *assetstorev1alpha2.Asset) 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 *AssetReconciler) updateStatus(ctx context.Context, namespacedName types.NamespacedName, commonStatus *assetstorev1alpha2.CommonAssetStatus) error { 144 updateFnc := func(instance *assetstorev1alpha2.Asset) 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 *AssetReconciler) isStatusUnchanged(instance *assetstorev1alpha2.Asset, 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 *AssetReconciler) update(ctx context.Context, namespacedName types.NamespacedName, updateFnc func(instance *assetstorev1alpha2.Asset) error) error { 168 err := retry.RetryOnConflict(retry.DefaultRetry, func() error { 169 instance := &assetstorev1alpha2.Asset{} 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 *AssetReconciler) SetupWithManager(mgr ctrl.Manager) error { 191 return ctrl.NewControllerManagedBy(mgr). 192 For(&assetstorev1alpha2.Asset{}). 193 WithOptions(controller.Options{ 194 MaxConcurrentReconciles: r.maxConcurrentReconciles, 195 }). 196 Complete(r) 197 } 198 199 func (r *AssetReconciler) findBucket(ctx context.Context, namespace, name string) (*assetstorev1alpha2.CommonBucketStatus, bool, error) { 200 instance := &assetstorev1alpha2.Bucket{} 201 202 namespacedName := types.NamespacedName{ 203 Namespace: namespace, 204 Name: name, 205 } 206 207 err := r.Get(ctx, namespacedName, instance) 208 if err != nil && !apiErrors.IsNotFound(err) { 209 return nil, false, err 210 } 211 212 if instance == nil || instance.Status.Phase != assetstorev1alpha2.BucketReady { 213 return nil, false, nil 214 } 215 216 return &instance.Status.CommonBucketStatus, true, nil 217 }