api: move serviceregistration client to servics to match CLI.

The service registration client name was used to provide a
distinction between the service block and the service client. This
however creates new wording to understand and does not match the
CLI, therefore this change fixes that so we have a Services
client.

Consul specific objects within the service file have been moved to
the consul location to create a clearer separation.
This commit is contained in:
James Rasell
2022-03-24 09:08:45 +01:00
parent e89dd5dcf7
commit 2ac318b6f7
10 changed files with 1153 additions and 1157 deletions

View File

@@ -1,5 +1,7 @@
package api
import "time"
// Consul represents configuration related to consul.
type Consul struct {
// (Enterprise-only) Namespace represents a Consul namespace.
@@ -33,3 +35,558 @@ func (c *Consul) MergeNamespace(namespace *string) {
c.Namespace = *namespace
}
}
// ConsulConnect represents a Consul Connect jobspec stanza.
type ConsulConnect struct {
Native bool `hcl:"native,optional"`
Gateway *ConsulGateway `hcl:"gateway,block"`
SidecarService *ConsulSidecarService `mapstructure:"sidecar_service" hcl:"sidecar_service,block"`
SidecarTask *SidecarTask `mapstructure:"sidecar_task" hcl:"sidecar_task,block"`
}
func (cc *ConsulConnect) Canonicalize() {
if cc == nil {
return
}
cc.SidecarService.Canonicalize()
cc.SidecarTask.Canonicalize()
cc.Gateway.Canonicalize()
}
// ConsulSidecarService represents a Consul Connect SidecarService jobspec
// stanza.
type ConsulSidecarService struct {
Tags []string `hcl:"tags,optional"`
Port string `hcl:"port,optional"`
Proxy *ConsulProxy `hcl:"proxy,block"`
DisableDefaultTCPCheck bool `mapstructure:"disable_default_tcp_check" hcl:"disable_default_tcp_check,optional"`
}
func (css *ConsulSidecarService) Canonicalize() {
if css == nil {
return
}
if len(css.Tags) == 0 {
css.Tags = nil
}
css.Proxy.Canonicalize()
}
// SidecarTask represents a subset of Task fields that can be set to override
// the fields of the Task generated for the sidecar
type SidecarTask struct {
Name string `hcl:"name,optional"`
Driver string `hcl:"driver,optional"`
User string `hcl:"user,optional"`
Config map[string]interface{} `hcl:"config,block"`
Env map[string]string `hcl:"env,block"`
Resources *Resources `hcl:"resources,block"`
Meta map[string]string `hcl:"meta,block"`
KillTimeout *time.Duration `mapstructure:"kill_timeout" hcl:"kill_timeout,optional"`
LogConfig *LogConfig `mapstructure:"logs" hcl:"logs,block"`
ShutdownDelay *time.Duration `mapstructure:"shutdown_delay" hcl:"shutdown_delay,optional"`
KillSignal string `mapstructure:"kill_signal" hcl:"kill_signal,optional"`
}
func (st *SidecarTask) Canonicalize() {
if st == nil {
return
}
if len(st.Config) == 0 {
st.Config = nil
}
if len(st.Env) == 0 {
st.Env = nil
}
if st.Resources == nil {
st.Resources = DefaultResources()
} else {
st.Resources.Canonicalize()
}
if st.LogConfig == nil {
st.LogConfig = DefaultLogConfig()
} else {
st.LogConfig.Canonicalize()
}
if len(st.Meta) == 0 {
st.Meta = nil
}
if st.KillTimeout == nil {
st.KillTimeout = timeToPtr(5 * time.Second)
}
if st.ShutdownDelay == nil {
st.ShutdownDelay = timeToPtr(0)
}
}
// ConsulProxy represents a Consul Connect sidecar proxy jobspec stanza.
type ConsulProxy struct {
LocalServiceAddress string `mapstructure:"local_service_address" hcl:"local_service_address,optional"`
LocalServicePort int `mapstructure:"local_service_port" hcl:"local_service_port,optional"`
ExposeConfig *ConsulExposeConfig `mapstructure:"expose" hcl:"expose,block"`
Upstreams []*ConsulUpstream `hcl:"upstreams,block"`
Config map[string]interface{} `hcl:"config,block"`
}
func (cp *ConsulProxy) Canonicalize() {
if cp == nil {
return
}
cp.ExposeConfig.Canonicalize()
if len(cp.Upstreams) == 0 {
cp.Upstreams = nil
}
for _, upstream := range cp.Upstreams {
upstream.Canonicalize()
}
if len(cp.Config) == 0 {
cp.Config = nil
}
}
// ConsulMeshGateway is used to configure mesh gateway usage when connecting to
// a connect upstream in another datacenter.
type ConsulMeshGateway struct {
// Mode configures how an upstream should be accessed with regard to using
// mesh gateways.
//
// local - the connect proxy makes outbound connections through mesh gateway
// originating in the same datacenter.
//
// remote - the connect proxy makes outbound connections to a mesh gateway
// in the destination datacenter.
//
// none (default) - no mesh gateway is used, the proxy makes outbound connections
// directly to destination services.
//
// https://www.consul.io/docs/connect/gateways/mesh-gateway#modes-of-operation
Mode string `mapstructure:"mode" hcl:"mode,optional"`
}
func (c *ConsulMeshGateway) Canonicalize() {
// Mode may be empty string, indicating behavior will defer to Consul
// service-defaults config entry.
return
}
func (c *ConsulMeshGateway) Copy() *ConsulMeshGateway {
if c == nil {
return nil
}
return &ConsulMeshGateway{
Mode: c.Mode,
}
}
// ConsulUpstream represents a Consul Connect upstream jobspec stanza.
type ConsulUpstream struct {
DestinationName string `mapstructure:"destination_name" hcl:"destination_name,optional"`
LocalBindPort int `mapstructure:"local_bind_port" hcl:"local_bind_port,optional"`
Datacenter string `mapstructure:"datacenter" hcl:"datacenter,optional"`
LocalBindAddress string `mapstructure:"local_bind_address" hcl:"local_bind_address,optional"`
MeshGateway *ConsulMeshGateway `mapstructure:"mesh_gateway" hcl:"mesh_gateway,block"`
}
func (cu *ConsulUpstream) Copy() *ConsulUpstream {
if cu == nil {
return nil
}
return &ConsulUpstream{
DestinationName: cu.DestinationName,
LocalBindPort: cu.LocalBindPort,
Datacenter: cu.Datacenter,
LocalBindAddress: cu.LocalBindAddress,
MeshGateway: cu.MeshGateway.Copy(),
}
}
func (cu *ConsulUpstream) Canonicalize() {
if cu == nil {
return
}
cu.MeshGateway.Canonicalize()
}
type ConsulExposeConfig struct {
Path []*ConsulExposePath `mapstructure:"path" hcl:"path,block"`
}
func (cec *ConsulExposeConfig) Canonicalize() {
if cec == nil {
return
}
if len(cec.Path) == 0 {
cec.Path = nil
}
}
type ConsulExposePath struct {
Path string `hcl:"path,optional"`
Protocol string `hcl:"protocol,optional"`
LocalPathPort int `mapstructure:"local_path_port" hcl:"local_path_port,optional"`
ListenerPort string `mapstructure:"listener_port" hcl:"listener_port,optional"`
}
// ConsulGateway is used to configure one of the Consul Connect Gateway types.
type ConsulGateway struct {
// Proxy is used to configure the Envoy instance acting as the gateway.
Proxy *ConsulGatewayProxy `hcl:"proxy,block"`
// Ingress represents the Consul Configuration Entry for an Ingress Gateway.
Ingress *ConsulIngressConfigEntry `hcl:"ingress,block"`
// Terminating represents the Consul Configuration Entry for a Terminating Gateway.
Terminating *ConsulTerminatingConfigEntry `hcl:"terminating,block"`
// Mesh indicates the Consul service should be a Mesh Gateway.
Mesh *ConsulMeshConfigEntry `hcl:"mesh,block"`
}
func (g *ConsulGateway) Canonicalize() {
if g == nil {
return
}
g.Proxy.Canonicalize()
g.Ingress.Canonicalize()
g.Terminating.Canonicalize()
}
func (g *ConsulGateway) Copy() *ConsulGateway {
if g == nil {
return nil
}
return &ConsulGateway{
Proxy: g.Proxy.Copy(),
Ingress: g.Ingress.Copy(),
Terminating: g.Terminating.Copy(),
}
}
type ConsulGatewayBindAddress struct {
Name string `hcl:",label"`
Address string `mapstructure:"address" hcl:"address,optional"`
Port int `mapstructure:"port" hcl:"port,optional"`
}
var (
// defaultGatewayConnectTimeout is the default amount of time connections to
// upstreams are allowed before timing out.
defaultGatewayConnectTimeout = 5 * time.Second
)
// ConsulGatewayProxy is used to tune parameters of the proxy instance acting as
// one of the forms of Connect gateways that Consul supports.
//
// https://www.consul.io/docs/connect/proxies/envoy#gateway-options
type ConsulGatewayProxy struct {
ConnectTimeout *time.Duration `mapstructure:"connect_timeout" hcl:"connect_timeout,optional"`
EnvoyGatewayBindTaggedAddresses bool `mapstructure:"envoy_gateway_bind_tagged_addresses" hcl:"envoy_gateway_bind_tagged_addresses,optional"`
EnvoyGatewayBindAddresses map[string]*ConsulGatewayBindAddress `mapstructure:"envoy_gateway_bind_addresses" hcl:"envoy_gateway_bind_addresses,block"`
EnvoyGatewayNoDefaultBind bool `mapstructure:"envoy_gateway_no_default_bind" hcl:"envoy_gateway_no_default_bind,optional"`
EnvoyDNSDiscoveryType string `mapstructure:"envoy_dns_discovery_type" hcl:"envoy_dns_discovery_type,optional"`
Config map[string]interface{} `hcl:"config,block"` // escape hatch envoy config
}
func (p *ConsulGatewayProxy) Canonicalize() {
if p == nil {
return
}
if p.ConnectTimeout == nil {
// same as the default from consul
p.ConnectTimeout = timeToPtr(defaultGatewayConnectTimeout)
}
if len(p.EnvoyGatewayBindAddresses) == 0 {
p.EnvoyGatewayBindAddresses = nil
}
if len(p.Config) == 0 {
p.Config = nil
}
}
func (p *ConsulGatewayProxy) Copy() *ConsulGatewayProxy {
if p == nil {
return nil
}
var binds map[string]*ConsulGatewayBindAddress = nil
if p.EnvoyGatewayBindAddresses != nil {
binds = make(map[string]*ConsulGatewayBindAddress, len(p.EnvoyGatewayBindAddresses))
for k, v := range p.EnvoyGatewayBindAddresses {
binds[k] = v
}
}
var config map[string]interface{} = nil
if p.Config != nil {
config = make(map[string]interface{}, len(p.Config))
for k, v := range p.Config {
config[k] = v
}
}
return &ConsulGatewayProxy{
ConnectTimeout: timeToPtr(*p.ConnectTimeout),
EnvoyGatewayBindTaggedAddresses: p.EnvoyGatewayBindTaggedAddresses,
EnvoyGatewayBindAddresses: binds,
EnvoyGatewayNoDefaultBind: p.EnvoyGatewayNoDefaultBind,
EnvoyDNSDiscoveryType: p.EnvoyDNSDiscoveryType,
Config: config,
}
}
// ConsulGatewayTLSConfig is used to configure TLS for a gateway.
type ConsulGatewayTLSConfig struct {
Enabled bool `hcl:"enabled,optional"`
}
func (tc *ConsulGatewayTLSConfig) Canonicalize() {
}
func (tc *ConsulGatewayTLSConfig) Copy() *ConsulGatewayTLSConfig {
if tc == nil {
return nil
}
return &ConsulGatewayTLSConfig{
Enabled: tc.Enabled,
}
}
// ConsulIngressService is used to configure a service fronted by the ingress gateway.
type ConsulIngressService struct {
// Namespace is not yet supported.
// Namespace string
Name string `hcl:"name,optional"`
Hosts []string `hcl:"hosts,optional"`
}
func (s *ConsulIngressService) Canonicalize() {
if s == nil {
return
}
if len(s.Hosts) == 0 {
s.Hosts = nil
}
}
func (s *ConsulIngressService) Copy() *ConsulIngressService {
if s == nil {
return nil
}
var hosts []string = nil
if n := len(s.Hosts); n > 0 {
hosts = make([]string, n)
copy(hosts, s.Hosts)
}
return &ConsulIngressService{
Name: s.Name,
Hosts: hosts,
}
}
const (
defaultIngressListenerProtocol = "tcp"
)
// ConsulIngressListener is used to configure a listener on a Consul Ingress
// Gateway.
type ConsulIngressListener struct {
Port int `hcl:"port,optional"`
Protocol string `hcl:"protocol,optional"`
Services []*ConsulIngressService `hcl:"service,block"`
}
func (l *ConsulIngressListener) Canonicalize() {
if l == nil {
return
}
if l.Protocol == "" {
// same as default from consul
l.Protocol = defaultIngressListenerProtocol
}
if len(l.Services) == 0 {
l.Services = nil
}
}
func (l *ConsulIngressListener) Copy() *ConsulIngressListener {
if l == nil {
return nil
}
var services []*ConsulIngressService = nil
if n := len(l.Services); n > 0 {
services = make([]*ConsulIngressService, n)
for i := 0; i < n; i++ {
services[i] = l.Services[i].Copy()
}
}
return &ConsulIngressListener{
Port: l.Port,
Protocol: l.Protocol,
Services: services,
}
}
// ConsulIngressConfigEntry represents the Consul Configuration Entry type for
// an Ingress Gateway.
//
// https://www.consul.io/docs/agent/config-entries/ingress-gateway#available-fields
type ConsulIngressConfigEntry struct {
// Namespace is not yet supported.
// Namespace string
TLS *ConsulGatewayTLSConfig `hcl:"tls,block"`
Listeners []*ConsulIngressListener `hcl:"listener,block"`
}
func (e *ConsulIngressConfigEntry) Canonicalize() {
if e == nil {
return
}
e.TLS.Canonicalize()
if len(e.Listeners) == 0 {
e.Listeners = nil
}
for _, listener := range e.Listeners {
listener.Canonicalize()
}
}
func (e *ConsulIngressConfigEntry) Copy() *ConsulIngressConfigEntry {
if e == nil {
return nil
}
var listeners []*ConsulIngressListener = nil
if n := len(e.Listeners); n > 0 {
listeners = make([]*ConsulIngressListener, n)
for i := 0; i < n; i++ {
listeners[i] = e.Listeners[i].Copy()
}
}
return &ConsulIngressConfigEntry{
TLS: e.TLS.Copy(),
Listeners: listeners,
}
}
type ConsulLinkedService struct {
Name string `hcl:"name,optional"`
CAFile string `hcl:"ca_file,optional" mapstructure:"ca_file"`
CertFile string `hcl:"cert_file,optional" mapstructure:"cert_file"`
KeyFile string `hcl:"key_file,optional" mapstructure:"key_file"`
SNI string `hcl:"sni,optional"`
}
func (s *ConsulLinkedService) Canonicalize() {
// nothing to do for now
}
func (s *ConsulLinkedService) Copy() *ConsulLinkedService {
if s == nil {
return nil
}
return &ConsulLinkedService{
Name: s.Name,
CAFile: s.CAFile,
CertFile: s.CertFile,
KeyFile: s.KeyFile,
SNI: s.SNI,
}
}
// ConsulTerminatingConfigEntry represents the Consul Configuration Entry type
// for a Terminating Gateway.
//
// https://www.consul.io/docs/agent/config-entries/terminating-gateway#available-fields
type ConsulTerminatingConfigEntry struct {
// Namespace is not yet supported.
// Namespace string
Services []*ConsulLinkedService `hcl:"service,block"`
}
func (e *ConsulTerminatingConfigEntry) Canonicalize() {
if e == nil {
return
}
if len(e.Services) == 0 {
e.Services = nil
}
for _, service := range e.Services {
service.Canonicalize()
}
}
func (e *ConsulTerminatingConfigEntry) Copy() *ConsulTerminatingConfigEntry {
if e == nil {
return nil
}
var services []*ConsulLinkedService = nil
if n := len(e.Services); n > 0 {
services = make([]*ConsulLinkedService, n)
for i := 0; i < n; i++ {
services[i] = e.Services[i].Copy()
}
}
return &ConsulTerminatingConfigEntry{
Services: services,
}
}
// ConsulMeshConfigEntry is a stub used to represent that the gateway service type
// should be for a Mesh Gateway. Unlike Ingress and Terminating, there is no
// actual Consul Config Entry type for mesh-gateway, at least for now. We still
// create a type for future proofing, instead just using a bool for example.
type ConsulMeshConfigEntry struct {
// nothing in here
}
func (e *ConsulMeshConfigEntry) Canonicalize() {
return
}
func (e *ConsulMeshConfigEntry) Copy() *ConsulMeshConfigEntry {
if e == nil {
return nil
}
return new(ConsulMeshConfigEntry)
}

