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 ContainerIPv4(id string) string
- func DisconnectContainerFromNetwork(containerName, networkName string) error
- func DockerClient() *client.Client
- func EC2ErrorXML(w http.ResponseWriter, code string, message string, requestID string, ...)
- func EnsureDockerNetwork(name string) (string, error)
- func FindContainerByIP(ip string) string
- 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 Identity(ctx context.Context) string
- func InitDocker(provider 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 OpenDB(dataDir string) (*sql.DB, error)
- func PathParam(r *http.Request, name string) string
- func ReadJSON(r *http.Request, v any) error
- func RemoveDockerNetwork(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 StartHTTPContainer(ctx context.Context, cfg HTTPContainerConfig) (string, error)
- func StopAndRemoveContainer(containerID string)
- func StopContainer(containerID string)
- func StreamContainerLogs(ctx context.Context, containerID string, sink LogSink)
- func TunnelUpgradedResponse(w http.ResponseWriter, resp *http.Response) error
- func WriteJSON(w http.ResponseWriter, statusCode int, v any)
- func WriteXML(w http.ResponseWriter, statusCode int, v any)
- type AWSQueryRouter
- 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 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 HTTPContainerConfig
- 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
- 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
- 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) 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 SandboxCloudRun = SandboxProfile{ Privileged: false, ReadonlyRootfs: false, CapDrop: []string{"ALL"}, CapAdd: []string{"NET_BIND_SERVICE", "SETUID", "SETGID", "CHOWN", "DAC_OVERRIDE", "FOWNER", "FSETID", "KILL", "SETPCAP", "SETFCAP"}, NoNewPrivileges: true, DenyDockerSocket: true, DenyHostNetwork: true, }
SandboxCloudRun matches Cloud Run / Cloud Run Functions Gen2's container execution environment. Cloud Run gVisor sandbox denies privileged, bans CAP_SYS_ADMIN, disallows raw sockets. User defaults to non-root unless the image's USER directive overrides. gVisor's syscall filter is stricter than local Docker enforces; these settings match the cap deny list Cloud Run applies on top of gVisor.
var SandboxGCFGen2 = SandboxCloudRun
SandboxGCFGen2 mirrors SandboxCloudRun — Cloud Functions Gen2 runs on Cloud Run Services underneath. Alias for clarity at call sites.
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 ContainerIPv4 ¶
ContainerIPv4 returns a running container's primary IPv4 address on its docker network, or "" if unavailable. A sim running inside a harness container reaches a workload by this bridge IP (routable container-to- container) rather than a host-published port, which binds the host's loopback — not the sim container's.
func DisconnectContainerFromNetwork ¶
DisconnectContainerFromNetwork removes a running container from a Docker network. Idempotent for already-disconnected containers.
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 FindContainerByIP ¶
FindContainerByIP inspects all simulator-managed Docker containers and returns the name of the one whose bridge/default-network IP matches `ip`. Used by the Cloud DNS slice: when an A record is added to a private zone, the simulator finds the container whose IP matches the record's Rrdatas[0] and connects it to the zone's Docker network with the record name as DNS alias. Returns "" if no managed container has the given IP.
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 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 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 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 RequestIDMiddleware ¶
RequestIDMiddleware generates a unique request ID and stores it in context. It also sets the provider-specific response header.
func ResolveLocalImage ¶
ResolveLocalImage maps cloud registry URIs back to Docker Hub images for local execution. Cloud backends resolve "alpine:latest" to cloud-specific registries:
- 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"
The simulator runs containers locally where only Docker Hub images exist, so these URIs must be resolved back to their original form.
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 StartHTTPContainer ¶
func StartHTTPContainer(ctx context.Context, cfg HTTPContainerConfig) (string, error)
StartHTTPContainer starts a container detached, with its container- internal :8080 published to the requested host port. Returns the container ID; the caller is responsible for stopping/removing the container via StopAndRemoveContainer when done. The image must already be present in the local docker daemon (no pull-from-network fallback — the sim runs entirely on local images so caller failures are surfaced as real "image not present" errors instead of hanging on registry-pull retries).
func StopAndRemoveContainer ¶
func StopAndRemoveContainer(containerID string)
StopAndRemoveContainer stops and removes the container. Idempotent — best-effort cleanup; errors are silenced because callers typically defer this and there's nothing useful to report.
func StopContainer ¶
func StopContainer(containerID string)
StopContainer stops a running container by ID.
func StreamContainerLogs ¶
StreamContainerLogs follows the container's stdout/stderr and writes each line to the provided sink. Returns when the container exits or `ctx` is cancelled. Used by the FaaS-bootstrap invocation flow to forward bootstrap + user-subprocess output into Cloud Logging while the invocation is in flight.
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.
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) 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) 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)
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")
// Sandbox enforces per-platform capability + permission parity
// with the real cloud. Zero value = no enforcement;
// every production sim caller must set this.
Sandbox SandboxProfile
}
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 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 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 HTTPContainerConfig ¶
type HTTPContainerConfig struct {
Image string // overlay image (must be locally available)
Architecture string // OS/arch (e.g. "linux/arm64"); workload's spec — never derived from host
HostPort int // host port to publish container's :8080 to
Command []string // entrypoint override (empty = use image default)
Args []string // command/args (empty = use image default)
Env map[string]string // env vars (must include PORT to match the published port-target)
Name string // container name (optional, auto-generated if empty)
Labels map[string]string // container labels for tracking
Binds []string // bind mounts (e.g., "vol:/path")
ExtraHosts []string // --add-host entries (e.g., "metadata.google.internal:host-gateway")
// Sandbox: per-platform capability + permission parity.
Sandbox SandboxProfile
}
HTTPContainerConfig describes a container that exposes an HTTP listener on its $PORT (default 8080), to be invoked over HTTP from the host. Used by the FaaS-bootstrap invocation flow (Cloud Functions Gen2, Lambda container mode) where the function image's ENTRYPOINT serves HTTP and the platform POSTs requests to it.
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
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
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 ("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) 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 }
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.