Skip to content
syncer.go 4.66 KiB
Newer Older
Timothee Gosselin's avatar
WIP
Timothee Gosselin committed
/*
Copyright 2018 Pressinfra SRL.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package syncer

import (
	"context"
	"fmt"

	"github.com/ankitrgadiya/operatorlib/pkg/interfaces"
Timothee Gosselin's avatar
WIP
Timothee Gosselin committed
	"github.com/go-test/deep"
	"github.com/iancoleman/strcase"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
	logf "sigs.k8s.io/controller-runtime/pkg/log"
)

var log = logf.Log.WithName("syncer")

const (
	eventNormal  = "Normal"
	eventWarning = "Warning"
)

func getKey(obj interfaces.Object) (types.NamespacedName, error) {
Timothee Gosselin's avatar
WIP
Timothee Gosselin committed
	key := types.NamespacedName{}

	objMeta, ok := obj.(metav1.Object)
	if !ok {
		return key, fmt.Errorf("%T is not a metav1.Object", obj)
	}

	key.Name = objMeta.GetName()
	key.Namespace = objMeta.GetNamespace()

	return key, nil
}

func basicEventReason(objKindName string, err error) string {
	if err != nil {
		return fmt.Sprintf("%sSyncFailed", strcase.ToCamel(objKindName))
	}

	return fmt.Sprintf("%sSyncSuccessfull", strcase.ToCamel(objKindName))
}

// Sync does the actual syncing and implements the syncer.Inteface Sync method
func Sync(ctx context.Context, s Interface) (SyncResult, error) {
	result := SyncResult{}
	recorder := s.GetRecorder()

Timothee Gosselin's avatar
Timothee Gosselin committed
	s.SetPreviousObject()

Timothee Gosselin's avatar
WIP
Timothee Gosselin committed
	key, err := getKey(s.Object())
	if err != nil {
		return result, err
	}
Timothee Gosselin's avatar
WIP
Timothee Gosselin committed
	result.Operation, err = controllerutil.CreateOrUpdate(ctx, s.GetClient(), s.Object(), Mutate(s))

	previousObject := s.PreviousWithoutSecretData()
Timothee Gosselin's avatar
WIP
Timothee Gosselin committed
	// check deep diff
	diff := deep.Equal(&previousObject, s.ObjectWithoutSecretData())
Timothee Gosselin's avatar
WIP
Timothee Gosselin committed

	// don't pass to user error for owner deletion, just don't create the object
	// nolint: gocritic
	if err == errOwnerDeleted {
		log.Info(string(result.Operation), "key", key, "kind", s.ObjectType(), "error", err)
		err = nil
	} else if err == ErrIgnore {
		log.V(1).Info("syncer skipped", "key", key, "kind", s.ObjectType())
		err = nil
	} else if err != nil {
		result.SetEventData(eventWarning, basicEventReason(s.ObjectName(), err),
Timothee Gosselin's avatar
WIP
Timothee Gosselin committed
			fmt.Sprintf("%s %s failed syncing: %s", s.ObjectType(), key, err))
		log.Error(err, string(result.Operation), "key", key, "kind", s.ObjectType(), "diff", diff)
	} else {
		result.SetEventData(eventNormal, basicEventReason(s.ObjectName(), err),
Timothee Gosselin's avatar
WIP
Timothee Gosselin committed
			fmt.Sprintf("%s %s %s successfully", s.ObjectType(), key, result.Operation))
		log.V(1).Info(string(result.Operation), "key", key, "kind", s.ObjectType(), "diff", diff)
	}

	if recorder != nil && s.ObjectOwner() != nil && result.EventType != "" && result.EventReason != "" && result.EventMessage != "" {
		if err != nil || result.Operation != controllerutil.OperationResultNone {
			recorder.Eventf(s.ObjectOwner(), result.EventType, result.EventReason, result.EventMessage)
		}
	}

	return result, err
}

// WithoutOwner partially implements implements the syncer interface for the case the subject has no owner
type WithoutOwner struct{}

// GetOwner implementation of syncer interface for the case the subject has no owner
func (*WithoutOwner) GetOwner() interfaces.Object {
Timothee Gosselin's avatar
WIP
Timothee Gosselin committed
	return nil
}

// Given an ObjectSyncer, returns a controllerutil.MutateFn which also sets the
// owner reference if the subject has one
func Mutate(s Interface) controllerutil.MutateFn {
	return func() error {
		obj := s.Object()
Timothee Gosselin's avatar
Timothee Gosselin committed

Timothee Gosselin's avatar
WIP
Timothee Gosselin committed
		mutate := s.Mutate()
		err := mutate()
		if err != nil {
			return err
		}

		if s.ObjectOwner() != nil {
			existingMeta, ok := obj.(metav1.Object)
			if !ok {
				return fmt.Errorf("%s is not a metav1.Object", s.ObjectType())
			}

			ownerMeta, ok := s.ObjectOwner().(metav1.Object)
			if !ok {
				return fmt.Errorf("%s is not a metav1.Object", s.ObjectOwnerType())
			}

			// set owner reference only if owner resource is not being deleted, otherwise the owner
			// reference will be reset in case of deleting with cascade=false.
			if ownerMeta.GetDeletionTimestamp().IsZero() {
				err := controllerutil.SetControllerReference(ownerMeta, existingMeta, s.GetScheme())
				if err != nil {
					return err
				}
			} else if ctime := existingMeta.GetCreationTimestamp(); ctime.IsZero() {
				// the owner is deleted, don't recreate the resource if does not exist, because gc
				// will not delete it again because has no owner reference set
				return errOwnerDeleted
			}
		}

		return nil
	}
}