mirror of
https://github.com/zrepl/zrepl.git
synced 2025-03-19 18:47:14 +01:00
Added include configuration support
A relatively straight forward change that adds "include" key support in the main configuration file. The config parser uses the list under this key to open the included configuration files parse them as configs and append their jobs to the main config file.
This commit is contained in:
parent
6e125bae97
commit
c92c141b51
@ -5,6 +5,8 @@ import (
|
||||
"log/syslog"
|
||||
"os"
|
||||
"time"
|
||||
"path/filepath"
|
||||
pathpkg "path"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/robfig/cron/v3"
|
||||
@ -24,6 +26,7 @@ const (
|
||||
type Config struct {
|
||||
Jobs []JobEnum `yaml:"jobs,optional"`
|
||||
Global *Global `yaml:"global,optional,fromdefaults"`
|
||||
Include []string `yaml:"include,optional"`
|
||||
}
|
||||
|
||||
func (c *Config) Job(name string) (*JobEnum, error) {
|
||||
@ -657,6 +660,7 @@ var ConfigFileDefaultLocations = []string{
|
||||
|
||||
func ParseConfig(path string) (i *Config, err error) {
|
||||
|
||||
// Parse main configuration file
|
||||
if path == "" {
|
||||
// Try default locations
|
||||
for _, l := range ConfigFileDefaultLocations {
|
||||
@ -679,7 +683,71 @@ func ParseConfig(path string) (i *Config, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
return ParseConfigBytes(bytes)
|
||||
i, err = ParseConfigBytes(bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = ExpandConfigInclude(path, i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return i, err
|
||||
}
|
||||
|
||||
func ExpandConfigInclude(configPath string, config *Config) (err error) {
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var includeConfigPaths []string
|
||||
for _, path := range config.Include {
|
||||
if configPath[0] != '/' {
|
||||
path = pathpkg.Join(pathpkg.Dir(configPath), path)
|
||||
}
|
||||
|
||||
stat, statErr := os.Stat(path)
|
||||
if statErr != nil {
|
||||
return errors.Errorf("Could not open included configuration path: %s", path)
|
||||
}
|
||||
|
||||
if stat.Mode().IsDir() {
|
||||
directoryPaths, err := filepath.Glob(path + "/*.yml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
includeConfigPaths = append(includeConfigPaths, directoryPaths...)
|
||||
} else if stat.Mode().IsRegular() {
|
||||
if extention := filepath.Ext(path); extention != ".yml" {
|
||||
return errors.Errorf("Only .yml files can be included: %s", path)
|
||||
}
|
||||
includeConfigPaths = append(includeConfigPaths, path)
|
||||
} else {
|
||||
return errors.Errorf("Only directories or .yml files can be included: %s", path)
|
||||
}
|
||||
}
|
||||
|
||||
for _, path := range includeConfigPaths {
|
||||
var bytes []byte
|
||||
if bytes, err = os.ReadFile(path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
includedConfig, err := ParseConfigBytes(bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(includedConfig.Include) > 0 {
|
||||
return errors.Errorf("Included configuration files cannot include other files: %s", path)
|
||||
}
|
||||
|
||||
config.Jobs = append(config.Jobs, includedConfig.Jobs...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseConfigBytes(bytes []byte) (*Config, error) {
|
||||
@ -687,6 +755,7 @@ func ParseConfigBytes(bytes []byte) (*Config, error) {
|
||||
if err := yaml.UnmarshalStrict(bytes, &c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if c != nil {
|
||||
return c, nil
|
||||
}
|
||||
|
43
internal/config/config_include_test.go
Normal file
43
internal/config/config_include_test.go
Normal file
@ -0,0 +1,43 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"github.com/kr/pretty"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIncludeSingle(t *testing.T) {
|
||||
path := "./samples/include.yml"
|
||||
|
||||
t.Run(path, func(t *testing.T) {
|
||||
config, err := ParseConfig(path)
|
||||
if err != nil {
|
||||
t.Errorf("error parsing %s:\n%+v", path, err)
|
||||
}
|
||||
|
||||
require.NotNil(t, config)
|
||||
require.NotNil(t, config.Global)
|
||||
require.NotEmpty(t, config.Jobs)
|
||||
|
||||
t.Logf("file: %s", path)
|
||||
t.Log(pretty.Sprint(config))
|
||||
})
|
||||
}
|
||||
|
||||
func TestIncludeDirectory(t *testing.T) {
|
||||
path := "./samples/include_directory.yml"
|
||||
|
||||
t.Run(path, func(t *testing.T) {
|
||||
config, err := ParseConfig(path)
|
||||
if err != nil {
|
||||
t.Errorf("error parsing %s:\n%+v", path, err)
|
||||
}
|
||||
|
||||
require.NotNil(t, config)
|
||||
require.NotNil(t, config.Global)
|
||||
require.NotEmpty(t, config.Jobs)
|
||||
|
||||
t.Logf("file: %s", path)
|
||||
t.Log(pretty.Sprint(config))
|
||||
})
|
||||
}
|
14
internal/config/samples/include.d/snap.yml
Normal file
14
internal/config/samples/include.d/snap.yml
Normal file
@ -0,0 +1,14 @@
|
||||
jobs:
|
||||
- name: snapjob
|
||||
type: snap
|
||||
filesystems: {
|
||||
"tank/frequently_changed<": true,
|
||||
}
|
||||
snapshotting:
|
||||
type: periodic
|
||||
interval: 2m
|
||||
prefix: zrepl_snapjob_
|
||||
pruning:
|
||||
keep:
|
||||
- type: last_n
|
||||
count: 60
|
2
internal/config/samples/include.yml
Normal file
2
internal/config/samples/include.yml
Normal file
@ -0,0 +1,2 @@
|
||||
include:
|
||||
- ./snap.yml
|
2
internal/config/samples/include_directory.yml
Normal file
2
internal/config/samples/include_directory.yml
Normal file
@ -0,0 +1,2 @@
|
||||
include:
|
||||
- ./include.d
|
Loading…
Reference in New Issue
Block a user