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
}