feat: add --location flag to specify datacenter region

Allow users to specify which Hetzner datacenter location to use for
the temporary server during image upload. Defaults to fsn1 for
backward compatibility.

Available locations: fsn1, nbg1, hel1, ash, hil, sin
This commit is contained in:
malpou 2025-12-16 15:13:22 +01:00
parent a9b16cf07c
commit 4bf44efe08
5 changed files with 28 additions and 3 deletions

View file

@ -73,7 +73,8 @@ export HCLOUD_TOKEN="<your token>"
hcloud-upload-image upload \ hcloud-upload-image upload \
--image-url "https://example.com/disk-image-x86.raw.bz2" \ --image-url "https://example.com/disk-image-x86.raw.bz2" \
--architecture x86 \ --architecture x86 \
--compression bz2 --compression bz2 \
--location nbg1 # Optional: defaults to fsn1
``` ```
To learn more, you can use the embedded help output or check out the [CLI help pages in this repository](docs/reference/cli/hcloud-upload-image.md).: To learn more, you can use the embedded help output or check out the [CLI help pages in this repository](docs/reference/cli/hcloud-upload-image.md).:
@ -123,6 +124,7 @@ func main() {
ImageURL: imageURL, ImageURL: imageURL,
ImageCompression: hcloudimages.CompressionBZ2, ImageCompression: hcloudimages.CompressionBZ2,
Architecture: hcloud.ArchitectureX86, Architecture: hcloud.ArchitectureX86,
Location: &hcloud.Location{Name: "nbg1"}, // Optional: defaults to fsn1
}) })
if err != nil { if err != nil {
panic(err) panic(err)

View file

@ -23,6 +23,7 @@ const (
uploadFlagServerType = "server-type" uploadFlagServerType = "server-type"
uploadFlagDescription = "description" uploadFlagDescription = "description"
uploadFlagLabels = "labels" uploadFlagLabels = "labels"
uploadFlagLocation = "location"
) )
//go:embed upload.md //go:embed upload.md
@ -54,6 +55,7 @@ var uploadCmd = &cobra.Command{
serverType, _ := cmd.Flags().GetString(uploadFlagServerType) serverType, _ := cmd.Flags().GetString(uploadFlagServerType)
description, _ := cmd.Flags().GetString(uploadFlagDescription) description, _ := cmd.Flags().GetString(uploadFlagDescription)
labels, _ := cmd.Flags().GetStringToString(uploadFlagLabels) labels, _ := cmd.Flags().GetStringToString(uploadFlagLabels)
location, _ := cmd.Flags().GetString(uploadFlagLocation)
options := hcloudimages.UploadOptions{ options := hcloudimages.UploadOptions{
ImageCompression: hcloudimages.Compression(imageCompression), ImageCompression: hcloudimages.Compression(imageCompression),
@ -102,6 +104,10 @@ var uploadCmd = &cobra.Command{
options.ServerType = &hcloud.ServerType{Name: serverType} options.ServerType = &hcloud.ServerType{Name: serverType}
} }
if location != "" {
options.Location = &hcloud.Location{Name: location}
}
image, err := client.Upload(ctx, options) image, err := client.Upload(ctx, options)
if err != nil { if err != nil {
return fmt.Errorf("failed to upload the image: %w", err) return fmt.Errorf("failed to upload the image: %w", err)
@ -148,4 +154,10 @@ func init() {
uploadCmd.Flags().String(uploadFlagDescription, "", "Description for the resulting image") uploadCmd.Flags().String(uploadFlagDescription, "", "Description for the resulting image")
uploadCmd.Flags().StringToString(uploadFlagLabels, map[string]string{}, "Labels for the resulting image") uploadCmd.Flags().StringToString(uploadFlagLabels, map[string]string{}, "Labels for the resulting image")
uploadCmd.Flags().String(uploadFlagLocation, "", "Datacenter location for the temporary server [default: fsn1, choices: fsn1, nbg1, hel1, ash, hil, sin]")
_ = uploadCmd.RegisterFlagCompletionFunc(
uploadFlagLocation,
cobra.FixedCompletions([]string{"fsn1", "nbg1", "hel1", "ash", "hil", "sin"}, cobra.ShellCompDirectiveNoFileComp),
)
} }

View file

@ -41,6 +41,7 @@ hcloud-upload-image upload (--image-path=<local-path> | --image-url=<url>) --arc
--image-path string Local path to the disk image that should be uploaded --image-path string Local path to the disk image that should be uploaded
--image-url string Remote URL of the disk image that should be uploaded --image-url string Remote URL of the disk image that should be uploaded
--labels stringToString Labels for the resulting image (default []) --labels stringToString Labels for the resulting image (default [])
--location string Datacenter location for the temporary server [default: fsn1, choices: fsn1, nbg1, hel1, ash, hil, sin]
--server-type string Explicitly use this server type to generate the image. Mutually exclusive with --architecture. --server-type string Explicitly use this server type to generate the image. Mutually exclusive with --architecture.
``` ```

View file

@ -94,6 +94,10 @@ type UploadOptions struct {
// We also always add a label `apricote.de/created-by=hcloud-image-upload` ([CreatedByLabel], [CreatedByValue]). // We also always add a label `apricote.de/created-by=hcloud-image-upload` ([CreatedByLabel], [CreatedByValue]).
Labels map[string]string Labels map[string]string
// Location is the datacenter location for the temporary server.
// Defaults to fsn1 if not specified.
Location *hcloud.Location
// DebugSkipResourceCleanup will skip the cleanup of the temporary SSH Key and Server. // DebugSkipResourceCleanup will skip the cleanup of the temporary SSH Key and Server.
DebugSkipResourceCleanup bool DebugSkipResourceCleanup bool
} }
@ -214,9 +218,14 @@ func (s *Client) Upload(ctx context.Context, options UploadOptions) (*hcloud.Ima
} }
} }
location := defaultLocation
if options.Location != nil {
location = options.Location
}
logger.DebugContext(ctx, "creating server with config", logger.DebugContext(ctx, "creating server with config",
"image", defaultImage.Name, "image", defaultImage.Name,
"location", defaultLocation.Name, "location", location.Name,
"serverType", serverType.Name, "serverType", serverType.Name,
) )
serverCreateResult, _, err := s.c.Server.Create(ctx, hcloud.ServerCreateOpts{ serverCreateResult, _, err := s.c.Server.Create(ctx, hcloud.ServerCreateOpts{
@ -230,7 +239,7 @@ func (s *Client) Upload(ctx context.Context, options UploadOptions) (*hcloud.Ima
StartAfterCreate: hcloud.Ptr(false), StartAfterCreate: hcloud.Ptr(false),
// Image will never be booted, we only boot into rescue system // Image will never be booted, we only boot into rescue system
Image: defaultImage, Image: defaultImage,
Location: defaultLocation, Location: location,
Labels: labels, Labels: labels,
}) })
if err != nil { if err != nil {

View file

@ -24,6 +24,7 @@ func ExampleClient_Upload() {
ImageURL: imageURL, ImageURL: imageURL,
ImageCompression: hcloudimages.CompressionBZ2, ImageCompression: hcloudimages.CompressionBZ2,
Architecture: hcloud.ArchitectureX86, Architecture: hcloud.ArchitectureX86,
Location: &hcloud.Location{Name: "nbg1"}, // Optional: defaults to fsn1
}) })
if err != nil { if err != nil {
panic(err) panic(err)