simulator

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 11, 2026 License: AGPL-3.0 Imports: 58 Imported by: 0

README

simulator

Shared framework for building local cloud service reimplementations. Provides HTTP server infrastructure, request routing, in-memory state management, authentication passthrough, and provider-specific error formatting.

All three cloud simulators (AWS, GCP, Azure) import this library as sim. The simulators built on this framework are not mocks or stubs — they reimplement actual cloud service semantics, with execution behavior driven by the same configuration (timeouts, replica counts, etc.) that the real services honor.

Components

File Purpose
server.go HTTP server with graceful shutdown, TLS support, health endpoint
config.go Configuration from environment variables
router.go AWS (X-Amz-Target), GCP (REST path), and Azure (ARM path) request routers
state.go Generic thread-safe StateStore[T] for in-memory resources
errors.go Provider-specific error formatters (AWS JSON, GCP JSON, Azure ARM, AWS XML)
middleware.go Request ID generation, identity extraction, request logging

StateStore

Generic, thread-safe key-value store for simulated resources:

store := sim.NewStateStore[*VPC]()
store.Put("vpc-123", &VPC{ID: "vpc-123"})
vpc, ok := store.Get("vpc-123")
store.Delete("vpc-123")
store.List()   // returns all values
store.Count()  // returns count

Routers

AWSRouter

Routes by X-Amz-Target header (AWS JSON protocol):

router := sim.NewAWSRouter()
router.Register("AmazonEC2ContainerServiceV20141113.RunTask", handleRunTask)
Path-based routing

GCP and Azure simulators register handlers on the server mux directly.

Error formatting

sim.AWSError(w, "ResourceNotFoundException", "Cluster not found", 400)
sim.GCPError(w, 404, "Zone not found", "NOT_FOUND")
sim.AzureError(w, 404, "ResourceNotFound", "Resource group not found")

Configuration

Loaded from environment variables via sim.ConfigFromEnv(provider):

Variable Default Description
SIM_LISTEN_ADDR :8443 Listen address
SIM_TLS_CERT TLS certificate file path
SIM_TLS_KEY TLS private key file path
SIM_LOG_LEVEL info Log level: trace, debug, info, warn, error
SIM_UI_OIDC_ISSUER Shauth OpenID Connect issuer; enables dashboard authentication with the remaining UI values
SIM_UI_OIDC_CLIENT_ID Dashboard relying-party client ID
SIM_UI_OIDC_CLIENT_SECRET Dashboard relying-party client secret
SIM_UI_PUBLIC_URL Exact public dashboard origin
SIM_UI_SESSION_SECRET Independent random session-signing secret of at least 32 bytes
SIM_UI_INSECURE_COOKIES false Allows HTTP only for explicit loopback integration tests
APPLICATION_RELEASE_REVISION Immutable 12–64 character lowercase hexadecimal revision or sha256: digest; required with dashboard authentication

Authenticated dashboards expose GET /auth/validation with Shauth's exact username, email, role, and immutable release fields. Anonymous and bearer-only requests receive an exact 303 to the dashboard's public /auth/signed-out page; validator material is accepted only by Shauth.

Usage

cfg := sim.ConfigFromEnv("aws")
srv, err := sim.NewServer(cfg)
if err != nil {
    log.Fatalf("simulator startup: %v", err)
}

// Register service handlers
srv.Handle("/ecs/", ecsHandler)

srv.Run() // blocks until SIGTERM/SIGINT

Testing

This library is tested transitively through the simulator and SDK/CLI/Terraform test suites.

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

View Source
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

View Source
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.

View Source
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

func ASCIIFold(s string) string

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

func ASCIIFoldUpper(s string) string

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 AWSErrorf

func AWSErrorf(w http.ResponseWriter, code string, statusCode int, format string, args ...any)

AWSErrorf writes an AWS-style error with a formatted message.

func AuthPassthroughMiddleware

func AuthPassthroughMiddleware(provider string) func(http.Handler) http.Handler

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

func AzureErrorf(w http.ResponseWriter, code string, statusCode int, format string, args ...any)

AzureErrorf writes an Azure-style error with a formatted message.

func CaseInsensitiveIndex

func CaseInsensitiveIndex(s, sub string) int

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

func CloseDB(db *sql.DB) error

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

func ConnectContainerToNetwork(containerName, networkName string, aliases []string) error

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

func ContainerPID(containerID string) (int, error)

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

func DefaultContainerNetworkGatewayIPv4() (string, error)

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

func DisconnectContainerFromNetwork(containerName, networkName string) error

