github.com/kyma-project/kyma/components/asset-store-controller-manager@v0.0.0-20191203152857-3792b5df17c5/internal/controllers/bucket_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 deleteBucketFinalizerName = "deletebucket.finalizers.assetstore.kyma-project.io"
    23  
    24  // BucketReconciler reconciles a Bucket object
    25  type BucketReconciler 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 BucketConfig struct {
    39  	MaxConcurrentReconciles int           `envconfig:"default=1"`
    40  	RelistInterval          time.Duration `envconfig:"default=30s"`
    41  	ExternalEndpoint        string        `envconfig:"-"`
    42  }
    43  
    44  func NewBucket(config BucketConfig, log logr.Logger, di *Container) *BucketReconciler {
    45  	deleteFinalizer := finalizer.New(deleteBucketFinalizerName)
    46  
    47  	return &BucketReconciler{
    48  		Client:                  di.Manager.GetClient(),
    49  		cacheSynchronizer:       di.Manager.GetCache().WaitForCacheSync,
    50  		Log:                     log,
    51  		recorder:                di.Manager.GetEventRecorderFor("bucket-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 Bucket object and makes changes based on the state read
    61  // +kubebuilder:rbac:groups=assetstore.kyma-project.io,resources=buckets,verbs=get;list;watch;create;update;patch;delete
    62  // +kubebuilder:rbac:groups=assetstore.kyma-project.io,resources=buckets/status,verbs=get;update;patch
    63  // +kubebuilder:rbac:groups="",resources=events,verbs=create;patch
    64  
    65  func (r *BucketReconciler) 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.Bucket{}
    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(), "namespace", instance.GetNamespace())
    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 *BucketReconciler) appendFinalizer(ctx context.Context, namespacedName types.NamespacedName) error {
   107  	updateFnc := func(instance *assetstorev1alpha2.Bucket) 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 *BucketReconciler) removeFinalizer(ctx context.Context, namespacedName types.NamespacedName) error {
   121  	updateFnc := func(instance *assetstorev1alpha2.Bucket) 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 *BucketReconciler) updateStatus(ctx context.Context, namespacedName types.NamespacedName, commonStatus *assetstorev1alpha2.CommonBucketStatus) error {
   136  	updateFnc := func(instance *assetstorev1alpha2.Bucket) 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 *BucketReconciler) isStatusUnchanged(instance *assetstorev1alpha2.Bucket, 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 *BucketReconciler) update(ctx context.Context, namespacedName types.NamespacedName, updateFnc func(instance *assetstorev1alpha2.Bucket) error) error {
   160  	err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
   161  		instance := &assetstorev1alpha2.Bucket{}
   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 *BucketReconciler) SetupWithManager(mgr ctrl.Manager) error {
   183  	return ctrl.NewControllerManagedBy(mgr).
   184  		For(&assetstorev1alpha2.Bucket{}).
   185  		WithOptions(controller.Options{
   186  			MaxConcurrentReconciles: r.maxConcurrentReconciles,
   187  		}).
   188  		Complete(r)
   189  }