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

SECURESIGN-1015 | Make sure route selector labels and managed annos are recconciled #613

Merged
merged 4 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 2 additions & 0 deletions internal/controller/annotations/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ const (

// TreeId Annotation inform that resource is associated with specific Merkle Tree
TreeId = "rhtas.redhat.com/treeId"

TLS = "service.beta.openshift.io/serving-cert-secret-name"
)

var inheritable = []string{
Expand Down
176 changes: 156 additions & 20 deletions internal/controller/common/action/base_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package action
import (
"context"
"errors"
"fmt"
"maps"
"reflect"
"strconv"
"strings"
Expand All @@ -23,6 +25,8 @@ import (
// OptimisticLockErrorMsg - ignore update error: https://github.com/kubernetes/kubernetes/issues/28149
const OptimisticLockErrorMsg = "the object has been modified; please apply your changes to the latest version and try again"

type EnsureOption func(current client.Object, expected client.Object) error

type BaseAction struct {
Client client.Client
Recorder record.EventRecorder
Expand Down Expand Up @@ -90,17 +94,24 @@ func (action *BaseAction) Requeue() *Result {
}
}

func (action *BaseAction) Ensure(ctx context.Context, obj client2.Object) (bool, error) {
func (action *BaseAction) Ensure(ctx context.Context, obj client2.Object, opts ...EnsureOption) (bool, error) {
var (
expected client2.Object
ok bool
err error
result controllerutil.OperationResult
)

if len(opts) == 0 {
opts = []EnsureOption{
EnsureSpec(),
}
}

if expected, ok = obj.DeepCopyObject().(client2.Object); !ok {
return false, errors.New("Can't create DeepCopy object")
return false, errors.New("can't create DeepCopy object")
}

err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
result, err = controllerutil.CreateOrUpdate(ctx, action.Client, obj, func() error {
annoStr, find := obj.GetAnnotations()[annotations.PausedReconciliation]
Expand All @@ -111,24 +122,11 @@ func (action *BaseAction) Ensure(ctx context.Context, obj client2.Object) (bool,
}
}

currentSpec := reflect.ValueOf(obj).Elem().FieldByName("Spec")
expectedSpec := reflect.ValueOf(expected).Elem().FieldByName("Spec")
if currentSpec == reflect.ValueOf(nil) {
// object without spec
// return without update
return nil
}
if !expectedSpec.IsValid() || !currentSpec.IsValid() {
return errors.New("spec is not valid")
}
if !currentSpec.CanSet() {
return errors.New("can't set expected spec to current object")
}

// WORKAROUND: CreateOrUpdate uses DeepEqual to compare
// DeepEqual does not honor default values
if !equality.Semantic.DeepDerivative(expectedSpec.Interface(), currentSpec.Interface()) {
currentSpec.Set(expectedSpec)
for _, opt := range opts {
err = opt(obj, expected)
if err != nil {
return err
}
}

return nil
Expand All @@ -142,3 +140,141 @@ func (action *BaseAction) Ensure(ctx context.Context, obj client2.Object) (bool,

return result != controllerutil.OperationResultNone, nil
}

func EnsureSpec() EnsureOption {
return func(current client.Object, expected client.Object) error {
currentSpec := reflect.ValueOf(current).Elem().FieldByName("Spec")
expectedSpec := reflect.ValueOf(expected).Elem().FieldByName("Spec")
if currentSpec == reflect.ValueOf(nil) {
// object without spec
// return without update
return nil
}
if !expectedSpec.IsValid() || !currentSpec.IsValid() {
return errors.New("spec is not valid")
}
if !currentSpec.CanSet() {
return errors.New("can't set expected spec to current object")
}

// WORKAROUND: CreateOrUpdate uses DeepEqual to compare
// DeepEqual does not honor default values
if !equality.Semantic.DeepDerivative(expectedSpec.Interface(), currentSpec.Interface()) {
currentSpec.Set(expectedSpec)
}
return nil
}
}

func EnsureRouteSelectorLabels(managedLabels ...string) EnsureOption {
return func(current client.Object, expected client.Object) error {
JasonPowr marked this conversation as resolved.
Show resolved Hide resolved
if current == nil || expected == nil {
return fmt.Errorf("nil object passed")
}

currentSpec := reflect.ValueOf(current).Elem().FieldByName("Spec")
expectedSpec := reflect.ValueOf(expected).Elem().FieldByName("Spec")
if !currentSpec.IsValid() || !expectedSpec.IsValid() {
return nil
}

//Current workaround for DeepEqual vs DeepDerivative, more info here https://issues.redhat.com/browse/SECURESIGN-1393
currentRouteSelectorLabels, expectedRouteSelectorLabels := getRouteSelectorLabels(currentSpec, expectedSpec)
if currentRouteSelectorLabels.CanSet() &&
!equality.Semantic.DeepEqual(currentRouteSelectorLabels.Interface(), expectedRouteSelectorLabels.Interface()) {
currentRouteSelectorLabels.Set(expectedRouteSelectorLabels)
}

gvk := current.GetObjectKind().GroupVersionKind()
if gvk.Kind == "Ingress" || gvk.Kind == "Route" {
if !reflect.DeepEqual(current.GetLabels(), expected.GetLabels()) {
if err := EnsureLabels(managedLabels...)(current, expected); err != nil {
return err
}
}
}
return nil
}
}

func EnsureLabels(managedLabels ...string) EnsureOption {
return func(current client.Object, expected client.Object) error {
expectedLabels := expected.GetLabels()
if expectedLabels == nil {
expectedLabels = map[string]string{}
}
currentLabels := current.GetLabels()
if currentLabels == nil {
currentLabels = map[string]string{}
}
mergedLabels := make(map[string]string)
maps.Copy(mergedLabels, currentLabels)

maps.DeleteFunc(mergedLabels, func(k, v string) bool {
_, existsInExpected := expectedLabels[k]
return !existsInExpected
})

for _, managedLabel := range managedLabels {
if val, exists := expectedLabels[managedLabel]; exists {
mergedLabels[managedLabel] = val
}
}
current.SetLabels(mergedLabels)
return nil
}
}

func EnsureAnnotations(managedAnnotations ...string) EnsureOption {
return func(current client.Object, expected client.Object) error {
expectedAnno := expected.GetAnnotations()
if expectedAnno == nil {
expectedAnno = map[string]string{}
}
currentAnno := current.GetAnnotations()
if currentAnno == nil {
currentAnno = map[string]string{}
}
mergedAnnotations := make(map[string]string)
maps.Copy(mergedAnnotations, currentAnno)

for _, managedAnno := range managedAnnotations {
if val, exists := expectedAnno[managedAnno]; exists {
mergedAnnotations[managedAnno] = val
} else {
delete(mergedAnnotations, managedAnno)
}
}
current.SetAnnotations(mergedAnnotations)
return nil
}
}

func getRouteSelectorLabels(currentSpec, expectedSpec reflect.Value) (reflect.Value, reflect.Value) {
var currentRouteSelectorLabels, expectedRouteSelectorLabels reflect.Value
getRouteSelectorLabels := func(spec reflect.Value, fieldName string) reflect.Value {
if field := spec.FieldByName(fieldName); field.IsValid() {
if routeSelectorLabels := field.FieldByName("RouteSelectorLabels"); routeSelectorLabels.IsValid() {
return routeSelectorLabels
}
}
return reflect.Value{}
}

// Handle Rekor and rekor search ui
currentRekorLabels := getRouteSelectorLabels(currentSpec, "RekorSearchUI")
expectedRekorLabels := getRouteSelectorLabels(expectedSpec, "RekorSearchUI")
if currentRekorLabels.IsValid() && expectedRekorLabels.IsValid() {
if !equality.Semantic.DeepEqual(currentRekorLabels.Interface(), expectedRekorLabels.Interface()) {
currentRouteSelectorLabels = currentRekorLabels
expectedRouteSelectorLabels = expectedRekorLabels
}
}

//Handle the rest
if !currentRouteSelectorLabels.IsValid() && !expectedRouteSelectorLabels.IsValid() {
currentRouteSelectorLabels = getRouteSelectorLabels(currentSpec, "ExternalAccess")
expectedRouteSelectorLabels = getRouteSelectorLabels(expectedSpec, "ExternalAccess")
}
return currentRouteSelectorLabels, expectedRouteSelectorLabels
}
Loading
Loading