paas

package
v1.801.191 Latest Latest
Warning

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

Go to latest
Published: Jul 23, 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 reads the operator's `hanzo.ai/v1` `App` CustomResource — the one workload kind the fleet runs on:

GET  /v1/paas/apps            — the fleet drift board (inventory.ts): list every
                                operator App 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 app row by CR name.
POST /v1/paas/apps/:app/deploy— zero-downtime ROLLING RESTART of the app's
                                Deployment (the `kubectl rollout restart`
                                mechanism): re-pulls the DECLARED image, recreates
                                pods gracefully. It never changes the declared
                                TAG (that stays a git commit, the one thing Hanzo
                                CD's selfHeal reconciles), so there is no drift to
                                revert. This is `hanzo deploy`.
GET  /v1/paas/health          — real k8s reachability + App CRD presence.

SECURITY — every route is authorized off ONE IAM identity, exactly like the /v1/runner build path (clients/platform/runner.go): the guard admits a validated principal (principal.Validated) who is a SuperAdmin OR an OrgAdmin, and each handler then CONFINES a non-super caller to its own org's platform namespaces (scopedNamespaces, keyed on principal.Org — never a client header). A SuperAdmin observes/acts on the whole fleet; an OrgAdmin only on the namespaces its org owns; a plain member or an unauthenticated caller is refused 403. So a tenant admin can never observe — or restart — another org's, or a platform, app, and the platform operator drives the board off a plain `hanzo login` with no shared token. The user-facing per-app PaaS view still lives in console; this is the CLI/operator 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.

release.go — the first-party release seam. build.go's RegisterServiceReleaser is the inversion that lets a build-completion path (clients/platform/release.go, or any package-cloud caller) request a rollout with no cloud⇄paas import cycle.

The App CRs in the platform namespaces are declared in universe git (infra/k8s/operator/crs/) and reconciled by Hanzo CD with selfHeal, so a direct spec.image patch is reverted on the next sync. releaseService therefore refuses and names the one way to roll a tag: commit it to the manifest. The clean-semver gate (splitReleaseImage) still validates the request so a caller gets an honest, specific error.

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 is behind the IAM guard (SuperAdmin or OrgAdmin, org-confined), then reads/patches the operator App CRs + their Deployments.

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