-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathconfig.go
349 lines (314 loc) · 8.95 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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
package stc
import (
"errors"
"fmt"
"github.com/xdrpp/stc/ini"
"github.com/xdrpp/stc/stcdetail"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
)
const configFileName = "stc.conf"
// When a user does not have an stc.conf configuration file, the
// library searches for one in $STCDIR/stc.conf, then /etc/stc.conf,
// then ../share/stc.conf (relative to the executable path). If none
// of those paths exists, then it uses the built-in contents specified
// by this variable.
var DefaultGlobalConfigContents = []byte(
`# Default Stellar network configurations for stc.
[net "main"]
network-id = "Public Global Stellar Network ; September 2015"
horizon = https://horizon.stellar.org/
native-asset = XLM
[net "test"]
horizon = https://horizon-testnet.stellar.org/
native-asset = TestXLM
[net "future"]
horizon = https://horizon-futurenet.stellar.org/
native-asset = TestXLM
[net "standalone"]
network-id = "Standalone Network ; February 2017"
horizon = http://localhost:8000/
native-asset = StandaloneXLM
`)
var globalConfigContents []byte
func getGlobalConfigContents() []byte {
if globalConfigContents != nil {
return globalConfigContents
}
confs := []string{
path.Join(getConfigDir(false), configFileName),
filepath.FromSlash("/etc/" + configFileName),
}
if exe, err := os.Executable(); err == nil {
confs = append(confs,
path.Join(path.Dir(path.Dir(exe)), "share", configFileName))
}
for _, conf := range confs {
if contents, err := ioutil.ReadFile(conf); err == nil {
globalConfigContents = contents
break
}
}
if globalConfigContents == nil {
globalConfigContents = DefaultGlobalConfigContents
}
return globalConfigContents
}
var stcDir string
func getConfigDir(create bool) string {
if stcDir != "" {
return stcDir
} else if d, ok := os.LookupEnv("STCDIR"); ok {
stcDir = d
} else if d, err := os.UserConfigDir(); err == nil {
stcDir = filepath.Join(d, "stc")
} else {
stcDir = ".stc"
}
if len(stcDir) > 0 && stcDir[0] != '/' {
if d, err := filepath.Abs(stcDir); err == nil {
stcDir = d
}
}
if _, err := os.Stat(stcDir); os.IsNotExist(err) && create &&
os.MkdirAll(stcDir, 0777) == nil {
if _, err = LoadStellarNet("main",
path.Join(stcDir, "main.net")); err == nil {
os.Symlink("main.net", path.Join(stcDir, "default.net"))
}
}
return stcDir
}
// Return the path to a file under the user's configuration directory.
// The configuration directory is found based on environment
// variables. From highest to lowest precedence tries $STCDIR,
// UserConfigDir() (i.e., on Unix $XDG_CONFIG_HOME/.stc or
// $HOME/.config/stc), or ./.stc, using the first one with for which
// the environment variable exists. If the configuration directory
// doesn't exist, it gets created, but the underlying path requested
// will not be created.
func ConfigPath(components ...string) string {
return path.Join(append([]string{getConfigDir(true)}, components...)...)
}
// Parse a series of INI configuration files specified by paths,
// followed by the global or built-in stc.conf file.
func ParseConfigFiles(sink ini.IniSink, paths ...string) error {
for _, path := range paths {
contents, _, err := stcdetail.ReadFile(path)
if err == nil {
err = ini.IniParseContents(sink, path, contents)
}
if err != nil && !os.IsNotExist(err) {
return err
}
}
// Finish with global configuration
err := ini.IniParseContents(sink, "", getGlobalConfigContents())
if err != nil {
return err
}
return nil
}
func ValidNetName(name string) bool {
return len(name) > 0 && name[0] != '.' &&
ini.ValidIniSubsection(name) &&
strings.IndexByte(name, '/') == -1
}
type stellarNetParser struct {
*StellarNet
// How to handle items in the current section
itemCB func(ini.IniItem) error
// This is intended to be initialized to true, and then gets set
// to false whenever Name gets set on StellarNet. The reason is
// that initially Name may be set from a source other than the
// configuration file, such as a command-line argument or the
// STCNET environment variable. If the configuration file does
// not set Name, then setName will never be set to false, which
// tells us we need to save it to the configuration file.
// (setName means set it in the configuration file.)
setName bool
}
func (snp *stellarNetParser) Item(ii ini.IniItem) error {
if snp.itemCB != nil {
return snp.itemCB(ii)
}
return nil
}
func (snp *stellarNetParser) doNet(ii ini.IniItem) error {
var target *string
switch ii.Key {
case "name":
if (snp.Name == "" || snp.setName) &&
ii.Val() != "" && ii.Subsection == nil {
if !ValidNetName(ii.Val()) {
return ErrInvalidNetName
}
snp.Name = ii.Val()
snp.setName = false
}
case "horizon":
target = &snp.Horizon
case "native-asset":
target = &snp.NativeAsset
case "network-id":
target = &snp.NetworkId
}
if target != nil {
if ii.Value == nil {
*target = ""
} else if *target == "" {
*target = ii.Val()
}
}
return nil
}
func (snp *stellarNetParser) doAccounts(ii ini.IniItem) error {
var acct MuxedAccount
if _, err := fmt.Sscan(ii.Key, &acct); err != nil {
return ini.BadKey(err.Error())
}
if ii.Value == nil {
delete(snp.Accounts, ii.Key)
} else if _, ok := snp.Accounts[ii.Key]; !ok {
snp.Accounts[ii.Key] = *ii.Value
}
return nil
}
func (snp *stellarNetParser) doSigners(ii ini.IniItem) error {
var signer SignerKey
if _, err := fmt.Sscan(ii.Key, &signer); err != nil {
return ini.BadKey(err.Error())
}
if ii.Value == nil {
snp.Signers.Del(ii.Key)
} else {
snp.Signers.Add(ii.Key, *ii.Value)
}
return nil
}
func (snp *stellarNetParser) Section(iss ini.IniSecStart) error {
snp.itemCB = nil
if iss.Subsection == nil ||
(*iss.Subsection == snp.Name && ValidNetName(snp.Name)) {
switch iss.Section {
case "net":
snp.itemCB = snp.doNet
case "accounts":
snp.itemCB = snp.doAccounts
case "signers":
snp.itemCB = snp.doSigners
}
}
return nil
}
func (snp *stellarNetParser) Done(ini.IniRange) {
if snp.setName {
snp.Edits.Set("net", "name", snp.Name)
snp.setName = false
}
}
var ErrNoNetworkId = errors.New("Cannot obtain Stellar network-id")
var ErrInvalidNetName = errors.New("Invalid or missing Stellar network name")
func (net *StellarNet) Validate() error {
if !ValidNetName(net.Name) {
return ErrInvalidNetName
}
if net.GetNetworkId() == "" {
return ErrNoNetworkId
}
return nil
}
func (net *StellarNet) IniSink() ini.IniSink {
if net.Signers == nil {
net.Signers = make(SignerCache)
}
if net.Accounts == nil {
net.Accounts = make(AccountHints)
}
return &stellarNetParser{
StellarNet: net,
setName: true,
}
}
// Load a Stellar network from an INI files. If path[0] does not
// exist but name is valid, the path will be created and net.name will
// be set to name. Otherwise the name argument is ignored. After all
// files in paths are parsed, the global stc.conf file will be parsed.
// After that, there must be a valid NetworkId or the function will
// return nil.
func LoadStellarNet(name string, paths ...string) (*StellarNet, error) {
ret := StellarNet{Name: name}
if len(paths) > 0 {
ret.SavePath = paths[0]
}
if err := ParseConfigFiles(ret.IniSink(), paths...); err != nil {
return nil, err
} else if err = ret.Validate(); err != nil {
return nil, err
}
ret.Save()
return &ret, nil
}
var netCache map[string]*StellarNet
// Load a network from under the ConfigPath() ($STCDIR) directory. If
// name is "", then it will look at the $STCNET environment variable
// and if that is unset load a default network. Returns nil if the
// network name does not exist. After loading the netname.net file,
// also parses $STCDIR/global.conf.
//
// Two pre-defined names are "main" and "test", with "main" being the
// default. Other networks can be created under ConfigPath(), or can
// be pre-specified (and created on demand) in stc.conf.
func DefaultStellarNet(name string) *StellarNet {
if !ValidNetName(name) {
name = os.Getenv("STCNET")
if !ValidNetName(name) {
name = "default"
}
}
if netCache == nil {
netCache = make(map[string]*StellarNet)
} else if net, ok := netCache[name]; ok {
return net
}
ret, err := LoadStellarNet(name, ConfigPath(name+".net"),
ConfigPath("global.conf"))
if ret == nil {
fmt.Fprintln(os.Stderr, err)
} else {
netCache[name] = ret
}
return ret
}
// Save any changes to SavePath. If SavePath does not exist, then
// create it with permissions Perm (subject to umask, of course).
func (net *StellarNet) SavePerm(perm os.FileMode) error {
if len(net.Edits) == 0 {
return nil
}
if net.SavePath == "" {
return os.ErrInvalid
}
var lf stcdetail.LockedFile
var err error
lf, err = stcdetail.LockFile(net.SavePath, perm)
if err != nil {
return err
}
defer lf.Abort()
contents, err := lf.ReadFile()
if err != nil && !os.IsNotExist(err) {
return err
}
ie, _ := ini.NewIniEdit(net.SavePath, contents)
net.Edits.Apply(ie)
ie.WriteTo(lf)
return lf.Commit()
}
// Save any changes to to SavePath. Equivalent to SavePerm(0666).
func (net *StellarNet) Save() error {
return net.SavePerm(0666)
}