service

package
v1.108.30 Latest Latest
Warning

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

Go to latest
Published: Aug 20, 2026 License: Apache-2.0 Imports: 44 Imported by: 0

Documentation

Overview

analytics.go is the ONE path by which visor rolls compute fleet/spend events into hanzoai/datastore (hanzo.compute_usage, ClickHouse) — the ANALYTICAL plane that mirrors, but is orthogonal to, the OPERATIONAL commerce ledger (tenant-data-hierarchy HIP). Commerce metering DEBITS an org's balance; this only RECORDS a fleet event for unified, cross-tenant rollups that admin.hanzo.ai reads by org / app / project — and by kind across visor's compute spectrum (machine, bot, cluster, nodepool, container, function).

Every emit is best-effort and fire-and-forget: the write runs in its own goroutine on a short-lived context and swallows all errors, so an unreachable or slow datastore NEVER blocks or fails a launch, hourly sweep, or destroy. Ingest is the datastore's ClickHouse HTTP interface (INSERT ... FORMAT JSONEachRow on the same DATASTORE_URL surface the other emitters use), so there is no new client dependency — net/http only.

cloud_cost.go is the ONE seam for reading a BYOC cloud account's spend — the input to fleet-billing tier (a), where connected cloud accounts are billed 1% of their cloud spend. Each cloud implements CloudCostReader with that cloud's real cost API (AWS Cost Explorer, Azure Cost Management, GCP BigQuery billing export, DigitalOcean billing). Readers are STATELESS pure API reads (this package must not import object — object imports service); the incremental "bill only new spend" watermark + idempotency live in the billing orchestrator, which owns the persistent cursor.

HONESTY CONTRACT: a reader returns ErrCostUnavailable when the stored credentials lack the cost-read scope that cloud needs (documented per cloud). The collector then SKIPS that account — no fee — rather than inventing a number. No spend is ever fabricated.

Package service — compute.go is Hanzo's resell compute surface over a single PLATFORM DigitalOcean account. It is distinct from the per-owner "bring your own cloud" Provider path (machine_cloud.go): here ONE Hanzo DO token (from KMS) backs every tenant, and droplets are namespaced by an org tag so list/get/ delete are scoped to the caller's org at the DigitalOcean layer — never the whole account. The catalog (regions/sizes/GPUs) is fetched once and cached so the dashboard is fast and DO is not hammered.

gate.go is the compute money gate: ONE price resolver, ONE pre-provision balance check, ONE debit. Every compute resource visor provisions rides through it — droplet launch, DOKS cluster create, node pool create, node pool scale — so there is a single answer to "what does this cost?" and a single answer to "is this org good for it?".

Two invariants, and both halves matter:

nothing provisions before its org is authorized for the first interval, and
nothing provisions at a price of zero.

A gate that authorizes a $0 charge is not a gate. An unpriced GPU slug billed as free is the same leak as no gate at all, reached by a different road.

metering.go is the ONE commerce metering path for resell compute. Both the launch debit (controllers/compute.go) and the recurring hourly debit (MeterRunningMachines, driven by task/ticker) build their client with NewMeteringClient and price with PriceToCents — there is no second metering path for /v1 machines. (The legacy billing/reporter.go meters DOKS NODE POOLS on a different, node-pool-specific event API; it is orthogonal and untouched.)

tenant.go is the ONE home for the org+project tenancy attribution shared by the resell compute surface (compute.go), the metering path (metering.go), and fleet billing (fleet_billing.go): how an org and a project are recovered from a machine's tags, encoded into a metering line, and validated so neither can ever corrupt the comma/colon-joined tag read-back or the commerce billing key.

The org is the tenant boundary and the debit destination; the project is a second attribution dimension WITHIN the org. The gateway mints X-Project-Id (like X-Org-Id); the empty project is the org's DEFAULT project and preserves today's behavior exactly — no project tag is written and the metering actor stays the bare org, so every keyed surface is backward-compatible.

Index

Constants

View Source
const (
	ComputeLaunched  = "launched"
	ComputeRunning   = "running"
	ComputeDestroyed = "destroyed"
)

Compute event kinds — the values of the `event` column. A launched row is written at provision, a running row each hour a machine stays up (alongside the recurring meter), and a destroyed row at teardown.

View Source
const (
	KindMachine   = "machine"
	KindTab       = "tab"
	KindBot       = "bot"
	KindCluster   = "cluster"
	KindNodePool  = "nodepool"
	KindContainer = "container"
	KindFunction  = "function"
)

Compute kinds — the values of the `kind` LowCardinality(String) lens in hanzo.compute_usage, spanning visor's compute spectrum:

machine   — a raw droplet/VM (no agent)
bot       — a machine running the @hanzo/bot agent (gw.hanzo.bot)
cluster   — a K8s cluster (service/doks.go)
nodepool  — a K8s node pool (object/node_pool.go)
container — a container workload
function  — a FaaS function (hanzoai/functions)

tab       — a machine you can OPEN: it runs `hanzo link`, so it publishes a
            terminal and appears in Tabs. A shell in the cloud, and nothing
            more — no agent, no runtime to keep fed.

The kinds NEST, they are not parallel cases. Every kind is at least a machine's worth of compute; a tab is a machine that also published a terminal; a bot is a tab that also runs the agent. Writing it that way means a bot is openable in Tabs for free — which is the thing that was missing, because you could launch a bot and then have no way to look inside it.

It also means `tab` is the useful default for a human: launching a bot to get a shell bootstraps an agent runtime nobody asked for, and the runtime is the expensive half. admin.hanzo.ai renders one lens per kind over the one table. Only machine and bot emit today; cluster/nodepool/container/function land later on this SAME table + kind — no schema migration needed (the column is open-ended).

View Source
const DefaultLaunchSize = "s-2vcpu-4gb"

SizeInfo is a resellable compute size. Only Hanzo's resale price is exposed — the wholesale cost and the upstream provider are never surfaced (brand policy; margin stays private). Markup is applied once in pricing.go. DefaultLaunchSize is the size a launch gets when the caller names none — the tabs "New cloud machine" button, a bare CLI launch. It is a real DO slug so the quote the launch handler computes and the droplet the provider creates agree on one size. A tab runs `hanzo link` and a terminal; this is the smallest tier that comfortably does that.

Variables

View Source
var ErrCostUnavailable = errors.New("cloud cost: unavailable (cost-read not configured for this provider)")

ErrCostUnavailable means this provider's spend cannot be read with the configured credentials/scope. The collector skips the provider (no fee); it is NOT an error condition to alert on, just "cost-read not wired for this account".

View Source
var ErrPriceUnavailable = errors.New("price unavailable")

ErrPriceUnavailable reports that a size's price could not be RESOLVED. It is deliberately distinct from a resolved price of zero, because collapsing the two is how an H100 pool bills nothing: a slug missing from the catalog reads as "free" and provisions at $0/hr for as long as it runs. Callers refuse to provision on this error — they never fall back to zero.

Functions

func AnalyticsConfigured

func AnalyticsConfigured() bool

AnalyticsConfigured reports whether compute events will actually be emitted — true exactly when DATASTORE_URL is set. Absent ⇒ EmitCompute is a no-op.

func AuthorizeCompute added in v1.108.24

func AuthorizeCompute(ctx context.Context, org, project string, cents int64) error

AuthorizeCompute is the pre-provision balance gate, and it is FAIL-CLOSED: every non-nil answer from commerce refuses the provision — insufficient funds, 401 on a rotated service token, 5xx, timeout, unreachable. Availability does not outrank billing here, because the resource on the other side of this call costs real money every hour it stays up, and nobody notices an unbilled GPU until the upstream invoice arrives.

cents is the FIRST INTERVAL's FULL cost (hourly × node count), never a token amount, so a one-cent balance cannot green-light an eight-GPU pool.

The balance consulted is PREPAID ONLY — see NewMeteringClient. Project scopes the tenant's own spend cap; the balance debited is always the org's.

