-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow specifying download format in vmexport #10400
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ | |
package vmexport | ||
|
||
import ( | ||
"compress/gzip" | ||
"context" | ||
"crypto/tls" | ||
"crypto/x509" | ||
|
@@ -70,6 +71,8 @@ const ( | |
SNAPSHOT_FLAG = "--snapshot" | ||
INSECURE_FLAG = "--insecure" | ||
KEEP_FLAG = "--keep-vme" | ||
RAW_FLAG = "--raw" | ||
DECOMPRESS_FLAG = "--decompress" | ||
PVC_FLAG = "--pvc" | ||
TTL_FLAG = "--ttl" | ||
MANIFEST_FLAG = "--manifest" | ||
|
@@ -127,7 +130,9 @@ var ( | |
shouldCreate bool | ||
includeSecret bool | ||
exportManifest bool | ||
rawImg bool | ||
portForward bool | ||
decompress bool | ||
localPort string | ||
serviceUrl string | ||
volumeName string | ||
|
@@ -153,6 +158,8 @@ type VMExportInfo struct { | |
KeepVme bool | ||
IncludeSecret bool | ||
ExportManifest bool | ||
RawImg bool | ||
Decompress bool | ||
PortForward bool | ||
LocalPort string | ||
OutputFile string | ||
|
@@ -261,6 +268,8 @@ func NewVirtualMachineExportCommand(clientConfig clientcmd.ClientConfig) *cobra. | |
cmd.Flags().StringVar(&localPort, "local-port", "0", "Defines the specific port to be used in port-forward.") | ||
cmd.Flags().BoolVar(&includeSecret, "include-secret", false, "When used with manifest and set to true include a secret that contains proper headers for CDI to import using the manifest") | ||
cmd.Flags().BoolVar(&exportManifest, "manifest", false, "Instead of downloading a volume, retrieve the VM manifest") | ||
cmd.Flags().BoolVar(&rawImg, "raw", false, "Used to download the raw image. By default, we always attempt to download the compressed one") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be better to always download the compressed file but have virtctl unzip while downloading/writing to the target There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just added a |
||
cmd.Flags().BoolVar(&decompress, "decompress", false, "Used to unzip the compressed image.") | ||
cmd.SetUsageTemplate(templates.UsageTemplate()) | ||
|
||
return cmd | ||
|
@@ -350,6 +359,8 @@ func (c *command) initVMExportInfo(vmeInfo *VMExportInfo) error { | |
vmeInfo.ShouldCreate = shouldCreate | ||
vmeInfo.Insecure = insecure | ||
vmeInfo.KeepVme = keepVme | ||
vmeInfo.RawImg = rawImg | ||
vmeInfo.Decompress = decompress | ||
vmeInfo.VolumeName = volumeName | ||
vmeInfo.ServiceURL = serviceUrl | ||
vmeInfo.OutputFormat = manifestOutputFormat | ||
|
@@ -551,7 +562,7 @@ func downloadVolume(client kubecli.KubevirtClient, vmexport *exportv1.VirtualMac | |
} | ||
|
||
// Lastly, copy the file to the expected output | ||
if err := copyFileWithProgressBar(vmeInfo.OutputWriter, resp); err != nil { | ||
if err := copyFileWithProgressBar(vmeInfo.OutputWriter, resp, vmeInfo.Decompress); err != nil { | ||
return err | ||
} | ||
|
||
|
@@ -579,6 +590,7 @@ func GetUrlFromVirtualMachineExport(vmexport *exportv1.VirtualMachineExport, vme | |
var ( | ||
downloadUrl string | ||
err error | ||
format exportv1.VirtualMachineExportVolumeFormat | ||
links *exportv1.VirtualMachineExportLink | ||
) | ||
|
||
|
@@ -597,21 +609,28 @@ func GetUrlFromVirtualMachineExport(vmexport *exportv1.VirtualMachineExport, vme | |
for _, exportVolume := range links.Volumes { | ||
// Access the requested volume | ||
if volumeNumber == 1 || exportVolume.Name == vmeInfo.VolumeName { | ||
for _, format := range exportVolume.Formats { | ||
// We always attempt to find and get the compressed file URL, so we only break the loop when one is found | ||
for _, format = range exportVolume.Formats { | ||
if format.Format == exportv1.KubeVirtGz || format.Format == exportv1.ArchiveGz || format.Format == exportv1.KubeVirtRaw { | ||
downloadUrl, err = replaceUrlWithServiceUrl(format.Url, vmeInfo) | ||
if err != nil { | ||
return "", err | ||
} | ||
} | ||
if format.Format == exportv1.KubeVirtGz || format.Format == exportv1.ArchiveGz { | ||
// By default, we always attempt to find and get the compressed file URL, so we only break the loop when one is found. | ||
// If --raw is set to true, we attempt to return the raw URL instead. | ||
if (vmeInfo.RawImg && format.Format == exportv1.KubeVirtRaw) || | ||
(!vmeInfo.RawImg && (format.Format == exportv1.KubeVirtGz || format.Format == exportv1.ArchiveGz)) { | ||
break | ||
} | ||
} | ||
} | ||
} | ||
|
||
// No need to decompress file if format is not gzip | ||
if format.Format == exportv1.KubeVirtRaw { | ||
vmeInfo.Decompress = false | ||
} | ||
|
||
if downloadUrl == "" { | ||
return "", fmt.Errorf("unable to get a valid URL from '%s/%s' VirtualMachineExport", vmexport.Namespace, vmexport.Name) | ||
} | ||
|
@@ -739,15 +758,28 @@ func getHTTPClient(transport *http.Transport, insecure bool) *http.Client { | |
} | ||
|
||
// copyFileWithProgressBar serves as a wrapper to copy the file with a progress bar | ||
func copyFileWithProgressBar(output io.Writer, resp *http.Response) error { | ||
func copyFileWithProgressBar(output io.Writer, resp *http.Response, decompress bool) error { | ||
var rd io.Reader | ||
barTemplate := fmt.Sprintf(`{{ "Downloading file:" }} {{counters . }} {{ cycle . %s }} {{speed . }}`, progressBarCycle) | ||
|
||
// start bar based on our template | ||
bar := pb.ProgressBarTemplate(barTemplate).Start(0) | ||
defer bar.Finish() | ||
rd := bar.NewProxyReader(resp.Body) | ||
barRd := bar.NewProxyReader(resp.Body) | ||
rd = barRd | ||
bar.Start() | ||
|
||
if decompress { | ||
// Create a new gzip reader | ||
gzipReader, err := gzip.NewReader(barRd) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optional nice to have: Is this code able to create a sparse file? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess we can decompress the file and then rewrite it to be sparse but that sounds out of scope for the PR imo. gzip can't directly decompress it to be sparse, maybe there's some other package that does it. I can take a better look. |
||
if err != nil { | ||
return err | ||
} | ||
defer gzipReader.Close() | ||
rd = gzipReader | ||
fmt.Println("Decompressing image:") | ||
} | ||
|
||
_, err := io.Copy(output, rd) | ||
return err | ||
} | ||
|
@@ -854,6 +886,12 @@ func handleCreateFlags() error { | |
if localPort != "0" { | ||
return fmt.Errorf(ErrIncompatibleFlag, LOCAL_PORT_FLAG, CREATE) | ||
} | ||
if rawImg { | ||
return fmt.Errorf(ErrIncompatibleFlag, RAW_FLAG, CREATE) | ||
} | ||
if decompress { | ||
return fmt.Errorf(ErrIncompatibleFlag, DECOMPRESS_FLAG, CREATE) | ||
} | ||
if serviceUrl != "" { | ||
return fmt.Errorf(ErrIncompatibleFlag, SERVICE_URL_FLAG, CREATE) | ||
} | ||
|
@@ -885,6 +923,12 @@ func handleDeleteFlags() error { | |
if localPort != "0" { | ||
return fmt.Errorf(ErrIncompatibleFlag, LOCAL_PORT_FLAG, DELETE) | ||
} | ||
if rawImg { | ||
return fmt.Errorf(ErrIncompatibleFlag, RAW_FLAG, DELETE) | ||
} | ||
if decompress { | ||
return fmt.Errorf(ErrIncompatibleFlag, DECOMPRESS_FLAG, DELETE) | ||
} | ||
if serviceUrl != "" { | ||
return fmt.Errorf(ErrIncompatibleFlag, SERVICE_URL_FLAG, DELETE) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little confused here. Why two options? Don't they ultimately bean the same thing?
What do you think of this, forget about these to flags for a sec. What if we:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I initially wanted to allow both downloading the raw file (--raw) and downloading and decompressing the gzipped one (--decompress). I know they produce the same output but thought it was nice to allow both ways (some users might care about downloading bigger files, others won't). I also prefer boolean flags since they are more immediate and less confusing. But if you prefer to have a single flag to specify the output I can change it, just wanted to share the rationale behind this design.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a case of too many knobs leading to user confusion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay so I added a
--format
flag to specify either raw or gzip.--output
is already a flag so couldn't use that one.