@@ -29,6 +29,10 @@ import (
29
29
"strings"
30
30
"time"
31
31
32
+ "github.com/aws/aws-sdk-go/aws"
33
+ "github.com/aws/aws-sdk-go/aws/session"
34
+ "github.com/aws/aws-sdk-go/service/s3"
35
+ "github.com/aws/aws-sdk-go/service/s3/s3manager"
32
36
"github.com/fsnotify/fsnotify"
33
37
"github.com/go-ini/ini"
34
38
"github.com/joho/godotenv"
@@ -87,8 +91,12 @@ type Config struct {
87
91
// watcher tracks changes of files.
88
92
watcher * fsnotify.Watcher
89
93
90
- // envWatcher tracks the waching of environment variables.
91
- envWatcher * time.Timer
94
+ // timerWatchers tracks the waching of non-inotify instances.
95
+ timerWatchers map [string ]* time.Timer
96
+
97
+ // watchInterval is used to choose the time interval to watch changes when
98
+ // using Read method.
99
+ watchInterval time.Duration
92
100
93
101
// lock avoids race condition.
94
102
lock * xylock.RWLock
@@ -124,9 +132,11 @@ func GetConfig(name string) *Config {
124
132
}
125
133
126
134
var cfg = & Config {
127
- config : make (map [string ]Value ),
128
- hook : make (map [string ]func (Event )),
129
- lock : & xylock.RWLock {},
135
+ config : make (map [string ]Value ),
136
+ hook : make (map [string ]func (Event )),
137
+ timerWatchers : make (map [string ]* time.Timer ),
138
+ watchInterval : 5 * time .Minute ,
139
+ lock : & xylock.RWLock {},
130
140
}
131
141
132
142
if name == "" {
@@ -151,22 +161,41 @@ func (c *Config) CloseWatcher() error {
151
161
c .watcher = nil
152
162
}
153
163
154
- if c . envWatcher != nil {
155
- c . envWatcher .Stop ()
156
- c . envWatcher = nil
164
+ for k , w := range c . timerWatchers {
165
+ w .Stop ()
166
+ delete ( c . timerWatchers , k )
157
167
}
158
168
159
169
return err
160
170
}
161
171
162
- // UnWatch removes a filename from the watcher.
172
+ // SetWatchInterval sets the time interval to watch the change when using Read()
173
+ // method.
174
+ func (c * Config ) SetWatchInterval (d time.Duration ) {
175
+ c .lock .Lock ()
176
+ defer c .lock .Unlock ()
177
+ c .watchInterval = d
178
+ }
179
+
180
+ // UnWatch removes a filename from the watcher. This method also works with s3
181
+ // url. Put "env" as parameter if you want to stop watching environment
182
+ // variables of LoadEnv().
163
183
func (c * Config ) UnWatch (filename string ) error {
164
184
c .lock .Lock ()
165
185
defer c .lock .Unlock ()
166
186
187
+ if w , ok := c .timerWatchers [filename ]; ok {
188
+ w .Stop ()
189
+ delete (c .timerWatchers , filename )
190
+ return nil
191
+ }
192
+
167
193
if c .watcher != nil {
168
- return c .watcher .Remove (filename )
194
+ if err := c .watcher .Remove (filename ); err != nil {
195
+ return ConfigError .New (err )
196
+ }
169
197
}
198
+
170
199
return nil
171
200
}
172
201
@@ -329,11 +358,6 @@ func (c *Config) ReadBytes(format Format, b []byte) error {
329
358
}
330
359
}
331
360
332
- // Read reads the config values from a string under any format.
333
- func (c * Config ) Read (format Format , s string ) error {
334
- return c .ReadBytes (format , []byte (s ))
335
- }
336
-
337
361
// ReadFile reads the config values from a file. If watch is true, it will
338
362
// reload config when the file is changed.
339
363
func (c * Config ) ReadFile (filename string , watch bool ) error {
@@ -344,16 +368,16 @@ func (c *Config) ReadFile(filename string, watch bool) error {
344
368
}
345
369
}
346
370
371
+ if fileFormat == UnknownFormat {
372
+ return FormatError .Newf ("unknown extension: %s" , filename )
373
+ }
374
+
347
375
if watch {
348
376
if err := c .watchFile (filename ); err != nil {
349
377
return err
350
378
}
351
379
}
352
380
353
- if fileFormat == UnknownFormat {
354
- return ExtensionError .Newf ("unknown extension: %s" , filename )
355
- }
356
-
357
381
if data , err := ioutil .ReadFile (filename ); err != nil {
358
382
if ! os .IsNotExist (err ) || ! watch {
359
383
return ConfigError .New (err )
@@ -365,6 +389,66 @@ func (c *Config) ReadFile(filename string, watch bool) error {
365
389
return nil
366
390
}
367
391
392
+ // ReadS3 reads a file from AWS S3 bucket and watch for their changes every
393
+ // duration. Set the duration as zero if no need to watch the change.
394
+ //
395
+ // You must provide the aws credentials in ~/.aws/credentials. The AWS_REGION
396
+ // is required.
397
+ func (c * Config ) ReadS3 (url string , d time.Duration ) error {
398
+ var fileFormat = UnknownFormat
399
+ for ext , format := range extensions {
400
+ if strings .HasSuffix (url , ext ) {
401
+ fileFormat = format
402
+ }
403
+ }
404
+
405
+ if fileFormat == UnknownFormat {
406
+ return FormatError .Newf ("unknown extension: %s" , url )
407
+ }
408
+
409
+ if ! strings .HasPrefix (url , "s3://" ) {
410
+ return FormatError .Newf ("can not parse the s3 url %s" , url )
411
+ }
412
+
413
+ var path = url [5 :]
414
+ var bucket , item , found = strings .Cut (path , "/" )
415
+ if ! found {
416
+ return FormatError .Newf ("not found item in path %s" , path )
417
+ }
418
+
419
+ var sess , err = session .NewSessionWithOptions (session.Options {
420
+ SharedConfigState : session .SharedConfigEnable ,
421
+ })
422
+
423
+ if err != nil {
424
+ return ConfigError .New (err )
425
+ }
426
+
427
+ var downloader = s3manager .NewDownloader (sess )
428
+ var buf = aws .NewWriteAtBuffer ([]byte {})
429
+ _ , err = downloader .Download (
430
+ buf ,
431
+ & s3.GetObjectInput {
432
+ Bucket : aws .String (bucket ),
433
+ Key : aws .String (item ),
434
+ })
435
+
436
+ if d != 0 {
437
+ c .lock .Lock ()
438
+ c .timerWatchers [url ] = time .AfterFunc (d , func () { c .ReadS3 (url , d ) })
439
+ c .lock .Unlock ()
440
+ }
441
+
442
+ if err != nil {
443
+ if d == 0 {
444
+ return ConfigError .New (err )
445
+ }
446
+ return nil
447
+ }
448
+
449
+ return c .ReadBytes (fileFormat , buf .Bytes ())
450
+ }
451
+
368
452
// LoadEnv loads all environment variables and watch for their changes every
369
453
// duration. Set the duration as zero if no need to watch the change.
370
454
func (c * Config ) LoadEnv (d time.Duration ) error {
@@ -379,13 +463,31 @@ func (c *Config) LoadEnv(d time.Duration) error {
379
463
380
464
if d != 0 {
381
465
c .lock .Lock ()
382
- c .envWatcher = time .AfterFunc (d , func () { c .LoadEnv (d ) })
466
+ c .timerWatchers [ "env" ] = time .AfterFunc (d , func () { c .LoadEnv (d ) })
383
467
c .lock .Unlock ()
384
468
}
385
469
386
470
return nil
387
471
}
388
472
473
+ // Read reads the config with any instance. If the instance is s3 url or
474
+ // environment variable, the watchInterval is used to choose the time interval
475
+ // for watching changes. If the instance is file path, it will watch the change
476
+ // if watchInterval > 0.
477
+ func (c * Config ) Read (path string ) error {
478
+ switch {
479
+ case path == "env" :
480
+ return c .LoadEnv (c .watchInterval )
481
+ case strings .HasPrefix (path , "s3://" ):
482
+ return c .ReadS3 (path , c .watchInterval )
483
+ default :
484
+ if c .watchInterval > 0 {
485
+ return c .ReadFile (path , true )
486
+ }
487
+ return c .ReadFile (path , false )
488
+ }
489
+ }
490
+
389
491
// Get returns the value assigned with the key. The latter returned value is
390
492
// false if they key doesn't exist.
391
493
func (c * Config ) Get (key string ) (Value , bool ) {
0 commit comments