Skip to content

Commit 6d9a4fa

Browse files
committed
Read config from env
1 parent 851f1bf commit 6d9a4fa

File tree

3 files changed

+142
-23
lines changed

3 files changed

+142
-23
lines changed

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

internal/config/config.go

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

28+
const (
29+
ClientIDLen = 32
30+
ClientSecretLen = 64
31+
32+
EnvPrefix = "ARDUINO_CLOUD"
33+
)
34+
2835
// Config contains all the configuration parameters
2936
// known by arduino-cloud-cli.
3037
type Config struct {
3138
Client string `map-structure:"client"` // Client ID of the user
3239
Secret string `map-structure:"secret"` // Secret ID of the user, unique for each Client ID
3340
}
3441

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

43115
v := viper.New()
44116
v.SetConfigName(Filename)
45-
v.AddConfigPath(configDir)
117+
v.AddConfigPath(*configDir)
46118
err = v.ReadInConfig()
47119
if err != nil {
48-
err = fmt.Errorf("%s: %w", "retrieving config file", err)
120+
err = fmt.Errorf(
121+
"config file found at %s but cannot read its content: %w",
122+
*configDir,
123+
err,
124+
)
49125
return nil, err
50126
}
51127

52128
conf := &Config{}
53-
v.Unmarshal(conf)
129+
err = v.Unmarshal(conf)
130+
if err != nil {
131+
return nil, fmt.Errorf(
132+
"config file found at %s but cannot unmarshal it: %w",
133+
*configDir,
134+
err,
135+
)
136+
}
137+
if err = conf.Validate(); err != nil {
138+
return nil, fmt.Errorf(
139+
"config file found at %s but is not valid: %w",
140+
*configDir,
141+
err,
142+
)
143+
}
144+
return conf, nil
145+
}
146+
147+
// fromEnv looks for configuration credentials in environment variables.
148+
// If credentials are not found, it returns a nil config without raising errors.
149+
// If invalid credentials are found, it returns an error.
150+
func fromEnv() (*Config, error) {
151+
v := viper.New()
152+
SetDefaults(v)
153+
v.SetEnvPrefix("ARDUINO_CLOUD")
154+
v.AutomaticEnv()
155+
156+
conf := &Config{}
157+
err := v.Unmarshal(conf)
158+
if err != nil {
159+
return nil, fmt.Errorf("cannot unmarshal config from environment variables: %w", err)
160+
}
161+
162+
if conf.IsEmpty() {
163+
return nil, nil
164+
}
165+
166+
if err = conf.Validate(); err != nil {
167+
return nil, fmt.Errorf(
168+
"config retrieved from environment variables with prefix '%s' are not valid: %w",
169+
EnvPrefix,
170+
err,
171+
)
172+
}
54173
return conf, nil
55174
}
56175

57-
func searchConfigDir() (string, error) {
176+
// searchConfigDir configuration file in different directories in the following order:
177+
// current working directory, parents of the current working directory, arduino15 default directory
178+
// Returns a nil string if no config file has been found, without raising errors
179+
// Returns an error if any problem is encountered during the file research which prevents
180+
// to understand whether a config file exists or not
181+
func searchConfigDir() (*string, error) {
58182
// Search in current directory and its parents.
59183
cwd, err := paths.Getwd()
60184
if err != nil {
61-
return "", err
185+
return nil, err
62186
}
63187
// Don't let bad naming mislead you, cwd.Parents()[0] is cwd itself so
64188
// we look in the current directory first and then on its parents.
65189
for _, path := range cwd.Parents() {
66190
if path.Join(Filename+".yaml").Exist() || path.Join(Filename+".json").Exist() {
67-
return path.String(), nil
191+
p := path.String()
192+
return &p, nil
68193
}
69194
}
70195

71196
// Search in arduino's default data directory.
72197
arduino15, err := arduino.DataDir()
73198
if err != nil {
74-
return "", err
199+
return nil, err
75200
}
76201
if arduino15.Join(Filename+".yaml").Exist() || arduino15.Join(Filename+".json").Exist() {
77-
return arduino15.String(), nil
202+
p := arduino15.String()
203+
return &p, nil
78204
}
79205

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

internal/config/default.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ var (
2626
// SetDefaults sets the default values for configuration keys.
2727
func SetDefaults(settings *viper.Viper) {
2828
// Client ID
29-
settings.SetDefault("client", "xxxxxxxxxxxxxx")
29+
settings.SetDefault("client", "")
3030
// Secret
31-
settings.SetDefault("secret", "xxxxxxxxxxxxxx")
31+
settings.SetDefault("secret", "")
3232
}

0 commit comments

Comments
 (0)