From 71782ace8687ead588e097df47fabea526ef4304 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Tue, 23 Nov 2021 18:15:51 +0100 Subject: [PATCH 01/10] Get default arduino15 folder --- internal/config/config.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/internal/config/config.go b/internal/config/config.go index 6a9287ee..495c4fd0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -19,7 +19,12 @@ package config import ( "fmt" + "os" + "path/filepath" + "runtime" + "github.com/arduino/go-paths-helper" + "github.com/arduino/go-win32-utils" "github.com/spf13/viper" ) @@ -46,3 +51,27 @@ func Retrieve() (*Config, error) { v.Unmarshal(conf) return conf, nil } + +// Get Arduino default directory (arduino15) +func ArduinoDir() (*paths.Path, error) { + userHomeDir, err := os.UserHomeDir() + if err != nil { + return nil, fmt.Errorf("unable to get user home dir: %w", err) + } + + var path *paths.Path + switch runtime.GOOS { + case "darwin": + path = paths.New(filepath.Join(userHomeDir, "Library", "Arduino15")) + case "windows": + localAppDataPath, err := win32.GetLocalAppDataFolder() + if err != nil { + return nil, fmt.Errorf("unable to get local app data folder: %w", err) + } + path = paths.New(filepath.Join(localAppDataPath, "Arduino15")) + default: // linux, android, *bsd, plan9 and other Unix-like systems + path = paths.New(filepath.Join(userHomeDir, ".arduino15")) + } + + return path, nil +} From 07f0c8eddf7ebf2030fb2f53b33505d7ec7fda76 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Tue, 23 Nov 2021 18:12:17 +0100 Subject: [PATCH 02/10] Set arduino15 as default config dir --- cli/config/init.go | 10 +++++----- command/config/init.go | 29 +++++++++++++++++++---------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/cli/config/init.go b/cli/config/init.go index 1ab8b58b..9ea43690 100644 --- a/cli/config/init.go +++ b/cli/config/init.go @@ -41,15 +41,15 @@ func initInitCommand() *cobra.Command { Run: runInitCommand, } - initCommand.Flags().StringVar(&initFlags.destDir, "dest-dir", ".", "Sets where to save the configuration file.") - initCommand.Flags().BoolVar(&initFlags.overwrite, "overwrite", false, "Overwrite existing config file.") + initCommand.Flags().StringVar(&initFlags.destDir, "dest-dir", "", "Sets where to save the configuration file") + initCommand.Flags().BoolVar(&initFlags.overwrite, "overwrite", false, "Overwrite existing config file") initCommand.Flags().StringVar(&initFlags.format, "config-format", "yaml", "Format of the configuration file, can be {yaml|json}") return initCommand } func runInitCommand(cmd *cobra.Command, args []string) { - logrus.Infof("Initializing a config file in folder: %s", initFlags.destDir) + logrus.Info("Initializing config file") params := &config.InitParams{ DestDir: initFlags.destDir, @@ -57,11 +57,11 @@ func runInitCommand(cmd *cobra.Command, args []string) { Format: initFlags.format, } - err := config.Init(params) + file, err := config.Init(params) if err != nil { feedback.Errorf("Error during config init: %v", err) os.Exit(errorcodes.ErrGeneric) } - logrus.Info("Config file successfully initialized") + logrus.Info("Config file successfully initialized as: %s", file) } diff --git a/command/config/init.go b/command/config/init.go index 4a350eac..91b2bccf 100644 --- a/command/config/init.go +++ b/command/config/init.go @@ -44,33 +44,42 @@ func validateFormatString(arg string) error { // Init initializes a configuration file with default values. // If the file doesn't exist, it is created. -// If it exists, it is written to only if overwrite param is true. -func Init(params *InitParams) error { - configPath, err := paths.New(params.DestDir).Abs() - if err != nil { - return fmt.Errorf("%s: %w", "cannot retrieve absolute path of passed dest-dir", err) +// If it already exists, it is written to only if overwrite param is true. +func Init(params *InitParams) (filepath string, err error) { + var configPath *paths.Path + if params.DestDir != "" { + configPath, err = paths.New(params.DestDir).Abs() + if err != nil { + return "", fmt.Errorf("cannot retrieve absolute path of passed dest-dir: %w", err) + } + } else { + configPath, err = config.ArduinoDir() + if err != nil { + return "", fmt.Errorf("cannot retrieve arduino default directory: %w", err) + } } + if !configPath.IsDir() { - return fmt.Errorf("%s: %w", "passed dest-dir is not a valid directory", err) + return "", fmt.Errorf("chosen destination dir is not valid: %w", err) } params.Format = strings.ToLower(params.Format) if err := validateFormatString(params.Format); err != nil { - return err + return "", err } configFile := configPath.Join(config.Filename + "." + params.Format) if !params.Overwrite && configFile.Exist() { - return errors.New("config file already exists, use --overwrite to discard the existing one") + return "", errors.New("config file already exists, use --overwrite to discard the existing one") } newSettings := viper.New() newSettings.SetConfigPermissions(os.FileMode(0600)) config.SetDefaults(newSettings) if err := newSettings.WriteConfigAs(configFile.String()); err != nil { - return fmt.Errorf("cannot create config file: %v", err) + return "", fmt.Errorf("cannot create config file: %v", err) } - return nil + return configFile.String(), nil } From 05a817eeb01c6e1effd9ef2f9260e5c71ad4d27c Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Tue, 23 Nov 2021 18:14:55 +0100 Subject: [PATCH 03/10] Get id and secret from prompt --- cli/config/init.go | 4 ++-- command/config/init.go | 50 +++++++++++++++++++++++++++++++++++++++--- go.mod | 2 ++ go.sum | 12 +++++----- 4 files changed, 57 insertions(+), 11 deletions(-) diff --git a/cli/config/init.go b/cli/config/init.go index 9ea43690..5b302129 100644 --- a/cli/config/init.go +++ b/cli/config/init.go @@ -36,8 +36,8 @@ var initFlags struct { func initInitCommand() *cobra.Command { initCommand := &cobra.Command{ Use: "init", - Short: "Initialize a configuration file with default values", - Long: "Initialize an Arduino IoT Cloud CLI configuration file with default values", + Short: "Initialize a configuration file", + Long: "Initialize an Arduino IoT Cloud CLI configuration", Run: runInitCommand, } diff --git a/command/config/init.go b/command/config/init.go index 91b2bccf..323b22b9 100644 --- a/command/config/init.go +++ b/command/config/init.go @@ -25,9 +25,15 @@ import ( "github.com/arduino/arduino-cloud-cli/internal/config" "github.com/arduino/go-paths-helper" + "github.com/manifoldco/promptui" "github.com/spf13/viper" ) +const ( + clientLen = 32 + secretLen = 64 +) + // InitParams contains the parameters needed to initialize a configuration file. type InitParams struct { DestDir string // Destination directory in which the configuration file will be saved @@ -42,7 +48,7 @@ func validateFormatString(arg string) error { return nil } -// Init initializes a configuration file with default values. +// Init initializes a configuration file with values passed in by the user. // If the file doesn't exist, it is created. // If it already exists, it is written to only if overwrite param is true. func Init(params *InitParams) (filepath string, err error) { @@ -69,17 +75,55 @@ func Init(params *InitParams) (filepath string, err error) { } configFile := configPath.Join(config.Filename + "." + params.Format) - if !params.Overwrite && configFile.Exist() { return "", errors.New("config file already exists, use --overwrite to discard the existing one") } + id, key, err := prompt() + if err != nil { + return "", err + } + newSettings := viper.New() newSettings.SetConfigPermissions(os.FileMode(0600)) - config.SetDefaults(newSettings) + newSettings.Set("client", id) + newSettings.Set("secret", key) if err := newSettings.WriteConfigAs(configFile.String()); err != nil { return "", fmt.Errorf("cannot create config file: %v", err) } return configFile.String(), nil } + +func prompt() (id, key string, err error) { + prompt := promptui.Prompt{ + Label: "client", + Validate: func(s string) error { + if len(s) != clientLen { + return errors.New("") + } + return nil + }, + } + id, err = prompt.Run() + if err != nil { + return "", "", fmt.Errorf("client prompt fail: %w", err) + } + + prompt = promptui.Prompt{ + Mask: '*', + Label: "secret", + Validate: func(s string) error { + if len(s) != secretLen { + return errors.New("") + } + return nil + }, + } + key, err = prompt.Run() + if err != nil { + return "", "", fmt.Errorf("secret prompt fail: %w", err) + } + + return id, key, nil +} diff --git a/go.mod b/go.mod index 640d62f2..116de811 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,13 @@ require ( github.com/antihax/optional v1.0.0 github.com/arduino/arduino-cli v0.0.0-20210607095659-16f41352eac3 github.com/arduino/go-paths-helper v1.6.1 + github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b github.com/arduino/iot-client-go v1.3.4-0.20211116175324-9a98dd4ad269 github.com/gofrs/uuid v4.0.0+incompatible github.com/google/go-cmp v0.5.6 github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6 github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5 + github.com/manifoldco/promptui v0.9.0 github.com/sirupsen/logrus v1.8.1 github.com/spf13/cobra v1.1.3 github.com/spf13/viper v1.7.0 diff --git a/go.sum b/go.sum index 900ab731..1a432fc8 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,6 @@ github.com/arduino/go-properties-orderedmap v1.3.0/go.mod h1:DKjD2VXY/NZmlingh4l github.com/arduino/go-timeutils v0.0.0-20171220113728-d1dd9e313b1b/go.mod h1:uwGy5PpN4lqW97FiLnbcx+xx8jly5YuPMJWfVwwjJiQ= github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b h1:3PjgYG5gVPA7cipp7vIR2lF96KkEJIFBJ+ANnuv6J20= github.com/arduino/go-win32-utils v0.0.0-20180330194947-ed041402e83b/go.mod h1:iIPnclBMYm1g32Q5kXoqng4jLhMStReIP7ZxaoUC2y8= -github.com/arduino/iot-client-go v1.3.4-0.20211103115604-d4d372164262 h1:qVq8cdkaRPaLc9DAjY/6rH3ocs6ZvnEJtD26f5++/RU= -github.com/arduino/iot-client-go v1.3.4-0.20211103115604-d4d372164262/go.mod h1:gYvpMt7Qw+OSScTLyIlCnpbvy9y96ey/2zhB4w6FoK0= github.com/arduino/iot-client-go v1.3.4-0.20211116175324-9a98dd4ad269 h1:01RHB48b1QRN5viXtdtmSkvyudxHqczDV+OD+1PuE6M= github.com/arduino/iot-client-go v1.3.4-0.20211116175324-9a98dd4ad269/go.mod h1:gYvpMt7Qw+OSScTLyIlCnpbvy9y96ey/2zhB4w6FoK0= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -76,8 +74,11 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cmaglie/go.rice v1.0.3 h1:ZBLmBdQp6ejc+n8eMNH0uuRSKkg6kKe6ORjXKnyHBYw= @@ -272,6 +273,8 @@ github.com/leonelquinteros/gotext v1.4.0/go.mod h1:yZGXREmoGTtBvZHNcc+Yfug49G/2s github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/marcinbor85/gohex v0.0.0-20210308104911-55fb1c624d84/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= @@ -499,8 +502,6 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 h1:VrJZAjbekhoRn7n5FBujY31gboH+iB3pdLxn3gE9FjU= -golang.org/x/net v0.0.0-20211101193420-4a448f8816b3/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -508,8 +509,6 @@ golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20211028175245-ba495a64dcb5 h1:v79phzBz03tsVCUTbvTBmmC3CUXF5mKYt7DA4ZVldpM= -golang.org/x/oauth2 v0.0.0-20211028175245-ba495a64dcb5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -528,6 +527,7 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= From ffe157286f13f89dbbb3d109aac8ee746b74348b Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Wed, 24 Nov 2021 18:40:37 +0100 Subject: [PATCH 04/10] Refactor cli config --- cli/config/init.go | 99 ++++++++++++++++++++++++++++--- command/config/init.go | 129 ----------------------------------------- 2 files changed, 91 insertions(+), 137 deletions(-) delete mode 100644 command/config/init.go diff --git a/cli/config/init.go b/cli/config/init.go index 5b302129..489100ee 100644 --- a/cli/config/init.go +++ b/cli/config/init.go @@ -18,13 +18,24 @@ package config import ( + "errors" + "fmt" "os" + "strings" "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" - "github.com/arduino/arduino-cloud-cli/command/config" + "github.com/arduino/arduino-cloud-cli/internal/config" + "github.com/arduino/go-paths-helper" + "github.com/manifoldco/promptui" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +const ( + clientIDLen = 32 + clientSecretLen = 64 ) var initFlags struct { @@ -51,17 +62,89 @@ func initInitCommand() *cobra.Command { func runInitCommand(cmd *cobra.Command, args []string) { logrus.Info("Initializing config file") - params := &config.InitParams{ - DestDir: initFlags.destDir, - Overwrite: initFlags.overwrite, - Format: initFlags.format, + // Get default destination directory if it's not passed + if initFlags.destDir == "" { + configPath, err := config.ArduinoDir() + initFlags.destDir = configPath.String() + if err != nil { + feedback.Errorf("Error during config init: cannot retrieve arduino default directory: %w", err) + os.Exit(errorcodes.ErrGeneric) + } + } + + // Validate format flag + initFlags.format = strings.ToLower(initFlags.format) + if initFlags.format != "json" && initFlags.format != "yaml" { + feedback.Error("Error during config init: passed format is not valid, select between 'json' and 'yaml'") + os.Exit(errorcodes.ErrGeneric) + } + + // Check that the destination directory is valid and build the configuration file path + configPath, err := paths.New(initFlags.destDir).Abs() + if err != nil { + feedback.Errorf("Error during config init: cannot retrieve absolute path of passed dest-dir: %w", err) + os.Exit(errorcodes.ErrGeneric) + } + if !configPath.IsDir() { + feedback.Error("Error during config init: passed dest-dir is not a valid directory") + os.Exit(errorcodes.ErrGeneric) + } + configFile := configPath.Join(config.Filename + "." + initFlags.format) + if !initFlags.overwrite && configFile.Exist() { + feedback.Error("Error during config init: config file already exists, use --overwrite to discard the existing one") + os.Exit(errorcodes.ErrGeneric) } - file, err := config.Init(params) + // Take needed configuration parameters starting an interactive mode + feedback.Print("To obtain your API credentials visit https://create.arduino.cc/iot/integrations") + id, key, err := paramsPrompt() if err != nil { - feedback.Errorf("Error during config init: %v", err) + feedback.Errorf("Error during config init: taking config parameters: %w", err) os.Exit(errorcodes.ErrGeneric) } - logrus.Info("Config file successfully initialized as: %s", file) + // Write the configuration file + newSettings := viper.New() + newSettings.SetConfigPermissions(os.FileMode(0600)) + newSettings.Set("client", id) + newSettings.Set("secret", key) + if err := newSettings.WriteConfigAs(configFile.String()); err != nil { + feedback.Errorf("Error during config init: cannot create config file: %w", err) + os.Exit(errorcodes.ErrGeneric) + } + + logrus.Info("Config file successfully initialized at: %s", configFile.String()) +} + +func paramsPrompt() (id, key string, err error) { + prompt := promptui.Prompt{ + Label: "Please enter the Client ID", + Validate: func(s string) error { + if len(s) != clientIDLen { + return errors.New("client-id not valid") + } + return nil + }, + } + id, err = prompt.Run() + if err != nil { + return "", "", fmt.Errorf("client prompt fail: %w", err) + } + + prompt = promptui.Prompt{ + Mask: '*', + Label: "Please enter the Client Secret", + Validate: func(s string) error { + if len(s) != clientSecretLen { + return errors.New("client secret not valid") + } + return nil + }, + } + key, err = prompt.Run() + if err != nil { + return "", "", fmt.Errorf("client secret prompt fail: %w", err) + } + + return id, key, nil } diff --git a/command/config/init.go b/command/config/init.go deleted file mode 100644 index 323b22b9..00000000 --- a/command/config/init.go +++ /dev/null @@ -1,129 +0,0 @@ -// This file is part of arduino-cloud-cli. -// -// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/) -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package config - -import ( - "errors" - "fmt" - "os" - "strings" - - "github.com/arduino/arduino-cloud-cli/internal/config" - "github.com/arduino/go-paths-helper" - "github.com/manifoldco/promptui" - "github.com/spf13/viper" -) - -const ( - clientLen = 32 - secretLen = 64 -) - -// InitParams contains the parameters needed to initialize a configuration file. -type InitParams struct { - DestDir string // Destination directory in which the configuration file will be saved - Overwrite bool // Overwrite specifies if existing config file should be overwritten - Format string // Config file format, can be 'json' or 'yaml' -} - -func validateFormatString(arg string) error { - if arg != "json" && arg != "yaml" { - return errors.New("passed format is not valid, select between 'json' and 'yaml'") - } - return nil -} - -// Init initializes a configuration file with values passed in by the user. -// If the file doesn't exist, it is created. -// If it already exists, it is written to only if overwrite param is true. -func Init(params *InitParams) (filepath string, err error) { - var configPath *paths.Path - if params.DestDir != "" { - configPath, err = paths.New(params.DestDir).Abs() - if err != nil { - return "", fmt.Errorf("cannot retrieve absolute path of passed dest-dir: %w", err) - } - } else { - configPath, err = config.ArduinoDir() - if err != nil { - return "", fmt.Errorf("cannot retrieve arduino default directory: %w", err) - } - } - - if !configPath.IsDir() { - return "", fmt.Errorf("chosen destination dir is not valid: %w", err) - } - - params.Format = strings.ToLower(params.Format) - if err := validateFormatString(params.Format); err != nil { - return "", err - } - - configFile := configPath.Join(config.Filename + "." + params.Format) - if !params.Overwrite && configFile.Exist() { - return "", errors.New("config file already exists, use --overwrite to discard the existing one") - } - - id, key, err := prompt() - if err != nil { - return "", err - } - - newSettings := viper.New() - newSettings.SetConfigPermissions(os.FileMode(0600)) - newSettings.Set("client", id) - newSettings.Set("secret", key) - if err := newSettings.WriteConfigAs(configFile.String()); err != nil { - return "", fmt.Errorf("cannot create config file: %v", err) - } - - return configFile.String(), nil -} - -func prompt() (id, key string, err error) { - prompt := promptui.Prompt{ - Label: "client", - Validate: func(s string) error { - if len(s) != clientLen { - return errors.New("") - } - return nil - }, - } - id, err = prompt.Run() - if err != nil { - return "", "", fmt.Errorf("client prompt fail: %w", err) - } - - prompt = promptui.Prompt{ - Mask: '*', - Label: "secret", - Validate: func(s string) error { - if len(s) != secretLen { - return errors.New("") - } - return nil - }, - } - key, err = prompt.Run() - if err != nil { - return "", "", fmt.Errorf("secret prompt fail: %w", err) - } - - return id, key, nil -} From bf6ffc5c536928327e7392ce0bde16e60e7944e0 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Wed, 24 Nov 2021 18:47:46 +0100 Subject: [PATCH 05/10] Fix error formatting --- cli/config/init.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cli/config/init.go b/cli/config/init.go index 489100ee..2af4c86b 100644 --- a/cli/config/init.go +++ b/cli/config/init.go @@ -67,7 +67,7 @@ func runInitCommand(cmd *cobra.Command, args []string) { configPath, err := config.ArduinoDir() initFlags.destDir = configPath.String() if err != nil { - feedback.Errorf("Error during config init: cannot retrieve arduino default directory: %w", err) + feedback.Errorf("Error during config init: cannot retrieve arduino default directory: %v", err) os.Exit(errorcodes.ErrGeneric) } } @@ -82,7 +82,7 @@ func runInitCommand(cmd *cobra.Command, args []string) { // Check that the destination directory is valid and build the configuration file path configPath, err := paths.New(initFlags.destDir).Abs() if err != nil { - feedback.Errorf("Error during config init: cannot retrieve absolute path of passed dest-dir: %w", err) + feedback.Errorf("Error during config init: cannot retrieve absolute path of passed dest-dir: %v", err) os.Exit(errorcodes.ErrGeneric) } if !configPath.IsDir() { @@ -99,7 +99,7 @@ func runInitCommand(cmd *cobra.Command, args []string) { feedback.Print("To obtain your API credentials visit https://create.arduino.cc/iot/integrations") id, key, err := paramsPrompt() if err != nil { - feedback.Errorf("Error during config init: taking config parameters: %w", err) + feedback.Errorf("Error during config init: taking config parameters: %v", err) os.Exit(errorcodes.ErrGeneric) } @@ -109,11 +109,11 @@ func runInitCommand(cmd *cobra.Command, args []string) { newSettings.Set("client", id) newSettings.Set("secret", key) if err := newSettings.WriteConfigAs(configFile.String()); err != nil { - feedback.Errorf("Error during config init: cannot create config file: %w", err) + feedback.Errorf("Error during config init: cannot create config file: %v", err) os.Exit(errorcodes.ErrGeneric) } - logrus.Info("Config file successfully initialized at: %s", configFile.String()) + logrus.Infof("Config file successfully initialized at: %s", configFile.String()) } func paramsPrompt() (id, key string, err error) { From da8f31257d67fffe57d10c14c1eca270529da83a Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Thu, 25 Nov 2021 10:09:32 +0100 Subject: [PATCH 06/10] Export arduino DataDir --- arduino/dir.go | 52 +++++++++++++++++++++++++++++++++++++++ cli/config/init.go | 3 ++- internal/config/config.go | 29 ---------------------- 3 files changed, 54 insertions(+), 30 deletions(-) create mode 100644 arduino/dir.go diff --git a/arduino/dir.go b/arduino/dir.go new file mode 100644 index 00000000..923c8f07 --- /dev/null +++ b/arduino/dir.go @@ -0,0 +1,52 @@ +// This file is part of arduino-cloud-cli. +// +// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package arduino + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + + "github.com/arduino/go-paths-helper" + "github.com/arduino/go-win32-utils" +) + +// DataDir returns the Arduino default data directory (arduino15) +func DataDir() (*paths.Path, error) { + userHomeDir, err := os.UserHomeDir() + if err != nil { + return nil, fmt.Errorf("unable to get user home dir: %w", err) + } + + var path *paths.Path + switch runtime.GOOS { + case "darwin": + path = paths.New(filepath.Join(userHomeDir, "Library", "Arduino15")) + case "windows": + localAppDataPath, err := win32.GetLocalAppDataFolder() + if err != nil { + return nil, fmt.Errorf("unable to get local app data folder: %w", err) + } + path = paths.New(filepath.Join(localAppDataPath, "Arduino15")) + default: // linux, android, *bsd, plan9 and other Unix-like systems + path = paths.New(filepath.Join(userHomeDir, ".arduino15")) + } + + return path, nil +} diff --git a/cli/config/init.go b/cli/config/init.go index 2af4c86b..160ee8e1 100644 --- a/cli/config/init.go +++ b/cli/config/init.go @@ -25,6 +25,7 @@ import ( "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cloud-cli/arduino" "github.com/arduino/arduino-cloud-cli/internal/config" "github.com/arduino/go-paths-helper" "github.com/manifoldco/promptui" @@ -64,7 +65,7 @@ func runInitCommand(cmd *cobra.Command, args []string) { // Get default destination directory if it's not passed if initFlags.destDir == "" { - configPath, err := config.ArduinoDir() + configPath, err := arduino.DataDir() initFlags.destDir = configPath.String() if err != nil { feedback.Errorf("Error during config init: cannot retrieve arduino default directory: %v", err) diff --git a/internal/config/config.go b/internal/config/config.go index 495c4fd0..6a9287ee 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -19,12 +19,7 @@ package config import ( "fmt" - "os" - "path/filepath" - "runtime" - "github.com/arduino/go-paths-helper" - "github.com/arduino/go-win32-utils" "github.com/spf13/viper" ) @@ -51,27 +46,3 @@ func Retrieve() (*Config, error) { v.Unmarshal(conf) return conf, nil } - -// Get Arduino default directory (arduino15) -func ArduinoDir() (*paths.Path, error) { - userHomeDir, err := os.UserHomeDir() - if err != nil { - return nil, fmt.Errorf("unable to get user home dir: %w", err) - } - - var path *paths.Path - switch runtime.GOOS { - case "darwin": - path = paths.New(filepath.Join(userHomeDir, "Library", "Arduino15")) - case "windows": - localAppDataPath, err := win32.GetLocalAppDataFolder() - if err != nil { - return nil, fmt.Errorf("unable to get local app data folder: %w", err) - } - path = paths.New(filepath.Join(localAppDataPath, "Arduino15")) - default: // linux, android, *bsd, plan9 and other Unix-like systems - path = paths.New(filepath.Join(userHomeDir, ".arduino15")) - } - - return path, nil -} From 2c3895d0d4a3af43c18f5b153cd8a436b4e6ca2a Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Thu, 25 Nov 2021 11:55:34 +0100 Subject: [PATCH 07/10] Update cli/config/init.go Co-authored-by: Giuseppe Lumia --- cli/config/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/config/init.go b/cli/config/init.go index 160ee8e1..7051b948 100644 --- a/cli/config/init.go +++ b/cli/config/init.go @@ -66,11 +66,11 @@ func runInitCommand(cmd *cobra.Command, args []string) { // Get default destination directory if it's not passed if initFlags.destDir == "" { configPath, err := arduino.DataDir() - initFlags.destDir = configPath.String() if err != nil { feedback.Errorf("Error during config init: cannot retrieve arduino default directory: %v", err) os.Exit(errorcodes.ErrGeneric) } + initFlags.destDir = configPath.String() } // Validate format flag From 842e213f5bbd649b008b0ec91e246c4233b791c3 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Thu, 25 Nov 2021 11:55:39 +0100 Subject: [PATCH 08/10] Update cli/config/init.go Co-authored-by: Giuseppe Lumia --- cli/config/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/config/init.go b/cli/config/init.go index 7051b948..508f9eb4 100644 --- a/cli/config/init.go +++ b/cli/config/init.go @@ -76,7 +76,7 @@ func runInitCommand(cmd *cobra.Command, args []string) { // Validate format flag initFlags.format = strings.ToLower(initFlags.format) if initFlags.format != "json" && initFlags.format != "yaml" { - feedback.Error("Error during config init: passed format is not valid, select between 'json' and 'yaml'") + feedback.Error("Error during config init: the provided format is not valid, it should be 'json' or 'yaml'") os.Exit(errorcodes.ErrGeneric) } From 4bcc7bad31abc0be8a0b651a78a5a6c7cb1ed668 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Thu, 25 Nov 2021 11:56:12 +0100 Subject: [PATCH 09/10] Update cli/config/init.go Co-authored-by: Giuseppe Lumia --- cli/config/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/config/init.go b/cli/config/init.go index 508f9eb4..da05b920 100644 --- a/cli/config/init.go +++ b/cli/config/init.go @@ -83,7 +83,7 @@ func runInitCommand(cmd *cobra.Command, args []string) { // Check that the destination directory is valid and build the configuration file path configPath, err := paths.New(initFlags.destDir).Abs() if err != nil { - feedback.Errorf("Error during config init: cannot retrieve absolute path of passed dest-dir: %v", err) + feedback.Errorf("Error during config init: cannot retrieve absolute path of dest-dir: %v", err) os.Exit(errorcodes.ErrGeneric) } if !configPath.IsDir() { From 6a2a541c68f376a6b68809fb58fe465b01806ea5 Mon Sep 17 00:00:00 2001 From: Paolo Calao Date: Thu, 25 Nov 2021 14:49:09 +0100 Subject: [PATCH 10/10] Improve errors and feedback --- cli/config/init.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/cli/config/init.go b/cli/config/init.go index da05b920..3002a7ee 100644 --- a/cli/config/init.go +++ b/cli/config/init.go @@ -76,23 +76,24 @@ func runInitCommand(cmd *cobra.Command, args []string) { // Validate format flag initFlags.format = strings.ToLower(initFlags.format) if initFlags.format != "json" && initFlags.format != "yaml" { - feedback.Error("Error during config init: the provided format is not valid, it should be 'json' or 'yaml'") + feedback.Error("Error during config init: format is not valid, provide 'json' or 'yaml'") os.Exit(errorcodes.ErrGeneric) } // Check that the destination directory is valid and build the configuration file path configPath, err := paths.New(initFlags.destDir).Abs() if err != nil { - feedback.Errorf("Error during config init: cannot retrieve absolute path of dest-dir: %v", err) + feedback.Errorf("Error during config init: cannot retrieve absolute path of %s: %v", initFlags.destDir, err) os.Exit(errorcodes.ErrGeneric) } if !configPath.IsDir() { - feedback.Error("Error during config init: passed dest-dir is not a valid directory") + feedback.Errorf("Error during config init: %s is not a valid directory", configPath) os.Exit(errorcodes.ErrGeneric) } configFile := configPath.Join(config.Filename + "." + initFlags.format) if !initFlags.overwrite && configFile.Exist() { - feedback.Error("Error during config init: config file already exists, use --overwrite to discard the existing one") + feedback.Errorf("Error during config init: %s already exists, use '--overwrite' to overwrite it", + configFile) os.Exit(errorcodes.ErrGeneric) } @@ -100,7 +101,7 @@ func runInitCommand(cmd *cobra.Command, args []string) { feedback.Print("To obtain your API credentials visit https://create.arduino.cc/iot/integrations") id, key, err := paramsPrompt() if err != nil { - feedback.Errorf("Error during config init: taking config parameters: %v", err) + feedback.Errorf("Error during config init: cannot take config params: %v", err) os.Exit(errorcodes.ErrGeneric) } @@ -114,7 +115,7 @@ func runInitCommand(cmd *cobra.Command, args []string) { os.Exit(errorcodes.ErrGeneric) } - logrus.Infof("Config file successfully initialized at: %s", configFile.String()) + feedback.Printf("Config file successfully initialized at: %s", configFile) } func paramsPrompt() (id, key string, err error) {