Skip to content

Commit

Permalink
Placement groups (#352)
Browse files Browse the repository at this point in the history
Signed-off-by: Lukas Kämmerling <[email protected]>

Co-authored-by: Adrian Huber <[email protected]>
  • Loading branch information
LKaemmerling and Adrian Huber authored Aug 16, 2021
1 parent 2ab6137 commit 4b8ed4d
Show file tree
Hide file tree
Showing 35 changed files with 1,250 additions and 16 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.5.2 // indirect
github.com/guptarohit/asciigraph v0.5.1
github.com/hetznercloud/hcloud-go v1.29.1
github.com/hetznercloud/hcloud-go v1.30.0
github.com/pelletier/go-toml v1.8.1
github.com/rjeczalik/interfaces v0.1.1
github.com/spf13/cobra v1.1.3
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hetznercloud/hcloud-go v1.29.1 h1:UiV+GZVEOFramb49ASbXfpJGjXa6FmJe3Hh+Ns3RUJ4=
github.com/hetznercloud/hcloud-go v1.29.1/go.mod h1:2C5uMtBiMoFr3m7lBFPf7wXTdh33CevmZpQIIDPGYJI=
github.com/hetznercloud/hcloud-go v1.30.0 h1:Q8Y+YHgum6XvyVfz2IFp2pLWtupEFbykl12D5TwdBig=
github.com/hetznercloud/hcloud-go v1.30.0/go.mod h1:2C5uMtBiMoFr3m7lBFPf7wXTdh33CevmZpQIIDPGYJI=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
Expand Down
2 changes: 2 additions & 0 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/hetznercloud/cli/internal/cmd/loadbalancertype"
"github.com/hetznercloud/cli/internal/cmd/location"
"github.com/hetznercloud/cli/internal/cmd/network"
"github.com/hetznercloud/cli/internal/cmd/placementgroup"
"github.com/hetznercloud/cli/internal/cmd/server"
"github.com/hetznercloud/cli/internal/cmd/servertype"
"github.com/hetznercloud/cli/internal/cmd/sshkey"
Expand Down Expand Up @@ -53,6 +54,7 @@ func NewRootCommand(state *state.State, client hcapi2.Client) *cobra.Command {
loadbalancertype.NewCommand(state, client),
certificate.NewCommand(state, client),
firewall.NewCommand(state, client),
placementgroup.NewCommand(state, client),
)
cmd.PersistentFlags().Duration("poll-interval", 500*time.Millisecond, "Interval at which to poll information, for example action progress")
return cmd
Expand Down
49 changes: 49 additions & 0 deletions internal/cmd/placementgroup/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package placementgroup

import (
"context"
"fmt"

"github.com/hetznercloud/cli/internal/cmd/base"
"github.com/hetznercloud/cli/internal/hcapi2"
"github.com/hetznercloud/cli/internal/state"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/spf13/cobra"
)

var CreateCmd = base.Cmd{
BaseCobraCommand: func(client hcapi2.Client) *cobra.Command {
cmd := &cobra.Command{
Use: "create FLAGS",
Short: "Create a placement group",
}
cmd.Flags().String("name", "", "Name")
cmd.MarkFlagRequired("name")

cmd.Flags().StringToString("label", nil, "User-defined labels ('key=value') (can be specified multiple times)")

cmd.Flags().String("type", "", "Type of the placement group")
cmd.MarkFlagRequired("type")
return cmd
},
Run: func(ctx context.Context, client hcapi2.Client, actionWaiter state.ActionWaiter, cmd *cobra.Command, args []string) error {
name, _ := cmd.Flags().GetString("name")
labels, _ := cmd.Flags().GetStringToString("label")
placementGroupType, _ := cmd.Flags().GetString("type")

opts := hcloud.PlacementGroupCreateOpts{
Name: name,
Labels: labels,
Type: hcloud.PlacementGroupType(placementGroupType),
}

result, _, err := client.PlacementGroup().Create(ctx, opts)
if err != nil {
return err
}

fmt.Printf("Placement group %d created\n", result.PlacementGroup.ID)

return nil
},
}
51 changes: 51 additions & 0 deletions internal/cmd/placementgroup/create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package placementgroup_test

import (
"context"
"testing"
"time"

"github.com/golang/mock/gomock"
"github.com/hetznercloud/cli/internal/cmd/placementgroup"
"github.com/hetznercloud/cli/internal/testutil"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/stretchr/testify/assert"
)

func TestCreate(t *testing.T) {
fx := testutil.NewFixture(t)
defer fx.Finish()

cmd := placementgroup.CreateCmd.CobraCommand(
context.Background(),
fx.Client,
fx.TokenEnsurer,
fx.ActionWaiter)
fx.ExpectEnsureToken()

opts := hcloud.PlacementGroupCreateOpts{
Name: "my Placement Group",
Labels: map[string]string{},
Type: hcloud.PlacementGroupTypeSpread,
}

placementGroup := hcloud.PlacementGroup{
ID: 897,
Name: opts.Name,
Created: time.Now(),
Labels: opts.Labels,
Type: opts.Type,
}

fx.Client.PlacementGroupClient.EXPECT().
Create(gomock.Any(), opts).
Return(hcloud.PlacementGroupCreateResult{PlacementGroup: &placementGroup, Action: nil}, nil, nil)

out, err := fx.Run(cmd, []string{"--name", placementGroup.Name, "--type", string(placementGroup.Type)})

expOut := `Placement group 897 created
`

assert.NoError(t, err)
assert.Equal(t, expOut, out)
}
26 changes: 26 additions & 0 deletions internal/cmd/placementgroup/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package placementgroup

import (
"context"

"github.com/hetznercloud/cli/internal/cmd/base"
"github.com/hetznercloud/cli/internal/hcapi2"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/spf13/cobra"
)

var DeleteCmd = base.DeleteCmd{
ResourceNameSingular: "placement group",
ShortDescription: "Delete a placement group",
NameSuggestions: func(c hcapi2.Client) func() []string { return c.PlacementGroup().Names },
Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error) {
return client.PlacementGroup().Get(ctx, idOrName)
},
Delete: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, resource interface{}) error {
placementGroup := resource.(*hcloud.PlacementGroup)
if _, err := client.PlacementGroup().Delete(ctx, placementGroup); err != nil {
return err
}
return nil
},
}
43 changes: 43 additions & 0 deletions internal/cmd/placementgroup/delete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package placementgroup_test

