config

package
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 9, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package config defines the nelmwave.yml schema and loads it, after gomplate rendering, into Go structures via confijer (type-aware defaults).

confijer binds config keys via the `json` struct tag (case-insensitively) and ignores `yaml` tags; the `yaml` tags here are used only when the plan package serializes these structs to .nelmwave/planfile.yml. Keep both tags in sync on every field.

Repositories and Releases are maps keyed by identity (repo name / release uniqname) rather than lists — the key is the single source of truth for the name, so the value structs carry no name field.

Index

Constants

View Source
const (
	// DriverSchemeKubernetes stores state in the release namespace.
	DriverSchemeKubernetes = "kubernetes"
	// DriverSchemePsql and its aliases store state in PostgreSQL.
	DriverSchemePsql       = "psql"
	DriverSchemePostgres   = "postgres"
	DriverSchemePostgresql = "postgresql"
)

Release state lives either in the cluster or in a database. nelm picks the implementation by a bare driver name; nelmwave takes a URL instead, so one field carries both the choice and its parameters — the same trick Repositories use to tell a helm repo from an OCI registry.

kubernetes://secrets            # the default
kubernetes://configmaps
psql://nelm@db.internal/nelm    # PostgreSQL, password from the environment
View Source
const (
	OCIScheme          = "oci://"
	OCIPlainHTTPScheme = "oci+http://"
)

OCI URL schemes. nelm and helm only know oci://; oci+http:// is nelmwave's spelling for "same thing, no TLS", so that a registry's transport is part of its address instead of a separate flag.

Variables

View Source
var DeletePropagations = []string{"Foreground", "Background", "Orphan"}

DeletePropagations are the values DeletePropagation accepts. They are Kubernetes' own DeletionPropagation values and are case-sensitive: nelm casts the string straight to metav1.DeletionPropagation without checking it.

View Source
var ProvenanceStrategies = []string{"never", "if-possible", "always", "later"}

ProvenanceStrategies are the values ProvenanceStrategy accepts, mirroring nelm's (helm's) verification strategies. Empty is allowed too and leaves nelm's default in place.

Functions

func ParseSelector

func ParseSelector(expr string) (labels.Selector, error)

ParseSelector parses a Kubernetes-style label selector, e.g. "app=api,env in (prod,stg),tier!=db". An empty string matches everything.

func Validate

func Validate(cfg *Config) error

Validate checks a parsed Config for structural correctness:

  • every release has a chart.name;
  • labels are valid Kubernetes labels;
  • needs reference existing releases, don't self-reference, and form a DAG (no cycles).

Release keys are non-empty and unique by construction (parsed and normalized in Parse). Namespace/kube-context are optional (taken from the current kube-context when omitted), so they are not checked here. All problems are collected and returned as a single joined error, in deterministic order.

Types

type Chart

type Chart struct {
	// Name is a helm-repo chart (repo/chart) or an OCI ref (oci://host/chart).
	Name string `json:"name" yaml:"name"`
	// Version is a chart version or constraint.
	Version string `json:"version" yaml:"version"`
}

Chart identifies a chart source.

type Config

type Config struct {
	// Project is a free-form name for the whole platform.
	Project string `json:"project" yaml:"project"`
	// Repositories are chart sources keyed by alias/host. Helm repos (https://)
	// and OCI registries (oci://) live together, distinguished by URL scheme;
	// a value may be a bare URL string or a full object (see Repository).
	Repositories map[string]Repository `json:"repositories" yaml:"repositories"`
	// Releases are the units nelmwave deploys, keyed by release name.
	Releases map[string]Release `json:"releases" yaml:"releases"`
	// contains filtered or unexported fields
}

Config is the root of a nelmwave manifest.

Global defaults for releases (e.g. common labels or values) are expressed via confijer's type-default mechanism: a top-level "Release:" block applies to every release. Maps (labels) deep-merge with a release's own values winning; slices (values) act as a default used only when the release omits its own.

func Parse

func Parse(data []byte) (*Config, error)

Parse unmarshals already-rendered nelmwave.yml bytes into a Config.

