How `toValues.js` works
This page explains what src/app/toValues.js does and how it is structured, so you can safely extend it when you add new UI fields or chart features.
At a high level, toValues(state) takes the React app state from App.jsx and converts it into the JavaScript object that eventually becomes values.yaml for the Helm chart. It is the single place where we:
- Normalize UI shapes (strings, booleans, structured lists) into the values schema.
- Apply defaults and omit empty values.
- Keep the output clean and predictable.
Helper functions
The file starts with a few small helpers:
-
clean(v)
Recursively removes:null,undefined,""(empty string)- Empty arrays
[] - Empty objects
{}
This keeps
values.yamlminimal (no noisy empty fields). -
parseYaml(str)
Safely parses a YAML string into a JS value. Returnsnullif the string is empty or invalid. We use this for all YAML textareas in the UI. -
asList(v)
Normalizes to an array:null/undefined→null- Single value →
[value] - Array → unchanged
This is used when a YAML field can be either a single object or a list, but the chart expects an array.
buildWorkload(wl)
buildWorkload(wl) takes one workload object from React state and turns it into the corresponding entry under:
values.deployments[]values.statefulsets[]values.cronjobs[]
The steps inside buildWorkload mirror the workload sections in baseline.js:
-
Basic identity
w.name←wl.namew.imageis built fromwl.image.repository,tag,digest,pullPolicy(with defaults andclean).replicaCountis set for non‑CronJobs whenreplicas !== 1.
-
CronJob schedule
For
type === 'CronJob',scheduleYaml(YAML textarea) is parsed and split into:scheduleconcurrencyPolicysuccessfulJobsHistoryLimitfailedJobsHistoryLimitbackoffLimitrestartPolicysuspend
-
Service
Uses
wl.service.enabledplus the YAML fromwl.service.yamlto produce:service.enabled: falsewhen explicitly disabled.service.enabled: truewhen enabled with or without a spec (older charts withserviceEnabledDefault: falsealways emitenabled: true).- Spec fields from YAML merged on top when provided.
The
headlessfield has version-aware behavior controlled byopts.statefulsetHeadlessDefault:Chart statefulsetHeadlessDefaultHeadless checked Headless unchecked Not touched v1.0.0, v1.1.0 falseemits trueemits nothing emits nothing v1.2.0+ (absent) emits nothing (chart default) emits false(opt-out)emits nothing -
Route (OpenShift)
Only for non‑CronJobs and when
route.enabledis true:- Starts from advanced
route.yaml(parsed intospec). - Merges in:
hostfrom therouteHosttext field (if set).- TLS
certificateandkeyfromrouteTlsCert/routeTlsKey(if set), merged intoroute.tls.
This produces a
routeobject compatible withtemplates/route.yaml. - Starts from advanced
-
Ingress
Similar pattern to Route but for standard Kubernetes Ingress, from
ingress.enabledandingress.yaml. -
Ports, env, resources
ports(YAML) →w.ports(array)env(YAML) →w.env(array)envFrom(YAML) →w.envFrom(array)resources(YAML) →w.resources(object)
-
Command / args
YAML arrays for
commandandargsare normalized withasListintow.commandandw.args. -
Probes
From the single
probesYamltextarea we split into:livenessProbereadinessProbestartupProbe
-
StatefulSet‑specific fields
For
type === 'StatefulSet':stsYamlyieldsserviceNameandpodManagementPolicy.updateStrategyYAML →w.updateStrategy.vctYAML →w.volumeClaimTemplates(array).
-
Labels and advanced pod settings
labelsandpodLabelsYAML →w.labels,w.podLabels.podSecandsecCtxYAML →w.podSecurityContext,w.securityContext.
-
Volumes and mounts
There are two sources:
- Advanced YAML fields:
volMounts(YAML) → startingvolumeMountslist.extraVols(YAML) → startingextraVolumeslist.
- Structured UI lists:
volumeMounts(list of{name, mountPath, subPath}) → merged intovolumeMounts.extraVolumes(list of{name, type, resourceName}) → merged intoextraVolumesas:persistentVolumeClaim.claimNameconfigMap.namesecret.secretName
Finally,
volumeMountsandextraVolumesare attached towvia theadvobject. - Advanced YAML fields:
-
Sidecars, init containers, scheduling
From various YAML fields:
sidecarsandinitCtrs→w.sidecars,w.initContainers.nodeSel→w.nodeSelector.tolerationsYAML →w.tolerations(array).affinityYAML →w.affinity.grace→terminationGracePeriodSeconds(unless it’s the default 30s).
-
Autoscaling, KEDA, PDB, SCC, certificate
- HPA (
hpa.enabled,hpa.yaml) →w.autoscaling. - KEDA ScaledObject (
keda.soEnabled,keda.soYaml) →w.keda.scaledObject. - KEDA ScaledJob (
keda.sjEnabled,keda.sjYaml) →w.keda.scaledJob. - PDB (
pdb.enabled,pdb.yaml) →w.podDisruptionBudget. - SCC (
scc.enabled,scc.name) →w.scc. - Certificate (
cert.enabled,cert.yaml) →w.certificate.
- HPA (
At the end, we return clean(w) so empty parts are removed.
toValues(state)
toValues takes the full app state from App.jsx:
const values = toValues({
workloads,
configMaps,
secrets,
sealedSecrets,
pvcs,
global,
persistentVolumes,
extraManifests,
})
and builds the final values object in these steps:
-
Split workloads by type
const deployments = workloads.filter(w => w.type === 'Deployment').map(buildWorkload)
const statefulsets = workloads.filter(w => w.type === 'StatefulSet').map(buildWorkload)
const cronjobs = workloads.filter(w => w.type === 'CronJob').map(buildWorkload)Non‑empty arrays are assigned to:
values.deploymentsvalues.statefulsetsvalues.cronjobs
-
Global settings
- ServiceAccount (
global.saCreate,global.saName) →values.serviceAccountwithcreateandname. - RBAC (
global.rbacCreate,global.rbacRules) →values.rbacwithcreateandrules(array). - Image pull secrets (
global.pullSecretsYAML) →values.imagePullSecrets(array).
- ServiceAccount (
-
Shared resources
These come from the shared ResourceList sections:
- ConfigMaps:
configMaps[]→values.configMaps = [{ name, data }] - Secrets:
secrets[]→values.secrets = [{ name, ...specFromYaml }] - Sealed Secrets:
sealedSecrets[]→values.sealedSecrets = [{ name, encryptedData }] - PVCs:
pvcs[]→values.persistentVolumeClaims = [{ name, size, storageClassName, accessModes }]
- ConfigMaps:
-
PersistentVolumes and extra manifests
persistentVolumesandextraManifestsare lists of entries with YAML inside. The helperflattenYamlItems:- Parses each
.yamlstring. - Normalizes single vs list using
asList. - Flattens everything into:
values.persistentVolumesvalues.extraManifests
- Parses each
-
Final clean
The function returns
valuesas‑is (it has already been cleaned where needed). When Docusaurus shows thevalues.yamlpreview, it comes from this object.
When you add or change fields
When you update the UI config (e.g. in baseline.js) you usually need to:
-
Decide where the data lives in state
- For per‑workload fields: add to
newWorkloadinApp.jsx. - For shared resources: add a new ResourceList section and corresponding state in
App.jsx.
- For per‑workload fields: add to
-
Teach
buildWorkloadortoValueshow to emit it- For simple scalar/YAML fields, parse with
parseYamland assign tow.<field>orvalues.<field>. - For lists, normalize with
asList. - Use
cleanwhen you want to drop empty values.
- For simple scalar/YAML fields, parse with
-
Keep complexity here, not in JSX
JSX components (WorkloadForm,SectionPanel,ResourceList) are generic. Business logic about how UI → values.yaml should live intoValues.js, so chart maintainers can reason about it in one place.