Help us shape the future of Go! Take the 2026 Go Developer Survey

deploy

package
v1.0.2 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 2026 License: Apache-2.0 Imports: 28 Imported by: 0

Documentation

Overview

Package deploy is forge's deploy-tier library: the pure functions that operate on the tier specs in pkg/deploy/v1alpha1.

  • RenderWorkloads (workload_render.go) turns an environment's Workload set into the Kubernetes objects that run it, under a Profile. It is the one Kubernetes renderer (ADR 0002 §4). Render (render.go) covers the per-object tiers, StaticSite and ManagedDatabase. Both are PURE and DETERMINISTIC: same input, byte-identical output, nothing derived from the time, the environment or a cluster read. Two executors call them — `forge env deploy` when self-hosted and the control plane's operators when hosted — and if the function could disagree with itself, those executors would fight over the same objects forever.
  • CheckShapeBand / RatioFloor (shape.go) are the hosted billing rule, in one place.
  • ObservedStateOf / Converged (observed.go) map observed status onto the deploy ledger's vocabulary.

Nothing here talks to a cluster, a registry, a bucket or the control plane. Hosted concerns — per-customer namespaces, hostname allocation, registry allowlists, quota, secret materialization — are composed AROUND Render's output by the control plane. They never enter this package.

Index

Constants

View Source
const (
	ObservabilityAgentName = "otel-agent"

	// ForwarderHeadersEnv is the same variable for the local forwarder.
	ForwarderHeadersEnv = agentHeadersEnv
)

Names of the objects the node agent renders.

View Source
const (
	LabelName      = "app.kubernetes.io/name"
	LabelManagedBy = "app.kubernetes.io/managed-by"
	LabelPartOf    = "app.kubernetes.io/part-of"
	// LabelVersion is the release a pod runs. Pod templates only: it is not
	// in any selector.
	LabelVersion = "app.kubernetes.io/version"
	// LabelEnv is the env-ownership stamp (kcl/lib/labels.k:84).
	LabelEnv = "forge.dev/env"
	// LabelJobName is a Job's STABLE handle, since its metadata.name carries
	// a spec hash (kcl/workloads/expand.k:706-709).
	LabelJobName = "forge.dev/job-name"
	// LabelSpecHash answers "did this Job actually change?" without a YAML
	// diff.
	LabelSpecHash = "forge.dev/spec-hash"
)

Label keys forge stamps on every object it renders (kcl/lib/labels.k managed_labels). Selectors use ONLY LabelName, because a selector is immutable on a Deployment and part-of must stay free to change.

View Source
const (
	// DefaultServicePort is the `http` port a service that declares none
	// listens on (kcl/workloads/expand.k:502, schema.k:154-155): serverkit's
	// standard mux.
	DefaultServicePort int32 = 8080

	// DefaultProgressDeadlineSeconds is the Deployment progressDeadlineSeconds
	// forge renders.
	DefaultProgressDeadlineSeconds int32 = 600
)

Defaults the renderer owns (the spec's own defaults are v1alpha1's).

View Source
const AnnotationDeletionPolicy = "forge.dev/deletion-policy"

AnnotationDeletionPolicy is stamped on a ManagedDatabase's Cluster so that whatever PRUNES rendered objects can see the author's retain decision without reading the spec. A retain-policy Cluster must never be pruned: deleting it deletes its volumes.

View Source
const AnnotationDeployPhase = "forge.dev/deploy-phase"

AnnotationDeployPhase places a standalone Job in the deploy (kcl/workloads/expand.k:714-718). internal/cluster reads it.

View Source
const BandGranularityMillicores = 125

BandGranularityMillicores is the coarsest CPU step at which the band-implied memory lands on a whole byte. It produces the conformant ladder 125m/512Mi, 250m/1Gi, 500m/2Gi, 1000m/4Gi, and so on.

View Source
const ClientIdentityKeyPattern = `^(k8s|reliant)\..*`

ClientIdentityKeyPattern matches the resource attributes a client must not be allowed to set on the node agent's OTLP input: Kubernetes identity (k8s.*), which the node supplies, and the hosting platform's own namespace (reliant.*). The node agent deletes them before k8sattributes runs. service.* and deployment.* are the app's own and are kept.

View Source
const DefaultStabilityWindow = 2 * time.Minute

DefaultStabilityWindow is how long a tier must have been continuously ready before it counts as converged.

View Source
const MemGiBPerVCPU = 4

MemGiBPerVCPU is the band: 4 GiB of memory per vCPU. It matches the top of the daemon ladder (8 CPU / 32 GiB), so that ladder is already conformant.

