Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
4649092
fix: correct group controller tests to properly reconcile resources
rmandal-redhat Jan 19, 2026
64be605
add git ignore file
rmandal-redhat Jan 22, 2026
b75c79c
Add styleguide for opentelemetry implementation
vinamra28 Jan 16, 2026
03c6519
adding async fetch of users feature for Snowflake backend
Pujathacker2210 Jan 12, 2026
d8f9aca
address PR review comments: improve error handling and reduce code du…
Pujathacker2210 Jan 13, 2026
85253cc
miving SnowflakeAsyncLoad in a new function and added uuid generation…
Pujathacker2210 Jan 15, 2026
26c5f2f
added support to handle snowflake async loads parallely for multiple …
Pujathacker2210 Jan 16, 2026
6f75359
Bump actions/setup-go from 6.1.0 to 6.2.0
dependabot[bot] Jan 19, 2026
3a4cdcf
update the sample file
rmandal-redhat Jan 22, 2026
0e04626
Parameterising the usernaut offboarding interval feild to AppConfig
sunando94 Jan 22, 2026
5b56b40
updated sample file name
rmandal-redhat Jan 27, 2026
2f3c26f
Merge remote-tracking branch 'upstream/main'
rmandal-redhat Jan 27, 2026
09550bc
Independent reconciliation of Group Params
maxkashyap41 Jan 13, 2026
e35122a
Adding exclusionList and security checks for null or empty userKey
sunando94 Feb 3, 2026
fb4001d
Bump github.com/redis/go-redis/v9 from 9.17.2 to 9.17.3
dependabot[bot] Jan 26, 2026
59c2d91
Bump step-security/harden-runner from 2.14.0 to 2.14.2
dependabot[bot] Feb 9, 2026
9039c47
Bump github.com/alicebob/miniredis/v2 from 2.34.0 to 2.36.1
dependabot[bot] Jan 23, 2026
5039da0
Bump k8s.io/client-go from 0.34.3 to 0.34.4
dependabot[bot] Feb 11, 2026
36ed38e
Bump github.com/onsi/ginkgo/v2 from 2.27.5 to 2.28.1
dependabot[bot] Feb 11, 2026
d65f2f9
Bump actions/dependency-review-action from 4.8.2 to 4.8.3
dependabot[bot] Feb 23, 2026
e060f85
Bump step-security/harden-runner from 2.14.2 to 2.15.0
dependabot[bot] Mar 2, 2026
349610b
Bump actions/setup-go from 6.2.0 to 6.3.0
dependabot[bot] Mar 2, 2026
368a1c6
Bump go.opentelemetry.io/otel/sdk from 1.34.0 to 1.40.0
dependabot[bot] Mar 5, 2026
669cf93
Bump actions/dependency-review-action from 4.8.3 to 4.9.0
dependabot[bot] Mar 9, 2026
bff0734
Bump docker/build-push-action from 6 to 7
dependabot[bot] Mar 9, 2026
126d13c
Bump step-security/harden-runner from 2.15.0 to 2.15.1
dependabot[bot] Mar 9, 2026
9e2414b
Bump docker/login-action from 3 to 4
dependabot[bot] Mar 9, 2026
d47cc30
Bump docker/metadata-action from 5 to 6
dependabot[bot] Mar 9, 2026
f5ce7e8
Bump github.com/onsi/gomega from 1.39.0 to 1.39.1
dependabot[bot] Feb 26, 2026
4ee47a0
Adding controller.maxConcurrentReconciles to control cr concurrency
sunando94 Feb 13, 2026
2c83441
Bump step-security/harden-runner from 2.15.1 to 2.16.0
dependabot[bot] Mar 16, 2026
c99caed
standardize first/last names for backend (#225)
tikandeabhay Mar 19, 2026
e40184c
feat: Add support for ldap query based groups (#184)
iambibhas Mar 23, 2026
071106f
fix: Add supported keys in QBG filters
Mar 23, 2026
e246393
Bump google.golang.org/grpc from 1.72.1 to 1.79.3
dependabot[bot] Mar 24, 2026
b9caf60
Bump github.com/goccy/go-yaml from 1.18.0 to 1.19.2
dependabot[bot] Mar 24, 2026
581c8fd
fix: correct group controller tests to properly reconcile resources
rmandal-redhat Jan 19, 2026
5f5e049
add git ignore file
rmandal-redhat Jan 22, 2026
e272a11
Parameterising the usernaut offboarding interval feild to AppConfig
sunando94 Jan 22, 2026
80c3c85
Bump k8s.io/apimachinery from 0.34.4 to 0.34.6
dependabot[bot] Mar 24, 2026
d672854
Bump github.com/redis/go-redis/extra/redisotel/v9 from 9.17.2 to 9.18.0
dependabot[bot] Mar 24, 2026
670dc6a
Bump github.com/gojek/heimdall/v7 from 7.0.3 to 7.1.0
dependabot[bot] Mar 24, 2026
260ec7f
Bump github.com/alicebob/miniredis/v2 from 2.36.1 to 2.37.0
dependabot[bot] Mar 24, 2026
3b5ead4
Bump k8s.io/client-go from 0.34.4 to 0.34.6
dependabot[bot] Mar 25, 2026
d8650d2
Bump github.com/go-ldap/ldap/v3 from 3.4.12 to 3.4.13
dependabot[bot] Mar 25, 2026
5c73dde
Handle user creation having `-` in username
vinamra28 Mar 26, 2026
52a2f77
fix: remove duplicate GetTransformedGroupNameOrFallback and test
rmandal-redhat Apr 6, 2026
b45f0e9
test: avoid running kind load in unit tests
rmandal-redhat Apr 6, 2026
582607b
Merge upstream/main into main (resolve MR #199 conflicts)
rmandal-redhat Apr 8, 2026
e04798f
updated issues with ldapclient mocks
rmandal-redhat Apr 8, 2026
17dc925
resolved the open comments by reviewer
rmandal-redhat Apr 8, 2026
6b3bf86
resolved the issue retaleted to utils.go,group_controller.go and grou…
rmandal-redhat Apr 8, 2026
e10ceb3
Merge upstream/main into main (sync for PR #199)
rmandal-redhat Apr 8, 2026
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
10 changes: 8 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,21 @@ go.work
*.swo
*~

appconfig/local.yaml

# Any certificate files
*.key
*.csr
*.p12
*.crt
*.pem

# Environment variables and secrets
.env
.env.local
.env.*.local

# Local development config with secrets
appconfig/local*.yaml
appconfig/.env*
Comment thread
rmandal-redhat marked this conversation as resolved.

# Secrets
secrets/
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,9 @@ mockgen: ## Generate mocks.
go install github.com/golang/mock/mockgen@latest; \
fi
@echo "Generating mocks"
@mockgen -source=pkg/clients/ldap/client.go -destination=pkg/clients/ldap/mocks/ldap_mock.go -package=mocks LDAPConnClient
@mockgen -source=pkg/clients/ldap/client.go -destination=internal/controller/mocks/ldap_mock.go -package=mocks LDAPClient
@mockgen -source=pkg/clients/client.go -destination=internal/controller/periodicjobs/mocks/client_mock.go -package=mocks Client
@$(GOBIN)/mockgen -source=pkg/clients/ldap/client.go -destination=pkg/clients/ldap/mocks/ldap_mock.go -package=mocks LDAPConnClient
@$(GOBIN)/mockgen -source=pkg/clients/ldap/client.go -destination=internal/controller/mocks/ldap_mock.go -package=mocks LDAPClient
@$(GOBIN)/mockgen -source=pkg/clients/client.go -destination=internal/controller/periodicjobs/mocks/client_mock.go -package=mocks Client
Comment thread
rmandal-redhat marked this conversation as resolved.

.PHONY: test
test: mockgen manifests generate fmt vet envtest ## Run tests.
Expand Down
1 change: 0 additions & 1 deletion appconfig/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,3 @@ offboardUserExclusionListConfigPath: "default_offboard_user_exclusion_list"
# Controller configuration
controllerConfig:
maxConcurrentReconciles: 1

5 changes: 3 additions & 2 deletions config/samples/v1alpha1_group.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ spec:
members:
users:
- subhatta
- vinod
- rmandal
group_params:
- backend: gitlab
name: gitlab
Expand All @@ -23,4 +23,5 @@ spec:
backends:
- name: fivetran
type: fivetran

- name: rhplatformtest
type: "snowflake"
19 changes: 19 additions & 0 deletions config/samples/v2_cleanup_group.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
apiVersion: operator.dataverse.redhat.com/v1alpha1
kind: Group
metadata:
labels:
app.kubernetes.io/name: usernaut
app.kubernetes.io/managed-by: kustomize
name: dataverse-source-cleanup-test
namespace: usernaut
spec:
group_name: dataverse-source-cleanup-test
members:
users:
- ruagrawa
- rmandal
- subhatta
backends:
- name: fivetran
type: fivetran

19 changes: 19 additions & 0 deletions config/samples/v3_consumer_group.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
apiVersion: operator.dataverse.redhat.com/v1alpha1
kind: Group
metadata:
labels:
app.kubernetes.io/name: usernaut
app.kubernetes.io/managed-by: kustomize
name: dataverse-consumer-bookingmaster-test
namespace: usernaut
spec:
group_name: dataverse-consumer-bookingmaster-test
members:
users:
- ruagrawa
- rmandal
- atikande
backends:
- name: fivetran
type: fivetran

19 changes: 19 additions & 0 deletions config/samples/v3_invalid_group.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
apiVersion: operator.dataverse.redhat.com/v1alpha1
kind: Group
metadata:
labels:
app.kubernetes.io/name: usernaut
app.kubernetes.io/managed-by: kustomize
name: invalid-group-test-name
namespace: usernaut
spec:
group_name: invalid-group-test-name
members:
users:
- ruagrawa
- rmandal
- atikande
backends:
- name: fivetran
type: fivetran

18 changes: 18 additions & 0 deletions config/samples/v3_valid_consumer_group.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: operator.dataverse.redhat.com/v1alpha1
kind: Group
metadata:
labels:
app.kubernetes.io/name: usernaut
app.kubernetes.io/managed-by: kustomize
name: dataverse-consumer-bookingmaster-test
namespace: usernaut
spec:
group_name: dataverse-consumer-bookingmaster-test
members:
users:
- ruagrawa
- atikande
backends:
- name: fivetran
type: fivetran

19 changes: 19 additions & 0 deletions config/samples/v4_invalid_consumer_group.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
apiVersion: operator.dataverse.redhat.com/v1alpha1
kind: Group
metadata:
labels:
app.kubernetes.io/name: usernaut
app.kubernetes.io/managed-by: kustomize
name: invalid-consumer-usernaut-test
namespace: usernaut
spec:
group_name: invalid-consumer-usernaut-test
members:
users:
- ruagrawa
- atikande
- subhatta
backends:
- name: fivetran
type: fivetran

17 changes: 17 additions & 0 deletions config/samples/v5_valid_consumer_group.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
apiVersion: operator.dataverse.redhat.com/v1alpha1
kind: Group
metadata:
labels:
app.kubernetes.io/name: usernaut
app.kubernetes.io/managed-by: kustomize
name: dataverse-aggregate-usernaut
namespace: usernaut
spec:
group_name: dataverse-aggregate-usernaut
members:
users:
- ruagrawa
- rmandal
backends:
- name: fivetran
type: fivetran
136 changes: 113 additions & 23 deletions internal/controller/group_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,28 @@ func (r *GroupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
"groups": groupCR.Spec.Members.Groups,
})

// Check if the group is configurable (has matching patterns for its backends)
isConfigurable := r.isGroupConfigurable(groupCR)
Comment thread
rmandal-redhat marked this conversation as resolved.
if !isConfigurable {
r.log.Warn("group is not configurable - no matching patterns found for backends")
// Mark as non-configurable in status
groupCR.Status.ReconciledUsers = []string{}
condition := metav1.Condition{
Type: usernautdevv1alpha1.GroupReadyCondition,
LastTransitionTime: metav1.Now(),
Status: metav1.ConditionFalse,
Message: "Group is not configurable - no matching patterns found in backend configuration",
Reason: "NonConfigurable",
ObservedGeneration: groupCR.Generation,
}
r.setCondition(&groupCR.Status.Conditions, condition)
Comment thread
rmandal-redhat marked this conversation as resolved.
if err := r.Status().Update(ctx, groupCR); err != nil {
r.log.WithError(err).Error("error updating group status for non-configurable group")
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}

var err error
queryMembers := []string{}
if groupCR.Spec.Members.LDAPQuery != nil {
Expand Down Expand Up @@ -697,9 +719,7 @@ func (r *GroupReconciler) handleDeletion(ctx context.Context, groupCR *usernautd
// Clean up user:groups reverse index for all members of this group
r.cleanupUserGroupsIndex(ctx, groupCR.Spec.GroupName)

if err := r.deleteBackendsTeam(ctx, groupCR); err != nil {
return err
}
r.deleteBackendsTeam(ctx, groupCR)

controllerutil.RemoveFinalizer(groupCR, groupFinalizer)
if err := r.Update(ctx, groupCR); err != nil {
Expand Down Expand Up @@ -736,63 +756,97 @@ func (r *GroupReconciler) cleanupUserGroupsIndex(ctx context.Context, groupName
r.log.WithField("group", groupName).Info("cleaned up user groups index successfully")
}

func (r *GroupReconciler) deleteBackendsTeam(ctx context.Context, groupCR *usernautdevv1alpha1.Group) error {
// deleteBackendsTeam performs best-effort backend and cache cleanup during deletion.
// It does not return an error: failures are logged so the finalizer can still be removed.
func (r *GroupReconciler) deleteBackendsTeam(ctx context.Context, groupCR *usernautdevv1alpha1.Group) {
r.log.Info("Finalizer: starting Backends team deletion cleanup")
groupName := groupCR.Spec.GroupName
hasErrors := false

for _, backend := range groupCR.Spec.Backends {
transformedGroupName, err := utils.GetTransformedGroupName(r.AppConfig, backend.Type, groupName)
// Use graceful fallback for deletion - we want to clean up even if pattern doesn't match
transformedGroupName := utils.GetTransformedGroupNameOrFallback(r.AppConfig, backend.Type, groupName)
Comment thread
rmandal-redhat marked this conversation as resolved.
backendLoggerInfo := r.log.WithFields(logrus.Fields{
"group_name": groupName,
"transformed_team_name": transformedGroupName,
"backend": backend.Name,
"backend_type": backend.Type,
})
backendLoggerInfo.Info("Finalizer: Deleting team from backend")
if err != nil {
backendLoggerInfo.WithError(err).Error("Finalizer: Error in transforming group name")
return err
}

backendClient, err := clients.New(backend.Name, backend.Type, r.AppConfig.BackendMap)
if err != nil {
backendLoggerInfo.WithError(err).Errorf("Finalizer: error creating client for backend %s", backend.Name)
return err
backendLoggerInfo.WithError(err).Warnf("Finalizer: error creating client for backend %s, skipping this backend", backend.Name)
hasErrors = true
continue // Skip this backend but continue with others
Comment thread
rmandal-redhat marked this conversation as resolved.
}

// Get team ID from consolidated group store (using original group name)
// NOTE: CacheMutex is already held by caller (handleDeletion)
teamID, err := r.Store.Group.GetBackendID(ctx, groupName, backend.Name, backend.Type)
if err != nil {
backendLoggerInfo.WithError(err).Error("Finalizer: error fetching team details from cache")
return err
backendLoggerInfo.WithError(err).Warn("Finalizer: error fetching team details from cache, team may not have been created")
hasErrors = true
}

// Same resolution order as fetchOrCreateTeam: GroupStore first, then TeamStore (preload) by transformed name
if teamID == "" && transformedGroupName != "" {
backendKey := backend.Name + "_" + backend.Type
teamBackends, tsErr := r.Store.Team.GetBackends(ctx, transformedGroupName)
if tsErr != nil {
backendLoggerInfo.WithError(tsErr).Warn("Finalizer: error fetching team from TeamStore during deletion")
hasErrors = true
} else if id, ok := teamBackends[backendKey]; ok && id != "" {
backendLoggerInfo.WithField("team_id", id).Info("Finalizer: resolved team ID from TeamStore for backend deletion")
teamID = id
}
}

if teamID != "" {
backendLoggerInfo.Infof("Finalizer: Deleting team with (ID: %s) from Backend %s", teamID, backend.Type)

if err := backendClient.DeleteTeamByID(ctx, teamID); err != nil {
backendLoggerInfo.WithError(err).Error("Finalizer: failed to delete team from the backend")
return err
backendLoggerInfo.WithError(err).Warn("Finalizer: failed to delete team from the backend, team may already be deleted")
hasErrors = true
// Continue processing - best effort deletion
Comment thread
rmandal-redhat marked this conversation as resolved.
} else {
backendLoggerInfo.Infof("Finalizer: Successfully deleted team with id '%s' from Backend %s", teamID, backend.Type)
}
} else if strings.EqualFold(backend.Type, "snowflake") && transformedGroupName != "" {
// Snowflake uses the role name as the REST identifier (see snowflake.CreateTeam / DeleteTeamByID).
roleName := strings.ToLower(transformedGroupName)
backendLoggerInfo.WithField("role_name", roleName).Info("Finalizer: no cached team ID; attempting Snowflake role delete by name")
if err := backendClient.DeleteTeamByID(ctx, roleName); err != nil {
backendLoggerInfo.WithError(err).Warn("Finalizer: Snowflake delete by role name failed; role may not exist or actual name may differ (e.g. pattern changed since create)")
hasErrors = true
} else {
backendLoggerInfo.Infof("Finalizer: Successfully deleted Snowflake role '%s'", roleName)
}
backendLoggerInfo.Infof("Finalizer: Successfully deleted team with id '%s' from Backend %s", teamID, backend.Type)
} else {
backendLoggerInfo.Info("Finalizer: No team ID found in cache, skipping backend deletion")
}

// Delete team entry from TeamStore (used for preload lookups)
if err := r.Store.Team.Delete(ctx, transformedGroupName); err != nil {
backendLoggerInfo.WithError(err).Warn("Finalizer: failed to delete team from TeamStore cache")
// Continue processing - TeamStore is secondary cache
if transformedGroupName != "" {
if err := r.Store.Team.Delete(ctx, transformedGroupName); err != nil {
backendLoggerInfo.WithError(err).Warn("Finalizer: failed to delete team from TeamStore cache")
// Continue processing - TeamStore is secondary cache
}
}
}

// Delete the entire group entry from cache (includes all backends and members)
if err := r.Store.Group.Delete(ctx, groupName); err != nil {
r.log.WithError(err).Error("Finalizer: failed to delete group from cache")
return err
r.log.WithError(err).Warn("Finalizer: failed to delete group from cache, may already be deleted")
hasErrors = true
Comment thread
rmandal-redhat marked this conversation as resolved.
// Don't return error - allow finalizer to complete
} else {
r.log.WithField("group", groupName).Info("Finalizer: Successfully deleted group from cache")
}
r.log.WithField("group", groupName).Info("Finalizer: Successfully deleted group from cache")

return nil
if hasErrors {
r.log.Warn("Finalizer: completed with some errors, but allowing deletion to proceed as it is a best-effort cleanup")
}
Comment thread
rmandal-redhat marked this conversation as resolved.
Comment thread
rmandal-redhat marked this conversation as resolved.
}

func (r *GroupReconciler) processUsers(ctx context.Context,
Expand Down Expand Up @@ -980,6 +1034,42 @@ func (r *GroupReconciler) fetchOrCreateTeam(ctx context.Context,
return newTeam.ID, nil
}

// isGroupConfigurable checks if a group has matching patterns for all its backends
// A group is considered configurable if at least one backend has a pattern that matches the group name
func (r *GroupReconciler) isGroupConfigurable(groupCR *usernautdevv1alpha1.Group) bool {
if len(groupCR.Spec.Backends) == 0 {
// No backends specified, consider it non-configurable
return false
}

for _, backend := range groupCR.Spec.Backends {
_, err := utils.GetTransformedGroupName(r.AppConfig, backend.Type, groupCR.Spec.GroupName)
if err == nil {
// At least one backend has a matching pattern
return true
}
}

// No backends have matching patterns
return false
}
Comment thread
rmandal-redhat marked this conversation as resolved.

// setCondition updates or adds a condition to the condition slice
func (r *GroupReconciler) setCondition(conditions *[]metav1.Condition, newCondition metav1.Condition) {
if conditions == nil {
*conditions = []metav1.Condition{}
}

for i, cond := range *conditions {
if cond.Type == newCondition.Type {
(*conditions)[i] = newCondition
return
}
}

*conditions = append(*conditions, newCondition)
}
Comment thread
rmandal-redhat marked this conversation as resolved.

// SetupWithManager sets up the controller with the Manager.
func (r *GroupReconciler) SetupWithManager(mgr ctrl.Manager) error {
// Add an index field for referenced groups
Expand Down
Loading