/
config.go
147 lines (121 loc) · 3.61 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
package cfx
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"go.uber.org/config"
)
const (
_defaultConfigName = "base"
)
var (
// ErrNoConfigsLoaded is thrown when no configuration files were loaded.
ErrNoConfigsLoaded = errors.New("no configuration files were loaded into the container")
// ErrConfigNotFound is thrown when a configuration cannot be located
ErrConfigNotFound = errors.New("could not find any valid config files")
yamlExts = map[string]bool{
".yaml": true,
".yml": true,
}
)
// Container is the type that allows users to parse sections of the YAML configuration
// as a coherent configuration tree.
type Container interface {
// Populate is used to load a block of YAML configuration into
// a target struct. Target should be a pointer to the config struct value.
Populate(key string, target interface{}) error
}
// NewConfig is used to create a container that can be used to extract configuration
// elements from a YAML file.
func NewConfig(env EnvContext) (Container, error) {
ret := &yamlContainer{}
// set the default YAML options
cfgopts := []config.YAMLOption{
config.Expand(os.LookupEnv),
}
// try and locate a base.yaml
basecfg, err := resolveConfig(env.ConfigPath, _defaultConfigName)
if err != nil && err != ErrConfigNotFound {
return ret, err
}
if basecfg != "" {
// we did locate a base.yaml file
cfgopts = append(cfgopts, config.File(basecfg))
}
// resolve the ${environment}.yaml
envcfg, err := resolveConfig(env.ConfigPath, env.Environment.String())
if err != nil {
return ret, err
}
cfgopts = append(cfgopts, config.File(envcfg))
// create the provider
provider, err := config.NewYAML(cfgopts...)
if err != nil {
return ret, fmt.Errorf("error constructing yaml configuration: %v", err)
}
if provider == nil {
return ret, errors.New("yaml config constructor returned nil provider")
}
ret.Lock()
ret.cfg = provider
ret.Unlock()
return ret, nil
}
// try to find a yaml/yml config by a given name in the provided config dir.
func resolveConfig(configDir string, name string) (string, error) {
// make sure the configDir exists
cd, err := os.Stat(configDir)
if err != nil {
if os.IsNotExist(err) {
return "", fmt.Errorf("config directory %s did not exist: %v", configDir, err)
}
if os.IsPermission(err) {
return "", fmt.Errorf("config directory %s is not readable: %v", configDir, err)
}
return "", fmt.Errorf("config directory %s could not be located: %v", configDir, err)
}
if !cd.IsDir() {
return "", fmt.Errorf("config directory %s is a file, not a directory", configDir)
}
// list all the files in the configDir
files, err := ioutil.ReadDir(configDir)
if err != nil {
return "", fmt.Errorf("could not list config directory: %v", err)
}
// iterate them
for _, x := range files {
if x.IsDir() {
continue // don't want a directory
}
fileext := filepath.Ext(x.Name())
// skip if it doesn't have .yaml or a .yml extension.
if _, exists := yamlExts[fileext]; !exists {
continue
}
// get the base filename without extension
basename := strings.Replace(filepath.Base(x.Name()), fileext, ``, -1)
// compare it against the provided name
if strings.EqualFold(basename, name) {
return filepath.Join(configDir, x.Name()), nil
}
}
// couldn't find anything
return "", ErrConfigNotFound
}
type yamlContainer struct {
sync.RWMutex
cfg *config.YAML
}
// Populate implements the cfgfx.Container interface.
func (y *yamlContainer) Populate(key string, target interface{}) error {
y.Lock()
defer y.Unlock()
if y.cfg == nil {
return ErrNoConfigsLoaded
}
return y.cfg.Get(key).Populate(target)
}