View Source
const ObservabilityAgentKind = "ObservabilityAgent"

ObservabilityAgentKind is the kind of the forge.dev/v1alpha1 record kcl/render.k emits, one per cluster that runs a forge workload.

View Source
const ReadOnlyRoleConnectionLimit int64 = 10

ReadOnlyRoleConnectionLimit caps the read-only role's concurrent sessions. It bounds the load an agent's or the platform's queries can put on a primary the application is serving from.

View Source
const RunAsUser int64 = 65532

RunAsUser is the identity every rendered pod runs under. It is forge's value (lib/services.k) and control-plane's (Config.RunAsUser) alike.

View Source
const RuntimeConfigFile = "config.js"

RuntimeConfigFile is the document's name. It is served at <basePath>/RuntimeConfigFile, beside the site's index.html.

View Source
const RuntimeConfigGlobal = "__FORGE_CONFIG__"

RuntimeConfigGlobal is the browser global a StaticSite's runtime config document assigns, and the one forge's generated config module reads.

Variables

View Source
var RestrictedKinds = map[string]bool{
	"Deployment":            true,
	"Service":               true,
	"ServiceAccount":        true,
	"PersistentVolumeClaim": true,
	"NetworkPolicy":         true,
	"PodDisruptionBudget":   true,
	"Job":                   true,
}

RestrictedKinds is every object kind RenderWorkloads may emit under ProfileRestricted: the objects that reach only the hosted user's own pods. No RBAC kind is in it, ever. A gating job is an initContainer inside a Deployment or Job, so it needs no kind of its own.

Functions

func BandMemoryBytes

func BandMemoryBytes(cpuMillicores int64) int64

BandMemoryBytes is the memory the band implies for a CPU request.

func CheckHostedResourcesWithin added in v1.0.1

func CheckHostedResourcesWithin(plan HostedPlan, claimable ClaimableLimits, r v1alpha1.Resources) error

CheckHostedResourcesWithin judges one hosted workload's resources under plan: the shape band for the standard plan, the claimable caps (claimable) for the claimable one. A refusal is a *ResourcesRefusal naming the conformant block.

func CheckShapeBand

func CheckShapeBand(r v1alpha1.Resources) error

CheckShapeBand reports whether a resource request sits exactly ON the band, with a reason naming the conformant shape when it does not.

EQUALITY, not a floor. The platform bills the max of what was asked and what the band implies, so an off-band request is not rejected at billing time: it is REPRICED silently, and the invoice stops matching the declaration. Requiring the request to be on the band at deploy time is what makes the bill equal the request. Only the REQUEST pair is checked, because requests are what is reserved and billed; limits are the workload's burst ceiling.

Defaults are applied first, so an omitted resources block (the 250m / 1 GiB entry shape) passes.

func Converged

func Converged(p v1alpha1.Phase, readySince, now time.Time, window time.Duration) bool

Converged is forge's definition: ready now AND continuously ready for at least window. It takes the time of the last transition INTO ready (the Ready condition's lastTransitionTime) and the current time as arguments, so it stays pure and every caller agrees given the same observation.

A zero readySince means the transition time is unknown, and that is NOT converged. Treating an unknown duration as "long enough" is the instant "ready" this definition exists to replace.

func DatabaseCredentialSecretName

func DatabaseCredentialSecretName(databaseName string) string

DatabaseCredentialSecretName is the Secret CloudNativePG publishes for a Cluster's application role: "<cluster>-app", in the Cluster's namespace. This is a CONTRACT WITH CNPG, not a name forge chooses. A DatabaseRef env var reads it directly, because the Cluster renders into the same namespace as the workload that references it.

func DatabaseIdentifiers

func DatabaseIdentifiers(name string) (database, role string)

DatabaseIdentifiers are the Postgres objects a ManagedDatabase creates. The Cluster name is the resource name unchanged. The database is that name with hyphens folded to underscores, so no reference ever has to remember to quote it. The role is the database plus "_app": it is distinct from the database and it is never `postgres`.

func KCLResources added in v1.0.1

func KCLResources(r v1alpha1.Resources) string

KCLResources renders r as a `resources = fw.Resources {...}` KCL field, memory in forge.mib, omitting unset limits.

func LowerContainer

func LowerContainer(c v1alpha1.Container) corev1.Container

LowerContainer lowers a Container to its Kubernetes form for a pod forge does not render itself (a raw Deployment in Bundle.manifests): image pull policy, command/args/env, ports, defaulted resources, the probes with forge's timings, and restartPolicy Always for a Native sidecar. It carries no securityContext and no /tmp mount, because the raw pod owns both. It is the SAME lowering sidecarContainer starts from, so the two cannot drift.

