paas

package
v1.786.145 Latest Latest
Warning

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

Go to latest
Published: Jul 8, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package paas mounts the native, in-process Hanzo PaaS control plane at /v1/paas/*: the "one and only one way to deploy" made native to the cloud binary. It is the Go port of the standalone Dokploy-based platform's build→deploy lifecycle (pkg/platform/src/services/ci/deploy-executor.ts + services/apps/inventory.ts + db/schema/apps-drift.ts), collapsed into an in-process cloud subsystem exactly like clients/ml is the k8s bridge for the Kubeflow CRDs.

The deploy mechanism is the SAME one the operator already reconciles: a merge-patch of the operator `Service` CR's `.spec.image`. No second deployer is invented; the Hanzo operator owns the rollout. This module only observes the declared/running/latest tags per service (the drift board) and flips the one CR field a deploy changes — the identical contract the Node deploy-executor implemented, now native.

drift.go is the PURE half: it derives the drift verdict for one observed service row and performs no IO. It is a faithful port of `pkg/platform/src/db/schema/apps-drift.ts` so the two implementations can never disagree about what "drift" means (one way to compute drift, period). The cluster reader (paas.go) owns observing the tags; this file only interprets them.

paas.go — the cluster-facing half of the native Hanzo PaaS control plane.

It mounts /v1/paas/* on the unified cloud binary and speaks to the SAME operator surface the standalone platform's deploy-executor drove: the `hanzo.ai/v1` `services` CustomResource. Two responsibilities, both a straight port of the Node platform:

GET  /v1/paas/apps            — the fleet drift board (inventory.ts): list every
                                operator Service CR across the platform
                                namespaces, read declared vs running tag +
                                health from the CR (+ its status), and attach
                                the drift verdict (drift.go / apps-drift.ts).
GET  /v1/paas/apps/:app       — one service row by CR name.
POST /v1/paas/apps/:app/deploy— deploy a new image tag by merge-patching the
                                Service CR's `.spec.image` (deploy-executor.ts).
                                The operator reconciles the rollout; cloud never
                                reimplements a deployer.
GET  /v1/paas/health          — real k8s reachability + Service CRD presence.

SECURITY — every route is GLOBAL-ADMIN ONLY, fail-closed, gated on the SAME predicate the rest of cloud uses: c.IsAdmin() (true only for a JWT-validated principal whose org is the admin org, matching the gateway's admin-guard — see clients/admin). Unlike clients/ml (per-tenant namespaces), the PaaS control plane reads and mutates SYSTEM Service CRs across the whole fleet, so it is admin-only: a tenant must never patch another org's — or a platform — service. The user-facing PaaS view lives in console; users never call this surface.

k8s client: built in-process from the in-cluster service account (rest.InClusterConfig) with a KUBECONFIG fallback for local/dev — the identical construction clients/ml uses. When no kubeconfig is resolvable the subsystem mounts anyway and every endpoint fails closed (503 + the real init error; the health route reports "degraded"), never status-theater.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsSemverTag

func IsSemverTag(tag string) bool

IsSemverTag reports whether tag is a strict `vX.Y.Z` semver tag.

func Mount

func Mount(app *zip.App, deps cloud.Deps) error

Mount wires the /v1/paas/* surface onto app. Every handler gates on c.IsAdmin() first (global-admin only), then reads/patches the operator Service CRs.

Types

type AppView

type AppView struct {
	ID          string   `json:"id"`   // <org>/<app>/<env>, e.g. hanzoai/iam/main
	Org         string   `json:"org"`  // image namespace, e.g. hanzoai
	App         string   `json:"app"`  // service / CR name, e.g. iam
	Env         string   `json:"env"`  // main|test|dev
	Repo        string   `json:"repo"` // owner/repo, e.g. hanzoai/iam
	Registry    string   `json:"registry"`
	DeclaredTag string   `json:"declaredTag"`
	RunningTag  string   `json:"runningTag"`
	LatestTag   string   `json:"latestTag"`
	Health      string   `json:"health"` // green|yellow|red|"" (unknown)
	Phase       string   `json:"phase"`  // operator status.phase (Running/…)
	Cluster     string   `json:"cluster"`
	Namespace   string   `json:"namespace"`
	Endpoints   []string `json:"endpoints"`
	Drift       Drift    `json:"drift"`
}

AppView is one service row on the drift board: the observed tags + topology + the derived drift verdict. It is the Go analogue of the platform's `AppView` (apps-api.ts) so console renders the same shape the Dokploy board did.

type Drift

type Drift struct {
	Severity DriftSeverity `json:"severity"`
	Flags    []DriftFlag   `json:"flags"`
}

Drift is the drift verdict for one observed service row: the ordered flags plus the rolled-up severity (apps-drift.ts `Drift`).

func ComputeDrift

func ComputeDrift(o Observed) Drift

ComputeDrift is the full drift verdict (flags + rolled-up severity) for one observed service row (apps-drift.ts `computeDrift`). Flags is always non-nil so the JSON encodes `[]`, never `null`.

type DriftFlag

type DriftFlag struct {
	Kind     DriftKind     `json:"kind"`
	Severity DriftSeverity `json:"severity"`
	Message  string        `json:"message"`
}

DriftFlag is a single drift finding: its kind, severity, and a human-readable reason (apps-drift.ts `DriftFlag`).

func ComputeDriftFlags

func ComputeDriftFlags(o Observed) []DriftFlag

ComputeDriftFlags derives the drift flags for one observed service row, exactly per the platform contract (apps-drift.ts `computeDriftFlags`).

Detection rules (each independent; a row may trip several):

  • floating-declared — DeclaredTag is set but not vX.Y.Z. The reconciler refuses non-semver declarations, so this is hard drift. (When the declaration itself is floating, comparing it against LatestTag for "stale" is meaningless, so stale is suppressed in that case.)
  • floating-running — RunningTag is set but not vX.Y.Z: the cluster is running a floating image. Hard drift.
  • stale — DeclaredTag and LatestTag are both known semver and differ: a newer release exists that is not yet declared.
  • un-rolled — DeclaredTag and RunningTag are both known and differ: the declaration has not reached the cluster yet.
  • no-release — a DeclaredTag exists but no GH Release was found (ReleaseURL "").
  • zero-assets — a GH Release exists (ReleaseURL set) but ReleaseAssets == 0.

Tags are compared verbatim (the reader stores reality un-normalized); no ordering is assumed beyond equality — matching the contract.

type DriftKind

type DriftKind string

DriftKind enumerates the kinds of drift from the platform contract (apps-drift.ts `DriftKind`). Each value is independent — one service row can carry several at once (e.g. a floating running tag with a zero-asset release).

const (
	// DriftStale — declared ≠ latest: a newer release exists but is not declared. (yellow)
	DriftStale DriftKind = "stale"
	// DriftUnrolled — running ≠ declared: the cluster has not rolled to the declared tag. (yellow)
	DriftUnrolled DriftKind = "un-rolled"
	// DriftFloatingDeclared — declaredTag is not strict semver; the reconciler would refuse it. (red)
	DriftFloatingDeclared DriftKind = "floating-declared"
	// DriftFloatingRunning — runningTag is not strict semver; policy violation on the cluster. (red)
	DriftFloatingRunning DriftKind = "floating-running"
	// DriftNoRelease — no GH Release found for the declared tag. (red)
	DriftNoRelease DriftKind = "no-release"
	// DriftZeroAssets — GH Release exists but shipped 0 assets. (red)
	DriftZeroAssets DriftKind = "zero-assets"
)

type DriftSeverity

type DriftSeverity string

DriftSeverity is the aggregate drift severity. "ok" = no flags; otherwise the max over flags.

const (
	SeverityOK     DriftSeverity = "ok"
	SeverityYellow DriftSeverity = "yellow"
	SeverityRed    DriftSeverity = "red"
)

func DriftSeverityOf

func DriftSeverityOf(flags []DriftFlag) DriftSeverity

DriftSeverityOf rolls a list of flags up to a single severity (red > yellow > ok). Mirrors apps-drift.ts `driftSeverity`.

type Observed

type Observed struct {
	DeclaredTag   string // what SHOULD run — spec.image.tag on the operator Service CR
	RunningTag    string // what ACTUALLY runs — observed from the CR status / Deployment
	LatestTag     string // newest released tag (GH release reader; empty until wired)
	ReleaseURL    string // GH Release URL for DeclaredTag (empty ⇒ no-release)
	ReleaseAssets int    // asset count on the GH Release (0 ⇒ zero-assets)
}

Observed is the minimal set of already-observed tag fields the drift derivation reads — mirrors the `Pick<App, …>` the TS `computeDrift` accepts. The reader (paas.go) fills these from the cluster; the release fields are populated by the GH-release reader (a follow-up), so today they are the honest zero value (ReleaseURL == "" ⇒ no-release, exactly like the un-populated TS columns).

Jump to

Keyboard shortcuts

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