View File

@@ -2,6 +2,7 @@ package api
import (
"testing"
"time"
"github.com/hashicorp/nomad/api/internal/testutil"
"github.com/stretchr/testify/require"
@@ -60,3 +61,455 @@ func TestConsul_MergeNamespace(t *testing.T) {
require.Nil(t, ns)
})
}
func TestConsulConnect_Canonicalize(t *testing.T) {
testutil.Parallel(t)
t.Run("nil connect", func(t *testing.T) {
cc := (*ConsulConnect)(nil)
cc.Canonicalize()
require.Nil(t, cc)
})
t.Run("empty connect", func(t *testing.T) {
cc := new(ConsulConnect)
cc.Canonicalize()
require.Empty(t, cc.Native)
require.Nil(t, cc.SidecarService)
require.Nil(t, cc.SidecarTask)
})
}
func TestConsulSidecarService_Canonicalize(t *testing.T) {
testutil.Parallel(t)
t.Run("nil sidecar_service", func(t *testing.T) {
css := (*ConsulSidecarService)(nil)
css.Canonicalize()
require.Nil(t, css)
})
t.Run("empty sidecar_service", func(t *testing.T) {
css := new(ConsulSidecarService)
css.Canonicalize()
require.Empty(t, css.Tags)
require.Nil(t, css.Proxy)
})
t.Run("non-empty sidecar_service", func(t *testing.T) {
css := &ConsulSidecarService{
Tags: make([]string, 0),
Port: "port",
Proxy: &ConsulProxy{
LocalServiceAddress: "lsa",
LocalServicePort: 80,
},
}
css.Canonicalize()
require.Equal(t, &ConsulSidecarService{
Tags: nil,
Port: "port",
Proxy: &ConsulProxy{
LocalServiceAddress: "lsa",
LocalServicePort: 80},
}, css)
})
}
func TestConsulProxy_Canonicalize(t *testing.T) {
testutil.Parallel(t)
t.Run("nil proxy", func(t *testing.T) {
cp := (*ConsulProxy)(nil)
cp.Canonicalize()
require.Nil(t, cp)
})
t.Run("empty proxy", func(t *testing.T) {
cp := new(ConsulProxy)
cp.Canonicalize()
require.Empty(t, cp.LocalServiceAddress)
require.Zero(t, cp.LocalServicePort)
require.Nil(t, cp.ExposeConfig)
require.Nil(t, cp.Upstreams)
require.Empty(t, cp.Config)
})
t.Run("non empty proxy", func(t *testing.T) {
cp := &ConsulProxy{
LocalServiceAddress: "127.0.0.1",
LocalServicePort: 80,
ExposeConfig: new(ConsulExposeConfig),
Upstreams: make([]*ConsulUpstream, 0),
Config: make(map[string]interface{}),
}
cp.Canonicalize()
require.Equal(t, "127.0.0.1", cp.LocalServiceAddress)
require.Equal(t, 80, cp.LocalServicePort)
require.Equal(t, &ConsulExposeConfig{}, cp.ExposeConfig)
require.Nil(t, cp.Upstreams)
require.Nil(t, cp.Config)
})
}
func TestConsulUpstream_Copy(t *testing.T) {
testutil.Parallel(t)
t.Run("nil upstream", func(t *testing.T) {
cu := (*ConsulUpstream)(nil)
result := cu.Copy()
require.Nil(t, result)
})
t.Run("complete upstream", func(t *testing.T) {
cu := &ConsulUpstream{
DestinationName: "dest1",
Datacenter: "dc2",
LocalBindPort: 2000,
LocalBindAddress: "10.0.0.1",
MeshGateway: &ConsulMeshGateway{Mode: "remote"},
}
result := cu.Copy()
require.Equal(t, cu, result)
})
}
func TestConsulUpstream_Canonicalize(t *testing.T) {
testutil.Parallel(t)
t.Run("nil upstream", func(t *testing.T) {
cu := (*ConsulUpstream)(nil)
cu.Canonicalize()
require.Nil(t, cu)
})
t.Run("complete", func(t *testing.T) {
cu := &ConsulUpstream{
DestinationName: "dest1",
Datacenter: "dc2",
LocalBindPort: 2000,
LocalBindAddress: "10.0.0.1",
MeshGateway: &ConsulMeshGateway{Mode: ""},
}
cu.Canonicalize()
require.Equal(t, &ConsulUpstream{
DestinationName: "dest1",
Datacenter: "dc2",
LocalBindPort: 2000,
LocalBindAddress: "10.0.0.1",
MeshGateway: &ConsulMeshGateway{Mode: ""},
}, cu)
})
}
func TestSidecarTask_Canonicalize(t *testing.T) {
testutil.Parallel(t)
t.Run("nil sidecar_task", func(t *testing.T) {
st := (*SidecarTask)(nil)
st.Canonicalize()
require.Nil(t, st)
})
t.Run("empty sidecar_task", func(t *testing.T) {
st := new(SidecarTask)
st.Canonicalize()
require.Nil(t, st.Config)
require.Nil(t, st.Env)
require.Equal(t, DefaultResources(), st.Resources)
require.Equal(t, DefaultLogConfig(), st.LogConfig)
require.Nil(t, st.Meta)
require.Equal(t, 5*time.Second, *st.KillTimeout)
require.Equal(t, 0*time.Second, *st.ShutdownDelay)
})
t.Run("non empty sidecar_task resources", func(t *testing.T) {
exp := DefaultResources()
exp.MemoryMB = intToPtr(333)
st := &SidecarTask{
Resources: &Resources{MemoryMB: intToPtr(333)},
}
st.Canonicalize()
require.Equal(t, exp, st.Resources)
})
}
func TestConsulGateway_Canonicalize(t *testing.T) {
testutil.Parallel(t)
t.Run("nil", func(t *testing.T) {
cg := (*ConsulGateway)(nil)
cg.Canonicalize()
require.Nil(t, cg)
})
t.Run("set defaults", func(t *testing.T) {
cg := &ConsulGateway{
Proxy: &ConsulGatewayProxy{
ConnectTimeout: nil,
EnvoyGatewayBindTaggedAddresses: true,
EnvoyGatewayBindAddresses: make(map[string]*ConsulGatewayBindAddress, 0),
EnvoyGatewayNoDefaultBind: true,
Config: make(map[string]interface{}, 0),
},
Ingress: &ConsulIngressConfigEntry{
TLS: &ConsulGatewayTLSConfig{
Enabled: false,
},
Listeners: make([]*ConsulIngressListener, 0),
},
}
cg.Canonicalize()
require.Equal(t, timeToPtr(5*time.Second), cg.Proxy.ConnectTimeout)
require.True(t, cg.Proxy.EnvoyGatewayBindTaggedAddresses)
require.Nil(t, cg.Proxy.EnvoyGatewayBindAddresses)
require.True(t, cg.Proxy.EnvoyGatewayNoDefaultBind)
require.Empty(t, cg.Proxy.EnvoyDNSDiscoveryType)
require.Nil(t, cg.Proxy.Config)
require.Nil(t, cg.Ingress.Listeners)
})
}
func TestConsulGateway_Copy(t *testing.T) {
testutil.Parallel(t)
t.Run("nil", func(t *testing.T) {
result := (*ConsulGateway)(nil).Copy()
require.Nil(t, result)
})
gateway := &ConsulGateway{
Proxy: &ConsulGatewayProxy{
ConnectTimeout: timeToPtr(3 * time.Second),
EnvoyGatewayBindTaggedAddresses: true,
EnvoyGatewayBindAddresses: map[string]*ConsulGatewayBindAddress{
"listener1": {Address: "10.0.0.1", Port: 2000},
"listener2": {Address: "10.0.0.1", Port: 2001},
},
EnvoyGatewayNoDefaultBind: true,
EnvoyDNSDiscoveryType: "STRICT_DNS",
Config: map[string]interface{}{
"foo": "bar",
"baz": 3,
},
},
Ingress: &ConsulIngressConfigEntry{
TLS: &ConsulGatewayTLSConfig{
Enabled: true,
},
Listeners: []*ConsulIngressListener{{
Port: 3333,
Protocol: "tcp",
Services: []*ConsulIngressService{{
Name: "service1",
Hosts: []string{
"127.0.0.1", "127.0.0.1:3333",
}},
}},
},
},
Terminating: &ConsulTerminatingConfigEntry{
Services: []*ConsulLinkedService{{
Name: "linked-service1",
}},
},
}
t.Run("complete", func(t *testing.T) {
result := gateway.Copy()
require.Equal(t, gateway, result)
})
}
func TestConsulIngressConfigEntry_Canonicalize(t *testing.T) {
testutil.Parallel(t)
t.Run("nil", func(t *testing.T) {
c := (*ConsulIngressConfigEntry)(nil)
c.Canonicalize()
require.Nil(t, c)
})
t.Run("empty fields", func(t *testing.T) {
c := &ConsulIngressConfigEntry{
TLS: nil,
Listeners: []*ConsulIngressListener{},
}
c.Canonicalize()
require.Nil(t, c.TLS)
require.Nil(t, c.Listeners)
})
t.Run("complete", func(t *testing.T) {
c := &ConsulIngressConfigEntry{
TLS: &ConsulGatewayTLSConfig{Enabled: true},
Listeners: []*ConsulIngressListener{{
Port: 9090,
Protocol: "http",
Services: []*ConsulIngressService{{
Name: "service1",
Hosts: []string{"1.1.1.1"},
}},
}},
}
c.Canonicalize()
require.Equal(t, &ConsulIngressConfigEntry{
TLS: &ConsulGatewayTLSConfig{Enabled: true},
Listeners: []*ConsulIngressListener{{
Port: 9090,
Protocol: "http",
Services: []*ConsulIngressService{{
Name: "service1",
Hosts: []string{"1.1.1.1"},
}},
}},
}, c)
})
}
func TestConsulIngressConfigEntry_Copy(t *testing.T) {
testutil.Parallel(t)
t.Run("nil", func(t *testing.T) {
result := (*ConsulIngressConfigEntry)(nil).Copy()
require.Nil(t, result)
})
entry := &ConsulIngressConfigEntry{
TLS: &ConsulGatewayTLSConfig{
Enabled: true,
},
Listeners: []*ConsulIngressListener{{
Port: 1111,
Protocol: "http",
Services: []*ConsulIngressService{{
Name: "service1",
Hosts: []string{"1.1.1.1", "1.1.1.1:9000"},
}, {
Name: "service2",
Hosts: []string{"2.2.2.2"},
}},
}},
}
t.Run("complete", func(t *testing.T) {
result := entry.Copy()
require.Equal(t, entry, result)
})
}
func TestConsulTerminatingConfigEntry_Canonicalize(t *testing.T) {
testutil.Parallel(t)
t.Run("nil", func(t *testing.T) {
c := (*ConsulTerminatingConfigEntry)(nil)
c.Canonicalize()
require.Nil(t, c)
})
t.Run("empty services", func(t *testing.T) {
c := &ConsulTerminatingConfigEntry{
Services: []*ConsulLinkedService{},
}
c.Canonicalize()
require.Nil(t, c.Services)
})
}
func TestConsulTerminatingConfigEntry_Copy(t *testing.T) {
testutil.Parallel(t)
t.Run("nil", func(t *testing.T) {
result := (*ConsulIngressConfigEntry)(nil).Copy()
require.Nil(t, result)
})
entry := &ConsulTerminatingConfigEntry{
Services: []*ConsulLinkedService{{
Name: "servic1",
}, {
Name: "service2",
CAFile: "ca_file.pem",
CertFile: "cert_file.pem",
KeyFile: "key_file.pem",
SNI: "sni.terminating.consul",
}},
}
t.Run("complete", func(t *testing.T) {
result := entry.Copy()
require.Equal(t, entry, result)
})
}
func TestConsulMeshConfigEntry_Canonicalize(t *testing.T) {
testutil.Parallel(t)
t.Run("nil", func(t *testing.T) {
ce := (*ConsulMeshConfigEntry)(nil)
ce.Canonicalize()
require.Nil(t, ce)
})
t.Run("instantiated", func(t *testing.T) {
ce := new(ConsulMeshConfigEntry)
ce.Canonicalize()
require.NotNil(t, ce)
})
}
func TestConsulMeshConfigEntry_Copy(t *testing.T) {
testutil.Parallel(t)
t.Run("nil", func(t *testing.T) {
ce := (*ConsulMeshConfigEntry)(nil)
ce2 := ce.Copy()
require.Nil(t, ce2)
})
t.Run("instantiated", func(t *testing.T) {
ce := new(ConsulMeshConfigEntry)
ce2 := ce.Copy()
require.NotNil(t, ce2)
})
}
func TestConsulMeshGateway_Canonicalize(t *testing.T) {
testutil.Parallel(t)
t.Run("nil", func(t *testing.T) {
c := (*ConsulMeshGateway)(nil)
c.Canonicalize()
require.Nil(t, c)
})
t.Run("unset mode", func(t *testing.T) {
c := &ConsulMeshGateway{Mode: ""}
c.Canonicalize()
require.Equal(t, "", c.Mode)
})
t.Run("set mode", func(t *testing.T) {
c := &ConsulMeshGateway{Mode: "remote"}
c.Canonicalize()
require.Equal(t, "remote", c.Mode)
})
}
func TestConsulMeshGateway_Copy(t *testing.T) {
testutil.Parallel(t)
t.Run("nil", func(t *testing.T) {
c := (*ConsulMeshGateway)(nil)
result := c.Copy()
require.Nil(t, result)
})
t.Run("instantiated", func(t *testing.T) {
c := &ConsulMeshGateway{
Mode: "local",
}
result := c.Copy()
require.Equal(t, c, result)
})
}