import (
"context"
"testing"
"time"

"github.com/golang/mock/gomock"
"github.com/hetznercloud/cli/internal/cmd/placementgroup"
"github.com/hetznercloud/cli/internal/testutil"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/stretchr/testify/assert"
)

func TestDelete(t *testing.T) {
fx := testutil.NewFixture(t)
defer fx.Finish()

cmd := placementgroup.DeleteCmd.CobraCommand(
context.Background(),
fx.Client,
fx.TokenEnsurer)
fx.ExpectEnsureToken()

placementGroup := hcloud.PlacementGroup{
ID: 897,
Name: "my Placement Group",
Created: time.Now(),
Labels: map[string]string{"key": "value"},
Servers: []int{4711, 4712},
Type: hcloud.PlacementGroupTypeSpread,
}

fx.Client.PlacementGroupClient.EXPECT().
Get(gomock.Any(), placementGroup.Name).
Return(&placementGroup, nil, nil)
fx.Client.PlacementGroupClient.EXPECT().
Delete(gomock.Any(), &placementGroup)

_, err := fx.Run(cmd, []string{placementGroup.Name})

assert.NoError(t, err)
}
49 changes: 49 additions & 0 deletions internal/cmd/placementgroup/describe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package placementgroup

import (
"context"
"fmt"

"github.com/dustin/go-humanize"
"github.com/hetznercloud/cli/internal/cmd/base"
"github.com/hetznercloud/cli/internal/cmd/util"
"github.com/hetznercloud/cli/internal/hcapi2"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/spf13/cobra"
)

