Helm — Interfaces#

Interface catalog#

kube.Interface#

  • Package: helm.sh/helm/v4/pkg/kube
  • File: pkg/kube/interface.go:31
  • Methods:
    Get(resources ResourceList, related bool) (map[string][]runtime.Object, error)
    Create(resources ResourceList, options ...ClientCreateOption) (*Result, error)
    Delete(resources ResourceList, policy metav1.DeletionPropagation) (*Result, []error)
    Update(original, target ResourceList, options ...ClientUpdateOption) (*Result, error)
    Build(reader io.Reader, validate bool) (ResourceList, error)
    IsReachable() error
    GetWaiter(ws WaitStrategy) (Waiter, error)
    GetPodList(namespace string, listOptions metav1.ListOptions) (*v1.PodList, error)
    OutputContainerLogsForPodList(podList *v1.PodList, namespace string, writerFunc func(...) io.Writer) error
    BuildTable(reader io.Reader, validate bool) (ResourceList, error)
  • Purpose: Abstracts all Kubernetes cluster operations needed by the action layer. The entire action layer (Install, Upgrade, Rollback, Uninstall) communicates with the cluster exclusively through this interface — no client-go types leak upward.
  • Implementations: kube.Client (real cluster, backed by client-go + fluxcd/cli-utils for SSA), kubefake.Client and kubefake.FailingKubeClient (test doubles in pkg/kube/fake/)
  • Design quality: Broad by Go interface standards (10 methods), but each method maps 1:1 to a Kubernetes CRUD or readiness concern. The comment explicitly requires concurrency safety. The BuildTable method has a TODO noting it should be folded into Build in Helm 5 — the designers are aware of the surface area. Well-segregated for its use case.

kube.Waiter#

  • Package: helm.sh/helm/v4/pkg/kube
  • File: pkg/kube/interface.go:81
  • Methods:
    Wait(resources ResourceList, timeout time.Duration) error
    WaitWithJobs(resources ResourceList, timeout time.Duration) error
    WaitForDelete(resources ResourceList, timeout time.Duration) error
    WatchUntilReady(resources ResourceList, timeout time.Duration) error
  • Purpose: Separates readiness-waiting logic from CRUD operations. Obtained from Interface.GetWaiter(WaitStrategy), allowing the wait strategy (regular, with-jobs) to select an appropriate implementation.
  • Implementations: Returned by kube.Client.GetWaiter() — the real waiter uses client-go watches; the fake implementation is a no-op.
  • Design quality: Well-segregated from Interface — waiting is a distinct concern from resource manipulation. The WatchUntilReady is specifically for hook lifecycle, with detailed documentation on what “ready” means per Kind.

driver.Driver (composed from role interfaces)#

  • Package: helm.sh/helm/v4/pkg/storage/driver
  • File: pkg/storage/driver/driver.go:99
  • Methods: Composed from four role interfaces plus Name() string:
    // Creator
    Create(key string, rls release.Releaser) error
    // Updator
    Update(key string, rls release.Releaser) error
    // Deletor
    Delete(key string) (release.Releaser, error)
    // Queryor
    Get(key string) (release.Releaser, error)
    List(filter func(release.Releaser) bool) ([]release.Releaser, error)
    Query(labels map[string]string) ([]release.Releaser, error)
    // Driver itself
    Name() string
  • Purpose: Persistence contract for release records. The Storage wrapper adds MaxHistory enforcement on top of this interface.
  • Implementations: driver.Secrets (Kubernetes Secrets, default), driver.ConfigMaps (Kubernetes ConfigMaps), driver.Memory (in-process, for testing), driver.SQL (PostgreSQL via jmoiron/sqlx, new in v4)
  • Design quality: The decomposition into Creator, Updator, Deletor, Queryor is a textbook Interface Segregation Principle application. The named role interfaces exist as standalone types, so code that only needs read access can declare driver.Queryor rather than the full Driver. The use of release.Releaser (a type alias for any) in method signatures is a v4 versioning strategy to allow the underlying release struct to evolve across major versions.

