From f6877a166ea7602ac2f65b85d9dcae16690f49c6 Mon Sep 17 00:00:00 2001 From: phm07 <22707808+phm07@users.noreply.github.com> Date: Wed, 10 Jul 2024 17:52:49 +0200 Subject: [PATCH] feat: allow configuring default sort columns for each supported resource (#795) This PR adds options to configure the default sorting column for each resource that supports the `list` command. See #418 and #434 --- internal/cmd/base/list.go | 21 +++- internal/cmd/base/list_test.go | 83 ++++++++++++--- internal/cmd/certificate/list.go | 2 + internal/cmd/config/helptext/generate.go | 21 +++- internal/cmd/config/helptext/preferences.txt | 3 + internal/cmd/datacenter/list.go | 2 + internal/cmd/firewall/list.go | 2 + internal/cmd/floatingip/list.go | 2 + internal/cmd/image/list.go | 3 + internal/cmd/iso/list.go | 2 + internal/cmd/iso/list_test.go | 2 +- internal/cmd/loadbalancer/list.go | 3 + internal/cmd/loadbalancertype/list.go | 4 +- internal/cmd/loadbalancertype/list_test.go | 2 +- internal/cmd/location/list.go | 2 + internal/cmd/network/list.go | 1 + internal/cmd/network/list_test.go | 2 +- internal/cmd/placementgroup/list.go | 2 + internal/cmd/primaryip/list.go | 2 + internal/cmd/server/list.go | 5 +- internal/cmd/servertype/list.go | 4 +- internal/cmd/servertype/list_test.go | 4 +- internal/cmd/sshkey/list.go | 2 + internal/cmd/volume/list.go | 2 + internal/state/config/options.go | 105 +++++++++++++++++++ 25 files changed, 253 insertions(+), 30 deletions(-) diff --git a/internal/cmd/base/list.go b/internal/cmd/base/list.go index a878a653..bfbdc032 100644 --- a/internal/cmd/base/list.go +++ b/internal/cmd/base/list.go @@ -2,6 +2,7 @@ package base import ( "fmt" + "os" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -10,11 +11,13 @@ import ( "github.com/hetznercloud/cli/internal/cmd/util" "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" + "github.com/hetznercloud/cli/internal/state/config" "github.com/hetznercloud/hcloud-go/v2/hcloud" ) // ListCmd allows defining commands for listing resources type ListCmd struct { + SortOption *config.Option[[]string] ResourceNamePlural string // e.g. "servers" JSONKeyGetByName string // e.g. "servers" DefaultColumns []string @@ -48,7 +51,7 @@ func (lc *ListCmd) CobraCommand(s state.State) *cobra.Command { if lc.AdditionalFlags != nil { lc.AdditionalFlags(cmd) } - cmd.Flags().StringSliceP("sort", "s", []string{"id:asc"}, "Determine the sorting of the result") + cmd.Flags().StringSliceP("sort", "s", []string{}, "Determine the sorting of the result") return cmd } @@ -61,7 +64,21 @@ func (lc *ListCmd) Run(s state.State, cmd *cobra.Command) error { LabelSelector: labelSelector, PerPage: 50, } - sorts, _ := cmd.Flags().GetStringSlice("sort") + + var sorts []string + if cmd.Flags().Changed("sort") { + if lc.SortOption == nil { + _, _ = fmt.Fprintln(os.Stderr, "Warning: resource does not support sorting. Ignoring --sort flag.") + } else { + sorts, _ = cmd.Flags().GetStringSlice("sort") + } + } else if lc.SortOption != nil { + var err error + sorts, err = lc.SortOption.Get(s.Config()) + if err != nil { + return err + } + } resources, err := lc.Fetch(s, cmd.Flags(), listOpts, sorts) if err != nil { diff --git a/internal/cmd/base/list_test.go b/internal/cmd/base/list_test.go index 8b51c7a0..088667cb 100644 --- a/internal/cmd/base/list_test.go +++ b/internal/cmd/base/list_test.go @@ -1,15 +1,19 @@ package base_test import ( + "cmp" "fmt" + "slices" "testing" "github.com/spf13/pflag" "github.com/hetznercloud/cli/internal/cmd/base" "github.com/hetznercloud/cli/internal/cmd/output" + "github.com/hetznercloud/cli/internal/cmd/util" "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" + "github.com/hetznercloud/cli/internal/state/config" "github.com/hetznercloud/cli/internal/testutil" "github.com/hetznercloud/hcloud-go/v2/hcloud" ) @@ -36,30 +40,62 @@ var fakeListCmd = &base.ListCmd{ DefaultColumns: []string{"id", "name"}, - Fetch: func(s state.State, set *pflag.FlagSet, opts hcloud.ListOpts, strings []string) ([]interface{}, error) { - return []interface{}{ - &fakeResource{ + Fetch: func(s state.State, set *pflag.FlagSet, opts hcloud.ListOpts, sort []string) ([]interface{}, error) { + resources := []*fakeResource{ + { + ID: 456, + Name: "test2", + }, + { ID: 123, Name: "test", }, - &fakeResource{ - ID: 321, - Name: "test2", - }, - &fakeResource{ - ID: 42, + { + ID: 789, Name: "test3", }, - }, nil + } + if len(sort) > 0 { + switch sort[0] { + case "id:asc": + slices.SortFunc(resources, func(a, b *fakeResource) int { + return cmp.Compare(a.ID, b.ID) + }) + case "id:desc": + slices.SortFunc(resources, func(a, b *fakeResource) int { + return cmp.Compare(b.ID, a.ID) + }) + case "name:asc": + slices.SortFunc(resources, func(a, b *fakeResource) int { + return cmp.Compare(a.Name, b.Name) + }) + case "name:desc": + slices.SortFunc(resources, func(a, b *fakeResource) int { + return cmp.Compare(b.Name, a.Name) + }) + } + } + return util.ToAnySlice(resources), nil }, } func TestList(t *testing.T) { - const resourceSchema = `[{"id": 123, "name": "test"}, {"id": 321, "name": "test2"}, {"id": 42, "name": "test3"}]` + sortOpt, cleanup := config.NewTestOption( + "sort.fakeresource", + "", + []string{"id:asc"}, + (config.DefaultPreferenceFlags&^config.OptionFlagPFlag)|config.OptionFlagSlice, + nil, + ) + defer cleanup() + + fakeListCmd.SortOption = sortOpt + + const resourceSchema = `[{"id": 123, "name": "test"}, {"id": 456, "name": "test2"}, {"id": 789, "name": "test3"}]` testutil.TestCommand(t, fakeListCmd, map[string]testutil.TestCase{ "no flags": { Args: []string{"list"}, - ExpOut: "ID NAME\n123 test\n321 test2\n42 test3\n", + ExpOut: "ID NAME\n123 test\n456 test2\n789 test3\n", }, "json": { Args: []string{"list", "-o=json"}, @@ -73,7 +109,7 @@ func TestList(t *testing.T) { }, "quiet": { Args: []string{"list", "--quiet"}, - ExpOut: "ID NAME\n123 test\n321 test2\n42 test3\n", + ExpOut: "ID NAME\n123 test\n456 test2\n789 test3\n", }, "json quiet": { Args: []string{"list", "-o=json", "--quiet"}, @@ -85,5 +121,26 @@ func TestList(t *testing.T) { ExpOut: resourceSchema, ExpOutType: testutil.DataTypeYAML, }, + "sort": { + Args: []string{"list", "--sort", "id:desc", "-o=json"}, + ExpOut: `[{"id": 789, "name": "test3"}, {"id": 456, "name": "test2"}, {"id": 123, "name": "test"}]`, + ExpOutType: testutil.DataTypeJSON, + }, + "no sort": { + Args: []string{"list", "--sort=", "-o=json"}, + ExpOut: `[{"id": 456, "name": "test2"}, {"id": 123, "name": "test"}, {"id": 789, "name": "test3"}]`, + ExpOutType: testutil.DataTypeJSON, + }, + "sort with option": { + Args: []string{"list", "-o=json"}, + PreRun: func(t *testing.T, fx *testutil.Fixture) { + sortOpt.Override(fx.Config, []string{"id:desc"}) + t.Cleanup(func() { + sortOpt.Override(fx.Config, nil) + }) + }, + ExpOut: `[{"id": 789, "name": "test3"}, {"id": 456, "name": "test2"}, {"id": 123, "name": "test"}]`, + ExpOutType: testutil.DataTypeJSON, + }, }) } diff --git a/internal/cmd/certificate/list.go b/internal/cmd/certificate/list.go index 9acc0a19..58005e35 100644 --- a/internal/cmd/certificate/list.go +++ b/internal/cmd/certificate/list.go @@ -11,6 +11,7 @@ import ( "github.com/hetznercloud/cli/internal/cmd/util" "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" + "github.com/hetznercloud/cli/internal/state/config" "github.com/hetznercloud/hcloud-go/v2/hcloud" "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) @@ -19,6 +20,7 @@ var ListCmd = base.ListCmd{ ResourceNamePlural: "Certificates", JSONKeyGetByName: "certificates", DefaultColumns: []string{"id", "name", "type", "domain_names", "not_valid_after", "age"}, + SortOption: config.OptionSortCertificate, Fetch: func(s state.State, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { opts := hcloud.CertificateListOpts{ListOpts: listOpts} diff --git a/internal/cmd/config/helptext/generate.go b/internal/cmd/config/helptext/generate.go index 0f48c431..1bb759bf 100644 --- a/internal/cmd/config/helptext/generate.go +++ b/internal/cmd/config/helptext/generate.go @@ -16,11 +16,19 @@ import ( //go:generate go run $GOFILE func main() { - generateTable("preferences.txt", config.OptionFlagPreference, true) - generateTable("other.txt", config.OptionFlagPreference, false) + generateTable( + "preferences.txt", + config.OptionFlagPreference|config.OptionFlagHidden, + config.OptionFlagPreference, + table.Row{"sort.", "Default sorting for resource", "string list", "sort.", "HCLOUD_SORT_", ""}, + ) + generateTable("other.txt", + config.OptionFlagPreference|config.OptionFlagHidden, + 0, + ) } -func generateTable(outFile string, filterFlag config.OptionFlag, hasFlag bool) { +func generateTable(outFile string, mask, filter config.OptionFlag, extraRows ...table.Row) { f, err := os.OpenFile(outFile, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { panic(err) @@ -46,7 +54,7 @@ func generateTable(outFile string, filterFlag config.OptionFlag, hasFlag bool) { var opts []config.IOption for _, opt := range config.Options { - if opt.HasFlags(filterFlag) != hasFlag { + if opt.GetFlags()&mask != filter { continue } opts = append(opts, opt) @@ -61,6 +69,11 @@ func generateTable(outFile string, filterFlag config.OptionFlag, hasFlag bool) { t.AppendSeparator() } + for _, row := range extraRows { + t.AppendRow(row) + t.AppendSeparator() + } + t.Render() } diff --git a/internal/cmd/config/helptext/preferences.txt b/internal/cmd/config/helptext/preferences.txt index 7c686bff..7049798a 100644 --- a/internal/cmd/config/helptext/preferences.txt +++ b/internal/cmd/config/helptext/preferences.txt @@ -19,4 +19,7 @@ ├──────────────────┼──────────────────────┼─────────────┼──────────────────┼─────────────────────────┼─────────────────┤ │ quiet │ If true, only print │ boolean │ quiet │ HCLOUD_QUIET │ --quiet │ │ │ error messages │ │ │ │ │ +├──────────────────┼──────────────────────┼─────────────┼──────────────────┼─────────────────────────┼─────────────────┤ +│ sort. │ Default sorting for │ string list │ sort. │ HCLOUD_SORT_ │ │ +│ │ resource │ │ │ │ │ └──────────────────┴──────────────────────┴─────────────┴──────────────────┴─────────────────────────┴─────────────────┘ diff --git a/internal/cmd/datacenter/list.go b/internal/cmd/datacenter/list.go index 0894c0d0..a01355e7 100644 --- a/internal/cmd/datacenter/list.go +++ b/internal/cmd/datacenter/list.go @@ -7,6 +7,7 @@ import ( "github.com/hetznercloud/cli/internal/cmd/output" "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" + "github.com/hetznercloud/cli/internal/state/config" "github.com/hetznercloud/hcloud-go/v2/hcloud" "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) @@ -15,6 +16,7 @@ var ListCmd = base.ListCmd{ ResourceNamePlural: "Datacenters", JSONKeyGetByName: "datacenters", DefaultColumns: []string{"id", "name", "description", "location"}, + SortOption: config.OptionSortDatacenter, Fetch: func(s state.State, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { opts := hcloud.DatacenterListOpts{ListOpts: listOpts} diff --git a/internal/cmd/firewall/list.go b/internal/cmd/firewall/list.go index a983840e..ae7a96e2 100644 --- a/internal/cmd/firewall/list.go +++ b/internal/cmd/firewall/list.go @@ -9,6 +9,7 @@ import ( "github.com/hetznercloud/cli/internal/cmd/output" "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" + "github.com/hetznercloud/cli/internal/state/config" "github.com/hetznercloud/hcloud-go/v2/hcloud" "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) @@ -17,6 +18,7 @@ var ListCmd = base.ListCmd{ ResourceNamePlural: "Firewalls", JSONKeyGetByName: "firewalls", DefaultColumns: []string{"id", "name", "rules_count", "applied_to_count"}, + SortOption: config.OptionSortFirewall, Fetch: func(s state.State, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { opts := hcloud.FirewallListOpts{ListOpts: listOpts} diff --git a/internal/cmd/floatingip/list.go b/internal/cmd/floatingip/list.go index c2710642..a73ed540 100644 --- a/internal/cmd/floatingip/list.go +++ b/internal/cmd/floatingip/list.go @@ -12,6 +12,7 @@ import ( "github.com/hetznercloud/cli/internal/cmd/util" "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" + "github.com/hetznercloud/cli/internal/state/config" "github.com/hetznercloud/hcloud-go/v2/hcloud" "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) @@ -20,6 +21,7 @@ var ListCmd = base.ListCmd{ ResourceNamePlural: "Floating IPs", JSONKeyGetByName: "floating_ips", DefaultColumns: []string{"id", "type", "name", "description", "ip", "home", "server", "dns", "age"}, + SortOption: config.OptionSortFloatingIP, Fetch: func(s state.State, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { opts := hcloud.FloatingIPListOpts{ListOpts: listOpts} diff --git a/internal/cmd/image/list.go b/internal/cmd/image/list.go index 5cdcc5b6..2a88165b 100644 --- a/internal/cmd/image/list.go +++ b/internal/cmd/image/list.go @@ -15,6 +15,7 @@ import ( "github.com/hetznercloud/cli/internal/cmd/util" "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" + "github.com/hetznercloud/cli/internal/state/config" "github.com/hetznercloud/hcloud-go/v2/hcloud" "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) @@ -23,6 +24,8 @@ var ListCmd = base.ListCmd{ ResourceNamePlural: "Images", JSONKeyGetByName: "images", DefaultColumns: []string{"id", "type", "name", "description", "architecture", "image_size", "disk_size", "created", "deprecated"}, + SortOption: config.OptionSortImage, + AdditionalFlags: func(cmd *cobra.Command) { cmd.Flags().StringSliceP("type", "t", []string{}, "Only show images of given type: system|app|snapshot|backup") cmd.RegisterFlagCompletionFunc("type", cmpl.SuggestCandidates("backup", "snapshot", "system", "app")) diff --git a/internal/cmd/iso/list.go b/internal/cmd/iso/list.go index 2eebd966..b3ad4a90 100644 --- a/internal/cmd/iso/list.go +++ b/internal/cmd/iso/list.go @@ -21,6 +21,8 @@ var ListCmd = base.ListCmd{ ResourceNamePlural: "ISOs", JSONKeyGetByName: "isos", DefaultColumns: []string{"id", "name", "description", "type", "architecture"}, + SortOption: nil, // ISOs does not support sorting + AdditionalFlags: func(cmd *cobra.Command) { cmd.Flags().StringSlice("architecture", []string{}, "Only show images of given architecture: x86|arm") cmd.RegisterFlagCompletionFunc("architecture", cmpl.SuggestCandidates(string(hcloud.ArchitectureX86), string(hcloud.ArchitectureARM))) diff --git a/internal/cmd/iso/list_test.go b/internal/cmd/iso/list_test.go index 31e87a12..a7295ae8 100644 --- a/internal/cmd/iso/list_test.go +++ b/internal/cmd/iso/list_test.go @@ -26,7 +26,7 @@ func TestList(t *testing.T) { gomock.Any(), hcloud.ISOListOpts{ ListOpts: hcloud.ListOpts{PerPage: 50}, - Sort: []string{"id:asc"}, + Sort: nil, // ISOs do not support sorting }, ). Return([]*hcloud.ISO{ diff --git a/internal/cmd/loadbalancer/list.go b/internal/cmd/loadbalancer/list.go index 3039143a..bbe31b96 100644 --- a/internal/cmd/loadbalancer/list.go +++ b/internal/cmd/loadbalancer/list.go @@ -11,6 +11,7 @@ import ( "github.com/hetznercloud/cli/internal/cmd/util" "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" + "github.com/hetznercloud/cli/internal/state/config" "github.com/hetznercloud/hcloud-go/v2/hcloud" "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) @@ -19,6 +20,8 @@ var ListCmd = base.ListCmd{ ResourceNamePlural: "Load Balancer", JSONKeyGetByName: "load_balancers", DefaultColumns: []string{"id", "name", "health", "ipv4", "ipv6", "type", "location", "network_zone", "age"}, + SortOption: config.OptionSortLoadBalancer, + Fetch: func(s state.State, _ *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 20fff54a..01a9acdc 100644 --- a/internal/cmd/loadbalancertype/list.go +++ b/internal/cmd/loadbalancertype/list.go @@ -14,8 +14,8 @@ import ( var ListCmd = base.ListCmd{ ResourceNamePlural: "Load Balancer Types", JSONKeyGetByName: "load_balancer_types", - - DefaultColumns: []string{"id", "name", "description", "max_services", "max_connections", "max_targets"}, + DefaultColumns: []string{"id", "name", "description", "max_services", "max_connections", "max_targets"}, + SortOption: nil, // Load Balancer Types do not support sorting Fetch: func(s state.State, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { opts := hcloud.LoadBalancerTypeListOpts{ListOpts: listOpts} diff --git a/internal/cmd/loadbalancertype/list_test.go b/internal/cmd/loadbalancertype/list_test.go index c3ed2f41..f384dee3 100644 --- a/internal/cmd/loadbalancertype/list_test.go +++ b/internal/cmd/loadbalancertype/list_test.go @@ -26,7 +26,7 @@ func TestList(t *testing.T) { gomock.Any(), hcloud.LoadBalancerTypeListOpts{ ListOpts: hcloud.ListOpts{PerPage: 50}, - Sort: []string{"id:asc"}, + Sort: nil, // Load Balancer Types do not support sorting }, ). Return([]*hcloud.LoadBalancerType{ diff --git a/internal/cmd/location/list.go b/internal/cmd/location/list.go index 25f2b14f..eaf16214 100644 --- a/internal/cmd/location/list.go +++ b/internal/cmd/location/list.go @@ -7,6 +7,7 @@ import ( "github.com/hetznercloud/cli/internal/cmd/output" "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" + "github.com/hetznercloud/cli/internal/state/config" "github.com/hetznercloud/hcloud-go/v2/hcloud" "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) @@ -15,6 +16,7 @@ var ListCmd = base.ListCmd{ ResourceNamePlural: "locations", JSONKeyGetByName: "locations", DefaultColumns: []string{"id", "name", "description", "network_zone", "country", "city"}, + SortOption: config.OptionSortLocation, Fetch: func(s state.State, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { opts := hcloud.LocationListOpts{ListOpts: listOpts} diff --git a/internal/cmd/network/list.go b/internal/cmd/network/list.go index d6da407f..aaaebc98 100644 --- a/internal/cmd/network/list.go +++ b/internal/cmd/network/list.go @@ -20,6 +20,7 @@ var ListCmd = base.ListCmd{ ResourceNamePlural: "Networks", JSONKeyGetByName: "networks", DefaultColumns: []string{"id", "name", "ip_range", "servers", "age"}, + SortOption: nil, // Networks do not support sorting Fetch: func(s state.State, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { opts := hcloud.NetworkListOpts{ListOpts: listOpts} diff --git a/internal/cmd/network/list_test.go b/internal/cmd/network/list_test.go index 79518e3b..ee2518a4 100644 --- a/internal/cmd/network/list_test.go +++ b/internal/cmd/network/list_test.go @@ -28,7 +28,7 @@ func TestList(t *testing.T) { PerPage: 50, LabelSelector: "foo=bar", }, - Sort: []string{"id:asc"}, + Sort: nil, // Networks do not support sorting }, ). Return([]*hcloud.Network{ diff --git a/internal/cmd/placementgroup/list.go b/internal/cmd/placementgroup/list.go index 487b620b..959c339e 100644 --- a/internal/cmd/placementgroup/list.go +++ b/internal/cmd/placementgroup/list.go @@ -11,6 +11,7 @@ import ( "github.com/hetznercloud/cli/internal/cmd/util" "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" + "github.com/hetznercloud/cli/internal/state/config" "github.com/hetznercloud/hcloud-go/v2/hcloud" "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) @@ -19,6 +20,7 @@ var ListCmd = base.ListCmd{ ResourceNamePlural: "Placement Groups", JSONKeyGetByName: "placement_groups", DefaultColumns: []string{"id", "name", "servers", "type", "age"}, + SortOption: config.OptionSortPlacementGroup, Fetch: func(s state.State, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { opts := hcloud.PlacementGroupListOpts{ListOpts: listOpts} diff --git a/internal/cmd/primaryip/list.go b/internal/cmd/primaryip/list.go index 5996d042..a06f23c6 100644 --- a/internal/cmd/primaryip/list.go +++ b/internal/cmd/primaryip/list.go @@ -12,6 +12,7 @@ import ( "github.com/hetznercloud/cli/internal/cmd/util" "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" + "github.com/hetznercloud/cli/internal/state/config" "github.com/hetznercloud/hcloud-go/v2/hcloud" "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) @@ -20,6 +21,7 @@ var ListCmd = base.ListCmd{ ResourceNamePlural: "Primary IPs", JSONKeyGetByName: "primary_ips", DefaultColumns: []string{"id", "type", "name", "ip", "assignee", "dns", "auto_delete", "age"}, + SortOption: config.OptionSortPrimaryIP, Fetch: func(s state.State, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { opts := hcloud.PrimaryIPListOpts{ListOpts: listOpts} diff --git a/internal/cmd/server/list.go b/internal/cmd/server/list.go index 4ae20539..9ee33baa 100644 --- a/internal/cmd/server/list.go +++ b/internal/cmd/server/list.go @@ -17,6 +17,7 @@ import ( "github.com/hetznercloud/cli/internal/cmd/util" "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" + "github.com/hetznercloud/cli/internal/state/config" "github.com/hetznercloud/hcloud-go/v2/hcloud" "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) @@ -36,8 +37,8 @@ var serverStatusStrings = []string{ var ListCmd = base.ListCmd{ ResourceNamePlural: "Servers", JSONKeyGetByName: "servers", - - DefaultColumns: []string{"id", "name", "status", "ipv4", "ipv6", "private_net", "datacenter", "age"}, + DefaultColumns: []string{"id", "name", "status", "ipv4", "ipv6", "private_net", "datacenter", "age"}, + SortOption: config.OptionSortServer, AdditionalFlags: func(cmd *cobra.Command) { cmd.Flags().StringSlice("status", nil, "Only servers with one of these statuses are displayed") diff --git a/internal/cmd/servertype/list.go b/internal/cmd/servertype/list.go index c55755a8..5294914f 100644 --- a/internal/cmd/servertype/list.go +++ b/internal/cmd/servertype/list.go @@ -17,8 +17,8 @@ import ( var ListCmd = base.ListCmd{ ResourceNamePlural: "Server Types", JSONKeyGetByName: "server_types", - - DefaultColumns: []string{"id", "name", "cores", "cpu_type", "architecture", "memory", "disk", "storage_type", "traffic"}, + DefaultColumns: []string{"id", "name", "cores", "cpu_type", "architecture", "memory", "disk", "storage_type", "traffic"}, + SortOption: nil, // Server Types do not support sorting Fetch: func(s state.State, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { opts := hcloud.ServerTypeListOpts{ListOpts: listOpts} diff --git a/internal/cmd/servertype/list_test.go b/internal/cmd/servertype/list_test.go index 4b8668b0..8b107774 100644 --- a/internal/cmd/servertype/list_test.go +++ b/internal/cmd/servertype/list_test.go @@ -27,7 +27,7 @@ func TestList(t *testing.T) { gomock.Any(), hcloud.ServerTypeListOpts{ ListOpts: hcloud.ListOpts{PerPage: 50}, - Sort: []string{"id:asc"}, + Sort: nil, // Server Types do not support sorting }, ). Return([]*hcloud.ServerType{ @@ -69,7 +69,7 @@ func TestListColumnDeprecated(t *testing.T) { gomock.Any(), hcloud.ServerTypeListOpts{ ListOpts: hcloud.ListOpts{PerPage: 50}, - Sort: []string{"id:asc"}, + Sort: nil, // Server Types do not support sorting }, ). Return([]*hcloud.ServerType{ diff --git a/internal/cmd/sshkey/list.go b/internal/cmd/sshkey/list.go index 996d4030..79099629 100644 --- a/internal/cmd/sshkey/list.go +++ b/internal/cmd/sshkey/list.go @@ -10,6 +10,7 @@ import ( "github.com/hetznercloud/cli/internal/cmd/util" "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" + "github.com/hetznercloud/cli/internal/state/config" "github.com/hetznercloud/hcloud-go/v2/hcloud" "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) @@ -18,6 +19,7 @@ var ListCmd = base.ListCmd{ ResourceNamePlural: "SSH keys", JSONKeyGetByName: "ssh_keys", DefaultColumns: []string{"id", "name", "fingerprint", "age"}, + SortOption: config.OptionSortSSHKey, Fetch: func(s state.State, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { opts := hcloud.SSHKeyListOpts{ListOpts: listOpts} diff --git a/internal/cmd/volume/list.go b/internal/cmd/volume/list.go index 713e14d5..2e85720b 100644 --- a/internal/cmd/volume/list.go +++ b/internal/cmd/volume/list.go @@ -12,6 +12,7 @@ import ( "github.com/hetznercloud/cli/internal/cmd/util" "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" + "github.com/hetznercloud/cli/internal/state/config" "github.com/hetznercloud/hcloud-go/v2/hcloud" "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) @@ -20,6 +21,7 @@ var ListCmd = base.ListCmd{ ResourceNamePlural: "Volumes", JSONKeyGetByName: "volumes", DefaultColumns: []string{"id", "name", "size", "server", "location", "age"}, + SortOption: config.OptionSortVolume, Fetch: func(s state.State, _ *pflag.FlagSet, listOpts hcloud.ListOpts, sorts []string) ([]interface{}, error) { opts := hcloud.VolumeListOpts{ListOpts: listOpts} diff --git a/internal/state/config/options.go b/internal/state/config/options.go index a52cc68f..2e593c8e 100644 --- a/internal/state/config/options.go +++ b/internal/state/config/options.go @@ -27,6 +27,8 @@ const ( OptionFlagSensitive // OptionFlagSlice indicates that the option value is a slice OptionFlagSlice + // OptionFlagHidden indicates that the option should not be shown in the help output + OptionFlagHidden DefaultPreferenceFlags = OptionFlagPreference | OptionFlagConfig | OptionFlagPFlag | OptionFlagEnv ) @@ -46,6 +48,8 @@ type IOption interface { FlagName() string // HasFlags returns true if the option has all the provided flags set HasFlags(src OptionFlag) bool + // GetFlags returns all flags set for the option + GetFlags() OptionFlag // GetAsAny reads the option value from the config and returns it as an any GetAsAny(c Config) (any, error) // OverrideAny sets the option value in the config to the provided any value @@ -141,6 +145,102 @@ var ( (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice, nil, ) + + OptionSortCertificate = newOpt( + "sort.certificate", + "Default sorting for Certificate resource", + []string{"id:asc"}, + (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, + nil, + ) + + OptionSortDatacenter = newOpt( + "sort.datacenter", + "Default sorting for Datacenter resource", + []string{"id:asc"}, + (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, + nil, + ) + + OptionSortFirewall = newOpt( + "sort.firewall", + "Default sorting for Firewall resource", + []string{"id:asc"}, + (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, + nil, + ) + + OptionSortFloatingIP = newOpt( + "sort.floating-ip", + "Default sorting for Floating IP resource", + []string{"id:asc"}, + (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, + nil, + ) + + OptionSortImage = newOpt( + "sort.image", + "Default sorting for Image resource", + []string{"id:asc"}, + (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, + nil, + ) + + OptionSortLoadBalancer = newOpt( + "sort.load-balancer", + "Default sorting for Load Balancer resource", + []string{"id:asc"}, + (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, + nil, + ) + + OptionSortLocation = newOpt( + "sort.location", + "Default sorting for Location resource", + []string{"id:asc"}, + (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, + nil, + ) + + OptionSortPlacementGroup = newOpt( + "sort.placement-group", + "Default sorting for Placement Group resource", + []string{"id:asc"}, + (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, + nil, + ) + + OptionSortPrimaryIP = newOpt( + "sort.primary-ip", + "Default sorting for Primary IP resource", + []string{"id:asc"}, + (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, + nil, + ) + + OptionSortServer = newOpt( + "sort.server", + "Default sorting for Server resource", + []string{"id:asc"}, + (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, + nil, + ) + + OptionSortSSHKey = newOpt( + "sort.ssh-key", + "Default sorting for SSH Key resource", + []string{"id:asc"}, + (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, + nil, + ) + + OptionSortVolume = newOpt( + "sort.volume", + "Default sorting for Volume resource", + []string{"id:asc"}, + (DefaultPreferenceFlags&^OptionFlagPFlag)|OptionFlagSlice|OptionFlagHidden, + nil, + ) ) type Option[T any] struct { @@ -210,6 +310,11 @@ func (o *Option[T]) HasFlags(src OptionFlag) bool { return (^o.Flags)&src == 0 } +func (o *Option[T]) GetFlags() OptionFlag { + return o.Flags + +} + func (o *Option[T]) GetName() string { return o.Name }