Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Sources field to Config #501

Merged
merged 6 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 58 additions & 34 deletions collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,14 @@

type Configs []*Config

type ConfigValues map[string]interface{}

// We don't allow yamls that are plain arrays because is has no use in Kairos
// and there is no way to merge an array yaml with a "map" yaml.
type Config map[string]interface{}
type Config struct {
Sources []string
Values ConfigValues
}

// MergeConfigURL looks for the "config_url" key and if it's found
// it downloads the remote config and merges it with the current one.
Expand Down Expand Up @@ -63,49 +68,53 @@
return c.MergeConfig(remoteConfig)
}

func (c *Config) toMap() (map[string]interface{}, error) {
var result map[string]interface{}
data, err := yaml.Marshal(c)
func (c *Config) valuesCopy() (ConfigValues, error) {
var result ConfigValues
data, err := yaml.Marshal(c.Values)
if err != nil {
return result, err
}

err = yaml.Unmarshal(data, &result)
return result, err
}

func (c *Config) applyMap(i interface{}) error {
data, err := yaml.Marshal(i)
if err != nil {
return err
}

err = yaml.Unmarshal(data, c)
return err
return result, err
}

// MergeConfig merges the config passed as parameter back to the receiver Config.
func (c *Config) MergeConfig(newConfig *Config) error {
var err error

// convert the two configs into maps
aMap, err := c.toMap()
aMap, err := c.valuesCopy()
if err != nil {
return err
}
bMap, err := newConfig.toMap()
bMap, err := newConfig.valuesCopy()
if err != nil {
return err
}

// TODO: Consider removing the `name:` key because in the end we end up with the
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same thing applies to "config_url" which ends up having the value of the last merged config. Maybe we should care about it in this PR?

// value from the last config merged. Ideally we should display the name in the "sources"
// comment next to the file but doing it here is not possible because the configs
// passed, could already be results of various merged thus we don't know which of
// the "sources" should take the "name" next to it.
//
// if _, exists := bMap.Values["name"]; exists {
// delete(bMap.Values, "name")
// }

// deep merge the two maps
cMap, err := DeepMerge(aMap, bMap)
mergedValues, err := DeepMerge(aMap, bMap)
if err != nil {
return err
}
finalConfig := Config{}
finalConfig.Sources = append(c.Sources, newConfig.Sources...)
finalConfig.Values = mergedValues.(ConfigValues)

// apply the result of the deepmerge into the base config
return c.applyMap(cMap)
*c = finalConfig

return nil
}

func mergeSlices(sliceA, sliceB []interface{}) ([]interface{}, error) {
Expand Down Expand Up @@ -148,7 +157,7 @@
return sliceA, nil
}

func deepMergeMaps(a, b map[string]interface{}) (map[string]interface{}, error) {
func deepMergeMaps(a, b ConfigValues) (ConfigValues, error) {
// go through all items in b and merge them to a
for k, v := range b {
current, ok := a[k]
Expand Down Expand Up @@ -190,15 +199,15 @@

// We don't support merging different data structures
if typeA.Kind() != typeB.Kind() {
return map[string]interface{}{}, fmt.Errorf("cannot merge %s with %s", typeA.String(), typeB.String())
return ConfigValues{}, fmt.Errorf("cannot merge %s with %s", typeA.String(), typeB.String())
}

if typeA.Kind() == reflect.Slice {
return mergeSlices(a.([]interface{}), b.([]interface{}))
}

if typeA.Kind() == reflect.Map {
return deepMergeMaps(a.(map[string]interface{}), b.(map[string]interface{}))
return deepMergeMaps(a.(ConfigValues), b.(ConfigValues))
}

// for any other type, b should take precedence
Expand All @@ -207,12 +216,22 @@

// String returns a string which is a Yaml representation of the Config.
func (c *Config) String() (string, error) {
data, err := yaml.Marshal(c)
sourcesComment := ""
config := *c
if len(config.Sources) > 0 {
sourcesComment = "# Sources:\n"
for _, s := range config.Sources {
sourcesComment += fmt.Sprintf("# - %s\n", s)
}
sourcesComment += "\n"
}

data, err := yaml.Marshal(config.Values)
if err != nil {
return "", err
return "", fmt.Errorf("marshalling the config to a string: %s", err)

Check warning on line 231 in collector/collector.go

View check run for this annotation

Codecov / codecov/patch

collector/collector.go#L231

Added line #L231 was not covered by tests
}

return fmt.Sprintf("%s\n\n%s", DefaultHeader, string(data)), nil
return fmt.Sprintf("%s\n\n%s%s", DefaultHeader, sourcesComment, string(data)), nil
}

func (cs Configs) Merge() (*Config, error) {
Expand Down Expand Up @@ -251,7 +270,7 @@
}

if o.Overwrites != "" {
yaml.Unmarshal([]byte(o.Overwrites), &mergedConfig) //nolint:errcheck
yaml.Unmarshal([]byte(o.Overwrites), &mergedConfig.Values) //nolint:errcheck
}

return mergedConfig, nil
Expand Down Expand Up @@ -295,10 +314,12 @@
}

var newConfig Config
err = yaml.Unmarshal(b, &newConfig)
err = yaml.Unmarshal(b, &newConfig.Values)
if err != nil && !nologs {
fmt.Printf("warning: failed to parse config:\n%s\n", err.Error())
}
newConfig.Sources = []string{f}

result = append(result, &newConfig)
} else {
if !nologs {
Expand All @@ -324,16 +345,17 @@
}
continue
}
err = yaml.Unmarshal(read, &newConfig)
err = yaml.Unmarshal(read, &newConfig.Values)
if err != nil {
err = json.Unmarshal(read, &newConfig)
err = json.Unmarshal(read, &newConfig.Values)
if err != nil {
if !nologs {
fmt.Printf("Error unmarshalling config(error: %s): %s", err.Error(), string(read))
}
continue
}
}
newConfig.Sources = []string{"reader"}
result = append(result, &newConfig)
}

Expand Down Expand Up @@ -380,7 +402,7 @@
// ParseCmdLine reads options from the kernel cmdline and returns the equivalent
// Config.
func ParseCmdLine(file string, filter func(d []byte) ([]byte, error)) (*Config, error) {
result := Config{}
result := Config{Sources: []string{"cmdline"}}
dotToYAML, err := machine.DotToYAML(file)
if err != nil {
return &result, err
Expand All @@ -391,7 +413,7 @@
return &result, err
}

err = yaml.Unmarshal(filteredYAML, &result)
err = yaml.Unmarshal(filteredYAML, &result.Values)
if err != nil {
return &result, err
}
Expand All @@ -401,7 +423,7 @@

// ConfigURL returns the value of config_url if set or empty string otherwise.
func (c Config) ConfigURL() string {
if val, hasKey := c["config_url"]; hasKey {
if val, hasKey := c.Values["config_url"]; hasKey {
if s, isString := val.(string); isString {
return s
}
Expand Down Expand Up @@ -446,10 +468,12 @@
return result, nil
}

if err := yaml.Unmarshal(body, result); err != nil {
if err := yaml.Unmarshal(body, &result.Values); err != nil {
return result, fmt.Errorf("could not unmarshal remote config to an object: %w", err)
}

result.Sources = []string{url}

return result, nil
}

Expand Down
Loading