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
11 changes: 11 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/go-core-stack/auth-gateway/pkg/apidocs"
"github.com/go-core-stack/auth-gateway/pkg/auth"
"github.com/go-core-stack/auth-gateway/pkg/config"
"github.com/go-core-stack/auth-gateway/pkg/controller/orgunit"
"github.com/go-core-stack/auth-gateway/pkg/controller/request"
"github.com/go-core-stack/auth-gateway/pkg/controller/roledef"
"github.com/go-core-stack/auth-gateway/pkg/controller/tenant"
Expand Down Expand Up @@ -544,6 +545,16 @@ func main() {
log.Panicf("failed to create email verification cleanup controller: %s", err)
}

// Start org-unit cleanup reconciler only when soft-delete is enabled.
// The reconciler handles hold-period expiry and hard-delete of
// soft-deleted org-units.
if conf.GetExperimental().AllowOUDelete {
_, err = orgunit.NewOrgUnitCleanupController(conf.GetExperimental())
if err != nil {
log.Panicf("failed to create org-unit cleanup controller: %s", err)
}
}

// role definition manager
resourceMgr := roledef.NewResourceManager()

Expand Down
92 changes: 92 additions & 0 deletions pkg/controller/orgunit/reconciler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright © 2025-2026 Prabhjot Singh Sethi, All Rights reserved
// Author: Prabhjot Singh Sethi <prabhjot.sethi@gmail.com>

package orgunit

import (
"context"
"log"
"time"

"github.com/go-core-stack/auth-gateway/pkg/config"
"github.com/go-core-stack/auth-gateway/pkg/table"
"github.com/go-core-stack/core/errors"
"github.com/go-core-stack/core/reconciler"
)

// OrgUnitCleanupController manages the lifecycle of soft-deleted org-units,
// hard-deleting them after the configured hold period expires.
type OrgUnitCleanupController struct {
tbl *table.OrgUnitTable
holdDuration int
}

type orgUnitReconciler struct {
reconciler.Controller
ctrl *OrgUnitCleanupController
}

func (r *orgUnitReconciler) Reconcile(k any) (*reconciler.Result, error) {
ctx := context.Background()
key := k.(*table.OrgUnitKey)

entry, err := r.ctrl.tbl.Find(ctx, key)
if err != nil {
if !errors.IsNotFound(err) {
// transient error — requeue with backoff
return &reconciler.Result{RequeueAfter: 5 * time.Second}, nil
}
// entry already gone — nothing to do
return &reconciler.Result{}, nil
}

// only process soft-deleted entries
if entry.Deleted == 0 {
return &reconciler.Result{}, nil
}

holdExpiry := entry.Deleted + int64(r.ctrl.holdDuration)
now := time.Now().Unix()

if holdExpiry > now {
// hold period has not expired — requeue after remaining time (+1s buffer)
remaining := holdExpiry - now
return &reconciler.Result{RequeueAfter: time.Duration(remaining+1) * time.Second}, nil
}

// hold period expired — hard-delete the entry
err = r.ctrl.tbl.DeleteKey(ctx, key)
if err != nil && !errors.IsNotFound(err) {
// transient error — requeue with backoff
log.Printf("orgunit reconciler: failed to hard-delete org-unit %s: %s", key.ID, err)
return &reconciler.Result{RequeueAfter: 5 * time.Second}, nil
}

log.Printf("orgunit reconciler: hard-deleted org-unit %s after hold period", key.ID)
return &reconciler.Result{}, nil
}

// NewOrgUnitCleanupController creates and registers the org-unit reconciler.
// It should only be called when experimental.allow_ou_delete is enabled.
func NewOrgUnitCleanupController(experimental config.ExperimentalConfig) (*OrgUnitCleanupController, error) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
tbl, err := table.GetOrgUnitTable()
if err != nil {
return nil, err
}

ctrl := &OrgUnitCleanupController{
tbl: tbl,
holdDuration: experimental.HoldDeletedOU,
}

r := &orgUnitReconciler{
ctrl: ctrl,
}

err = tbl.Register("OrgUnitCleanupController", r)
if err != nil {
return nil, err
}

return ctrl, nil
}
26 changes: 26 additions & 0 deletions pkg/table/org-unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ package table

import (
"context"
"log"

"go.mongodb.org/mongo-driver/v2/bson"

"github.com/go-core-stack/core/db"
"github.com/go-core-stack/core/errors"
Expand Down Expand Up @@ -65,6 +68,29 @@ func (t *OrgUnitTable) FindByTenant(ctx context.Context, tenant, ouId string) ([
return t.FindMany(ctx, filter, 0, 0)
}

// ReconcilerGetAllKeys returns keys for all soft-deleted org-unit entries
// (deleted > 0). This overrides the generic Table.ReconcilerGetAllKeys to
// ensure the reconciler only bootstraps entries that are pending hard-delete.
func (t *OrgUnitTable) ReconcilerGetAllKeys() []any {
type keyOnly struct {
Key OrgUnitKey `bson:"_id,omitempty"`
}

filter := bson.M{"deleted": bson.M{"$gt": 0}}

list := []keyOnly{}
err := t.col.FindMany(context.Background(), filter, &list)
if err != nil {
log.Panicf("orgunit: failed to fetch deleted keys: %s", err)
}

keys := make([]any, 0, len(list))
for _, k := range list {
keys = append(keys, &k.Key)
}
return keys
}

func (t *OrgUnitTable) StartEventLogger() error {
logger := db.NewEventLogger[OrgUnitKey, OrgUnitEntry](t.col, nil)
return logger.Start(context.Background())
Expand Down
Loading