feat: export macOS VMs as OCI cloud images - #27
Conversation
Change-Id: I70cc47e0593b5734a45987e8e7a000d66bbcdde8
78e06d8 to
d5ca633
Compare
CMGS
left a comment
There was a problem hiding this comment.
stepping back: this verb inverts the stack's image model, and i don't think it should land in any form.
- images here are immutable goldens minted OUTSIDE the runtime — cocoon-macos's come from the ci image pipeline (docs/image-pipeline.md: 'built entirely by CI'), cocoon's from docker-export →
image import. the runtime consumes images; it doesn't produce them. - cocoon's image surface is list / import / pull / rm / inspect — deliberately no
image export. what cocoon does have for moving vm-derived state between hosts issnapshot export/snapshot import(tar/dir, pairing withvm clone --from-dir). so both halves here break parity:vm exportmakes the runtime an image producer,image exportadds a verb the base system chose not to have. - the mechanical value is thin anyway:
image pullalready consumes what plainoras pushproduces (pickQcow2Layerprefers the.qcow2-titled layer — 'what oras push writes'), so publishing a customized disk is a documented 4-liner:
cocoon-macos vm stop m1
qemu-img convert -O qcow2 -c $STATE/vms/m1/disk.qcow2 out.qcow2 # convert flattens the backing chain
cocoon-macos vm start m1 [--vnc ...]
oras push ghcr.io/team/macos-custom:v1 out.qcow2
no new artifact format, no lock orchestration, and the #30 merge-ordering dependency evaporates.
suggestion: close this and add the recipe to docs/images.md. if cross-host macos vm movement becomes a real need, the cocoon-parity shape is snapshot export/import, not minting goldens from vms.
(if some form survives anyway, the body's 'upload latency does not extend downtime' sidesteps that downtime = the whole qemu-img convert -c of a 15-30 GB chain inside the vm lock — see inline.)
| } | ||
| } | ||
|
|
||
| return utils.RunQemuImg(ctx, "convert", "-p", "-f", "qcow2", "-O", "qcow2", "-c", r.Disk, tmpPath) |
There was a problem hiding this comment.
this runs inside the vm lock with the guest stopped — downtime is the full flatten+compress of the chain (minutes at 15-30 GB), not 'not the upload'. if the verb survives at all: freeze the delta instead — copy/reflink the overlay right after stop, restart immediately, convert the frozen copy outside the lock.
| const pullConns = 8 | ||
|
|
||
| const ( | ||
| artifactTypeOSImage = "application/vnd.cocoonstack.os-image.v1+json" |
There was a problem hiding this comment.
second artifact format for the same payload — image pull already consumes plain oras push output (pickQcow2Layer below: 'what oras push writes'). a custom artifactType/mediaType makes exported images the odd ones out next to the ci-published goldens.
| return nil | ||
| } | ||
|
|
||
| func (h *Handler) Export(cmd *cobra.Command, args []string) error { |
There was a problem hiding this comment.
cocoon's image surface has no export (list/import/pull/rm/inspect); vm-derived state moves via snapshot export/import there. no consumer, no counterpart — drop.
Summary
imageandvm exportcommandsLifecycle behavior
The destination is validated before the VM is stopped. A running VM is stopped under its lifecycle lock, flattened, and restarted before the registry upload begins, so upload latency does not extend downtime. A VM that was already stopped remains stopped.
Temporary files are synchronized before atomic replacement, and successful OCI publication is not misreported as a total failure if only the optional local retain step fails.
Merge ordering
#30 should land first. It makes a queued
vm startidempotent when export has already restarted QEMU and restores a missing VNC proxy without restarting a healthy guest.Validation
make lintgo test ./...