diff --git a/integrations/operator/apis/resources/v1/loginrule_types.go b/integrations/operator/apis/resources/v1/loginrule_types.go index affc8af6e30e9..5fc62a7b2d3be 100644 --- a/integrations/operator/apis/resources/v1/loginrule_types.go +++ b/integrations/operator/apis/resources/v1/loginrule_types.go @@ -112,3 +112,11 @@ func (l *LoginRuleResource) SetOrigin(origin string) { func (l *LoginRuleResource) GetMetadata() types.Metadata { return *l.LoginRule.Metadata } + +func (l *LoginRuleResource) GetRevision() string { + return l.LoginRule.Metadata.GetRevision() +} + +func (l *LoginRuleResource) SetRevision(rev string) { + l.LoginRule.Metadata.SetRevision(rev) +} diff --git a/integrations/operator/controllers/resources/role_controller.go b/integrations/operator/controllers/resources/role_controller.go index bcf5aea5d1dbd..7eadad7b9b1af 100644 --- a/integrations/operator/controllers/resources/role_controller.go +++ b/integrations/operator/controllers/resources/role_controller.go @@ -171,6 +171,9 @@ func (r *RoleReconciler) Upsert(ctx context.Context, obj kclient.Object) error { } } + if existingResource != nil { + teleportResource.SetRevision(existingResource.GetRevision()) + } r.AddTeleportResourceOrigin(teleportResource) // If an error happens we want to put it in status.conditions before returning. diff --git a/integrations/operator/controllers/resources/teleport_reconciler.go b/integrations/operator/controllers/resources/teleport_reconciler.go index de63ef2d4df24..2b0b1d3956a48 100644 --- a/integrations/operator/controllers/resources/teleport_reconciler.go +++ b/integrations/operator/controllers/resources/teleport_reconciler.go @@ -35,6 +35,8 @@ type TeleportResource interface { GetName() string SetOrigin(string) GetMetadata() types.Metadata + GetRevision() string + SetRevision(string) } // TeleportKubernetesResource is a Kubernetes resource representing a Teleport resource @@ -133,6 +135,11 @@ func (r TeleportResourceReconciler[T, K]) Upsert(ctx context.Context, obj kclien teleportResource.SetOrigin(types.OriginKubernetes) + // Propagate revision as required by opportunistic locking + if exists { + teleportResource.SetRevision(existingResource.GetRevision()) + } + // We apply resource-specific mutations. if mutator, ok := r.resourceClient.(TeleportResourceMutator[T]); ok { mutator.Mutate(teleportResource, existingResource) diff --git a/integrations/operator/controllers/resources/user_controller_test.go b/integrations/operator/controllers/resources/user_controller_test.go index 641a80e79e19a..240cbae634741 100644 --- a/integrations/operator/controllers/resources/user_controller_test.go +++ b/integrations/operator/controllers/resources/user_controller_test.go @@ -21,9 +21,10 @@ import ( "testing" "time" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/gravitational/trace" "github.com/mitchellh/mapstructure" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -347,7 +348,7 @@ func TestUserUpdate(t *testing.T) { require.NoError(t, err) // TeleportUser was updated with new roles - return assert.ElementsMatch(t, tUser.GetRoles(), []string{"x", "z"}) + return compareRoles([]string{"x", "z"}, tUser.GetRoles()) }) // Updating the user in K8S @@ -373,7 +374,7 @@ func TestUserUpdate(t *testing.T) { require.NoError(t, err) // TeleportUser updated with new roles - return assert.ElementsMatch(t, tUser.GetRoles(), []string{"x", "z", "y"}) + return compareRoles([]string{"x", "y", "z"}, tUser.GetRoles()) }) require.Equal(t, setup.OperatorName, tUser.GetCreatedBy().User.Name, "createdBy has not been erased") } @@ -419,3 +420,11 @@ func getUserStatusConditionError(object map[string]interface{}) []metav1.Conditi } return conditionsWithError } + +func compareRoles(expected, actual []string) bool { + return cmp.Diff( + expected, + actual, + cmpopts.SortSlices(func(a, b string) bool { return a < b }), + ) == "" +}