Support .d directory for k3s config file (#3162)

Configuration will be loaded from config.yaml and then config.yaml.d/*.(yaml|yml) in
alphanumeric order.  The merging is done by just taking the last value of
a key found, so LIFO for keys.  Slices are not merged but replaced.

Signed-off-by: Darren Shepherd <darren@rancher.com>
This commit is contained in:
Darren Shepherd 2021-04-15 11:29:24 -07:00 committed by GitHub
parent 601c4984f5
commit a0a1071aa5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 77 additions and 17 deletions

View File

@ -6,8 +6,10 @@ import (
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"github.com/rancher/k3s/pkg/agent/util"
"github.com/rancher/wrangler/pkg/data/convert"
"gopkg.in/yaml.v2"
)
@ -19,7 +21,7 @@ type Parser struct {
DefaultConfig string
}
// Parser will parse an os.Args style slice looking for Parser.FlagNames after Parse.After.
// Parse will parse an os.Args style slice looking for Parser.FlagNames after Parse.After.
// It will read the parameter value of Parse.FlagNames and read the file, appending all flags directly after
// the Parser.After value. This means a the non-config file flags will override, or if a slice append to, the config
// file values.
@ -110,31 +112,69 @@ func (p *Parser) findStart(args []string) ([]string, []string, bool) {
return args, nil, false
}
func dotDFiles(basefile string) (result []string, _ error) {
files, err := ioutil.ReadDir(basefile + ".d")
if os.IsNotExist(err) {
return nil, nil
} else if err != nil {
return nil, err
}
for _, file := range files {
if file.IsDir() || !util.HasSuffixI(file.Name(), ".yaml", ".yml") {
continue
}
result = append(result, filepath.Join(basefile+".d", file.Name()))
}
return
}
func readConfigFile(file string) (result []string, _ error) {
bytes, err := readConfigFileData(file)
files, err := dotDFiles(file)
if err != nil {
return nil, err
}
data := yaml.MapSlice{}
if err := yaml.Unmarshal(bytes, &data); err != nil {
_, err = os.Stat(file)
if os.IsNotExist(err) && len(files) > 0 {
} else if err != nil {
return nil, err
} else {
files = append([]string{file}, files...)
}
for _, i := range data {
k, v := convert.ToString(i.Key), i.Value
prefix := "--"
if len(k) == 1 {
prefix = "-"
keySeen := map[string]bool{}
for i := len(files) - 1; i >= 0; i-- {
file := files[i]
bytes, err := readConfigFileData(file)
if err != nil {
return nil, err
}
if slice, ok := v.([]interface{}); ok {
for _, v := range slice {
result = append(result, prefix+k+"="+convert.ToString(v))
data := yaml.MapSlice{}
if err := yaml.Unmarshal(bytes, &data); err != nil {
return nil, err
}
for _, i := range data {
k, v := convert.ToString(i.Key), i.Value
if keySeen[k] {
continue
}
keySeen[k] = true
prefix := "--"
if len(k) == 1 {
prefix = "-"
}
if slice, ok := v.([]interface{}); ok {
for _, v := range slice {
result = append(result, prefix+k+"="+convert.ToString(v))
}
} else {
str := convert.ToString(v)
result = append(result, prefix+k+"="+str)
}
} else {
str := convert.ToString(v)
result = append(result, prefix+k+"="+str)
}
}

View File

@ -150,8 +150,9 @@ func TestConfigFile(t *testing.T) {
func TestParse(t *testing.T) {
testDataOutput := []string{
"--foo-bar=baz",
"--foo-bar=bar-foo",
"--a-slice=1",
"--a-slice=1.5",
"--a-slice=2",
"--a-slice=",
"--a-slice=three",
@ -205,7 +206,7 @@ func TestParse(t *testing.T) {
input: []string{"server", "-c=missing"},
output: []string{"server", "-c=missing"},
what: "fail when missing config",
err: "open missing: no such file or directory",
err: "stat missing: no such file or directory",
},
{
parser: Parser{
@ -217,6 +218,16 @@ func TestParse(t *testing.T) {
output: append(append([]string{"before", "server"}, testDataOutput...), "before", "-c", "./testdata/data.yaml", "after"),
what: "read config file",
},
{
parser: Parser{
After: []string{"server", "agent"},
FlagNames: []string{"-c", "--config"},
DefaultConfig: "missing",
},
input: []string{"before", "server", "before", "-c", "./testdata/data.yaml.d/02-data.yaml", "after"},
output: []string{"before", "server", "--foo-bar=bar-foo", "before", "-c", "./testdata/data.yaml.d/02-data.yaml", "after"},
what: "read single config file",
},
}
for _, testCase := range testCases {

View File

@ -0,0 +1,7 @@
foo-bar: get-overriden
a-slice:
- 1
- "1.5"
- "2"
- ""
- three

View File

@ -0,0 +1 @@
foo-bar: ignored

View File

@ -0,0 +1 @@
foo-bar: bar-foo