kcp

package
v0.2.13 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: Apache-2.0 Imports: 42 Imported by: 0

Documentation

Overview

Package kcp bootstraps kcp API resources.

Index

Constants

View Source
const (
	// EdgeRouteWorkspaceAnnotation is the team workspace holding the edge.
	EdgeRouteWorkspaceAnnotation = "edges.railgrid.ai/route-workspace"
	// EdgeRouteEdgeAnnotation is the KubernetesCluster edge's name.
	EdgeRouteEdgeAnnotation = "edges.railgrid.ai/route-edge"
)

Edge routing for org-owned providers.

A self-hosted provider's control half dials out — it reaches kcp itself, so Templates and Instances reconcile with no help. Its DATA half is the problem: /services/providers/{name}/** is a hub→provider call, and a provider in a tenant's own cluster has no address the hub can dial. The edge tunnel is the transport for that one hop (docs/byo-provider-edge-transport.md).

Routing must not be readable from anything the provider controls, or a tenant could point platform traffic wherever they liked (E-1). So the edge is recorded by the HUB, at registration, as annotations on the provider Workspace — an object in the org's tree that only the hub writes.

View Source
const HubSystemNamespace = "railgrid-hub"

HubSystemNamespace is the namespace inside root:railgrid:system:controllers that holds the hub's cross-replica coordination state: the controller leader-election Lease and the shared browser-session / app-access-code Secrets. It is hub-internal — no tenant, provider, or user identity is ever granted access to system:controllers.

View Source
const WorkspaceDeletionAnnotation = "tenants.railgrid.ai/deletion-requested-at"

WorkspaceDeletionAnnotation is the annotation key used to mark a kcp Workspace as soft-deleted. The soft-delete reconciler (roadmap step 8) reads this on every reconcile and triggers the cascade once the 30-day grace window from the annotation's RFC3339 value has elapsed. kept on the kcp Workspace (rather than a railgrid wrapper CRD) because the kcp Workspace IS the source of truth for workspace lifecycle.

View Source
const WorkspaceDisplayNameAnnotation = "tenants.railgrid.ai/display-name"

WorkspaceDisplayNameAnnotation is the annotation key used to mark the human-facing display name on a kcp Workspace. v1 stores it as an annotation rather than a separate CRD field because kcp's Workspace type doesn't carry a displayName slot. Editable via the REST PATCH endpoint.

Variables

This section is empty.

Functions

func AppendClusterPath deprecated

func AppendClusterPath(host, clusterPath string) string

AppendClusterPath sets the /clusters/<path> segment on a kcp URL. If the host already contains a /clusters/ path (e.g. from the admin kubeconfig), it is replaced rather than appended.

Deprecated: use apiurl.KCPClusterURL directly.

func ProviderEdgeServiceName

func ProviderEdgeServiceName(providerName string) string

ProviderEdgeServiceName is the hub-owned Service standing in front of one org-owned provider. Derived from the provider name so the hub can always name it without a lookup, and prefixed so it cannot collide with a Service a tenant created for an appliance.

func ValidateProviders

func ValidateProviders(enabled []string) error

ValidateProviders is a thin re-export of providers.ResolveEnabledBuiltins that discards the resolved spec list. Used at process start (server.Run) to fail fast on a bad --providers flag BEFORE embedded kcp boots — callers in this package import providers anyway for the registry, so the indirection only saves callers in pkg/hub from learning about the providers.BuiltinSpec type when they just want a yes/no answer.

Types

type AppAccessGrant

type AppAccessGrant struct {
	// Binding is the ClusterRoleBinding name (the revocation handle).
	Binding string `json:"binding"`
	// App is the published instance name the grant opens.
	App string `json:"app"`
	// User is the platform User metadata.name the grant was issued to.
	User string `json:"user"`
	// CreatedAt is the binding's creation timestamp.
	CreatedAt time.Time `json:"createdAt"`
}

AppAccessGrant is the portal-facing view of one published-app invitation: plain workspace RBAC, surfaced so tenant settings can show who can open which private app.

type Bootstrapper

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

Bootstrapper sets up the kcp workspace hierarchy and API exports.

func NewBootstrapper

func NewBootstrapper(config *rest.Config) *Bootstrapper

NewBootstrapper creates a new bootstrapper.

func (*Bootstrapper) Bootstrap

func (b *Bootstrapper) Bootstrap(ctx context.Context) error

Bootstrap creates the workspace hierarchy:

root:railgrid                          - Root railgrid workspace
root:railgrid:providers                - Parent of per-provider sub-workspaces
  root:railgrid:providers:{name}       - One provider (restricted `provider` type)
root:railgrid:tenants:{uuid}:{ws}:{edge}  - Tenant org/team/edge fleet
root:railgrid:system:controllers       - ALL platform APIExports + schemas
root:railgrid:system:providers         - Provider + CatalogEntry objects
root:railgrid:system:tenants           - User/Organization/Membership objects

func (*Bootstrapper) ChildWorkspaceConfig

func (b *Bootstrapper) ChildWorkspaceConfig(orgUUID, wsUUID string) *rest.Config

ChildWorkspaceConfig returns a rest.Config targeting the child Workspace at root:railgrid:tenants:{orgUUID}:{wsUUID}. Used by REST endpoints that operate inside a Workspace (e.g. the ServiceAccount surface) so they can mint a typed kube clientset without rebuilding path strings themselves.

func (*Bootstrapper) ClearWorkspaceDeletionAnnotation

func (b *Bootstrapper) ClearWorkspaceDeletionAnnotation(ctx context.Context, orgUUID, wsUUID string) error

ClearWorkspaceDeletionAnnotation removes the soft-delete annotation from the Workspace, signalling undelete. Idempotent on already-absent. It performs one conditional update and returns conflicts to the REST layer, which re-reads the annotation before deciding whether a retry is safe. That boundary prevents a stale clear from deleting a newer deletion request.

func (*Bootstrapper) ControllersConfig

