Skip to content

Commit

Permalink
PoC rework ensure
Browse files Browse the repository at this point in the history
  • Loading branch information
bouskaJ committed Nov 25, 2024
1 parent ad29b1c commit 581f50f
Show file tree
Hide file tree
Showing 10 changed files with 302 additions and 175 deletions.
24 changes: 24 additions & 0 deletions internal/controller/common/action/base_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ import (
"strings"
"time"

"github.com/securesign/operator/internal/apis"
"github.com/securesign/operator/internal/controller/annotations"
"github.com/securesign/operator/internal/controller/constants"
"k8s.io/apimachinery/pkg/api/equality"
apiErrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

Expand Down Expand Up @@ -62,6 +65,7 @@ func (action *BaseAction) StatusUpdate(ctx context.Context, obj client2.Object)
return &Result{Result: reconcile.Result{Requeue: false}}
}

// Deprecated: Use Error function
func (action *BaseAction) Failed(err error) *Result {
action.Logger.Error(err, "error during action execution")
return &Result{
Expand All @@ -70,6 +74,25 @@ func (action *BaseAction) Failed(err error) *Result {
}
}

func (action *BaseAction) Error(ctx context.Context, err error, instance apis.ConditionsAwareObject) *Result {
if errors.Is(err, reconcile.TerminalError(err)) {
instance.SetCondition(metav1.Condition{
Type: constants.Ready,
Status: metav1.ConditionFalse,
Reason: constants.Failure,
Message: err.Error(),
})
if updateErr := action.Client.Status().Update(ctx, instance); updateErr != nil {
err = errors.Join(err, updateErr)
}
}
action.Logger.Error(err, "error during action execution")
return &Result{
Err: err,
}
}

// Deprecated: Use Error function with TerminalError passed as an argument
func (action *BaseAction) FailedWithStatusUpdate(ctx context.Context, err error, instance client2.Object) *Result {
if e := action.Client.Status().Update(ctx, instance); e != nil {
if strings.Contains(err.Error(), OptimisticLockErrorMsg) {
Expand All @@ -96,6 +119,7 @@ func (action *BaseAction) Requeue() *Result {
}
}

// Deprecated: Use kubernetes.CreateOrUpdate function
func (action *BaseAction) Ensure(ctx context.Context, obj client2.Object, opts ...EnsureOption) (bool, error) {
var (
expected client2.Object
Expand Down
27 changes: 27 additions & 0 deletions internal/controller/common/utils/kubernetes/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import (
"strconv"
"strings"

"github.com/securesign/operator/internal/controller/annotations"
apiErrors "k8s.io/apimachinery/pkg/api/errors"
k8sLabels "k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/util/retry"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

v13 "github.com/openshift/api/operator/v1"
"github.com/securesign/operator/internal/controller/common/utils"
Expand Down Expand Up @@ -133,3 +137,26 @@ func FindByLabelSelector(ctx context.Context, c client.Client, list client.Objec

return c.List(ctx, list, client.InNamespace(namespace), listOptions)
}

func CreateOrUpdate[T client.Object](ctx context.Context, cli client.Client, obj T, fn ...func(object T) error) (result controllerutil.OperationResult, err error) {
err = retry.OnError(retry.DefaultRetry, func(err error) bool {
return apiErrors.IsConflict(err) || apiErrors.IsAlreadyExists(err)
}, func() error {
var createUpdateError error
result, createUpdateError = controllerutil.CreateOrUpdate(ctx, cli, obj, func() (fnError error) {
annoStr, find := obj.GetAnnotations()[annotations.PausedReconciliation]
if find {
annoBool, _ := strconv.ParseBool(annoStr)
if annoBool {
return
}
}
for _, f := range fn {
fnError = errors.Join(fnError, f(obj))
}
return
})
return createUpdateError
})
return
}
18 changes: 18 additions & 0 deletions internal/controller/common/utils/kubernetes/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,21 @@ func getDeploymentCondition(status v1.DeploymentStatus, condType v1.DeploymentCo
}
return nil
}

func FindContainerByName(dp *v1.Deployment, containerName string) *corev1.Container {
for i, c := range dp.Spec.Template.Spec.Containers {
if c.Name == containerName {
return &dp.Spec.Template.Spec.Containers[i]
}
}
return nil
}

func FindVolumeByName(dp *v1.Deployment, volumeName string) *corev1.Volume {
for i, v := range dp.Spec.Template.Spec.Volumes {
if v.Name == volumeName {
return &dp.Spec.Template.Spec.Volumes[i]
}
}
return nil
}
45 changes: 45 additions & 0 deletions internal/controller/common/utils/kubernetes/ensure/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package ensure

import (
"slices"

"golang.org/x/exp/maps"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

func managedDeleteFunction(managed []string) func(string, string) bool {
return func(key, _ string) bool {
return slices.Contains(managed, key)
}
}

func Labels[T client.Object](managedLabels []string, labels map[string]string) func(T) error {
return func(obj T) (e error) {
if obj.GetLabels() == nil {
obj.SetLabels(labels)
return
}
maps.DeleteFunc(obj.GetLabels(), managedDeleteFunction(managedLabels))
maps.Copy(obj.GetLabels(), labels)
return
}
}

func Annotations[T client.Object](managedAnnotations []string, annotations map[string]string) func(T) error {
return func(obj T) (e error) {
if obj.GetAnnotations() == nil {
obj.SetAnnotations(annotations)
return
}
maps.DeleteFunc(obj.GetAnnotations(), managedDeleteFunction(managedAnnotations))
maps.Copy(obj.GetAnnotations(), annotations)
return
}
}

func ControllerReference[T client.Object](owner client.Object, cli client.Client) func(controlled T) error {
return func(controlled T) error {
return controllerutil.SetControllerReference(owner, controlled, cli.Scheme())
}
}
13 changes: 13 additions & 0 deletions internal/controller/common/utils/kubernetes/ensure/deployment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ensure

import (
"github.com/securesign/operator/internal/controller/common/utils"
v1 "k8s.io/api/apps/v1"
)

func Proxy() func(*v1.Deployment) error {
return func(dp *v1.Deployment) error {
utils.SetProxyEnvs(dp)
return nil
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package action
package ensure

import (
"context"

"golang.org/x/exp/maps"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

"testing"

"github.com/go-logr/logr"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gstruct"
consolev1 "github.com/openshift/api/console/v1"
Expand All @@ -24,32 +23,11 @@ import (
"k8s.io/apimachinery/pkg/util/intstr"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/record"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

type dumpAction struct {
BaseAction
}

func newDumpAction() Action[*rhtasv1alpha1.Securesign] {
return &dumpAction{}
}

func (d dumpAction) Name() string {
return "dump"
}

func (d dumpAction) CanHandle(_ context.Context, _ *rhtasv1alpha1.Securesign) bool {
return true
}

func (d dumpAction) Handle(_ context.Context, _ *rhtasv1alpha1.Securesign) *Result {
return d.Continue()
}

func TestBaseAction_Ensure(t *testing.T) {
func Test_Ensure(t *testing.T) {
addAnnotations := func(object client.Object, annotations map[string]string) client.Object {
object.SetAnnotations(annotations)
return object
Expand All @@ -62,15 +40,15 @@ func TestBaseAction_Ensure(t *testing.T) {
tests := []struct {
name string
object client.Object
verify func(Gomega, client.WithWatch, bool, error)
verify func(Gomega, client.WithWatch, controllerutil.OperationResult, error)
env env
}{
{
name: "create new object",
object: kubernetes.CreateService("default", "service", "http", 80, 80, map[string]string{}),
verify: func(g Gomega, cli client.WithWatch, result bool, err error) {
verify: func(g Gomega, cli client.WithWatch, result controllerutil.OperationResult, err error) {
g.Expect(err).ToNot(HaveOccurred())
g.Expect(result).To(BeTrue())
g.Expect(result).To(Equal(controllerutil.OperationResultCreated))
nn := types.NamespacedName{
Namespace: "default",
Name: "service",
Expand All @@ -95,9 +73,9 @@ func TestBaseAction_Ensure(t *testing.T) {
object: kubernetes.CreateService("default", "service", "http", 80, 80, map[string]string{
"new": "label",
}),
verify: func(g Gomega, cli client.WithWatch, result bool, err error) {
verify: func(g Gomega, cli client.WithWatch, result controllerutil.OperationResult, err error) {
g.Expect(err).ToNot(HaveOccurred())
g.Expect(result).To(BeTrue())
g.Expect(result).To(Equal(controllerutil.OperationResultUpdated))
nn := types.NamespacedName{
Namespace: "default",
Name: "service",
Expand Down Expand Up @@ -126,9 +104,9 @@ func TestBaseAction_Ensure(t *testing.T) {
object: kubernetes.CreateService("default", "service", "http", 80, 80, map[string]string{
"unmanaged": "value",
}),
verify: func(g Gomega, cli client.WithWatch, result bool, err error) {
verify: func(g Gomega, cli client.WithWatch, result controllerutil.OperationResult, err error) {
g.Expect(err).ToNot(HaveOccurred())
g.Expect(result).To(BeTrue())
g.Expect(result).To(Equal(controllerutil.OperationResultUpdated))
nn := types.NamespacedName{
Namespace: "default",
Name: "service",
Expand Down Expand Up @@ -160,9 +138,9 @@ func TestBaseAction_Ensure(t *testing.T) {
map[string]string{
"new": "annotation",
}),
verify: func(g Gomega, cli client.WithWatch, result bool, err error) {
verify: func(g Gomega, cli client.WithWatch, result controllerutil.OperationResult, err error) {
g.Expect(err).ToNot(HaveOccurred())
g.Expect(result).To(BeTrue())
g.Expect(result).To(Equal(controllerutil.OperationResultUpdated))
nn := types.NamespacedName{
Namespace: "default",
Name: "service",
Expand Down Expand Up @@ -192,9 +170,9 @@ func TestBaseAction_Ensure(t *testing.T) {
},
},
object: addAnnotations(kubernetes.CreateService("default", "service", "http", 80, 80, map[string]string{}), map[string]string{}),
verify: func(g Gomega, cli client.WithWatch, result bool, err error) {
verify: func(g Gomega, cli client.WithWatch, result controllerutil.OperationResult, err error) {
g.Expect(err).ToNot(HaveOccurred())
g.Expect(result).To(BeTrue())
g.Expect(result).To(Equal(controllerutil.OperationResultUpdated))
nn := types.NamespacedName{
Namespace: "default",
Name: "service",
Expand All @@ -218,9 +196,9 @@ func TestBaseAction_Ensure(t *testing.T) {
},
},
object: kubernetes.CreateService("default", "service", "https", 443, 443, map[string]string{}),
verify: func(g Gomega, cli client.WithWatch, result bool, err error) {
verify: func(g Gomega, cli client.WithWatch, result controllerutil.OperationResult, err error) {
g.Expect(err).ToNot(HaveOccurred())
g.Expect(result).To(BeTrue())
g.Expect(result).To(Equal(controllerutil.OperationResultUpdated))
nn := types.NamespacedName{
Namespace: "default",
Name: "service",
Expand Down Expand Up @@ -260,9 +238,9 @@ func TestBaseAction_Ensure(t *testing.T) {
},
},
},
verify: func(g Gomega, cli client.WithWatch, result bool, err error) {
verify: func(g Gomega, cli client.WithWatch, result controllerutil.OperationResult, err error) {
g.Expect(err).ToNot(HaveOccurred())
g.Expect(result).To(BeFalse())
g.Expect(result).To(Equal(controllerutil.OperationResultNone))
nn := types.NamespacedName{
Namespace: "default",
Name: "test",
Expand All @@ -280,9 +258,9 @@ func TestBaseAction_Ensure(t *testing.T) {
},
},
object: kubernetes.CreateService("default", "service", "http", 80, 80, map[string]string{}),
verify: func(g Gomega, cli client.WithWatch, result bool, err error) {
verify: func(g Gomega, cli client.WithWatch, result controllerutil.OperationResult, err error) {
g.Expect(err).ToNot(HaveOccurred())
g.Expect(result).To(BeFalse())
g.Expect(result).To(Equal(controllerutil.OperationResultNone))
nn := types.NamespacedName{
Namespace: "default",
Name: "service",
Expand All @@ -308,9 +286,9 @@ func TestBaseAction_Ensure(t *testing.T) {
},
},
object: kubernetes.CreateService("default", "service", "http", 443, 443, map[string]string{}),
verify: func(g Gomega, cli client.WithWatch, result bool, err error) {
verify: func(g Gomega, cli client.WithWatch, result controllerutil.OperationResult, err error) {
g.Expect(err).ToNot(HaveOccurred())
g.Expect(result).To(BeFalse())
g.Expect(result).To(Equal(controllerutil.OperationResultNone))
nn := types.NamespacedName{
Namespace: "default",
Name: "service",
Expand All @@ -336,9 +314,9 @@ func TestBaseAction_Ensure(t *testing.T) {
},
},
object: kubernetes.CreateService("default", "service", "http", 443, 443, map[string]string{}),
verify: func(g Gomega, cli client.WithWatch, result bool, err error) {
verify: func(g Gomega, cli client.WithWatch, result controllerutil.OperationResult, err error) {
g.Expect(err).ToNot(HaveOccurred())
g.Expect(result).To(BeTrue())
g.Expect(result).To(Equal(controllerutil.OperationResultUpdated))
nn := types.NamespacedName{
Namespace: "default",
Name: "service",
Expand All @@ -361,15 +339,24 @@ func TestBaseAction_Ensure(t *testing.T) {
WithStatusSubresource(tt.env.objects...).
Build()

a := newDumpAction()
a.InjectClient(c)
a.InjectLogger(logr.Logger{})
a.InjectRecorder(record.NewFakeRecorder(10))
managed := []string{"new", "old", "managed"}
got, err := kubernetes.CreateOrUpdate(ctx, c, tt.object.DeepCopyObject().(client.Object),
Labels[client.Object](managed, tt.object.GetLabels()),
Annotations[client.Object](managed, tt.object.GetAnnotations()),
func(obj client.Object) error {
var (
svc *v1.Service
ok bool
)
if svc, ok = obj.(*v1.Service); ok {
// svc object
svc.Spec.Ports = tt.object.(*v1.Service).Spec.Ports
}

return nil

da := a.(*dumpAction)
labelKeys := maps.Keys(map[string]string{"new": "label", "managed": "value"})
annoKeys := maps.Keys(map[string]string{"old": "annotation", "new": "annotation", "managed": "value"})
got, err := da.Ensure(ctx, tt.object, EnsureSpec(), EnsureLabels(labelKeys...), EnsureAnnotations(annoKeys...))
},
)
tt.verify(g, c, got, err)
})
}
Expand Down
Loading

0 comments on commit 581f50f

Please sign in to comment.