DisconnectContainerFromNetwork removes a running container from a Docker network. Idempotent for already-disconnected containers.

func DisconnectContainerNetworks

func DisconnectContainerNetworks(containerID string) error

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

func DockerClient() *client.Client

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

func EnsureDockerNetwork(name string) (string, error)

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 EnsureVPCNetwork(name, cidr string) (string, error)

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 GCPErrorf

func GCPErrorf(w http.ResponseWriter, code int, status string, format string, args ...any)

GCPErrorf writes a GCP-style error with a formatted message.

func HasPersistentWorkloadIdentity

func HasPersistentWorkloadIdentity() bool

HasPersistentWorkloadIdentity reports whether workload ownership is scoped to a durable simulator state directory.

func Identity

func Identity(ctx context.Context) string

Identity returns the caller identity from the context.

func InitDocker

func InitDocker(provider string, preserveWorkloads bool, stateDir string) *client.Client

InitDocker initializes the shared Docker client and verifies connectivity. Must be called at simulator startup. Fatally exits if Docker is not available.

func IsUpgradeRequest

func IsUpgradeRequest(r *http.Request) bool

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 KillProcess

func KillProcess(pid int) error

KillProcess sends SIGTERM to a tracked process.

func LoggingMiddleware

func LoggingMiddleware(logger zerolog.Logger, provider string) func(http.Handler) http.Handler

LoggingMiddleware logs each request with zerolog.

func MissingNetfilterTableHint

func MissingNetfilterTableHint(err error) string

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

func OpenDB(dataDir string) (*sql.DB, error)

OpenDB opens a SQLite database at the given path with WAL mode enabled. Creates the directory and file if they don't exist.

func PathParam

func PathParam(r *http.Request, name string) string

PathParam extracts a path parameter from the request using Go 1.22+ routing.

func PullImage

func PullImage(ctx context.Context, imageName, platform string) error

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

func ReadJSON(r *http.Request, v any) error

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

func RemoveDockerNetwork(name string) error

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

func RemoveExistingContainer(containerID string) error

RemoveExistingContainer stops and removes a workload whose owning control-plane process can no longer drive it.

func RemoveVolume

func RemoveVolume(name string) error

RemoveVolume removes one explicitly named simulator-managed Docker volume. Callers own the lifecycle decision; this helper never enumerates or prunes unrelated volumes.

func RequestID

func RequestID(ctx context.Context) string

RequestID returns the request ID from the context.

func RequestIDMiddleware

func RequestIDMiddleware(provider string) func(http.Handler) http.Handler

RequestIDMiddleware generates a unique request ID and stores it in context. It also sets the provider-specific response header.

func ResolveLocalImage

func ResolveLocalImage(image string) string

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

func StartExistingContainer(containerID string) error

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

func SyncContainerHostEntries(containerName, marker string, entries []HostEntry) error

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

func WaitContainerRemoved(containerID string, timeout time.Duration) error

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.

func WriteJSON

func WriteJSON(w http.ResponseWriter, statusCode int, v any)

WriteJSON writes a JSON response with the given status code.

func WriteXML

func WriteXML(w http.ResponseWriter, statusCode int, v any)

WriteXML writes an XML response with the given status code.

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 NewAWSRouter

func NewAWSRouter() *AWSRouter

NewAWSRouter creates a new AWS request router.

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"

func (*AWSRouter) ServeHTTP

func (r *AWSRouter) ServeHTTP(w http.ResponseWriter, req *http.Request)

ServeHTTP dispatches to the handler matching the X-Amz-Target header.

func (*AWSRouter) Targets

func (r *AWSRouter) Targets() []string

Targets returns every registered X-Amz-Target value. The spec-conformance tests validate this table against the vendored Smithy models (specs/cloud-api/aws/).

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

func ConfigFromEnv(provider string) Config

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) Byte

func (r *FrameReader) Byte() (byte, error)

Byte reads one byte.

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 FuncSink

type FuncSink func(LogLine)

FuncSink wraps a function as a LogSink.

func (FuncSink) WriteLog

func (f FuncSink) WriteLog(line LogLine)

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.

func NewGCPRouter

func NewGCPRouter() *GCPRouter

NewGCPRouter creates a new GCP request router.

func (*GCPRouter) Handle

func (r *GCPRouter) Handle(pattern string, handler http.HandlerFunc)

Handle registers a pattern (e.g., "POST /v2/projects/{project}/locations/{location}/jobs").

func (*GCPRouter) ServeHTTP

func (r *GCPRouter) ServeHTTP(w http.ResponseWriter, req *http.Request)

ServeHTTP dispatches to the matching path handler.