func (b *Bootstrapper) ControllersConfig() *rest.Config

ControllersConfig returns a rest.Config targeting root:railgrid:system:controllers, where the platform APIExports and the hub's own coordination objects live.

func (*Bootstrapper) CreateMCPServer

func (b *Bootstrapper) CreateMCPServer(ctx context.Context, clusterName, name, displayName, instructions string, readOnly bool) error

CreateMCPServer creates an MCPServer CR. The reconciler provisions identity.

func (*Bootstrapper) DeleteChildWorkspace

func (b *Bootstrapper) DeleteChildWorkspace(ctx context.Context, orgUUID, wsUUID string) error

DeleteChildWorkspace removes the kcp Workspace at root:railgrid:tenants:{orgUUID}:{wsUUID}. Idempotent on NotFound.

func (*Bootstrapper) DeleteMCPServer

func (b *Bootstrapper) DeleteMCPServer(ctx context.Context, clusterName, name, expectedUID string) error

DeleteMCPServer removes an MCPServer; its identity objects GC via owner refs.

func (*Bootstrapper) DeleteOrgMembership

func (b *Bootstrapper) DeleteOrgMembership(ctx context.Context, orgUUID, userName string) error

DeleteOrgMembership removes a single Membership CR from the Org workspace. Idempotent on NotFound.

func (*Bootstrapper) DeleteOrgMemberships

func (b *Bootstrapper) DeleteOrgMemberships(ctx context.Context, orgUUID string) error

DeleteOrgMemberships removes every Membership CR inside the Organization workspace at root:railgrid:tenants:{orgUUID}. Used by the soft-delete cascade right before tearing down the workspace itself, so the index sync sees a clean delta. Idempotent on NotFound / empty list.

func (*Bootstrapper) DeleteOrgProviderWorkspace

func (b *Bootstrapper) DeleteOrgProviderWorkspace(ctx context.Context, orgUUID, name string) error

DeleteOrgProviderWorkspace deletes one org-owned provider workspace. kcp cascades everything inside it: the ServiceAccount and its token, the APIExport, the APIResourceSchemas, and the CatalogEntry — which is what removes the provider from the catalog registry. Idempotent (NotFound is a no-op).

APIBindings that tenant workspaces already hold against the deleted export are NOT removed here; per kcp's semantics they go NotReady. Callers that want a clean teardown should Disable the provider in each workspace first.

func (*Bootstrapper) DeleteOrgWorkspace

func (b *Bootstrapper) DeleteOrgWorkspace(ctx context.Context, orgUUID string) error

DeleteOrgWorkspace removes the kcp Workspace at root:railgrid:tenants:{orgUUID}. Idempotent on NotFound. Cascade callers should ensure all child Workspaces and the in-workspace Memberships have already been removed; kcp will delete the LogicalCluster.

func (*Bootstrapper) DeleteProviderAPIBinding

func (b *Bootstrapper) DeleteProviderAPIBinding(ctx context.Context, orgUUID, wsUUID, bindingName string) error

DeleteProviderAPIBinding removes the named provider APIBinding from the child workspace root:railgrid:tenants:{orgUUID}:{wsUUID}. NotFound is a no-op so the Disable action is idempotent. Counterpart to EnsureProviderAPIBinding.

func (*Bootstrapper) EnsureChildWorkspace

func (b *Bootstrapper) EnsureChildWorkspace(ctx context.Context, orgUUID, wsUUID string) error

EnsureChildWorkspace materializes a kcp Workspace at root:railgrid:tenants:{orgUUID}:{wsUUID} of type `workspace` (see config/kcp/workspacetype-workspace.yaml). Used by the organization bootstrap controller to create the User's default team Workspace inside their personal Org so the portal can pin a default X-Railgrid-Workspace header. Idempotent: returns nil on AlreadyExists and blocks until the workspace reports Ready.

The hub-mediated rule from O-10 only applies to the Organization workspace itself; the child team Workspace IS tenant-accessible. This method is invoked from the org bootstrap controller with the hub's admin credentials so the WorkspaceType admission's bind check against tenants.railgrid.ai passes (same chain that already powers EnsureOrgWorkspace).

func (*Bootstrapper) EnsureChildWorkspaceAdmin

func (b *Bootstrapper) EnsureChildWorkspaceAdmin(ctx context.Context, orgUUID, wsUUID, rbacIdentity string) error

EnsureChildWorkspaceAdmin grants cluster-admin in the child team Workspace to the given rbacIdentity. Thin wrapper over EnsureWorkspaceAdmin with the canonical child-workspace path. Idempotent.

func (*Bootstrapper) EnsureChildWorkspaceDefaultMCPServer

func (b *Bootstrapper) EnsureChildWorkspaceDefaultMCPServer(ctx context.Context, orgUUID, wsUUID string) error

EnsureChildWorkspaceDefaultMCPServer seeds the "default" MCPServer CR inside the child team Workspace. Thin wrapper over EnsureDefaultMCPServer with the canonical child-workspace path. Idempotent.

func (*Bootstrapper) EnsureChildWorkspaceDisplayName

func (b *Bootstrapper) EnsureChildWorkspaceDisplayName(ctx context.Context, orgUUID, wsUUID, displayName string) error

EnsureChildWorkspaceDisplayName stamps the display-name annotation only when the Workspace does not carry one yet. Reconcilers use this to give bootstrap-provisioned workspaces a human-readable default without clobbering a rename the user made since — the absence check runs inside the update loop, so a rename that lands mid-flight wins.

func (*Bootstrapper) EnsureChildWorkspaceRailgridBinding

func (b *Bootstrapper) EnsureChildWorkspaceRailgridBinding(ctx context.Context, orgUUID, wsUUID string) error

EnsureChildWorkspaceRailgridBinding creates an APIBinding to root:railgrid:providers.core.railgrid.ai inside the child team Workspace, accepting the permission claims railgrid controllers need. This is what makes Edge, MCPServer, Placement, VirtualWorkload usable inside the user's default Workspace.

