service

package
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: May 20, 2026 License: Apache-2.0 Imports: 59 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// URLKindNative forwards to GatewayConfig.NativeAPIBaseURL.
	URLKindNative = cluster.URLKindNative
	// URLKindE2B forwards to GatewayConfig.E2BAPIBaseURL.
	URLKindE2B = cluster.URLKindE2B
)

Variables

View Source
var ErrSyncNotConnected = errors.New("ws-proxy sync not connected")

ErrSyncNotConnected is returned when an operation requires an active sync session to ws-proxy but none is established.

Functions

func RewriteImageForCluster added in v0.0.2

func RewriteImageForCluster(image, currentClusterID string, store RegistryStore) string

RewriteImageForCluster rewrites the registry host of image when the image belongs to a private registry owned by a different cluster.

Rewrite rules:

  1. Parse the image reference to extract its registry host.
  2. Look up the host in the store. If not found → public registry, return as-is.
  3. If the owning cluster equals currentClusterID → already local, return as-is.
  4. Find a registry of the same Type in currentClusterID. If none found → warn and return as-is (never block the request).
  5. Replace the registry host prefix and return the rewritten image.

func TemplateToGen

func TemplateToGen(t *domain.SandboxTemplate) gen.SandboxTemplate

TemplateToGen converts a domain.SandboxTemplate to the generated gen.SandboxTemplate type. Shared between the API Server handlers and the WsProxy internal HTTP API so that both return an identical response shape.

func TemplateToSummaryGen

func TemplateToSummaryGen(t *domain.SandboxTemplate) gen.SandboxTemplateSummary

TemplateToSummaryGen converts a domain.SandboxTemplate to the lightweight gen.SandboxTemplateSummary type (omits docs/crdYaml).

func ValidateContainerImage

func ValidateContainerImage(image string) error

ValidateContainerImage checks that image is a syntactically valid Docker/OCI image reference (e.g. "nginx:1.25", "ghcr.io/org/repo@sha256:abc..."). It returns a *domain.AppError (400 Bad Request) on failure, nil on success. Empty strings are silently accepted (callers skip empty images before calling this).

Types

type APIKeyService

type APIKeyService interface {
	Create(ctx context.Context, input domain.CreateAPIKeyInput) (*domain.APIKeyResult, *domain.AppError)
	List(ctx context.Context) (*domain.ListAPIKeysResult, *domain.AppError)
	ListByTeamAndUser(ctx context.Context, team, user string) (*domain.ListAPIKeysResult, *domain.AppError)
	Get(ctx context.Context, keyID string) (*domain.APIKeyItem, *domain.AppError)
	Delete(ctx context.Context, input domain.DeleteAPIKeyInput) *domain.AppError
	// Promote elevates a locally-created key to a global key by syncing it
	// through the ws-proxy manager to all Worker clusters.
	Promote(ctx context.Context, keyID string) *domain.AppError
}

APIKeyService defines business operations for API key management.

func NewAPIKeyService

func NewAPIKeyService(store apikey.KeyStore) APIKeyService

NewAPIKeyService creates a new APIKeyService backed by the provided KeyStore. If store is nil, all operations will return a ServiceUnavailable error.

func NewAPIKeyServiceWithSync

func NewAPIKeyServiceWithSync(store apikey.KeyStore, syncSvc SyncService) APIKeyService

NewAPIKeyServiceWithSync creates a new APIKeyService that forwards Create/Delete operations to the master cluster via the provided SyncService WS channel. Local List/Get continue to use the local store.

type ClusterConfigSink

type ClusterConfigSink interface {
	ApplyClusterConfig(ctx context.Context, cfg cluster.ClusterConfig) error
}

ClusterConfigSink receives ClusterConfig snapshots from ws-proxy and persists them (e.g. to a Kubernetes ConfigMap) so that other Worker components (ExtProc, in-process DNS resolver, ...) can read the latest state. Implementations must treat a zero-valued snapshot (no clusters and no host aliases) as a no-op.

type ClusterService

type ClusterService interface {
	// List returns the cluster catalog sorted by ID. When store or localID are
	// empty the result is a single-entry list containing the local cluster, or an
	// empty slice when neither is configured. The list is a defensive copy; the
	// caller may mutate it freely.
	List(ctx context.Context) ([]domain.ClusterSummary, *domain.AppError)
}

ClusterService exposes the routing-visible cluster list to API consumers. It wraps a cluster.Store and tags the local cluster so SDK/CLI callers can distinguish the native home of a request from remote worker clusters.

