mirror of
https://github.com/kairos-io/kairos-agent.git
synced 2025-06-03 01:44:53 +00:00
seedling: Glob all configs found (#502)
* 🌱 Glob all configs found This allows to read all the configs found during scanning - this is especially useful in first boot as it allows to have separate config file logic split into several files Signed-off-by: Ettore Di Giacinto <mudler@users.noreply.github.com> * 🌱 Simplify config merge logic Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * WIP Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> * 🌱 Make the function work Signed-off-by: Ettore Di Giacinto <mudler@users.noreply.github.com> Signed-off-by: mudler <mudler@kairos.io> Signed-off-by: Ettore Di Giacinto <mudler@users.noreply.github.com> Signed-off-by: Dimitris Karakasilis <dimitris@karakasilis.me> Signed-off-by: mudler <mudler@kairos.io> Co-authored-by: Dimitris Karakasilis <dimitris@karakasilis.me>
This commit is contained in:
parent
8bf1babcce
commit
3d92e4ae43
@ -12,6 +12,7 @@ import (
|
||||
retry "github.com/avast/retry-go"
|
||||
"github.com/kairos-io/kairos/pkg/machine"
|
||||
"github.com/kairos-io/kairos/sdk/bundles"
|
||||
"github.com/kairos-io/kairos/sdk/unstructured"
|
||||
yip "github.com/mudler/yip/pkg/schema"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
@ -33,7 +34,6 @@ type Config struct {
|
||||
Install *Install `yaml:"install,omitempty"`
|
||||
//cloudFileContent string
|
||||
originalData map[string]interface{}
|
||||
location string
|
||||
header string
|
||||
ConfigURL string `yaml:"config_url,omitempty"`
|
||||
Options map[string]string `yaml:"options,omitempty"`
|
||||
@ -87,10 +87,6 @@ func (c Config) Unmarshal(o interface{}) error {
|
||||
return yaml.Unmarshal([]byte(c.String()), o)
|
||||
}
|
||||
|
||||
func (c Config) Location() string {
|
||||
return c.location
|
||||
}
|
||||
|
||||
func (c Config) Data() map[string]interface{} {
|
||||
return c.originalData
|
||||
}
|
||||
@ -110,69 +106,23 @@ func (c Config) String() string {
|
||||
return string(dat)
|
||||
}
|
||||
|
||||
func (c Config) IsValid() bool {
|
||||
return c.Install != nil ||
|
||||
c.ConfigURL != "" ||
|
||||
len(c.Bundles) != 0
|
||||
}
|
||||
|
||||
func Scan(opts ...Option) (c *Config, err error) {
|
||||
|
||||
o := &Options{}
|
||||
|
||||
if err := o.Apply(opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dir := o.ScanDir
|
||||
|
||||
c = &Config{}
|
||||
func allFiles(dir []string) []string {
|
||||
files := []string{}
|
||||
for _, d := range dir {
|
||||
if f, err := listFiles(d); err == nil {
|
||||
files = append(files, f...)
|
||||
}
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
configFound := false
|
||||
lastYamlFileFound := ""
|
||||
|
||||
// Scanning happens as best-effort, therefore unmarshalling skips errors here.
|
||||
for _, f := range files {
|
||||
if fileSize(f) > 1.0 {
|
||||
//fmt.Println("warning: Skipping file ", f, "as exceeds 1 MB in size")
|
||||
continue
|
||||
}
|
||||
b, err := os.ReadFile(f)
|
||||
if err == nil {
|
||||
// best effort. skip lint checks
|
||||
yaml.Unmarshal(b, c) //nolint:errcheck
|
||||
if exists, header := HasHeader(string(b), ""); c.IsValid() || exists {
|
||||
c.location = f
|
||||
yaml.Unmarshal(b, &c.originalData) //nolint:errcheck
|
||||
configFound = true
|
||||
if exists {
|
||||
c.header = header
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// record back the only yaml file found (if any)
|
||||
if strings.HasSuffix(strings.ToLower(f), "yaml") || strings.HasSuffix(strings.ToLower(f), "yml") {
|
||||
lastYamlFileFound = f
|
||||
}
|
||||
}
|
||||
func Scan(opts ...Option) (c *Config, err error) {
|
||||
o := &Options{}
|
||||
if err := o.Apply(opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// use last recorded if no config is found valid
|
||||
if !configFound && lastYamlFileFound != "" {
|
||||
b, err := os.ReadFile(lastYamlFileFound)
|
||||
if err == nil {
|
||||
yaml.Unmarshal(b, c) //nolint:errcheck
|
||||
c.location = lastYamlFileFound
|
||||
yaml.Unmarshal(b, &c.originalData) //nolint:errcheck
|
||||
}
|
||||
}
|
||||
c = parseConfig(o.ScanDir)
|
||||
|
||||
if o.MergeBootCMDLine {
|
||||
d, err := machine.DotToYAML(o.BootCMDLineFile)
|
||||
@ -253,7 +203,9 @@ func listFiles(dir string) ([]string, error) {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
content = append(content, path)
|
||||
if !info.IsDir() {
|
||||
content = append(content, path)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
@ -307,3 +259,57 @@ func MergeYAML(objs ...interface{}) ([]byte, error) {
|
||||
func AddHeader(header, data string) string {
|
||||
return fmt.Sprintf("%s\n%s", header, data)
|
||||
}
|
||||
|
||||
func FindYAMLWithKey(s string, opts ...Option) ([]string, error) {
|
||||
o := &Options{}
|
||||
|
||||
result := []string{}
|
||||
if err := o.Apply(opts...); err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
files := allFiles(o.ScanDir)
|
||||
|
||||
for _, f := range files {
|
||||
dat, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
fmt.Printf("warning: skipping file '%s' - %s\n", f, err.Error())
|
||||
}
|
||||
|
||||
found, err := unstructured.YAMLHasKey(s, dat)
|
||||
if err != nil {
|
||||
fmt.Printf("warning: skipping file '%s' - %s\n", f, err.Error())
|
||||
}
|
||||
|
||||
if found {
|
||||
result = append(result, f)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// parseConfig merges all config back in one structure.
|
||||
func parseConfig(dir []string) *Config {
|
||||
files := allFiles(dir)
|
||||
c := &Config{}
|
||||
for _, f := range files {
|
||||
if fileSize(f) > 1.0 {
|
||||
fmt.Printf("warning: skipping %s. too big (>1MB)\n", f)
|
||||
continue
|
||||
}
|
||||
b, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
fmt.Printf("warning: skipping %s. %s\n", f, err.Error())
|
||||
continue
|
||||
}
|
||||
yaml.Unmarshal(b, c) //nolint:errcheck
|
||||
yaml.Unmarshal(b, &c.originalData) //nolint:errcheck
|
||||
if exists, header := HasHeader(string(b), ""); exists {
|
||||
c.header = header
|
||||
}
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
. "github.com/kairos-io/kairos/pkg/config"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type TConfig struct {
|
||||
@ -30,20 +31,19 @@ type TConfig struct {
|
||||
} `yaml:"kairos"`
|
||||
}
|
||||
|
||||
var _ = Describe("Get config", func() {
|
||||
var _ = Describe("Config", func() {
|
||||
var d string
|
||||
BeforeEach(func() {
|
||||
d, _ = os.MkdirTemp("", "xxxx")
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
if d != "" {
|
||||
os.RemoveAll(d)
|
||||
}
|
||||
})
|
||||
|
||||
Context("directory", func() {
|
||||
|
||||
var d string
|
||||
BeforeEach(func() {
|
||||
d, _ = os.MkdirTemp("", "xxxx")
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
if d != "" {
|
||||
os.RemoveAll(d)
|
||||
}
|
||||
})
|
||||
|
||||
headerCheck := func(c *Config) {
|
||||
ok, header := HasHeader(c.String(), DefaultHeader)
|
||||
ExpectWithOffset(1, ok).To(BeTrue())
|
||||
@ -60,6 +60,37 @@ var _ = Describe("Get config", func() {
|
||||
Expect(c.Options["foo"]).To(Equal("bar"))
|
||||
})
|
||||
|
||||
It("reads multiple config files", func() {
|
||||
var cc string = `#kairos-config
|
||||
baz: bar
|
||||
kairos:
|
||||
network_token: foo
|
||||
`
|
||||
var c2 string = `
|
||||
b: f
|
||||
c: d
|
||||
`
|
||||
|
||||
err := os.WriteFile(filepath.Join(d, "test"), []byte(cc), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
err = os.WriteFile(filepath.Join(d, "b"), []byte(c2), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
c, err := Scan(Directories(d))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(c).ToNot(BeNil())
|
||||
providerCfg := &TConfig{}
|
||||
err = c.Unmarshal(providerCfg)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(providerCfg.Kairos).ToNot(BeNil())
|
||||
Expect(providerCfg.Kairos.NetworkToken).To(Equal("foo"))
|
||||
all := map[string]string{}
|
||||
yaml.Unmarshal([]byte(c.String()), &all)
|
||||
Expect(all["b"]).To(Equal("f"))
|
||||
Expect(all["baz"]).To(Equal("bar"))
|
||||
})
|
||||
|
||||
It("reads config file greedly", func() {
|
||||
|
||||
var cc string = `#kairos-config
|
||||
@ -150,4 +181,54 @@ config_url: "https://gist.githubusercontent.com/mudler/7e3d0426fce8bfaaeb2644f83
|
||||
headerCheck(c)
|
||||
})
|
||||
})
|
||||
|
||||
Describe("FindYAMLWithKey", func() {
|
||||
var c1Path, c2Path string
|
||||
|
||||
BeforeEach(func() {
|
||||
var c1 = `
|
||||
a: 1
|
||||
b:
|
||||
c: foo
|
||||
d:
|
||||
e: bar
|
||||
`
|
||||
|
||||
var c2 = `
|
||||
b:
|
||||
c: foo2
|
||||
`
|
||||
c1Path = filepath.Join(d, "c1.yaml")
|
||||
c2Path = filepath.Join(d, "c2.yaml")
|
||||
|
||||
err := os.WriteFile(c1Path, []byte(c1), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
err = os.WriteFile(c2Path, []byte(c2), os.ModePerm)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("can find a top level key", func() {
|
||||
r, err := FindYAMLWithKey("a", Directories(d))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(r).To(Equal([]string{c1Path}))
|
||||
})
|
||||
|
||||
It("can find a nested key", func() {
|
||||
r, err := FindYAMLWithKey("d.e", Directories(d))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(r).To(Equal([]string{c1Path}))
|
||||
})
|
||||
|
||||
It("returns multiple files when key exists in them", func() {
|
||||
r, err := FindYAMLWithKey("b.c", Directories(d))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(r).To(ContainElements(c1Path, c2Path))
|
||||
})
|
||||
|
||||
It("return an empty list when key is not found", func() {
|
||||
r, err := FindYAMLWithKey("does.not.exist", Directories(d))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(r).To(BeEmpty())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user