appcatalog

package
v0.14.31-dev Latest Latest
Warning

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

Go to latest
Published: Aug 29, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Overview

Package appcatalog resolves operator-facing public app ids to a Helm chart manifest pair — apps/<id>/app.yaml plus an optional apps/<id>/values.yaml — in an open-source dhnt/appstore checkout.

This is the "apps" half of the peer-DKS appstore parity surface; internal/agent/builtincatalog is the "builtins" half (raw builtin/<name>/install.yaml manifests). The two trees live side by side in the same catalog root but resolve independently: an apps/<id> entry names a Helm chart (repo/name/version + values), never a raw manifest, and is rendered into a k3s helm.cattle.io/v1 HelmChart object (see render.go) rather than applied byte-for-byte.

Confinement mirrors builtincatalog's approach deliberately: a symlink-resolved, traversal-checked path under the catalog root, an operator-name regex, and a required-subdirectory probe for catalog discovery. It knows nothing about cloudbox's private manifests.

Index

Constants

View Source
const (
	APIVersion = "appstore.dhnt.io/v1"
	Kind       = "AppEntry"
)

APIVersion / Kind are the ONLY apps/<id>/app.yaml envelope this package understands — they mirror the real dhnt/appstore catalog format exactly (see ../appstore/apps/<id>/app.yaml). Any other value — including a missing one — fails closed; there is no best-effort parse of an unknown shape.

View Source
const (
	HelmChartAPIVersion = "helm.cattle.io/v1"
	HelmChartKind       = "HelmChart"
)

HelmChartAPIVersion / HelmChartKind name the k3s built-in helm-controller custom resource this package renders into. k3s ships the helm-controller and its CRD by default on every node running the embedded control plane, so a peer-hosted DKS plane needs no separate Helm CLI install, no pinned/managed Helm binary, and no chart-template execution inside this process — the controller performs `helm upgrade --install` itself, in-cluster, from the fields set below.

View Source
const (
	AppLabelKey            = "outpost.dhnt.io/app-id"
	NamespaceAnnotationKey = "outpost.dhnt.io/app-namespace"
	ReleaseAnnotationKey   = "outpost.dhnt.io/app-release"
)

AppLabelKey / NamespaceAnnotationKey / ReleaseAnnotationKey stamp the rendered object with the catalog id and the caller's install coordinates, so Status/Uninstall (which only need to recompute a name — see ReleaseObject) and any operator inspecting the cluster with `kubectl` can trace a HelmChart CR back to its appstore origin.

View Source
const HelmChartNamespace = "kube-system"

HelmChartNamespace is the fixed namespace k3s's helm-controller watches for HelmChart custom resources system-wide (the same namespace k3s's own built-in HelmChart CRs, e.g. traefik, live in). The actual release lands in Target.Namespace via spec.targetNamespace + spec.createNamespace — the HelmChart object itself lives here so it can be created before its own target (per-user) namespace exists.

Variables

This section is empty.

Functions

func List

func List(catalog string) (string, []string, error)

List returns apps which have a regular app.yaml within the catalog.

func ReleaseObject

func ReleaseObject(t Target) (*unstructured.Unstructured, error)

ReleaseObject builds the addressable (kind/namespace/name) reference Status/Uninstall Get/Delete against — the identical object Render would apply for the same manifest+target, without needing the manifest or values at all (a pure, deterministic name derivation).

func Render

func Render(m *Manifest, valuesYAML string, t Target) (*unstructured.Unstructured, error)

Render builds the helm.cattle.io/v1 HelmChart object that installs m's chart into t.Namespace under release t.Release, embedding valuesYAML byte-for-byte as spec.valuesContent.

Every field is set through unstructured.SetNestedField on a typed Go value — there is no string formatting, YAML templating, or shell invocation anywhere in this path, so nothing from the manifest or values file is ever interpolated into a command line. The k3s helm-controller reconciling the resulting object is what actually executes Helm, inside the cluster, from these structured fields.

func ResolveCatalog

func ResolveCatalog(catalog string) (string, error)

ResolveCatalog canonicalizes an explicit root, or discovers an appstore sibling in an umbrella source checkout. There is intentionally no network fallback: unavailable assets fail closed.

Types

type ChartRef

type ChartRef struct {
	// Repo is the Helm chart repository URL. Must be https:// or oci://
	// — never a bare string that could be shell-interpreted.
	Repo string `yaml:"repo"`
	// Name is the chart name within Repo.
	Name string `yaml:"name"`
	// Version pins an exact chart version — no floating "latest".
	Version string `yaml:"version"`
}

