Documentation
¶
Overview ¶
Package provisioningsvc is the Hanzo Cloud provisioning control plane. 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, one Provisioner each:
sql -> Postgres sql.hanzo.svc:5432 CREATE DATABASE + ROLE
vector -> Qdrant vector.hanzo.svc:6333 PUT /collections/{name}
datastore -> ClickHouse datastore.hanzo.svc:8123 CREATE DATABASE + USER
kv -> Redis kv.hanzo.svc:6379 ACL SETUSER (keyspace scope)
search -> Meilisearch search.hanzo.svc:7700 POST /indexes
s3 -> S3/MinIO s3.hanzo.svc:9000 MakeBucket
docdb -> MongoDB docdb.hanzo.svc:27017 createCollection + createUser
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 *zip.App, deps cloud.Deps) error
- func SanitizeOrg(org string) string
- 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) PhysicalExists(ctx context.Context, physical string) (bool, error)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BucketName ¶ added in v1.786.10
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 ¶ added in v1.786.10
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 SanitizeOrg ¶ added in v1.786.10
SanitizeOrg exports the org-slug normalizer so clients/s3 derives the caller's org tag from the SAME reduced slug this control plane keys on — otherwise a bucket created here (keyed on the sanitized org) would be invisible to a file manager that hashed the raw org, and vice-versa.
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
}
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 ({DataDir}/provisioning.db) 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) 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.