diff --git a/internal/cli/root.go b/internal/cli/root.go index 9251fa27..f22d5e27 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -5,6 +5,7 @@ import ( "github.com/spf13/cobra" + "github.com/hetznercloud/cli/internal/cmd/all" "github.com/hetznercloud/cli/internal/cmd/certificate" "github.com/hetznercloud/cli/internal/cmd/completion" "github.com/hetznercloud/cli/internal/cmd/context" @@ -39,6 +40,7 @@ func NewRootCommand(state *state.State, client hcapi2.Client) *cobra.Command { DisableFlagsInUseLine: true, } cmd.AddCommand( + all.NewCommand(state, client), floatingip.NewCommand(state, client), image.NewCommand(state, client), server.NewCommand(state, client), diff --git a/internal/cmd/all/all.go b/internal/cmd/all/all.go new file mode 100644 index 00000000..0dba942f --- /dev/null +++ b/internal/cmd/all/all.go @@ -0,0 +1,22 @@ +package all + +import ( + "github.com/spf13/cobra" + + "github.com/hetznercloud/cli/internal/hcapi2" + "github.com/hetznercloud/cli/internal/state" +) + +func NewCommand(cli *state.State, client hcapi2.Client) *cobra.Command { + cmd := &cobra.Command{ + Use: "all", + Short: "Commands that apply to all resources", + Args: cobra.NoArgs, + TraverseChildren: true, + DisableFlagsInUseLine: true, + } + cmd.AddCommand( + listCmd.CobraCommand(cli.Context, client, cli, cli), + ) + return cmd +} diff --git a/internal/cmd/all/list.go b/internal/cmd/all/list.go new file mode 100644 index 00000000..6a3db418 --- /dev/null +++ b/internal/cmd/all/list.go @@ -0,0 +1,186 @@ +package all + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + + "github.com/hetznercloud/cli/internal/cmd/base" + "github.com/hetznercloud/cli/internal/cmd/certificate" + "github.com/hetznercloud/cli/internal/cmd/firewall" + "github.com/hetznercloud/cli/internal/cmd/floatingip" + "github.com/hetznercloud/cli/internal/cmd/image" + "github.com/hetznercloud/cli/internal/cmd/iso" + "github.com/hetznercloud/cli/internal/cmd/loadbalancer" + "github.com/hetznercloud/cli/internal/cmd/network" + "github.com/hetznercloud/cli/internal/cmd/output" + "github.com/hetznercloud/cli/internal/cmd/placementgroup" + "github.com/hetznercloud/cli/internal/cmd/primaryip" + "github.com/hetznercloud/cli/internal/cmd/server" + "github.com/hetznercloud/cli/internal/cmd/sshkey" + "github.com/hetznercloud/cli/internal/cmd/volume" + "github.com/hetznercloud/cli/internal/hcapi2" + "github.com/hetznercloud/cli/internal/state" + "github.com/hetznercloud/hcloud-go/v2/hcloud" +) + +var allCmds = []base.ListCmd{ + server.ListCmd, + image.ListCmd, + placementgroup.ListCmd, + primaryip.ListCmd, + iso.ListCmd, + volume.ListCmd, + loadbalancer.ListCmd, + floatingip.ListCmd, + network.ListCmd, + firewall.ListCmd, + certificate.ListCmd, + sshkey.ListCmd, +} + +var listCmd = base.Cmd{ + BaseCobraCommand: func(client hcapi2.Client) *cobra.Command { + + var resources []string + for _, cmd := range allCmds { + resources = append(resources, " - "+cmd.ResourceNamePlural) + } + + cmd := &cobra.Command{ + Use: "list FLAGS", + Short: "List all resources in the project", + Long: `List all resources in the project. This does not include static/public resources like locations, public ISOs, etc. + +Listed resources are: +` + strings.Join(resources, "\n"), + } + + cmd.Flags().StringP("selector", "l", "", "Selector to filter by labels") + + cmd.Flags().Bool("paid", false, "Only list resources that cost money") + + output.AddFlag(cmd, output.OptionJSON()) + + return cmd + }, + Run: func(ctx context.Context, client hcapi2.Client, actionWaiter state.ActionWaiter, cmd *cobra.Command, args []string) error { + + paid, _ := cmd.Flags().GetBool("paid") + labelSelector, _ := cmd.Flags().GetString("selector") + + outOpts := output.FlagsForCommand(cmd) + + var cmds []base.ListCmd + if paid { + cmds = []base.ListCmd{ + server.ListCmd, + image.ListCmd, + primaryip.ListCmd, + volume.ListCmd, + loadbalancer.ListCmd, + floatingip.ListCmd, + } + } else { + cmds = []base.ListCmd{ + server.ListCmd, + image.ListCmd, + placementgroup.ListCmd, + primaryip.ListCmd, + iso.ListCmd, + volume.ListCmd, + loadbalancer.ListCmd, + floatingip.ListCmd, + network.ListCmd, + firewall.ListCmd, + certificate.ListCmd, + sshkey.ListCmd, + } + } + + type response struct { + result []any + err error + } + responseChs := make([]chan response, len(cmds)) + + // Start all requests in parallel in order to minimize response time + for i, lc := range cmds { + i, lc := i, lc + ch := make(chan response) + responseChs[i] = ch + + go func() { + defer close(ch) + + listOpts := hcloud.ListOpts{ + LabelSelector: labelSelector, + } + + flagSet := pflag.NewFlagSet(lc.JSONKeyGetByName, pflag.ExitOnError) + + switch lc.JSONKeyGetByName { + case image.ListCmd.JSONKeyGetByName: + flagSet.StringSlice("type", []string{"backup", "snapshot"}, "") + case iso.ListCmd.JSONKeyGetByName: + flagSet.StringSlice("type", []string{"private"}, "") + } + + // FlagSet has to be parsed to be populated. + // We pass an empty slice because we defined the flags earlier. + _ = flagSet.Parse([]string{}) + + result, err := lc.Fetch(ctx, client, flagSet, listOpts, []string{}) + ch <- response{result, err} + }() + } + + // Wait for all requests to finish and collect results + resources := make([][]any, len(cmds)) + for i, responseCh := range responseChs { + response := <-responseCh + if err := response.err; err != nil { + return err + } + resources[i] = response.result + } + + if outOpts.IsSet("json") { + jsonSchema := make(map[string]any) + for i, lc := range cmds { + jsonSchema[lc.JSONKeyGetByName] = lc.JSONSchema(resources[i]) + } + jsonBytes, err := json.Marshal(jsonSchema) + if err != nil { + return err + } + fmt.Printf("%s\n", jsonBytes) + return nil + } + + for i, lc := range cmds { + cols := lc.DefaultColumns + table := lc.OutputTable(client) + table.WriteHeader(cols) + + if len(resources[i]) == 0 { + continue + } + + fmt.Print(strings.ToUpper(lc.ResourceNamePlural) + "\n---\n") + for _, resource := range resources[i] { + table.Write(cols, resource) + } + if err := table.Flush(); err != nil { + return err + } + fmt.Println() + } + + return nil + }, +} diff --git a/internal/cmd/all/list_test.go b/internal/cmd/all/list_test.go new file mode 100644 index 00000000..a45962d3 --- /dev/null +++ b/internal/cmd/all/list_test.go @@ -0,0 +1,301 @@ +package all + +import ( + "context" + "net" + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + + "github.com/hetznercloud/cli/internal/testutil" + "github.com/hetznercloud/hcloud-go/v2/hcloud" +) + +func TestListAll(t *testing.T) { + fx := testutil.NewFixture(t) + defer fx.Finish() + + time.Local = time.UTC + + cmd := listCmd.CobraCommand( + context.Background(), + fx.Client, + fx.TokenEnsurer, + fx.ActionWaiter) + fx.ExpectEnsureToken() + + fx.Client.ServerClient.EXPECT(). + AllWithOpts(gomock.Any(), hcloud.ServerListOpts{}). + Return([]*hcloud.Server{ + { + ID: 123, + Name: "my server", + Status: hcloud.ServerStatusRunning, + PublicNet: hcloud.ServerPublicNet{ + IPv4: hcloud.ServerPublicNetIPv4{ + IP: net.ParseIP("192.0.2.1"), + }, + }, + Created: time.Now().Add(-72 * time.Hour), + Datacenter: &hcloud.Datacenter{Name: "hel1-dc2"}, + }, + }, nil) + fx.Client.NetworkClient.EXPECT(). + AllWithOpts(gomock.Any(), hcloud.NetworkListOpts{}). + Return([]*hcloud.Network{ + { + ID: 123, + Name: "test-net", + IPRange: &net.IPNet{IP: net.ParseIP("192.0.2.1"), Mask: net.CIDRMask(24, 32)}, + Servers: []*hcloud.Server{{ID: 3421}}, + Created: time.Now().Add(-10 * time.Second), + }, + }, nil) + fx.Client.LoadBalancerClient.EXPECT(). + AllWithOpts(gomock.Any(), hcloud.LoadBalancerListOpts{}). + Return([]*hcloud.LoadBalancer{ + { + ID: 123, + LoadBalancerType: &hcloud.LoadBalancerType{Name: "lb11"}, + Location: &hcloud.Location{Name: "fsn1", NetworkZone: hcloud.NetworkZoneEUCentral}, + Name: "foobar", + PublicNet: hcloud.LoadBalancerPublicNet{ + IPv4: hcloud.LoadBalancerPublicNetIPv4{ + IP: net.ParseIP("192.0.2.1"), + }, + IPv6: hcloud.LoadBalancerPublicNetIPv6{ + IP: net.IPv6zero, + }, + }, + Created: time.Now().Add(-5 * time.Hour), + }, + }, nil) + fx.Client.CertificateClient.EXPECT(). + AllWithOpts(gomock.Any(), hcloud.CertificateListOpts{}). + Return([]*hcloud.Certificate{ + { + ID: 123, + Name: "test", + Type: hcloud.CertificateTypeManaged, + DomainNames: []string{"example.com"}, + NotValidAfter: time.Date(2036, 8, 20, 12, 0, 0, 0, time.UTC), + Created: time.Now().Add(-10 * time.Hour), + }, + }, nil) + fx.Client.FirewallClient.EXPECT(). + AllWithOpts(gomock.Any(), hcloud.FirewallListOpts{}). + Return([]*hcloud.Firewall{ + { + ID: 123, + Name: "test", + Created: time.Now().Add(-7 * time.Minute), + Rules: make([]hcloud.FirewallRule, 5), + AppliedTo: make([]hcloud.FirewallResource, 2), + }, + }, nil) + fx.Client.PrimaryIPClient.EXPECT(). + AllWithOpts(gomock.Any(), hcloud.PrimaryIPListOpts{}). + Return([]*hcloud.PrimaryIP{ + { + ID: 123, + Name: "test", + Created: time.Now().Add(-2 * time.Hour), + Datacenter: &hcloud.Datacenter{Name: "fsn1-dc14"}, + Type: hcloud.PrimaryIPTypeIPv4, + IP: net.ParseIP("127.0.0.1"), + }, + }, nil) + fx.Client.FloatingIPClient.EXPECT(). + AllWithOpts(gomock.Any(), hcloud.FloatingIPListOpts{}). + Return([]*hcloud.FloatingIP{ + { + ID: 123, + Name: "test", + Created: time.Now().Add(-24 * time.Hour), + Type: hcloud.FloatingIPTypeIPv4, + IP: net.ParseIP("127.0.0.1"), + HomeLocation: &hcloud.Location{Name: "fsn1"}, + }, + }, nil) + fx.Client.ImageClient.EXPECT(). + AllWithOpts(gomock.Any(), hcloud.ImageListOpts{Type: []hcloud.ImageType{hcloud.ImageTypeBackup, hcloud.ImageTypeSnapshot}, IncludeDeprecated: true}). + Return([]*hcloud.Image{ + { + ID: 1, + Type: hcloud.ImageTypeBackup, + Name: "test", + Created: time.Date(2036, 8, 20, 12, 0, 0, 0, time.UTC), + Architecture: hcloud.ArchitectureARM, + DiskSize: 20, + }, + }, nil) + fx.Client.VolumeClient.EXPECT(). + AllWithOpts(gomock.Any(), hcloud.VolumeListOpts{}). + Return([]*hcloud.Volume{ + { + ID: 123, + Name: "test", + Location: &hcloud.Location{Name: "fsn1"}, + Size: 50, + Created: time.Now().Add(-500 * time.Hour), + }, + }, nil) + fx.Client.ISOClient.EXPECT(). + AllWithOpts(gomock.Any(), hcloud.ISOListOpts{}). + Return([]*hcloud.ISO{ + { + ID: 123, + Name: "test", + Type: hcloud.ISOTypePrivate, + Architecture: hcloud.Ptr(hcloud.ArchitectureARM), + }, + }, nil) + fx.Client.PlacementGroupClient.EXPECT(). + AllWithOpts(gomock.Any(), hcloud.PlacementGroupListOpts{}). + Return([]*hcloud.PlacementGroup{ + { + ID: 123, + Name: "test", + Created: time.Now().Add(-10 * time.Hour), + Type: hcloud.PlacementGroupTypeSpread, + Servers: make([]int64, 5), + }, + }, nil) + fx.Client.SSHKeyClient.EXPECT(). + AllWithOpts(gomock.Any(), hcloud.SSHKeyListOpts{}). + Return([]*hcloud.SSHKey{ + { + ID: 123, + Name: "test", + Created: time.Now().Add(-2 * time.Hour), + }, + }, nil) + + out, err := fx.Run(cmd, []string{}) + + expOut := `SERVERS +--- +ID NAME STATUS IPV4 IPV6 PRIVATE NET DATACENTER AGE +123 my server running 192.0.2.1 - - hel1-dc2 3d + +IMAGES +--- +ID TYPE NAME DESCRIPTION ARCHITECTURE IMAGE SIZE DISK SIZE CREATED DEPRECATED +1 backup test - arm - 20 GB Wed Aug 20 12:00:00 UTC 2036 - + +PLACEMENT GROUPS +--- +ID NAME SERVERS TYPE AGE +123 test 5 servers spread 10h + +PRIMARY IPS +--- +ID TYPE NAME IP ASSIGNEE DNS AUTO DELETE AGE +123 ipv4 test 127.0.0.1 - - no 2h + +ISOS +--- +ID NAME DESCRIPTION TYPE ARCHITECTURE +123 test - private arm + +VOLUMES +--- +ID NAME SIZE SERVER LOCATION AGE +123 test 50 GB - fsn1 20d + +LOAD BALANCER +--- +ID NAME HEALTH IPV4 IPV6 TYPE LOCATION NETWORK ZONE AGE +123 foobar healthy 192.0.2.1 :: lb11 fsn1 eu-central 5h + +FLOATING IPS +--- +ID TYPE NAME DESCRIPTION IP HOME SERVER DNS AGE +123 ipv4 test - 127.0.0.1 fsn1 - - 1d + +NETWORKS +--- +ID NAME IP RANGE SERVERS AGE +123 test-net 192.0.2.1/24 1 server 10s + +FIREWALLS +--- +ID NAME RULES COUNT APPLIED TO COUNT +123 test 5 Rules 2 Servers | 0 Label Selectors + +CERTIFICATES +--- +ID NAME TYPE DOMAIN NAMES NOT VALID AFTER AGE +123 test managed example.com Wed Aug 20 12:00:00 UTC 2036 10h + +SSH KEYS +--- +ID NAME FINGERPRINT AGE +123 test - 2h + +` + + assert.NoError(t, err) + assert.Equal(t, expOut, out) +} + +func TestListAllPaidJSON(t *testing.T) { + fx := testutil.NewFixture(t) + defer fx.Finish() + + time.Local = time.UTC + + cmd := listCmd.CobraCommand( + context.Background(), + fx.Client, + fx.TokenEnsurer, + fx.ActionWaiter) + fx.ExpectEnsureToken() + + fx.Client.ServerClient.EXPECT(). + AllWithOpts(gomock.Any(), hcloud.ServerListOpts{}). + Return([]*hcloud.Server{}, nil) + fx.Client.LoadBalancerClient.EXPECT(). + AllWithOpts(gomock.Any(), hcloud.LoadBalancerListOpts{}). + Return([]*hcloud.LoadBalancer{}, nil) + fx.Client.PrimaryIPClient.EXPECT(). + AllWithOpts(gomock.Any(), hcloud.PrimaryIPListOpts{}). + Return([]*hcloud.PrimaryIP{}, nil) + fx.Client.FloatingIPClient.EXPECT(). + AllWithOpts(gomock.Any(), hcloud.FloatingIPListOpts{}). + Return([]*hcloud.FloatingIP{}, nil) + fx.Client.ImageClient.EXPECT(). + AllWithOpts(gomock.Any(), hcloud.ImageListOpts{Type: []hcloud.ImageType{hcloud.ImageTypeBackup, hcloud.ImageTypeSnapshot}, IncludeDeprecated: true}). + Return([]*hcloud.Image{ + { + ID: 114690387, + Name: "debian-12", + Description: "Debian 12", + Type: hcloud.ImageTypeSystem, + Status: hcloud.ImageStatusAvailable, + RapidDeploy: true, + OSVersion: "12", + OSFlavor: "debian", + DiskSize: 5, + Architecture: hcloud.ArchitectureX86, + Created: time.Date(2023, 6, 13, 6, 0, 0, 0, time.UTC), + }, + }, nil) + fx.Client.VolumeClient.EXPECT(). + AllWithOpts(gomock.Any(), hcloud.VolumeListOpts{}). + Return([]*hcloud.Volume{}, nil) + + out, err := fx.Run(cmd, []string{"--paid", "-o=json"}) + + expOut := "{\"floating_ips\":[],\"images\":[{\"id\":114690387,\"status\":\"available\",\"type\":\"system\"," + + "\"name\":\"debian-12\",\"description\":\"Debian 12\",\"image_size\":0,\"disk_size\":5,\"created\":" + + "\"2023-06-13T06:00:00Z\",\"created_from\":null,\"bound_to\":null,\"os_flavor\":\"debian\",\"os_version\":" + + "\"12\",\"architecture\":\"x86\",\"rapid_deploy\":true,\"protection\":{\"delete\":false},\"deprecated\":" + + "\"0001-01-01T00:00:00Z\",\"deleted\":\"0001-01-01T00:00:00Z\",\"labels\":null}],\"load_balancers\":[]," + + "\"primary_ips\":[],\"servers\":[],\"volumes\":[]}\n" + + assert.NoError(t, err) + assert.Equal(t, expOut, out) +} diff --git a/internal/cmd/base/list.go b/internal/cmd/base/list.go index af50e056..94e55433 100644 --- a/internal/cmd/base/list.go +++ b/internal/cmd/base/list.go @@ -17,6 +17,7 @@ import ( // ListCmd allows defining commands for listing resources type ListCmd struct { ResourceNamePlural string // e.g. "servers" + JSONKeyGetByName string // e.g. "servers" DefaultColumns []string Fetch func(context.Context, hcapi2.Client, *pflag.FlagSet, hcloud.ListOpts, []string) ([]interface{}, error) AdditionalFlags func(*cobra.Command) diff --git a/internal/cmd/certificate/list.go b/internal/cmd/certificate/list.go index d21bdbee..cd834214 100644 --- a/internal/cmd/certificate/list.go +++ b/internal/cmd/certificate/list.go @@ -17,6 +17,7 @@ import ( var ListCmd = base.ListCmd{ ResourceNamePlural: "Certificates", + JSONKeyGetByName: "certificates", DefaultColumns: []string{"id", "name", "type", "domain_names", "not_valid_after", "age"}, Fetch: func(ctx context.Context, client hcapi2.Client, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { diff --git a/internal/cmd/datacenter/list.go b/internal/cmd/datacenter/list.go index 46e4110b..aae25e19 100644 --- a/internal/cmd/datacenter/list.go +++ b/internal/cmd/datacenter/list.go @@ -15,6 +15,7 @@ import ( var ListCmd = base.ListCmd{ ResourceNamePlural: "Datacenters", + JSONKeyGetByName: "datacenters", DefaultColumns: []string{"id", "name", "description", "location"}, Fetch: func(ctx context.Context, client hcapi2.Client, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { diff --git a/internal/cmd/firewall/list.go b/internal/cmd/firewall/list.go index a3bc31d3..1a20cd6e 100644 --- a/internal/cmd/firewall/list.go +++ b/internal/cmd/firewall/list.go @@ -15,6 +15,7 @@ import ( var ListCmd = base.ListCmd{ ResourceNamePlural: "Firewalls", + JSONKeyGetByName: "firewalls", DefaultColumns: []string{"id", "name", "rules_count", "applied_to_count"}, Fetch: func(ctx context.Context, client hcapi2.Client, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { diff --git a/internal/cmd/floatingip/list.go b/internal/cmd/floatingip/list.go index e19acd7e..553499ef 100644 --- a/internal/cmd/floatingip/list.go +++ b/internal/cmd/floatingip/list.go @@ -18,6 +18,7 @@ import ( var ListCmd = base.ListCmd{ ResourceNamePlural: "Floating IPs", + JSONKeyGetByName: "floating_ips", DefaultColumns: []string{"id", "type", "name", "description", "ip", "home", "server", "dns", "age"}, Fetch: func(ctx context.Context, client hcapi2.Client, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { diff --git a/internal/cmd/image/list.go b/internal/cmd/image/list.go index daf3f1d8..c01ad949 100644 --- a/internal/cmd/image/list.go +++ b/internal/cmd/image/list.go @@ -21,6 +21,7 @@ import ( var ListCmd = base.ListCmd{ ResourceNamePlural: "Images", + JSONKeyGetByName: "images", DefaultColumns: []string{"id", "type", "name", "description", "architecture", "image_size", "disk_size", "created", "deprecated"}, AdditionalFlags: func(cmd *cobra.Command) { cmd.Flags().StringSliceP("type", "t", []string{}, "Only show images of given type") diff --git a/internal/cmd/iso/list.go b/internal/cmd/iso/list.go index d67c49f2..5d8f5514 100644 --- a/internal/cmd/iso/list.go +++ b/internal/cmd/iso/list.go @@ -20,6 +20,7 @@ import ( var ListCmd = base.ListCmd{ ResourceNamePlural: "ISOs", + JSONKeyGetByName: "isos", DefaultColumns: []string{"id", "name", "description", "type", "architecture"}, AdditionalFlags: func(cmd *cobra.Command) { cmd.Flags().StringSlice("architecture", []string{}, "Only show images of given architecture: x86|arm") diff --git a/internal/cmd/loadbalancer/list.go b/internal/cmd/loadbalancer/list.go index b49d74b2..e85994d0 100644 --- a/internal/cmd/loadbalancer/list.go +++ b/internal/cmd/loadbalancer/list.go @@ -17,8 +17,8 @@ import ( var ListCmd = base.ListCmd{ ResourceNamePlural: "Load Balancer", - - DefaultColumns: []string{"id", "name", "health", "ipv4", "ipv6", "type", "location", "network_zone", "age"}, + JSONKeyGetByName: "load_balancers", + DefaultColumns: []string{"id", "name", "health", "ipv4", "ipv6", "type", "location", "network_zone", "age"}, Fetch: func(ctx context.Context, client hcapi2.Client, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { opts := hcloud.LoadBalancerListOpts{ListOpts: listOpts} if len(sorts) > 0 { diff --git a/internal/cmd/loadbalancertype/list.go b/internal/cmd/loadbalancertype/list.go index d22d4a97..50ae0e1e 100644 --- a/internal/cmd/loadbalancertype/list.go +++ b/internal/cmd/loadbalancertype/list.go @@ -15,6 +15,7 @@ import ( var ListCmd = base.ListCmd{ ResourceNamePlural: "Load Balancer Types", + JSONKeyGetByName: "load_balancer_types", DefaultColumns: []string{"id", "name", "description", "max_services", "max_connections", "max_targets"}, diff --git a/internal/cmd/location/list.go b/internal/cmd/location/list.go index e693f83c..61c6b508 100644 --- a/internal/cmd/location/list.go +++ b/internal/cmd/location/list.go @@ -14,7 +14,8 @@ import ( ) var ListCmd = base.ListCmd{ - ResourceNamePlural: "Locations", + ResourceNamePlural: "locations", + JSONKeyGetByName: "locations", DefaultColumns: []string{"id", "name", "description", "network_zone", "country", "city"}, Fetch: func(ctx context.Context, client hcapi2.Client, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { diff --git a/internal/cmd/network/list.go b/internal/cmd/network/list.go index bea1eca7..46eea6b8 100644 --- a/internal/cmd/network/list.go +++ b/internal/cmd/network/list.go @@ -18,6 +18,7 @@ import ( var ListCmd = base.ListCmd{ ResourceNamePlural: "Networks", + JSONKeyGetByName: "networks", DefaultColumns: []string{"id", "name", "ip_range", "servers", "age"}, Fetch: func(ctx context.Context, client hcapi2.Client, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { diff --git a/internal/cmd/placementgroup/list.go b/internal/cmd/placementgroup/list.go index 9d094a9c..b0529454 100644 --- a/internal/cmd/placementgroup/list.go +++ b/internal/cmd/placementgroup/list.go @@ -17,6 +17,7 @@ import ( var ListCmd = base.ListCmd{ ResourceNamePlural: "Placement Groups", + JSONKeyGetByName: "placement_groups", DefaultColumns: []string{"id", "name", "servers", "type", "age"}, Fetch: func(ctx context.Context, client hcapi2.Client, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { diff --git a/internal/cmd/primaryip/list.go b/internal/cmd/primaryip/list.go index 8b31006b..f366de1a 100644 --- a/internal/cmd/primaryip/list.go +++ b/internal/cmd/primaryip/list.go @@ -18,6 +18,7 @@ import ( var ListCmd = base.ListCmd{ ResourceNamePlural: "Primary IPs", + JSONKeyGetByName: "primary_ips", DefaultColumns: []string{"id", "type", "name", "ip", "assignee", "dns", "auto_delete", "age"}, Fetch: func(ctx context.Context, client hcapi2.Client, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { diff --git a/internal/cmd/server/list.go b/internal/cmd/server/list.go index 2a7971a6..59d169fb 100644 --- a/internal/cmd/server/list.go +++ b/internal/cmd/server/list.go @@ -20,6 +20,7 @@ import ( var ListCmd = base.ListCmd{ ResourceNamePlural: "Servers", + JSONKeyGetByName: "servers", DefaultColumns: []string{"id", "name", "status", "ipv4", "ipv6", "private_net", "datacenter", "age"}, diff --git a/internal/cmd/servertype/list.go b/internal/cmd/servertype/list.go index f8a5e2cf..862fc4c3 100644 --- a/internal/cmd/servertype/list.go +++ b/internal/cmd/servertype/list.go @@ -16,6 +16,7 @@ import ( var ListCmd = base.ListCmd{ ResourceNamePlural: "Server Types", + JSONKeyGetByName: "server_types", DefaultColumns: []string{"id", "name", "cores", "cpu_type", "architecture", "memory", "disk", "storage_type", "traffic"}, diff --git a/internal/cmd/sshkey/list.go b/internal/cmd/sshkey/list.go index a1734c80..1fdf800d 100644 --- a/internal/cmd/sshkey/list.go +++ b/internal/cmd/sshkey/list.go @@ -16,6 +16,7 @@ import ( var ListCmd = base.ListCmd{ ResourceNamePlural: "SSH keys", + JSONKeyGetByName: "ssh_keys", DefaultColumns: []string{"id", "name", "fingerprint", "age"}, Fetch: func(ctx context.Context, client hcapi2.Client, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { diff --git a/internal/cmd/volume/list.go b/internal/cmd/volume/list.go index 2e711853..fed1f483 100644 --- a/internal/cmd/volume/list.go +++ b/internal/cmd/volume/list.go @@ -18,6 +18,7 @@ import ( var ListCmd = base.ListCmd{ ResourceNamePlural: "Volumes", + JSONKeyGetByName: "volumes", DefaultColumns: []string{"id", "name", "size", "server", "location", "age"}, Fetch: func(ctx context.Context, client hcapi2.Client, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) {