It runs normalizations that confijer cannot do itself (confijer silently drops a scalar where it expects a struct):

  1. values/store entries written as a bare scalar are rewritten to {src: ...};
  2. repository entries written as a bare URL string are rewritten to {url: ...};
  3. every resolved Src is canonicalized so equivalent spellings collapse (see canonicalizeSrc).

It does not validate; call Validate after.

func (*Config) DirectNeeds

func (c *Config) DirectNeeds(self string, r Release) ([]string, error)

DirectNeeds returns just the resolved dependency uniqnames (sorted).

func (*Config) ResolveNeeds

func (c *Config) ResolveNeeds(self string, r Release) ([]ResolvedNeed, error)

ResolveNeeds resolves the concrete dependency edges of release `self` (body r): explicit Releases that exist in the config (carrying their Optional flag) plus releases matched by the inlined label selector (always optional). Self is excluded; the result is sorted by uniqname and deduplicated, with the required side winning when a target appears both ways. It errors on an invalid selector.

The result is memoized per release (see Config.needsCache): callers ask for the same edges repeatedly — validation walks the graph for cycles, then the plan projects it — and each resolution is O(number of releases). r is expected to be c.Releases[self]; passing a different body returns whatever was cached for that key.

type FileRef

type FileRef struct {
	// Src is a datasource reference: a local path, or a URL with any gomplate
	// datasource scheme (env:, vault://, s3://, http(s)://, git://, ...).
	// Behaviour is chosen by extension: *.yml/*.yaml are copied, *.yml.tpl are
	// rendered through gomplate, *.sops is decrypted with sops (and *.tpl.sops
	// is decrypted, then rendered).
	Src string `json:"src" yaml:"src"`
	// Name names the resolved artifact file under .nelmwave/ (values or store).
	// Empty means nelmwave derives an index-prefixed basename automatically.
	Name string `json:"name" yaml:"name,omitempty"`
	// Optional skips a source that does not exist instead of failing the build.
	// It covers "the file is not there" (os.ErrNotExist) — a datasource that
	// exists but errors (HTTP 500, a broken template) still fails.
	Optional bool `json:"optional" yaml:"optional,omitempty"`
}

FileRef is a single file source resolved through the datasource layer. The same type backs both a release's values and its store files. Name optionally names the resolved artifact under .nelmwave/; when empty an index-prefixed basename is used. The internal artifact directory layout is otherwise owned by nelmwave, not the user.

In the manifest an entry may be written in any of these equivalent forms:

values:
  - src: file://values/pg.yml.tpl   # mapping, with scheme
  - file://values/pg.yml.tpl        # bare string, with scheme
  - src: values/pg.yml.tpl          # mapping, no scheme (local file)
  - values/pg.yml.tpl               # bare string, no scheme (local file)

The bare-string forms are expanded to the mapping form during parsing, and a missing/file:// scheme collapses to a plain local path (see canonicalizeSrc).

type LabelSelectorRequirement

type LabelSelectorRequirement struct {
	Key      string   `json:"key" yaml:"key"`
	Operator string   `json:"operator" yaml:"operator"`
	Values   []string `json:"values" yaml:"values,omitempty"`
}

