Skip to content

Commit 52bbe00

Browse files
committed
Refactor cli package (#103)
- avoid global variables - run functions take flags as argument > this allows to call them in tests - put the error handling (with os.Exit()) in a single point
1 parent d947a4e commit 52bbe00

26 files changed

+483
-356
lines changed

cli/cli.go

+22-17
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,29 @@ import (
3535
"github.com/spf13/cobra"
3636
)
3737

38-
var cliFlags struct {
38+
type cliFlags struct {
3939
verbose bool
4040
outputFormat string
4141
}
4242

4343
func Execute() {
44+
flags := &cliFlags{}
4445
cli := &cobra.Command{
45-
Use: "arduino-cloud-cli",
46-
Short: "Arduino Cloud CLI.",
47-
Long: "Arduino Cloud Command Line Interface (arduino-cloud-cli).",
48-
PersistentPreRun: preRun,
46+
Use: "arduino-cloud-cli",
47+
Short: "Arduino Cloud CLI.",
48+
Long: "Arduino Cloud Command Line Interface (arduino-cloud-cli).",
49+
PersistentPreRun: func(cmd *cobra.Command, args []string) {
50+
if err := preRun(flags); err != nil {
51+
feedback.Error(err)
52+
os.Exit(errorcodes.ErrBadCall)
53+
}
54+
},
4955
}
56+
cli.PersistentFlags().BoolVarP(&flags.verbose, "verbose", "v", false, "Print the logs on the standard output.")
57+
validOutputFormats := []string{"text", "json", "jsonmini", "yaml"}
58+
cli.PersistentFlags().StringVar(&flags.outputFormat, "format", "text",
59+
fmt.Sprintf("The output format, can be: %s", strings.Join(validOutputFormats, ", ")),
60+
)
5061

5162
cli.AddCommand(version.NewCommand())
5263
cli.AddCommand(credentials.NewCommand())
@@ -55,12 +66,6 @@ func Execute() {
5566
cli.AddCommand(dashboard.NewCommand())
5667
cli.AddCommand(ota.NewCommand())
5768

58-
cli.PersistentFlags().BoolVarP(&cliFlags.verbose, "verbose", "v", false, "Print the logs on the standard output.")
59-
validOutputFormats := []string{"text", "json", "jsonmini", "yaml"}
60-
cli.PersistentFlags().StringVar(&cliFlags.outputFormat, "format", "text",
61-
fmt.Sprintf("The output format, can be: %s", strings.Join(validOutputFormats, ", ")),
62-
)
63-
6469
if err := cli.Execute(); err != nil {
6570
fmt.Fprintln(os.Stderr, err)
6671
}
@@ -77,22 +82,22 @@ func parseFormatString(arg string) (feedback.OutputFormat, bool) {
7782
return f, found
7883
}
7984

80-
func preRun(cmd *cobra.Command, args []string) {
85+
func preRun(flags *cliFlags) error {
8186
logrus.SetOutput(ioutil.Discard)
8287
// enable log only if verbose flag is passed
83-
if cliFlags.verbose {
88+
if flags.verbose {
8489
logrus.SetLevel(logrus.InfoLevel)
8590
logrus.SetOutput(os.Stdout)
8691
}
8792

8893
// normalize the format strings
89-
cliFlags.outputFormat = strings.ToLower(cliFlags.outputFormat)
94+
flags.outputFormat = strings.ToLower(flags.outputFormat)
9095
// check the right output format was passed
91-
format, found := parseFormatString(cliFlags.outputFormat)
96+
format, found := parseFormatString(flags.outputFormat)
9297
if !found {
93-
feedback.Error("Invalid output format: " + cliFlags.outputFormat)
94-
os.Exit(errorcodes.ErrBadCall)
98+
return fmt.Errorf("invalid output format: %s", flags.outputFormat)
9599
}
96100
// use the output format to configure the Feedback
97101
feedback.SetFormat(format)
102+
return nil
98103
}

cli/credentials/find.go

+9-4
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,25 @@ func initFindCommand() *cobra.Command {
3232
Use: "find",
3333
Short: "Find the credentials that would be used in your current directory",
3434
Long: "Find the credentials to access Arduino IoT Cloud that would be used in your current directory",
35-
Run: runFindCommand,
35+
Run: func(cmd *cobra.Command, args []string) {
36+
if err := runFindCommand(); err != nil {
37+
feedback.Errorf("Error during credentials find: %v", err)
38+
os.Exit(errorcodes.ErrGeneric)
39+
}
40+
},
3641
}
3742

3843
return findCommand
3944
}
4045

41-
func runFindCommand(cmd *cobra.Command, args []string) {
46+
func runFindCommand() error {
4247
logrus.Info("Looking for credentials")
4348

4449
src, err := config.FindCredentials()
4550
if err != nil {
46-
feedback.Errorf("Error during credentials find: %v", err)
47-
os.Exit(errorcodes.ErrGeneric)
51+
return err
4852
}
4953

5054
feedback.Printf("Using credentials in: %s", src)
55+
return nil
5156
}

cli/credentials/init.go

+28-30
Original file line numberDiff line numberDiff line change
@@ -34,77 +34,75 @@ import (
3434
"github.com/spf13/viper"
3535
)
3636

37-
var initFlags struct {
37+
type initFlags struct {
3838
destDir string
3939
overwrite bool
4040
format string
4141
}
4242

4343
func initInitCommand() *cobra.Command {
44+
flags := &initFlags{}
4445
initCommand := &cobra.Command{
4546
Use: "init",
4647
Short: "Initialize a credentials file",
4748
Long: "Initialize an Arduino IoT Cloud CLI credentials file",
48-
Run: runInitCommand,
49+
Run: func(cmd *cobra.Command, args []string) {
50+
if err := runInitCommand(flags); err != nil {
51+
feedback.Errorf("Error during credentials init: %v", err)
52+
os.Exit(errorcodes.ErrGeneric)
53+
}
54+
},
4955
}
5056

51-
initCommand.Flags().StringVar(&initFlags.destDir, "dest-dir", "", "Sets where to save the credentials file")
52-
initCommand.Flags().BoolVar(&initFlags.overwrite, "overwrite", false, "Overwrite existing credentials file")
53-
initCommand.Flags().StringVar(&initFlags.format, "file-format", "yaml", "Format of the credentials file, can be {yaml|json}")
57+
initCommand.Flags().StringVar(&flags.destDir, "dest-dir", "", "Sets where to save the credentials file")
58+
initCommand.Flags().BoolVar(&flags.overwrite, "overwrite", false, "Overwrite existing credentials file")
59+
initCommand.Flags().StringVar(&flags.format, "file-format", "yaml", "Format of the credentials file, can be {yaml|json}")
5460

5561
return initCommand
5662
}
5763

58-
func runInitCommand(cmd *cobra.Command, args []string) {
64+
func runInitCommand(flags *initFlags) error {
5965
logrus.Info("Initializing credentials file")
6066

6167
// Get default destination directory if it's not passed
62-
if initFlags.destDir == "" {
68+
if flags.destDir == "" {
6369
credPath, err := arduino.DataDir()
6470
if err != nil {
65-
feedback.Errorf("Error during credentials init: cannot retrieve arduino default directory: %v", err)
66-
os.Exit(errorcodes.ErrGeneric)
71+
return fmt.Errorf("cannot retrieve arduino default directory: %w", err)
6772
}
6873
// Create arduino default directory if it does not exist
6974
if credPath.NotExist() {
7075
if err = credPath.MkdirAll(); err != nil {
71-
feedback.Errorf("Error during credentials init: cannot create arduino default directory %s: %v", credPath, err)
72-
os.Exit(errorcodes.ErrGeneric)
76+
return fmt.Errorf("cannot create arduino default directory %s: %w", credPath, err)
7377
}
7478
}
75-
initFlags.destDir = credPath.String()
79+
flags.destDir = credPath.String()
7680
}
7781

7882
// Validate format flag
79-
initFlags.format = strings.ToLower(initFlags.format)
80-
if initFlags.format != "json" && initFlags.format != "yaml" {
81-
feedback.Error("Error during credentials init: format is not valid, provide 'json' or 'yaml'")
82-
os.Exit(errorcodes.ErrGeneric)
83+
flags.format = strings.ToLower(flags.format)
84+
if flags.format != "json" && flags.format != "yaml" {
85+
return fmt.Errorf("format is not valid, provide 'json' or 'yaml'")
8386
}
8487

8588
// Check that the destination directory is valid and build the credentials file path
86-
credPath, err := paths.New(initFlags.destDir).Abs()
89+
credPath, err := paths.New(flags.destDir).Abs()
8790
if err != nil {
88-
feedback.Errorf("Error during credentials init: cannot retrieve absolute path of %s: %v", initFlags.destDir, err)
89-
os.Exit(errorcodes.ErrGeneric)
91+
return fmt.Errorf("cannot retrieve absolute path of %s: %w", flags.destDir, err)
9092
}
9193
if !credPath.IsDir() {
92-
feedback.Errorf("Error during credentials init: %s is not a valid directory", credPath)
93-
os.Exit(errorcodes.ErrGeneric)
94+
return fmt.Errorf("%s is not a valid directory", credPath)
9495
}
95-
credFile := credPath.Join(config.CredentialsFilename + "." + initFlags.format)
96-
if !initFlags.overwrite && credFile.Exist() {
97-
feedback.Errorf("Error during credentials init: %s already exists, use '--overwrite' to overwrite it",
98-
credFile)
99-
os.Exit(errorcodes.ErrGeneric)
96+
credFile := credPath.Join(config.CredentialsFilename + "." + flags.format)
97+
if !flags.overwrite && credFile.Exist() {
98+
return fmt.Errorf("%s already exists, use '--overwrite' to overwrite it", credFile)
10099
}
101100

102101
// Take needed credentials starting an interactive mode
103102
feedback.Print("To obtain your API credentials visit https://create.arduino.cc/iot/integrations")
104103
id, key, err := paramsPrompt()
105104
if err != nil {
106-
feedback.Errorf("Error during credentials init: cannot take credentials params: %v", err)
107-
os.Exit(errorcodes.ErrGeneric)
105+
return fmt.Errorf("cannot take credentials params: %w", err)
108106
}
109107

110108
// Write the credentials file
@@ -113,11 +111,11 @@ func runInitCommand(cmd *cobra.Command, args []string) {
113111
newSettings.Set("client", id)
114112
newSettings.Set("secret", key)
115113
if err := newSettings.WriteConfigAs(credFile.String()); err != nil {
116-
feedback.Errorf("Error during credentials init: cannot write credentials file: %v", err)
117-
os.Exit(errorcodes.ErrGeneric)
114+
return fmt.Errorf("cannot write credentials file: %w", err)
118115
}
119116

120117
feedback.Printf("Credentials file successfully initialized at: %s", credFile)
118+
return nil
121119
}
122120

123121
func paramsPrompt() (id, key string, err error) {

cli/dashboard/create.go

+20-15
Original file line numberDiff line numberDiff line change
@@ -30,54 +30,59 @@ import (
3030
"github.com/spf13/cobra"
3131
)
3232

33-
var createFlags struct {
33+
type createFlags struct {
3434
name string
3535
template string
3636
override map[string]string
3737
}
3838

3939
func initCreateCommand() *cobra.Command {
40+
flags := &createFlags{}
4041
createCommand := &cobra.Command{
4142
Use: "create",
4243
Short: "Create a dashboard from a template",
4344
Long: "Create a dashboard from a template for Arduino IoT Cloud",
44-
Run: runCreateCommand,
45+
Run: func(cmd *cobra.Command, args []string) {
46+
if err := runCreateCommand(flags); err != nil {
47+
feedback.Errorf("Error during dashboard create: %v", err)
48+
os.Exit(errorcodes.ErrGeneric)
49+
}
50+
},
4551
}
46-
createCommand.Flags().StringVarP(&createFlags.name, "name", "n", "", "Dashboard name")
47-
createCommand.Flags().StringVarP(&createFlags.template, "template", "t", "",
52+
createCommand.Flags().StringVarP(&flags.name, "name", "n", "", "Dashboard name")
53+
createCommand.Flags().StringVarP(&flags.template, "template", "t", "",
4854
"File containing a dashboard template, JSON and YAML format are supported",
4955
)
50-
createCommand.Flags().StringToStringVarP(&createFlags.override, "override", "o", nil,
56+
createCommand.Flags().StringToStringVarP(&flags.override, "override", "o", nil,
5157
"Map stating the items to be overridden. Ex: 'thing-0=xxxxxxxx,thing-1=yyyyyyyy'")
5258

5359
createCommand.MarkFlagRequired("template")
5460
return createCommand
5561
}
5662

57-
func runCreateCommand(cmd *cobra.Command, args []string) {
58-
logrus.Infof("Creating dashboard from template %s", createFlags.template)
63+
func runCreateCommand(flags *createFlags) error {
64+
logrus.Infof("Creating dashboard from template %s", flags.template)
5965

6066
cred, err := config.RetrieveCredentials()
6167
if err != nil {
62-
feedback.Errorf("Error during dashboard create: retrieving credentials: %v", err)
63-
os.Exit(errorcodes.ErrGeneric)
68+
return fmt.Errorf("retrieving credentials: %w", err)
6469
}
6570

6671
params := &dashboard.CreateParams{
67-
Template: createFlags.template,
68-
Override: createFlags.override,
72+
Template: flags.template,
73+
Override: flags.override,
6974
}
70-
if createFlags.name != "" {
71-
params.Name = &createFlags.name
75+
if flags.name != "" {
76+
params.Name = &flags.name
7277
}
7378

7479
dashboard, err := dashboard.Create(params, cred)
7580
if err != nil {
76-
feedback.Errorf("Error during dashboard create: %v", err)
77-
os.Exit(errorcodes.ErrGeneric)
81+
return err
7882
}
7983

8084
feedback.PrintResult(createResult{dashboard})
85+
return nil
8186
}
8287

8388
type createResult struct {

cli/dashboard/delete.go

+16-10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package dashboard
1919

2020
import (
21+
"fmt"
2122
"os"
2223

2324
"github.com/arduino/arduino-cli/cli/errorcodes"
@@ -28,37 +29,42 @@ import (
2829
"github.com/spf13/cobra"
2930
)
3031

31-
var deleteFlags struct {
32+
type deleteFlags struct {
3233
id string
3334
}
3435

3536
func initDeleteCommand() *cobra.Command {
37+
flags := &deleteFlags{}
3638
deleteCommand := &cobra.Command{
3739
Use: "delete",
3840
Short: "Delete a dashboard",
3941
Long: "Delete a dashboard from Arduino IoT Cloud",
40-
Run: runDeleteCommand,
42+
Run: func(cmd *cobra.Command, args []string) {
43+
if err := runDeleteCommand(flags); err != nil {
44+
feedback.Errorf("Error during dashboard delete: %v", err)
45+
os.Exit(errorcodes.ErrGeneric)
46+
}
47+
},
4148
}
42-
deleteCommand.Flags().StringVarP(&deleteFlags.id, "id", "i", "", "Dashboard ID")
49+
deleteCommand.Flags().StringVarP(&flags.id, "id", "i", "", "Dashboard ID")
4350
deleteCommand.MarkFlagRequired("id")
4451
return deleteCommand
4552
}
4653

47-
func runDeleteCommand(cmd *cobra.Command, args []string) {
48-
logrus.Infof("Deleting dashboard %s", deleteFlags.id)
54+
func runDeleteCommand(flags *deleteFlags) error {
55+
logrus.Infof("Deleting dashboard %s", flags.id)
4956

5057
cred, err := config.RetrieveCredentials()
5158
if err != nil {
52-
feedback.Errorf("Error during dashboard delete: retrieving credentials: %v", err)
53-
os.Exit(errorcodes.ErrGeneric)
59+
return fmt.Errorf("retrieving credentials: %w", err)
5460
}
5561

56-
params := &dashboard.DeleteParams{ID: deleteFlags.id}
62+
params := &dashboard.DeleteParams{ID: flags.id}
5763
err = dashboard.Delete(params, cred)
5864
if err != nil {
59-
feedback.Errorf("Error during dashboard delete: %v", err)
60-
os.Exit(errorcodes.ErrGeneric)
65+
return err
6166
}
6267

6368
logrus.Info("Dashboard successfully deleted")
69+
return nil
6470
}

0 commit comments

Comments
 (0)