diff --git a/pkg/config/config.go b/pkg/config/config.go index 3248035..eb5882f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -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 +} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index b966edf..d7614e4 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -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()) + }) + }) })