Documentation
¶
Overview ¶
Package simulator provides a shared framework for building cloud service simulators.
Each simulator is an HTTP server that implements a subset of a cloud provider's REST API surface. The framework provides common infrastructure: HTTP server, request routing, in-memory state management, authentication passthrough, and provider-specific error formatting.
Index ¶
- Constants
- Variables
- func ASCIIFold(s string) string
- func ASCIIFoldUpper(s string) string
- func AWSError(w http.ResponseWriter, code string, message string, statusCode int)
- func AWSErrorf(w http.ResponseWriter, code string, statusCode int, format string, args ...any)
- func AuthPassthroughMiddleware(provider string) func(http.Handler) http.Handler
- func AzureError(w http.ResponseWriter, code string, message string, statusCode int)
- func AzureErrorf(w http.ResponseWriter, code string, statusCode int, format string, args ...any)
- func CaseInsensitiveIndex(s, sub string) int
- func CleanupContainers()
- func CloseDB(db *sql.DB) error
- func ConnectContainerToNetwork(containerName, networkName string, aliases []string) error
- func ContainerPID(containerID string) (int, error)
- func DefaultContainerNetworkGatewayIPv4() (string, error)
- func DisconnectContainerFromNetwork(containerName, networkName string) error
- func DisconnectContainerNetworks(containerID string) error
- func DockerClient() *client.Client
- func EC2ErrorXML(w http.ResponseWriter, code string, message string, requestID string, ...)
- func EnsureDockerNetwork(name string) (string, error)
- func EnsureVPCNetwork(name, cidr string) (string, error)
- func GCPError(w http.ResponseWriter, code int, message string, status string)
- func GCPErrorf(w http.ResponseWriter, code int, status string, format string, args ...any)
- func HasPersistentWorkloadIdentity() bool
- func Identity(ctx context.Context) string
- func InitDocker(provider string, preserveWorkloads bool, stateDir string) *client.Client
- func IsUpgradeRequest(r *http.Request) bool
- func KillProcess(pid int) error
- func LoggingMiddleware(logger zerolog.Logger, provider string) func(http.Handler) http.Handler
- func MissingNetfilterTableHint(err error) string
- func OpenDB(dataDir string) (*sql.DB, error)
- func PathParam(r *http.Request, name string) string
- func PullImage(ctx context.Context, imageName, platform string) error
- func ReadJSON(r *http.Request, v any) error
- func RemoveDockerNetwork(name string) error
- func RemoveExistingContainer(containerID string) error
- func RemoveVolume(name string) error
- func RequestID(ctx context.Context) string
- func RequestIDMiddleware(provider string) func(http.Handler) http.Handler
- func ResolveLocalImage(image string) string
- func RunContainerReaper() bool
- func RuntimeInfo() string
- func S3ErrorXML(w http.ResponseWriter, code string, message string, resource string, ...)
- func StartExistingContainer(containerID string) error
- func StopContainer(containerID string)
- func SyncContainerHostEntries(containerName, marker string, entries []HostEntry) error
- func TunnelUpgradedResponse(w http.ResponseWriter, resp *http.Response) error
- func WaitContainerRemoved(containerID string, timeout time.Duration) error
- func WriteJSON(w http.ResponseWriter, statusCode int, v any)
- func WriteXML(w http.ResponseWriter, statusCode int, v any)
- type AWSQueryRouter
- func (r *AWSQueryRouter) Handler(version, action string) (http.HandlerFunc, bool)
- func (r *AWSQueryRouter) Register(action string, handler http.HandlerFunc)
- func (r *AWSQueryRouter) RegisterVersioned(version, action string, handler http.HandlerFunc)
- func (r *AWSQueryRouter) ServeHTTP(w http.ResponseWriter, req *http.Request)
- func (r *AWSQueryRouter) VersionedActions() map[string][]string
- type AWSRouter
- type AzureRouter
- type Config
- type ContainerConfig
- type ContainerHandle
- type ExistingContainer
- type FrameReader
- func (r *FrameReader) Byte() (byte, error)
- func (r *FrameReader) Offset() int
- func (r *FrameReader) Remaining() int
- func (r *FrameReader) Take(n int) ([]byte, error)
- func (r *FrameReader) Uint8() (uint8, error)
- func (r *FrameReader) Uint16() (uint16, error)
- func (r *FrameReader) Uint32() (uint32, error)
- func (r *FrameReader) Uint64() (uint64, error)
- type FuncSink
- type GCPRouter
- type HostEntry
- type LogLine
- type LogSink
- type MemoryStore
- func (s *MemoryStore[T]) Delete(id string) bool
- func (s *MemoryStore[T]) Filter(fn func(T) bool) []T
- func (s *MemoryStore[T]) Get(id string) (T, bool)
- func (s *MemoryStore[T]) Len() int
- func (s *MemoryStore[T]) List() []T
- func (s *MemoryStore[T]) Put(id string, item T)
- func (s *MemoryStore[T]) Update(id string, fn func(*T)) bool
- func (s *MemoryStore[T]) Upsert(id string, fn func(*T))
- type NoopSink
- type OCIBlob
- type OCIManifest
- type OCIRegistry
- type OCIUpload
- type OTelLogWriter
- type Observability
- type ParseGuard
- type ProcessConfig
- type ProcessHandle
- type ProcessResult
- type ProcessTracker
- type S3ErrorResponse
- type SQLiteStore
- func (s *SQLiteStore[T]) Delete(id string) bool
- func (s *SQLiteStore[T]) Filter(fn func(T) bool) []T
- func (s *SQLiteStore[T]) Get(id string) (T, bool)
- func (s *SQLiteStore[T]) Len() int
- func (s *SQLiteStore[T]) List() []T
- func (s *SQLiteStore[T]) Put(id string, item T)
- func (s *SQLiteStore[T]) Update(id string, fn func(*T)) bool
- func (s *SQLiteStore[T]) Upsert(id string, fn func(*T))
- type SandboxProfile
- type Scanner
- func (sc *Scanner) ConsumePrefix(p string) bool
- func (sc *Scanner) Eof() bool
- func (sc *Scanner) HasPrefix(p string) bool
- func (sc *Scanner) HasPrefixFold(p string) bool
- func (sc *Scanner) Len() int
- func (sc *Scanner) Next() byte
- func (sc *Scanner) Peek() byte
- func (sc *Scanner) PeekAt(n int) byte
- func (sc *Scanner) Pos() int
- func (sc *Scanner) Rest() string
- func (sc *Scanner) SetPos(p int)
- func (sc *Scanner) SkipSpace()
- func (sc *Scanner) Slice(start, end int) string
- type Server
- func (s *Server) DB() *sql.DB
- func (s *Server) Handle(pattern string, handler http.Handler)
- func (s *Server) HandleFunc(pattern string, handler http.HandlerFunc)
- func (s *Server) HandleUIFunc(pattern string, handler http.HandlerFunc)
- func (s *Server) ListenAndServe() error
- func (s *Server) Logger() zerolog.Logger
- func (s *Server) Mux() *http.ServeMux
- func (s *Server) RegisterUI(fsys fs.FS)
- func (s *Server) RoutePatterns() []string
- func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
- func (s *Server) SetSpecValidator(v SpecValidator)
- func (s *Server) StartBackground(worker func(context.Context))
- func (s *Server) Tracker() *ProcessTracker
- func (s *Server) WrapHandler(middleware func(http.Handler) http.Handler)
- type ServiceHandler
- type SpecValidator
- type SpecViolation
- type StateStore
- type Store
Constants ¶
const ( DefaultMaxParseDepth = 512 DefaultMaxParseNodes = 100000 )
DefaultMaxParseDepth / DefaultMaxParseNodes are sane caps for the sims' expression parsers — far above any real query, far below stack/OOM limits.
Variables ¶
var SandboxFargate = SandboxProfile{ Privileged: false, ReadonlyRootfs: false, CapDrop: []string{"ALL"}, CapAdd: []string{"SETUID", "SETGID", "CHOWN", "DAC_OVERRIDE", "FOWNER", "FSETID", "KILL", "SETPCAP", "NET_BIND_SERVICE", "SETFCAP", "SYS_CHROOT"}, NoNewPrivileges: true, DenyDockerSocket: true, DenyHostNetwork: true, }
SandboxFargate matches ECS Fargate's task isolation. Fargate permits more flexibility than Lambda (e.g. user from image) but still denies host net + docker.sock + privileged.
var SandboxLambda = SandboxProfile{ Privileged: false, ReadonlyRootfs: true, User: "1051:1051", CapDrop: []string{"ALL"}, NoNewPrivileges: true, TmpfsSize: "size=512m", DenyDockerSocket: true, DenyHostNetwork: true, }
SandboxLambda matches AWS Lambda's container execution environment: read-only rootfs except /tmp; sandbox user (uid 1051); no new privileges; minimal capabilities.
Source: AWS Lambda Operator Guide — "Lambda runtime environment" (https://docs.aws.amazon.com/lambda/latest/operatorguide/runtime-environment.html).
Functions ¶
func ASCIIFold ¶
ASCIIFold lowercases ONLY ASCII A–Z and is byte-length preserving. Unlike strings.ToLower (Unicode-aware; invalid UTF-8 → 3-byte U+FFFD) it never changes the byte length, so an index computed against ASCIIFold(s) is valid in s. Keywords/operators in the sims are ASCII, so case-insensitive matching is unchanged.
func ASCIIFoldUpper ¶
ASCIIFoldUpper uppercases ONLY ASCII a–z and is byte-length preserving.
func AWSError ¶
func AWSError(w http.ResponseWriter, code string, message string, statusCode int)
AWSError writes an AWS-style JSON error response.
AWS error format:
{"__type": "SomeException", "message": "details"}
func AuthPassthroughMiddleware ¶
AuthPassthroughMiddleware extracts auth identity from provider-specific headers without validating credentials. The identity is stored in the request context. Requests without auth headers are accepted.
func AzureError ¶
func AzureError(w http.ResponseWriter, code string, message string, statusCode int)
AzureError writes an Azure ARM-style JSON error response.
Azure error format:
{"error": {"code": "ResourceNotFound", "message": "details"}}
func AzureErrorf ¶
AzureErrorf writes an Azure-style error with a formatted message.
func CaseInsensitiveIndex ¶
CaseInsensitiveIndex returns the byte index of the first ASCII-case-insensitive occurrence of sub in s, or -1. Because ASCIIFold preserves byte length, the returned index is valid for slicing the ORIGINAL s.
func CleanupContainers ¶
func CleanupContainers()
CleanupContainers stops and removes all simulator-managed containers. Also prunes any Docker networks labeled `sockerless-sim=true` that aren't in use (typically namespace-backed networks that weren't explicitly removed by a DeleteNamespace call). Called on simulator shutdown.
func CloseDB ¶
CloseDB checkpoints all committed WAL records into the database before closing it. FULL synchronous mode protects committed transactions when the host loses power; the explicit checkpoint makes an orderly service shutdown leave one self-contained database file for the next process.
func ConnectContainerToNetwork ¶
ConnectContainerToNetwork connects a running container to a Docker network with the given DNS aliases. Idempotent: if the container is already on the network, the call updates aliases and returns nil.
func ContainerPID ¶
ContainerPID returns the host PID of a running container's main process, used to plumb a veth into the container's network namespace (the netns VPC fabric).
func DefaultContainerNetworkGatewayIPv4 ¶
DefaultContainerNetworkGatewayIPv4 returns the host-side gateway of the container runtime's default bridge. A simulator process running directly on Linux listens in the host namespace, so workload containers reach its callback listeners through this address. Standard host aliases can point outside that Linux host (notably inside a Podman machine), whereas the runtime-reported bridge gateway is the actual packet coordinate.
func DisconnectContainerFromNetwork ¶
DisconnectContainerFromNetwork removes a running container from a Docker network. Idempotent for already-disconnected containers.
func DisconnectContainerNetworks ¶
DisconnectContainerNetworks detaches a running container from every Docker network it currently has. The container keeps its process namespace alive, which lets callers attach their own network fabric afterward.
func DockerClient ¶
DockerClient returns the shared Docker client. InitDocker must have been called first.
func EC2ErrorXML ¶
func EC2ErrorXML(w http.ResponseWriter, code string, message string, requestID string, statusCode int)
EC2ErrorXML writes an AWS Query Protocol XML error response. Used by EC2, IAM, and STS services.
Format:
<Response><Errors><Error><Code>...</Code><Message>...</Message></Error></Errors><RequestId>...</RequestId></Response>
func EnsureDockerNetwork ¶
EnsureDockerNetwork creates a user-defined Docker network with the given name if it doesn't exist. Returns the network ID (existing or newly created). Used by the Cloud Map simulator to back each private DNS namespace with a real Docker network so cross-container DNS works via Docker's embedded DNS resolver.
func EnsureVPCNetwork ¶
func GCPError ¶
func GCPError(w http.ResponseWriter, code int, message string, status string)
GCPError writes a GCP-style JSON error response.
GCP error format:
{"error": {"code": 404, "message": "details", "status": "NOT_FOUND", "details": []}}
func HasPersistentWorkloadIdentity ¶
func HasPersistentWorkloadIdentity() bool
HasPersistentWorkloadIdentity reports whether workload ownership is scoped to a durable simulator state directory.
func InitDocker ¶
InitDocker initializes the shared Docker client and verifies connectivity. Must be called at simulator startup. Fatally exits if Docker is not available.
func IsUpgradeRequest ¶
IsUpgradeRequest reports whether r asks to leave HTTP behind — `Connection: Upgrade` naming a protocol in `Upgrade:` (WebSocket in practice, but the check is protocol-agnostic because the tunnelling below is too).
A forwarding data plane must know this BEFORE it sends the request: an upgraded connection is long-lived, so the request timeout a normal proxied request needs would sever it mid-session.
func LoggingMiddleware ¶
LoggingMiddleware logs each request with zerolog.
func MissingNetfilterTableHint ¶
MissingNetfilterTableHint names the kernel dependency behind a container runtime's refusal to wire a container onto a network, and returns "" for every other failure. Docker 28 and later programs a raw-table PREROUTING DROP rule when it attaches a container to a bridge network, so a kernel built without the corresponding netfilter table cannot start the workload at all; the runtime reports the table it could not initialise but not the module that supplies it, which leaves a minimal guest kernel (a Firecracker microVM, a container-optimised image) looking like a simulator defect.
func OpenDB ¶
OpenDB opens a SQLite database at the given path with WAL mode enabled. Creates the directory and file if they don't exist.
func PullImage ¶
PullImage makes the simulator runtime's bounded, stream-aware pull path available to cloud-product translators that must inspect an image before they can construct its container configuration.
func ReadJSON ¶
ReadJSON reads and decodes a JSON request body into the given value. The read is capped at maxQueryJSONBody so an unbounded body can't exhaust memory.
func RemoveDockerNetwork ¶
RemoveDockerNetwork removes a simulator-managed Docker network if it exists. Errors are returned so callers can log them; idempotent for a missing network.
func RemoveExistingContainer ¶
RemoveExistingContainer stops and removes a workload whose owning control-plane process can no longer drive it.
func RemoveVolume ¶
RemoveVolume removes one explicitly named simulator-managed Docker volume. Callers own the lifecycle decision; this helper never enumerates or prunes unrelated volumes.
func RequestIDMiddleware ¶
RequestIDMiddleware generates a unique request ID and stores it in context. It also sets the provider-specific response header.
func ResolveLocalImage ¶
ResolveLocalImage maps pull-through-cache coordinates back to their upstream Docker Hub images for local execution. Cloud backends can resolve "alpine:latest" to cloud-specific private registry caches:
- GCP AR: "us-central1-docker.pkg.dev/project/docker-hub/library/alpine:latest"
- AWS ECR: "123456789.dkr.ecr.eu-west-1.amazonaws.com/alpine:latest"
- Azure ACR: "myacr.azurecr.io/library/alpine:latest"
Public registry coordinates are already directly pullable by the local container engine and remain unchanged.
func RunContainerReaper ¶
func RunContainerReaper() bool
RunContainerReaper handles the detached cleanup mode before a simulator initializes its API server. It returns false for an ordinary invocation.
func RuntimeInfo ¶
func RuntimeInfo() string
RuntimeInfo returns the container runtime name and version for display.
func S3ErrorXML ¶
func S3ErrorXML(w http.ResponseWriter, code string, message string, resource string, requestID string, statusCode int)
S3ErrorXML writes an S3-style XML error response.
func StartExistingContainer ¶
StartExistingContainer resumes an exited persistent workload container without replacing it, preserving its filesystem, mounts, identity, and port bindings.
func StopContainer ¶
func StopContainer(containerID string)
StopContainer stops a running container by ID.
func SyncContainerHostEntries ¶
SyncContainerHostEntries rewrites a simulator-managed block in a container's /etc/hosts. Docker exposes the backing hosts file path in ContainerInspect; updating it gives netns-backed tasks real libc name resolution without attaching another Docker network to the namespace.
func TunnelUpgradedResponse ¶
func TunnelUpgradedResponse(w http.ResponseWriter, resp *http.Response) error
TunnelUpgradedResponse finishes a 101 Switching Protocols by introducing the two connections to each other and copying bytes until one of them ends.
Relaying the handshake is not enough, and getting only that half right fails in a way that looks like success. `client.Do` + `io.Copy(w, resp.Body)` hands the client a correct-looking 101 — right `Sec-WebSocket-Accept`, right negotiated extensions — and then silently drops everything the client sends, because a ResponseWriter has no path back to the target. The client's frames go nowhere, the target never answers something it never received, and the peer reports a HANDSHAKE TIMEOUT rather than a connection error. So this runs for every 101, whatever the protocol.
Returns an error only for failures that happen BEFORE the client connection is hijacked, so the caller can still write an error response. Once the hijack succeeds the ResponseWriter is spent and nothing may be written to it; a tunnel torn down by either peer is a normal ending, not a proxy error, so it reports nil.
func WaitContainerRemoved ¶
WaitContainerRemoved waits until the runtime no longer owns containerID. Lifecycle callers use this after requesting a stop when the cloud resource must not report its terminal state while its network endpoint or mounts are still present.
Types ¶
type AWSQueryRouter ¶
type AWSQueryRouter struct {
// contains filtered or unexported fields
}
AWSQueryRouter routes AWS Query Protocol requests based on the (Version, Action) form parameter pair. Multiple AWS services define an Action with the same name (`ListTagsForResource` exists on RDS, SNS, ElastiCache, CloudWatchLogs at minimum); the canonical disambiguator is the per-service `Version` form field (RDS=2014-10-31, ElastiCache=2015-02-02, SNS=2010-03-31, etc.).
Services with collision-prone Action names register via `RegisterVersioned(version, action, handler)`. Services whose action names are globally unique (EC2's `CreateVpc`, STS's `GetCallerIdentity`) keep using the legacy `Register(action, handler)` form; those handlers live under the empty-string version bucket and act as a fallback when no versioned match.
func NewAWSQueryRouter ¶
func NewAWSQueryRouter() *AWSQueryRouter
NewAWSQueryRouter creates a new AWS Query Protocol request router.
func (*AWSQueryRouter) Handler ¶
func (r *AWSQueryRouter) Handler(version, action string) (http.HandlerFunc, bool)
Handler returns the handler registered for a (Version, Action) pair using the same versioned-first lookup as ServeHTTP. Internal AWS service integrations use it to enter the real Query service implementation without creating a signed loopback request.
func (*AWSQueryRouter) Register ¶
func (r *AWSQueryRouter) Register(action string, handler http.HandlerFunc)
Register adds a handler for an Action value with no Version constraint. Used by services whose Action names don't collide across AWS — EC2 / IAM / STS / IAM SLR+OIDC.
Example action: "CreateVpc", "GetCallerIdentity"
func (*AWSQueryRouter) RegisterVersioned ¶
func (r *AWSQueryRouter) RegisterVersioned(version, action string, handler http.HandlerFunc)
RegisterVersioned adds a handler for an (Action, Version) pair. Required when the same Action name is defined by multiple AWS services — the Version form parameter selects which service the caller is targeting.
Example: r.RegisterVersioned("2014-10-31", "ListTagsForResource", handleRDSListTags) so RDS's ListTagsForResource doesn't shadow (or get shadowed by) ElastiCache's ListTagsForResource at the router level.
func (*AWSQueryRouter) ServeHTTP ¶
func (r *AWSQueryRouter) ServeHTTP(w http.ResponseWriter, req *http.Request)
ServeHTTP dispatches by (Version, Action). When Version is set and a versioned handler exists, it wins. Otherwise the legacy bucket (version="") is consulted as a fallback.
func (*AWSQueryRouter) VersionedActions ¶
func (r *AWSQueryRouter) VersionedActions() map[string][]string
VersionedActions returns every registered (version, action) pair; actions registered through the legacy Register form appear under the empty-string version. The spec-conformance tests validate this table against the vendored Smithy models (specs/cloud-api/aws/).
type AWSRouter ¶
type AWSRouter struct {
// contains filtered or unexported fields
}
AWSRouter routes AWS API requests based on the X-Amz-Target header. AWS JSON services use POST with X-Amz-Target: ServiceName.ActionName.
func (*AWSRouter) Handler ¶
func (r *AWSRouter) Handler(target string) (http.HandlerFunc, bool)
Handler returns the handler registered for an X-Amz-Target value. Internal AWS service integrations use this to enter the same service implementation as an external SDK request without making a loopback HTTP call.
func (*AWSRouter) Register ¶
func (r *AWSRouter) Register(target string, handler http.HandlerFunc)
Register adds a handler for an X-Amz-Target value. Example target: "AmazonEC2ContainerServiceV20141113.RunTask"
type AzureRouter ¶
type AzureRouter struct {
// contains filtered or unexported fields
}
AzureRouter routes Azure ARM API requests based on resource provider paths. Azure ARM uses paths like /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.App/jobs/{name}.
func NewAzureRouter ¶
func NewAzureRouter() *AzureRouter
NewAzureRouter creates a new Azure request router.
func (*AzureRouter) Handle ¶
func (r *AzureRouter) Handle(pattern string, handler http.HandlerFunc)
Handle registers a pattern for ARM resource paths.
func (*AzureRouter) ServeHTTP ¶
func (r *AzureRouter) ServeHTTP(w http.ResponseWriter, req *http.Request)
ServeHTTP dispatches to the matching path handler. It validates that the api-version query parameter is present.
type Config ¶
type Config struct {
// ListenAddr is the address to listen on (e.g., ":4566").
ListenAddr string
// TLSCert is the path to the TLS certificate file. Empty disables TLS.
TLSCert string
// TLSKey is the path to the TLS private key file.
TLSKey string
// LogLevel is the zerolog log level (trace, debug, info, warn, error).
LogLevel string
// Provider identifies the cloud provider (aws, gcp, azure).
Provider string
// DataDir is the directory for persistent storage (SQLite database, PID files).
// Set via SIM_DATA_DIR. Empty uses a temp directory.
DataDir string
// Persist enables SQLite-backed state persistence.
// Set via SIM_PERSIST=true. Default false (in-memory only).
Persist bool
// LogWriter, when non-nil, is added alongside the ConsoleWriter
// in a MultiLevelWriter so each zerolog event also flows to OTel
// logs via the OTelLogWriter bridge. main.go assigns this from
// InitObservability when the operator brought up the OTel stack
// . Unset = today's stderr-only behaviour.
LogWriter *OTelLogWriter
UIOIDCIssuer string
UIOIDCClientID string
UIOIDCClientSecret string
UIPublicURL string
UISessionSecret string
UIOIDCInsecureCookies bool
ApplicationReleaseRevision string
}
Config holds the simulator server configuration.
func ConfigFromEnv ¶
ConfigFromEnv loads configuration from environment variables.
SIM_LISTEN_ADDR — listen address (default ":8443") SIM_TLS_CERT — TLS certificate file path SIM_TLS_KEY — TLS private key file path SIM_LOG_LEVEL — log level (default "info") SIM_UI_OIDC_ISSUER — Shauth or another OpenID Connect issuer SIM_UI_OIDC_CLIENT_ID — simulator dashboard relying-party client ID SIM_UI_OIDC_CLIENT_SECRET — simulator dashboard relying-party secret SIM_UI_PUBLIC_URL — externally visible simulator origin SIM_UI_SESSION_SECRET — random secret used to sign local sessions APPLICATION_RELEASE_REVISION — immutable deployed release exposed to Shauth validation
type ContainerConfig ¶
type ContainerConfig struct {
Image string // container image (e.g., "alpine:latest")
Architecture string // OS/arch (e.g. "linux/arm64"); see field-level docstring above
Command []string // entrypoint override (empty = use image default)
Args []string // command/args (empty = use image default)
Env map[string]string // environment variables
Timeout time.Duration // max execution time (0 = no limit)
Labels map[string]string // container labels for tracking
Network string // Docker network to join (optional)
IPAddress string // static IPv4 within Network (optional; the VPC ENI IP)
NetworkMode string // Docker network mode (e.g. "container:<id>" for shared netns)
Name string // container name (optional, auto-generated if empty)
Tty bool // allocate a pseudo-TTY
OpenStdin bool // keep stdin open
Binds []string // bind mounts (e.g., "vol:/path")
ExtraHosts []string // --add-host entries (e.g., "host.docker.internal:host-gateway")
// DNS are the resolvers written into the container's /etc/resolv.conf. A
// workload in its own network namespace cannot reach the embedded resolver
// Docker would otherwise configure, so the namespace's own resolver is named
// here; empty leaves Docker's default in place.
DNS []string
WorkingDir string // working directory inside the container (optional)
// PublishPorts maps containerPort → hostPort (bound on 127.0.0.1).
// Used by host-addressed data planes that must reach a workload's
// listener cross-platform: container IPs are only routable from the
// host on Linux, while a loopback port binding works on Docker
// Desktop (macOS/Windows) too. The caller allocates the host port.
PublishPorts map[int]int
// Sandbox: per-platform capability + permission restrictions. Each
// cloud-product handler picks the matching profile (SandboxLambda,
// SandboxFargate, and so on). Zero value = no sandbox enforcement;
// callers without an explicit profile see a one-time warning at
// startup but the container still runs. Production callers must
// always set Sandbox.
Sandbox SandboxProfile
// MemoryBytes is the hard memory limit (cgroup memory.max) applied to the
// container, in bytes. Zero = unbounded. Cloud handlers translate the
// product's advertised sizing (ECS/Fargate task or container memory) here
// so the container's cgroup matches what the metadata advertises.
MemoryBytes int64
// NanoCPU is the CPU limit (cgroup cpu.max) in units of 1e-9 CPUs — e.g.
// 1_000_000_000 == 1 vCPU. Zero = unbounded. Cloud handlers translate the
// product's advertised CPU sizing here.
NanoCPU int64
}
ContainerConfig describes a container to run.
Architecture carries the workload's target arch (e.g. "linux/arm64", "linux/amd64"). The simulator never derives this from the host — the workload's spec carries the field; cloud-product translators pass it through. Empty string means "use the image's default" which in practice resolves to the host arch via Docker (treat that as a not-yet-migrated caller).
type ContainerHandle ¶
type ContainerHandle struct {
ContainerID string
// contains filtered or unexported fields
}
ContainerHandle manages a running container.
func AdoptContainer ¶
func AdoptContainer(containerID string, cfg ContainerConfig, sink LogSink) (*ContainerHandle, error)
AdoptContainer attaches lifecycle observation to a container created by an earlier persistent simulator process. It never restarts the workload; callers decide whether an exited cloud workload should remain terminal or resume.
func StartContainer ¶
func StartContainer(cfg ContainerConfig, sink LogSink) *ContainerHandle
StartContainer pulls the image (if needed), creates and starts a container. Returns a ContainerHandle immediately. Call handle.Wait() to block until exit. Stdout/stderr are streamed to the LogSink.
func StartContainerSync ¶
func StartContainerSync(cfg ContainerConfig, sink LogSink) (*ContainerHandle, error)
StartContainerSync is like StartContainer but returns the handle with ContainerID populated. Blocks until the container is created and started (but not until it exits).
func (*ContainerHandle) Cancel ¶
func (h *ContainerHandle) Cancel()
Cancel stops and removes the container.
func (*ContainerHandle) Wait ¶
func (h *ContainerHandle) Wait() ProcessResult
Wait blocks until the container exits.
type ExistingContainer ¶
type ExistingContainer struct {
ID string
Running bool
PublishedPorts map[int]int
Labels map[string]string
}
ExistingContainer describes a workload container that outlived a persistent simulator control-plane process. Service slices use the same cloud-resource labels they supplied at creation to reclaim ownership after restart.
func FindExistingContainers ¶
func FindExistingContainers(labels map[string]string) ([]ExistingContainer, error)
FindExistingContainers returns every container carrying all requested labels, including exited containers whose terminal result has not yet been reconciled into the durable cloud resource.
type FrameReader ¶
type FrameReader struct {
// contains filtered or unexported fields
}
FrameReader is a bounds-checked cursor over an attacker-controlled byte buffer. Every read returns an error rather than panicking on a short buffer, and Take rejects a negative or over-long length so a wire-declared size can never slice past the buffer or drive a giant allocation. Use it for hand-rolled binary wire decoders instead of raw binary.BigEndian + b[i:j].
func NewFrameReader ¶
func NewFrameReader(data []byte) *FrameReader
NewFrameReader returns a reader positioned at the start of data.
func (*FrameReader) Offset ¶
func (r *FrameReader) Offset() int
Offset reports the current read position.
func (*FrameReader) Remaining ¶
func (r *FrameReader) Remaining() int
Remaining reports the number of unread bytes.
func (*FrameReader) Take ¶
func (r *FrameReader) Take(n int) ([]byte, error)
Take returns the next n bytes (a sub-slice of the backing buffer — do not mutate). A negative or over-long n yields io.ErrUnexpectedEOF.
func (*FrameReader) Uint8 ¶
func (r *FrameReader) Uint8() (uint8, error)
Uint8 reads a 1-byte unsigned int.
func (*FrameReader) Uint16 ¶
func (r *FrameReader) Uint16() (uint16, error)
Uint16 reads a 2-byte big-endian unsigned int.
func (*FrameReader) Uint32 ¶
func (r *FrameReader) Uint32() (uint32, error)
Uint32 reads a 4-byte big-endian unsigned int.
func (*FrameReader) Uint64 ¶
func (r *FrameReader) Uint64() (uint64, error)
Uint64 reads an 8-byte big-endian unsigned int.
type GCPRouter ¶
type GCPRouter struct {
// contains filtered or unexported fields
}
GCPRouter routes GCP API requests based on URL path patterns. GCP REST APIs use paths like /v2/projects/{project}/locations/{location}/jobs.
type LogSink ¶
type LogSink interface {
WriteLog(line LogLine)
}
LogSink receives log lines as they are produced. Each cloud implements its own sink (CloudWatch, Cloud Logging, Log Analytics).
type MemoryStore ¶
type MemoryStore[T any] struct { // contains filtered or unexported fields }
MemoryStore is an in-memory implementation of Store backed by a map.
func NewStateStore ¶
func NewStateStore[T any]() *MemoryStore[T]
NewStateStore creates a new in-memory store. Returns Store[T] for interface compatibility.
func (*MemoryStore[T]) Delete ¶
func (s *MemoryStore[T]) Delete(id string) bool
func (*MemoryStore[T]) Filter ¶
func (s *MemoryStore[T]) Filter(fn func(T) bool) []T
Filter returns snapshots of the stored items matching fn.
func (*MemoryStore[T]) Get ¶
func (s *MemoryStore[T]) Get(id string) (T, bool)
Get returns a snapshot of the stored item. Reference fields (maps, slices, pointers) are deep-copied so callers cannot mutate store-owned state.
func (*MemoryStore[T]) Len ¶
func (s *MemoryStore[T]) Len() int
func (*MemoryStore[T]) List ¶
func (s *MemoryStore[T]) List() []T
List returns snapshots of all stored items.
func (*MemoryStore[T]) Put ¶
func (s *MemoryStore[T]) Put(id string, item T)
func (*MemoryStore[T]) Update ¶
func (s *MemoryStore[T]) Update(id string, fn func(*T)) bool
func (*MemoryStore[T]) Upsert ¶
func (s *MemoryStore[T]) Upsert(id string, fn func(*T))
Upsert atomically create-or-modifies the item at id under the single write lock (absent → the zero value), avoiding the Update-then-Put race.
type OCIBlob ¶
OCIBlob is a stored content-addressed blob (image config or layer), keyed by `repo@digest`.
type OCIManifest ¶
OCIManifest is a stored image manifest, keyed by `repo:reference` (tag or digest).
type OCIRegistry ¶
type OCIRegistry struct {
Manifests Store[OCIManifest]
Blobs Store[OCIBlob]
Uploads Store[OCIUpload]
// OnManifestPut, if set, is invoked after a manifest is stored so the cloud
// can register a control-plane image row.
OnManifestPut func(repo, ref, contentType string, data []byte)
// HydrateManifest, if set, is invoked on a manifest GET/HEAD miss to let the
// cloud's pull-through cache populate the manifest (+ its blobs) via
// PutBlob/PutManifest. Returns true if it populated the requested manifest.
HydrateManifest func(reg *OCIRegistry, repo, ref string) bool
// SkipPath, if set, returns true for `/v2/`-prefixed paths the surrounding
// mux serves elsewhere (e.g. GCP's `/v2/projects/` control-plane routes);
// those get a 404 here so the cloud handler can take them.
SkipPath func(path string) bool
// contains filtered or unexported fields
}
OCIRegistry serves the OCI Distribution data plane from a trio of stores.
func (*OCIRegistry) PutBlob ¶
func (reg *OCIRegistry) PutBlob(repo, digest, contentType string, data []byte)
PutBlob stores a content-addressed blob (used by hydration hooks).
func (*OCIRegistry) PutManifest ¶
func (reg *OCIRegistry) PutManifest(repo, ref, contentType string, data []byte)
PutManifest stores a manifest under both its tag/reference and its digest (used by hydration hooks).
func (*OCIRegistry) Register ¶
func (reg *OCIRegistry) Register(srv *Server)
Register mounts the data plane on the /v2/ subtree for every method, then dispatches by method internally. Registering one method-specific subtree per verb (rather than a bare `/v2/` or per-method `{wildcard...}` patterns) sidesteps three ServeMux pitfalls at once: the Go 1.22 rule that a `{name...}` wildcard must be the final segment; the method-pattern split that left blob-upload POST unrouted on ACR; and the conflict a bare all-method `/v2/` raises against a method-specific root pattern like awsJson's `POST /` (a method-specific subtree is strictly more specific, so it wins cleanly).
type OCIUpload ¶
OCIUpload tracks an in-progress (possibly chunked) blob upload, keyed by its upload UUID.
type OTelLogWriter ¶
type OTelLogWriter struct {
// contains filtered or unexported fields
}
OTelLogWriter — zerolog → OTel logs bridge. Implements io.Writer so it slots into zerolog.MultiLevelWriter alongside ConsoleWriter.
type Observability ¶
type Observability struct {
LogWriter *OTelLogWriter
Shutdown func(context.Context) error
}
Observability bundles trace + log SDK shutdown + a zerolog Writer that mirrors entries to the OTel logs SDK. Mirror of `backends/core.Observability` — bleephub is a separate Go module without backend-core as a dep, so the bridge lives here.
func InitObservability ¶
func InitObservability(serviceName string) (*Observability, error)
InitObservability sets up both tracer + logger providers when OTEL_EXPORTER_OTLP_ENDPOINT is set. Returns a zero-value Observability with a no-op Shutdown when OTel is disabled.
Components-decoupled invariant intact.
type ParseGuard ¶
type ParseGuard struct {
// contains filtered or unexported fields
}
ParseGuard bounds recursion depth and total node count for a recursive-descent parser so deeply-nested or pathological input can't blow the stack or hang. Enter() at the top of each recursive production, Leave() (usually deferred) on the way out; bail out of the parse when Enter() returns false.
func NewParseGuard ¶
func NewParseGuard(maxDepth, maxNodes int) *ParseGuard
NewParseGuard returns a guard with the given caps (0 = the Default caps).
func (*ParseGuard) Enter ¶
func (g *ParseGuard) Enter() bool
Enter records one level of recursion / one node and reports whether the parse is still within budget. A false return means the input is too deep/large and the parser must stop (treat as a parse error).
func (*ParseGuard) Exceeded ¶
func (g *ParseGuard) Exceeded() bool
Exceeded reports whether any budget has been blown.
func (*ParseGuard) Leave ¶
func (g *ParseGuard) Leave()
Leave undoes one Enter's depth (not the node count — total work stays capped).
type ProcessConfig ¶
type ProcessConfig struct {
Command []string // entrypoint + args (e.g. ["echo", "hello"])
Env map[string]string // environment variables
Dir string // working directory (optional)
Timeout time.Duration // max execution time (0 = no timeout)
}
ProcessConfig describes what to execute.
type ProcessHandle ¶
type ProcessHandle struct {
// contains filtered or unexported fields
}
ProcessHandle allows waiting on or cancellation of a running process.
func StartProcess ¶
func StartProcess(cfg ProcessConfig, sink LogSink) *ProcessHandle
StartProcess launches a command and streams output to the sink. Returns a handle for waiting/cancellation. Non-blocking.
func StartTrackedProcess ¶
func StartTrackedProcess(id string, cfg ProcessConfig, sink LogSink, tracker *ProcessTracker) *ProcessHandle
StartTrackedProcess launches a process and tracks its PID for recovery. The tracker may be nil (no persistence), in which case this is equivalent to StartProcess.
func (*ProcessHandle) Wait ¶
func (h *ProcessHandle) Wait() ProcessResult
Wait blocks until the process completes.
type ProcessResult ¶
type ProcessResult struct {
ExitCode int
StartedAt time.Time
StoppedAt time.Time
Error error // non-nil if process failed to start
}
ProcessResult is returned when the process completes.
type ProcessTracker ¶
type ProcessTracker struct {
// contains filtered or unexported fields
}
ProcessTracker persists running process PIDs to disk for recovery after restart.
func NewProcessTracker ¶
func NewProcessTracker(dir string) *ProcessTracker
NewProcessTracker creates a tracker that stores PID files in the given directory. Returns nil if dir is empty (tracking disabled).
func (*ProcessTracker) LiveProcesses ¶
func (t *ProcessTracker) LiveProcesses() map[string]int
LiveProcesses scans the PID directory and returns IDs mapped to PIDs for processes that are still alive. Dead PID files are cleaned up.
func (*ProcessTracker) Track ¶
func (t *ProcessTracker) Track(id string, pid int)
Track records a process PID for later recovery.
func (*ProcessTracker) Untrack ¶
func (t *ProcessTracker) Untrack(id string)
Untrack removes the PID file for a completed process.
type S3ErrorResponse ¶
type S3ErrorResponse struct {
XMLName xml.Name `xml:"Error"`
Code string `xml:"Code"`
Message string `xml:"Message"`
Resource string `xml:"Resource,omitempty"`
RequestID string `xml:"RequestId"`
}
S3Error writes an S3-style XML error response.
S3 uses XML for error responses, unlike other AWS services.
type SQLiteStore ¶
type SQLiteStore[T any] struct { // contains filtered or unexported fields }
SQLiteStore is a persistent implementation of Store backed by SQLite. Each instance maps to a single table with (key TEXT, value BLOB) schema.
func NewSQLiteStore ¶
NewSQLiteStore creates a persistent store backed by a SQLite table. Creates the table if it doesn't exist.
func (*SQLiteStore[T]) Delete ¶
func (s *SQLiteStore[T]) Delete(id string) bool
func (*SQLiteStore[T]) Filter ¶
func (s *SQLiteStore[T]) Filter(fn func(T) bool) []T
func (*SQLiteStore[T]) Get ¶
func (s *SQLiteStore[T]) Get(id string) (T, bool)
func (*SQLiteStore[T]) Len ¶
func (s *SQLiteStore[T]) Len() int
func (*SQLiteStore[T]) List ¶
func (s *SQLiteStore[T]) List() []T
func (*SQLiteStore[T]) Put ¶
func (s *SQLiteStore[T]) Put(id string, item T)
func (*SQLiteStore[T]) Update ¶
func (s *SQLiteStore[T]) Update(id string, fn func(*T)) bool
func (*SQLiteStore[T]) Upsert ¶
func (s *SQLiteStore[T]) Upsert(id string, fn func(*T))
Upsert atomically create-or-modifies the row at id under the single write lock (absent → the zero value), avoiding the Update-then-Put race.
type SandboxProfile ¶
type SandboxProfile struct {
// Privileged: real clouds NEVER allow privileged containers for
// workload code (only some platform-managed system containers).
// Always false here.
Privileged bool
// ReadonlyRootfs: Lambda + Cloud Run + Functions Gen2 + ACA + AZF
// all enforce read-only rootfs for workload containers; only
// declared writable mounts (e.g. /tmp) are writable.
ReadonlyRootfs bool
// User: when non-empty, force this UID:GID (or "name") into
// container.Config.User. Lambda uses uid 1051 ("sbx_user1051");
// Cloud Run defaults to non-root if not specified by image.
User string
// CapDrop: capabilities to drop. ALL means drop the kernel
// CAP_BASE set; CapAdd lets specific caps back in.
CapDrop []string
// CapAdd: capabilities to keep. Real Lambda workloads have ~zero
// extra caps. Cloud Run grants SETUID/SETGID by default; ACA
// similar.
CapAdd []string
// NoNewPrivileges: maps to `--security-opt=no-new-privileges`.
// Hardens setuid binaries against escalation. All clouds enforce.
NoNewPrivileges bool
// TmpfsSize: when non-empty, mount /tmp as tmpfs with this size
// option string ("size=512m"). Lambda enforces tmpfs /tmp.
TmpfsSize string
// DenyDockerSocket: refuse to mount the host's docker.sock under
// any path. Real clouds expose no such surface.
DenyDockerSocket bool
// DenyHostNetwork: refuse `NetworkMode=host`. Real clouds expose
// no host networking to workloads.
DenyHostNetwork bool
}
SandboxProfile encodes the security restrictions the real cloud platform applies to workload containers. The sim enforces them on the local Docker daemon so workloads that "work in the sim" can't rely on privileges the real cloud would reject.
Profiles are deliberately MORE restrictive than the cloud where the Docker primitive permits a stricter setting cheaply (e.g. cap drops): "never higher than the real cloud" is the bar; equal-or- stricter is acceptable.
func (SandboxProfile) Apply ¶
func (p SandboxProfile) Apply(hostCfg *container.HostConfig, containerCfg *container.Config) error
Apply mutates the given HostConfig to enforce the profile. Returns an error if cfg.NetworkMode or cfg.Binds violates a deny rule (these are caller mistakes — not silently fixed).
type Scanner ¶
type Scanner struct {
// contains filtered or unexported fields
}
Scanner is a bounds-safe cursor over a string. Every accessor is range-checked so a parser can never panic on an index or slice, regardless of input.
func NewScanner ¶
NewScanner returns a Scanner positioned at the start of s.
func (*Scanner) ConsumePrefix ¶
ConsumePrefix advances past p when the remaining input starts with it.
func (*Scanner) HasPrefixFold ¶
HasPrefixFold is HasPrefix with ASCII-case-insensitive matching (byte-length preserving via ASCIIFold, so it stays slice-safe).
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the main simulator HTTP server.
func NewServer ¶
NewServer creates a new simulator server with the given configuration.
Returns an error when persistence is requested (cfg.Persist=true) but the data directory cannot be opened. Operator asked for durable state; degrading silently to in-memory would mask misconfiguration (bad path, perms, full disk) and produce silent data loss across restarts.
func (*Server) HandleFunc ¶
func (s *Server) HandleFunc(pattern string, handler http.HandlerFunc)
HandleFunc registers a handler function on the server's mux.
func (*Server) HandleUIFunc ¶
func (s *Server) HandleUIFunc(pattern string, handler http.HandlerFunc)
HandleUIFunc registers a simulator operator endpoint that shares the user interface's OpenID Connect session boundary. Native cloud API routes must continue to use Handle or HandleFunc so their public protocol is unchanged.
func (*Server) ListenAndServe ¶
ListenAndServe starts the server and blocks until shutdown. It listens for SIGTERM and SIGINT for graceful shutdown.
func (*Server) RegisterUI ¶
RegisterUI registers an embedded SPA at /ui/ and redirects GET / to /ui/. When a simulated service already owns the API root — S3's ListBuckets is "GET /{$}" — the API surface wins: registering the redirect anyway would panic the mux at startup, and the UI stays reachable at /ui/ directly.
func (*Server) RoutePatterns ¶
RoutePatterns returns every pattern registered through Handle / HandleFunc, in registration order.
func (*Server) ServeHTTP ¶
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP serves through the same final handler chain as ListenAndServe.
func (*Server) SetSpecValidator ¶
func (s *Server) SetSpecValidator(v SpecValidator)
SetSpecValidator registers the per-cloud validator and, when SOCKERLESS_SPEC_VALIDATE is set, arms the capture middleware. Must be called before ListenAndServe / ServeHTTP.
func (*Server) StartBackground ¶
StartBackground runs service work under the server lifecycle. The worker must return when ctx is cancelled. ListenAndServe cancels and drains every registered worker before checkpointing and closing SQLite, so no service can query durable state after orderly shutdown has closed the database.
func (*Server) Tracker ¶
func (s *Server) Tracker() *ProcessTracker
Tracker returns the process tracker, or nil if persistence is disabled.
type ServiceHandler ¶
type ServiceHandler interface {
// ServiceName returns the display name of the service (e.g., "ECS", "CloudRun").
ServiceName() string
}
ServiceHandler handles requests for a single simulated cloud service.
type SpecValidator ¶
type SpecValidator func(req *http.Request, reqBody []byte, status int, respHeader http.Header, respBody []byte) []SpecViolation
SpecValidator inspects one exchange and reports spec divergences. status/respBody are the simulator's response; reqBody is the request payload (already drained and restored on the request the handler saw).
type SpecViolation ¶
type SpecViolation struct {
// Op identifies the operation (X-Amz-Target, "METHOD /path", ...).
Op string `json:"op"`
// Kind classifies the divergence (unknown-field, type-mismatch, ...).
Kind string `json:"kind"`
// Field is the JSON path of the offending member.
Field string `json:"field"`
// Detail is a human-readable explanation.
Detail string `json:"detail"`
}
SpecViolation is one observed divergence between a simulator response and the vendored cloud API spec (specs/cloud-api/).
func (SpecViolation) Key ¶
func (v SpecViolation) Key() string
Key is the stable identity used by the ratchet allowlist.
type StateStore ¶
type StateStore[T any] = MemoryStore[T]
StateStore is an alias for backward compatibility. New code should use Store[T] interface or MemoryStore[T] directly.
type Store ¶
type Store[T any] interface { Get(id string) (T, bool) Put(id string, item T) Delete(id string) bool List() []T Filter(fn func(T) bool) []T Len() int Update(id string, fn func(*T)) bool // Upsert atomically applies fn to the item at id under a single lock, // creating it from the zero value when absent (create-or-modify). Use it // for read-modify-write that must not race a concurrent writer the way a // separate Update-then-Put pair would. Upsert(id string, fn func(*T)) }
Store is the interface for a typed key-value store. Implemented by MemoryStore (in-memory) and SQLiteStore (persistent).
func MakeStore ¶
MakeStore returns a SQLiteStore if db is non-nil, or a MemoryStore otherwise.
When db is non-nil but NewSQLiteStore fails (CREATE TABLE rejected by SQLite — typically corruption or fs perms post-OpenDB), the process exits via log.Fatalf rather than silently dropping that one table back to memory while its neighbours stay durable. Half- persistent state would surface as confusing "some data lost on restart" reports later.
All 100+ call sites are register*-time, so log.Fatalf here is the equivalent of a startup error — operator sees the message and the failing table name immediately, no degraded-mode running.