2024-05-02 20:19:25 +02:00
package cmd
import (
2025-05-04 00:28:11 +02:00
_ "embed"
2024-05-02 20:19:25 +02:00
"fmt"
2025-05-04 00:28:11 +02:00
"net/http"
2024-05-02 20:19:25 +02:00
"net/url"
2024-05-09 18:16:37 +02:00
"os"
2024-05-02 20:19:25 +02:00
"github.com/hetznercloud/hcloud-go/v2/hcloud"
"github.com/spf13/cobra"
2024-05-04 22:13:33 +02:00
"github.com/apricote/hcloud-upload-image/hcloudimages"
2024-05-02 21:42:36 +02:00
"github.com/apricote/hcloud-upload-image/hcloudimages/contextlogger"
2024-05-02 20:19:25 +02:00
)
const (
uploadFlagImageURL = "image-url"
2024-05-09 18:16:37 +02:00
uploadFlagImagePath = "image-path"
2024-05-02 20:19:25 +02:00
uploadFlagCompression = "compression"
2025-05-04 00:28:11 +02:00
uploadFlagFormat = "format"
2024-05-02 20:19:25 +02:00
uploadFlagArchitecture = "architecture"
2024-06-23 16:21:14 +02:00
uploadFlagServerType = "server-type"
2024-05-02 20:19:25 +02:00
uploadFlagDescription = "description"
uploadFlagLabels = "labels"
)
2025-05-04 00:28:11 +02:00
//go:embed upload.md
var longDescription string
2024-05-02 20:19:25 +02:00
// uploadCmd represents the upload command
var uploadCmd = & cobra . Command {
2024-05-09 18:16:37 +02:00
Use : "upload (--image-path=<local-path> | --image-url=<url>) --architecture=<x86|arm>" ,
2024-05-02 20:19:25 +02:00
Short : "Upload the specified disk image into your Hetzner Cloud project." ,
2025-05-04 00:28:11 +02:00
Long : longDescription ,
2024-05-09 18:16:37 +02:00
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"
2025-05-04 00:28:11 +02:00
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`,
2024-11-02 21:57:53 +01:00
DisableAutoGenTag : true ,
2024-05-09 18:16:37 +02:00
2024-05-05 00:05:09 +02:00
GroupID : "primary" ,
2024-05-02 20:19:25 +02:00
2024-05-10 18:06:38 +02:00
PreRun : initClient ,
2024-05-02 20:19:25 +02:00
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
ctx := cmd . Context ( )
logger := contextlogger . From ( ctx )
imageURLString , _ := cmd . Flags ( ) . GetString ( uploadFlagImageURL )
2024-05-09 18:16:37 +02:00
imagePathString , _ := cmd . Flags ( ) . GetString ( uploadFlagImagePath )
2024-05-02 20:19:25 +02:00
imageCompression , _ := cmd . Flags ( ) . GetString ( uploadFlagCompression )
2025-05-04 00:28:11 +02:00
imageFormat , _ := cmd . Flags ( ) . GetString ( uploadFlagFormat )
2024-05-02 20:19:25 +02:00
architecture , _ := cmd . Flags ( ) . GetString ( uploadFlagArchitecture )
2024-06-23 16:21:14 +02:00
serverType , _ := cmd . Flags ( ) . GetString ( uploadFlagServerType )
2024-05-02 20:19:25 +02:00
description , _ := cmd . Flags ( ) . GetString ( uploadFlagDescription )
labels , _ := cmd . Flags ( ) . GetStringToString ( uploadFlagLabels )
2024-05-09 18:16:37 +02:00
options := hcloudimages . UploadOptions {
2024-05-04 22:13:33 +02:00
ImageCompression : hcloudimages . Compression ( imageCompression ) ,
2025-05-04 00:28:11 +02:00
ImageFormat : hcloudimages . Format ( imageFormat ) ,
2024-05-02 20:19:25 +02:00
Description : hcloud . Ptr ( description ) ,
Labels : labels ,
2024-05-09 18:16:37 +02:00
}
if imageURLString != "" {
imageURL , err := url . Parse ( imageURLString )
if err != nil {
return fmt . Errorf ( "unable to parse url from --%s=%q: %w" , uploadFlagImageURL , imageURLString , err )
}
2025-05-04 00:28:11 +02:00
// 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
}
2024-05-09 18:16:37 +02:00
options . ImageURL = imageURL
} else if imagePathString != "" {
2025-05-04 00:28:11 +02:00
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 ( )
}
2024-05-09 18:16:37 +02:00
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
}
2024-06-23 16:21:14 +02:00
if architecture != "" {
options . Architecture = hcloud . Architecture ( architecture )
} else if serverType != "" {
options . ServerType = & hcloud . ServerType { Name : serverType }
}
2024-05-09 18:16:37 +02:00
image , err := client . Upload ( ctx , options )
2024-05-02 20:19:25 +02:00
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 ( ) {
2024-11-02 21:57:53 +01:00
RootCmd . AddCommand ( uploadCmd )
2024-05-02 20:19:25 +02:00
2024-05-09 18:16:37 +02:00
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 )
2024-05-02 20:19:25 +02:00
2024-05-09 19:15:54 +02:00
uploadCmd . Flags ( ) . String ( uploadFlagCompression , "" , "Type of compression that was used on the disk image [choices: bz2, xz]" )
2024-05-02 20:19:25 +02:00
_ = uploadCmd . RegisterFlagCompletionFunc (
uploadFlagCompression ,
2024-05-09 19:15:54 +02:00
cobra . FixedCompletions ( [ ] string { string ( hcloudimages . CompressionBZ2 ) , string ( hcloudimages . CompressionXZ ) } , cobra . ShellCompDirectiveNoFileComp ) ,
2024-05-02 20:19:25 +02:00
)
2025-05-04 00:28:11 +02:00
uploadCmd . Flags ( ) . String ( uploadFlagFormat , "" , "Format of the image. [choices: qcow2]" )
_ = uploadCmd . RegisterFlagCompletionFunc (
uploadFlagFormat ,
cobra . FixedCompletions ( [ ] string { string ( hcloudimages . FormatQCOW2 ) } , cobra . ShellCompDirectiveNoFileComp ) ,
)
2024-05-09 18:16:37 +02:00
uploadCmd . Flags ( ) . String ( uploadFlagArchitecture , "" , "CPU architecture of the disk image [choices: x86, arm]" )
2024-05-02 20:19:25 +02:00
_ = uploadCmd . RegisterFlagCompletionFunc (
uploadFlagArchitecture ,
cobra . FixedCompletions ( [ ] string { string ( hcloud . ArchitectureX86 ) , string ( hcloud . ArchitectureARM ) } , cobra . ShellCompDirectiveNoFileComp ) ,
)
2024-06-23 16:21:14 +02:00
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 )
2024-05-02 20:19:25 +02:00
2024-05-09 18:16:37 +02:00
uploadCmd . Flags ( ) . String ( uploadFlagDescription , "" , "Description for the resulting image" )
2024-05-02 20:19:25 +02:00
2024-05-09 18:16:37 +02:00
uploadCmd . Flags ( ) . StringToString ( uploadFlagLabels , map [ string ] string { } , "Labels for the resulting image" )
2024-05-02 20:19:25 +02:00
}