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
- func List(catalog string) (string, []string, error)
- func ReleaseObject(t Target) (*unstructured.Unstructured, error)
- func Render(m *Manifest, valuesYAML string, t Target) (*unstructured.Unstructured, error)
- func ResolveCatalog(catalog string) (string, error)
- type ChartRef
- type Entry
- type Maintainer
- type Manifest
- type Metadata
- type RBAC
- type Spec
- type Target
Constants ¶
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.
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.
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.
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 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 ¶
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 ¶
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 ¶
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.