Status: Alpha (under active development). APIs, behavior, and module layout may change without notice. Not recommended for production use.
A local mock server suite for SAKURA Cloud APIs, inspired by LocalStack. Run SAKURA Cloud services locally for development and testing without connecting to the real API.
Every service is available as a subcommand of the single sakumock binary (e.g. sakumock simplemq), and as a Go library for spinning up in-process test servers. See each service's README for details.
Under sakumock all every control plane shares one listener (127.0.0.1:18000 by default) and is served under its Path (e.g. http://127.0.0.1:18000/kms). A service run on its own (sakumock kms) or under sakumock all --per-service-ports listens on its Standalone Port instead, at the root path.
| Service | Path | Standalone Port | Module | Description |
|---|---|---|---|---|
| simplemq | /simplemq |
18080 | github.com/sacloud/sakumock/simplemq |
SimpleMQ message API |
| secretmanager | /secretmanager |
18082 | github.com/sacloud/sakumock/secretmanager |
SecretManager API |
| kms | /kms |
18081 | github.com/sacloud/sakumock/kms |
KMS key management API |
| simplenotification | /simplenotification |
18083 | github.com/sacloud/sakumock/simplenotification |
Simple Notification message-send API |
| monitoringsuite | /monitoringsuite |
18084 | github.com/sacloud/sakumock/monitoringsuite |
Monitoring Suite control-plane API |
| eventbus | /eventbus |
18085 | github.com/sacloud/sakumock/eventbus |
EventBus control-plane API |
| objectstorage | /objectstorage |
18086 | github.com/sacloud/sakumock/objectstorage |
Object Storage control-plane API (optional S3 data plane via versitygw) |
| iam | /iam |
18087 | github.com/sacloud/sakumock/iam |
IAM control-plane API |
| apprun | /apprun |
18088 | github.com/sacloud/sakumock/apprun |
AppRun control-plane API (optional Docker data plane) |
| apprundedicated | /apprun-dedicated |
18089 | github.com/sacloud/sakumock/apprundedicated |
AppRun Dedicated control-plane API (optional Docker data plane) |
| workflows | /workflows |
18090 | github.com/sacloud/sakumock/workflows |
Workflows control-plane API (optional Runbook execution engine) |
| apigw | /apigw |
18091 | github.com/sacloud/sakumock/apigw |
API Gateway control-plane API (optional gateway data plane) |
| cloudhsm | /cloudhsm |
18092 | github.com/sacloud/sakumock/cloudhsm |
CloudHSM control-plane API |
| seg | /seg |
18093 | github.com/sacloud/sakumock/seg |
Service Endpoint Gateway control-plane API |
| addon | /addon |
18094 | github.com/sacloud/sakumock/addon |
Add-on API (AI / CDN / security / data analytics resources) |
Install with mise, which fetches the prebuilt binary straight from the GitHub Releases:
# Install globally (latest release)
mise use -g github:sacloud/sakumock@latestOr pin a version in your project's mise.toml:
[tools]
"github:sacloud/sakumock" = "0.2.1"Alternatively, download a prebuilt binary from the Releases page, or use the container image (see Docker):
docker pull ghcr.io/sacloud/sakumock:latestRun every service together in one process. This is the usual way to use sakumock:
sakumock allEvery service's control plane listens on 127.0.0.1:18000, each under /<service> — the paths of the real APIs overlap across services, so the prefix is what tells them apart (e.g. http://127.0.0.1:18000/kms, http://127.0.0.1:18000/simplemq). Change the address with --addr. Data planes, when enabled, keep their own listeners (see each service's README).
Run sakumock all --help for flags. Per-service flags are available with a service prefix (e.g. --kms-latency, --simplemq-rate-limit).
The sakumock env subcommand prints the environment variables your client (SAKURA Cloud SDK or the Terraform provider) needs as a dotenv file, so you never hand-copy endpoints. It starts no server, so you can run it before (or independently of) sakumock all:
sakumock env > ./sakumock.env
# In the shell that runs your SDK / Terraform:
set -a; source ./sakumock.env; set +a
terraform applyThe file sets each service's SAKURA_ENDPOINTS_* override plus dummy
credentials. The dummy credentials also act as a safety net: a request to an API
that sakumock does not mock reaches the real endpoint but fails authentication
instead of touching your account.
Pass --export to prefix every line with export , so the output can be
sourced directly (e.g. with direnv or a plain shell)
without set -a:
sakumock env --export > .envrc # or: source <(sakumock env --export)By default the endpoints point at sakumock all's listen address (--addr, with
each service's path). Pass sakumock env the same flags you pass sakumock all
(--addr, --per-service-ports, --config, ...) and the endpoints match. When the client
reaches sakumock over the network — most importantly from a container — pass
--host to substitute the host the client actually uses (the port is kept):
sakumock env --host localhost > sakumock.envRun sakumock env to see the full list of variables, or sakumock all --help for each flag's environment variable.
sakumock all accepts per-service flags, a config file, and environment variables.
Per-service flags keep their defaults and are available with a service prefix (e.g. --kms-latency, --simplemq-rate-limit). Suite-wide flags such as --addr have no prefix. Run sakumock all --help for a full listing.
Instead of passing many flags, sakumock all can read a config file (--config, YAML or JSON by extension) with options grouped per service:
# sakumock.yaml
addr: 127.0.0.1:19000
simplemq:
database: /var/lib/sakumock/mq.db
message-expire: 96h
kms:
latency: 5ssakumock all --config sakumock.yamlEach per-service flag maps to a key by stripping the service prefix: --simplemq-message-expire becomes message-expire under simplemq:, --kms-latency becomes latency under kms:. Suite-wide flags are top-level keys (addr, per-service-ports, listen-host, ...). Precedence, highest first: command-line flag, then config file, then environment variable, then the flag's default.
Every per-service setting also has an environment variable, which is the most convenient way to configure the mock in a container. The names are <SERVICE>_<SETTING> (e.g. KMS_LATENCY, SIMPLEMQ_RATE_LIMIT, MONITORINGSUITE_DEBUG); each flag's exact variable is shown in sakumock all --help as ($VAR). They apply under sakumock all just as they do for the standalone subcommands:
KMS_LATENCY=200ms SIMPLEMQ_RATE_LIMIT=10 sakumock allPass --per-service-ports (or SAKUMOCK_PER_SERVICE_PORTS=true) to serve each control plane on its own port at the root path instead — the Standalone Port in the service table, changed with the prefixed --<service>-addr flag (e.g. --kms-addr). Pass the flag to sakumock env as well so the endpoints match:
sakumock all --per-service-ports
sakumock env --per-service-ports > sakumock.envYou can also run any service on its own as a subcommand (same flags, without the service prefix). Each listens on its Standalone Port at the root path, e.g. SAKURA_ENDPOINTS_KMS=http://127.0.0.1:18081:
sakumock simplemq &
sakumock secretmanager &
sakumock kms &
sakumock simplenotification &
sakumock monitoringsuite &
sakumock eventbus &
sakumock objectstorage &
sakumock iam &
sakumock apprun &
sakumock apprun-dedicated &
sakumock workflows &
sakumock cloudhsm &
sakumock seg &
sakumock addon &Run sakumock --help to list services, and sakumock <service> --help for its flags.
The binary carries this README, the CHANGELOG, the compose example, the Terraform configuration of the end-to-end test, and every service README, so the documentation can be read without a source checkout — including by an LLM agent driving the CLI. sakumock docs prints an index in the llms.txt convention; topics are named after the service subcommands.
sakumock docs # index of topics
sakumock docs kms # a service README (same as `sakumock kms --docs`)
sakumock docs kms --toc # headings only
sakumock docs kms --section "Fixed keys" # one section, by heading text or slug (fixed-keys)
sakumock docs readme --section fault-injection
sakumock docs terraform --section apigw # known-good sacloud/sakura provider resources for one service
sakumock docs --search fault # grep every topic: topic:line:section: text
sakumock docs eventbus --search simplemq # grep one topic; the section slug feeds --section
sakumock docs --all > sakumock-docs.md # everything concatenated (llms-full.txt style)Each service also accepts --docs (sakumock kms --docs, sakumock-kms --docs) to print its own README, next to --routes for its endpoint table.
To test client timeouts, every service can delay its responses with --latency DURATION (e.g. 500ms, 2s). It applies to the control-plane API (and to same-port data planes such as simplemq messages); the mock-only /_sakumock/ inspection endpoints are never delayed.
Services with a separate-listener in-process data plane — Monitoring Suite ingest, AppRun / AppRun Dedicated proxies, API Gateway — delay it independently with --data-plane-latency DURATION, so a slow ingest or proxy path can be tested without slowing the control plane (and vice versa). The object storage S3 data plane is served by versitygw, an external process, and has no --data-plane-latency.
# Under sakumock all, use the service prefix
sakumock all --monitoringsuite-latency 200ms --monitoringsuite-data-plane-latency 2sTo test how a client (SDK, Terraform provider, your application) handles server errors and network failures, every service can probabilistically inject faults into its control-plane API with --fault CODE:RATE[:PHASE] (repeatable):
CODE— the HTTP status to return (200–599), orresetto drop the TCP connection abruptly (the client sees a transport error such asconnection reset by peer, not an HTTP response).RATE— the probability in(0, 1]. Each request rolls once; the configured rates are exact and mutually exclusive, so they must sum to at most 1.PHASE— when the fault fires, relative to the mock's own processing:before(default): the request is rejected before the handler runs — no state changes, safe to retry.after: the handler runs first (state changes do happen), then its response is discarded and replaced by the fault — emulates "the server committed but the client got an error/disconnect", which is what exercises retry idempotency in a client.
# Standalone: 10% HTTP 500, 2% connection reset
sakumock kms --fault 500:0.1 --fault reset:0.02
# Under sakumock all, use the service prefix
sakumock all --kms-fault 500:0.1 --kms-fault 500:0.1:after
# Env var (comma-separated)
KMS_FAULT=500:0.1,reset:0.02 sakumock allConfig file (quote the specs — unquoted 500:0.1 in YAML flow style would parse as a mapping):
kms:
fault:
- "500:0.1"
- "reset:0.02:after"An injected status is returned in the service's own error envelope with a fault injection message, and the request log records it (reset logs as status 499). When tracing is enabled, the request's span is annotated with sakumock.fault.code and sakumock.fault.phase (plus sakumock.fault.replaced_status for after faults), and a reset marks the span status as error — so an injected fault is distinguishable from a real one in traces. Faults fire before authentication, rate limiting, and validation — like an infrastructure failure, an injected fault can mask a would-be 401/429/400. They never apply to the mock-only /_sakumock/ inspection endpoints, and in this iteration they cover control planes only (the separate-listener data planes — object storage S3, Monitoring Suite ingest, AppRun proxies, API Gateway — are not fault-injected).
Pass a certificate and key (--tls-cert/--tls-key, or SAKUMOCK_TLS_CERT/SAKUMOCK_TLS_KEY) to serve every listener over HTTPS — all control planes and data planes share the one cert (they run on the same host, only the port differs). TLS is enabled only when both files are set; otherwise everything stays plain HTTP. The object storage data plane is served by versitygw, which is handed the same cert/key (--cert/--key) so it terminates TLS itself. Standalone subcommands accept the same --tls-cert/--tls-key (env <SERVICE>_TLS_CERT / <SERVICE>_TLS_KEY). sakumock env emits https:// endpoints when TLS is set; with a self-signed cert the client must be told to trust it.
Pass --enable-service-link (or SAKUMOCK_ENABLE_SERVICE_LINK=true) to let services forward requests to each other, just as the real SAKURA Cloud platform does. Currently EventBus forwards fired jobs to their destination service:
| Source | Destination | What happens |
|---|---|---|
| EventBus | SimpleMQ | Sends a message to the configured queue |
| EventBus | SimpleNotification | Sends a notification to the configured group |
Without --enable-service-link (the default), firings are recorded but not forwarded — each service works in isolation.
Service link requires the source service's data plane to be enabled as well — EventBus needs --eventbus-enable-data-plane to fire events:
sakumock all --enable-service-link --eventbus-enable-data-planeService link is only available with sakumock all; standalone services cannot discover each other's addresses.
sakumock can emit OpenTelemetry traces for every request it handles — one server span per request, continuing the client's trace when the request carries a W3C traceparent header. Configuration uses the standard OTEL environment variables only; there are no sakumock flags:
# Export spans to any OTLP/HTTP endpoint (a collector, Jaeger, ...):
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 sakumock allTracing is enabled when OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_TRACES_ENDPOINT is set (and OTEL_SDK_DISABLED is not true); otherwise it is completely off. Spans are exported over OTLP http/protobuf by default; set OTEL_EXPORTER_OTLP_PROTOCOL=grpc (or OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=grpc) to export over gRPC instead (e.g. OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317).
- Spans are named after the matched route (e.g.
GET /cloud/zone/is1a/api/kms/1.0/keys/{id}) and carryhttp.routeplus asakumock.serviceattribute identifying the service; the resource isservice.name=sakumock(override withOTEL_SERVICE_NAME). - Every
requestlog line gainstrace_idandspan_idattributes, so logs and traces correlate. - All listeners are instrumented: every control plane and the in-process data planes (monitoring suite ingest, AppRun proxies, API gateway). The object storage S3 data plane (versitygw, an external process) is not.
- The SDK's other standard env vars work as usual:
OTEL_RESOURCE_ATTRIBUTES,OTEL_TRACES_SAMPLER,OTEL_EXPORTER_OTLP_HEADERS,OTEL_BSP_*, ...
sakumock can even export to its own Monitoring Suite mock ingest:
MONITORINGSUITE_ENABLE_DATA_PLANE=true \
MONITORINGSUITE_DATA_PLANE_DUMP_DIR=/tmp/telemetry \
OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:28084 sakumock allNote that in this loopback setup each export POST is itself traced, so an idle sakumock emits one heartbeat span per batch interval — harmless, but visible in the dumps.
Every service exposes mock-only /_sakumock/ endpoints for observing or driving the mock — listing accepted messages, inspecting fired deliveries, injecting events, and resetting state. These endpoints do not exist in the real SAKURA Cloud API.
See each service's README for available endpoints and response shapes:
- eventbus — Inspection endpoints (
/_sakumock/events,/_sakumock/tick,/_sakumock/deliveries) - simplenotification — Inspection endpoints (
/_sakumock/messages)
Each service with inspection endpoints provides an InspectionClient — an HTTP client that wraps the /_sakumock/ calls and returns typed domain objects. Use it from integration tests that run against a sakumock process or container (not just in-process test servers):
import "github.com/sacloud/sakumock/eventbus"
ic := eventbus.NewInspectionClient("http://localhost:18000/eventbus")
ds, _ := ic.InjectEvent(ctx, eventbus.Event{Source: "//monitoringsuite..."})
ds, _ = ic.Deliveries(ctx)
_ = ic.ClearDeliveries(ctx)The sakumock inspect subcommand calls inspection endpoints from the command line:
# List recorded firings
sakumock inspect eventbus deliveries
# Inject an event and see which triggers fired
sakumock inspect eventbus inject-event --source "//monitoringsuite..."
# Force schedule evaluation at a specific time
sakumock inspect eventbus tick --at 2024-01-01T09:00:00+09:00
# List accepted notification messages
sakumock inspect simplenotification messages
# Clear state between test runs
sakumock inspect eventbus clear-deliveries
sakumock inspect simplenotification clear-messagesEach service subcommand accepts --addr to override the server address. The address resolves in order of precedence (highest first): --addr flag, then SAKURA_ENDPOINTS_* environment variable (so sakumock env output works as-is), then the built-in default.
A multi-platform image (linux/amd64, linux/arm64) is published to GitHub Container Registry.
The default command runs every service bound to 0.0.0.0, so the published port is reachable from the host. Every control plane shares port 18000, each under /<service>:
docker run --rm \
-p 18000:18000 \
ghcr.io/sacloud/sakumock:latestA second tag, :latest-dataplane (and :<version>-dataplane), enables every service's data plane by default: the Object Storage S3 data plane (bundling the versitygw S3 gateway; OBJECT_STORAGE_ENABLE_DATA_PLANE=true, listening on 0.0.0.0:28086), the Monitoring Suite telemetry ingest data plane (MONITORINGSUITE_ENABLE_DATA_PLANE=true, listening on 0.0.0.0:28084), the AppRun / AppRun Dedicated Docker data planes (APPRUN_ENABLE_DATA_PLANE=true on 0.0.0.0:28088, APPRUN_DEDICATED_ENABLE_DATA_PLANE=true on 0.0.0.0:28089), and the API Gateway data plane (APIGW_ENABLE_DATA_PLANE=true on 0.0.0.0:28091). The default image includes none of these, so use this tag when you need a data plane:
docker run --rm \
-p 18000:18000 \
-p 28084:28084 -p 28086:28086 -p 28091:28091 \
ghcr.io/sacloud/sakumock:latest-dataplaneObjects are stored under /home/nonroot/data; mount a volume there to persist them. The image runs as a non-root user (uid 65532), so prefer a named volume (-v sakumock-data:/home/nonroot/data, which inherits the right ownership) — a bind-mounted host directory must be writable by uid 65532 or the data plane fails to start.
The AppRun and AppRun Dedicated data planes start Docker containers for each deployed application and reverse-proxy traffic to them. This requires the host Docker socket and --network host:
docker run --rm --network host \
-v /var/run/docker.sock:/var/run/docker.sock \
ghcr.io/sacloud/sakumock:latest-dataplane-v /var/run/docker.sock:/var/run/docker.sock— lets the mock start and stop sibling containers on the host Docker daemon.--network host— the reverse proxy connects to containers via127.0.0.1:<port>, so the mock must share the host's network namespace to reach the published ports. With--network hostthe-pflags are unnecessary (all ports are directly accessible).- The host Docker socket must be readable by the container's user. Add the host's docker group GID with
--group-add $(stat -c '%g' /var/run/docker.sock)(orchmod 666 /var/run/docker.sockon the host as a less secure alternative).
If you do not need the AppRun data planes, disable them with environment variables to skip the Docker dependency:
docker run --rm \
-e APPRUN_ENABLE_DATA_PLANE=false \
-e APPRUN_DEDICATED_ENABLE_DATA_PLANE=false \
-p 18000:18000 \
-p 28084:28084 -p 28086:28086 -p 28091:28091 \
ghcr.io/sacloud/sakumock:latest-dataplaneConfigure the mock's behavior with the per-service environment variables (see Environment Variables) — handier than flags in a container:
docker run --rm -p 18000:18000 \
-e KMS_LATENCY=200ms -e KMS_RATE_LIMIT=10 \
ghcr.io/sacloud/sakumock:latestWriting an env file inside the container is not useful: a file there is invisible to a client on the host, and the in-container listen host is not how the client reaches the service. Instead, generate the client env with the env subcommand, telling it the host the client uses (the host shell does the redirection):
docker run --rm ghcr.io/sacloud/sakumock:latest env --host localhost > sakumock.env
set -a; source ./sakumock.env; set +a
terraform applyFor docker compose, run env as a oneshot that writes the file into a shared volume (the image has no shell, so use --output rather than >) and have your app load it. See examples/compose.yaml for a complete example.
Each service can also be used as a Go library to spin up in-process test servers:
import "github.com/sacloud/sakumock/secretmanager"
srv := secretmanager.NewTestServer(secretmanager.Config{})
defer srv.Close()
// srv.TestURL() returns http://127.0.0.1:<random-port>See AGENTS.md for module conventions, file structure, public API contracts, port allocation, and the architectural guidelines each service must follow.
This project is published under Apache 2.0 License.