repo

package
v0.4.41 Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package repo implements the decentralized, Helm-style module distribution client described in docs/design/module-distribution-v2.md: repos are static index files, the source of truth is the module repo + its signed image, and the platform is demoted to an optional aggregator.

This package is the client foundation — repo config, index fetch/cache, and resolution. It does NOT yet replace internal/catalog or the install path; wiring install/up onto it is a later phase (kept separate so the working install keeps working).

Index

Constants

View Source
const (
	// DefaultRepoName is the baked-in repo added on first run. Removable.
	DefaultRepoName = "tinysystems"
	// DefaultRepoURL is the baked-in default index — the tiny-systems/modules
	// repo, served raw off main. (A prettier GitHub Pages URL can replace this
	// once Pages is enabled; the raw URL is a plain-GET static file either way.)
	DefaultRepoURL = "https://raw.githubusercontent.com/tiny-systems/modules/main/index.yaml"
)
View Source
const APIVersion = "tiny/v2"

APIVersion is the index schema version this client understands.

View Source
const ManifestFile = "module.yaml"

ManifestFile is the filename a module repo publishes to describe itself for the index generator. It is the transparent, git-visible source the generator aggregates (alongside — eventually — OCI image annotations).

Variables

This section is empty.

Functions

func Major

func Major(version string) (int, error)

Major parses the SemVer major from a version string ("2.3.1" → 2, "v0.5.24" → 0, "1.0.0-rc1" → 1). The major is the coexistence coordinate: two majors of a module run side by side in the same namespace, so it drives the release name and every K8s resource identity. See docs/design §7.

func MarshalIndex

func MarshalIndex(idx *Index, generated string) ([]byte, error)

MarshalIndex renders an index to YAML, stamping apiVersion and (when given) a generated timestamp. The timestamp is passed in rather than read from the clock so callers stay deterministic/testable.

func ReleaseName

func ReleaseName(repo, module string, major int) string

ReleaseName is the coexistence-safe helm release / resource identity: `<repo>-<module>-v<major>`. Two coordinates are derived at install time, so both dimensions can coexist in one namespace (distinct releases, labels, node refs):

  • major — v1 and v2 of the same module (design §7)
  • repo — the same module name published by different people (§7.1). A cluster holds modules from many publishers and nothing stops two of them shipping "http-module"; without this the second install silently replaces the first.

repo is the LOCAL repo name from repos.yaml, not the GitHub org — identity stays under the cluster operator's control and survives an upstream rename. It may contain dashes (tiny-systems does); this string is an opaque identity and is never split back into its parts. Two pairs can therefore generate one name ("a-b"+"c" and "a"+"b-c"), which the installer catches by comparing the authoritative repo/module labels rather than by restricting names.

An empty repo yields the legacy `<module>-v<major>` form, so callers that don't know the publisher keep working.

func RenderValues

func RenderValues(raw []byte, fills map[string]string) (map[string]any, error)

RenderValues fills a module's raw values.yaml holes (`${cluster.<name>}`) from the resolved cluster settings and parses the result. An unresolved hole is an error, not a silent blank — a half-filled values file must never reach helm.

Types

type BaseValues

type BaseValues func(image, release, version, namespace, natsURL string) map[string]any

BaseValues builds the harness (operator-chart) values every module install needs, from the plan's identity fields. provision.BaseValues satisfies it — injected so this package stays free of the helm/chart dependency graph.

type Bundle

type Bundle struct {
	Name         string `json:"name"`
	Chart        string `json:"chart"` // oci://… or repo/chart
	ChartVersion string `json:"chartVersion,omitempty"`
	DefaultOn    bool   `json:"defaultOn,omitempty"`
}

Bundle is an optional third-party Helm release a module offers (§3.5).

type BundlePlan

type BundlePlan struct {
	Name         string
	Chart        string
	ChartVersion string
	ReleaseName  string // <moduleRelease>-<bundle>
}

BundlePlan is one bundle's install — its own helm release next to the module.

type Config

type Config struct {
	Repos []Repo `json:"repos"`
}

Config is the client-side repo list, persisted at ${XDG_CONFIG_HOME:-~/.config}/tiny/repos.yaml.

func LoadConfig

func LoadConfig() (*Config, error)

LoadConfig reads repos.yaml, seeding the default repo when the file is absent. A present-but-empty file is respected (the user removed the default).

func (*Config) Save

func (c *Config) Save() error

Save writes repos.yaml, creating the config dir if needed.

type Helm

type Helm interface {
	// UpgradeInstall installs or upgrades release in namespace from chart at
	// version (a range or exact), with the given merged values.
	UpgradeInstall(ctx context.Context, release, namespace, chart, version string, values map[string]any) error
}

Helm is the subset of helm the installer needs. provision.Client will satisfy it via a thin adapter at cutover time; tests fake it. Keeping the executor behind this interface is what makes install planning testable without a cluster and keeps this package free of the helm dependency graph.

type Index

type Index struct {
	APIVersion string             `json:"apiVersion"`
	Generated  string             `json:"generated,omitempty"`
	Modules    map[string]*Module `json:"modules"`
}

