mirror of
https://github.com/zrepl/zrepl.git
synced 2024-11-22 08:23:50 +01:00
d684302864
tests expected that a KeepRule returns a *keep* list whereas it actually returns a *destroy* list.
212 lines
5.0 KiB
Go
212 lines
5.0 KiB
Go
package cmd
|
|
|
|
import (
|
|
"io/ioutil"
|
|
|
|
"fmt"
|
|
"github.com/go-yaml/yaml"
|
|
"github.com/mitchellh/mapstructure"
|
|
"github.com/pkg/errors"
|
|
"github.com/problame/go-streamrpc"
|
|
"github.com/zrepl/zrepl/cmd/pruning/retentiongrid"
|
|
"github.com/zrepl/zrepl/config"
|
|
"os"
|
|
)
|
|
|
|
var ConfigFileDefaultLocations []string = []string{
|
|
"/etc/zrepl/zrepl.yml",
|
|
"/usr/local/etc/zrepl/zrepl.yml",
|
|
}
|
|
|
|
const (
|
|
JobNameControl string = "control"
|
|
)
|
|
|
|
var ReservedJobNames []string = []string{
|
|
JobNameControl,
|
|
}
|
|
|
|
type ConfigParsingContext struct {
|
|
Global *Global
|
|
}
|
|
|
|
func ParseConfig(path string) (config *Config, err error) {
|
|
|
|
if path == "" {
|
|
// Try default locations
|
|
for _, l := range ConfigFileDefaultLocations {
|
|
stat, err := os.Stat(l)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if !stat.Mode().IsRegular() {
|
|
err = errors.Errorf("file at default location is not a regular file: %s", l)
|
|
continue
|
|
}
|
|
path = l
|
|
break
|
|
}
|
|
}
|
|
|
|
var i interface{}
|
|
|
|
var bytes []byte
|
|
|
|
if bytes, err = ioutil.ReadFile(path); err != nil {
|
|
err = errors.WithStack(err)
|
|
return
|
|
}
|
|
|
|
if err = yaml.Unmarshal(bytes, &i); err != nil {
|
|
err = errors.WithStack(err)
|
|
return
|
|
}
|
|
|
|
return parseConfig(i)
|
|
}
|
|
|
|
func parseConfig(i interface{}) (c *Config, err error) {
|
|
|
|
var asMap struct {
|
|
Global map[string]interface{}
|
|
Jobs []map[string]interface{}
|
|
}
|
|
if err := mapstructure.Decode(i, &asMap); err != nil {
|
|
return nil, errors.Wrap(err, "config root must be a dict")
|
|
}
|
|
|
|
c = &Config{}
|
|
|
|
// Parse global with defaults
|
|
c.Global.Serve.Stdinserver.SockDir = "/var/run/zrepl/stdinserver"
|
|
c.Global.Control.Sockpath = "/var/run/zrepl/control"
|
|
|
|
err = mapstructure.Decode(asMap.Global, &c.Global)
|
|
if err != nil {
|
|
err = errors.Wrap(err, "mapstructure error on 'global' section: %s")
|
|
return
|
|
}
|
|
|
|
if c.Global.logging, err = parseLogging(asMap.Global["logging"]); err != nil {
|
|
return nil, errors.Wrap(err, "cannot parse logging section")
|
|
}
|
|
|
|
cpc := ConfigParsingContext{&c.Global}
|
|
jpc := JobParsingContext{cpc}
|
|
c.Jobs = make(map[string]Job, len(asMap.Jobs))
|
|
|
|
// FIXME internal jobs should not be mixed with user jobs
|
|
// Monitoring Jobs
|
|
var monJobs []map[string]interface{}
|
|
if err := mapstructure.Decode(asMap.Global["monitoring"], &monJobs); err != nil {
|
|
return nil, errors.Wrap(err, "cannot parse monitoring section")
|
|
}
|
|
for i, jc := range monJobs {
|
|
if jc["name"] == "" || jc["name"] == nil {
|
|
// FIXME internal jobs should not require a name...
|
|
jc["name"] = fmt.Sprintf("prometheus-%d", i)
|
|
}
|
|
job, err := parseJob(jpc, jc)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "cannot parse monitoring job #%d", i)
|
|
}
|
|
if job.JobType() != JobTypePrometheus {
|
|
return nil, errors.Errorf("monitoring job #%d has invalid job type", i)
|
|
}
|
|
c.Jobs[job.JobName()] = job
|
|
}
|
|
|
|
// Regular Jobs
|
|
for i := range asMap.Jobs {
|
|
job, err := parseJob(jpc, asMap.Jobs[i])
|
|
if err != nil {
|
|
// Try to find its name
|
|
namei, ok := asMap.Jobs[i]["name"]
|
|
if !ok {
|
|
namei = fmt.Sprintf("<no name, entry #%d in list>", i)
|
|
}
|
|
err = errors.Wrapf(err, "cannot parse job '%v'", namei)
|
|
return nil, err
|
|
}
|
|
jn := job.JobName()
|
|
if _, ok := c.Jobs[jn]; ok {
|
|
err = errors.Errorf("duplicate or invalid job name: %s", jn)
|
|
return nil, err
|
|
}
|
|
c.Jobs[job.JobName()] = job
|
|
}
|
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
type JobParsingContext struct {
|
|
ConfigParsingContext
|
|
}
|
|
|
|
func parseJob(c config.Global, in config.JobEnum) (j Job, err error) {
|
|
|
|
switch v := in.Ret.(type) {
|
|
case config.PullJob:
|
|
return parsePullJob(c, v)
|
|
case config.SourceJob:
|
|
return parseSourceJob(c, v)
|
|
case config.LocalJob:
|
|
return parseLocalJob(c, v)
|
|
default:
|
|
panic(fmt.Sprintf("implementation error: unknown job type %s", v))
|
|
}
|
|
|
|
}
|
|
|
|
func parseConnect(in config.ConnectEnum) (c streamrpc.Connecter, err error) {
|
|
switch v := in.Ret.(type) {
|
|
case config.SSHStdinserverConnect:
|
|
return parseSSHStdinserverConnecter(v)
|
|
case config.TCPConnect:
|
|
return parseTCPConnecter(v)
|
|
case config.TLSConnect:
|
|
return parseTLSConnecter(v)
|
|
default:
|
|
panic(fmt.Sprintf("unknown connect type %v", v))
|
|
}
|
|
}
|
|
|
|
func parsePruning(in []config.PruningEnum, willSeeBookmarks bool) (p Pruner, err error) {
|
|
|
|
policies := make([]PrunePolicy, len(in))
|
|
for i := range in {
|
|
if policies[i], err = parseKeepRule(in[i]); err != nil {
|
|
return nil, errors.Wrapf(err, "invalid keep rule #%d:", i)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func parseKeepRule(in config.PruningEnum) (p PrunePolicy, err error) {
|
|
switch v := in.Ret.(type) {
|
|
case config.PruneGrid:
|
|
return retentiongrid.ParseGridPrunePolicy(v, willSeeBookmarks)
|
|
//case config.PruneKeepLastN:
|
|
//case config.PruneKeepRegex:
|
|
//case config.PruneKeepNotReplicated:
|
|
default:
|
|
panic(fmt.Sprintf("unknown keep rule type %v", v))
|
|
}
|
|
}
|
|
|
|
func parseAuthenticatedChannelListenerFactory(c config.Global, in config.ServeEnum) (p ListenerFactory, err error) {
|
|
|
|
switch v := in.Ret.(type) {
|
|
case config.StdinserverServer:
|
|
return parseStdinserverListenerFactory(c, v)
|
|
case config.TCPServe:
|
|
return parseTCPListenerFactory(c, v)
|
|
case config.TLSServe:
|
|
return parseTLSListenerFactory(c, v)
|
|
default:
|
|
panic(fmt.Sprintf("unknown listener type %v", v))
|
|
}
|
|
|
|
}
|