The legacy tenant-workspace path (CreateTenantWorkspace) used to create the same binding inside root:railgrid:tenants:{userID}. PR #211 retires that flow; the bootstrap controller now drives this method for every personal-Org default Workspace.

The tenancy.kcp.io `workspaces` claim IS accepted here. It does not widen the tenant user's own RBAC — a permission claim grants the APIExport's controllers (railgrid, running over the core.railgrid.ai virtual workspace) access to Workspace objects inside this child Workspace. The edge mount reconciler needs it: it creates an `edge`-typed mount Workspace per kubernetes Edge and watches it via Owns(&Workspace{}) (pkg/hub/controllers/edge/mount_reconciler.go). The core.railgrid.ai APIExport already declares this claim (config/kcp/apiexport-core.railgrid.ai.yaml); leaving it unaccepted is what produced the "exported but not specified" reconcile warnings on the binding.

Preventing tenants from creating arbitrary child workspaces is a SEPARATE control, enforced by the `workspace` WorkspaceType (limitAllowedChildren caps children to the leaf `edge` type — see config/kcp/workspacetype-workspace.yaml), not by withholding this claim.

func (*Bootstrapper) EnsureDefaultMCPServer

func (b *Bootstrapper) EnsureDefaultMCPServer(ctx context.Context, clusterName string) error

EnsureDefaultMCPServer creates a "default" MCPServer CR in the tenant workspace if absent, so every tenant has one ready-to-use endpoint. The in-core reconciler provisions its identity. Idempotent.

func (*Bootstrapper) EnsureHubSystemNamespace

func (b *Bootstrapper) EnsureHubSystemNamespace(ctx context.Context) error

EnsureHubSystemNamespace creates HubSystemNamespace in system:controllers. Idempotent on AlreadyExists, so every replica can call it concurrently at startup.

func (*Bootstrapper) EnsureOrgMembership

func (b *Bootstrapper) EnsureOrgMembership(ctx context.Context, orgUUID, userName, role string) error

EnsureOrgMembership creates a Membership CR inside the Organization workspace at root:railgrid:tenants:{orgUUID} granting the given User the given role at scope=org. Idempotent — returns nil if a Membership with the same metadata.name already exists, regardless of role drift (an admin demoting a member is owned by a separate Role-patch endpoint per O-12 and never comes through this path).

metadata.name = userName so the existence check is a cheap Get on a known key, not a List+filter. PR #4 ships only the bootstrap path (personal-Org admin Membership for the User); manual Org membership management lands in PR #10.

func (*Bootstrapper) EnsureOrgProviderWorkspace

func (b *Bootstrapper) EnsureOrgProviderWorkspace(ctx context.Context, orgUUID, name string) (string, error)

EnsureOrgProviderWorkspace materializes one org-owned provider workspace at root:railgrid:tenants:{orgUUID}:providers:{name}, creating the parent `providers` workspace first if needed. Blocks until Ready and returns the workspace's logical cluster ID (Workspace.spec.cluster) — the same value kcp embeds in the provider SA's token claims, which callers need to build qualified RBAC subjects. Idempotent.

func (*Bootstrapper) EnsureOrgProvidersWorkspace

func (b *Bootstrapper) EnsureOrgProvidersWorkspace(ctx context.Context, orgUUID string) error

EnsureOrgProvidersWorkspace materializes the well-known `providers` child of an Org workspace (root:railgrid:tenants:{orgUUID}:providers) as a `universal` workspace, and blocks until it is Ready. Idempotent.

It is created lazily on the first org-provider registration rather than during org bootstrap: most Orgs never register one, and an empty universal workspace per Org would be pure overhead in the common case.

func (*Bootstrapper) EnsureOrgWorkspace

func (b *Bootstrapper) EnsureOrgWorkspace(ctx context.Context, orgUUID string) error

EnsureOrgWorkspace creates a kcp Workspace at root:railgrid:tenants:{orgUUID} of type `organization` (see config/kcp/workspacetype-organization.yaml). Idempotent: returns nil on AlreadyExists. Blocks until the workspace is Ready so callers can immediately patch the corresponding Organization CR's status.

Per docs/organizations.md decision O-10, Org workspaces are hub-mediated only — tenants never receive a kubeconfig pointing here. This method is invoked from the Organization bootstrap controller with the hub's own admin config; no per-User RBAC is granted inside the workspace.

The "organization" WorkspaceType's defaultAPIBindings bring tenants.railgrid.ai (Organization, CatalogEntry, future Membership) and tenancy.kcp.io (Workspace for child team-workspace creation in PR #3) into the Org workspace.

func (*Bootstrapper) EnsureProviderAPIBinding

func (b *Bootstrapper) EnsureProviderAPIBinding(
	ctx context.Context,
	orgUUID, wsUUID, bindingName, exportPath, exportName string,
	claims []ProviderClaim,
) error

EnsureProviderAPIBinding creates (or no-ops on AlreadyExists) an APIBinding named `bindingName` in the child workspace root:railgrid:tenants:{orgUUID}:{wsUUID}, pointing at exportPath/exportName.

Used by the server-side POST /api/orgs/{org}/workspaces/{ws}/providers/{name}/enable handler so the portal doesn't have to talk to /clusters/{cluster}/apis/... directly — the hub's user-facing kcp proxy pins every user to their User.Spec.DefaultCluster and would 403 any non-default workspace even when commit #220's per-workspace RBAC grants are in place. The proxy's defaultCluster check happens BEFORE forwarding to kcp, so even valid RBAC can't get through. Routing the enable action server- side via the kcp-admin client sidesteps that pre-check.

PermissionClaims state: Accepted iff the user ticked the claim in the confirmation dialog, Rejected otherwise. kcp refuses to mark the binding Bound when any provider-required claim is Rejected, so the response surfaces the mismatch to the user automatically.

func (*Bootstrapper) EnsureProviderEdgeService