Index is a repo's catalog — a static file served at the repo URL. See docs/design/module-distribution-v2.md §4.1.

func BuildIndex

func BuildIndex(manifests []Manifest) (*Index, error)

BuildIndex assembles a tiny/v2 index from module manifests, sorting each module's versions newest-first (the invariant Resolve.latest relies on). Duplicate module names are an error — a module belongs to exactly one repo.

func GenerateFromDir

func GenerateFromDir(dir string) (*Index, error)

GenerateFromDir walks dir for module.yaml files and builds the index. One manifest per file; nested layout (dir/<module>/module.yaml) is fine.

func ParseIndex

func ParseIndex(data []byte) (*Index, error)

ParseIndex decodes and lightly validates an index document (YAML or JSON — sigs.k8s.io/yaml handles both).

type InstallPlan

type InstallPlan struct {
	ReleaseName  string            // <module>-v<major> — coexistence-safe
	Namespace    string            //
	Version      string            // SemVer (for the operator's --version)
	Image        string            // ghcr ref
	Digest       string            // for cosign/digest verification
	Chart        string            // harness chart name
	ChartVersion string            // compatible range
	Cosign       bool              // verify before install
	Fills        map[string]string // cluster holes the module asked for → values
	MissingFills []string          // holes the module needs but the cluster didn't provide
	Bundles      []BundlePlan      // extra helm releases to install alongside
}

InstallPlan is the concrete description of what installing a resolved module entails. It is produced with no side effects (no helm, no network), so it can be shown and confirmed before anything runs. The actual values-file fetch + hole substitution + helm calls are the installer's job (a later phase); this hands it every coordinate it needs.

func Install

func Install(
	ctx context.Context,
	merged *Merged,
	ref, namespace string,
	clusterSettings map[string]string,
	selectedBundles []string,
	base BaseValues,
	helm Helm,
	installed InstalledModules,
) (*InstallPlan, error)

Install runs the full pipeline for one module reference:

resolve   → find the module version across the merged index
plan      → derive release name (<repo>-<module>-v<major>), image, fills, bundles
reconcile → settle that name against what is already installed: adopt a
            legacy pre-publisher release, refuse another publisher's
values    → harness base (identity/plumbing) ⊕ module overlay (rbac/ingress/…),
            the overlay's ${cluster.*} holes filled and winning on conflicts
apply     → helm-install the module release + each bundle

The module's values.yaml is inlined in the index (self-contained/offline). base + helm + installed are injected so this stays the pure orchestration seam. installed may be nil (fresh-cluster semantics: no adoption, no guard).

type InstalledModules added in v0.4.41

type InstalledModules interface {
	// Lookup returns the module image ref of whatever occupies release in
	// namespace (`ghcr.io/<org>/<module>:<tag>`) — the authoritative answer to
	// "whose module is this?", since it is literally what the pod runs. Returns
	// found=false when no such release exists, and an empty image when the
	// release exists but its image could not be read.
	//
	// Primitives, not a struct, so implementations satisfy this structurally
	// without importing this package — same pattern as BaseValues and Helm.
	Lookup(ctx context.Context, namespace, release string) (image string, found bool, err error)
}

InstalledModules reports what already occupies a release name in the namespace. It exists for the two things a rename cannot do without looking first (design §7.1):

  • ADOPT — a cluster installed before the publisher coordinate holds `http-module-v0`. Installing the newly-computed `tinysystems-http-module-v0` would stand a second copy beside it while every existing flow node stays bound to the old name. Adopting the legacy release keeps those flows working and makes the change a non-event.
  • GUARD — publisher names may contain dashes, so two different pairs can generate one release name ("a-b"+"c" and "a"+"b-c"). Installing over it would silently replace someone else's module.

Optional: nil skips both (fresh-cluster semantics, and what unit tests use).

type Installer

type Installer struct {
	// contains filtered or unexported fields
}

Installer applies an InstallPlan: the module's harness release plus one helm release per selected bundle. It performs no resolution or planning — hand it a plan and the module's rendered values.

func NewInstaller

func NewInstaller(h Helm) *Installer

NewInstaller returns an installer over a Helm backend.

func (*Installer) Apply

func (i *Installer) Apply(ctx context.Context, plan *InstallPlan, values map[string]any) error

Apply installs the module release and its bundles. values is the module's rendered values overlay (see RenderValues). It refuses to run with unfilled cluster holes so a half-configured install can't reach the cluster.

type Manifest

type Manifest struct {
	Name   string `json:"name"`
	Module `json:",inline"`
}

Manifest is one module repo's self-description (its `module.yaml`): the module name plus everything an index entry carries. The embedded Module fields (source, description, category, versions) are inlined into the file.

func ParseManifest

func ParseManifest(data []byte) (*Manifest, error)

ParseManifest decodes a module.yaml.

type Merged

type Merged struct {
	// contains filtered or unexported fields
}

Merged is a read-only view over every configured repo's index, ordered so resolution is deterministic. Repo names are unique (config enforces it).