var DescribeCmd = base.DescribeCmd{
ResourceNameSingular: "placement group",
ShortDescription: "Describe a placement group",
JSONKeyGetByID: "placement group",
JSONKeyGetByName: "placement groups",
NameSuggestions: func(c hcapi2.Client) func() []string { return c.PlacementGroup().Names },
Fetch: func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error) {
return client.PlacementGroup().Get(ctx, idOrName)
},
PrintText: func(_ context.Context, client hcapi2.Client, cmd *cobra.Command, resource interface{}) error {
placementGroup := resource.(*hcloud.PlacementGroup)

fmt.Printf("ID:\t\t%d\n", placementGroup.ID)
fmt.Printf("Name:\t\t%s\n", placementGroup.Name)
fmt.Printf("Created:\t%s (%s)\n", util.Datetime(placementGroup.Created), humanize.Time(placementGroup.Created))

fmt.Print("Labels:\n")
if len(placementGroup.Labels) == 0 {
fmt.Print(" No labels\n")
} else {
for key, value := range placementGroup.Labels {
fmt.Printf(" %s: %s\n", key, value)
}
}

fmt.Print("Servers:\n")
for _, serverID := range placementGroup.Servers {
fmt.Printf(" - Server ID:\t\t%d\n", serverID)
fmt.Printf(" Server Name:\t%s\n", client.Server().ServerName(serverID))
}

fmt.Printf("Type:\t\t%s\n", placementGroup.Type)
return nil
},
}
66 changes: 66 additions & 0 deletions internal/cmd/placementgroup/describe_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package placementgroup_test

import (
"context"
"fmt"
"testing"
"time"

"github.com/dustin/go-humanize"
"github.com/golang/mock/gomock"
"github.com/hetznercloud/cli/internal/cmd/placementgroup"
"github.com/hetznercloud/cli/internal/cmd/util"
"github.com/hetznercloud/cli/internal/testutil"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/stretchr/testify/assert"
)

func TestDescribe(t *testing.T) {
fx := testutil.NewFixture(t)
defer fx.Finish()

cmd := placementgroup.DescribeCmd.CobraCommand(
context.Background(),
fx.Client,
fx.TokenEnsurer)
fx.ExpectEnsureToken()

placementGroup := hcloud.PlacementGroup{
ID: 897,
Name: "my Placement Group",
Created: time.Date(2021, 07, 23, 10, 0, 0, 0, time.UTC),
Labels: map[string]string{"key": "value"},
Servers: []int{4711, 4712},
Type: hcloud.PlacementGroupTypeSpread,
}

fx.Client.PlacementGroupClient.EXPECT().
Get(gomock.Any(), placementGroup.Name).
Return(&placementGroup, nil, nil)
fx.Client.ServerClient.EXPECT().
ServerName(4711).
Return("server1")
fx.Client.ServerClient.EXPECT().
ServerName(4712).
Return("server2")

out, err := fx.Run(cmd, []string{placementGroup.Name})

expOut := fmt.Sprintf(`ID: 897
Name: my Placement Group
Created: %s (%s)
Labels:
key: value
Servers:
- Server ID: 4711
Server Name: server1
- Server ID: 4712
Server Name: server2
Type: spread
`, util.Datetime(placementGroup.Created),
humanize.Time(placementGroup.Created),
)

assert.NoError(t, err)
assert.Equal(t, expOut, out)
}
35 changes: 35 additions & 0 deletions internal/cmd/placementgroup/labels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package placementgroup

import (
"context"
"fmt"

"github.com/hetznercloud/cli/internal/cmd/base"
"github.com/hetznercloud/cli/internal/hcapi2"
"github.com/hetznercloud/hcloud-go/hcloud"
)

var LabelCmds = base.LabelCmds{
ResourceNameSingular: "placement group",
ShortDescriptionAdd: "Add a label to a placement group",
ShortDescriptionRemove: "Remove a label from a placement group",
NameSuggestions: func(c hcapi2.Client) func() []string { return c.PlacementGroup().Names },
LabelKeySuggestions: func(c hcapi2.Client) func(idOrName string) []string { return c.PlacementGroup().LabelKeys },
FetchLabels: func(ctx context.Context, client hcapi2.Client, idOrName string) (map[string]string, int, error) {
placementGroup, _, err := client.PlacementGroup().Get(ctx, idOrName)
if err != nil {
return nil, 0, err
}
if placementGroup == nil {
return nil, 0, fmt.Errorf("placement group not found: %s", idOrName)
}
return placementGroup.Labels, placementGroup.ID, nil
},
SetLabels: func(ctx context.Context, client hcapi2.Client, id int, labels map[string]string) error {
opts := hcloud.PlacementGroupUpdateOpts{
Labels: labels,
}
_, _, err := client.PlacementGroup().Update(ctx, &hcloud.PlacementGroup{ID: id}, opts)
return err
},
}
Loading

0 comments on commit 4b8ed4d

Please sign in to comment.