func Billable added in v1.108.24

func Billable(ctx context.Context, sweep string) bool

Billable reports whether a billing sweep can actually debit — and makes a NO loud. An absent or rotated COMMERCE_SERVICE_TOKEN stops ALL revenue collection while the machines keep running and keep costing us money upstream, so the silent early-return this replaces was a revenue outage wearing the uniform of a healthy service: no error, no log, no metric, green dashboards, zero invoices.

sweep names the caller ("compute.hourly", "pool.hourly", …) so "the sweep did not run this hour" is alertable on one metric series.

func CanonicalKind

func CanonicalKind(kind string) string

CanonicalKind normalizes an arbitrary kind string to the known compute spectrum, falling back to machine for anything unrecognized (including empty). Applied on every WRITE (SetKind) and READ (EmitCompute), so a missing or garbage tag safely resolves to machine and the LowCardinality column only ever sees a known value. Fleet launches set bot explicitly; a raw single launch carries no kind and so resolves to machine.

func ComputeConfigured

func ComputeConfigured() bool

ComputeConfigured reports whether the platform DO token is present, so callers can return a clean 503 instead of a cryptic client error.

It answers "is a credential SET", which is a different question from "does the credential WORK" — see ComputeReachable. A revoked token is still a non-empty string, so this stays true through a revocation and every fallback written for the unconfigured case is dead code exactly when it is needed.

func ComputeReachable added in v1.108.27

func ComputeReachable(ctx context.Context) error

ComputeReachable proves the provider credential actually works, by spending one authenticated round trip on it rather than inspecting its length.

It exists because presence is not reachability. `token != ""` cannot tell a live token from a revoked one, so every caller that gated on ComputeConfigured took the configured branch and failed INSIDE it — reporting zeros that read as real data instead of "not connected". The hourly money sweep is where that is most expensive, so the sweep asks this question before it commits to an hour.

The two answers a caller must tell apart:

nil   — either the configured cloud account has nothing to ask (no token configured, so
        there are no platform resources at all and an empty answer is the TRUE
        one), or the provider answered. Both mean: proceed.
error — a credential IS configured and did not work. Nothing about the platform
        account can be known this hour.

That distinction is the whole point and it is the same one livePools already draws one level down: "there is nothing to ask" and "the answer did not come back" are different facts, and only the second is an error.

Account.Get is the cheapest call that proves the credential itself: O(1), no paging, and it is what a revoked token 401s on. The client is built through the one constructor, so this inherits the 30s bound every other DigitalOcean call gets and cannot wedge the caller.

func CreatedInHour added in v1.108.24

func CreatedInHour(createdTime, stamp string) bool

CreatedInHour reports whether an RFC3339 create time falls in the same UTC hour bucket as stamp ("YYYYMMDDHH"). An empty or unparseable time is NOT in the hour (metered normally) — we only ever SKIP a resource we can prove the provision path already billed this hour.

Exported because it is the ONE launch-hour rule, and every provision path that debits its first hour up front (machines, node pools) needs its recurring sweep to honour the same rule or that hour is billed twice.

func DeleteOrgKubernetesCluster

func DeleteOrgKubernetesCluster(org, id string, forget forgetCluster) error

DeleteOrgKubernetesCluster destroys a platform cluster by id, but ONLY if it carries the caller org's hanzo-org tag — the same isolation as GetOrgKubernetesCluster, so a tenant can never delete another tenant's cluster. An already-absent cluster is a no-op success (idempotent delete).

The cluster's billable rows go with it. They are what the hourly sweep bills, so a row outliving its cluster is not stale data — it is an invoice for nodes that no longer exist. The meter is stopped even for an already-absent cluster, so a retry after a partial delete still closes the bill.

func DeleteOrgMachine

func DeleteOrgMachine(org, id string) error

DeleteOrgMachine deletes a machine only after confirming it belongs to org.

func EmitCompute

func EmitCompute(org, event string, m *Machine, priceCents int64)