func NewClusterService

func NewClusterService(store *cluster.Store, localClusterID string) ClusterService

NewClusterService returns a ClusterService backed by the given store and local cluster ID. store may be nil (common in single-cluster deployments); in that case List reports only the local cluster if localID is set.

type CreateKeyRequest

type CreateKeyRequest struct {
	Namespace   string
	User        string
	Team        string
	Role        string
	Description string
	QuotaURL    string
	ExpiresAt   string // RFC3339; empty = never expires

	// Import/promote mode: when TokenHash + HashPrefix are set the Hub calls
	// CreateFromHash instead of generating a new token.
	TokenHash  string
	HashPrefix string
	IssuedAt   string // RFC3339
	RawToken   string // plaintext token for promote
}

CreateKeyRequest carries the parameters for forwarding an API key CreateKey RPC to the Hub.

type CreateKeyResponse

type CreateKeyResponse struct {
	RawToken   string
	KeyID      string
	TokenHash  string
	HashPrefix string
	IssuedAt   string // RFC3339
}

CreateKeyResponse is the parsed result of a successful Hub-side CreateKey.

type CrossClusterForwarder

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

CrossClusterForwarder transparently forwards HTTP requests to a remote cluster's API endpoint. It is protocol-agnostic: the original gin.Context request (method, path, query string, headers, body) is forwarded verbatim; only the base URL is swapped according to urlKind and the target cluster's gateway configuration.

Callers (handlers) should:

  1. Detect the cross-cluster case early (right after extracting clusterID).
  2. Call Forward — it writes the remote response directly to gc.Writer.
  3. Return from the handler immediately after Forward returns.

func NewCrossClusterForwarder

func NewCrossClusterForwarder(store *cluster.Store, localClusterID string) *CrossClusterForwarder

NewCrossClusterForwarder creates a forwarder. Returns nil if store or localClusterID is empty, which disables cross-cluster support.

The forwarder's HTTP client uses a custom Dialer that consults the Manager- pushed host-alias resolver before falling back to the system DNS, so that gateway hostnames unreachable via kube-dns (e.g. internal zones on the worker network) can be resolved purely via the sync channel without touching Pod spec.

func (*CrossClusterForwarder) Forward

func (f *CrossClusterForwarder) Forward(gc *gin.Context, targetClusterID string, urlKind URLKind, body io.Reader)

Forward transparently proxies the incoming gin request to targetCluster. urlKind selects whether to target the Native or E2B endpoint.

body overrides the request body sent to the remote cluster. Pass nil to use gc.Request.Body as-is (suitable for GET/DELETE or when the body has not yet been consumed). Pass a non-nil reader when the handler has already read the body (e.g. strict-server mode where oapi-codegen parses the body before the handler runs) — in that case the caller should re-marshal the parsed struct and pass it here.

The full remote response (status, headers, body) is written directly to gc.Writer. On network/configuration errors, Forward writes a 502 JSON error response itself so the caller can always return immediately after this call.

func (*CrossClusterForwarder) IsCrossCluster

func (f *CrossClusterForwarder) IsCrossCluster(clusterID string) bool

IsCrossCluster reports whether clusterID refers to a remote cluster that requires forwarding. Returns false when the forwarder is nil (cross-cluster disabled), clusterID is empty, or clusterID equals the local cluster.

func (*CrossClusterForwarder) LocalClusterID

func (f *CrossClusterForwarder) LocalClusterID() string

LocalClusterID returns the configured local cluster ID. Empty when the forwarder is nil (cross-cluster disabled) or no local ID was configured.

type ExecTokenRecord

type ExecTokenRecord struct {
	SandboxID  string
	Namespace  string
	PodName    string
	Containers []string
	ExpiresAt  time.Time
}

ExecTokenRecord holds the information associated with a one-time exec token.

type ExtProcClient

type ExtProcClient interface {
	PushRoute(ctx context.Context, r RouteInfo) error
	EvictRoute(ctx context.Context, sandboxID string) error
	GetLastActive(ctx context.Context) (map[string]time.Time, error)
	Close() error
}

ExtProcClient is the Controller-side view of the ExtProc control-plane RPC. Implementations are safe for concurrent use; close via Close when the process is shutting down.

func NewExtProcClient

func NewExtProcClient(target, adminKey string) (ExtProcClient, error)

