Skip to content

Commit

Permalink
Implement Label Selector and IP target support (#258)
Browse files Browse the repository at this point in the history
  • Loading branch information
LKaemmerling authored Aug 10, 2020
1 parent d5a31ce commit c1bd46c
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 41 deletions.
51 changes: 42 additions & 9 deletions cli/load_balancer_add_target.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package cli

import (
"fmt"
"net"

"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/spf13/cobra"
)
Expand All @@ -21,31 +23,43 @@ func newLoadBalancerAddTargetCommand(cli *CLI) *cobra.Command {
cmd.Flag("server").Annotations = map[string][]string{
cobra.BashCompCustom: {"__hcloud_server_names"},
}
cmd.Flags().String("label-selector", "", "Label Selector")
cmd.Flags().Bool("use-private-ip", false, "Determine if the Load Balancer should connect to the target via the network")
cmd.Flags().String("ip", "", "Use the passed IP address as target")
return cmd
}

func runLoadBalancerAddTarget(cli *CLI, cmd *cobra.Command, args []string) error {
serverIdOrName, _ := cmd.Flags().GetString("server")
var (
action *hcloud.Action
loadBalancer *hcloud.LoadBalancer
err error
)

idOrName := args[0]
usePrivateIP, _ := cmd.Flags().GetBool("use-private-ip")
serverIDOrName, _ := cmd.Flags().GetString("server")
labelSelector, _ := cmd.Flags().GetString("label-selector")
ipAddr, _ := cmd.Flags().GetString("ip")

loadBalancer, _, err := cli.Client().LoadBalancer.Get(cli.Context, idOrName)
if err != nil {
if !exactlyOneSet(serverIDOrName, labelSelector, ipAddr) {
return fmt.Errorf("--server, --label-selector, and --ip are mutually exclusive")
}
if loadBalancer, _, err = cli.Client().LoadBalancer.Get(cli.Context, idOrName); err != nil {
return err
}
if loadBalancer == nil {
return fmt.Errorf("Load Balancer not found: %s", idOrName)
}

var action *hcloud.Action
if serverIdOrName != "" {
server, _, err := cli.Client().Server.Get(cli.Context, serverIdOrName)
switch {
case serverIDOrName != "":
server, _, err := cli.Client().Server.Get(cli.Context, serverIDOrName)
if err != nil {
return err
}
if server == nil {
return fmt.Errorf("server not found: %s", serverIdOrName)
return fmt.Errorf("server not found: %s", serverIDOrName)
}
action, _, err = cli.Client().LoadBalancer.AddServerTarget(cli.Context, loadBalancer, hcloud.LoadBalancerAddServerTargetOpts{
Server: server,
Expand All @@ -54,8 +68,27 @@ func runLoadBalancerAddTarget(cli *CLI, cmd *cobra.Command, args []string) error
if err != nil {
return err
}
} else {
return fmt.Errorf("specify one of server")
case labelSelector != "":
action, _, err = cli.Client().LoadBalancer.AddLabelSelectorTarget(cli.Context, loadBalancer, hcloud.LoadBalancerAddLabelSelectorTargetOpts{
Selector: labelSelector,
UsePrivateIP: hcloud.Bool(usePrivateIP),
})
if err != nil {
return err
}
case ipAddr != "":
ip := net.ParseIP(ipAddr)
if ip == nil {
return fmt.Errorf("invalid ip provided")
}
action, _, err = cli.Client().LoadBalancer.AddIPTarget(cli.Context, loadBalancer, hcloud.LoadBalancerAddIPTargetOpts{
IP: ip,
})
if err != nil {
return err
}
default:
return fmt.Errorf("specify one of --server, --label-selector, or --ip")
}

if err := cli.ActionProgress(cli.Context, action); err != nil {
Expand Down
64 changes: 46 additions & 18 deletions cli/load_balancer_describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cli
import (
"encoding/json"
"fmt"

humanize "github.com/dustin/go-humanize"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/spf13/cobra"
Expand All @@ -19,12 +20,13 @@ func newLoadBalancerDescribeCommand(cli *CLI) *cobra.Command {
RunE: cli.wrap(runLoadBalancerDescribe),
}
addOutputFlag(cmd, outputOptionJSON(), outputOptionFormat())
//cmd.Flags().Bool("expand-targets", false, "Expand all label_selector targets")
cmd.Flags().Bool("expand-targets", false, "Expand all label_selector targets")
return cmd
}

func runLoadBalancerDescribe(cli *CLI, cmd *cobra.Command, args []string) error {
outputFlags := outputFlagsForCommand(cmd)
withLabelSelectorTargets, _ := cmd.Flags().GetBool("expand-targets")
idOrName := args[0]
loadBalancer, resp, err := cli.Client().LoadBalancer.Get(cli.Context, idOrName)
if err != nil {
Expand All @@ -40,11 +42,11 @@ func runLoadBalancerDescribe(cli *CLI, cmd *cobra.Command, args []string) error
case outputFlags.IsSet("format"):
return describeFormat(loadBalancer, outputFlags["format"][0])
default:
return loadBalancerDescribeText(cli, loadBalancer)
return loadBalancerDescribeText(cli, loadBalancer, withLabelSelectorTargets)
}
}

func loadBalancerDescribeText(cli *CLI, loadBalancer *hcloud.LoadBalancer) error {
func loadBalancerDescribeText(cli *CLI, loadBalancer *hcloud.LoadBalancer, withLabelSelectorTargets bool) error {
fmt.Printf("ID:\t\t\t\t%d\n", loadBalancer.ID)
fmt.Printf("Name:\t\t\t\t%s\n", loadBalancer.Name)
fmt.Printf("Created:\t\t\t%s (%s)\n", datetime(loadBalancer.Created), humanize.Time(loadBalancer.Created))
Expand Down Expand Up @@ -119,26 +121,52 @@ func loadBalancerDescribeText(cli *CLI, loadBalancer *hcloud.LoadBalancer) error
fmt.Printf("Targets:\n")
if len(loadBalancer.Targets) == 0 {
fmt.Print(" No targets\n")
} else {
for _, target := range loadBalancer.Targets {
fmt.Printf(" - Type:\t\t\t%s\n", target.Type)
if target.Server != nil {
fmt.Printf(" Server:\n")
fmt.Printf(" ID:\t\t\t%d\n", target.Server.Server.ID)
fmt.Printf(" Name:\t\t\t%s\n", cli.GetServerName(target.Server.Server.ID))
fmt.Printf(" Use Private IP:\t\t%s\n", yesno(target.UsePrivateIP))
fmt.Printf(" Status:\n")
for _, healthStatus := range target.HealthStatus {
fmt.Printf(" - Service:\t\t\t%d\n", healthStatus.ListenPort)
fmt.Printf(" Status:\t\t\t%s\n", healthStatus.Status)
}
for _, target := range loadBalancer.Targets {
fmt.Printf(" - Type:\t\t\t%s\n", target.Type)
switch target.Type {
case hcloud.LoadBalancerTargetTypeServer:
fmt.Printf(" Server:\n")
fmt.Printf(" ID:\t\t\t%d\n", target.Server.Server.ID)
fmt.Printf(" Name:\t\t\t%s\n", cli.GetServerName(target.Server.Server.ID))
fmt.Printf(" Use Private IP:\t\t%s\n", yesno(target.UsePrivateIP))
fmt.Printf(" Status:\n")
for _, healthStatus := range target.HealthStatus {
fmt.Printf(" - Service:\t\t\t%d\n", healthStatus.ListenPort)
fmt.Printf(" Status:\t\t\t%s\n", healthStatus.Status)
}
case hcloud.LoadBalancerTargetTypeLabelSelector:
fmt.Printf(" Label Selector:\t\t%s\n", target.LabelSelector.Selector)
fmt.Printf(" Targets: (%d)\n", len(target.Targets))
if len(target.Targets) == 0 {
fmt.Print(" No targets\n")
}
if !withLabelSelectorTargets {
continue
}
for _, lbtarget := range target.Targets {
fmt.Printf(" - Type:\t\t\t\t%s\n", lbtarget.Type)
fmt.Printf(" Server ID:\t\t\t%d\n", lbtarget.Server.Server.ID)
fmt.Printf(" Status:\n")
for _, healthStatus := range lbtarget.HealthStatus {
fmt.Printf(" - Service:\t\t\t%d\n", healthStatus.ListenPort)
fmt.Printf(" Status:\t\t\t%s\n", healthStatus.Status)
}
}
case hcloud.LoadBalancerTargetTypeIP:
fmt.Printf(" IP:\t\t\t\t%s\n", target.IP.IP)
fmt.Printf(" Status:\n")
for _, healthStatus := range target.HealthStatus {
fmt.Printf(" - Service:\t\t\t%d\n", healthStatus.ListenPort)
fmt.Printf(" Status:\t\t\t%s\n", healthStatus.Status)
}
}
}

fmt.Printf("Traffic:\n")
fmt.Printf(" Outgoing:\t%v\n", humanize.Bytes(loadBalancer.OutgoingTraffic))
fmt.Printf(" Ingoing:\t%v\n", humanize.Bytes(loadBalancer.IngoingTraffic))
fmt.Printf(" Included:\t%v\n", humanize.Bytes(loadBalancer.IncludedTraffic))
fmt.Printf(" Outgoing:\t%v\n", humanize.IBytes(loadBalancer.OutgoingTraffic))
fmt.Printf(" Ingoing:\t%v\n", humanize.IBytes(loadBalancer.IngoingTraffic))
fmt.Printf(" Included:\t%v\n", humanize.IBytes(loadBalancer.IncludedTraffic))

fmt.Printf("Protection:\n")
fmt.Printf(" Delete:\t%s\n", yesno(loadBalancer.Protection.Delete))
Expand Down
6 changes: 6 additions & 0 deletions cli/load_balancer_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ func runLoadBalancerList(cli *CLI, cmd *cobra.Command, args []string) error {
if target.Type == hcloud.LoadBalancerTargetTypeServer {
targetSchema.Server = &schema.LoadBalancerTargetServer{ID: target.Server.Server.ID}
}
if target.Type == hcloud.LoadBalancerTargetTypeLabelSelector {
targetSchema.LabelSelector = &schema.LoadBalancerTargetLabelSelector{Selector: target.LabelSelector.Selector}
}
if target.Type == hcloud.LoadBalancerTargetTypeIP {
targetSchema.IP = &schema.LoadBalancerTargetIP{IP: target.IP.IP}
}
for _, healthStatus := range target.HealthStatus {
targetSchema.HealthStatus = append(targetSchema.HealthStatus, schema.LoadBalancerTargetHealthStatus{
ListenPort: healthStatus.ListenPort,
Expand Down
46 changes: 37 additions & 9 deletions cli/load_balancer_remove_target.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package cli

import (
"fmt"
"net"

"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/spf13/cobra"
)
Expand All @@ -21,37 +23,63 @@ func newLoadBalancerRemoveTargetCommand(cli *CLI) *cobra.Command {
cmd.Flag("server").Annotations = map[string][]string{
cobra.BashCompCustom: {"__hcloud_server_names"},
}

cmd.Flags().String("label-selector", "", "Label Selector")
cmd.Flags().String("ip", "", "IP address of an IP target")
return cmd
}

func runLoadBalancerRemoveTarget(cli *CLI, cmd *cobra.Command, args []string) error {
serverIdOrName, _ := cmd.Flags().GetString("server")
var (
action *hcloud.Action
loadBalancer *hcloud.LoadBalancer
err error
)

serverIDOrName, _ := cmd.Flags().GetString("server")
labelSelector, _ := cmd.Flags().GetString("label-selector")
ipAddr, _ := cmd.Flags().GetString("ip")

idOrName := args[0]

loadBalancer, _, err := cli.Client().LoadBalancer.Get(cli.Context, idOrName)
loadBalancer, _, err = cli.Client().LoadBalancer.Get(cli.Context, idOrName)
if err != nil {
return err
}
if loadBalancer == nil {
return fmt.Errorf("Load Balancer not found: %s", idOrName)
}

var action *hcloud.Action
if serverIdOrName == "" {
return fmt.Errorf("specify a server")
} else if serverIdOrName != "" {
server, _, err := cli.Client().Server.Get(cli.Context, serverIdOrName)
if !exactlyOneSet(serverIDOrName, labelSelector, ipAddr) {
return fmt.Errorf("--server, --label-selector, and --ip are mutually exclusive")
}
switch {
case serverIDOrName != "":
server, _, err := cli.Client().Server.Get(cli.Context, serverIDOrName)
if err != nil {
return err
}
if server == nil {
return fmt.Errorf("server not found: %s", serverIdOrName)
return fmt.Errorf("server not found: %s", serverIDOrName)
}
action, _, err = cli.Client().LoadBalancer.RemoveServerTarget(cli.Context, loadBalancer, server)
if err != nil {
return err
}
case labelSelector != "":
action, _, err = cli.Client().LoadBalancer.RemoveLabelSelectorTarget(cli.Context, loadBalancer, labelSelector)
if err != nil {
return err
}
case ipAddr != "":
ip := net.ParseIP(ipAddr)
if ip == nil {
return fmt.Errorf("invalid ip provided")
}
if action, _, err = cli.Client().LoadBalancer.RemoveIPTarget(cli.Context, loadBalancer, ip); err != nil {
return err
}
default:
return fmt.Errorf("specify one of --server, --label-selector, or --ip")
}

if err := cli.ActionProgress(cli.Context, action); err != nil {
Expand Down
16 changes: 14 additions & 2 deletions cli/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ package cli
import (
"encoding/json"
"fmt"
"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/hetznercloud/hcloud-go/hcloud/schema"
"os"
"strings"
"text/template"
"time"

"github.com/hetznercloud/hcloud-go/hcloud"
"github.com/hetznercloud/hcloud-go/hcloud/schema"

"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -45,6 +46,17 @@ func chainRunE(fns ...func(cmd *cobra.Command, args []string) error) func(cmd *c
}
}

func exactlyOneSet(s string, ss ...string) bool {
set := s != ""
for _, s := range ss {
if set && s != "" {
return false
}
set = set || s != ""
}
return set
}

var outputDescription = `Output can be controlled with the -o flag. Use -o noheader to suppress the
table header. Displayed columns and their order can be set with
-o columns=%s (see available columns below).`
Expand Down
48 changes: 48 additions & 0 deletions cli/util_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package cli

import "testing"

func TestOnlyOneSet(t *testing.T) {
tests := []struct {
name string
s string
ss []string
expected bool
}{
{
name: "only arg emtpy",
expected: false,
},
{
name: "only arg non-empty",
s: "s",
expected: true,
},
{
name: "first arg non-empty, rest empty",
s: "s",
ss: []string{""},
expected: true,
},
{
name: "at least one other arg non-empty",
s: "s",
ss: []string{"", "s"},
},
{
name: "only one arg non-empty",
ss: []string{"", "s"},
expected: true,
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
actual := exactlyOneSet(tt.s, tt.ss...)
if tt.expected != actual {
t.Errorf("expected %t; got %t", tt.expected, actual)
}
})
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ require (
github.com/cheggaaa/pb/v3 v3.0.4
github.com/dustin/go-humanize v1.0.0
github.com/fatih/structs v1.1.0
github.com/hetznercloud/hcloud-go v1.19.0
github.com/hetznercloud/hcloud-go v1.20.0
github.com/pelletier/go-toml v1.7.0
github.com/spf13/cobra v0.0.7
github.com/spf13/pflag v1.0.5
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmg
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hetznercloud/hcloud-go v1.19.0 h1:8g28MQg8Eg97K7GASKUnaTZSNKo9CB73Xfxbt/NjvnU=
github.com/hetznercloud/hcloud-go v1.19.0/go.mod h1:EhElojlVU1biA5JgBaV8rRU1vE5+iYke402kXC9pooE=
github.com/hetznercloud/hcloud-go v1.20.0 h1:4EFRCoOlEUdg4qv4doVOKkGRpJhZlh/l6sUSJpConpw=
github.com/hetznercloud/hcloud-go v1.20.0/go.mod h1:EhElojlVU1biA5JgBaV8rRU1vE5+iYke402kXC9pooE=
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

0 comments on commit c1bd46c

Please sign in to comment.