Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: split up label command into separate Fetch/GetLabels/GetIDOrName #967

Merged
merged 2 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
38 changes: 22 additions & 16 deletions internal/cmd/base/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,20 @@ import (
)

// LabelCmds allows defining commands for adding labels to resources.
type LabelCmds struct {
type LabelCmds[T any] struct {
ResourceNameSingular string
ShortDescriptionAdd string
ShortDescriptionRemove string
NameSuggestions func(client hcapi2.Client) func() []string
LabelKeySuggestions func(client hcapi2.Client) func(idOrName string) []string
FetchLabels func(s state.State, idOrName string) (map[string]string, int64, error)
SetLabels func(s state.State, id int64, labels map[string]string) error
Fetch func(s state.State, idOrName string) (T, error)
SetLabels func(s state.State, resource T, labels map[string]string) error
GetLabels func(resource T) map[string]string
GetIDOrName func(resource T) string
}

// AddCobraCommand creates a command that can be registered with cobra.
func (lc *LabelCmds) AddCobraCommand(s state.State) *cobra.Command {
func (lc *LabelCmds[T]) AddCobraCommand(s state.State) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("add-label [--overwrite] <%s> <label>...", util.ToKebabCase(lc.ResourceNameSingular)),
Short: lc.ShortDescriptionAdd,
Expand All @@ -43,15 +45,17 @@ func (lc *LabelCmds) AddCobraCommand(s state.State) *cobra.Command {
}

// RunAdd executes an add label command
func (lc *LabelCmds) RunAdd(s state.State, cmd *cobra.Command, args []string) error {
func (lc *LabelCmds[T]) RunAdd(s state.State, cmd *cobra.Command, args []string) error {
overwrite, _ := cmd.Flags().GetBool("overwrite")
idOrName := args[0]

labels, id, err := lc.FetchLabels(s, idOrName)
resource, err := lc.Fetch(s, idOrName)
if err != nil {
return err
}

labels, idOrName := lc.GetLabels(resource), lc.GetIDOrName(resource)

if labels == nil {
labels = map[string]string{}
}
Expand All @@ -62,17 +66,17 @@ func (lc *LabelCmds) RunAdd(s state.State, cmd *cobra.Command, args []string) er
keys = append(keys, key)

if _, ok := labels[key]; ok && !overwrite {
return fmt.Errorf("label %s on %s %d already exists", key, lc.ResourceNameSingular, id)
return fmt.Errorf("label %s on %s %s already exists", key, lc.ResourceNameSingular, idOrName)
}

labels[key] = val
}

if err := lc.SetLabels(s, id, labels); err != nil {
if err := lc.SetLabels(s, resource, labels); err != nil {
return err
}

cmd.Printf("Label(s) %s added to %s %d\n", strings.Join(keys, ", "), lc.ResourceNameSingular, id)
cmd.Printf("Label(s) %s added to %s %s\n", strings.Join(keys, ", "), lc.ResourceNameSingular, idOrName)
return nil
}

Expand All @@ -87,7 +91,7 @@ func validateAddLabel(_ *cobra.Command, args []string) error {
}

// RemoveCobraCommand creates a command that can be registered with cobra.
func (lc *LabelCmds) RemoveCobraCommand(s state.State) *cobra.Command {
func (lc *LabelCmds[T]) RemoveCobraCommand(s state.State) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("remove-label <%s> (--all | <label>...)", util.ToKebabCase(lc.ResourceNameSingular)),
Short: lc.ShortDescriptionRemove,
Expand All @@ -113,34 +117,36 @@ func (lc *LabelCmds) RemoveCobraCommand(s state.State) *cobra.Command {
}

// RunRemove executes a remove label command
func (lc *LabelCmds) RunRemove(s state.State, cmd *cobra.Command, args []string) error {
func (lc *LabelCmds[T]) RunRemove(s state.State, cmd *cobra.Command, args []string) error {
all, _ := cmd.Flags().GetBool("all")
idOrName := args[0]

labels, id, err := lc.FetchLabels(s, idOrName)
resource, err := lc.Fetch(s, idOrName)
if err != nil {
return err
}

labels, idOrName := lc.GetLabels(resource), lc.GetIDOrName(resource)

if all {
labels = make(map[string]string)
} else {
for _, key := range args[1:] {
if _, ok := labels[key]; !ok {
return fmt.Errorf("label %s on %s %d does not exist", key, lc.ResourceNameSingular, id)
return fmt.Errorf("label %s on %s %s does not exist", key, lc.ResourceNameSingular, idOrName)
}
delete(labels, key)
}
}

if err := lc.SetLabels(s, id, labels); err != nil {
if err := lc.SetLabels(s, resource, labels); err != nil {
return err
}

if all {
cmd.Printf("All labels removed from %s %d\n", lc.ResourceNameSingular, id)
cmd.Printf("All labels removed from %s %s\n", lc.ResourceNameSingular, idOrName)
} else {
cmd.Printf("Label(s) %s removed from %s %d\n", strings.Join(args[1:], ", "), lc.ResourceNameSingular, id)
cmd.Printf("Label(s) %s removed from %s %s\n", strings.Join(args[1:], ", "), lc.ResourceNameSingular, idOrName)
}

return nil
Expand Down
21 changes: 14 additions & 7 deletions internal/cmd/certificate/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,41 @@ package certificate

import (
"fmt"
"strconv"

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

var LabelCmds = base.LabelCmds{
var LabelCmds = base.LabelCmds[*hcloud.Certificate]{
ResourceNameSingular: "certificate",
ShortDescriptionAdd: "Add a label to an certificate",
ShortDescriptionRemove: "Remove a label from an certificate",
NameSuggestions: func(c hcapi2.Client) func() []string { return c.Certificate().Names },
LabelKeySuggestions: func(c hcapi2.Client) func(idOrName string) []string { return c.Certificate().LabelKeys },
FetchLabels: func(s state.State, idOrName string) (map[string]string, int64, error) {
Fetch: func(s state.State, idOrName string) (*hcloud.Certificate, error) {
certificate, _, err := s.Client().Certificate().Get(s, idOrName)
if err != nil {
return nil, 0, err
return nil, err
}
if certificate == nil {
return nil, 0, fmt.Errorf("certificate not found: %s", idOrName)
return nil, fmt.Errorf("certificate not found: %s", idOrName)
}
return certificate.Labels, certificate.ID, nil
return certificate, nil
},
SetLabels: func(s state.State, id int64, labels map[string]string) error {
SetLabels: func(s state.State, cert *hcloud.Certificate, labels map[string]string) error {
opts := hcloud.CertificateUpdateOpts{
Labels: labels,
}
_, _, err := s.Client().Certificate().Update(s, &hcloud.Certificate{ID: id}, opts)
_, _, err := s.Client().Certificate().Update(s, cert, opts)
return err
},
GetLabels: func(cert *hcloud.Certificate) map[string]string {
return cert.Labels
},
GetIDOrName: func(cert *hcloud.Certificate) string {
return strconv.FormatInt(cert.ID, 10)
},
}
16 changes: 9 additions & 7 deletions internal/cmd/certificate/labels_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,18 @@ func TestLabelRemove(t *testing.T) {
cmd := certificate.LabelCmds.RemoveCobraCommand(fx.State())
fx.ExpectEnsureToken()

cert := &hcloud.Certificate{
ID: 123,
Labels: map[string]string{
"key": "value",
},
}

fx.Client.CertificateClient.EXPECT().
Get(gomock.Any(), "123").
Return(&hcloud.Certificate{
ID: 123,
Labels: map[string]string{
"key": "value",
},
}, nil, nil)
Return(cert, nil, nil)
fx.Client.CertificateClient.EXPECT().
Update(gomock.Any(), &hcloud.Certificate{ID: 123}, hcloud.CertificateUpdateOpts{
Update(gomock.Any(), cert, hcloud.CertificateUpdateOpts{
Labels: make(map[string]string),
})

Expand Down
21 changes: 14 additions & 7 deletions internal/cmd/firewall/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,41 @@ package firewall

import (
"fmt"
"strconv"

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

var LabelCmds = base.LabelCmds{
var LabelCmds = base.LabelCmds[*hcloud.Firewall]{
ResourceNameSingular: "firewall",
ShortDescriptionAdd: "Add a label to an firewall",
ShortDescriptionRemove: "Remove a label from an firewall",
NameSuggestions: func(c hcapi2.Client) func() []string { return c.Firewall().Names },
LabelKeySuggestions: func(c hcapi2.Client) func(idOrName string) []string { return c.Firewall().LabelKeys },
FetchLabels: func(s state.State, idOrName string) (map[string]string, int64, error) {
Fetch: func(s state.State, idOrName string) (*hcloud.Firewall, error) {
firewall, _, err := s.Client().Firewall().Get(s, idOrName)
if err != nil {
return nil, 0, err
return nil, err
}
if firewall == nil {
return nil, 0, fmt.Errorf("firewall not found: %s", idOrName)
return nil, fmt.Errorf("firewall not found: %s", idOrName)
}
return firewall.Labels, firewall.ID, nil
return firewall, nil
},
SetLabels: func(s state.State, id int64, labels map[string]string) error {
SetLabels: func(s state.State, firewall *hcloud.Firewall, labels map[string]string) error {
opts := hcloud.FirewallUpdateOpts{
Labels: labels,
}
_, _, err := s.Client().Firewall().Update(s, &hcloud.Firewall{ID: id}, opts)
_, _, err := s.Client().Firewall().Update(s, firewall, opts)
return err
},
GetLabels: func(firewall *hcloud.Firewall) map[string]string {
return firewall.Labels
},
GetIDOrName: func(firewall *hcloud.Firewall) string {
return strconv.FormatInt(firewall.ID, 10)
},
}
16 changes: 9 additions & 7 deletions internal/cmd/firewall/labels_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,18 @@ func TestLabelRemove(t *testing.T) {
cmd := firewall.LabelCmds.RemoveCobraCommand(fx.State())
fx.ExpectEnsureToken()

fw := &hcloud.Firewall{
ID: 123,
Labels: map[string]string{
"key": "value",
},
}

fx.Client.FirewallClient.EXPECT().
Get(gomock.Any(), "123").
Return(&hcloud.Firewall{
ID: 123,
Labels: map[string]string{
"key": "value",
},
}, nil, nil)
Return(fw, nil, nil)
fx.Client.FirewallClient.EXPECT().
Update(gomock.Any(), &hcloud.Firewall{ID: 123}, hcloud.FirewallUpdateOpts{
Update(gomock.Any(), fw, hcloud.FirewallUpdateOpts{
Labels: make(map[string]string),
})

Expand Down
21 changes: 14 additions & 7 deletions internal/cmd/floatingip/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,41 @@ package floatingip

import (
"fmt"
"strconv"

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

var LabelCmds = base.LabelCmds{
var LabelCmds = base.LabelCmds[*hcloud.FloatingIP]{
ResourceNameSingular: "Floating IP",
ShortDescriptionAdd: "Add a label to an Floating IP",
ShortDescriptionRemove: "Remove a label from an Floating IP",
NameSuggestions: func(c hcapi2.Client) func() []string { return c.FloatingIP().Names },
LabelKeySuggestions: func(c hcapi2.Client) func(idOrName string) []string { return c.FloatingIP().LabelKeys },
FetchLabels: func(s state.State, idOrName string) (map[string]string, int64, error) {
Fetch: func(s state.State, idOrName string) (*hcloud.FloatingIP, error) {
floatingIP, _, err := s.Client().FloatingIP().Get(s, idOrName)
if err != nil {
return nil, 0, err
return nil, err
}
if floatingIP == nil {
return nil, 0, fmt.Errorf("floating IP not found: %s", idOrName)
return nil, fmt.Errorf("floating IP not found: %s", idOrName)
}
return floatingIP.Labels, floatingIP.ID, nil
return floatingIP, nil
},
SetLabels: func(s state.State, id int64, labels map[string]string) error {
SetLabels: func(s state.State, floatingIP *hcloud.FloatingIP, labels map[string]string) error {
opts := hcloud.FloatingIPUpdateOpts{
Labels: labels,
}
_, _, err := s.Client().FloatingIP().Update(s, &hcloud.FloatingIP{ID: id}, opts)
_, _, err := s.Client().FloatingIP().Update(s, floatingIP, opts)
return err
},
GetLabels: func(floatingIP *hcloud.FloatingIP) map[string]string {
return floatingIP.Labels
},
GetIDOrName: func(floatingIP *hcloud.FloatingIP) string {
return strconv.FormatInt(floatingIP.ID, 10)
},
}
16 changes: 9 additions & 7 deletions internal/cmd/floatingip/labels_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,18 @@ func TestLabelRemove(t *testing.T) {
cmd := floatingip.LabelCmds.RemoveCobraCommand(fx.State())
fx.ExpectEnsureToken()

floatingIP := &hcloud.FloatingIP{
ID: 123,
Labels: map[string]string{
"key": "value",
},
}

fx.Client.FloatingIPClient.EXPECT().
Get(gomock.Any(), "123").
Return(&hcloud.FloatingIP{
ID: 123,
Labels: map[string]string{
"key": "value",
},
}, nil, nil)
Return(floatingIP, nil, nil)
fx.Client.FloatingIPClient.EXPECT().
Update(gomock.Any(), &hcloud.FloatingIP{ID: 123}, hcloud.FloatingIPUpdateOpts{
Update(gomock.Any(), floatingIP, hcloud.FloatingIPUpdateOpts{
Labels: make(map[string]string),
})

Expand Down
22 changes: 14 additions & 8 deletions internal/cmd/image/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,37 @@ import (
"github.com/hetznercloud/hcloud-go/v2/hcloud"
)

var LabelCmds = base.LabelCmds{
var LabelCmds = base.LabelCmds[*hcloud.Image]{
ResourceNameSingular: "image",
ShortDescriptionAdd: "Add a label to an image",
ShortDescriptionRemove: "Remove a label from an image",
NameSuggestions: func(c hcapi2.Client) func() []string { return c.Image().Names },
LabelKeySuggestions: func(c hcapi2.Client) func(idOrName string) []string { return c.Image().LabelKeys },
FetchLabels: func(s state.State, idOrName string) (map[string]string, int64, error) {
Fetch: func(s state.State, idOrName string) (*hcloud.Image, error) {
id, err := strconv.ParseInt(idOrName, 10, 64)
if err != nil {
return nil, 0, fmt.Errorf("invalid snapshot or backup ID %q", idOrName)
return nil, fmt.Errorf("invalid snapshot or backup ID %q", idOrName)
}
image, _, err := s.Client().Image().GetByID(s, id)
if err != nil {
return nil, 0, err
return nil, err
}
if image == nil {
return nil, 0, fmt.Errorf("image not found: %s", idOrName)
return nil, fmt.Errorf("image not found: %s", idOrName)
}
return image.Labels, image.ID, nil
return image, nil
},
SetLabels: func(s state.State, id int64, labels map[string]string) error {
SetLabels: func(s state.State, image *hcloud.Image, labels map[string]string) error {
opts := hcloud.ImageUpdateOpts{
Labels: labels,
}
_, _, err := s.Client().Image().Update(s, &hcloud.Image{ID: id}, opts)
_, _, err := s.Client().Image().Update(s, image, opts)
return err
},
GetLabels: func(image *hcloud.Image) map[string]string {
return image.Labels
},
GetIDOrName: func(image *hcloud.Image) string {
return strconv.FormatInt(image.ID, 10)
},
}
Loading