mirror of
https://github.com/kemko/nomad.git
synced 2026-01-02 08:25:43 +03:00
Adds missing interpolation step to the `meta` blocks when building the task environment. Also fixes incorrect parameter order in the test assertion and adds diagnostics to the test.
859 lines
26 KiB
Go
859 lines
26 KiB
Go
package taskenv
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
|
|
hcl "github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/gohcl"
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
|
"github.com/hashicorp/nomad/helper/uuid"
|
|
"github.com/hashicorp/nomad/nomad/mock"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
"github.com/hashicorp/nomad/plugins/drivers"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const (
|
|
// Node values that tests can rely on
|
|
metaKey = "instance"
|
|
metaVal = "t2-micro"
|
|
attrKey = "arch"
|
|
attrVal = "amd64"
|
|
nodeName = "test node"
|
|
nodeClass = "test class"
|
|
|
|
// Environment variable values that tests can rely on
|
|
envOneKey = "NOMAD_IP"
|
|
envOneVal = "127.0.0.1"
|
|
envTwoKey = "NOMAD_PORT_WEB"
|
|
envTwoVal = ":80"
|
|
)
|
|
|
|
var (
|
|
// portMap for use in tests as its set after Builder creation
|
|
portMap = map[string]int{
|
|
"https": 443,
|
|
}
|
|
)
|
|
|
|
func testEnvBuilder() *Builder {
|
|
n := mock.Node()
|
|
n.Attributes = map[string]string{
|
|
attrKey: attrVal,
|
|
}
|
|
n.Meta = map[string]string{
|
|
metaKey: metaVal,
|
|
}
|
|
n.Name = nodeName
|
|
n.NodeClass = nodeClass
|
|
|
|
task := mock.Job().TaskGroups[0].Tasks[0]
|
|
task.Env = map[string]string{
|
|
envOneKey: envOneVal,
|
|
envTwoKey: envTwoVal,
|
|
}
|
|
return NewBuilder(n, mock.Alloc(), task, "global")
|
|
}
|
|
|
|
func TestEnvironment_ParseAndReplace_Env(t *testing.T) {
|
|
env := testEnvBuilder()
|
|
|
|
input := []string{fmt.Sprintf(`"${%v}"!`, envOneKey), fmt.Sprintf("${%s}${%s}", envOneKey, envTwoKey)}
|
|
act := env.Build().ParseAndReplace(input)
|
|
exp := []string{fmt.Sprintf(`"%s"!`, envOneVal), fmt.Sprintf("%s%s", envOneVal, envTwoVal)}
|
|
|
|
if !reflect.DeepEqual(act, exp) {
|
|
t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
|
|
}
|
|
}
|
|
|
|
func TestEnvironment_ParseAndReplace_Meta(t *testing.T) {
|
|
input := []string{fmt.Sprintf("${%v%v}", nodeMetaPrefix, metaKey)}
|
|
exp := []string{metaVal}
|
|
env := testEnvBuilder()
|
|
act := env.Build().ParseAndReplace(input)
|
|
|
|
if !reflect.DeepEqual(act, exp) {
|
|
t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
|
|
}
|
|
}
|
|
|
|
func TestEnvironment_ParseAndReplace_Attr(t *testing.T) {
|
|
input := []string{fmt.Sprintf("${%v%v}", nodeAttributePrefix, attrKey)}
|
|
exp := []string{attrVal}
|
|
env := testEnvBuilder()
|
|
act := env.Build().ParseAndReplace(input)
|
|
|
|
if !reflect.DeepEqual(act, exp) {
|
|
t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
|
|
}
|
|
}
|
|
|
|
func TestEnvironment_ParseAndReplace_Node(t *testing.T) {
|
|
input := []string{fmt.Sprintf("${%v}", nodeNameKey), fmt.Sprintf("${%v}", nodeClassKey)}
|
|
exp := []string{nodeName, nodeClass}
|
|
env := testEnvBuilder()
|
|
act := env.Build().ParseAndReplace(input)
|
|
|
|
if !reflect.DeepEqual(act, exp) {
|
|
t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
|
|
}
|
|
}
|
|
|
|
func TestEnvironment_ParseAndReplace_Mixed(t *testing.T) {
|
|
input := []string{
|
|
fmt.Sprintf("${%v}${%v%v}", nodeNameKey, nodeAttributePrefix, attrKey),
|
|
fmt.Sprintf("${%v}${%v%v}", nodeClassKey, nodeMetaPrefix, metaKey),
|
|
fmt.Sprintf("${%v}${%v}", envTwoKey, nodeClassKey),
|
|
}
|
|
exp := []string{
|
|
fmt.Sprintf("%v%v", nodeName, attrVal),
|
|
fmt.Sprintf("%v%v", nodeClass, metaVal),
|
|
fmt.Sprintf("%v%v", envTwoVal, nodeClass),
|
|
}
|
|
env := testEnvBuilder()
|
|
act := env.Build().ParseAndReplace(input)
|
|
|
|
if !reflect.DeepEqual(act, exp) {
|
|
t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
|
|
}
|
|
}
|
|
|
|
func TestEnvironment_ReplaceEnv_Mixed(t *testing.T) {
|
|
input := fmt.Sprintf("${%v}${%v%v}", nodeNameKey, nodeAttributePrefix, attrKey)
|
|
exp := fmt.Sprintf("%v%v", nodeName, attrVal)
|
|
env := testEnvBuilder()
|
|
act := env.Build().ReplaceEnv(input)
|
|
|
|
if act != exp {
|
|
t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
|
|
}
|
|
}
|
|
|
|
func TestEnvironment_AsList(t *testing.T) {
|
|
n := mock.Node()
|
|
n.Meta = map[string]string{
|
|
"metaKey": "metaVal",
|
|
}
|
|
a := mock.Alloc()
|
|
a.Job.ParentID = fmt.Sprintf("mock-parent-service-%s", uuid.Generate())
|
|
a.AllocatedResources.Tasks["web"] = &structs.AllocatedTaskResources{
|
|
Cpu: structs.AllocatedCpuResources{CpuShares: 500},
|
|
Memory: structs.AllocatedMemoryResources{
|
|
MemoryMB: 256,
|
|
MemoryMaxMB: 512,
|
|
},
|
|
Networks: []*structs.NetworkResource{{
|
|
Device: "eth0",
|
|
IP: "127.0.0.1",
|
|
ReservedPorts: []structs.Port{{Label: "https", Value: 8080}},
|
|
MBits: 50,
|
|
DynamicPorts: []structs.Port{{Label: "http", Value: 80}},
|
|
}},
|
|
}
|
|
|
|
a.AllocatedResources.Tasks["ssh"] = &structs.AllocatedTaskResources{
|
|
Networks: []*structs.NetworkResource{
|
|
{
|
|
Device: "eth0",
|
|
IP: "192.168.0.100",
|
|
MBits: 50,
|
|
ReservedPorts: []structs.Port{
|
|
{Label: "ssh", Value: 22},
|
|
{Label: "other", Value: 1234},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
a.Namespace = "not-default"
|
|
task := a.Job.TaskGroups[0].Tasks[0]
|
|
task.Env = map[string]string{
|
|
"taskEnvKey": "taskEnvVal",
|
|
}
|
|
env := NewBuilder(n, a, task, "global").SetDriverNetwork(
|
|
&drivers.DriverNetwork{PortMap: map[string]int{"https": 443}},
|
|
)
|
|
|
|
act := env.Build().List()
|
|
exp := []string{
|
|
"taskEnvKey=taskEnvVal",
|
|
"NOMAD_ADDR_http=127.0.0.1:80",
|
|
"NOMAD_PORT_http=80",
|
|
"NOMAD_IP_http=127.0.0.1",
|
|
"NOMAD_ADDR_https=127.0.0.1:8080",
|
|
"NOMAD_PORT_https=443",
|
|
"NOMAD_IP_https=127.0.0.1",
|
|
"NOMAD_HOST_PORT_http=80",
|
|
"NOMAD_HOST_PORT_https=8080",
|
|
"NOMAD_TASK_NAME=web",
|
|
"NOMAD_GROUP_NAME=web",
|
|
"NOMAD_ADDR_ssh_other=192.168.0.100:1234",
|
|
"NOMAD_ADDR_ssh_ssh=192.168.0.100:22",
|
|
"NOMAD_IP_ssh_other=192.168.0.100",
|
|
"NOMAD_IP_ssh_ssh=192.168.0.100",
|
|
"NOMAD_PORT_ssh_other=1234",
|
|
"NOMAD_PORT_ssh_ssh=22",
|
|
"NOMAD_CPU_LIMIT=500",
|
|
"NOMAD_DC=dc1",
|
|
"NOMAD_NAMESPACE=not-default",
|
|
"NOMAD_REGION=global",
|
|
"NOMAD_MEMORY_LIMIT=256",
|
|
"NOMAD_MEMORY_MAX_LIMIT=512",
|
|
"NOMAD_META_ELB_CHECK_INTERVAL=30s",
|
|
"NOMAD_META_ELB_CHECK_MIN=3",
|
|
"NOMAD_META_ELB_CHECK_TYPE=http",
|
|
"NOMAD_META_FOO=bar",
|
|
"NOMAD_META_OWNER=armon",
|
|
"NOMAD_META_elb_check_interval=30s",
|
|
"NOMAD_META_elb_check_min=3",
|
|
"NOMAD_META_elb_check_type=http",
|
|
"NOMAD_META_foo=bar",
|
|
"NOMAD_META_owner=armon",
|
|
fmt.Sprintf("NOMAD_JOB_ID=%s", a.Job.ID),
|
|
"NOMAD_JOB_NAME=my-job",
|
|
fmt.Sprintf("NOMAD_JOB_PARENT_ID=%s", a.Job.ParentID),
|
|
fmt.Sprintf("NOMAD_ALLOC_ID=%s", a.ID),
|
|
"NOMAD_ALLOC_INDEX=0",
|
|
}
|
|
sort.Strings(act)
|
|
sort.Strings(exp)
|
|
require.Equal(t, exp, act)
|
|
}
|
|
|
|
func TestEnvironment_AllValues(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
n := mock.Node()
|
|
n.Meta = map[string]string{
|
|
"metaKey": "metaVal",
|
|
"nested.meta.key": "a",
|
|
"invalid...metakey": "b",
|
|
}
|
|
a := mock.ConnectAlloc()
|
|
a.Job.ParentID = fmt.Sprintf("mock-parent-service-%s", uuid.Generate())
|
|
a.AllocatedResources.Tasks["web"].Networks[0] = &structs.NetworkResource{
|
|
Device: "eth0",
|
|
IP: "127.0.0.1",
|
|
ReservedPorts: []structs.Port{{Label: "https", Value: 8080}},
|
|
MBits: 50,
|
|
DynamicPorts: []structs.Port{{Label: "http", Value: 80}},
|
|
}
|
|
a.AllocatedResources.Tasks["ssh"] = &structs.AllocatedTaskResources{
|
|
Networks: []*structs.NetworkResource{
|
|
{
|
|
Device: "eth0",
|
|
IP: "192.168.0.100",
|
|
MBits: 50,
|
|
ReservedPorts: []structs.Port{
|
|
{Label: "ssh", Value: 22},
|
|
{Label: "other", Value: 1234},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
a.AllocatedResources.Shared.Ports = structs.AllocatedPorts{
|
|
{
|
|
Label: "admin",
|
|
Value: 32000,
|
|
To: 9000,
|
|
HostIP: "127.0.0.1",
|
|
},
|
|
}
|
|
|
|
sharedNet := a.AllocatedResources.Shared.Networks[0]
|
|
|
|
// Add group network port with only a host port.
|
|
sharedNet.DynamicPorts = append(sharedNet.DynamicPorts, structs.Port{
|
|
Label: "hostonly",
|
|
Value: 9998,
|
|
})
|
|
|
|
// Add group network reserved port with a To value.
|
|
sharedNet.ReservedPorts = append(sharedNet.ReservedPorts, structs.Port{
|
|
Label: "static",
|
|
Value: 9997,
|
|
To: 97,
|
|
})
|
|
|
|
task := a.Job.TaskGroups[0].Tasks[0]
|
|
task.Env = map[string]string{
|
|
"taskEnvKey": "taskEnvVal",
|
|
"nested.task.key": "x",
|
|
"invalid...taskkey": "y",
|
|
".a": "a",
|
|
"b.": "b",
|
|
".": "c",
|
|
}
|
|
task.Meta = map[string]string{
|
|
"taskMetaKey-${NOMAD_TASK_NAME}": "taskMetaVal-${node.unique.id}",
|
|
"foo": "bar",
|
|
}
|
|
env := NewBuilder(n, a, task, "global").SetDriverNetwork(
|
|
&drivers.DriverNetwork{PortMap: map[string]int{"https": 443}},
|
|
)
|
|
|
|
values, errs, err := env.Build().AllValues()
|
|
require.NoError(t, err)
|
|
|
|
// Assert the keys we couldn't nest were reported
|
|
require.Len(t, errs, 5)
|
|
require.Contains(t, errs, "invalid...taskkey")
|
|
require.Contains(t, errs, "meta.invalid...metakey")
|
|
require.Contains(t, errs, ".a")
|
|
require.Contains(t, errs, "b.")
|
|
require.Contains(t, errs, ".")
|
|
|
|
exp := map[string]string{
|
|
// Node
|
|
"node.unique.id": n.ID,
|
|
"node.region": "global",
|
|
"node.datacenter": n.Datacenter,
|
|
"node.unique.name": n.Name,
|
|
"node.class": n.NodeClass,
|
|
"meta.metaKey": "metaVal",
|
|
"attr.arch": "x86",
|
|
"attr.driver.exec": "1",
|
|
"attr.driver.mock_driver": "1",
|
|
"attr.kernel.name": "linux",
|
|
"attr.nomad.version": "0.5.0",
|
|
|
|
// 0.9 style meta and attr
|
|
"node.meta.metaKey": "metaVal",
|
|
"node.attr.arch": "x86",
|
|
"node.attr.driver.exec": "1",
|
|
"node.attr.driver.mock_driver": "1",
|
|
"node.attr.kernel.name": "linux",
|
|
"node.attr.nomad.version": "0.5.0",
|
|
|
|
// Env
|
|
"taskEnvKey": "taskEnvVal",
|
|
"NOMAD_ADDR_http": "127.0.0.1:80",
|
|
"NOMAD_PORT_http": "80",
|
|
"NOMAD_IP_http": "127.0.0.1",
|
|
"NOMAD_ADDR_https": "127.0.0.1:8080",
|
|
"NOMAD_PORT_https": "443",
|
|
"NOMAD_IP_https": "127.0.0.1",
|
|
"NOMAD_HOST_PORT_http": "80",
|
|
"NOMAD_HOST_PORT_https": "8080",
|
|
"NOMAD_TASK_NAME": "web",
|
|
"NOMAD_GROUP_NAME": "web",
|
|
"NOMAD_ADDR_ssh_other": "192.168.0.100:1234",
|
|
"NOMAD_ADDR_ssh_ssh": "192.168.0.100:22",
|
|
"NOMAD_IP_ssh_other": "192.168.0.100",
|
|
"NOMAD_IP_ssh_ssh": "192.168.0.100",
|
|
"NOMAD_PORT_ssh_other": "1234",
|
|
"NOMAD_PORT_ssh_ssh": "22",
|
|
"NOMAD_CPU_LIMIT": "500",
|
|
"NOMAD_DC": "dc1",
|
|
"NOMAD_NAMESPACE": "default",
|
|
"NOMAD_REGION": "global",
|
|
"NOMAD_MEMORY_LIMIT": "256",
|
|
"NOMAD_META_ELB_CHECK_INTERVAL": "30s",
|
|
"NOMAD_META_ELB_CHECK_MIN": "3",
|
|
"NOMAD_META_ELB_CHECK_TYPE": "http",
|
|
"NOMAD_META_FOO": "bar",
|
|
"NOMAD_META_OWNER": "armon",
|
|
"NOMAD_META_elb_check_interval": "30s",
|
|
"NOMAD_META_elb_check_min": "3",
|
|
"NOMAD_META_elb_check_type": "http",
|
|
"NOMAD_META_foo": "bar",
|
|
"NOMAD_META_owner": "armon",
|
|
"NOMAD_META_taskMetaKey_web": "taskMetaVal-" + n.ID,
|
|
"NOMAD_JOB_ID": a.Job.ID,
|
|
"NOMAD_JOB_NAME": "my-job",
|
|
"NOMAD_JOB_PARENT_ID": a.Job.ParentID,
|
|
"NOMAD_ALLOC_ID": a.ID,
|
|
"NOMAD_ALLOC_INDEX": "0",
|
|
"NOMAD_PORT_connect_proxy_testconnect": "9999",
|
|
"NOMAD_HOST_PORT_connect_proxy_testconnect": "9999",
|
|
"NOMAD_PORT_hostonly": "9998",
|
|
"NOMAD_HOST_PORT_hostonly": "9998",
|
|
"NOMAD_PORT_static": "97",
|
|
"NOMAD_HOST_PORT_static": "9997",
|
|
"NOMAD_ADDR_admin": "127.0.0.1:32000",
|
|
"NOMAD_HOST_ADDR_admin": "127.0.0.1:32000",
|
|
"NOMAD_IP_admin": "127.0.0.1",
|
|
"NOMAD_HOST_IP_admin": "127.0.0.1",
|
|
"NOMAD_PORT_admin": "9000",
|
|
"NOMAD_ALLOC_PORT_admin": "9000",
|
|
"NOMAD_HOST_PORT_admin": "32000",
|
|
|
|
// 0.9 style env map
|
|
`env["taskEnvKey"]`: "taskEnvVal",
|
|
`env["NOMAD_ADDR_http"]`: "127.0.0.1:80",
|
|
`env["nested.task.key"]`: "x",
|
|
`env["invalid...taskkey"]`: "y",
|
|
`env[".a"]`: "a",
|
|
`env["b."]`: "b",
|
|
`env["."]`: "c",
|
|
}
|
|
|
|
evalCtx := &hcl.EvalContext{
|
|
Variables: values,
|
|
}
|
|
|
|
for k, expectedVal := range exp {
|
|
t.Run(k, func(t *testing.T) {
|
|
// Parse HCL containing the test key
|
|
hclStr := fmt.Sprintf(`"${%s}"`, k)
|
|
expr, diag := hclsyntax.ParseExpression([]byte(hclStr), "test.hcl", hcl.Pos{})
|
|
require.Empty(t, diag)
|
|
|
|
// Decode with the TaskEnv values
|
|
out := ""
|
|
diag = gohcl.DecodeExpression(expr, evalCtx, &out)
|
|
require.Empty(t, diag)
|
|
require.Equal(t, expectedVal, out,
|
|
fmt.Sprintf("expected %q got %q", expectedVal, out))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEnvironment_VaultToken(t *testing.T) {
|
|
n := mock.Node()
|
|
a := mock.Alloc()
|
|
env := NewBuilder(n, a, a.Job.TaskGroups[0].Tasks[0], "global")
|
|
env.SetVaultToken("123", "vault-namespace", false)
|
|
|
|
{
|
|
act := env.Build().All()
|
|
if act[VaultToken] != "" {
|
|
t.Fatalf("Unexpected environment variables: %s=%q", VaultToken, act[VaultToken])
|
|
}
|
|
if act[VaultNamespace] != "" {
|
|
t.Fatalf("Unexpected environment variables: %s=%q", VaultNamespace, act[VaultNamespace])
|
|
}
|
|
}
|
|
|
|
{
|
|
act := env.SetVaultToken("123", "", true).Build().List()
|
|
exp := "VAULT_TOKEN=123"
|
|
found := false
|
|
foundNs := false
|
|
for _, entry := range act {
|
|
if entry == exp {
|
|
found = true
|
|
}
|
|
if strings.HasPrefix(entry, "VAULT_NAMESPACE=") {
|
|
foundNs = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("did not find %q in:\n%s", exp, strings.Join(act, "\n"))
|
|
}
|
|
if foundNs {
|
|
t.Fatalf("found unwanted VAULT_NAMESPACE in:\n%s", strings.Join(act, "\n"))
|
|
}
|
|
}
|
|
|
|
{
|
|
act := env.SetVaultToken("123", "vault-namespace", true).Build().List()
|
|
exp := "VAULT_TOKEN=123"
|
|
expNs := "VAULT_NAMESPACE=vault-namespace"
|
|
found := false
|
|
foundNs := false
|
|
for _, entry := range act {
|
|
if entry == exp {
|
|
found = true
|
|
}
|
|
if entry == expNs {
|
|
foundNs = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("did not find %q in:\n%s", exp, strings.Join(act, "\n"))
|
|
}
|
|
if !foundNs {
|
|
t.Fatalf("did not find %q in:\n%s", expNs, strings.Join(act, "\n"))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEnvironment_Envvars(t *testing.T) {
|
|
envMap := map[string]string{"foo": "baz", "bar": "bang"}
|
|
n := mock.Node()
|
|
a := mock.Alloc()
|
|
task := a.Job.TaskGroups[0].Tasks[0]
|
|
task.Env = envMap
|
|
net := &drivers.DriverNetwork{PortMap: portMap}
|
|
act := NewBuilder(n, a, task, "global").SetDriverNetwork(net).Build().All()
|
|
for k, v := range envMap {
|
|
actV, ok := act[k]
|
|
if !ok {
|
|
t.Fatalf("missing %q in %#v", k, act)
|
|
}
|
|
if v != actV {
|
|
t.Fatalf("expected %s=%q but found %q", k, v, actV)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestEnvironment_HookVars asserts hook env vars are LWW and deletes of later
|
|
// writes allow earlier hook's values to be visible.
|
|
func TestEnvironment_HookVars(t *testing.T) {
|
|
n := mock.Node()
|
|
a := mock.Alloc()
|
|
builder := NewBuilder(n, a, a.Job.TaskGroups[0].Tasks[0], "global")
|
|
|
|
// Add vars from two hooks and assert the second one wins on
|
|
// conflicting keys.
|
|
builder.SetHookEnv("hookA", map[string]string{
|
|
"foo": "bar",
|
|
"baz": "quux",
|
|
})
|
|
builder.SetHookEnv("hookB", map[string]string{
|
|
"foo": "123",
|
|
"hookB": "wins",
|
|
})
|
|
|
|
{
|
|
out := builder.Build().All()
|
|
assert.Equal(t, "123", out["foo"])
|
|
assert.Equal(t, "quux", out["baz"])
|
|
assert.Equal(t, "wins", out["hookB"])
|
|
}
|
|
|
|
// Asserting overwriting hook vars allows the first hooks original
|
|
// value to be used.
|
|
builder.SetHookEnv("hookB", nil)
|
|
{
|
|
out := builder.Build().All()
|
|
assert.Equal(t, "bar", out["foo"])
|
|
assert.Equal(t, "quux", out["baz"])
|
|
assert.NotContains(t, out, "hookB")
|
|
}
|
|
}
|
|
|
|
// TestEnvironment_DeviceHookVars asserts device hook env vars are accessible
|
|
// separately.
|
|
func TestEnvironment_DeviceHookVars(t *testing.T) {
|
|
require := require.New(t)
|
|
n := mock.Node()
|
|
a := mock.Alloc()
|
|
builder := NewBuilder(n, a, a.Job.TaskGroups[0].Tasks[0], "global")
|
|
|
|
// Add vars from two hooks and assert the second one wins on
|
|
// conflicting keys.
|
|
builder.SetHookEnv("hookA", map[string]string{
|
|
"foo": "bar",
|
|
"baz": "quux",
|
|
})
|
|
builder.SetDeviceHookEnv("devices", map[string]string{
|
|
"hook": "wins",
|
|
})
|
|
|
|
b := builder.Build()
|
|
deviceEnv := b.DeviceEnv()
|
|
require.Len(deviceEnv, 1)
|
|
require.Contains(deviceEnv, "hook")
|
|
|
|
all := b.Map()
|
|
require.Contains(all, "foo")
|
|
}
|
|
|
|
func TestEnvironment_Interpolate(t *testing.T) {
|
|
n := mock.Node()
|
|
n.Attributes["arch"] = "x86"
|
|
n.NodeClass = "test class"
|
|
a := mock.Alloc()
|
|
task := a.Job.TaskGroups[0].Tasks[0]
|
|
task.Env = map[string]string{"test": "${node.class}", "test2": "${attr.arch}"}
|
|
env := NewBuilder(n, a, task, "global").Build()
|
|
|
|
exp := []string{fmt.Sprintf("test=%s", n.NodeClass), fmt.Sprintf("test2=%s", n.Attributes["arch"])}
|
|
found1, found2 := false, false
|
|
for _, entry := range env.List() {
|
|
switch entry {
|
|
case exp[0]:
|
|
found1 = true
|
|
case exp[1]:
|
|
found2 = true
|
|
}
|
|
}
|
|
if !found1 || !found2 {
|
|
t.Fatalf("expected to find %q and %q but got:\n%s",
|
|
exp[0], exp[1], strings.Join(env.List(), "\n"))
|
|
}
|
|
}
|
|
|
|
func TestEnvironment_AppendHostEnvvars(t *testing.T) {
|
|
host := os.Environ()
|
|
if len(host) < 2 {
|
|
t.Skip("No host environment variables. Can't test")
|
|
}
|
|
skip := strings.Split(host[0], "=")[0]
|
|
env := testEnvBuilder().
|
|
SetHostEnvvars([]string{skip}).
|
|
Build()
|
|
|
|
act := env.Map()
|
|
if len(act) < 1 {
|
|
t.Fatalf("Host environment variables not properly set")
|
|
}
|
|
if _, ok := act[skip]; ok {
|
|
t.Fatalf("Didn't filter environment variable %q", skip)
|
|
}
|
|
}
|
|
|
|
// TestEnvironment_DashesInTaskName asserts dashes in port labels are properly
|
|
// converted to underscores in environment variables.
|
|
// See: https://github.com/hashicorp/nomad/issues/2405
|
|
func TestEnvironment_DashesInTaskName(t *testing.T) {
|
|
a := mock.Alloc()
|
|
task := a.Job.TaskGroups[0].Tasks[0]
|
|
task.Env = map[string]string{
|
|
"test-one-two": "three-four",
|
|
"NOMAD_test_one_two": "three-five",
|
|
}
|
|
envMap := NewBuilder(mock.Node(), a, task, "global").Build().Map()
|
|
|
|
if envMap["test-one-two"] != "three-four" {
|
|
t.Fatalf("Expected test-one-two=three-four in TaskEnv; found:\n%#v", envMap)
|
|
}
|
|
if envMap["NOMAD_test_one_two"] != "three-five" {
|
|
t.Fatalf("Expected NOMAD_test_one_two=three-five in TaskEnv; found:\n%#v", envMap)
|
|
}
|
|
}
|
|
|
|
// TestEnvironment_UpdateTask asserts env vars and task meta are updated when a
|
|
// task is updated.
|
|
func TestEnvironment_UpdateTask(t *testing.T) {
|
|
a := mock.Alloc()
|
|
a.Job.TaskGroups[0].Meta = map[string]string{"tgmeta": "tgmetaval"}
|
|
task := a.Job.TaskGroups[0].Tasks[0]
|
|
task.Name = "orig"
|
|
task.Env = map[string]string{"env": "envval"}
|
|
task.Meta = map[string]string{"taskmeta": "taskmetaval"}
|
|
builder := NewBuilder(mock.Node(), a, task, "global")
|
|
|
|
origMap := builder.Build().Map()
|
|
if origMap["NOMAD_TASK_NAME"] != "orig" {
|
|
t.Errorf("Expected NOMAD_TASK_NAME=orig but found %q", origMap["NOMAD_TASK_NAME"])
|
|
}
|
|
if origMap["NOMAD_META_taskmeta"] != "taskmetaval" {
|
|
t.Errorf("Expected NOMAD_META_taskmeta=taskmetaval but found %q", origMap["NOMAD_META_taskmeta"])
|
|
}
|
|
if origMap["env"] != "envval" {
|
|
t.Errorf("Expected env=envva but found %q", origMap["env"])
|
|
}
|
|
if origMap["NOMAD_META_tgmeta"] != "tgmetaval" {
|
|
t.Errorf("Expected NOMAD_META_tgmeta=tgmetaval but found %q", origMap["NOMAD_META_tgmeta"])
|
|
}
|
|
|
|
a.Job.TaskGroups[0].Meta = map[string]string{"tgmeta2": "tgmetaval2"}
|
|
task.Name = "new"
|
|
task.Env = map[string]string{"env2": "envval2"}
|
|
task.Meta = map[string]string{"taskmeta2": "taskmetaval2"}
|
|
|
|
newMap := builder.UpdateTask(a, task).Build().Map()
|
|
if newMap["NOMAD_TASK_NAME"] != "new" {
|
|
t.Errorf("Expected NOMAD_TASK_NAME=new but found %q", newMap["NOMAD_TASK_NAME"])
|
|
}
|
|
if newMap["NOMAD_META_taskmeta2"] != "taskmetaval2" {
|
|
t.Errorf("Expected NOMAD_META_taskmeta=taskmetaval but found %q", newMap["NOMAD_META_taskmeta2"])
|
|
}
|
|
if newMap["env2"] != "envval2" {
|
|
t.Errorf("Expected env=envva but found %q", newMap["env2"])
|
|
}
|
|
if newMap["NOMAD_META_tgmeta2"] != "tgmetaval2" {
|
|
t.Errorf("Expected NOMAD_META_tgmeta=tgmetaval but found %q", newMap["NOMAD_META_tgmeta2"])
|
|
}
|
|
if v, ok := newMap["NOMAD_META_taskmeta"]; ok {
|
|
t.Errorf("Expected NOMAD_META_taskmeta to be unset but found: %q", v)
|
|
}
|
|
}
|
|
|
|
// TestEnvironment_InterpolateEmptyOptionalMeta asserts that in a parameterized
|
|
// job, if an optional meta field is not set, it will get interpolated as an
|
|
// empty string.
|
|
func TestEnvironment_InterpolateEmptyOptionalMeta(t *testing.T) {
|
|
require := require.New(t)
|
|
a := mock.Alloc()
|
|
a.Job.ParameterizedJob = &structs.ParameterizedJobConfig{
|
|
MetaOptional: []string{"metaopt1", "metaopt2"},
|
|
}
|
|
a.Job.Dispatched = true
|
|
task := a.Job.TaskGroups[0].Tasks[0]
|
|
task.Meta = map[string]string{"metaopt1": "metaopt1val"}
|
|
env := NewBuilder(mock.Node(), a, task, "global").Build()
|
|
require.Equal("metaopt1val", env.ReplaceEnv("${NOMAD_META_metaopt1}"))
|
|
require.Empty(env.ReplaceEnv("${NOMAD_META_metaopt2}"))
|
|
}
|
|
|
|
// TestEnvironment_Upsteams asserts that group.service.upstreams entries are
|
|
// added to the environment.
|
|
func TestEnvironment_Upstreams(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Add some upstreams to the mock alloc
|
|
a := mock.Alloc()
|
|
tg := a.Job.LookupTaskGroup(a.TaskGroup)
|
|
tg.Services = []*structs.Service{
|
|
// Services without Connect should be ignored
|
|
{
|
|
Name: "ignoreme",
|
|
},
|
|
// All upstreams from a service should be added
|
|
{
|
|
Name: "remote_service",
|
|
Connect: &structs.ConsulConnect{
|
|
SidecarService: &structs.ConsulSidecarService{
|
|
Proxy: &structs.ConsulProxy{
|
|
Upstreams: []structs.ConsulUpstream{
|
|
{
|
|
DestinationName: "foo-bar",
|
|
LocalBindPort: 1234,
|
|
},
|
|
{
|
|
DestinationName: "bar",
|
|
LocalBindPort: 5678,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Ensure the upstreams can be interpolated
|
|
tg.Tasks[0].Env = map[string]string{
|
|
"foo": "${NOMAD_UPSTREAM_ADDR_foo_bar}",
|
|
"bar": "${NOMAD_UPSTREAM_PORT_foo-bar}",
|
|
}
|
|
|
|
env := NewBuilder(mock.Node(), a, tg.Tasks[0], "global").Build().Map()
|
|
require.Equal(t, "127.0.0.1:1234", env["NOMAD_UPSTREAM_ADDR_foo_bar"])
|
|
require.Equal(t, "127.0.0.1", env["NOMAD_UPSTREAM_IP_foo_bar"])
|
|
require.Equal(t, "1234", env["NOMAD_UPSTREAM_PORT_foo_bar"])
|
|
require.Equal(t, "127.0.0.1:5678", env["NOMAD_UPSTREAM_ADDR_bar"])
|
|
require.Equal(t, "127.0.0.1", env["NOMAD_UPSTREAM_IP_bar"])
|
|
require.Equal(t, "5678", env["NOMAD_UPSTREAM_PORT_bar"])
|
|
require.Equal(t, "127.0.0.1:1234", env["foo"])
|
|
require.Equal(t, "1234", env["bar"])
|
|
}
|
|
|
|
func TestEnvironment_SetPortMapEnvs(t *testing.T) {
|
|
envs := map[string]string{
|
|
"foo": "bar",
|
|
"NOMAD_PORT_ssh": "2342",
|
|
}
|
|
ports := map[string]int{
|
|
"ssh": 22,
|
|
"http": 80,
|
|
}
|
|
|
|
envs = SetPortMapEnvs(envs, ports)
|
|
|
|
expected := map[string]string{
|
|
"foo": "bar",
|
|
"NOMAD_PORT_ssh": "22",
|
|
"NOMAD_PORT_http": "80",
|
|
}
|
|
require.Equal(t, expected, envs)
|
|
}
|
|
|
|
func TestEnvironment_TasklessBuilder(t *testing.T) {
|
|
node := mock.Node()
|
|
alloc := mock.Alloc()
|
|
alloc.Job.Meta["jobt"] = "foo"
|
|
alloc.Job.TaskGroups[0].Meta["groupt"] = "bar"
|
|
require := require.New(t)
|
|
var taskEnv *TaskEnv
|
|
require.NotPanics(func() {
|
|
taskEnv = NewBuilder(node, alloc, nil, "global").SetAllocDir("/tmp/alloc").Build()
|
|
})
|
|
|
|
require.Equal("foo", taskEnv.ReplaceEnv("${NOMAD_META_jobt}"))
|
|
require.Equal("bar", taskEnv.ReplaceEnv("${NOMAD_META_groupt}"))
|
|
}
|
|
|
|
func TestTaskEnv_ClientPath(t *testing.T) {
|
|
builder := testEnvBuilder()
|
|
builder.SetAllocDir("/tmp/testAlloc")
|
|
builder.SetClientSharedAllocDir("/tmp/testAlloc/alloc")
|
|
builder.SetClientTaskRoot("/tmp/testAlloc/testTask")
|
|
builder.SetClientTaskLocalDir("/tmp/testAlloc/testTask/local")
|
|
builder.SetClientTaskSecretsDir("/tmp/testAlloc/testTask/secrets")
|
|
env := builder.Build()
|
|
|
|
testCases := []struct {
|
|
label string
|
|
input string
|
|
joinOnEscape bool
|
|
escapes bool
|
|
expected string
|
|
}{
|
|
{
|
|
// this is useful behavior for exec-based tasks, allowing template or artifact
|
|
// destination anywhere in the chroot
|
|
label: "join on escape if requested",
|
|
input: "/tmp",
|
|
joinOnEscape: true,
|
|
expected: "/tmp/testAlloc/testTask/tmp",
|
|
escapes: false,
|
|
},
|
|
{
|
|
// template source behavior does not perform unconditional join
|
|
label: "do not join on escape unless requested",
|
|
input: "/tmp",
|
|
joinOnEscape: false,
|
|
expected: "/tmp",
|
|
escapes: true,
|
|
},
|
|
{
|
|
// relative paths are always joined to the task root dir
|
|
// escape from task root dir and shared alloc dir should be detected
|
|
label: "detect escape for relative paths",
|
|
input: "..",
|
|
joinOnEscape: true,
|
|
expected: "/tmp/testAlloc",
|
|
escapes: true,
|
|
},
|
|
{
|
|
// shared alloc dir should be available from ../alloc, for historical reasons
|
|
// this is not an escape
|
|
label: "relative access to shared alloc dir",
|
|
input: "../alloc/somefile",
|
|
joinOnEscape: true,
|
|
expected: "/tmp/testAlloc/alloc/somefile",
|
|
escapes: false,
|
|
},
|
|
{
|
|
label: "interpolate shared alloc dir",
|
|
input: "${NOMAD_ALLOC_DIR}/somefile",
|
|
joinOnEscape: false,
|
|
expected: "/tmp/testAlloc/alloc/somefile",
|
|
escapes: false,
|
|
},
|
|
{
|
|
label: "interpolate task local dir",
|
|
input: "${NOMAD_TASK_DIR}/somefile",
|
|
joinOnEscape: false,
|
|
expected: "/tmp/testAlloc/testTask/local/somefile",
|
|
escapes: false,
|
|
},
|
|
{
|
|
label: "interpolate task secrts dir",
|
|
input: "${NOMAD_SECRETS_DIR}/somefile",
|
|
joinOnEscape: false,
|
|
expected: "/tmp/testAlloc/testTask/secrets/somefile",
|
|
escapes: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.label, func(t *testing.T) {
|
|
path, escapes := env.ClientPath(tc.input, tc.joinOnEscape)
|
|
assert.Equal(t, tc.escapes, escapes, "escape check")
|
|
assert.Equal(t, tc.expected, path, "interpolated path")
|
|
})
|
|
}
|
|
}
|