func ReconcileResource()

in pkg/controller/common/reconciler/reconciler.go [72:189]


func ReconcileResource(params Params) error {
	err := params.CheckNilValues()
	if err != nil {
		return err
	}

	gvk, err := apiutil.GVKForObject(params.Expected, scheme.Scheme)
	if err != nil {
		return err
	}

	if params.Owner != nil {
		if err := controllerutil.SetControllerReference(params.Owner, params.Expected, scheme.Scheme); err != nil {
			return err
		}
	}

	kind := gvk.Kind
	namespace := params.Expected.GetNamespace()
	name := params.Expected.GetName()
	log := ulog.FromContext(params.Context).WithValues("kind", kind, "namespace", namespace, "name", name)
	create := func() error {
		log.Info("Creating resource")
		if params.PreCreate != nil {
			if err := params.PreCreate(); err != nil {
				return err
			}
		}

		// Copy the content of params.Expected into params.Reconciled.
		// Unfortunately it's not straightforward to change the value of an interface underlying pointer,
		// so we need a small bit of reflection here.
		// This will panic if params.Expected and params.Reconciled don't have the same underlying type.
		expectedCopyValue := reflect.ValueOf(params.Expected.DeepCopyObject()).Elem()
		reflect.ValueOf(params.Reconciled).Elem().Set(expectedCopyValue)
		// Create the object, which modifies params.Reconciled in-place
		err = params.Client.Create(params.Context, params.Reconciled)
		if err != nil {
			return err
		}
		log.Info("Created resource successfully", resourceVersionKey, params.Reconciled.GetResourceVersion())
		return nil
	}

	// Check if already exists
	err = params.Client.Get(params.Context, types.NamespacedName{Name: name, Namespace: namespace}, params.Reconciled)
	if err != nil && apierrors.IsNotFound(err) {
		return create()
	} else if err != nil {
		log.Error(err, fmt.Sprintf("Generic GET for %s %s/%s failed with error", kind, namespace, name))
		return fmt.Errorf("failed to get %s %s/%s: %w", kind, namespace, name, err)
	}

	if params.NeedsRecreate != nil && params.NeedsRecreate() {
		log.Info("Deleting resource as it cannot be updated, it will be recreated")
		reconciledMeta, err := meta.Accessor(params.Reconciled)
		if err != nil {
			return err
		}
		// Using a precondition here to make sure we delete the version of the resource we intend to delete and
		// to avoid accidentally deleting a resource already recreated for example
		uidToDelete := reconciledMeta.GetUID()
		resourceVersionToDelete := reconciledMeta.GetResourceVersion()
		opt := client.Preconditions{
			UID:             &uidToDelete,
			ResourceVersion: &resourceVersionToDelete,
		}

		err = params.Client.Delete(params.Context, params.Expected, opt)
		if err != nil && !apierrors.IsNotFound(err) {
			return fmt.Errorf("failed to delete %s %s/%s: %w", kind, namespace, name, err)
		}
		log.Info("Deleted resource successfully")
		return create()
	}

	//nolint:nestif
	// Update if needed
	if params.NeedsUpdate() {
		log.Info("Updating resource")
		if params.PreUpdate != nil {
			if err := params.PreUpdate(); err != nil {
				return err
			}
		}
		reconciledMeta, err := meta.Accessor(params.Reconciled)
		if err != nil {
			return err
		}

		// retain the resource version to avoid unconditional updates
		resourceVersion := reconciledMeta.GetResourceVersion()
		params.UpdateReconciled()
		// and set the resource version back into the resource to indicate the state we are basing the update off of
		reconciledMeta.SetResourceVersion(resourceVersion)
		// also keep the owner references up to date
		expectedMeta, err := meta.Accessor(params.Expected)
		if err != nil {
			return err
		}
		expectedOwners := expectedMeta.GetOwnerReferences()
		if expectedOwners != nil {
			// we can safely assume we have just one reference here given that it was created just above
			// but we don't want to replace wholesale in case a user has set an additional reference
			k8s.OverrideControllerReference(reconciledMeta, expectedOwners[0])
		}

		err = params.Client.Update(params.Context, params.Reconciled)
		if err != nil {
			return err
		}
		if params.PostUpdate != nil {
			params.PostUpdate()
		}
		log.Info("Updated resource successfully", resourceVersionKey, params.Reconciled.GetResourceVersion())
	}
	return nil
}