NewExtProcClient dials the ExtProc control-plane gRPC server at target and configures per-RPC admin-key credentials. target is a gRPC dial string (e.g. "agentbox-extproc.agentbox-system.svc:9003"). For backwards compatibility with the old HTTP flag value, any leading "http://" or "https://" scheme is stripped silently so stale deployment manifests keep working. adminKey may be empty in dev mode; the server side is expected to match.

type IAMResolveResult

type IAMResolveResult struct {
	// Username is the normalised username.
	Username string
	// Team is the team derived from ScitixQuota labels. Empty when no quota found.
	Team string
	// Namespace is the effective Kubernetes namespace to use for this user.
	// When the computed namespace (t-{team}-{username}) exists in the cluster it
	// equals that value; otherwise it falls back to "default".
	Namespace string
	// QuotaURL is the quota URL label value, if found.
	QuotaURL string
}

IAMResolveResult holds the resolved identity information for an IAM user.

type IAMService

type IAMService interface {
	// ResolveNamespace resolves the effective namespace for a given team+user pair.
	// Results are cached permanently (until process restart) for performance.
	ResolveNamespace(ctx context.Context, team, user string) (string, *domain.AppError)
}

IAMService defines operations related to IAM identity resolution.

func NewIAMService

func NewIAMService(c client.Client) IAMService

NewIAMService creates an IAMService backed by Kubernetes ScitixQuota CRs.

type ListSandboxesResult

type ListSandboxesResult struct {
	Items []domain.Sandbox
	Total int
}

ListSandboxesResult wraps the paginated result and total count for List operations.

type OrganizationService

type OrganizationService interface {
	// ListTeams returns all unique team names found across API Key secrets.
	ListTeams(ctx context.Context) ([]string, *domain.AppError)

	// ListUsersByTeam returns all unique user names belonging to the given team,
	// derived from API Key secrets filtered by team label.
	ListUsersByTeam(ctx context.Context, team string) ([]string, *domain.AppError)

	// ListNamespaces returns all Kubernetes namespace names visible to the operator.
	ListNamespaces(ctx context.Context) ([]string, *domain.AppError)
}

OrganizationService defines operations for querying organizational structure (teams, users, namespaces) derived from API Key Secret labels.

func NewOrganizationService

func NewOrganizationService(c client.Client, ks apikey.KeyStore) OrganizationService

NewOrganizationService creates an OrganizationService backed by Kubernetes resources. keyStore is used to enumerate teams and users via API Key Secret labels.

type QuotaService

type QuotaService interface {
	// ListForUser returns all quotas matching the given user and team. When
	// the underlying provider is disabled the result is an empty list, not
	// an error — callers should treat that as "feature unavailable".
	ListForUser(ctx context.Context, user, team string) ([]domain.QuotaInfo, *domain.AppError)
}

QuotaService defines operations for querying user-visible quotas.

The concrete behaviour (ScitixQuota CRD, external API, disabled stub, ...) is selected by the quota.Provider injected at construction time. The service layer itself stays vendor-neutral — see pkg/plugins/quota for the provider contract and pkg/scitix/quota for the Scitix implementation.

func NewQuotaServiceFromProvider

func NewQuotaServiceFromProvider(p quotaplugin.Provider) QuotaService

NewQuotaServiceFromProvider wraps a quota.Provider in the service-level interface consumed by HTTP handlers. The provider may be a Noop, which yields a service whose ListForUser always returns (nil, nil).

type RegistryStore added in v0.0.2

type RegistryStore interface {
	LookupRegistry(host string) (clusterID, typ string, ok bool)
	RegistryForType(clusterID, typ string) (host string, ok bool)
}

RegistryStore is the subset of cluster.Store used by RewriteImageForCluster. Extracted as an interface so tests can inject a lightweight fake without depending on the full Store implementation.

type RouteInfo

type RouteInfo struct {
	SandboxID string
	Namespace string
	PodName   string
}

RouteInfo is the set of fields the Controller pushes to ExtProc after a successful sandbox claim. SandboxID is the raw UUID (no cluster prefix). Phase and PodIP are intentionally absent: ExtProc reads both live from the Pod informer at request time, so this payload never carries stale state.

type SandboxPoolService

