From 4bf44efe08c1f3b09d739ec4f2512766e8799a7f Mon Sep 17 00:00:00 2001 From: malpou Date: Tue, 16 Dec 2025 15:13:22 +0100 Subject: [PATCH] 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 --- README.md | 4 +++- cmd/upload.go | 12 ++++++++++++ docs/reference/cli/hcloud-upload-image_upload.md | 1 + hcloudimages/client.go | 13 +++++++++++-- hcloudimages/doc_test.go | 1 + 5 files changed, 28 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1cf2081..04a2999 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,8 @@ export HCLOUD_TOKEN="" hcloud-upload-image upload \ --image-url "https://example.com/disk-image-x86.raw.bz2" \ --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).: @@ -123,6 +124,7 @@ func main() { ImageURL: imageURL, ImageCompression: hcloudimages.CompressionBZ2, Architecture: hcloud.ArchitectureX86, + Location: &hcloud.Location{Name: "nbg1"}, // Optional: defaults to fsn1 }) if err != nil { panic(err) diff --git a/cmd/upload.go b/cmd/upload.go index d7b5b65..d66ff6c 100644 --- a/cmd/upload.go +++ b/cmd/upload.go @@ -23,6 +23,7 @@ const ( uploadFlagServerType = "server-type" uploadFlagDescription = "description" uploadFlagLabels = "labels" + uploadFlagLocation = "location" ) //go:embed upload.md @@ -54,6 +55,7 @@ var uploadCmd = &cobra.Command{ serverType, _ := cmd.Flags().GetString(uploadFlagServerType) description, _ := cmd.Flags().GetString(uploadFlagDescription) labels, _ := cmd.Flags().GetStringToString(uploadFlagLabels) + location, _ := cmd.Flags().GetString(uploadFlagLocation) options := hcloudimages.UploadOptions{ ImageCompression: hcloudimages.Compression(imageCompression), @@ -102,6 +104,10 @@ var uploadCmd = &cobra.Command{ options.ServerType = &hcloud.ServerType{Name: serverType} } + if location != "" { + options.Location = &hcloud.Location{Name: location} + } + image, err := client.Upload(ctx, options) if err != nil { 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().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), + ) } diff --git a/docs/reference/cli/hcloud-upload-image_upload.md b/docs/reference/cli/hcloud-upload-image_upload.md index e127e3f..7ca3f31 100644 --- a/docs/reference/cli/hcloud-upload-image_upload.md +++ b/docs/reference/cli/hcloud-upload-image_upload.md @@ -41,6 +41,7 @@ hcloud-upload-image upload (--image-path= | --image-url=) --arc --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 --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. ``` diff --git a/hcloudimages/client.go b/hcloudimages/client.go index 5940f98..cf2f4e7 100644 --- a/hcloudimages/client.go +++ b/hcloudimages/client.go @@ -94,6 +94,10 @@ type UploadOptions struct { // We also always add a label `apricote.de/created-by=hcloud-image-upload` ([CreatedByLabel], [CreatedByValue]). 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 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", "image", defaultImage.Name, - "location", defaultLocation.Name, + "location", location.Name, "serverType", serverType.Name, ) 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), // Image will never be booted, we only boot into rescue system Image: defaultImage, - Location: defaultLocation, + Location: location, Labels: labels, }) if err != nil { diff --git a/hcloudimages/doc_test.go b/hcloudimages/doc_test.go index 4905bdf..e07c720 100644 --- a/hcloudimages/doc_test.go +++ b/hcloudimages/doc_test.go @@ -24,6 +24,7 @@ func ExampleClient_Upload() { ImageURL: imageURL, ImageCompression: hcloudimages.CompressionBZ2, Architecture: hcloud.ArchitectureX86, + Location: &hcloud.Location{Name: "nbg1"}, // Optional: defaults to fsn1 }) if err != nil { panic(err)