View File

@@ -1,129 +0,0 @@
package api
import (
"fmt"
"net/url"
)
// ServiceRegistrations is used to query the service endpoints.
type ServiceRegistrations struct {
client *Client
}
// ServiceRegistration is an instance of a single allocation advertising itself
// as a named service with a specific address. Each registration is constructed
// from the job specification Service block. Whether the service is registered
// within Nomad, and therefore generates a ServiceRegistration is controlled by
// the Service.Provider parameter.
type ServiceRegistration struct {
// ID is the unique identifier for this registration. It currently follows
// the Consul service registration format to provide consistency between
// the two solutions.
ID string
// ServiceName is the human friendly identifier for this service
// registration.
ServiceName string
// Namespace represents the namespace within which this service is
// registered.
Namespace string
// NodeID is Node.ID on which this service registration is currently
// running.
NodeID string
// Datacenter is the DC identifier of the node as identified by
// Node.Datacenter.
Datacenter string
// JobID is Job.ID and represents the job which contained the service block
// which resulted in this service registration.
JobID string
// AllocID is Allocation.ID and represents the allocation within which this
// service is running.
AllocID string
// Tags are determined from either Service.Tags or Service.CanaryTags and
// help identify this service. Tags can also be used to perform lookups of
// services depending on their state and role.
Tags []string
// Address is the IP address of this service registration. This information
// comes from the client and is not guaranteed to be routable; this depends
// on cluster network topology.
Address string
// Port is the port number on which this service registration is bound. It
// is determined by a combination of factors on the client.
Port int
CreateIndex uint64
ModifyIndex uint64
}
// ServiceRegistrationListStub represents all service registrations held within a
// single namespace.
type ServiceRegistrationListStub struct {
// Namespace details the namespace in which these services have been
// registered.
Namespace string
// Services is a list of services found within the namespace.
Services []*ServiceRegistrationStub
}
// ServiceRegistrationStub is the stub object describing an individual
// namespaced service. The object is built in a manner which would allow us to
// add additional fields in the future, if we wanted.
type ServiceRegistrationStub struct {
// ServiceName is the human friendly name for this service as specified
// within Service.Name.
ServiceName string
// Tags is a list of unique tags found for this service. The list is
// de-duplicated automatically by Nomad.
Tags []string
}
// ServiceRegistrations returns a new handle on the services endpoints.
func (c *Client) ServiceRegistrations() *ServiceRegistrations {
return &ServiceRegistrations{client: c}
}
// List can be used to list all service registrations currently stored within
// the target namespace. It returns a stub response object.
func (s *ServiceRegistrations) List(q *QueryOptions) ([]*ServiceRegistrationListStub, *QueryMeta, error) {
var resp []*ServiceRegistrationListStub
qm, err := s.client.query("/v1/services", &resp, q)
if err != nil {
return nil, qm, err
}
return resp, qm, nil
}
// Get is used to return a list of service registrations whose name matches the
// specified parameter.
func (s *ServiceRegistrations) Get(serviceName string, q *QueryOptions) ([]*ServiceRegistration, *QueryMeta, error) {
var resp []*ServiceRegistration
qm, err := s.client.query("/v1/service/"+url.PathEscape(serviceName), &resp, q)
if err != nil {
return nil, qm, err
}
return resp, qm, nil
}
// Delete can be used to delete an individual service registration as defined
// by its service name and service ID.
func (s *ServiceRegistrations) Delete(serviceName, serviceID string, q *WriteOptions) (*WriteMeta, error) {
path := fmt.Sprintf("/v1/service/%s/%s", url.PathEscape(serviceName), url.PathEscape(serviceID))
wm, err := s.client.delete(path, nil, q)
if err != nil {
return nil, err
}
return wm, nil
}

