Configure nginx using a ConfigMap
This commit is contained in:
parent
28f9cb0b2b
commit
d9934ec4db
17 changed files with 378 additions and 417 deletions
|
|
@ -23,10 +23,6 @@ import (
|
|||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
const (
|
||||
nginxEvent = "NGINX"
|
||||
)
|
||||
|
||||
// Start starts a nginx (master process) and waits. If the process ends
|
||||
// we need to kill the controller process and return the reason.
|
||||
func (ngx *NginxManager) Start() {
|
||||
|
|
@ -54,11 +50,12 @@ func (ngx *NginxManager) Start() {
|
|||
// shut down, stop accepting new connections and continue to service current requests
|
||||
// until all such requests are serviced. After that, the old worker processes exit.
|
||||
// http://nginx.org/en/docs/beginners_guide.html#control
|
||||
func (ngx *NginxManager) CheckAndReload(cfg *nginxConfiguration, upstreams []*Upstream, servers []*Server, servicesL4 []*Upstream) {
|
||||
func (ngx *NginxManager) CheckAndReload(cfg *nginxConfiguration, ingressCfg IngressConfig) {
|
||||
ngx.reloadLock.Lock()
|
||||
defer ngx.reloadLock.Unlock()
|
||||
|
||||
newCfg, err := ngx.writeCfg(cfg, upstreams, servers, servicesL4)
|
||||
newCfg, err := ngx.writeCfg(cfg, ingressCfg)
|
||||
|
||||
if err != nil {
|
||||
glog.Errorf("failed to write new nginx configuration. Avoiding reload: %v", err)
|
||||
return
|
||||
|
|
|
|||
105
controllers/nginx-third-party/nginx/main.go
vendored
105
controllers/nginx-third-party/nginx/main.go
vendored
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package nginx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
|
@ -26,10 +27,11 @@ import (
|
|||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/contrib/ingress/controllers/nginx-third-party/ssl"
|
||||
"github.com/fatih/structs"
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
client "k8s.io/kubernetes/pkg/client/unversioned"
|
||||
k8sruntime "k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -82,153 +84,138 @@ const (
|
|||
type nginxConfiguration struct {
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size
|
||||
// Sets the maximum allowed size of the client request body
|
||||
BodySize string `json:"bodySize,omitempty" structs:",omitempty"`
|
||||
BodySize string `json:"bodySize,omitempty" structs:"bodySize,omitempty"`
|
||||
|
||||
// http://nginx.org/en/docs/ngx_core_module.html#error_log
|
||||
// Configures logging level [debug | info | notice | warn | error | crit | alert | emerg]
|
||||
// Log levels above are listed in the order of increasing severity
|
||||
ErrorLogLevel string `json:"errorLogLevel,omitempty" structs:",omitempty"`
|
||||
ErrorLogLevel string `json:"errorLogLevel,omitempty" structs:"errorLogLevel,omitempty"`
|
||||
|
||||
// Enables or disables the header HTS in servers running SSL
|
||||
UseHTS bool `json:"useHTS,omitempty" structs:",omitempty"`
|
||||
UseHTS bool `json:"useHTS,omitempty" structs:"useHTS,omitempty"`
|
||||
|
||||
// Enables or disables the use of HTS in all the subdomains of the servername
|
||||
HTSIncludeSubdomains bool `json:"htsIncludeSubdomains,omitempty" structs:",omitempty"`
|
||||
HTSIncludeSubdomains bool `json:"htsIncludeSubdomains,omitempty" structs:"htsIncludeSubdomains,omitempty"`
|
||||
|
||||
// HTTP Strict Transport Security (often abbreviated as HSTS) is a security feature (HTTP header)
|
||||
// that tell browsers that it should only be communicated with using HTTPS, instead of using HTTP.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/Security/HTTP_strict_transport_security
|
||||
// max-age is the time, in seconds, that the browser should remember that this site is only to be
|
||||
// accessed using HTTPS.
|
||||
HTSMaxAge string `json:"htsMaxAge,omitempty" structs:",omitempty"`
|
||||
HTSMaxAge string `json:"htsMaxAge,omitempty" structs:"htsMaxAge,omitempty"`
|
||||
|
||||
// Time during which a keep-alive client connection will stay open on the server side.
|
||||
// The zero value disables keep-alive client connections
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#keepalive_timeout
|
||||
KeepAlive int `json:"keepAlive,omitempty" structs:",omitempty"`
|
||||
KeepAlive int `json:"keepAlive,omitempty" structs:"keepAlive,omitempty"`
|
||||
|
||||
// Maximum number of simultaneous connections that can be opened by each worker process
|
||||
// http://nginx.org/en/docs/ngx_core_module.html#worker_connections
|
||||
MaxWorkerConnections int `json:"maxWorkerConnections,omitempty" structs:",omitempty"`
|
||||
MaxWorkerConnections int `json:"maxWorkerConnections,omitempty" structs:"maxWorkerConnections,omitempty"`
|
||||
|
||||
// Defines a timeout for establishing a connection with a proxied server.
|
||||
// It should be noted that this timeout cannot usually exceed 75 seconds.
|
||||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_connect_timeout
|
||||
ProxyConnectTimeout int `json:"proxyConnectTimeout,omitempty" structs:",omitempty"`
|
||||
ProxyConnectTimeout int `json:"proxyConnectTimeout,omitempty" structs:"proxyConnectTimeout,omitempty"`
|
||||
|
||||
// If UseProxyProtocol is enabled ProxyRealIPCIDR defines the default the IP/network address
|
||||
// of your external load balancer
|
||||
ProxyRealIPCIDR string `json:"proxyRealIPCIDR,omitempty" structs:",omitempty"`
|
||||
ProxyRealIPCIDR string `json:"proxyRealIPCIDR,omitempty" structs:"proxyRealIPCIDR,omitempty"`
|
||||
|
||||
// Timeout in seconds for reading a response from the proxied server. The timeout is set only between
|
||||
// two successive read operations, not for the transmission of the whole response
|
||||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_read_timeout
|
||||
ProxyReadTimeout int `json:"proxyReadTimeout,omitempty" structs:",omitempty"`
|
||||
ProxyReadTimeout int `json:"proxyReadTimeout,omitempty" structs:"proxyReadTimeout,omitempty"`
|
||||
|
||||
// Timeout in seconds for transmitting a request to the proxied server. The timeout is set only between
|
||||
// two successive write operations, not for the transmission of the whole request.
|
||||
// http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_send_timeout
|
||||
ProxySendTimeout int `json:"proxySendTimeout,omitempty" structs:",omitempty"`
|
||||
ProxySendTimeout int `json:"proxySendTimeout,omitempty" structs:"proxySendTimeout,omitempty"`
|
||||
|
||||
// Configures name servers used to resolve names of upstream servers into addresses
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#resolver
|
||||
Resolver string `json:"resolver,omitempty" structs:",omitempty"`
|
||||
Resolver string `json:"resolver,omitempty" structs:"resolver,omitempty"`
|
||||
|
||||
// Maximum size of the server names hash tables used in server names, map directive’s values,
|
||||
// MIME types, names of request header strings, etcd.
|
||||
// http://nginx.org/en/docs/hash.html
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#server_names_hash_max_size
|
||||
ServerNameHashMaxSize int `json:"serverNameHashMaxSize,omitempty" structs:",omitempty"`
|
||||
ServerNameHashMaxSize int `json:"serverNameHashMaxSize,omitempty" structs:"serverNameHashMaxSize,omitempty"`
|
||||
|
||||
// Size of the bucker for the server names hash tables
|
||||
// http://nginx.org/en/docs/hash.html
|
||||
// http://nginx.org/en/docs/http/ngx_http_core_module.html#server_names_hash_bucket_size
|
||||
ServerNameHashBucketSize int `json:"serverNameHashBucketSize,omitempty" structs:",omitempty"`
|
||||
ServerNameHashBucketSize int `json:"serverNameHashBucketSize,omitempty" structs:"serverNameHashBucketSize,omitempty"`
|
||||
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_buffer_size
|
||||
// Sets the size of the buffer used for sending data.
|
||||
// 4k helps NGINX to improve TLS Time To First Byte (TTTFB)
|
||||
// https://www.igvita.com/2013/12/16/optimizing-nginx-tls-time-to-first-byte/
|
||||
SSLBufferSize string `json:"sslBufferSize,omitempty" structs:",omitempty"`
|
||||
SSLBufferSize string `json:"sslBufferSize,omitempty" structs:"sslBufferSize,omitempty"`
|
||||
|
||||
// Enabled ciphers list to enabled. The ciphers are specified in the format understood by
|
||||
// the OpenSSL library
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_ciphers
|
||||
SSLCiphers string `json:"sslCiphers,omitempty" structs:",omitempty"`
|
||||
SSLCiphers string `json:"sslCiphers,omitempty" structs:"sslCiphers,omitempty"`
|
||||
|
||||
// Base64 string that contains Diffie-Hellman key to help with "Perfect Forward Secrecy"
|
||||
// https://www.openssl.org/docs/manmaster/apps/dhparam.html
|
||||
// https://wiki.mozilla.org/Security/Server_Side_TLS#DHE_handshake_and_dhparam
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_dhparam
|
||||
SSLDHParam string `json:"sslDHParam,omitempty" structs:",omitempty"`
|
||||
SSLDHParam string `json:"sslDHParam,omitempty" structs:"sslDHParam,omitempty"`
|
||||
|
||||
// SSL enabled protocols to use
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_protocols
|
||||
SSLProtocols string `json:"sslProtocols,omitempty" structs:",omitempty"`
|
||||
SSLProtocols string `json:"sslProtocols,omitempty" structs:"sslProtocols,omitempty"`
|
||||
|
||||
// Enables or disables the use of shared SSL cache among worker processes.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache
|
||||
SSLSessionCache bool `json:"sslSessionCache,omitempty" structs:",omitempty"`
|
||||
SSLSessionCache bool `json:"sslSessionCache,omitempty" structs:"sslSessionCache,omitempty"`
|
||||
|
||||
// Size of the SSL shared cache between all worker processes.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_cache
|
||||
SSLSessionCacheSize string `json:"sslSessionCacheSize,omitempty" structs:",omitempty"`
|
||||
SSLSessionCacheSize string `json:"sslSessionCacheSize,omitempty" structs:"sslSessionCacheSize,omitempty"`
|
||||
|
||||
// Enables or disables session resumption through TLS session tickets.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_tickets
|
||||
SSLSessionTickets bool `json:"sslSessionTickets,omitempty" structs:",omitempty"`
|
||||
SSLSessionTickets bool `json:"sslSessionTickets,omitempty" structs:"sslSessionTickets,omitempty"`
|
||||
|
||||
// Time during which a client may reuse the session parameters stored in a cache.
|
||||
// http://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_session_timeout
|
||||
SSLSessionTimeout string `json:"sslSessionTimeout,omitempty" structs:",omitempty"`
|
||||
SSLSessionTimeout string `json:"sslSessionTimeout,omitempty" structs:"sslSessionTimeout,omitempty"`
|
||||
|
||||
// Enables or disables the use of the PROXY protocol to receive client connection
|
||||
// (real IP address) information passed through proxy servers and load balancers
|
||||
// such as HAproxy and Amazon Elastic Load Balancer (ELB).
|
||||
// https://www.nginx.com/resources/admin-guide/proxy-protocol/
|
||||
UseProxyProtocol bool `json:"useProxyProtocol,omitempty" structs:",omitempty"`
|
||||
UseProxyProtocol bool `json:"useProxyProtocol,omitempty" structs:"useProxyProtocol,omitempty"`
|
||||
|
||||
// Enables or disables the use of the nginx module that compresses responses using the "gzip" method
|
||||
// http://nginx.org/en/docs/http/ngx_http_gzip_module.html
|
||||
UseGzip bool `json:"useGzip,omitempty" structs:",omitempty"`
|
||||
UseGzip bool `json:"useGzip,omitempty" structs:"useGzip,omitempty"`
|
||||
|
||||
// MIME types in addition to "text/html" to compress. The special value “*” matches any MIME type.
|
||||
// Responses with the “text/html” type are always compressed if UseGzip is enabled
|
||||
GzipTypes string `json:"gzipTypes,omitempty" structs:",omitempty"`
|
||||
GzipTypes string `json:"gzipTypes,omitempty" structs:"gzipTypes,omitempty"`
|
||||
|
||||
// Defines the number of worker processes. By default auto means number of available CPU cores
|
||||
// http://nginx.org/en/docs/ngx_core_module.html#worker_processes
|
||||
WorkerProcesses string `json:"workerProcesses,omitempty" structs:",omitempty"`
|
||||
}
|
||||
|
||||
// Service service definition to use in nginx template
|
||||
type Service struct {
|
||||
ServiceName string
|
||||
ServicePort string
|
||||
Namespace string
|
||||
// ExposedPort port used by nginx to listen for the stream upstream
|
||||
ExposedPort string
|
||||
WorkerProcesses string `json:"workerProcesses,omitempty" structs:"workerProcesses,omitempty"`
|
||||
}
|
||||
|
||||
// NginxManager ...
|
||||
type NginxManager struct {
|
||||
defCfg *nginxConfiguration
|
||||
defResolver string
|
||||
|
||||
// path to the configuration file to be used by nginx
|
||||
ConfigFile string
|
||||
|
||||
defCfg *nginxConfiguration
|
||||
|
||||
defResolver string
|
||||
|
||||
sslDHParam string
|
||||
|
||||
servicesL4 []Service
|
||||
|
||||
client *client.Client
|
||||
// template loaded ready to be used to generate the nginx configuration file
|
||||
template *template.Template
|
||||
|
||||
// obj runtime object to be used in events
|
||||
obj k8sruntime.Object
|
||||
|
||||
reloadLock *sync.Mutex
|
||||
}
|
||||
|
||||
|
|
@ -280,7 +267,7 @@ func NewManager(kubeClient *client.Client) *NginxManager {
|
|||
|
||||
ngx.createCertsDir(sslDirectory)
|
||||
|
||||
ngx.sslDHParam = ssl.SearchDHParamFile(sslDirectory)
|
||||
ngx.sslDHParam = ngx.SearchDHParamFile(sslDirectory)
|
||||
|
||||
ngx.loadTemplate()
|
||||
|
||||
|
|
@ -292,3 +279,25 @@ func (nginx *NginxManager) createCertsDir(base string) {
|
|||
glog.Fatalf("Couldn't create directory %v: %v", base, err)
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigMapAsString returns a ConfigMap with the default NGINX
|
||||
// configuration to be used a guide to provide a custom configuration
|
||||
func ConfigMapAsString() string {
|
||||
cfg := &api.ConfigMap{}
|
||||
cfg.Name = "custom-name"
|
||||
cfg.Namespace = "a-valid-namespace"
|
||||
cfg.Data = make(map[string]string)
|
||||
|
||||
data := structs.Map(newDefaultNginxCfg())
|
||||
for k, v := range data {
|
||||
cfg.Data[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
out, err := yaml.Marshal(cfg)
|
||||
if err != nil {
|
||||
glog.Warningf("Unexpected error creating default configuration: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(out)
|
||||
}
|
||||
|
|
|
|||
15
controllers/nginx-third-party/nginx/nginx.go
vendored
15
controllers/nginx-third-party/nginx/nginx.go
vendored
|
|
@ -16,10 +16,11 @@ limitations under the License.
|
|||
|
||||
package nginx
|
||||
|
||||
// IngressNGINXConfig describes an NGINX configuration
|
||||
type IngressNGINXConfig struct {
|
||||
Upstreams []Upstream
|
||||
Servers []Server
|
||||
// IngressConfig describes an NGINX configuration
|
||||
type IngressConfig struct {
|
||||
Upstreams []*Upstream
|
||||
Servers []*Server
|
||||
TCPUpstreams []*Location
|
||||
}
|
||||
|
||||
// Upstream describes an NGINX upstream
|
||||
|
|
@ -63,7 +64,7 @@ func (c UpstreamServerByAddrPort) Less(i, j int) bool {
|
|||
// Server describes an NGINX server
|
||||
type Server struct {
|
||||
Name string
|
||||
Locations []Location
|
||||
Locations []*Location
|
||||
SSL bool
|
||||
SSLCertificate string
|
||||
SSLCertificateKey string
|
||||
|
|
@ -85,7 +86,7 @@ type Location struct {
|
|||
}
|
||||
|
||||
// LocationByPath sorts location by path
|
||||
type LocationByPath []Location
|
||||
type LocationByPath []*Location
|
||||
|
||||
func (c LocationByPath) Len() int { return len(c) }
|
||||
func (c LocationByPath) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
|
|
@ -93,7 +94,7 @@ func (c LocationByPath) Less(i, j int) bool {
|
|||
return c[i].Path < c[j].Path
|
||||
}
|
||||
|
||||
// NewDefaultServer return an UpstreamServer to be use as default server returns 502.
|
||||
// NewDefaultServer return an UpstreamServer to be use as default server that returns 503.
|
||||
func NewDefaultServer() UpstreamServer {
|
||||
return UpstreamServer{Address: "127.0.0.1", Port: "8181"}
|
||||
}
|
||||
|
|
|
|||
21
controllers/nginx-third-party/nginx/ssl.go
vendored
21
controllers/nginx-third-party/nginx/ssl.go
vendored
|
|
@ -70,3 +70,24 @@ func (nginx *NginxManager) CheckSSLCertificate(secretName string) ([]string, err
|
|||
glog.V(2).Infof("DNS %v %v\n", cn, len(cn))
|
||||
return cn, nil
|
||||
}
|
||||
|
||||
// SearchDHParamFile iterates all the secrets mounted inside the /etc/nginx-ssl directory
|
||||
// in order to find a file with the name dhparam.pem. If such file exists it will
|
||||
// returns the path. If not it just returns an empty string
|
||||
func (nginx *NginxManager) SearchDHParamFile(baseDir string) string {
|
||||
files, _ := ioutil.ReadDir(baseDir)
|
||||
for _, file := range files {
|
||||
if !file.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
dhPath := fmt.Sprintf("%v/%v/dhparam.pem", baseDir, file.Name())
|
||||
if _, err := os.Stat(dhPath); err == nil {
|
||||
glog.Infof("using file '%v' for parameter ssl_dhparam", dhPath)
|
||||
return dhPath
|
||||
}
|
||||
}
|
||||
|
||||
glog.Warning("no file dhparam.pem found in secrets")
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
22
controllers/nginx-third-party/nginx/template.go
vendored
22
controllers/nginx-third-party/nginx/template.go
vendored
|
|
@ -24,7 +24,6 @@ import (
|
|||
|
||||
"github.com/fatih/structs"
|
||||
"github.com/golang/glog"
|
||||
"github.com/imdario/mergo"
|
||||
)
|
||||
|
||||
var funcMap = template.FuncMap{
|
||||
|
|
@ -43,15 +42,15 @@ func (ngx *NginxManager) loadTemplate() {
|
|||
ngx.template = tmpl
|
||||
}
|
||||
|
||||
func (ngx *NginxManager) writeCfg(cfg *nginxConfiguration, upstreams []*Upstream, servers []*Server, tcpUpstreams []*Upstream) (bool, error) {
|
||||
func (ngx *NginxManager) writeCfg(cfg *nginxConfiguration, ingressCfg IngressConfig) (bool, error) {
|
||||
fromMap := structs.Map(cfg)
|
||||
toMap := structs.Map(ngx.defCfg)
|
||||
curNginxCfg := mergo.MergeWithOverwrite(toMap, fromMap)
|
||||
curNginxCfg := merge(toMap, fromMap)
|
||||
|
||||
conf := make(map[string]interface{})
|
||||
conf["upstreams"] = upstreams
|
||||
conf["servers"] = servers
|
||||
conf["tcpUpstreams"] = tcpUpstreams
|
||||
conf["upstreams"] = ingressCfg.Upstreams
|
||||
conf["servers"] = ingressCfg.Servers
|
||||
conf["tcpUpstreams"] = ingressCfg.TCPUpstreams
|
||||
conf["defResolver"] = ngx.defResolver
|
||||
conf["sslDHParam"] = ngx.sslDHParam
|
||||
conf["cfg"] = curNginxCfg
|
||||
|
|
@ -59,11 +58,7 @@ func (ngx *NginxManager) writeCfg(cfg *nginxConfiguration, upstreams []*Upstream
|
|||
buffer := new(bytes.Buffer)
|
||||
err := ngx.template.Execute(buffer, conf)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
changed, err := ngx.needsReload(buffer)
|
||||
if err != nil {
|
||||
glog.Infof("NGINX error: %v", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
|
@ -75,5 +70,10 @@ func (ngx *NginxManager) writeCfg(cfg *nginxConfiguration, upstreams []*Upstream
|
|||
glog.Infof("NGINX configuration: %v", string(b))
|
||||
}
|
||||
|
||||
changed, err := ngx.needsReload(buffer)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return changed, nil
|
||||
}
|
||||
|
|
|
|||
33
controllers/nginx-third-party/nginx/utils.go
vendored
33
controllers/nginx-third-party/nginx/utils.go
vendored
|
|
@ -24,6 +24,7 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
|
@ -76,8 +77,7 @@ func getDnsServers() []string {
|
|||
return nameservers
|
||||
}
|
||||
|
||||
// ReadConfig obtains the configuration defined by the user or returns the default if it does not
|
||||
// exists or if is not a well formed json object
|
||||
// ReadConfig obtains the configuration defined by the user merged with the defaults.
|
||||
func (ngx *NginxManager) ReadConfig(config *api.ConfigMap) (*nginxConfiguration, error) {
|
||||
if len(config.Data) == 0 {
|
||||
return newDefaultNginxCfg(), nil
|
||||
|
|
@ -157,3 +157,32 @@ func diff(b1, b2 []byte) (data []byte, err error) {
|
|||
}
|
||||
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) {
|
||||
value := reflect.ValueOf(iface)
|
||||
if value.Kind() == reflect.Map {
|
||||
m := map[string]interface{}{}
|
||||
for _, k := range value.MapKeys() {
|
||||
m[k.String()] = value.MapIndex(k).Interface()
|
||||
}
|
||||
|
||||
return m, true
|
||||
}
|
||||
|
||||
return map[string]interface{}{}, false
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue