I'm building a form editor for the Kubernetes resources in the Kube.ist console, somewhere to change an image tag, add a replica or adjust a memory limit. These are ordinary application jobs, and I'd like them to be easy to reach while I'm managing the deployment. I can read YAML. I don't want to reconstruct a whole deployment in my head every time I change one value. A box for replicas, rows for labels, a control for the memory amount. That is the sort of editing experience I'm aiming for. The API loads YAML into a form model and saves the edited model back.

I'd like to open a resource, change one thing and save it. That leaves the translator responsible for all the other values too, including ones I haven't looked at.

YAML → form → YAML looks straightforward on paper. Then I have to make sure 25% still means the same thing when it comes back.

Take maxSurge, which controls the extra replicas permitted during an update. 25% and 25 describe different allowances. JavaScript's parseInt will happily turn the first into the second:

console.log(parseInt("25%", 10)); // 25

With four desired replicas, 25% permits one extra replica during an update. A plain 25 permits twenty-five.

The call succeeded, well, it returned a number. Nothing looks broken. I'd certainly notice the difference when twenty-five extra replicas were allowed to start.

Take a memory value such as 1.5Gi. The decimal part and the suffix both matter. This small splitting helper shows how easily we can lose that distinction:

const splitResource = value =>
  value.match(/([\d]+)([\w|%]+)/).splice(1);

console.log(splitResource("1.5Gi")); // ["5", "Gi"]

The expression finds a run of digits followed by word characters, % or a literal |. It isn't anchored to the beginning of the string, so when the dot prevents a match starting at 1 the search continues and finds 5Gi. The form can show 5 in the amount box and Gi in the unit box without looking broken, then saving joins them into 5Gi and the original fraction has gone. I'd almost prefer a crash here. I'd have to look at a crash. A perfectly respectable memory value can make it all the way to deployment. A Deployment percentage belongs to a different field contract, even though a casual string helper can make memory amounts and replica allowances both look like “number plus unit”.

Kubernetes quantities have their own syntax, including decimal values and recognised suffixes. CPU values such as 500m need that syntax too. The resource documentation gives the accepted forms.

Keep the original value as text if the friendlier control would change its meaning. I can live with a text box.

If I do split it into controls, the conversion has to understand the entire accepted input. An unrecognised value needs an explicit way to remain intact or an error the user can act on.

The form needs information the file doesn't

A label map is a different kind of translation:

{"team": "platform", "environment": "test"}

The form wants a list of editable rows. Each row needs a stable identity so changing its key doesn't make the interface confuse it with another row:

[
  {"row": "a", "key": "team", "value": "platform"},
  {"row": "b", "key": "environment", "value": "test"}
]

Those row identifiers belong to the editing session. They aren't Kubernetes labels and should disappear on save, leaving the actual keys and values.

A temporary empty row may need to disappear too, while two rows with the same label key need a deliberate validation rule.

Change team to owner. That should still be row a. I want to rename a label without its row jumping around, so I'm adding information for the form which Kubernetes has no use for.

A name needs parsing too

Image fields have another problem. One character can have different jobs. In registry.local:5000/app:1.2, a colon separates the registry port and another separates the tag. Split on every colon and I get three pieces with no clue which one I'm editing. The convenience of separate fields has brought a grammar along with it. More controls, more conversion rules, another parser.... So much for a friendly box.

Testing the trip

The loader and saver need testing in both directions. A fixture can start with a supported manifest, load it into the form model, save it, and compare the result with the expected manifest.

Before writing the expected file, I have to choose what the test should preserve. Am I happy for the indentation and quotes to change? What about comments? Plain objects won't keep all of that. Saving the file and approving the result would let the test make that choice for me.

A few test inputs make that distinction visible:

InputWhat the test protects
maxSurge: "25%"Percentage remains a percentage
memory: "1.5Gi"Fraction and unit survive together
image: registry.local:5000/app:1.2Port and tag retain their separate roles
Two editable label rowsRow identities stay out of the manifest

I'd open the golden-file diff and read it as an edit somebody asked me to deploy. Did we change the requested field? What else moved? A test named “round trip” is quite capable of protecting 5Gi forever if I approve that as its expected output.

The other direction matters as well. Start with a valid form model, save it, then load it again. That checks whether the form will recognise its own output. Fixtures for fields outside the editor's model need a clear policy of their own.

What about a field the form doesn't understand? If I drop it when loading, an unrelated edit can remove it from the saved manifest. I'd need to keep those parts or make the limitation clear before somebody saves. Opening the form shouldn't take those choices away from them.

So I'm still asking for the friendly boxes, with more work behind them than I first wanted. If I open a manifest to change its label, its memory limit should be none of my business.

This entry came over from blog.haddon.me. I’ve tidied up the grammar and flow, bringing it closer to how I write now. I used AI to help with the grammar, structure and flow. I have dyslexia, and my writing often comes out as a rambling scrawl, so it can be hard to see what I was actually working on at the time. I think the tidied versions are easier to digest and more interesting to read. The artefacts got the same tidy-up while I was at it.