Skip to content

Commit

Permalink
refactor: make state mockable (#659)
Browse files Browse the repository at this point in the history
Co-authored-by: Jonas L. <[email protected]>
  • Loading branch information
phm07 and jooola authored Jan 4, 2024
1 parent f636b5e commit 72ec720
Show file tree
Hide file tree
Showing 299 changed files with 1,303 additions and 1,996 deletions.
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,
}
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

0 comments on commit 72ec720

Please sign in to comment.