Skip to content

Commit fb17d37

Browse files
committed
Add organization optional param to credentials
1 parent c0be3f5 commit fb17d37

File tree

3 files changed

+109
-31
lines changed

3 files changed

+109
-31
lines changed

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
}

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(

internal/config/credentials_test.go

+69-20
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,15 @@ import (
2828

2929
func TestRetrieveCredentials(t *testing.T) {
3030
var (
31-
validSecret = "qaRZGEbnQNNvmaeTLqy8Bxs22wLZ6H7obIiNSveTLPdoQuylANnuy6WBOw16XoqH"
32-
validClient = "CQ4iZ5sebOfhGRwUn3IV0r1YFMNrMTIx"
33-
validConfig = &Credentials{validClient, validSecret}
34-
invalidConfig = &Credentials{"", validSecret}
35-
clientEnv = EnvPrefix + "_CLIENT"
36-
secretEnv = EnvPrefix + "_SECRET"
31+
validSecret = "qaRZGEbnQNNvmaeTLqy8Bxs22wLZ6H7obIiNSveTLPdoQuylANnuy6WBOw16XoqH"
32+
validClient = "CQ4iZ5sebOfhGRwUn3IV0r1YFMNrMTIx"
33+
validOrganization = "dc6a6159-3cd5-41a2-b391-553b1351cd98"
34+
validConfig = &Credentials{Client: validClient, Secret: validSecret}
35+
validWithOptionalConfig = &Credentials{Client: validClient, Secret: validSecret, Organization: validOrganization}
36+
invalidConfig = &Credentials{Client: "", Secret: validSecret}
37+
clientEnv = EnvPrefix + "_CLIENT"
38+
secretEnv = EnvPrefix + "_SECRET"
39+
organizationEnv = EnvPrefix + "_ORGANIZATION"
3740
)
3841

3942
tests := []struct {
@@ -44,7 +47,7 @@ func TestRetrieveCredentials(t *testing.T) {
4447
wantedErr bool
4548
}{
4649
{
47-
name: "valid credentials written in env",
50+
name: "valid credentials with only mandatory params written in env",
4851
pre: func() {
4952
os.Setenv(clientEnv, validConfig.Client)
5053
os.Setenv(secretEnv, validConfig.Secret)
@@ -57,6 +60,22 @@ func TestRetrieveCredentials(t *testing.T) {
5760
wantedErr: false,
5861
},
5962

63+
{
64+
name: "valid credentials with optional params written in env",
65+
pre: func() {
66+
os.Setenv(clientEnv, validWithOptionalConfig.Client)
67+
os.Setenv(secretEnv, validWithOptionalConfig.Secret)
68+
os.Setenv(organizationEnv, validWithOptionalConfig.Organization)
69+
},
70+
post: func() {
71+
os.Unsetenv(clientEnv)
72+
os.Unsetenv(secretEnv)
73+
os.Unsetenv(organizationEnv)
74+
},
75+
wantedConfig: validWithOptionalConfig,
76+
wantedErr: false,
77+
},
78+
6079
{
6180
name: "invalid credentials written in env",
6281
pre: func() {
@@ -92,6 +111,27 @@ func TestRetrieveCredentials(t *testing.T) {
92111
wantedErr: false,
93112
},
94113

114+
{
115+
name: "valid credentials with optional params written in parent of cwd",
116+
pre: func() {
117+
parent := "test-parent"
118+
cwd := "test-parent/test-cwd"
119+
os.MkdirAll(cwd, os.FileMode(0777))
120+
// Write valid credentials in parent dir
121+
os.Chdir(parent)
122+
b, _ := json.Marshal(validWithOptionalConfig)
123+
os.WriteFile(CredentialsFilename+".json", b, os.FileMode(0777))
124+
// Cwd has no credentials file
125+
os.Chdir("test-cwd")
126+
},
127+
post: func() {
128+
os.Chdir("../..")
129+
os.RemoveAll("test-parent")
130+
},
131+
wantedConfig: validWithOptionalConfig,
132+
wantedErr: false,
133+
},
134+
95135
{
96136
name: "invalid credentials written in cwd, ignore credentials of parent dir",
97137
pre: func() {
@@ -161,21 +201,24 @@ func TestRetrieveCredentials(t *testing.T) {
161201

162202
func TestValidate(t *testing.T) {
163203
var (
164-
validSecret = "qaRZGEbnQNNvmaeTLqy8Bxs22wLZ6H7obIiNSveTLPdoQuylANnuy6WBOw16XoqH"
165-
validClient = "CQ4iZ5sebOfhGRwUn3IV0r1YFMNrMTIx"
204+
validSecret = "qaRZGEbnQNNvmaeTLqy8Bxs22wLZ6H7obIiNSveTLPdoQuylANnuy6WBOw16XoqH"
205+
validClient = "CQ4iZ5sebOfhGRwUn3IV0r1YFMNrMTIx"
206+
validOrganization = "dc6a6159-3cd5-41a2-b391-553b1351cd98"
166207
)
167208
tests := []struct {
168209
name string
169210
config *Credentials
170211
valid bool
171212
}{
172213
{
173-
name: "valid credentials",
174-
config: &Credentials{
175-
Client: validClient,
176-
Secret: validSecret,
177-
},
178-
valid: true,
214+
name: "valid credentials",
215+
config: &Credentials{Client: validClient, Secret: validSecret, Organization: validOrganization},
216+
valid: true,
217+
},
218+
{
219+
name: "valid credentials, organization is optional",
220+
config: &Credentials{Client: validClient, Secret: validSecret, Organization: ""},
221+
valid: true,
179222
},
180223
{
181224
name: "invalid client id",
@@ -209,10 +252,11 @@ func TestValidate(t *testing.T) {
209252
}
210253
}
211254

212-
func TestIsEmpty(t *testing.T) {
255+
func TestComplete(t *testing.T) {
213256
var (
214-
validSecret = "qaRZGEbnQNNvmaeTLqy8Bxs22wLZ6H7obIiNSveTLPdoQuylANnuy6WBOw16XoqH"
215-
validClient = "CQ4iZ5sebOfhGRwUn3IV0r1YFMNrMTIx"
257+
validSecret = "qaRZGEbnQNNvmaeTLqy8Bxs22wLZ6H7obIiNSveTLPdoQuylANnuy6WBOw16XoqH"
258+
validClient = "CQ4iZ5sebOfhGRwUn3IV0r1YFMNrMTIx"
259+
validOrganization = "dc6a6159-3cd5-41a2-b391-553b1351cd98"
216260
)
217261
tests := []struct {
218262
name string
@@ -221,7 +265,12 @@ func TestIsEmpty(t *testing.T) {
221265
}{
222266
{
223267
name: "empty credentials",
224-
config: &Credentials{Client: "", Secret: ""},
268+
config: &Credentials{Client: "", Secret: "", Organization: ""},
269+
want: true,
270+
},
271+
{
272+
name: "empty mandatory credentials - optionals given",
273+
config: &Credentials{Client: "", Secret: "", Organization: validOrganization},
225274
want: true,
226275
},
227276
{
@@ -238,7 +287,7 @@ func TestIsEmpty(t *testing.T) {
238287

239288
for _, tt := range tests {
240289
t.Run(tt.name, func(t *testing.T) {
241-
got := tt.config.IsEmpty()
290+
got := tt.config.Complete()
242291
if got != tt.want {
243292
t.Errorf("Expected %v but got %v, with credentials: %v", tt.want, got, tt.config)
244293
}

0 commit comments

Comments
 (0)