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