postrenderer.PostRenderer#

  • Package: helm.sh/helm/v4/pkg/postrenderer
  • File: pkg/postrenderer/postrenderer.go:30
  • Methods:
    Run(renderedManifests *bytes.Buffer) (modifiedManifests *bytes.Buffer, err error)
  • Purpose: Extension point between template rendering and Kubernetes apply. After pkg/engine renders all chart templates to a YAML stream, the action layer optionally passes the entire stream through this interface before splitting it and sending to the kube client. Enables arbitrary YAML transformation (Kustomize overlays, custom scripts, WASM plugins).
  • Implementations: postRendererPlugin (backed by plugin.Runtime — exec-based or WASM), plus any user-supplied implementation (the interface is public and exported for embedding in third-party tools).
  • Design quality: Minimal, single-method interface — maximum implementability. The *bytes.Buffer choice (rather than io.Reader/io.Writer) is pragmatic: callers need to know when the buffer is complete before splitting it back into per-file records. This is the primary extensibility hook for ecosystem tools.

action.RESTClientGetter#

  • Package: helm.sh/helm/v4/pkg/action
  • File: pkg/action/action.go:416
  • Methods:
    ToRESTConfig() (*rest.Config, error)
    ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error)
    ToRESTMapper() (meta.RESTMapper, error)
  • Purpose: Abstracts how a *rest.Config (Kubernetes REST client configuration) is obtained. Stored on Configuration and used lazily to construct the kube.Client and capability discovery on first use. The lazyClient pattern in pkg/action/lazyclient.go wraps this getter.
  • Implementations: cli.EnvSettings.RESTClientGetter() returns the standard genericclioptions.ConfigFlags from k8s.io/cli-runtime, which reads kubeconfig files. Test code passes custom implementations.
  • Design quality: Intentionally mirrors the k8s.io/cli-runtime RESTClientGetter interface — Helm does not reinvent this; it provides its own type so it is not forced to take a direct dependency on cli-runtime in pkg/action. A clean boundary-seam interface.

getter.Getter#

  • Package: helm.sh/helm/v4/pkg/getter
  • File: pkg/getter/getter.go:156
  • Methods:
    Get(url string, options ...Option) (*bytes.Buffer, error)
  • Purpose: Protocol-agnostic chart fetching. Used by pkg/downloader and the action layer to retrieve charts from HTTP/HTTPS endpoints, OCI registries, or local paths. The Provider / Providers registry maps URL schemes to Constructor functions that produce Getter instances.
  • Implementations: httpGetter (HTTP/HTTPS), OCIGetter (OCI registries), local filesystem path (implicitly, via chart loader)
  • Design quality: Single-method interface — ideal for the strategy pattern. The Provider/Providers registry design allows third parties to register new schemes without modifying core code, though this extension point is not advertised as a plugin API.

release.Accessor and chart.Accessor#

  • Package: helm.sh/helm/v4/pkg/release, helm.sh/helm/v4/pkg/chart
  • Files: pkg/release/interfaces.go:29, pkg/chart/interfaces.go:26
  • Methods (release.Accessor):
    Name() string; Namespace() string; Version() int; Hooks() []Hook
    Manifest() string; Notes() string; Labels() map[string]string
    Chart() chart.Charter; Status() string; ApplyMethod() string; DeployedAt() time.Time
  • Methods (chart.Accessor):
    Name() string; IsRoot() bool; MetadataAsMap() map[string]any
    Files() []*common.File; Templates() []*common.File; ChartFullPath() string
    IsLibraryChart() bool; Dependencies() []Charter; MetaDependencies() []Dependency
    Values() map[string]any; Schema() []byte; Deprecated() bool
  • Purpose: Version-stable access facades for the Release and Chart domain objects. In Helm v4 the underlying structs are versioned (e.g., pkg/release/v1); these interfaces allow code that works across versions to depend on the interface rather than a specific struct. The type aliases Releaser any and Charter any in the same packages allow untyped storage at storage-driver boundaries.
  • Implementations: release/v1.Release, chart/v2.Chart and chart/v3.Chart
  • Design quality: Deliberately broad — they expose the full domain object via accessor methods. This is a versioning strategy, not an ISP exercise. The use of any type aliases alongside concrete interfaces is an unconventional but intentional Helm v4 design for forward compatibility.