func PVCName

func PVCName(workloadName string) string

PVCName is the claim backing a workload's storageGiB. The name is exported because an observer must read back the claim this package emitted.

func RatioFloor

func RatioFloor(cpuMillicores, memoryBytes int64) (int64, int64)

RatioFloor rounds UP the deficient axis of a (cpu millicores, memory bytes) request to the band. It is the metering half of the same rule: a lopsided shape costs what it occupies. It is pure, so admission and metering can call the same function.

Bytes, not control-plane's MiB. The neutral unit is bytes everywhere in this package, and a MiB-shaped floor would put a unit conversion at every call site.

func ReadOnlyRoleSecret added in v1.0.0

func ReadOnlyRoleSecret(name, password string, ctx Context) (*unstructured.Unstructured, error)

ReadOnlyRoleSecret renders the kubernetes.io/basic-auth Secret that carries the read-only role's credentials for the ManagedDatabase called name, in ctx.Namespace. CNPG reads it as the managed role's passwordSecret, and a client that queries the database as the role (`forge db query`, the control plane) reads the same Secret.

THE CALLER SUPPLIES THE PASSWORD and must create the Secret only if absent: rotating it on every apply would lock out a client that has the old one, and a render that invented a password could not be byte-identical twice. The `cnpg.io/reload` label makes CNPG apply a changed password immediately rather than at its next reconcile.

func Render

func Render(obj runtime.Object, ctx Context) ([]*unstructured.Unstructured, error)

Render renders one StaticSite or ManagedDatabase. The object's metadata.name is the resource name and its spec is the declaration. Its metadata.namespace is NOT consulted: on hosted the CR lives where the control plane keeps it, not where the workload runs, so the target is always ctx.Namespace.

A Workload is REFUSED here. Its `before` ordering and workloadURL references are relations between workloads, so one Workload cannot be rendered correctly on its own. Render the env's whole set with RenderWorkloads.

StaticSite renders NO Kubernetes objects. Its executor is the release planner (bucket sync, retention, CDN invalidation), not an apply, so it returns an empty list rather than an error: a caller rendering a whole environment should not have to special-case it.

func RenderManagedDatabase

func RenderManagedDatabase(name string, spec v1alpha1.ManagedDatabaseSpec, ctx Context) ([]*unstructured.Unstructured, error)

RenderManagedDatabase renders a ManagedDatabase into a CloudNativePG Cluster.

This is control-plane's closed shape (manageddatabase.BuildClusterPlan), the one that ran live. enableSuperuserAccess is written as false rather than left to CNPG's default: the value is the difference between a customer holding an owner role and holding `postgres`, and a CNPG release changing its default must not change that.

Backups are rendered when, and only when, the target offers a store

ctx.DatabaseBackup nil still renders a Cluster with NO backup stanza, and that remains deliberate: a Cluster carrying a backup section it cannot reach would LOOK backed up while archiving nothing. What changed is that it is no longer the ONLY thing this renders — an environment that has declared an object store gets continuous WAL archiving, scheduled base backups and a retention window, which is what the gate in control-plane's manageddatabase/doc.go was waiting for.

The stanza is the CNPG-I Barman Cloud plugin (`spec.plugins`), not `spec.backup.barmanObjectStore`: the in-tree form is deprecated from CNPG 1.26 and slated for removal, and moving a tier off its backup mechanism after it holds customer data is the migration nobody wants to schedule.

serverName, and why a restore may not reuse one

Each database archives under its OWN serverName inside the bucket prefix, and a RESTORED database is given a fresh one (`<name>-r<n>`). This is not tidiness. barman-cloud refuses to archive into a non-empty prefix: a restored Cluster pointed at its source's serverName fails barman-cloud-check-wal-archive with "Expected empty archive", the recovery job crash-loops, and the restore never completes. Verified against CNPG 1.30.0 with plugin v0.15.0 — the first restore attempt failed exactly this way, and giving the restored cluster its own serverName fixed it.

A fresh serverName also means a restore never writes into the WAL stream it is reading, so a botched restore cannot corrupt the backups it came from.

The Cluster is unstructured on purpose. Importing CNPG's typed API would pull the whole operator module into every forge consumer to type-check six fields.

Self-hosted, applying this requires the CNPG operator in the cluster (and, for backups, the Barman Cloud plugin). The executor should detect the postgresql.cnpg.io CRD and fail with a runbook if it is missing.

func RenderObservabilityAgent

func RenderObservabilityAgent(s ObservabilityAgentSpec, ctx ObservabilityContext) ([]*unstructured.Unstructured, error)

