Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PoC rework ensure #740

Merged
merged 2 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading