Skip to content

Commit

Permalink
feat: add common reconciliation functions to cut down on code duplica…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
redhatrises committed Oct 6, 2023
1 parent 265b122 commit a9c78a8
Show file tree
Hide file tree
Showing 3 changed files with 242 additions and 0 deletions.
19 changes: 19 additions & 0 deletions api/falcon/v1alpha1/conditions.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package v1alpha1

import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

const (
// Following strings are condition types

Expand All @@ -25,6 +27,23 @@ const (
ReasonSucceeded string = "Succeeded"
ReasonUpdateSucceeded string = "UpdateSucceeded"
ReasonUpdateFailed string = "UpdateFailed"
ReasonDeleteSucceeded string = "DeleteSucceeded"
ReasonDeleteFailed string = "DeleteFailed"
ReasonFailed string = "Failed"
ReasonDiscovered string = "Discovered"
)

// FalconAdmissionStatus defines the observed state of FalconAdmission
type FalconCRStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file

// Version of the CrowdStrike Falcon Sensor
Sensor *string `json:"sensor,omitempty"`

// Version of the CrowdStrike Falcon Operator
Version string `json:"version,omitempty"`

// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
}
199 changes: 199 additions & 0 deletions internal/controller/common/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,188 @@ package common
import (
"context"
"fmt"
"strings"

"github.com/crowdstrike/falcon-operator/api/falcon/v1alpha1"
falconv1alpha1 "github.com/crowdstrike/falcon-operator/api/falcon/v1alpha1"
"github.com/go-logr/logr"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)

func Create(r client.Client, sch *runtime.Scheme, ctx context.Context, req ctrl.Request, log logr.Logger, falconObject client.Object, falconStatus *v1alpha1.FalconCRStatus, obj runtime.Object) error {
switch o := obj.(type) {
case client.Object:
name := o.GetName()
namespace := o.GetNamespace()
gvk := o.GetObjectKind().GroupVersionKind()
fgvk := falconObject.GetObjectKind().GroupVersionKind()
condType := fmt.Sprintf("%sReady", strings.ToUpper(gvk.Kind[:1])+gvk.Kind[1:])

err := ctrl.SetControllerReference(falconObject, o, sch)
if err != nil {
log.Error(err, fmt.Sprintf("unable to set controller reference for %s %s", fgvk.Kind, gvk.Kind))
return err
}

log.Info(logMessage("Creating a new", fgvk.Kind, gvk.Kind), oLogMessage(gvk.Kind, "Name"), name, oLogMessage(gvk.Kind, "Namespace"), namespace)
err = r.Create(ctx, o)
if err != nil {
log.Error(err, logMessage("Failed to create", fgvk.Kind, gvk.Kind), oLogMessage(gvk.Kind, "Name"), name, oLogMessage(gvk.Kind, "Namespace"), namespace)

if err := ConditionsUpdate(r, ctx, req, log, falconObject, falconStatus,
metav1.Condition{
Status: metav1.ConditionFalse,
Reason: falconv1alpha1.ReasonInstallFailed,
Type: condType,
Message: fmt.Sprintf("%s %s creation has failed", fgvk.Kind, gvk.Kind),
ObservedGeneration: falconObject.GetGeneration(),
}); err != nil {
return err
}

return err
}

if err := ConditionsUpdate(r, ctx, req, log, falconObject, falconStatus,
metav1.Condition{
Status: metav1.ConditionTrue,
Reason: falconv1alpha1.ReasonInstallSucceeded,
Type: condType,
Message: fmt.Sprintf("%s %s has been successfully created", fgvk.Kind, gvk.Kind),
ObservedGeneration: falconObject.GetGeneration(),
}); err != nil {
return err
}

return nil
default:
return fmt.Errorf("Unrecognized kubernetes object type: %T", obj)
}
}

func Update(r client.Client, ctx context.Context, req ctrl.Request, log logr.Logger, falconObject client.Object, falconStatus *v1alpha1.FalconCRStatus, obj runtime.Object) error {
switch o := obj.(type) {
case client.Object:
name := o.GetName()
namespace := o.GetNamespace()
gvk := o.GetObjectKind().GroupVersionKind()
fgvk := falconObject.GetObjectKind().GroupVersionKind()
condType := fmt.Sprintf("%sReady", strings.ToUpper(gvk.Kind[:1])+gvk.Kind[1:])

log.Info(logMessage("Updating", fgvk.Kind, gvk.Kind), oLogMessage(gvk.Kind, "Name"), name, oLogMessage(gvk.Kind, "Namespace"), namespace)
err := r.Update(ctx, o)
if err != nil {
log.Error(err, logMessage("Failed to update", fgvk.Kind, gvk.Kind), oLogMessage(gvk.Kind, "Name"), name, oLogMessage(gvk.Kind, "Namespace"), namespace)

if err := ConditionsUpdate(r, ctx, req, log, falconObject, falconStatus,
metav1.Condition{
Status: metav1.ConditionFalse,
Reason: falconv1alpha1.ReasonUpdateFailed,
Type: condType,
Message: fmt.Sprintf("%s %s update has failed", fgvk.Kind, gvk.Kind),
ObservedGeneration: falconObject.GetGeneration(),
}); err != nil {
return err
}

return err
}

if err := ConditionsUpdate(r, ctx, req, log, falconObject, falconStatus,
metav1.Condition{
Status: metav1.ConditionTrue,
Reason: falconv1alpha1.ReasonUpdateSucceeded,
Type: condType,
Message: fmt.Sprintf("%s %s has been successfully updated", fgvk.Kind, gvk.Kind),
ObservedGeneration: falconObject.GetGeneration(),
}); err != nil {
return err
}

return nil
default:
return fmt.Errorf("Unrecognized kubernetes object type: %T", obj)
}
}