EmitCompute records one fleet event for machine m into the datastore. org is the authoritative owner (from IAM on launch; recovered from the machine's own tag on the recurring/destroy paths); app/project/kind are recovered from m's tags; priceCents is the resale price for this event's hour (0 for destroyed). It is fire-and-forget: a no-op when analytics is unconfigured or m is nil, otherwise the write is handed to a goroutine so it NEVER delays or fails the caller.

func EmitComputeEvent

func EmitComputeEvent(ev ComputeEvent)

EmitComputeEvent is the ONE kind-agnostic emit: it records a single compute fleet event of ANY kind (machine, bot, cluster, nodepool, container, function) into the datastore. It canonicalizes the kind and stamps ts when the caller left it empty, then — like every emit — is best-effort and fire-and-forget: a no-op when analytics is unconfigured, otherwise handed to a goroutine so it NEVER delays or fails the caller. EmitCompute (machine) and the cluster / nodepool emitters are all thin adapters over this one path.

func HanzoPrice

func HanzoPrice(doPrice float64, isGPU bool) float64

HanzoPrice converts a DigitalOcean list price (USD) into Hanzo's resale price (USD). isGPU selects the GPU multiplier. Rounded to 5 decimals so hourly micro-prices (e.g. $0.00744/hr) survive while monthly stays clean.

func HourStamp added in v1.108.24

func HourStamp(now time.Time) string

HourStamp is the per-hour bucket for the RequestID: the same running resource metered twice within one wall-clock hour carries the SAME RequestID (the client dedup hint). The authoritative once-per-hour guarantee is the single-flight lease in the ticker (object.ClaimMeterHour), because commerce does not dedup the withdraw on requestId.

func HourlyCents added in v1.108.24

func HourlyCents(slug string) (int64, error)

HourlyCents resolves a size slug to Hanzo's resale price in cents per hour, from the SAME catalog the /v1/machines/launch quote and debit read (SizeBySlug → PriceToCents). There is no second price table.

Both a slug absent from the catalog AND a slug whose resolved price is <= 0 yield ErrPriceUnavailable. The upstream publishes no zero-priced size, so a zero here means the price did not resolve — calling that "free by policy" is precisely the mistake this function exists to prevent. A genuinely free SKU would be a catalog decision, expressed in the catalog, not an absence.

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether err wraps a DigitalOcean 404 response, meaning the resource is already gone. Callers treat this as success when deleting.

func KubernetesConfigured added in v1.108.30

func KubernetesConfigured() bool

KubernetesConfigured reports whether any cloud is configured, so a caller can answer "not configured" distinctly from "configured and empty".

func MachineApp

func MachineApp(m *Machine) string

MachineApp / MachineProject recover a machine's OPTIONAL app/project scope from its own tags — the read-back counterparts of SetScope, mirroring MachineKind. Empty when the machine carries no such tag (scope is optional). Used by the ?project= list filter and available to any scope-aware read.

func MachineKind

func MachineKind(m *Machine) string

MachineKind recovers a machine's canonical compute kind from its own tags — the read-back counterpart of SetKind (which writes the kind on launch). It is the ONE way to read a live machine's kind (the ?kind= list filter and EmitCompute both use it), so a missing/garbage tag resolves to machine.

func MachineProject

func MachineProject(m *Machine) string

func MeterActor

func MeterActor(org, project string) string

MeterActor encodes org+project into the commerce metering Actor — the audit-trail identity recorded on every usage transaction. It NEVER changes which balance is gated or debited: that is ALWAYS the org (the Usage.User billing key), so one org credit covers all its projects. Actor only ATTRIBUTES the line, which is what makes spend reportable per project. It is the ONE place org+project is folded into a metering line, shared by the launch debit, the recurring sweep, and every fleet-billing tier.

Empty project == the org's default project == today's behavior (Actor == org), so threading project through the existing launch and sweep debits is a no-op for any caller that does not set X-Project-Id. A named project yields "org/project" (the same org/sub shape commerce already documents for Actor).

func MeterRunningMachines

func MeterRunningMachines(ctx context.Context)

MeterRunningMachines debits every RUNNING metered machine one hour of its resale price to its OWNING org — the recurring counterpart to the launch debit. It is the "a running bound machine debits the org" rule: a machine that stays up keeps drawing down the org's credit balance, hour by hour.

Per machine: org is recovered from the machine's own hanzo-org tag (never trusted from a client — it is the tag LaunchOrgMachine injected), the hourly price comes from the resale catalog (SizeBySlug → PriceToCents), and the debit carries RequestID "compute-<machineID>-<YYYYMMDDHH>". Recording is decoupled from gating (the machine already ran that hour, so the cost must be recorded); enforcement/suspend on a depleted balance is a separate control. A per-machine error is logged and does not abort the sweep.

EXACTLY-ONCE PER HOUR is enforced OUTSIDE the RequestID: commerce's RecordUsage does NOT dedup the withdraw on requestId, so the key is only a reconciliation hint. The real once-per-hour guarantees are (1) the ticker's per-hour single-flight lease (object.ClaimMeterHour) so only one replica sweeps, and (2) skipping a machine's LAUNCH hour here (the launch path already billed it).

No-op when metering is unconfigured or when compute is unconfigured (no platform token) — nothing to enumerate, nothing to debit.

func MeteringConfigured

func MeteringConfigured() bool

MeteringConfigured reports whether the recurring meter will actually debit. The client's own Enabled() only checks the base URL (which always defaults to the in-cluster commerce), so the real "is billing wired" signal is the operator-provisioned service token (KMS-synced COMMERCE_SERVICE_TOKEN) — the same credential the launch path needs to authorize. Absent ⇒ the sweep is a safe no-op: an unconfigured deployment is never blocked or spammed with failed (401) debits.

func NewMeteringClient

func NewMeteringClient(org string) *metering.Client

NewMeteringClient builds the commerce metering client for an org. The commerce base URL and the admin-scoped service token both come from the environment (the operator wires the token from KMS as COMMERCE_SERVICE_TOKEN). When the token is absent the client fails closed on Authorize, so real launches are denied while quotes still work, and the recurring meter is a no-op (Record short-circuits on !Enabled()). This is the SAME client construction every visor debit uses, so they all key the same per-org ledger.

TierAware is OFF: the compute gate reads the org's PREPAID balance, not the tier-aware effective balance that folds in the included plan allotment. Compute is the one surface where a request provisions a resource that keeps drawing upstream cost for as long as it runs, so a promotional grant must not be convertible into GPU-hours — a free-tier credit is meant to buy inference, not an H100. TierAware only affects Authorize's balance read (metering.fetchAvailable picks /v1/billing/tier over /v1/billing/balance); Record is unaffected, so the fleet-billing lines this client also carries are unchanged.

func NormalizeProject

func NormalizeProject(project string) string

NormalizeProject trims and validates a project scope read from the gateway-minted X-Project-Id header at the edge, returning the canonical project or "" (the org's default project) for an absent or invalid value. It is the ONE normalization the controllers apply, so every keyed surface downstream receives a project that already survives the tag/meter read-back. Built on the same validProjectSlug predicate the write boundaries use, so there is one project rule expressed once.

func Opens added in v1.108.22

func Opens(kind string) bool

Opens reports whether a kind publishes a terminal — whether you can open it in Tabs. It is the one place that fact lives, so the launch path, the UI and any sweep agree by construction instead of by three matching switch statements.

The kinds nest, so this is a threshold and not a set: tab is where a terminal first appears, and everything richer than a tab has one too. A bot is a tab that also runs the agent, which is exactly why you can look inside one.

func PriceToCents

func PriceToCents(price float64) int64

PriceToCents converts a USD price to whole cents for billing. It Ceils (a paid product never under-charges) but subtracts a 1e-9 epsilon first so float64 overshoot on a whole-cent price (0.07*100 = 7.00000000000000089) does not round up to 8. A true sub-cent price still ceils to >= 1; a $0 price yields 0 (free, no charge). This is the ONE price→cents rule shared by launch and recurring metering.

func Provision added in v1.108.24

func Provision(ctx context.Context, org, project string, authorize, debit int64, model string, provision func() (string, error)) error

Provision is the ONE metered provision, and every compute resource visor creates rides it: a droplet launch, a DOKS cluster, a node pool, a scale-up. Under the org's hold it authorizes, provisions, and debits — in that order, so the money moves before the next request reads the balance.

TWO amounts, because they answer two different questions, and answering both with one number is a bug in whichever direction the number leans:

authorize — the CEILING the org must be good for. For an autoscaling pool
            that is the quantity it is ALLOWED to reach, because nothing
            gates the growth: the upstream adds nodes on its own and no
            request reaches visor, so the balance has to cover the ceiling
            up front or it never gets asked again.
debit     — what the org actually OWES right now, which is the resource it
            actually got. Charging the ceiling here bills sixteen nodes for
            a pool that has one, at hour one, forever after refunded by
            nobody. The other fifteen become chargeable when the upstream
            actually adds them, and the hourly sweep is what charges them.

model is the size slug. provision does the work and returns the idempotency key naming what it provisioned — or "" when the provision carries no charge of its OWN, which is the scale path: the added nodes are billed by the recurring sweep from its next hour, and a debit here would overlap that same hour's sweep.

A failed provision debits nothing. A failed DEBIT does not un-provision — the resource exists and is drawing upstream cost, and refusing to hand it over would leave the customer paying for something they were told they did not get — but it is loud, because nothing reconciles it.

func RateOf added in v1.108.24

func RateOf(si *SizeInfo) (int64, error)

RateOf is the price half of HourlyCents, for a size the caller has ALREADY resolved from the catalog — which the launch path has, since it resolved the size to quote it. There is one refuse-rather-than-zero rule and this is it; HourlyCents is the slug half in front of it, not a second copy.

func RecordCompute added in v1.108.24

func RecordCompute(ctx context.Context, org, project string, cents int64, model, status, requestID string) error

RecordCompute debits cents to the org's commerce ledger on the ONE compute meter line — Provider "compute", attributed to org+project through the shared MeterActor — and mirrors it as an OTel metric.

model is the size slug, status the lifecycle point ("launched"/"running"), and requestID the idempotency hint. Commerce does NOT dedup on requestID, so the once-per-unit guarantee belongs to the caller (the hour lease for sweeps, the launch-hour skip for anything the provision path already billed).

func RegisterCarrier added in v1.108.30

func RegisterCarrier(c Carrier)

RegisterCarrier installs the carrier every provider client is built with. Unregistered, visor holds the token itself and calls the cloud directly — which is what it did before egress existed and what a single-binary or local run still wants.

func RegisterCredentials added in v1.108.30

func RegisterCredentials(f func() []Credential)

RegisterCredentials teaches this process which cloud accounts it may spend on. A deployment that can enumerate them (object, from its Provider rows) registers the reader; nothing here knows how they are stored.

func SetKind

func SetKind(spec *CreateMachineSpec, kind string)

SetKind records the launch's kind on the spec's tags so it flows onto the droplet and is recovered — via the same tag read-back as org/app/project — by the emit, sweep, and destroy paths. It canonicalizes the value and inits the tag map if needed. Both launch surfaces (single-machine compute.go and fleet.go) call it, so kind is set exactly one way.

func SetScope

func SetScope(spec *CreateMachineSpec, app, project string)

SetScope records the launch's OPTIONAL app/project scope on the spec's tags so it flows onto the droplet and is recovered — via the same tag read-back as org/kind (tagValue) — by the emit, sweep, and destroy paths (EmitCompute reads hanzo-app / hanzo-project into ComputeEvent.App/Project). Beneath org in the org > app > project hierarchy, both are optional: an empty value, or one carrying the tag read-back's "," / ":" separators (guarded by safeTagField exactly as org's attribution tag is), is skipped — so a launch that omits them is never broken and no unparseable tag is ever emitted. Both launch surfaces set scope exactly one way through here, mirroring SetKind.

Types

type Carrier added in v1.108.30

type Carrier func(p Credential) (*http.Client, error)

Carrier builds the http.Client a provider SDK makes its calls with.

This is the ONE place a cloud credential turns into an outbound request, which is what makes it the one place the credential can stop being here. Registered with a carrier that reaches hanzoai/egress, visor sends the request and egress attaches the key: the token is never in this process, never in its environment and never in its config, so reading a pod here yields nothing to spend.

Both SDKs take an http.Client — godo.NewClient(c), hcloud.WithHTTPClient(c) — so this is a transport swap and not an SDK rewrite.

type CloudCostReader

type CloudCostReader interface {
	// MonthToDateCents returns the account's spend so far this UTC month, in cents.
	// Zero is valid (no spend → no fee). ErrCostUnavailable (or any error) makes the
	// collector skip this account for this run — never fabricating a figure.
	MonthToDateCents(ctx context.Context, now time.Time) (int64, error)
}

CloudCostReader reads a BYOC cloud account's cumulative spend for the current UTC calendar month, in whole cents of the account's billing currency. Month-to-date is the ONE figure every cloud can report uniformly (a per-day figure is not available from every cloud, e.g. DigitalOcean's balance endpoint), so the billing orchestrator bills 1% of the INCREMENT since it last billed — totaling 1% of the month's spend, idempotently, with month-rollover handled by the cursor key.

func NewCostReader

func NewCostReader(providerType, clientId, clientSecret, region, costScope string) (CloudCostReader, error)

NewCostReader builds the cost reader for a BYOC provider from its stored creds. providerType is the internal provider type; (clientId, clientSecret, region) is the managed-machine credential triple; costScope is Provider.CostReadScope (the cloud-specific extra identifier the cost API needs). Returns ErrCostUnavailable for a provider type with no cost reader, so the collector skips it honestly. The internal provider names never leave this package (brand policy).

type ComputeEvent

type ComputeEvent struct {
	Org        string `json:"org"`
	App        string `json:"app"`
	Project    string `json:"project"`
	Kind       string `json:"kind"`
	Event      string `json:"event"`
	MachineID  string `json:"machine_id"`
	Size       string `json:"size"`
	PriceCents int64  `json:"price_cents"`
	Ts         string `json:"ts"`
}

ComputeEvent is one row of hanzo.compute_usage. The json tags are the exact ClickHouse column names (JSONEachRow maps by key), making this struct the single Go mirror of the schema in hanzoai/datastore (hanzo/schema.sql). ts is pre-formatted in ClickHouse's default DateTime input format so no per-request parsing setting is needed; kind is a plain LowCardinality(String) value.

type Cpu

type Cpu struct {
	Processors int `json:"processors"`
}

type CreateClusterNodePool

type CreateClusterNodePool struct {
	Name  string `json:"name,omitempty"`
	Size  string `json:"size"`
	Count int    `json:"count"`
}

CreateClusterNodePool is the initial worker pool of a new cluster: its instance size and how many nodes. Name is optional (defaults to "<cluster>-pool").

type CreateClusterSpec

type CreateClusterSpec struct {
	Name     string                `json:"name"`
	Region   string                `json:"region"`
	Version  string                `json:"version"`
	NodePool CreateClusterNodePool `json:"nodePool"`
	// Provider picks the cloud. Optional while exactly one is configured;
	// required once there are several, because guessing puts a customer's
	// cluster on a cloud they did not choose.
	Provider string `json:"provider,omitempty"`
}

CreateClusterSpec is the minimal request to provision a DOKS cluster: identity (name), placement (region), the Kubernetes version, and ONE initial node pool. It is deliberately small — the resell surface provisions a single-pool cluster; further pools are added through the node-pool surface. Additional pools can be grown later; this is the create seed, not the full topology.

type CreateLoadBalancerSpec added in v1.108.29

type CreateLoadBalancerSpec struct {
	Name        string `json:"name"`
	DisplayName string `json:"displayName"`
	Region      string `json:"region"`
	Type        string `json:"type"`
	Size        string `json:"size"`
	// ForwardingRules empty yields plain HTTP 80 to 80 — the same default the
	// provider's own console applies, so an omitted list is a usable balancer
	// rather than a rejected request.
	ForwardingRules []ForwardingRule `json:"forwardingRules"`
}

type CreateMachineSpec

type CreateMachineSpec struct {
	Name         string            `json:"name"`
	DisplayName  string            `json:"displayName"`
	InstanceType string            `json:"instanceType"` // e.g. "t3.medium", "mac2.metal"
	ImageID      string            `json:"imageId"`      // AMI ID, image name, etc.
	OS           string            `json:"os"`           // "linux", "macos", "windows"
	Region       string            `json:"region"`
	Tags         map[string]string `json:"tags,omitempty"`
	SSHKeyIDs    []string          `json:"sshKeyIds,omitempty"` // Provider SSH key IDs
}

CreateMachineSpec describes parameters for launching a new cloud instance.

type CreateNodePoolSpec

type CreateNodePoolSpec struct {
	Name      string            `json:"name"`
	Size      string            `json:"size"`
	Count     int               `json:"count"`
	MinNodes  int               `json:"minNodes"`
	MaxNodes  int               `json:"maxNodes"`
	AutoScale bool              `json:"autoScale"`
	Tags      []string          `json:"tags,omitempty"`
	Labels    map[string]string `json:"labels,omitempty"`
}

type CreateVolumeSpec

type CreateVolumeSpec struct {
	Name        string `json:"name"`
	DisplayName string `json:"displayName"`
	Size        int    `json:"size"` // GB
	Region      string `json:"region"`
	Format      string `json:"format"`    // "ext4", "xfs"
	MachineID   string `json:"machineId"` // optional: attach on create
}

type CreateVpcSpec added in v1.108.29

type CreateVpcSpec struct {
	Name        string `json:"name"`
	DisplayName string `json:"displayName"`
	Region      string `json:"region"`
	Cidr        string `json:"cidr"`
}

type Credential added in v1.108.30

type Credential struct {
	Provider string // "DigitalOcean", "AWS", "Hetzner", ... as NewMachineClient spells it
	Name     string // tells two accounts of one cloud apart; "" means the only one
	KeyID    string
	Secret   string
	Region   string
}

Credential is one cloud account this deployment may spend on: which cloud, a name to tell several accounts of the same cloud apart, and the keys.

Exported because the SOURCE of these lives in object (the Provider rows), and object imports service — so the source is registered inward rather than read outward. Same seam, same reason, as object.RegisterMembership.

type DOKSClient

type DOKSClient struct {
	Client    *godo.Client
	ClusterID string
}

func NewDOKSClient

func NewDOKSClient(token, clusterID string) (*DOKSClient, error)

func NewDOKSClientFromConfig added in v1.108.30

func NewDOKSClientFromConfig(clusterID string) (*DOKSClient, error)

NewDOKSClientFromConfig builds a DOKS client on Hanzo's provider token. It is the cluster analogue of newDigitalOceanClient; per-org isolation is enforced by the callers via the hanzo-org tag, never by this client.

clusterID is empty for ACCOUNT-level operations (list/get/create/delete), which address a cluster by id, and set for the pool operations bound to one cluster.

func (*DOKSClient) CreateCluster

func (c *DOKSClient) CreateCluster(ctx context.Context, spec *CreateClusterSpec, tags []string) (*KubernetesCluster, error)

CreateCluster provisions a DOKS cluster from spec, tagging it with tags so it associates to the owning org (the node/cluster listers scope by that tag).

func (*DOKSClient) CreateNodePool

func (c *DOKSClient) CreateNodePool(spec *CreateNodePoolSpec) (*NodePool, error)

func (*DOKSClient) DeleteCluster

func (c *DOKSClient) DeleteCluster(ctx context.Context, id string) error

DeleteCluster destroys a cluster by id. A DO 404 (already gone) is the caller's success to interpret via IsNotFound — DeleteCluster itself reports the raw error.

func (*DOKSClient) DeleteNodePool

func (c *DOKSClient) DeleteNodePool(poolID string) error

func (*DOKSClient) GetCluster

func (c *DOKSClient) GetCluster(ctx context.Context, id string) (*KubernetesClusterDetail, error)

GetCluster returns one cluster's full detail — identity, node pools and the worker nodes as Machines. The Get is authoritative: it carries the pools (with their nodes) directly, so no separate pool re-list is needed.

func (*DOKSClient) GetNodePool

func (c *DOKSClient) GetNodePool(poolID string) (*NodePool, error)

func (*DOKSClient) ListClusters

func (c *DOKSClient) ListClusters(ctx context.Context) ([]*KubernetesCluster, error)

ListClusters returns every DOKS cluster visible to this client's token, in the clean KubernetesCluster shape. It is clustersByTag with no tag filter — the ONE cluster enumeration, so the tenant-scoped and account-wide lists never drift.

func (*DOKSClient) ListNodePools

func (c *DOKSClient) ListNodePools() ([]*NodePool, error)

func (*DOKSClient) NodeMachines

func (c *DOKSClient) NodeMachines(ctx context.Context) ([]*Machine, error)

NodeMachines returns one Machine per worker node in THIS client's cluster — the BYOC path where a Provider names a single cluster. The cluster Get supplies the region/name; ListNodePools supplies the pools whose nodes become the machines.

func (*DOKSClient) Provider added in v1.108.30

func (c *DOKSClient) Provider() string

Provider satisfies KubernetesClientInterface.

func (*DOKSClient) RecycleNodePoolNodes

func (c *DOKSClient) RecycleNodePoolNodes(poolID string, nodeIDs []string) error

func (*DOKSClient) UpdateNodePool

func (c *DOKSClient) UpdateNodePool(poolID string, spec *CreateNodePoolSpec) (*NodePool, error)

type ForwardingRule added in v1.108.29

type ForwardingRule struct {
	EntryProtocol  string `json:"entryProtocol"`
	EntryPort      int    `json:"entryPort"`
	TargetProtocol string `json:"targetProtocol"`
	TargetPort     int    `json:"targetPort"`
}

ForwardingRule is one listen-to-backend mapping.

type GPUSpec

type GPUSpec struct {
	Count    int    `json:"count"`
	Model    string `json:"model"`
	Vram     int    `json:"vram"`
	VramUnit string `json:"vramUnit"`
}

GPUSpec is the GPU detail for a GPU-backed size.

type ImageInfo

type ImageInfo struct {
	ID           int      `json:"id,omitempty"`   // custom/app images select by ID
	Slug         string   `json:"slug,omitempty"` // distributions select by slug
	Name         string   `json:"name"`
	Distribution string   `json:"distribution,omitempty"`
	Kind         string   `json:"kind"` // "distribution" | "application" | "custom"
	Regions      []string `json:"regions,omitempty"`
	MinDiskGB    int      `json:"minDiskGb,omitempty"`
	SizeGB       float64  `json:"sizeGb,omitempty"`
	Status       string   `json:"status,omitempty"` // custom: "pending" -> "available"
}

ImageInfo is one selectable image.

func CreateOrgImage

func CreateOrgImage(org, name, url, region, distribution string) (*ImageInfo, error)

CreateOrgImage registers a custom image from a URL into the configured cloud account, tagged to org so only that org sees it in ListImages. Creation is async (Status "pending" -> "available"); once available the image is launchable by its returned ID (LaunchOrgMachine accepts a numeric ImageID as a custom image).

func ListImages

func ListImages(org string) ([]ImageInfo, error)

ListImages returns what an org may launch: shared distributions + 1-click applications, plus the org's OWN custom images.

type KubernetesCapable added in v1.108.30

type KubernetesCapable interface {
	Kubernetes() KubernetesClientInterface
}

type KubernetesClientInterface added in v1.108.30

type KubernetesClientInterface interface {
	// Provider names the cloud, matching the provider Type strings used by
	// NewMachineClient ("DigitalOcean", "AWS", ...).
	Provider() string
	ListClusters(ctx context.Context) ([]*KubernetesCluster, error)
	GetCluster(ctx context.Context, id string) (*KubernetesClusterDetail, error)
	CreateCluster(ctx context.Context, spec *CreateClusterSpec, tags []string) (*KubernetesCluster, error)
	DeleteCluster(ctx context.Context, id string) error
	NodeMachines(ctx context.Context) ([]*Machine, error)
}

KubernetesClientInterface is the one Kubernetes expression Visor coordinates across k8s-native clouds, the same shape MachineClientInterface has beside it. A cloud gains clusters by implementing KubernetesCapable on the provider client NewMachineClient already builds — never by being named in a second registry.

type KubernetesCluster

type KubernetesCluster struct {
	ID         string   `json:"id"`
	Name       string   `json:"name"`
	RegionSlug string   `json:"regionSlug"`
	Status     string   `json:"status"`
	Tags       []string `json:"tags"`
	// Provider names the cloud this cluster is on. Additive and omitted when
	// unset, so a single-cloud response is byte-identical to what it was.
	Provider string `json:"provider,omitempty"`
}

KubernetesCluster is a DOKS cluster in the shape visor surfaces: identity, region, status and tags. Tags carry ownership (hanzo-org:<org>) used to scope a platform-account cluster to the org that owns it.

func CreateOrgKubernetesCluster

func CreateOrgKubernetesCluster(org, project string, spec *CreateClusterSpec, record recordSeed) (*KubernetesCluster, error)

CreateOrgKubernetesCluster provisions a DOKS cluster in the configured cloud account for org, stamping it managed-by + hanzo-org:<org> so it associates to the tenant exactly like a droplet — which is what makes it visible to that org's cluster and node listers (and invisible to every other org).

PLATFORM ACCOUNT means Hanzo pays the upstream bill for every node in the seed pool, so this goes through the money gate exactly like a droplet launch — and records the pool it provisioned, so the sweep keeps billing it.

func ListOrgKubernetesClusters

func ListOrgKubernetesClusters(org string) ([]*KubernetesCluster, error)

ListOrgKubernetesClusters returns every DOKS cluster in the configured cloud account tagged for org — the platform analogue of ListOrgMachines for whole clusters. Per-org isolation is by the cluster's hanzo-org tag: a tenant only ever sees its own clusters, never another org's.

type KubernetesClusterDetail

type KubernetesClusterDetail struct {
	KubernetesCluster
	NodePools []*NodePool `json:"nodePools"`
	Nodes     []*Machine  `json:"nodes"`
}

KubernetesClusterDetail is a cluster plus its node pools and the worker nodes expanded as fleet Machines — the ONE detail shape for GET .../clusters/:id. It embeds KubernetesCluster so identity/region/status/tags flatten into the same top-level JSON the list emits; NodePools carries the authoritative pool topology and Nodes the per-worker machines (the SAME shape ListOrgMachines emits).

func GetOrgKubernetesCluster

func GetOrgKubernetesCluster(org, id string) (*KubernetesClusterDetail, error)

GetOrgKubernetesCluster returns one platform cluster's detail (pools + worker nodes), but ONLY if it carries the caller org's hanzo-org tag. A cluster owned by another org — or a missing cluster — resolves to (nil, nil): the controller renders it as "not found", so a tenant can never read another tenant's cluster by guessing an id.

type LivePool added in v1.108.30

type LivePool struct {
	// Org owns the cluster, recovered from its hanzo-org tag. Empty means the
	// cluster is unattributable and nothing may be billed for it.
	Org string
	// ClusterID and PoolID are the upstream identity — the pair no customer can
	// rename, delete, or collide with another tenant's.
	ClusterID string
	PoolID    string
	// Name is the pool's upstream name. It addresses the cached row; it never
	// identifies the pool.
	Name string
	Size string
	// Nodes is the LIVE node count, so an autoscaled pool bills what it grew to.
	Nodes int
	// Created is the owning cluster's creation time (RFC3339), the fallback
	// launch-hour marker for a pool with no stored row.
	Created string
}

LivePool is ONE node pool in the configured cloud account as the PROVIDER reports it right now — which cluster it belongs to, which org owns that cluster, its node slug, and how many nodes it is ACTUALLY running.

This is the billable unit of a platform cluster, and the provider is its author. The stored row is a cache of it: useful for the rate the org was authorized at and the project it belongs to, and authoritative for neither existence nor count.

func ListLivePools added in v1.108.30

func ListLivePools(ctx context.Context) ([]LivePool, error)

ListLivePools returns every node pool of every cluster in Hanzo's platform account, with the org that owns it and its LIVE node count — the authoritative answer to "what is this account running, for whom, and how much of it".

It is the node-pool analogue of ListMeteredMachines and shares the ONE cluster enumeration (listClustersFull) with the cluster and node listers, so a pool's identity is sourced identically wherever it surfaces.

A cluster with no hanzo-org tag yields pools with an empty Org: they are returned rather than dropped, so the sweep can report them as unattributable instead of silently running an untagged cluster for free.

type LoadBalancer added in v1.108.29

type LoadBalancer struct {
	Name        string
	Id          string
	DisplayName string
	Region      string
	Type        string
	Ip          string
	State       string
	Targets     int
}

LoadBalancer is a managed load balancer, in the shape every provider is mapped onto. Targets is the real count of attached backends, not a capacity.

type LoadBalancerCapable added in v1.108.30

type LoadBalancerCapable interface {
	LoadBalancers() LoadBalancerClientInterface
}

type LoadBalancerClientInterface added in v1.108.29

type LoadBalancerClientInterface interface {
	GetLoadBalancers() ([]*LoadBalancer, error)
	GetLoadBalancer(name string) (*LoadBalancer, error)
	CreateLoadBalancer(spec *CreateLoadBalancerSpec) (*LoadBalancer, error)
	DeleteLoadBalancer(name string) error
}

func NewLoadBalancerClient added in v1.108.29

func NewLoadBalancerClient(providerType string, accessKeyId string, accessKeySecret string, region string) (LoadBalancerClientInterface, error)

type LoadBalancerDigitalOceanClient added in v1.108.29

type LoadBalancerDigitalOceanClient struct {
	Client *godo.Client
	// contains filtered or unexported fields
}

func (*LoadBalancerDigitalOceanClient) CreateLoadBalancer added in v1.108.29

func (*LoadBalancerDigitalOceanClient) DeleteLoadBalancer added in v1.108.29

func (c *LoadBalancerDigitalOceanClient) DeleteLoadBalancer(name string) error

func (*LoadBalancerDigitalOceanClient) GetLoadBalancer added in v1.108.29

func (c *LoadBalancerDigitalOceanClient) GetLoadBalancer(name string) (*LoadBalancer, error)

func (*LoadBalancerDigitalOceanClient) GetLoadBalancers added in v1.108.29

func (c *LoadBalancerDigitalOceanClient) GetLoadBalancers() ([]*LoadBalancer, error)

GetLoadBalancers walks every page, for the reason GetVpcs does.

type Machine

type Machine struct {
	Owner       string `xorm:"varchar(100) notnull pk" json:"owner"`
	Name        string `xorm:"varchar(100) notnull pk" json:"name"`
	Id          string `xorm:"varchar(100)" json:"id"`
	Provider    string `xorm:"varchar(100)" json:"provider"`
	CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
	UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
	ExpireTime  string `xorm:"varchar(100)" json:"expireTime"`
	DisplayName string `xorm:"varchar(100)" json:"displayName"`

	Region   string `xorm:"varchar(100)" json:"region"`
	Zone     string `xorm:"varchar(100)" json:"zone"`
	Category string `xorm:"varchar(100)" json:"category"`
	Type     string `xorm:"varchar(100)" json:"type"`
	Size     string `xorm:"varchar(100)" json:"size"`
	Tag      string `xorm:"varchar(100)" json:"tag"`
	State    string `xorm:"varchar(100)" json:"state"`

	Image     string `xorm:"varchar(100)" json:"image"`
	Os        string `xorm:"varchar(100)" json:"os"`
	PublicIp  string `xorm:"varchar(100)" json:"publicIp"`
	PrivateIp string `xorm:"varchar(100)" json:"privateIp"`
	CpuSize   string `xorm:"varchar(100)" json:"cpuSize"`
	MemSize   string `xorm:"varchar(100)" json:"memSize"`
}

func GetOrgMachine

func GetOrgMachine(org, id string) (*Machine, error)

GetOrgMachine returns a single machine only if it belongs to org; otherwise nil (no cross-tenant leak, even to a valid caller of another org).

func LaunchOrgMachine

func LaunchOrgMachine(org, project string, spec *CreateMachineSpec) (*Machine, error)

LaunchOrgMachine provisions a droplet in the configured cloud account, tagged so it is owned by org and attributed to project. Both attribution tags are injected here (never trusted from the client body) so the machine is always attributable to the right tenant AND project.

org is validated as a clean slug first: it becomes BOTH the hanzo-org attribution tag (read back by the hourly meter) AND the commerce billing key, so a value carrying the meter's "," / ":" separators must never reach the tag. A validated IAM owner claim is already a DNS-label slug, so this only rejects a malformed/forged org — it never breaks a real tenant. project is validated the same way; the EMPTY project is the org's default and writes no hanzo-project tag (backward-compatible with every machine launched before the project dimension).

func ListMeteredMachines added in v1.108.30

func ListMeteredMachines() ([]*Machine, error)

ListMeteredMachines returns every droplet in the configured cloud account that billableDroplet admits — the set the recurring hourly meter debits. It lists across ALL orgs (no per-org tag filter): the org is recovered per machine from its own tag, so ONE sweep meters every tenant's running machines.

func ListOrgKubernetesNodes

func ListOrgKubernetesNodes(org string) ([]*Machine, error)

ListOrgKubernetesNodes returns one Machine per DOKS worker node for every cluster in Hanzo's PLATFORM DigitalOcean account tagged for org — the platform analogue of ListOrgMachines, but for managed-Kubernetes nodes. DOKS worker droplets carry k8s tags, not a hanzo-org DROPLET tag, so they never surface through ListOrgMachines; this lists them via the managed-Kubernetes API and maps each node to the same Machine shape. Per-org isolation is by the cluster's hanzo-org tag — a tenant can only ever see its own clusters' nodes.

func ListOrgMachines

func ListOrgMachines(org, project string) ([]*Machine, error)

ListOrgMachines returns the droplets tagged for org — per-org isolation enforced at the DigitalOcean layer via an exact tag query — optionally narrowed to a single project.

project scopes the result WITHIN the org: the empty project is the org's default and returns EVERY org machine (today's behavior — a machine launched before the project dimension carries no hanzo-project tag), while a named project returns only the machines carrying that hanzo-project tag. Project is an attribution and view dimension, not a second isolation boundary — org is the tenant boundary, so get/delete stay org-scoped and only listing narrows by project.

type MachineAliyunClient

type MachineAliyunClient struct {
	Client *ecs.Client
}

func (MachineAliyunClient) CreateMachine

func (client MachineAliyunClient) CreateMachine(spec *CreateMachineSpec) (*Machine, error)

func (MachineAliyunClient) GetMachine

func (client MachineAliyunClient) GetMachine(name string) (*Machine, error)

func (MachineAliyunClient) GetMachines

func (client MachineAliyunClient) GetMachines() ([]*Machine, error)

func (MachineAliyunClient) UpdateMachineState

func (client MachineAliyunClient) UpdateMachineState(name string, state string) (bool, string, error)

type MachineAwsClient

type MachineAwsClient struct {
	Client *ec2.Client
	// contains filtered or unexported fields
}

func (MachineAwsClient) CreateMachine

func (client MachineAwsClient) CreateMachine(spec *CreateMachineSpec) (*Machine, error)

func (MachineAwsClient) GetMachine

func (client MachineAwsClient) GetMachine(name string) (*Machine, error)

func (MachineAwsClient) GetMachines

func (client MachineAwsClient) GetMachines() ([]*Machine, error)

func (MachineAwsClient) UpdateMachineState

func (client MachineAwsClient) UpdateMachineState(name string, state string) (bool, string, error)

type MachineAzureClient

type MachineAzureClient struct {
	Client *armcompute.VirtualMachinesClient
	// contains filtered or unexported fields
}

func (MachineAzureClient) CreateMachine

func (client MachineAzureClient) CreateMachine(spec *CreateMachineSpec) (*Machine, error)

func (MachineAzureClient) GetMachine

func (client MachineAzureClient) GetMachine(name string) (*Machine, error)

func (MachineAzureClient) GetMachines

func (client MachineAzureClient) GetMachines() ([]*Machine, error)

func (MachineAzureClient) UpdateMachineState

func (client MachineAzureClient) UpdateMachineState(name string, state string) (bool, string, error)

type MachineClientInterface

type MachineClientInterface interface {
	GetMachines() ([]*Machine, error)
	GetMachine(name string) (*Machine, error)
	UpdateMachineState(name string, state string) (bool, string, error)
	CreateMachine(spec *CreateMachineSpec) (*Machine, error)
}

func NewMachineClient

func NewMachineClient(c Credential) (MachineClientInterface, error)

type MachineDigitalOceanClient

type MachineDigitalOceanClient struct {
	Client *godo.Client
	// contains filtered or unexported fields
}

func (MachineDigitalOceanClient) CreateMachine

func (client MachineDigitalOceanClient) CreateMachine(spec *CreateMachineSpec) (*Machine, error)

func (MachineDigitalOceanClient) DeleteMachine

func (client MachineDigitalOceanClient) DeleteMachine(name string) error

func (MachineDigitalOceanClient) GetMachine

func (client MachineDigitalOceanClient) GetMachine(name string) (*Machine, error)

func (MachineDigitalOceanClient) GetMachines

func (client MachineDigitalOceanClient) GetMachines() ([]*Machine, error)

func (MachineDigitalOceanClient) Kubernetes added in v1.108.30

Kubernetes is DigitalOcean's managed-cluster face, over the SAME godo client this machine client already holds. One credential, one transport, two nouns — which is what keeps there from being a second way to reach DigitalOcean.

func (MachineDigitalOceanClient) LoadBalancers added in v1.108.30

func (MachineDigitalOceanClient) UpdateMachineState

func (client MachineDigitalOceanClient) UpdateMachineState(name string, state string) (bool, string, error)

func (MachineDigitalOceanClient) Volumes added in v1.108.30

Volumes, Vpcs and LoadBalancers are DigitalOcean's other nouns over the SAME godo client and region. Each was a separate factory with its own list of cloud names; a capability on the one client is the same fact with nothing to drift.

func (MachineDigitalOceanClient) Vpcs added in v1.108.30

type MachineGcpClient

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

func (MachineGcpClient) CreateMachine

func (client MachineGcpClient) CreateMachine(spec *CreateMachineSpec) (*Machine, error)

func (MachineGcpClient) GetMachine

func (client MachineGcpClient) GetMachine(name string) (*Machine, error)

func (MachineGcpClient) GetMachines

func (client MachineGcpClient) GetMachines() ([]*Machine, error)

func (MachineGcpClient) UpdateMachineState

func (client MachineGcpClient) UpdateMachineState(name string, state string) (bool, string, error)

type MachineHetznerClient

type MachineHetznerClient struct {
	Client *hcloud.Client
	// contains filtered or unexported fields
}

func (MachineHetznerClient) CreateMachine

func (client MachineHetznerClient) CreateMachine(spec *CreateMachineSpec) (*Machine, error)

func (MachineHetznerClient) GetMachine

func (client MachineHetznerClient) GetMachine(name string) (*Machine, error)

func (MachineHetznerClient) GetMachines

func (client MachineHetznerClient) GetMachines() ([]*Machine, error)

func (MachineHetznerClient) UpdateMachineState

func (client MachineHetznerClient) UpdateMachineState(name string, state string) (bool, string, error)

func (MachineHetznerClient) Volumes added in v1.108.30

Volumes is Hetzner's volume noun over the same hcloud client. Hetzner sells no managed Kubernetes, VPC or load balancer through this service, so it implements none of those capabilities and the factories say so without a list.

type MachineKvmClient

type MachineKvmClient struct {
	L *libvirt.Libvirt
}

func (MachineKvmClient) CreateMachine

func (client MachineKvmClient) CreateMachine(spec *CreateMachineSpec) (*Machine, error)

func (MachineKvmClient) GetMachine

func (client MachineKvmClient) GetMachine(name string) (*Machine, error)

func (MachineKvmClient) GetMachines

func (client MachineKvmClient) GetMachines() ([]*Machine, error)

func (MachineKvmClient) UpdateMachineState

func (client MachineKvmClient) UpdateMachineState(name string, state string) (bool, string, error)

type MachineLightsailClient

type MachineLightsailClient struct {
	Client *lightsail.Client
	// contains filtered or unexported fields
}

func (MachineLightsailClient) CreateMachine

func (client MachineLightsailClient) CreateMachine(spec *CreateMachineSpec) (*Machine, error)

func (MachineLightsailClient) GetMachine

func (client MachineLightsailClient) GetMachine(name string) (*Machine, error)

func (MachineLightsailClient) GetMachines

func (client MachineLightsailClient) GetMachines() ([]*Machine, error)

func (MachineLightsailClient) UpdateMachineState

func (client MachineLightsailClient) UpdateMachineState(name string, state string) (bool, string, error)

type MachinePveClient

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

func (MachinePveClient) CreateMachine

func (client MachinePveClient) CreateMachine(spec *CreateMachineSpec) (*Machine, error)

func (MachinePveClient) GetMachine

func (client MachinePveClient) GetMachine(name string) (*Machine, error)

func (MachinePveClient) GetMachines

func (client MachinePveClient) GetMachines() ([]*Machine, error)

func (MachinePveClient) UpdateMachineState

func (client MachinePveClient) UpdateMachineState(name string, state string) (bool, string, error)

type MachineVmwareClient

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

func (MachineVmwareClient) CreateMachine

func (client MachineVmwareClient) CreateMachine(spec *CreateMachineSpec) (*Machine, error)

func (MachineVmwareClient) GetMachine

func (client MachineVmwareClient) GetMachine(name string) (*Machine, error)

func (MachineVmwareClient) GetMachines

func (client MachineVmwareClient) GetMachines() ([]*Machine, error)

func (MachineVmwareClient) UpdateMachineState

func (client MachineVmwareClient) UpdateMachineState(name string, state string) (bool, string, error)

type NodeInfo

type NodeInfo struct {
	ID        string `json:"id"`
	Name      string `json:"name"`
	Status    string `json:"status"`
	DropletID string `json:"dropletId"`
	CreatedAt string `json:"createdAt"`
	UpdatedAt string `json:"updatedAt"`
}

type NodePool

type NodePool struct {
	ID        string            `json:"id"`
	Name      string            `json:"name"`
	Size      string            `json:"size"`
	Count     int               `json:"count"`
	MinNodes  int               `json:"minNodes"`
	MaxNodes  int               `json:"maxNodes"`
	AutoScale bool              `json:"autoScale"`
	Nodes     []NodeInfo        `json:"nodes"`
	Tags      []string          `json:"tags"`
	Labels    map[string]string `json:"labels"`
}

type ProviderStatus added in v1.108.30

type ProviderStatus struct {
	Provider string `json:"provider"`
	// Account tells two credentials on one cloud apart. Empty when there is only one.
	Account string `json:"account,omitempty"`
	OK      bool   `json:"ok"`
	Reason  string `json:"reason,omitempty"`
}

ProviderStatus is one configured cloud and whether it answered. Reported by KubernetesProviderStatus so an empty cluster list means "you have none" only when every backend is ok — a list that silently drops an unreachable cloud is the same three bytes as an empty estate.

func KubernetesProviderStatus added in v1.108.30

func KubernetesProviderStatus(ctx context.Context) []ProviderStatus

KubernetesProviderStatus reports every configured cloud and whether it answers right now, by making the cheapest real call each one has. This is the health half of the plane; the cluster list is the data half, and neither reports the other's business.

type RegionInfo

type RegionInfo struct {
	Slug      string   `json:"slug"`
	Name      string   `json:"name"`
	Available bool     `json:"available"`
	Features  []string `json:"features"`
	Sizes     []string `json:"sizes"`
}

RegionInfo is a resellable region.

func ListRegions

func ListRegions() ([]RegionInfo, error)

ListRegions returns the cached DigitalOcean regions catalog.

type SeedPool added in v1.108.24

type SeedPool struct {
	Org       string
	Project   string
	ClusterID string
	Name      string
	Size      string
	Count     int
	CentsHour int64
}

SeedPool is the node pool a cluster create provisions, in the terms the store needs to make it BILLABLE. A cluster's nodes are the cluster's whole cost, and the hourly sweep bills node-pool rows — so a cluster that provisions a pool and writes no row is billed its first hour by the provision path and then runs free forever. That is what this type exists to prevent.

type SizeInfo

type SizeInfo struct {
	Slug         string   `json:"slug"`
	Vcpus        int      `json:"vcpus"`
	MemoryMB     int      `json:"memoryMb"`
	DiskGB       int      `json:"diskGb"`
	Available    bool     `json:"available"`
	Regions      []string `json:"regions"`
	GPU          *GPUSpec `json:"gpu,omitempty"`
	Currency     string   `json:"currency"`
	PriceHourly  float64  `json:"priceHourly"`
	PriceMonthly float64  `json:"priceMonthly"`
}

func ListGPUSizes

func ListGPUSizes() ([]SizeInfo, error)

ListGPUSizes returns only the GPU-backed sizes from the catalog.

func ListSizes

func ListSizes() ([]SizeInfo, error)

ListSizes returns the cached, resale-priced sizes catalog.

func SizeBySlug

func SizeBySlug(slug string) (*SizeInfo, error)

SizeBySlug returns the resale size for a slug, or nil if unknown. Used to price launch quotes.

type VirtualMachine

type VirtualMachine struct {
	ID     string `json:"id"`
	Cpu    Cpu    `json:"cpu"`
	Memory int    `json:"memory"`
}

type VirtualMachinePath

type VirtualMachinePath struct {
	ID   string `json:"id"`
	Path string `json:"path"`
}

type Volume

type Volume struct {
	Name        string
	Id          string
	DisplayName string
	Region      string
	Size        int    // GB
	State       string // "Available", "Attached", "Creating"
	Format      string
	MachineName string // attached server name/ID, empty if detached
}

type VolumeCapable added in v1.108.30

type VolumeCapable interface {
	Volumes() VolumeClientInterface
}

VolumeCapable, VpcCapable and LoadBalancerCapable are the other nouns a provider client may speak. Same rule as KubernetesCapable: an assertion on the one client, never a second registry of clouds.

type VolumeClientInterface

type VolumeClientInterface interface {
	GetVolumes() ([]*Volume, error)
	GetVolume(name string) (*Volume, error)
	CreateVolume(spec *CreateVolumeSpec) (*Volume, error)
	DeleteVolume(name string) error
	AttachVolume(volumeName string, machineName string) error
	DetachVolume(volumeName string) error
	ResizeVolume(volumeName string, sizeGB int) error
}

func NewVolumeClient

func NewVolumeClient(providerType string, accessKeyId string, accessKeySecret string, region string) (VolumeClientInterface, error)

type VolumeDigitalOceanClient

type VolumeDigitalOceanClient struct {
	Client *godo.Client
	// contains filtered or unexported fields
}

func (*VolumeDigitalOceanClient) AttachVolume

func (c *VolumeDigitalOceanClient) AttachVolume(volumeName string, machineName string) error

func (*VolumeDigitalOceanClient) CreateVolume

func (c *VolumeDigitalOceanClient) CreateVolume(spec *CreateVolumeSpec) (*Volume, error)

func (*VolumeDigitalOceanClient) DeleteVolume

func (c *VolumeDigitalOceanClient) DeleteVolume(name string) error

func (*VolumeDigitalOceanClient) DetachVolume

func (c *VolumeDigitalOceanClient) DetachVolume(volumeName string) error

func (*VolumeDigitalOceanClient) GetVolume

func (c *VolumeDigitalOceanClient) GetVolume(name string) (*Volume, error)

func (*VolumeDigitalOceanClient) GetVolumes

func (c *VolumeDigitalOceanClient) GetVolumes() ([]*Volume, error)

func (*VolumeDigitalOceanClient) ResizeVolume

func (c *VolumeDigitalOceanClient) ResizeVolume(volumeName string, sizeGB int) error

type VolumeHetznerClient

type VolumeHetznerClient struct {
	Client *hcloud.Client
	// contains filtered or unexported fields
}

func (*VolumeHetznerClient) AttachVolume

func (c *VolumeHetznerClient) AttachVolume(volumeName string, machineName string) error

func (*VolumeHetznerClient) CreateVolume

func (c *VolumeHetznerClient) CreateVolume(spec *CreateVolumeSpec) (*Volume, error)

func (*VolumeHetznerClient) DeleteVolume

func (c *VolumeHetznerClient) DeleteVolume(name string) error

func (*VolumeHetznerClient) DetachVolume

func (c *VolumeHetznerClient) DetachVolume(volumeName string) error

func (*VolumeHetznerClient) GetVolume

func (c *VolumeHetznerClient) GetVolume(name string) (*Volume, error)

func (*VolumeHetznerClient) GetVolumes

func (c *VolumeHetznerClient) GetVolumes() ([]*Volume, error)

func (*VolumeHetznerClient) ResizeVolume

func (c *VolumeHetznerClient) ResizeVolume(volumeName string, sizeGB int) error

type Vpc added in v1.108.29

type Vpc struct {
	Name        string
	Id          string
	DisplayName string
	Region      string
	Cidr        string
	State       string
}

Vpc is a private network, in the shape every provider is mapped onto.

type VpcCapable added in v1.108.30

type VpcCapable interface {
	Vpcs() VpcClientInterface
}

type VpcClientInterface added in v1.108.29

type VpcClientInterface interface {
	GetVpcs() ([]*Vpc, error)
	GetVpc(name string) (*Vpc, error)
	CreateVpc(spec *CreateVpcSpec) (*Vpc, error)
	DeleteVpc(name string) error
}

func NewVpcClient added in v1.108.29

func NewVpcClient(providerType string, accessKeyId string, accessKeySecret string, region string) (VpcClientInterface, error)

type VpcDigitalOceanClient added in v1.108.29

type VpcDigitalOceanClient struct {
	Client *godo.Client
	// contains filtered or unexported fields
}

func (*VpcDigitalOceanClient) CreateVpc added in v1.108.29

func (c *VpcDigitalOceanClient) CreateVpc(spec *CreateVpcSpec) (*Vpc, error)

func (*VpcDigitalOceanClient) DeleteVpc added in v1.108.29

func (c *VpcDigitalOceanClient) DeleteVpc(name string) error

func (*VpcDigitalOceanClient) GetVpc added in v1.108.29

func (c *VpcDigitalOceanClient) GetVpc(name string) (*Vpc, error)

func (*VpcDigitalOceanClient) GetVpcs added in v1.108.29

func (c *VpcDigitalOceanClient) GetVpcs() ([]*Vpc, error)

GetVpcs walks every page. A single List answers at most one page, so returning it directly would silently report a fraction of the account as the whole of it.

Jump to

Keyboard shortcuts

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