Skip to content

Commit

Permalink
refactor: make base describe command generic
Browse files Browse the repository at this point in the history
More type safety <3
  • Loading branch information
phm07 authored and apricote committed Jan 31, 2025
1 parent 0ccd41c commit 591d1d8
Show file tree
Hide file tree
Showing 18 changed files with 56 additions and 86 deletions.
10 changes: 5 additions & 5 deletions internal/cmd/base/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
)

// DescribeCmd allows defining commands for describing a resource.
type DescribeCmd struct {
type DescribeCmd[T any] struct {
ResourceNameSingular string // e.g. "server"
ShortDescription string
// key in API response JSON to use for extracting object from response body for JSON output.
Expand All @@ -26,12 +26,12 @@ type DescribeCmd struct {
AdditionalFlags func(*cobra.Command)
// Fetch is called to fetch the resource to describe.
// The first returned interface is the resource itself as a hcloud struct, the second is the schema for the resource.
Fetch func(s state.State, cmd *cobra.Command, idOrName string) (interface{}, interface{}, error)
PrintText func(s state.State, cmd *cobra.Command, resource interface{}) error
Fetch func(s state.State, cmd *cobra.Command, idOrName string) (T, any, error)
PrintText func(s state.State, cmd *cobra.Command, resource T) error
}

// CobraCommand creates a command that can be registered with cobra.
func (dc *DescribeCmd) CobraCommand(s state.State) *cobra.Command {
func (dc *DescribeCmd[T]) CobraCommand(s state.State) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("describe [options] <%s>", util.ToKebabCase(dc.ResourceNameSingular)),
Short: dc.ShortDescription,
Expand All @@ -52,7 +52,7 @@ func (dc *DescribeCmd) CobraCommand(s state.State) *cobra.Command {
}

// Run executes a describe command.
func (dc *DescribeCmd) Run(s state.State, cmd *cobra.Command, args []string) error {
func (dc *DescribeCmd[T]) Run(s state.State, cmd *cobra.Command, args []string) error {
outputFlags := output.FlagsForCommand(cmd)

quiet, err := config.OptionQuiet.Get(s.Config())
Expand Down
7 changes: 3 additions & 4 deletions internal/cmd/base/describe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import (
"github.com/hetznercloud/cli/internal/testutil"
)

var fakeDescribeCmd = &base.DescribeCmd{
var fakeDescribeCmd = &base.DescribeCmd[*fakeResource]{
ResourceNameSingular: "Fake resource",

Fetch: func(_ state.State, cmd *cobra.Command, _ string) (interface{}, interface{}, error) {
Fetch: func(_ state.State, cmd *cobra.Command, _ string) (*fakeResource, any, error) {
cmd.Println("Fetching fake resource")

resource := &fakeResource{
Expand All @@ -26,8 +26,7 @@ var fakeDescribeCmd = &base.DescribeCmd{
return resource, util.Wrap("resource", resource), nil
},

PrintText: func(_ state.State, cmd *cobra.Command, resource interface{}) error {
rsc := resource.(*fakeResource)
PrintText: func(_ state.State, cmd *cobra.Command, rsc *fakeResource) error {
cmd.Printf("ID: %d\n", rsc.ID)
cmd.Printf("Name: %s\n", rsc.Name)
return nil
Expand Down
7 changes: 3 additions & 4 deletions internal/cmd/certificate/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,20 @@ import (
"github.com/hetznercloud/hcloud-go/v2/hcloud"
)

var DescribeCmd = base.DescribeCmd{
var DescribeCmd = base.DescribeCmd[*hcloud.Certificate]{
ResourceNameSingular: "certificate",
ShortDescription: "Describe an certificate",
JSONKeyGetByID: "certificate",
JSONKeyGetByName: "certificates",
NameSuggestions: func(c hcapi2.Client) func() []string { return c.Certificate().Names },
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (interface{}, interface{}, error) {
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (*hcloud.Certificate, any, error) {
cert, _, err := s.Client().Certificate().Get(s, idOrName)
if err != nil {
return nil, nil, err
}
return cert, hcloud.SchemaFromCertificate(cert), nil
},
PrintText: func(s state.State, cmd *cobra.Command, resource interface{}) error {
cert := resource.(*hcloud.Certificate)
PrintText: func(s state.State, cmd *cobra.Command, cert *hcloud.Certificate) error {
cmd.Printf("ID:\t\t\t%d\n", cert.ID)
cmd.Printf("Name:\t\t\t%s\n", cert.Name)
cmd.Printf("Type:\t\t\t%s\n", cert.Type)
Expand Down
8 changes: 3 additions & 5 deletions internal/cmd/datacenter/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,20 @@ import (
"github.com/hetznercloud/hcloud-go/v2/hcloud"
)

var DescribeCmd = base.DescribeCmd{
var DescribeCmd = base.DescribeCmd[*hcloud.Datacenter]{
ResourceNameSingular: "datacenter",
ShortDescription: "Describe an datacenter",
JSONKeyGetByID: "datacenter",
JSONKeyGetByName: "datacenters",
NameSuggestions: func(c hcapi2.Client) func() []string { return c.Datacenter().Names },
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (interface{}, interface{}, error) {
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (*hcloud.Datacenter, any, error) {
dc, _, err := s.Client().Datacenter().Get(s, idOrName)
if err != nil {
return nil, nil, err
}
return dc, hcloud.SchemaFromDatacenter(dc), nil
},
PrintText: func(s state.State, cmd *cobra.Command, resource interface{}) error {
datacenter := resource.(*hcloud.Datacenter)

PrintText: func(s state.State, cmd *cobra.Command, datacenter *hcloud.Datacenter) error {
cmd.Printf("ID:\t\t%d\n", datacenter.ID)
cmd.Printf("Name:\t\t%s\n", datacenter.Name)
cmd.Printf("Description:\t%s\n", datacenter.Description)
Expand Down
8 changes: 3 additions & 5 deletions internal/cmd/firewall/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,20 @@ import (
"github.com/hetznercloud/hcloud-go/v2/hcloud"
)

var DescribeCmd = base.DescribeCmd{
var DescribeCmd = base.DescribeCmd[*hcloud.Firewall]{
ResourceNameSingular: "firewall",
ShortDescription: "Describe an firewall",
JSONKeyGetByID: "firewall",
JSONKeyGetByName: "firewalls",
NameSuggestions: func(c hcapi2.Client) func() []string { return c.Firewall().Names },
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (interface{}, interface{}, error) {
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (*hcloud.Firewall, any, error) {
fw, _, err := s.Client().Firewall().Get(s, idOrName)
if err != nil {
return nil, nil, err
}
return fw, hcloud.SchemaFromFirewall(fw), nil
},
PrintText: func(s state.State, cmd *cobra.Command, resource interface{}) error {
firewall := resource.(*hcloud.Firewall)

PrintText: func(s state.State, cmd *cobra.Command, firewall *hcloud.Firewall) error {
cmd.Printf("ID:\t\t%d\n", firewall.ID)
cmd.Printf("Name:\t\t%s\n", firewall.Name)
cmd.Printf("Created:\t%s (%s)\n", util.Datetime(firewall.Created), humanize.Time(firewall.Created))
Expand Down
8 changes: 3 additions & 5 deletions internal/cmd/floatingip/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,20 @@ import (
"github.com/hetznercloud/hcloud-go/v2/hcloud"
)

var DescribeCmd = base.DescribeCmd{
var DescribeCmd = base.DescribeCmd[*hcloud.FloatingIP]{
ResourceNameSingular: "Floating IP",
ShortDescription: "Describe an Floating IP",
JSONKeyGetByID: "floating_ip",
JSONKeyGetByName: "floating_ips",
NameSuggestions: func(c hcapi2.Client) func() []string { return c.FloatingIP().Names },
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (interface{}, interface{}, error) {
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (*hcloud.FloatingIP, any, error) {
ip, _, err := s.Client().FloatingIP().Get(s, idOrName)
if err != nil {
return nil, nil, err
}
return ip, hcloud.SchemaFromFloatingIP(ip), nil
},
PrintText: func(s state.State, cmd *cobra.Command, resource interface{}) error {
floatingIP := resource.(*hcloud.FloatingIP)

PrintText: func(s state.State, cmd *cobra.Command, floatingIP *hcloud.FloatingIP) error {
cmd.Printf("ID:\t\t%d\n", floatingIP.ID)
cmd.Printf("Type:\t\t%s\n", floatingIP.Type)
cmd.Printf("Name:\t\t%s\n", floatingIP.Name)
Expand Down
8 changes: 3 additions & 5 deletions internal/cmd/image/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
"github.com/hetznercloud/hcloud-go/v2/hcloud"
)

var DescribeCmd = base.DescribeCmd{
var DescribeCmd = base.DescribeCmd[*hcloud.Image]{
ResourceNameSingular: "image",
ShortDescription: "Describe an image",
JSONKeyGetByID: "image",
Expand All @@ -26,7 +26,7 @@ var DescribeCmd = base.DescribeCmd{
_ = cmd.RegisterFlagCompletionFunc("architecture", cmpl.SuggestCandidates(string(hcloud.ArchitectureX86), string(hcloud.ArchitectureARM)))
},
NameSuggestions: func(c hcapi2.Client) func() []string { return c.Image().Names },
Fetch: func(s state.State, cmd *cobra.Command, idOrName string) (interface{}, interface{}, error) {
Fetch: func(s state.State, cmd *cobra.Command, idOrName string) (*hcloud.Image, any, error) {
_, err := strconv.ParseInt(idOrName, 10, 64)
isID := err == nil

Expand All @@ -45,9 +45,7 @@ var DescribeCmd = base.DescribeCmd{
}
return img, hcloud.SchemaFromImage(img), nil
},
PrintText: func(_ state.State, cmd *cobra.Command, resource interface{}) error {
image := resource.(*hcloud.Image)

PrintText: func(_ state.State, cmd *cobra.Command, image *hcloud.Image) error {
cmd.Printf("ID:\t\t%d\n", image.ID)
cmd.Printf("Type:\t\t%s\n", image.Type)
cmd.Printf("Status:\t\t%s\n", image.Status)
Expand Down
8 changes: 3 additions & 5 deletions internal/cmd/iso/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,20 @@ import (
)

// DescribeCmd defines a command for describing a iso.
var DescribeCmd = base.DescribeCmd{
var DescribeCmd = base.DescribeCmd[*hcloud.ISO]{
ResourceNameSingular: "iso",
ShortDescription: "Describe a iso",
JSONKeyGetByID: "iso",
JSONKeyGetByName: "isos",
NameSuggestions: func(c hcapi2.Client) func() []string { return c.Location().Names },
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (interface{}, interface{}, error) {
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (*hcloud.ISO, any, error) {
iso, _, err := s.Client().ISO().Get(s, idOrName)
if err != nil {
return nil, nil, err
}
return iso, hcloud.SchemaFromISO(iso), nil
},
PrintText: func(_ state.State, cmd *cobra.Command, resource interface{}) error {
iso := resource.(*hcloud.ISO)

PrintText: func(_ state.State, cmd *cobra.Command, iso *hcloud.ISO) error {
cmd.Printf("ID:\t\t%d\n", iso.ID)
cmd.Printf("Name:\t\t%s\n", iso.Name)
cmd.Printf("Description:\t%s\n", iso.Description)
Expand Down
7 changes: 3 additions & 4 deletions internal/cmd/loadbalancer/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ import (
)

// DescribeCmd defines a command for describing a LoadBalancer.
var DescribeCmd = base.DescribeCmd{
var DescribeCmd = base.DescribeCmd[*hcloud.LoadBalancer]{
ResourceNameSingular: "Load Balancer",
ShortDescription: "Describe a Load Balancer",
JSONKeyGetByID: "load_balancer",
JSONKeyGetByName: "load_balancers",
NameSuggestions: func(c hcapi2.Client) func() []string { return c.LoadBalancer().Names },
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (interface{}, interface{}, error) {
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (*hcloud.LoadBalancer, any, error) {
lb, _, err := s.Client().LoadBalancer().Get(s, idOrName)
if err != nil {
return nil, nil, err
Expand All @@ -28,9 +28,8 @@ var DescribeCmd = base.DescribeCmd{
AdditionalFlags: func(cmd *cobra.Command) {
cmd.Flags().Bool("expand-targets", false, "Expand all label_selector targets")
},
PrintText: func(s state.State, cmd *cobra.Command, resource interface{}) error {
PrintText: func(s state.State, cmd *cobra.Command, loadBalancer *hcloud.LoadBalancer) error {
withLabelSelectorTargets, _ := cmd.Flags().GetBool("expand-targets")
loadBalancer := resource.(*hcloud.LoadBalancer)
cmd.Printf("ID:\t\t\t\t%d\n", loadBalancer.ID)
cmd.Printf("Name:\t\t\t\t%s\n", loadBalancer.Name)
cmd.Printf("Created:\t\t\t%s (%s)\n", util.Datetime(loadBalancer.Created), humanize.Time(loadBalancer.Created))
Expand Down
8 changes: 3 additions & 5 deletions internal/cmd/loadbalancertype/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,20 @@ import (
"github.com/hetznercloud/hcloud-go/v2/hcloud"
)

var DescribeCmd = base.DescribeCmd{
var DescribeCmd = base.DescribeCmd[*hcloud.LoadBalancerType]{
ResourceNameSingular: "Load Balancer Type",
ShortDescription: "Describe a Load Balancer type",
JSONKeyGetByID: "load_balancer_type",
JSONKeyGetByName: "load_balancer_types",
NameSuggestions: func(c hcapi2.Client) func() []string { return c.LoadBalancerType().Names },
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (interface{}, interface{}, error) {
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (*hcloud.LoadBalancerType, any, error) {
lbt, _, err := s.Client().LoadBalancerType().Get(s, idOrName)
if err != nil {
return nil, nil, err
}
return lbt, hcloud.SchemaFromLoadBalancerType(lbt), nil
},
PrintText: func(s state.State, cmd *cobra.Command, resource interface{}) error {
loadBalancerType := resource.(*hcloud.LoadBalancerType)

PrintText: func(s state.State, cmd *cobra.Command, loadBalancerType *hcloud.LoadBalancerType) error {
cmd.Printf("ID:\t\t\t\t%d\n", loadBalancerType.ID)
cmd.Printf("Name:\t\t\t\t%s\n", loadBalancerType.Name)
cmd.Printf("Description:\t\t\t%s\n", loadBalancerType.Description)
Expand Down
8 changes: 3 additions & 5 deletions internal/cmd/location/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,20 @@ import (
)

// DescribeCmd defines a command for describing a location.
var DescribeCmd = base.DescribeCmd{
var DescribeCmd = base.DescribeCmd[*hcloud.Location]{
ResourceNameSingular: "location",
ShortDescription: "Describe a location",
JSONKeyGetByID: "location",
JSONKeyGetByName: "locations",
NameSuggestions: func(c hcapi2.Client) func() []string { return c.Location().Names },
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (interface{}, interface{}, error) {
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (*hcloud.Location, any, error) {
l, _, err := s.Client().Location().Get(s, idOrName)
if err != nil {
return nil, nil, err
}
return l, hcloud.SchemaFromLocation(l), nil
},
PrintText: func(_ state.State, cmd *cobra.Command, resource interface{}) error {
location := resource.(*hcloud.Location)

PrintText: func(_ state.State, cmd *cobra.Command, location *hcloud.Location) error {
cmd.Printf("ID:\t\t%d\n", location.ID)
cmd.Printf("Name:\t\t%s\n", location.Name)
cmd.Printf("Description:\t%s\n", location.Description)
Expand Down
8 changes: 3 additions & 5 deletions internal/cmd/network/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,20 @@ import (
)

// DescribeCmd defines a command for describing a network.
var DescribeCmd = base.DescribeCmd{
var DescribeCmd = base.DescribeCmd[*hcloud.Network]{
ResourceNameSingular: "network",
ShortDescription: "Describe a network",
JSONKeyGetByID: "network",
JSONKeyGetByName: "networks",
NameSuggestions: func(c hcapi2.Client) func() []string { return c.Network().Names },
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (interface{}, interface{}, error) {
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (*hcloud.Network, any, error) {
n, _, err := s.Client().Network().Get(s, idOrName)
if err != nil {
return nil, nil, err
}
return n, hcloud.SchemaFromNetwork(n), nil
},
PrintText: func(_ state.State, cmd *cobra.Command, resource interface{}) error {
network := resource.(*hcloud.Network)

PrintText: func(_ state.State, cmd *cobra.Command, network *hcloud.Network) error {
cmd.Printf("ID:\t\t%d\n", network.ID)
cmd.Printf("Name:\t\t%s\n", network.Name)
cmd.Printf("Created:\t%s (%s)\n", util.Datetime(network.Created), humanize.Time(network.Created))
Expand Down
8 changes: 3 additions & 5 deletions internal/cmd/placementgroup/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,20 @@ import (
"github.com/hetznercloud/hcloud-go/v2/hcloud"
)

var DescribeCmd = base.DescribeCmd{
var DescribeCmd = base.DescribeCmd[*hcloud.PlacementGroup]{
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(s state.State, _ *cobra.Command, idOrName string) (interface{}, interface{}, error) {
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (*hcloud.PlacementGroup, any, error) {
pg, _, err := s.Client().PlacementGroup().Get(s, idOrName)
if err != nil {
return nil, nil, err
}
return pg, hcloud.SchemaFromPlacementGroup(pg), nil
},
PrintText: func(s state.State, cmd *cobra.Command, resource interface{}) error {
placementGroup := resource.(*hcloud.PlacementGroup)

PrintText: func(s state.State, cmd *cobra.Command, placementGroup *hcloud.PlacementGroup) error {
cmd.Printf("ID:\t\t%d\n", placementGroup.ID)
cmd.Printf("Name:\t\t%s\n", placementGroup.Name)
cmd.Printf("Created:\t%s (%s)\n", util.Datetime(placementGroup.Created), humanize.Time(placementGroup.Created))
Expand Down
8 changes: 3 additions & 5 deletions internal/cmd/primaryip/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,20 @@ import (
"github.com/hetznercloud/hcloud-go/v2/hcloud"
)

var DescribeCmd = base.DescribeCmd{
var DescribeCmd = base.DescribeCmd[*hcloud.PrimaryIP]{
ResourceNameSingular: "Primary IP",
ShortDescription: "Describe an Primary IP",
JSONKeyGetByID: "primary_ip",
JSONKeyGetByName: "primary_ips",
NameSuggestions: func(c hcapi2.Client) func() []string { return c.PrimaryIP().Names },
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (interface{}, interface{}, error) {
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (*hcloud.PrimaryIP, any, error) {
ip, _, err := s.Client().PrimaryIP().Get(s, idOrName)
if err != nil {
return nil, nil, err
}
return ip, hcloud.SchemaFromPrimaryIP(ip), nil
},
PrintText: func(_ state.State, cmd *cobra.Command, resource interface{}) error {
primaryIP := resource.(*hcloud.PrimaryIP)

PrintText: func(_ state.State, cmd *cobra.Command, primaryIP *hcloud.PrimaryIP) error {
cmd.Printf("ID:\t\t%d\n", primaryIP.ID)
cmd.Printf("Name:\t\t%s\n", primaryIP.Name)
cmd.Printf("Created:\t%s (%s)\n", util.Datetime(primaryIP.Created), humanize.Time(primaryIP.Created))
Expand Down
8 changes: 3 additions & 5 deletions internal/cmd/server/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,20 @@ import (
"github.com/hetznercloud/hcloud-go/v2/hcloud"
)

var DescribeCmd = base.DescribeCmd{
var DescribeCmd = base.DescribeCmd[*hcloud.Server]{
ResourceNameSingular: "server",
ShortDescription: "Describe a server",
JSONKeyGetByID: "server",
JSONKeyGetByName: "servers",
NameSuggestions: func(c hcapi2.Client) func() []string { return c.Server().Names },
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (interface{}, interface{}, error) {
Fetch: func(s state.State, _ *cobra.Command, idOrName string) (*hcloud.Server, any, error) {
srv, _, err := s.Client().Server().Get(s, idOrName)
if err != nil {
return nil, nil, err
}
return srv, hcloud.SchemaFromServer(srv), nil
},
PrintText: func(s state.State, cmd *cobra.Command, resource interface{}) error {
server := resource.(*hcloud.Server)

PrintText: func(s state.State, cmd *cobra.Command, server *hcloud.Server) error {
cmd.Printf("ID:\t\t%d\n", server.ID)
cmd.Printf("Name:\t\t%s\n", server.Name)
cmd.Printf("Status:\t\t%s\n", server.Status)
Expand Down
Loading

0 comments on commit 591d1d8

Please sign in to comment.