Documentation
¶
Index ¶
- Constants
- func LocalShardAPIURL(organization, project, instanceName string) string
- func ShardNamespace() string
- func WorldKey(org, project string) string
- type Client
- func (c *Client) Config() Config
- func (c *Client) Deploy(ctx context.Context, opts DeployOpts) error
- func (c *Client) DeployServices(ctx context.Context, opts DeployServicesOpts) error
- func (c *Client) DeployShard(ctx context.Context, cfg toml.Config, shardID string) error
- func (c *Client) DeployWorld(ctx context.Context, cfg toml.Config, onStep func(step string)) error
- func (c *Client) DeployedShards(ctx context.Context) ([]DeployedShard, error)
- func (c *Client) DeployedWorldKeys(ctx context.Context) (map[string]struct{}, error)
- func (c *Client) IsRunning(ctx context.Context) (bool, error)
- func (c *Client) PruneOrphanedShards(ctx context.Context, cfg toml.Config) error
- func (c *Client) Purge(ctx context.Context, _ PurgeOpts) error
- func (c *Client) PurgeShardState(ctx context.Context, org, project, instanceID string) error
- func (c *Client) PurgeWorldState(ctx context.Context, org, project string) error
- func (c *Client) RequiredBootstrapImages(ctx context.Context) ([]string, error)
- func (c *Client) ResetLogRouting()
- func (c *Client) ShardPoolsFromConfig(cfg toml.Config) ([]ShardPool, error)
- func (c *Client) Start(ctx context.Context, opts StartOpts) error
- func (c *Client) StartPlatform(ctx context.Context, onStep func(step string)) error
- func (c *Client) Stop(ctx context.Context, _ StopOpts) error
- func (c *Client) StreamPlatformLogs(ctx context.Context, ref PlatformPodRef, opts LogsOpts, out chan<- LogLine) error
- func (c *Client) StreamShardLogs(ctx context.Context, opts LogsOpts, out chan<- LogLine) error
- func (c *Client) UndeployShard(ctx context.Context, shardID string) error
- func (c *Client) UndeployShards(ctx context.Context) error
- func (c *Client) UndeployWorld(ctx context.Context) error
- func (c *Client) WaitForShardsReady(ctx context.Context, cfg toml.Config, onProgress func(ready, expected int))
- type Config
- type DeployOpts
- type DeployServicesOpts
- type DeployShard
- type DeployedShard
- type LogLine
- type LogsOpts
- type PlatformPodRef
- type PurgeOpts
- type ResourceValues
- type Resources
- type ShardPool
- type StartOpts
- type StopOpts
- type TokenSource
Constants ¶
const DefaultRegion = "us-west1"
DefaultRegion is the region for all local (k3d) shards + [[services]]. It matches the docker backend so the same game-client config reaches both. Override a single service via [[services.env]] CARDINAL_REGION.
Variables ¶
This section is empty.
Functions ¶
func LocalShardAPIURL ¶
LocalShardAPIURL returns the Traefik-routed Cardinal API URL for a shard instance on the local cluster.
func ShardNamespace ¶
func ShardNamespace() string
ShardNamespace is the single namespace the editor deploys the operator and every world's shards into. Exported for the editor; see operatorNamespace.
func WorldKey ¶
WorldKey is a world's stable identity, "{org}/{project}". The editor matches it against the org/project read back from running ShardPool CRs (DeployedWorldKeys) to re-derive run-state after a restart. It's an identity string, not a k8s name (shards share one fixed namespace, ShardNamespace), so it needs no DNS-1123 sanitizing; the slash keeps the two fields unambiguous.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client orchestrates the local Cardinal cluster lifecycle: k3d bring-up, platform install, per-project operator + ShardPools, and Deploy RPC calls.
func NewClient ¶
NewClient constructs a Client, defaulting unset fields. Also installs the log level into k3d's global logger (package-level state in k3d's lib).
func (*Client) Deploy ¶
func (c *Client) Deploy(ctx context.Context, opts DeployOpts) error
Deploy retags each shard's built image to the registry-prefixed ref the operator expects, imports it into the cluster's containerd, and calls the operator's Deploy RPC. Caller builds the images first (via pkg/docker).
Import instead of push: `k3d-<name>.localhost` doesn't resolve on host and the registry's host port is randomized. Importing tars the image straight into containerd; IfNotPresent pull-policy then skips the registry lookup.
func (*Client) DeployServices ¶
func (c *Client) DeployServices(ctx context.Context, opts DeployServicesOpts) error
DeployServices is the path-kind counterpart to Deploy: it retags each source-built [[services]] image to the registry-prefixed ref, imports it into the cluster's containerd, and re-applies the Deployment/Service with the imported tag. There's no operator RPC for services — k8s rolls the pod itself when the container image string changes. Caller builds the images first (via pkg/docker). A clean no-op when no path-kind services are declared.
func (*Client) DeployShard ¶
DeployShard re-applies a single shard's ShardPool CR translated from cfg, without touching the operator, project DB, [[services]], or any other shard's pool. The operator must already be running (DeployWorld's job) — this only brings shardID's pool back, e.g. after UndeployShard + PurgeShardState.
func (*Client) DeployWorld ¶
DeployWorld brings a world up on an already-running cluster (via StartPlatform): installs the operator, waits for Ready, then applies the world's ShardPool CRs. Operator and shards co-locate in operatorNamespace (prod-style, namespace- scoped); one world at a time keeps that namespace unambiguous. Idempotent.
onStep, if non-nil, gets a short label before each phase — same contract as StartPlatform's onStep.
func (*Client) DeployedShards ¶
func (c *Client) DeployedShards(ctx context.Context) ([]DeployedShard, error)
DeployedShards lists the shards currently deployed on the cluster (from ShardPool CRs) with their owning world's organization/project. This lets callers address a shard via the Gateway without a local world.toml.
func (*Client) DeployedWorldKeys ¶
DeployedWorldKeys returns the WorldKey ("{org}/{project}") of every world with ShardPools on the cluster, letting the editor re-derive which saved worlds are live after a restart. At most one entry (one world at a time).
Reads the ShardPool CRs straight from the apiserver, not via the operator's Status RPC: the operator only runs while a world is deployed, so querying it would fail in exactly the "is anything running?" case this answers.
func (*Client) IsRunning ¶
IsRunning reports whether the shared local cluster exists and is currently running. A cluster paused via Stop exists but is not running. Reconciling editor run-state after a restart uses this to confirm a persisted k8s world is still live before adopting it — without re-establishing port-forwards.
func (*Client) PruneOrphanedShards ¶
PruneOrphanedShards deletes the ShardPool CRs of shards no longer in cfg.
func (*Client) PurgeShardState ¶
PurgeShardState is the single-instance analog of PurgeWorldState: it deletes only instanceID's JetStream snapshot bucket, leaving every other instance's state (and pods) untouched. Call AFTER UndeployShard: it first waits for that instance's pods to terminate, so a shutting-down replica can't re-snapshot over the wipe.
func (*Client) PurgeWorldState ¶
PurgeWorldState wipes one world's persisted state so its next deploy starts on a fresh tick: it deletes every JetStream snapshot bucket under the "{org}_{project}_" prefix (also catching shards since removed from config, which a per-shard match would miss). Assumes no two local worlds share that prefix — safe one-world-at-a-time.
Call AFTER undeploying the shards: it first waits for this world's shard pods to terminate so a shutting-down shard can't re-snapshot over the wipe.
func (*Client) RequiredBootstrapImages ¶
RequiredBootstrapImages returns the images a fresh cluster-create needs (k3s node, tools, load-balancer, registry), or nil if the cluster already exists (its node images are already present). Callers can pre-pull this list themselves before Start instead of pulling opaquely inside cluster-create.
func (*Client) ResetLogRouting ¶
func (c *Client) ResetLogRouting()
ResetLogRouting stops routing k3d's global logger through OnK3DLog. k3d's logger is package-level state — it stays wired to the most recent NewClient's callback even after that call returns, so callers whose OnK3DLog updates per-operation UI (e.g. a phasebox row) must call this once the operation finishes, or a lingering background goroutine could resurrect already-torn-down UI. Falls back to a silent no-op, not os.Stderr, since a stray late line has nowhere useful to go.
func (*Client) ShardPoolsFromConfig ¶
ShardPoolsFromConfig translates a world.toml Config into one ShardPool per shard ID. Pool expansion (pool_size > 1 in world.toml) collapses back into a single ShardPool with PoolSize set to the instance count. Backend defaults apply for fields the TOML doesn't specify.
func (*Client) Start ¶
Start brings up the local cluster end-to-end for one project — the bundled convenience path (world-cli `world start`) composing StartPlatform then DeployWorld. The editor instead drives those two separately so the cluster lifecycle is independent of any one world.
func (*Client) StartPlatform ¶
StartPlatform brings up the shared, world-agnostic cluster environment:
k3d up → apply CRD → ensure platform (NATS, Traefik).
No operator, no world resources — those are DeployWorld's job (mirroring prod, the operator is namespace-scoped and co-located with its shards, so it comes up per-world at deploy time). StartPlatform blocks until shared NATS and Traefik are Ready so shards can connect and clients can route as soon as DeployWorld creates them.
onStep, if non-nil, gets a short label before each phase so callers (the editor's cluster toggle) can show progress through the ~15-25s cold start.
func (*Client) Stop ¶
Stop pauses the k3d cluster (`k3d cluster stop`). Pod state is preserved on disk; the next Start brings everything back up quickly. For single-project dev (the v1 surface), this is what `world stop` does.
Multi-project label-selector deletion (per ADR-055's stop semantics) is deferred until per-project namespacing lands.
func (*Client) StreamPlatformLogs ¶
func (c *Client) StreamPlatformLogs(ctx context.Context, ref PlatformPodRef, opts LogsOpts, out chan<- LogLine) error
StreamPlatformLogs tails the current pod for the given platform component by hitting the kube-apiserver's Pods.GetLogs endpoint directly. Closes out on return.
On pod-gone (rolling restart, eviction), re-resolves the selector and re-subscribes. ctx cancellation stops the stream. opts.FollowWindow bounds only the follow phase, not the initial pod discovery.
This deliberately bypasses cardinal-operator: the operator's StreamPodLogs is hard-coded to a single namespace (the one it watches for ShardPools), so it can't reach platform pods outside that namespace.
func (*Client) StreamShardLogs ¶
StreamShardLogs opens a server-streaming log subscription against cardinal-operator for one or more shards/instances and sends every line into out. One goroutine per pod. Returns when ctx is canceled or every stream has terminated. Always closes out, including on error, so a caller ranging over it terminates.
Pod identity is fetched from the operator's Status RPC. If a pod disappears mid-stream (rolling deploy, eviction), the goroutine refreshes Status and reconnects to the replacement pod. ctx cancellation stops all streams and is not an error.
The returned error joins whatever each pod's stream ended on, so a caller can tell "the dump finished" from "it never started". Because it blocks, out must be drained concurrently — the same contract StreamPlatformLogs and logs.StreamFn already document.
func (*Client) UndeployShard ¶
UndeployShard is the single-shard analog of UndeployShards: it deletes just shardID's ShardPool CR (k8s GCs its owned pods), leaving every other shard and the operator untouched. Pairs with DeployShard + PurgeShardState so reloading one shard with --purge can't take down the rest of the world.
func (*Client) UndeployShards ¶
UndeployShards is the shards-only half of UndeployWorld: it deletes this world's ShardPool CRs (k8s GCs the owned pods) but leaves the operator running. Keeping the operator up makes the next DeployWorld cheap — its ensureOperator + readiness wait become no-ops instead of a ~10-25s operator cold start.
func (*Client) UndeployWorld ¶
UndeployWorld tears a world down: deletes its ShardPool CRs (k8s GCs the owned shard Deployments via owner references) then removes the operator, reversing DeployWorld. The shared cluster stays up, and it needs no world config so an unparseable world can still be torn down.
It leaves the operator's Service/RBAC for the next DeployWorld to re-apply; operator RPCs during the no-world window get connection-refused, so callers should read ShardPool CRs (DeployedWorldKeys) to tell what's deployed.
func (*Client) WaitForShardsReady ¶
func (c *Client) WaitForShardsReady(ctx context.Context, cfg toml.Config, onProgress func(ready, expected int))
WaitForShardsReady blocks until every shard instance has a Ready pod running the just-deployed image:tag, or shardReadyTimeout elapses, so a caller can act on the shards without racing a still-ContainerCreating pod. Matching the new tag keeps a rolling redeploy honest — the old-tag pod stays Ready for a beat but doesn't count.
onProgress, if non-nil, gets the current ready/expected pod count after every poll tick — lets a caller show live "N/M ready" progress instead of one static spinner for the whole wait.
Best-effort (no error): the deploy already succeeded, so a readiness hiccup must never fail it; a wedged shard still surfaces after the timeout.
type Config ¶
type Config struct {
ClusterName string // passed to `k3d cluster create`
RegistryName string // attached registry for shard image refs
// K3sImage must be explicit when calling k3d as a library — the CLI sets
// this via -ldflags at link time, so an empty value here yields a
// rootfs-less server container.
K3sImage string
APIEndpoint string // localhost URL for Traefik's Cardinal API edge (k3d NodePort)
OperatorEndpoint string // URL for the operator RPC; localhost NodePort for k3d
// TokenSource authenticates a remote operator. Nil for k3d, which is unauthenticated.
TokenSource TokenSource
DBEndpoint string // localhost host:port for the project DB (k3d NodePort)
// LogLevel is a logrus-style string ("trace"/"debug"/"info"/"warn"/
// "error"); empty defaults to "info". Routes into k3d's internal logger.
LogLevel string
// OnK3DLog, if set, receives each essential k3d log line (see
// k3dEssentialFormatter) instead of it going to os.Stderr — needed by
// callers with their own terminal renderer, so a second writer doesn't
// race its redraws. nil preserves the original os.Stderr behavior.
OnK3DLog func(line string)
}
Config holds Client construction inputs. Zero-valued fields fall back to Defaults().
type DeployOpts ¶
type DeployOpts struct {
Project string
Shards []DeployShard
// OnResult, if non-nil, gets each shard's result once (nil on success).
OnResult func(shardID string, err error)
}
DeployOpts controls Deploy behavior.
type DeployServicesOpts ¶
DeployServicesOpts controls DeployServices behavior.
type DeployShard ¶
type DeployShard struct {
ID string // matches ShardPool.shardID
SourceImage string // e.g. "<project>-<id>-shard:latest" from pkg/docker
}
DeployShard pairs a shard ID with the already-built local image tag.
type DeployedShard ¶
DeployedShard describes a shard deployed on the cluster and the world (organization/project) it belongs to.
type LogLine ¶
type LogLine struct {
ShardID string // matches ShardPool.shardID (e.g. "gameplay", "lobby")
// InstanceName is the operator's per-instance ID — same as ShardID for
// pool_size=1 shards, "<shardID>" / "<shardID>-2" / … "<shardID>-N"
// for pool_size=N. Use this (not ShardID) to label log lines so the
// 10 pods of a "gameplay" pool render distinguishably.
InstanceName string
PodName string // ephemeral; changes on rolling deploy
// Timestamp is RFC3339Nano from the kubelet; empty if unavailable.
Timestamp string
Line string
}
LogLine is one rendered line from a shard pod, tagged with its source so the consumer can color / route per-shard or per-instance.
type LogsOpts ¶
type LogsOpts struct {
// ShardIDs filters which shard pools to follow. Empty = all shards from
// the operator's Status. Combine with InstanceNames for finer scope.
ShardIDs []string
// InstanceNames filters to specific pool instances (e.g. "gameplay-3").
// Empty = every instance in the matching pools.
InstanceNames []string
// TailLines is how many historical lines to replay before tailing. 0 →
// operator default (200). Capped server-side at 10000.
TailLines int32
// Previous fetches logs from the previous container incarnation. Useful
// for inspecting a pod that just crashed.
Previous bool
// FollowWindow, if set, bounds how long StreamPlatformLogs follows a pod
// once found. Zero means follow until ctx is done.
FollowWindow time.Duration
}
LogsOpts controls StreamShardLogs.
type PlatformPodRef ¶
type PlatformPodRef struct {
Name string // display label, used as the LogLine.InstanceName tag
Namespace string
Selector string // label selector (e.g. "app=nats")
}
PlatformPodRef identifies a platform component for log streaming. Unlike shards — which the cardinal-operator enumerates via its Status RPC — these run in their own namespaces and aren't owned by any ShardPool, so the world-cli has to talk to the kube-apiserver directly.
func PlatformPods ¶
func PlatformPods() []PlatformPodRef
PlatformPods returns the platform components the picker offers, in display order.
func ProjectServicePods ¶
func ProjectServicePods(project string, cfg toml.Config) []PlatformPodRef
ProjectServicePods returns the per-project [[services]] + auto project DB the picker offers, in declaration order with the DB last. Like Traefik/NATS, these aren't ShardPool-owned, so they stream via StreamPlatformLogs (direct kube-apiserver) — no port-forward involved. The auto "{project}-db" is listed whenever DeployWorld provisions it — i.e. when no config_db service brings its own (the same gate as client.go's DeployWorld), so any deployed auto DB is streamable. With a config_db service, only the declared [[services]] appear.
type PurgeOpts ¶
type PurgeOpts struct{}
PurgeOpts controls Purge behavior. Reserved for future multi-project use.
type ResourceValues ¶
ResourceValues uses milliCPU for CPU and MiB for Memory.
type Resources ¶
type Resources struct {
Requests ResourceValues
Limits ResourceValues
}
Resources is the request + limit pair for a ShardPool.
func DefaultResources ¶
func DefaultResources() Resources
DefaultResources returns the resource requests/limits applied when a shard in world.toml doesn't specify them. Matches the values used by Rampage's current dev-cluster ShardPools.
type ShardPool ¶
type ShardPool struct {
ShardID string
Organization string
Project string
Region string
Image string
ImageTag string
TickRate int32
Mode string
PoolSize int32
Resources Resources
LogLevel string
}
ShardPool is the plain-Go representation of a ShardPool CR, ready to be converted to a Kubernetes object when applied via client-go. The shape mirrors apps/cardinal-operator/api/v1.ShardPoolSpec.
type StartOpts ¶
type StartOpts struct {
Project string
Config toml.Config
// OnStep, if non-nil, gets a short label before each phase of
// StartPlatform and DeployWorld (see StartPlatform's doc) — lets `world
// start` show a live checklist instead of a static spinner during the
// ~15-60s cold start.
OnStep func(step string)
}
StartOpts controls Start behavior.