organization

package
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Sep 16, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Overview

Package organization implements the bootstrap controller that gives every User a personal Organization on creation (per docs/organizations.md §Personal Org). It reconciles two kinds in root:railgrid:users:

  • User: when a User has no status.personalOrg, the controller creates a personal Organization for them and patches status.personalOrg with the new UUID. Idempotent: re-running on the same User is a no-op once status.personalOrg is set.

  • Organization: ensures status.workspacePath is set to the canonical `root:railgrid:orgs:{metadata.name}` once and only once. The actual kcp Workspace at that path is NOT created by this PR — that lands in PR #2 when the `organization` WorkspaceType is registered. Until then the controller leaves a WorkspaceReady=False condition with reason AwaitingWorkspaceType so observers know the Organization is half-baked by design.

Scope as of PR #4:

  • User → Organization bootstrap (PR #1).
  • kcp Workspace creation at root:railgrid:orgs:{uuid} of type `organization`, idempotent + self-healing per O-11 (PR #2).
  • Admin Membership write inside the Org workspace + UserMembershipIndex entry sync (PR #4). The reconciler is now a four-step state machine: WorkspaceReady → MembershipReady → IndexSynced → Ready.

NOT yet:

  • Full multi-cluster Membership controller that watches user-added Memberships and reflects them in the index. PR #4 handles the personal-Org bootstrap path inline; manual Org / Workspace membership management lands with the portal REST surface.
  • User-facing RBAC inside the Org workspace (Org workspaces are hub-mediated only per O-10; no per-User kubeconfig is ever issued).

Index

Constants

View Source
const AnnotationCatalogEntryCreationMigrated = "tenants.railgrid.ai/catalog-entry-creation-migrated"

AnnotationCatalogEntryCreationMigrated marks an Organization the one-time catalogEntryCreation backfill has visited. Its absence is what identifies an Organization that predates the admin default.

Variables

This section is empty.

Functions

func BackfillCatalogEntryCreation

func BackfillCatalogEntryCreation(ctx context.Context, reader client.Reader, writer client.Client) (int, error)

BackfillCatalogEntryCreation pins the pre-flip behaviour onto every Organization created before spec.catalogEntryCreation defaulted to admin.

The hub used to read an empty field as "members"; it now reads it as "admin". Organizations that never set the field relied on the old reading, so each one without the migrated annotation gets an explicit "members" written — an Organization that already carries a value keeps it — and the annotation stamped, after which the backfill never touches it again. New Organizations are created with an explicit value and are simply stamped on first sight.

reader supplies the Organization list and MUST NOT be cache-backed: an empty list from a cold cache is indistinguishable from an empty cluster, and this backfill treats "nothing to do" as final. writer applies the patches.

Returns the number of Organizations updated. Errors are returned after the first failed write; the caller retries, and every write is idempotent.

func NewManager

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

NewManager constructs a controller-runtime manager bound to a single workspace's rest.Config (typically the root:railgrid:users workspace returned by Bootstrapper.UsersConfig). The hub server calls this and runs the manager in a goroutine alongside the multicluster managers.

func SetupWithManager

func SetupWithManager(mgr manager.Manager, provisioner WorkspaceProvisioner) error

SetupWithManager registers the User and Organization watches with mgr. provisioner is invoked from the status-reconcile step to materialize the kcp Workspace at root:railgrid:orgs:{uuid}. Pass nil only for tests that don't exercise the workspace-creation path.

The controller is keyed on User (the trigger for personal-Org creation) and additionally watches Organization so existing Organizations whose status is stale (missing workspacePath, missing conditions, or whose workspace creation previously failed) get reconciled too. Both kinds map to the User key — for User watches the caller's name; for Organization watches, the user identified by status.personalOrg back-reference.

Types

type Reconciler

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

Reconciler bootstraps personal Organizations for new Users and reconciles Organization status. See package doc for scope.

func (*Reconciler) Reconcile

func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error)

Reconcile drives both flows: User → Organization bootstrap, and Organization status backfill. Request.Name is the User CR name; mapping from Organization watches uses status.personalOrg to find the User.

type WorkspaceProvisioner

type WorkspaceProvisioner interface {
	// EnsureOrgWorkspace materializes the kcp Workspace at
	// root:railgrid:orgs:{orgUUID}. Idempotent (per O-11).
	EnsureOrgWorkspace(ctx context.Context, orgUUID string) error

	// EnsureOrgMembership creates an org-scope Membership CR inside the
	// Organization workspace granting userName the given role.
	// Idempotent.
	EnsureOrgMembership(ctx context.Context, orgUUID, userName, role string) error

	// EnsureChildWorkspace materializes the kcp Workspace at
	// root:railgrid:orgs:{orgUUID}:{wsUUID} of type `workspace`. Used to
	// create the user's default team Workspace inside their personal
	// Org so the portal can pin a default X-Railgrid-Workspace header.
	// Idempotent (per O-11).
	EnsureChildWorkspace(ctx context.Context, orgUUID, wsUUID string) error

	// EnsureChildWorkspaceDisplayName stamps the display-name
	// annotation on the child Workspace when none is set yet, so the
	// default workspace surfaces under a human-readable name instead
	// of its UUID. Never overwrites an existing name — a user rename
	// must survive reconciles. Idempotent.
	EnsureChildWorkspaceDisplayName(ctx context.Context, orgUUID, wsUUID, displayName string) error

	// EnsureChildWorkspaceRailgridBinding materializes an APIBinding to
	// root:railgrid:providers.core.railgrid.ai inside the child team
	// Workspace, with the permission claims railgrid controllers need.
	// This is what makes Edge / MCPServer / Placement / VirtualWorkload
	// usable inside the user's default Workspace. Idempotent.
	EnsureChildWorkspaceRailgridBinding(ctx context.Context, orgUUID, wsUUID string) error

	// EnsureChildWorkspaceAdmin grants cluster-admin to the User in
	// the child team Workspace via a ClusterRoleBinding for the User's
	// rbacIdentity. Idempotent.
	EnsureChildWorkspaceAdmin(ctx context.Context, orgUUID, wsUUID, rbacIdentity string) error

	// ListChildTeamWorkspaces lists the Org's team workspaces (no
	// infrastructure children). Used by the RBAC backfill to bind an
	// org-scope admin into every child workspace (O-15).
	ListChildTeamWorkspaces(ctx context.Context, orgUUID string) ([]string, error)

	// EnsureChildWorkspaceDefaultMCPServer seeds the "default"
	// MCPServer CR inside the child team Workspace so users have a
	// working MCP endpoint out of the box. Idempotent.
	EnsureChildWorkspaceDefaultMCPServer(ctx context.Context, orgUUID, wsUUID string) error

	// GetChildWorkspaceClusterName returns the kcp logical-cluster
	// short hash for the child team Workspace. Used by Step J to patch
	// User.spec.DefaultCluster with the hash form (which kubectl /
	// /clusters/{hash} address by) rather than the full path.
	GetChildWorkspaceClusterName(ctx context.Context, orgUUID, wsUUID string) (string, error)
}

WorkspaceProvisioner is the slice of the kcp Bootstrapper that this controller needs to materialize Organization workspaces, the default child Workspace inside each personal Org, and the two scope-flavored Memberships inside them. Pulled out as an interface so unit tests can use a fake (see controller_test.go) without standing up an embedded kcp.

Implemented by *pkg/hub/kcp.Bootstrapper.

Jump to

Keyboard shortcuts

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