Add config entry/state for Ingress Gateways (#7483)

* Add Ingress gateway config entry and other relevant structs

* Add api package tests for ingress gateways

* Embed EnterpriseMeta into ingress service struct

* Add namespace fields to api module and test consul config write decoding

* Don't require a port for ingress gateways

* Add snakeJSON and camelJSON cases in command test

* Run Normalize on service's ent metadata

Sadly cannot think of a way to test this in OSS.

* Every protocol requires at least 1 service

* Validate ingress protocols

* Update agent/structs/config_entry_gateways.go

Co-authored-by: Chris Piraino <cpiraino@hashicorp.com>
Co-authored-by: Freddy <freddygv@users.noreply.github.com>
This commit is contained in:
Kyle Havlovitz 2020-03-31 09:59:10 -07:00 committed by GitHub
parent 5035fe782a
commit c911174327
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 935 additions and 5 deletions

View File

@ -325,6 +325,7 @@ func (s *Store) validateProposedConfigEntryInGraph(
case structs.ServiceRouter:
case structs.ServiceSplitter:
case structs.ServiceResolver:
case structs.IngressGateway:
default:
return fmt.Errorf("unhandled kind %q during validation of %q", kind, name)
}

View File

@ -20,6 +20,7 @@ const (
ServiceRouter string = "service-router"
ServiceSplitter string = "service-splitter"
ServiceResolver string = "service-resolver"
IngressGateway string = "ingress-gateway"
ProxyConfigGlobal string = "global"
@ -376,6 +377,15 @@ func ConfigEntryDecodeRulesForKind(kind string) (skipWhenPatching []string, tran
"only_passing": "onlypassing",
"service_subset": "servicesubset",
}, nil
case IngressGateway:
return []string{
"listeners",
"Listeners",
"listeners.services",
"Listeners.Services",
}, map[string]string{
"service_subset": "servicesubset",
}, nil
default:
return nil, nil, fmt.Errorf("kind %q should be explicitly handled here", kind)
}
@ -466,6 +476,8 @@ func MakeConfigEntry(kind, name string) (ConfigEntry, error) {
return &ServiceSplitterConfigEntry{Name: name}, nil
case ServiceResolver:
return &ServiceResolverConfigEntry{Name: name}, nil
case IngressGateway:
return &IngressGatewayConfigEntry{Name: name}, nil
default:
return nil, fmt.Errorf("invalid config entry kind: %s", kind)
}
@ -477,6 +489,8 @@ func ValidateConfigEntryKind(kind string) bool {
return true
case ServiceRouter, ServiceSplitter, ServiceResolver:
return true
case IngressGateway:
return true
default:
return false
}

View File

@ -0,0 +1,161 @@
package structs
import (
"fmt"
"strings"
"github.com/hashicorp/consul/acl"
)
// IngressGatewayConfigEntry manages the configuration for an ingress service
// with the given name.
type IngressGatewayConfigEntry struct {
// Kind of the config entry. This will be set to structs.IngressGateway.
Kind string
// Name is used to match the config entry with its associated ingress gateway
// service. This should match the name provided in the service definition.
Name string
// Listeners declares what ports the ingress gateway should listen on, and
// what services to associated to those ports.
Listeners []IngressListener
EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
RaftIndex
}
type IngressListener struct {
// Port declares the port on which the ingress gateway should listen for traffic.
Port int
// Protocol declares what type of traffic this listener is expected to
// receive. Depending on the protocol, a listener might support multiplexing
// services over a single port, or additional discovery chain features. The
// current supported values are: (tcp | http).
Protocol string
// Services declares the set of services to which the listener forwards
// traffic.
//
// For "tcp" protocol listeners, only a single service is allowed.
// For "http" listeners, multiple services can be declared.
Services []IngressService
}
type IngressService struct {
// Name declares the service to which traffic should be forwarded.
//
// This can either be a specific service, or the wildcard specifier,
// "*". If the wildcard specifier is provided, the listener must be of "http"
// protocol and means that the listener will forward traffic to all services.
Name string
// ServiceSubset declares the specific service subset to which traffic should
// be sent. This must match an existing service subset declared in a
// service-resolver config entry.
ServiceSubset string
EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
}
func (e *IngressGatewayConfigEntry) GetKind() string {
return IngressGateway
}
func (e *IngressGatewayConfigEntry) GetName() string {
if e == nil {
return ""
}
return e.Name
}
func (e *IngressGatewayConfigEntry) Normalize() error {
if e == nil {
return fmt.Errorf("config entry is nil")
}
e.Kind = IngressGateway
for _, listener := range e.Listeners {
listener.Protocol = strings.ToLower(listener.Protocol)
for i := range listener.Services {
listener.Services[i].EnterpriseMeta.Normalize()
}
}
e.EnterpriseMeta.Normalize()
return nil
}
func (e *IngressGatewayConfigEntry) Validate() error {
validProtocols := map[string]bool{
"http": true,
"tcp": true,
}
declaredPorts := make(map[int]bool)
for _, listener := range e.Listeners {
if _, ok := declaredPorts[listener.Port]; ok {
return fmt.Errorf("port %d declared on two listeners", listener.Port)
}
declaredPorts[listener.Port] = true
if _, ok := validProtocols[listener.Protocol]; !ok {
return fmt.Errorf("Protocol must be either 'http' or 'tcp', '%s' is an unsupported protocol.", listener.Protocol)
}
for _, s := range listener.Services {
if s.Name == WildcardSpecifier && listener.Protocol != "http" {
return fmt.Errorf("Wildcard service name is only valid for protocol = 'http' (listener on port %d)", listener.Port)
}
if s.Name == "" {
return fmt.Errorf("Service name cannot be blank (listener on port %d)", listener.Port)
}
if s.NamespaceOrDefault() == WildcardSpecifier {
return fmt.Errorf("Wildcard namespace is not supported for ingress services (listener on port %d)", listener.Port)
}
}
if len(listener.Services) == 0 {
return fmt.Errorf("No service declared for listener with port %d", listener.Port)
}
// Validate that http features aren't being used with tcp or another non-supported protocol.
if listener.Protocol != "http" && len(listener.Services) > 1 {
return fmt.Errorf("Multiple services per listener are only supported for protocol = 'http' (listener on port %d)",
listener.Port)
}
}
return nil
}
func (e *IngressGatewayConfigEntry) CanRead(authz acl.Authorizer) bool {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.OperatorRead(&authzContext) == acl.Allow
}
func (e *IngressGatewayConfigEntry) CanWrite(authz acl.Authorizer) bool {
var authzContext acl.AuthorizerContext
e.FillAuthzContext(&authzContext)
return authz.OperatorWrite(&authzContext) == acl.Allow
}
func (e *IngressGatewayConfigEntry) GetRaftIndex() *RaftIndex {
if e == nil {
return &RaftIndex{}
}
return &e.RaftIndex
}
func (e *IngressGatewayConfigEntry) GetEnterpriseMeta() *EnterpriseMeta {
if e == nil {
return nil
}
return &e.EnterpriseMeta
}

View File

@ -0,0 +1,187 @@
package structs
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestIngressConfigEntry_Validate(t *testing.T) {
t.Parallel()
cases := []struct {
name string
entry IngressGatewayConfigEntry
expectErr string
}{
{
name: "port conflict",
entry: IngressGatewayConfigEntry{
Kind: "ingress-gateway",
Name: "ingress-web",
Listeners: []IngressListener{
{
Port: 1111,
Protocol: "tcp",
Services: []IngressService{
{
Name: "mysql",
},
},
},
{
Port: 1111,
Protocol: "tcp",
Services: []IngressService{
{
Name: "postgres",
},
},
},
},
},
expectErr: "port 1111 declared on two listeners",
},
{
name: "http features: wildcard",
entry: IngressGatewayConfigEntry{
Kind: "ingress-gateway",
Name: "ingress-web",
Listeners: []IngressListener{
{
Port: 1111,
Protocol: "http",
Services: []IngressService{
{
Name: "*",
},
},
},
},
},
},
{
name: "http features: wildcard service on invalid protocol",
entry: IngressGatewayConfigEntry{
Kind: "ingress-gateway",
Name: "ingress-web",
Listeners: []IngressListener{
{
Port: 1111,
Protocol: "tcp",
Services: []IngressService{
{
Name: "*",
},
},
},
},
},
expectErr: "Wildcard service name is only valid for protocol",
},
{
name: "http features: multiple services",
entry: IngressGatewayConfigEntry{
Kind: "ingress-gateway",
Name: "ingress-web",
Listeners: []IngressListener{
{
Port: 1111,
Protocol: "tcp",
Services: []IngressService{
{
Name: "db1",
},
{
Name: "db2",
},
},
},
},
},
expectErr: "multiple services per listener are only supported for protocol",
},
{
name: "tcp listener requires a defined service",
entry: IngressGatewayConfigEntry{
Kind: "ingress-gateway",
Name: "ingress-web",
Listeners: []IngressListener{
{
Port: 1111,
Protocol: "tcp",
Services: []IngressService{},
},
},
},
expectErr: "no service declared for listener with port 1111",
},
{
name: "http listener requires a defined service",
entry: IngressGatewayConfigEntry{
Kind: "ingress-gateway",
Name: "ingress-web",
Listeners: []IngressListener{
{
Port: 1111,
Protocol: "http",
Services: []IngressService{},
},
},
},
expectErr: "no service declared for listener with port 1111",
},
{
name: "empty service name not supported",
entry: IngressGatewayConfigEntry{
Kind: "ingress-gateway",
Name: "ingress-web",
Listeners: []IngressListener{
{
Port: 1111,
Protocol: "tcp",
Services: []IngressService{
{},
},
},
},
},
expectErr: "Service name cannot be blank",
},
{
name: "protocol validation",
entry: IngressGatewayConfigEntry{
Kind: "ingress-gateway",
Name: "ingress-web",
Listeners: []IngressListener{
{
Port: 1111,
Protocol: "asdf",
Services: []IngressService{
{
Name: "db",
},
},
},
},
},
expectErr: "Protocol must be either 'http' or 'tcp', 'asdf' is an unsupported protocol.",
},
}
for _, test := range cases {
// We explicitly copy the variable for the range statement so that can run
// tests in parallel.
tc := test
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
err := tc.entry.Validate()
if tc.expectErr != "" {
require.Error(t, err)
requireContainsLower(t, err.Error(), tc.expectErr)
} else {
require.NoError(t, err)
}
})
}
}

