-
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathconfig.go
227 lines (205 loc) · 6.58 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// This file is part of arduino-cloud-cli.
//
// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package config
import (
"fmt"
"github.com/arduino/arduino-cloud-cli/arduino"
"github.com/arduino/go-paths-helper"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
)
const (
// ClientIDLen specifies the length of Arduino IoT Cloud client ids.
ClientIDLen = 32
// ClientSecretLen specifies the length of Arduino IoT Cloud client secrets.
ClientSecretLen = 64
// EnvPrefix is the prefix environment variables should have to be
// fetched as config parameters during the config retrieval.
EnvPrefix = "ARDUINO_CLOUD"
)
// Config contains all the configuration parameters
// known by arduino-cloud-cli.
type Config struct {
Client string `map-structure:"client"` // Client ID of the user
Secret string `map-structure:"secret"` // Secret ID of the user, unique for each Client ID
}
// Validate the config.
// If config is not valid, it returns an error explaining the reason.
func (c *Config) Validate() error {
if len(c.Client) != ClientIDLen {
return fmt.Errorf(
"client id not valid, expected len %d but got %d",
ClientIDLen,
len(c.Client),
)
}
if len(c.Secret) != ClientSecretLen {
return fmt.Errorf(
"client secret not valid, expected len %d but got %d",
ClientSecretLen,
len(c.Secret),
)
}
return nil
}
// IsEmpty checks if config has no params set.
func (c *Config) IsEmpty() bool {
return len(c.Client) == 0 && len(c.Secret) == 0
}
// Retrieve looks for configuration parameters in
// environment variables or in configuration file.
// Returns error if no config is found.
func Retrieve() (*Config, error) {
// Config extracted from environment has highest priority
logrus.Info("Looking for configuration in environment variables")
c, err := fromEnv()
if err != nil {
return nil, fmt.Errorf("reading config from environment variables: %w", err)
}
// Return the config only if it has been found
if c != nil {
logrus.Info("Configuration found in environment variables")
return c, nil
}
logrus.Info("Looking for configuration in file system")
c, err = fromFile()
if err != nil {
return nil, fmt.Errorf("reading config from file: %w", err)
}
if c != nil {
return c, nil
}
return nil, fmt.Errorf(
"config has not been found neither in environment variables " +
"nor in the current directory, its parents or in arduino15",
)
}
// fromFile looks for a configuration file.
// If a config file is not found, it returns a nil config without raising errors.
// If invalid config file is found, it returns an error.
func fromFile() (*Config, error) {
// Looks for a configuration file
configDir, err := searchConfigDir()
if err != nil {
return nil, fmt.Errorf("can't get config directory: %w", err)
}
// Return nil config if no config file is found
if configDir == nil {
return nil, nil
}
v := viper.New()
v.SetConfigName(Filename)
v.AddConfigPath(*configDir)
err = v.ReadInConfig()
if err != nil {
err = fmt.Errorf(
"config file found at %s but cannot read its content: %w",
*configDir,
err,
)
return nil, err
}
conf := &Config{}
err = v.Unmarshal(conf)
if err != nil {
return nil, fmt.Errorf(
"config file found at %s but cannot unmarshal it: %w",
*configDir,
err,
)
}
if err = conf.Validate(); err != nil {
return nil, fmt.Errorf(
"config file found at %s but is not valid: %w",
*configDir,
err,
)
}
return conf, nil
}
// fromEnv looks for configuration credentials in environment variables.
// If credentials are not found, it returns a nil config without raising errors.
// If invalid credentials are found, it returns an error.
func fromEnv() (*Config, error) {
v := viper.New()
SetDefaults(v)
v.SetEnvPrefix(EnvPrefix)
v.AutomaticEnv()
conf := &Config{}
err := v.Unmarshal(conf)
if err != nil {
return nil, fmt.Errorf("cannot unmarshal config from environment variables: %w", err)
}
if conf.IsEmpty() {
return nil, nil
}
if err = conf.Validate(); err != nil {
return nil, fmt.Errorf(
"config retrieved from environment variables with prefix '%s' are not valid: %w",
EnvPrefix,
err,
)
}
return conf, nil
}
// searchConfigDir configuration file in different directories in the following order:
// current working directory, parents of the current working directory, arduino15 default directory.
// Returns a nil string if no config file has been found, without raising errors.
// Returns an error if any problem is encountered during the file research which prevents
// to understand whether a config file exists or not.
func searchConfigDir() (*string, error) {
// Search in current directory and its parents.
cwd, err := paths.Getwd()
if err != nil {
return nil, err
}
// Don't let bad naming mislead you, cwd.Parents()[0] is cwd itself so
// we look in the current directory first and then on its parents.
for _, path := range cwd.Parents() {
logrus.Infof("Looking for configuration in %s", path)
if file, found := configFileInDir(path); found {
logrus.Infof("Configuration found at %s", file)
p := path.String()
return &p, nil
}
}
// Search in arduino's default data directory.
arduino15, err := arduino.DataDir()
if err != nil {
return nil, err
}
logrus.Infof("Looking for configuration in %s", arduino15)
if file, found := configFileInDir(arduino15); found {
logrus.Infof("Configuration found at %s", file)
p := arduino15.String()
return &p, nil
}
// Didn't find config file in the current directory, its parents or in arduino15"
return nil, nil
}
// configFileInDir looks for a configuration file in the passed directory.
// If a configuration file is found, then it is returned.
// In case of multiple config files, it returns the one with the highest priority
// according to viper.
func configFileInDir(dir *paths.Path) (filepath *paths.Path, found bool) {
for _, ext := range viper.SupportedExts {
if filepath = dir.Join(Filename + "." + ext); filepath.Exist() {
return filepath, true
}
}
return nil, false
}