func Delete(r client.Client, ctx context.Context, req ctrl.Request, log logr.Logger, falconObject client.Object, falconStatus *v1alpha1.FalconCRStatus, obj runtime.Object) error {
switch o := obj.(type) {
case client.Object:
name := o.GetName()
namespace := o.GetNamespace()
gvk := o.GetObjectKind().GroupVersionKind()
fgvk := falconObject.GetObjectKind().GroupVersionKind()
condType := fmt.Sprintf("%sReady", strings.ToUpper(gvk.Kind[:1])+gvk.Kind[1:])

log.Info(logMessage("Deleting", fgvk.Kind, gvk.Kind), oLogMessage(gvk.Kind, "Name"), name, oLogMessage(gvk.Kind, "Namespace"), namespace)
err := r.Delete(ctx, o)
if err != nil {
log.Error(err, logMessage("Failed to delete", fgvk.Kind, gvk.Kind), oLogMessage(gvk.Kind, "Name"), name, oLogMessage(gvk.Kind, "Namespace"), namespace)

if err := ConditionsUpdate(r, ctx, req, log, falconObject, falconStatus,
metav1.Condition{
Status: metav1.ConditionFalse,
Reason: falconv1alpha1.ReasonDeleteFailed,
Type: condType,
Message: fmt.Sprintf("%s %s deletion has failed", fgvk.Kind, gvk.Kind),
ObservedGeneration: falconObject.GetGeneration(),
}); err != nil {
return err
}

return err
}

if err := ConditionsUpdate(r, ctx, req, log, falconObject, falconStatus,
metav1.Condition{
Status: metav1.ConditionTrue,
Reason: falconv1alpha1.ReasonDeleteSucceeded,
Type: condType,
Message: fmt.Sprintf("%s %s has been successfully deleted", fgvk.Kind, gvk.Kind),
ObservedGeneration: falconObject.GetGeneration(),
}); err != nil {
return err
}

return nil
default:
return fmt.Errorf("Unrecognized kubernetes object type: %T", obj)
}
}

// ConditionsUpdate updates the Falcon Object CR conditions
func ConditionsUpdate(r client.Client, ctx context.Context, req ctrl.Request, log logr.Logger, falconObject client.Object, falconStatus *v1alpha1.FalconCRStatus, falconCondition metav1.Condition) error {
if !meta.IsStatusConditionPresentAndEqual(falconStatus.Conditions, falconCondition.Type, falconCondition.Status) {
fgvk := falconObject.GetObjectKind().GroupVersionKind()

// Re-fetch the memcached Custom Resource before update the status
// so that we have the latest state of the resource on the cluster and we will avoid
// raise the issue "the object has been modified, please apply
// your changes to the latest version and try again" which would re-trigger the reconciliation
err := r.Get(ctx, req.NamespacedName, falconObject)
if err != nil {
log.Error(err, fmt.Sprintf("Failed to re-fetch %s for status update", fgvk.Kind))
return err
}

// The following implementation will update the status
meta.SetStatusCondition(&falconStatus.Conditions, falconCondition)
err = r.Status().Update(ctx, falconObject)
if err != nil {
log.Error(err, fmt.Sprintf("Failed to update %s status", fgvk.Kind))
return err
}
}

return nil
}

func GetReadyPod(r client.Client, ctx context.Context, namespace string, matchingLabels client.MatchingLabels) (*corev1.Pod, error) {
podList := &corev1.PodList{}
listOpts := []client.ListOption{
Expand All @@ -29,3 +206,25 @@ func GetReadyPod(r client.Client, ctx context.Context, namespace string, matchin

return &corev1.Pod{}, fmt.Errorf("No Injector pod found in a Ready state")
}

func GetDeployment(r client.Client, ctx context.Context, namespace string, matchingLabels client.MatchingLabels) (*appsv1.Deployment, error) {
depList := &appsv1.DeploymentList{}
listOpts := []client.ListOption{
client.InNamespace(namespace),
matchingLabels,
}

if err := r.List(ctx, depList, listOpts...); err != nil {
return nil, fmt.Errorf("unable to list deployments: %v", err)
}

return &depList.Items[0], nil
}

func oLogMessage(kind, obj string) string {
return fmt.Sprintf("%s.%s", kind, obj)
}

func logMessage(msg, falconKind, kind string) string {
return fmt.Sprintf("%s %s %s", msg, falconKind, kind)
}
24 changes: 24 additions & 0 deletions internal/controller/common/utils_test.go
Original file line number Diff line number Diff line change
@@ -1 +1,25 @@
package common

import (
"testing"

"github.com/google/go-cmp/cmp"
)

func TestOLogMessage(t *testing.T) {
want := "test.test"
got := oLogMessage("test", "test")

if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("oLogMessage() mismatch (-want +got):\n%s", diff)
}
}

func TestLogMessage(t *testing.T) {
want := "test test test"
got := logMessage("test", "test", "test")

if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("logMessage() mismatch (-want +got):\n%s", diff)
}
}

0 comments on commit a9c78a8

Please sign in to comment.