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
This commit is contained in:
Malthe Poulsen 2025-12-22 13:36:50 +01:00 committed by GitHub
parent a9b16cf07c
commit fcbc14aab6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
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)