github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/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  	hcl "github.com/hashicorp/hcl/v2"
    12  	"github.com/hashicorp/hcl/v2/gohcl"
    13  	"github.com/hashicorp/hcl/v2/hclsyntax"
    14  	"github.com/hashicorp/nomad/helper/uuid"
    15  	"github.com/hashicorp/nomad/nomad/mock"
    16  	"github.com/hashicorp/nomad/nomad/structs"
    17  	"github.com/hashicorp/nomad/plugins/drivers"
    18  	"github.com/stretchr/testify/assert"
    19  	"github.com/stretchr/testify/require"
    20  )
    21  
    22  const (
    23  	// Node values that tests can rely on
    24  	metaKey   = "instance"
    25  	metaVal   = "t2-micro"
    26  	attrKey   = "arch"
    27  	attrVal   = "amd64"
    28  	nodeName  = "test node"
    29  	nodeClass = "test class"
    30  
    31  	// Environment variable values that tests can rely on
    32  	envOneKey = "NOMAD_IP"
    33  	envOneVal = "127.0.0.1"
    34  	envTwoKey = "NOMAD_PORT_WEB"
    35  	envTwoVal = ":80"
    36  )
    37  
    38  var (
    39  	// portMap for use in tests as its set after Builder creation
    40  	portMap = map[string]int{
    41  		"https": 443,
    42  	}
    43  )
    44  
    45  func testEnvBuilder() *Builder {
    46  	n := mock.Node()
    47  	n.Attributes = map[string]string{
    48  		attrKey: attrVal,
    49  	}
    50  	n.Meta = map[string]string{
    51  		metaKey: metaVal,
    52  	}
    53  	n.Name = nodeName
    54  	n.NodeClass = nodeClass
    55  
    56  	task := mock.Job().TaskGroups[0].Tasks[0]
    57  	task.Env = map[string]string{
    58  		envOneKey: envOneVal,
    59  		envTwoKey: envTwoVal,
    60  	}
    61  	return NewBuilder(n, mock.Alloc(), task, "global")
    62  }
    63  
    64  func TestEnvironment_ParseAndReplace_Env(t *testing.T) {
    65  	env := testEnvBuilder()
    66  
    67  	input := []string{fmt.Sprintf(`"${%v}"!`, envOneKey), fmt.Sprintf("${%s}${%s}", envOneKey, envTwoKey)}
    68  	act := env.Build().ParseAndReplace(input)
    69  	exp := []string{fmt.Sprintf(`"%s"!`, envOneVal), fmt.Sprintf("%s%s", envOneVal, envTwoVal)}
    70  
    71  	if !reflect.DeepEqual(act, exp) {
    72  		t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
    73  	}
    74  }
    75  
    76  func TestEnvironment_ParseAndReplace_Meta(t *testing.T) {
    77  	input := []string{fmt.Sprintf("${%v%v}", nodeMetaPrefix, metaKey)}
    78  	exp := []string{metaVal}
    79  	env := testEnvBuilder()
    80  	act := env.Build().ParseAndReplace(input)
    81  
    82  	if !reflect.DeepEqual(act, exp) {
    83  		t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
    84  	}
    85  }
    86  
    87  func TestEnvironment_ParseAndReplace_Attr(t *testing.T) {
    88  	input := []string{fmt.Sprintf("${%v%v}", nodeAttributePrefix, attrKey)}
    89  	exp := []string{attrVal}
    90  	env := testEnvBuilder()
    91  	act := env.Build().ParseAndReplace(input)
    92  
    93  	if !reflect.DeepEqual(act, exp) {
    94  		t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
    95  	}
    96  }
    97  
    98  func TestEnvironment_ParseAndReplace_Node(t *testing.T) {
    99  	input := []string{fmt.Sprintf("${%v}", nodeNameKey), fmt.Sprintf("${%v}", nodeClassKey)}
   100  	exp := []string{nodeName, nodeClass}
   101  	env := testEnvBuilder()
   102  	act := env.Build().ParseAndReplace(input)
   103  
   104  	if !reflect.DeepEqual(act, exp) {
   105  		t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
   106  	}
   107  }
   108  
   109  func TestEnvironment_ParseAndReplace_Mixed(t *testing.T) {
   110  	input := []string{
   111  		fmt.Sprintf("${%v}${%v%v}", nodeNameKey, nodeAttributePrefix, attrKey),
   112  		fmt.Sprintf("${%v}${%v%v}", nodeClassKey, nodeMetaPrefix, metaKey),
   113  		fmt.Sprintf("${%v}${%v}", envTwoKey, nodeClassKey),
   114  	}
   115  	exp := []string{
   116  		fmt.Sprintf("%v%v", nodeName, attrVal),
   117  		fmt.Sprintf("%v%v", nodeClass, metaVal),
   118  		fmt.Sprintf("%v%v", envTwoVal, nodeClass),
   119  	}
   120  	env := testEnvBuilder()
   121  	act := env.Build().ParseAndReplace(input)
   122  
   123  	if !reflect.DeepEqual(act, exp) {
   124  		t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
   125  	}
   126  }
   127  
   128  func TestEnvironment_ReplaceEnv_Mixed(t *testing.T) {
   129  	input := fmt.Sprintf("${%v}${%v%v}", nodeNameKey, nodeAttributePrefix, attrKey)
   130  	exp := fmt.Sprintf("%v%v", nodeName, attrVal)
   131  	env := testEnvBuilder()
   132  	act := env.Build().ReplaceEnv(input)
   133  
   134  	if act != exp {
   135  		t.Fatalf("ParseAndReplace(%v) returned %#v; want %#v", input, act, exp)
   136  	}
   137  }
   138  
   139  func TestEnvironment_AsList(t *testing.T) {
   140  	n := mock.Node()
   141  	n.Meta = map[string]string{
   142  		"metaKey": "metaVal",
   143  	}
   144  	a := mock.Alloc()
   145  	a.Job.ParentID = fmt.Sprintf("mock-parent-service-%s", uuid.Generate())
   146  	a.AllocatedResources.Tasks["web"].Networks[0] = &structs.NetworkResource{
   147  		Device:        "eth0",
   148  		IP:            "127.0.0.1",
   149  		ReservedPorts: []structs.Port{{Label: "https", Value: 8080}},
   150  		MBits:         50,
   151  		DynamicPorts:  []structs.Port{{Label: "http", Value: 80}},
   152  	}
   153  	a.AllocatedResources.Tasks["ssh"] = &structs.AllocatedTaskResources{
   154  		Networks: []*structs.NetworkResource{
   155  			{
   156  				Device: "eth0",
   157  				IP:     "192.168.0.100",
   158  				MBits:  50,
   159  				ReservedPorts: []structs.Port{
   160  					{Label: "ssh", Value: 22},
   161  					{Label: "other", Value: 1234},
   162  				},
   163  			},
   164  		},
   165  	}
   166  	a.Namespace = "not-default"
   167  	task := a.Job.TaskGroups[0].Tasks[0]
   168  	task.Env = map[string]string{
   169  		"taskEnvKey": "taskEnvVal",
   170  	}
   171  	env := NewBuilder(n, a, task, "global").SetDriverNetwork(
   172  		&drivers.DriverNetwork{PortMap: map[string]int{"https": 443}},
   173  	)
   174  
   175  	act := env.Build().List()
   176  	exp := []string{
   177  		"taskEnvKey=taskEnvVal",
   178  		"NOMAD_ADDR_http=127.0.0.1:80",
   179  		"NOMAD_PORT_http=80",
   180  		"NOMAD_IP_http=127.0.0.1",
   181  		"NOMAD_ADDR_https=127.0.0.1:8080",
   182  		"NOMAD_PORT_https=443",
   183  		"NOMAD_IP_https=127.0.0.1",
   184  		"NOMAD_HOST_PORT_http=80",
   185  		"NOMAD_HOST_PORT_https=8080",
   186  		"NOMAD_TASK_NAME=web",
   187  		"NOMAD_GROUP_NAME=web",
   188  		"NOMAD_ADDR_ssh_other=192.168.0.100:1234",
   189  		"NOMAD_ADDR_ssh_ssh=192.168.0.100:22",
   190  		"NOMAD_IP_ssh_other=192.168.0.100",
   191  		"NOMAD_IP_ssh_ssh=192.168.0.100",
   192  		"NOMAD_PORT_ssh_other=1234",
   193  		"NOMAD_PORT_ssh_ssh=22",
   194  		"NOMAD_CPU_LIMIT=500",
   195  		"NOMAD_DC=dc1",
   196  		"NOMAD_NAMESPACE=not-default",
   197  		"NOMAD_REGION=global",
   198  		"NOMAD_MEMORY_LIMIT=256",
   199  		"NOMAD_META_ELB_CHECK_INTERVAL=30s",
   200  		"NOMAD_META_ELB_CHECK_MIN=3",
   201  		"NOMAD_META_ELB_CHECK_TYPE=http",
   202  		"NOMAD_META_FOO=bar",
   203  		"NOMAD_META_OWNER=armon",
   204  		"NOMAD_META_elb_check_interval=30s",
   205  		"NOMAD_META_elb_check_min=3",
   206  		"NOMAD_META_elb_check_type=http",
   207  		"NOMAD_META_foo=bar",
   208  		"NOMAD_META_owner=armon",
   209  		fmt.Sprintf("NOMAD_JOB_ID=%s", a.Job.ID),
   210  		"NOMAD_JOB_NAME=my-job",
   211  		fmt.Sprintf("NOMAD_JOB_PARENT_ID=%s", a.Job.ParentID),
   212  		fmt.Sprintf("NOMAD_ALLOC_ID=%s", a.ID),
   213  		"NOMAD_ALLOC_INDEX=0",
   214  	}
   215  	sort.Strings(act)
   216  	sort.Strings(exp)
   217  	require.Equal(t, exp, act)
   218  }
   219  
   220  // COMPAT(0.11): Remove in 0.11
   221  func TestEnvironment_AsList_Old(t *testing.T) {
   222  	n := mock.Node()
   223  	n.Meta = map[string]string{
   224  		"metaKey": "metaVal",
   225  	}
   226  	a := mock.Alloc()
   227  	a.AllocatedResources = nil
   228  	a.Resources = &structs.Resources{
   229  		CPU:      500,
   230  		MemoryMB: 256,
   231  		DiskMB:   150,
   232  		Networks: []*structs.NetworkResource{
   233  			{
   234  				Device: "eth0",
   235  				IP:     "192.168.0.100",
   236  				ReservedPorts: []structs.Port{
   237  					{Label: "ssh", Value: 22},
   238  					{Label: "other", Value: 1234},
   239  				},
   240  				MBits:        50,
   241  				DynamicPorts: []structs.Port{{Label: "http", Value: 2000}},
   242  			},
   243  		},
   244  	}
   245  	a.TaskResources = map[string]*structs.Resources{
   246  		"web": {
   247  			CPU:      500,
   248  			MemoryMB: 256,
   249  			Networks: []*structs.NetworkResource{
   250  				{
   251  					Device:        "eth0",
   252  					IP:            "127.0.0.1",
   253  					ReservedPorts: []structs.Port{{Label: "https", Value: 8080}},
   254  					MBits:         50,
   255  					DynamicPorts:  []structs.Port{{Label: "http", Value: 80}},
   256  				},
   257  			},
   258  		},
   259  	}
   260  	a.TaskResources["ssh"] = &structs.Resources{
   261  		Networks: []*structs.NetworkResource{
   262  			{
   263  				Device: "eth0",
   264  				IP:     "192.168.0.100",
   265  				MBits:  50,
   266  				ReservedPorts: []structs.Port{
   267  					{Label: "ssh", Value: 22},
   268  					{Label: "other", Value: 1234},
   269  				},
   270  			},
   271  		},
   272  	}
   273  
   274  	// simulate canonicalization on restore or fetch
   275  	a.Canonicalize()
   276  
   277  	task := a.Job.TaskGroups[0].Tasks[0]
   278  	task.Env = map[string]string{
   279  		"taskEnvKey": "taskEnvVal",
   280  	}
   281  	task.Resources.Networks = []*structs.NetworkResource{
   282  		// Nomad 0.8 didn't fully populate the fields in task Resource Networks
   283  		{
   284  			IP:            "",
   285  			ReservedPorts: []structs.Port{{Label: "https"}},
   286  			DynamicPorts:  []structs.Port{{Label: "http"}},
   287  		},
   288  	}
   289  	env := NewBuilder(n, a, task, "global").SetDriverNetwork(
   290  		&drivers.DriverNetwork{PortMap: map[string]int{"https": 443}},
   291  	)
   292  
   293  	act := env.Build().List()
   294  	exp := []string{
   295  		"taskEnvKey=taskEnvVal",
   296  		"NOMAD_ADDR_http=127.0.0.1:80",
   297  		"NOMAD_PORT_http=80",
   298  		"NOMAD_IP_http=127.0.0.1",
   299  		"NOMAD_ADDR_https=127.0.0.1:8080",
   300  		"NOMAD_PORT_https=443",
   301  		"NOMAD_IP_https=127.0.0.1",
   302  		"NOMAD_HOST_PORT_http=80",
   303  		"NOMAD_HOST_PORT_https=8080",
   304  		"NOMAD_TASK_NAME=web",
   305  		"NOMAD_GROUP_NAME=web",
   306  		"NOMAD_ADDR_ssh_other=192.168.0.100:1234",
   307  		"NOMAD_ADDR_ssh_ssh=192.168.0.100:22",
   308  		"NOMAD_IP_ssh_other=192.168.0.100",
   309  		"NOMAD_IP_ssh_ssh=192.168.0.100",
   310  		"NOMAD_PORT_ssh_other=1234",
   311  		"NOMAD_PORT_ssh_ssh=22",
   312  		"NOMAD_CPU_LIMIT=500",
   313  		"NOMAD_DC=dc1",
   314  		"NOMAD_NAMESPACE=default",
   315  		"NOMAD_REGION=global",
   316  		"NOMAD_MEMORY_LIMIT=256",
   317  		"NOMAD_META_ELB_CHECK_INTERVAL=30s",
   318  		"NOMAD_META_ELB_CHECK_MIN=3",
   319  		"NOMAD_META_ELB_CHECK_TYPE=http",
   320  		"NOMAD_META_FOO=bar",
   321  		"NOMAD_META_OWNER=armon",
   322  		"NOMAD_META_elb_check_interval=30s",
   323  		"NOMAD_META_elb_check_min=3",
   324  		"NOMAD_META_elb_check_type=http",
   325  		"NOMAD_META_foo=bar",
   326  		"NOMAD_META_owner=armon",
   327  		fmt.Sprintf("NOMAD_JOB_ID=%s", a.Job.ID),
   328  		"NOMAD_JOB_NAME=my-job",
   329  		fmt.Sprintf("NOMAD_ALLOC_ID=%s", a.ID),
   330  		"NOMAD_ALLOC_INDEX=0",
   331  	}
   332  	sort.Strings(act)
   333  	sort.Strings(exp)
   334  	require.Equal(t, exp, act)
   335  }
   336  
   337  func TestEnvironment_AllValues(t *testing.T) {
   338  	t.Parallel()
   339  
   340  	n := mock.Node()
   341  	n.Meta = map[string]string{
   342  		"metaKey":           "metaVal",
   343  		"nested.meta.key":   "a",
   344  		"invalid...metakey": "b",
   345  	}
   346  	a := mock.ConnectAlloc()
   347  	a.Job.ParentID = fmt.Sprintf("mock-parent-service-%s", uuid.Generate())
   348  	a.AllocatedResources.Tasks["web"].Networks[0] = &structs.NetworkResource{
   349  		Device:        "eth0",
   350  		IP:            "127.0.0.1",
   351  		ReservedPorts: []structs.Port{{Label: "https", Value: 8080}},
   352  		MBits:         50,
   353  		DynamicPorts:  []structs.Port{{Label: "http", Value: 80}},
   354  	}
   355  	a.AllocatedResources.Tasks["ssh"] = &structs.AllocatedTaskResources{
   356  		Networks: []*structs.NetworkResource{
   357  			{
   358  				Device: "eth0",
   359  				IP:     "192.168.0.100",
   360  				MBits:  50,
   361  				ReservedPorts: []structs.Port{
   362  					{Label: "ssh", Value: 22},
   363  					{Label: "other", Value: 1234},
   364  				},
   365  			},
   366  		},
   367  	}
   368  
   369  	a.AllocatedResources.Shared.Ports = structs.AllocatedPorts{
   370  		{
   371  			Label:  "admin",
   372  			Value:  32000,
   373  			To:     9000,
   374  			HostIP: "127.0.0.1",
   375  		},
   376  	}
   377  
   378  	sharedNet := a.AllocatedResources.Shared.Networks[0]
   379  
   380  	// Add group network port with only a host port.
   381  	sharedNet.DynamicPorts = append(sharedNet.DynamicPorts, structs.Port{
   382  		Label: "hostonly",
   383  		Value: 9998,
   384  	})
   385  
   386  	// Add group network reserved port with a To value.
   387  	sharedNet.ReservedPorts = append(sharedNet.ReservedPorts, structs.Port{
   388  		Label: "static",
   389  		Value: 9997,
   390  		To:    97,
   391  	})
   392  
   393  	task := a.Job.TaskGroups[0].Tasks[0]
   394  	task.Env = map[string]string{
   395  		"taskEnvKey":        "taskEnvVal",
   396  		"nested.task.key":   "x",
   397  		"invalid...taskkey": "y",
   398  		".a":                "a",
   399  		"b.":                "b",
   400  		".":                 "c",
   401  	}
   402  	env := NewBuilder(n, a, task, "global").SetDriverNetwork(
   403  		&drivers.DriverNetwork{PortMap: map[string]int{"https": 443}},
   404  	)
   405  
   406  	values, errs, err := env.Build().AllValues()
   407  	require.NoError(t, err)
   408  
   409  	// Assert the keys we couldn't nest were reported
   410  	require.Len(t, errs, 5)
   411  	require.Contains(t, errs, "invalid...taskkey")
   412  	require.Contains(t, errs, "meta.invalid...metakey")
   413  	require.Contains(t, errs, ".a")
   414  	require.Contains(t, errs, "b.")
   415  	require.Contains(t, errs, ".")
   416  
   417  	exp := map[string]string{
   418  		// Node
   419  		"node.unique.id":          n.ID,
   420  		"node.region":             "global",
   421  		"node.datacenter":         n.Datacenter,
   422  		"node.unique.name":        n.Name,
   423  		"node.class":              n.NodeClass,
   424  		"meta.metaKey":            "metaVal",
   425  		"attr.arch":               "x86",
   426  		"attr.driver.exec":        "1",
   427  		"attr.driver.mock_driver": "1",
   428  		"attr.kernel.name":        "linux",
   429  		"attr.nomad.version":      "0.5.0",
   430  
   431  		// 0.9 style meta and attr
   432  		"node.meta.metaKey":            "metaVal",
   433  		"node.attr.arch":               "x86",
   434  		"node.attr.driver.exec":        "1",
   435  		"node.attr.driver.mock_driver": "1",
   436  		"node.attr.kernel.name":        "linux",
   437  		"node.attr.nomad.version":      "0.5.0",
   438  
   439  		// Env
   440  		"taskEnvKey":                                "taskEnvVal",
   441  		"NOMAD_ADDR_http":                           "127.0.0.1:80",
   442  		"NOMAD_PORT_http":                           "80",
   443  		"NOMAD_IP_http":                             "127.0.0.1",
   444  		"NOMAD_ADDR_https":                          "127.0.0.1:8080",
   445  		"NOMAD_PORT_https":                          "443",
   446  		"NOMAD_IP_https":                            "127.0.0.1",
   447  		"NOMAD_HOST_PORT_http":                      "80",
   448  		"NOMAD_HOST_PORT_https":                     "8080",
   449  		"NOMAD_TASK_NAME":                           "web",
   450  		"NOMAD_GROUP_NAME":                          "web",
   451  		"NOMAD_ADDR_ssh_other":                      "192.168.0.100:1234",
   452  		"NOMAD_ADDR_ssh_ssh":                        "192.168.0.100:22",
   453  		"NOMAD_IP_ssh_other":                        "192.168.0.100",
   454  		"NOMAD_IP_ssh_ssh":                          "192.168.0.100",
   455  		"NOMAD_PORT_ssh_other":                      "1234",
   456  		"NOMAD_PORT_ssh_ssh":                        "22",
   457  		"NOMAD_CPU_LIMIT":                           "500",
   458  		"NOMAD_DC":                                  "dc1",
   459  		"NOMAD_NAMESPACE":                           "default",
   460  		"NOMAD_REGION":                              "global",
   461  		"NOMAD_MEMORY_LIMIT":                        "256",
   462  		"NOMAD_META_ELB_CHECK_INTERVAL":             "30s",
   463  		"NOMAD_META_ELB_CHECK_MIN":                  "3",
   464  		"NOMAD_META_ELB_CHECK_TYPE":                 "http",
   465  		"NOMAD_META_FOO":                            "bar",
   466  		"NOMAD_META_OWNER":                          "armon",
   467  		"NOMAD_META_elb_check_interval":             "30s",
   468  		"NOMAD_META_elb_check_min":                  "3",
   469  		"NOMAD_META_elb_check_type":                 "http",
   470  		"NOMAD_META_foo":                            "bar",
   471  		"NOMAD_META_owner":                          "armon",
   472  		"NOMAD_JOB_ID":                              a.Job.ID,
   473  		"NOMAD_JOB_NAME":                            "my-job",
   474  		"NOMAD_JOB_PARENT_ID":                       a.Job.ParentID,
   475  		"NOMAD_ALLOC_ID":                            a.ID,
   476  		"NOMAD_ALLOC_INDEX":                         "0",
   477  		"NOMAD_PORT_connect_proxy_testconnect":      "9999",
   478  		"NOMAD_HOST_PORT_connect_proxy_testconnect": "9999",
   479  		"NOMAD_PORT_hostonly":                       "9998",
   480  		"NOMAD_HOST_PORT_hostonly":                  "9998",
   481  		"NOMAD_PORT_static":                         "97",
   482  		"NOMAD_HOST_PORT_static":                    "9997",
   483  		"NOMAD_ADDR_admin":                          "127.0.0.1:32000",
   484  		"NOMAD_HOST_ADDR_admin":                     "127.0.0.1:32000",
   485  		"NOMAD_IP_admin":                            "127.0.0.1",
   486  		"NOMAD_HOST_IP_admin":                       "127.0.0.1",
   487  		"NOMAD_PORT_admin":                          "9000",
   488  		"NOMAD_ALLOC_PORT_admin":                    "9000",
   489  		"NOMAD_HOST_PORT_admin":                     "32000",
   490  
   491  		// 0.9 style env map
   492  		`env["taskEnvKey"]`:        "taskEnvVal",
   493  		`env["NOMAD_ADDR_http"]`:   "127.0.0.1:80",
   494  		`env["nested.task.key"]`:   "x",
   495  		`env["invalid...taskkey"]`: "y",
   496  		`env[".a"]`:                "a",
   497  		`env["b."]`:                "b",
   498  		`env["."]`:                 "c",
   499  	}
   500  
   501  	evalCtx := &hcl.EvalContext{
   502  		Variables: values,
   503  	}
   504  
   505  	for k, expectedVal := range exp {
   506  		t.Run(k, func(t *testing.T) {
   507  			// Parse HCL containing the test key
   508  			hclStr := fmt.Sprintf(`"${%s}"`, k)
   509  			expr, diag := hclsyntax.ParseExpression([]byte(hclStr), "test.hcl", hcl.Pos{})
   510  			require.Empty(t, diag)
   511  
   512  			// Decode with the TaskEnv values
   513  			out := ""
   514  			diag = gohcl.DecodeExpression(expr, evalCtx, &out)
   515  			require.Empty(t, diag)
   516  			require.Equal(t, out, expectedVal)
   517  		})
   518  	}
   519  }
   520  
   521  func TestEnvironment_VaultToken(t *testing.T) {
   522  	n := mock.Node()
   523  	a := mock.Alloc()
   524  	env := NewBuilder(n, a, a.Job.TaskGroups[0].Tasks[0], "global")
   525  	env.SetVaultToken("123", "vault-namespace", false)
   526  
   527  	{
   528  		act := env.Build().All()
   529  		if act[VaultToken] != "" {
   530  			t.Fatalf("Unexpected environment variables: %s=%q", VaultToken, act[VaultToken])
   531  		}
   532  		if act[VaultNamespace] != "" {
   533  			t.Fatalf("Unexpected environment variables: %s=%q", VaultNamespace, act[VaultNamespace])
   534  		}
   535  	}
   536  
   537  	{
   538  		act := env.SetVaultToken("123", "", true).Build().List()
   539  		exp := "VAULT_TOKEN=123"
   540  		found := false
   541  		foundNs := false
   542  		for _, entry := range act {
   543  			if entry == exp {
   544  				found = true
   545  			}
   546  			if strings.HasPrefix(entry, "VAULT_NAMESPACE=") {
   547  				foundNs = true
   548  			}
   549  		}
   550  		if !found {
   551  			t.Fatalf("did not find %q in:\n%s", exp, strings.Join(act, "\n"))
   552  		}
   553  		if foundNs {
   554  			t.Fatalf("found unwanted VAULT_NAMESPACE in:\n%s", strings.Join(act, "\n"))
   555  		}
   556  	}
   557  
   558  	{
   559  		act := env.SetVaultToken("123", "vault-namespace", true).Build().List()
   560  		exp := "VAULT_TOKEN=123"
   561  		expNs := "VAULT_NAMESPACE=vault-namespace"
   562  		found := false
   563  		foundNs := false
   564  		for _, entry := range act {
   565  			if entry == exp {
   566  				found = true
   567  			}
   568  			if entry == expNs {
   569  				foundNs = true
   570  			}
   571  		}
   572  		if !found {
   573  			t.Fatalf("did not find %q in:\n%s", exp, strings.Join(act, "\n"))
   574  		}
   575  		if !foundNs {
   576  			t.Fatalf("did not find %q in:\n%s", expNs, strings.Join(act, "\n"))
   577  		}
   578  	}
   579  }
   580  
   581  func TestEnvironment_Envvars(t *testing.T) {
   582  	envMap := map[string]string{"foo": "baz", "bar": "bang"}
   583  	n := mock.Node()
   584  	a := mock.Alloc()
   585  	task := a.Job.TaskGroups[0].Tasks[0]
   586  	task.Env = envMap
   587  	net := &drivers.DriverNetwork{PortMap: portMap}
   588  	act := NewBuilder(n, a, task, "global").SetDriverNetwork(net).Build().All()
   589  	for k, v := range envMap {
   590  		actV, ok := act[k]
   591  		if !ok {
   592  			t.Fatalf("missing %q in %#v", k, act)
   593  		}
   594  		if v != actV {
   595  			t.Fatalf("expected %s=%q but found %q", k, v, actV)
   596  		}
   597  	}
   598  }
   599  
   600  // TestEnvironment_HookVars asserts hook env vars are LWW and deletes of later
   601  // writes allow earlier hook's values to be visible.
   602  func TestEnvironment_HookVars(t *testing.T) {
   603  	n := mock.Node()
   604  	a := mock.Alloc()
   605  	builder := NewBuilder(n, a, a.Job.TaskGroups[0].Tasks[0], "global")
   606  
   607  	// Add vars from two hooks and assert the second one wins on
   608  	// conflicting keys.
   609  	builder.SetHookEnv("hookA", map[string]string{
   610  		"foo": "bar",
   611  		"baz": "quux",
   612  	})
   613  	builder.SetHookEnv("hookB", map[string]string{
   614  		"foo":   "123",
   615  		"hookB": "wins",
   616  	})
   617  
   618  	{
   619  		out := builder.Build().All()
   620  		assert.Equal(t, "123", out["foo"])
   621  		assert.Equal(t, "quux", out["baz"])
   622  		assert.Equal(t, "wins", out["hookB"])
   623  	}
   624  
   625  	// Asserting overwriting hook vars allows the first hooks original
   626  	// value to be used.
   627  	builder.SetHookEnv("hookB", nil)
   628  	{
   629  		out := builder.Build().All()
   630  		assert.Equal(t, "bar", out["foo"])
   631  		assert.Equal(t, "quux", out["baz"])
   632  		assert.NotContains(t, out, "hookB")
   633  	}
   634  }
   635  
   636  // TestEnvironment_DeviceHookVars asserts device hook env vars are accessible
   637  // separately.
   638  func TestEnvironment_DeviceHookVars(t *testing.T) {
   639  	require := require.New(t)
   640  	n := mock.Node()
   641  	a := mock.Alloc()
   642  	builder := NewBuilder(n, a, a.Job.TaskGroups[0].Tasks[0], "global")
   643  
   644  	// Add vars from two hooks and assert the second one wins on
   645  	// conflicting keys.
   646  	builder.SetHookEnv("hookA", map[string]string{
   647  		"foo": "bar",
   648  		"baz": "quux",
   649  	})
   650  	builder.SetDeviceHookEnv("devices", map[string]string{
   651  		"hook": "wins",
   652  	})
   653  
   654  	b := builder.Build()
   655  	deviceEnv := b.DeviceEnv()
   656  	require.Len(deviceEnv, 1)
   657  	require.Contains(deviceEnv, "hook")
   658  
   659  	all := b.Map()
   660  	require.Contains(all, "foo")
   661  }
   662  
   663  func TestEnvironment_Interpolate(t *testing.T) {
   664  	n := mock.Node()
   665  	n.Attributes["arch"] = "x86"
   666  	n.NodeClass = "test class"
   667  	a := mock.Alloc()
   668  	task := a.Job.TaskGroups[0].Tasks[0]
   669  	task.Env = map[string]string{"test": "${node.class}", "test2": "${attr.arch}"}
   670  	env := NewBuilder(n, a, task, "global").Build()
   671  
   672  	exp := []string{fmt.Sprintf("test=%s", n.NodeClass), fmt.Sprintf("test2=%s", n.Attributes["arch"])}
   673  	found1, found2 := false, false
   674  	for _, entry := range env.List() {
   675  		switch entry {
   676  		case exp[0]:
   677  			found1 = true
   678  		case exp[1]:
   679  			found2 = true
   680  		}
   681  	}
   682  	if !found1 || !found2 {
   683  		t.Fatalf("expected to find %q and %q but got:\n%s",
   684  			exp[0], exp[1], strings.Join(env.List(), "\n"))
   685  	}
   686  }
   687  
   688  func TestEnvironment_AppendHostEnvvars(t *testing.T) {
   689  	host := os.Environ()
   690  	if len(host) < 2 {
   691  		t.Skip("No host environment variables. Can't test")
   692  	}
   693  	skip := strings.Split(host[0], "=")[0]
   694  	env := testEnvBuilder().
   695  		SetHostEnvvars([]string{skip}).
   696  		Build()
   697  
   698  	act := env.Map()
   699  	if len(act) < 1 {
   700  		t.Fatalf("Host environment variables not properly set")
   701  	}
   702  	if _, ok := act[skip]; ok {
   703  		t.Fatalf("Didn't filter environment variable %q", skip)
   704  	}
   705  }
   706  
   707  // TestEnvironment_DashesInTaskName asserts dashes in port labels are properly
   708  // converted to underscores in environment variables.
   709  // See: https://github.com/hashicorp/nomad/issues/2405
   710  func TestEnvironment_DashesInTaskName(t *testing.T) {
   711  	a := mock.Alloc()
   712  	task := a.Job.TaskGroups[0].Tasks[0]
   713  	task.Env = map[string]string{
   714  		"test-one-two":       "three-four",
   715  		"NOMAD_test_one_two": "three-five",
   716  	}
   717  	envMap := NewBuilder(mock.Node(), a, task, "global").Build().Map()
   718  
   719  	if envMap["test-one-two"] != "three-four" {
   720  		t.Fatalf("Expected test-one-two=three-four in TaskEnv; found:\n%#v", envMap)
   721  	}
   722  	if envMap["NOMAD_test_one_two"] != "three-five" {
   723  		t.Fatalf("Expected NOMAD_test_one_two=three-five in TaskEnv; found:\n%#v", envMap)
   724  	}
   725  }
   726  
   727  // TestEnvironment_UpdateTask asserts env vars and task meta are updated when a
   728  // task is updated.
   729  func TestEnvironment_UpdateTask(t *testing.T) {
   730  	a := mock.Alloc()
   731  	a.Job.TaskGroups[0].Meta = map[string]string{"tgmeta": "tgmetaval"}
   732  	task := a.Job.TaskGroups[0].Tasks[0]
   733  	task.Name = "orig"
   734  	task.Env = map[string]string{"env": "envval"}
   735  	task.Meta = map[string]string{"taskmeta": "taskmetaval"}
   736  	builder := NewBuilder(mock.Node(), a, task, "global")
   737  
   738  	origMap := builder.Build().Map()
   739  	if origMap["NOMAD_TASK_NAME"] != "orig" {
   740  		t.Errorf("Expected NOMAD_TASK_NAME=orig but found %q", origMap["NOMAD_TASK_NAME"])
   741  	}
   742  	if origMap["NOMAD_META_taskmeta"] != "taskmetaval" {
   743  		t.Errorf("Expected NOMAD_META_taskmeta=taskmetaval but found %q", origMap["NOMAD_META_taskmeta"])
   744  	}
   745  	if origMap["env"] != "envval" {
   746  		t.Errorf("Expected env=envva but found %q", origMap["env"])
   747  	}
   748  	if origMap["NOMAD_META_tgmeta"] != "tgmetaval" {
   749  		t.Errorf("Expected NOMAD_META_tgmeta=tgmetaval but found %q", origMap["NOMAD_META_tgmeta"])
   750  	}
   751  
   752  	a.Job.TaskGroups[0].Meta = map[string]string{"tgmeta2": "tgmetaval2"}
   753  	task.Name = "new"
   754  	task.Env = map[string]string{"env2": "envval2"}
   755  	task.Meta = map[string]string{"taskmeta2": "taskmetaval2"}
   756  
   757  	newMap := builder.UpdateTask(a, task).Build().Map()
   758  	if newMap["NOMAD_TASK_NAME"] != "new" {
   759  		t.Errorf("Expected NOMAD_TASK_NAME=new but found %q", newMap["NOMAD_TASK_NAME"])
   760  	}
   761  	if newMap["NOMAD_META_taskmeta2"] != "taskmetaval2" {
   762  		t.Errorf("Expected NOMAD_META_taskmeta=taskmetaval but found %q", newMap["NOMAD_META_taskmeta2"])
   763  	}
   764  	if newMap["env2"] != "envval2" {
   765  		t.Errorf("Expected env=envva but found %q", newMap["env2"])
   766  	}
   767  	if newMap["NOMAD_META_tgmeta2"] != "tgmetaval2" {
   768  		t.Errorf("Expected NOMAD_META_tgmeta=tgmetaval but found %q", newMap["NOMAD_META_tgmeta2"])
   769  	}
   770  	if v, ok := newMap["NOMAD_META_taskmeta"]; ok {
   771  		t.Errorf("Expected NOMAD_META_taskmeta to be unset but found: %q", v)
   772  	}
   773  }
   774  
   775  // TestEnvironment_InterpolateEmptyOptionalMeta asserts that in a parameterized
   776  // job, if an optional meta field is not set, it will get interpolated as an
   777  // empty string.
   778  func TestEnvironment_InterpolateEmptyOptionalMeta(t *testing.T) {
   779  	require := require.New(t)
   780  	a := mock.Alloc()
   781  	a.Job.ParameterizedJob = &structs.ParameterizedJobConfig{
   782  		MetaOptional: []string{"metaopt1", "metaopt2"},
   783  	}
   784  	a.Job.Dispatched = true
   785  	task := a.Job.TaskGroups[0].Tasks[0]
   786  	task.Meta = map[string]string{"metaopt1": "metaopt1val"}
   787  	env := NewBuilder(mock.Node(), a, task, "global").Build()
   788  	require.Equal("metaopt1val", env.ReplaceEnv("${NOMAD_META_metaopt1}"))
   789  	require.Empty(env.ReplaceEnv("${NOMAD_META_metaopt2}"))
   790  }
   791  
   792  // TestEnvironment_Upsteams asserts that group.service.upstreams entries are
   793  // added to the environment.
   794  func TestEnvironment_Upstreams(t *testing.T) {
   795  	t.Parallel()
   796  
   797  	// Add some upstreams to the mock alloc
   798  	a := mock.Alloc()
   799  	tg := a.Job.LookupTaskGroup(a.TaskGroup)
   800  	tg.Services = []*structs.Service{
   801  		// Services without Connect should be ignored
   802  		{
   803  			Name: "ignoreme",
   804  		},
   805  		// All upstreams from a service should be added
   806  		{
   807  			Name: "remote_service",
   808  			Connect: &structs.ConsulConnect{
   809  				SidecarService: &structs.ConsulSidecarService{
   810  					Proxy: &structs.ConsulProxy{
   811  						Upstreams: []structs.ConsulUpstream{
   812  							{
   813  								DestinationName: "foo-bar",
   814  								LocalBindPort:   1234,
   815  							},
   816  							{
   817  								DestinationName: "bar",
   818  								LocalBindPort:   5678,
   819  							},
   820  						},
   821  					},
   822  				},
   823  			},
   824  		},
   825  	}
   826  
   827  	// Ensure the upstreams can be interpolated
   828  	tg.Tasks[0].Env = map[string]string{
   829  		"foo": "${NOMAD_UPSTREAM_ADDR_foo_bar}",
   830  		"bar": "${NOMAD_UPSTREAM_PORT_foo-bar}",
   831  	}
   832  
   833  	env := NewBuilder(mock.Node(), a, tg.Tasks[0], "global").Build().Map()
   834  	require.Equal(t, "127.0.0.1:1234", env["NOMAD_UPSTREAM_ADDR_foo_bar"])
   835  	require.Equal(t, "127.0.0.1", env["NOMAD_UPSTREAM_IP_foo_bar"])
   836  	require.Equal(t, "1234", env["NOMAD_UPSTREAM_PORT_foo_bar"])
   837  	require.Equal(t, "127.0.0.1:5678", env["NOMAD_UPSTREAM_ADDR_bar"])
   838  	require.Equal(t, "127.0.0.1", env["NOMAD_UPSTREAM_IP_bar"])
   839  	require.Equal(t, "5678", env["NOMAD_UPSTREAM_PORT_bar"])
   840  	require.Equal(t, "127.0.0.1:1234", env["foo"])
   841  	require.Equal(t, "1234", env["bar"])
   842  }
   843  
   844  func TestEnvironment_SetPortMapEnvs(t *testing.T) {
   845  	envs := map[string]string{
   846  		"foo":            "bar",
   847  		"NOMAD_PORT_ssh": "2342",
   848  	}
   849  	ports := map[string]int{
   850  		"ssh":  22,
   851  		"http": 80,
   852  	}
   853  
   854  	envs = SetPortMapEnvs(envs, ports)
   855  
   856  	expected := map[string]string{
   857  		"foo":             "bar",
   858  		"NOMAD_PORT_ssh":  "22",
   859  		"NOMAD_PORT_http": "80",
   860  	}
   861  	require.Equal(t, expected, envs)
   862  }
   863  
   864  func TestEnvironment_TasklessBuilder(t *testing.T) {
   865  	node := mock.Node()
   866  	alloc := mock.Alloc()
   867  	alloc.Job.Meta["jobt"] = "foo"
   868  	alloc.Job.TaskGroups[0].Meta["groupt"] = "bar"
   869  	require := require.New(t)
   870  	var taskEnv *TaskEnv
   871  	require.NotPanics(func() {
   872  		taskEnv = NewBuilder(node, alloc, nil, "global").SetAllocDir("/tmp/alloc").Build()
   873  	})
   874  
   875  	require.Equal("foo", taskEnv.ReplaceEnv("${NOMAD_META_jobt}"))
   876  	require.Equal("bar", taskEnv.ReplaceEnv("${NOMAD_META_groupt}"))
   877  }
   878  
   879  func TestTaskEnv_ClientPath(t *testing.T) {
   880  	builder := testEnvBuilder()
   881  	builder.SetAllocDir("/tmp/testAlloc")
   882  	builder.SetClientSharedAllocDir("/tmp/testAlloc/alloc")
   883  	builder.SetClientTaskRoot("/tmp/testAlloc/testTask")
   884  	builder.SetClientTaskLocalDir("/tmp/testAlloc/testTask/local")
   885  	builder.SetClientTaskSecretsDir("/tmp/testAlloc/testTask/secrets")
   886  	env := builder.Build()
   887  
   888  	testCases := []struct {
   889  		label        string
   890  		input        string
   891  		joinOnEscape bool
   892  		escapes      bool
   893  		expected     string
   894  	}{
   895  		{
   896  			// this is useful behavior for exec-based tasks, allowing template or artifact
   897  			// destination anywhere in the chroot
   898  			label:        "join on escape if requested",
   899  			input:        "/tmp",
   900  			joinOnEscape: true,
   901  			expected:     "/tmp/testAlloc/testTask/tmp",
   902  			escapes:      false,
   903  		},
   904  		{
   905  			// template source behavior does not perform unconditional join
   906  			label:        "do not join on escape unless requested",
   907  			input:        "/tmp",
   908  			joinOnEscape: false,
   909  			expected:     "/tmp",
   910  			escapes:      true,
   911  		},
   912  		{
   913  			// relative paths are always joined to the task root dir
   914  			// escape from task root dir and shared alloc dir should be detected
   915  			label:        "detect escape for relative paths",
   916  			input:        "..",
   917  			joinOnEscape: true,
   918  			expected:     "/tmp/testAlloc",
   919  			escapes:      true,
   920  		},
   921  		{
   922  			// shared alloc dir should be available from ../alloc, for historical reasons
   923  			// this is not an escape
   924  			label:        "relative access to shared alloc dir",
   925  			input:        "../alloc/somefile",
   926  			joinOnEscape: true,
   927  			expected:     "/tmp/testAlloc/alloc/somefile",
   928  			escapes:      false,
   929  		},
   930  		{
   931  			label:        "interpolate shared alloc dir",
   932  			input:        "${NOMAD_ALLOC_DIR}/somefile",
   933  			joinOnEscape: false,
   934  			expected:     "/tmp/testAlloc/alloc/somefile",
   935  			escapes:      false,
   936  		},
   937  		{
   938  			label:        "interpolate task local dir",
   939  			input:        "${NOMAD_TASK_DIR}/somefile",
   940  			joinOnEscape: false,
   941  			expected:     "/tmp/testAlloc/testTask/local/somefile",
   942  			escapes:      false,
   943  		},
   944  		{
   945  			label:        "interpolate task secrts dir",
   946  			input:        "${NOMAD_SECRETS_DIR}/somefile",
   947  			joinOnEscape: false,
   948  			expected:     "/tmp/testAlloc/testTask/secrets/somefile",
   949  			escapes:      false,
   950  		},
   951  	}
   952  
   953  	for _, tc := range testCases {
   954  		t.Run(tc.label, func(t *testing.T) {
   955  			path, escapes := env.ClientPath(tc.input, tc.joinOnEscape)
   956  			assert.Equal(t, tc.escapes, escapes, "escape check")
   957  			assert.Equal(t, tc.expected, path, "interpolated path")
   958  		})
   959  	}
   960  }