forked from shunfei/cronsun
-
Notifications
You must be signed in to change notification settings - Fork 0
/
conf.go
301 lines (255 loc) · 5.98 KB
/
conf.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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
package conf
import (
"errors"
"io/ioutil"
"os"
"path"
"strings"
"time"
client "github.com/coreos/etcd/clientv3"
"github.com/fsnotify/fsnotify"
"github.com/go-gomail/gomail"
"github.com/satori/go.uuid"
"github.com/shunfei/cronsun/db"
"github.com/shunfei/cronsun/event"
"github.com/shunfei/cronsun/log"
"github.com/shunfei/cronsun/utils"
)
var (
Config = new(Conf)
initialized bool
watcher *fsnotify.Watcher
exitChan = make(chan struct{})
)
func Init(confFile string, watchConfiFile bool) error {
if initialized {
return nil
}
if err := Config.parse(confFile); err != nil {
return err
}
if watchConfiFile {
if err := Config.watch(confFile); err != nil {
return err
}
}
initialized = true
return nil
}
type Conf struct {
Node string // node 进程路径
Proc string // 当前执行任务路径
Cmd string // cmd 路径
Once string // 马上执行任务路径
Csctl string // csctl 发送执行命令的路径
Lock string // job lock 路径
Group string // 节点分组
Noticer string // 通知
PIDFile string
UUIDFile string
Ttl int64 // 节点超时时间,单位秒
ReqTimeout int // 请求超时时间,单位秒
// 执行任务信息过期时间,单位秒
// 0 为不过期
ProcTtl int64
// 记录任务执行中的信息的执行时间阀值,单位秒
// 0 为不限制
ProcReq int64
// 单机任务锁过期时间,单位秒
// 默认 300
LockTtl int64
Etcd *etcdConfig
Mgo *db.Config
Web *webConfig
Mail *MailConf
Security *Security
}
type etcdConfig struct {
Endpoints []string
Username string
Password string
DialTimeout int64 // 单位秒
conf client.Config
}
func (e *etcdConfig) Copy() client.Config {
return e.conf
}
type webConfig struct {
BindAddr string
Auth struct {
Enabled bool
}
Session SessionConfig
LogCleaner struct {
EveryMinute int
ExpirationDays int
}
}
type SessionConfig struct {
Expiration int
CookieName string
StorePrefixPath string
}
type MailConf struct {
Enable bool
To []string
// 如果配置,则按 http api 方式发送,否则按 smtp 方式发送
HttpAPI string
// 如果此时间段内没有邮件发送,则关闭 SMTP 连接,单位/秒
Keepalive int64
*gomail.Dialer
}
type Security struct {
// 是不开启安全选项
// true 开启
// 所执行的命令只能是机器上的脚本,仅支持配置项里的扩展名
// 执行用户只能选择配置里的用户
// false 关闭,命令和用户可以用动填写
Open bool `json:"open"`
// 配置执行用户
Users []string `json:"users"`
// 支持的执行的脚本扩展名
Ext []string `json:"ext"`
}
// 返回前后包含斜杆的 /a/b/ 的前缀
func cleanKeyPrefix(p string) string {
p = path.Clean(p)
if p[0] != '/' {
p = "/" + p
}
p += "/"
return p
}
var errUUIDFilePathRequired = errors.New("the UUIDFile file path is required, see base.json.sample")
func (c *Conf) UUID() (string, error) {
c.UUIDFile = strings.TrimSpace(c.UUIDFile)
if len(c.UUIDFile) == 0 {
return "", errUUIDFilePathRequired
}
c.UUIDFile = path.Clean(c.UUIDFile)
b, err := ioutil.ReadFile(c.UUIDFile)
if err == nil {
if len(b) == 0 {
return c.genUUID()
}
return string(b), nil
}
if !os.IsNotExist(err) {
return "", err
}
return c.genUUID()
}
func (c *Conf) genUUID() (string, error) {
u, err := uuid.NewV4()
if err != nil {
return "", err
}
uuidDir := path.Dir(c.UUIDFile)
if err := os.MkdirAll(uuidDir, 0755); err != nil {
return "", err
}
err = ioutil.WriteFile(c.UUIDFile, []byte(u.String()), 0600)
if err != nil {
return "", err
}
return u.String(), nil
}
func (c *Conf) parse(confFile string) error {
err := utils.LoadExtendConf(confFile, c)
if err != nil {
return err
}
if c.Etcd.DialTimeout > 0 {
c.Etcd.conf.DialTimeout = time.Duration(c.Etcd.DialTimeout) * time.Second
}
c.Etcd.conf.Username = c.Etcd.Username
c.Etcd.conf.Password = c.Etcd.Password
c.Etcd.conf.Endpoints = c.Etcd.Endpoints
if c.Ttl <= 0 {
c.Ttl = 10
}
if c.LockTtl < 2 {
c.LockTtl = 300
}
if c.Mail.Keepalive <= 0 {
c.Mail.Keepalive = 30
}
if c.Mgo.Timeout <= 0 {
c.Mgo.Timeout = 10 * time.Second
} else {
c.Mgo.Timeout *= time.Second
}
if c.Web != nil {
if c.Web.LogCleaner.EveryMinute < 0 {
c.Web.LogCleaner.EveryMinute = 30
}
if c.Web.LogCleaner.ExpirationDays <= 0 {
c.Web.LogCleaner.ExpirationDays = 1
}
}
c.Node = cleanKeyPrefix(c.Node)
c.Proc = cleanKeyPrefix(c.Proc)
c.Cmd = cleanKeyPrefix(c.Cmd)
c.Once = cleanKeyPrefix(c.Once)
c.Csctl = cleanKeyPrefix(c.Csctl)
c.Lock = cleanKeyPrefix(c.Lock)
c.Group = cleanKeyPrefix(c.Group)
c.Noticer = cleanKeyPrefix(c.Noticer)
return nil
}
func (c *Conf) watch(confFile string) error {
var err error
watcher, err = fsnotify.NewWatcher()
if err != nil {
return err
}
go func() {
duration := 3 * time.Second
timer, update := time.NewTimer(duration), false
for {
select {
case <-exitChan:
return
case event := <-watcher.Events:
// 保存文件时会产生多个事件
if event.Op&(fsnotify.Write|fsnotify.Chmod) > 0 {
update = true
}
timer.Reset(duration)
case <-timer.C:
if update {
c.reload(confFile)
event.Emit(event.WAIT, nil)
update = false
}
timer.Reset(duration)
case err := <-watcher.Errors:
log.Warnf("config watcher err: %v", err)
}
}
}()
return watcher.Add(confFile)
}
// 重新加载配置项
// 注:与系统资源相关的选项不生效,需重启程序
// Etcd
// Mgo
// Web
func (c *Conf) reload(confFile string) {
cf := new(Conf)
if err := cf.parse(confFile); err != nil {
log.Warnf("config file reload err: %s", err.Error())
return
}
// etcd key 选项需要重启
cf.Node, cf.Proc, cf.Cmd, cf.Once, cf.Csctl, cf.Lock, cf.Group, cf.Noticer = c.Node, c.Proc, c.Cmd, c.Once, c.Csctl, c.Lock, c.Group, c.Noticer
*c = *cf
log.Infof("config file[%s] reload success", confFile)
return
}
func Exit(i interface{}) {
close(exitChan)
if watcher != nil {
watcher.Close()
}
}