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