From 1c9302126e0474072d1f6b723b67030a9152dc51 Mon Sep 17 00:00:00 2001 From: Joshua Gilman Date: Sat, 2 May 2026 19:57:40 -0700 Subject: [PATCH] feat(schemas): add Talos image build schema --- .release-please-manifest.json | 1 + schemas/lab/README.md | 5 ++ schemas/lab/moon.yml | 3 +- schemas/lab/talos/README.md | 33 ++++++++ schemas/lab/talos/cue_types_gen.go | 120 ++++++++++++++++++++++++++++ schemas/lab/talos/doc.go | 2 + schemas/lab/talos/schema.cue | 122 +++++++++++++++++++++++++++++ schemas/lab/talos/schema_source.go | 11 +++ tools/labctl/moon.yml | 8 ++ 9 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 schemas/lab/talos/README.md create mode 100644 schemas/lab/talos/cue_types_gen.go create mode 100644 schemas/lab/talos/doc.go create mode 100644 schemas/lab/talos/schema.cue create mode 100644 schemas/lab/talos/schema_source.go diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 5e03dbc..3e5c395 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -4,5 +4,6 @@ "bootstrap/kro": "1.1.0", "services/dns-mirror": "0.3.1", "services/github-token-broker": "0.2.1", + "schemas/lab": "0.1.0", "tools/labctl": "0.2.0" } diff --git a/schemas/lab/README.md b/schemas/lab/README.md index f4ac070..1f54b6f 100644 --- a/schemas/lab/README.md +++ b/schemas/lab/README.md @@ -5,6 +5,11 @@ This module contains shared CUE schemas for GilmanLab lab tooling. The CUE definitions are the source of truth. Generated Go types are committed for Go consumers that need typed access to the same contracts. +Packages: + +- `incusos` — IncusOS image build and seed configuration. +- `talos` — Talos Image Factory download and NoCloud config artifact builds. + Validate and regenerate from this directory: ```sh diff --git a/schemas/lab/moon.yml b/schemas/lab/moon.yml index 24d38b8..52a471e 100644 --- a/schemas/lab/moon.yml +++ b/schemas/lab/moon.yml @@ -60,6 +60,8 @@ tasks: test: command: 'go test ./...' toolchains: 'system' + deps: + - 'schemas:generate-check' inputs: - '**/*.go' - 'go.mod' @@ -71,7 +73,6 @@ tasks: - 'schemas:format' - 'schemas:tidy' - 'schemas:vet' - - 'schemas:generate-check' - 'schemas:test' options: cache: false diff --git a/schemas/lab/talos/README.md b/schemas/lab/talos/README.md new file mode 100644 index 0000000..bc9eaac --- /dev/null +++ b/schemas/lab/talos/README.md @@ -0,0 +1,33 @@ +# Talos Schema + +This package defines the CUE schema for Talos image build configuration. It is +used by platform tooling, including `labctl`, to validate the Image Factory +source, Talos machine configuration delivery, and generated local artifacts for +the temporary bootstrap cluster. + +The CUE schema is the source of truth; Go types are generated from `schema.cue`. + +## Example + +```cue +package images + +import talos "github.com/gilmanlab/platform/schemas/lab/talos" + +build: talos.#ImageBuild & { + name: "bootstrap-talos-controlplane" + source: { + version: "v1.13.0" + } + config: { + userData: path: "controlplane.yaml" + metaData: localHostname: "bootstrap-controlplane-1" + } + output: { + dir: ".state/images/talos-bootstrap" + format: "img" + bootArtifactName: "talos-bootstrap-amd64.img" + configArtifactName: "talos-bootstrap-cidata.img" + } +} +``` diff --git a/schemas/lab/talos/cue_types_gen.go b/schemas/lab/talos/cue_types_gen.go new file mode 100644 index 0000000..559679e --- /dev/null +++ b/schemas/lab/talos/cue_types_gen.go @@ -0,0 +1,120 @@ +// Code generated by "cue exp gengotypes"; DO NOT EDIT. + +package talos + +// NonEmptyString is a required string value that must not be empty. +type NonEmptyString string + +// HTTPURL is an HTTP or HTTPS URL. +type HTTPURL string + +// RelativePath is a repository- or config-relative path. +type RelativePath string + +// ArtifactName is a local artifact filename, not a path. +type ArtifactName string + +// SchematicID is an Image Factory schematic ID. +type SchematicID string + +// TalosVersion selects an exact Talos Linux release. +type TalosVersion string + +// Architecture selects the Talos image architecture. +type Architecture string + +// Platform selects the Talos image platform. +type Platform string + +// SourceArtifact selects the Image Factory artifact to download. +type SourceArtifact string + +// OutputFormat selects the local artifact format produced by the build. +type OutputFormat string + +// ConfigDelivery selects how the Talos machine configuration is delivered. +type ConfigDelivery string + +// DNSLabel is a single RFC 1123-style hostname label. +type DNSLabel string + +// FileInput points at a local file consumed by the build. +type FileInput struct { + // Path is a relative path from the build config directory. + Path RelativePath `json:"path"` +} + +// ImageSource describes the Talos Image Factory asset to download. +type ImageSource struct { + // FactoryURL is the Image Factory base URL. + FactoryURL string `json:"factoryURL"` + + // Version is the exact Talos Linux release to download. + Version TalosVersion `json:"version"` + + // SchematicID identifies the Image Factory schematic. + SchematicID string `json:"schematicID"` + + // Platform selects the Talos platform image. + Platform Platform `json:"platform"` + + // Arch selects the Talos image architecture. + Arch Architecture `json:"arch"` + + // Artifact selects the compressed Image Factory disk artifact. + Artifact SourceArtifact `json:"artifact"` +} + +// NoCloudMetaData describes the meta-data file in the NoCloud cidata image. +type NoCloudMetaData struct { + // LocalHostname is the VM hostname advertised through NoCloud. + LocalHostname DNSLabel `json:"localHostname"` + + // InstanceID identifies this NoCloud instance. It defaults to localHostname. + InstanceID string `json:"instanceID"` +} + +// MachineConfig describes Talos machine configuration delivery. +type MachineConfig struct { + // Delivery controls how config files are packaged for Talos. + Delivery ConfigDelivery `json:"delivery"` + + // UserData is the Talos machine config written as NoCloud user-data. + UserData FileInput `json:"userData"` + + // MetaData is the NoCloud meta-data payload. + MetaData NoCloudMetaData `json:"metaData"` + + // NetworkConfig optionally points at a NoCloud network-config file. + NetworkConfig FileInput `json:"networkConfig,omitempty"` +} + +// ImageOutput describes the local artifacts produced by the build. +type ImageOutput struct { + // Dir is the output directory for generated artifacts. + Dir NonEmptyString `json:"dir"` + + // Format is the local artifact format. + Format OutputFormat `json:"format"` + + // BootArtifactName is the Talos boot disk IMG filename. + BootArtifactName ArtifactName `json:"bootArtifactName"` + + // ConfigArtifactName is the NoCloud cidata IMG filename. + ConfigArtifactName ArtifactName `json:"configArtifactName"` +} + +// ImageBuild is the top-level Talos image download and config packaging contract. +type ImageBuild struct { + // Name is the stable build name used in logs and generated metadata. + Name NonEmptyString `json:"name"` + + // Source describes the upstream Talos image to download. + Source ImageSource `json:"source"` + + // Config describes Talos machine configuration delivery. + Config MachineConfig `json:"config"` + + // Output describes the local artifacts produced by the build. + Output ImageOutput `json:"output"` +} diff --git a/schemas/lab/talos/doc.go b/schemas/lab/talos/doc.go new file mode 100644 index 0000000..92da5e8 --- /dev/null +++ b/schemas/lab/talos/doc.go @@ -0,0 +1,2 @@ +// Package talos contains generated Go types for the Talos image build schema. +package talos diff --git a/schemas/lab/talos/schema.cue b/schemas/lab/talos/schema.cue new file mode 100644 index 0000000..6429665 --- /dev/null +++ b/schemas/lab/talos/schema.cue @@ -0,0 +1,122 @@ +package talos + +@go(talos) + +// NonEmptyString is a required string value that must not be empty. +#NonEmptyString: (string & !="") | error("must be a non-empty string") + +// HTTPURL is an HTTP or HTTPS URL. +#HTTPURL: (#NonEmptyString & =~"^https?://") | error("must be an HTTP or HTTPS URL") + +// RelativePath is a repository- or config-relative path. +#RelativePath: (#NonEmptyString & !~"^/" & !~"(^|/)\\.\\.(/|$)" & !~"\\\\") | + error("path must be relative and use forward slashes") + +// ArtifactName is a local artifact filename, not a path. +#ArtifactName: (#NonEmptyString & !~"/" & !~"\\\\" & =~"\\.img$") | + error("artifact name must be an .img filename") + +// SchematicID is an Image Factory schematic ID. +#SchematicID: =~"^[a-f0-9]{64}$" | error("schematic ID must be a 64-character lowercase hex string") + +// TalosVersion selects an exact Talos Linux release. +#TalosVersion: =~"^v[0-9]+\\.[0-9]+\\.[0-9]+(-[0-9A-Za-z.-]+)?(\\+[0-9A-Za-z.-]+)?$" | + error("Talos version must be an exact release like v1.13.0") + +// Architecture selects the Talos image architecture. +#Architecture: *"amd64" | "amd64" | "arm64" | error("architecture must be amd64 or arm64") + +// Platform selects the Talos image platform. +#Platform: *"nocloud" | "nocloud" | error("platform must be nocloud") + +// SourceArtifact selects the Image Factory artifact to download. +#SourceArtifact: *"raw.xz" | "raw.xz" | error("source artifact must be raw.xz") + +// OutputFormat selects the local artifact format produced by the build. +#OutputFormat: *"img" | "img" | error("format must be img") + +// ConfigDelivery selects how the Talos machine configuration is delivered. +#ConfigDelivery: *"nocloud-cidata" | "nocloud-cidata" | + error("config delivery must be nocloud-cidata") + +// DNSLabel is a single RFC 1123-style hostname label. +#DNSLabel: (#NonEmptyString & =~"^[a-z0-9]([-a-z0-9]*[a-z0-9])?$") | + error("hostname must be a lowercase DNS label") + +// FileInput points at a local file consumed by the build. +#FileInput: { + @go(FileInput) + + // Path is a relative path from the build config directory. + path!: #RelativePath +} + +// ImageSource describes the Talos Image Factory asset to download. +#ImageSource: { + @go(ImageSource) + + // FactoryURL is the Image Factory base URL. + factoryURL: *"https://factory.talos.dev" | #HTTPURL @go(FactoryURL) + // Version is the exact Talos Linux release to download. + version!: #TalosVersion + // SchematicID identifies the Image Factory schematic. + schematicID: *"376567988ad370138ad0c15e58e1dcefd698631a5d546e5f7c7e1a6d167663f3" | #SchematicID @go(SchematicID) + // Platform selects the Talos platform image. + platform: #Platform + // Arch selects the Talos image architecture. + arch: #Architecture + // Artifact selects the compressed Image Factory disk artifact. + artifact: #SourceArtifact +} + +// NoCloudMetaData describes the meta-data file in the NoCloud cidata image. +#NoCloudMetaData: { + @go(NoCloudMetaData) + + // LocalHostname is the VM hostname advertised through NoCloud. + localHostname!: #DNSLabel @go(LocalHostname) + // InstanceID identifies this NoCloud instance. It defaults to localHostname. + instanceID: *localHostname | #NonEmptyString @go(InstanceID) +} + +// MachineConfig describes Talos machine configuration delivery. +#MachineConfig: { + @go(MachineConfig) + + // Delivery controls how config files are packaged for Talos. + delivery: #ConfigDelivery + // UserData is the Talos machine config written as NoCloud user-data. + userData!: #FileInput @go(UserData) + // MetaData is the NoCloud meta-data payload. + metaData!: #NoCloudMetaData @go(MetaData) + // NetworkConfig optionally points at a NoCloud network-config file. + networkConfig?: #FileInput @go(NetworkConfig) +} + +// ImageOutput describes the local artifacts produced by the build. +#ImageOutput: { + @go(ImageOutput) + + // Dir is the output directory for generated artifacts. + dir!: #NonEmptyString + // Format is the local artifact format. + format: #OutputFormat + // BootArtifactName is the Talos boot disk IMG filename. + bootArtifactName!: #ArtifactName @go(BootArtifactName) + // ConfigArtifactName is the NoCloud cidata IMG filename. + configArtifactName!: #ArtifactName @go(ConfigArtifactName) +} + +// ImageBuild is the top-level Talos image download and config packaging contract. +#ImageBuild: { + @go(ImageBuild) + + // Name is the stable build name used in logs and generated metadata. + name!: #NonEmptyString + // Source describes the upstream Talos image to download. + source!: #ImageSource + // Config describes Talos machine configuration delivery. + config!: #MachineConfig + // Output describes the local artifacts produced by the build. + output!: #ImageOutput +} diff --git a/schemas/lab/talos/schema_source.go b/schemas/lab/talos/schema_source.go new file mode 100644 index 0000000..026a01c --- /dev/null +++ b/schemas/lab/talos/schema_source.go @@ -0,0 +1,11 @@ +package talos + +import _ "embed" + +//go:embed schema.cue +var schemaSource string + +// SchemaSource returns the CUE source for the Talos schema package. +func SchemaSource() string { + return schemaSource +} diff --git a/tools/labctl/moon.yml b/tools/labctl/moon.yml index f69ac8b..353e6db 100644 --- a/tools/labctl/moon.yml +++ b/tools/labctl/moon.yml @@ -14,6 +14,8 @@ tasks: lint: command: 'bash -lc "if find . -name ''*.go'' -not -path ''./vendor/*'' -print -quit | grep -q .; then go tool golangci-lint run --config .golangci.yml ./...; else echo ''no Go files to lint''; fi"' toolchains: 'system' + deps: + - 'schemas:generate-check' inputs: - '/go.work' - '/go.work.sum' @@ -29,6 +31,8 @@ tasks: format: command: 'bash -lc "if find . -name ''*.go'' -not -path ''./vendor/*'' -print -quit | grep -q .; then go tool golangci-lint fmt --config .golangci.yml --diff; else echo ''no Go files to format''; fi"' toolchains: 'system' + deps: + - 'schemas:generate-check' inputs: - '/go.work' - '/go.work.sum' @@ -44,6 +48,8 @@ tasks: test: command: 'bash -lc "if find . -name ''*.go'' -not -path ''./vendor/*'' -print -quit | grep -q .; then go test ./...; else echo ''no Go packages to test''; fi"' toolchains: 'system' + deps: + - 'schemas:generate-check' inputs: - '/go.work' - '/go.work.sum' @@ -83,6 +89,8 @@ tasks: printf "version output: %s\n" "${output}" [[ "${output}" == "labctl dev" ]] toolchains: 'system' + deps: + - 'schemas:generate-check' inputs: - '/go.work' - '/go.work.sum'