RenderObservabilityAgent renders the node agent: Namespace, ServiceAccount, ClusterRole + Binding, ConfigMap, DaemonSet and the node-local Service. Pure and deterministic.

WHAT FILELOG NEEDS. Container stdout lives in /var/log/pods on the node, root-readable only, so the pod mounts that directory read-only as a hostPath and runs as root with every capability dropped and a read-only root filesystem. A second hostPath (agentStorageDir) holds checkpoints and the exporter's queue. hostPath is forbidden by pod-security "baseline", so the namespace is labelled privileged: this is why the agent is its own namespace and why it is never rendered by the Restricted (hosted) profile.

func RenderWorkloads

func RenderWorkloads(ws []v1alpha1.Workload, p v1alpha1.Profile, ctx Context) ([]*unstructured.Unstructured, error)

RenderWorkloads renders an environment's whole workload SET into the Kubernetes objects that run it. It is the ONE Kubernetes renderer (ADR 0002 §4): the CLI calls it for Cluster-bound workloads under ProfileFull, and the control plane's operator calls it for Hosted ones under ProfileRestricted.

It takes the set rather than one workload because two of its policies are relations BETWEEN workloads: a job's `before` becomes an initContainer on the workloads it gates, and a `before` that names nothing is refused. A per-workload entry point could not see either.

It is the port of kcl/workloads/expand.k (with capabilities.k, render.k and lib/{rbac,netpol,labels}.k), which it replaces. Every policy cites the KCL it came from. Where it deliberately differs, the comment says so, and .scratch/parity.md lists every difference.

Per kind:

service   Deployment + Service (+ PDB, PVC) + ServiceAccount [+ RBAC] [+ NetworkPolicy]
worker    Deployment (+ PDB, PVC) + ServiceAccount [+ RBAC] [+ NetworkPolicy]
operator  Deployment [+ Service when it declares ports] (+ PDB, PVC) + ServiceAccount
          + Role + RoleBinding (config-read defaults, leases derived)
          + ClusterRole + ClusterRoleBinding (CRD rules derived)
job       standalone: Job + ServiceAccount [+ RBAC] [+ NetworkPolicy];
          with `before`: nothing of its own, an initContainer on each gated pod
cron      CronJob + ServiceAccount [+ RBAC] [+ NetworkPolicy]
tool      nothing (built into the image, never scheduled)

[+ RBAC] is a Role + RoleBinding (the config-read defaults + namespacedRBAC) whenever either tier is declared, plus, under the Full profile, a ClusterRole + ClusterRoleBinding carrying ONLY clusterRBAC. Both bind the workload's one ServiceAccount (see identity). [+ NetworkPolicy] is the per-workload ingress policy: always under ProfileRestricted, and under ProfileFull only when Context.Network is set.

Every spec is validated under p FIRST, all-or-nothing: no object is emitted for a set with any error, and every error is reported together.

It is PURE and DETERMINISTIC. Same input, byte-identical output. Two executors apply its output against the same objects, and a renderer that disagreed with itself would make them fight forever.

What it does NOT do, because it is hosted policy the operator composes around the result: RuntimeClass and node isolation, the registry allowlist, the image pull secret, the SecretRef customer-prefix rule, hostname routes, quota and the shape band. It also does not render the Namespace. The namespace belongs to the environment (hosted: to the platform), not to any workload in it.

func ResolveEnvWorkloadURLs

func ResolveEnvWorkloadURLs(env []v1alpha1.EnvVar, resolve WorkloadURLResolver) ([]v1alpha1.EnvVar, error)

ResolveEnvWorkloadURLs returns a copy of env with every workloadURL reference replaced by its resolved literal value. Render refuses an unresolved reference, so this is the step that must run before it.

func ResolveRuntimeConfig

func ResolveRuntimeConfig(rc map[string]v1alpha1.RuntimeConfigValue, resolve WorkloadURLResolver) (map[string]string, error)

ResolveRuntimeConfig turns a StaticSite's declared runtime config into the flat key -> string document the browser receives, resolving every workloadURL reference through resolve.

Every failing entry is reported at once, so a spec with two dangling references is fixed in one pass. The input is validated first: a spec that sets both or neither channel has no correct reading, and resolving it would hide that.

func RunsBeforeRollout added in v1.0.1

func RunsBeforeRollout(s v1alpha1.WorkloadSpec) bool

