Skip to content

Commit 6c3ee82

Browse files
committed
Improve output format (#32)
Allows users to choose an output format between text and json. Writes to stdout (through fmt.Print..) have been replaced into writes to a logger. If the verbose flag -v is passed to the command line, then the logger will be enabled. Otherwise, all the logs messages will be discarded. These changes allow users to save results in files. For example, with the following command, a device is provisioned and its properties are written into a json file: ./iot-cloud-cli device create --format json --name <deviceName> > deviceName.json * Replace fmt print into log info * Distinguish board from device * Improve device format output * Fix log infof * Improve logs of arduino-cli This commit has two effects: - arduino-cli init command logs are masked out. - arduino-cli upload logs are logged only if logrus was already enabled. * Fix log msg * Update go mod * Improve thing output format * Refactor thing output format * Fix device iot client * Improve errors: use stderr and error codes * cli package: don't return error * Remove unreachable code * Improve log initialization * Remove duplication of info messages * Add arduino-cli source to upload logs * Return consistent output
1 parent ce7724b commit 6c3ee82

24 files changed

+376
-223
lines changed

arduino/cli/commander.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package cli
33
import (
44
"context"
55
"fmt"
6-
"io/ioutil"
76
"os"
87
"path/filepath"
98

@@ -24,8 +23,8 @@ type commander struct {
2423
// programmatically call arduino-cli commands.
2524
// It directly imports the golang packages of the arduino-cli.
2625
func NewCommander() (arduino.Commander, error) {
27-
// Discard arduino-cli log messages
28-
logrus.SetOutput(ioutil.Discard)
26+
// Discard arduino-cli log info messages
27+
logrus.SetLevel(logrus.PanicLevel)
2928
// Initialize arduino-cli configuration
3029
configuration.Settings = configuration.Init(configuration.FindConfigFileInArgsOrWorkingDirectory(os.Args))
3130
// Create arduino-cli instance, needed to execute arduino-cli commands
@@ -35,6 +34,8 @@ func NewCommander() (arduino.Commander, error) {
3534
return nil, err
3635
}
3736

37+
// Re-enable info level log
38+
logrus.SetLevel(logrus.InfoLevel)
3839
cmd := &commander{inst}
3940
return cmd, nil
4041
}
@@ -62,10 +63,10 @@ func (c *commander) UploadBin(fqbn, bin, port string) error {
6263
Verbose: false,
6364
}
6465

65-
if _, err := upload.Upload(context.Background(), req, os.Stdout, os.Stderr); err != nil {
66+
l := logrus.StandardLogger().WithField("source", "arduino-cli").Writer()
67+
if _, err := upload.Upload(context.Background(), req, l, l); err != nil {
6668
err = fmt.Errorf("%s: %w", "uploading binary", err)
6769
return err
6870
}
69-
7071
return nil
7172
}

cli/cli.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"os"
7+
"strings"
8+
9+
"github.com/arduino/arduino-cli/cli/errorcodes"
10+
"github.com/arduino/arduino-cli/cli/feedback"
11+
"github.com/arduino/iot-cloud-cli/cli/config"
12+
"github.com/arduino/iot-cloud-cli/cli/device"
13+
"github.com/arduino/iot-cloud-cli/cli/thing"
14+
"github.com/sirupsen/logrus"
15+
"github.com/spf13/cobra"
16+
)
17+
18+
var cliFlags struct {
19+
verbose bool
20+
outputFormat string
21+
}
22+
23+
func Execute() {
24+
cli := &cobra.Command{
25+
Use: "arduino-cloud-cli",
26+
Short: "Arduino Cloud CLI.",
27+
Long: "Arduino Cloud Command Line Interface (arduino-cloud-cli).",
28+
PersistentPreRun: preRun,
29+
}
30+
31+
cli.AddCommand(config.NewCommand())
32+
cli.AddCommand(device.NewCommand())
33+
cli.AddCommand(thing.NewCommand())
34+
35+
cli.PersistentFlags().BoolVarP(&cliFlags.verbose, "verbose", "v", false, "Print the logs on the standard output.")
36+
cli.PersistentFlags().StringVar(&cliFlags.outputFormat, "format", "text", "The output format, can be {text|json}.")
37+
38+
if err := cli.Execute(); err != nil {
39+
fmt.Fprintln(os.Stderr, err)
40+
}
41+
}
42+
43+
func parseFormatString(arg string) (feedback.OutputFormat, bool) {
44+
f, found := map[string]feedback.OutputFormat{
45+
"json": feedback.JSON,
46+
"text": feedback.Text,
47+
}[arg]
48+
49+
return f, found
50+
}
51+
52+
func preRun(cmd *cobra.Command, args []string) {
53+
logrus.SetOutput(ioutil.Discard)
54+
// enable log only if verbose flag is passed
55+
if cliFlags.verbose {
56+
logrus.SetLevel(logrus.InfoLevel)
57+
logrus.SetOutput(os.Stdout)
58+
}
59+
60+
// normalize the format strings
61+
cliFlags.outputFormat = strings.ToLower(cliFlags.outputFormat)
62+
// check the right output format was passed
63+
format, found := parseFormatString(cliFlags.outputFormat)
64+
if !found {
65+
feedback.Error("Invalid output format: " + cliFlags.outputFormat)
66+
os.Exit(errorcodes.ErrBadCall)
67+
}
68+
// use the output format to configure the Feedback
69+
feedback.SetFormat(format)
70+
}

cli/config/config.go

+12-10
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package config
22

33
import (
4-
"fmt"
4+
"os"
55
"strings"
66

7+
"github.com/arduino/arduino-cli/cli/errorcodes"
78
"github.com/arduino/arduino-cli/cli/feedback"
89
paths "github.com/arduino/go-paths-helper"
910
"github.com/arduino/iot-cloud-cli/command/config"
11+
"github.com/sirupsen/logrus"
1012
"github.com/spf13/cobra"
1113
"github.com/spf13/viper"
1214
)
@@ -22,17 +24,18 @@ func NewCommand() *cobra.Command {
2224
Use: "config",
2325
Short: "Set the configuration file",
2426
Long: "Set the configuration file to access Arduino IoT Cloud",
25-
RunE: runConfigCommand,
27+
Run: runConfigCommand,
2628
}
2729
configCommand.Flags().StringVarP(&configFlags.file, "file", "f", "", "Existing configuration yaml file")
2830
configCommand.Flags().StringVarP(&configFlags.client, "client", "c", "", "Client ID")
2931
configCommand.Flags().StringVarP(&configFlags.secret, "secret", "s", "", "Secret ID")
3032
return configCommand
3133
}
3234

33-
func runConfigCommand(cmd *cobra.Command, args []string) error {
35+
func runConfigCommand(cmd *cobra.Command, args []string) {
3436
if configFlags.file == "" && (configFlags.client == "" || configFlags.secret == "") {
35-
return fmt.Errorf("%s", "Provide either a yaml file or credentials\n")
37+
feedback.Error("Error during config: provide either a yaml file or credentials")
38+
os.Exit(errorcodes.ErrGeneric)
3639
}
3740

3841
conf := viper.New()
@@ -45,8 +48,8 @@ func runConfigCommand(cmd *cobra.Command, args []string) error {
4548
conf.AddConfigPath(".")
4649
err := conf.ReadInConfig()
4750
if err != nil {
48-
feedback.Errorf("Fatal error config file: %v", err)
49-
return err
51+
feedback.Errorf("Error during config: fatal error config file: %v", err)
52+
os.Exit(errorcodes.ErrGeneric)
5053
}
5154

5255
} else {
@@ -56,10 +59,9 @@ func runConfigCommand(cmd *cobra.Command, args []string) error {
5659

5760
err := config.Config(conf)
5861
if err != nil {
59-
feedback.Errorf("Storing config file: %v", err)
60-
return err
62+
feedback.Errorf("Error during config: storing config file: %v", err)
63+
os.Exit(errorcodes.ErrGeneric)
6164
}
6265

63-
fmt.Println("Configuration file updated")
64-
return nil
66+
logrus.Info("Configuration file updated")
6567
}

cli/device/create.go

+30-7
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@ package device
22

33
import (
44
"fmt"
5+
"os"
56

7+
"github.com/arduino/arduino-cli/cli/errorcodes"
8+
"github.com/arduino/arduino-cli/cli/feedback"
69
"github.com/arduino/iot-cloud-cli/command/device"
10+
"github.com/sirupsen/logrus"
711
"github.com/spf13/cobra"
812
)
913

@@ -18,7 +22,7 @@ func initCreateCommand() *cobra.Command {
1822
Use: "create",
1923
Short: "Create a device",
2024
Long: "Create a device for Arduino IoT Cloud",
21-
RunE: runCreateCommand,
25+
Run: runCreateCommand,
2226
}
2327
createCommand.Flags().StringVarP(&createFlags.port, "port", "p", "", "Device port")
2428
createCommand.Flags().StringVarP(&createFlags.name, "name", "n", "", "Device name")
@@ -27,8 +31,8 @@ func initCreateCommand() *cobra.Command {
2731
return createCommand
2832
}
2933

30-
func runCreateCommand(cmd *cobra.Command, args []string) error {
31-
fmt.Printf("Creating device with name %s\n", createFlags.name)
34+
func runCreateCommand(cmd *cobra.Command, args []string) {
35+
logrus.Infof("Creating device with name %s\n", createFlags.name)
3236

3337
params := &device.CreateParams{
3438
Name: createFlags.name,
@@ -40,11 +44,30 @@ func runCreateCommand(cmd *cobra.Command, args []string) error {
4044
params.Fqbn = &createFlags.fqbn
4145
}
4246

43-
devID, err := device.Create(params)
47+
dev, err := device.Create(params)
4448
if err != nil {
45-
return err
49+
feedback.Errorf("Error during device create: %v", err)
50+
os.Exit(errorcodes.ErrGeneric)
4651
}
4752

48-
fmt.Printf("IoT Cloud device created with ID: %s\n", devID)
49-
return nil
53+
feedback.PrintResult(createResult{dev})
54+
}
55+
56+
type createResult struct {
57+
device *device.DeviceInfo
58+
}
59+
60+
func (r createResult) Data() interface{} {
61+
return r.device
62+
}
63+
64+
func (r createResult) String() string {
65+
return fmt.Sprintf(
66+
"name: %s\nid: %s\nboard: %s\nserial-number: %s\nfqbn: %s",
67+
r.device.Name,
68+
r.device.ID,
69+
r.device.Board,
70+
r.device.Serial,
71+
r.device.FQBN,
72+
)
5073
}

cli/device/delete.go

+10-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package device
22

33
import (
4-
"fmt"
4+
"os"
55

6+
"github.com/arduino/arduino-cli/cli/errorcodes"
7+
"github.com/arduino/arduino-cli/cli/feedback"
68
"github.com/arduino/iot-cloud-cli/command/device"
9+
"github.com/sirupsen/logrus"
710
"github.com/spf13/cobra"
811
)
912

@@ -16,22 +19,22 @@ func initDeleteCommand() *cobra.Command {
1619
Use: "delete",
1720
Short: "Delete a device",
1821
Long: "Delete a device from Arduino IoT Cloud",
19-
RunE: runDeleteCommand,
22+
Run: runDeleteCommand,
2023
}
2124
deleteCommand.Flags().StringVarP(&deleteFlags.id, "id", "i", "", "Device ID")
2225
deleteCommand.MarkFlagRequired("id")
2326
return deleteCommand
2427
}
2528

26-
func runDeleteCommand(cmd *cobra.Command, args []string) error {
27-
fmt.Printf("Deleting device %s\n", deleteFlags.id)
29+
func runDeleteCommand(cmd *cobra.Command, args []string) {
30+
logrus.Infof("Deleting device %s\n", deleteFlags.id)
2831

2932
params := &device.DeleteParams{ID: deleteFlags.id}
3033
err := device.Delete(params)
3134
if err != nil {
32-
return err
35+
feedback.Errorf("Error during device delete: %v", err)
36+
os.Exit(errorcodes.ErrGeneric)
3337
}
3438

35-
fmt.Println("Device successfully deleted")
36-
return nil
39+
logrus.Info("Device successfully deleted")
3740
}

cli/device/list.go

+12-11
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package device
22

33
import (
4-
"fmt"
4+
"os"
55

6+
"github.com/arduino/arduino-cli/cli/errorcodes"
67
"github.com/arduino/arduino-cli/cli/feedback"
78
"github.com/arduino/arduino-cli/table"
89
"github.com/arduino/iot-cloud-cli/command/device"
10+
"github.com/sirupsen/logrus"
911
"github.com/spf13/cobra"
1012
)
1113

@@ -14,33 +16,32 @@ func initListCommand() *cobra.Command {
1416
Use: "list",
1517
Short: "List devices",
1618
Long: "List devices on Arduino IoT Cloud",
17-
RunE: runListCommand,
19+
Run: runListCommand,
1820
}
1921
return listCommand
2022
}
2123

22-
func runListCommand(cmd *cobra.Command, args []string) error {
23-
fmt.Println("Listing devices")
24+
func runListCommand(cmd *cobra.Command, args []string) {
25+
logrus.Info("Listing devices")
2426

2527
devs, err := device.List()
2628
if err != nil {
27-
return err
29+
feedback.Errorf("Error during device list: %v", err)
30+
os.Exit(errorcodes.ErrGeneric)
2831
}
2932

30-
feedback.PrintResult(result{devs})
31-
32-
return nil
33+
feedback.PrintResult(listResult{devs})
3334
}
3435

35-
type result struct {
36+
type listResult struct {
3637
devices []device.DeviceInfo
3738
}
3839

39-
func (r result) Data() interface{} {
40+
func (r listResult) Data() interface{} {
4041
return r.devices
4142
}
4243

43-
func (r result) String() string {
44+
func (r listResult) String() string {
4445
if len(r.devices) == 0 {
4546
return "No devices found."
4647
}

cli/root.go

-22
This file was deleted.

0 commit comments

Comments
 (0)