open-nomad/client/taskenv/services_test.go
Luiz Aoqui 1ae26981a0
connect: interpolate task env in config values (#14445)
When configuring Consul Service Mesh, it's sometimes necessary to
provide dynamic value that are only known to Nomad at runtime. By
interpolating configuration values (in addition to configuration keys),
user are able to pass these dynamic values to Consul from their Nomad
jobs.
2022-09-02 15:00:28 -04:00

434 lines
11 KiB
Go

package taskenv
import (
"testing"
"time"
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/helper/pointer"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/stretchr/testify/require"
)
// TestInterpolateServices asserts that all service
// and check fields are properly interpolated.
func TestInterpolateServices(t *testing.T) {
ci.Parallel(t)
services := []*structs.Service{
{
Name: "${name}",
PortLabel: "${portlabel}",
Tags: []string{"${tags}"},
Meta: map[string]string{
"meta-key": "${meta}",
},
CanaryMeta: map[string]string{
"canarymeta-key": "${canarymeta}",
},
Address: "${address}",
TaggedAddresses: map[string]string{
"${ta-key}": "${ta-address}",
},
Checks: []*structs.ServiceCheck{
{
Name: "${checkname}",
Type: "${checktype}",
Command: "${checkcmd}",
Args: []string{"${checkarg}"},
Path: "${checkstr}",
Protocol: "${checkproto}",
PortLabel: "${checklabel}",
InitialStatus: "${checkstatus}",
Method: "${checkmethod}",
Header: map[string][]string{
"${checkheaderk}": {"${checkheaderv}"},
},
},
},
},
}
env := &TaskEnv{
EnvMap: map[string]string{
"name": "name",
"portlabel": "portlabel",
"tags": "tags",
"meta": "meta-value",
"address": "example.com",
"ta-key": "public_wan",
"ta-address": "1.2.3.4",
"canarymeta": "canarymeta-value",
"checkname": "checkname",
"checktype": "checktype",
"checkcmd": "checkcmd",
"checkarg": "checkarg",
"checkstr": "checkstr",
"checkpath": "checkpath",
"checkproto": "checkproto",
"checklabel": "checklabel",
"checkstatus": "checkstatus",
"checkmethod": "checkmethod",
"checkheaderk": "checkheaderk",
"checkheaderv": "checkheaderv",
},
}
interpolated := InterpolateServices(env, services)
exp := []*structs.Service{
{
Name: "name",
PortLabel: "portlabel",
Tags: []string{"tags"},
Meta: map[string]string{
"meta-key": "meta-value",
},
CanaryMeta: map[string]string{
"canarymeta-key": "canarymeta-value",
},
Address: "example.com",
TaggedAddresses: map[string]string{
"public_wan": "1.2.3.4",
},
Checks: []*structs.ServiceCheck{
{
Name: "checkname",
Type: "checktype",
Command: "checkcmd",
Args: []string{"checkarg"},
Path: "checkstr",
Protocol: "checkproto",
PortLabel: "checklabel",
InitialStatus: "checkstatus",
Method: "checkmethod",
Header: map[string][]string{
"checkheaderk": {"checkheaderv"},
},
},
},
},
}
require.Equal(t, exp, interpolated)
}
var testEnv = NewTaskEnv(
map[string]string{"foo": "bar", "baz": "blah"},
map[string]string{"foo": "bar", "baz": "blah"},
nil, nil, "", "")
func TestInterpolate_interpolateMapStringSliceString(t *testing.T) {
ci.Parallel(t)
t.Run("nil", func(t *testing.T) {
require.Nil(t, interpolateMapStringSliceString(testEnv, nil))
})
t.Run("not nil", func(t *testing.T) {
require.Equal(t, map[string][]string{
"a": {"b"},
"bar": {"blah", "c"},
}, interpolateMapStringSliceString(testEnv, map[string][]string{
"a": {"b"},
"${foo}": {"${baz}", "c"},
}))
})
}
func TestInterpolate_interpolateMapStringString(t *testing.T) {
ci.Parallel(t)
t.Run("nil", func(t *testing.T) {
require.Nil(t, interpolateMapStringString(testEnv, nil))
})
t.Run("not nil", func(t *testing.T) {
require.Equal(t, map[string]string{
"a": "b",
"bar": "blah",
}, interpolateMapStringString(testEnv, map[string]string{
"a": "b",
"${foo}": "${baz}",
}))
})
}
func TestInterpolate_interpolateMapStringInterface(t *testing.T) {
ci.Parallel(t)
t.Run("nil", func(t *testing.T) {
require.Nil(t, interpolateMapStringInterface(testEnv, nil))
})
t.Run("not nil", func(t *testing.T) {
require.Equal(t, map[string]interface{}{
"a": 1,
"bar": 2,
}, interpolateMapStringInterface(testEnv, map[string]interface{}{
"a": 1,
"${foo}": 2,
}))
})
}
func TestInterpolate_interpolateConnect(t *testing.T) {
ci.Parallel(t)
e := map[string]string{
"tag1": "_tag1",
"port1": "12345",
"address1": "1.2.3.4",
"destination1": "_dest1",
"datacenter1": "_datacenter1",
"localbindaddress1": "127.0.0.2",
"path1": "_path1",
"protocol1": "_protocol1",
"port2": "_port2",
"config1": "_config1",
"driver1": "_driver1",
"user1": "_user1",
"config2": "_config2",
"env1": "_env1",
"env2": "_env2",
"mode1": "_mode1",
"device1": "_device1",
"cidr1": "10.0.0.0/64",
"ip1": "1.1.1.1",
"server1": "10.0.0.1",
"search1": "10.0.0.2",
"option1": "10.0.0.3",
"port3": "_port3",
"network1": "_network1",
"port4": "_port4",
"network2": "_network2",
"resource1": "_resource1",
"meta1": "_meta1",
"meta2": "_meta2",
"signal1": "_signal1",
"bind1": "_bind1",
"address2": "10.0.0.4",
"config3": "_config3",
"protocol2": "_protocol2",
"service1": "_service1",
"host1": "_host1",
}
env := NewTaskEnv(e, e, nil, nil, "", "")
connect := &structs.ConsulConnect{
Native: false,
SidecarService: &structs.ConsulSidecarService{
Tags: []string{"${tag1}", "tag2"},
Port: "${port1}",
Proxy: &structs.ConsulProxy{
LocalServiceAddress: "${address1}",
LocalServicePort: 10000,
Upstreams: []structs.ConsulUpstream{{
DestinationName: "${destination1}",
Datacenter: "${datacenter1}",
LocalBindPort: 10001,
LocalBindAddress: "${localbindaddress1}",
}},
Expose: &structs.ConsulExposeConfig{
Paths: []structs.ConsulExposePath{{
Path: "${path1}",
Protocol: "${protocol1}",
ListenerPort: "${port2}",
LocalPathPort: 10002,
}},
},
Config: map[string]interface{}{
"${config1}": 1,
"port": "${port1}",
},
},
},
SidecarTask: &structs.SidecarTask{
Name: "name", // not interpolated by taskenv
Driver: "${driver1}",
User: "${user1}",
Config: map[string]interface{}{"${config2}": 2},
Env: map[string]string{"${env1}": "${env2}"},
Resources: &structs.Resources{
CPU: 1,
MemoryMB: 2,
DiskMB: 3,
IOPS: 4,
Networks: structs.Networks{{
Mode: "${mode1}",
Device: "${device1}",
CIDR: "${cidr1}",
IP: "${ip1}",
MBits: 1,
DNS: &structs.DNSConfig{
Servers: []string{"${server1}"},
Searches: []string{"${search1}"},
Options: []string{"${option1}"},
},
ReservedPorts: []structs.Port{{
Label: "${port3}",
Value: 9000,
To: 9000,
HostNetwork: "${network1}",
}},
DynamicPorts: []structs.Port{{
Label: "${port4}",
Value: 9001,
To: 9001,
HostNetwork: "${network2}",
}},
}},
Devices: structs.ResourceDevices{{
Name: "${resource1}",
}},
},
Meta: map[string]string{"${meta1}": "${meta2}"},
KillTimeout: pointer.Of(1 * time.Second),
LogConfig: &structs.LogConfig{
MaxFiles: 1,
MaxFileSizeMB: 2,
},
ShutdownDelay: pointer.Of(2 * time.Second),
KillSignal: "${signal1}",
},
Gateway: &structs.ConsulGateway{
Proxy: &structs.ConsulGatewayProxy{
ConnectTimeout: pointer.Of(3 * time.Second),
EnvoyGatewayBindTaggedAddresses: true,
EnvoyGatewayBindAddresses: map[string]*structs.ConsulGatewayBindAddress{
"${bind1}": {
Address: "${address2}",
Port: 8000,
},
},
EnvoyGatewayNoDefaultBind: true,
Config: map[string]interface{}{
"${config3}": 4,
},
},
Ingress: &structs.ConsulIngressConfigEntry{
TLS: &structs.ConsulGatewayTLSConfig{
Enabled: true,
},
Listeners: []*structs.ConsulIngressListener{{
Protocol: "${protocol2}",
Port: 8001,
Services: []*structs.ConsulIngressService{{
Name: "${service1}",
Hosts: []string{"${host1}", "host2"},
}},
}},
},
},
}
interpolateConnect(env, connect)
require.Equal(t, &structs.ConsulConnect{
Native: false,
SidecarService: &structs.ConsulSidecarService{
Tags: []string{"_tag1", "tag2"},
Port: "12345",
Proxy: &structs.ConsulProxy{
LocalServiceAddress: "1.2.3.4",
LocalServicePort: 10000,
Upstreams: []structs.ConsulUpstream{{
DestinationName: "_dest1",
Datacenter: "_datacenter1",
LocalBindPort: 10001,
LocalBindAddress: "127.0.0.2",
}},
Expose: &structs.ConsulExposeConfig{
Paths: []structs.ConsulExposePath{{
Path: "_path1",
Protocol: "_protocol1",
ListenerPort: "_port2",
LocalPathPort: 10002,
}},
},
Config: map[string]interface{}{
"_config1": 1,
"port": "12345",
},
},
},
SidecarTask: &structs.SidecarTask{
Name: "name", // not interpolated by InterpolateServices
Driver: "_driver1",
User: "_user1",
Config: map[string]interface{}{"_config2": 2},
Env: map[string]string{"_env1": "_env2"},
Resources: &structs.Resources{
CPU: 1,
MemoryMB: 2,
DiskMB: 3,
IOPS: 4,
Networks: structs.Networks{{
Mode: "_mode1",
Device: "_device1",
CIDR: "10.0.0.0/64",
IP: "1.1.1.1",
MBits: 1,
DNS: &structs.DNSConfig{
Servers: []string{"10.0.0.1"},
Searches: []string{"10.0.0.2"},
Options: []string{"10.0.0.3"},
},
ReservedPorts: []structs.Port{{
Label: "_port3",
Value: 9000,
To: 9000,
HostNetwork: "_network1",
}},
DynamicPorts: []structs.Port{{
Label: "_port4",
Value: 9001,
To: 9001,
HostNetwork: "_network2",
}},
}},
Devices: structs.ResourceDevices{{
Name: "_resource1",
}},
},
Meta: map[string]string{"_meta1": "_meta2"},
KillTimeout: pointer.Of(1 * time.Second),
LogConfig: &structs.LogConfig{
MaxFiles: 1,
MaxFileSizeMB: 2,
},
ShutdownDelay: pointer.Of(2 * time.Second),
KillSignal: "_signal1",
},
Gateway: &structs.ConsulGateway{
Proxy: &structs.ConsulGatewayProxy{
ConnectTimeout: pointer.Of(3 * time.Second),
EnvoyGatewayBindTaggedAddresses: true,
EnvoyGatewayBindAddresses: map[string]*structs.ConsulGatewayBindAddress{
"_bind1": {
Address: "10.0.0.4",
Port: 8000,
},
},
EnvoyGatewayNoDefaultBind: true,
Config: map[string]interface{}{
"_config3": 4,
},
},
Ingress: &structs.ConsulIngressConfigEntry{
TLS: &structs.ConsulGatewayTLSConfig{
Enabled: true,
},
Listeners: []*structs.ConsulIngressListener{{
Protocol: "_protocol2",
Port: 8001,
Services: []*structs.ConsulIngressService{{
Name: "_service1",
Hosts: []string{"_host1", "host2"},
}},
}},
},
},
}, connect)
}