Skip to content

Commit 9171cda

Browse files
authored
Introduce organization optional parameter in credentials (#105)
Credentials can now also include an optional organization ID that specifies the organization to use.
1 parent c0be3f5 commit 9171cda

25 files changed

+142
-57
lines changed

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Another example: let's say that the execution of the previous command results in
3131
## Set credentials
3232

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

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

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

56-
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.
57+
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.
58+
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)
5759

5860
#### Find credentials
5961

cli/credentials/init.go

+21-5
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func runInitCommand(flags *initFlags) error {
100100

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

121-
func paramsPrompt() (id, key string, err error) {
122+
func paramsPrompt() (id, key, org string, err error) {
122123
prompt := promptui.Prompt{
123124
Label: "Please enter the Client ID",
124125
Validate: func(s string) error {
@@ -130,7 +131,7 @@ func paramsPrompt() (id, key string, err error) {
130131
}
131132
id, err = prompt.Run()
132133
if err != nil {
133-
return "", "", fmt.Errorf("client prompt fail: %w", err)
134+
return "", "", "", fmt.Errorf("client prompt fail: %w", err)
134135
}
135136

136137
prompt = promptui.Prompt{
@@ -145,8 +146,23 @@ func paramsPrompt() (id, key string, err error) {
145146
}
146147
key, err = prompt.Run()
147148
if err != nil {
148-
return "", "", fmt.Errorf("client secret prompt fail: %w", err)
149+
return "", "", "", fmt.Errorf("client secret prompt fail: %w", err)
149150
}
150151

151-
return id, key, nil
152+
prompt = promptui.Prompt{
153+
Mask: '*',
154+
Label: "Please enter the Organization ID - if any - Leave empty otherwise",
155+
Validate: func(s string) error {
156+
if len(s) != 0 && len(s) != config.OrganizationLen {
157+
return errors.New("organization id not valid")
158+
}
159+
return nil
160+
},
161+
}
162+
org, err = prompt.Run()
163+
if err != nil {
164+
return "", "", "", fmt.Errorf("organization id prompt fail: %w", err)
165+
}
166+
167+
return id, key, org, nil
152168
}

command/dashboard/create.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type CreateParams struct {
3434

3535
// Create allows to create a new dashboard.
3636
func Create(params *CreateParams, cred *config.Credentials) (*DashboardInfo, error) {
37-
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
37+
iotClient, err := iot.NewClient(cred)
3838
if err != nil {
3939
return nil, err
4040
}

command/dashboard/delete.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type DeleteParams struct {
3131
// Delete command is used to delete a dashboard
3232
// from Arduino IoT Cloud.
3333
func Delete(params *DeleteParams, cred *config.Credentials) error {
34-
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
34+
iotClient, err := iot.NewClient(cred)
3535
if err != nil {
3636
return err
3737
}

command/dashboard/extract.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type ExtractParams struct {
3434
// Extract command is used to extract a dashboard template
3535
// from a dashboard on Arduino IoT Cloud.
3636
func Extract(params *ExtractParams, cred *config.Credentials) (map[string]interface{}, error) {
37-
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
37+
iotClient, err := iot.NewClient(cred)
3838
if err != nil {
3939
return nil, err
4040
}

command/dashboard/list.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525
// List command is used to list
2626
// the dashboards of Arduino IoT Cloud.
2727
func List(cred *config.Credentials) ([]DashboardInfo, error) {
28-
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
28+
iotClient, err := iot.NewClient(cred)
2929
if err != nil {
3030
return nil, err
3131
}

command/device/create.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func Create(params *CreateParams, cred *config.Credentials) (*DeviceInfo, error)
6363
)
6464
}
6565

66-
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
66+
iotClient, err := iot.NewClient(cred)
6767
if err != nil {
6868
return nil, err
6969
}

command/device/creategeneric.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ type DeviceGenericInfo struct {
4444

4545
// CreateGeneric command is used to add a new generic device to Arduino IoT Cloud.
4646
func CreateGeneric(params *CreateGenericParams, cred *config.Credentials) (*DeviceGenericInfo, error) {
47-
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
47+
iotClient, err := iot.NewClient(cred)
4848
if err != nil {
4949
return nil, err
5050
}

command/device/createlora.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func CreateLora(params *CreateLoraParams, cred *config.Credentials) (*DeviceLora
107107
return nil, err
108108
}
109109

110-
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
110+
iotClient, err := iot.NewClient(cred)
111111
if err != nil {
112112
return nil, err
113113
}

command/device/delete.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func Delete(params *DeleteParams, cred *config.Credentials) error {
4343
return errors.New("cannot use both ID and Tags. only one of them should be not nil")
4444
}
4545

46-
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
46+
iotClient, err := iot.NewClient(cred)
4747
if err != nil {
4848
return err
4949
}

command/device/list.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type ListParams struct {
3333
// List command is used to list
3434
// the devices of Arduino IoT Cloud.
3535
func List(params *ListParams, cred *config.Credentials) ([]DeviceInfo, error) {
36-
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
36+
iotClient, err := iot.NewClient(cred)
3737
if err != nil {
3838
return nil, err
3939
}

command/device/listfrequency.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type FrequencyPlanInfo struct {
3434
// ListFrequencyPlans command is used to list
3535
// the supported LoRa frequency plans.
3636
func ListFrequencyPlans(cred *config.Credentials) ([]FrequencyPlanInfo, error) {
37-
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
37+
iotClient, err := iot.NewClient(cred)
3838
if err != nil {
3939
return nil, err
4040
}

command/ota/massupload.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func MassUpload(params *MassUploadParams, cred *config.Credentials) ([]Result, e
7171
return nil, fmt.Errorf("%s: %w", "cannot generate .ota file", err)
7272
}
7373

74-
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
74+
iotClient, err := iot.NewClient(cred)
7575
if err != nil {
7676
return nil, err
7777
}

command/ota/upload.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ type UploadParams struct {
4545
// Upload command is used to upload a firmware OTA,
4646
// on a device of Arduino IoT Cloud.
4747
func Upload(params *UploadParams, cred *config.Credentials) error {
48-
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
48+
iotClient, err := iot.NewClient(cred)
4949
if err != nil {
5050
return err
5151
}

command/tag/create.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type CreateTagsParams struct {
3535
// CreateTags allows to create or overwrite tags
3636
// on a resource of Arduino IoT Cloud.
3737
func CreateTags(params *CreateTagsParams, cred *config.Credentials) error {
38-
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
38+
iotClient, err := iot.NewClient(cred)
3939
if err != nil {
4040
return err
4141
}

command/tag/delete.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type DeleteTagsParams struct {
3535
// DeleteTags command is used to delete tags of a device
3636
// from Arduino IoT Cloud.
3737
func DeleteTags(params *DeleteTagsParams, cred *config.Credentials) error {
38-
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
38+
iotClient, err := iot.NewClient(cred)
3939
if err != nil {
4040
return err
4141
}

command/thing/bind.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type BindParams struct {
3333
// Bind command is used to bind a thing to a device
3434
// on Arduino IoT Cloud.
3535
func Bind(params *BindParams, cred *config.Credentials) error {
36-
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
36+
iotClient, err := iot.NewClient(cred)
3737
if err != nil {
3838
return err
3939
}

command/thing/clone.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type CloneParams struct {
3333

3434
// Clone allows to create a new thing from an already existing one.
3535
func Clone(params *CloneParams, cred *config.Credentials) (*ThingInfo, error) {
36-
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
36+
iotClient, err := iot.NewClient(cred)
3737
if err != nil {
3838
return nil, err
3939
}

command/thing/create.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type CreateParams struct {
3434

3535
// Create allows to create a new thing.
3636
func Create(params *CreateParams, cred *config.Credentials) (*ThingInfo, error) {
37-
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
37+
iotClient, err := iot.NewClient(cred)
3838
if err != nil {
3939
return nil, err
4040
}

command/thing/delete.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func Delete(params *DeleteParams, cred *config.Credentials) error {
4343
return errors.New("cannot use both ID and Tags. only one of them should be not nil")
4444
}
4545

46-
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
46+
iotClient, err := iot.NewClient(cred)
4747
if err != nil {
4848
return err
4949
}

command/thing/extract.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type ExtractParams struct {
3434
// Extract command is used to extract a thing template
3535
// from a thing on Arduino IoT Cloud.
3636
func Extract(params *ExtractParams, cred *config.Credentials) (map[string]interface{}, error) {
37-
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
37+
iotClient, err := iot.NewClient(cred)
3838
if err != nil {
3939
return nil, err
4040
}

command/thing/list.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ type ListParams struct {
3636
// List command is used to list
3737
// the things of Arduino IoT Cloud.
3838
func List(params *ListParams, cred *config.Credentials) ([]ThingInfo, error) {
39-
iotClient, err := iot.NewClient(cred.Client, cred.Secret)
39+
iotClient, err := iot.NewClient(cred)
4040
if err != nil {
4141
return nil, err
4242
}

internal/config/credentials.go

+19-6
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ const (
3131
ClientIDLen = 32
3232
// ClientSecretLen specifies the length of Arduino IoT Cloud client secrets.
3333
ClientSecretLen = 64
34+
// OrganizationLen specifies the length of Arduino IoT Cloud organization.
35+
OrganizationLen = 36
3436

3537
// EnvPrefix is the prefix environment variables should have to be
3638
// fetched as credentials parameters during the credentials retrieval.
@@ -46,12 +48,15 @@ func SetEmptyCredentials(settings *viper.Viper) {
4648
settings.SetDefault("client", "")
4749
// Secret
4850
settings.SetDefault("secret", "")
51+
// OrganizationID
52+
settings.SetDefault("organization", "")
4953
}
5054

5155
// Credentials contains the parameters of Arduino IoT Cloud credentials.
5256
type Credentials struct {
53-
Client string `mapstructure:"client"` // Client ID of the user
54-
Secret string `mapstructure:"secret"` // Secret ID of the user, unique for each Client ID
57+
Client string `mapstructure:"client"` // Client ID of the user; mandatory.
58+
Secret string `mapstructure:"secret"` // Secret ID of the user, unique for each Client ID; mandatory.
59+
Organization string `mapstructure:"organization"` // Organization ID of the user; this is considered optional.
5560
}
5661

5762
// Validate the credentials.
@@ -71,11 +76,19 @@ func (c *Credentials) Validate() error {
7176
len(c.Secret),
7277
)
7378
}
79+
if len(c.Organization) != 0 && len(c.Organization) != OrganizationLen {
80+
return fmt.Errorf(
81+
"organization not valid, expected len %d but got %d",
82+
OrganizationLen,
83+
len(c.Organization),
84+
)
85+
}
7486
return nil
7587
}
7688

77-
// IsEmpty checks if credentials has no params set.
78-
func (c *Credentials) IsEmpty() bool {
89+
// Complete checks if Credentials has all the mandatory params set.
90+
// Optional parameters are not considered here.
91+
func (c *Credentials) Complete() bool {
7992
return len(c.Client) == 0 && len(c.Secret) == 0
8093
}
8194

@@ -91,7 +104,7 @@ func FindCredentials() (source string, err error) {
91104
if err != nil {
92105
return "", fmt.Errorf("looking for credentials in environment variables: %w", err)
93106
}
94-
if !c.IsEmpty() {
107+
if c.Complete() {
95108
logrus.Infof("Credentials found in environment variables with prefix '%s'", EnvPrefix)
96109
return "environment variables", nil
97110
}
@@ -123,7 +136,7 @@ func RetrieveCredentials() (cred *Credentials, err error) {
123136
return nil, fmt.Errorf("reading credentials from environment variables: %w", err)
124137
}
125138
// Returns credentials if found in env
126-
if !cred.IsEmpty() {
139+
if !cred.Complete() {
127140
// Returns error if credentials are found but are not valid
128141
if err := cred.Validate(); err != nil {
129142
return nil, fmt.Errorf(

0 commit comments

Comments
 (0)