func (b *Bootstrapper) EnsureProviderEdgeService(ctx context.Context, orgUUID string, route EdgeRoute, target ClusterServiceTarget) error

EnsureProviderEdgeService reconciles the hub-owned edges.railgrid.ai/Service that fronts one org-owned provider.

spec.auth is "passthrough": a provider backend authorizes the END USER, so substituting the Service's own token would collapse per-user RBAC into "anyone who can reach the tunnel" (E-5). The object is hub-owned and fully overwritten on every reconcile, so a tenant editing its target has that edit reverted rather than silently redirecting platform traffic (E-4).

func (*Bootstrapper) EnsureWorkspaceAdmin

func (b *Bootstrapper) EnsureWorkspaceAdmin(ctx context.Context, clusterName, rbacIdentity string) error

EnsureWorkspaceAdmin ensures cluster-admin is granted to rbacIdentity in the workspace identified by clusterName. Idempotent — safe to call on every login.

func (*Bootstrapper) GetChildWorkspaceClusterName

func (b *Bootstrapper) GetChildWorkspaceClusterName(ctx context.Context, orgUUID, wsUUID string) (string, error)

GetChildWorkspaceClusterName returns the kcp logical-cluster short hash (e.g. "2mmugqjf6k4nwuve") for the child team Workspace at root:railgrid:tenants:{orgUUID}:{wsUUID}. kcp sets it in Workspace.spec.cluster when the workspace reaches phase Ready; EnsureChildWorkspace blocks on Ready, so by the time this method is called the field is populated. The short hash is the form kubectl / the kcp proxy address by — using the full path in kubeconfigs makes for ugly URLs and breaks tools that index on cluster name.

func (*Bootstrapper) GetEdgeInstallTarget

func (b *Bootstrapper) GetEdgeInstallTarget(ctx context.Context, orgUUID, wsUUID, name string) (*EdgeInstallTarget, error)

GetEdgeInstallTarget resolves one edge by (workspace, name), returning (nil, nil) when it does not exist or the edges provider is not enabled in that workspace. Both cases mean the same thing to a caller checking a precondition — there is no such target — and distinguishing them in the error would leak whether a workspace has edges enabled to a caller who named a workspace they cannot otherwise see.

func (*Bootstrapper) GetMCPServerToken

func (b *Bootstrapper) GetMCPServerToken(ctx context.Context, clusterName, name string) (string, error)

GetMCPServerToken reads the long-lived token for a named MCPServer by following status.tokenSecretRef. Returns "" (not an error) when the server or token is not provisioned yet, so the UI can show a "provisioning" state.

func (*Bootstrapper) GetOrgClusterName

func (b *Bootstrapper) GetOrgClusterName(ctx context.Context, orgUUID string) (string, error)

GetOrgClusterName returns the kcp logical cluster name of an Organization workspace at root:railgrid:tenants:{orgUUID} once it is Ready. The cluster name is what status.workspaceCluster on the Organization CR can record for observers that need the canonical kcp identifier rather than the human-readable path.

func (*Bootstrapper) GetOrgMembershipRole

func (b *Bootstrapper) GetOrgMembershipRole(ctx context.Context, orgUUID, userName string) (string, error)

