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

feat: add "all list" command to list all available resources #580

Merged
merged 1 commit into from
Oct 25, 2023
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
2 changes: 2 additions & 0 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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),
Expand Down
22 changes: 22 additions & 0 deletions internal/cmd/all/all.go
Original file line number Diff line number Diff line change
@@ -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
}
186 changes: 186 additions & 0 deletions internal/cmd/all/list.go
Original file line number Diff line number Diff line change
@@ -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{})
phm07 marked this conversation as resolved.
Show resolved Hide resolved

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))
phm07 marked this conversation as resolved.
Show resolved Hide resolved
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
},
}
Loading
Loading