chart2kro

Helm chart kro ResourceGraphDefinition

Turn Helm charts into kro APIs you can trust

chart2kro converts a Helm chart into a kro ResourceGraphDefinition: a Kubernetes API whose instances deploy what the chart deploys. Every input is verified against Helm with kro's own runtime, and every value it does not expose comes with a reason.

brew install hupe1980/tap/chart2kro

Or go install github.com/hupe1980/chart2kro/cmd/chart2kro@latest, or the GitHub Action. Single static binary; no Helm, kubectl or cluster needed to convert. Output runs on kro v0.9.2 or newer.

$ chart2kro convert oci://ghcr.io/…/podinfo -o podinfo/
podinfo 6.15.0 → Podinfo.kro.run/v1alpha1: 3 resources,
  29 exposed, 33 frozen, 0 failed, 60 unused
  (256 witnesses, kro v0.9.4, Kubernetes 1.37)
  frozen: changes-structure             15
  frozen: toggles-fields                 8
  frozen: transformed                    6
  frozen: unknown-kind                   4
  unused: affects-only-dropped-objects  11
$ kubectl apply -f podinfo/rgd.yaml
$ kubectl apply -f podinfo/instance.yaml
podinfo.kro.run/podinfo created

Porting a chart to kro by hand is guesswork

A Helm chart is a template program: a value can end up in one field, in five, inside a string, behind a condition, or nowhere. Rewriting that as a kro ResourceGraphDefinition means deciding, value by value, what is safe to expose and how. chart2kro makes those decisions by experiment, checks each one with kro's own code, and tells you why it decided.

What you get

Accepted by kro

Every RGD is checked offline with kro's own admission schema and graph builder before it is written. chart2kro never writes an RGD kro would reject.

Faithful to Helm

An instance with the defaults deploys what helm template deploys for that release name and namespace.

Verified inputs

A value becomes an input only after kro's runtime, run offline, reproduced Helm's output for it. Loops over lists become kro collections.

Every decision explained

Inputs carry the chart's own values.yaml docs. Values that are not exposed are listed with a stable reason code and what to do about it.

Isolated instances

Names come from the instance and namespaces from where it is created. Selectors never reach another instance's pods.

No secret leaks

Secrets you pass with -f never become defaults. Passwords the chart would generate become required inputs, never derived from public data.

How it works

  1. Render

    The chart is rendered in memory with the Helm v4 SDK, exactly like helm template.

  2. Probe

    Each value is rendered again with typed probe values to see which fields it controls, and how.

  3. Assemble and validate

    Mappings become CEL expressions, includeWhen conditions and schema fields. kro's own code validates the result.

  4. Verify

    kro's runtime instantiates the RGD offline and compares it with Helm's render. Any mismatch freezes the value.

Read how it works →

From values.yaml to an API

Your chart
# values.yaml
replicaCount: 1
image:
  repository: nginx
  tag: "1.27"

# templates/deployment.yaml
metadata:
  name: {{ .Release.Name }}-nginx
spec:
  replicas: {{ .Values.replicaCount }}
  template:
    spec:
      containers:
        - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
The generated ResourceGraphDefinition
spec:
  schema:
    kind: Nginx
    spec:
      replicaCount: integer | default=1
      image:
        repository: string | default="nginx"
        tag: string | default="1.27"
  resources:
    - id: deployment
      template:
        metadata:
          name: ${schema.metadata.name}-nginx
        spec:
          replicas: ${schema.spec.replicaCount}
          template:
            spec:
              containers:
                - image: ${schema.spec.image.repository}:${schema.spec.image.tag}

Instances are named after themselves and isolated by an added selector label. Booleans that switch whole resources become includeWhen.

Built for platform teams

🎛️ Design a small API

Set platform opinions with Helm's own -f, choose the knobs with --expose. Exposure is strict: CI fails if a knob cannot be verified.

chart2kro convert ./app \
  -f platform.yaml \
  --expose 'image.tag,replicaCount' \
  --group apps.example.com -o api/

🚦 Keep it current in CI

One step in a pull request: fail when the committed RGD is stale or a chart upgrade would break existing instances.

- uses: hupe1980/chart2kro@v0.2.0
  with:
    chart: charts/app
    output-dir: api

🚚 Move releases to kro

Turn the values of a running Helm release into an instance of the new API. Unexposed values fail loudly instead of disappearing.

helm get values web -o yaml > web.yaml
chart2kro instance ./app \
  --from web.yaml --name web

Questions

Does chart2kro need a cluster, Helm or kubectl?

No. Loading, rendering, validation and verification all run in-process. You only need a cluster with kro to apply the result.

Which kro versions are supported?

The generated RGDs need kro v0.9.2 or newer. chart2kro validates with the kro v0.9.4 libraries, and its end-to-end tests run on both v0.9.2 and v0.9.4.

What happens to values chart2kro cannot translate?

They stay at their default in every instance and are listed with a reason, for example transformed when the chart passes the value through upper. See Limitations and Troubleshooting.

Is the conversion proven equivalent to the chart?

No tool can prove that for arbitrary templates. chart2kro validates each exposed value at concrete witnesses (the report counts them) and freezes anything that did not match Helm. It never guesses.

Can I use private registries and repositories?

Yes: oci:// references, repo/chart from your Helm repositories.yaml, --repo URLs, basic auth and TLS client certificates. Credentials are never written or logged.

Can I edit the generated RGD?

Yes, it is plain YAML. Most teams keep it generated instead: commit the output, and let convert --check and diff guard it in CI.

Convert your first chart in two minutes

Install the binary, point it at a chart, apply the result.