mirror of
https://github.com/apricote/hcloud-upload-image.git
synced 2025-08-06 22:06:58 +02:00
It is now possible to upload qcow2 images. These images will be converted to raw disk images on the cloud server. In the CLI you can use the new `--format=qcow2` flag to upload qcow2 images. In the library you can set `UploadOptions.ImageFormat` to `FormatQCOW2`. Because of the underlying process, qcow2 images need to be written to a file first. This limits their size to 960 MB at the moment. The CLI automatically checks the file size (if possible) and shows a warning if this limit would be triggered. The library accepts an input with the file size and logs a warning if the limit would be triggered. Closes #44
152 lines
5.5 KiB
Go
152 lines
5.5 KiB
Go
package cmd
|
|
|
|
import (
|
|
_ "embed"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
|
|
"github.com/hetznercloud/hcloud-go/v2/hcloud"
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/apricote/hcloud-upload-image/hcloudimages"
|
|
"github.com/apricote/hcloud-upload-image/hcloudimages/contextlogger"
|
|
)
|
|
|
|
const (
|
|
uploadFlagImageURL = "image-url"
|
|
uploadFlagImagePath = "image-path"
|
|
uploadFlagCompression = "compression"
|
|
uploadFlagFormat = "format"
|
|
uploadFlagArchitecture = "architecture"
|
|
uploadFlagServerType = "server-type"
|
|
uploadFlagDescription = "description"
|
|
uploadFlagLabels = "labels"
|
|
)
|
|
|
|
//go:embed upload.md
|
|
var longDescription string
|
|
|
|
// uploadCmd represents the upload command
|
|
var uploadCmd = &cobra.Command{
|
|
Use: "upload (--image-path=<local-path> | --image-url=<url>) --architecture=<x86|arm>",
|
|
Short: "Upload the specified disk image into your Hetzner Cloud project.",
|
|
Long: longDescription,
|
|
Example: ` hcloud-upload-image upload --image-path /home/you/images/custom-linux-image-x86.bz2 --architecture x86 --compression bz2 --description "My super duper custom linux"
|
|
hcloud-upload-image upload --image-url https://examples.com/image-arm.raw --architecture arm --labels foo=bar,version=latest
|
|
hcloud-upload-image upload --image-url https://examples.com/image-x86.qcow2 --architecture x86 --format qcow2`,
|
|
DisableAutoGenTag: true,
|
|
|
|
GroupID: "primary",
|
|
|
|
PreRun: initClient,
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
ctx := cmd.Context()
|
|
logger := contextlogger.From(ctx)
|
|
|
|
imageURLString, _ := cmd.Flags().GetString(uploadFlagImageURL)
|
|
imagePathString, _ := cmd.Flags().GetString(uploadFlagImagePath)
|
|
imageCompression, _ := cmd.Flags().GetString(uploadFlagCompression)
|
|
imageFormat, _ := cmd.Flags().GetString(uploadFlagFormat)
|
|
architecture, _ := cmd.Flags().GetString(uploadFlagArchitecture)
|
|
serverType, _ := cmd.Flags().GetString(uploadFlagServerType)
|
|
description, _ := cmd.Flags().GetString(uploadFlagDescription)
|
|
labels, _ := cmd.Flags().GetStringToString(uploadFlagLabels)
|
|
|
|
options := hcloudimages.UploadOptions{
|
|
ImageCompression: hcloudimages.Compression(imageCompression),
|
|
ImageFormat: hcloudimages.Format(imageFormat),
|
|
Description: hcloud.Ptr(description),
|
|
Labels: labels,
|
|
}
|
|
|
|
if imageURLString != "" {
|
|
imageURL, err := url.Parse(imageURLString)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to parse url from --%s=%q: %w", uploadFlagImageURL, imageURLString, err)
|
|
}
|
|
|
|
// Check for image size
|
|
resp, err := http.Head(imageURL.String())
|
|
switch {
|
|
case err != nil:
|
|
logger.DebugContext(ctx, "failed to check for file size, error on request", "err", err)
|
|
case resp.ContentLength == -1:
|
|
logger.DebugContext(ctx, "failed to check for file size, server did not set the Content-Length", "err", err)
|
|
default:
|
|
options.ImageSize = resp.ContentLength
|
|
}
|
|
|
|
options.ImageURL = imageURL
|
|
} else if imagePathString != "" {
|
|
stat, err := os.Stat(imagePathString)
|
|
if err != nil {
|
|
logger.DebugContext(ctx, "failed to check for file size, error on stat", "err", err)
|
|
} else {
|
|
options.ImageSize = stat.Size()
|
|
}
|
|
|
|
imageFile, err := os.Open(imagePathString)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to read file from --%s=%q: %w", uploadFlagImagePath, imagePathString, err)
|
|
}
|
|
|
|
options.ImageReader = imageFile
|
|
}
|
|
|
|
if architecture != "" {
|
|
options.Architecture = hcloud.Architecture(architecture)
|
|
} else if serverType != "" {
|
|
options.ServerType = &hcloud.ServerType{Name: serverType}
|
|
}
|
|
|
|
image, err := client.Upload(ctx, options)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to upload the image: %w", err)
|
|
}
|
|
|
|
logger.InfoContext(ctx, "Successfully uploaded the image!", "image", image.ID)
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
RootCmd.AddCommand(uploadCmd)
|
|
|
|
uploadCmd.Flags().String(uploadFlagImageURL, "", "Remote URL of the disk image that should be uploaded")
|
|
uploadCmd.Flags().String(uploadFlagImagePath, "", "Local path to the disk image that should be uploaded")
|
|
uploadCmd.MarkFlagsMutuallyExclusive(uploadFlagImageURL, uploadFlagImagePath)
|
|
uploadCmd.MarkFlagsOneRequired(uploadFlagImageURL, uploadFlagImagePath)
|
|
|
|
uploadCmd.Flags().String(uploadFlagCompression, "", "Type of compression that was used on the disk image [choices: bz2, xz]")
|
|
_ = uploadCmd.RegisterFlagCompletionFunc(
|
|
uploadFlagCompression,
|
|
cobra.FixedCompletions([]string{string(hcloudimages.CompressionBZ2), string(hcloudimages.CompressionXZ)}, cobra.ShellCompDirectiveNoFileComp),
|
|
)
|
|
|
|
uploadCmd.Flags().String(uploadFlagFormat, "", "Format of the image. [choices: qcow2]")
|
|
_ = uploadCmd.RegisterFlagCompletionFunc(
|
|
uploadFlagFormat,
|
|
cobra.FixedCompletions([]string{string(hcloudimages.FormatQCOW2)}, cobra.ShellCompDirectiveNoFileComp),
|
|
)
|
|
|
|
uploadCmd.Flags().String(uploadFlagArchitecture, "", "CPU architecture of the disk image [choices: x86, arm]")
|
|
_ = uploadCmd.RegisterFlagCompletionFunc(
|
|
uploadFlagArchitecture,
|
|
cobra.FixedCompletions([]string{string(hcloud.ArchitectureX86), string(hcloud.ArchitectureARM)}, cobra.ShellCompDirectiveNoFileComp),
|
|
)
|
|
|
|
uploadCmd.Flags().String(uploadFlagServerType, "", "Explicitly use this server type to generate the image. Mutually exclusive with --architecture.")
|
|
|
|
// Only one of them needs to be set
|
|
uploadCmd.MarkFlagsOneRequired(uploadFlagArchitecture, uploadFlagServerType)
|
|
uploadCmd.MarkFlagsMutuallyExclusive(uploadFlagArchitecture, uploadFlagServerType)
|
|
|
|
uploadCmd.Flags().String(uploadFlagDescription, "", "Description for the resulting image")
|
|
|
|
uploadCmd.Flags().StringToString(uploadFlagLabels, map[string]string{}, "Labels for the resulting image")
|
|
}
|