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 }