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
- Variables
- func BandMemoryBytes(cpuMillicores int64) int64
- func CheckHostedResourcesWithin(plan HostedPlan, claimable ClaimableLimits, r v1alpha1.Resources) error
- func CheckShapeBand(r v1alpha1.Resources) error
- func Converged(p v1alpha1.Phase, readySince, now time.Time, window time.Duration) bool
- func DatabaseCredentialSecretName(databaseName string) string
- func DatabaseIdentifiers(name string) (database, role string)
- func KCLResources(r v1alpha1.Resources) string
- func LowerContainer(c v1alpha1.Container) corev1.Container
- func PVCName(workloadName string) string
- func RatioFloor(cpuMillicores, memoryBytes int64) (int64, int64)
- func ReadOnlyRoleSecret(name, password string, ctx Context) (*unstructured.Unstructured, error)
- func Render(obj runtime.Object, ctx Context) ([]*unstructured.Unstructured, error)
- func RenderManagedDatabase(name string, spec v1alpha1.ManagedDatabaseSpec, ctx Context) ([]*unstructured.Unstructured, error)
- func RenderObservabilityAgent(s ObservabilityAgentSpec, ctx ObservabilityContext) ([]*unstructured.Unstructured, error)
- func RenderWorkloads(ws []v1alpha1.Workload, p v1alpha1.Profile, ctx Context) ([]*unstructured.Unstructured, error)
- func ResolveEnvWorkloadURLs(env []v1alpha1.EnvVar, resolve WorkloadURLResolver) ([]v1alpha1.EnvVar, error)
- func ResolveRuntimeConfig(rc map[string]v1alpha1.RuntimeConfigValue, resolve WorkloadURLResolver) (map[string]string, error)
- func RunsBeforeRollout(s v1alpha1.WorkloadSpec) bool
- func RuntimeConfigJS(values map[string]string) (string, error)
- type ClaimableLimits
- type Context
- type DatabaseBackup
- type DatabasePlacement
- type EnvNetworkPolicy
- type HostedPlan
- type ObservabilityAgentSpec
- type ObservabilityContext
- type ObservabilitySecretKey
- type ObservabilitySignals
- type ObservedState
- type ResourcesRefusal
- type WorkloadURLResolver
Constants ¶
const ( ObservabilityAgentName = "otel-agent" // ForwarderHeadersEnv is the same variable for the local forwarder. ForwarderHeadersEnv = agentHeadersEnv )
Names of the objects the node agent renders.
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.
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).
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.
const AnnotationDeployPhase = "forge.dev/deploy-phase"
AnnotationDeployPhase places a standalone Job in the deploy (kcl/workloads/expand.k:714-718). internal/cluster reads it.
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.
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.
const DefaultStabilityWindow = 2 * time.Minute
DefaultStabilityWindow is how long a tier must have been continuously ready before it counts as converged.
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.
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.
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.
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.
const RuntimeConfigFile = "config.js"
RuntimeConfigFile is the document's name. It is served at <basePath>/RuntimeConfigFile, beside the site's index.html.
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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
KCLResources renders r as a `resources = fw.Resources {...}` KCL field, memory in forge.mib, omitting unset limits.
func LowerContainer ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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.
Source Files
¶
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. |