-
Notifications
You must be signed in to change notification settings - Fork 183
/
loader.go
163 lines (135 loc) · 4.38 KB
/
loader.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
package config
import (
"context"
"fmt"
"math"
"os"
"regexp"
"strings"
"time"
yaml "gopkg.in/yaml.v2"
"github.com/creasty/defaults"
"github.com/pkg/errors"
"github.com/signalfx/signalfx-agent/internal/core/config/sources"
"github.com/signalfx/signalfx-agent/internal/utils"
"github.com/signalfx/signalfx-agent/internal/utils/structtags"
log "github.com/sirupsen/logrus"
)
// LoadConfig handles loading the main config file and recursively rendering
// any dynamic values in the config. If watchInterval is 0, the config will be
// loaded once and sent to the returned channel, after which the channel will
// be closed. Otherwise, the returned channel will remain open and will be
// sent any config updates.
func LoadConfig(ctx context.Context, configPath string) (<-chan *Config, error) {
configYAML, configFileChanges, err := sources.ReadConfig(configPath, ctx.Done())
if err != nil {
return nil, errors.WithMessage(err, "Could not read config file "+configPath)
}
dynamicValueCtx, cancelDynamic := context.WithCancel(ctx)
finalYAML, dynamicChanges, err := sources.ReadDynamicValues(configYAML, dynamicValueCtx.Done())
if err != nil {
cancelDynamic()
return nil, err
}
config, err := loadYAML(finalYAML)
if err != nil {
cancelDynamic()
return nil, err
}
// Give it enough room to hold the initial config load.
loads := make(chan *Config, 1)
loads <- config
if configFileChanges != nil {
go func() {
for {
// We can have changes either in the dynamic values or the
// config file itself. If the config file changes, we have to
// recreate the dynamic value watcher since it is configured
// from the config file.
select {
case configYAML = <-configFileChanges:
cancelDynamic()
dynamicValueCtx, cancelDynamic = context.WithCancel(ctx)
finalYAML, dynamicChanges, err = sources.ReadDynamicValues(configYAML, dynamicValueCtx.Done())
if err != nil {
log.WithError(err).Error("Could not read dynamic values in config after change")
time.Sleep(5 * time.Second)
continue
}
config, err := loadYAML(finalYAML)
if err != nil {
log.WithError(err).Error("Could not parse config after change")
continue
}
loads <- config
case finalYAML = <-dynamicChanges:
config, err := loadYAML(finalYAML)
if err != nil {
log.WithError(err).Error("Could not parse config after change")
continue
}
loads <- config
case <-ctx.Done():
cancelDynamic()
return
}
}
}()
} else {
cancelDynamic()
}
return loads, nil
}
func loadYAML(fileContent []byte) (*Config, error) {
config := &Config{}
preprocessedContent := preprocessConfig(fileContent)
err := yaml.UnmarshalStrict(preprocessedContent, config)
if err != nil {
// Provide some context about where the parse error occurred
for _, e := range err.(*yaml.TypeError).Errors {
line := utils.ParseLineNumberFromYAMLError(e)
context := string(preprocessedContent)
if line != 0 {
lines := strings.Split(context, "\n")
context = strings.Join(lines[int(math.Max(float64(line-5), 0)):line], "\n")
context += "\n^^^^^^^\n"
context += strings.Join(lines[line:int(math.Min(float64(line+5), float64(len(lines))))], "\n")
}
log.Errorf("Could not unmarshal config file:\n\n%s\n\n%s\n", context, err.Error())
}
return nil, err
}
if err := defaults.Set(config); err != nil {
panic(fmt.Sprintf("Config defaults are wrong types: %s", err))
}
if err := structtags.CopyTo(config); err != nil {
panic(fmt.Sprintf("Error copying configs to fields: %v", err))
}
return config.initialize()
}
var envVarRE = regexp.MustCompile(`\${\s*([\w-]+?)\s*}`)
// Hold all of the envvars so that when they are sanitized from the proc we can
// still get to them when we need to rerender config
var envVarCache = make(map[string]string)
var envVarWhitelist = map[string]bool{
"MY_NODE_NAME": true,
}
// Replaces envvar syntax with the actual envvars
func preprocessConfig(content []byte) []byte {
return envVarRE.ReplaceAllFunc(content, func(bs []byte) []byte {
parts := envVarRE.FindSubmatch(bs)
envvar := string(parts[1])
val, ok := envVarCache[envvar]
if !ok {
val = os.Getenv(envvar)
envVarCache[envvar] = val
log.WithFields(log.Fields{
"envvar": envvar,
}).Debug("Sanitizing envvar from agent")
if !envVarWhitelist[envvar] {
os.Unsetenv(envvar)
}
}
return []byte(val)
})
}