Skip to content
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
41 changes: 35 additions & 6 deletions integrations/operator/controllers/resources/base_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package resources

import (
"context"
"fmt"

"github.com/gravitational/trace"
apierrors "k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -29,9 +30,18 @@ import (
ctrllog "sigs.k8s.io/controller-runtime/pkg/log"
)

// DeletionFinalizer is a name of finalizer added to resource's 'finalizers' field
// for tracking deletion events.
const DeletionFinalizer = "resources.teleport.dev/deletion"
const (
// DeletionFinalizer is a name of finalizer added to resource's 'finalizers' field
// for tracking deletion events.
DeletionFinalizer = "resources.teleport.dev/deletion"
// AnnotationFlagIgnore is the Kubernetes annotation containing the "ignore" flag.
// When set to true, the operator will not reconcile the CR.
AnnotationFlagIgnore = "teleport.dev/ignore"
// AnnotationFlagKeep is the Kubernetes annotation containing the "keep" flag.
// When set to true, the operator will not delete the Teleport resource if the
// CR is deleted.
AnnotationFlagKeep = "teleport.dev/keep"
)

type DeleteExternal func(context.Context, kclient.Object) error
type UpsertExternal func(context.Context, kclient.Object) error
Expand Down Expand Up @@ -79,15 +89,24 @@ func (r ResourceBaseReconciler) Do(ctx context.Context, req ctrl.Request, obj kc
return ctrl.Result{}, trace.Wrap(err)
}

if isIgnored(obj) {
log.Info(fmt.Sprintf("Resource is flagged with annotation %q, it will not be reconciled.", AnnotationFlagIgnore))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an log.Infof?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly not :/

return ctrl.Result{}, nil
}

hasDeletionFinalizer := controllerutil.ContainsFinalizer(obj, DeletionFinalizer)
isMarkedToBeDeleted := !obj.GetDeletionTimestamp().IsZero()

// Delete
if isMarkedToBeDeleted {
if hasDeletionFinalizer {
log.Info("deleting object in Teleport")
if err := r.DeleteExternal(ctx, obj); err != nil && !trace.IsNotFound(err) {
return ctrl.Result{}, trace.Wrap(err)
if isKept(obj) {
log.Info(fmt.Sprintf("Resource is flagged with annotation %q, it will not be deleted in Teleport.", AnnotationFlagKeep))
} else {
log.Info("deleting object in Teleport")
if err := r.DeleteExternal(ctx, obj); err != nil && !trace.IsNotFound(err) {
return ctrl.Result{}, trace.Wrap(err)
}
}

log.Info("removing finalizer")
Expand Down Expand Up @@ -115,3 +134,13 @@ func (r ResourceBaseReconciler) Do(ctx context.Context, req ctrl.Request, obj kc
err := r.UpsertExternal(ctx, obj)
return ctrl.Result{}, trace.Wrap(err)
}

// isIgnored checks if the CR should be ignored
func isIgnored(obj kclient.Object) bool {
return checkAnnotationFlag(obj, AnnotationFlagIgnore, false /* defaults to false */)
}

// isKept checks if the Teleport resource should be kept if the CR is deleted
func isKept(obj kclient.Object) bool {
return checkAnnotationFlag(obj, AnnotationFlagKeep, false /* defaults to false */)
}
16 changes: 16 additions & 0 deletions integrations/operator/controllers/resources/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package resources
import (
"context"
"fmt"
"strconv"

"github.com/gravitational/trace"
"k8s.io/apimachinery/pkg/api/meta"
Expand Down Expand Up @@ -169,3 +170,18 @@ func GetUnstructuredObjectFromGVK(gvk schema.GroupVersionKind) (*unstructured.Un
obj.SetGroupVersionKind(gvk)
return &obj, nil
}

// checkAnnotationFlag checks is the Kubernetes resource is annotated with a
// flag and parses its value. Returns the default value if the flag is missing
// or the annotation value cannot be parsed.
func checkAnnotationFlag(object kclient.Object, flagName string, defaultValue bool) bool {
annotation, ok := object.GetAnnotations()[flagName]
if !ok {
return defaultValue
}
value, err := strconv.ParseBool(annotation)
if err != nil {
return defaultValue
}
return value
}
69 changes: 69 additions & 0 deletions integrations/operator/controllers/resources/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

"github.com/gravitational/teleport/api/types"
)
Expand Down Expand Up @@ -95,3 +96,71 @@ func TestCheckOwnership(t *testing.T) {
})
}
}

func TestCheckAnnotationFlag(t *testing.T) {
testFlag := "foo"
tests := []struct {
name string
annotations map[string]string
defaultValue bool
expectedOutput bool
}{
{
name: "flag set true, default true",
annotations: map[string]string{testFlag: "true"},
defaultValue: true,
expectedOutput: true,
},
{
name: "flag set false, default true",
annotations: map[string]string{testFlag: "false"},
defaultValue: true,
expectedOutput: false,
},
{
name: "flag set true, default false",
annotations: map[string]string{testFlag: "true"},
defaultValue: false,
expectedOutput: true,
},
{
name: "flag set false, default false",
annotations: map[string]string{testFlag: "false"},
defaultValue: false,
expectedOutput: false,
},
{
name: "flag missing, default true",
annotations: map[string]string{},
defaultValue: true,
expectedOutput: true,
},
{
name: "flag missing, default false",
annotations: map[string]string{},
defaultValue: false,
expectedOutput: false,
},
{
name: "flag malformed, default true",
annotations: map[string]string{testFlag: "malformed"},
defaultValue: true,
expectedOutput: true,
},
{
name: "flag malformed, default false",
annotations: map[string]string{testFlag: "malformed"},
defaultValue: false,
expectedOutput: false,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
obj := &unstructured.Unstructured{}
obj.SetAnnotations(tt.annotations)
require.Equal(t, tt.expectedOutput, checkAnnotationFlag(obj, testFlag, tt.defaultValue))
})
}
}