Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions docs/reference/manual/hcloud_primary-ip_create.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion docs/reference/manual/hcloud_server_create.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions internal/cmd/datacenter/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ func DescribeDatacenter(client hcapi2.Client, datacenter *hcloud.Datacenter, sho
fmt.Fprintf(&sb, "Name:\t%s\n", datacenter.Name)
fmt.Fprintf(&sb, "Description:\t%s\n", datacenter.Description)

fmt.Fprintln(&sb)
fmt.Fprintf(&sb, "Location:\n")
fmt.Fprint(&sb, util.PrefixLines(location.DescribeLocation(datacenter.Location), " "))

if short {
return sb.String()
}

fmt.Fprintln(&sb)
fmt.Fprintf(&sb, "Location:\n")
fmt.Fprint(&sb, util.PrefixLines(location.DescribeLocation(datacenter.Location), " "))

type ServerTypeStatus struct {
ID int64
Available bool
Expand Down
46 changes: 39 additions & 7 deletions internal/cmd/primaryip/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package primaryip

import (
"fmt"
"strings"

"github.com/spf13/cobra"

Expand All @@ -16,8 +17,12 @@ import (
var CreateCmd = base.CreateCmd[*hcloud.PrimaryIP]{
BaseCobraCommand: func(client hcapi2.Client) *cobra.Command {
cmd := &cobra.Command{
Use: "create [options] --type <ipv4|ipv6> --name <name>",
Short: "Create a Primary IP",
Use: "create [options] --type <ipv4|ipv6> --name <name>",
Short: "Create a Primary IP",
Long: `Create a Primary IP.

The --datacenter flag is deprecated. Use --location or --assignee-id instead.
See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters`,
TraverseChildren: true,
DisableFlagsInUseLine: true,
}
Expand All @@ -28,9 +33,12 @@ var CreateCmd = base.CreateCmd[*hcloud.PrimaryIP]{
cmd.Flags().String("name", "", "Name (required)")
_ = cmd.MarkFlagRequired("name")

cmd.Flags().Int64("assignee-id", 0, "Assignee (usually a Server) to assign Primary IP to (required if 'datacenter' is not specified)")
cmd.Flags().Int64("assignee-id", 0, "Assignee (usually a Server) to assign Primary IP to")

cmd.Flags().String("location", "", "Location (ID or name) of Primary IP")
_ = cmd.RegisterFlagCompletionFunc("location", cmpl.SuggestCandidatesF(client.Location().Names))

cmd.Flags().String("datacenter", "", "Datacenter (ID or name) (required if 'assignee-id' is not specified)")
cmd.Flags().String("datacenter", "", "Datacenter (name) (deprecated)")
_ = cmd.RegisterFlagCompletionFunc("datacenter", cmpl.SuggestCandidatesF(client.Datacenter().Names))

cmd.Flags().StringToString("label", nil, "User-defined labels ('key=value') (can be specified multiple times)")
Expand All @@ -40,15 +48,16 @@ var CreateCmd = base.CreateCmd[*hcloud.PrimaryIP]{

cmd.Flags().Bool("auto-delete", false, "Delete Primary IP if assigned resource is deleted (true, false)")

cmd.MarkFlagsOneRequired("assignee-id", "datacenter")
cmd.MarkFlagsMutuallyExclusive("assignee-id", "datacenter")
cmd.MarkFlagsOneRequired("assignee-id", "datacenter", "location")
cmd.MarkFlagsMutuallyExclusive("assignee-id", "datacenter", "location")
return cmd
},
Run: func(s state.State, cmd *cobra.Command, _ []string) (*hcloud.PrimaryIP, any, error) {
typ, _ := cmd.Flags().GetString("type")
name, _ := cmd.Flags().GetString("name")
assigneeID, _ := cmd.Flags().GetInt64("assignee-id")
datacenter, _ := cmd.Flags().GetString("datacenter")
locationIDOrName, _ := cmd.Flags().GetString("location")
labels, _ := cmd.Flags().GetStringToString("label")
protection, _ := cmd.Flags().GetStringSlice("enable-protection")
autoDelete, _ := cmd.Flags().GetBool("auto-delete")
Expand All @@ -62,7 +71,6 @@ var CreateCmd = base.CreateCmd[*hcloud.PrimaryIP]{
Type: hcloud.PrimaryIPType(typ),
Name: name,
AssigneeType: "server",
Datacenter: datacenter,
Labels: labels,
}
if assigneeID != 0 {
Expand All @@ -71,6 +79,30 @@ var CreateCmd = base.CreateCmd[*hcloud.PrimaryIP]{
if cmd.Flags().Changed("auto-delete") {
createOpts.AutoDelete = &autoDelete
}
if cmd.Flags().Changed("location") {
location, _, err := s.Client().Location().Get(s, locationIDOrName)
if err != nil {
return nil, nil, err
}
if location == nil {
return nil, nil, fmt.Errorf("Location not found: %s", locationIDOrName)
}
createOpts.Location = location.Name
}
if cmd.Flags().Changed("datacenter") {
cmd.PrintErrln("Warning: The --datacenter flag is deprecated. Use --location or --assignee-id instead.")

// Backward compatible datacenter argument.
// datacenter hel1-dc2 => location hel1
parts := strings.Split(datacenter, "-")

if len(parts) != 2 {
return nil, nil, fmt.Errorf("Datacenter name is not valid, expected format $LOCATION-$DATACENTER, but got: %s", datacenter)
}

locationName := parts[0]
createOpts.Location = locationName
}

result, _, err := s.Client().PrimaryIP().Create(s, createOpts)
if err != nil {
Expand Down
8 changes: 6 additions & 2 deletions internal/cmd/primaryip/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,14 @@ func TestCreate(t *testing.T) {

out, errOut, err := fx.Run(cmd, []string{"--name=my-ip", "--type=ipv4", "--datacenter=fsn1-dc14", "--auto-delete", "--label", "foo=bar"})

expErr := "Warning: The --datacenter flag is deprecated. Use --location or --assignee-id instead.\n"

expOut := `Primary IP 1 created
IPv4: 192.168.2.1
`

require.NoError(t, err)
assert.Empty(t, errOut)
assert.Equal(t, expErr, errOut)
assert.Equal(t, expOut, out)
}

Expand Down Expand Up @@ -124,7 +126,9 @@ func TestCreateJSON(t *testing.T) {

jsonOut, out, err := fx.Run(cmd, []string{"-o=json", "--name=my-ip", "--type=ipv4", "--datacenter=fsn1-dc14", "--auto-delete", "--label", "foo=bar"})

expOut := "Primary IP 1 created\n"
expOut := `Warning: The --datacenter flag is deprecated. Use --location or --assignee-id instead.
Primary IP 1 created
`

require.NoError(t, err)
assert.Equal(t, expOut, out)
Expand Down
12 changes: 10 additions & 2 deletions internal/cmd/primaryip/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/hetznercloud/cli/internal/cmd/base"
"github.com/hetznercloud/cli/internal/cmd/datacenter"
"github.com/hetznercloud/cli/internal/cmd/location"
"github.com/hetznercloud/cli/internal/cmd/util"
"github.com/hetznercloud/cli/internal/hcapi2"
"github.com/hetznercloud/cli/internal/state"
Expand Down Expand Up @@ -62,8 +63,15 @@ var DescribeCmd = base.DescribeCmd[*hcloud.PrimaryIP]{
util.DescribeLabels(out, primaryIP.Labels, "")

fmt.Fprintln(out)
fmt.Fprintf(out, "Datacenter:\n")
fmt.Fprintf(out, "%s", util.PrefixLines(datacenter.DescribeDatacenter(s.Client(), primaryIP.Datacenter, true), " "))
fmt.Fprintf(out, "Location:\n")
fmt.Fprintf(out, "%s", util.PrefixLines(location.DescribeLocation(primaryIP.Location), " "))

if primaryIP.Datacenter != nil {
fmt.Fprintln(out)
fmt.Fprintf(out, "Datacenter:\n")
fmt.Fprintf(out, "%s", util.PrefixLines(datacenter.DescribeDatacenter(s.Client(), primaryIP.Datacenter, true), " "))
}

return nil
},
}
34 changes: 19 additions & 15 deletions internal/cmd/primaryip/describe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,16 @@ func TestDescribe(t *testing.T) {
Blocked: true,
AutoDelete: false,
AssigneeType: "server",
Datacenter: &hcloud.Datacenter{ID: 0, Location: &hcloud.Location{ID: 0}},
Location: &hcloud.Location{
ID: 3,
Name: "hel1",
Description: "Helsinki DC Park 1",
NetworkZone: "eu-central",
Country: "FI",
City: "Helsinki",
Latitude: 60.169855,
Longitude: 24.938379,
},
}

fx.Client.PrimaryIPClient.EXPECT().
Expand Down Expand Up @@ -64,20 +73,15 @@ Protection:
Labels:
No labels

Datacenter:
ID: 0
Name:
Description:

Location:
ID: 0
Name:
Description:
Network Zone:
Country:
City:
Latitude: 0.000000
Longitude: 0.000000
Location:
ID: 3
Name: hel1
Description: Helsinki DC Park 1
Network Zone: eu-central
Country: FI
City: Helsinki
Latitude: 60.169855
Longitude: 24.938379
`, util.Datetime(primaryIP.Created), humanize.Time(primaryIP.Created))

require.NoError(t, err)
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/server/change_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ var ChangeTypeCmd = base.Cmd{
return fmt.Errorf("Server Type not found: %s", serverTypeIDOrName)
}

cmd.Print(deprecatedServerTypeWarning(serverType, server.Datacenter.Location.Name))
cmd.Print(deprecatedServerTypeWarning(serverType, server.Location.Name))

keepDisk, _ := cmd.Flags().GetBool("keep-disk")
opts := hcloud.ServerChangeTypeOpts{
Expand Down
4 changes: 2 additions & 2 deletions internal/cmd/server/change_type_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestChangeType(t *testing.T) {
cmd := server.ChangeTypeCmd.CobraCommand(fx.State())
fx.ExpectEnsureToken()

srv := &hcloud.Server{ID: 123, Name: "my-server", Datacenter: &hcloud.Datacenter{Location: &hcloud.Location{Name: "fsn1"}}}
srv := &hcloud.Server{ID: 123, Name: "my-server", Location: &hcloud.Location{Name: "fsn1"}}
st := &hcloud.ServerType{ID: 456, Name: "cax21", Locations: []hcloud.ServerTypeLocation{{Location: &hcloud.Location{Name: "fsn1"}}}}

fx.Client.ServerClient.EXPECT().
Expand Down Expand Up @@ -53,7 +53,7 @@ func TestChangeTypeKeepDisk(t *testing.T) {
cmd := server.ChangeTypeCmd.CobraCommand(fx.State())
fx.ExpectEnsureToken()

srv := &hcloud.Server{ID: 123, Name: "my-server", Datacenter: &hcloud.Datacenter{Location: &hcloud.Location{Name: "fsn1"}}}
srv := &hcloud.Server{ID: 123, Name: "my-server", Location: &hcloud.Location{Name: "fsn1"}}
st := &hcloud.ServerType{ID: 456, Name: "cax21", Locations: []hcloud.ServerTypeLocation{{Location: &hcloud.Location{Name: "fsn1"}}}}

fx.Client.ServerClient.EXPECT().
Expand Down
46 changes: 37 additions & 9 deletions internal/cmd/server/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"mime/multipart"
"net/textproto"
"os"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -41,6 +42,10 @@ var CreateCmd = base.CreateCmd[*createResult]{
cmd := &cobra.Command{
Use: "create [options] --name <name> --type <server-type> --image <image>",
Short: "Create a Server",
Long: `Create a Server.

The --datacenter flag is deprecated. Use --location instead.
See https://docs.hetzner.cloud/changelog#2025-12-16-phasing-out-datacenters`,
}

cmd.Flags().String("name", "", "Server name (required)")
Expand All @@ -57,7 +62,7 @@ var CreateCmd = base.CreateCmd[*createResult]{
cmd.Flags().String("location", "", "Location (ID or name)")
_ = cmd.RegisterFlagCompletionFunc("location", cmpl.SuggestCandidatesF(client.Location().Names))

cmd.Flags().String("datacenter", "", "Datacenter (ID or name)")
cmd.Flags().String("datacenter", "", "Datacenter (ID or name) (deprecated)")
_ = cmd.RegisterFlagCompletionFunc("datacenter", cmpl.SuggestCandidatesF(client.Datacenter().Names))

cmd.Flags().StringSlice("ssh-key", nil, "ID or name of SSH Key to inject (can be specified multiple times)")
Expand Down Expand Up @@ -433,17 +438,40 @@ func createOptsFromFlags(
}

if datacenterIDOrName != "" {
var datacenter *hcloud.Datacenter
datacenter, _, err = s.Client().Datacenter().Get(s, datacenterIDOrName)
cmd.PrintErrln("Warning: The --datacenter flag is deprecated. Use --location instead.")

// If parseable as ID -> GetByID and use Location Name
// Else -> Backwards-compatible split
var datacenterID int64
datacenterID, err = strconv.ParseInt(datacenterIDOrName, 10, 64)

if err != nil {
return
}
if datacenter == nil {
err = fmt.Errorf("Datacenter not found: %s", datacenterIDOrName)
return
// Input was a valid number/ID
var datacenter *hcloud.Datacenter
datacenter, _, err = s.Client().Datacenter().GetByID(s, datacenterID)
if err != nil {
return
}
if datacenter == nil {
err = fmt.Errorf("Datacenter not found: %s", datacenterIDOrName)
return
}
createOpts.Location = datacenter.Location
} else {
// Input was not a valid number/ID, probably DC name

// Backward compatible datacenter argument.
// datacenter hel1-dc2 => location hel1
parts := strings.Split(datacenterIDOrName, "-")

if len(parts) != 2 {
err = fmt.Errorf("Datacenter name is not valid, expected format $LOCATION-$DATACENTER, but got: %s", datacenterIDOrName)
}

createOpts.Location = &hcloud.Location{Name: parts[0]}
}
createOpts.Datacenter = datacenter
}

if locationIDOrName != "" {
var location *hcloud.Location
location, _, err = s.Client().Location().Get(s, locationIDOrName)
Expand Down
Loading
Loading