From fcbc14aab6d495d2c67d653f9ea1ff56a39a8c2f Mon Sep 17 00:00:00 2001 From: Malthe Poulsen <30603252+malpou@users.noreply.github.com> Date: Mon, 22 Dec 2025 13:36:50 +0100 Subject: [PATCH] feat: add --location flag to specify datacenter region (#141) 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 Implements: #142 --- 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)