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 state mockable #659

Merged
merged 5 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
30 changes: 13 additions & 17 deletions cmd/hcloud/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"os"

"github.com/hetznercloud/cli/internal/cli"
"github.com/hetznercloud/cli/internal/hcapi2"
"github.com/hetznercloud/cli/internal/state"
)

Expand All @@ -16,25 +15,22 @@ func init() {
}

func main() {
cliState := state.New()
configPath := os.Getenv("HCLOUD_CONFIG")
if configPath == "" {
configPath = state.DefaultConfigPath()
}

cfg, err := state.ReadConfig(configPath)
if err != nil {
log.Fatalf("unable to read config file %q: %s\n", configPath, err)
}

if cliState.ConfigPath != "" {
_, err := os.Stat(cliState.ConfigPath)
switch {
case err == nil:
if err := cliState.ReadConfig(); err != nil {
log.Fatalf("unable to read config file %q: %s\n", cliState.ConfigPath, err)
}
case os.IsNotExist(err):
break
default:
log.Fatalf("unable to read config file %q: %s\n", cliState.ConfigPath, err)
}
s, err := state.New(cfg)
if err != nil {
log.Fatalln(err)
}

cliState.ReadEnv()
apiClient := hcapi2.NewClient(cliState.Client())
rootCommand := cli.NewRootCommand(cliState, apiClient)
rootCommand := cli.NewRootCommand(s)
if err := rootCommand.Execute(); err != nil {
log.Fatalln(err)
}
Expand Down
43 changes: 21 additions & 22 deletions internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,10 @@ import (
"github.com/hetznercloud/cli/internal/cmd/sshkey"
"github.com/hetznercloud/cli/internal/cmd/version"
"github.com/hetznercloud/cli/internal/cmd/volume"
"github.com/hetznercloud/cli/internal/hcapi2"
"github.com/hetznercloud/cli/internal/state"
)

func NewRootCommand(state *state.State, client hcapi2.Client) *cobra.Command {
func NewRootCommand(s state.State) *cobra.Command {
cmd := &cobra.Command{
Use: "hcloud",
Short: "Hetzner Cloud CLI",
Expand All @@ -41,26 +40,26 @@ func NewRootCommand(state *state.State, client hcapi2.Client) *cobra.Command {
DisableFlagsInUseLine: true,
}
jooola marked this conversation as resolved.
Show resolved Hide resolved
cmd.AddCommand(
all.NewCommand(state, client),
floatingip.NewCommand(state, client),
image.NewCommand(state, client),
server.NewCommand(state, client),
sshkey.NewCommand(state, client),
version.NewCommand(state),
completion.NewCommand(state),
servertype.NewCommand(state, client),
context.NewCommand(state),
datacenter.NewCommand(state, client),
location.NewCommand(state, client),
iso.NewCommand(state, client),
volume.NewCommand(state, client),
network.NewCommand(state, client),
loadbalancer.NewCommand(state, client),
loadbalancertype.NewCommand(state, client),
certificate.NewCommand(state, client),
firewall.NewCommand(state, client),
placementgroup.NewCommand(state, client),
primaryip.NewCommand(state, client),
all.NewCommand(s),
floatingip.NewCommand(s),
image.NewCommand(s),
server.NewCommand(s),
sshkey.NewCommand(s),
version.NewCommand(s),
completion.NewCommand(s),
servertype.NewCommand(s),
context.NewCommand(s),
datacenter.NewCommand(s),
location.NewCommand(s),
iso.NewCommand(s),
volume.NewCommand(s),
network.NewCommand(s),
loadbalancer.NewCommand(s),
loadbalancertype.NewCommand(s),
certificate.NewCommand(s),
firewall.NewCommand(s),
placementgroup.NewCommand(s),
primaryip.NewCommand(s),
)
cmd.PersistentFlags().Duration("poll-interval", 500*time.Millisecond, "Interval at which to poll information, for example action progress")
cmd.SetOut(os.Stdout)
Expand Down
5 changes: 2 additions & 3 deletions internal/cmd/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ 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 {
func NewCommand(s state.State) *cobra.Command {
cmd := &cobra.Command{
Use: "all",
Short: "Commands that apply to all resources",
Expand All @@ -16,7 +15,7 @@ func NewCommand(cli *state.State, client hcapi2.Client) *cobra.Command {
DisableFlagsInUseLine: true,
}
cmd.AddCommand(
listCmd.CobraCommand(cli.Context, client, cli, cli),
listCmd.CobraCommand(s),
)
return cmd
}
7 changes: 3 additions & 4 deletions internal/cmd/all/list.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package all

import (
"context"
"strings"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -67,7 +66,7 @@ Listed resources are:

return cmd
},
Run: func(ctx context.Context, client hcapi2.Client, actionWaiter state.ActionWaiter, cmd *cobra.Command, args []string) error {
Run: func(s state.State, cmd *cobra.Command, args []string) error {

paid, _ := cmd.Flags().GetBool("paid")
labelSelector, _ := cmd.Flags().GetString("selector")
Expand Down Expand Up @@ -133,7 +132,7 @@ Listed resources are:
// We pass an empty slice because we defined the flags earlier.
_ = flagSet.Parse([]string{})

result, err := lc.Fetch(ctx, client, flagSet, listOpts, []string{})
result, err := lc.Fetch(s, flagSet, listOpts, []string{})
ch <- response{result, err}
}()
}
Expand Down Expand Up @@ -162,7 +161,7 @@ Listed resources are:

for i, lc := range cmds {
cols := lc.DefaultColumns
table := lc.OutputTable(client)
table := lc.OutputTable(s.Client())
table.WriteHeader(cols)

if len(resources[i]) == 0 {
Expand Down
13 changes: 2 additions & 11 deletions internal/cmd/all/list_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package all

import (
"context"
_ "embed"
"net"
"testing"
Expand All @@ -23,11 +22,7 @@ func TestListAll(t *testing.T) {

time.Local = time.UTC

cmd := listCmd.CobraCommand(
context.Background(),
fx.Client,
fx.TokenEnsurer,
fx.ActionWaiter)
cmd := listCmd.CobraCommand(fx.State())
fx.ExpectEnsureToken()

fx.Client.ServerClient.EXPECT().
Expand Down Expand Up @@ -251,11 +246,7 @@ func TestListAllPaidJSON(t *testing.T) {

time.Local = time.UTC

cmd := listCmd.CobraCommand(
context.Background(),
fx.Client,
fx.TokenEnsurer,
fx.ActionWaiter)
cmd := listCmd.CobraCommand(fx.State())
fx.ExpectEnsureToken()

fx.Client.ServerClient.EXPECT().
Expand Down
16 changes: 6 additions & 10 deletions internal/cmd/base/cmd.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package base

import (
"context"

"github.com/spf13/cobra"

"github.com/hetznercloud/cli/internal/cmd/util"
Expand All @@ -13,14 +11,12 @@ import (
// Cmd allows defining commands for generic resource-based commands
type Cmd struct {
BaseCobraCommand func(hcapi2.Client) *cobra.Command
Run func(context.Context, hcapi2.Client, state.ActionWaiter, *cobra.Command, []string) error
Run func(state.State, *cobra.Command, []string) error
}

// CobraCommand creates a command that can be registered with cobra.
func (gc *Cmd) CobraCommand(
ctx context.Context, client hcapi2.Client, tokenEnsurer state.TokenEnsurer, actionWaiter state.ActionWaiter,
) *cobra.Command {
cmd := gc.BaseCobraCommand(client)
func (gc *Cmd) CobraCommand(s state.State) *cobra.Command {
cmd := gc.BaseCobraCommand(s.Client())

if cmd.Args == nil {
cmd.Args = cobra.NoArgs
Expand All @@ -30,13 +26,13 @@ func (gc *Cmd) CobraCommand(
cmd.DisableFlagsInUseLine = true

if cmd.PreRunE != nil {
cmd.PreRunE = util.ChainRunE(cmd.PreRunE, tokenEnsurer.EnsureToken)
cmd.PreRunE = util.ChainRunE(cmd.PreRunE, s.EnsureToken)
} else {
cmd.PreRunE = tokenEnsurer.EnsureToken
cmd.PreRunE = s.EnsureToken
}

cmd.RunE = func(cmd *cobra.Command, args []string) error {
return gc.Run(ctx, client, actionWaiter, cmd, args)
return gc.Run(s, cmd, args)
}

return cmd
Expand Down
19 changes: 8 additions & 11 deletions internal/cmd/base/create.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package base

import (
"context"
"os"

"github.com/spf13/cobra"
Expand All @@ -17,15 +16,13 @@ type CreateCmd struct {
BaseCobraCommand func(hcapi2.Client) *cobra.Command
// Run is the function that will be called when the command is executed.
// It should return the created resource, the schema of the resource and an error.
Run func(context.Context, hcapi2.Client, state.ActionWaiter, *cobra.Command, []string) (any, any, error)
PrintResource func(context.Context, hcapi2.Client, *cobra.Command, any)
Run func(state.State, *cobra.Command, []string) (any, any, error)
PrintResource func(state.State, *cobra.Command, any)
}

// CobraCommand creates a command that can be registered with cobra.
func (cc *CreateCmd) CobraCommand(
ctx context.Context, client hcapi2.Client, tokenEnsurer state.TokenEnsurer, actionWaiter state.ActionWaiter,
) *cobra.Command {
cmd := cc.BaseCobraCommand(client)
func (cc *CreateCmd) CobraCommand(s state.State) *cobra.Command {
cmd := cc.BaseCobraCommand(s.Client())

output.AddFlag(cmd, output.OptionJSON(), output.OptionYAML())

Expand All @@ -37,9 +34,9 @@ func (cc *CreateCmd) CobraCommand(
cmd.DisableFlagsInUseLine = true

if cmd.PreRunE != nil {
cmd.PreRunE = util.ChainRunE(cmd.PreRunE, tokenEnsurer.EnsureToken)
cmd.PreRunE = util.ChainRunE(cmd.PreRunE, s.EnsureToken)
} else {
cmd.PreRunE = tokenEnsurer.EnsureToken
cmd.PreRunE = s.EnsureToken
}

cmd.RunE = func(cmd *cobra.Command, args []string) error {
Expand All @@ -52,7 +49,7 @@ func (cc *CreateCmd) CobraCommand(
cmd.SetOut(os.Stdout)
}

resource, schema, err := cc.Run(ctx, client, actionWaiter, cmd, args)
resource, schema, err := cc.Run(s, cmd, args)
if err != nil {
return err
}
Expand All @@ -64,7 +61,7 @@ func (cc *CreateCmd) CobraCommand(
return util.DescribeYAML(schema)
}
} else if cc.PrintResource != nil && resource != nil {
cc.PrintResource(ctx, client, cmd, resource)
cc.PrintResource(s, cmd, resource)
}
return nil
}
Expand Down
21 changes: 9 additions & 12 deletions internal/cmd/base/delete.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package base

import (
"context"
"fmt"
"reflect"
"strings"
Expand All @@ -21,24 +20,22 @@ type DeleteCmd struct {
ShortDescription string
NameSuggestions func(client hcapi2.Client) func() []string
AdditionalFlags func(*cobra.Command)
Fetch func(ctx context.Context, client hcapi2.Client, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error)
Delete func(ctx context.Context, client hcapi2.Client, actionWaiter state.ActionWaiter, cmd *cobra.Command, resource interface{}) error
Fetch func(s state.State, cmd *cobra.Command, idOrName string) (interface{}, *hcloud.Response, error)
Delete func(s state.State, cmd *cobra.Command, resource interface{}) error
}

// CobraCommand creates a command that can be registered with cobra.
func (dc *DeleteCmd) CobraCommand(
ctx context.Context, client hcapi2.Client, tokenEnsurer state.TokenEnsurer, actionWaiter state.ActionWaiter,
) *cobra.Command {
func (dc *DeleteCmd) CobraCommand(s state.State) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("delete [FLAGS] %s", strings.ToUpper(dc.ResourceNameSingular)),
Short: dc.ShortDescription,
Args: cobra.ExactArgs(1),
ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(dc.NameSuggestions(client))),
ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(dc.NameSuggestions(s.Client()))),
TraverseChildren: true,
DisableFlagsInUseLine: true,
PreRunE: util.ChainRunE(tokenEnsurer.EnsureToken),
PreRunE: util.ChainRunE(s.EnsureToken),
RunE: func(cmd *cobra.Command, args []string) error {
return dc.Run(ctx, client, actionWaiter, cmd, args)
return dc.Run(s, cmd, args)
},
}
if dc.AdditionalFlags != nil {
Expand All @@ -48,10 +45,10 @@ func (dc *DeleteCmd) CobraCommand(
}

// Run executes a describe command.
func (dc *DeleteCmd) Run(ctx context.Context, client hcapi2.Client, actionWaiter state.ActionWaiter, cmd *cobra.Command, args []string) error {
func (dc *DeleteCmd) Run(s state.State, cmd *cobra.Command, args []string) error {

idOrName := args[0]
resource, _, err := dc.Fetch(ctx, client, cmd, idOrName)
resource, _, err := dc.Fetch(s, cmd, idOrName)
if err != nil {
return err
}
Expand All @@ -62,7 +59,7 @@ func (dc *DeleteCmd) Run(ctx context.Context, client hcapi2.Client, actionWaiter
return fmt.Errorf("%s not found: %s", dc.ResourceNameSingular, idOrName)
}

if err := dc.Delete(ctx, client, actionWaiter, cmd, resource); err != nil {
if err := dc.Delete(s, cmd, resource); err != nil {
return fmt.Errorf("deleting %s %s failed: %s", dc.ResourceNameSingular, idOrName, err)
}
cmd.Printf("%s %v deleted\n", dc.ResourceNameSingular, idOrName)
Expand Down
Loading
Loading