Fix nginx ingress controller bug around config map merging
* a config map bool value of false cannot overwritte a true value from defaults * implement merging in ReadConfig * remove helper function merge * adds tests to ensure config is read properly
This commit is contained in:
parent
4159a40da4
commit
94e6702385
3 changed files with 130 additions and 23 deletions
|
@ -48,10 +48,6 @@ func (ngx *Manager) loadTemplate() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ngx *Manager) writeCfg(cfg nginxConfiguration, ingressCfg IngressConfig) (bool, error) {
|
func (ngx *Manager) writeCfg(cfg nginxConfiguration, ingressCfg IngressConfig) (bool, error) {
|
||||||
fromMap := structs.Map(cfg)
|
|
||||||
toMap := structs.Map(ngx.defCfg)
|
|
||||||
curNginxCfg := merge(toMap, fromMap)
|
|
||||||
|
|
||||||
conf := make(map[string]interface{})
|
conf := make(map[string]interface{})
|
||||||
conf["upstreams"] = ingressCfg.Upstreams
|
conf["upstreams"] = ingressCfg.Upstreams
|
||||||
conf["servers"] = ingressCfg.Servers
|
conf["servers"] = ingressCfg.Servers
|
||||||
|
@ -59,7 +55,7 @@ func (ngx *Manager) writeCfg(cfg nginxConfiguration, ingressCfg IngressConfig) (
|
||||||
conf["udpUpstreams"] = ingressCfg.UDPUpstreams
|
conf["udpUpstreams"] = ingressCfg.UDPUpstreams
|
||||||
conf["defResolver"] = ngx.defResolver
|
conf["defResolver"] = ngx.defResolver
|
||||||
conf["sslDHParam"] = ngx.sslDHParam
|
conf["sslDHParam"] = ngx.sslDHParam
|
||||||
conf["cfg"] = fixKeyNames(curNginxCfg)
|
conf["cfg"] = fixKeyNames(structs.Map(cfg))
|
||||||
|
|
||||||
buffer := new(bytes.Buffer)
|
buffer := new(bytes.Buffer)
|
||||||
err := ngx.template.Execute(buffer, conf)
|
err := ngx.template.Execute(buffer, conf)
|
||||||
|
|
|
@ -59,18 +59,36 @@ func getDNSServers() []string {
|
||||||
return nameservers
|
return nameservers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getConfigKeyToStructKeyMap returns a map with the ConfigMapKey as key and the StructName as value.
|
||||||
|
func getConfigKeyToStructKeyMap() map[string]string {
|
||||||
|
keyMap := map[string]string{}
|
||||||
|
n := &nginxConfiguration{}
|
||||||
|
val := reflect.Indirect(reflect.ValueOf(n))
|
||||||
|
for i := 0; i < val.Type().NumField(); i++ {
|
||||||
|
fieldSt := val.Type().Field(i)
|
||||||
|
configMapKey := strings.Split(fieldSt.Tag.Get("structs"), ",")[0]
|
||||||
|
structKey := fieldSt.Name
|
||||||
|
keyMap[configMapKey] = structKey
|
||||||
|
}
|
||||||
|
return keyMap
|
||||||
|
}
|
||||||
|
|
||||||
// ReadConfig obtains the configuration defined by the user merged with the defaults.
|
// ReadConfig obtains the configuration defined by the user merged with the defaults.
|
||||||
func (ngx *Manager) ReadConfig(config *api.ConfigMap) nginxConfiguration {
|
func (ngx *Manager) ReadConfig(config *api.ConfigMap) nginxConfiguration {
|
||||||
if len(config.Data) == 0 {
|
if len(config.Data) == 0 {
|
||||||
return newDefaultNginxCfg()
|
return newDefaultNginxCfg()
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := newDefaultNginxCfg()
|
cfgCM := nginxConfiguration{}
|
||||||
|
cfgDefault := newDefaultNginxCfg()
|
||||||
|
|
||||||
|
metadata := &mapstructure.Metadata{}
|
||||||
|
|
||||||
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||||
TagName: "structs",
|
TagName: "structs",
|
||||||
Result: &cfg,
|
Result: &cfgCM,
|
||||||
WeaklyTypedInput: true,
|
WeaklyTypedInput: true,
|
||||||
|
Metadata: metadata,
|
||||||
})
|
})
|
||||||
|
|
||||||
err = decoder.Decode(config.Data)
|
err = decoder.Decode(config.Data)
|
||||||
|
@ -78,7 +96,26 @@ func (ngx *Manager) ReadConfig(config *api.ConfigMap) nginxConfiguration {
|
||||||
glog.Infof("%v", err)
|
glog.Infof("%v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg
|
keyMap := getConfigKeyToStructKeyMap()
|
||||||
|
|
||||||
|
valCM := reflect.Indirect(reflect.ValueOf(cfgCM))
|
||||||
|
|
||||||
|
for _, key := range metadata.Keys {
|
||||||
|
fieldName, ok := keyMap[key]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
valDefault := reflect.ValueOf(&cfgDefault).Elem().FieldByName(fieldName)
|
||||||
|
|
||||||
|
fieldCM := valCM.FieldByName(fieldName)
|
||||||
|
|
||||||
|
if valDefault.IsValid() {
|
||||||
|
valDefault.Set(fieldCM)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfgDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ngx *Manager) needsReload(data *bytes.Buffer) (bool, error) {
|
func (ngx *Manager) needsReload(data *bytes.Buffer) (bool, error) {
|
||||||
|
@ -143,21 +180,6 @@ func diff(b1, b2 []byte) (data []byte, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func merge(dst, src map[string]interface{}) map[string]interface{} {
|
|
||||||
for key, srcVal := range src {
|
|
||||||
if dstVal, ok := dst[key]; ok {
|
|
||||||
srcMap, srcMapOk := toMap(srcVal)
|
|
||||||
dstMap, dstMapOk := toMap(dstVal)
|
|
||||||
if srcMapOk && dstMapOk {
|
|
||||||
srcVal = merge(dstMap, srcMap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dst[key] = srcVal
|
|
||||||
}
|
|
||||||
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
func toMap(iface interface{}) (map[string]interface{}, bool) {
|
func toMap(iface interface{}) (map[string]interface{}, bool) {
|
||||||
value := reflect.ValueOf(iface)
|
value := reflect.ValueOf(iface)
|
||||||
if value.Kind() == reflect.Map {
|
if value.Kind() == reflect.Map {
|
||||||
|
|
89
controllers/nginx/nginx/utils_test.go
Normal file
89
controllers/nginx/nginx/utils_test.go
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package nginx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/kubernetes/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getConfigNginxBool(data map[string]string) nginxConfiguration {
|
||||||
|
manager := &Manager{}
|
||||||
|
configMap := &api.ConfigMap{
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
return manager.ReadConfig(configMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManagerReadConfigBoolFalse(t *testing.T) {
|
||||||
|
configNginx := getConfigNginxBool(map[string]string{
|
||||||
|
"hts-include-subdomains": "false",
|
||||||
|
"use-proxy-protocol": "false",
|
||||||
|
})
|
||||||
|
if configNginx.HTSIncludeSubdomains {
|
||||||
|
t.Error("Failed to config boolean value (default true) to false")
|
||||||
|
}
|
||||||
|
if configNginx.UseProxyProtocol {
|
||||||
|
t.Error("Failed to config boolean value (default false) to false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManagerReadConfigBoolTrue(t *testing.T) {
|
||||||
|
configNginx := getConfigNginxBool(map[string]string{
|
||||||
|
"hts-include-subdomains": "true",
|
||||||
|
"use-proxy-protocol": "true",
|
||||||
|
})
|
||||||
|
if !configNginx.HTSIncludeSubdomains {
|
||||||
|
t.Error("Failed to config boolean value (default true) to true")
|
||||||
|
}
|
||||||
|
if !configNginx.UseProxyProtocol {
|
||||||
|
t.Error("Failed to config boolean value (default false) to true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManagerReadConfigBoolNothing(t *testing.T) {
|
||||||
|
configNginx := getConfigNginxBool(map[string]string{
|
||||||
|
"invaild-key": "true",
|
||||||
|
})
|
||||||
|
if !configNginx.HTSIncludeSubdomains {
|
||||||
|
t.Error("Failed to get default boolean value true")
|
||||||
|
}
|
||||||
|
if configNginx.UseProxyProtocol {
|
||||||
|
t.Error("Failed to get default boolean value false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManagerReadConfigStringSet(t *testing.T) {
|
||||||
|
configNginx := getConfigNginxBool(map[string]string{
|
||||||
|
"ssl-protocols": "TLSv1.2",
|
||||||
|
})
|
||||||
|
exp := "TLSv1.2"
|
||||||
|
if configNginx.SSLProtocols != exp {
|
||||||
|
t.Error("Failed to set string value true actual='%s' expected='%s'", configNginx.SSLProtocols, exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManagerReadConfigStringNothing(t *testing.T) {
|
||||||
|
configNginx := getConfigNginxBool(map[string]string{
|
||||||
|
"not-existing": "TLSv1.2",
|
||||||
|
})
|
||||||
|
exp := "10m"
|
||||||
|
if configNginx.SSLSessionTimeout != exp {
|
||||||
|
t.Error("Failed to set string value true actual='%s' expected='%s'", configNginx.SSLSessionTimeout, exp)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue