Documentation
¶
Overview ¶
Package provisioning is one-click data add-ons: a SQL, key-value, document, vector, search or object store, wired straight into your app.
It turns "create a database" into a real logical resource inside an already-live, shared product backend, per the unified /v1 binary (HIP-0106).
One HTTP surface, seven kinds, two strategies:
DEDICATED-instance — the four on-demand data add-ons. Each org OWNS its instance (an operator Datastore CR in tenant-<org>), so its admin credential is naturally tenant-scoped; the assembled DSN is injected as <KIND>_URL into the app instance's addons Secret, switching it off Base onto the backend:
kv -> Hanzo KV Datastore type=valkey kv://…:6379 sql -> Hanzo SQL Datastore type=postgresql postgres://…:5432 docdb -> Hanzo DocDB Datastore type=docdb mongodb://…:27017 datastore -> Hanzo Datastore Datastore type=datastore datastore://…:8123
SHARED-logical — a logical resource inside an already-live shared backend:
vector -> Qdrant vector.hanzo.svc:6333 PUT /collections/{name}
search -> Meilisearch search.hanzo.svc:7700 POST /indexes
s3 -> S3/SeaweedFS s3.hanzo.svc:9000 MakeBucket
Tenancy: every request is scoped to the gateway-minted org (X-Org-Id / c.Org()). Empty org is rejected 403 unless the caller is an admin. The physical resource on the shared backend is namespaced "o"<hash(org)>_<name> with a FIXED-WIDTH org hash, so the org→name boundary is unambiguous and two distinct tenants can never fold onto one backend resource. A global UNIQUE(physical_name) guard makes any residual fold fail closed with 409.
Secrets: generated per-resource passwords are sealed in Hanzo KMS (client-side encrypted) and only a secret_ref is persisted. When KMS is not configured the service degrades safely — it returns the password once in the create response and stores NOTHING in plaintext. See kms.go.
Index ¶
- func BucketName(org, name string) string
- func BucketPrefix(org string) string
- func Mount(app cloud.Router, deps cloud.Deps) error
- func Shutdown(context.Context) error
- type Provisioner
- type Resource
- type Store
- func (s *Store) Close() error
- func (s *Store) Delete(ctx context.Context, org, kind, name string) (bool, error)
- func (s *Store) Get(ctx context.Context, org, kind, name string) (Resource, error)
- func (s *Store) Insert(ctx context.Context, r Resource) error
- func (s *Store) List(ctx context.Context, org, kind string) ([]Resource, error)
- func (s *Store) ListAllByStatus(ctx context.Context, status string) ([]Resource, error)
- func (s *Store) ListByInstance(ctx context.Context, org, instance string) ([]Resource, error)
- func (s *Store) PhysicalExists(ctx context.Context, physical string) (bool, error)
- func (s *Store) UpdateStatus(ctx context.Context, org, kind, name, status string) (bool, error)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BucketName ¶
BucketName + BucketPrefix export the tenant→S3-bucket naming so the ONE convention is shared, not re-implemented. clients/s3 (the /v1/s3 file manager) operates on the SAME buckets this control plane allocates for kind "s3", so a bucket provisioned via POST /v1/s3 {name:x} is browsable there as bucket "x". The two subsystems MUST derive the S3 bucket name identically or the tenant boundary drifts between "allocate" and "operate" — and worse, a file manager using the raw physical name would try to create an underscore-containing bucket that S3 rejects. The full derivation is bucketName(physicalName(org,name)): the fixed-width org-hash prefix makes it injective in (org,name); the '_'→'-' fold makes it a DNS-safe S3 name. clients/s3 imports provisioning for exactly these; the dependency is one-directional (provisioning never imports s3), so no cycle.
func BucketPrefix ¶
BucketPrefix is the S3-bucket-name prefix ALL of an org's buckets share (== bucketName("o"+orgHash(org))+"-"). clients/s3 lists all buckets and filters to this prefix (the caller only ever sees its own), then strips it to recover friendly names. Derived through bucketName so it matches the real bucket names exactly, INCLUDING the '_'→'-' fold of the org-hash separator.
func Mount ¶
Mount wires the provisioning surface onto app per HIP-0106. It is the complex flavour of the generic subsystem: it keeps a package global (mounted) for cross-package reach and starts a recurring footprint meter, so it constructs the Service value directly rather than through cloud.Mount.
Types ¶
type Provisioner ¶
type Provisioner interface {
Create(ctx context.Context, physicalName, user, password string) (connString, host string, port int, db string, err error)
Drop(ctx context.Context, physicalName, user string) error
}
Provisioner creates and drops one kind of logical resource inside a shared, already-live backend. Create receives the namespaced physical name plus a per-resource user + password (the handler generates these); it returns a client connection string, the public host/port of the backend service, and the logical database/collection/bucket name. Backends without per-resource auth (Qdrant, Meilisearch, S3) ignore user/password and return an empty username via the handler's kind map.
type Resource ¶
type Resource struct {
ID string
Org string
Kind string
Name string
PhysicalName string
SecretRef string
Host string
Port int
Username string
DBName string
Status string
CreatedAt int64
// Size is the declared storage footprint of a DEDICATED-instance resource
// (e.g. "10Gi"), empty for the shared-logical kinds. It is the live-size
// source the recurring footprint meter multiplies into a per-org GB-time
// charge — so an instance the org runs is billed for what it reserves.
Size string
// Instance binds a DEDICATED-instance resource to the app instance whose
// on-demand add-on it is (e.g. "commerce"). When set, the assembled DSN is
// injected as <KIND>_URL into the Secret "<instance>-addons" in tenant-<org>,
// so that instance switches off Base onto this backend; drop removes it and
// the instance reverts to Base. Empty = not instance-bound (the DSN is only
// returned once, wired by the caller) — the pre-instance-binding behavior, so
// every existing provision is unchanged.
Instance string
}
Resource is one row of provisioned_resources: the control-plane record for a logical resource (database, bucket, collection, …) created inside a shared backend. It never carries the plaintext password — only secret_ref, the KMS key under which the password is sealed.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the provisioning metadata database. ONE SQLite file — the system namespace's "provisioning" — holds every org's records; tenant isolation is by the org column, enforced at the query layer. MaxOpenConns(1) serializes access so multi-step writes never race the SQLite file lock.
func (*Store) Delete ¶
Delete removes the resource row inside a transaction. Reports whether a row was actually deleted.
func (*Store) Insert ¶
Insert writes one resource row inside a transaction. A UNIQUE(org,kind,name) OR UNIQUE(physical_name) violation surfaces as errConflict so the caller can roll back the backend side-effects it already performed.
func (*Store) ListAllByStatus ¶
ListAllByStatus returns every row (across ALL orgs) in the given status. It is the read side of the recurring footprint meter: the sweep lists every "ready" row and charges each dedicated instance's OWN org. Rows carry Org/Kind/Size so the caller attributes and prices without a second lookup.
func (*Store) ListByInstance ¶
ListByInstance returns every resource an org has bound to one app instance, oldest first — the set of on-demand add-ons active for that instance (each is one <KIND>_URL projected into the instance's addons Secret). Scoped to (org, instance) so it can never surface another tenant's bindings.
func (*Store) PhysicalExists ¶
PhysicalExists reports whether ANY org already owns the given physical backend name. This is the global (cross-org) uniqueness pre-check: paired with the UNIQUE(physical_name) index it lets the handler fail closed with 409 BEFORE it touches a backend, so a residual name-fold (or hash collision) can never silently provision over another tenant's physical resource.
func (*Store) UpdateStatus ¶
UpdateStatus advances one resource's status (e.g. dedicated "provisioning" -> "ready" once the operator reports the instance is up). Scoped to (org,kind, name) so it can never touch another tenant's row. Reports whether a row moved.