A Go library and CLI tool for working with Open Component Model (OCM) Helm values templates.
When an OCM ComponentVersion is transferred from one OCI Registry to another, the default values of Helm Charts will contain images of the old OCI Registry. This library provides functionality to manage and render Helm values templates that are embedded as resources in OCM components, ensuring that image references are correctly resolved regardless of the registry they're accessed from.
The library provides mechanisms to:
- Find Helm Values Templates: Locate Helm values templates in OCM components using a label-based approach (
opendefense.cloud/helm/values-for) - Render Templates: Process the templates using Go's text/template with sprig functions for flexible value substitution
- Extract Component Data: Automatically extract resource information from OCM components and prepare it for templating
For a smoother development experience, a small ocm-kit CLI is also provided, which allows local testing of rendering helm values templates or verifying outputs.
- Go 1.25.7 or later
- Docker (for running e2e tests)
- OCM CLI (for e2e tests)
go build ./...# Run all tests
make test
# Run particular tests
go test ./helmvalues# Run e2e tests with default version (timestamp-based)
make e2e
# Run e2e tests with a stable component version
VERSION=0.1.0 make e2e
# Run e2e tests but keep zot registry running
make e2e-keep-zot
# Stop and remove zot registry
make e2e-stop-zotThe ocm-kit command-line tool renders Helm values templates from OCM components.
ocm-kit <component-version-ref> [flags]Where <component-version-ref> is in the format: protocol://host/namespace//component:version
Examples:
# Render first values template from component in local OCI registry
ocm-kit "http://localhost:5000/my-components//opendefense.cloud/arc:0.1.0"
# Render specific values template from remote registry by providing which helm resource it is for
ocm-kit "https://registry.example.com/stable//example.com/myapp:1.2.3" -r my-chart
# Use a local template file instead of component template
ocm-kit "http://localhost:5000/my-components//opendefense.cloud/arc:0.1.0" \
--local-helm-values-template ./values.yaml.tpl-r, --chart-resource string- Name of the Helm chart resource in the component (default: "")-f, --local-helm-values-template string- Path to a local Helm values template file (overrides component template)-p, --pull-secrets-file string- Path to a pull secrets JSON file mapping registries to Kubernetes secret names-h, --help- Display help message
ocm-kit reuses OCM's standard credential resolution. On startup it calls
ocmutils.Configure, which loads $HOME/.ocmconfig if present. Place your
registry credentials in that file and they are picked up transparently — there
are no CLI flags or environment variables for credentials.
A minimal config that authenticates against a single OCI registry:
type: generic.config.ocm.software/v1
configurations:
- type: credentials.config.ocm.software
consumers:
- identity:
type: OCIRegistry
hostname: ghcr.io
pathprefix: opendefensecloud
credentials:
- type: Credentials/v1
properties:
username: <user>
password: <token>identity selects which requests the credentials apply to. hostname is
matched exactly; pathprefix matches by path components (the most specific
prefix wins). scheme (http/https) and port are optional — leaving them
out matches any scheme/port on the host. This is how you scope different
tokens to different namespaces on the same registry.
For a local insecure registry:
type: generic.config.ocm.software/v1
configurations:
- type: credentials.config.ocm.software
consumers:
- identity:
type: OCIRegistry
scheme: http
hostname: 127.0.0.1
port: 5000
credentials:
- type: Credentials/v1
properties:
username: admin
password: adminTo reuse credentials you have already configured for Docker, point OCM at
~/.docker/config.json instead of duplicating them. With
propagateConsumerIdentity: true, every entry in the docker config is
exposed as an OCIRegistry consumer:
type: generic.config.ocm.software/v1
configurations:
- type: credentials.config.ocm.software
repositories:
- repository:
type: DockerConfig/v1
dockerConfigFile: "~/.docker/config.json"
propagateConsumerIdentity: truerepositories and consumers can be combined in the same configuration —
explicit consumers take precedence over what is propagated from the docker
config, so you can override individual hosts when needed.
See the OCM credentials tutorial for the full set of identity types and credential providers.
When rendering the Helm values template, you may need to attach
imagePullSecrets to deployments. The --pull-secrets-file flag and the
pullSecretFor template function work together to map OCI registries to
Kubernetes Secret names.
This approach allows the template author to specify where to add
imagePullSecrets in a values.yaml while the deployer has control over
deployment specific data like the concrete secret names.
The pull secrets file uses the following format:
{
"$schema": "https://raw.githubusercontent.com/opendefensecloud/ocm-kit/refs/heads/main/helmvalues/pullsecrets-schema.json",
"pullSecrets": [
{
"registry": "docker.io",
"secretName": "docker-hub-cred"
},
{
"registry": "ghcr.io/my-org",
"secretName": "ghcr-org-cred"
},
{
"registry": "localhost:5000",
"secretName": "regcred"
}
]
}The pullSecretFor function in templates resolves an OCI reference (hostname,
hostname/repo, or full image ref) to the matching secret name. Resolution walks
from most-specific to least-specific path segments:
pullSecretFor "ghcr.io/my-org/my-repo:latest"checksghcr.io/my-org/my-repo->ghcr.io/my-org->ghcr.iopullSecretFor "docker.io"checks the registry host directly
If no match is found pullSecretFor returns the empty string.
It is advised that templates may always be written in a way that gracefully
handle pullSecretFor returning an empty string value. Like in the example
below, template authors can make use of go-template's with expression.
Example template usage:
{{- $image := index .OCIResources "my-image" }}
{{- with pullSecretFor $image.Host }}
imagePullSecrets:
- name: {{ . }}
{{- end }}CLI invocation:
ocm-kit "https://example.com/my-components//example.com/my-component:0.1.0" \
--pull-secrets-file ./pull-secrets.jsonapiserver:
image:
{{- $apiserver := index .OCIResources "arc-apiserver-image" }}
repository: {{ $apiserver.Host }}/{{ $apiserver.Repository }}
tag: {{ $apiserver.Tag }}
controller:
image:
{{- $controller := index .OCIResources "arc-controller-manager-image" }}
repository: {{ $controller.Host }}/{{ $controller.Repository }}
tag: {{ $controller.Tag }}
etcd:
image:
{{- $etcdImage := index .OCIResources "etcd-image" }}
repository: {{ $etcdImage.Host }}/{{ $etcdImage.Repository }}
tag: {{ $etcdImage.Tag }}apiserver:
image:
repository: localhost:5000/my-components/opendefensecloud/arc-apiserver
tag: v0.2.0
controller:
image:
repository: localhost:5000/my-components/opendefensecloud/arc-controller-manager
tag: v0.2.0package main
import (
"context"
"fmt"
"log"
"go.opendefense.cloud/ocm-kit/helmvalues"
"go.opendefense.cloud/ocm-kit/compver"
"ocm.software/ocm/api/ocm"
"ocm.software/ocm/api/ocm/extensions/repositories/ocireg"
)
func main() {
ctx := context.Background()
// Parse component version reference
cvr, err := compver.SplitRef("http://localhost:5000/my-components//opendefense.cloud/arc:0.1.0")
if err != nil {
log.Fatal(err)
}
// Setup OCM repository
octx := ocm.FromContext(ctx)
repo, err := octx.RepositoryForSpec(ocireg.NewRepositorySpec(cvr.BaseURL()))
if err != nil {
log.Fatal(err)
}
defer repo.Close()
// Get component version
compVer, err := repo.LookupComponentVersion(cvr.ComponentName, cvr.Version)
if err != nil {
log.Fatal(err)
}
defer compVer.Close()
// Find helm values template for a specific chart
tmpl, err := helmvalues.GetHelmValuesTemplate(compVer, "helm-chart")
if err != nil {
log.Fatal(err)
}
// Get rendering input with component data
input, err := helmvalues.GetRenderingInput(compVer)
if err != nil {
log.Fatal(err)
}
// Render the template
renderedValues, err := helmvalues.Render(tmpl, input)
if err != nil {
log.Fatal(err)
}
fmt.Println(renderedValues)
}When rendering templates, the following data is available via the context:
A map of all oci resources in the component by resource name. Only resources with an OCI-based access method are listed:
Each resource is automatically parsed into an object with:
.Host- The registry host.Repository- The repository path.Tag- The image tag.Digest- The image digest
For other access methods (OCI blobs, local blobs, S3, Git, etc.), the relevant fields are extracted into structured maps.
Access example:
{{- $image := index .OCIresources "my-image" }}
repository: {{ $image.host }}/{{ $image.repository }}
tag: {{ $image.tag }}A map of registries to secret-names. Use the pullSecretFor template function
to refer to possible imagePullSecrets. It implements a hierarchical
path-resolution logic (See Pull Secrets).
Component metadata available as a compdesc.ComponentSpec, providing access to:
- Component name and version
- Provider information
- Resources list
- Sources
- References
- Repository contexts
Helm values templates should be labeled in the OCM component descriptor with:
labels:
- name: opendefense.cloud/values-for
value: <helm-chart-resource-name>This label indicates which Helm chart resource this template is for.
github.com/Masterminds/sprig/v3- Template functionsgithub.com/mandelsoft/vfs- Virtual filesystem handlingocm.software/ocm- OCM API
See LICENSE file