zrepl/cmd/config/config.go

341 lines
7.7 KiB
Go
Raw Normal View History

2018-08-26 15:17:15 +02:00
package config
import (
"fmt"
"github.com/pkg/errors"
2018-08-26 16:44:34 +02:00
"github.com/zrepl/yaml-config"
2018-08-26 15:17:15 +02:00
"io/ioutil"
2018-08-26 16:44:34 +02:00
"os"
2018-08-26 19:20:08 +02:00
"regexp"
"strconv"
2018-08-26 16:44:34 +02:00
"time"
2018-08-26 15:17:15 +02:00
)
2018-08-26 16:44:34 +02:00
type Config struct {
Jobs []JobEnum `yaml:"jobs"`
Global Global `yaml:"global"`
}
type JobEnum struct {
2018-08-26 15:17:15 +02:00
Ret interface{}
}
2018-08-26 16:44:34 +02:00
type PushJob struct {
2018-08-26 23:11:50 +02:00
Type string `yaml:"type"`
Name string `yaml:"name"`
Replication PushReplication `yaml:"replication"`
Snapshotting Snapshotting `yaml:"snapshotting"`
Pruning PruningSenderReceiver `yaml:"pruning"`
2018-08-26 15:17:15 +02:00
}
2018-08-26 16:44:34 +02:00
type SinkJob struct {
Type string `yaml:"type"`
2018-08-26 23:11:50 +02:00
Name string `yaml:"name"`
2018-08-26 15:17:15 +02:00
Replication SinkReplication `yaml:"replication"`
}
2018-08-26 23:11:50 +02:00
type PullJob struct {
Type string `yaml:"type"`
Name string `yaml:"name"`
Replication PullReplication `yaml:"replication"`
Pruning PruningSenderReceiver `yaml:"pruning"`
}
type PullReplication struct {
Connect ConnectEnum `yaml:"connect"`
RootDataset string `yaml:"root_dataset"`
}
type SourceJob struct {
Type string `yaml:"type"`
Name string `yaml:"name"`
Replication SourceReplication `yaml:"replication"`
Snapshotting Snapshotting `yaml:"snapshotting"`
Pruning PruningLocal `yaml:"pruning"`
}
2018-08-26 15:17:15 +02:00
type PushReplication struct {
2018-08-26 16:44:34 +02:00
Connect ConnectEnum `yaml:"connect"`
2018-08-26 15:17:15 +02:00
Filesystems map[string]bool `yaml:"filesystems"`
}
type SinkReplication struct {
2018-08-26 16:44:34 +02:00
RootDataset string `yaml:"root_dataset"`
Serve ServeEnum `yaml:"serve"`
2018-08-26 15:17:15 +02:00
}
2018-08-26 23:11:50 +02:00
type SourceReplication struct {
Serve ServeEnum `yaml:"serve"`
Filesystems map[string]bool `yaml:"filesystems"`
}
2018-08-26 15:17:15 +02:00
type Snapshotting struct {
2018-08-26 16:44:34 +02:00
SnapshotPrefix string `yaml:"snapshot_prefix"`
Interval time.Duration `yaml:"interval"`
2018-08-26 15:17:15 +02:00
}
2018-08-26 23:11:50 +02:00
type PruningSenderReceiver struct {
KeepSender []PruningEnum `yaml:"keep_sender"`
KeepReceiver []PruningEnum `yaml:"keep_receiver"`
}
type PruningLocal struct {
Keep []PruningEnum `yaml:"keep"`
2018-08-26 15:17:15 +02:00
}
type Global struct {
2018-08-26 20:25:06 +02:00
Logging []LoggingOutletEnum `yaml:"logging"`
Monitoring []MonitoringEnum `yaml:"monitoring,optional"`
2018-08-26 23:11:50 +02:00
Control GlobalControl `yaml:"control"`
Serve GlobalServe `yaml:"serve"`
2018-08-26 15:17:15 +02:00
}
type ConnectEnum struct {
Ret interface{}
}
type TCPConnect struct {
2018-08-26 16:44:34 +02:00
Type string `yaml:"type"`
2018-08-26 15:17:15 +02:00
Address string `yaml:"address"`
}
type TLSConnect struct {
2018-08-26 16:44:34 +02:00
Type string `yaml:"type"`
2018-08-26 15:17:15 +02:00
Address string `yaml:"address"`
2018-08-26 16:44:34 +02:00
Ca string `yaml:"ca"`
Cert string `yaml:"cert"`
Key string `yaml:"key"`
2018-08-26 15:17:15 +02:00
}
type ServeEnum struct {
Ret interface{}
}
type TCPServe struct {
2018-08-26 16:44:34 +02:00
Type string `yaml:"type"`
Listen string `yaml:"listen"`
2018-08-26 15:17:15 +02:00
Clients map[string]string `yaml:"clients"`
}
type TLSServe struct {
2018-08-26 16:44:34 +02:00
Type string `yaml:"type"`
2018-08-26 15:17:15 +02:00
Listen string `yaml:"listen"`
2018-08-26 16:44:34 +02:00
Ca string `yaml:"ca"`
Cert string `yaml:"cert"`
Key string `yaml:"key"`
2018-08-26 15:17:15 +02:00
}
type PruningEnum struct {
Ret interface{}
}
type PruneKeepNotReplicated struct {
Type string `yaml:"type"`
}
type PruneKeepLastN struct {
2018-08-26 16:44:34 +02:00
Type string `yaml:"type"`
Count int `yaml:"count"`
2018-08-26 15:17:15 +02:00
}
type LoggingOutletEnum struct {
Ret interface{}
}
2018-08-26 16:44:34 +02:00
type LoggingOutletCommon struct {
Type string `yaml:"type"`
Level string `yaml:"level"`
Format string `yaml:"format"`
}
2018-08-26 15:17:15 +02:00
type StdoutLoggingOutlet struct {
2018-08-26 16:44:34 +02:00
LoggingOutletCommon `yaml:",inline"`
Time bool `yaml:"time"`
2018-08-26 15:17:15 +02:00
}
type SyslogLoggingOutlet struct {
2018-08-26 16:44:34 +02:00
LoggingOutletCommon `yaml:",inline"`
RetryInterval time.Duration `yaml:"retry_interval"`
}
type TCPLoggingOutlet struct {
LoggingOutletCommon `yaml:",inline"`
2018-08-26 23:11:50 +02:00
Address string `yaml:"address"`
Net string `yaml:"net,default=tcp"`
RetryInterval time.Duration `yaml:"retry_interval"`
TLS *TCPLoggingOutletTLS `yaml:"tls,optional"`
2018-08-26 16:44:34 +02:00
}
type TCPLoggingOutletTLS struct {
CA string `yaml:"ca"`
Cert string `yaml:"cert"`
Key string `yaml:"key"`
2018-08-26 15:17:15 +02:00
}
2018-08-26 20:25:06 +02:00
type MonitoringEnum struct {
Ret interface{}
}
type PrometheusMonitoring struct {
Type string `yaml:"type"`
Listen string `yaml:"listen"`
}
2018-08-26 23:11:50 +02:00
type GlobalControl struct {
SockPath string `yaml:"sockpath,default=/var/run/zrepl/control"`
}
type GlobalServe struct {
StdinServer GlobalStdinServer `yaml:"stdinserver"`
}
type GlobalStdinServer struct {
SockDir string `yaml:"sockdir,default=/var/run/zrepl/stdinserver"`
}
2018-08-26 15:17:15 +02:00
func enumUnmarshal(u func(interface{}, bool) error, types map[string]interface{}) (interface{}, error) {
var in struct {
Type string
}
if err := u(&in, true); err != nil {
return nil, err
}
if in.Type == "" {
return nil, &yaml.TypeError{[]string{"must specify type"}}
}
v, ok := types[in.Type]
if !ok {
return nil, &yaml.TypeError{[]string{fmt.Sprintf("invalid type name %q", in.Type)}}
}
if err := u(v, false); err != nil {
return nil, err
}
return v, nil
}
2018-08-26 16:44:34 +02:00
func (t *JobEnum) UnmarshalYAML(u func(interface{}, bool) error) (err error) {
2018-08-26 15:17:15 +02:00
t.Ret, err = enumUnmarshal(u, map[string]interface{}{
2018-08-26 23:11:50 +02:00
"push": &PushJob{},
"sink": &SinkJob{},
"pull": &PullJob{},
"source": &SourceJob{},
2018-08-26 15:17:15 +02:00
})
return
}
func (t *ConnectEnum) UnmarshalYAML(u func(interface{}, bool) error) (err error) {
t.Ret, err = enumUnmarshal(u, map[string]interface{}{
"tcp": &TCPConnect{},
"tls": &TLSConnect{},
})
return
}
func (t *ServeEnum) UnmarshalYAML(u func(interface{}, bool) error) (err error) {
t.Ret, err = enumUnmarshal(u, map[string]interface{}{
"tcp": &TCPServe{},
"tls": &TLSServe{},
})
return
}
func (t *PruningEnum) UnmarshalYAML(u func(interface{}, bool) error) (err error) {
t.Ret, err = enumUnmarshal(u, map[string]interface{}{
"not_replicated": &PruneKeepNotReplicated{},
2018-08-26 16:44:34 +02:00
"last_n": &PruneKeepLastN{},
"grid": &PruneGrid{},
2018-08-26 15:17:15 +02:00
})
return
}
func (t *LoggingOutletEnum) UnmarshalYAML(u func(interface{}, bool) error) (err error) {
t.Ret, err = enumUnmarshal(u, map[string]interface{}{
"stdout": &StdoutLoggingOutlet{},
"syslog": &SyslogLoggingOutlet{},
2018-08-26 16:44:34 +02:00
"tcp": &TCPLoggingOutlet{},
2018-08-26 15:17:15 +02:00
})
return
}
2018-08-26 20:25:06 +02:00
func (t *MonitoringEnum) UnmarshalYAML(u func(interface{}, bool) error) (err error) {
t.Ret, err = enumUnmarshal(u, map[string]interface{}{
"prometheus": &PrometheusMonitoring{},
})
return
}
2018-08-26 15:17:15 +02:00
var ConfigFileDefaultLocations = []string{
"/etc/zrepl/zrepl.yml",
"/usr/local/etc/zrepl/zrepl.yml",
}
2018-08-26 16:44:34 +02:00
func ParseConfig(path string) (i Config, err error) {
2018-08-26 15:17:15 +02:00
if path == "" {
// Try default locations
for _, l := range ConfigFileDefaultLocations {
stat, statErr := os.Stat(l)
if statErr != nil {
continue
}
if !stat.Mode().IsRegular() {
err = errors.Errorf("file at default location is not a regular file: %s", l)
return
}
path = l
break
}
}
var bytes []byte
if bytes, err = ioutil.ReadFile(path); err != nil {
return
}
if err = yaml.UnmarshalStrict(bytes, &i); err != nil {
return
}
return
}
2018-08-26 19:20:08 +02:00
var durationStringRegex *regexp.Regexp = regexp.MustCompile(`^\s*(\d+)\s*(s|m|h|d|w)\s*$`)
func parsePostitiveDuration(e string) (d time.Duration, err error) {
comps := durationStringRegex.FindStringSubmatch(e)
if len(comps) != 3 {
err = fmt.Errorf("does not match regex: %s %#v", e, comps)
return
}
durationFactor, err := strconv.ParseInt(comps[1], 10, 64)
if err != nil {
return 0, err
}
if durationFactor <= 0 {
return 0, errors.New("duration must be positive integer")
}
var durationUnit time.Duration
switch comps[2] {
case "s":
durationUnit = time.Second
case "m":
durationUnit = time.Minute
case "h":
durationUnit = time.Hour
case "d":
durationUnit = 24 * time.Hour
case "w":
durationUnit = 24 * 7 * time.Hour
default:
err = fmt.Errorf("contains unknown time unit '%s'", comps[2])
return
}
d = time.Duration(durationFactor) * durationUnit
return
}