diff --git a/README.md b/README.md index 37492a71..e0ccebcf 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,12 @@ Credentials file is supported in two different format: json and yaml. Use the `- It is also possible to specify credentials directly in `ARDUINO_CLOUD_CLIENT` and `ARDUINO_CLOUD_SECRET` environment variables. Credentials specified in environment variables have higher priority than the ones specified in credentials files. +#### Find credentials + +To have information about which credentials would be used in the current folder you can use the following command: + +`$ arduino-cloud-cli credentials find` + ## Device provisioning When provisioning a device, you can optionally specify the port to which the device is connected to and its fqbn. If they are not given, then the first device found will be provisioned. diff --git a/cli/credentials/credentials.go b/cli/credentials/credentials.go index 43e9baad..cd286d91 100644 --- a/cli/credentials/credentials.go +++ b/cli/credentials/credentials.go @@ -29,6 +29,7 @@ func NewCommand() *cobra.Command { } credentialsCommand.AddCommand(initInitCommand()) + credentialsCommand.AddCommand(initFindCommand()) return credentialsCommand } diff --git a/cli/credentials/find.go b/cli/credentials/find.go new file mode 100644 index 00000000..a060577c --- /dev/null +++ b/cli/credentials/find.go @@ -0,0 +1,51 @@ +// 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 credentials + +import ( + "os" + + "github.com/arduino/arduino-cli/cli/errorcodes" + "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cloud-cli/internal/config" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +func initFindCommand() *cobra.Command { + findCommand := &cobra.Command{ + Use: "find", + Short: "Find the credentials that would be used in your current directory", + Long: "Find the credentials to access Arduino IoT Cloud that would be used in your current directory", + Run: runFindCommand, + } + + return findCommand +} + +func runFindCommand(cmd *cobra.Command, args []string) { + logrus.Info("Looking for credentials") + + src, err := config.FindCredentials() + if err != nil { + feedback.Errorf("Error during credentials find: %v", err) + os.Exit(errorcodes.ErrGeneric) + } + + feedback.Printf("Using credentials in: %s", src) +} diff --git a/internal/config/config.go b/internal/config/config.go index e8c7c552..7a99e1a9 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -24,16 +24,16 @@ import ( "github.com/spf13/viper" ) -// searchConfigDir looks for a configuration file in different directories in the following order: +// searchConfigFile looks for a configuration file in different directories in the following order: // current working directory, parents of the current working directory, arduino15 default directory. -// Returns a nil string if no config file has been found, without raising errors. +// Returns empty string and false if no config file has been found, without raising errors. // Returns an error if any problem is encountered during the file research which prevents // to understand whether a config file exists or not. -func searchConfigDir(confname string) (*string, error) { +func searchConfigFile(confname string) (dir string, found bool, err error) { // Search in current directory and its parents. cwd, err := paths.Getwd() if err != nil { - return nil, err + return "", false, err } // Don't let bad naming mislead you, cwd.Parents()[0] is cwd itself so // we look in the current directory first and then on its parents. @@ -41,25 +41,23 @@ func searchConfigDir(confname string) (*string, error) { logrus.Infof("Looking for %s in %s", confname, path) if file, found := configFileInDir(confname, path); found { logrus.Infof("Found %s at %s", confname, file) - p := path.String() - return &p, nil + return file.String(), true, nil } } // Search in arduino's default data directory. arduino15, err := arduino.DataDir() if err != nil { - return nil, err + return "", false, err } logrus.Infof("Looking for %s in %s", confname, arduino15) if file, found := configFileInDir(confname, arduino15); found { logrus.Infof("%s found at %s", confname, file) - p := arduino15.String() - return &p, nil + return file.String(), true, nil } // Didn't find config file in the current directory, its parents or in arduino15" - return nil, nil + return "", false, nil } // configFileInDir looks for a configuration file in the passed directory. diff --git a/internal/config/credentials.go b/internal/config/credentials.go index 7d6a287c..7f2e571a 100644 --- a/internal/config/credentials.go +++ b/internal/config/credentials.go @@ -19,7 +19,9 @@ package config import ( "fmt" + "strings" + "github.com/arduino/go-paths-helper" "github.com/sirupsen/logrus" "github.com/spf13/viper" ) @@ -38,8 +40,8 @@ const ( CredentialsFilename = "arduino-cloud-credentials" ) -// SetDefaultCredentials sets the default credentials values. -func SetDefaultCredentials(settings *viper.Viper) { +// SetEmptyCredentials sets the default credentials values to empty strings. +func SetEmptyCredentials(settings *viper.Viper) { // Client ID settings.SetDefault("client", "") // Secret @@ -77,89 +79,110 @@ func (c *Credentials) IsEmpty() bool { return len(c.Client) == 0 && len(c.Secret) == 0 } -// RetrieveCredentials looks for credentials in +// FindCredentials looks for credentials in // environment variables or in credentials file. -// Returns error if no credentials are found. -func RetrieveCredentials() (*Credentials, error) { +// Returns the source of found credentials (env or filepath). +// Returns an error if credentials are not found +// specifying paths where the credentials are searched. +func FindCredentials() (source string, err error) { // Credentials extracted from environment has highest priority logrus.Info("Looking for credentials in environment variables") c, err := fromEnv() if err != nil { - return nil, fmt.Errorf("reading credentials from environment variables: %w", err) + return "", fmt.Errorf("looking for credentials in environment variables: %w", err) } - // Return credentials only if found - if c != nil { - logrus.Info("Credentials found in environment variables") - return c, nil + if !c.IsEmpty() { + logrus.Infof("Credentials found in environment variables with prefix '%s'", EnvPrefix) + return "environment variables", nil } logrus.Info("Looking for credentials in file system") - c, err = fromFile() + path, found, err := searchConfigFile(CredentialsFilename) if err != nil { - return nil, fmt.Errorf("reading credentials from file: %w", err) + return "", fmt.Errorf("looking for credentials files: %w", err) } - if c != nil { - return c, nil + if found { + return path, nil } - return nil, fmt.Errorf( + return "", fmt.Errorf( "credentials have not been found neither in environment variables " + "nor in the current directory, its parents or in arduino15", ) } -// fromFile looks for a credentials file. -// If a credentials file is not found, it returns nil credentials without raising errors. -// If invalid credentials file is found, it returns an error. -func fromFile() (*Credentials, error) { - // Looks for a credentials file - configDir, err := searchConfigDir(CredentialsFilename) +// RetrieveCredentials retrieves credentials from +// environment variables or credentials file. +// Returns error if credentials are not found or +// if found credentials are invalid. +func RetrieveCredentials() (cred *Credentials, err error) { + // Credentials extracted from environment has highest priority + logrus.Info("Looking for credentials in environment variables") + cred, err = fromEnv() + if err != nil { + return nil, fmt.Errorf("reading credentials from environment variables: %w", err) + } + // Returns credentials if found in env + if !cred.IsEmpty() { + // Returns error if credentials are found but are not valid + if err := cred.Validate(); err != nil { + return nil, fmt.Errorf( + "credentials retrieved from environment variables with prefix '%s' are not valid: %w", EnvPrefix, err, + ) + } + logrus.Infof("Credentials found in environment variables with prefix '%s'", EnvPrefix) + return cred, nil + } + + logrus.Info("Looking for credentials in file system") + filepath, found, err := searchConfigFile(CredentialsFilename) if err != nil { return nil, fmt.Errorf("can't get credentials directory: %w", err) } - // Return nil credentials if no config file is found - if configDir == nil { - return nil, nil + // Returns credentials if found in a file + if found { + if cred, err = fromFile(filepath); err != nil { + return nil, fmt.Errorf("reading credentials from file %s: %w", filepath, err) + } + // Returns error if credentials are found but are not valid + if err := cred.Validate(); err != nil { + return nil, fmt.Errorf( + "credentials retrieved from file %s are not valid: %w", filepath, err, + ) + } + return cred, nil } + return nil, fmt.Errorf( + "credentials have not been found neither in environment variables " + + "nor in the current directory, its parents or in arduino15", + ) +} + +// fromFile retrieves credentials from a credentials file. +// Returns error if credentials are not found or cannot be fetched. +func fromFile(filepath string) (*Credentials, error) { v := viper.New() - v.SetConfigName(CredentialsFilename) - v.AddConfigPath(*configDir) - err = v.ReadInConfig() + v.SetConfigFile(filepath) + v.SetConfigType(strings.TrimLeft(paths.New(filepath).Ext(), ".")) + err := v.ReadInConfig() if err != nil { - err = fmt.Errorf( - "credentials file found at %s but cannot read its content: %w", - *configDir, - err, - ) - return nil, err + return nil, fmt.Errorf("cannot read credentials file: %w", err) } cred := &Credentials{} err = v.Unmarshal(cred) if err != nil { - return nil, fmt.Errorf( - "credentials file found at %s but cannot unmarshal it: %w", - *configDir, - err, - ) - } - if err = cred.Validate(); err != nil { - return nil, fmt.Errorf( - "credentials file found at %s but is not valid: %w", - *configDir, - err, - ) + return nil, fmt.Errorf("cannot unmarshal credentials file: %w", err) } return cred, nil } -// fromEnv looks for credentials in environment variables. -// If credentials are not found, it returns nil credentials without raising errors. -// If invalid credentials are found, it returns an error. +// fromEnv retrieves credentials from environment variables. +// Returns empty credentials if not found. func fromEnv() (*Credentials, error) { v := viper.New() - SetDefaultCredentials(v) + SetEmptyCredentials(v) v.SetEnvPrefix(EnvPrefix) v.AutomaticEnv() @@ -168,17 +191,5 @@ func fromEnv() (*Credentials, error) { if err != nil { return nil, fmt.Errorf("cannot unmarshal credentials from environment variables: %w", err) } - - if cred.IsEmpty() { - return nil, nil - } - - if err = cred.Validate(); err != nil { - return nil, fmt.Errorf( - "credentials retrieved from environment variables with prefix '%s' are not valid: %w", - EnvPrefix, - err, - ) - } return cred, nil }