github.com/manicqin/nomad@v0.9.5/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  	a.Namespace = "not-default"
   165  	task := a.Job.TaskGroups[0].Tasks[0]
   166  	task.Env = map[string]string{
   167  		"taskEnvKey": "taskEnvVal",
   168  	}
   169  	env := NewBuilder(n, a, task, "global").SetDriverNetwork(
   170  		&drivers.DriverNetwork{PortMap: map[string]int{"https": 443}},
   171  	)
   172  
   173  	act := env.Build().List()
   174  	exp := []string{
   175  		"taskEnvKey=taskEnvVal",
   176  		"NOMAD_ADDR_http=127.0.0.1:80",
   177  		"NOMAD_PORT_http=80",
   178  		"NOMAD_IP_http=127.0.0.1",
   179  		"NOMAD_ADDR_https=127.0.0.1:8080",
   180  		"NOMAD_PORT_https=443",
   181  		"NOMAD_IP_https=127.0.0.1",
   182  		"NOMAD_HOST_PORT_http=80",
   183  		"NOMAD_HOST_PORT_https=8080",
   184  		"NOMAD_TASK_NAME=web",
   185  		"NOMAD_GROUP_NAME=web",
   186  		"NOMAD_ADDR_ssh_other=192.168.0.100:1234",
   187  		"NOMAD_ADDR_ssh_ssh=192.168.0.100:22",
   188  		"NOMAD_IP_ssh_other=192.168.0.100",
   189  		"NOMAD_IP_ssh_ssh=192.168.0.100",
   190  		"NOMAD_PORT_ssh_other=1234",
   191  		"NOMAD_PORT_ssh_ssh=22",
   192  		"NOMAD_CPU_LIMIT=500",
   193  		"NOMAD_DC=dc1",
   194  		"NOMAD_NAMESPACE=not-default",
   195  		"NOMAD_REGION=global",
   196  		"NOMAD_MEMORY_LIMIT=256",
   197  		"NOMAD_META_ELB_CHECK_INTERVAL=30s",
   198  		"NOMAD_META_ELB_CHECK_MIN=3",
   199  		"NOMAD_META_ELB_CHECK_TYPE=http",
   200  		"NOMAD_META_FOO=bar",
   201  		"NOMAD_META_OWNER=armon",
   202  		"NOMAD_META_elb_check_interval=30s",
   203  		"NOMAD_META_elb_check_min=3",
   204  		"NOMAD_META_elb_check_type=http",
   205  		"NOMAD_META_foo=bar",
   206  		"NOMAD_META_owner=armon",
   207  		"NOMAD_JOB_NAME=my-job",
   208  		fmt.Sprintf("NOMAD_ALLOC_ID=%s", a.ID),
   209  		"NOMAD_ALLOC_INDEX=0",
   210  	}
   211  	sort.Strings(act)
   212  	sort.Strings(exp)
   213  	require.Equal(t, exp, act)
   214  }
   215  
   216  // COMPAT(0.11): Remove in 0.11
   217  func TestEnvironment_AsList_Old(t *testing.T) {
   218  	n := mock.Node()
   219  	n.Meta = map[string]string{
   220  		"metaKey": "metaVal",
   221  	}
   222  	a := mock.Alloc()
   223  	a.AllocatedResources = nil
   224  	a.Resources = &structs.Resources{
   225  		CPU:      500,
   226  		MemoryMB: 256,
   227  		DiskMB:   150,
   228  		Networks: []*structs.NetworkResource{
   229  			{
   230  				Device: "eth0",
   231  				IP:     "192.168.0.100",
   232  				ReservedPorts: []structs.Port{
   233  					{Label: "ssh", Value: 22},
   234  					{Label: "other", Value: 1234},
   235  				},
   236  				MBits:        50,
   237  				DynamicPorts: []structs.Port{{Label: "http", Value: 2000}},
   238  			},
   239  		},
   240  	}
   241  	a.TaskResources = map[string]*structs.Resources{
   242  		"web": {
   243  			CPU:      500,
   244  			MemoryMB: 256,
   245  			Networks: []*structs.NetworkResource{
   246  				{
   247  					Device:        "eth0",
   248  					IP:            "127.0.0.1",
   249  					ReservedPorts: []structs.Port{{Label: "https", Value: 8080}},
   250  					MBits:         50,
   251  					DynamicPorts:  []structs.Port{{Label: "http", Value: 80}},
   252  				},
   253  			},
   254  		},
   255  	}
   256  	a.TaskResources["ssh"] = &structs.Resources{
   257  		Networks: []*structs.NetworkResource{
   258  			{
   259  				Device: "eth0",
   260  				IP:     "192.168.0.100",
   261  				MBits:  50,
   262  				ReservedPorts: []structs.Port{
   263  					{Label: "ssh", Value: 22},
   264  					{Label: "other", Value: 1234},
   265  				},
   266  			},
   267  		},
   268  	}
   269  	task := a.Job.TaskGroups[0].Tasks[0]
   270  	task.Env = map[string]string{
   271  		"taskEnvKey": "taskEnvVal",
   272  	}
   273  	task.Resources.Networks = []*structs.NetworkResource{
   274  		// Nomad 0.8 didn't fully populate the fields in task Resource Networks
   275  		{
   276  			IP:            "",
   277  			ReservedPorts: []structs.Port{{Label: "https"}},
   278  			DynamicPorts:  []structs.Port{{Label: "http"}},
   279  		},
   280  	}
   281  	env := NewBuilder(n, a, task, "global").SetDriverNetwork(
   282  		&drivers.DriverNetwork{PortMap: map[string]int{"https": 443}},
   283  	)
   284  
   285  	act := env.Build().List()
   286  	exp := []string{
   287  		"taskEnvKey=taskEnvVal",
   288  		"NOMAD_ADDR_http=127.0.0.1:80",
   289  		"NOMAD_PORT_http=80",
   290  		"NOMAD_IP_http=127.0.0.1",
   291  		"NOMAD_ADDR_https=127.0.0.1:8080",
   292  		"NOMAD_PORT_https=443",
   293  		"NOMAD_IP_https=127.0.0.1",
   294  		"NOMAD_HOST_PORT_http=80",
   295  		"NOMAD_HOST_PORT_https=8080",
   296  		"NOMAD_TASK_NAME=web",
   297  		"NOMAD_GROUP_NAME=web",
   298  		"NOMAD_ADDR_ssh_other=192.168.0.100:1234",
   299  		"NOMAD_ADDR_ssh_ssh=192.168.0.100:22",
   300  		"NOMAD_IP_ssh_other=192.168.0.100",
   301  		"NOMAD_IP_ssh_ssh=192.168.0.100",
   302  		"NOMAD_PORT_ssh_other=1234",
   303  		"NOMAD_PORT_ssh_ssh=22",
   304  		"NOMAD_CPU_LIMIT=500",
   305  		"NOMAD_DC=dc1",
   306  		"NOMAD_NAMESPACE=default",
   307  		"NOMAD_REGION=global",
   308  		"NOMAD_MEMORY_LIMIT=256",
   309  		"NOMAD_META_ELB_CHECK_INTERVAL=30s",
   310  		"NOMAD_META_ELB_CHECK_MIN=3",
   311  		"NOMAD_META_ELB_CHECK_TYPE=http",
   312  		"NOMAD_META_FOO=bar",
   313  		"NOMAD_META_OWNER=armon",
   314  		"NOMAD_META_elb_check_interval=30s",
   315  		"NOMAD_META_elb_check_min=3",
   316  		"NOMAD_META_elb_check_type=http",
   317  		"NOMAD_META_foo=bar",
   318  		"NOMAD_META_owner=armon",
   319  		"NOMAD_JOB_NAME=my-job",
   320  		fmt.Sprintf("NOMAD_ALLOC_ID=%s", a.ID),
   321  		"NOMAD_ALLOC_INDEX=0",
   322  	}
   323  	sort.Strings(act)
   324  	sort.Strings(exp)
   325  	require.Equal(t, exp, act)
   326  }
   327  
   328  func TestEnvironment_AllValues(t *testing.T) {
   329  	t.Parallel()
   330  
   331  	n := mock.Node()
   332  	n.Meta = map[string]string{
   333  		"metaKey":           "metaVal",
   334  		"nested.meta.key":   "a",
   335  		"invalid...metakey": "b",
   336  	}
   337  	a := mock.ConnectAlloc()
   338  	a.AllocatedResources.Tasks["web"].Networks[0] = &structs.NetworkResource{
   339  		Device:        "eth0",
   340  		IP:            "127.0.0.1",
   341  		ReservedPorts: []structs.Port{{Label: "https", Value: 8080}},
   342  		MBits:         50,
   343  		DynamicPorts:  []structs.Port{{Label: "http", Value: 80}},
   344  	}
   345  	a.AllocatedResources.Tasks["ssh"] = &structs.AllocatedTaskResources{
   346  		Networks: []*structs.NetworkResource{
   347  			{
   348  				Device: "eth0",
   349  				IP:     "192.168.0.100",
   350  				MBits:  50,
   351  				ReservedPorts: []structs.Port{
   352  					{Label: "ssh", Value: 22},
   353  					{Label: "other", Value: 1234},
   354  				},
   355  			},
   356  		},
   357  	}
   358  
   359  	sharedNet := a.AllocatedResources.Shared.Networks[0]
   360  
   361  	// Add group network port with only a host port.
   362  	sharedNet.DynamicPorts = append(sharedNet.DynamicPorts, structs.Port{
   363  		Label: "hostonly",
   364  		Value: 9998,
   365  	})
   366  
   367  	// Add group network reserved port with a To value.
   368  	sharedNet.ReservedPorts = append(sharedNet.ReservedPorts, structs.Port{
   369  		Label: "static",
   370  		Value: 9997,
   371  		To:    97,
   372  	})
   373  
   374  	task := a.Job.TaskGroups[0].Tasks[0]
   375  	task.Env = map[string]string{
   376  		"taskEnvKey":        "taskEnvVal",
   377  		"nested.task.key":   "x",
   378  		"invalid...taskkey": "y",
   379  		".a":                "a",
   380  		"b.":                "b",
   381  		".":                 "c",
   382  	}
   383  	env := NewBuilder(n, a, task, "global").SetDriverNetwork(
   384  		&drivers.DriverNetwork{PortMap: map[string]int{"https": 443}},
   385  	)
   386  
   387  	values, errs, err := env.Build().AllValues()
   388  	require.NoError(t, err)
   389  
   390  	// Assert the keys we couldn't nest were reported
   391  	require.Len(t, errs, 5)
   392  	require.Contains(t, errs, "invalid...taskkey")
   393  	require.Contains(t, errs, "meta.invalid...metakey")
   394  	require.Contains(t, errs, ".a")
   395  	require.Contains(t, errs, "b.")
   396  	require.Contains(t, errs, ".")
   397  
   398  	exp := map[string]string{
   399  		// Node
   400  		"node.unique.id":          n.ID,
   401  		"node.region":             "global",
   402  		"node.datacenter":         n.Datacenter,
   403  		"node.unique.name":        n.Name,
   404  		"node.class":              n.NodeClass,
   405  		"meta.metaKey":            "metaVal",
   406  		"attr.arch":               "x86",
   407  		"attr.driver.exec":        "1",
   408  		"attr.driver.mock_driver": "1",
   409  		"attr.kernel.name":        "linux",
   410  		"attr.nomad.version":      "0.5.0",
   411  
   412  		// 0.9 style meta and attr
   413  		"node.meta.metaKey":            "metaVal",
   414  		"node.attr.arch":               "x86",
   415  		"node.attr.driver.exec":        "1",
   416  		"node.attr.driver.mock_driver": "1",
   417  		"node.attr.kernel.name":        "linux",
   418  		"node.attr.nomad.version":      "0.5.0",
   419  
   420  		// Env
   421  		"taskEnvKey":                                "taskEnvVal",
   422  		"NOMAD_ADDR_http":                           "127.0.0.1:80",
   423  		"NOMAD_PORT_http":                           "80",
   424  		"NOMAD_IP_http":                             "127.0.0.1",
   425  		"NOMAD_ADDR_https":                          "127.0.0.1:8080",
   426  		"NOMAD_PORT_https":                          "443",
   427  		"NOMAD_IP_https":                            "127.0.0.1",
   428  		"NOMAD_HOST_PORT_http":                      "80",
   429  		"NOMAD_HOST_PORT_https":                     "8080",
   430  		"NOMAD_TASK_NAME":                           "web",
   431  		"NOMAD_GROUP_NAME":                          "web",
   432  		"NOMAD_ADDR_ssh_other":                      "192.168.0.100:1234",
   433  		"NOMAD_ADDR_ssh_ssh":                        "192.168.0.100:22",
   434  		"NOMAD_IP_ssh_other":                        "192.168.0.100",
   435  		"NOMAD_IP_ssh_ssh":                          "192.168.0.100",
   436  		"NOMAD_PORT_ssh_other":                      "1234",
   437  		"NOMAD_PORT_ssh_ssh":                        "22",
   438  		"NOMAD_CPU_LIMIT":                           "500",
   439  		"NOMAD_DC":                                  "dc1",
   440  		"NOMAD_NAMESPACE":                           "default",
   441  		"NOMAD_REGION":                              "global",
   442  		"NOMAD_MEMORY_LIMIT":                        "256",
   443  		"NOMAD_META_ELB_CHECK_INTERVAL":             "30s",
   444  		"NOMAD_META_ELB_CHECK_MIN":                  "3",
   445  		"NOMAD_META_ELB_CHECK_TYPE":                 "http",
   446  		"NOMAD_META_FOO":                            "bar",
   447  		"NOMAD_META_OWNER":                          "armon",
   448  		"NOMAD_META_elb_check_interval":             "30s",
   449  		"NOMAD_META_elb_check_min":                  "3",
   450  		"NOMAD_META_elb_check_type":                 "http",
   451  		"NOMAD_META_foo":                            "bar",
   452  		"NOMAD_META_owner":                          "armon",
   453  		"NOMAD_JOB_NAME":                            "my-job",
   454  		"NOMAD_ALLOC_ID":                            a.ID,
   455  		"NOMAD_ALLOC_INDEX":                         "0",
   456  		"NOMAD_PORT_connect_proxy_testconnect":      "9999",
   457  		"NOMAD_HOST_PORT_connect_proxy_testconnect": "9999",
   458  		"NOMAD_PORT_hostonly":                       "9998",
   459  		"NOMAD_HOST_PORT_hostonly":                  "9998",
   460  		"NOMAD_PORT_static":                         "97",
   461  		"NOMAD_HOST_PORT_static":                    "9997",
   462  
   463  		// 0.9 style env map
   464  		`env["taskEnvKey"]`:        "taskEnvVal",
   465  		`env["NOMAD_ADDR_http"]`:   "127.0.0.1:80",
   466  		`env["nested.task.key"]`:   "x",
   467  		`env["invalid...taskkey"]`: "y",
   468  		`env[".a"]`:                "a",
   469  		`env["b."]`:                "b",
   470  		`env["."]`:                 "c",
   471  	}
   472  
   473  	evalCtx := &hcl.EvalContext{
   474  		Variables: values,
   475  	}
   476  
   477  	for k, expectedVal := range exp {
   478  		t.Run(k, func(t *testing.T) {
   479  			// Parse HCL containing the test key
   480  			hclStr := fmt.Sprintf(`"${%s}"`, k)
   481  			expr, diag := hclsyntax.ParseExpression([]byte(hclStr), "test.hcl", hcl.Pos{})
   482  			require.Empty(t, diag)
   483  
   484  			// Decode with the TaskEnv values
   485  			out := ""
   486  			diag = gohcl.DecodeExpression(expr, evalCtx, &out)
   487  			require.Empty(t, diag)
   488  			require.Equal(t, out, expectedVal)
   489  		})
   490  	}
   491  }
   492  
   493  func TestEnvironment_VaultToken(t *testing.T) {
   494  	n := mock.Node()
   495  	a := mock.Alloc()
   496  	env := NewBuilder(n, a, a.Job.TaskGroups[0].Tasks[0], "global")
   497  	env.SetVaultToken("123", "vault-namespace", false)
   498  
   499  	{
   500  		act := env.Build().All()
   501  		if act[VaultToken] != "" {
   502  			t.Fatalf("Unexpected environment variables: %s=%q", VaultToken, act[VaultToken])
   503  		}
   504  		if act[VaultNamespace] != "" {
   505  			t.Fatalf("Unexpected environment variables: %s=%q", VaultNamespace, act[VaultNamespace])
   506  		}
   507  	}
   508  
   509  	{
   510  		act := env.SetVaultToken("123", "", true).Build().List()
   511  		exp := "VAULT_TOKEN=123"
   512  		found := false
   513  		foundNs := false
   514  		for _, entry := range act {
   515  			if entry == exp {
   516  				found = true
   517  			}
   518  			if strings.HasPrefix(entry, "VAULT_NAMESPACE=") {
   519  				foundNs = true
   520  			}
   521  		}
   522  		if !found {
   523  			t.Fatalf("did not find %q in:\n%s", exp, strings.Join(act, "\n"))
   524  		}
   525  		if foundNs {
   526  			t.Fatalf("found unwanted VAULT_NAMESPACE in:\n%s", strings.Join(act, "\n"))
   527  		}
   528  	}
   529  
   530  	{
   531  		act := env.SetVaultToken("123", "vault-namespace", true).Build().List()
   532  		exp := "VAULT_TOKEN=123"
   533  		expNs := "VAULT_NAMESPACE=vault-namespace"
   534  		found := false
   535  		foundNs := false
   536  		for _, entry := range act {
   537  			if entry == exp {
   538  				found = true
   539  			}
   540  			if entry == expNs {
   541  				foundNs = true
   542  			}
   543  		}
   544  		if !found {
   545  			t.Fatalf("did not find %q in:\n%s", exp, strings.Join(act, "\n"))
   546  		}
   547  		if !foundNs {
   548  			t.Fatalf("did not find %q in:\n%s", expNs, strings.Join(act, "\n"))
   549  		}
   550  	}
   551  }
   552  
   553  func TestEnvironment_Envvars(t *testing.T) {
   554  	envMap := map[string]string{"foo": "baz", "bar": "bang"}
   555  	n := mock.Node()
   556  	a := mock.Alloc()
   557  	task := a.Job.TaskGroups[0].Tasks[0]
   558  	task.Env = envMap
   559  	net := &drivers.DriverNetwork{PortMap: portMap}
   560  	act := NewBuilder(n, a, task, "global").SetDriverNetwork(net).Build().All()
   561  	for k, v := range envMap {
   562  		actV, ok := act[k]
   563  		if !ok {
   564  			t.Fatalf("missing %q in %#v", k, act)
   565  		}
   566  		if v != actV {
   567  			t.Fatalf("expected %s=%q but found %q", k, v, actV)
   568  		}
   569  	}
   570  }
   571  
   572  // TestEnvironment_HookVars asserts hook env vars are LWW and deletes of later
   573  // writes allow earlier hook's values to be visible.
   574  func TestEnvironment_HookVars(t *testing.T) {
   575  	n := mock.Node()
   576  	a := mock.Alloc()
   577  	builder := NewBuilder(n, a, a.Job.TaskGroups[0].Tasks[0], "global")
   578  
   579  	// Add vars from two hooks and assert the second one wins on
   580  	// conflicting keys.
   581  	builder.SetHookEnv("hookA", map[string]string{
   582  		"foo": "bar",
   583  		"baz": "quux",
   584  	})
   585  	builder.SetHookEnv("hookB", map[string]string{
   586  		"foo":   "123",
   587  		"hookB": "wins",
   588  	})
   589  
   590  	{
   591  		out := builder.Build().All()
   592  		assert.Equal(t, "123", out["foo"])
   593  		assert.Equal(t, "quux", out["baz"])
   594  		assert.Equal(t, "wins", out["hookB"])
   595  	}
   596  
   597  	// Asserting overwriting hook vars allows the first hooks original
   598  	// value to be used.
   599  	builder.SetHookEnv("hookB", nil)
   600  	{
   601  		out := builder.Build().All()
   602  		assert.Equal(t, "bar", out["foo"])
   603  		assert.Equal(t, "quux", out["baz"])
   604  		assert.NotContains(t, out, "hookB")
   605  	}
   606  }
   607  
   608  // TestEnvironment_DeviceHookVars asserts device hook env vars are accessible
   609  // separately.
   610  func TestEnvironment_DeviceHookVars(t *testing.T) {
   611  	require := require.New(t)
   612  	n := mock.Node()
   613  	a := mock.Alloc()
   614  	builder := NewBuilder(n, a, a.Job.TaskGroups[0].Tasks[0], "global")
   615  
   616  	// Add vars from two hooks and assert the second one wins on
   617  	// conflicting keys.
   618  	builder.SetHookEnv("hookA", map[string]string{
   619  		"foo": "bar",
   620  		"baz": "quux",
   621  	})
   622  	builder.SetDeviceHookEnv("devices", map[string]string{
   623  		"hook": "wins",
   624  	})
   625  
   626  	b := builder.Build()
   627  	deviceEnv := b.DeviceEnv()
   628  	require.Len(deviceEnv, 1)
   629  	require.Contains(deviceEnv, "hook")
   630  
   631  	all := b.Map()
   632  	require.Contains(all, "foo")
   633  }
   634  
   635  func TestEnvironment_Interpolate(t *testing.T) {
   636  	n := mock.Node()
   637  	n.Attributes["arch"] = "x86"
   638  	n.NodeClass = "test class"
   639  	a := mock.Alloc()
   640  	task := a.Job.TaskGroups[0].Tasks[0]
   641  	task.Env = map[string]string{"test": "${node.class}", "test2": "${attr.arch}"}
   642  	env := NewBuilder(n, a, task, "global").Build()
   643  
   644  	exp := []string{fmt.Sprintf("test=%s", n.NodeClass), fmt.Sprintf("test2=%s", n.Attributes["arch"])}
   645  	found1, found2 := false, false
   646  	for _, entry := range env.List() {
   647  		switch entry {
   648  		case exp[0]:
   649  			found1 = true
   650  		case exp[1]:
   651  			found2 = true
   652  		}
   653  	}
   654  	if !found1 || !found2 {
   655  		t.Fatalf("expected to find %q and %q but got:\n%s",
   656  			exp[0], exp[1], strings.Join(env.List(), "\n"))
   657  	}
   658  }
   659  
   660  func TestEnvironment_AppendHostEnvvars(t *testing.T) {
   661  	host := os.Environ()
   662  	if len(host) < 2 {
   663  		t.Skip("No host environment variables. Can't test")
   664  	}
   665  	skip := strings.Split(host[0], "=")[0]
   666  	env := testEnvBuilder().
   667  		SetHostEnvvars([]string{skip}).
   668  		Build()
   669  
   670  	act := env.Map()
   671  	if len(act) < 1 {
   672  		t.Fatalf("Host environment variables not properly set")
   673  	}
   674  	if _, ok := act[skip]; ok {
   675  		t.Fatalf("Didn't filter environment variable %q", skip)
   676  	}
   677  }
   678  
   679  // TestEnvironment_DashesInTaskName asserts dashes in port labels are properly
   680  // converted to underscores in environment variables.
   681  // See: https://github.com/hashicorp/nomad/issues/2405
   682  func TestEnvironment_DashesInTaskName(t *testing.T) {
   683  	a := mock.Alloc()
   684  	task := a.Job.TaskGroups[0].Tasks[0]
   685  	task.Env = map[string]string{
   686  		"test-one-two":       "three-four",
   687  		"NOMAD_test_one_two": "three-five",
   688  	}
   689  	envMap := NewBuilder(mock.Node(), a, task, "global").Build().Map()
   690  
   691  	if envMap["test-one-two"] != "three-four" {
   692  		t.Fatalf("Expected test-one-two=three-four in TaskEnv; found:\n%#v", envMap)
   693  	}
   694  	if envMap["NOMAD_test_one_two"] != "three-five" {
   695  		t.Fatalf("Expected NOMAD_test_one_two=three-five in TaskEnv; found:\n%#v", envMap)
   696  	}
   697  }
   698  
   699  // TestEnvironment_UpdateTask asserts env vars and task meta are updated when a
   700  // task is updated.
   701  func TestEnvironment_UpdateTask(t *testing.T) {
   702  	a := mock.Alloc()
   703  	a.Job.TaskGroups[0].Meta = map[string]string{"tgmeta": "tgmetaval"}
   704  	task := a.Job.TaskGroups[0].Tasks[0]
   705  	task.Name = "orig"
   706  	task.Env = map[string]string{"env": "envval"}
   707  	task.Meta = map[string]string{"taskmeta": "taskmetaval"}
   708  	builder := NewBuilder(mock.Node(), a, task, "global")
   709  
   710  	origMap := builder.Build().Map()
   711  	if origMap["NOMAD_TASK_NAME"] != "orig" {
   712  		t.Errorf("Expected NOMAD_TASK_NAME=orig but found %q", origMap["NOMAD_TASK_NAME"])
   713  	}
   714  	if origMap["NOMAD_META_taskmeta"] != "taskmetaval" {
   715  		t.Errorf("Expected NOMAD_META_taskmeta=taskmetaval but found %q", origMap["NOMAD_META_taskmeta"])
   716  	}
   717  	if origMap["env"] != "envval" {
   718  		t.Errorf("Expected env=envva but found %q", origMap["env"])
   719  	}
   720  	if origMap["NOMAD_META_tgmeta"] != "tgmetaval" {
   721  		t.Errorf("Expected NOMAD_META_tgmeta=tgmetaval but found %q", origMap["NOMAD_META_tgmeta"])
   722  	}
   723  
   724  	a.Job.TaskGroups[0].Meta = map[string]string{"tgmeta2": "tgmetaval2"}
   725  	task.Name = "new"
   726  	task.Env = map[string]string{"env2": "envval2"}
   727  	task.Meta = map[string]string{"taskmeta2": "taskmetaval2"}
   728  
   729  	newMap := builder.UpdateTask(a, task).Build().Map()
   730  	if newMap["NOMAD_TASK_NAME"] != "new" {
   731  		t.Errorf("Expected NOMAD_TASK_NAME=new but found %q", newMap["NOMAD_TASK_NAME"])
   732  	}
   733  	if newMap["NOMAD_META_taskmeta2"] != "taskmetaval2" {
   734  		t.Errorf("Expected NOMAD_META_taskmeta=taskmetaval but found %q", newMap["NOMAD_META_taskmeta2"])
   735  	}
   736  	if newMap["env2"] != "envval2" {
   737  		t.Errorf("Expected env=envva but found %q", newMap["env2"])
   738  	}
   739  	if newMap["NOMAD_META_tgmeta2"] != "tgmetaval2" {
   740  		t.Errorf("Expected NOMAD_META_tgmeta=tgmetaval but found %q", newMap["NOMAD_META_tgmeta2"])
   741  	}
   742  	if v, ok := newMap["NOMAD_META_taskmeta"]; ok {
   743  		t.Errorf("Expected NOMAD_META_taskmeta to be unset but found: %q", v)
   744  	}
   745  }
   746  
   747  // TestEnvironment_InterpolateEmptyOptionalMeta asserts that in a parameterized
   748  // job, if an optional meta field is not set, it will get interpolated as an
   749  // empty string.
   750  func TestEnvironment_InterpolateEmptyOptionalMeta(t *testing.T) {
   751  	require := require.New(t)
   752  	a := mock.Alloc()
   753  	a.Job.ParameterizedJob = &structs.ParameterizedJobConfig{
   754  		MetaOptional: []string{"metaopt1", "metaopt2"},
   755  	}
   756  	a.Job.Dispatched = true
   757  	task := a.Job.TaskGroups[0].Tasks[0]
   758  	task.Meta = map[string]string{"metaopt1": "metaopt1val"}
   759  	env := NewBuilder(mock.Node(), a, task, "global").Build()
   760  	require.Equal("metaopt1val", env.ReplaceEnv("${NOMAD_META_metaopt1}"))
   761  	require.Empty(env.ReplaceEnv("${NOMAD_META_metaopt2}"))
   762  }
   763  
   764  // TestEnvironment_Upsteams asserts that group.service.upstreams entries are
   765  // added to the environment.
   766  func TestEnvironment_Upstreams(t *testing.T) {
   767  	t.Parallel()
   768  
   769  	// Add some upstreams to the mock alloc
   770  	a := mock.Alloc()
   771  	tg := a.Job.LookupTaskGroup(a.TaskGroup)
   772  	tg.Services = []*structs.Service{
   773  		// Services without Connect should be ignored
   774  		{
   775  			Name: "ignoreme",
   776  		},
   777  		// All upstreams from a service should be added
   778  		{
   779  			Name: "remote_service",
   780  			Connect: &structs.ConsulConnect{
   781  				SidecarService: &structs.ConsulSidecarService{
   782  					Proxy: &structs.ConsulProxy{
   783  						Upstreams: []structs.ConsulUpstream{
   784  							{
   785  								DestinationName: "foo-bar",
   786  								LocalBindPort:   1234,
   787  							},
   788  							{
   789  								DestinationName: "bar",
   790  								LocalBindPort:   5678,
   791  							},
   792  						},
   793  					},
   794  				},
   795  			},
   796  		},
   797  	}
   798  
   799  	// Ensure the upstreams can be interpolated
   800  	tg.Tasks[0].Env = map[string]string{
   801  		"foo": "${NOMAD_UPSTREAM_ADDR_foo_bar}",
   802  		"bar": "${NOMAD_UPSTREAM_PORT_foo-bar}",
   803  	}
   804  
   805  	env := NewBuilder(mock.Node(), a, tg.Tasks[0], "global").Build().Map()
   806  	require.Equal(t, "127.0.0.1:1234", env["NOMAD_UPSTREAM_ADDR_foo_bar"])
   807  	require.Equal(t, "127.0.0.1", env["NOMAD_UPSTREAM_IP_foo_bar"])
   808  	require.Equal(t, "1234", env["NOMAD_UPSTREAM_PORT_foo_bar"])
   809  	require.Equal(t, "127.0.0.1:5678", env["NOMAD_UPSTREAM_ADDR_bar"])
   810  	require.Equal(t, "127.0.0.1", env["NOMAD_UPSTREAM_IP_bar"])
   811  	require.Equal(t, "5678", env["NOMAD_UPSTREAM_PORT_bar"])
   812  	require.Equal(t, "127.0.0.1:1234", env["foo"])
   813  	require.Equal(t, "1234", env["bar"])
   814  }
   815  
   816  func TestEnvironment_SetPortMapEnvs(t *testing.T) {
   817  	envs := map[string]string{
   818  		"foo":            "bar",
   819  		"NOMAD_PORT_ssh": "2342",
   820  	}
   821  	ports := map[string]int{
   822  		"ssh":  22,
   823  		"http": 80,
   824  	}
   825  
   826  	envs = SetPortMapEnvs(envs, ports)
   827  
   828  	expected := map[string]string{
   829  		"foo":             "bar",
   830  		"NOMAD_PORT_ssh":  "22",
   831  		"NOMAD_PORT_http": "80",
   832  	}
   833  	require.Equal(t, expected, envs)
   834  }
   835  
   836  func TestEnvironment_TasklessBuilder(t *testing.T) {
   837  	node := mock.Node()
   838  	alloc := mock.Alloc()
   839  	alloc.Job.Meta["jobt"] = "foo"
   840  	alloc.Job.TaskGroups[0].Meta["groupt"] = "bar"
   841  	require := require.New(t)
   842  	var taskEnv *TaskEnv
   843  	require.NotPanics(func() {
   844  		taskEnv = NewBuilder(node, alloc, nil, "global").SetAllocDir("/tmp/alloc").Build()
   845  	})
   846  
   847  	require.Equal("foo", taskEnv.ReplaceEnv("${NOMAD_META_jobt}"))
   848  	require.Equal("bar", taskEnv.ReplaceEnv("${NOMAD_META_groupt}"))
   849  }