You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* snapshot: extract ReadSnapshotEnvelope shared helper
Move the canonical snapshot.json envelope read/write into the snapshot
package so cmd/vm can consume an envelope-bearing directory directly
(needed for the upcoming \`vm clone --from-dir\` and
\`vm restore --from-dir\` paths) without depending on localfile internals.
- snapshot.SnapshotJSONName + envelopeVersion (single const block).
- snapshot.ReadSnapshotEnvelope(dir) parses + version-checks; returns
ErrEnvelopeMissing when the file is absent so callers can surface a
precise message instead of a generic open error.
- snapshot.WriteSnapshotEnvelope(dir, cfg) writes via AtomicWriteJSON.
- localfile/export.go now references snapshot.SnapshotJSONName.
- localfile/import.go readAndRemoveSnapshotJSON delegates the parse to
the shared helper; remove-after-read semantics preserved.
* snapshot: add export --to-dir for directory-based handoff
Adds the snapshot.DirectoryExporter optional interface and a
LocalFile.ExportToDir implementation. The dir form pairs with the
upcoming \`vm clone --from-dir\` and \`vm restore --from-dir\`,
giving users an rsync-friendly handoff path without a tar round-trip.
- snapshot.DirectoryExporter: ExportToDir(ctx, ref, dir) error
- LocalFile.ExportToDir: rejects non-empty target dirs to avoid silently
merging into an unrelated tree, writes a fresh snapshot.json envelope
via WriteSnapshotEnvelope, then ReflinkCopy's each data file. Reflink
is zero-cost on btrfs/xfs and falls back to plain copy elsewhere; the
result is always standalone (rsync-friendly), unlike the hardlinks
DirectClone uses internally for memory pages.
- cmd/snapshot: --to-dir flag, mutually exclusive with --output/--gzip.
* vm: clone --from-dir for arbitrary snapshot directories
Lets users clone from any directory containing a snapshot.json envelope
(e.g. an extracted export.tar, an rsync'd \`snapshot export --to-dir\`
result, or an NFS-mounted golden image) without first registering the
snapshot in the local DB.
- cloneCmd args relaxed to MaximumNArgs(1); positional SNAPSHOT and
--from-dir are mutually exclusive (validated in the handler so the
error message names both forms).
- cloneFromDir reads the envelope, routes to the matching backend by
cfg.Hypervisor, asserts hypervisor.Direct, then funnels through the
same DirectClone path as the snapshot-DB form.
- cloneDirect refactored to share cloneFromSrcDir with cloneFromDir so
the prepareClone → DirectClone → finalize sequence isn't duplicated.
The dir is read-only across the call so multiple clones of the same dir
(golden image use case) are safe. Trust boundary unchanged: existing
ValidateMetaPaths still rejects envelopes whose storage paths escape
the local rootDir/runDir.
* vm: restore --from-dir with --force ownership gate
Symmetric with \`vm clone --from-dir\` but with an extra safety check:
the envelope's snapshot ID must belong to vm.SnapshotIDs unless --force.
This keeps the typical cross-host "sync the same VM's backup over here"
flow zero-friction while making "use unrelated state to overwrite this
VM" an explicit, opt-in operation (data-loss footgun otherwise).
- restoreCmd args relaxed to RangeArgs(1, 2); positional SNAPSHOT and
--from-dir are mutually exclusive (handler-validated).
- restoreFromDir reads the envelope, resolves the VM's hypervisor,
asserts hypervisor.Direct, runs the same NIC-count + resource-flag
validation as the snapshot-DB form, then DirectRestore over the dir.
- --force does NOT add the envelope's ID to vm.SnapshotIDs — restoring
doesn't change ownership semantics, the foreign snapshot remains
external to this VM's lineage.
* docs: --from-dir + --to-dir directory-based snapshot workflow
Bullet point in features + extended export flag table + a worked example
covering the three flows: golden image clone, cross-host same-VM
restore, force-overriding a foreign-lineage restore.
* style: trim verbose godoc on the new envelope/clone/restore paths
Comments per SKILL.md should explain WHY, not narrate or restate the
signature. This pass keeps the load-bearing rationale (atomic write
reason, soft-gate semantics, reflink-vs-hardlink trade-off) and
collapses the rest.
* snapshot: write envelope last in ExportToDir as completion marker
ExportToDir wrote snapshot.json before copying payload files. A
concurrent \`vm restore --from-dir\` reading the envelope could pass
preflight (which only stat()s presence, not byte-completeness) and kill
the running VM while a memory-range or COW file was still mid-copy,
leaving the source VM in Error state with a partial restore.
Move WriteSnapshotEnvelope to after the copy loop so envelope existence
is now an all-data-ready marker. ensureEmptyDir already guarantees the
dir starts empty, so a stale envelope can't survive into the new
export.
* snapshot: envelope helpers take/return SnapshotConfig by value
Returning *types.SnapshotConfig forced an awkward
\`return &envelope.Config\` and a \`*cfg\` deref inside Write.
SnapshotConfig is small (no identity, embedded Config + a few scalars +
a map ref) so copying is cheap; switching to value drops the pointer
dance in the helper bodies. Callers that need a pointer downstream now
take \`&cfg\` once at the call site, which is symmetric with how every
other local SnapshotConfig in cocoon is handled.
readAndRemoveSnapshotJSON in snapshot/localfile/import.go follows the
same shift; the Import flow now embeds cfg by value into the
SnapshotRecord literal instead of dereferencing.
* snapshot+vm: SnapshotConfig flows by value through read-only paths
snapshot.Direct.DataDir, snapshotRecordToConfig, CloneVMConfigFromFlags,
RestoreVMConfigFromFlags, mergeResourceFlags, prepareClone,
cloneFromSrcDir, validateFCCloneOverrides — all of these previously
took or returned *types.SnapshotConfig but only ever read fields. The
chain forced \`return &cfg\` / \`*cfg\` / \`&cfg\` dances at every layer.
Switch to value semantics inside the cmd-layer + snapshot-backend
boundary; the hypervisor backend interface (Clone / DirectClone) keeps
*SnapshotConfig because that's the wider stable API surface, and the
single deref happens at that boundary. SnapshotConfig is small (no
identity, embedded Config + a few scalars + a map ref) so the copy is
cheap and pointer-only mattered for sharing semantics that nothing
actually used here.
* snapshot: Snapshot.Restore returns SnapshotConfig by value
Mirrors the DataDir flip: the snap-DB-side Restore was the only place
in the cmd/vm clone path that still forced \`*cfg\`/\`&cfg\` dancing.
Drop the pointer return on the interface, value-out from LocalFile, and
the cmd-side caller derefs once at the hypervisor boundary just like
DirectClone.
* vm: extract snapshotSource helper for --from-dir vs positional SNAPSHOT
Clone and Restore had near-identical mutex / required-arg blocks; the
only difference was the leading-positional count (0 for clone where
SNAPSHOT is the only arg, 1 for restore where args[0] is VM). Capture
that as baseArgs and let the helper return (fromDir, snapRef) with
exactly one non-empty.
* snapshot: inline ensureEmptyDir into ExportToDir
Single caller (the new ExportToDir); the helper added a function-call
indirection without earning it. Inline the 8-line check; the comment
above the block carries the rationale (no silent merge into an unrelated
tree).
* vm+snapshot: /code+/simplify followups
- ExportToDir: replace inline mkdir + 3-state switch with utils.EnsureDirs
(existing 0o750 helper); drop the dead snapshot.json skip in the copy
loop since registered DataDir results never carry the envelope (Create
doesn't write it; Import removes it via readAndRemoveSnapshotJSON).
- run.go: 'nic count mismatch' -> 'NIC count mismatch' (ALL-CAPS acronym
rule); fixes both restoreFromDir and the pre-existing legacy Restore
message for consistency.
- cloneFromDir: copy *config.Config locally before flipping
UseFirecracker so the mutation doesn't leak to the caller's shared
conf (CLI tolerates it; daemons embedding cocoon would notice).
- restoreCmd: PreRunE rejects --force without --from-dir so misuse fails
loud instead of silently no-op.
* vm: extract runDirectRestore to share between restoreDirect and restoreFromDir
restoreDirect (snapshot-DB path) and restoreFromDir (--from-dir path)
duplicated the wantJSON-log + DirectRestore + output tail. Pull it out
into a single helper, parameterized by sourceLabel. -7 lines net.
* snapshot: export EnvelopeVersion + add DirectoryExporter compile-time check
Two strict-walk findings:
1. snapshot/localfile/export.go:80 had a literal Version: 1 that should
reference the snapshot package's version constant. Promote
envelopeVersion -> EnvelopeVersion (exported) and use
snapshot.EnvelopeVersion at the cross-package call site. Future
format bumps now have one source of truth.
2. localfile.LocalFile didn't carry a compile-time assertion against
snapshot.DirectoryExporter (the new interface). Add it alongside the
existing Snapshot/Direct/CompressedExporter checks so signature drift
in either direction surfaces at build time.
-**Snapshot & clone** — `cocoon snapshot save` captures a running VM's full state (memory, disks, config); `cocoon vm clone` restores it as a new VM with fresh network and identity, resource inheritance with validation
23
-
-**Snapshot export & import** — `cocoon snapshot export` packages a snapshot into a portable `.tar.gz` archive (with sparse-aware pax headers); `cocoon snapshot import` restores it on another host or cluster; supports piping via stdout/stdin for direct host-to-host transfer
23
+
-**Snapshot export & import** — `cocoon snapshot export` packages a snapshot into a portable `.tar.gz` archive (with sparse-aware pax headers); `cocoon snapshot import` restores it on another host or cluster; supports piping via stdout/stdin for direct host-to-host transfer; `--to-dir` writes a directory form (with `snapshot.json` envelope) for NFS / rsync-friendly handoff
24
+
-**Clone / restore from a directory** — `cocoon vm clone --from-dir DIR` and `cocoon vm restore --from-dir DIR` consume any directory containing a `snapshot.json` envelope without first registering the snapshot in the local DB; the dir is treated as read-only so multi-VM golden-image use cases work without copying
24
25
-**Live status monitoring** — `cocoon vm status` watches VM state changes in real time via fsnotify, with refresh mode (top-like) and event-stream mode (append-only, for scripting and vk-cocoon integration)
|`--output`, `-o`|`<name-or-id>.tar.gz`| Output file path (`-` for stdout) |
233
+
|`--gzip`|`false`| Compress output with gzip |
234
+
|`--to-dir`|| Export into a directory (must be empty/absent) instead of a tar; pairs with `vm clone --from-dir`|
235
+
236
+
`--to-dir` writes a `snapshot.json` envelope alongside reflink-copied data files. Useful for NFS golden images or rsync-friendly handoff: `cocoon snapshot export snap -o ... --to-dir /nfs/golden && rsync ...`. Mutually exclusive with `--output` and `--gzip`.
232
237
233
238
### Import Flags
234
239
@@ -241,6 +246,25 @@ Applies to `cocoon snapshot import`:
241
246
242
247
When FILE is omitted, data is read from stdin. This enables piping: `cocoon snapshot export snap1 -o - | ssh host2 cocoon snapshot import --name snap1`.
243
248
249
+
### Direct Clone / Restore From a Directory
250
+
251
+
`vm clone --from-dir DIR` and `vm restore --from-dir DIR` accept any directory containing a `snapshot.json` envelope (output of `snapshot export --to-dir`, or an extracted `.tar`). The snapshot does not need to be in the local snapshot DB:
252
+
253
+
```bash
254
+
# Build a portable snapshot dir, ship it, clone from it:
cocoon vm clone --from-dir /nfs/golden --name fresh-vm --pull
258
+
259
+
# Restore the same VM's externally-staged backup (envelope ID matches → silent OK):
260
+
cocoon vm restore my-vm --from-dir /sync/from-host-a
261
+
262
+
# Force-restore a foreign snapshot (acknowledges data-loss risk):
263
+
cocoon vm restore my-vm --from-dir /unrelated/lineage --force
264
+
```
265
+
266
+
The dir is read-only across the call, so multiple clones of the same dir (golden image use case) are safe. Pass `--pull` if the base image's blobs may not be present locally — `EnsureImage` reads `image_blob_ids` from the envelope and pulls as needed.
restoreCmd.Flags().String("storage", "", "COW disk size (empty = keep current)")
122
132
restoreCmd.Flags().Bool("on-demand", false, "use UFFD on-demand memory loading for faster restore (CH only; snapshot file must remain on disk)")
133
+
restoreCmd.Flags().String("from-dir", "", "restore from a snapshot directory (must contain snapshot.json) instead of the local snapshot DB; mutually exclusive with positional SNAPSHOT")
134
+
restoreCmd.Flags().Bool("force", false, "skip the snapshot-belongs-to-VM check (only meaningful with --from-dir; risk of restoring to an unrelated lineage)")
cmd.Flags().Bool("no-direct-io", false, "disable O_DIRECT on writable disks (inherit from snapshot if not set)")
260
272
cmd.Flags().Bool("on-demand", false, "use UFFD on-demand memory loading for faster clone (CH only; snapshot file must remain on disk)")
261
273
cmd.Flags().Bool("pull", false, "auto-pull base image if not found locally (for cross-node clone)")
274
+
cmd.Flags().String("from-dir", "", "clone from a snapshot directory (must contain snapshot.json) instead of the local snapshot DB; mutually exclusive with positional SNAPSHOT")
0 commit comments