diff --git a/nameresolution/configuration.go b/nameresolution/configuration.go new file mode 100644 index 000000000..2680fe1d7 --- /dev/null +++ b/nameresolution/configuration.go @@ -0,0 +1,48 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation and Dapr Contributors. +// Licensed under the MIT License. +// ------------------------------------------------------------ + +package nameresolution + +import ( + "fmt" +) + +// ConvertConfig converts map[interface{}]interface{} to map[string]interface{} to normalize +// for JSON and usage in component initialization. +func ConvertConfig(i interface{}) (interface{}, error) { + var err error + switch x := i.(type) { + case map[interface{}]interface{}: + m2 := map[string]interface{}{} + for k, v := range x { + if strKey, ok := k.(string); ok { + if m2[strKey], err = ConvertConfig(v); err != nil { + return nil, err + } + } else { + return nil, fmt.Errorf("error parsing config field: %v", k) + } + } + + return m2, nil + case map[string]interface{}: + m2 := map[string]interface{}{} + for k, v := range x { + if m2[k], err = ConvertConfig(v); err != nil { + return nil, err + } + } + + return m2, nil + case []interface{}: + for i, v := range x { + if x[i], err = ConvertConfig(v); err != nil { + return nil, err + } + } + } + + return i, nil +} diff --git a/nameresolution/configuration_test.go b/nameresolution/configuration_test.go new file mode 100644 index 000000000..aa2935df5 --- /dev/null +++ b/nameresolution/configuration_test.go @@ -0,0 +1,97 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation and Dapr Contributors. +// Licensed under the MIT License. +// ------------------------------------------------------------ + +package nameresolution_test + +import ( + "testing" + + "github.com/dapr/components-contrib/nameresolution" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestConfiguration(t *testing.T) { + tests := map[string]struct { + input interface{} + expected interface{} + err string + }{ + "simple": {input: "test", expected: "test"}, + "map of string to interface{}": { + input: map[string]interface{}{ + "test": "1234", + "nested": map[string]interface{}{ + "value": "5678", + }, + }, expected: map[string]interface{}{ + "test": "1234", + "nested": map[string]interface{}{ + "value": "5678", + }, + }, + }, + "map of string to interface{} with error": { + input: map[string]interface{}{ + "test": "1234", + "nested": map[interface{}]interface{}{ + 5678: "5678", + }, + }, err: "error parsing config field: 5678", + }, + "map of interface{} to interface{}": { + input: map[string]interface{}{ + "test": "1234", + "nested": map[interface{}]interface{}{ + "value": "5678", + }, + }, expected: map[string]interface{}{ + "test": "1234", + "nested": map[string]interface{}{ + "value": "5678", + }, + }, + }, + "map of interface{} to interface{} with error": { + input: map[interface{}]interface{}{ + "test": "1234", + "nested": map[interface{}]interface{}{ + 5678: "5678", + }, + }, err: "error parsing config field: 5678", + }, + "slice of interface{}": { + input: []interface{}{ + map[interface{}]interface{}{ + "value": "5678", + }, + }, expected: []interface{}{ + map[string]interface{}{ + "value": "5678", + }, + }, + }, + "slice of interface{} with error": { + input: []interface{}{ + map[interface{}]interface{}{ + 1234: "1234", + }, + }, err: "error parsing config field: 1234", + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + actual, err := nameresolution.ConvertConfig(tc.input) + if tc.err != "" { + require.Error(t, err) + assert.EqualError(t, err, tc.err) + } else { + require.NoError(t, err) + } + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/nameresolution/consul/configuration.go b/nameresolution/consul/configuration.go index 3c9cdf2e3..5bcca2940 100644 --- a/nameresolution/consul/configuration.go +++ b/nameresolution/consul/configuration.go @@ -7,6 +7,8 @@ import ( "time" consul "github.com/hashicorp/consul/api" + + "github.com/dapr/components-contrib/nameresolution" ) // The intermediateConfig is based off of the consul api types. User configurations are @@ -37,7 +39,7 @@ type configSpec struct { func parseConfig(rawConfig interface{}) (configSpec, error) { result := configSpec{} config := intermediateConfig{} - rawConfig, err := convertGenericConfig(rawConfig) + rawConfig, err := nameresolution.ConvertConfig(rawConfig) if err != nil { return result, err } @@ -59,34 +61,6 @@ func parseConfig(rawConfig interface{}) (configSpec, error) { return result, nil } -// helper function for transforming interface{} into json serializable -func convertGenericConfig(i interface{}) (interface{}, error) { - var err error - switch x := i.(type) { - case map[interface{}]interface{}: - m2 := map[string]interface{}{} - for k, v := range x { - if strKey, ok := k.(string); ok { - if m2[strKey], err = convertGenericConfig(v); err != nil { - return nil, err - } - } else { - return nil, fmt.Errorf("error parsing config field: %v", k) - } - } - - return m2, nil - case []interface{}: - for i, v := range x { - if x[i], err = convertGenericConfig(v); err != nil { - return nil, err - } - } - } - - return i, nil -} - func mapConfig(config intermediateConfig) configSpec { return configSpec{ Client: mapClientConfig(config.Client), diff --git a/nameresolution/kubernetes/kubernetes.go b/nameresolution/kubernetes/kubernetes.go index 089367bc1..75fb73be0 100644 --- a/nameresolution/kubernetes/kubernetes.go +++ b/nameresolution/kubernetes/kubernetes.go @@ -12,22 +12,42 @@ import ( "github.com/dapr/kit/logger" ) +const ( + DefaultClusterDomain = "cluster.local" + ClusterDomainKey = "clusterDomain" +) + type resolver struct { - logger logger.Logger + logger logger.Logger + clusterDomain string } // NewResolver creates Kubernetes name resolver. func NewResolver(logger logger.Logger) nameresolution.Resolver { - return &resolver{logger: logger} + return &resolver{ + logger: logger, + clusterDomain: DefaultClusterDomain, + } } // Init initializes Kubernetes name resolver. func (k *resolver) Init(metadata nameresolution.Metadata) error { + configInterface, err := nameresolution.ConvertConfig(metadata.Configuration) + if err != nil { + return err + } + if config, ok := configInterface.(map[string]string); ok { + clusterDomain := config[ClusterDomainKey] + if clusterDomain != "" { + k.clusterDomain = clusterDomain + } + } + return nil } // ResolveID resolves name to address in Kubernetes. func (k *resolver) ResolveID(req nameresolution.ResolveRequest) (string, error) { // Dapr requires this formatting for Kubernetes services - return fmt.Sprintf("%s-dapr.%s.svc.cluster.local:%d", req.ID, req.Namespace, req.Port), nil + return fmt.Sprintf("%s-dapr.%s.svc.%s:%d", req.ID, req.Namespace, k.clusterDomain, req.Port), nil } diff --git a/nameresolution/kubernetes/kubernetes_test.go b/nameresolution/kubernetes/kubernetes_test.go index f984c721a..40212ebb1 100644 --- a/nameresolution/kubernetes/kubernetes_test.go +++ b/nameresolution/kubernetes/kubernetes_test.go @@ -23,3 +23,19 @@ func TestResolve(t *testing.T) { assert.Nil(t, err) assert.Equal(t, target, u) } + +func TestResolveWithCustomClusterDomain(t *testing.T) { + resolver := NewResolver(logger.NewLogger("test")) + _ = resolver.Init(nameresolution.Metadata{ + Configuration: map[string]string{ + "clusterDomain": "mydomain.com", + }, + }) + request := nameresolution.ResolveRequest{ID: "myid", Namespace: "abc", Port: 1234} + + u := "myid-dapr.abc.svc.mydomain.com:1234" + target, err := resolver.ResolveID(request) + + assert.Nil(t, err) + assert.Equal(t, target, u) +}