ChartRef names the Helm chart an app.yaml installs (spec.chart).

type Entry

type Entry struct {
	ID      string
	Catalog string
	// AppFile is <catalog>/apps/<id>/app.yaml — always present.
	AppFile string
}

Entry is one resolved OSS appstore app.

func Resolve

func Resolve(catalog, id string) (Entry, error)

Resolve maps id to <catalog>/apps/<id>/app.yaml. catalog may be an umbrella sibling appstore checkout or a fetched/versioned appstore tree. Empty catalog discovers only deterministic development-checkout locations.

The values override is NOT resolved here: spec.defaultValuesFile inside app.yaml is what names it (or declares none), so that resolution can only happen after the manifest is parsed — see Load.

type Maintainer

type Maintainer struct {
	Name  string `yaml:"name,omitempty"`
	Email string `yaml:"email,omitempty"`
}

Maintainer mirrors one metadata.maintainers[] entry. Advisory only — never validated or acted on.

type Manifest

type Manifest struct {
	APIVersion string   `yaml:"apiVersion"`
	Kind       string   `yaml:"kind"`
	Metadata   Metadata `yaml:"metadata"`
	Spec       Spec     `yaml:"spec"`
}

Manifest is the decoded, validated apps/<id>/app.yaml — the real dhnt/appstore AppEntry shape, decoded 1:1.

func Load

func Load(entry Entry) (*Manifest, string, error)

Load reads and validates entry's app manifest (apiVersion/kind envelope, id match, and chart shape — fail closed on any of them) plus the raw bytes of its declared values override (spec.defaultValuesFile — see loadDeclaredValues), returned UNMODIFIED (never templated or otherwise interpolated — see Render).

There is deliberately no runtime license field to validate: license compatibility is enforced by dhnt/appstore CURATION (a public, OSS-only, reviewed catalog — see ../appstore/README.md), not by a field that the real catalog entries do not carry. Requiring one here would reject every genuine catalog app.

type Metadata

type Metadata struct {
	ID          string       `yaml:"id"`
	Name        string       `yaml:"name"`
	Version     string       `yaml:"version"`
	Categories  []string     `yaml:"categories"`
	Tags        []string     `yaml:"tags"`
	Maintainers []Maintainer `yaml:"maintainers"`
	Description string       `yaml:"description"`
	Homepage    string       `yaml:"homepage"`
	Featured    bool         `yaml:"featured"`
	Visibility  string       `yaml:"visibility"`
}

Metadata mirrors the app.yaml metadata block. Only id is load-bearing (it must match the catalog directory name); everything else is operator-facing catalog copy carried through to Show.

type RBAC

type RBAC struct {
	ClusterScoped bool `yaml:"clusterScoped"`
}

RBAC mirrors spec.rbac. clusterScoped tells cloudbox whether the chart needs cluster-scoped resources; on this peer-hosted path it is surfaced but the install always targets the caller's own namespace.

type Spec

type Spec struct {
	Chart ChartRef `yaml:"chart"`
	// TargetNamespace is the catalog's namespace template (e.g.
	// "{{.UserNamespace}}"). It is cloudbox-side templating metadata; the
	// peer-hosted install path instead takes an explicit, already-resolved
	// namespace from Target (see render.go) and never expands this string.
	TargetNamespace string `yaml:"targetNamespace"`
	RBAC            RBAC   `yaml:"rbac"`
	// DefaultValuesFile is the AUTHORITATIVE name of the sibling values
	// override (conventionally "values.yaml"). Empty means the app takes
	// no values override at all — a chart with sane defaults needs none.
	// When non-empty, Load resolves it as a bare filename sibling of
	// app.yaml (never a path — traversal and symlink escape are refused)
	// and requires it to exist: a catalog entry that DECLARES a values
	// file but does not ship it is a broken entry, not "no values". See
	// loadDeclaredValues.
	DefaultValuesFile string `yaml:"defaultValuesFile"`
}

Spec mirrors the app.yaml spec block.

type Target

type Target struct {
	// Namespace is the target/user namespace the chart is installed
	// into (spec.targetNamespace). Required.
	Namespace string
	// Release distinguishes multiple instances of the same app within
	// one namespace. Defaults to the app id when empty (one instance is
	// the common case).
	Release string
}

Target names the per-user/per-instance install coordinates. Both fields are required and validated — there is no default namespace, so two different users (or two installs of the same app for one user) can never collide silently.

Jump to

Keyboard shortcuts

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