type HostEntry

type HostEntry struct {
	IP   string
	Name string
}

type LogLine

type LogLine struct {
	Stream    string // "stdout" or "stderr"
	Text      string
	Timestamp time.Time
}

LogLine is a single line of captured output.

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 NoopSink

type NoopSink struct{}

NoopSink discards all log output.

func (NoopSink) WriteLog

func (NoopSink) WriteLog(LogLine)

type OCIBlob

type OCIBlob struct {
	Digest      string
	ContentType string
	Data        []byte
}

OCIBlob is a stored content-addressed blob (image config or layer), keyed by `repo@digest`.

type OCIManifest

type OCIManifest struct {
	ContentType string
	Digest      string
	Data        []byte
	Repo        string
	Ref         string
}

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

type OCIUpload struct {
	UUID string
	Repo string
	Data []byte
}

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.

func (*OTelLogWriter) Write

func (w *OTelLogWriter) Write(p []byte) (int, error)

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) Cancel

func (h *ProcessHandle) Cancel()

Cancel kills the process.

func (*ProcessHandle) Pid

func (h *ProcessHandle) Pid() int

Pid returns the OS process ID.

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

func NewSQLiteStore[T any](db *sql.DB, table string) (*SQLiteStore[T], error)

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

func NewScanner(s string) *Scanner

NewScanner returns a Scanner positioned at the start of s.

func (*Scanner) ConsumePrefix

func (sc *Scanner) ConsumePrefix(p string) bool

ConsumePrefix advances past p when the remaining input starts with it.

func (*Scanner) Eof

func (sc *Scanner) Eof() bool

Eof reports whether the cursor is at or past the end.

func (*Scanner) HasPrefix

func (sc *Scanner) HasPrefix(p string) bool

HasPrefix reports whether the remaining input starts with p.

func (*Scanner) HasPrefixFold

func (sc *Scanner) HasPrefixFold(p string) bool

HasPrefixFold is HasPrefix with ASCII-case-insensitive matching (byte-length preserving via ASCIIFold, so it stays slice-safe).

func (*Scanner) Len

func (sc *Scanner) Len() int

func (*Scanner) Next

func (sc *Scanner) Next() byte

Next returns the current byte and advances, or 0 at EOF (cursor unchanged).

func (*Scanner) Peek

func (sc *Scanner) Peek() byte

Peek returns the current byte without advancing, or 0 at EOF.

func (*Scanner) PeekAt

func (sc *Scanner) PeekAt(n int) byte

PeekAt returns the byte at offset n from the cursor, or 0 if out of range.

func (*Scanner) Pos

func (sc *Scanner) Pos() int

Pos / Len / SetPos manage the cursor; SetPos clamps into [0, len].

func (*Scanner) Rest

func (sc *Scanner) Rest() string

Rest returns the unconsumed suffix (never panics — pos is always clamped).

func (*Scanner) SetPos

func (sc *Scanner) SetPos(p int)

func (*Scanner) SkipSpace

func (sc *Scanner) SkipSpace()

SkipSpace advances over ASCII spaces and tabs.

func (*Scanner) Slice

func (sc *Scanner) Slice(start, end int) string

Slice returns s[start:end] with both bounds clamped into range; an inverted range yields "".

type Server

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

Server is the main simulator HTTP server.

func NewServer

func NewServer(cfg Config) (*Server, error)

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) DB

func (s *Server) DB() *sql.DB

DB returns the SQLite database connection, or nil if persistence is disabled.

func (*Server) Handle

func (s *Server) Handle(pattern string, handler http.Handler)

Handle registers a pattern on the server's mux.

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

func (s *Server) ListenAndServe() error

ListenAndServe starts the server and blocks until shutdown. It listens for SIGTERM and SIGINT for graceful shutdown.

func (*Server) Logger

func (s *Server) Logger() zerolog.Logger

Logger returns the server's logger for use by service handlers.

func (*Server) Mux

func (s *Server) Mux() *http.ServeMux

Mux returns the underlying ServeMux for direct registration.

func (*Server) RegisterUI

func (s *Server) RegisterUI(fsys fs.FS)

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

func (s *Server) RoutePatterns() []string

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

func (s *Server) StartBackground(worker func(context.Context))

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.

func (*Server) WrapHandler

func (s *Server) WrapHandler(middleware func(http.Handler) http.Handler)

WrapHandler wraps the server's final handler chain. Host-addressed cloud data planes use this to route before generic path handlers.

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

func MakeStore[T any](db *sql.DB, table string) Store[T]

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.

Jump to

Keyboard shortcuts

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