GetOrgMembershipRole returns the role of a single Membership in the Org workspace. NotFound if the user has no Membership; "" plus nil error if the Membership exists but has no role (shouldn't happen).

func (*Bootstrapper) GetOrgProviderWorkspace

func (b *Bootstrapper) GetOrgProviderWorkspace(ctx context.Context, orgUUID, name string) (*OrgProviderWorkspace, error)

GetOrgProviderWorkspace returns one org-owned provider workspace, or (nil, nil) when it does not exist.

func (*Bootstrapper) GetProviderEdgeRoute

func (b *Bootstrapper) GetProviderEdgeRoute(ctx context.Context, orgUUID, providerName string) (*EdgeRoute, error)

GetProviderEdgeRoute reads the binding back. Returns nil when the provider has none — a provider registered before edge transport existed, which stays on its declared backend URL rather than becoming unroutable.

func (*Bootstrapper) GetWorkspaceDeletionRequestedAt

func (b *Bootstrapper) GetWorkspaceDeletionRequestedAt(ctx context.Context, orgUUID, wsUUID string) (*time.Time, bool, error)

GetWorkspaceDeletionRequestedAt reads the soft-delete annotation (WorkspaceDeletionAnnotation) from the child Workspace and returns the parsed timestamp. The bool reports whether the annotation was present at all (so callers can distinguish "no soft-delete requested" from "annotation present but malformed" — malformed surfaces as an error rather than a missing timestamp).

func (*Bootstrapper) GetWorkspaceDisplayName

func (b *Bootstrapper) GetWorkspaceDisplayName(ctx context.Context, orgUUID, wsUUID string) (string, error)

GetWorkspaceDisplayName reads the display-name annotation. Empty string if absent. Workspace-not-found surfaces as IsNotFound.

func (*Bootstrapper) ListAppAccessGrants

func (b *Bootstrapper) ListAppAccessGrants(ctx context.Context, orgUUID, wsUUID string) ([]AppAccessGrant, error)

ListAppAccessGrants lists the published-app access grants (labeled ClusterRoleBindings) in the child workspace. Same kcp-admin/proxy-avoidance rationale as ListProviderAPIBindings.

func (*Bootstrapper) ListChildTeamWorkspaces

func (b *Bootstrapper) ListChildTeamWorkspaces(ctx context.Context, orgUUID string) ([]string, error)

ListChildTeamWorkspaces is ListChildWorkspaces without the infrastructure children an Org workspace can also hold — today just the well-known `providers` container for org-owned providers.

Every tenant-facing consumer wants this one: the workspace switcher, the admin workspace list, and the kcp proxy's authorizer all treat a child of an Org as "a team workspace a member can belong to". The providers container is neither — it holds no Memberships and no UMI rows, so surfacing it yields a workspace row that cannot be entered (selecting it sets an X-Railgrid-Workspace no membership matches, and every subsequent call 403s), and authorizing it would hand every org member access to the workspace that parents provider credentials.

ListChildWorkspaces stays unfiltered for lifecycle callers: the Org deletion cascade must delete the container too.

func (*Bootstrapper) ListChildWorkspaces

func (b *Bootstrapper) ListChildWorkspaces(ctx context.Context, orgUUID string) ([]string, error)

ListChildWorkspaces returns the names of every child Workspace under root:railgrid:tenants:{orgUUID}. Empty list if the Org workspace is gone.

func (*Bootstrapper) ListEdgeInstallTargets

func (b *Bootstrapper) ListEdgeInstallTargets(ctx context.Context, orgUUID string) ([]EdgeInstallTarget, error)

ListEdgeInstallTargets returns every KubernetesCluster edge across the Org's team workspaces, connected or not.

Workspaces where the edges provider is not enabled contribute nothing and are not an error: the resource simply is not served there, which kcp reports as NotFound or a no-match. That is the ordinary state of a workspace that never onboarded an edge, so treating it as a failure would make the whole listing fail for the one Org most likely to be asking.

Non-eligible edges are returned too, deliberately. "You have an edge but its agent is not connected" and "you have no edge at all" need different remedies, and the caller can only tell them apart if it sees both.

func (*Bootstrapper) ListMCPServers

func (b *Bootstrapper) ListMCPServers(ctx context.Context, clusterName string) ([]MCPServerInfo, error)

ListMCPServers returns every MCPServer in the tenant workspace.

func (*Bootstrapper) ListOrgMembershipRoles

func (b *Bootstrapper) ListOrgMembershipRoles(ctx context.Context, orgUUID string) (map[string]string, error)

ListOrgMembershipRoles returns user name → role for every Membership in the Organization workspace. Used to find the Org's admins when a child workspace is created, so O-15 (org admin = implicit admin in every child workspace) holds at the kcp RBAC layer too. Empty map if the Org workspace has no Memberships or has been deleted.

func (*Bootstrapper) ListOrgMemberships

func (b *Bootstrapper) ListOrgMemberships(ctx context.Context, orgUUID string) ([]string, error)

ListOrgMemberships returns the user names (Membership.metadata.name) of every Membership in the Organization workspace. Used by the soft-delete cascade to find which UMIs to mark / strip when an Org or one of its Workspaces enters / exits its grace window. Empty slice if the Org workspace has no Memberships or has been deleted.

func (*Bootstrapper) ListOrgProviderWorkspaces

func (b *Bootstrapper) ListOrgProviderWorkspaces(ctx context.Context, orgUUID string) ([]OrgProviderWorkspace, error)

ListOrgProviderWorkspaces returns the provider sub-workspaces under root:railgrid:tenants:{orgUUID}:providers. An Org that has never registered one has no `providers` workspace at all, which is not an error — it lists empty.

func (*Bootstrapper) ListOrgWorkspaces

func (b *Bootstrapper) ListOrgWorkspaces(ctx context.Context) ([]string, error)

ListOrgWorkspaces returns the names (UUIDs) of every Organization workspace at root:railgrid:tenants. Used by the soft-delete reconciler's Workspace branch to fan out across Orgs at resync time without standing up per-Org dynamic informers.

func (*Bootstrapper) ListProviderAPIBindings

func (b *Bootstrapper) ListProviderAPIBindings(ctx context.Context, orgUUID, wsUUID string) (map[string]ProviderBinding, error)

ListProviderAPIBindings returns the set of Bound provider APIBindings present in the child workspace root:railgrid:tenants:{orgUUID}:{wsUUID}, keyed by provider name. Used by the GET /api/orgs/{org}/workspaces/{ws}/ providers/enabled handler so the portal can render the per-workspace "enabled providers" set on every workspace switch — without going through the kcp user-proxy, which 403s any non-default workspace path even when commit #220's per-workspace RBAC would have allowed the read.

Filtering rule: a binding counts as a "provider binding" iff its spec.reference.export.path names a provider workspace and its status.phase is Bound. Two shapes qualify:

root:railgrid:providers:<name>                    a platform provider
root:railgrid:tenants:<orgUUID>:providers:<name>   an org-owned provider

Org-owned exports count only for their OWN org, so a binding that somehow referenced another Org's provider is not reported as enabled here. The trailing segment is the provider name; the binding's own metadata.name is the value (existing convention is binding.name == provider.name).

func (*Bootstrapper) ListProviderAPIBindingsForExport added in v0.2.7

func (b *Bootstrapper) ListProviderAPIBindingsForExport(ctx context.Context, exportPath, exportName string) ([]ProviderBindingRef, error)

ListProviderAPIBindingsForExport returns every APIBinding in the tenant fleet that binds exportPath/exportName — the cross-workspace counterpart to ListProviderAPIBindings, which answers "what is enabled in THIS workspace".

It exists for fleet-wide claim migrations (AGENTS.md §5.1: a provider's permission claims live on its APIExport, but what a tenant actually granted lives on that tenant's own APIBinding, in that tenant's own workspace, and `init` never touches those). Two deliberate differences from the per-workspace variant:

  • No status.phase filter. A binding held out of Bound because it is missing a claim is exactly the one a claims migration has to reach.
  • Matching is on the export reference, not on the path-derived provider name, so an Org that self-hosts a provider under the same name as the platform one is not swept up by a migration of the platform copy.

A workspace that cannot be listed is skipped rather than failing the walk: one unreachable tenant must not hide the rest of the fleet from an operator.

func (*Bootstrapper) OrgProviderConfig

func (b *Bootstrapper) OrgProviderConfig(orgUUID, name string) *rest.Config

OrgProviderConfig returns a rest.Config targeting one org-owned provider workspace. Used by the provisioner to mint that provider's ServiceAccount and kubeconfig without rebuilding path strings.

func (*Bootstrapper) OrgsConfig

func (b *Bootstrapper) OrgsConfig() *rest.Config

OrgsConfig returns a rest.Config targeting the root:railgrid:tenants parent workspace. The Organization bootstrap controller uses this to create child Workspaces of type `organization` — one per Organization CR — at root:railgrid:tenants:{org-uuid}. The fleet location is unchanged by the system-workspace restructure.

func (*Bootstrapper) PatchOrgMembershipRole

func (b *Bootstrapper) PatchOrgMembershipRole(ctx context.Context, orgUUID, userName, role string) error

PatchOrgMembershipRole updates the role on an existing Membership CR in the Org workspace. NotFound if the Membership doesn't exist. No-op when the role already matches.

func (*Bootstrapper) ReacceptProviderAPIBindingClaims added in v0.2.7

func (b *Bootstrapper) ReacceptProviderAPIBindingClaims(
	ctx context.Context,
	ref ProviderBindingRef,
	exportPath, exportName string,
	claims []ProviderClaim,
) (bool, error)

ReacceptProviderAPIBindingClaims rewrites one binding's spec.permissionClaims to `claims` — the provider's CatalogEntry claim set as it stands today — accepting each one, and reports whether anything changed.

This is the migration half of AGENTS.md §5.1. A provider that starts requiring a newly-declared claim breaks every already-enabled tenant on rollout, because `init` only updates the provider-side APIExport: the tenant's binding keeps the claim set it accepted when it was enabled. Bound or not, that binding is the object that decides what the provider is actually allowed to touch.

Two rules make re-running this safe:

  • A claim the tenant EXPLICITLY REJECTED stays Rejected. Rejecting is a decision the tenant made about their own workspace; a migration propagates the provider's claim set, it does not overturn consent.
  • A claim already on the binding keeps its selector. Only genuinely new claims take their scope from the export, because kcp makes an accepted claim's selector immutable, so a claim narrowed after a workspace enabled the provider keeps that workspace's original (wider) scope until the provider is disabled and re-enabled there.

Claims the provider no longer declares are dropped: the target is the CatalogEntry's current set, not the union with history. ProviderClaim.Accepted is ignored — the type is shared with the Enable flow, where a human ticked each box; here the whole point is that the provider already declares them.

func (*Bootstrapper) RecordProviderEdgeBinding

func (b *Bootstrapper) RecordProviderEdgeBinding(ctx context.Context, orgUUID, providerName, wsUUID, edgeName string) error

RecordProviderEdgeBinding stamps the chosen edge onto the provider Workspace.

Called at registration, after the edge has been verified connected. Writing it here rather than on the CatalogEntry is the point: the CatalogEntry is authored by the tenant's own chart, and routing read from tenant-authored fields is routing the tenant controls.

func (*Bootstrapper) RemoveAppAccessGrant

func (b *Bootstrapper) RemoveAppAccessGrant(ctx context.Context, orgUUID, wsUUID, bindingName string) error

RemoveAppAccessGrant revokes one published-app invitation by deleting its ClusterRoleBinding. It refuses to touch bindings that do not carry the app-access label so this endpoint can never delete unrelated RBAC. NotFound is a no-op for idempotent revocation.

func (*Bootstrapper) ResolveProviderEdgeRoute

func (b *Bootstrapper) ResolveProviderEdgeRoute(ctx context.Context, orgUUID, providerName, backendURL string) (*EdgeRoute, error)

ResolveProviderEdgeRoute returns the usable edge route for an org-owned provider, reconciling the hub-owned Service on the way.

Implements providers.EdgeRouteResolver. Returns (nil, nil) when the provider has no recorded binding — one registered before edge transport existed, which keeps its old behaviour rather than becoming abruptly unroutable.

backendURL is the address the provider published about itself; it is parsed and validated as cluster DNS, never trusted as-is. A backend the hub cannot make sense of is an error, not a silent fallback: falling back would dial an address inside the tenant's cluster from the hub.

func (*Bootstrapper) RevokeChildWorkspaceAdmin

func (b *Bootstrapper) RevokeChildWorkspaceAdmin(ctx context.Context, orgUUID, wsUUID, rbacIdentity string) error

RevokeChildWorkspaceAdmin is RevokeWorkspaceAdmin with the canonical child-workspace path. Idempotent.

func (*Bootstrapper) RevokeWorkspaceAdmin

func (b *Bootstrapper) RevokeWorkspaceAdmin(ctx context.Context, clusterName, rbacIdentity string) error

RevokeWorkspaceAdmin removes the cluster-admin grant for rbacIdentity in the workspace identified by clusterName. Idempotent on NotFound. Only the caller's own per-user binding is touched; every other member keeps theirs.

func (*Bootstrapper) SetWorkspaceDeletionAnnotation

func (b *Bootstrapper) SetWorkspaceDeletionAnnotation(ctx context.Context, orgUUID, wsUUID string, at time.Time) error

SetWorkspaceDeletionAnnotation stamps the kcp Workspace at root:railgrid:tenants:{orgUUID}:{wsUUID} with the soft-delete annotation (WorkspaceDeletionAnnotation) carrying the given timestamp. The soft-delete reconciler picks it up on its next poll. Once a deletion timestamp exists it is never replaced: the first request owns the recovery-window start, and retries are successful no-ops. A concurrent update is retried against a fresh Workspace so it cannot reset that clock.

func (*Bootstrapper) SetWorkspaceDisplayName

func (b *Bootstrapper) SetWorkspaceDisplayName(ctx context.Context, orgUUID, wsUUID, displayName string) error

SetWorkspaceDisplayName stamps / overwrites the display-name annotation on the Workspace.

func (*Bootstrapper) UpdateMCPServer

func (b *Bootstrapper) UpdateMCPServer(ctx context.Context, clusterName, name, displayName, instructions string, readOnly bool) error

UpdateMCPServer patches an MCPServer's spec fields.

func (*Bootstrapper) UsersConfig

func (b *Bootstrapper) UsersConfig() *rest.Config

UsersConfig returns a rest.Config targeting root:railgrid:system:tenants, where the User / Organization / Membership CR OBJECTS are stored (this replaces the former root:railgrid:users). The org *fleet* lives separately under root:railgrid:tenants (see OrgsConfig).

func (*Bootstrapper) WithEnabledProviders

func (b *Bootstrapper) WithEnabledProviders(names []string) *Bootstrapper

WithEnabledProviders sets the subset of builtin providers the bootstrapper will write into root:railgrid:providers. Pass the value of the --providers flag; nil/empty selects every known builtin.

type ClusterServiceTarget

type ClusterServiceTarget struct {
	Name      string
	Namespace string
	Port      int32
	Scheme    string
}

ClusterServiceTarget is a Kubernetes Service inside a tenant's cluster, parsed from an address the provider published about itself.

func ParseClusterServiceTarget

func ParseClusterServiceTarget(backendURL string) (*ClusterServiceTarget, error)

ParseClusterServiceTarget reads a provider's declared backend URL as a cluster-DNS address and refuses anything else.

The provider's own CatalogEntry is the only thing that knows where it listens in the tenant's cluster — the chart's fullname and namespace are its choice, not something the hub can predict (an operator-mode install lands on `<release>-<chart>.<serve-ns>.svc`, not the `<name>.railgrid-provider-<name>.svc` a hub-side guess would produce). So the address is read from there but not trusted: it must be a `.svc` / `.svc.cluster.local` name, so the worst a tenant can aim this at is something inside the cluster the tunnel already terminates in. An IP, an external host, or a bare name is refused rather than silently proxied.

type EdgeInstallTarget

type EdgeInstallTarget struct {
	// Workspace is the team workspace UUID the edge lives in.
	Workspace string
	// WorkspaceDisplayName is that workspace's human label, when it has one.
	WorkspaceDisplayName string
	// Name is the KubernetesCluster's metadata.name.
	Name string
	// Connected reports an active agent tunnel (status.connected).
	Connected bool
	// Phase is status.phase, e.g. "AwaitingAgent" or "Connected". Carried so
	// the UI can explain a not-Connected edge rather than just greying it out.
	Phase string
	// AgentVersion is the railgrid build running on the agent, when reported.
	AgentVersion string
}

EdgeInstallTarget is one candidate cluster for a self-hosted provider, flattened across the Org's team workspaces.

Connected is the field that decides eligibility. An edge whose agent has never dialed in, or whose tunnel has dropped, is a cluster the hub cannot reach — installing a provider there produces a backend nothing can call.

type EdgeRoute

type EdgeRoute struct {
	// WorkspaceUUID is the team workspace holding the edge and the Service.
	WorkspaceUUID string
	// EdgeName is the KubernetesCluster edge whose agent carries the tunnel.
	EdgeName string
	// ServiceName is the hub-owned edges.railgrid.ai/Service. Derived, never read
	// from tenant input.
	ServiceName string
	// Cluster is WorkspaceUUID's kcp logical-cluster ID — what the edges proxy
	// path addresses. Empty until ResolveProviderEdgeRoute has filled it in.
	Cluster string
}

EdgeRoute is where a self-hosted provider's backend is reached: which workspace holds the edge, which edge, and the hub-owned Service standing in front of the provider inside the tenant's cluster.

type EmbeddedKCP

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

EmbeddedKCP wraps a kcp server that runs in-process.

func NewEmbeddedKCP

func NewEmbeddedKCP(opts EmbeddedKCPOptions) *EmbeddedKCP

func (*EmbeddedKCP) AdminConfig

func (e *EmbeddedKCP) AdminConfig() *rest.Config

AdminConfig returns a rest.Config for the kcp admin user. This should only be called after Ready() returns.

func (*EmbeddedKCP) AdminKubeconfigPath

func (e *EmbeddedKCP) AdminKubeconfigPath() string

AdminKubeconfigPath returns the path to the admin kubeconfig file.

func (*EmbeddedKCP) Ready

func (e *EmbeddedKCP) Ready() <-chan struct{}

Ready returns a channel that is closed when kcp is ready to serve requests.

func (*EmbeddedKCP) Run

func (e *EmbeddedKCP) Run(ctx context.Context) error

Run starts the embedded kcp server and blocks until context is cancelled. It returns an error if the server fails to start.

type EmbeddedKCPOptions

type EmbeddedKCPOptions struct {
	RootDir          string
	SecurePort       int
	BindAddress      string
	BatteriesInclude []string

	// TLS certificate/key files for the KCP API server.
	// When set, KCP uses these instead of auto-generating self-signed certs.
	// This is important in Kubernetes where pod IPs change on restart.
	TLSCertFile string
	TLSKeyFile  string

	// ShardExternalURL maps to kcp's --shard-external-url flag. It's the URL
	// kcp publishes into APIExportEndpointSlice.status.endpoints[].url and
	// CachedResourceEndpointSlice.status.endpoints[].url for outside consumers
	// to dial. Empty defaults to the auto-detected external address (for an
	// embedded kcp bound to 127.0.0.1 that's "https://127.0.0.1:6443") which
	// is unreachable from inside a kind pod — set this to
	// "https://host.docker.internal:6443" or similar when other workloads
	// (e.g. the kro multicluster controller) need to dial back.
	ShardExternalURL string

	// ShardVirtualWorkspaceURL maps to kcp's --shard-virtual-workspace-url flag.
	// This is the URL embedded into APIExportEndpointSlice endpoints —
	// `--shard-external-url` only updates Shard.spec.externalURL, but the
	// endpoint URLs are derived from Shard.spec.virtualWorkspaceURL (kcp
	// keeps these split so the VW server can run on a separate host).
	// Same default-and-override semantics as ShardExternalURL — in
	// practice both want the same value for a single-shard embedded
	// dev setup.
	ShardVirtualWorkspaceURL string

	// StaticAuthTokens are bearer tokens that kcp should accept directly
	// via its token-auth-file mechanism. This allows static token users
	// to be authenticated natively by kcp (needed for workspace mounts).
	StaticAuthTokens []string

	// OIDC options for native kcp authentication. When OIDCIssuerURL and
	// OIDCClientID are both set, kcp will verify bearer tokens against the
	// configured issuer and run requests as the resulting OIDC identity.
	//
	// Defaults are tuned to match the proxy/User CRD identity scheme
	// (User.Spec.RBACIdentity = "railgrid:<email>", see pkg/server/auth/handler.go):
	//   UsernameClaim  = "email"
	//   UsernamePrefix = "railgrid:"
	//   GroupsClaim    = "groups"
	//   GroupsPrefix   = "railgrid:"
	OIDCIssuerURL      string
	OIDCClientID       string
	OIDCCAFile         string
	OIDCUsernameClaim  string
	OIDCUsernamePrefix string
	OIDCGroupsClaim    string
	OIDCGroupsPrefix   string
}

EmbeddedKCPOptions contains configuration for the embedded kcp server.

type MCPFederatedProviderInfo

type MCPFederatedProviderInfo struct {
	Name        string                 `json:"name"`
	DisplayName string                 `json:"displayName,omitempty"`
	Reachable   bool                   `json:"reachable"`
	Message     string                 `json:"message,omitempty"`
	Tools       []MCPFederatedToolInfo `json:"tools,omitempty"`
}

MCPFederatedProviderInfo mirrors railgridv1alpha1.FederatedMCPProvider for the portal.

type MCPFederatedToolInfo

type MCPFederatedToolInfo struct {
	Name        string `json:"name"`
	Title       string `json:"title,omitempty"`
	Description string `json:"description,omitempty"`
}

MCPFederatedToolInfo mirrors railgridv1alpha1.FederatedMCPTool for the portal.

type MCPServerInfo

type MCPServerInfo struct {
	Name         string `json:"name"`
	UID          string `json:"uid"`
	DisplayName  string `json:"displayName,omitempty"`
	Instructions string `json:"instructions,omitempty"`
	ReadOnly     bool   `json:"readOnly,omitempty"`
	Phase        string `json:"phase,omitempty"`
	URL          string `json:"url,omitempty"`
	// FederatedProviders is the live tool inventory the reconciler stamped on
	// status.federatedProviders — which providers this server federates and the
	// tools each advertises to it.
	FederatedProviders []MCPFederatedProviderInfo `json:"federatedProviders,omitempty"`
	// ToolsRefreshedTime is when the inventory above was last recomputed (RFC3339).
	ToolsRefreshedTime string `json:"toolsRefreshedTime,omitempty"`
}

MCPServerInfo is a portal-facing view of an MCPServer CR.

type OrgProviderWorkspace

type OrgProviderWorkspace struct {
	Name    string
	Cluster string
	Phase   string
}

OrgProviderWorkspace is one org-owned provider sub-workspace as listed from kcp. It reports what exists in the workspace tree, independent of whether the provider's chart has been installed and has registered a CatalogEntry yet — that gap is exactly the "registered but not yet connected" state the org admin UI needs to show.

type ProviderBinding

type ProviderBinding struct {
	// Name is the APIBinding's metadata.name (by convention, the provider name).
	Name string
	// ExportPath is the workspace path of the APIExport it binds.
	ExportPath string
	// SelfHosted is true when ExportPath is under the tenant fleet, i.e. the
	// binding targets an Org's own provider rather than a platform one.
	SelfHosted bool
	// Terminating is true when the binding has been deleted but kcp's cascade
	// cleanup (which removes every CR of the bound APIs first) has not finished.
	// The binding still serves until then, so it still counts as enabled.
	Terminating bool
	// DeletionBlocked is kcp's explanation of what is holding a terminating
	// binding's deletion open — typically leftover CR finalizers whose
	// controller is gone. Empty when deletion is progressing normally or the
	// binding is not terminating.
	DeletionBlocked string
}

ProviderBinding describes one bound provider APIBinding in a workspace.

SelfHosted is the field that earns this type: once an Org can self-host a provider under the same name as the platform one, "is `edges` enabled here?" stops being a yes/no question. A workspace that enabled the platform edges before the Org started self-hosting still points at the platform export, and showing that as plain "Enabled" would tell the user they are running their own instance when they are not. Switching is a Disable + Enable, not an in-place retarget, so the distinction has to survive all the way to the UI.

type ProviderBindingRef added in v0.2.7

type ProviderBindingRef struct {
	OrgUUID       string
	WorkspaceUUID string
	BindingName   string
}

ProviderBindingRef locates one provider APIBinding in the tenant fleet: the (org, workspace) pair whose logical cluster holds it, plus its name. The per-workspace reads already know where they are; a fleet-wide walk does not, so the coordinates have to travel with the result.

type ProviderClaim

type ProviderClaim struct {
	Group    string
	Resource string
	Verbs    []string
	Accepted bool
	// MatchLabels is the claim's declared scope: the label set an object must
	// carry for this claim to reach it. Empty accepts the claim for every
	// object of the resource in the workspace (kcp's matchAll).
	//
	// This is the field that turns "the provider may read every Secret in your
	// workspace" into "the provider may read the Secrets it labelled as its
	// own". kcp enforces it on both sides of the APIExport virtual workspace:
	// its permission-claim labeler stamps the internal
	// claimed.internal.apis.kcp.io/<export> label only on objects the selector
	// matches, the virtual workspace filters LIST/WATCH by that label and 404s
	// a GET without it, and its virtual-workspace admission refuses a write
	// whose object does not match (stamping the matchLabels when they are
	// simply absent). See docs/cross-provider-simplification.md X-4.
	MatchLabels map[string]string
}

ProviderClaim is the wire shape the REST handler hands EnsureProviderAPIBinding — one entry per permission claim the provider DECLARED in its CatalogEntry, plus a flag whether the user accepted or rejected it in the Enable confirmation dialog. Mirrors providers.PermissionClaim but lives here so the bootstrap package stays free of an import on pkg/hub/providers.

Jump to

Keyboard shortcuts

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