softdelete

package
v0.2.38 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 2, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Overview

Package softdelete implements roadmap step 8: the soft-delete reconciler that honours the 30-day grace window from docs/organizations.md decisions O-8 (User delete) and O-13 (Org + Workspace delete).

One controller, three reconcilers:

  • User: watches User CRs; when status.deletionRequestedAt is set and the grace window has elapsed, drives the User cascade (personal Org → UMI → User CR).

  • Organization: watches Organization CRs; when status.deletionRequestedAt is set and the grace window has elapsed, drives the Org cascade (child Workspaces → in-Org Memberships → Org workspace → Org CR), stripping the org-scope UMI entries of every member as it goes.

  • Workspace: kcp Workspaces aren't in our scheme, so this branch polls (default every minute) — lists Org workspaces, lists each Org's child Workspaces, reads the annotation tenants.railgrid.ai/deletion-requested-at and, once the grace window expires — or at once when the companion annotation tenants.railgrid.ai/deletion-forced is set — drives the Workspace cascade (kcp Workspace + workspace-scope UMI rows).

Inside the grace window the reconciler does NOT delete anything — it surfaces a DeletionInProgress condition on the parent CR and marks the corresponding UserMembershipIndex rows with softDeletedAt so the portal switcher (step 10) hides them. Undelete (the deletionRequestedAt / annotation being cleared back to nil) clears the marker and the condition; nothing brings back resources that the post-window cascade has already removed. A forced Workspace deletion is the one exception: it has no window, and the undelete paths refuse to clear it.

Index

Constants

View Source
const (
	// DefaultGracePeriod is the soft-delete window per O-8 / O-13 when
	// the hub is not told otherwise. One number covers Users, Orgs and
	// Workspaces alike; the hub's --soft-delete-grace-period flag (the
	// chart's hub.softDeleteGracePeriod) overrides it for every kind at
	// once, and the REST undelete handlers read the same value so a
	// restore is refused exactly when the cascade would have started.
	// Matches the wording in docs/organizations.md.
	DefaultGracePeriod = 30 * 24 * time.Hour
)

Variables

This section is empty.

Functions

func NewManager

func NewManager(cfg *rest.Config, scheme *runtime.Scheme) (manager.Manager, error)

NewManager constructs a controller-runtime manager bound to the users workspace's rest.Config (typically the same one the organization bootstrap controller uses). Separated from the bootstrap controller's manager so soft-delete restarts don't take the bootstrap workqueue down.

func SetupWithManager

func SetupWithManager(mgr manager.Manager, provisioner Provisioner, gracePeriod time.Duration, opts ...Option) error

SetupWithManager registers the User and Organization watches with mgr and kicks off the Workspace-branch poll goroutine. The Workspace branch needs the manager's lifecycle (it stops when the manager's context cancels) which is why we run it as a Runnable instead of an independent goroutine in server.go.

gracePeriod is the recovery window; a non-positive value selects DefaultGracePeriod.

Types

type Option added in v0.2.34

type Option func(*Reconciler)

Option configures the Reconciler SetupWithManager builds.

func WithWorkspaceObserver added in v0.2.34

func WithWorkspaceObserver(fn func(ctx context.Context, orgUUID string)) Option

WithWorkspaceObserver registers fn, called with the organization UUID after the workspace cascade has asked kcp to delete one of its child Workspaces, so a derived view of an organization's workspaces (the metering membership report) follows the deletion at once instead of on its periodic re-list. The organization cascade does not call it: the Organization itself is going, and the view's owner removes its objects on that. fn runs synchronously in the sweep with its context.

type Provisioner

type Provisioner interface {
	// DeleteOrgWorkspace removes the kcp Workspace at
	// root:railgrid:orgs:{orgUUID}. Idempotent on NotFound.
	DeleteOrgWorkspace(ctx context.Context, orgUUID string) error

	// DeleteChildWorkspace removes the kcp Workspace at
	// root:railgrid:orgs:{orgUUID}:{wsUUID}. Idempotent on NotFound.
	// It reports true when the Workspace was live and this call asked kcp
	// to delete it, false when it was already terminating or gone, so a
	// sweep that finds a workspace still held by its finalizers does not
	// treat it as a fresh change.
	DeleteChildWorkspace(ctx context.Context, orgUUID, wsUUID string) (deleted bool, err error)

	// ListChildWorkspaces returns the names of every child Workspace
	// inside the Org workspace at root:railgrid:orgs:{orgUUID}. Empty if
	// the parent Org workspace has been deleted.
	ListChildWorkspaces(ctx context.Context, orgUUID string) ([]string, error)

	// ListOrgWorkspaces returns the names (UUIDs) of every
	// Organization workspace at root:railgrid:orgs. Drives the
	// Workspace-branch poll sweep.
	ListOrgWorkspaces(ctx context.Context) ([]string, error)

	// GetWorkspaceDeletionRequestedAt reads the
	// tenants.railgrid.ai/deletion-requested-at annotation from the
	// child Workspace. The second return reports presence — callers
	// can distinguish "no soft-delete requested" from "annotation
	// present but malformed".
	GetWorkspaceDeletionRequestedAt(ctx context.Context, orgUUID, wsUUID string) (*time.Time, bool, error)

	// GetWorkspaceDeletionForced reports whether the child Workspace
	// carries tenants.railgrid.ai/deletion-forced: its admin asked for
	// deletion without the recovery window, so the cascade runs on the
	// next sweep however much of the grace period is left.
	GetWorkspaceDeletionForced(ctx context.Context, orgUUID, wsUUID string) (bool, error)

	// DeleteOrgMemberships removes every Membership CR inside the
	// Organization workspace. Run during Org cascade so UMI strips
	// see a clean delta.
	DeleteOrgMemberships(ctx context.Context, orgUUID string) error

	// ListOrgMemberships returns the user names (Membership.name) of
	// every Membership in the Organization workspace. Used inside the
	// grace window to mark every member's UMI rows, and during cascade
	// to know which UMIs to strip.
	ListOrgMemberships(ctx context.Context, orgUUID string) ([]string, error)
}

Provisioner is the slice of the kcp Bootstrapper that the soft-delete reconciler needs to inspect and tear down workspaces. Pulled out as an interface so unit tests use a fake without standing up embedded kcp; mirrors the WorkspaceProvisioner pattern in pkg/hub/controllers/organization.

Implemented by *pkg/hub/kcp.Bootstrapper.

type Reconciler

type Reconciler struct {
	// contains filtered or unexported fields
}

Reconciler is the shared receiver for the three soft-delete reconcile entry points. The User and Organization branches are watch-driven via controller-runtime; the Workspace branch is poll-driven via runWorkspacePoll(ctx).

func (*Reconciler) GracePeriod added in v0.2.31

func (r *Reconciler) GracePeriod() time.Duration

GracePeriod returns the configured recovery window, falling back to DefaultGracePeriod when the reconciler was built without one.

type WorkspaceWatcher added in v0.2.38

type WorkspaceWatcher interface {
	// WatchChildWorkspaces opens a watch on the child Workspaces of
	// root:railgrid:tenants:{orgUUID}. The watch replays every existing
	// Workspace as ADDED when it opens, which is how a restart catches up.
	WatchChildWorkspaces(ctx context.Context, orgUUID string) (watch.Interface, error)
}

WorkspaceWatcher is the half of Provisioner that lets the Workspace branch run on watch events: one watch per organization on its child Workspaces, instead of listing every organization's children on a timer. The Bootstrapper implements it; a provisioner that cannot (tests) keeps the sweep.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL