-
Notifications
You must be signed in to change notification settings - Fork 718
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move Kibana configuration in a Secret (#977)
* Migrate all env vars in the config settings file * Reconcile a new config secret using a static kibana.yml config settings file (named <name>-kb-config) * Update the deployment spec with the new config volume and volumeMount * Reuse Elasticsearch settings CanonicalConfig bits (move in a common package)
- Loading branch information
Showing
35 changed files
with
931 additions
and
701 deletions.
There are no files selected for viewing
290 changes: 290 additions & 0 deletions
290
operators/pkg/controller/common/settings/canonical_config.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,290 @@ | ||
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
// or more contributor license agreements. Licensed under the Elastic License; | ||
// you may not use this file except in compliance with the Elastic License. | ||
|
||
package settings | ||
|
||
import ( | ||
"fmt" | ||
"reflect" | ||
"sort" | ||
"strconv" | ||
"strings" | ||
|
||
ucfg "github.com/elastic/go-ucfg" | ||
udiff "github.com/elastic/go-ucfg/diff" | ||
uyaml "github.com/elastic/go-ucfg/yaml" | ||
"github.com/pkg/errors" | ||
yaml "gopkg.in/yaml.v2" | ||
) | ||
|
||
// CanonicalConfig contains configuration for an Elastic resource ("elasticsearch.yml" or "kibana.yml"), | ||
// as a hierarchical key-value configuration. | ||
type CanonicalConfig ucfg.Config | ||
|
||
// Options are config options for the YAML file. Currently contains only support for dotted keys. | ||
var Options = []ucfg.Option{ucfg.PathSep(".")} | ||
|
||
// NewCanonicalConfig creates a new empty config. | ||
func NewCanonicalConfig() *CanonicalConfig { | ||
return fromConfig(ucfg.New()) | ||
} | ||
|
||
// NewCanonicalConfigFrom creates a new config from the API type. | ||
func NewCanonicalConfigFrom(data untypedDict) (*CanonicalConfig, error) { | ||
config, err := ucfg.NewFrom(data, Options...) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return fromConfig(config), nil | ||
} | ||
|
||
// MustCanonicalConfig creates a new config and panics on errors. | ||
// Use for testing only. | ||
func MustCanonicalConfig(cfg interface{}) *CanonicalConfig { | ||
config, err := ucfg.NewFrom(cfg, Options...) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return fromConfig(config) | ||
} | ||
|
||
// MustNewSingleValue creates a new config holding a single string value. | ||
// Convenience constructor, will panic in the unlikely event of errors. | ||
func MustNewSingleValue(k string, v string) *CanonicalConfig { | ||
cfg := fromConfig(ucfg.New()) | ||
err := cfg.asUCfg().SetString(k, -1, v, Options...) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return cfg | ||
} | ||
|
||
// ParseConfig parses the given configuration content into a CanonicalConfig. | ||
// Expects content to be in YAML format. | ||
func ParseConfig(yml []byte) (*CanonicalConfig, error) { | ||
config, err := uyaml.NewConfig(yml, Options...) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return fromConfig(config), nil | ||
} | ||
|
||
// SetStrings sets key to string vals in c. An error is returned if key is invalid. | ||
func (c *CanonicalConfig) SetStrings(key string, vals ...string) error { | ||
if c == nil { | ||
return errors.New("config is nil") | ||
} | ||
switch len(vals) { | ||
case 0: | ||
return errors.New("Nothing to set") | ||
default: | ||
for i, v := range vals { | ||
err := c.asUCfg().SetString(key, i, v, Options...) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// Unpack returns a typed config given a struct pointer. | ||
func (c *CanonicalConfig) Unpack(cfg interface{}) error { | ||
if reflect.ValueOf(cfg).Kind() != reflect.Ptr { | ||
panic("Unpack expects a struct pointer as argument") | ||
} | ||
return c.asUCfg().Unpack(cfg, Options...) | ||
} | ||
|
||
// MergeWith merges the content of c and c2. | ||
// In case of conflict, c2 is taking precedence. | ||
func (c *CanonicalConfig) MergeWith(cfgs ...*CanonicalConfig) error { | ||
for _, c2 := range cfgs { | ||
if c2 == nil { | ||
continue | ||
} | ||
err := c.asUCfg().Merge(c2.asUCfg(), Options...) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// HasPrefixes returns all keys in c that have one of the given prefix keys. | ||
// Keys are expected in dotted form. | ||
func (c *CanonicalConfig) HasPrefixes(keys []string) []string { | ||
var has []string | ||
flatKeys := c.asUCfg().FlattenedKeys(Options...) | ||
for _, s := range keys { | ||
for _, k := range flatKeys { | ||
if strings.HasPrefix(k, s) { | ||
has = append(has, s) | ||
} | ||
} | ||
} | ||
return has | ||
} | ||
|
||
// Render returns the content of the configuration file, | ||
// with fields sorted alphabetically | ||
func (c *CanonicalConfig) Render() ([]byte, error) { | ||
if c == nil { | ||
return []byte{}, nil | ||
} | ||
var out untypedDict | ||
err := c.asUCfg().Unpack(&out) | ||
if err != nil { | ||
return []byte{}, err | ||
} | ||
return yaml.Marshal(out) | ||
} | ||
|
||
type untypedDict = map[string]interface{} | ||
|
||
// Diff returns the flattened keys where c and c2 differ. | ||
func (c *CanonicalConfig) Diff(c2 *CanonicalConfig, ignore []string) []string { | ||
var diff []string | ||
if c == c2 { | ||
return diff | ||
} | ||
if c == nil && c2 != nil { | ||
return c2.asUCfg().FlattenedKeys(Options...) | ||
} | ||
if c != nil && c2 == nil { | ||
return c.asUCfg().FlattenedKeys(Options...) | ||
} | ||
keyDiff := udiff.CompareConfigs(c.asUCfg(), c2.asUCfg(), Options...) | ||
diff = append(diff, keyDiff[udiff.Add]...) | ||
diff = append(diff, keyDiff[udiff.Remove]...) | ||
diff = removeIgnored(diff, ignore) | ||
if len(diff) > 0 { | ||
return diff | ||
} | ||
// at this point both configs should contain the same keys but may have different values | ||
var cUntyped untypedDict | ||
var c2Untyped untypedDict | ||
err := c.asUCfg().Unpack(&cUntyped, Options...) | ||
if err != nil { | ||
return []string{err.Error()} | ||
} | ||
err = c2.asUCfg().Unpack(&c2Untyped, Options...) | ||
if err != nil { | ||
return []string{err.Error()} | ||
} | ||
|
||
diff = diffMap(cUntyped, c2Untyped, "") | ||
return removeIgnored(diff, ignore) | ||
} | ||
|
||
func removeIgnored(diff, toIgnore []string) []string { | ||
var result []string | ||
for _, d := range diff { | ||
if canIgnore(d, toIgnore) { | ||
continue | ||
} | ||
result = append(result, d) | ||
} | ||
sort.StringSlice(result).Sort() | ||
return result | ||
} | ||
|
||
func canIgnore(diff string, toIgnore []string) bool { | ||
for _, prefix := range toIgnore { | ||
if strings.HasPrefix(diff, prefix) { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
func diffMap(c1, c2 untypedDict, key string) []string { | ||
// invariant: keys match | ||
// invariant: json-style map i.e no structs no pointers | ||
var diff []string | ||
for k, v := range c1 { | ||
newKey := k | ||
if len(key) != 0 { | ||
newKey = key + "." + k | ||
} | ||
v2 := c2[k] | ||
switch v.(type) { | ||
case untypedDict: | ||
l, r, err := asUntypedDict(v, v2) | ||
if err != nil { | ||
diff = append(diff, newKey) | ||
} | ||
diff = append(diff, diffMap(l, r, newKey)...) | ||
case []interface{}: | ||
l, r, err := asUntypedSlice(v, v2) | ||
if err != nil { | ||
diff = append(diff, newKey) | ||
} | ||
diff = append(diff, diffSlice(l, r, newKey)...) | ||
default: | ||
if v != v2 { | ||
diff = append(diff, newKey) | ||
} | ||
} | ||
} | ||
return diff | ||
} | ||
|
||
func diffSlice(s, s2 []interface{}, key string) []string { | ||
// invariant: keys match | ||
// invariant: s,s2 are json-style arrays/slices i.e no structs no pointers | ||
if len(s) != len(s2) { | ||
return []string{key} | ||
} | ||
var diff []string | ||
for i, v := range s { | ||
v2 := s2[i] | ||
newKey := key + "." + strconv.Itoa(i) | ||
switch v.(type) { | ||
case untypedDict: | ||
l, r, err := asUntypedDict(v, v2) | ||
if err != nil { | ||
diff = append(diff, newKey) | ||
} | ||
diff = append(diff, diffMap(l, r, newKey)...) | ||
case []interface{}: | ||
l, r, err := asUntypedSlice(v, v2) | ||
if err != nil { | ||
diff = append(diff, newKey) | ||
} | ||
diff = append(diff, diffSlice(l, r, newKey)...) | ||
default: | ||
if v != v2 { | ||
diff = append(diff, newKey) | ||
} | ||
} | ||
} | ||
return diff | ||
} | ||
|
||
func asUntypedDict(l, r interface{}) (untypedDict, untypedDict, error) { | ||
lhs, ok := l.(untypedDict) | ||
rhs, ok2 := r.(untypedDict) | ||
if !ok || !ok2 { | ||
return nil, nil, fmt.Errorf("map assertation failed for l: %t r: %t", ok, ok2) | ||
} | ||
return lhs, rhs, nil | ||
} | ||
|
||
func asUntypedSlice(l, r interface{}) ([]interface{}, []interface{}, error) { | ||
lhs, ok := l.([]interface{}) | ||
rhs, ok2 := r.([]interface{}) | ||
if !ok || !ok2 { | ||
return nil, nil, fmt.Errorf("slice assertation failed for l: %t r: %t", ok, ok2) | ||
} | ||
return lhs, rhs, nil | ||
} | ||
|
||
func (c *CanonicalConfig) asUCfg() *ucfg.Config { | ||
return (*ucfg.Config)(c) | ||
} | ||
|
||
func fromConfig(in *ucfg.Config) *CanonicalConfig { | ||
return (*CanonicalConfig)(in) | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.