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: make base describe command generic #972

Merged
merged 1 commit 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
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