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 1ab8b58b..3002a7ee 100644
--- a/cli/config/init.go
+++ b/cli/config/init.go
@@ -18,13 +18,25 @@
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/arduino"
+ "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 {
@@ -36,32 +48,105 @@ 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,
}
- 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")
+
+ // Get default destination directory if it's not passed
+ if initFlags.destDir == "" {
+ configPath, err := arduino.DataDir()
+ 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
+ initFlags.format = strings.ToLower(initFlags.format)
+ if initFlags.format != "json" && initFlags.format != "yaml" {
+ feedback.Error("Error during config init: format is not valid, provide 'json' or 'yaml'")
+ os.Exit(errorcodes.ErrGeneric)
+ }
- params := &config.InitParams{
- DestDir: initFlags.destDir,
- Overwrite: initFlags.overwrite,
- Format: initFlags.format,
+ // 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 %s: %v", initFlags.destDir, err)
+ os.Exit(errorcodes.ErrGeneric)
+ }
+ if !configPath.IsDir() {
+ 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.Errorf("Error during config init: %s already exists, use '--overwrite' to overwrite it",
+ configFile)
+ os.Exit(errorcodes.ErrGeneric)
}
- 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: cannot take config params: %v", err)
os.Exit(errorcodes.ErrGeneric)
}
- logrus.Info("Config file successfully initialized")
+ // 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: %v", err)
+ os.Exit(errorcodes.ErrGeneric)
+ }
+
+ feedback.Printf("Config file successfully initialized at: %s", configFile)
+}
+
+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 4a350eac..00000000
--- a/command/config/init.go
+++ /dev/null
@@ -1,76 +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/spf13/viper"
-)
-
-// 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 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 !configPath.IsDir() {
- return fmt.Errorf("%s: %w", "passed dest-dir is not a valid directory", 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")
- }
-
- 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 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=