Interface patterns#

  • Size distribution: Wide range. Single-method (PostRenderer, Getter) to 10-method (kube.Interface). The storage driver interfaces explicitly decompose a 7-method set into 4 role interfaces of 1-3 methods each. Overall the project leans toward purposefully-sized interfaces.
  • Embedding: Heavy use in driver.Driver, which embeds Creator + Updator + Deletor + Queryor. Also kube.InterfaceWaitOptions extends kube.Interface pattern (though implemented separately). Embedding is used as a composition mechanism for both the driver and for the Accessor interfaces that inherit Charter/Hook markers.
  • Implicit satisfaction: Mixed. Core infrastructure interfaces (kube.Interface, driver.Driver) are defined by their packages (provider-defined), but the implementations are in the same or adjacent packages. PostRenderer and Getter are clearly consumer-defined — they exist so third parties can implement them. RESTClientGetter is defined in pkg/action (the consumer) rather than in pkg/kube (the infrastructure side), which is correct Go style.
  • stdlib interfaces used: io.Reader in kube.Interface.Build() and BuildTable(); io.Writer indirectly via writerFunc callbacks; fmt.Stringer is not implemented on major types (they expose Name() rather than String()). The bytes.Buffer in PostRenderer replaces io.ReadWriter for practical reasons.

Key abstractions#

  1. kube.Interface — The most architecturally load-bearing interface. All action types talk to Kubernetes only through this abstraction, enabling the entire test suite to run without a real cluster. Its fake implementations are as important as the interface itself.

  2. driver.Driver and its role interfaces — The cleanest ISP example in the codebase. The four decomposed role interfaces (Creator, Updator, Deletor, Queryor) are individually usable and make the storage contract explicit and minimal per caller. The SQL backend landing in v4 vindicates this design.

  3. postrenderer.PostRenderer — The primary extension point for ecosystem tools. Its single-method design makes it trivially implementable. Kustomize integration, custom webhook transforms, and the upcoming WASM renderer all implement exactly this one method.

  4. action.RESTClientGetter — Architectural seam that decouples pkg/action (the embeddable library) from k8s.io/cli-runtime (a CLI concern). Without this interface, embedding pkg/action would drag in kubeconfig-file-reading logic appropriate only for CLI use.

  5. getter.Getter — The protocol-abstraction interface that makes Helm’s chart sources pluggable (HTTP, OCI, local). Combined with the Provider/Providers registry, it defines a scheme-dispatch pattern that lets new transport protocols be added without touching the downloader or action layer.


Interface-driven extensibility#

Helm uses interfaces at three distinct extensibility tiers:

Tier 1 — Internal swappability (testability): kube.Interface, driver.Driver, action.RESTClientGetter. These exist primarily to enable the fake/stub implementations used in unit tests. The real and fake implementations live in the same package or an adjacent fake/ sub-package. Users of pkg/action as a library can also swap these to connect Helm’s action layer to non-standard Kubernetes environments (e.g., dry-run recorders, auditing proxies).

Tier 2 — User-facing extension points: postrenderer.PostRenderer, getter.Getter. These are explicitly designed for user/ecosystem customization. PostRenderer is part of the public API of pkg/action (the Install.PostRenderer field is exported). Any tool that imports pkg/action can inject a custom post-renderer to transform manifests before apply. Getter allows registering new URL schemes via the Providers registry.

Tier 3 — Version-compatibility facades: release.Accessor, chart.Accessor. These are v4-era interfaces that insulate cross-version code from struct layout changes. They are not extension points in the traditional sense — they are stability contracts as the domain model evolves across major Helm versions.