github.com/manicqin/nomad@v0.9.5/client/allocrunner/taskrunner/template/template_test.go (about) 1 package template 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "strings" 11 "sync" 12 "testing" 13 "time" 14 15 ctestutil "github.com/hashicorp/consul/testutil" 16 "github.com/hashicorp/nomad/client/config" 17 "github.com/hashicorp/nomad/client/taskenv" 18 "github.com/hashicorp/nomad/helper" 19 "github.com/hashicorp/nomad/helper/uuid" 20 "github.com/hashicorp/nomad/nomad/mock" 21 "github.com/hashicorp/nomad/nomad/structs" 22 sconfig "github.com/hashicorp/nomad/nomad/structs/config" 23 "github.com/hashicorp/nomad/testutil" 24 "github.com/stretchr/testify/assert" 25 "github.com/stretchr/testify/require" 26 ) 27 28 const ( 29 // TestTaskName is the name of the injected task. It should appear in the 30 // environment variable $NOMAD_TASK_NAME 31 TestTaskName = "test-task" 32 ) 33 34 // MockTaskHooks is a mock of the TaskHooks interface useful for testing 35 type MockTaskHooks struct { 36 Restarts int 37 RestartCh chan struct{} 38 39 Signals []string 40 SignalCh chan struct{} 41 signalLock sync.Mutex 42 43 // SignalError is returned when Signal is called on the mock hook 44 SignalError error 45 46 UnblockCh chan struct{} 47 48 KillEvent *structs.TaskEvent 49 KillCh chan struct{} 50 51 Events []*structs.TaskEvent 52 EmitEventCh chan struct{} 53 } 54 55 func NewMockTaskHooks() *MockTaskHooks { 56 return &MockTaskHooks{ 57 UnblockCh: make(chan struct{}, 1), 58 RestartCh: make(chan struct{}, 1), 59 SignalCh: make(chan struct{}, 1), 60 KillCh: make(chan struct{}, 1), 61 EmitEventCh: make(chan struct{}, 1), 62 } 63 } 64 func (m *MockTaskHooks) Restart(ctx context.Context, event *structs.TaskEvent, failure bool) error { 65 m.Restarts++ 66 select { 67 case m.RestartCh <- struct{}{}: 68 default: 69 } 70 return nil 71 } 72 73 func (m *MockTaskHooks) Signal(event *structs.TaskEvent, s string) error { 74 m.signalLock.Lock() 75 m.Signals = append(m.Signals, s) 76 m.signalLock.Unlock() 77 select { 78 case m.SignalCh <- struct{}{}: 79 default: 80 } 81 82 return m.SignalError 83 } 84 85 func (m *MockTaskHooks) Kill(ctx context.Context, event *structs.TaskEvent) error { 86 m.KillEvent = event 87 select { 88 case m.KillCh <- struct{}{}: 89 default: 90 } 91 return nil 92 } 93 94 func (m *MockTaskHooks) EmitEvent(event *structs.TaskEvent) { 95 m.Events = append(m.Events, event) 96 select { 97 case m.EmitEventCh <- struct{}{}: 98 default: 99 } 100 } 101 102 func (m *MockTaskHooks) SetState(state string, event *structs.TaskEvent) {} 103 104 // testHarness is used to test the TaskTemplateManager by spinning up 105 // Consul/Vault as needed 106 type testHarness struct { 107 manager *TaskTemplateManager 108 mockHooks *MockTaskHooks 109 templates []*structs.Template 110 envBuilder *taskenv.Builder 111 node *structs.Node 112 config *config.Config 113 vaultToken string 114 taskDir string 115 vault *testutil.TestVault 116 consul *ctestutil.TestServer 117 emitRate time.Duration 118 } 119 120 // newTestHarness returns a harness starting a dev consul and vault server, 121 // building the appropriate config and creating a TaskTemplateManager 122 func newTestHarness(t *testing.T, templates []*structs.Template, consul, vault bool) *testHarness { 123 region := "global" 124 harness := &testHarness{ 125 mockHooks: NewMockTaskHooks(), 126 templates: templates, 127 node: mock.Node(), 128 config: &config.Config{ 129 Region: region, 130 TemplateConfig: &config.ClientTemplateConfig{ 131 FunctionBlacklist: []string{"plugin"}, 132 DisableSandbox: false, 133 }}, 134 emitRate: DefaultMaxTemplateEventRate, 135 } 136 137 // Build the task environment 138 a := mock.Alloc() 139 task := a.Job.TaskGroups[0].Tasks[0] 140 task.Name = TestTaskName 141 harness.envBuilder = taskenv.NewBuilder(harness.node, a, task, region) 142 143 // Make a tempdir 144 d, err := ioutil.TempDir("", "ct_test") 145 if err != nil { 146 t.Fatalf("Failed to make tmpdir: %v", err) 147 } 148 harness.taskDir = d 149 150 if consul { 151 harness.consul, err = ctestutil.NewTestServer() 152 if err != nil { 153 t.Fatalf("error starting test Consul server: %v", err) 154 } 155 harness.config.ConsulConfig = &sconfig.ConsulConfig{ 156 Addr: harness.consul.HTTPAddr, 157 } 158 } 159 160 if vault { 161 harness.vault = testutil.NewTestVault(t) 162 harness.config.VaultConfig = harness.vault.Config 163 harness.vaultToken = harness.vault.RootToken 164 } 165 166 return harness 167 } 168 169 func (h *testHarness) start(t *testing.T) { 170 if err := h.startWithErr(); err != nil { 171 t.Fatalf("failed to build task template manager: %v", err) 172 } 173 } 174 175 func (h *testHarness) startWithErr() error { 176 var err error 177 h.manager, err = NewTaskTemplateManager(&TaskTemplateManagerConfig{ 178 UnblockCh: h.mockHooks.UnblockCh, 179 Lifecycle: h.mockHooks, 180 Events: h.mockHooks, 181 Templates: h.templates, 182 ClientConfig: h.config, 183 VaultToken: h.vaultToken, 184 TaskDir: h.taskDir, 185 EnvBuilder: h.envBuilder, 186 MaxTemplateEventRate: h.emitRate, 187 retryRate: 10 * time.Millisecond, 188 }) 189 190 return err 191 } 192 193 func (h *testHarness) setEmitRate(d time.Duration) { 194 h.emitRate = d 195 } 196 197 // stop is used to stop any running Vault or Consul server plus the task manager 198 func (h *testHarness) stop() { 199 if h.vault != nil { 200 h.vault.Stop() 201 } 202 if h.consul != nil { 203 h.consul.Stop() 204 } 205 if h.manager != nil { 206 h.manager.Stop() 207 } 208 if h.taskDir != "" { 209 os.RemoveAll(h.taskDir) 210 } 211 } 212 213 func TestTaskTemplateManager_InvalidConfig(t *testing.T) { 214 t.Parallel() 215 hooks := NewMockTaskHooks() 216 clientConfig := &config.Config{Region: "global"} 217 taskDir := "foo" 218 a := mock.Alloc() 219 envBuilder := taskenv.NewBuilder(mock.Node(), a, a.Job.TaskGroups[0].Tasks[0], clientConfig.Region) 220 221 cases := []struct { 222 name string 223 config *TaskTemplateManagerConfig 224 expectedErr string 225 }{ 226 { 227 name: "nil config", 228 config: nil, 229 expectedErr: "Nil config passed", 230 }, 231 { 232 name: "bad lifecycle hooks", 233 config: &TaskTemplateManagerConfig{ 234 UnblockCh: hooks.UnblockCh, 235 Events: hooks, 236 ClientConfig: clientConfig, 237 TaskDir: taskDir, 238 EnvBuilder: envBuilder, 239 MaxTemplateEventRate: DefaultMaxTemplateEventRate, 240 }, 241 expectedErr: "lifecycle hooks", 242 }, 243 { 244 name: "bad event hooks", 245 config: &TaskTemplateManagerConfig{ 246 UnblockCh: hooks.UnblockCh, 247 Lifecycle: hooks, 248 ClientConfig: clientConfig, 249 TaskDir: taskDir, 250 EnvBuilder: envBuilder, 251 MaxTemplateEventRate: DefaultMaxTemplateEventRate, 252 }, 253 expectedErr: "event hook", 254 }, 255 { 256 name: "bad client config", 257 config: &TaskTemplateManagerConfig{ 258 UnblockCh: hooks.UnblockCh, 259 Lifecycle: hooks, 260 Events: hooks, 261 TaskDir: taskDir, 262 EnvBuilder: envBuilder, 263 MaxTemplateEventRate: DefaultMaxTemplateEventRate, 264 }, 265 expectedErr: "client config", 266 }, 267 { 268 name: "bad task dir", 269 config: &TaskTemplateManagerConfig{ 270 UnblockCh: hooks.UnblockCh, 271 ClientConfig: clientConfig, 272 Lifecycle: hooks, 273 Events: hooks, 274 EnvBuilder: envBuilder, 275 MaxTemplateEventRate: DefaultMaxTemplateEventRate, 276 }, 277 expectedErr: "task directory", 278 }, 279 { 280 name: "bad env builder", 281 config: &TaskTemplateManagerConfig{ 282 UnblockCh: hooks.UnblockCh, 283 ClientConfig: clientConfig, 284 Lifecycle: hooks, 285 Events: hooks, 286 TaskDir: taskDir, 287 MaxTemplateEventRate: DefaultMaxTemplateEventRate, 288 }, 289 expectedErr: "task environment", 290 }, 291 { 292 name: "bad max event rate", 293 config: &TaskTemplateManagerConfig{ 294 UnblockCh: hooks.UnblockCh, 295 ClientConfig: clientConfig, 296 Lifecycle: hooks, 297 Events: hooks, 298 TaskDir: taskDir, 299 EnvBuilder: envBuilder, 300 }, 301 expectedErr: "template event rate", 302 }, 303 { 304 name: "valid", 305 config: &TaskTemplateManagerConfig{ 306 UnblockCh: hooks.UnblockCh, 307 ClientConfig: clientConfig, 308 Lifecycle: hooks, 309 Events: hooks, 310 TaskDir: taskDir, 311 EnvBuilder: envBuilder, 312 MaxTemplateEventRate: DefaultMaxTemplateEventRate, 313 }, 314 }, 315 { 316 name: "invalid signal", 317 config: &TaskTemplateManagerConfig{ 318 UnblockCh: hooks.UnblockCh, 319 Templates: []*structs.Template{ 320 { 321 DestPath: "foo", 322 EmbeddedTmpl: "hello, world", 323 ChangeMode: structs.TemplateChangeModeSignal, 324 ChangeSignal: "foobarbaz", 325 }, 326 }, 327 ClientConfig: clientConfig, 328 Lifecycle: hooks, 329 Events: hooks, 330 TaskDir: taskDir, 331 EnvBuilder: envBuilder, 332 MaxTemplateEventRate: DefaultMaxTemplateEventRate, 333 }, 334 expectedErr: "parse signal", 335 }, 336 } 337 338 for _, c := range cases { 339 t.Run(c.name, func(t *testing.T) { 340 _, err := NewTaskTemplateManager(c.config) 341 if err != nil { 342 if c.expectedErr == "" { 343 t.Fatalf("unexpected error: %v", err) 344 } else if !strings.Contains(err.Error(), c.expectedErr) { 345 t.Fatalf("expected error to contain %q; got %q", c.expectedErr, err.Error()) 346 } 347 } else if c.expectedErr != "" { 348 t.Fatalf("expected an error to contain %q", c.expectedErr) 349 } 350 }) 351 } 352 } 353 354 func TestTaskTemplateManager_HostPath(t *testing.T) { 355 t.Parallel() 356 // Make a template that will render immediately and write it to a tmp file 357 f, err := ioutil.TempFile("", "") 358 if err != nil { 359 t.Fatalf("Bad: %v", err) 360 } 361 defer f.Close() 362 defer os.Remove(f.Name()) 363 364 content := "hello, world!" 365 if _, err := io.WriteString(f, content); err != nil { 366 t.Fatalf("Bad: %v", err) 367 } 368 369 file := "my.tmpl" 370 template := &structs.Template{ 371 SourcePath: f.Name(), 372 DestPath: file, 373 ChangeMode: structs.TemplateChangeModeNoop, 374 } 375 376 harness := newTestHarness(t, []*structs.Template{template}, false, false) 377 harness.start(t) 378 defer harness.stop() 379 380 // Wait for the unblock 381 select { 382 case <-harness.mockHooks.UnblockCh: 383 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 384 t.Fatalf("Task unblock should have been called") 385 } 386 387 // Check the file is there 388 path := filepath.Join(harness.taskDir, file) 389 raw, err := ioutil.ReadFile(path) 390 if err != nil { 391 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 392 } 393 394 if s := string(raw); s != content { 395 t.Fatalf("Unexpected template data; got %q, want %q", s, content) 396 } 397 398 // Change the config to disallow host sources 399 harness = newTestHarness(t, []*structs.Template{template}, false, false) 400 harness.config.Options = map[string]string{ 401 hostSrcOption: "false", 402 } 403 if err := harness.startWithErr(); err == nil || !strings.Contains(err.Error(), "absolute") { 404 t.Fatalf("Expected absolute template path disallowed: %v", err) 405 } 406 } 407 408 func TestTaskTemplateManager_Unblock_Static(t *testing.T) { 409 t.Parallel() 410 // Make a template that will render immediately 411 content := "hello, world!" 412 file := "my.tmpl" 413 template := &structs.Template{ 414 EmbeddedTmpl: content, 415 DestPath: file, 416 ChangeMode: structs.TemplateChangeModeNoop, 417 } 418 419 harness := newTestHarness(t, []*structs.Template{template}, false, false) 420 harness.start(t) 421 defer harness.stop() 422 423 // Wait for the unblock 424 select { 425 case <-harness.mockHooks.UnblockCh: 426 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 427 t.Fatalf("Task unblock should have been called") 428 } 429 430 // Check the file is there 431 path := filepath.Join(harness.taskDir, file) 432 raw, err := ioutil.ReadFile(path) 433 if err != nil { 434 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 435 } 436 437 if s := string(raw); s != content { 438 t.Fatalf("Unexpected template data; got %q, want %q", s, content) 439 } 440 } 441 442 func TestTaskTemplateManager_Permissions(t *testing.T) { 443 t.Parallel() 444 // Make a template that will render immediately 445 content := "hello, world!" 446 file := "my.tmpl" 447 template := &structs.Template{ 448 EmbeddedTmpl: content, 449 DestPath: file, 450 ChangeMode: structs.TemplateChangeModeNoop, 451 Perms: "777", 452 } 453 454 harness := newTestHarness(t, []*structs.Template{template}, false, false) 455 harness.start(t) 456 defer harness.stop() 457 458 // Wait for the unblock 459 select { 460 case <-harness.mockHooks.UnblockCh: 461 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 462 t.Fatalf("Task unblock should have been called") 463 } 464 465 // Check the file is there 466 path := filepath.Join(harness.taskDir, file) 467 fi, err := os.Stat(path) 468 if err != nil { 469 t.Fatalf("Failed to stat file: %v", err) 470 } 471 472 if m := fi.Mode(); m != os.ModePerm { 473 t.Fatalf("Got mode %v; want %v", m, os.ModePerm) 474 } 475 } 476 477 func TestTaskTemplateManager_Unblock_Static_NomadEnv(t *testing.T) { 478 t.Parallel() 479 // Make a template that will render immediately 480 content := `Hello Nomad Task: {{env "NOMAD_TASK_NAME"}}` 481 expected := fmt.Sprintf("Hello Nomad Task: %s", TestTaskName) 482 file := "my.tmpl" 483 template := &structs.Template{ 484 EmbeddedTmpl: content, 485 DestPath: file, 486 ChangeMode: structs.TemplateChangeModeNoop, 487 } 488 489 harness := newTestHarness(t, []*structs.Template{template}, false, false) 490 harness.start(t) 491 defer harness.stop() 492 493 // Wait for the unblock 494 select { 495 case <-harness.mockHooks.UnblockCh: 496 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 497 t.Fatalf("Task unblock should have been called") 498 } 499 500 // Check the file is there 501 path := filepath.Join(harness.taskDir, file) 502 raw, err := ioutil.ReadFile(path) 503 if err != nil { 504 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 505 } 506 507 if s := string(raw); s != expected { 508 t.Fatalf("Unexpected template data; got %q, want %q", s, expected) 509 } 510 } 511 512 func TestTaskTemplateManager_Unblock_Static_AlreadyRendered(t *testing.T) { 513 t.Parallel() 514 // Make a template that will render immediately 515 content := "hello, world!" 516 file := "my.tmpl" 517 template := &structs.Template{ 518 EmbeddedTmpl: content, 519 DestPath: file, 520 ChangeMode: structs.TemplateChangeModeNoop, 521 } 522 523 harness := newTestHarness(t, []*structs.Template{template}, false, false) 524 525 // Write the contents 526 path := filepath.Join(harness.taskDir, file) 527 if err := ioutil.WriteFile(path, []byte(content), 0777); err != nil { 528 t.Fatalf("Failed to write data: %v", err) 529 } 530 531 harness.start(t) 532 defer harness.stop() 533 534 // Wait for the unblock 535 select { 536 case <-harness.mockHooks.UnblockCh: 537 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 538 t.Fatalf("Task unblock should have been called") 539 } 540 541 // Check the file is there 542 path = filepath.Join(harness.taskDir, file) 543 raw, err := ioutil.ReadFile(path) 544 if err != nil { 545 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 546 } 547 548 if s := string(raw); s != content { 549 t.Fatalf("Unexpected template data; got %q, want %q", s, content) 550 } 551 } 552 553 func TestTaskTemplateManager_Unblock_Consul(t *testing.T) { 554 t.Parallel() 555 // Make a template that will render based on a key in Consul 556 key := "foo" 557 content := "barbaz" 558 embedded := fmt.Sprintf(`{{key "%s"}}`, key) 559 file := "my.tmpl" 560 template := &structs.Template{ 561 EmbeddedTmpl: embedded, 562 DestPath: file, 563 ChangeMode: structs.TemplateChangeModeNoop, 564 } 565 566 harness := newTestHarness(t, []*structs.Template{template}, true, false) 567 harness.start(t) 568 defer harness.stop() 569 570 // Ensure no unblock 571 select { 572 case <-harness.mockHooks.UnblockCh: 573 t.Fatalf("Task unblock should have not have been called") 574 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 575 } 576 577 // Write the key to Consul 578 harness.consul.SetKV(t, key, []byte(content)) 579 580 // Wait for the unblock 581 select { 582 case <-harness.mockHooks.UnblockCh: 583 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 584 t.Fatalf("Task unblock should have been called") 585 } 586 587 // Check the file is there 588 path := filepath.Join(harness.taskDir, file) 589 raw, err := ioutil.ReadFile(path) 590 if err != nil { 591 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 592 } 593 594 if s := string(raw); s != content { 595 t.Fatalf("Unexpected template data; got %q, want %q", s, content) 596 } 597 } 598 599 func TestTaskTemplateManager_Unblock_Vault(t *testing.T) { 600 t.Parallel() 601 require := require.New(t) 602 // Make a template that will render based on a key in Vault 603 vaultPath := "secret/data/password" 604 key := "password" 605 content := "barbaz" 606 embedded := fmt.Sprintf(`{{with secret "%s"}}{{.Data.data.%s}}{{end}}`, vaultPath, key) 607 file := "my.tmpl" 608 template := &structs.Template{ 609 EmbeddedTmpl: embedded, 610 DestPath: file, 611 ChangeMode: structs.TemplateChangeModeNoop, 612 } 613 614 harness := newTestHarness(t, []*structs.Template{template}, false, true) 615 harness.start(t) 616 defer harness.stop() 617 618 // Ensure no unblock 619 select { 620 case <-harness.mockHooks.UnblockCh: 621 t.Fatalf("Task unblock should not have been called") 622 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 623 } 624 625 // Write the secret to Vault 626 logical := harness.vault.Client.Logical() 627 _, err := logical.Write(vaultPath, map[string]interface{}{"data": map[string]interface{}{key: content}}) 628 require.NoError(err) 629 630 // Wait for the unblock 631 select { 632 case <-harness.mockHooks.UnblockCh: 633 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 634 t.Fatalf("Task unblock should have been called") 635 } 636 637 // Check the file is there 638 path := filepath.Join(harness.taskDir, file) 639 raw, err := ioutil.ReadFile(path) 640 if err != nil { 641 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 642 } 643 644 if s := string(raw); s != content { 645 t.Fatalf("Unexpected template data; got %q, want %q", s, content) 646 } 647 } 648 649 func TestTaskTemplateManager_Unblock_Multi_Template(t *testing.T) { 650 t.Parallel() 651 // Make a template that will render immediately 652 staticContent := "hello, world!" 653 staticFile := "my.tmpl" 654 template := &structs.Template{ 655 EmbeddedTmpl: staticContent, 656 DestPath: staticFile, 657 ChangeMode: structs.TemplateChangeModeNoop, 658 } 659 660 // Make a template that will render based on a key in Consul 661 consulKey := "foo" 662 consulContent := "barbaz" 663 consulEmbedded := fmt.Sprintf(`{{key "%s"}}`, consulKey) 664 consulFile := "consul.tmpl" 665 template2 := &structs.Template{ 666 EmbeddedTmpl: consulEmbedded, 667 DestPath: consulFile, 668 ChangeMode: structs.TemplateChangeModeNoop, 669 } 670 671 harness := newTestHarness(t, []*structs.Template{template, template2}, true, false) 672 harness.start(t) 673 defer harness.stop() 674 675 // Ensure no unblock 676 select { 677 case <-harness.mockHooks.UnblockCh: 678 t.Fatalf("Task unblock should have not have been called") 679 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 680 } 681 682 // Check that the static file has been rendered 683 path := filepath.Join(harness.taskDir, staticFile) 684 raw, err := ioutil.ReadFile(path) 685 if err != nil { 686 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 687 } 688 689 if s := string(raw); s != staticContent { 690 t.Fatalf("Unexpected template data; got %q, want %q", s, staticContent) 691 } 692 693 // Write the key to Consul 694 harness.consul.SetKV(t, consulKey, []byte(consulContent)) 695 696 // Wait for the unblock 697 select { 698 case <-harness.mockHooks.UnblockCh: 699 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 700 t.Fatalf("Task unblock should have been called") 701 } 702 703 // Check the consul file is there 704 path = filepath.Join(harness.taskDir, consulFile) 705 raw, err = ioutil.ReadFile(path) 706 if err != nil { 707 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 708 } 709 710 if s := string(raw); s != consulContent { 711 t.Fatalf("Unexpected template data; got %q, want %q", s, consulContent) 712 } 713 } 714 715 func TestTaskTemplateManager_Rerender_Noop(t *testing.T) { 716 t.Parallel() 717 // Make a template that will render based on a key in Consul 718 key := "foo" 719 content1 := "bar" 720 content2 := "baz" 721 embedded := fmt.Sprintf(`{{key "%s"}}`, key) 722 file := "my.tmpl" 723 template := &structs.Template{ 724 EmbeddedTmpl: embedded, 725 DestPath: file, 726 ChangeMode: structs.TemplateChangeModeNoop, 727 } 728 729 harness := newTestHarness(t, []*structs.Template{template}, true, false) 730 harness.start(t) 731 defer harness.stop() 732 733 // Ensure no unblock 734 select { 735 case <-harness.mockHooks.UnblockCh: 736 t.Fatalf("Task unblock should have not have been called") 737 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 738 } 739 740 // Write the key to Consul 741 harness.consul.SetKV(t, key, []byte(content1)) 742 743 // Wait for the unblock 744 select { 745 case <-harness.mockHooks.UnblockCh: 746 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 747 t.Fatalf("Task unblock should have been called") 748 } 749 750 // Check the file is there 751 path := filepath.Join(harness.taskDir, file) 752 raw, err := ioutil.ReadFile(path) 753 if err != nil { 754 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 755 } 756 757 if s := string(raw); s != content1 { 758 t.Fatalf("Unexpected template data; got %q, want %q", s, content1) 759 } 760 761 // Update the key in Consul 762 harness.consul.SetKV(t, key, []byte(content2)) 763 764 select { 765 case <-harness.mockHooks.RestartCh: 766 t.Fatalf("Noop ignored: %+v", harness.mockHooks) 767 case <-harness.mockHooks.SignalCh: 768 t.Fatalf("Noop ignored: %+v", harness.mockHooks) 769 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 770 } 771 772 // Check the file has been updated 773 path = filepath.Join(harness.taskDir, file) 774 raw, err = ioutil.ReadFile(path) 775 if err != nil { 776 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 777 } 778 779 if s := string(raw); s != content2 { 780 t.Fatalf("Unexpected template data; got %q, want %q", s, content2) 781 } 782 } 783 784 func TestTaskTemplateManager_Rerender_Signal(t *testing.T) { 785 t.Parallel() 786 // Make a template that renders based on a key in Consul and sends SIGALRM 787 key1 := "foo" 788 content1_1 := "bar" 789 content1_2 := "baz" 790 embedded1 := fmt.Sprintf(`{{key "%s"}}`, key1) 791 file1 := "my.tmpl" 792 template := &structs.Template{ 793 EmbeddedTmpl: embedded1, 794 DestPath: file1, 795 ChangeMode: structs.TemplateChangeModeSignal, 796 ChangeSignal: "SIGALRM", 797 } 798 799 // Make a template that renders based on a key in Consul and sends SIGBUS 800 key2 := "bam" 801 content2_1 := "cat" 802 content2_2 := "dog" 803 embedded2 := fmt.Sprintf(`{{key "%s"}}`, key2) 804 file2 := "my-second.tmpl" 805 template2 := &structs.Template{ 806 EmbeddedTmpl: embedded2, 807 DestPath: file2, 808 ChangeMode: structs.TemplateChangeModeSignal, 809 ChangeSignal: "SIGBUS", 810 } 811 812 harness := newTestHarness(t, []*structs.Template{template, template2}, true, false) 813 harness.start(t) 814 defer harness.stop() 815 816 // Ensure no unblock 817 select { 818 case <-harness.mockHooks.UnblockCh: 819 t.Fatalf("Task unblock should have not have been called") 820 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 821 } 822 823 // Write the key to Consul 824 harness.consul.SetKV(t, key1, []byte(content1_1)) 825 harness.consul.SetKV(t, key2, []byte(content2_1)) 826 827 // Wait for the unblock 828 select { 829 case <-harness.mockHooks.UnblockCh: 830 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 831 t.Fatalf("Task unblock should have been called") 832 } 833 834 if len(harness.mockHooks.Signals) != 0 { 835 t.Fatalf("Should not have received any signals: %+v", harness.mockHooks) 836 } 837 838 // Update the keys in Consul 839 harness.consul.SetKV(t, key1, []byte(content1_2)) 840 harness.consul.SetKV(t, key2, []byte(content2_2)) 841 842 // Wait for signals 843 timeout := time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second) 844 OUTER: 845 for { 846 select { 847 case <-harness.mockHooks.RestartCh: 848 t.Fatalf("Restart with signal policy: %+v", harness.mockHooks) 849 case <-harness.mockHooks.SignalCh: 850 harness.mockHooks.signalLock.Lock() 851 s := harness.mockHooks.Signals 852 harness.mockHooks.signalLock.Unlock() 853 if len(s) != 2 { 854 continue 855 } 856 break OUTER 857 case <-timeout: 858 t.Fatalf("Should have received two signals: %+v", harness.mockHooks) 859 } 860 } 861 862 // Check the files have been updated 863 path := filepath.Join(harness.taskDir, file1) 864 raw, err := ioutil.ReadFile(path) 865 if err != nil { 866 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 867 } 868 869 if s := string(raw); s != content1_2 { 870 t.Fatalf("Unexpected template data; got %q, want %q", s, content1_2) 871 } 872 873 path = filepath.Join(harness.taskDir, file2) 874 raw, err = ioutil.ReadFile(path) 875 if err != nil { 876 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 877 } 878 879 if s := string(raw); s != content2_2 { 880 t.Fatalf("Unexpected template data; got %q, want %q", s, content2_2) 881 } 882 } 883 884 func TestTaskTemplateManager_Rerender_Restart(t *testing.T) { 885 t.Parallel() 886 // Make a template that renders based on a key in Consul and sends restart 887 key1 := "bam" 888 content1_1 := "cat" 889 content1_2 := "dog" 890 embedded1 := fmt.Sprintf(`{{key "%s"}}`, key1) 891 file1 := "my.tmpl" 892 template := &structs.Template{ 893 EmbeddedTmpl: embedded1, 894 DestPath: file1, 895 ChangeMode: structs.TemplateChangeModeRestart, 896 } 897 898 harness := newTestHarness(t, []*structs.Template{template}, true, false) 899 harness.start(t) 900 defer harness.stop() 901 902 // Ensure no unblock 903 select { 904 case <-harness.mockHooks.UnblockCh: 905 t.Fatalf("Task unblock should have not have been called") 906 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 907 } 908 909 // Write the key to Consul 910 harness.consul.SetKV(t, key1, []byte(content1_1)) 911 912 // Wait for the unblock 913 select { 914 case <-harness.mockHooks.UnblockCh: 915 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 916 t.Fatalf("Task unblock should have been called") 917 } 918 919 // Update the keys in Consul 920 harness.consul.SetKV(t, key1, []byte(content1_2)) 921 922 // Wait for restart 923 timeout := time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second) 924 OUTER: 925 for { 926 select { 927 case <-harness.mockHooks.RestartCh: 928 break OUTER 929 case <-harness.mockHooks.SignalCh: 930 t.Fatalf("Signal with restart policy: %+v", harness.mockHooks) 931 case <-timeout: 932 t.Fatalf("Should have received a restart: %+v", harness.mockHooks) 933 } 934 } 935 936 // Check the files have been updated 937 path := filepath.Join(harness.taskDir, file1) 938 raw, err := ioutil.ReadFile(path) 939 if err != nil { 940 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 941 } 942 943 if s := string(raw); s != content1_2 { 944 t.Fatalf("Unexpected template data; got %q, want %q", s, content1_2) 945 } 946 } 947 948 func TestTaskTemplateManager_Interpolate_Destination(t *testing.T) { 949 t.Parallel() 950 // Make a template that will have its destination interpolated 951 content := "hello, world!" 952 file := "${node.unique.id}.tmpl" 953 template := &structs.Template{ 954 EmbeddedTmpl: content, 955 DestPath: file, 956 ChangeMode: structs.TemplateChangeModeNoop, 957 } 958 959 harness := newTestHarness(t, []*structs.Template{template}, false, false) 960 harness.start(t) 961 defer harness.stop() 962 963 // Ensure unblock 964 select { 965 case <-harness.mockHooks.UnblockCh: 966 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 967 t.Fatalf("Task unblock should have been called") 968 } 969 970 // Check the file is there 971 actual := fmt.Sprintf("%s.tmpl", harness.node.ID) 972 path := filepath.Join(harness.taskDir, actual) 973 raw, err := ioutil.ReadFile(path) 974 if err != nil { 975 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 976 } 977 978 if s := string(raw); s != content { 979 t.Fatalf("Unexpected template data; got %q, want %q", s, content) 980 } 981 } 982 983 func TestTaskTemplateManager_Signal_Error(t *testing.T) { 984 t.Parallel() 985 require := require.New(t) 986 987 // Make a template that renders based on a key in Consul and sends SIGALRM 988 key1 := "foo" 989 content1 := "bar" 990 content2 := "baz" 991 embedded1 := fmt.Sprintf(`{{key "%s"}}`, key1) 992 file1 := "my.tmpl" 993 template := &structs.Template{ 994 EmbeddedTmpl: embedded1, 995 DestPath: file1, 996 ChangeMode: structs.TemplateChangeModeSignal, 997 ChangeSignal: "SIGALRM", 998 } 999 1000 harness := newTestHarness(t, []*structs.Template{template}, true, false) 1001 harness.start(t) 1002 defer harness.stop() 1003 1004 harness.mockHooks.SignalError = fmt.Errorf("test error") 1005 1006 // Write the key to Consul 1007 harness.consul.SetKV(t, key1, []byte(content1)) 1008 1009 // Wait a little 1010 select { 1011 case <-harness.mockHooks.UnblockCh: 1012 case <-time.After(time.Duration(2*testutil.TestMultiplier()) * time.Second): 1013 t.Fatalf("Should have received unblock: %+v", harness.mockHooks) 1014 } 1015 1016 // Write the key to Consul 1017 harness.consul.SetKV(t, key1, []byte(content2)) 1018 1019 // Wait for kill channel 1020 select { 1021 case <-harness.mockHooks.KillCh: 1022 break 1023 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 1024 t.Fatalf("Should have received a signals: %+v", harness.mockHooks) 1025 } 1026 1027 require.NotNil(harness.mockHooks.KillEvent) 1028 require.Contains(harness.mockHooks.KillEvent.DisplayMessage, "failed to send signals") 1029 } 1030 1031 // TestTaskTemplateManager_FiltersProcessEnvVars asserts that we only render 1032 // environment variables found in task env-vars and not read the nomad host 1033 // process environment variables. nomad host process environment variables 1034 // are to be treated the same as not found environment variables. 1035 func TestTaskTemplateManager_FiltersEnvVars(t *testing.T) { 1036 t.Parallel() 1037 1038 defer os.Setenv("NOMAD_TASK_NAME", os.Getenv("NOMAD_TASK_NAME")) 1039 os.Setenv("NOMAD_TASK_NAME", "should be overridden by task") 1040 1041 testenv := "TESTENV_" + strings.ReplaceAll(uuid.Generate(), "-", "") 1042 os.Setenv(testenv, "MY_TEST_VALUE") 1043 defer os.Unsetenv(testenv) 1044 1045 // Make a template that will render immediately 1046 content := `Hello Nomad Task: {{env "NOMAD_TASK_NAME"}} 1047 TEST_ENV: {{ env "` + testenv + `" }} 1048 TEST_ENV_NOT_FOUND: {{env "` + testenv + `_NOTFOUND" }}` 1049 expected := fmt.Sprintf("Hello Nomad Task: %s\nTEST_ENV: \nTEST_ENV_NOT_FOUND: ", TestTaskName) 1050 1051 file := "my.tmpl" 1052 template := &structs.Template{ 1053 EmbeddedTmpl: content, 1054 DestPath: file, 1055 ChangeMode: structs.TemplateChangeModeNoop, 1056 } 1057 1058 harness := newTestHarness(t, []*structs.Template{template}, false, false) 1059 harness.start(t) 1060 defer harness.stop() 1061 1062 // Wait for the unblock 1063 select { 1064 case <-harness.mockHooks.UnblockCh: 1065 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 1066 require.Fail(t, "Task unblock should have been called") 1067 } 1068 1069 // Check the file is there 1070 path := filepath.Join(harness.taskDir, file) 1071 raw, err := ioutil.ReadFile(path) 1072 require.NoError(t, err) 1073 1074 require.Equal(t, expected, string(raw)) 1075 } 1076 1077 // TestTaskTemplateManager_Env asserts templates with the env flag set are read 1078 // into the task's environment. 1079 func TestTaskTemplateManager_Env(t *testing.T) { 1080 t.Parallel() 1081 template := &structs.Template{ 1082 EmbeddedTmpl: ` 1083 # Comment lines are ok 1084 1085 FOO=bar 1086 foo=123 1087 ANYTHING_goes=Spaces are=ok! 1088 `, 1089 DestPath: "test.env", 1090 ChangeMode: structs.TemplateChangeModeNoop, 1091 Envvars: true, 1092 } 1093 harness := newTestHarness(t, []*structs.Template{template}, true, false) 1094 harness.start(t) 1095 defer harness.stop() 1096 1097 // Wait a little 1098 select { 1099 case <-harness.mockHooks.UnblockCh: 1100 case <-time.After(time.Duration(2*testutil.TestMultiplier()) * time.Second): 1101 t.Fatalf("Should have received unblock: %+v", harness.mockHooks) 1102 } 1103 1104 // Validate environment 1105 env := harness.envBuilder.Build().Map() 1106 if len(env) < 3 { 1107 t.Fatalf("expected at least 3 env vars but found %d:\n%#v\n", len(env), env) 1108 } 1109 if env["FOO"] != "bar" { 1110 t.Errorf("expected FOO=bar but found %q", env["FOO"]) 1111 } 1112 if env["foo"] != "123" { 1113 t.Errorf("expected foo=123 but found %q", env["foo"]) 1114 } 1115 if env["ANYTHING_goes"] != "Spaces are=ok!" { 1116 t.Errorf("expected ANYTHING_GOES='Spaces are ok!' but found %q", env["ANYTHING_goes"]) 1117 } 1118 } 1119 1120 // TestTaskTemplateManager_Env_Missing asserts the core env 1121 // template processing function returns errors when files don't exist 1122 func TestTaskTemplateManager_Env_Missing(t *testing.T) { 1123 t.Parallel() 1124 d, err := ioutil.TempDir("", "ct_env_missing") 1125 if err != nil { 1126 t.Fatalf("err: %v", err) 1127 } 1128 defer os.RemoveAll(d) 1129 1130 // Fake writing the file so we don't have to run the whole template manager 1131 err = ioutil.WriteFile(filepath.Join(d, "exists.env"), []byte("FOO=bar\n"), 0644) 1132 if err != nil { 1133 t.Fatalf("error writing template file: %v", err) 1134 } 1135 1136 templates := []*structs.Template{ 1137 { 1138 EmbeddedTmpl: "FOO=bar\n", 1139 DestPath: "exists.env", 1140 Envvars: true, 1141 }, 1142 { 1143 EmbeddedTmpl: "WHAT=ever\n", 1144 DestPath: "missing.env", 1145 Envvars: true, 1146 }, 1147 } 1148 1149 if vars, err := loadTemplateEnv(templates, d, taskenv.NewEmptyTaskEnv()); err == nil { 1150 t.Fatalf("expected an error but instead got env vars: %#v", vars) 1151 } 1152 } 1153 1154 // TestTaskTemplateManager_Env_InterpolatedDest asserts the core env 1155 // template processing function handles interpolated destinations 1156 func TestTaskTemplateManager_Env_InterpolatedDest(t *testing.T) { 1157 t.Parallel() 1158 require := require.New(t) 1159 1160 d, err := ioutil.TempDir("", "ct_env_interpolated") 1161 if err != nil { 1162 t.Fatalf("err: %v", err) 1163 } 1164 defer os.RemoveAll(d) 1165 1166 // Fake writing the file so we don't have to run the whole template manager 1167 err = ioutil.WriteFile(filepath.Join(d, "exists.env"), []byte("FOO=bar\n"), 0644) 1168 if err != nil { 1169 t.Fatalf("error writing template file: %v", err) 1170 } 1171 1172 templates := []*structs.Template{ 1173 { 1174 EmbeddedTmpl: "FOO=bar\n", 1175 DestPath: "${NOMAD_META_path}.env", 1176 Envvars: true, 1177 }, 1178 } 1179 1180 // Build the env 1181 taskEnv := taskenv.NewTaskEnv( 1182 map[string]string{"NOMAD_META_path": "exists"}, 1183 map[string]string{}, map[string]string{}) 1184 1185 vars, err := loadTemplateEnv(templates, d, taskEnv) 1186 require.NoError(err) 1187 require.Contains(vars, "FOO") 1188 require.Equal(vars["FOO"], "bar") 1189 } 1190 1191 // TestTaskTemplateManager_Env_Multi asserts the core env 1192 // template processing function returns combined env vars from multiple 1193 // templates correctly. 1194 func TestTaskTemplateManager_Env_Multi(t *testing.T) { 1195 t.Parallel() 1196 d, err := ioutil.TempDir("", "ct_env_missing") 1197 if err != nil { 1198 t.Fatalf("err: %v", err) 1199 } 1200 defer os.RemoveAll(d) 1201 1202 // Fake writing the files so we don't have to run the whole template manager 1203 err = ioutil.WriteFile(filepath.Join(d, "zzz.env"), []byte("FOO=bar\nSHARED=nope\n"), 0644) 1204 if err != nil { 1205 t.Fatalf("error writing template file 1: %v", err) 1206 } 1207 err = ioutil.WriteFile(filepath.Join(d, "aaa.env"), []byte("BAR=foo\nSHARED=yup\n"), 0644) 1208 if err != nil { 1209 t.Fatalf("error writing template file 2: %v", err) 1210 } 1211 1212 // Templates will get loaded in order (not alpha sorted) 1213 templates := []*structs.Template{ 1214 { 1215 DestPath: "zzz.env", 1216 Envvars: true, 1217 }, 1218 { 1219 DestPath: "aaa.env", 1220 Envvars: true, 1221 }, 1222 } 1223 1224 vars, err := loadTemplateEnv(templates, d, taskenv.NewEmptyTaskEnv()) 1225 if err != nil { 1226 t.Fatalf("expected no error: %v", err) 1227 } 1228 if vars["FOO"] != "bar" { 1229 t.Errorf("expected FOO=bar but found %q", vars["FOO"]) 1230 } 1231 if vars["BAR"] != "foo" { 1232 t.Errorf("expected BAR=foo but found %q", vars["BAR"]) 1233 } 1234 if vars["SHARED"] != "yup" { 1235 t.Errorf("expected FOO=bar but found %q", vars["yup"]) 1236 } 1237 } 1238 1239 func TestTaskTemplateManager_Rerender_Env(t *testing.T) { 1240 t.Parallel() 1241 // Make a template that renders based on a key in Consul and sends restart 1242 key1 := "bam" 1243 key2 := "bar" 1244 content1_1 := "cat" 1245 content1_2 := "dog" 1246 t1 := &structs.Template{ 1247 EmbeddedTmpl: ` 1248 FOO={{key "bam"}} 1249 `, 1250 DestPath: "test.env", 1251 ChangeMode: structs.TemplateChangeModeRestart, 1252 Envvars: true, 1253 } 1254 t2 := &structs.Template{ 1255 EmbeddedTmpl: ` 1256 BAR={{key "bar"}} 1257 `, 1258 DestPath: "test2.env", 1259 ChangeMode: structs.TemplateChangeModeRestart, 1260 Envvars: true, 1261 } 1262 1263 harness := newTestHarness(t, []*structs.Template{t1, t2}, true, false) 1264 harness.start(t) 1265 defer harness.stop() 1266 1267 // Ensure no unblock 1268 select { 1269 case <-harness.mockHooks.UnblockCh: 1270 t.Fatalf("Task unblock should have not have been called") 1271 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 1272 } 1273 1274 // Write the key to Consul 1275 harness.consul.SetKV(t, key1, []byte(content1_1)) 1276 harness.consul.SetKV(t, key2, []byte(content1_1)) 1277 1278 // Wait for the unblock 1279 select { 1280 case <-harness.mockHooks.UnblockCh: 1281 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 1282 t.Fatalf("Task unblock should have been called") 1283 } 1284 1285 env := harness.envBuilder.Build().Map() 1286 if v, ok := env["FOO"]; !ok || v != content1_1 { 1287 t.Fatalf("Bad env for FOO: %v %v", v, ok) 1288 } 1289 if v, ok := env["BAR"]; !ok || v != content1_1 { 1290 t.Fatalf("Bad env for BAR: %v %v", v, ok) 1291 } 1292 1293 // Update the keys in Consul 1294 harness.consul.SetKV(t, key1, []byte(content1_2)) 1295 1296 // Wait for restart 1297 timeout := time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second) 1298 OUTER: 1299 for { 1300 select { 1301 case <-harness.mockHooks.RestartCh: 1302 break OUTER 1303 case <-harness.mockHooks.SignalCh: 1304 t.Fatalf("Signal with restart policy: %+v", harness.mockHooks) 1305 case <-timeout: 1306 t.Fatalf("Should have received a restart: %+v", harness.mockHooks) 1307 } 1308 } 1309 1310 env = harness.envBuilder.Build().Map() 1311 if v, ok := env["FOO"]; !ok || v != content1_2 { 1312 t.Fatalf("Bad env for FOO: %v %v", v, ok) 1313 } 1314 if v, ok := env["BAR"]; !ok || v != content1_1 { 1315 t.Fatalf("Bad env for BAR: %v %v", v, ok) 1316 } 1317 } 1318 1319 // TestTaskTemplateManager_Config_ServerName asserts the tls_server_name 1320 // setting is propagated to consul-template's configuration. See #2776 1321 func TestTaskTemplateManager_Config_ServerName(t *testing.T) { 1322 t.Parallel() 1323 c := config.DefaultConfig() 1324 c.VaultConfig = &sconfig.VaultConfig{ 1325 Enabled: helper.BoolToPtr(true), 1326 Addr: "https://localhost/", 1327 TLSServerName: "notlocalhost", 1328 } 1329 config := &TaskTemplateManagerConfig{ 1330 ClientConfig: c, 1331 VaultToken: "token", 1332 } 1333 ctconf, err := newRunnerConfig(config, nil) 1334 if err != nil { 1335 t.Fatalf("unexpected error: %v", err) 1336 } 1337 1338 if *ctconf.Vault.SSL.ServerName != c.VaultConfig.TLSServerName { 1339 t.Fatalf("expected %q but found %q", c.VaultConfig.TLSServerName, *ctconf.Vault.SSL.ServerName) 1340 } 1341 } 1342 1343 // TestTaskTemplateManager_Config_VaultGrace asserts the vault_grace setting is 1344 // propagated to consul-template's configuration. 1345 func TestTaskTemplateManager_Config_VaultGrace(t *testing.T) { 1346 t.Parallel() 1347 assert := assert.New(t) 1348 c := config.DefaultConfig() 1349 c.Node = mock.Node() 1350 c.VaultConfig = &sconfig.VaultConfig{ 1351 Enabled: helper.BoolToPtr(true), 1352 Addr: "https://localhost/", 1353 TLSServerName: "notlocalhost", 1354 } 1355 1356 alloc := mock.Alloc() 1357 config := &TaskTemplateManagerConfig{ 1358 ClientConfig: c, 1359 VaultToken: "token", 1360 1361 // Make a template that will render immediately 1362 Templates: []*structs.Template{ 1363 { 1364 EmbeddedTmpl: "bar", 1365 DestPath: "foo", 1366 ChangeMode: structs.TemplateChangeModeNoop, 1367 VaultGrace: 10 * time.Second, 1368 }, 1369 { 1370 EmbeddedTmpl: "baz", 1371 DestPath: "bam", 1372 ChangeMode: structs.TemplateChangeModeNoop, 1373 VaultGrace: 100 * time.Second, 1374 }, 1375 }, 1376 EnvBuilder: taskenv.NewBuilder(c.Node, alloc, alloc.Job.TaskGroups[0].Tasks[0], c.Region), 1377 } 1378 1379 ctmplMapping, err := parseTemplateConfigs(config) 1380 assert.Nil(err, "Parsing Templates") 1381 1382 ctconf, err := newRunnerConfig(config, ctmplMapping) 1383 assert.Nil(err, "Building Runner Config") 1384 assert.NotNil(ctconf.Vault.Grace, "Vault Grace Pointer") 1385 assert.Equal(10*time.Second, *ctconf.Vault.Grace, "Vault Grace Value") 1386 } 1387 1388 // TestTaskTemplateManager_Config_VaultNamespace asserts the Vault namespace setting is 1389 // propagated to consul-template's configuration. 1390 func TestTaskTemplateManager_Config_VaultNamespace(t *testing.T) { 1391 t.Parallel() 1392 assert := assert.New(t) 1393 1394 testNS := "test-namespace" 1395 c := config.DefaultConfig() 1396 c.Node = mock.Node() 1397 c.VaultConfig = &sconfig.VaultConfig{ 1398 Enabled: helper.BoolToPtr(true), 1399 Addr: "https://localhost/", 1400 TLSServerName: "notlocalhost", 1401 Namespace: testNS, 1402 } 1403 1404 alloc := mock.Alloc() 1405 config := &TaskTemplateManagerConfig{ 1406 ClientConfig: c, 1407 VaultToken: "token", 1408 EnvBuilder: taskenv.NewBuilder(c.Node, alloc, alloc.Job.TaskGroups[0].Tasks[0], c.Region), 1409 } 1410 1411 ctmplMapping, err := parseTemplateConfigs(config) 1412 assert.Nil(err, "Parsing Templates") 1413 1414 ctconf, err := newRunnerConfig(config, ctmplMapping) 1415 assert.Nil(err, "Building Runner Config") 1416 assert.NotNil(ctconf.Vault.Grace, "Vault Grace Pointer") 1417 assert.Equal(testNS, *ctconf.Vault.Namespace, "Vault Namespace Value") 1418 } 1419 1420 func TestTaskTemplateManager_BlockedEvents(t *testing.T) { 1421 t.Parallel() 1422 require := require.New(t) 1423 1424 // Make a template that will render based on a key in Consul 1425 var embedded string 1426 for i := 0; i < 5; i++ { 1427 embedded += fmt.Sprintf(`{{key "%d"}}`, i) 1428 } 1429 1430 file := "my.tmpl" 1431 template := &structs.Template{ 1432 EmbeddedTmpl: embedded, 1433 DestPath: file, 1434 ChangeMode: structs.TemplateChangeModeNoop, 1435 } 1436 1437 harness := newTestHarness(t, []*structs.Template{template}, true, false) 1438 harness.setEmitRate(100 * time.Millisecond) 1439 harness.start(t) 1440 defer harness.stop() 1441 1442 // Ensure that we get a blocked event 1443 select { 1444 case <-harness.mockHooks.UnblockCh: 1445 t.Fatalf("Task unblock should have not have been called") 1446 case <-harness.mockHooks.EmitEventCh: 1447 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 1448 t.Fatalf("timeout") 1449 } 1450 1451 // Check to see we got a correct message 1452 require.Len(harness.mockHooks.Events, 1) 1453 require.Contains(harness.mockHooks.Events[0].DisplayMessage, "and 2 more") 1454 1455 // Write 3 keys to Consul 1456 for i := 0; i < 3; i++ { 1457 harness.consul.SetKV(t, fmt.Sprintf("%d", i), []byte{0xa}) 1458 } 1459 1460 // Ensure that we get a blocked event 1461 select { 1462 case <-harness.mockHooks.UnblockCh: 1463 t.Fatalf("Task unblock should have not have been called") 1464 case <-harness.mockHooks.EmitEventCh: 1465 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 1466 t.Fatalf("timeout") 1467 } 1468 1469 // TODO 1470 // Check to see we got a correct message 1471 eventMsg := harness.mockHooks.Events[len(harness.mockHooks.Events)-1].DisplayMessage 1472 if !strings.Contains(eventMsg, "Missing") || strings.Contains(eventMsg, "more") { 1473 t.Fatalf("bad event: %q", eventMsg) 1474 } 1475 }