Simplify CLI by requiring explicit job names.
Job names are derived from job type + user-defined name in config file
CLI now has subcommands corresponding 1:1 to the config file sections:
push,pull,autosnap,prune
A subcommand always expects a job name, thus executes exactly one job.
Dict-style syntax also used for PullACL and Sink sections.
jobrun package is currently only used for autosnap, all others need to
be invoked repeatedly via external tool.
Plan is to re-integrate jobrun in an explicit daemon-mode (subcommand).
This commit is contained in:
+109
-70
@@ -19,6 +19,13 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
JobSectionPush string = "push"
|
||||
JobSectionPull string = "pull"
|
||||
JobSectionPrune string = "prune"
|
||||
JobSectionAutosnap string = "autosnap"
|
||||
)
|
||||
|
||||
type Pool struct {
|
||||
Name string
|
||||
Transport Transport
|
||||
@@ -43,12 +50,14 @@ type SSHTransport struct {
|
||||
}
|
||||
|
||||
type Push struct {
|
||||
JobName string // for use with jobrun package
|
||||
To *Pool
|
||||
Filter zfs.DatasetMapping
|
||||
InitialReplPolicy rpc.InitialReplPolicy
|
||||
RepeatStrategy jobrun.RepeatStrategy
|
||||
}
|
||||
type Pull struct {
|
||||
JobName string // for use with jobrun package
|
||||
From *Pool
|
||||
Mapping zfs.DatasetMapping
|
||||
InitialReplPolicy rpc.InitialReplPolicy
|
||||
@@ -61,27 +70,27 @@ type ClientMapping struct {
|
||||
}
|
||||
|
||||
type Prune struct {
|
||||
Name string
|
||||
JobName string // for use with jobrun package
|
||||
DatasetFilter zfs.DatasetMapping
|
||||
SnapshotFilter zfs.FilesystemVersionFilter
|
||||
RetentionPolicy *RetentionGrid // TODO abstract interface to support future policies?
|
||||
}
|
||||
|
||||
type Autosnap struct {
|
||||
Name string
|
||||
JobName string // for use with jobrun package
|
||||
Prefix string
|
||||
Interval jobrun.RepeatStrategy
|
||||
DatasetFilter zfs.DatasetMapping
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Pools []Pool
|
||||
Pushs []Push
|
||||
Pulls []Pull
|
||||
Sinks []ClientMapping
|
||||
PullACLs []ClientMapping
|
||||
Prunes []Prune
|
||||
Autosnaps []Autosnap
|
||||
Pools map[string]*Pool
|
||||
Pushs map[string]*Push // job name -> job
|
||||
Pulls map[string]*Pull // job name -> job
|
||||
Sinks map[string]*ClientMapping // client identity -> mapping
|
||||
PullACLs map[string]*ClientMapping // client identity -> mapping
|
||||
Prunes map[string]*Prune // job name -> job
|
||||
Autosnaps map[string]*Autosnap // job name -> job
|
||||
}
|
||||
|
||||
func ParseConfig(path string) (config Config, err error) {
|
||||
@@ -106,13 +115,12 @@ func parseMain(root map[string]interface{}) (c Config, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
poolLookup := func(name string) (*Pool, error) {
|
||||
for _, pool := range c.Pools {
|
||||
if pool.Name == name {
|
||||
return &pool, nil
|
||||
}
|
||||
poolLookup := func(name string) (pool *Pool, err error) {
|
||||
pool = c.Pools[name]
|
||||
if pool == nil {
|
||||
err = fmt.Errorf("pool '%s' not defined", name)
|
||||
}
|
||||
return nil, errors.New(fmt.Sprintf("pool '%s' not defined", name))
|
||||
return
|
||||
}
|
||||
|
||||
if c.Pushs, err = parsePushs(root["pushs"], poolLookup); err != nil {
|
||||
@@ -133,23 +141,32 @@ func parseMain(root map[string]interface{}) (c Config, err error) {
|
||||
if c.Autosnaps, err = parseAutosnaps(root["autosnap"]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func parsePools(v interface{}) (pools []Pool, err error) {
|
||||
func fullJobName(section, name string) (full string, err error) {
|
||||
if len(name) < 1 {
|
||||
err = fmt.Errorf("job name not set")
|
||||
return
|
||||
}
|
||||
full = fmt.Sprintf("%s.%s", section, name)
|
||||
return
|
||||
}
|
||||
|
||||
asList := make([]struct {
|
||||
Name string
|
||||
func parsePools(v interface{}) (pools map[string]*Pool, err error) {
|
||||
|
||||
asMap := make(map[string]struct {
|
||||
Transport map[string]interface{}
|
||||
}, 0)
|
||||
if err = mapstructure.Decode(v, &asList); err != nil {
|
||||
if err = mapstructure.Decode(v, &asMap); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pools = make([]Pool, len(asList))
|
||||
for i, p := range asList {
|
||||
pools = make(map[string]*Pool, len(asMap))
|
||||
for name, p := range asMap {
|
||||
|
||||
if p.Name == rpc.LOCAL_TRANSPORT_IDENTITY {
|
||||
if name == rpc.LOCAL_TRANSPORT_IDENTITY {
|
||||
err = errors.New(fmt.Sprintf("pool name '%s' reserved for local pulls", rpc.LOCAL_TRANSPORT_IDENTITY))
|
||||
return
|
||||
}
|
||||
@@ -158,8 +175,8 @@ func parsePools(v interface{}) (pools []Pool, err error) {
|
||||
if transport, err = parseTransport(p.Transport); err != nil {
|
||||
return
|
||||
}
|
||||
pools[i] = Pool{
|
||||
Name: p.Name,
|
||||
pools[name] = &Pool{
|
||||
Name: name,
|
||||
Transport: transport,
|
||||
}
|
||||
}
|
||||
@@ -194,31 +211,34 @@ func parseTransport(it map[string]interface{}) (t Transport, err error) {
|
||||
|
||||
type poolLookup func(name string) (*Pool, error)
|
||||
|
||||
func parsePushs(v interface{}, pl poolLookup) (p []Push, err error) {
|
||||
func parsePushs(v interface{}, pl poolLookup) (p map[string]*Push, err error) {
|
||||
|
||||
asList := make([]struct {
|
||||
asMap := make(map[string]struct {
|
||||
To string
|
||||
Filter map[string]string
|
||||
InitialReplPolicy string
|
||||
Repeat map[string]string
|
||||
}, 0)
|
||||
|
||||
if err = mapstructure.Decode(v, &asList); err != nil {
|
||||
if err = mapstructure.Decode(v, &asMap); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
p = make([]Push, len(asList))
|
||||
p = make(map[string]*Push, len(asMap))
|
||||
|
||||
for i, e := range asList {
|
||||
for name, e := range asMap {
|
||||
|
||||
var toPool *Pool
|
||||
if toPool, err = pl(e.To); err != nil {
|
||||
return
|
||||
}
|
||||
push := Push{
|
||||
push := &Push{
|
||||
To: toPool,
|
||||
}
|
||||
|
||||
if push.JobName, err = fullJobName(JobSectionPush, name); err != nil {
|
||||
return
|
||||
}
|
||||
if push.Filter, err = parseComboMapping(e.Filter); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -231,34 +251,39 @@ func parsePushs(v interface{}, pl poolLookup) (p []Push, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
p[i] = push
|
||||
p[name] = push
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func parsePulls(v interface{}, pl poolLookup) (p []Pull, err error) {
|
||||
func parsePulls(v interface{}, pl poolLookup) (p map[string]*Pull, err error) {
|
||||
|
||||
asList := make([]struct {
|
||||
asMap := make(map[string]struct {
|
||||
From string
|
||||
Mapping map[string]string
|
||||
InitialReplPolicy string
|
||||
Repeat map[string]string
|
||||
}, 0)
|
||||
|
||||
if err = mapstructure.Decode(v, &asList); err != nil {
|
||||
if err = mapstructure.Decode(v, &asMap); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
p = make([]Pull, len(asList))
|
||||
p = make(map[string]*Pull, len(asMap))
|
||||
|
||||
for i, e := range asList {
|
||||
for name, e := range asMap {
|
||||
|
||||
if len(e.From) < 1 {
|
||||
err = fmt.Errorf("source pool not set (from attribute is empty)")
|
||||
return
|
||||
}
|
||||
|
||||
var fromPool *Pool
|
||||
|
||||
if e.From == rpc.LOCAL_TRANSPORT_IDENTITY {
|
||||
fromPool = &Pool{
|
||||
Name: "local",
|
||||
Name: rpc.LOCAL_TRANSPORT_IDENTITY,
|
||||
Transport: LocalTransport{},
|
||||
}
|
||||
} else {
|
||||
@@ -267,9 +292,12 @@ func parsePulls(v interface{}, pl poolLookup) (p []Pull, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
pull := Pull{
|
||||
pull := &Pull{
|
||||
From: fromPool,
|
||||
}
|
||||
if pull.JobName, err = fullJobName(JobSectionPull, name); err != nil {
|
||||
return
|
||||
}
|
||||
if pull.Mapping, err = parseComboMapping(e.Mapping); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -280,7 +308,7 @@ func parsePulls(v interface{}, pl poolLookup) (p []Pull, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
p[i] = pull
|
||||
p[name] = pull
|
||||
}
|
||||
|
||||
return
|
||||
@@ -337,35 +365,35 @@ func expectList(v interface{}) (asList []interface{}, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func parseClientMappings(v interface{}) (cm []ClientMapping, err error) {
|
||||
func parseClientMappings(v interface{}) (cm map[string]*ClientMapping, err error) {
|
||||
|
||||
var asList []interface{}
|
||||
if asList, err = expectList(v); err != nil {
|
||||
var asMap map[string]interface{}
|
||||
if err = mapstructure.Decode(v, &asMap); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cm = make([]ClientMapping, len(asList))
|
||||
cm = make(map[string]*ClientMapping, len(asMap))
|
||||
|
||||
for i, e := range asList {
|
||||
var m ClientMapping
|
||||
if m, err = parseClientMapping(e); err != nil {
|
||||
for identity, e := range asMap {
|
||||
var m *ClientMapping
|
||||
if m, err = parseClientMapping(e, identity); err != nil {
|
||||
return
|
||||
}
|
||||
cm[i] = m
|
||||
cm[identity] = m
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseClientMapping(v interface{}) (s ClientMapping, err error) {
|
||||
func parseClientMapping(v interface{}, identity string) (s *ClientMapping, err error) {
|
||||
t := struct {
|
||||
From string
|
||||
Mapping map[string]string
|
||||
}{}
|
||||
if err = mapstructure.Decode(v, &t); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.From = t.From
|
||||
s = &ClientMapping{
|
||||
From: identity,
|
||||
}
|
||||
s.Mapping, err = parseComboMapping(t.Mapping)
|
||||
return
|
||||
}
|
||||
@@ -457,26 +485,29 @@ func (t *LocalTransport) SetHandler(handler rpc.RPCHandler) {
|
||||
t.Handler = handler
|
||||
}
|
||||
|
||||
func parsePrunes(m interface{}) (rets []Prune, err error) {
|
||||
func parsePrunes(m interface{}) (rets map[string]*Prune, err error) {
|
||||
|
||||
asList := make([]map[string]interface{}, 0)
|
||||
asList := make(map[string]map[string]interface{}, 0)
|
||||
if err = mapstructure.Decode(m, &asList); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rets = make([]Prune, len(asList))
|
||||
rets = make(map[string]*Prune, len(asList))
|
||||
|
||||
for i, e := range asList {
|
||||
if rets[i], err = parsePrune(e); err != nil {
|
||||
err = fmt.Errorf("cannot parse prune job #%d: %s", i+1, err)
|
||||
for name, e := range asList {
|
||||
var prune *Prune
|
||||
if prune, err = parsePrune(e, name); err != nil {
|
||||
err = fmt.Errorf("cannot parse prune job %s: %s", name, err)
|
||||
return
|
||||
}
|
||||
rets[name] = prune
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func parsePrune(e map[string]interface{}) (prune Prune, err error) {
|
||||
func parsePrune(e map[string]interface{}, name string) (prune *Prune, err error) {
|
||||
|
||||
// Only support grid policy for now
|
||||
policyName, ok := e["policy"]
|
||||
if !ok || policyName != "grid" {
|
||||
@@ -485,7 +516,6 @@ func parsePrune(e map[string]interface{}) (prune Prune, err error) {
|
||||
}
|
||||
|
||||
var i struct {
|
||||
Name string
|
||||
Grid string
|
||||
DatasetFilter map[string]string `mapstructure:"dataset_filter"`
|
||||
SnapshotFilter map[string]string `mapstructure:"snapshot_filter"`
|
||||
@@ -495,7 +525,11 @@ func parsePrune(e map[string]interface{}) (prune Prune, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
prune.Name = i.Name
|
||||
prune = &Prune{}
|
||||
|
||||
if prune.JobName, err = fullJobName(JobSectionPrune, name); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse grid policy
|
||||
intervals, err := parseRetentionGridIntervalsString(i.Grid)
|
||||
@@ -636,30 +670,31 @@ func parseSnapshotFilter(fm map[string]string) (snapFilter zfs.FilesystemVersion
|
||||
return
|
||||
}
|
||||
|
||||
func parseAutosnaps(m interface{}) (snaps []Autosnap, err error) {
|
||||
func parseAutosnaps(m interface{}) (snaps map[string]*Autosnap, err error) {
|
||||
|
||||
asList := make([]map[string]interface{}, 0)
|
||||
if err = mapstructure.Decode(m, &asList); err != nil {
|
||||
asMap := make(map[string]interface{}, 0)
|
||||
if err = mapstructure.Decode(m, &asMap); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
snaps = make([]Autosnap, len(asList))
|
||||
snaps = make(map[string]*Autosnap, len(asMap))
|
||||
|
||||
for i, e := range asList {
|
||||
if snaps[i], err = parseAutosnap(e); err != nil {
|
||||
err = fmt.Errorf("cannot parse autonsap job #%d: %s", i+1, err)
|
||||
for name, e := range asMap {
|
||||
var snap *Autosnap
|
||||
if snap, err = parseAutosnap(e, name); err != nil {
|
||||
err = fmt.Errorf("cannot parse autonsap job %s: %s", name, err)
|
||||
return
|
||||
}
|
||||
snaps[name] = snap
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func parseAutosnap(m map[string]interface{}) (a Autosnap, err error) {
|
||||
func parseAutosnap(m interface{}, name string) (a *Autosnap, err error) {
|
||||
|
||||
var i struct {
|
||||
Name string
|
||||
Prefix string
|
||||
Interval string
|
||||
DatasetFilter map[string]string `mapstructure:"dataset_filter"`
|
||||
@@ -670,7 +705,11 @@ func parseAutosnap(m map[string]interface{}) (a Autosnap, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
a.Name = i.Name
|
||||
a = &Autosnap{}
|
||||
|
||||
if a.JobName, err = fullJobName(JobSectionAutosnap, name); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if len(i.Prefix) < 1 {
|
||||
err = fmt.Errorf("prefix must not be empty")
|
||||
|
||||
Reference in New Issue
Block a user