fix(tiller): register YAML representer for ImageRef #5

Merged
arrdem merged 1 commit from arrdem/imageref-yaml-representer into trunk 2026-04-24 06:45:38 +00:00
Contributor

Summary

ImageRef values auto-unwrap from OpaquePythonObject at the Starlark→Python boundary, so they reach the rendered manifest tree as raw dataclass instances. Without a custom YAML representer, PyYAML emits them as the !!python/object:tiller.types.ImageRef tag shape, which is invalid Kubernetes YAML.

This was fine in practice for all existing Tillerfiles because the builder functions (deployment, statefulset, etc.) convert their image= argument via str(image) before the value enters the manifest. But any user-composed dict — e.g. extra_containers=[{"image": some_ref}] for a sidecar — skips that conversion and reaches yaml.dump with the raw ImageRef.

Change

Register _imageref_representer on the dumper to emit str(imageref) (already correctly defined as repo:tag or repo@digest). Users can now pass ImageRef values anywhere in a manifest without needing to stringify manually.

Motivation

A downstream flowmetal Tillerfile wanted to add a chaos sidecar to a Deployment via extra_containers=; it was forced to type chaos_image as a plain str input to avoid the rendering bug, losing the tag/digest semantics ImageRef would provide.

Test plan

Three new tests in tests/test_render.py:

  • test_imageref_in_image_kwarg — baseline: ImageRef passed to deployment(image=) still renders as repo:tag.
  • test_imageref_in_extra_container — defect case: ImageRef inside a raw extra_containers=[{...}] dict now renders correctly, and the rendered YAML contains no !!python/object tag (explicit regression guard).
  • test_imageref_digest — digest form (repo@sha256:...) also works.

242 tests pass in the full tiller suite.

### Summary `ImageRef` values auto-unwrap from `OpaquePythonObject` at the Starlark→Python boundary, so they reach the rendered manifest tree as raw dataclass instances. Without a custom YAML representer, PyYAML emits them as the `!!python/object:tiller.types.ImageRef` tag shape, which is invalid Kubernetes YAML. This was fine in practice for all existing Tillerfiles because the builder functions (`deployment`, `statefulset`, etc.) convert their `image=` argument via `str(image)` before the value enters the manifest. But any user-composed dict — e.g. `extra_containers=[{"image": some_ref}]` for a sidecar — skips that conversion and reaches `yaml.dump` with the raw ImageRef. ### Change Register `_imageref_representer` on the dumper to emit `str(imageref)` (already correctly defined as `repo:tag` or `repo@digest`). Users can now pass ImageRef values anywhere in a manifest without needing to stringify manually. ### Motivation A downstream flowmetal Tillerfile wanted to add a chaos sidecar to a Deployment via `extra_containers=`; it was forced to type `chaos_image` as a plain `str` input to avoid the rendering bug, losing the tag/digest semantics `ImageRef` would provide. ### Test plan Three new tests in `tests/test_render.py`: - `test_imageref_in_image_kwarg` — baseline: ImageRef passed to `deployment(image=)` still renders as `repo:tag`. - `test_imageref_in_extra_container` — defect case: ImageRef inside a raw `extra_containers=[{...}]` dict now renders correctly, and the rendered YAML contains no `!!python/object` tag (explicit regression guard). - `test_imageref_digest` — digest form (`repo@sha256:...`) also works. 242 tests pass in the full tiller suite.
ImageRef values auto-unwrap from OpaquePythonObject at the
Starlark->Python boundary, so they reach the rendered manifest
tree as raw dataclass instances. Without a custom representer
PyYAML emits them as the `!!python/object:tiller.types.ImageRef`
tag shape, which is invalid Kubernetes YAML.

This was fine in practice for all existing Tillerfiles because
the builder functions (`deployment`, `statefulset`, etc.) convert
their `image=` argument via `str(image)` before the value enters
the manifest. But any user-composed dict — e.g.
`extra_containers=[{"image": some_ref}]` for a sidecar — skips
that conversion and reaches yaml.dump with the raw ImageRef.

Register `_imageref_representer` on the dumper to emit
`str(imageref)` (which is already correctly defined as
`repo:tag` or `repo@digest`). Users can now pass ImageRef
values anywhere in a manifest without needing to stringify
manually.

Prompted by a downstream Tillerfile that wanted to add a chaos
sidecar to a Deployment; it was forced to type `chaos_image` as
a plain `str` input to avoid the rendering bug, losing the
tag/digest semantics ImageRef would provide.

Three new tests in tests/test_render.py pin the behavior:
- ImageRef passed to `deployment(image=)`.
- ImageRef inside a raw `extra_containers=[{...}]` dict.
- ImageRef with a digest rather than a tag.
The extra_containers test also asserts the rendered YAML
contains no `!!python/object` tag — an explicit regression
guard on the class of bug.
arrdem merged commit 134207fdf8 into trunk 2026-04-24 06:45:38 +00:00
arrdem deleted branch arrdem/imageref-yaml-representer 2026-04-24 06:45:44 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
arrdem/source!5
No description provided.