LabelSelectorRequirement is one matchLabelsExpressions entry (same shape as Kubernetes' metav1.LabelSelectorRequirement).

type Namespace

type Namespace struct {
	// Create makes nelmwave ensure the namespace exists before applying
	// (nelm's NoCreateNamespace = !Create).
	Create bool `json:"create" yaml:"create" default:"true"`
	// Delete removes the namespace after the release is uninstalled (nelm's
	// DeleteReleaseNamespace). It is deliberately not the mirror of Create: the
	// namespace is not owned by the release, so deleting it takes everything
	// else living there with it. Off unless asked for.
	Delete bool `json:"delete" yaml:"delete,omitempty"`
	// Annotations are applied to the namespace itself. They are merged into
	// whatever is already there; nelmwave never removes annotations it does not
	// manage.
	Annotations map[string]string `json:"annotations" yaml:"annotations,omitempty"`
	// Labels are applied to the namespace itself, with the same merge semantics
	// as Annotations. Useful for policy selectors such as
	// pod-security.kubernetes.io/enforce or istio-injection.
	Labels map[string]string `json:"labels" yaml:"labels,omitempty"`
}

Namespace holds the settings for a release's namespace. The namespace *name* is part of the release key ("api@production"), never a field here.

As a distinct type it also gets a confijer type-default bucket, so a top-level "Namespace:" block applies the same creation policy and metadata to every release.

func (Namespace) HasMetadata

func (n Namespace) HasMetadata() bool

HasMetadata reports whether any namespace metadata was declared, i.e. whether nelmwave has to touch the namespace object beyond letting nelm create it.

type NeedRelease

type NeedRelease struct {
	// Optional lets the run proceed when this dependency is filtered out of the
	// selection: the edge is dropped with a warning instead of failing. By
	// default a declared dependency is required, matching what `optional` means
	// for values and stores.
	Optional bool `json:"optional" yaml:"optional,omitempty"`
}

NeedRelease holds options for a single explicit release dependency.

type Needs

type Needs struct {
	// Releases lists explicit dependencies keyed by uniqname
	// ("name[@namespace[@kubecontext]]"). The value carries per-dependency
	// options (currently Optional); it is a struct so more can be added.
	Releases map[string]NeedRelease `json:"releases" yaml:"releases,omitempty"`
	// MatchLabels selects dependency releases by exact label match.
	MatchLabels map[string]string `json:"matchLabels" yaml:"matchLabels,omitempty"`
	// MatchLabelsExpressions selects dependency releases by set-based label
	// requirements (operators In, NotIn, Exists, DoesNotExist).
	MatchLabelsExpressions []LabelSelectorRequirement `json:"matchLabelsExpressions" yaml:"matchLabelsExpressions,omitempty"`
}

Needs declares what a release depends on. All parts are combined: a release waits for every release named in Releases plus every release matched by the inlined label selector (MatchLabels + MatchLabelsExpressions, Kubernetes semantics). An empty label selector adds no dependencies (it does NOT match everything).

type Release

type Release struct {
	// Labels are used for k8s-style selection (-l) and are free-form. They also
	// end up on the release storage object (see release.Spec.Labels).
	Labels map[string]string `json:"labels" yaml:"labels"`
	// Annotations are stored with each revision of the release (nelm's
	// ReleaseInfoAnnotations) and read back via `nelm release get`. Unlike
	// Labels they cannot be selected on, and unlike Kubernetes annotations they
	// are not attached to any object — so pipeline URLs, commit messages and
	// other things too long or too punctuated to be a label fit here.
	//
	// These describe the release, not its resources: annotations for every
	// rendered resource are a separate concern (nelm's ExtraAnnotations).
	Annotations map[string]string `json:"annotations" yaml:"annotations,omitempty"`
	// Needs declares the releases that must be applied before this one (DAG
	// edges), by explicit uniqname and/or by label selector.
	Needs Needs `json:"needs" yaml:"needs,omitempty"`

	// Chart points at a helm-repo or OCI chart. Always required: nelmwave
	// orchestrates external charts only and ships no templates of its own.
	Chart Chart `json:"chart" yaml:"chart"`

	// Values are per-release value sources, merged on top of global Values.
	Values []FileRef `json:"values" yaml:"values"`
	// Sets are inline chart value overrides applied on top of Values (highest
	// precedence). Keys are dotted paths (like helm --set, e.g. "image.tag");
	// values keep their YAML type (int/string/bool/...). Passed to nelm as
	// type-preserving JSON overrides.
	Sets map[string]any `json:"sets" yaml:"sets,omitempty"`
	// Stores are companion files resolved and stored alongside the plan.
	Stores []FileRef `json:"stores" yaml:"stores"`

	// Namespace configures the release's namespace — not which namespace it is
	// (that comes from the release key), but whether nelmwave creates it and what
	// metadata it carries.
	Namespace Namespace `json:"namespace" yaml:"namespace,omitempty"`

	// Timeout bounds the operation, e.g. "5m". Empty means nelm's default.
	Timeout string `json:"timeout" yaml:"timeout,omitempty"`
	// AutoRollback rolls back to the last deployed revision on failure
	// (nelm AutoRollback, akin to helm --atomic).
	AutoRollback bool `json:"autoRollback" yaml:"autoRollback,omitempty"`

	// ForceAdoption takes over a resource that another Helm release claims via
	// meta.helm.sh/release-name. Without it nelm refuses to touch it, which is
	// what you want everywhere except migrations and release renames.
	ForceAdoption bool `json:"forceAdoption" yaml:"forceAdoption,omitempty"`
	// RemoveManualChanges reclaims fields added to a resource by hand (kubectl
	// edit) that the manifest does not mention. On by default, as in nelm; set
	// it to false to leave such fields alone.
	RemoveManualChanges bool `json:"removeManualChanges" yaml:"removeManualChanges" default:"true"`
	// InstallCRDs installs the CRDs shipped in the chart's crds/ directory. On
	// by default; turn it off where CRDs are managed by a separate pipeline.
	InstallCRDs bool `json:"installCRDs" yaml:"installCRDs" default:"true"`
	// DeletePropagation is the default deletion strategy for this release's
	// resources: Foreground (nelm's default), Background or Orphan. A single
	// resource can still override it with werf.io/delete-propagation.
	DeletePropagation string `json:"deletePropagation" yaml:"deletePropagation,omitempty"`
	// HistoryLimit caps how many revisions of this release are kept in storage.
	// 0 leaves nelm's default of 10.
	HistoryLimit int `json:"historyLimit" yaml:"historyLimit,omitempty"`
	// DriverURL says where the release's state is kept, as a URL:
	// kubernetes://secrets (the default), kubernetes://configmaps, or
	// psql://user@host/db. See ParseDriverURL.
	//
	// Usually set once in the top-level Release: block — a manifest whose
	// releases keep state in different places is a good way to lose one.
	DriverURL string `json:"driverURL" yaml:"driverURL,omitempty"`
}

Release is a single deployable unit: one nelm release. Its identity — name, namespace and kube-context — lives entirely in the Config.Releases map key (see Uniqname), so the struct carries none of those fields.

func (Release) Matches

func (r Release) Matches(sel labels.Selector) bool

Matches reports whether a release's labels satisfy sel.

type Repository

type Repository struct {
	// URL is the repository index URL (https://...) or OCI registry
	// (oci://... / oci+http://...).
	URL string `json:"url" yaml:"url"`
	// Username / Password are optional basic-auth credentials.
	Username string `json:"username" yaml:"username,omitempty"`
	Password string `json:"password" yaml:"password,omitempty"`
	// InsecureSkipTLSVerify disables TLS verification for this repo.
	InsecureSkipTLSVerify bool `json:"insecureSkipTLSVerify" yaml:"insecureSkipTLSVerify,omitempty"`
	// PassCredentials forwards credentials to all domains, not just the repo host.
	PassCredentials bool `json:"passCredentials" yaml:"passCredentials,omitempty"`
	// CAFile is a path to a CA bundle for this repo.
	CAFile string `json:"caFile" yaml:"caFile,omitempty"`
	// CertFile / KeyFile are the client TLS certificate and key presented to the
	// repository (mTLS). CAFile says whom we trust; these say who we are.
	CertFile string `json:"certFile" yaml:"certFile,omitempty"`
	KeyFile  string `json:"keyFile" yaml:"keyFile,omitempty"`
	// SkipUpdate stops the chart's declared dependencies from being refreshed
	// against the repository before they are pulled. It only affects charts with
	// a dependencies: section — the chart itself is fetched either way.
	SkipUpdate bool `json:"skipUpdate" yaml:"skipUpdate,omitempty"`
	// RequestTimeout bounds a single request to the repository, e.g. "30s".
	// Empty means no per-request limit (the release timeout still applies).
	RequestTimeout string `json:"requestTimeout" yaml:"requestTimeout,omitempty"`
	// ProvenanceStrategy decides whether a chart's PGP signature (its .prov
	// file) is verified before the chart is used: never (nelm's default),
	// if-possible, always, later. Empty means never.
	ProvenanceStrategy string `json:"provenanceStrategy" yaml:"provenanceStrategy,omitempty"`
	// ProvenanceKeyring is the path to a keyring with the public keys the
	// signature is checked against. Empty leaves helm's default
	// (~/.gnupg/pubring.gpg).
	ProvenanceKeyring string `json:"provenanceKeyring" yaml:"provenanceKeyring,omitempty"`
}

Repository is a chart source keyed by alias/host in Config.Repositories. The URL scheme says what it is and how to reach it: https:// (or http://) is a classic Helm repository, oci:// an OCI registry over TLS, oci+http:// one without.

In the manifest a repository may be written as a bare URL string or as a full object; the bare form is expanded to {url: "..."} during parsing:

repositories:
  bitnami: https://charts.bitnami.com/bitnami   # bare URL
  ghcr.io: oci://ghcr.io                         # bare OCI URL
  dev: oci+http://registry:5000                  # OCI without TLS
  private:                                       # full form (needs auth)
    url: oci://registry.example.com
    username: [[ .Env.REGISTRY_USER ]]
    password: [[ .Env.REGISTRY_PASS ]]

func (Repository) IsOCI

func (r Repository) IsOCI() bool

IsOCI reports whether this repository is an OCI registry, with or without TLS.

func (Repository) IsOCIPlainHTTP

func (r Repository) IsOCIPlainHTTP() bool

IsOCIPlainHTTP reports whether this registry is reached over http://.

type ResolvedNeed

type ResolvedNeed struct {
	Uniqname string
	Optional bool
}

ResolvedNeed is one resolved dependency edge: the target release uniqname and whether the dependency is optional (see NeedRelease.Optional). Label-matched dependencies are always optional — a selector casts a wide net, and failing because it happened to catch a filtered-out release would be surprising.

type StorageDriver

type StorageDriver struct {
	// Driver is nelm's ReleaseStorageDriver ("secrets", "configmaps", "sql").
	// Empty means the manifest said nothing and nelm's default applies.
	Driver string
	// SQLConnection is the libpq connection string, set only for sql.
	SQLConnection string
	// HasPassword reports whether the URL embedded a password. Callers warn
	// about it: a password in the manifest is written to the planfile as-is.
	HasPassword bool
}

StorageDriver is a parsed driverURL: what to tell nelm, and — for SQL — how to connect.

func ParseDriverURL

func ParseDriverURL(raw string) (StorageDriver, error)

ParseDriverURL turns a driverURL into a StorageDriver. An empty string is valid and selects nelm's default (secrets).

"memory" is deliberately unsupported. nelm has such a driver, but state that dies with the process cannot work here: the next up would find no history, treat the release as new, and try to adopt the resources it installed itself.

type Uniqname

type Uniqname struct {
	Name        string
	Namespace   string
	KubeContext string
}

Uniqname is the unique identity of a release — the nelmwave equivalent of helmwave's uniqname, extended with a kube-context. It is the release's name plus an optional target namespace and kube-context, encoded in the manifest as the Config.Releases map key in the form:

name[@namespace[@kubecontext]]

Omitted namespace/kube-context mean "use the current kube-context and its default namespace"; that resolution happens at apply time, not at build.

func ParseUniqname

func ParseUniqname(key string) (Uniqname, error)

ParseUniqname parses a "name[@namespace[@kubecontext]]" key.

The kube-context segment may itself contain '@' (e.g. "user@cluster"), so the key is split into at most three fields — name and namespace cannot contain '@', the trailing kube-context can. Name must be non-empty.

func (Uniqname) String

func (u Uniqname) String() string

String renders the identity back to its canonical map-key form, dropping trailing empty segments: "name", "name@ns", "name@ns@ctx", or "name@@ctx" (namespace empty but kube-context set).

Jump to

Keyboard shortcuts

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