func NewMerged

func NewMerged(order []string, byRepo map[string]*Index) *Merged

NewMerged builds a merged view. order fixes precedence for ambiguity reporting (not silent shadowing — ambiguity is an error, see Resolve).

func (*Merged) List

func (m *Merged) List() []ModuleSummary

List returns every available module across the merged indexes, sorted by name — the discovery scan layer (get_module_readme pulls the detail).

func (*Merged) Resolve

func (m *Merged) Resolve(ref string) (*Resolved, error)

Resolve finds a module version across the merged indexes. Accepted forms:

module                 module[@version] searched across all repos
repo/module            module in that specific repo
module@1.2.3           a specific version
repo/module@1.2.3

A bare name present in more than one repo is an error (name the repos); a name absent everywhere retries with the legacy `-v0` alias in both directions (http-module ⇄ http-module-v0) to bridge the migration.

type Module

type Module struct {
	Source      string     `json:"source,omitempty"` // github.com/org/repo — also where the README lives
	Description string     `json:"description,omitempty"`
	Category    string     `json:"category,omitempty"`
	Versions    []*Version `json:"versions"`
}

Module is one module's entry: identity, presentation, and its versions. Description is the one-line summary (lists). The full markdown doc is NOT inlined — it's the module repo's own README, fetched on demand from Source (which is the doc's source of truth). See ReadmeURL.

func (*Module) ReadmeURL

func (m *Module) ReadmeURL() string

ReadmeURL derives the raw README URL from a module's Source (github.com/org/repo → raw.githubusercontent.com/org/repo/HEAD/README.md). Returns "" if Source isn't a github.com coordinate. The platform/agent fetches this on demand for detail views — install never needs it, so it stays online- only while install data stays offline-cached.

type ModuleSummary

type ModuleSummary struct {
	Repo          string `json:"repo"`
	Name          string `json:"name"`
	Description   string `json:"description,omitempty"`
	Category      string `json:"category,omitempty"`
	Source        string `json:"source,omitempty"`
	LatestVersion string `json:"latestVersion,omitempty"`
	ReadmeURL     string `json:"readmeURL,omitempty"`
}

ModuleSummary is the limited, cheap-to-scan info an agent uses to shortlist a module before fetching its full README. No versions/values/schemas — just enough to decide "is this worth a closer look?".

type Repo

type Repo struct {
	Name string `json:"name"`
	URL  string `json:"url"`
}

Repo is a configured repo: a unique name and its index URL.

type Resolved

type Resolved struct {
	Repo    string
	Name    string
	Module  *Module
	Version *Version
}

Resolved is a fully-resolved install target.

func (*Resolved) Plan

func (r *Resolved) Plan(namespace string, clusterSettings map[string]string, selected []string) (*InstallPlan, error)

Plan turns a resolved target into an InstallPlan. clusterSettings supplies values for the module's cluster holes (ingressClass, storageClass, …); selected chooses bundles: nil → module defaults, ["none"] → zero, else exactly those named. It executes nothing.

func (*Resolved) ReleaseName

func (r *Resolved) ReleaseName() (string, error)

ReleaseName derives the release name for a resolved target from its repo + module name + the version's SemVer major.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store manages the configured repos and their cached indexes. It is the entry point the CLI and (later) the installer use.

func Open

func Open() (*Store, error)

Open loads the repo config (seeding the default repo on first run).

func (*Store) Add

func (s *Store) Add(name, rawURL string) error

Add registers a new repo. Names must be unique; URLs must be http(s).

func (*Store) List

func (s *Store) List() []Repo

List returns the configured repos in order.

func (*Store) Merged

func (s *Store) Merged() (*Merged, error)

Merged reads the cached indexes and returns a merged, resolvable view. Repos with no cached index yet (never updated) are skipped; the caller can suggest `tiny repo update`.

func (*Store) Remove

func (s *Store) Remove(name string) error

Remove drops a repo and its cached index.

func (*Store) Update

func (s *Store) Update(ctx context.Context) error

Update fetches every configured repo's index and caches it. It returns the first fetch error but still caches whatever succeeded, so a single dead repo doesn't block the others.

type Version

type Version struct {
	Version      string   `json:"version"`
	Image        string   `json:"image"`
	Digest       string   `json:"digest,omitempty"`
	Chart        string   `json:"chart,omitempty"`        // harness chart name
	ChartVersion string   `json:"chartVersion,omitempty"` // compatible range
	Values       string   `json:"values,omitempty"`       // inline values.yaml (with ${cluster.*} holes)
	ClusterFills []string `json:"clusterFills,omitempty"` // holes tiny fills
	Bundles      []Bundle `json:"bundles,omitempty"`
	Cosign       bool     `json:"cosign,omitempty"` // image + entry are signed
}

Version is one installable release: the image + the values/chart coordinates and optional bundles needed to install it.

func (*Version) Major

func (v *Version) Major() (int, error)

Major returns the version's SemVer major.

Jump to

Keyboard shortcuts

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