2019-04-29 22:08:09 +00:00
|
|
|
package api
|
|
|
|
|
|
|
|
import (
|
2019-08-05 22:15:22 +00:00
|
|
|
"encoding/json"
|
2019-04-29 22:08:09 +00:00
|
|
|
"testing"
|
2019-08-05 22:15:22 +00:00
|
|
|
"time"
|
2019-04-29 22:08:09 +00:00
|
|
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestAPI_ConfigEntries(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
c, s := makeClient(t)
|
|
|
|
defer s.Stop()
|
|
|
|
|
|
|
|
config_entries := c.ConfigEntries()
|
|
|
|
|
|
|
|
t.Run("Proxy Defaults", func(t *testing.T) {
|
|
|
|
global_proxy := &ProxyConfigEntry{
|
|
|
|
Kind: ProxyDefaults,
|
|
|
|
Name: ProxyConfigGlobal,
|
|
|
|
Config: map[string]interface{}{
|
|
|
|
"foo": "bar",
|
|
|
|
"bar": 1.0,
|
|
|
|
},
|
2020-09-29 14:11:57 +00:00
|
|
|
Meta: map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
"gir": "zim",
|
|
|
|
},
|
2019-04-29 22:08:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// set it
|
2019-04-30 23:27:16 +00:00
|
|
|
_, wm, err := config_entries.Set(global_proxy, nil)
|
2019-04-29 22:08:09 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, wm)
|
|
|
|
require.NotEqual(t, 0, wm.RequestTime)
|
|
|
|
|
|
|
|
// get it
|
|
|
|
entry, qm, err := config_entries.Get(ProxyDefaults, ProxyConfigGlobal, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, qm)
|
|
|
|
require.NotEqual(t, 0, qm.RequestTime)
|
|
|
|
|
|
|
|
// verify it
|
|
|
|
readProxy, ok := entry.(*ProxyConfigEntry)
|
|
|
|
require.True(t, ok)
|
|
|
|
require.Equal(t, global_proxy.Kind, readProxy.Kind)
|
|
|
|
require.Equal(t, global_proxy.Name, readProxy.Name)
|
|
|
|
require.Equal(t, global_proxy.Config, readProxy.Config)
|
2020-09-29 14:11:57 +00:00
|
|
|
require.Equal(t, global_proxy.Meta, readProxy.Meta)
|
|
|
|
require.Equal(t, global_proxy.Meta, readProxy.GetMeta())
|
2019-04-29 22:08:09 +00:00
|
|
|
|
|
|
|
global_proxy.Config["baz"] = true
|
2019-04-30 23:27:16 +00:00
|
|
|
// CAS update fail
|
|
|
|
written, _, err := config_entries.CAS(global_proxy, 0, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.False(t, written)
|
|
|
|
|
|
|
|
// CAS update success
|
|
|
|
written, wm, err = config_entries.CAS(global_proxy, readProxy.ModifyIndex, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, wm)
|
|
|
|
require.NotEqual(t, 0, wm.RequestTime)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.True(t, written)
|
|
|
|
|
|
|
|
// Non CAS update
|
|
|
|
global_proxy.Config["baz"] = "baz"
|
|
|
|
_, wm, err = config_entries.Set(global_proxy, nil)
|
2019-04-29 22:08:09 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, wm)
|
|
|
|
require.NotEqual(t, 0, wm.RequestTime)
|
|
|
|
|
|
|
|
// list it
|
|
|
|
entries, qm, err := config_entries.List(ProxyDefaults, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, qm)
|
|
|
|
require.NotEqual(t, 0, qm.RequestTime)
|
|
|
|
require.Len(t, entries, 1)
|
|
|
|
readProxy, ok = entries[0].(*ProxyConfigEntry)
|
|
|
|
require.True(t, ok)
|
|
|
|
require.Equal(t, global_proxy.Kind, readProxy.Kind)
|
|
|
|
require.Equal(t, global_proxy.Name, readProxy.Name)
|
|
|
|
require.Equal(t, global_proxy.Config, readProxy.Config)
|
|
|
|
|
|
|
|
// delete it
|
|
|
|
wm, err = config_entries.Delete(ProxyDefaults, ProxyConfigGlobal, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, wm)
|
|
|
|
require.NotEqual(t, 0, wm.RequestTime)
|
|
|
|
|
2020-06-05 19:28:03 +00:00
|
|
|
_, _, err = config_entries.Get(ProxyDefaults, ProxyConfigGlobal, nil)
|
2019-04-29 22:08:09 +00:00
|
|
|
require.Error(t, err)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Service Defaults", func(t *testing.T) {
|
|
|
|
service := &ServiceConfigEntry{
|
|
|
|
Kind: ServiceDefaults,
|
|
|
|
Name: "foo",
|
|
|
|
Protocol: "udp",
|
2020-09-29 14:11:57 +00:00
|
|
|
Meta: map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
"gir": "zim",
|
|
|
|
},
|
2019-04-29 22:08:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
service2 := &ServiceConfigEntry{
|
|
|
|
Kind: ServiceDefaults,
|
|
|
|
Name: "bar",
|
|
|
|
Protocol: "tcp",
|
|
|
|
}
|
|
|
|
|
|
|
|
// set it
|
2019-04-30 23:27:16 +00:00
|
|
|
_, wm, err := config_entries.Set(service, nil)
|
2019-04-29 22:08:09 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, wm)
|
|
|
|
require.NotEqual(t, 0, wm.RequestTime)
|
|
|
|
|
|
|
|
// also set the second one
|
2019-04-30 23:27:16 +00:00
|
|
|
_, wm, err = config_entries.Set(service2, nil)
|
2019-04-29 22:08:09 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, wm)
|
|
|
|
require.NotEqual(t, 0, wm.RequestTime)
|
|
|
|
|
|
|
|
// get it
|
|
|
|
entry, qm, err := config_entries.Get(ServiceDefaults, "foo", nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, qm)
|
|
|
|
require.NotEqual(t, 0, qm.RequestTime)
|
|
|
|
|
|
|
|
// verify it
|
|
|
|
readService, ok := entry.(*ServiceConfigEntry)
|
|
|
|
require.True(t, ok)
|
|
|
|
require.Equal(t, service.Kind, readService.Kind)
|
|
|
|
require.Equal(t, service.Name, readService.Name)
|
|
|
|
require.Equal(t, service.Protocol, readService.Protocol)
|
2020-09-29 14:11:57 +00:00
|
|
|
require.Equal(t, service.Meta, readService.Meta)
|
|
|
|
require.Equal(t, service.Meta, readService.GetMeta())
|
2019-04-29 22:08:09 +00:00
|
|
|
|
|
|
|
// update it
|
|
|
|
service.Protocol = "tcp"
|
2019-04-30 23:27:16 +00:00
|
|
|
|
|
|
|
// CAS fail
|
|
|
|
written, _, err := config_entries.CAS(service, 0, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.False(t, written)
|
|
|
|
|
|
|
|
// CAS success
|
|
|
|
written, wm, err = config_entries.CAS(service, readService.ModifyIndex, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, wm)
|
|
|
|
require.NotEqual(t, 0, wm.RequestTime)
|
|
|
|
require.True(t, written)
|
|
|
|
|
|
|
|
// update no cas
|
2019-05-01 23:39:31 +00:00
|
|
|
service.Protocol = "http"
|
2019-04-30 23:27:16 +00:00
|
|
|
|
|
|
|
_, wm, err = config_entries.Set(service, nil)
|
2019-04-29 22:08:09 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, wm)
|
|
|
|
require.NotEqual(t, 0, wm.RequestTime)
|
|
|
|
|
|
|
|
// list them
|
|
|
|
entries, qm, err := config_entries.List(ServiceDefaults, nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, qm)
|
|
|
|
require.NotEqual(t, 0, qm.RequestTime)
|
|
|
|
require.Len(t, entries, 2)
|
|
|
|
|
|
|
|
for _, entry = range entries {
|
|
|
|
switch entry.GetName() {
|
|
|
|
case "foo":
|
2019-05-01 23:39:31 +00:00
|
|
|
// this also verifies that the update value was persisted and
|
2019-04-29 22:08:09 +00:00
|
|
|
// the updated values are seen
|
|
|
|
readService, ok = entry.(*ServiceConfigEntry)
|
|
|
|
require.True(t, ok)
|
|
|
|
require.Equal(t, service.Kind, readService.Kind)
|
|
|
|
require.Equal(t, service.Name, readService.Name)
|
|
|
|
require.Equal(t, service.Protocol, readService.Protocol)
|
|
|
|
case "bar":
|
|
|
|
readService, ok = entry.(*ServiceConfigEntry)
|
|
|
|
require.True(t, ok)
|
|
|
|
require.Equal(t, service2.Kind, readService.Kind)
|
|
|
|
require.Equal(t, service2.Name, readService.Name)
|
|
|
|
require.Equal(t, service2.Protocol, readService.Protocol)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// delete it
|
|
|
|
wm, err = config_entries.Delete(ServiceDefaults, "foo", nil)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, wm)
|
|
|
|
require.NotEqual(t, 0, wm.RequestTime)
|
|
|
|
|
|
|
|
// verify deletion
|
2020-06-05 19:28:03 +00:00
|
|
|
_, _, err = config_entries.Get(ServiceDefaults, "foo", nil)
|
2019-04-29 22:08:09 +00:00
|
|
|
require.Error(t, err)
|
|
|
|
})
|
|
|
|
}
|
2019-08-05 22:15:22 +00:00
|
|
|
|
|
|
|
func TestDecodeConfigEntry(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
for _, tc := range []struct {
|
|
|
|
name string
|
|
|
|
body string
|
|
|
|
expect ConfigEntry
|
|
|
|
expectErr string
|
|
|
|
}{
|
2019-09-26 02:55:52 +00:00
|
|
|
{
|
|
|
|
name: "expose-paths: kitchen sink proxy",
|
|
|
|
body: `
|
|
|
|
{
|
|
|
|
"Kind": "proxy-defaults",
|
|
|
|
"Name": "global",
|
|
|
|
"Expose": {
|
|
|
|
"Checks": true,
|
|
|
|
"Paths": [
|
|
|
|
{
|
|
|
|
"LocalPathPort": 8080,
|
|
|
|
"ListenerPort": 21500,
|
|
|
|
"Path": "/healthz",
|
|
|
|
"Protocol": "http2"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
expect: &ProxyConfigEntry{
|
|
|
|
Kind: "proxy-defaults",
|
|
|
|
Name: "global",
|
|
|
|
Expose: ExposeConfig{
|
|
|
|
Checks: true,
|
|
|
|
Paths: []ExposePath{
|
|
|
|
{
|
|
|
|
LocalPathPort: 8080,
|
|
|
|
ListenerPort: 21500,
|
|
|
|
Path: "/healthz",
|
|
|
|
Protocol: "http2",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "expose-paths: kitchen sink service default",
|
|
|
|
body: `
|
|
|
|
{
|
|
|
|
"Kind": "service-defaults",
|
|
|
|
"Name": "global",
|
|
|
|
"Expose": {
|
|
|
|
"Checks": true,
|
|
|
|
"Paths": [
|
|
|
|
{
|
|
|
|
"LocalPathPort": 8080,
|
|
|
|
"ListenerPort": 21500,
|
|
|
|
"Path": "/healthz",
|
|
|
|
"Protocol": "http2"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
expect: &ServiceConfigEntry{
|
|
|
|
Kind: "service-defaults",
|
|
|
|
Name: "global",
|
|
|
|
Expose: ExposeConfig{
|
|
|
|
Checks: true,
|
|
|
|
Paths: []ExposePath{
|
|
|
|
{
|
|
|
|
LocalPathPort: 8080,
|
|
|
|
ListenerPort: 21500,
|
|
|
|
Path: "/healthz",
|
|
|
|
Protocol: "http2",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2019-08-05 22:15:22 +00:00
|
|
|
{
|
|
|
|
name: "proxy-defaults",
|
|
|
|
body: `
|
|
|
|
{
|
|
|
|
"Kind": "proxy-defaults",
|
|
|
|
"Name": "main",
|
2020-09-02 19:10:25 +00:00
|
|
|
"Meta" : {
|
|
|
|
"foo": "bar",
|
|
|
|
"gir": "zim"
|
|
|
|
},
|
2019-08-05 22:15:22 +00:00
|
|
|
"Config": {
|
|
|
|
"foo": 19,
|
|
|
|
"bar": "abc",
|
|
|
|
"moreconfig": {
|
|
|
|
"moar": "config"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"MeshGateway": {
|
|
|
|
"Mode": "remote"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
expect: &ProxyConfigEntry{
|
|
|
|
Kind: "proxy-defaults",
|
|
|
|
Name: "main",
|
2020-09-02 19:10:25 +00:00
|
|
|
Meta: map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
"gir": "zim",
|
|
|
|
},
|
2019-08-05 22:15:22 +00:00
|
|
|
Config: map[string]interface{}{
|
|
|
|
"foo": float64(19),
|
|
|
|
"bar": "abc",
|
|
|
|
"moreconfig": map[string]interface{}{
|
|
|
|
"moar": "config",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
MeshGateway: MeshGatewayConfig{
|
|
|
|
Mode: MeshGatewayModeRemote,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "service-defaults",
|
|
|
|
body: `
|
|
|
|
{
|
|
|
|
"Kind": "service-defaults",
|
|
|
|
"Name": "main",
|
2020-09-02 19:10:25 +00:00
|
|
|
"Meta" : {
|
|
|
|
"foo": "bar",
|
|
|
|
"gir": "zim"
|
|
|
|
},
|
2019-08-05 22:15:22 +00:00
|
|
|
"Protocol": "http",
|
2019-08-19 17:19:44 +00:00
|
|
|
"ExternalSNI": "abc-123",
|
2019-08-05 22:15:22 +00:00
|
|
|
"MeshGateway": {
|
|
|
|
"Mode": "remote"
|
2021-03-09 05:10:27 +00:00
|
|
|
},
|
|
|
|
"Connect": {
|
|
|
|
"UpstreamConfigs": {
|
|
|
|
"redis": {
|
|
|
|
"PassiveHealthCheck": {
|
|
|
|
"MaxFailures": 3,
|
|
|
|
"Interval": "2s"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"finance/billing": {
|
|
|
|
"MeshGateway": {
|
|
|
|
"Mode": "remote"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"UpstreamDefaults": {
|
|
|
|
"ClusterJSON": "zip",
|
|
|
|
"ListenerJSON": "zop",
|
|
|
|
"ConnectTimeoutMs": 5000,
|
|
|
|
"Protocol": "http",
|
|
|
|
"Limits": {
|
|
|
|
"MaxConnections": 3,
|
|
|
|
"MaxPendingRequests": 4,
|
|
|
|
"MaxConcurrentRequests": 5
|
|
|
|
},
|
|
|
|
"PassiveHealthCheck": {
|
|
|
|
"MaxFailures": 5,
|
|
|
|
"Interval": "4s"
|
|
|
|
}
|
|
|
|
}
|
2019-08-05 22:15:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
expect: &ServiceConfigEntry{
|
2020-09-02 19:10:25 +00:00
|
|
|
Kind: "service-defaults",
|
|
|
|
Name: "main",
|
|
|
|
Meta: map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
"gir": "zim",
|
|
|
|
},
|
2019-08-19 17:19:44 +00:00
|
|
|
Protocol: "http",
|
|
|
|
ExternalSNI: "abc-123",
|
2019-08-05 22:15:22 +00:00
|
|
|
MeshGateway: MeshGatewayConfig{
|
|
|
|
Mode: MeshGatewayModeRemote,
|
|
|
|
},
|
2021-03-09 05:10:27 +00:00
|
|
|
Connect: ConnectConfiguration{
|
|
|
|
UpstreamConfigs: map[string]UpstreamConfig{
|
|
|
|
"redis": {
|
|
|
|
PassiveHealthCheck: PassiveHealthCheck{
|
|
|
|
MaxFailures: 3,
|
|
|
|
Interval: 2 * time.Second,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"finance/billing": {
|
|
|
|
MeshGateway: MeshGatewayConfig{Mode: "remote"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
UpstreamDefaults: UpstreamConfig{
|
|
|
|
ClusterJSON: "zip",
|
|
|
|
ListenerJSON: "zop",
|
|
|
|
Protocol: "http",
|
|
|
|
ConnectTimeoutMs: 5000,
|
|
|
|
Limits: UpstreamLimits{
|
|
|
|
MaxConnections: 3,
|
|
|
|
MaxPendingRequests: 4,
|
|
|
|
MaxConcurrentRequests: 5,
|
|
|
|
},
|
|
|
|
PassiveHealthCheck: PassiveHealthCheck{
|
|
|
|
MaxFailures: 5,
|
|
|
|
Interval: 4 * time.Second,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2019-08-05 22:15:22 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "service-router: kitchen sink",
|
|
|
|
body: `
|
|
|
|
{
|
|
|
|
"Kind": "service-router",
|
|
|
|
"Name": "main",
|
2020-09-02 19:10:25 +00:00
|
|
|
"Meta" : {
|
|
|
|
"foo": "bar",
|
|
|
|
"gir": "zim"
|
|
|
|
},
|
2019-08-05 22:15:22 +00:00
|
|
|
"Routes": [
|
|
|
|
{
|
|
|
|
"Match": {
|
|
|
|
"HTTP": {
|
|
|
|
"PathExact": "/foo",
|
|
|
|
"Header": [
|
|
|
|
{
|
|
|
|
"Name": "debug1",
|
|
|
|
"Present": true
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Name": "debug2",
|
|
|
|
"Present": false,
|
|
|
|
"Invert": true
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Name": "debug3",
|
|
|
|
"Exact": "1"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Name": "debug4",
|
|
|
|
"Prefix": "aaa"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Name": "debug5",
|
|
|
|
"Suffix": "bbb"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Name": "debug6",
|
|
|
|
"Regex": "a.*z"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"Destination": {
|
|
|
|
"Service": "carrot",
|
|
|
|
"ServiceSubset": "kale",
|
|
|
|
"Namespace": "leek",
|
|
|
|
"PrefixRewrite": "/alternate",
|
|
|
|
"RequestTimeout": "99s",
|
|
|
|
"NumRetries": 12345,
|
|
|
|
"RetryOnConnectFailure": true,
|
|
|
|
"RetryOnStatusCodes": [401, 209]
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Match": {
|
|
|
|
"HTTP": {
|
|
|
|
"PathPrefix": "/foo",
|
|
|
|
"Methods": [ "GET", "DELETE" ],
|
|
|
|
"QueryParam": [
|
|
|
|
{
|
|
|
|
"Name": "hack1",
|
|
|
|
"Present": true
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Name": "hack2",
|
|
|
|
"Exact": "1"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Name": "hack3",
|
|
|
|
"Regex": "a.*z"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Match": {
|
|
|
|
"HTTP": {
|
|
|
|
"PathRegex": "/foo"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
expect: &ServiceRouterConfigEntry{
|
|
|
|
Kind: "service-router",
|
|
|
|
Name: "main",
|
2020-09-02 19:10:25 +00:00
|
|
|
Meta: map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
"gir": "zim",
|
|
|
|
},
|
2019-08-05 22:15:22 +00:00
|
|
|
Routes: []ServiceRoute{
|
|
|
|
{
|
|
|
|
Match: &ServiceRouteMatch{
|
|
|
|
HTTP: &ServiceRouteHTTPMatch{
|
|
|
|
PathExact: "/foo",
|
|
|
|
Header: []ServiceRouteHTTPMatchHeader{
|
|
|
|
{
|
|
|
|
Name: "debug1",
|
|
|
|
Present: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "debug2",
|
|
|
|
Present: false,
|
|
|
|
Invert: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "debug3",
|
|
|
|
Exact: "1",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "debug4",
|
|
|
|
Prefix: "aaa",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "debug5",
|
|
|
|
Suffix: "bbb",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "debug6",
|
|
|
|
Regex: "a.*z",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Destination: &ServiceRouteDestination{
|
|
|
|
Service: "carrot",
|
|
|
|
ServiceSubset: "kale",
|
|
|
|
Namespace: "leek",
|
|
|
|
PrefixRewrite: "/alternate",
|
|
|
|
RequestTimeout: 99 * time.Second,
|
|
|
|
NumRetries: 12345,
|
|
|
|
RetryOnConnectFailure: true,
|
|
|
|
RetryOnStatusCodes: []uint32{401, 209},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Match: &ServiceRouteMatch{
|
|
|
|
HTTP: &ServiceRouteHTTPMatch{
|
|
|
|
PathPrefix: "/foo",
|
|
|
|
Methods: []string{"GET", "DELETE"},
|
|
|
|
QueryParam: []ServiceRouteHTTPMatchQueryParam{
|
|
|
|
{
|
|
|
|
Name: "hack1",
|
|
|
|
Present: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "hack2",
|
|
|
|
Exact: "1",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "hack3",
|
|
|
|
Regex: "a.*z",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Match: &ServiceRouteMatch{
|
|
|
|
HTTP: &ServiceRouteHTTPMatch{
|
|
|
|
PathRegex: "/foo",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "service-splitter: kitchen sink",
|
|
|
|
body: `
|
|
|
|
{
|
|
|
|
"Kind": "service-splitter",
|
|
|
|
"Name": "main",
|
2020-09-02 19:10:25 +00:00
|
|
|
"Meta" : {
|
|
|
|
"foo": "bar",
|
|
|
|
"gir": "zim"
|
|
|
|
},
|
2019-08-05 22:15:22 +00:00
|
|
|
"Splits": [
|
|
|
|
{
|
|
|
|
"Weight": 99.1,
|
|
|
|
"ServiceSubset": "v1"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Weight": 0.9,
|
|
|
|
"Service": "other",
|
|
|
|
"Namespace": "alt"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
expect: &ServiceSplitterConfigEntry{
|
|
|
|
Kind: ServiceSplitter,
|
|
|
|
Name: "main",
|
2020-09-02 19:10:25 +00:00
|
|
|
Meta: map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
"gir": "zim",
|
|
|
|
},
|
2019-08-05 22:15:22 +00:00
|
|
|
Splits: []ServiceSplit{
|
|
|
|
{
|
|
|
|
Weight: 99.1,
|
|
|
|
ServiceSubset: "v1",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Weight: 0.9,
|
|
|
|
Service: "other",
|
|
|
|
Namespace: "alt",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "service-resolver: subsets with failover",
|
|
|
|
body: `
|
|
|
|
{
|
|
|
|
"Kind": "service-resolver",
|
|
|
|
"Name": "main",
|
2020-09-02 19:10:25 +00:00
|
|
|
"Meta" : {
|
|
|
|
"foo": "bar",
|
|
|
|
"gir": "zim"
|
|
|
|
},
|
2019-08-05 22:15:22 +00:00
|
|
|
"DefaultSubset": "v1",
|
|
|
|
"ConnectTimeout": "15s",
|
|
|
|
"Subsets": {
|
|
|
|
"v1": {
|
|
|
|
"Filter": "Service.Meta.version == v1"
|
|
|
|
},
|
|
|
|
"v2": {
|
|
|
|
"Filter": "Service.Meta.version == v2",
|
|
|
|
"OnlyPassing": true
|
|
|
|
}
|
|
|
|
},
|
|
|
|
"Failover": {
|
|
|
|
"v2": {
|
|
|
|
"Service": "failcopy",
|
|
|
|
"ServiceSubset": "sure",
|
|
|
|
"Namespace": "neighbor",
|
|
|
|
"Datacenters": ["dc5", "dc14"]
|
|
|
|
},
|
|
|
|
"*": {
|
|
|
|
"Datacenters": ["dc7"]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}`,
|
|
|
|
expect: &ServiceResolverConfigEntry{
|
2020-09-02 19:10:25 +00:00
|
|
|
Kind: "service-resolver",
|
|
|
|
Name: "main",
|
|
|
|
Meta: map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
"gir": "zim",
|
|
|
|
},
|
2019-08-05 22:15:22 +00:00
|
|
|
DefaultSubset: "v1",
|
|
|
|
ConnectTimeout: 15 * time.Second,
|
|
|
|
Subsets: map[string]ServiceResolverSubset{
|
|
|
|
"v1": {
|
|
|
|
Filter: "Service.Meta.version == v1",
|
|
|
|
},
|
|
|
|
"v2": {
|
|
|
|
Filter: "Service.Meta.version == v2",
|
|
|
|
OnlyPassing: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Failover: map[string]ServiceResolverFailover{
|
|
|
|
"v2": {
|
|
|
|
Service: "failcopy",
|
|
|
|
ServiceSubset: "sure",
|
|
|
|
Namespace: "neighbor",
|
|
|
|
Datacenters: []string{"dc5", "dc14"},
|
|
|
|
},
|
|
|
|
"*": {
|
|
|
|
Datacenters: []string{"dc7"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "service-resolver: redirect",
|
|
|
|
body: `
|
|
|
|
{
|
|
|
|
"Kind": "service-resolver",
|
|
|
|
"Name": "main",
|
|
|
|
"Redirect": {
|
|
|
|
"Service": "other",
|
|
|
|
"ServiceSubset": "backup",
|
|
|
|
"Namespace": "alt",
|
|
|
|
"Datacenter": "dc9"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
expect: &ServiceResolverConfigEntry{
|
|
|
|
Kind: "service-resolver",
|
|
|
|
Name: "main",
|
|
|
|
Redirect: &ServiceResolverRedirect{
|
|
|
|
Service: "other",
|
|
|
|
ServiceSubset: "backup",
|
|
|
|
Namespace: "alt",
|
|
|
|
Datacenter: "dc9",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "service-resolver: default",
|
|
|
|
body: `
|
|
|
|
{
|
|
|
|
"Kind": "service-resolver",
|
|
|
|
"Name": "main"
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
expect: &ServiceResolverConfigEntry{
|
|
|
|
Kind: "service-resolver",
|
|
|
|
Name: "main",
|
|
|
|
},
|
|
|
|
},
|
2020-09-12 00:34:03 +00:00
|
|
|
{
|
|
|
|
name: "service-resolver: envoy hash lb kitchen sink",
|
|
|
|
body: `
|
|
|
|
{
|
|
|
|
"Kind": "service-resolver",
|
|
|
|
"Name": "main",
|
|
|
|
"LoadBalancer": {
|
|
|
|
"Policy": "ring_hash",
|
|
|
|
"RingHashConfig": {
|
|
|
|
"MinimumRingSize": 1,
|
|
|
|
"MaximumRingSize": 2
|
|
|
|
},
|
|
|
|
"HashPolicies": [
|
|
|
|
{
|
|
|
|
"Field": "cookie",
|
|
|
|
"FieldValue": "good-cookie",
|
|
|
|
"CookieConfig": {
|
|
|
|
"TTL": "1s",
|
|
|
|
"Path": "/oven"
|
|
|
|
},
|
|
|
|
"Terminal": true
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Field": "cookie",
|
|
|
|
"FieldValue": "less-good-cookie",
|
|
|
|
"CookieConfig": {
|
|
|
|
"Session": true,
|
|
|
|
"Path": "/toaster"
|
|
|
|
},
|
|
|
|
"Terminal": true
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Field": "header",
|
|
|
|
"FieldValue": "x-user-id"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"SourceIP": true
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
expect: &ServiceResolverConfigEntry{
|
|
|
|
Kind: "service-resolver",
|
|
|
|
Name: "main",
|
|
|
|
LoadBalancer: &LoadBalancer{
|
|
|
|
Policy: "ring_hash",
|
|
|
|
RingHashConfig: &RingHashConfig{
|
|
|
|
MinimumRingSize: 1,
|
|
|
|
MaximumRingSize: 2,
|
|
|
|
},
|
|
|
|
HashPolicies: []HashPolicy{
|
|
|
|
{
|
|
|
|
Field: "cookie",
|
|
|
|
FieldValue: "good-cookie",
|
|
|
|
CookieConfig: &CookieConfig{
|
|
|
|
TTL: 1 * time.Second,
|
|
|
|
Path: "/oven",
|
|
|
|
},
|
|
|
|
Terminal: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Field: "cookie",
|
|
|
|
FieldValue: "less-good-cookie",
|
|
|
|
CookieConfig: &CookieConfig{
|
|
|
|
Session: true,
|
|
|
|
Path: "/toaster",
|
|
|
|
},
|
|
|
|
Terminal: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Field: "header",
|
|
|
|
FieldValue: "x-user-id",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
SourceIP: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "service-resolver: envoy least request kitchen sink",
|
|
|
|
body: `
|
|
|
|
{
|
|
|
|
"Kind": "service-resolver",
|
|
|
|
"Name": "main",
|
|
|
|
"LoadBalancer": {
|
|
|
|
"Policy": "least_request",
|
|
|
|
"LeastRequestConfig": {
|
|
|
|
"ChoiceCount": 2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
expect: &ServiceResolverConfigEntry{
|
|
|
|
Kind: "service-resolver",
|
|
|
|
Name: "main",
|
|
|
|
LoadBalancer: &LoadBalancer{
|
|
|
|
Policy: "least_request",
|
|
|
|
LeastRequestConfig: &LeastRequestConfig{
|
|
|
|
ChoiceCount: 2,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-03-31 16:59:10 +00:00
|
|
|
{
|
|
|
|
name: "ingress-gateway",
|
|
|
|
body: `
|
|
|
|
{
|
|
|
|
"Kind": "ingress-gateway",
|
|
|
|
"Name": "ingress-web",
|
2020-09-02 19:10:25 +00:00
|
|
|
"Meta" : {
|
|
|
|
"foo": "bar",
|
|
|
|
"gir": "zim"
|
|
|
|
},
|
2020-04-30 20:50:25 +00:00
|
|
|
"Tls": {
|
|
|
|
"Enabled": true
|
|
|
|
},
|
2020-03-31 16:59:10 +00:00
|
|
|
"Listeners": [
|
|
|
|
{
|
|
|
|
"Port": 8080,
|
|
|
|
"Protocol": "http",
|
|
|
|
"Services": [
|
|
|
|
{
|
|
|
|
"Name": "web",
|
|
|
|
"Namespace": "foo"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Name": "db"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Port": 9999,
|
|
|
|
"Protocol": "tcp",
|
|
|
|
"Services": [
|
|
|
|
{
|
|
|
|
"Name": "mysql"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
expect: &IngressGatewayConfigEntry{
|
|
|
|
Kind: "ingress-gateway",
|
|
|
|
Name: "ingress-web",
|
2020-09-02 19:10:25 +00:00
|
|
|
Meta: map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
"gir": "zim",
|
|
|
|
},
|
2020-04-30 20:50:25 +00:00
|
|
|
TLS: GatewayTLSConfig{
|
|
|
|
Enabled: true,
|
|
|
|
},
|
2020-03-31 16:59:10 +00:00
|
|
|
Listeners: []IngressListener{
|
2020-06-16 17:19:31 +00:00
|
|
|
{
|
2020-03-31 16:59:10 +00:00
|
|
|
Port: 8080,
|
|
|
|
Protocol: "http",
|
|
|
|
Services: []IngressService{
|
2020-06-16 17:19:31 +00:00
|
|
|
{
|
2020-03-31 16:59:10 +00:00
|
|
|
Name: "web",
|
|
|
|
Namespace: "foo",
|
|
|
|
},
|
2020-06-16 17:19:31 +00:00
|
|
|
{
|
2020-03-31 16:59:10 +00:00
|
|
|
Name: "db",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-06-16 17:19:31 +00:00
|
|
|
{
|
2020-03-31 16:59:10 +00:00
|
|
|
Port: 9999,
|
|
|
|
Protocol: "tcp",
|
|
|
|
Services: []IngressService{
|
2020-06-16 17:19:31 +00:00
|
|
|
{
|
2020-03-31 16:59:10 +00:00
|
|
|
Name: "mysql",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-03-31 19:27:32 +00:00
|
|
|
{
|
|
|
|
name: "terminating-gateway",
|
|
|
|
body: `
|
|
|
|
{
|
|
|
|
"Kind": "terminating-gateway",
|
|
|
|
"Name": "terminating-west",
|
2020-09-02 19:10:25 +00:00
|
|
|
"Meta" : {
|
|
|
|
"foo": "bar",
|
|
|
|
"gir": "zim"
|
|
|
|
},
|
2020-03-31 19:27:32 +00:00
|
|
|
"Services": [
|
|
|
|
{
|
2020-09-02 19:10:25 +00:00
|
|
|
"Namespace": "foo",
|
2020-03-31 19:27:32 +00:00
|
|
|
"Name": "web",
|
|
|
|
"CAFile": "/etc/ca.pem",
|
|
|
|
"CertFile": "/etc/cert.pem",
|
2020-04-27 22:25:37 +00:00
|
|
|
"KeyFile": "/etc/tls.key",
|
|
|
|
"SNI": "mydomain"
|
2020-03-31 19:27:32 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"Name": "api"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Namespace": "bar",
|
|
|
|
"Name": "*"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}`,
|
|
|
|
expect: &TerminatingGatewayConfigEntry{
|
|
|
|
Kind: "terminating-gateway",
|
|
|
|
Name: "terminating-west",
|
2020-09-02 19:10:25 +00:00
|
|
|
Meta: map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
"gir": "zim",
|
|
|
|
},
|
2020-03-31 19:27:32 +00:00
|
|
|
Services: []LinkedService{
|
|
|
|
{
|
|
|
|
Namespace: "foo",
|
|
|
|
Name: "web",
|
|
|
|
CAFile: "/etc/ca.pem",
|
|
|
|
CertFile: "/etc/cert.pem",
|
|
|
|
KeyFile: "/etc/tls.key",
|
2020-04-27 22:25:37 +00:00
|
|
|
SNI: "mydomain",
|
2020-03-31 19:27:32 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "api",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Namespace: "bar",
|
|
|
|
Name: "*",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2020-10-06 22:09:13 +00:00
|
|
|
{
|
|
|
|
name: "service-intentions: kitchen sink",
|
|
|
|
body: `
|
|
|
|
{
|
|
|
|
"Kind": "service-intentions",
|
|
|
|
"Name": "web",
|
|
|
|
"Meta" : {
|
|
|
|
"foo": "bar",
|
|
|
|
"gir": "zim"
|
|
|
|
},
|
|
|
|
"Sources": [
|
|
|
|
{
|
|
|
|
"Name": "foo",
|
|
|
|
"Action": "deny",
|
|
|
|
"Type": "consul",
|
|
|
|
"Description": "foo desc"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Name": "bar",
|
|
|
|
"Action": "allow",
|
|
|
|
"Description": "bar desc"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Name": "l7",
|
|
|
|
"Permissions": [
|
|
|
|
{
|
|
|
|
"Action": "deny",
|
|
|
|
"HTTP": {
|
|
|
|
"PathExact": "/admin",
|
|
|
|
"Header": [
|
|
|
|
{
|
|
|
|
"Name": "hdr-present",
|
|
|
|
"Present": true
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Name": "hdr-exact",
|
|
|
|
"Exact": "exact"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Name": "hdr-prefix",
|
|
|
|
"Prefix": "prefix"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Name": "hdr-suffix",
|
|
|
|
"Suffix": "suffix"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Name": "hdr-regex",
|
|
|
|
"Regex": "regex"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Name": "hdr-absent",
|
|
|
|
"Present": true,
|
|
|
|
"Invert": true
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Action": "allow",
|
|
|
|
"HTTP": {
|
|
|
|
"PathPrefix": "/v3/"
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Action": "allow",
|
|
|
|
"HTTP": {
|
|
|
|
"PathRegex": "/v[12]/.*",
|
|
|
|
"Methods": [
|
|
|
|
"GET",
|
|
|
|
"POST"
|
|
|
|
]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Name": "*",
|
|
|
|
"Action": "deny",
|
|
|
|
"Description": "wild desc"
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
expect: &ServiceIntentionsConfigEntry{
|
|
|
|
Kind: "service-intentions",
|
|
|
|
Name: "web",
|
|
|
|
Meta: map[string]string{
|
|
|
|
"foo": "bar",
|
|
|
|
"gir": "zim",
|
|
|
|
},
|
|
|
|
Sources: []*SourceIntention{
|
|
|
|
{
|
|
|
|
Name: "foo",
|
|
|
|
Action: "deny",
|
|
|
|
Type: "consul",
|
|
|
|
Description: "foo desc",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "bar",
|
|
|
|
Action: "allow",
|
|
|
|
Description: "bar desc",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "l7",
|
|
|
|
Permissions: []*IntentionPermission{
|
|
|
|
{
|
|
|
|
Action: "deny",
|
|
|
|
HTTP: &IntentionHTTPPermission{
|
|
|
|
PathExact: "/admin",
|
|
|
|
Header: []IntentionHTTPHeaderPermission{
|
|
|
|
{
|
|
|
|
Name: "hdr-present",
|
|
|
|
Present: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "hdr-exact",
|
|
|
|
Exact: "exact",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "hdr-prefix",
|
|
|
|
Prefix: "prefix",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "hdr-suffix",
|
|
|
|
Suffix: "suffix",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "hdr-regex",
|
|
|
|
Regex: "regex",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "hdr-absent",
|
|
|
|
Present: true,
|
|
|
|
Invert: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Action: "allow",
|
|
|
|
HTTP: &IntentionHTTPPermission{
|
|
|
|
PathPrefix: "/v3/",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Action: "allow",
|
|
|
|
HTTP: &IntentionHTTPPermission{
|
|
|
|
PathRegex: "/v[12]/.*",
|
|
|
|
Methods: []string{"GET", "POST"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "*",
|
|
|
|
Action: "deny",
|
|
|
|
Description: "wild desc",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2019-08-05 22:15:22 +00:00
|
|
|
} {
|
|
|
|
tc := tc
|
|
|
|
|
|
|
|
t.Run(tc.name+": DecodeConfigEntry", func(t *testing.T) {
|
|
|
|
var raw map[string]interface{}
|
|
|
|
require.NoError(t, json.Unmarshal([]byte(tc.body), &raw))
|
|
|
|
|
|
|
|
got, err := DecodeConfigEntry(raw)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, tc.expect, got)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run(tc.name+": DecodeConfigEntryFromJSON", func(t *testing.T) {
|
|
|
|
got, err := DecodeConfigEntryFromJSON([]byte(tc.body))
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, tc.expect, got)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run(tc.name+": DecodeConfigEntrySlice", func(t *testing.T) {
|
|
|
|
var raw []map[string]interface{}
|
|
|
|
require.NoError(t, json.Unmarshal([]byte("["+tc.body+"]"), &raw))
|
|
|
|
|
|
|
|
got, err := decodeConfigEntrySlice(raw)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, got, 1)
|
|
|
|
require.Equal(t, tc.expect, got[0])
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|