Skip to content

Commit

Permalink
Allow include config files
Browse files Browse the repository at this point in the history
Strict toml read config
Close #70
  • Loading branch information
rekby committed May 25, 2019
1 parent 1ab68cd commit 62c2da2
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 8 deletions.
2 changes: 1 addition & 1 deletion cmd/a_main-packr.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

101 changes: 95 additions & 6 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ package main

import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strings"

"github.com/gobuffalo/packr"

Expand All @@ -20,10 +25,12 @@ import (
)

type ConfigGeneral struct {
IssueTimeout int
StorageDir string
AcmeServer string
StoreJSONMetadata bool
IssueTimeout int
StorageDir string
AcmeServer string
StoreJSONMetadata bool
IncludeConfigs []string
MaxConfigFilesRead int
}

//go:generate packr
Expand All @@ -50,15 +57,20 @@ type logConfig struct {
}

var (
_config *configType
_config *configType
parsedConfigFiles = 0
)

func getConfig(ctx context.Context) *configType {
if _config == nil {
logger := zc.LNop(ctx).With(zap.String("config_file", *configFileP))
logger.Info("Read config")
_config = readConfig(ctx, *configFileP)
_config = &configType{}
mergeConfigBytes(ctx, _config, defaultConfig(ctx), "default")
mergeConfigByTemplate(ctx, _config, *configFileP)
applyFlags(ctx, _config)
logger.Info("Parse configs finished", zap.Int("readed_files", parsedConfigFiles),
zap.Int("max_read_files", _config.General.MaxConfigFilesRead))
}
return _config
}
Expand Down Expand Up @@ -95,3 +107,80 @@ func readConfig(ctx context.Context, file string) *configType {
}
return &res
}

func mergeConfigByTemplate(ctx context.Context, c *configType, filepathTemplate string) {
logger := zc.LNop(ctx).With(zap.String("config_file", filepathTemplate))
if !hasMeta(filepathTemplate) {
mergeConfigByFilepath(ctx, c, filepathTemplate)
return
}

filenames, err := filepath.Glob(filepathTemplate)
log.DebugFatal(logger, err, "Expand config file template",
zap.String("filepathTemplate", filepathTemplate), zap.Strings("files", filenames))
for _, filename := range filenames {
mergeConfigByFilepath(ctx, c, filename)
}
}

func mergeConfigByFilepath(ctx context.Context, c *configType, filename string) {
logger := zc.LNop(ctx).With(zap.String("config_file", filename))
if parsedConfigFiles > c.General.MaxConfigFilesRead {
logger.Fatal("Exceed max config files read count", zap.Int("MaxConfigFilesRead", c.General.MaxConfigFilesRead))
}
parsedConfigFiles++

var err error
if !filepath.IsAbs(filename) {
var filepathNew string
filepathNew, err = filepath.Abs(filename)
log.DebugFatal(logger, err, "Convert filepath to absolute",
zap.String("old", filename), zap.String("new", filepathNew))
filename = filepathNew
}

content, err := ioutil.ReadFile(filename)
log.DebugFatal(logger, err, "Read filename")

dir, err := os.Getwd()
log.DebugFatal(logger, err, "Current workdir", zap.String("dir", dir))
fileDir := filepath.Dir(filename)
if dir != fileDir {
err = os.Chdir(fileDir)
log.DebugFatal(logger, err, "Chdir to config filename directory")
defer func() {
err = os.Chdir(dir)
log.DebugFatal(logger, err, "Restore workdir to", zap.String("dir", dir))
}()
}
mergeConfigBytes(ctx, c, content, filename)
}

// hasMeta reports whether path contains any of the magic characters
// recognized by Match.
// copy from filepath module
func hasMeta(path string) bool {
magicChars := `*?[`
if runtime.GOOS != "windows" {
magicChars = `*?[\`
}
return strings.ContainsAny(path, magicChars)
}

func mergeConfigBytes(ctx context.Context, c *configType, content []byte, file string) {
// for prevent loop by existed included
c.General.IncludeConfigs = nil

meta, err := toml.Decode(string(content), c)
if err == nil && len(meta.Undecoded()) > 0 {
err = fmt.Errorf("unknown fields: %v", meta.Undecoded())
}
log.InfoFatal(zc.L(ctx), err, "Parse config file", zap.String("config_file", file))

if len(c.General.IncludeConfigs) > 0 {
includeConfigs := c.General.IncludeConfigs // need save because it will reset while merging
for _, file := range includeConfigs {
mergeConfigByTemplate(ctx, c, file)
}
}
}
2 changes: 1 addition & 1 deletion cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package main
import "flag"

var (
configFileP = flag.String("config", "", "Path to config file. Empty for no read config.")
configFileP = flag.String("config", "config.tom[l]", "Path to config file. Internally expand glob syntax.")
defaultConfigP = flag.Bool("print-default-config", false, "Write default config to stdout and exit.")
versionP = flag.Bool("version", false, "print version and exit")
testAcmeServerP = flag.Bool("test-acme-server", false, "Use test acme server, instead address from config")
Expand Down
11 changes: 11 additions & 0 deletions cmd/static/default-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ StoreJSONMetadata = true
#Test server: https://acme-staging-v02.api.letsencrypt.org/directory
AcmeServer = "https://acme-v01.api.letsencrypt.org/directory"

# Include other config files
# It support glob syntax
# If it has path without template - the file must exist.
# For allow optional include file - it can contain some glob symbol
# Included configs merge with current readed state.
# example=[ "config.tom[l]" ]
IncludeConfigs = []

# For prevent infinite loop and consume all memory if cycle in includes
MaxConfigFilesRead = 10000

[Log]
EnableLogToFile = true
EnableLogToStdErr = true
Expand Down

0 comments on commit 62c2da2

Please sign in to comment.