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  }