mirror of
https://github.com/zrepl/zrepl.git
synced 2024-12-25 16:38:49 +01:00
config: restructure in 'jobs' and 'global' section
This commit is contained in:
parent
fa4d2098a8
commit
f3689563b5
@ -8,7 +8,16 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Jobs map[string]Job
|
||||
Global Global
|
||||
Jobs map[string]Job
|
||||
}
|
||||
|
||||
type Global struct {
|
||||
Serve struct {
|
||||
Stdinserver struct {
|
||||
SockDir string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type RPCConnecter interface {
|
||||
|
@ -3,10 +3,10 @@ package cmd
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
"fmt"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/pkg/errors"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func ParseConfig(path string) (config *Config, err error) {
|
||||
@ -30,43 +30,76 @@ func ParseConfig(path string) (config *Config, err error) {
|
||||
|
||||
func parseConfig(i interface{}) (c *Config, err error) {
|
||||
|
||||
var jm map[string]map[string]interface{}
|
||||
if err := mapstructure.Decode(i, &jm); err != nil {
|
||||
return nil, errors.Wrap(err, "config must be a dict with job name as key and jobs as values")
|
||||
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{
|
||||
Jobs: make(map[string]Job, len(jm)),
|
||||
c = &Config{}
|
||||
|
||||
// Parse global with defaults
|
||||
c.Global.Serve.Stdinserver.SockDir = "/var/run/zrepl/stdinserver"
|
||||
err = mapstructure.Decode(asMap.Global, &c.Global)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "cannot parse global section: %s")
|
||||
return
|
||||
}
|
||||
|
||||
for name := range jm {
|
||||
|
||||
c.Jobs[name], err = parseJob(name, jm[name])
|
||||
// Parse Jobs
|
||||
c.Jobs = make(map[string]Job, len(asMap.Jobs))
|
||||
for i := range asMap.Jobs {
|
||||
job, err := parseJob(asMap.Jobs[i])
|
||||
if err != nil {
|
||||
err = errors.Wrapf(err, "cannot parse job '%s'", name)
|
||||
// Try to find its name
|
||||
namei, ok := asMap.Jobs[i]["name"]
|
||||
if !ok {
|
||||
namei = fmt.Sprintf("<no name, %i in list>", i)
|
||||
}
|
||||
err = errors.Wrapf(err, "cannot parse job '%v'", namei)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.Jobs[job.JobName()] = job
|
||||
}
|
||||
|
||||
return c, nil
|
||||
|
||||
}
|
||||
|
||||
func parseJob(name string, i map[string]interface{}) (j Job, err error) {
|
||||
|
||||
jobtype_i, ok := i["type"]
|
||||
func extractStringField(i map[string]interface{}, key string, notempty bool) (field string, err error) {
|
||||
vi, ok := i[key]
|
||||
if !ok {
|
||||
err = errors.New("must have field 'type'")
|
||||
return nil, err
|
||||
err = errors.Errorf("must have field '%s'", key)
|
||||
return "", err
|
||||
}
|
||||
jobtype_str, ok := jobtype_i.(string)
|
||||
field, ok = vi.(string)
|
||||
if !ok {
|
||||
err = errors.New("'type' field must have type string")
|
||||
return nil, err
|
||||
err = errors.Errorf("'%s' field must have type string", key)
|
||||
return "", err
|
||||
}
|
||||
if notempty && len(field) <= 0 {
|
||||
err = errors.Errorf("'%s' field must not be empty", key)
|
||||
return "", err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseJob(i map[string]interface{}) (j Job, err error) {
|
||||
|
||||
name, err := extractStringField(i, "name", true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch jobtype_str {
|
||||
jobtype, err := extractStringField(i, "type", true)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
switch jobtype {
|
||||
case "pull":
|
||||
return parsePullJob(name, i)
|
||||
case "source":
|
||||
@ -74,7 +107,7 @@ func parseJob(name string, i map[string]interface{}) (j Job, err error) {
|
||||
case "local":
|
||||
return parseLocalJob(name, i)
|
||||
default:
|
||||
return nil, errors.Errorf("unknown job type '%s'", jobtype_str)
|
||||
return nil, errors.Errorf("unknown job type '%s'", jobtype)
|
||||
}
|
||||
|
||||
panic("implementation error")
|
||||
@ -83,22 +116,17 @@ func parseJob(name string, i map[string]interface{}) (j Job, err error) {
|
||||
}
|
||||
|
||||
func parseConnect(i map[string]interface{}) (c RPCConnecter, err error) {
|
||||
type_i, ok := i["type"]
|
||||
if !ok {
|
||||
err = errors.New("must have field 'type'")
|
||||
return
|
||||
}
|
||||
type_str, ok := type_i.(string)
|
||||
if !ok {
|
||||
err = errors.New("'type' field must have type string")
|
||||
|
||||
t, err := extractStringField(i, "type", true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch type_str {
|
||||
switch t {
|
||||
case "ssh+stdinserver":
|
||||
return parseSSHStdinserverConnecter(i)
|
||||
default:
|
||||
return nil, errors.Errorf("unknown connection type '%s'", type_str)
|
||||
return nil, errors.Errorf("unknown connection type '%s'", t)
|
||||
}
|
||||
|
||||
panic("implementation error")
|
||||
@ -131,9 +159,8 @@ err:
|
||||
|
||||
func parsePrunePolicy(v map[string]interface{}) (p PrunePolicy, err error) {
|
||||
|
||||
policyName, ok := v["policy"]
|
||||
if !ok {
|
||||
err = errors.Errorf("policy name not specified")
|
||||
policyName, err := extractStringField(v, "policy", true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@ -152,22 +179,16 @@ func parsePrunePolicy(v map[string]interface{}) (p PrunePolicy, err error) {
|
||||
|
||||
func parseAuthenticatedChannelListenerFactory(v map[string]interface{}) (p AuthenticatedChannelListenerFactory, err error) {
|
||||
|
||||
t, ok := v["type"]
|
||||
if !ok {
|
||||
err = errors.Errorf("must specify 'type' field")
|
||||
return
|
||||
}
|
||||
s, ok := t.(string)
|
||||
if !ok {
|
||||
err = errors.Errorf("'type' must be a string")
|
||||
return
|
||||
t, err := extractStringField(v, "type", true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch s{
|
||||
switch t {
|
||||
case "stdinserver":
|
||||
return parseStdinserverListenerFactory(v)
|
||||
default:
|
||||
err = errors.Errorf("unknown type '%s'", s)
|
||||
err = errors.Errorf("unknown type '%s'", t)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
mirror_local:
|
||||
|
||||
jobs:
|
||||
- name: mirror_local
|
||||
type: local
|
||||
|
||||
# snapshot the filesystems matched by the left-hand-side of the mapping
|
||||
|
@ -1,7 +1,7 @@
|
||||
fullbackup_prod1:
|
||||
|
||||
# connect to remote using ssh / stdinserver command
|
||||
jobs:
|
||||
- name: fullbackup_prod1
|
||||
type: pull
|
||||
# connect to remote using ssh / stdinserver command
|
||||
connect:
|
||||
type: ssh+stdinserver
|
||||
host: prod1.example.com
|
||||
|
@ -1,14 +1,30 @@
|
||||
fullbackup_prod1:
|
||||
global:
|
||||
serve:
|
||||
stdinserver:
|
||||
# Directory where AF_UNIX sockets for stdinserver command are placed.
|
||||
#
|
||||
# `zrepl stdinserver CLIENT_IDENTITY`
|
||||
# * connects to the socket in $sockdir/CLIENT_IDENTITY
|
||||
# * sends its stdin / stdout file descriptors to the `zrepl daemon` process (see cmsg(3))
|
||||
# * does nothing more
|
||||
#
|
||||
# This enables a setup where `zrepl daemon` is not directly exposed to the internet
|
||||
# but instead all traffic is tunnelled through SSH.
|
||||
# The server with the source job has an authorized_keys file entry for the public key
|
||||
# used by the corresponding pull job
|
||||
#
|
||||
# command="/mnt/zrepl stdinserver CLIENT_IDENTITY" ssh-ed25519 AAAAC3NzaC1E... zrepl@pullingserver
|
||||
#
|
||||
# Below is the default value.
|
||||
sockdir: /var/run/zrepl/stdinserver
|
||||
|
||||
jobs:
|
||||
|
||||
- name: fullbackup_prod1
|
||||
# expect remote to connect via ssh+stdinserver with fullbackup_prod1 as client_identity
|
||||
type: source
|
||||
serve:
|
||||
# Creates an AF_UNIX socket with name client_identity in a well-known directory
|
||||
# private to the zrepl user (which runs both the zrepld and the stdinserver command via authorized_keys)
|
||||
# The stdinserver command connects to that socket and sends its stdin and stdout
|
||||
# file descriptors over that UNIX socket to the zrepld.
|
||||
# This avoids additional memory-to-memory copies and is more portable than splice(2) on Linux.
|
||||
type: stdinserver
|
||||
type: stdinserver # see global.serve.stdinserver for explanation
|
||||
client_identity: fullbackup_prod1
|
||||
|
||||
# snapshot these filesystems every 10m with zrepl_ as prefix
|
||||
|
@ -1,4 +1,5 @@
|
||||
fullbackup_prod1:
|
||||
jobs:
|
||||
- name: fullbackup_prod1
|
||||
|
||||
# expect remote to connect via ssh+stdinserver with fullbackup_prod1 as client_identity
|
||||
type: push-sink
|
||||
|
@ -1,4 +1,5 @@
|
||||
fullbackup_prod1:
|
||||
jobs:
|
||||
- name: fullbackup_prod1
|
||||
|
||||
# connect to remote using ssh / stdinserver command
|
||||
type: push
|
||||
|
Loading…
Reference in New Issue
Block a user