Documentation
¶
Index ¶
- func CompareVersions(a, b string) int
- func HashSelection(sel Selection, baseName string) (string, error)
- func IsValidHash(s string) bool
- func RenderDockerfile(rv ResolvedValues, base Base) (string, error)
- func SeedCatalog(ctx context.Context, store SeedCatalogStore) error
- func ValidateResolved(rv ResolvedValues) error
- func ValidateSelection(sel Selection) error
- type Base
- type BaseUpdateKind
- type BaseUpdates
- type Build
- type BuildStatus
- type Config
- type ConfigScope
- type ConfigStatus
- type Extension
- type ExtensionType
- type FileSpec
- type HTTPManifestResolver
- type HashResolution
- type KnownFailure
- type ManifestResolver
- type PlatformConfig
- type ResolvedValue
- type ResolvedValues
- type SeedCatalogData
- type SeedCatalogStore
- type SeedExtensionEntry
- type Selection
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func CompareVersions ¶ added in v0.25.0
CompareVersions orders dot-separated numeric version strings ("0.10.0" > "0.9.0"). Non-numeric segments compare lexically; a longer prefix of equal segments is greater. Tolerates arbitrary segment counts.
func HashSelection ¶
HashSelection computes the content-addressed schematic hash over the sorted-deduped selection IDs + base name. Pure, deterministic, no I/O. Returns "s-" + first 16 hex chars of SHA-256 (design/0046 #1, #2).
Version is deliberately NOT in the preimage — version is a separate axis (design/0046 #2): the same schematic hash identifies a family of images, one per base version that has been built.
func IsValidHash ¶ added in v0.25.0
IsValidHash reports whether s is a well-formed schematic hash.
func RenderDockerfile ¶
func RenderDockerfile(rv ResolvedValues, base Base) (string, error)
RenderDockerfile renders the deterministic Dockerfile for a workspace image from frozen resolved values + a base. Pure function of (ResolvedValues, Base): identical inputs always render an identical Dockerfile byte-for-byte, so a content-addressed tag maps to exactly one reproducible build (design/0046 build-execution section).
The Dockerfile only ever layers onto the operator-approved base (base.Ref()). It cannot reference an arbitrary image, and every layer is derived from operator-authored catalog values (no user free text) — this is the structural guard against the injection surface.
Design 0053 §4.4 (S4): no ENTRYPOINT is stamped — the entrypoint no longer exists and the pod spec's overlay supervisor command owns the process. The MinBaseVersion floor is deleted with it: nothing platform-side remains baked, so there is no baked-agentd contract to be stale (the incident class the floor guarded against — #871 — is structurally impossible for overlay pods, which is the only mode).
func SeedCatalog ¶ added in v0.8.0
func SeedCatalog(ctx context.Context, store SeedCatalogStore) error
SeedCatalog idempotently upserts the seed data into the store. For each extension: if it already exists in the DB, it is NOT overwritten (runtime admin changes — retire, review flag — take precedence). Only NEW extensions are inserted. A transient DB error on GetExtension propagates (does NOT fall through to insert). Bases are always upserted (they're version-pinned and immutable per version).
Note: if a seed extension's value changes (e.g. playwright-deps package list), the existing row is NOT updated — the operator must retire the old extension and let the new one be inserted on next boot. This is intentional: extensions are immutable per design/0046 #7.
func ValidateResolved ¶
func ValidateResolved(rv ResolvedValues) error
ValidateResolved checks the resolved shape: if non-empty, every entry has a known type, a non-empty value, and (for file) an absolute non-traversal path + valid octal mode. Empty is valid — an extension-less config is a named alias for the base image (design permits it; the picker shows it). Used as a defensive check on data crossing the DB boundary.
func ValidateSelection ¶
ValidateSelection checks that the selection is non-empty and every ID matches the extension ID charset. It does NOT check existence in the catalog — that is the store's concern (ResolveSelection does it).
Types ¶
type Base ¶
type Base struct {
Name string `json:"name" yaml:"name"`
Version string `json:"version" yaml:"version"`
Image string `json:"image" yaml:"image"`
Tag string `json:"tag,omitempty" yaml:"tag,omitempty"`
Digest string `json:"digest,omitempty" yaml:"digest,omitempty"`
IsDefault bool `json:"isDefault" yaml:"isDefault"`
}
Base is a (name, version) row of an operator-approved base image. Composite-keyed: old versions persist (design/0046 #8).
type BaseUpdateKind ¶ added in v0.15.12
type BaseUpdateKind string
BaseUpdateKind discriminates BaseUpdates.
const ( BaseUpdateVersionBump BaseUpdateKind = "version_bump" BaseUpdateBaseMigration BaseUpdateKind = "base_migration" )
type BaseUpdates ¶ added in v0.15.12
type BaseUpdates struct {
Kind BaseUpdateKind `json:"kind"`
CurrentBaseName string `json:"currentBaseName"`
CurrentBaseVersion string `json:"currentBaseVersion"`
// LatestBaseVersion is the newest published version of the SAME
// base name (set for both kinds when one exists).
LatestBaseVersion string `json:"latestBaseVersion,omitempty"`
// DefaultBaseName/Version are set for base_migration only.
DefaultBaseName string `json:"defaultBaseName,omitempty"`
DefaultBaseVersion string `json:"defaultBaseVersion,omitempty"`
}
BaseUpdates is the update pill payload on a config (omitempty on the wire: absent = fresh).
func ComputeBaseUpdates ¶ added in v0.15.12
func ComputeBaseUpdates(c Config, bases []Base) *BaseUpdates
ComputeBaseUpdates returns the available base movement for c given the full catalog base list, or nil when the config is fresh / its base is retired.
type Build ¶
type Build struct {
ID string `json:"id"`
ConfigID string `json:"configId"`
Hash string `json:"hash"`
BaseName string `json:"baseName"`
BaseVersion string `json:"baseVersion"`
ResolvedValues ResolvedValues `json:"resolvedValues"`
Architectures []string `json:"architectures"`
ImageRef string `json:"imageRef,omitempty"`
Digest string `json:"digest,omitempty"`
Status BuildStatus `json:"status"`
GHRunID *int64 `json:"ghRunId,omitempty"`
FailureReason string `json:"-"` // admin-only
Explanation string `json:"explanation,omitempty"`
TriggeredBy *string `json:"triggeredBy,omitempty"`
StartedAt time.Time `json:"startedAt"`
FinishedAt *time.Time `json:"finishedAt,omitempty"`
CallbackToken string `json:"-"` // per-build secret; ConstantTimeCompare on callback
// Billing attribution (design/0047 Q1). Mirrors the config's scope/org_id
// so billing queries can attribute build cost without a join. NULL for
// coalesced builds (build initiated by a different scope).
Scope ConfigScope `json:"scope,omitempty"`
OrgID *string `json:"orgId,omitempty"`
}
Build is one row of image_factory_builds. One row per API dispatch — transient retry happens inside the GH Actions workflow (design/0046 #12), so the API sees exactly one dispatch + one final result.
type BuildStatus ¶
type BuildStatus string
BuildStatus enumerates the build lifecycle.
const ( BuildDispatched BuildStatus = "dispatched" BuildSucceeded BuildStatus = "succeeded" BuildFailed BuildStatus = "failed" )
type Config ¶
type Config struct {
ID string `json:"id"`
Hash string `json:"hash"`
Name string `json:"name"`
Selection Selection `json:"selection"`
ResolvedValues ResolvedValues `json:"resolvedValues"`
BaseName string `json:"baseName"`
BaseVersion string `json:"baseVersion"`
Scope ConfigScope `json:"scope"`
OwnerID *string `json:"ownerId,omitempty"`
OrgID *string `json:"orgId,omitempty"`
Status ConfigStatus `json:"status"`
// UpdatesAvailable is computed on read (never stored): the
// base-update pill payload (#928). nil/absent = fresh.
UpdatesAvailable *BaseUpdates `json:"updatesAvailable,omitempty"`
}
Config is a saved user/org/platform config.
type ConfigScope ¶
type ConfigScope string
ConfigScope enumerates the three friendly-name scopes (design/0046 #25).
const ( ScopeMember ConfigScope = "member" ScopeOrg ConfigScope = "org" ScopePlatform ConfigScope = "platform" )
type ConfigStatus ¶
type ConfigStatus string
ConfigStatus is the config lifecycle pill (design/0046 #20).
const ( StatusBuilding ConfigStatus = "building" StatusReady ConfigStatus = "ready" StatusRejected ConfigStatus = "rejected" )
type Extension ¶
type Extension struct {
ID string `json:"id"`
Type ExtensionType `json:"type"`
Value string `json:"value"`
FileSpec *FileSpec `json:"fileSpec,omitempty"`
SupportedBases []string `json:"supportedBases"`
Retired bool `json:"retired"`
ReviewRequested bool `json:"reviewRequested"`
Description string `json:"description,omitempty"`
}
Extension is a catalog row. Immutable-once-published (design/0046 #7): the build-relevant fields (Type, Value, FileSpec, SupportedBases) do not change after creation. Only Retired, ReviewRequested, Description mutate.
type ExtensionType ¶
type ExtensionType string
ExtensionType enumerates the catalog extension kinds. "run" and "env" are deliberately absent — they were the injection vectors (design/0046 #5).
const ( ExtensionTypeApt ExtensionType = "apt" ExtensionTypeMise ExtensionType = "mise" ExtensionTypeFile ExtensionType = "file" )
type FileSpec ¶
type FileSpec struct {
Path string `json:"path"`
Mode string `json:"mode,omitempty"` // octal string "0755"; empty → "0644"
}
FileSpec is the target for a type=file extension. Path must be absolute (no traversal); Mode defaults to "0644" when empty.
type HTTPManifestResolver ¶ added in v0.25.0
type HTTPManifestResolver struct {
// Base is the registry API origin, e.g. "https://ghcr.io". Defaults
// to https://ghcr.io when empty.
Base string
// Client is the HTTP client; a default with a 15s timeout is used
// when nil.
Client *http.Client
}
HTTPManifestResolver resolves digests from an OCI Distribution registry (ghcr shape: anonymous pull token + HEAD manifest). The token endpoint and www-authenticate flow match ghcr.io and standard registries.
func NewHTTPManifestResolver ¶ added in v0.25.0
func NewHTTPManifestResolver() *HTTPManifestResolver
NewHTTPManifestResolver constructs a resolver with sane defaults.
func (*HTTPManifestResolver) ResolveDigest ¶ added in v0.25.0
func (r *HTTPManifestResolver) ResolveDigest(ctx context.Context, image, tag string) (string, error)
ResolveDigest fetches an anonymous pull token, then HEADs the manifest (accepting OCI index / Docker manifest-list media types) and returns the docker-content-digest header value.
image may be a full reference whose first path segment is a registry host (e.g. "ghcr.io/acme/base") — the host is derived from the ref and wins over the resolver's default Base. A bare repo ("acme/base") uses Base (ghcr.io by default).
type HashResolution ¶ added in v0.25.0
type HashResolution struct {
Hash string `json:"hash"`
Selection Selection `json:"selection"`
BaseName string `json:"baseName"`
// Versions are the distinct base versions with a succeeded or
// in-flight build under this hash, newest first.
Versions []string `json:"versions"`
}
HashResolution is the public content-address lookup for a schematic hash: the selection it names and every base version built under it. A hash is a content address over public catalog extension IDs — it is not scope-sensitive (builds coalesce scope-agnostically; images are platform-wide artifacts), so any authenticated user may resolve one to re-select the same options.
type KnownFailure ¶
type KnownFailure struct {
SelectionHash string `json:"selectionHash"`
Selection Selection `json:"selection"`
BaseName string `json:"baseName"`
Explanation string `json:"explanation,omitempty"`
FailureReason string `json:"-"` // admin-only; never serialized to non-admins
DetectedAt time.Time `json:"detectedAt"`
Retriable bool `json:"retriable"`
}
KnownFailure is a row of image_factory_known_failures — the blocklist.
type ManifestResolver ¶ added in v0.25.0
type ManifestResolver interface {
ResolveDigest(ctx context.Context, repo, tag string) (string, error)
}
ManifestResolver resolves a container image tag to its manifest digest. It doubles as the existence gate for base-sync: a tag that cannot be resolved must never enter the catalog (GH Actions could not pull it).
type PlatformConfig ¶
type PlatformConfig struct {
Architectures []string `json:"architectures"`
}
PlatformConfig is the single-row platform-level factory config.
type ResolvedValue ¶
type ResolvedValue struct {
Type ExtensionType `json:"type"`
Value string `json:"value"`
FileSpec *FileSpec `json:"fileSpec,omitempty"`
}
ResolvedValue is one entry in the resolved_values JSONB. It is the cached projection of an Extension's build fields, frozen at config-save time. Shape pinned per design/0047.
type ResolvedValues ¶
type ResolvedValues map[string]ResolvedValue
ResolvedValues maps extension ID → frozen resolved value. This is the exact JSONB shape stored on image_factory_configs.resolved_values and image_factory_builds.resolved_values.
func ResolveSelection ¶
func ResolveSelection(sel Selection, exts map[string]Extension, baseName string) (ResolvedValues, error)
ResolveSelection joins a Selection against a set of catalog Extensions and returns the frozen ResolvedValues projection stored on the config (design/0046 #10). Errors if any ID is missing, retired, or unsupported on baseName. Pure: no I/O.
func (ResolvedValues) Selection ¶
func (rv ResolvedValues) Selection() Selection
Selection recovers the extension IDs from the resolved values map, sorted. Used by the callback handler to reconstruct the selection for the known_failures row (the build stores resolved_values, not the selection).
type SeedCatalogData ¶ added in v0.8.0
type SeedCatalogData struct {
Architectures []string `json:"architectures" yaml:"architectures"`
Bases []Base `json:"bases" yaml:"bases"`
Extensions []SeedExtensionEntry `json:"extensions" yaml:"extensions"`
}
SeedCatalogData is the parsed structure of catalog.seed.yaml.
func LoadSeed ¶ added in v0.8.0
func LoadSeed() (SeedCatalogData, error)
LoadSeed parses catalog.seed.yaml (embedded via go:embed). If an env var CATALOG_SEED_PATH is set, loads from that file instead (operator override). Returns the parsed seed data.
type SeedCatalogStore ¶ added in v0.8.0
type SeedCatalogStore interface {
SetPlatformConfig(ctx context.Context, pc PlatformConfig) error
// SeedUpsertBase is the boot-seed upsert: unlike the admin-path
// UpsertBase it never clears other defaults and never overwrites an
// existing row's is_default (#936). (Admin UpsertBase is not on this
// interface — SeedCatalog never calls it.)
SeedUpsertBase(ctx context.Context, b Base) error
GetExtension(ctx context.Context, id string) (Extension, error)
PublishExtension(ctx context.Context, e Extension) error
}
SeedCatalogStore is the subset of the DB store needed to seed the catalog.
type SeedExtensionEntry ¶ added in v0.8.0
type SeedExtensionEntry struct {
ID string `json:"id" yaml:"id"`
Type ExtensionType `json:"type" yaml:"type"`
Value string `json:"value" yaml:"value"`
FileSpec *FileSpec `json:"fileSpec,omitempty" yaml:"fileSpec,omitempty"`
SupportedBases []string `json:"supportedBases" yaml:"supportedBases"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
}
SeedExtensionEntry is one extension in the seed file. The YAML uses camelCase for fileSpec (matching the JSON tag on FileSpec).
func (SeedExtensionEntry) ToExtension ¶ added in v0.8.0
func (s SeedExtensionEntry) ToExtension() Extension
ToExtension converts a seed entry to a domain Extension.