RunsBeforeRollout reports whether a job workload runs to completion BEFORE the workloads it belongs to roll out: a standalone job in the pre-rollout phase, or a GATING job (`before = [...]`, `before = [fw.BEFORE_ALL]`), which the renderer runs ahead of every workload it names. Either is the shape of a service's own migration, which is what the claimable plan admits beside its one service.

The two spellings are one fact. EffectiveDeployPhase answers only for a standalone job (a gating job's phase is its dependents'), so judging a migration by it alone refused the scaffold's `before = [fw.BEFORE_ALL]` migrate job, while `before` and `deployPhase` cannot both be set.

func RuntimeConfigJS

func RuntimeConfigJS(values map[string]string) (string, error)

RuntimeConfigJS renders the runtime config document a StaticSite serves at <basePath>/config.js:

window.__FORGE_CONFIG__ = {"API_URL":"https://..."};

It is the ONE renderer of that document for resolved specs, so the control plane's operator and forge produce identical bytes for identical input. Keys are emitted sorted (encoding/json sorts map keys), which keeps the document byte-stable across syncs and lets a no-op sync be detected by comparing bytes.

Types

type ClaimableLimits added in v1.0.1

type ClaimableLimits struct {
	MaxServices      int
	MaxReplicas      int
	MaxMigrationJobs int
	// WorkloadCPUMillicores and WorkloadMemoryBytes are each workload's
	// request AND limit cap (the service, and separately its migration job).
	WorkloadCPUMillicores int64
	WorkloadMemoryBytes   int64

	MaxDatabases      int
	DatabaseInstances int
	// DatabaseDeclaredStorageGiB is the largest storageGiB a ManagedDatabase
	// may declare. The platform provisions DatabaseStorageBytes whatever it
	// declares, and sizes the Postgres pod itself (DatabaseCPUMillicores,
	// DatabaseMemoryBytes, DatabaseMaxConnections): informational here.
	DatabaseDeclaredStorageGiB int32
	DatabaseCPUMillicores      int64
	DatabaseMemoryBytes        int64
	DatabaseStorageBytes       int64
	DatabaseMaxConnections     int

	MaxStaticSites int
}

ClaimableLimits is the claimable plan's caps as the control plane publishes them (GetClaimableAvailability's `limits`; control-plane internal/planlimits/claimable.go is their one declaration). Per workload, and with no burst: a limit is held to the cap as well as the request.