View File

@@ -1,17 +0,0 @@
package api
import (
"testing"
)
func TestServiceRegistrations_List(t *testing.T) {
// TODO(jrasell) add tests once registration process is in place.
}
func TestServiceRegistrations_Get(t *testing.T) {
// TODO(jrasell) add tests once registration process is in place.
}
func TestServiceRegistrations_Delete(t *testing.T) {
// TODO(jrasell) add tests once registration process is in place.
}

View File

@@ -2,9 +2,134 @@ package api
import (
"fmt"
"net/url"
"time"
)
// ServiceRegistration is an instance of a single allocation advertising itself
// as a named service with a specific address. Each registration is constructed
// from the job specification Service block. Whether the service is registered
// within Nomad, and therefore generates a ServiceRegistration is controlled by
// the Service.Provider parameter.
type ServiceRegistration struct {
// ID is the unique identifier for this registration. It currently follows
// the Consul service registration format to provide consistency between
// the two solutions.
ID string
// ServiceName is the human friendly identifier for this service
// registration.
ServiceName string
// Namespace represents the namespace within which this service is
// registered.
Namespace string
// NodeID is Node.ID on which this service registration is currently
// running.
NodeID string
// Datacenter is the DC identifier of the node as identified by
// Node.Datacenter.
Datacenter string
// JobID is Job.ID and represents the job which contained the service block
// which resulted in this service registration.
JobID string
// AllocID is Allocation.ID and represents the allocation within which this
// service is running.
AllocID string
// Tags are determined from either Service.Tags or Service.CanaryTags and
// help identify this service. Tags can also be used to perform lookups of
// services depending on their state and role.
Tags []string
// Address is the IP address of this service registration. This information
// comes from the client and is not guaranteed to be routable; this depends
// on cluster network topology.
Address string
// Port is the port number on which this service registration is bound. It
// is determined by a combination of factors on the client.
Port int
CreateIndex uint64
ModifyIndex uint64
}
// ServiceRegistrationListStub represents all service registrations held within a
// single namespace.
type ServiceRegistrationListStub struct {
// Namespace details the namespace in which these services have been
// registered.
Namespace string
// Services is a list of services found within the namespace.
Services []*ServiceRegistrationStub
}
// ServiceRegistrationStub is the stub object describing an individual
// namespaced service. The object is built in a manner which would allow us to
// add additional fields in the future, if we wanted.
type ServiceRegistrationStub struct {
// ServiceName is the human friendly name for this service as specified
// within Service.Name.
ServiceName string
// Tags is a list of unique tags found for this service. The list is
// de-duplicated automatically by Nomad.
Tags []string
}
// Services is used to query the service endpoints.
type Services struct {
client *Client
}
// Services returns a new handle on the services endpoints.
func (c *Client) Services() *Services {
return &Services{client: c}
}
// List can be used to list all service registrations currently stored within
// the target namespace. It returns a stub response object.
func (s *Services) List(q *QueryOptions) ([]*ServiceRegistrationListStub, *QueryMeta, error) {
var resp []*ServiceRegistrationListStub
qm, err := s.client.query("/v1/services", &resp, q)
if err != nil {
return nil, qm, err
}
return resp, qm, nil
}
// Get is used to return a list of service registrations whose name matches the
// specified parameter.
func (s *Services) Get(serviceName string, q *QueryOptions) ([]*ServiceRegistration, *QueryMeta, error) {
var resp []*ServiceRegistration
qm, err := s.client.query("/v1/service/"+url.PathEscape(serviceName), &resp, q)
if err != nil {
return nil, qm, err
}
return resp, qm, nil
}
// Delete can be used to delete an individual service registration as defined
// by its service name and service ID.
func (s *Services) Delete(serviceName, serviceID string, q *WriteOptions) (*WriteMeta, error) {
path := fmt.Sprintf("/v1/service/%s/%s", url.PathEscape(serviceName), url.PathEscape(serviceID))
wm, err := s.client.delete(path, nil, q)
if err != nil {
return nil, err
}
return wm, nil
}
// CheckRestart describes if and when a task should be restarted based on
// failing health checks.
type CheckRestart struct {
@@ -181,557 +306,3 @@ func (s *Service) Canonicalize(t *Task, tg *TaskGroup, job *Job) {
}
}
}
// ConsulConnect represents a Consul Connect jobspec stanza.
type ConsulConnect struct {
Native bool `hcl:"native,optional"`
Gateway *ConsulGateway `hcl:"gateway,block"`
SidecarService *ConsulSidecarService `mapstructure:"sidecar_service" hcl:"sidecar_service,block"`
SidecarTask *SidecarTask `mapstructure:"sidecar_task" hcl:"sidecar_task,block"`
}
func (cc *ConsulConnect) Canonicalize() {
if cc == nil {
return
}
cc.SidecarService.Canonicalize()
cc.SidecarTask.Canonicalize()
cc.Gateway.Canonicalize()
}
// ConsulSidecarService represents a Consul Connect SidecarService jobspec
// stanza.
type ConsulSidecarService struct {
Tags []string `hcl:"tags,optional"`
Port string `hcl:"port,optional"`
Proxy *ConsulProxy `hcl:"proxy,block"`
DisableDefaultTCPCheck bool `mapstructure:"disable_default_tcp_check" hcl:"disable_default_tcp_check,optional"`
}
func (css *ConsulSidecarService) Canonicalize() {
if css == nil {
return
}
if len(css.Tags) == 0 {
css.Tags = nil
}
css.Proxy.Canonicalize()
}
// SidecarTask represents a subset of Task fields that can be set to override
// the fields of the Task generated for the sidecar
type SidecarTask struct {
Name string `hcl:"name,optional"`
Driver string `hcl:"driver,optional"`
User string `hcl:"user,optional"`
Config map[string]interface{} `hcl:"config,block"`
Env map[string]string `hcl:"env,block"`
Resources *Resources `hcl:"resources,block"`
Meta map[string]string `hcl:"meta,block"`
KillTimeout *time.Duration `mapstructure:"kill_timeout" hcl:"kill_timeout,optional"`
LogConfig *LogConfig `mapstructure:"logs" hcl:"logs,block"`
ShutdownDelay *time.Duration `mapstructure:"shutdown_delay" hcl:"shutdown_delay,optional"`
KillSignal string `mapstructure:"kill_signal" hcl:"kill_signal,optional"`
}
func (st *SidecarTask) Canonicalize() {
if st == nil {
return
}
if len(st.Config) == 0 {
st.Config = nil
}
if len(st.Env) == 0 {
st.Env = nil
}
if st.Resources == nil {
st.Resources = DefaultResources()
} else {
st.Resources.Canonicalize()
}
if st.LogConfig == nil {
st.LogConfig = DefaultLogConfig()
} else {
st.LogConfig.Canonicalize()
}
if len(st.Meta) == 0 {
st.Meta = nil
}
if st.KillTimeout == nil {
st.KillTimeout = timeToPtr(5 * time.Second)
}
if st.ShutdownDelay == nil {
st.ShutdownDelay = timeToPtr(0)
}
}
// ConsulProxy represents a Consul Connect sidecar proxy jobspec stanza.
type ConsulProxy struct {
LocalServiceAddress string `mapstructure:"local_service_address" hcl:"local_service_address,optional"`
LocalServicePort int `mapstructure:"local_service_port" hcl:"local_service_port,optional"`
ExposeConfig *ConsulExposeConfig `mapstructure:"expose" hcl:"expose,block"`
Upstreams []*ConsulUpstream `hcl:"upstreams,block"`
Config map[string]interface{} `hcl:"config,block"`
}
func (cp *ConsulProxy) Canonicalize() {
if cp == nil {
return
}
cp.ExposeConfig.Canonicalize()
if len(cp.Upstreams) == 0 {
cp.Upstreams = nil
}
for _, upstream := range cp.Upstreams {
upstream.Canonicalize()
}
if len(cp.Config) == 0 {
cp.Config = nil
}
}
// ConsulMeshGateway is used to configure mesh gateway usage when connecting to
// a connect upstream in another datacenter.
type ConsulMeshGateway struct {
// Mode configures how an upstream should be accessed with regard to using
// mesh gateways.
//
// local - the connect proxy makes outbound connections through mesh gateway
// originating in the same datacenter.
//
// remote - the connect proxy makes outbound connections to a mesh gateway
// in the destination datacenter.
//
// none (default) - no mesh gateway is used, the proxy makes outbound connections
// directly to destination services.
//
// https://www.consul.io/docs/connect/gateways/mesh-gateway#modes-of-operation
Mode string `mapstructure:"mode" hcl:"mode,optional"`
}
func (c *ConsulMeshGateway) Canonicalize() {
// Mode may be empty string, indicating behavior will defer to Consul
// service-defaults config entry.
return
}
func (c *ConsulMeshGateway) Copy() *ConsulMeshGateway {
if c == nil {
return nil
}
return &ConsulMeshGateway{
Mode: c.Mode,
}
}
// ConsulUpstream represents a Consul Connect upstream jobspec stanza.
type ConsulUpstream struct {
DestinationName string `mapstructure:"destination_name" hcl:"destination_name,optional"`
LocalBindPort int `mapstructure:"local_bind_port" hcl:"local_bind_port,optional"`
Datacenter string `mapstructure:"datacenter" hcl:"datacenter,optional"`
LocalBindAddress string `mapstructure:"local_bind_address" hcl:"local_bind_address,optional"`
MeshGateway *ConsulMeshGateway `mapstructure:"mesh_gateway" hcl:"mesh_gateway,block"`
}
func (cu *ConsulUpstream) Copy() *ConsulUpstream {
if cu == nil {
return nil
}
return &ConsulUpstream{
DestinationName: cu.DestinationName,
LocalBindPort: cu.LocalBindPort,
Datacenter: cu.Datacenter,
LocalBindAddress: cu.LocalBindAddress,
MeshGateway: cu.MeshGateway.Copy(),
}
}
func (cu *ConsulUpstream) Canonicalize() {
if cu == nil {
return
}
cu.MeshGateway.Canonicalize()
}
type ConsulExposeConfig struct {
Path []*ConsulExposePath `mapstructure:"path" hcl:"path,block"`
}
func (cec *ConsulExposeConfig) Canonicalize() {
if cec == nil {
return
}
if len(cec.Path) == 0 {
cec.Path = nil
}
}
type ConsulExposePath struct {
Path string `hcl:"path,optional"`
Protocol string `hcl:"protocol,optional"`
LocalPathPort int `mapstructure:"local_path_port" hcl:"local_path_port,optional"`
ListenerPort string `mapstructure:"listener_port" hcl:"listener_port,optional"`
}
// ConsulGateway is used to configure one of the Consul Connect Gateway types.
type ConsulGateway struct {
// Proxy is used to configure the Envoy instance acting as the gateway.
Proxy *ConsulGatewayProxy `hcl:"proxy,block"`
// Ingress represents the Consul Configuration Entry for an Ingress Gateway.
Ingress *ConsulIngressConfigEntry `hcl:"ingress,block"`
// Terminating represents the Consul Configuration Entry for a Terminating Gateway.
Terminating *ConsulTerminatingConfigEntry `hcl:"terminating,block"`
// Mesh indicates the Consul service should be a Mesh Gateway.
Mesh *ConsulMeshConfigEntry `hcl:"mesh,block"`
}
func (g *ConsulGateway) Canonicalize() {
if g == nil {
return
}
g.Proxy.Canonicalize()
g.Ingress.Canonicalize()
g.Terminating.Canonicalize()
}
func (g *ConsulGateway) Copy() *ConsulGateway {
if g == nil {
return nil
}
return &ConsulGateway{
Proxy: g.Proxy.Copy(),
Ingress: g.Ingress.Copy(),
Terminating: g.Terminating.Copy(),
}
}
type ConsulGatewayBindAddress struct {
Name string `hcl:",label"`
Address string `mapstructure:"address" hcl:"address,optional"`
Port int `mapstructure:"port" hcl:"port,optional"`
}
var (
// defaultGatewayConnectTimeout is the default amount of time connections to
// upstreams are allowed before timing out.
defaultGatewayConnectTimeout = 5 * time.Second
)
// ConsulGatewayProxy is used to tune parameters of the proxy instance acting as
// one of the forms of Connect gateways that Consul supports.
//
// https://www.consul.io/docs/connect/proxies/envoy#gateway-options
type ConsulGatewayProxy struct {
ConnectTimeout *time.Duration `mapstructure:"connect_timeout" hcl:"connect_timeout,optional"`
EnvoyGatewayBindTaggedAddresses bool `mapstructure:"envoy_gateway_bind_tagged_addresses" hcl:"envoy_gateway_bind_tagged_addresses,optional"`
EnvoyGatewayBindAddresses map[string]*ConsulGatewayBindAddress `mapstructure:"envoy_gateway_bind_addresses" hcl:"envoy_gateway_bind_addresses,block"`
EnvoyGatewayNoDefaultBind bool `mapstructure:"envoy_gateway_no_default_bind" hcl:"envoy_gateway_no_default_bind,optional"`
EnvoyDNSDiscoveryType string `mapstructure:"envoy_dns_discovery_type" hcl:"envoy_dns_discovery_type,optional"`
Config map[string]interface{} `hcl:"config,block"` // escape hatch envoy config
}
func (p *ConsulGatewayProxy) Canonicalize() {
if p == nil {
return
}
if p.ConnectTimeout == nil {
// same as the default from consul
p.ConnectTimeout = timeToPtr(defaultGatewayConnectTimeout)
}
if len(p.EnvoyGatewayBindAddresses) == 0 {
p.EnvoyGatewayBindAddresses = nil
}
if len(p.Config) == 0 {
p.Config = nil
}
}
func (p *ConsulGatewayProxy) Copy() *ConsulGatewayProxy {
if p == nil {
return nil
}
var binds map[string]*ConsulGatewayBindAddress = nil
if p.EnvoyGatewayBindAddresses != nil {
binds = make(map[string]*ConsulGatewayBindAddress, len(p.EnvoyGatewayBindAddresses))
for k, v := range p.EnvoyGatewayBindAddresses {
binds[k] = v
}
}
var config map[string]interface{} = nil
if p.Config != nil {
config = make(map[string]interface{}, len(p.Config))
for k, v := range p.Config {
config[k] = v
}
}
return &ConsulGatewayProxy{
ConnectTimeout: timeToPtr(*p.ConnectTimeout),
EnvoyGatewayBindTaggedAddresses: p.EnvoyGatewayBindTaggedAddresses,
EnvoyGatewayBindAddresses: binds,
EnvoyGatewayNoDefaultBind: p.EnvoyGatewayNoDefaultBind,
EnvoyDNSDiscoveryType: p.EnvoyDNSDiscoveryType,
Config: config,
}
}
// ConsulGatewayTLSConfig is used to configure TLS for a gateway.
type ConsulGatewayTLSConfig struct {
Enabled bool `hcl:"enabled,optional"`
}
func (tc *ConsulGatewayTLSConfig) Canonicalize() {
}
func (tc *ConsulGatewayTLSConfig) Copy() *ConsulGatewayTLSConfig {
if tc == nil {
return nil
}
return &ConsulGatewayTLSConfig{
Enabled: tc.Enabled,
}
}
// ConsulIngressService is used to configure a service fronted by the ingress gateway.
type ConsulIngressService struct {
// Namespace is not yet supported.
// Namespace string
Name string `hcl:"name,optional"`
Hosts []string `hcl:"hosts,optional"`
}
func (s *ConsulIngressService) Canonicalize() {
if s == nil {
return
}
if len(s.Hosts) == 0 {
s.Hosts = nil
}
}
func (s *ConsulIngressService) Copy() *ConsulIngressService {
if s == nil {
return nil
}
var hosts []string = nil
if n := len(s.Hosts); n > 0 {
hosts = make([]string, n)
copy(hosts, s.Hosts)
}
return &ConsulIngressService{
Name: s.Name,
Hosts: hosts,
}
}
const (
defaultIngressListenerProtocol = "tcp"
)
// ConsulIngressListener is used to configure a listener on a Consul Ingress
// Gateway.
type ConsulIngressListener struct {
Port int `hcl:"port,optional"`
Protocol string `hcl:"protocol,optional"`
Services []*ConsulIngressService `hcl:"service,block"`
}
func (l *ConsulIngressListener) Canonicalize() {
if l == nil {
return
}
if l.Protocol == "" {
// same as default from consul
l.Protocol = defaultIngressListenerProtocol
}
if len(l.Services) == 0 {
l.Services = nil
}
}
func (l *ConsulIngressListener) Copy() *ConsulIngressListener {
if l == nil {
return nil
}
var services []*ConsulIngressService = nil
if n := len(l.Services); n > 0 {
services = make([]*ConsulIngressService, n)
for i := 0; i < n; i++ {
services[i] = l.Services[i].Copy()
}
}
return &ConsulIngressListener{
Port: l.Port,
Protocol: l.Protocol,
Services: services,
}
}
// ConsulIngressConfigEntry represents the Consul Configuration Entry type for
// an Ingress Gateway.
//
// https://www.consul.io/docs/agent/config-entries/ingress-gateway#available-fields
type ConsulIngressConfigEntry struct {
// Namespace is not yet supported.
// Namespace string
TLS *ConsulGatewayTLSConfig `hcl:"tls,block"`
Listeners []*ConsulIngressListener `hcl:"listener,block"`
}
func (e *ConsulIngressConfigEntry) Canonicalize() {
if e == nil {
return
}
e.TLS.Canonicalize()
if len(e.Listeners) == 0 {
e.Listeners = nil
}
for _, listener := range e.Listeners {
listener.Canonicalize()
}
}
func (e *ConsulIngressConfigEntry) Copy() *ConsulIngressConfigEntry {
if e == nil {
return nil
}
var listeners []*ConsulIngressListener = nil
if n := len(e.Listeners); n > 0 {
listeners = make([]*ConsulIngressListener, n)
for i := 0; i < n; i++ {
listeners[i] = e.Listeners[i].Copy()
}
}
return &ConsulIngressConfigEntry{
TLS: e.TLS.Copy(),
Listeners: listeners,
}
}
type ConsulLinkedService struct {
Name string `hcl:"name,optional"`
CAFile string `hcl:"ca_file,optional" mapstructure:"ca_file"`
CertFile string `hcl:"cert_file,optional" mapstructure:"cert_file"`
KeyFile string `hcl:"key_file,optional" mapstructure:"key_file"`
SNI string `hcl:"sni,optional"`
}
func (s *ConsulLinkedService) Canonicalize() {
// nothing to do for now
}
func (s *ConsulLinkedService) Copy() *ConsulLinkedService {
if s == nil {
return nil
}
return &ConsulLinkedService{
Name: s.Name,
CAFile: s.CAFile,
CertFile: s.CertFile,
KeyFile: s.KeyFile,
SNI: s.SNI,
}
}
// ConsulTerminatingConfigEntry represents the Consul Configuration Entry type
// for a Terminating Gateway.
//
// https://www.consul.io/docs/agent/config-entries/terminating-gateway#available-fields
type ConsulTerminatingConfigEntry struct {
// Namespace is not yet supported.
// Namespace string
Services []*ConsulLinkedService `hcl:"service,block"`
}
func (e *ConsulTerminatingConfigEntry) Canonicalize() {
if e == nil {
return
}
if len(e.Services) == 0 {
e.Services = nil
}
for _, service := range e.Services {
service.Canonicalize()
}
}
func (e *ConsulTerminatingConfigEntry) Copy() *ConsulTerminatingConfigEntry {
if e == nil {
return nil
}
var services []*ConsulLinkedService = nil
if n := len(e.Services); n > 0 {
services = make([]*ConsulLinkedService, n)
for i := 0; i < n; i++ {
services[i] = e.Services[i].Copy()
}
}
return &ConsulTerminatingConfigEntry{
Services: services,
}
}
// ConsulMeshConfigEntry is a stub used to represent that the gateway service type
// should be for a Mesh Gateway. Unlike Ingress and Terminating, there is no
// actual Consul Config Entry type for mesh-gateway, at least for now. We still
// create a type for future proofing, instead just using a bool for example.
type ConsulMeshConfigEntry struct {
// nothing in here
}
func (e *ConsulMeshConfigEntry) Canonicalize() {
return
}
func (e *ConsulMeshConfigEntry) Copy() *ConsulMeshConfigEntry {
if e == nil {
return nil
}
return new(ConsulMeshConfigEntry)
}

