-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconfig.go
133 lines (121 loc) · 3.03 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
package config
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/mitchellh/mapstructure"
"github.com/spf13/viper"
)
// Supported configuration file formats.
//
// Should be used as a second argument to Load.
const (
YAML = "yaml"
JSON = "json"
TOML = "toml"
)
var OverwriteEnvPrefix = "CONFIG_OVERWRITE_"
var supportedFormats = map[string]bool{
YAML: true,
JSON: true,
TOML: true,
}
// LoadFile reads configuration data from the named file
// and unmarshals it into v.
//
// Internally it calls Load.
func LoadFile(filename string, v interface{}) error {
f, err := os.Open(filename)
if err != nil {
return fmt.Errorf("config: failed to open config file: %w", err)
}
// Get file extensions and then strip it from the leading dot.
configType := strings.TrimPrefix(filepath.Ext(filename), ".")
return Load(f, configType, v)
}
// Load reads configuration data encoded in the format specified by configType from in
// and unmarshals it into v.
//
// Load returns an error when data is in wrong or unsupported format,
// or when it failed to unmarshal data into v.
func Load(in io.Reader, configType string, v interface{}) error {
if !supportedFormats[configType] {
if configType == "" {
return errors.New("config: configuration format should be provided as a second argument to Load")
}
return fmt.Errorf("config: %s - unsupported configuration format", configType)
}
// Load in configuration with viper helpers.
viper.SetConfigType(configType)
err := viper.ReadConfig(in)
if err != nil {
return fmt.Errorf("config: failed to read in config: %w", err)
}
// Iterate over all viper keys expanding $VARIABLE and ${VARIABLE} values.
for _, key := range viper.AllKeys() {
newKey, ok := os.LookupEnv(OverwriteEnvPrefix + key)
if !ok {
newKey = os.Expand(viper.GetString(key), mapping)
}
viper.Set(key, newKey)
}
return unmarshal(&v)
}
func unmarshal(v interface{}) error {
err := viper.Unmarshal(&v, func(dc *mapstructure.DecoderConfig) {
dc.TagName = "config"
})
if err != nil {
return fmt.Errorf("config: failed to unmarshal config: %w", err)
}
return nil
}
var pattern = regexp.MustCompile("(?i)[_a-z][_a-z0-9]*")
// mapping is a second argument for os.Expand function.
func mapping(s string) string {
loc := pattern.FindStringIndex(s)
// if no match then silently ignore it.
if loc == nil {
return s
}
key := s[loc[0]:loc[1]]
switch m := s[loc[1]:]; {
case strings.HasPrefix(m, ":-"):
val := os.Getenv(key)
if val == "" {
return m[2:]
}
return val
case strings.HasPrefix(m, "-"):
val, found := os.LookupEnv(key)
if found {
return val
}
return m[1:]
case strings.HasPrefix(m, ":?"):
val := os.Getenv(key)
if val == "" {
message := m[2:]
if message == "" {
message = key + " is empty or not set"
}
panic(message)
}
return val
case strings.HasPrefix(m, "?"):
val, found := os.LookupEnv(key)
if found {
return val
}
message := m[1:]
if message == "" {
message = key + " is not set"
}
panic(message)
}
return os.Getenv(key)
}