type SandboxPoolService interface {
	Create(ctx context.Context, input domain.CreateSandboxPoolInput) (*domain.SandboxPool, *domain.AppError)
	List(ctx context.Context, namespace, team, user string) ([]domain.SandboxPool, *domain.AppError)
	Get(ctx context.Context, namespace, name string) (*domain.SandboxPool, *domain.AppError)
	Update(ctx context.Context, input domain.UpdateSandboxPoolInput) (*domain.SandboxPool, *domain.AppError)
	Delete(ctx context.Context, namespace, name string) (*domain.DeleteSandboxPoolResult, *domain.AppError)
	// SyncTemplate re-reads the pool's source SandboxTemplate and patches the pool's EmbeddedSandboxTemplate.
	// Does not change replicas. Returns error if pool has no templateName annotation.
	SyncTemplate(ctx context.Context, input domain.SyncSandboxPoolTemplateInput) (*domain.SandboxPool, *domain.AppError)
	// SyncTemplatePreview dry-runs SyncTemplate: returns what the EmbeddedSandboxTemplate would look like
	// after applying all overrides, without writing to Kubernetes.
	SyncTemplatePreview(ctx context.Context, input domain.SyncSandboxPoolTemplateInput) (*domain.SyncTemplatePreviewResult, *domain.AppError)
}

SandboxPoolService defines business operations for SandboxPools.

func NewSandboxPoolService

func NewSandboxPoolService(c client.Client, clientset kubernetes.Interface, pluginManager *plugins.PluginManager) SandboxPoolService

NewSandboxPoolService creates a new SandboxPoolService backed by the given K8s client. clientset may be nil (disables Event-based diagnostics in Get). pluginManager may be nil (disables lifecycle plugins — open-source mode).

type SandboxService

type SandboxService interface {
	Create(ctx context.Context, input domain.CreateSandboxInput) (*domain.Sandbox, *domain.AppError)
	List(ctx context.Context, filter domain.ListSandboxesFilter) (*ListSandboxesResult, *domain.AppError)
	Get(ctx context.Context, namespace, sandboxID string) (*domain.Sandbox, *domain.AppError)
	Delete(ctx context.Context, namespace, sandboxID string) (*domain.DeleteSandboxResult, *domain.AppError)
	// SetTimeout updates the idle timeout annotation on the sandbox pod.
	// A timeout of 0 removes the annotation (no expiry).
	SetTimeout(ctx context.Context, namespace, sandboxID string, timeout time.Duration) *domain.AppError
	// GetLogs retrieves logs for a sandbox. For active sandboxes it fetches live
	// logs from the K8s API; for runtime sources it reads the log file via exec.
	GetLogs(ctx context.Context, namespace, sandboxID string, opts domain.GetLogsOptions) (*domain.SandboxLogs, *domain.AppError)
	// CreateExecToken generates a single-use exec token (TTL 30 s) for the given sandbox.
	// The sandbox must be in the Running phase.
	CreateExecToken(ctx context.Context, namespace, sandboxID string) (string, *domain.AppError)
	// ValidateExecToken validates and consumes the token.
	// Returns ExecTokenInfo on success, or a 401 AppError if the token is invalid / expired.
	ValidateExecToken(tokenStr string) (*domain.ExecTokenInfo, *domain.AppError)
	// ExecCommand runs a one-shot command inside the sandbox pod (non-interactive, no TTY).
	// The sandbox must be in Running phase. clientset must be non-nil.
	ExecCommand(ctx context.Context, namespace, sandboxID string, input domain.ExecCommandInput) (*domain.ExecCommandResult, *domain.AppError)
	// IsReady checks if all runtime readiness probes for a sandbox pass.
	// If a runtime has no ReadinessProbe configured, it is considered ready.
	IsReady(ctx context.Context, namespace, sandboxID string) (*domain.SandboxReadinessResult, *domain.AppError)
}

SandboxService defines business operations for sandboxes.

func NewSandboxService

func NewSandboxService(c client.Client, cs kubernetes.Interface, restCfg *rest.Config, s store.SandboxStore, gatewayBaseURL string, localClusterID string, extprocClient ExtProcClient, registryStore RegistryStore) SandboxService