View File

@@ -9,6 +9,19 @@ import (
"github.com/stretchr/testify/require"
)
func TestServiceRegistrations_List(t *testing.T) {
// TODO(jrasell) add tests once registration process is in place.
}
func TestServiceRegistrations_Get(t *testing.T) {
// TODO(jrasell) add tests once registration process is in place.
}
func TestServiceRegistrations_Delete(t *testing.T) {
// TODO(jrasell) add tests once registration process is in place.
}
func TestService_Canonicalize(t *testing.T) {
testutil.Parallel(t)
@@ -128,146 +141,6 @@ func TestService_CheckRestart(t *testing.T) {
require.True(t, service.Checks[2].CheckRestart.IgnoreWarnings)
}
func TestService_Connect_Canonicalize(t *testing.T) {
testutil.Parallel(t)
t.Run("nil connect", func(t *testing.T) {
cc := (*ConsulConnect)(nil)
cc.Canonicalize()
require.Nil(t, cc)
})
t.Run("empty connect", func(t *testing.T) {
cc := new(ConsulConnect)
cc.Canonicalize()
require.Empty(t, cc.Native)
require.Nil(t, cc.SidecarService)
require.Nil(t, cc.SidecarTask)
})
}
func TestService_Connect_ConsulSidecarService_Canonicalize(t *testing.T) {
testutil.Parallel(t)
t.Run("nil sidecar_service", func(t *testing.T) {
css := (*ConsulSidecarService)(nil)
css.Canonicalize()
require.Nil(t, css)
})
t.Run("empty sidecar_service", func(t *testing.T) {
css := new(ConsulSidecarService)
css.Canonicalize()
require.Empty(t, css.Tags)
require.Nil(t, css.Proxy)
})
t.Run("non-empty sidecar_service", func(t *testing.T) {
css := &ConsulSidecarService{
Tags: make([]string, 0),
Port: "port",
Proxy: &ConsulProxy{
LocalServiceAddress: "lsa",
LocalServicePort: 80,
},
}
css.Canonicalize()
require.Equal(t, &ConsulSidecarService{
Tags: nil,
Port: "port",
Proxy: &ConsulProxy{
LocalServiceAddress: "lsa",
LocalServicePort: 80},
}, css)
})
}
func TestService_Connect_ConsulProxy_Canonicalize(t *testing.T) {
testutil.Parallel(t)
t.Run("nil proxy", func(t *testing.T) {
cp := (*ConsulProxy)(nil)
cp.Canonicalize()
require.Nil(t, cp)
})
t.Run("empty proxy", func(t *testing.T) {
cp := new(ConsulProxy)
cp.Canonicalize()
require.Empty(t, cp.LocalServiceAddress)
require.Zero(t, cp.LocalServicePort)
require.Nil(t, cp.ExposeConfig)
require.Nil(t, cp.Upstreams)
require.Empty(t, cp.Config)
})
t.Run("non empty proxy", func(t *testing.T) {
cp := &ConsulProxy{
LocalServiceAddress: "127.0.0.1",
LocalServicePort: 80,
ExposeConfig: new(ConsulExposeConfig),
Upstreams: make([]*ConsulUpstream, 0),
Config: make(map[string]interface{}),
}
cp.Canonicalize()
require.Equal(t, "127.0.0.1", cp.LocalServiceAddress)
require.Equal(t, 80, cp.LocalServicePort)
require.Equal(t, &ConsulExposeConfig{}, cp.ExposeConfig)
require.Nil(t, cp.Upstreams)
require.Nil(t, cp.Config)
})
}
func TestService_Connect_ConsulUpstream_Copy(t *testing.T) {
testutil.Parallel(t)
t.Run("nil upstream", func(t *testing.T) {
cu := (*ConsulUpstream)(nil)
result := cu.Copy()
require.Nil(t, result)
})
t.Run("complete upstream", func(t *testing.T) {
cu := &ConsulUpstream{
DestinationName: "dest1",
Datacenter: "dc2",
LocalBindPort: 2000,
LocalBindAddress: "10.0.0.1",
MeshGateway: &ConsulMeshGateway{Mode: "remote"},
}
result := cu.Copy()
require.Equal(t, cu, result)
})
}
func TestService_Connect_ConsulUpstream_Canonicalize(t *testing.T) {
testutil.Parallel(t)
t.Run("nil upstream", func(t *testing.T) {
cu := (*ConsulUpstream)(nil)
cu.Canonicalize()
require.Nil(t, cu)
})
t.Run("complete", func(t *testing.T) {
cu := &ConsulUpstream{
DestinationName: "dest1",
Datacenter: "dc2",
LocalBindPort: 2000,
LocalBindAddress: "10.0.0.1",
MeshGateway: &ConsulMeshGateway{Mode: ""},
}
cu.Canonicalize()
require.Equal(t, &ConsulUpstream{
DestinationName: "dest1",
Datacenter: "dc2",
LocalBindPort: 2000,
LocalBindAddress: "10.0.0.1",
MeshGateway: &ConsulMeshGateway{Mode: ""},
}, cu)
})
}
func TestService_Connect_proxy_settings(t *testing.T) {
testutil.Parallel(t)
@@ -319,316 +192,4 @@ func TestService_Tags(t *testing.T) {
r.True(service.EnableTagOverride)
r.Equal([]string{"a", "b"}, service.Tags)
r.Equal([]string{"c", "d"}, service.CanaryTags)
}
func TestService_Connect_SidecarTask_Canonicalize(t *testing.T) {
testutil.Parallel(t)
t.Run("nil sidecar_task", func(t *testing.T) {
st := (*SidecarTask)(nil)
st.Canonicalize()
require.Nil(t, st)
})
t.Run("empty sidecar_task", func(t *testing.T) {
st := new(SidecarTask)
st.Canonicalize()
require.Nil(t, st.Config)
require.Nil(t, st.Env)
require.Equal(t, DefaultResources(), st.Resources)
require.Equal(t, DefaultLogConfig(), st.LogConfig)
require.Nil(t, st.Meta)
require.Equal(t, 5*time.Second, *st.KillTimeout)
require.Equal(t, 0*time.Second, *st.ShutdownDelay)
})
t.Run("non empty sidecar_task resources", func(t *testing.T) {
exp := DefaultResources()
exp.MemoryMB = intToPtr(333)
st := &SidecarTask{
Resources: &Resources{MemoryMB: intToPtr(333)},
}
st.Canonicalize()
require.Equal(t, exp, st.Resources)
})
}
func TestService_ConsulGateway_Canonicalize(t *testing.T) {
testutil.Parallel(t)
t.Run("nil", func(t *testing.T) {
cg := (*ConsulGateway)(nil)
cg.Canonicalize()
require.Nil(t, cg)
})
t.Run("set defaults", func(t *testing.T) {
cg := &ConsulGateway{
Proxy: &ConsulGatewayProxy{
ConnectTimeout: nil,
EnvoyGatewayBindTaggedAddresses: true,
EnvoyGatewayBindAddresses: make(map[string]*ConsulGatewayBindAddress, 0),
EnvoyGatewayNoDefaultBind: true,
Config: make(map[string]interface{}, 0),
},
Ingress: &ConsulIngressConfigEntry{
TLS: &ConsulGatewayTLSConfig{
Enabled: false,
},
Listeners: make([]*ConsulIngressListener, 0),
},
}
cg.Canonicalize()
require.Equal(t, timeToPtr(5*time.Second), cg.Proxy.ConnectTimeout)
require.True(t, cg.Proxy.EnvoyGatewayBindTaggedAddresses)
require.Nil(t, cg.Proxy.EnvoyGatewayBindAddresses)
require.True(t, cg.Proxy.EnvoyGatewayNoDefaultBind)
require.Empty(t, cg.Proxy.EnvoyDNSDiscoveryType)
require.Nil(t, cg.Proxy.Config)
require.Nil(t, cg.Ingress.Listeners)
})
}
func TestService_ConsulGateway_Copy(t *testing.T) {
testutil.Parallel(t)
t.Run("nil", func(t *testing.T) {
result := (*ConsulGateway)(nil).Copy()
require.Nil(t, result)
})
gateway := &ConsulGateway{
Proxy: &ConsulGatewayProxy{
ConnectTimeout: timeToPtr(3 * time.Second),
EnvoyGatewayBindTaggedAddresses: true,
EnvoyGatewayBindAddresses: map[string]*ConsulGatewayBindAddress{
"listener1": {Address: "10.0.0.1", Port: 2000},
"listener2": {Address: "10.0.0.1", Port: 2001},
},
EnvoyGatewayNoDefaultBind: true,
EnvoyDNSDiscoveryType: "STRICT_DNS",
Config: map[string]interface{}{
"foo": "bar",
"baz": 3,
},
},
Ingress: &ConsulIngressConfigEntry{
TLS: &ConsulGatewayTLSConfig{
Enabled: true,
},
Listeners: []*ConsulIngressListener{{
Port: 3333,
Protocol: "tcp",
Services: []*ConsulIngressService{{
Name: "service1",
Hosts: []string{
"127.0.0.1", "127.0.0.1:3333",
}},
}},
},
},
Terminating: &ConsulTerminatingConfigEntry{
Services: []*ConsulLinkedService{{
Name: "linked-service1",
}},
},
}
t.Run("complete", func(t *testing.T) {
result := gateway.Copy()
require.Equal(t, gateway, result)
})
}
func TestService_ConsulIngressConfigEntry_Canonicalize(t *testing.T) {
testutil.Parallel(t)
t.Run("nil", func(t *testing.T) {
c := (*ConsulIngressConfigEntry)(nil)
c.Canonicalize()
require.Nil(t, c)
})
t.Run("empty fields", func(t *testing.T) {
c := &ConsulIngressConfigEntry{
TLS: nil,
Listeners: []*ConsulIngressListener{},
}
c.Canonicalize()
require.Nil(t, c.TLS)
require.Nil(t, c.Listeners)
})
t.Run("complete", func(t *testing.T) {
c := &ConsulIngressConfigEntry{
TLS: &ConsulGatewayTLSConfig{Enabled: true},
Listeners: []*ConsulIngressListener{{
Port: 9090,
Protocol: "http",
Services: []*ConsulIngressService{{
Name: "service1",
Hosts: []string{"1.1.1.1"},
}},
}},
}
c.Canonicalize()
require.Equal(t, &ConsulIngressConfigEntry{
TLS: &ConsulGatewayTLSConfig{Enabled: true},
Listeners: []*ConsulIngressListener{{
Port: 9090,
Protocol: "http",
Services: []*ConsulIngressService{{
Name: "service1",
Hosts: []string{"1.1.1.1"},
}},
}},
}, c)
})
}
func TestService_ConsulIngressConfigEntry_Copy(t *testing.T) {
testutil.Parallel(t)
t.Run("nil", func(t *testing.T) {
result := (*ConsulIngressConfigEntry)(nil).Copy()
require.Nil(t, result)
})
entry := &ConsulIngressConfigEntry{
TLS: &ConsulGatewayTLSConfig{
Enabled: true,
},
Listeners: []*ConsulIngressListener{{
Port: 1111,
Protocol: "http",
Services: []*ConsulIngressService{{
Name: "service1",
Hosts: []string{"1.1.1.1", "1.1.1.1:9000"},
}, {
Name: "service2",
Hosts: []string{"2.2.2.2"},
}},
}},
}
t.Run("complete", func(t *testing.T) {
result := entry.Copy()
require.Equal(t, entry, result)
})
}
func TestService_ConsulTerminatingConfigEntry_Canonicalize(t *testing.T) {
testutil.Parallel(t)
t.Run("nil", func(t *testing.T) {
c := (*ConsulTerminatingConfigEntry)(nil)
c.Canonicalize()
require.Nil(t, c)
})
t.Run("empty services", func(t *testing.T) {
c := &ConsulTerminatingConfigEntry{
Services: []*ConsulLinkedService{},
}
c.Canonicalize()
require.Nil(t, c.Services)
})
}
func TestService_ConsulTerminatingConfigEntry_Copy(t *testing.T) {
testutil.Parallel(t)
t.Run("nil", func(t *testing.T) {
result := (*ConsulIngressConfigEntry)(nil).Copy()
require.Nil(t, result)
})
entry := &ConsulTerminatingConfigEntry{
Services: []*ConsulLinkedService{{
Name: "servic1",
}, {
Name: "service2",
CAFile: "ca_file.pem",
CertFile: "cert_file.pem",
KeyFile: "key_file.pem",
SNI: "sni.terminating.consul",
}},
}
t.Run("complete", func(t *testing.T) {
result := entry.Copy()
require.Equal(t, entry, result)
})
}
func TestService_ConsulMeshConfigEntry_Canonicalize(t *testing.T) {
testutil.Parallel(t)
t.Run("nil", func(t *testing.T) {
ce := (*ConsulMeshConfigEntry)(nil)
ce.Canonicalize()
require.Nil(t, ce)
})
t.Run("instantiated", func(t *testing.T) {
ce := new(ConsulMeshConfigEntry)
ce.Canonicalize()
require.NotNil(t, ce)
})
}
func TestService_ConsulMeshConfigEntry_Copy(t *testing.T) {
testutil.Parallel(t)
t.Run("nil", func(t *testing.T) {
ce := (*ConsulMeshConfigEntry)(nil)
ce2 := ce.Copy()
require.Nil(t, ce2)
})
t.Run("instantiated", func(t *testing.T) {
ce := new(ConsulMeshConfigEntry)
ce2 := ce.Copy()
require.NotNil(t, ce2)
})
}
func TestService_ConsulMeshGateway_Canonicalize(t *testing.T) {
testutil.Parallel(t)
t.Run("nil", func(t *testing.T) {
c := (*ConsulMeshGateway)(nil)
c.Canonicalize()
require.Nil(t, c)
})
t.Run("unset mode", func(t *testing.T) {
c := &ConsulMeshGateway{Mode: ""}
c.Canonicalize()
require.Equal(t, "", c.Mode)
})
t.Run("set mode", func(t *testing.T) {
c := &ConsulMeshGateway{Mode: "remote"}
c.Canonicalize()
require.Equal(t, "remote", c.Mode)
})
}
func TestService_ConsulMeshGateway_Copy(t *testing.T) {
testutil.Parallel(t)
t.Run("nil", func(t *testing.T) {
c := (*ConsulMeshGateway)(nil)
result := c.Copy()
require.Nil(t, result)
})
t.Run("instantiated", func(t *testing.T) {
c := &ConsulMeshGateway{
Mode: "local",
}
result := c.Copy()
require.Equal(t, c, result)
})
}
}

View File

@@ -58,7 +58,7 @@ func (s *ServiceDeleteCommand) Run(args []string) int {
return 1
}
if _, err := client.ServiceRegistrations().Delete(args[0], args[1], nil); err != nil {
if _, err := client.Services().Delete(args[0], args[1], nil); err != nil {
s.Ui.Error(fmt.Sprintf("Error deleting service registration: %s", err))
return 1
}

View File

@@ -61,7 +61,7 @@ func TestServiceDeleteCommand_Run(t *testing.T) {
require.Equal(t, 0, registerCode)
// Detail the service as we need the ID.
serviceList, _, err := client.ServiceRegistrations().Get("service-discovery-nomad-delete", nil)
serviceList, _, err := client.Services().Get("service-discovery-nomad-delete", nil)
require.NoError(t, err)
require.Len(t, serviceList, 1)

View File

@@ -92,7 +92,7 @@ func (s *ServiceInfoCommand) Run(args []string) int {
return 1
}
serviceInfo, _, err := client.ServiceRegistrations().Get(args[0], nil)
serviceInfo, _, err := client.Services().Get(args[0], nil)
if err != nil {
s.Ui.Error(fmt.Sprintf("Error listing service registrations: %s", err))
return 1

View File

@@ -89,7 +89,7 @@ func (s *ServiceListCommand) Run(args []string) int {
return 1
}
list, _, err := client.ServiceRegistrations().List(nil)
list, _, err := client.Services().List(nil)
if err != nil {
s.Ui.Error(fmt.Sprintf("Error listing service registrations: %s", err))
return 1