View File

@ -532,6 +532,121 @@ func TestDecodeConfigEntry(t *testing.T) {
Name: "main",
},
},
{
name: "ingress-gateway: kitchen sink",
snake: `
kind = "ingress-gateway"
name = "ingress-web"
listeners = [
{
port = 8080
protocol = "http"
services = [
{
name = "web"
},
{
name = "db"
}
]
},
{
port = 9999
protocol = "tcp"
services = [
{
name = "mysql"
}
]
},
{
port = 2234
protocol = "tcp"
services = [
{
name = "postgres"
service_subset = "v1"
}
]
}
]
`,
camel: `
Kind = "ingress-gateway"
Name = "ingress-web"
Listeners = [
{
Port = 8080
Protocol = "http"
Services = [
{
Name = "web"
},
{
Name = "db"
}
]
},
{
Port = 9999
Protocol = "tcp"
Services = [
{
Name = "mysql"
}
]
},
{
Port = 2234
Protocol = "tcp"
Services = [
{
Name = "postgres"
ServiceSubset = "v1"
}
]
}
]
`,
expect: &IngressGatewayConfigEntry{
Kind: "ingress-gateway",
Name: "ingress-web",
Listeners: []IngressListener{
IngressListener{
Port: 8080,
Protocol: "http",
Services: []IngressService{
IngressService{
Name: "web",
},
IngressService{
Name: "db",
},
},
},
IngressListener{
Port: 9999,
Protocol: "tcp",
Services: []IngressService{
IngressService{
Name: "mysql",
},
},
},
IngressListener{
Port: 2234,
Protocol: "tcp",
Services: []IngressService{
IngressService{
Name: "postgres",
ServiceSubset: "v1",
},
},
},
},
},
},
} {
tc := tc

View File

@ -358,7 +358,7 @@ func (u *Upstream) Identifier() string {
name := u.DestinationName
typ := u.DestinationType
if typ != UpstreamDestTypePreparedQuery && u.DestinationNamespace != "" && u.DestinationNamespace != "default" {
if typ != UpstreamDestTypePreparedQuery && u.DestinationNamespace != "" && u.DestinationNamespace != IntentionDefaultNamespace {
name = u.DestinationNamespace + "/" + u.DestinationName
}
if u.Datacenter != "" {

View File

@ -894,6 +894,11 @@ const (
// ServiceKindTerminatingGateway is a Terminating Gateway for the Connect
// feature. This service will proxy connections to services outside the mesh.
ServiceKindTerminatingGateway ServiceKind = "terminating-gateway"
// ServiceKindIngressGateway is an Ingress Gateway for the Connect feature.
// This service allows external traffic to enter the mesh based on
// centralized configuration.
ServiceKindIngressGateway ServiceKind = "ingress-gateway"
)
// Type to hold a address and port of a service
@ -1039,7 +1044,9 @@ func (s *NodeService) IsSidecarProxy() bool {
}
func (s *NodeService) IsGateway() bool {
return s.Kind == ServiceKindMeshGateway || s.Kind == ServiceKindTerminatingGateway
return s.Kind == ServiceKindMeshGateway ||
s.Kind == ServiceKindTerminatingGateway ||
s.Kind == ServiceKindIngressGateway
}
// Validate validates the node service configuration.
@ -1138,8 +1145,8 @@ func (s *NodeService) Validate() error {
// Gateway validation
if s.IsGateway() {
// Gateways must have a port
if s.Port == 0 {
// Non-ingress gateways must have a port
if s.Port == 0 && s.Kind != ServiceKindIngressGateway {
result = multierror.Append(result, fmt.Errorf("Port must be non-zero for a %s", s.Kind))
}

View File

@ -43,7 +43,7 @@ func (m *EnterpriseMeta) LessThan(_ *EnterpriseMeta) bool {
}
func (m *EnterpriseMeta) NamespaceOrDefault() string {
return "default"
return IntentionDefaultNamespace
}
func EnterpriseMetaInitializer(_ string) EnterpriseMeta {

View File

@ -420,6 +420,7 @@ func TestStructs_NodeService_ValidateTerminatingGateway(t *testing.T) {
Modify func(*NodeService)
Err string
}
cases := map[string]testCase{
"valid": testCase{
func(x *NodeService) {},
@ -467,6 +468,59 @@ func TestStructs_NodeService_ValidateTerminatingGateway(t *testing.T) {
}
}
func TestStructs_NodeService_ValidateIngressGateway(t *testing.T) {
type testCase struct {
Modify func(*NodeService)
Err string
}
cases := map[string]testCase{
"valid": testCase{
func(x *NodeService) {},
"",
},
"sidecar-service": testCase{
func(x *NodeService) { x.Connect.SidecarService = &ServiceDefinition{} },
"cannot have a sidecar service",
},
"proxy-destination-name": testCase{
func(x *NodeService) { x.Proxy.DestinationServiceName = "foo" },
"Proxy.DestinationServiceName configuration is invalid",
},
"proxy-destination-id": testCase{
func(x *NodeService) { x.Proxy.DestinationServiceID = "foo" },
"Proxy.DestinationServiceID configuration is invalid",
},
"proxy-local-address": testCase{
func(x *NodeService) { x.Proxy.LocalServiceAddress = "127.0.0.1" },
"Proxy.LocalServiceAddress configuration is invalid",
},
"proxy-local-port": testCase{
func(x *NodeService) { x.Proxy.LocalServicePort = 36 },
"Proxy.LocalServicePort configuration is invalid",
},
"proxy-upstreams": testCase{
func(x *NodeService) { x.Proxy.Upstreams = []Upstream{Upstream{}} },
"Proxy.Upstreams configuration is invalid",
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
ns := TestNodeServiceIngressGateway(t, "10.0.0.5")
tc.Modify(ns)
err := ns.Validate()
if tc.Err == "" {
require.NoError(t, err)
} else {
require.Error(t, err)
require.Contains(t, strings.ToLower(err.Error()), strings.ToLower(tc.Err))
}
})
}
}
func TestStructs_NodeService_ValidateExposeConfig(t *testing.T) {
type testCase struct {
Modify func(*NodeService)

View File

@ -112,6 +112,14 @@ func TestNodeServiceMeshGatewayWithAddrs(t testing.T, address string, port int,
}
}
func TestNodeServiceIngressGateway(t testing.T, address string) *NodeService {
return &NodeService{
Kind: ServiceKindIngressGateway,
Service: "ingress-gateway",
Address: address,
}
}
// TestNodeServiceSidecar returns a *NodeService representing a service
// registration with a nested Sidecar registration.
func TestNodeServiceSidecar(t testing.T) *NodeService {

View File

@ -17,6 +17,7 @@ const (
ServiceRouter string = "service-router"
ServiceSplitter string = "service-splitter"
ServiceResolver string = "service-resolver"
IngressGateway string = "ingress-gateway"
ProxyConfigGlobal string = "global"
)
@ -157,6 +158,8 @@ func makeConfigEntry(kind, name string) (ConfigEntry, error) {
return &ServiceSplitterConfigEntry{Kind: kind, Name: name}, nil
case ServiceResolver:
return &ServiceResolverConfigEntry{Kind: kind, Name: name}, nil
case IngressGateway:
return &IngressGatewayConfigEntry{Kind: kind, Name: name}, nil
default:
return nil, fmt.Errorf("invalid config entry kind: %s", kind)
}

View File

@ -0,0 +1,84 @@
package api
// IngressGatewayConfigEntry manages the configuration for an ingress service
// with the given name.
type IngressGatewayConfigEntry struct {
// Kind of the config entry. This should be set to api.IngressGateway.
Kind string
// Name is used to match the config entry with its associated ingress gateway
// service. This should match the name provided in the service definition.
Name string
// Namespace is the namespace the IngressGateway is associated with
// Namespacing is a Consul Enterprise feature.
Namespace string `json:",omitempty"`
// Listeners declares what ports the ingress gateway should listen on, and
// what services to associated to those ports.
Listeners []IngressListener
// CreateIndex is the Raft index this entry was created at. This is a
// read-only field.
CreateIndex uint64
// ModifyIndex is used for the Check-And-Set operations and can also be fed
// back into the WaitIndex of the QueryOptions in order to perform blocking
// queries.
ModifyIndex uint64
}
// IngressListener manages the configuration for a listener on a specific port.
type IngressListener struct {
// Port declares the port on which the ingress gateway should listen for traffic.
Port int
// Protocol declares what type of traffic this listener is expected to
// receive. Depending on the protocol, a listener might support multiplexing
// services over a single port, or additional discovery chain features. The
// current supported values are: (tcp | http).
Protocol string
// Services declares the set of services to which the listener forwards
// traffic.
//
// For "tcp" protocol listeners, only a single service is allowed.
// For "http" listeners, multiple services can be declared.
Services []IngressService
}
// IngressService manages configuration for services that are exposed to
// ingress traffic.
type IngressService struct {
// Name declares the service to which traffic should be forwarded.
//
// This can either be a specific service instance, or the wildcard specifier,
// "*". If the wildcard specifier is provided, the listener must be of "http"
// protocol and means that the listener will forward traffic to all services.
Name string
// Namespace is the namespace where the service is located.
// Namespacing is a Consul Enterprise feature.
Namespace string `json:",omitempty"`
// ServiceSubset declares the specific service subset to which traffic should
// be sent. This must match an existing service subset declared in a
// service-resolver config entry.
ServiceSubset string
}
func (i *IngressGatewayConfigEntry) GetKind() string {
return i.Kind
}
func (i *IngressGatewayConfigEntry) GetName() string {
return i.Name
}
func (i *IngressGatewayConfigEntry) GetCreateIndex() uint64 {
return i.CreateIndex
}
func (i *IngressGatewayConfigEntry) GetModifyIndex() uint64 {
return i.ModifyIndex
}

View File

@ -0,0 +1,127 @@
package api
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestAPI_ConfigEntries_IngressGateway(t *testing.T) {
t.Parallel()
c, s := makeClient(t)
defer s.Stop()
config_entries := c.ConfigEntries()
ingress1 := &IngressGatewayConfigEntry{
Kind: IngressGateway,
Name: "foo",
}
ingress2 := &IngressGatewayConfigEntry{
Kind: IngressGateway,
Name: "bar",
}
// set it
_, wm, err := config_entries.Set(ingress1, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// also set the second one
_, wm, err = config_entries.Set(ingress2, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// get it
entry, qm, err := config_entries.Get(IngressGateway, "foo", nil)
require.NoError(t, err)
require.NotNil(t, qm)
require.NotEqual(t, 0, qm.RequestTime)
// verify it
readIngress, ok := entry.(*IngressGatewayConfigEntry)
require.True(t, ok)
require.Equal(t, ingress1.Kind, readIngress.Kind)
require.Equal(t, ingress1.Name, readIngress.Name)
// update it
ingress1.Listeners = []IngressListener{
{
Port: 2222,
Protocol: "tcp",
Services: []IngressService{
{
Name: "asdf",
},
},
},
}
// CAS fail
written, _, err := config_entries.CAS(ingress1, 0, nil)
require.NoError(t, err)
require.False(t, written)
// CAS success
written, wm, err = config_entries.CAS(ingress1, readIngress.ModifyIndex, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
require.True(t, written)
// update no cas
ingress2.Listeners = []IngressListener{
{
Port: 3333,
Protocol: "http",
Services: []IngressService{
{
Name: "qwer",
},
},
},
}
_, wm, err = config_entries.Set(ingress2, nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// list them
entries, qm, err := config_entries.List(IngressGateway, 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":
// this also verifies that the update value was persisted and
// the updated values are seen
readIngress, ok = entry.(*IngressGatewayConfigEntry)
require.True(t, ok)
require.Equal(t, ingress1.Kind, readIngress.Kind)
require.Equal(t, ingress1.Name, readIngress.Name)
require.Equal(t, ingress1.Listeners, readIngress.Listeners)
case "bar":
readIngress, ok = entry.(*IngressGatewayConfigEntry)
require.True(t, ok)
require.Equal(t, ingress2.Kind, readIngress.Kind)
require.Equal(t, ingress2.Name, readIngress.Name)
require.Equal(t, ingress2.Listeners, readIngress.Listeners)
}
}
// delete it
wm, err = config_entries.Delete(IngressGateway, "foo", nil)
require.NoError(t, err)
require.NotNil(t, wm)
require.NotEqual(t, 0, wm.RequestTime)
// verify deletion
entry, qm, err = config_entries.Get(IngressGateway, "foo", nil)
require.Error(t, err)
}

View File

@ -613,6 +613,67 @@ func TestDecodeConfigEntry(t *testing.T) {
Name: "main",
},
},
{
name: "ingress-gateway",
body: `
{
"Kind": "ingress-gateway",
"Name": "ingress-web",
"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",
Listeners: []IngressListener{
IngressListener{
Port: 8080,
Protocol: "http",
Services: []IngressService{
IngressService{
Name: "web",
Namespace: "foo",
},
IngressService{
Name: "db",
},
},
},
IngressListener{
Port: 9999,
Protocol: "tcp",
Services: []IngressService{
IngressService{
Name: "mysql",
},
},
},
},
},
},
} {
tc := tc

View File

@ -1122,6 +1122,114 @@ func TestParseConfigEntry(t *testing.T) {
},
},
},
{
name: "ingress-gateway: kitchen sink",
snake: `
kind = "ingress-gateway"
name = "ingress-web"
listeners = [
{
port = 8080
protocol = "http"
services = [
{
name = "web"
service_subset = "v1"
},
{
name = "db"
namespace = "foo"
}
]
}
]
`,
camel: `
Kind = "ingress-gateway"
Name = "ingress-web"
Listeners = [
{
Port = 8080
Protocol = "http"
Services = [
{
Name = "web"
ServiceSubset = "v1"
},
{
Name = "db"
Namespace = "foo"
}
]
}
]
`,
snakeJSON: `
{
"kind": "ingress-gateway",
"name": "ingress-web",
"listeners": [
{
"port": 8080,
"protocol": "http",
"services": [
{
"name": "web",
"service_subset": "v1"
},
{
"name": "db",
"namespace": "foo"
}
]
}
]
}
`,
camelJSON: `
{
"Kind": "ingress-gateway",
"Name": "ingress-web",
"Listeners": [
{
"Port": 8080,
"Protocol": "http",
"Services": [
{
"Name": "web",
"ServiceSubset": "v1"
},
{
"Name": "db",
"Namespace": "foo"
}
]
}
]
}
`,
expect: &api.IngressGatewayConfigEntry{
Kind: "ingress-gateway",
Name: "ingress-web",
Listeners: []api.IngressListener{
{
Port: 8080,
Protocol: "http",
Services: []api.IngressService{
{
Name: "web",
ServiceSubset: "v1",
},
{
Name: "db",
Namespace: "foo",
},
},
},
},
},
},
} {
tc := tc