NewSandboxService creates a new SandboxService backed by the given K8s client and optional stores. clientset is used for live log retrieval and exec; it may be nil (live logs and exec will be unavailable). restConfig is the Kubernetes REST config for exec via SPDY; it may be nil (exec will be unavailable). gatewayBaseURL is the base URL of the Envoy gateway (e.g. http://gateway.example.com); it may be empty. localClusterID identifies the local cluster for cross-cluster sandbox ID prefixing; it may be empty. extprocClient pushes new sandbox routes to ExtProc so Create can return without polling; may be nil in tests. registryStore supplies per-cluster registry metadata for automatic image host rewriting; may be nil (no rewriting).

type SandboxTemplateService

type SandboxTemplateService interface {
	// List returns templates visible to the caller (auth). isAdmin=true bypasses
	// visibility filtering and returns all templates.
	List(ctx context.Context, auth domain.AuthInfo, isAdmin bool) ([]domain.SandboxTemplate, *domain.AppError)
	// Get returns a single template if it is visible to the caller. isAdmin=true
	// bypasses visibility filtering. Returns ErrCodeNotFound when the template
	// does not exist or the caller cannot see it (to avoid leaking names).
	Get(ctx context.Context, name string, auth domain.AuthInfo, isAdmin bool) (*domain.SandboxTemplate, *domain.AppError)
	// Admin only:
	Create(ctx context.Context, tmpl *agentsv1alpha1.SandboxTemplate) (*domain.SandboxTemplate, *domain.AppError)
	Update(ctx context.Context, tmpl *agentsv1alpha1.SandboxTemplate) (*domain.SandboxTemplate, *domain.AppError)
	Delete(ctx context.Context, name string) *domain.AppError
	// CreateOrUpdate upserts a SandboxTemplate. It is used by SyncService to apply
	// sync events from ws-proxy. The operation is idempotent: if the template already
	// exists its spec is replaced; otherwise it is created.
	CreateOrUpdate(ctx context.Context, tmpl *agentsv1alpha1.SandboxTemplate) *domain.AppError
	// StripStaleGlobalLabels removes the sync-source=global label from any local
	// template whose name is NOT present in knownNames. This corrects templates
	// that were mistakenly (or historically) labeled as global but no longer exist
	// on the master cluster, preventing delete requests from being forwarded to
	// ws-proxy and receiving a 404.
	StripStaleGlobalLabels(ctx context.Context, knownNames map[string]struct{}) *domain.AppError
}

SandboxTemplateService defines business operations for SandboxTemplates.

func NewSandboxTemplateService

func NewSandboxTemplateService(c client.Client) SandboxTemplateService

NewSandboxTemplateService creates a new SandboxTemplateService backed by the given K8s client.

type SyncHTTPError

type SyncHTTPError struct {
	Status  int
	Message string
}

SyncHTTPError carries an HTTP-style status code translated from a gRPC status code. It is the cross-package error type that pkg/apiserver/handlers/server.go uses to map sync forwarding failures into the appropriate native API responses.

func (*SyncHTTPError) Error added in v0.0.3

func (e *SyncHTTPError) Error() string

type SyncService

type SyncService interface {
	// OnConnect registers a new ClientConn (produced by wsmux.DialGRPC after
	// a fresh /v1/ws/sync upgrade). Returns a connection generation ID that
	// must be passed back to OnDisconnect so that a stale teardown does not
	// race against a newer connect.
	OnConnect(conn *grpc.ClientConn) uint64

	// OnDisconnect tears down all background Watch goroutines associated
	// with connID and clears the ClientConn — but only if connID still
	// matches the currently-registered conn.
	OnDisconnect(connID uint64)

	RequestCreate(ctx context.Context, req CreateKeyRequest) (*CreateKeyResponse, error)
	RequestDelete(ctx context.Context, name string) error
	RequestTemplateCreate(ctx context.Context, raw json.RawMessage) error
	RequestTemplateUpdate(ctx context.Context, raw json.RawMessage) error
	RequestTemplateDelete(ctx context.Context, name string) error
}

SyncService manages the gRPC client connection to ws-proxy. It exposes synchronous request methods for the apiserver business layer and runs background goroutines that consume the three Watch* streams to keep the local KeyStore / SandboxTemplate / ClusterConfig state up to date.

func NewSyncService

func NewSyncService(store apikey.KeyStore) SyncService

NewSyncService creates a new SyncService.

func NewSyncServiceFull

func NewSyncServiceFull(store apikey.KeyStore, templateSvc SandboxTemplateService, clusterSink ClusterConfigSink) SyncService

NewSyncServiceFull creates a SyncService with all optional components wired in.

func NewSyncServiceWithTemplate

func NewSyncServiceWithTemplate(store apikey.KeyStore, templateSvc SandboxTemplateService) SyncService

NewSyncServiceWithTemplate creates a SyncService that also handles SandboxTemplate sync events.

type URLKind

type URLKind = cluster.URLKind

URLKind selects which base URL from GatewayConfig to use when forwarding. It is an alias for cluster.URLKind so existing callers keep their import surface while the header-merge logic shares the canonical enum.

Jump to

Keyboard shortcuts

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