11package vm
22
33import (
4+ "context"
45 "encoding/json"
56 "fmt"
67 "net"
@@ -13,31 +14,63 @@ import (
1314
1415 "github.com/spf13/cobra"
1516
17+ "github.com/cocoonstack/cocoon/config"
18+ "github.com/cocoonstack/cocoon/images/cloudimg"
19+ "github.com/cocoonstack/cocoon/types"
20+ "github.com/cocoonstack/cocoon/utils"
21+
1622 "github.com/cocoonstack/cocoon-macos/qemu"
1723)
1824
25+ // resolveBase returns the immutable base qcow2 for IMAGE: a direct filesystem path (legacy), else
26+ // an image ref resolved through cocoon's cloudimg store (returns the content-addressed blob + its
27+ // digest). Per-VM overlays are baked on this base, which stays read-only.
28+ func resolveBase (cmd * cobra.Command , image , name string ) (string , string , error ) {
29+ if _ , err := os .Stat (image ); err == nil {
30+ return image , "" , nil
31+ }
32+ ctx := cmd .Context ()
33+ if ctx == nil {
34+ ctx = context .Background ()
35+ }
36+ store , err := cloudimg .New (ctx , & config.Config {RootDir : stateDir (cmd ), DNS : "8.8.8.8,1.1.1.1" })
37+ if err != nil {
38+ return "" , "" , err
39+ }
40+ vm := & types.VMConfig {Config : types.Config {Image : image }, Name : name }
41+ sc , _ , err := store .Config (ctx , []* types.VMConfig {vm })
42+ if err != nil {
43+ return "" , "" , fmt .Errorf ("resolve image %q (not a file, not in the store): %w" , image , err )
44+ }
45+ if len (sc ) == 0 || len (sc [0 ]) == 0 {
46+ return "" , "" , fmt .Errorf ("image %q resolved to no disk" , image )
47+ }
48+ return sc [0 ][0 ].Path , vm .ImageDigest , nil
49+ }
50+
1951// Handler implements Actions by cloning a golden macOS qcow2 (copy-on-write overlay)
2052// and launching qemu-system-x86_64 on an x86 Linux/KVM host.
2153type Handler struct {}
2254
2355func NewHandler () * Handler { return & Handler {} }
2456
2557type record struct {
26- Name string `json:"name"`
27- Image string `json:"image"`
28- Disk string `json:"disk"`
29- OpenCore string `json:"opencore"`
30- OVMFCode string `json:"ovmf_code"`
31- OVMFVars string `json:"ovmf_vars"`
32- CPUs int `json:"cpus"`
33- Memory string `json:"memory"`
34- VNCDisp int `json:"vnc"`
35- SSHPort int `json:"ssh_port"`
36- PID int `json:"pid"`
37- Created string `json:"created"`
38- MAC string `json:"mac,omitempty"`
39- SMBIOS * qemu.SMBIOS `json:"smbios,omitempty"`
40- VNCPass string `json:"vnc_password,omitempty"`
58+ Name string `json:"name"`
59+ Image string `json:"image"`
60+ ImageDigest string `json:"image_digest,omitempty"`
61+ Disk string `json:"disk"`
62+ OpenCore string `json:"opencore"`
63+ OVMFCode string `json:"ovmf_code"`
64+ OVMFVars string `json:"ovmf_vars"`
65+ CPUs int `json:"cpus"`
66+ Memory string `json:"memory"`
67+ VNCDisp int `json:"vnc"`
68+ SSHPort int `json:"ssh_port"`
69+ PID int `json:"pid"`
70+ Created string `json:"created"`
71+ MAC string `json:"mac,omitempty"`
72+ SMBIOS * qemu.SMBIOS `json:"smbios,omitempty"`
73+ VNCPass string `json:"vnc_password,omitempty"`
4174}
4275
4376func stateDir (cmd * cobra.Command ) string {
@@ -93,9 +126,18 @@ func (h *Handler) create(cmd *cobra.Command, image string) (*record, error) {
93126 if err := os .MkdirAll (dir , 0o755 ); err != nil {
94127 return nil , err
95128 }
129+ base , digest , err := resolveBase (cmd , image , name )
130+ if err != nil {
131+ return nil , err
132+ }
96133 overlay := filepath .Join (dir , "disk.qcow2" )
97- if out , err := exec .Command ("qemu-img" , "create" , "-f" , "qcow2" , "-b" , image , "-F" , "qcow2" , overlay ).CombinedOutput (); err != nil {
98- return nil , fmt .Errorf ("qemu-img create overlay: %v: %s" , err , out )
134+ ctx := cmd .Context ()
135+ if ctx == nil {
136+ ctx = context .Background ()
137+ }
138+ // bake a per-VM CoW overlay on the immutable base (cocoon's storage convention; base stays RO)
139+ if err := utils .RunQemuImg (ctx , "create" , "-f" , "qcow2" , "-F" , "qcow2" , "-b" , base , overlay ); err != nil {
140+ return nil , fmt .Errorf ("bake overlay on %s: %w" , base , err )
99141 }
100142 ovmfVars := filepath .Join (dir , "OVMF_VARS.fd" )
101143 if err := copyFile (varsTmpl , ovmfVars ); err != nil {
@@ -107,7 +149,7 @@ func (h *Handler) create(cmd *cobra.Command, image string) (*record, error) {
107149 ssh , _ := cmd .Flags ().GetInt ("ssh-port" )
108150 vncPass , _ := cmd .Flags ().GetString ("vnc-password" )
109151 r := & record {
110- Name : name , Image : image , Disk : overlay , OpenCore : oc , OVMFCode : code , OVMFVars : ovmfVars ,
152+ Name : name , Image : image , ImageDigest : digest , Disk : overlay , OpenCore : oc , OVMFCode : code , OVMFVars : ovmfVars ,
111153 CPUs : cpus , Memory : mem , VNCDisp : vnc , SSHPort : ssh , VNCPass : vncPass , Created : time .Now ().Format (time .RFC3339 ),
112154 }
113155 if random , _ := cmd .Flags ().GetBool ("random-smbios" ); random {
0 commit comments