github.com/smithx10/nomad@v0.9.1-rc1/client/taskenv/env_test.go (about)

     1  package taskenv
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"reflect"
     7  	"sort"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/hashicorp/hcl2/gohcl"
    12  	"github.com/hashicorp/hcl2/hcl"
    13  	"github.com/hashicorp/hcl2/hcl/hclsyntax"
    14  	"github.com/hashicorp/nomad/nomad/mock"
    15  	"github.com/hashicorp/nomad/nomad/structs"
    16  	"github.com/hashicorp/nomad/plugins/drivers"
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  const (
    22  	// Node values that tests can rely on
    23  	metaKey   = "instance"
    24  	metaVal   = "t2-micro"
    25  	attrKey   = "arch"
    26  	attrVal   = "amd64"
    27  	nodeName  = "test node"
    28  	nodeClass = "test class"
    29  
    30  	// Environment variable values that tests can rely on
    31  	envOneKey = "NOMAD_IP"
    32  	envOneVal = "127.0.0.1"
    33  	envTwoKey = "NOMAD_PORT_WEB"
    34  	envTwoVal = ":80"
    35  )
    36  
    37  var (
    38  	// portMap for use in tests as its set after Builder creation
    39  	portMap = map[string]int{
    40  		"https": 443,
    41  	}
    42  )
    43  
    44  func testEnvBuilder() *Builder {
    45  	n := mock.Node()
    46  	n.Attributes = map[string]string{
    47  		attrKey: attrVal,
    48  	}
    49  	n.Meta = map[string]string{
    50  		metaKey: metaVal,
    51  	}
    52  	n.Name = nodeName
    53  	n.NodeClass = nodeClass
    54  
    55  	task := mock.Job().TaskGroups[0].Tasks[0]
    56  	task.Env = map[string]string{
    57  		envOneKey: envOneVal,
    58  		envTwoKey: envTwoVal,
    59  	}
    60  	return NewBuilder(n, mock.Alloc(), task, "global")
    61  }
    62  
    63  func TestEnvironment_ParseAndReplace_Env(t *testing.T) {
    64  	env := testEnvBuilder()
    65  
    66  	input := []string{fmt.Sprintf(`"${%v}"!`, envOneKey), fmt.Sprintf("${%s}${%s}", envOneKey, envTwoKey)}
    67  	act := env.Build().ParseAndReplace(input)
    68  	exp := []string{fmt.Sprintf(`"%s"!`, envOneVal), fmt.Sprintf("%s%s", envOneVal, envTwoVal)}
    69  
    70  	if !reflect.DeepEqual(act, exp) {
    71  		t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
    72  	}
    73  }
    74  
    75  func TestEnvironment_ParseAndReplace_Meta(t *testing.T) {
    76  	input := []string{fmt.Sprintf("${%v%v}", nodeMetaPrefix, metaKey)}
    77  	exp := []string{metaVal}
    78  	env := testEnvBuilder()
    79  	act := env.Build().ParseAndReplace(input)
    80  
    81  	if !reflect.DeepEqual(act, exp) {
    82  		t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
    83  	}
    84  }
    85  
    86  func TestEnvironment_ParseAndReplace_Attr(t *testing.T) {
    87  	input := []string{fmt.Sprintf("${%v%v}", nodeAttributePrefix, attrKey)}
    88  	exp := []string{attrVal}
    89  	env := testEnvBuilder()
    90  	act := env.Build().ParseAndReplace(input)
    91  
    92  	if !reflect.DeepEqual(act, exp) {
    93  		t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
    94  	}
    95  }
    96  
    97  func TestEnvironment_ParseAndReplace_Node(t *testing.T) {
    98  	input := []string{fmt.Sprintf("${%v}", nodeNameKey), fmt.Sprintf("${%v}", nodeClassKey)}
    99  	exp := []string{nodeName, nodeClass}
   100  	env := testEnvBuilder()
   101  	act := env.Build().ParseAndReplace(input)
   102  
   103  	if !reflect.DeepEqual(act, exp) {
   104  		t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
   105  	}
   106  }
   107  
   108  func TestEnvironment_ParseAndReplace_Mixed(t *testing.T) {
   109  	input := []string{
   110  		fmt.Sprintf("${%v}${%v%v}", nodeNameKey, nodeAttributePrefix, attrKey),
   111  		fmt.Sprintf("${%v}${%v%v}", nodeClassKey, nodeMetaPrefix, metaKey),
   112  		fmt.Sprintf("${%v}${%v}", envTwoKey, nodeClassKey),
   113  	}
   114  	exp := []string{
   115  		fmt.Sprintf("%v%v", nodeName, attrVal),
   116  		fmt.Sprintf("%v%v", nodeClass, metaVal),
   117  		fmt.Sprintf("%v%v", envTwoVal, nodeClass),
   118  	}
   119  	env := testEnvBuilder()
   120  	act := env.Build().ParseAndReplace(input)
   121  
   122  	if !reflect.DeepEqual(act, exp) {
   123  		t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
   124  	}
   125  }
   126  
   127  func TestEnvironment_ReplaceEnv_Mixed(t *testing.T) {
   128  	input := fmt.Sprintf("${%v}${%v%v}", nodeNameKey, nodeAttributePrefix, attrKey)
   129  	exp := fmt.Sprintf("%v%v", nodeName, attrVal)
   130  	env := testEnvBuilder()
   131  	act := env.Build().ReplaceEnv(input)
   132  
   133  	if act != exp {
   134  		t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
   135  	}
   136  }
   137  
   138  func TestEnvironment_AsList(t *testing.T) {
   139  	n := mock.Node()
   140  	n.Meta = map[string]string{
   141  		"metaKey": "metaVal",
   142  	}
   143  	a := mock.Alloc()
   144  	a.AllocatedResources.Tasks["web"].Networks[0] = &structs.NetworkResource{
   145  		Device:        "eth0",
   146  		IP:            "127.0.0.1",
   147  		ReservedPorts: []structs.Port{{Label: "https", Value: 8080}},
   148  		MBits:         50,
   149  		DynamicPorts:  []structs.Port{{Label: "http", Value: 80}},
   150  	}
   151  	a.AllocatedResources.Tasks["ssh"] = &structs.AllocatedTaskResources{
   152  		Networks: []*structs.NetworkResource{
   153  			{
   154  				Device: "eth0",
   155  				IP:     "192.168.0.100",
   156  				MBits:  50,
   157  				ReservedPorts: []structs.Port{
   158  					{Label: "ssh", Value: 22},
   159  					{Label: "other", Value: 1234},
   160  				},
   161  			},
   162  		},
   163  	}
   164  	task := a.Job.TaskGroups[0].Tasks[0]
   165  	task.Env = map[string]string{
   166  		"taskEnvKey": "taskEnvVal",
   167  	}
   168  	env := NewBuilder(n, a, task, "global").SetDriverNetwork(
   169  		&drivers.DriverNetwork{PortMap: map[string]int{"https": 443}},
   170  	)
   171  
   172  	act := env.Build().List()
   173  	exp := []string{
   174  		"taskEnvKey=taskEnvVal",
   175  		"NOMAD_ADDR_http=127.0.0.1:80",
   176  		"NOMAD_PORT_http=80",
   177  		"NOMAD_IP_http=127.0.0.1",
   178  		"NOMAD_ADDR_https=127.0.0.1:8080",
   179  		"NOMAD_PORT_https=443",
   180  		"NOMAD_IP_https=127.0.0.1",
   181  		"NOMAD_HOST_PORT_http=80",
   182  		"NOMAD_HOST_PORT_https=8080",
   183  		"NOMAD_TASK_NAME=web",
   184  		"NOMAD_GROUP_NAME=web",
   185  		"NOMAD_ADDR_ssh_other=192.168.0.100:1234",
   186  		"NOMAD_ADDR_ssh_ssh=192.168.0.100:22",
   187  		"NOMAD_IP_ssh_other=192.168.0.100",
   188  		"NOMAD_IP_ssh_ssh=192.168.0.100",
   189  		"NOMAD_PORT_ssh_other=1234",
   190  		"NOMAD_PORT_ssh_ssh=22",
   191  		"NOMAD_CPU_LIMIT=500",
   192  		"NOMAD_DC=dc1",
   193  		"NOMAD_REGION=global",
   194  		"NOMAD_MEMORY_LIMIT=256",
   195  		"NOMAD_META_ELB_CHECK_INTERVAL=30s",
   196  		"NOMAD_META_ELB_CHECK_MIN=3",
   197  		"NOMAD_META_ELB_CHECK_TYPE=http",
   198  		"NOMAD_META_FOO=bar",
   199  		"NOMAD_META_OWNER=armon",
   200  		"NOMAD_META_elb_check_interval=30s",
   201  		"NOMAD_META_elb_check_min=3",
   202  		"NOMAD_META_elb_check_type=http",
   203  		"NOMAD_META_foo=bar",
   204  		"NOMAD_META_owner=armon",
   205  		"NOMAD_JOB_NAME=my-job",
   206  		fmt.Sprintf("NOMAD_ALLOC_ID=%s", a.ID),
   207  		"NOMAD_ALLOC_INDEX=0",
   208  	}
   209  	sort.Strings(act)
   210  	sort.Strings(exp)
   211  	require.Equal(t, exp, act)
   212  }
   213  
   214  // COMPAT(0.11): Remove in 0.11
   215  func TestEnvironment_AsList_Old(t *testing.T) {
   216  	n := mock.Node()
   217  	n.Meta = map[string]string{
   218  		"metaKey": "metaVal",
   219  	}
   220  	a := mock.Alloc()
   221  	a.AllocatedResources = nil
   222  	a.Resources = &structs.Resources{
   223  		CPU:      500,
   224  		MemoryMB: 256,
   225  		DiskMB:   150,
   226  		Networks: []*structs.NetworkResource{
   227  			{
   228  				Device: "eth0",
   229  				IP:     "192.168.0.100",
   230  				ReservedPorts: []structs.Port{
   231  					{Label: "admin", Value: 5000},
   232  					{Label: "ssh", Value: 22},
   233  					{Label: "other", Value: 1234},
   234  				},
   235  				MBits:        50,
   236  				DynamicPorts: []structs.Port{{Label: "http"}},
   237  			},
   238  		},
   239  	}
   240  	a.TaskResources = map[string]*structs.Resources{
   241  		"web": {
   242  			CPU:      500,
   243  			MemoryMB: 256,
   244  			Networks: []*structs.NetworkResource{
   245  				{
   246  					Device:        "eth0",
   247  					IP:            "192.168.0.100",
   248  					ReservedPorts: []structs.Port{{Label: "admin", Value: 5000}},
   249  					MBits:         50,
   250  					DynamicPorts:  []structs.Port{{Label: "http", Value: 2000}},
   251  				},
   252  			},
   253  		},
   254  	}
   255  	a.TaskResources["ssh"] = &structs.Resources{
   256  		Networks: []*structs.NetworkResource{
   257  			{
   258  				Device: "eth0",
   259  				IP:     "192.168.0.100",
   260  				MBits:  50,
   261  				ReservedPorts: []structs.Port{
   262  					{Label: "ssh", Value: 22},
   263  					{Label: "other", Value: 1234},
   264  				},
   265  			},
   266  		},
   267  	}
   268  	task := a.Job.TaskGroups[0].Tasks[0]
   269  	task.Env = map[string]string{
   270  		"taskEnvKey": "taskEnvVal",
   271  	}
   272  	task.Resources.Networks = []*structs.NetworkResource{
   273  		{
   274  			IP:            "127.0.0.1",
   275  			ReservedPorts: []structs.Port{{Label: "http", Value: 80}},
   276  			DynamicPorts:  []structs.Port{{Label: "https", Value: 8080}},
   277  		},
   278  	}
   279  	env := NewBuilder(n, a, task, "global").SetDriverNetwork(
   280  		&drivers.DriverNetwork{PortMap: map[string]int{"https": 443}},
   281  	)
   282  
   283  	act := env.Build().List()
   284  	exp := []string{
   285  		"taskEnvKey=taskEnvVal",
   286  		"NOMAD_ADDR_http=127.0.0.1:80",
   287  		"NOMAD_PORT_http=80",
   288  		"NOMAD_IP_http=127.0.0.1",
   289  		"NOMAD_ADDR_https=127.0.0.1:8080",
   290  		"NOMAD_PORT_https=443",
   291  		"NOMAD_IP_https=127.0.0.1",
   292  		"NOMAD_HOST_PORT_http=80",
   293  		"NOMAD_HOST_PORT_https=8080",
   294  		"NOMAD_TASK_NAME=web",
   295  		"NOMAD_GROUP_NAME=web",
   296  		"NOMAD_ADDR_ssh_other=192.168.0.100:1234",
   297  		"NOMAD_ADDR_ssh_ssh=192.168.0.100:22",
   298  		"NOMAD_IP_ssh_other=192.168.0.100",
   299  		"NOMAD_IP_ssh_ssh=192.168.0.100",
   300  		"NOMAD_PORT_ssh_other=1234",
   301  		"NOMAD_PORT_ssh_ssh=22",
   302  		"NOMAD_CPU_LIMIT=500",
   303  		"NOMAD_DC=dc1",
   304  		"NOMAD_REGION=global",
   305  		"NOMAD_MEMORY_LIMIT=256",
   306  		"NOMAD_META_ELB_CHECK_INTERVAL=30s",
   307  		"NOMAD_META_ELB_CHECK_MIN=3",
   308  		"NOMAD_META_ELB_CHECK_TYPE=http",
   309  		"NOMAD_META_FOO=bar",
   310  		"NOMAD_META_OWNER=armon",
   311  		"NOMAD_META_elb_check_interval=30s",
   312  		"NOMAD_META_elb_check_min=3",
   313  		"NOMAD_META_elb_check_type=http",
   314  		"NOMAD_META_foo=bar",
   315  		"NOMAD_META_owner=armon",
   316  		"NOMAD_JOB_NAME=my-job",
   317  		fmt.Sprintf("NOMAD_ALLOC_ID=%s", a.ID),
   318  		"NOMAD_ALLOC_INDEX=0",
   319  	}
   320  	sort.Strings(act)
   321  	sort.Strings(exp)
   322  	require.Equal(t, exp, act)
   323  }
   324  
   325  func TestEnvironment_AllValues(t *testing.T) {
   326  	t.Parallel()
   327  
   328  	n := mock.Node()
   329  	n.Meta = map[string]string{
   330  		"metaKey":           "metaVal",
   331  		"nested.meta.key":   "a",
   332  		"invalid...metakey": "b",
   333  	}
   334  	a := mock.Alloc()
   335  	a.AllocatedResources.Tasks["web"].Networks[0] = &structs.NetworkResource{
   336  		Device:        "eth0",
   337  		IP:            "127.0.0.1",
   338  		ReservedPorts: []structs.Port{{Label: "https", Value: 8080}},
   339  		MBits:         50,
   340  		DynamicPorts:  []structs.Port{{Label: "http", Value: 80}},
   341  	}
   342  	a.AllocatedResources.Tasks["ssh"] = &structs.AllocatedTaskResources{
   343  		Networks: []*structs.NetworkResource{
   344  			{
   345  				Device: "eth0",
   346  				IP:     "192.168.0.100",
   347  				MBits:  50,
   348  				ReservedPorts: []structs.Port{
   349  					{Label: "ssh", Value: 22},
   350  					{Label: "other", Value: 1234},
   351  				},
   352  			},
   353  		},
   354  	}
   355  	task := a.Job.TaskGroups[0].Tasks[0]
   356  	task.Env = map[string]string{
   357  		"taskEnvKey":        "taskEnvVal",
   358  		"nested.task.key":   "x",
   359  		"invalid...taskkey": "y",
   360  		".a":                "a",
   361  		"b.":                "b",
   362  		".":                 "c",
   363  	}
   364  	env := NewBuilder(n, a, task, "global").SetDriverNetwork(
   365  		&drivers.DriverNetwork{PortMap: map[string]int{"https": 443}},
   366  	)
   367  
   368  	values, errs, err := env.Build().AllValues()
   369  	require.NoError(t, err)
   370  
   371  	// Assert the keys we couldn't nest were reported
   372  	require.Len(t, errs, 5)
   373  	require.Contains(t, errs, "invalid...taskkey")
   374  	require.Contains(t, errs, "meta.invalid...metakey")
   375  	require.Contains(t, errs, ".a")
   376  	require.Contains(t, errs, "b.")
   377  	require.Contains(t, errs, ".")
   378  
   379  	exp := map[string]string{
   380  		// Node
   381  		"node.unique.id":          n.ID,
   382  		"node.region":             "global",
   383  		"node.datacenter":         n.Datacenter,
   384  		"node.unique.name":        n.Name,
   385  		"node.class":              n.NodeClass,
   386  		"meta.metaKey":            "metaVal",
   387  		"attr.arch":               "x86",
   388  		"attr.driver.exec":        "1",
   389  		"attr.driver.mock_driver": "1",
   390  		"attr.kernel.name":        "linux",
   391  		"attr.nomad.version":      "0.5.0",
   392  
   393  		// 0.9 style meta and attr
   394  		"node.meta.metaKey":            "metaVal",
   395  		"node.attr.arch":               "x86",
   396  		"node.attr.driver.exec":        "1",
   397  		"node.attr.driver.mock_driver": "1",
   398  		"node.attr.kernel.name":        "linux",
   399  		"node.attr.nomad.version":      "0.5.0",
   400  
   401  		// Env
   402  		"taskEnvKey":                    "taskEnvVal",
   403  		"NOMAD_ADDR_http":               "127.0.0.1:80",
   404  		"NOMAD_PORT_http":               "80",
   405  		"NOMAD_IP_http":                 "127.0.0.1",
   406  		"NOMAD_ADDR_https":              "127.0.0.1:8080",
   407  		"NOMAD_PORT_https":              "443",
   408  		"NOMAD_IP_https":                "127.0.0.1",
   409  		"NOMAD_HOST_PORT_http":          "80",
   410  		"NOMAD_HOST_PORT_https":         "8080",
   411  		"NOMAD_TASK_NAME":               "web",
   412  		"NOMAD_GROUP_NAME":              "web",
   413  		"NOMAD_ADDR_ssh_other":          "192.168.0.100:1234",
   414  		"NOMAD_ADDR_ssh_ssh":            "192.168.0.100:22",
   415  		"NOMAD_IP_ssh_other":            "192.168.0.100",
   416  		"NOMAD_IP_ssh_ssh":              "192.168.0.100",
   417  		"NOMAD_PORT_ssh_other":          "1234",
   418  		"NOMAD_PORT_ssh_ssh":            "22",
   419  		"NOMAD_CPU_LIMIT":               "500",
   420  		"NOMAD_DC":                      "dc1",
   421  		"NOMAD_REGION":                  "global",
   422  		"NOMAD_MEMORY_LIMIT":            "256",
   423  		"NOMAD_META_ELB_CHECK_INTERVAL": "30s",
   424  		"NOMAD_META_ELB_CHECK_MIN":      "3",
   425  		"NOMAD_META_ELB_CHECK_TYPE":     "http",
   426  		"NOMAD_META_FOO":                "bar",
   427  		"NOMAD_META_OWNER":              "armon",
   428  		"NOMAD_META_elb_check_interval": "30s",
   429  		"NOMAD_META_elb_check_min":      "3",
   430  		"NOMAD_META_elb_check_type":     "http",
   431  		"NOMAD_META_foo":                "bar",
   432  		"NOMAD_META_owner":              "armon",
   433  		"NOMAD_JOB_NAME":                "my-job",
   434  		"NOMAD_ALLOC_ID":                a.ID,
   435  		"NOMAD_ALLOC_INDEX":             "0",
   436  
   437  		// 0.9 style env map
   438  		`env["taskEnvKey"]`:        "taskEnvVal",
   439  		`env["NOMAD_ADDR_http"]`:   "127.0.0.1:80",
   440  		`env["nested.task.key"]`:   "x",
   441  		`env["invalid...taskkey"]`: "y",
   442  		`env[".a"]`:                "a",
   443  		`env["b."]`:                "b",
   444  		`env["."]`:                 "c",
   445  	}
   446  
   447  	evalCtx := &hcl.EvalContext{
   448  		Variables: values,
   449  	}
   450  
   451  	for k, expectedVal := range exp {
   452  		t.Run(k, func(t *testing.T) {
   453  			// Parse HCL containing the test key
   454  			hclStr := fmt.Sprintf(`"${%s}"`, k)
   455  			expr, diag := hclsyntax.ParseExpression([]byte(hclStr), "test.hcl", hcl.Pos{})
   456  			require.Empty(t, diag)
   457  
   458  			// Decode with the TaskEnv values
   459  			out := ""
   460  			diag = gohcl.DecodeExpression(expr, evalCtx, &out)
   461  			require.Empty(t, diag)
   462  			require.Equal(t, out, expectedVal)
   463  		})
   464  	}
   465  }
   466  
   467  func TestEnvironment_VaultToken(t *testing.T) {
   468  	n := mock.Node()
   469  	a := mock.Alloc()
   470  	env := NewBuilder(n, a, a.Job.TaskGroups[0].Tasks[0], "global")
   471  	env.SetVaultToken("123", false)
   472  
   473  	{
   474  		act := env.Build().All()
   475  		if act[VaultToken] != "" {
   476  			t.Fatalf("Unexpected environment variables: %s=%q", VaultToken, act[VaultToken])
   477  		}
   478  	}
   479  
   480  	{
   481  		act := env.SetVaultToken("123", true).Build().List()
   482  		exp := "VAULT_TOKEN=123"
   483  		found := false
   484  		for _, entry := range act {
   485  			if entry == exp {
   486  				found = true
   487  				break
   488  			}
   489  		}
   490  		if !found {
   491  			t.Fatalf("did not find %q in:\n%s", exp, strings.Join(act, "\n"))
   492  		}
   493  	}
   494  }
   495  
   496  func TestEnvironment_Envvars(t *testing.T) {
   497  	envMap := map[string]string{"foo": "baz", "bar": "bang"}
   498  	n := mock.Node()
   499  	a := mock.Alloc()
   500  	task := a.Job.TaskGroups[0].Tasks[0]
   501  	task.Env = envMap
   502  	net := &drivers.DriverNetwork{PortMap: portMap}
   503  	act := NewBuilder(n, a, task, "global").SetDriverNetwork(net).Build().All()
   504  	for k, v := range envMap {
   505  		actV, ok := act[k]
   506  		if !ok {
   507  			t.Fatalf("missing %q in %#v", k, act)
   508  		}
   509  		if v != actV {
   510  			t.Fatalf("expected %s=%q but found %q", k, v, actV)
   511  		}
   512  	}
   513  }
   514  
   515  // TestEnvironment_HookVars asserts hook env vars are LWW and deletes of later
   516  // writes allow earlier hook's values to be visible.
   517  func TestEnvironment_HookVars(t *testing.T) {
   518  	n := mock.Node()
   519  	a := mock.Alloc()
   520  	builder := NewBuilder(n, a, a.Job.TaskGroups[0].Tasks[0], "global")
   521  
   522  	// Add vars from two hooks and assert the second one wins on
   523  	// conflicting keys.
   524  	builder.SetHookEnv("hookA", map[string]string{
   525  		"foo": "bar",
   526  		"baz": "quux",
   527  	})
   528  	builder.SetHookEnv("hookB", map[string]string{
   529  		"foo":   "123",
   530  		"hookB": "wins",
   531  	})
   532  
   533  	{
   534  		out := builder.Build().All()
   535  		assert.Equal(t, "123", out["foo"])
   536  		assert.Equal(t, "quux", out["baz"])
   537  		assert.Equal(t, "wins", out["hookB"])
   538  	}
   539  
   540  	// Asserting overwriting hook vars allows the first hooks original
   541  	// value to be used.
   542  	builder.SetHookEnv("hookB", nil)
   543  	{
   544  		out := builder.Build().All()
   545  		assert.Equal(t, "bar", out["foo"])
   546  		assert.Equal(t, "quux", out["baz"])
   547  		assert.NotContains(t, out, "hookB")
   548  	}
   549  }
   550  
   551  // TestEnvironment_DeviceHookVars asserts device hook env vars are accessible
   552  // separately.
   553  func TestEnvironment_DeviceHookVars(t *testing.T) {
   554  	require := require.New(t)
   555  	n := mock.Node()
   556  	a := mock.Alloc()
   557  	builder := NewBuilder(n, a, a.Job.TaskGroups[0].Tasks[0], "global")
   558  
   559  	// Add vars from two hooks and assert the second one wins on
   560  	// conflicting keys.
   561  	builder.SetHookEnv("hookA", map[string]string{
   562  		"foo": "bar",
   563  		"baz": "quux",
   564  	})
   565  	builder.SetDeviceHookEnv("devices", map[string]string{
   566  		"hook": "wins",
   567  	})
   568  
   569  	b := builder.Build()
   570  	deviceEnv := b.DeviceEnv()
   571  	require.Len(deviceEnv, 1)
   572  	require.Contains(deviceEnv, "hook")
   573  
   574  	all := b.Map()
   575  	require.Contains(all, "foo")
   576  }
   577  
   578  func TestEnvironment_Interpolate(t *testing.T) {
   579  	n := mock.Node()
   580  	n.Attributes["arch"] = "x86"
   581  	n.NodeClass = "test class"
   582  	a := mock.Alloc()
   583  	task := a.Job.TaskGroups[0].Tasks[0]
   584  	task.Env = map[string]string{"test": "${node.class}", "test2": "${attr.arch}"}
   585  	env := NewBuilder(n, a, task, "global").Build()
   586  
   587  	exp := []string{fmt.Sprintf("test=%s", n.NodeClass), fmt.Sprintf("test2=%s", n.Attributes["arch"])}
   588  	found1, found2 := false, false
   589  	for _, entry := range env.List() {
   590  		switch entry {
   591  		case exp[0]:
   592  			found1 = true
   593  		case exp[1]:
   594  			found2 = true
   595  		}
   596  	}
   597  	if !found1 || !found2 {
   598  		t.Fatalf("expected to find %q and %q but got:\n%s",
   599  			exp[0], exp[1], strings.Join(env.List(), "\n"))
   600  	}
   601  }
   602  
   603  func TestEnvironment_AppendHostEnvvars(t *testing.T) {
   604  	host := os.Environ()
   605  	if len(host) < 2 {
   606  		t.Skip("No host environment variables. Can't test")
   607  	}
   608  	skip := strings.Split(host[0], "=")[0]
   609  	env := testEnvBuilder().
   610  		SetHostEnvvars([]string{skip}).
   611  		Build()
   612  
   613  	act := env.Map()
   614  	if len(act) < 1 {
   615  		t.Fatalf("Host environment variables not properly set")
   616  	}
   617  	if _, ok := act[skip]; ok {
   618  		t.Fatalf("Didn't filter environment variable %q", skip)
   619  	}
   620  }
   621  
   622  // TestEnvironment_DashesInTaskName asserts dashes in port labels are properly
   623  // converted to underscores in environment variables.
   624  // See: https://github.com/hashicorp/nomad/issues/2405
   625  func TestEnvironment_DashesInTaskName(t *testing.T) {
   626  	a := mock.Alloc()
   627  	task := a.Job.TaskGroups[0].Tasks[0]
   628  	task.Env = map[string]string{"test-one-two": "three-four"}
   629  	envMap := NewBuilder(mock.Node(), a, task, "global").Build().Map()
   630  
   631  	if envMap["test_one_two"] != "three-four" {
   632  		t.Fatalf("Expected test_one_two=three-four in TaskEnv; found:\n%#v", envMap)
   633  	}
   634  }
   635  
   636  // TestEnvironment_UpdateTask asserts env vars and task meta are updated when a
   637  // task is updated.
   638  func TestEnvironment_UpdateTask(t *testing.T) {
   639  	a := mock.Alloc()
   640  	a.Job.TaskGroups[0].Meta = map[string]string{"tgmeta": "tgmetaval"}
   641  	task := a.Job.TaskGroups[0].Tasks[0]
   642  	task.Name = "orig"
   643  	task.Env = map[string]string{"env": "envval"}
   644  	task.Meta = map[string]string{"taskmeta": "taskmetaval"}
   645  	builder := NewBuilder(mock.Node(), a, task, "global")
   646  
   647  	origMap := builder.Build().Map()
   648  	if origMap["NOMAD_TASK_NAME"] != "orig" {
   649  		t.Errorf("Expected NOMAD_TASK_NAME=orig but found %q", origMap["NOMAD_TASK_NAME"])
   650  	}
   651  	if origMap["NOMAD_META_taskmeta"] != "taskmetaval" {
   652  		t.Errorf("Expected NOMAD_META_taskmeta=taskmetaval but found %q", origMap["NOMAD_META_taskmeta"])
   653  	}
   654  	if origMap["env"] != "envval" {
   655  		t.Errorf("Expected env=envva but found %q", origMap["env"])
   656  	}
   657  	if origMap["NOMAD_META_tgmeta"] != "tgmetaval" {
   658  		t.Errorf("Expected NOMAD_META_tgmeta=tgmetaval but found %q", origMap["NOMAD_META_tgmeta"])
   659  	}
   660  
   661  	a.Job.TaskGroups[0].Meta = map[string]string{"tgmeta2": "tgmetaval2"}
   662  	task.Name = "new"
   663  	task.Env = map[string]string{"env2": "envval2"}
   664  	task.Meta = map[string]string{"taskmeta2": "taskmetaval2"}
   665  
   666  	newMap := builder.UpdateTask(a, task).Build().Map()
   667  	if newMap["NOMAD_TASK_NAME"] != "new" {
   668  		t.Errorf("Expected NOMAD_TASK_NAME=new but found %q", newMap["NOMAD_TASK_NAME"])
   669  	}
   670  	if newMap["NOMAD_META_taskmeta2"] != "taskmetaval2" {
   671  		t.Errorf("Expected NOMAD_META_taskmeta=taskmetaval but found %q", newMap["NOMAD_META_taskmeta2"])
   672  	}
   673  	if newMap["env2"] != "envval2" {
   674  		t.Errorf("Expected env=envva but found %q", newMap["env2"])
   675  	}
   676  	if newMap["NOMAD_META_tgmeta2"] != "tgmetaval2" {
   677  		t.Errorf("Expected NOMAD_META_tgmeta=tgmetaval but found %q", newMap["NOMAD_META_tgmeta2"])
   678  	}
   679  	if v, ok := newMap["NOMAD_META_taskmeta"]; ok {
   680  		t.Errorf("Expected NOMAD_META_taskmeta to be unset but found: %q", v)
   681  	}
   682  }
   683  
   684  // TestEnvironment_InterpolateEmptyOptionalMeta asserts that in a parameterized
   685  // job, if an optional meta field is not set, it will get interpolated as an
   686  // empty string.
   687  func TestEnvironment_InterpolateEmptyOptionalMeta(t *testing.T) {
   688  	require := require.New(t)
   689  	a := mock.Alloc()
   690  	a.Job.ParameterizedJob = &structs.ParameterizedJobConfig{
   691  		MetaOptional: []string{"metaopt1", "metaopt2"},
   692  	}
   693  	a.Job.Dispatched = true
   694  	task := a.Job.TaskGroups[0].Tasks[0]
   695  	task.Meta = map[string]string{"metaopt1": "metaopt1val"}
   696  	env := NewBuilder(mock.Node(), a, task, "global").Build()
   697  	require.Equal("metaopt1val", env.ReplaceEnv("${NOMAD_META_metaopt1}"))
   698  	require.Empty(env.ReplaceEnv("${NOMAD_META_metaopt2}"))
   699  }