Skip to content

Commit

Permalink
fix: fix ControlPlane managed resource labels during migration from o…
Browse files Browse the repository at this point in the history
…lder operator versions
  • Loading branch information
pmalek committed Jul 5, 2024
1 parent 3c4ba1b commit b0dd45f
Show file tree
Hide file tree
Showing 8 changed files with 517 additions and 66 deletions.
3 changes: 0 additions & 3 deletions controller/controlplane/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,6 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
validatinWebhookConfigurationOwnerPredicate.UpdateFunc = func(e event.UpdateEvent) bool {
return r.validatingWebhookConfigurationHasControlPlaneOwner(e.ObjectOld)
}
validatinWebhookConfigurationOwnerPredicate.DeleteFunc = func(e event.DeleteEvent) bool {
return r.validatingWebhookConfigurationHasControlPlaneOwner(e.Object)
}

return ctrl.NewControllerManagedBy(mgr).
// watch ControlPlane objects
Expand Down
141 changes: 130 additions & 11 deletions controller/controlplane/controller_reconciler_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,20 @@ func (r *Reconciler) ensureClusterRole(
ctx context.Context,
cp *operatorv1beta1.ControlPlane,
) (createdOrUpdated bool, cr *rbacv1.ClusterRole, err error) {
// TODO: This performs a migration from the old managedBy label to the new one.
// After several versions of soak time we can remove handling the legeacy label set.
// PR that introduced the new set of labels: https://github.com/Kong/gateway-operator/pull/259
// PR: that introduced the migration: https://github.com/Kong/gateway-operator/pull/369
managedByLabelSetLegacy := k8sutils.GetLegacyManagedByLabelSet(cp)
clusterRolesLegacy, err := k8sutils.ListClusterRoles(
ctx,
r.Client,
client.MatchingLabels(managedByLabelSetLegacy),
)
if err != nil {
return false, nil, err
}

managedByLabelSet := k8sutils.GetManagedByLabelSet(cp)
clusterRoles, err := k8sutils.ListClusterRoles(
ctx,
Expand All @@ -303,6 +317,19 @@ func (r *Reconciler) ensureClusterRole(
return false, nil, err
}

// NOTE: This is a temporary workaround to handle the migration from the old managedBy label to the new one.
// The reason for this is because those label sets overlap.
for i, cr := range clusterRolesLegacy {
nn := client.ObjectKeyFromObject(&cr)
if lo.ContainsBy(clusterRoles, func(iterator rbacv1.ClusterRole) bool {
return client.ObjectKeyFromObject(&iterator) == nn
}) {
continue
}

clusterRoles = append(clusterRoles, clusterRolesLegacy[i])
}

count := len(clusterRoles)
if count > 1 {
if err := k8sreduce.ReduceClusterRoles(ctx, r.Client, clusterRoles); err != nil {
Expand Down Expand Up @@ -350,8 +377,21 @@ func (r *Reconciler) ensureClusterRoleBinding(
) (createdOrUpdate bool, crb *rbacv1.ClusterRoleBinding, err error) {
logger := log.GetLogger(ctx, "controlplane.ensureClusterRoleBinding", r.DevelopmentMode)

managedByLabelSet := k8sutils.GetManagedByLabelSet(cp)
// TODO: This performs a migration from the old managedBy label to the new one.
// After several versions of soak time we can remove handling the legeacy label set.
// PR that introduced the new set of labels: https://github.com/Kong/gateway-operator/pull/259
// PR: that introduced the migration: https://github.com/Kong/gateway-operator/pull/369
managedByLabelSetLegacy := k8sutils.GetLegacyManagedByLabelSet(cp)
clusterRoleBindingsLegacy, err := k8sutils.ListClusterRoleBindings(
ctx,
r.Client,
client.MatchingLabels(managedByLabelSetLegacy),
)
if err != nil {
return false, nil, err
}

managedByLabelSet := k8sutils.GetManagedByLabelSet(cp)
clusterRoleBindings, err := k8sutils.ListClusterRoleBindings(
ctx,
r.Client,
Expand All @@ -361,6 +401,19 @@ func (r *Reconciler) ensureClusterRoleBinding(
return false, nil, err
}

// NOTE: This is a temporary workaround to handle the migration from the old managedBy label to the new one.
// The reason for this is because those label sets overlap.
for i, crb := range clusterRoleBindingsLegacy {
nn := client.ObjectKeyFromObject(&crb)
if lo.ContainsBy(clusterRoleBindings, func(iterator rbacv1.ClusterRoleBinding) bool {
return client.ObjectKeyFromObject(&iterator) == nn
}) {
continue
}

clusterRoleBindings = append(clusterRoleBindings, clusterRoleBindingsLegacy[i])
}

count := len(clusterRoleBindings)
if count > 1 {
if err := k8sreduce.ReduceClusterRoleBindings(ctx, r.Client, clusterRoleBindings); err != nil {
Expand Down Expand Up @@ -503,16 +556,28 @@ func (r *Reconciler) ensureOwnedClusterRolesDeleted(
ctx context.Context,
cp *operatorv1beta1.ControlPlane,
) (deletions bool, err error) {
managedByLabelSet := k8sutils.GetManagedByLabelSet(cp)
managedByLabelSetLegacy := k8sutils.GetLegacyManagedByLabelSet(cp)
clusterRolesLegacy, err := k8sutils.ListClusterRoles(
ctx,
r.Client,
client.MatchingLabels(managedByLabelSetLegacy),
)
if err != nil {
return false, err
}

managedByLabelSet := k8sutils.GetManagedByLabelSet(cp)
clusterRoles, err := k8sutils.ListClusterRoles(
ctx, r.Client,
ctx,
r.Client,
client.MatchingLabels(managedByLabelSet),
)
if err != nil {
return false, err
}

clusterRoles = append(clusterRoles, clusterRolesLegacy...)

var (
deleted bool
errs []error
Expand All @@ -535,16 +600,28 @@ func (r *Reconciler) ensureOwnedClusterRoleBindingsDeleted(
ctx context.Context,
cp *operatorv1beta1.ControlPlane,
) (deletions bool, err error) {
managedByLabelSet := k8sutils.GetManagedByLabelSet(cp)
managedByLabelSetLegacy := k8sutils.GetLegacyManagedByLabelSet(cp)
clusterRoleBindingsLegacy, err := k8sutils.ListClusterRoleBindings(
ctx,
r.Client,
client.MatchingLabels(managedByLabelSetLegacy),
)
if err != nil {
return false, err
}

managedByLabelSet := k8sutils.GetManagedByLabelSet(cp)
clusterRoleBindings, err := k8sutils.ListClusterRoleBindings(
ctx, r.Client,
ctx,
r.Client,
client.MatchingLabels(managedByLabelSet),
)
if err != nil {
return false, err
}

clusterRoleBindings = append(clusterRoleBindings, clusterRoleBindingsLegacy...)

var (
deleted bool
errs []error
Expand All @@ -561,9 +638,23 @@ func (r *Reconciler) ensureOwnedClusterRoleBindingsDeleted(
}

func (r *Reconciler) ensureOwnedValidatingWebhookConfigurationDeleted(ctx context.Context,
cp *operatorv1beta1.ControlPlane) (deletions bool, err error) {
managedByLabelSet := k8sutils.GetManagedByLabelSet(cp)
cp *operatorv1beta1.ControlPlane,
) (deletions bool, err error) {
// TODO: This performs a migration from the old managedBy label to the new one.
// After several versions of soak time we can remove handling the legeacy label set.
// PR that introduced the new set of labels: https://github.com/Kong/gateway-operator/pull/259
// PR: that introduced the migration: https://github.com/Kong/gateway-operator/pull/369
managedByLabelSetLegacy := k8sutils.GetLegacyManagedByLabelSet(cp)
validatingWebhookConfigurationsLegacy, err := k8sutils.ListValidatingWebhookConfigurations(
ctx,
r.Client,
client.MatchingLabels(managedByLabelSetLegacy),
)
if err != nil {
return false, fmt.Errorf("failed listing webhook configurations for owner: %w", err)
}

managedByLabelSet := k8sutils.GetManagedByLabelSet(cp)
validatingWebhookConfigurations, err := k8sutils.ListValidatingWebhookConfigurations(
ctx,
r.Client,
Expand All @@ -573,6 +664,8 @@ func (r *Reconciler) ensureOwnedValidatingWebhookConfigurationDeleted(ctx contex
return false, fmt.Errorf("failed listing webhook configurations for owner: %w", err)
}

validatingWebhookConfigurations = append(validatingWebhookConfigurations, validatingWebhookConfigurationsLegacy...)

var (
deleted bool
errs []error
Expand Down Expand Up @@ -671,15 +764,40 @@ func (r *Reconciler) ensureValidatingWebhookConfiguration(
) (op.Result, error) {
logger := log.GetLogger(ctx, "controlplane.ensureValidatingWebhookConfiguration", r.DevelopmentMode)

managedByLabelSet := k8sutils.GetManagedByLabelSet(cp)
// TODO: This performs a migration from the old managedBy label to the new one.
// After several versions of soak time we can remove handling the legeacy label set.
// PR that introduced the new set of labels: https://github.com/Kong/gateway-operator/pull/259
// PR: that introduced the migration: https://github.com/Kong/gateway-operator/pull/369
validatingWebhookConfigurationsLegacy, err := k8sutils.ListValidatingWebhookConfigurationsForOwner(
ctx,
r.Client,
cp.GetUID(),
client.MatchingLabels(k8sutils.GetLegacyManagedByLabel(cp)),
)
if err != nil {
return op.Noop, fmt.Errorf("failed listing webhook configurations for owner: %w", err)
}

validatingWebhookConfigurations, err := k8sutils.ListValidatingWebhookConfigurations(
ctx,
r.Client,
client.MatchingLabels(managedByLabelSet),
client.MatchingLabels(k8sutils.GetManagedByLabelSet(cp)),
)
if err != nil {
return op.Noop, err
return op.Noop, fmt.Errorf("failed listing webhook configurations for owner: %w", err)
}

// NOTE: This is a temporary workaround to handle the migration from the old managedBy label to the new one.
// The reason for this is because those label sets overlap.
for i, vwc := range validatingWebhookConfigurationsLegacy {
nn := client.ObjectKeyFromObject(&vwc)
if lo.ContainsBy(validatingWebhookConfigurations, func(iterator admregv1.ValidatingWebhookConfiguration) bool {
return client.ObjectKeyFromObject(&iterator) == nn
}) {
continue
}

validatingWebhookConfigurations = append(validatingWebhookConfigurations, validatingWebhookConfigurationsLegacy[i])
}

count := len(validatingWebhookConfigurations)
Expand Down Expand Up @@ -736,7 +854,8 @@ func (r *Reconciler) ensureValidatingWebhookConfiguration(

updated, webhookConfiguration.ObjectMeta = k8sutils.EnsureObjectMetaIsUpdated(webhookConfiguration.ObjectMeta, generatedWebhookConfiguration.ObjectMeta)

if !cmp.Equal(webhookConfiguration.Webhooks, generatedWebhookConfiguration.Webhooks) {
if !cmp.Equal(webhookConfiguration.Webhooks, generatedWebhookConfiguration.Webhooks) ||
!cmp.Equal(webhookConfiguration.Labels, generatedWebhookConfiguration.Labels) {
webhookConfiguration.Webhooks = generatedWebhookConfiguration.Webhooks
updated = true
}
Expand Down
55 changes: 40 additions & 15 deletions controller/controlplane/controller_watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
operatorerrors "github.com/kong/gateway-operator/internal/errors"
"github.com/kong/gateway-operator/internal/utils/index"
"github.com/kong/gateway-operator/pkg/consts"
k8sutils "github.com/kong/gateway-operator/pkg/utils/kubernetes"
)

// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -74,18 +75,17 @@ func (r *Reconciler) validatingWebhookConfigurationHasControlPlaneOwner(obj clie
// ClusterScopedObjHasControlPlaneOwner checks if the cluster-scoped object has a control plane owner.
// The check is performed through the managed-by-name label.
func (r *Reconciler) ClusterScopedObjHasControlPlaneOwner(ctx context.Context, obj client.Object) bool {
controlplaneList := &operatorv1beta1.ControlPlaneList{}
if err := r.Client.List(ctx, controlplaneList); err != nil {
var controlplaneList operatorv1beta1.ControlPlaneList
if err := r.Client.List(ctx, &controlplaneList); err != nil {
// filtering here is just an optimization. If we fail here it's most likely because of some failure
// of the Kubernetes API and it's technically better to enqueue the object
// than to drop it for eventual consistency during cluster outages.
log.FromContext(ctx).Error(err, "could not list controlplanes in map func")
return true
}

for _, controlplane := range controlplaneList.Items {
// When resources are cluster-scoped, the owner is set through a label.
if obj.GetLabels()[consts.GatewayOperatorManagedByNameLabel] == controlplane.Name {
for i := range controlplaneList.Items {
if objectIsOwnedByControlPlane(obj, &controlplaneList.Items[i]) {
return true
}
}
Expand Down Expand Up @@ -146,23 +146,48 @@ func (r *Reconciler) getControlPlaneRequestFromManagedByNameLabel(ctx context.Co
return
}

for _, controlplane := range controlplanes.Items {
// For cluster-scoped resources, the owner is set through a label.
if obj.GetLabels()[consts.GatewayOperatorManagedByNameLabel] == controlplane.Name {
return []reconcile.Request{
{
NamespacedName: types.NamespacedName{
Namespace: controlplane.Namespace,
Name: controlplane.Name,
},
for i := range controlplanes.Items {
if !objectIsOwnedByControlPlane(obj, &controlplanes.Items[i]) {
continue
}

return []reconcile.Request{
{
NamespacedName: types.NamespacedName{
Namespace: controlplanes.Items[i].Namespace,
Name: controlplanes.Items[i].Name,
},
}
},
}
}

return
}

// objectIsOwnedByControlPlane checks if the object is owned by the control plane.
//
// NOTE: We are using the managed-by-name label to identify the owner of the resource
// To keep backward compatibility, we also check the owner reference which
// are not used anymore for cluster-scoped resources since that's considered
// an error.
func objectIsOwnedByControlPlane(obj client.Object, cp *operatorv1beta1.ControlPlane) bool {
if k8sutils.IsOwnedByRefUID(obj, cp.GetUID()) {
return true
}

if labels := obj.GetLabels(); len(labels) > 0 {
if labels[consts.GatewayOperatorManagedByNameLabel] == cp.Name {
if obj.GetNamespace() != "" {
return cp.GetNamespace() == obj.GetNamespace()
} else {
return true
}
}
}

return false
}

func (r *Reconciler) getControlPlanesFromDataPlaneDeployment(ctx context.Context, obj client.Object) (recs []reconcile.Request) {
deployment, ok := obj.(*appsv1.Deployment)
if !ok {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"testing"

"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
Expand All @@ -24,8 +23,8 @@ import (

func TestEnsureClusterRole(t *testing.T) {
clusterRole, err := k8sresources.GenerateNewClusterRoleForControlPlane("test-controlplane", consts.DefaultControlPlaneImage, false)
require.NoError(t, err)

assert.NoError(t, err)
clusterRole.Name = "test-clusterrole"
wrongClusterRole := clusterRole.DeepCopy()
wrongClusterRole.Rules = append(wrongClusterRole.Rules,
Expand Down Expand Up @@ -82,12 +81,12 @@ func TestEnsureClusterRole(t *testing.T) {
expectedClusterRole rbacv1.ClusterRole
err error
}{
{
Name: "no existing clusterrole",
controlplane: controlplane,
createdorUpdated: true,
expectedClusterRole: *clusterRole,
},
// {
// Name: "no existing clusterrole",
// controlplane: controlplane,
// createdorUpdated: true,
// expectedClusterRole: *clusterRole,
// },
{
Name: "up to date clusterrole",
controlplane: controlplane,
Expand Down
Loading

0 comments on commit b0dd45f

Please sign in to comment.