Skip to content

Introduce organization optional parameter in credentials #105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Another example: let's say that the execution of the previous command results in
## Set credentials

arduino-cloud-cli needs a credentials file containing an Arduino IoT Cloud client ID and its corresponding secret.
Credentials can also include an optional organization ID that specifies the organization to use.
You can retrieve these credentials from the [cloud](https://create.arduino.cc/iot/integrations) by creating a new API key.

Once you have the credentials, execute the following command and provide them:
Expand All @@ -53,7 +54,8 @@ The credentials file is supported in two different formats: json and yaml. Use t

`$ arduino-cloud-cli credentials init --file-format json`

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.
It is also possible to specify credentials directly in `ARDUINO_CLOUD_CLIENT`, `ARDUINO_CLOUD_SECRET` and optionally `ARDUINO_CLOUD_ORGANIZATION` environment variables. Credentials specified in environment variables have higher priority than the ones specified in credentials files.
Please note that credentials are correctly extracted from environment variables only if all the mandatory credentials parameters (client and secret) are found in environment variables. (think of it as another config file but with higher priority)

#### Find credentials

Expand Down
26 changes: 21 additions & 5 deletions cli/credentials/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func runInitCommand(flags *initFlags) error {

// Take needed credentials starting an interactive mode
feedback.Print("To obtain your API credentials visit https://create.arduino.cc/iot/integrations")
id, key, err := paramsPrompt()
id, key, org, err := paramsPrompt()
if err != nil {
return fmt.Errorf("cannot take credentials params: %w", err)
}
Expand All @@ -110,6 +110,7 @@ func runInitCommand(flags *initFlags) error {
newSettings.SetConfigPermissions(os.FileMode(0600))
newSettings.Set("client", id)
newSettings.Set("secret", key)
newSettings.Set("organization", org)
if err := newSettings.WriteConfigAs(credFile.String()); err != nil {
return fmt.Errorf("cannot write credentials file: %w", err)
}
Expand All @@ -118,7 +119,7 @@ func runInitCommand(flags *initFlags) error {
return nil
}

func paramsPrompt() (id, key string, err error) {
func paramsPrompt() (id, key, org string, err error) {
prompt := promptui.Prompt{
Label: "Please enter the Client ID",
Validate: func(s string) error {
Expand All @@ -130,7 +131,7 @@ func paramsPrompt() (id, key string, err error) {
}
id, err = prompt.Run()
if err != nil {
return "", "", fmt.Errorf("client prompt fail: %w", err)
return "", "", "", fmt.Errorf("client prompt fail: %w", err)
}

prompt = promptui.Prompt{
Expand All @@ -145,8 +146,23 @@ func paramsPrompt() (id, key string, err error) {
}
key, err = prompt.Run()
if err != nil {
return "", "", fmt.Errorf("client secret prompt fail: %w", err)
return "", "", "", fmt.Errorf("client secret prompt fail: %w", err)
}

return id, key, nil
prompt = promptui.Prompt{
Mask: '*',
Label: "Please enter the Organization ID - if any - Leave empty otherwise",
Validate: func(s string) error {
if len(s) != 0 && len(s) != config.OrganizationLen {
return errors.New("organization id not valid")
}
return nil
},
}
org, err = prompt.Run()
if err != nil {
return "", "", "", fmt.Errorf("organization id prompt fail: %w", err)
}

return id, key, org, nil
}
2 changes: 1 addition & 1 deletion command/dashboard/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type CreateParams struct {

// Create allows to create a new dashboard.
func Create(params *CreateParams, cred *config.Credentials) (*DashboardInfo, error) {
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
iotClient, err := iot.NewClient(cred)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion command/dashboard/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type DeleteParams struct {
// Delete command is used to delete a dashboard
// from Arduino IoT Cloud.
func Delete(params *DeleteParams, cred *config.Credentials) error {
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
iotClient, err := iot.NewClient(cred)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion command/dashboard/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type ExtractParams struct {
// Extract command is used to extract a dashboard template
// from a dashboard on Arduino IoT Cloud.
func Extract(params *ExtractParams, cred *config.Credentials) (map[string]interface{}, error) {
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
iotClient, err := iot.NewClient(cred)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion command/dashboard/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import (
// List command is used to list
// the dashboards of Arduino IoT Cloud.
func List(cred *config.Credentials) ([]DashboardInfo, error) {
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
iotClient, err := iot.NewClient(cred)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion command/device/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func Create(params *CreateParams, cred *config.Credentials) (*DeviceInfo, error)
)
}

iotClient, err := iot.NewClient(cred.Client, cred.Secret)
iotClient, err := iot.NewClient(cred)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion command/device/creategeneric.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ type DeviceGenericInfo struct {

// CreateGeneric command is used to add a new generic device to Arduino IoT Cloud.
func CreateGeneric(params *CreateGenericParams, cred *config.Credentials) (*DeviceGenericInfo, error) {
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
iotClient, err := iot.NewClient(cred)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion command/device/createlora.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func CreateLora(params *CreateLoraParams, cred *config.Credentials) (*DeviceLora
return nil, err
}

iotClient, err := iot.NewClient(cred.Client, cred.Secret)
iotClient, err := iot.NewClient(cred)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion command/device/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func Delete(params *DeleteParams, cred *config.Credentials) error {
return errors.New("cannot use both ID and Tags. only one of them should be not nil")
}

iotClient, err := iot.NewClient(cred.Client, cred.Secret)
iotClient, err := iot.NewClient(cred)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion command/device/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type ListParams struct {
// List command is used to list
// the devices of Arduino IoT Cloud.
func List(params *ListParams, cred *config.Credentials) ([]DeviceInfo, error) {
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
iotClient, err := iot.NewClient(cred)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion command/device/listfrequency.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type FrequencyPlanInfo struct {
// ListFrequencyPlans command is used to list
// the supported LoRa frequency plans.
func ListFrequencyPlans(cred *config.Credentials) ([]FrequencyPlanInfo, error) {
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
iotClient, err := iot.NewClient(cred)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion command/ota/massupload.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func MassUpload(params *MassUploadParams, cred *config.Credentials) ([]Result, e
return nil, fmt.Errorf("%s: %w", "cannot generate .ota file", err)
}

iotClient, err := iot.NewClient(cred.Client, cred.Secret)
iotClient, err := iot.NewClient(cred)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion command/ota/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ type UploadParams struct {
// Upload command is used to upload a firmware OTA,
// on a device of Arduino IoT Cloud.
func Upload(params *UploadParams, cred *config.Credentials) error {
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
iotClient, err := iot.NewClient(cred)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion command/tag/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type CreateTagsParams struct {
// CreateTags allows to create or overwrite tags
// on a resource of Arduino IoT Cloud.
func CreateTags(params *CreateTagsParams, cred *config.Credentials) error {
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
iotClient, err := iot.NewClient(cred)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion command/tag/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type DeleteTagsParams struct {
// DeleteTags command is used to delete tags of a device
// from Arduino IoT Cloud.
func DeleteTags(params *DeleteTagsParams, cred *config.Credentials) error {
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
iotClient, err := iot.NewClient(cred)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion command/thing/bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type BindParams struct {
// Bind command is used to bind a thing to a device
// on Arduino IoT Cloud.
func Bind(params *BindParams, cred *config.Credentials) error {
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
iotClient, err := iot.NewClient(cred)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion command/thing/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type CloneParams struct {

// Clone allows to create a new thing from an already existing one.
func Clone(params *CloneParams, cred *config.Credentials) (*ThingInfo, error) {
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
iotClient, err := iot.NewClient(cred)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion command/thing/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type CreateParams struct {

// Create allows to create a new thing.
func Create(params *CreateParams, cred *config.Credentials) (*ThingInfo, error) {
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
iotClient, err := iot.NewClient(cred)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion command/thing/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func Delete(params *DeleteParams, cred *config.Credentials) error {
return errors.New("cannot use both ID and Tags. only one of them should be not nil")
}

iotClient, err := iot.NewClient(cred.Client, cred.Secret)
iotClient, err := iot.NewClient(cred)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion command/thing/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type ExtractParams struct {
// Extract command is used to extract a thing template
// from a thing on Arduino IoT Cloud.
func Extract(params *ExtractParams, cred *config.Credentials) (map[string]interface{}, error) {
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
iotClient, err := iot.NewClient(cred)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion command/thing/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type ListParams struct {
// List command is used to list
// the things of Arduino IoT Cloud.
func List(params *ListParams, cred *config.Credentials) ([]ThingInfo, error) {
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
iotClient, err := iot.NewClient(cred)
if err != nil {
return nil, err
}
Expand Down
25 changes: 19 additions & 6 deletions internal/config/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ const (
ClientIDLen = 32
// ClientSecretLen specifies the length of Arduino IoT Cloud client secrets.
ClientSecretLen = 64
// OrganizationLen specifies the length of Arduino IoT Cloud organization.
OrganizationLen = 36

// EnvPrefix is the prefix environment variables should have to be
// fetched as credentials parameters during the credentials retrieval.
Expand All @@ -46,12 +48,15 @@ func SetEmptyCredentials(settings *viper.Viper) {
settings.SetDefault("client", "")
// Secret
settings.SetDefault("secret", "")
// OrganizationID
settings.SetDefault("organization", "")
}

// Credentials contains the parameters of Arduino IoT Cloud credentials.
type Credentials struct {
Client string `mapstructure:"client"` // Client ID of the user
Secret string `mapstructure:"secret"` // Secret ID of the user, unique for each Client ID
Client string `mapstructure:"client"` // Client ID of the user; mandatory.
Secret string `mapstructure:"secret"` // Secret ID of the user, unique for each Client ID; mandatory.
Organization string `mapstructure:"organization"` // Organization ID of the user; this is considered optional.
}

// Validate the credentials.
Expand All @@ -71,11 +76,19 @@ func (c *Credentials) Validate() error {
len(c.Secret),
)
}
if len(c.Organization) != 0 && len(c.Organization) != OrganizationLen {
return fmt.Errorf(
"organization not valid, expected len %d but got %d",
OrganizationLen,
len(c.Organization),
)
}
return nil
}

// IsEmpty checks if credentials has no params set.
func (c *Credentials) IsEmpty() bool {
// Complete checks if Credentials has all the mandatory params set.
// Optional parameters are not considered here.
func (c *Credentials) Complete() bool {
return len(c.Client) == 0 && len(c.Secret) == 0
}

Expand All @@ -91,7 +104,7 @@ func FindCredentials() (source string, err error) {
if err != nil {
return "", fmt.Errorf("looking for credentials in environment variables: %w", err)
}
if !c.IsEmpty() {
if c.Complete() {
logrus.Infof("Credentials found in environment variables with prefix '%s'", EnvPrefix)
return "environment variables", nil
}
Expand Down Expand Up @@ -123,7 +136,7 @@ func RetrieveCredentials() (cred *Credentials, err error) {
return nil, fmt.Errorf("reading credentials from environment variables: %w", err)
}
// Returns credentials if found in env
if !cred.IsEmpty() {
if !cred.Complete() {
// Returns error if credentials are found but are not valid
if err := cred.Validate(); err != nil {
return nil, fmt.Errorf(
Expand Down
Loading