11package main
22
33import (
4- "compress/gzip"
54 "context"
65 "fmt"
7- "io"
86 "log"
97 "os"
108 "os/exec"
11- "strconv"
129 "time"
1310
1411 "github.com/google/go-containerregistry/pkg/authn"
@@ -21,35 +18,24 @@ import (
2118 cobra "github.com/spf13/cobra"
2219 corev1 "k8s.io/api/core/v1"
2320 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+ "k8s.io/apimachinery/pkg/util/wait"
2422 kvcorev1 "kubevirt.io/api/core/v1"
2523 v1beta1 "kubevirt.io/api/export/v1beta1"
2624 kubecli "kubevirt.io/client-go/kubecli"
2725 tar "kubevirt.io/containerdisks/pkg/build"
2826)
2927
3028const (
29+ pollInterval = 15 * time .Second
30+ pollTimeout = 3600 * time .Second
3131 diskPath string = "./tmp/disk.img.gz"
3232 diskPathDecompressed string = "./tmp/disk.img"
3333 diskPathConverted string = "./tmp/disk.qcow2"
3434)
3535
36- func applyVirtualMachineExport (vmNamespace , vmName string ) error {
36+ func applyVirtualMachineExport (client kubecli. KubevirtClient , vmNamespace , vmName string ) error {
3737 log .Println ("Applying VirtualMachineExport object..." )
3838
39- client , err := kubecli .GetKubevirtClient ()
40- if err != nil {
41- return err
42- }
43-
44- env := os .Getenv ("VM_NAMESPACE" )
45- if env != "" {
46- vmNamespace = env
47- }
48-
49- if vmNamespace == "" {
50- return fmt .Errorf ("VM namespace is not defined. Set VM_NAMESPACE or parameter." )
51- }
52-
5339 vmExport := & v1beta1.VirtualMachineExport {
5440 ObjectMeta : metav1.ObjectMeta {
5541 Name : vmName ,
@@ -64,68 +50,69 @@ func applyVirtualMachineExport(vmNamespace, vmName string) error {
6450 },
6551 }
6652
67- _ , err = client .VirtualMachineExport (vmNamespace ).Create (context .Background (), vmExport , metav1.CreateOptions {})
53+ _ , err : = client .VirtualMachineExport (vmNamespace ).Create (context .Background (), vmExport , metav1.CreateOptions {})
6854 return err
6955}
7056
71- func downloadVirtualMachineDiskImage (vmName , volumeName string ) error {
72- log .Printf ("Downloading disk image from %s Virtual Machine...\n " , vmName )
73-
74- cmd := exec .Command ("usr/bin/virtctl" , "vmexport" , "download" , vmName , "--vm" , vmName , "--volume" , volumeName , "--output" , diskPath )
75- cmd .Stdout = os .Stdout
76- cmd .Stderr = os .Stderr
57+ func getRawDiskUrlFromVirtualMachineExport (client kubecli.KubevirtClient , vmNamespace , vmName , volumeName string ) (string , error ) {
58+ log .Println ("Waiting for VirtualMachineExport to be ready..." )
7759
78- if err := cmd .Run (); err != nil {
79- return err
60+ vmExport , err := getVirtualMachineExportOnceReady (client , vmNamespace , vmName )
61+ if err != nil {
62+ return "" , err
8063 }
8164
82- if fileInfo , err := os . Stat ( diskPath ); err != nil || fileInfo . Size () == 0 {
83- return fmt .Errorf ("File does not exist or is empty ." )
65+ if vmExport . Status . Links == nil && vmExport . Status . Links . Internal == nil {
66+ return "" , fmt .Errorf ("No links found in VirtualMachineExport status ." )
8467 }
8568
86- log .Println ("Download completed successfully." )
87- return nil
88- }
89-
90- func decompressVirtualMachineDiskImage () error {
91- log .Println ("Decompressing downloaded disk image..." )
69+ for _ , volume := range vmExport .Status .Links .Internal .Volumes {
70+ if volumeName != volume .Name {
71+ continue
72+ }
9273
93- diskImg , err := os .Open (diskPath )
94- if err != nil {
95- return err
74+ for _ , format := range volume .Formats {
75+ if format .Format == v1beta1 .KubeVirtRaw {
76+ return format .Url , nil
77+ }
78+ }
9679 }
97- defer diskImg .Close ()
80+ return "" , fmt .Errorf ("Could not get raw disk URL from the VirtualMachineExport object." )
81+ }
9882
99- gzipReader , err := gzip .NewReader (diskImg )
100- if err != nil {
101- return err
102- }
103- defer gzipReader .Close ()
83+ func getVirtualMachineExportOnceReady (client kubecli.KubevirtClient , vmNamespace , vmName string ) (* v1beta1.VirtualMachineExport , error ) {
84+ var vmExport * v1beta1.VirtualMachineExport
10485
105- newDiskImg , err := os . Create ( diskPathDecompressed )
106- if err != nil {
107- return err
108- }
109- defer newDiskImg . Close ()
86+ poller := func ( ctx context. Context ) ( bool , error ) {
87+ vmExport , err := client . VirtualMachineExport ( vmNamespace ). Get ( ctx , vmName , metav1. GetOptions {})
88+ if err != nil {
89+ return false , err
90+ }
11091
111- _ , err = io .Copy (newDiskImg , gzipReader )
112- if err != nil {
113- return err
92+ if vmExport .Status .Phase == v1beta1 .Ready {
93+ return true , nil
94+ }
95+ return false , nil
11496 }
11597
116- err = os . Chmod ( diskPathDecompressed , 0666 ) // Grants read and write permission to everyone.
98+ err := wait . PollUntilContextTimeout ( context . Background (), pollInterval , pollTimeout , true , poller )
11799 if err != nil {
118- return err
100+ return nil , fmt . Errorf ( "Failed to wait for VirtualMachineExport to be ready: %v" , err )
119101 }
120-
121- log .Println ("Decompression completed successfully." )
122- return nil
102+ return vmExport , nil
123103}
124104
125- func convertRawDiskImageToQcow2 () error {
105+ func convertRawDiskImageToQcow2 (rawDiskUrl string ) error {
126106 log .Println ("Converting raw disk image to qcow2 format..." )
127107
128- cmd := exec .Command ("qemu-img" , "convert" , "-f" , "raw" , "-O" , "qcow2" , diskPathDecompressed , diskPathConverted )
108+ cmd := exec .Command (
109+ "nbdkit" ,
110+ "-r" ,
111+ "curl" ,
112+ rawDiskUrl ,
113+ "--run" ,
114+ fmt .Sprintf ("qemu-img convert \" $uri\" -O qcow2 %s" , diskPathConverted ),
115+ )
129116 cmd .Stdout = os .Stdout
130117 cmd .Stderr = os .Stderr
131118
@@ -141,29 +128,6 @@ func convertRawDiskImageToQcow2() error {
141128 return nil
142129}
143130
144- func prepareVirtualMachineDiskImage (enableVirtSysprep string ) error {
145- enabled , err := strconv .ParseBool (enableVirtSysprep )
146- if err != nil {
147- return err
148- }
149-
150- if ! enabled {
151- log .Println ("Skipping disk image preparation." )
152- return nil
153- }
154-
155- os .Setenv ("LIBGUESTFS_BACKEND" , "direct" )
156- cmd := exec .Command ("virt-sysprep" , "--format" , "qcow2" , "-a" , diskPathConverted )
157- cmd .Stdout = os .Stdout
158- cmd .Stderr = os .Stderr
159-
160- if err := cmd .Run (); err != nil {
161- return err
162- }
163-
164- return nil
165- }
166-
167131func buildContainerDisk (diskPath string ) (v1.Image , error ) {
168132 layer , err := tarball .LayerFromOpener (tar .StreamLayerOpener (diskPath ))
169133 if err != nil {
@@ -202,24 +166,31 @@ func pushContainerDisk(image v1.Image, imageDestination string, pushTimeout int)
202166 return nil
203167}
204168
205- func run (vmNamespace , vmName , volumeName , imageDestination , enableVirtSysprep string , pushTimeout int ) error {
206- if err := applyVirtualMachineExport (vmNamespace , vmName ); err != nil {
169+ func run (vmNamespace , vmName , volumeName , imageDestination string , pushTimeout int ) error {
170+ client , err := kubecli .GetKubevirtClient ()
171+ if err != nil {
207172 return err
208173 }
209174
210- if err := downloadVirtualMachineDiskImage (vmName , volumeName ); err != nil {
211- return err
175+ env := os .Getenv ("VM_NAMESPACE" )
176+ if env != "" {
177+ vmNamespace = env
178+ }
179+
180+ if vmNamespace == "" {
181+ return fmt .Errorf ("VM namespace is not defined. Set VM_NAMESPACE or parameter." )
212182 }
213183
214- if err := decompressVirtualMachineDiskImage ( ); err != nil {
184+ if err := applyVirtualMachineExport ( client , vmNamespace , vmName ); err != nil {
215185 return err
216186 }
217187
218- if err := convertRawDiskImageToQcow2 (); err != nil {
188+ rawDiskUrl , err := getRawDiskUrlFromVirtualMachineExport (client , vmNamespace , vmName , volumeName )
189+ if err != nil {
219190 return err
220191 }
221192
222- if err := prepareVirtualMachineDiskImage ( enableVirtSysprep ); err != nil {
193+ if err := convertRawDiskImageToQcow2 ( rawDiskUrl ); err != nil {
223194 return err
224195 }
225196
@@ -236,7 +207,6 @@ func main() {
236207 var vmName string
237208 var volumeName string
238209 var imageDestination string
239- var enableVirtSysprep string
240210 var pushTimeout int
241211
242212 var command = & cobra.Command {
@@ -245,7 +215,7 @@ func main() {
245215 Run : func (cmd * cobra.Command , args []string ) {
246216 log .Println ("Extracts disk and uploads it to a container registry..." )
247217
248- if err := run (vmNamespace , vmName , volumeName , imageDestination , enableVirtSysprep , pushTimeout ); err != nil {
218+ if err := run (vmNamespace , vmName , volumeName , imageDestination , pushTimeout ); err != nil {
249219 log .Panicln (err )
250220 }
251221
@@ -257,7 +227,6 @@ func main() {
257227 command .Flags ().StringVar (& vmName , "vmname" , "" , "name of the virtual machine" )
258228 command .Flags ().StringVar (& volumeName , "volumename" , "" , "volume name of the virtual machine" )
259229 command .Flags ().StringVar (& imageDestination , "imagedestination" , "" , "destination of the image in container registry" )
260- command .Flags ().StringVar (& enableVirtSysprep , "enablevirtsysprep" , "false" , "enable or disable virt-sysprep" )
261230 command .Flags ().IntVar (& pushTimeout , "pushtimeout" , 60 , "containerdisk push timeout in minutes" )
262231 command .MarkFlagRequired ("vmname" )
263232 command .MarkFlagRequired ("volumename" )
0 commit comments