forked from prometheus-community/jiralert
-
Notifications
You must be signed in to change notification settings - Fork 0
/
config.go
239 lines (210 loc) · 6.77 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
package jiralert
import (
"fmt"
"io/ioutil"
"net/url"
"path/filepath"
"strings"
log "github.com/golang/glog"
"gopkg.in/yaml.v2"
)
// Secret is a string that must not be revealed on marshaling.
type Secret string
// MarshalYAML implements the yaml.Marshaler interface.
func (s Secret) MarshalYAML() (interface{}, error) {
if s != "" {
return "<secret>", nil
}
return nil, nil
}
// UnmarshalYAML implements the yaml.Unmarshaler interface for Secrets.
func (s *Secret) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain Secret
return unmarshal((*plain)(s))
}
// LoadConfig parses the YAML input into a Config.
func LoadConfig(s string) (*Config, error) {
cfg := &Config{}
err := yaml.Unmarshal([]byte(s), cfg)
if err != nil {
return nil, err
}
log.V(1).Infof("Loaded config:\n%+v", cfg)
return cfg, nil
}
// LoadConfigFile parses the given YAML file into a Config.
func LoadConfigFile(filename string) (*Config, []byte, error) {
log.V(1).Infof("Loading configuration from %q", filename)
content, err := ioutil.ReadFile(filename)
if err != nil {
return nil, nil, err
}
cfg, err := LoadConfig(string(content))
if err != nil {
return nil, nil, err
}
resolveFilepaths(filepath.Dir(filename), cfg)
return cfg, content, nil
}
// resolveFilepaths joins all relative paths in a configuration
// with a given base directory.
func resolveFilepaths(baseDir string, cfg *Config) {
join := func(fp string) string {
if len(fp) == 0 || filepath.IsAbs(fp) {
return fp
}
absFp := filepath.Join(baseDir, fp)
log.V(2).Infof("Relative path %q resolved to %q", fp, absFp)
return absFp
}
cfg.Template = join(cfg.Template)
}
// ReceiverConfig is the configuration for one receiver. It has a unique name and includes API access fields (URL, user
// and password) and issue fields (required -- e.g. project, issue type -- and optional -- e.g. priority).
type ReceiverConfig struct {
Name string `yaml:"name" json:"name"`
// API access fields
APIURL string `yaml:"api_url" json:"api_url"`
User string `yaml:"user" json:"user"`
Password Secret `yaml:"password" json:"password"`
// Required issue fields
Project string `yaml:"project" json:"project"`
IssueType string `yaml:"issue_type" json:"issue_type"`
Summary string `yaml:"summary" json:"summary"`
ReopenState string `yaml:"reopen_state" json:"reopen_state"`
// Optional issue fields
Priority string `yaml:"priority" json:"priority"`
Description string `yaml:"description" json:"description"`
WontFixResolution string `yaml:"wont_fix_resolution" json:"wont_fix_resolution"`
Fields map[string]interface{} `yaml:"fields" json:"fields"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline" json:"-"`
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (rc *ReceiverConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
type plain ReceiverConfig
if err := unmarshal((*plain)(rc)); err != nil {
return err
}
return checkOverflow(rc.XXX, "receiver")
}
// Config is the top-level configuration for JIRAlert's config file.
type Config struct {
Defaults *ReceiverConfig `yaml:"defaults,omitempty" json:"defaults,omitempty"`
Receivers []*ReceiverConfig `yaml:"receivers,omitempty" json:"receivers,omitempty"`
Template string `yaml:"template" json:"template"`
// Catches all undefined fields and must be empty after parsing.
XXX map[string]interface{} `yaml:",inline" json:"-"`
}
func (c Config) String() string {
b, err := yaml.Marshal(c)
if err != nil {
return fmt.Sprintf("<error creating config string: %s>", err)
}
return string(b)
}
// UnmarshalYAML implements the yaml.Unmarshaler interface.
func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
// We want to set c to the defaults and then overwrite it with the input.
// To make unmarshal fill the plain data struct rather than calling UnmarshalYAML
// again, we have to hide it using a type indirection.
type plain Config
if err := unmarshal((*plain)(c)); err != nil {
return err
}
for _, rc := range c.Receivers {
if rc.Name == "" {
return fmt.Errorf("missing name for receiver %+v", rc)
}
// Check API access fields
if rc.APIURL == "" {
if c.Defaults.APIURL == "" {
return fmt.Errorf("missing api_url in receiver %q", rc.Name)
}
rc.APIURL = c.Defaults.APIURL
}
if _, err := url.Parse(rc.APIURL); err != nil {
return fmt.Errorf("invalid api_url %q in receiver %q: %s", rc.APIURL, rc.Name, err)
}
if rc.User == "" {
if c.Defaults.User == "" {
return fmt.Errorf("missing user in receiver %q", rc.Name)
}
rc.User = c.Defaults.User
}
if rc.Password == "" {
if c.Defaults.Password == "" {
return fmt.Errorf("missing password in receiver %q", rc.Name)
}
rc.Password = c.Defaults.Password
}
// Check required issue fields
if rc.Project == "" {
if c.Defaults.Project == "" {
return fmt.Errorf("missing project in receiver %q", rc.Name)
}
rc.Project = c.Defaults.Project
}
if rc.IssueType == "" {
if c.Defaults.IssueType == "" {
return fmt.Errorf("missing issue_type in receiver %q", rc.Name)
}
rc.IssueType = c.Defaults.IssueType
}
if rc.Summary == "" {
if c.Defaults.Summary == "" {
return fmt.Errorf("missing summary in receiver %q", rc.Name)
}
rc.Summary = c.Defaults.Summary
}
if rc.ReopenState == "" {
if c.Defaults.ReopenState == "" {
return fmt.Errorf("missing reopen_state in receiver %q", rc.Name)
}
rc.ReopenState = c.Defaults.ReopenState
}
// Populate optional issue fields, where necessary
if rc.Priority == "" && c.Defaults.Priority != "" {
rc.Priority = c.Defaults.Priority
}
if rc.Description == "" && c.Defaults.Description != "" {
rc.Description = c.Defaults.Description
}
if rc.WontFixResolution == "" && c.Defaults.WontFixResolution != "" {
rc.WontFixResolution = c.Defaults.WontFixResolution
}
if len(c.Defaults.Fields) > 0 {
for key, value := range c.Defaults.Fields {
if _, ok := rc.Fields[key]; !ok {
rc.Fields[key] = value
}
}
}
}
if len(c.Receivers) == 0 {
return fmt.Errorf("no receivers defined")
}
if c.Template == "" {
return fmt.Errorf("missing template file")
}
return checkOverflow(c.XXX, "config")
}
// ReceiverByName loops the receiver list and returns the first instance with that name
func (c *Config) ReceiverByName(name string) *ReceiverConfig {
for _, rc := range c.Receivers {
if rc.Name == name {
return rc
}
}
return nil
}
func checkOverflow(m map[string]interface{}, ctx string) error {
if len(m) > 0 {
var keys []string
for k := range m {
keys = append(keys, k)
}
return fmt.Errorf("unknown fields in %s: %s", ctx, strings.Join(keys, ", "))
}
return nil
}