forge does not own these numbers. A caller asks the control plane for them (internal/cli's claimable limits) and falls back to ClaimableLimitsFallback only when it cannot, saying so.

func ClaimableLimitsFallback added in v1.0.1

func ClaimableLimitsFallback() ClaimableLimits

ClaimableLimitsFallback is forge's compiled-in copy of the claimable plan's caps (control-plane, 2026-10-29), for when the control plane cannot be asked. A caller that uses it says so: the platform's own answer wins.

func (ClaimableLimits) CheckResources added in v1.0.1

func (l ClaimableLimits) CheckResources(r v1alpha1.Resources) error

CheckResources holds one workload's resources to the claimable caps.

func (ClaimableLimits) DatabaseStorage added in v1.0.2

func (l ClaimableLimits) DatabaseStorage() string

DatabaseStorage is the database's storage as prose, stating both numbers: the data the platform provisions and the whole-GiB volume that holds it ("256 MiB of data (1 GiB volume)").

func (ClaimableLimits) Resources added in v1.0.1

func (l ClaimableLimits) Resources() v1alpha1.Resources

Resources is the largest shape the claimable plan admits, request equal to limit. It is what a refusal suggests.

func (ClaimableLimits) Valid added in v1.0.1

func (l ClaimableLimits) Valid() bool

Valid reports whether l can judge anything: a published answer that left the workload caps zero (an older or broken control plane) cannot, and the caller falls back instead of refusing every workload.

func (ClaimableLimits) WorkloadShape added in v1.0.2

func (l ClaimableLimits) WorkloadShape() string

WorkloadShape is each workload's cap as prose: "125m / 128 MiB".

type Context

type Context struct {
	// Namespace every rendered object lands in. Required.
	Namespace string
	// PartOf is the app.kubernetes.io/part-of value (the project). Empty
	// falls back to each object's own name (kcl/lib/labels.k:185-191).
	PartOf string
	// Env is the forge.dev/env ownership stamp (kcl/lib/labels.k). It is
	// written to every object's labels and every pod template's labels,
	// never to a selector. Empty emits no label, matching labels.k's
	// "no -D env= binding" case.
	Env string
	// Version is the release being deployed, stamped as
	// app.kubernetes.io/version on pod templates (never a selector) so log
	// lines file-scraped from the node carry the version the pod runs. It is
	// set from the release, so the label — and the rollout it causes — moves
	// only when the release does. Empty emits no label.
	Version string
	// Network, when set, adds the ENV-WIDE NetworkPolicy bundle
	// (kcl/lib/netpol.k): default-deny plus the DNS, same-namespace,
	// external-egress, telemetry and ingress-controller allows.
	//
	// WHY IT IS HERE, AND OPTIONAL. The per-workload ingress policy is a
	// fact about one workload (which of ITS ports may be dialled, and from
	// where), so RenderWorkloads always emits it. The bundle is a fact about
	// the NAMESPACE: which egress the environment's database, collector and
	// gateway need. RenderWorkloads already renders a whole env's set, which
	// is the one place that knows every workload shares the namespace, so
	// the bundle rides the same call instead of a second KCL renderer
	// (kcl/workloads/render.k:171 today). It is a pointer because the two
	// executors want different answers: a self-hosted env declares its
	// egress here, while the hosted control plane passes nil and layers its
	// own per-customer egress rules on the namespace it owns.
	Network *EnvNetworkPolicy
	// ImagePullSecrets are names of EXISTING image-pull Secrets in Namespace
	// (a private registry's credentials, e.g. ghcr-creds). An environment
	// fact, not a workload one: the same workload pulls anonymously from a
	// local registry in dev and with a credential in prod.
	//
	// PLACEMENT, as lib/rbac.k and lib/services.k:487 had it: on every
	// ServiceAccount forge generates (so every pod, Job and CronJob bound to
	// it pulls with them, and the pod spec stays untouched), and on the POD
	// SPEC only for a workload with a serviceAccount override, because forge
	// does not own that ServiceAccount and Kubernetes unions pod-level pull
	// secrets with the SA's own.
	ImagePullSecrets []string

	// DatabaseBackup, when set, is the object store a ManagedDatabase
	// archives WAL and base backups to. Nil renders a Cluster with no
	// backup stanza — see RenderManagedDatabase for why that stayed the
	// default rather than becoming an error.
	//
	// IT IS A TARGET FACT, NOT A SPEC FIELD, for the same reason Namespace
	// is: which bucket a database backs up to, and with which identity, is
	// decided by whoever runs the environment. A hosted user declares that
	// they want a database and (via spec.Restore) where to recover it
	// from; they never name a bucket, and a spec field would let them name
	// somebody else's.
	DatabaseBackup *DatabaseBackup

	// DatabasePlacement pins a ManagedDatabase's instances onto the nodes
	// that may hold data. Empty renders no placement, which is what a
	// single-pool cluster wants.
	DatabasePlacement *DatabasePlacement
}

Context carries the TARGET facts a spec deliberately does not: where the objects land, which project and environment they belong to, and the env-wide network policy.

Nothing hosted is in here. There is no org, no customer, no allocated hostname and no image allowlist. The control plane stamps those AROUND the renderer's output. If the renderer took them as inputs it would stop being the same function for both destinations.

type DatabaseBackup

type DatabaseBackup struct {
	// Bucket is the destination, as a barman destinationPath WITHOUT the
	// per-database suffix: "s3://forge-db-backups/<org>/<env>" or
	// "gs://...". RenderManagedDatabase appends the database's own segment,
	// so two databases can never share a prefix.
	// +required
	Bucket string

	// EndpointURL overrides the object store endpoint (an in-cluster
	// S3-compatible store in dev and e2e). Empty uses the provider's real
	// endpoint, which is what prod wants.
	EndpointURL string

	// CredentialSecret names a Secret in the database's namespace holding
	// ACCESS_KEY_ID and ACCESS_SECRET_KEY. Empty means NO static
	// credential: on GCS the instance authenticates with Workload Identity
	// (googleCredentials.gkeEnvironment), which is the prod mode and the
	// reason this is not required.
	CredentialSecret string

	// RetentionPolicy is a barman retention window ("30d"). Empty keeps
	// backups forever, which is a cost decision rather than a safe default,
	// so environments set it.
	RetentionPolicy string

	// ServerName overrides the WAL stream this database archives under,
	// inside the bucket prefix. Empty derives one from the database name
	// (see databaseServerName).
	//
	// THE PLATFORM OWNS STREAM IDENTITY BECAUSE THE DERIVED NAME IS NOT
	// ENOUGH FOR A REPEATED IN-PLACE RESTORE. Restoring "app" from "app"
	// derives the stream "app-restored". Do it a SECOND time — the first
	// restore picked the wrong timestamp, which is the single most likely
	// reason to restore twice — and the derived name is unchanged, so the
	// new cluster tries to archive into a prefix that already has WAL in
	// it. barman refuses ("Expected empty archive"), the recovery job
	// crash-loops, and the failure arrives during an incident.
	//
	// An executor that can restore more than once must therefore allocate
	// a fresh stream per restore (a counter, the deployment generation, a
	// timestamp) and pass it here. forge cannot derive it: the number of
	// times this database has been restored is not in the spec.
	ServerName string
}

DatabaseBackup is the object store behind the managed-database tier, as the CNPG-I Barman Cloud plugin consumes it.

THE PLUGIN, NOT spec.backup.barmanObjectStore. CNPG deprecated the in-tree stanza in 1.26 and is removing it; a data tier that has to be migrated off its backup mechanism after it holds customer rows is strictly worse than one that adopts the replacement while it holds none.

type DatabasePlacement

type DatabasePlacement struct {
	// NodeSelector restricts instances to matching nodes.
	NodeSelector map[string]string
	// Tolerations let instances onto tainted nodes.
	Tolerations []v1alpha1.Toleration
}

DatabasePlacement is where a database's instances may run. A data tier's placement is not the same question as a stateless workload's: these pods own persistent volumes, so the nodes they land on decide where the data physically lives and whether it survives a pool being recycled.

type EnvNetworkPolicy

type EnvNetworkPolicy struct {
	// EgressPorts are TCP ports every pod may dial anywhere: 443 for HTTPS,
	// 5432 for a managed Postgres outside the cluster. Empty omits the
	// allow-egress-external policy.
	EgressPorts []int32
	// TelemetryNamespace runs the OTLP collector. Empty omits the policy.
	TelemetryNamespace string
	// IngressNamespace runs the Gateway / ingress controller. Empty omits
	// the policy.
	IngressNamespace string
}

EnvNetworkPolicy is the input to the env-wide NetworkPolicy bundle (kcl/lib/netpol.k:53, WorkloadEnv's egress_ports / telemetry_namespace / ingress_namespace, kcl/workloads/render.k:96-116).

type HostedPlan added in v1.0.1

type HostedPlan string

HostedPlan is the plan a hosted environment is admitted under.

const (
	// HostedPlanStandard is an organization's own, billed plan. The zero
	// value: an env that names no plan is judged as standard.
	HostedPlanStandard HostedPlan = ""
	// HostedPlanClaimable is the anonymous, platform-funded plan of a
	// claimable deploy.
	HostedPlanClaimable HostedPlan = "claimable"
)

func (HostedPlan) String added in v1.0.1

func (p HostedPlan) String() string

String names the plan for messages.

type ObservabilityAgentSpec

type ObservabilityAgentSpec struct {
	// Endpoint is the destination's OTLP base URL; Protocol is
	// "http/protobuf" or "grpc".
	Endpoint string `json:"endpoint"`
	Protocol string `json:"protocol"`
	// HeadersSecret holds the destination's request headers as a JSON object
	// of header name to value. Nil sends none.
	HeadersSecret *ObservabilitySecretKey `json:"headersSecret,omitempty"`
	// TokenSecret holds a bearer token as the key's whole value (no JSON, no
	// scheme). The node agent mounts it as a file and sends it as
	// "Authorization: Bearer <token>" through the collector's bearertokenauth
	// extension, which re-reads the file when it changes: a Secret that is
	// renewed in place (kubectl, a control plane) takes effect with no restart.
	// Mutually exclusive with HeadersSecret, which is read once at start.
	TokenSecret *ObservabilitySecretKey `json:"tokenSecret,omitempty"`
	Signals     ObservabilitySignals    `json:"signals"`
	// Image is the collector image; MemoryLimitMiB bounds the agent.
	Image          string `json:"image,omitempty"`
	MemoryLimitMiB int    `json:"memoryLimitMiB,omitempty"`
}

ObservabilityAgentSpec is the declaration both renderers take. It is the `spec` of the ObservabilityAgent record kcl/render.k emits.

func (ObservabilityAgentSpec) AgentConfig

func (s ObservabilityAgentSpec) AgentConfig(namespace string) (string, error)

AgentConfig is the node agent's collector configuration, as YAML.

func (ObservabilityAgentSpec) CollectorArgs

func (s ObservabilityAgentSpec) CollectorArgs(configPath string) []string

CollectorArgs are the collector's command-line args. The feature gate is what lets the profiles pipeline load.

func (ObservabilityAgentSpec) ForwarderConfig

func (s ObservabilityAgentSpec) ForwarderConfig() (string, error)

ForwarderConfig is the local forwarder's collector configuration: OTLP in on 4317/4318, the files `forge env up` writes under /var/log/forge in, and the destination out. There is no Kubernetes here to enrich from.

type ObservabilityContext

type ObservabilityContext struct {
	// Namespace the agent runs in. It is created here, because the agent
	// needs hostPath volumes the Restricted pod-security level forbids, so
	// the namespace must not inherit the env's restricted one.
	Namespace string
	PartOf    string
	Env       string
}

ObservabilityContext is the placement of one agent.

type ObservabilitySecretKey

type ObservabilitySecretKey struct {
	Name string `json:"name"`
	Key  string `json:"key"`
}

ObservabilitySecretKey names one key of a Secret.

type ObservabilitySignals

type ObservabilitySignals struct {
	Traces   bool `json:"traces"`
	Metrics  bool `json:"metrics"`
	Logs     bool `json:"logs"`
	Profiles bool `json:"profiles"`
}

ObservabilitySignals says which signals the collector ships.

type ObservedState

type ObservedState string

ObservedState is the destination-neutral health vocabulary: the ONE mapping between a tier's Phase, the control plane's deployments.observed_state column, and forge's deploystate verdicts.

The three vocabularies disagreed. control-plane's ledger had seven values (pending, progressing, ready, degraded, suspended, deleted, unknown). forge's deploystate had converged / converging / diverged / degraded / unknown. The tiers had a phase set per kind. The semantic mismatch that matters is "converged": control-plane called a deployment ready the instant a replica was ready, while forge's definition requires the state to HOLD for a stability window. A pod that is ready, crashes, and comes back ready every 90 seconds is ready at every instant anyone samples it, and converged at none of them. forge's definition wins because it is the one that answers the question a promotion gate asks.

const (
	ObservedPending     ObservedState = "pending"
	ObservedProgressing ObservedState = "progressing"
	ObservedReady       ObservedState = "ready"
	ObservedDegraded    ObservedState = "degraded"
	ObservedSuspended   ObservedState = "suspended"
	ObservedDeleted     ObservedState = "deleted"
	ObservedUnknown     ObservedState = "unknown"
)

The values are the control plane's observed_state column, spelled identically so the ledger can store them unconverted.

func ObservedStateOf

func ObservedStateOf(p v1alpha1.Phase) ObservedState

ObservedStateOf maps a tier Phase onto the ledger vocabulary.

Phase        observed_state   why
Pending      pending          declared, nothing applied
Progressing  progressing      applied, converging
Ready        ready            serving what was declared
Degraded     degraded         exists but not serving
Failed       degraded         the platform refused; nothing serves.
                              The ledger has no "failed", and degraded
                              is the one that pages.
Locked       deleted          the resource is gone; the retained data is
                              not addressable through it
""/other     unknown          unset is visible as a value, never as ready

type ResourcesRefusal added in v1.0.1

type ResourcesRefusal struct {
	Plan   HostedPlan
	Reason string
	// Want is the conformant block: the nearest shape the plan admits.
	Want v1alpha1.Resources
}

ResourcesRefusal is a resources block a plan does not admit, with the conformant block to declare instead. Its message is self-contained; Fix is the KCL literal a caller that knows WHERE the block is declared prints beside it.

func (*ResourcesRefusal) Error added in v1.0.1

func (e *ResourcesRefusal) Error() string

func (*ResourcesRefusal) Fix added in v1.0.1

func (e *ResourcesRefusal) Fix() string

Fix is Want as the KCL a binder or workload declares:

resources = fw.Resources {cpuRequestMillicores = 250, ...}

type WorkloadURLResolver

type WorkloadURLResolver func(name string) (string, error)

WorkloadURLResolver answers "what is the public URL of the workload named name, in this environment?". It returns an error when the name is unknown or the workload has no URL; a resolver never guesses.

Directories

Path Synopsis
Package eso holds what forge pins about the External Secrets Operator component (forge.ExternalSecretsOperator): the chart and image it installs, the keyless-signing identity that image must carry, and the CRDs forge supplies itself.
Package eso holds what forge pins about the External Secrets Operator component (forge.ExternalSecretsOperator): the chart and image it installs, the keyless-signing identity that image must carry, and the CRDs forge supplies itself.
Package openbao renders forge.OpenBao, the secret store an env runs itself.
Package openbao renders forge.OpenBao, the secret store an env runs itself.
Package v1alpha1 is the ONE declaration of forge's deploy kinds — Workload, StaticSite and ManagedDatabase — in API group forge.dev.
Package v1alpha1 is the ONE declaration of forge's deploy kinds — Workload, StaticSite and ManagedDatabase — in API group forge.dev.

Jump to

Keyboard shortcuts

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