Skip to content

Commit df6ea6e

Browse files
Paolo Calaopolldo
Paolo Calao
authored andcommitted
Fetch config from env (#76)
Allows the cli to fetch configuration credentials from environment variables. Config credentials can now be specified directly in `ARDUINO_CLOUD_CLIENT` and `ARDUINO_CLOUD_SECRET` environment variables. The config is now searched in this way: - if credentials are found on environment variables, return them - otherwise look for them in config files - if not found anywhere return an error
1 parent ad5bff1 commit df6ea6e

File tree

6 files changed

+396
-24
lines changed

6 files changed

+396
-24
lines changed

Diff for: README.md

+2
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ Configuration file is supported in two different format: json and yaml. Use the
5353

5454
`$ arduino-cloud-cli config init --config-format json`
5555

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 config files.
57+
5658
## Device provisioning
5759

5860
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 for: cli/config/init.go

+2-7
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,6 @@ import (
3434
"github.com/spf13/viper"
3535
)
3636

37-
const (
38-
clientIDLen = 32
39-
clientSecretLen = 64
40-
)
41-
4237
var initFlags struct {
4338
destDir string
4439
overwrite bool
@@ -122,7 +117,7 @@ func paramsPrompt() (id, key string, err error) {
122117
prompt := promptui.Prompt{
123118
Label: "Please enter the Client ID",
124119
Validate: func(s string) error {
125-
if len(s) != clientIDLen {
120+
if len(s) != config.ClientIDLen {
126121
return errors.New("client-id not valid")
127122
}
128123
return nil
@@ -137,7 +132,7 @@ func paramsPrompt() (id, key string, err error) {
137132
Mask: '*',
138133
Label: "Please enter the Client Secret",
139134
Validate: func(s string) error {
140-
if len(s) != clientSecretLen {
135+
if len(s) != config.ClientSecretLen {
141136
return errors.New("client secret not valid")
142137
}
143138
return nil

Diff for: internal/config/config.go

+136-14
Original file line numberDiff line numberDiff line change
@@ -25,60 +25,182 @@ import (
2525
"github.com/spf13/viper"
2626
)
2727

28+
const (
29+
// ClientIDLen specifies the length of Arduino IoT Cloud client ids.
30+
ClientIDLen = 32
31+
// ClientSecretLen specifies the length of Arduino IoT Cloud client secrets.
32+
ClientSecretLen = 64
33+
34+
// EnvPrefix is the prefix environment variables should have to be
35+
// fetched as config parameters during the config retrieval.
36+
EnvPrefix = "ARDUINO_CLOUD"
37+
)
38+
2839
// Config contains all the configuration parameters
2940
// known by arduino-cloud-cli.
3041
type Config struct {
3142
Client string `map-structure:"client"` // Client ID of the user
3243
Secret string `map-structure:"secret"` // Secret ID of the user, unique for each Client ID
3344
}
3445

35-
// Retrieve returns the actual parameters contained in the
36-
// configuration file, if any. Returns error if no config file is found.
46+
// Validate the config.
47+
// If config is not valid, it returns an error explaining the reason.
48+
func (c *Config) Validate() error {
49+
if len(c.Client) != ClientIDLen {
50+
return fmt.Errorf(
51+
"client id not valid, expected len %d but got %d",
52+
ClientIDLen,
53+
len(c.Client),
54+
)
55+
}
56+
if len(c.Secret) != ClientSecretLen {
57+
return fmt.Errorf(
58+
"client secret not valid, expected len %d but got %d",
59+
ClientSecretLen,
60+
len(c.Secret),
61+
)
62+
}
63+
return nil
64+
}
65+
66+
// IsEmpty checks if config has no params set.
67+
func (c *Config) IsEmpty() bool {
68+
return len(c.Client) == 0 && len(c.Secret) == 0
69+
}
70+
71+
// Retrieve looks for configuration parameters in
72+
// environment variables or in configuration file.
73+
// Returns error if no config is found.
3774
func Retrieve() (*Config, error) {
75+
// Config extracted from environment has highest priority
76+
c, err := fromEnv()
77+
if err != nil {
78+
return nil, fmt.Errorf("reading config from environment variables: %w", err)
79+
}
80+
// Return the config only if it has been found
81+
if c != nil {
82+
return c, nil
83+
}
84+
85+
c, err = fromFile()
86+
if err != nil {
87+
return nil, fmt.Errorf("reading config from file: %w", err)
88+
}
89+
if c != nil {
90+
return c, nil
91+
}
92+
93+
return nil, fmt.Errorf(
94+
"config has not been found neither in environment variables " +
95+
"nor in the current directory, its parents or in arduino15",
96+
)
97+
}
98+
99+
// fromFile looks for a configuration file.
100+
// If a config file is not found, it returns a nil config without raising errors.
101+
// If invalid config file is found, it returns an error.
102+
func fromFile() (*Config, error) {
103+
// Looks for a configuration file
38104
configDir, err := searchConfigDir()
39105
if err != nil {
40106
return nil, fmt.Errorf("can't get config directory: %w", err)
41107
}
108+
// Return nil config if no config file is found
109+
if configDir == nil {
110+
return nil, nil
111+
}
42112

43113
v := viper.New()
44114
v.SetConfigName(Filename)
45-
v.AddConfigPath(configDir)
115+
v.AddConfigPath(*configDir)
46116
err = v.ReadInConfig()
47117
if err != nil {
48-
err = fmt.Errorf("%s: %w", "retrieving config file", err)
118+
err = fmt.Errorf(
119+
"config file found at %s but cannot read its content: %w",
120+
*configDir,
121+
err,
122+
)
49123
return nil, err
50124
}
51125

52126
conf := &Config{}
53-
v.Unmarshal(conf)
127+
err = v.Unmarshal(conf)
128+
if err != nil {
129+
return nil, fmt.Errorf(
130+
"config file found at %s but cannot unmarshal it: %w",
131+
*configDir,
132+
err,
133+
)
134+
}
135+
if err = conf.Validate(); err != nil {
136+
return nil, fmt.Errorf(
137+
"config file found at %s but is not valid: %w",
138+
*configDir,
139+
err,
140+
)
141+
}
54142
return conf, nil
55143
}
56144

57-
func searchConfigDir() (string, error) {
145+
// fromEnv looks for configuration credentials in environment variables.
146+
// If credentials are not found, it returns a nil config without raising errors.
147+
// If invalid credentials are found, it returns an error.
148+
func fromEnv() (*Config, error) {
149+
v := viper.New()
150+
SetDefaults(v)
151+
v.SetEnvPrefix(EnvPrefix)
152+
v.AutomaticEnv()
153+
154+
conf := &Config{}
155+
err := v.Unmarshal(conf)
156+
if err != nil {
157+
return nil, fmt.Errorf("cannot unmarshal config from environment variables: %w", err)
158+
}
159+
160+
if conf.IsEmpty() {
161+
return nil, nil
162+
}
163+
164+
if err = conf.Validate(); err != nil {
165+
return nil, fmt.Errorf(
166+
"config retrieved from environment variables with prefix '%s' are not valid: %w",
167+
EnvPrefix,
168+
err,
169+
)
170+
}
171+
return conf, nil
172+
}
173+
174+
// searchConfigDir configuration file in different directories in the following order:
175+
// current working directory, parents of the current working directory, arduino15 default directory.
176+
// Returns a nil string if no config file has been found, without raising errors.
177+
// Returns an error if any problem is encountered during the file research which prevents
178+
// to understand whether a config file exists or not.
179+
func searchConfigDir() (*string, error) {
58180
// Search in current directory and its parents.
59181
cwd, err := paths.Getwd()
60182
if err != nil {
61-
return "", err
183+
return nil, err
62184
}
63185
// Don't let bad naming mislead you, cwd.Parents()[0] is cwd itself so
64186
// we look in the current directory first and then on its parents.
65187
for _, path := range cwd.Parents() {
66188
if path.Join(Filename+".yaml").Exist() || path.Join(Filename+".json").Exist() {
67-
return path.String(), nil
189+
p := path.String()
190+
return &p, nil
68191
}
69192
}
70193

71194
// Search in arduino's default data directory.
72195
arduino15, err := arduino.DataDir()
73196
if err != nil {
74-
return "", err
197+
return nil, err
75198
}
76199
if arduino15.Join(Filename+".yaml").Exist() || arduino15.Join(Filename+".json").Exist() {
77-
return arduino15.String(), nil
200+
p := arduino15.String()
201+
return &p, nil
78202
}
79203

80-
return "", fmt.Errorf(
81-
"didn't find config file in the current directory, its parents or in %s",
82-
arduino15.String(),
83-
)
204+
// Didn't find config file in the current directory, its parents or in arduino15"
205+
return nil, nil
84206
}

0 commit comments

Comments
 (0)