github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/client/consul_template_test.go (about) 1 package client 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "strings" 10 "testing" 11 "time" 12 13 ctestutil "github.com/hashicorp/consul/testutil" 14 "github.com/hashicorp/nomad/client/config" 15 "github.com/hashicorp/nomad/client/driver/env" 16 "github.com/hashicorp/nomad/helper" 17 "github.com/hashicorp/nomad/nomad/mock" 18 "github.com/hashicorp/nomad/nomad/structs" 19 sconfig "github.com/hashicorp/nomad/nomad/structs/config" 20 "github.com/hashicorp/nomad/testutil" 21 ) 22 23 const ( 24 // TestTaskName is the name of the injected task. It should appear in the 25 // environment variable $NOMAD_TASK_NAME 26 TestTaskName = "test-task" 27 ) 28 29 // MockTaskHooks is a mock of the TaskHooks interface useful for testing 30 type MockTaskHooks struct { 31 Restarts int 32 RestartCh chan struct{} 33 34 Signals []os.Signal 35 SignalCh chan struct{} 36 37 // SignalError is returned when Signal is called on the mock hook 38 SignalError error 39 40 UnblockCh chan struct{} 41 Unblocked bool 42 43 KillReason string 44 KillCh chan struct{} 45 } 46 47 func NewMockTaskHooks() *MockTaskHooks { 48 return &MockTaskHooks{ 49 UnblockCh: make(chan struct{}, 1), 50 RestartCh: make(chan struct{}, 1), 51 SignalCh: make(chan struct{}, 1), 52 KillCh: make(chan struct{}, 1), 53 } 54 } 55 func (m *MockTaskHooks) Restart(source, reason string) { 56 m.Restarts++ 57 select { 58 case m.RestartCh <- struct{}{}: 59 default: 60 } 61 } 62 63 func (m *MockTaskHooks) Signal(source, reason string, s os.Signal) error { 64 m.Signals = append(m.Signals, s) 65 select { 66 case m.SignalCh <- struct{}{}: 67 default: 68 } 69 70 return m.SignalError 71 } 72 73 func (m *MockTaskHooks) Kill(source, reason string, fail bool) { 74 m.KillReason = reason 75 select { 76 case m.KillCh <- struct{}{}: 77 default: 78 } 79 } 80 81 func (m *MockTaskHooks) UnblockStart(source string) { 82 if !m.Unblocked { 83 close(m.UnblockCh) 84 } 85 86 m.Unblocked = true 87 } 88 89 // testHarness is used to test the TaskTemplateManager by spinning up 90 // Consul/Vault as needed 91 type testHarness struct { 92 manager *TaskTemplateManager 93 mockHooks *MockTaskHooks 94 templates []*structs.Template 95 envBuilder *env.Builder 96 node *structs.Node 97 config *config.Config 98 vaultToken string 99 taskDir string 100 vault *testutil.TestVault 101 consul *ctestutil.TestServer 102 } 103 104 // newTestHarness returns a harness starting a dev consul and vault server, 105 // building the appropriate config and creating a TaskTemplateManager 106 func newTestHarness(t *testing.T, templates []*structs.Template, consul, vault bool) *testHarness { 107 region := "global" 108 harness := &testHarness{ 109 mockHooks: NewMockTaskHooks(), 110 templates: templates, 111 node: mock.Node(), 112 config: &config.Config{Region: region}, 113 } 114 115 // Build the task environment 116 a := mock.Alloc() 117 task := a.Job.TaskGroups[0].Tasks[0] 118 task.Name = TestTaskName 119 harness.envBuilder = env.NewBuilder(harness.node, a, task, region) 120 121 // Make a tempdir 122 d, err := ioutil.TempDir("", "ct_test") 123 if err != nil { 124 t.Fatalf("Failed to make tmpdir: %v", err) 125 } 126 harness.taskDir = d 127 128 if consul { 129 harness.consul, err = ctestutil.NewTestServer() 130 if err != nil { 131 t.Fatalf("error starting test Consul server: %v", err) 132 } 133 harness.config.ConsulConfig = &sconfig.ConsulConfig{ 134 Addr: harness.consul.HTTPAddr, 135 } 136 } 137 138 if vault { 139 harness.vault = testutil.NewTestVault(t).Start() 140 harness.config.VaultConfig = harness.vault.Config 141 harness.vaultToken = harness.vault.RootToken 142 } 143 144 return harness 145 } 146 147 func (h *testHarness) start(t *testing.T) { 148 manager, err := NewTaskTemplateManager(h.mockHooks, h.templates, 149 h.config, h.vaultToken, h.taskDir, h.envBuilder) 150 if err != nil { 151 t.Fatalf("failed to build task template manager: %v", err) 152 } 153 154 h.manager = manager 155 } 156 157 func (h *testHarness) startWithErr() error { 158 manager, err := NewTaskTemplateManager(h.mockHooks, h.templates, 159 h.config, h.vaultToken, h.taskDir, h.envBuilder) 160 h.manager = manager 161 return err 162 } 163 164 // stop is used to stop any running Vault or Consul server plus the task manager 165 func (h *testHarness) stop() { 166 if h.vault != nil { 167 h.vault.Stop() 168 } 169 if h.consul != nil { 170 h.consul.Stop() 171 } 172 if h.manager != nil { 173 h.manager.Stop() 174 } 175 if h.taskDir != "" { 176 os.RemoveAll(h.taskDir) 177 } 178 } 179 180 func TestTaskTemplateManager_Invalid(t *testing.T) { 181 t.Parallel() 182 hooks := NewMockTaskHooks() 183 var tmpls []*structs.Template 184 region := "global" 185 config := &config.Config{Region: region} 186 taskDir := "foo" 187 vaultToken := "" 188 a := mock.Alloc() 189 envBuilder := env.NewBuilder(mock.Node(), a, a.Job.TaskGroups[0].Tasks[0], config.Region) 190 191 _, err := NewTaskTemplateManager(nil, nil, nil, "", "", nil) 192 if err == nil { 193 t.Fatalf("Expected error") 194 } 195 196 _, err = NewTaskTemplateManager(nil, tmpls, config, vaultToken, taskDir, envBuilder) 197 if err == nil || !strings.Contains(err.Error(), "task hook") { 198 t.Fatalf("Expected invalid task hook error: %v", err) 199 } 200 201 _, err = NewTaskTemplateManager(hooks, tmpls, nil, vaultToken, taskDir, envBuilder) 202 if err == nil || !strings.Contains(err.Error(), "config") { 203 t.Fatalf("Expected invalid config error: %v", err) 204 } 205 206 _, err = NewTaskTemplateManager(hooks, tmpls, config, vaultToken, "", envBuilder) 207 if err == nil || !strings.Contains(err.Error(), "task directory") { 208 t.Fatalf("Expected invalid task dir error: %v", err) 209 } 210 211 _, err = NewTaskTemplateManager(hooks, tmpls, config, vaultToken, taskDir, nil) 212 if err == nil || !strings.Contains(err.Error(), "task environment") { 213 t.Fatalf("Expected invalid task environment error: %v", err) 214 } 215 216 tm, err := NewTaskTemplateManager(hooks, tmpls, config, vaultToken, taskDir, envBuilder) 217 if err != nil { 218 t.Fatalf("Unexpected error: %v", err) 219 } else if tm == nil { 220 t.Fatalf("Bad %v", tm) 221 } 222 223 // Build a template with a bad signal 224 tmpl := &structs.Template{ 225 DestPath: "foo", 226 EmbeddedTmpl: "hello, world", 227 ChangeMode: structs.TemplateChangeModeSignal, 228 ChangeSignal: "foobarbaz", 229 } 230 231 tmpls = append(tmpls, tmpl) 232 tm, err = NewTaskTemplateManager(hooks, tmpls, config, vaultToken, taskDir, envBuilder) 233 if err == nil || !strings.Contains(err.Error(), "Failed to parse signal") { 234 t.Fatalf("Expected signal parsing error: %v", err) 235 } 236 } 237 238 func TestTaskTemplateManager_HostPath(t *testing.T) { 239 t.Parallel() 240 // Make a template that will render immediately and write it to a tmp file 241 f, err := ioutil.TempFile("", "") 242 if err != nil { 243 t.Fatalf("Bad: %v", err) 244 } 245 defer f.Close() 246 defer os.Remove(f.Name()) 247 248 content := "hello, world!" 249 if _, err := io.WriteString(f, content); err != nil { 250 t.Fatalf("Bad: %v", err) 251 } 252 253 file := "my.tmpl" 254 template := &structs.Template{ 255 SourcePath: f.Name(), 256 DestPath: file, 257 ChangeMode: structs.TemplateChangeModeNoop, 258 } 259 260 harness := newTestHarness(t, []*structs.Template{template}, false, false) 261 harness.start(t) 262 defer harness.stop() 263 264 // Wait for the unblock 265 select { 266 case <-harness.mockHooks.UnblockCh: 267 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 268 t.Fatalf("Task unblock should have been called") 269 } 270 271 // Check the file is there 272 path := filepath.Join(harness.taskDir, file) 273 raw, err := ioutil.ReadFile(path) 274 if err != nil { 275 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 276 } 277 278 if s := string(raw); s != content { 279 t.Fatalf("Unexpected template data; got %q, want %q", s, content) 280 } 281 282 // Change the config to disallow host sources 283 harness = newTestHarness(t, []*structs.Template{template}, false, false) 284 harness.config.Options = map[string]string{ 285 hostSrcOption: "false", 286 } 287 if err := harness.startWithErr(); err == nil || !strings.Contains(err.Error(), "absolute") { 288 t.Fatalf("Expected absolute template path disallowed: %v", err) 289 } 290 } 291 292 func TestTaskTemplateManager_Unblock_Static(t *testing.T) { 293 t.Parallel() 294 // Make a template that will render immediately 295 content := "hello, world!" 296 file := "my.tmpl" 297 template := &structs.Template{ 298 EmbeddedTmpl: content, 299 DestPath: file, 300 ChangeMode: structs.TemplateChangeModeNoop, 301 } 302 303 harness := newTestHarness(t, []*structs.Template{template}, false, false) 304 harness.start(t) 305 defer harness.stop() 306 307 // Wait for the unblock 308 select { 309 case <-harness.mockHooks.UnblockCh: 310 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 311 t.Fatalf("Task unblock should have been called") 312 } 313 314 // Check the file is there 315 path := filepath.Join(harness.taskDir, file) 316 raw, err := ioutil.ReadFile(path) 317 if err != nil { 318 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 319 } 320 321 if s := string(raw); s != content { 322 t.Fatalf("Unexpected template data; got %q, want %q", s, content) 323 } 324 } 325 326 func TestTaskTemplateManager_Permissions(t *testing.T) { 327 t.Parallel() 328 // Make a template that will render immediately 329 content := "hello, world!" 330 file := "my.tmpl" 331 template := &structs.Template{ 332 EmbeddedTmpl: content, 333 DestPath: file, 334 ChangeMode: structs.TemplateChangeModeNoop, 335 Perms: "777", 336 } 337 338 harness := newTestHarness(t, []*structs.Template{template}, false, false) 339 harness.start(t) 340 defer harness.stop() 341 342 // Wait for the unblock 343 select { 344 case <-harness.mockHooks.UnblockCh: 345 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 346 t.Fatalf("Task unblock should have been called") 347 } 348 349 // Check the file is there 350 path := filepath.Join(harness.taskDir, file) 351 fi, err := os.Stat(path) 352 if err != nil { 353 t.Fatalf("Failed to stat file: %v", err) 354 } 355 356 if m := fi.Mode(); m != os.ModePerm { 357 t.Fatalf("Got mode %v; want %v", m, os.ModePerm) 358 } 359 } 360 361 func TestTaskTemplateManager_Unblock_Static_NomadEnv(t *testing.T) { 362 t.Parallel() 363 // Make a template that will render immediately 364 content := `Hello Nomad Task: {{env "NOMAD_TASK_NAME"}}` 365 expected := fmt.Sprintf("Hello Nomad Task: %s", TestTaskName) 366 file := "my.tmpl" 367 template := &structs.Template{ 368 EmbeddedTmpl: content, 369 DestPath: file, 370 ChangeMode: structs.TemplateChangeModeNoop, 371 } 372 373 harness := newTestHarness(t, []*structs.Template{template}, false, false) 374 harness.start(t) 375 defer harness.stop() 376 377 // Wait for the unblock 378 select { 379 case <-harness.mockHooks.UnblockCh: 380 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 381 t.Fatalf("Task unblock should have been called") 382 } 383 384 // Check the file is there 385 path := filepath.Join(harness.taskDir, file) 386 raw, err := ioutil.ReadFile(path) 387 if err != nil { 388 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 389 } 390 391 if s := string(raw); s != expected { 392 t.Fatalf("Unexpected template data; got %q, want %q", s, expected) 393 } 394 } 395 396 func TestTaskTemplateManager_Unblock_Static_AlreadyRendered(t *testing.T) { 397 t.Parallel() 398 // Make a template that will render immediately 399 content := "hello, world!" 400 file := "my.tmpl" 401 template := &structs.Template{ 402 EmbeddedTmpl: content, 403 DestPath: file, 404 ChangeMode: structs.TemplateChangeModeNoop, 405 } 406 407 harness := newTestHarness(t, []*structs.Template{template}, false, false) 408 409 // Write the contents 410 path := filepath.Join(harness.taskDir, file) 411 if err := ioutil.WriteFile(path, []byte(content), 0777); err != nil { 412 t.Fatalf("Failed to write data: %v", err) 413 } 414 415 harness.start(t) 416 defer harness.stop() 417 418 // Wait for the unblock 419 select { 420 case <-harness.mockHooks.UnblockCh: 421 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 422 t.Fatalf("Task unblock should have been called") 423 } 424 425 // Check the file is there 426 path = filepath.Join(harness.taskDir, file) 427 raw, err := ioutil.ReadFile(path) 428 if err != nil { 429 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 430 } 431 432 if s := string(raw); s != content { 433 t.Fatalf("Unexpected template data; got %q, want %q", s, content) 434 } 435 } 436 437 func TestTaskTemplateManager_Unblock_Consul(t *testing.T) { 438 t.Parallel() 439 // Make a template that will render based on a key in Consul 440 key := "foo" 441 content := "barbaz" 442 embedded := fmt.Sprintf(`{{key "%s"}}`, key) 443 file := "my.tmpl" 444 template := &structs.Template{ 445 EmbeddedTmpl: embedded, 446 DestPath: file, 447 ChangeMode: structs.TemplateChangeModeNoop, 448 } 449 450 // Drop the retry rate 451 testRetryRate = 10 * time.Millisecond 452 453 harness := newTestHarness(t, []*structs.Template{template}, true, false) 454 harness.start(t) 455 defer harness.stop() 456 457 // Ensure no unblock 458 select { 459 case <-harness.mockHooks.UnblockCh: 460 t.Fatalf("Task unblock should have not have been called") 461 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 462 } 463 464 // Write the key to Consul 465 harness.consul.SetKV(t, key, []byte(content)) 466 467 // Wait for the unblock 468 select { 469 case <-harness.mockHooks.UnblockCh: 470 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 471 t.Fatalf("Task unblock should have been called") 472 } 473 474 // Check the file is there 475 path := filepath.Join(harness.taskDir, file) 476 raw, err := ioutil.ReadFile(path) 477 if err != nil { 478 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 479 } 480 481 if s := string(raw); s != content { 482 t.Fatalf("Unexpected template data; got %q, want %q", s, content) 483 } 484 } 485 486 func TestTaskTemplateManager_Unblock_Vault(t *testing.T) { 487 t.Parallel() 488 // Make a template that will render based on a key in Vault 489 vaultPath := "secret/password" 490 key := "password" 491 content := "barbaz" 492 embedded := fmt.Sprintf(`{{with secret "%s"}}{{.Data.%s}}{{end}}`, vaultPath, key) 493 file := "my.tmpl" 494 template := &structs.Template{ 495 EmbeddedTmpl: embedded, 496 DestPath: file, 497 ChangeMode: structs.TemplateChangeModeNoop, 498 } 499 500 // Drop the retry rate 501 testRetryRate = 10 * time.Millisecond 502 503 harness := newTestHarness(t, []*structs.Template{template}, false, true) 504 harness.start(t) 505 defer harness.stop() 506 507 // Ensure no unblock 508 select { 509 case <-harness.mockHooks.UnblockCh: 510 t.Fatalf("Task unblock should not have been called") 511 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 512 } 513 514 // Write the secret to Vault 515 logical := harness.vault.Client.Logical() 516 logical.Write(vaultPath, map[string]interface{}{key: content}) 517 518 // Wait for the unblock 519 select { 520 case <-harness.mockHooks.UnblockCh: 521 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 522 t.Fatalf("Task unblock should have been called") 523 } 524 525 // Check the file is there 526 path := filepath.Join(harness.taskDir, file) 527 raw, err := ioutil.ReadFile(path) 528 if err != nil { 529 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 530 } 531 532 if s := string(raw); s != content { 533 t.Fatalf("Unexpected template data; got %q, want %q", s, content) 534 } 535 } 536 537 func TestTaskTemplateManager_Unblock_Multi_Template(t *testing.T) { 538 t.Parallel() 539 // Make a template that will render immediately 540 staticContent := "hello, world!" 541 staticFile := "my.tmpl" 542 template := &structs.Template{ 543 EmbeddedTmpl: staticContent, 544 DestPath: staticFile, 545 ChangeMode: structs.TemplateChangeModeNoop, 546 } 547 548 // Make a template that will render based on a key in Consul 549 consulKey := "foo" 550 consulContent := "barbaz" 551 consulEmbedded := fmt.Sprintf(`{{key "%s"}}`, consulKey) 552 consulFile := "consul.tmpl" 553 template2 := &structs.Template{ 554 EmbeddedTmpl: consulEmbedded, 555 DestPath: consulFile, 556 ChangeMode: structs.TemplateChangeModeNoop, 557 } 558 559 // Drop the retry rate 560 testRetryRate = 10 * time.Millisecond 561 562 harness := newTestHarness(t, []*structs.Template{template, template2}, true, false) 563 harness.start(t) 564 defer harness.stop() 565 566 // Ensure no unblock 567 select { 568 case <-harness.mockHooks.UnblockCh: 569 t.Fatalf("Task unblock should have not have been called") 570 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 571 } 572 573 // Check that the static file has been rendered 574 path := filepath.Join(harness.taskDir, staticFile) 575 raw, err := ioutil.ReadFile(path) 576 if err != nil { 577 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 578 } 579 580 if s := string(raw); s != staticContent { 581 t.Fatalf("Unexpected template data; got %q, want %q", s, staticContent) 582 } 583 584 // Write the key to Consul 585 harness.consul.SetKV(t, consulKey, []byte(consulContent)) 586 587 // Wait for the unblock 588 select { 589 case <-harness.mockHooks.UnblockCh: 590 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 591 t.Fatalf("Task unblock should have been called") 592 } 593 594 // Check the consul file is there 595 path = filepath.Join(harness.taskDir, consulFile) 596 raw, err = ioutil.ReadFile(path) 597 if err != nil { 598 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 599 } 600 601 if s := string(raw); s != consulContent { 602 t.Fatalf("Unexpected template data; got %q, want %q", s, consulContent) 603 } 604 } 605 606 func TestTaskTemplateManager_Rerender_Noop(t *testing.T) { 607 t.Parallel() 608 // Make a template that will render based on a key in Consul 609 key := "foo" 610 content1 := "bar" 611 content2 := "baz" 612 embedded := fmt.Sprintf(`{{key "%s"}}`, key) 613 file := "my.tmpl" 614 template := &structs.Template{ 615 EmbeddedTmpl: embedded, 616 DestPath: file, 617 ChangeMode: structs.TemplateChangeModeNoop, 618 } 619 620 // Drop the retry rate 621 testRetryRate = 10 * time.Millisecond 622 623 harness := newTestHarness(t, []*structs.Template{template}, true, false) 624 harness.start(t) 625 defer harness.stop() 626 627 // Ensure no unblock 628 select { 629 case <-harness.mockHooks.UnblockCh: 630 t.Fatalf("Task unblock should have not have been called") 631 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 632 } 633 634 // Write the key to Consul 635 harness.consul.SetKV(t, key, []byte(content1)) 636 637 // Wait for the unblock 638 select { 639 case <-harness.mockHooks.UnblockCh: 640 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 641 t.Fatalf("Task unblock should have been called") 642 } 643 644 // Check the file is there 645 path := filepath.Join(harness.taskDir, file) 646 raw, err := ioutil.ReadFile(path) 647 if err != nil { 648 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 649 } 650 651 if s := string(raw); s != content1 { 652 t.Fatalf("Unexpected template data; got %q, want %q", s, content1) 653 } 654 655 // Update the key in Consul 656 harness.consul.SetKV(t, key, []byte(content2)) 657 658 select { 659 case <-harness.mockHooks.RestartCh: 660 t.Fatalf("Noop ignored: %+v", harness.mockHooks) 661 case <-harness.mockHooks.SignalCh: 662 t.Fatalf("Noop ignored: %+v", harness.mockHooks) 663 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 664 } 665 666 // Check the file has been updated 667 path = filepath.Join(harness.taskDir, file) 668 raw, err = ioutil.ReadFile(path) 669 if err != nil { 670 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 671 } 672 673 if s := string(raw); s != content2 { 674 t.Fatalf("Unexpected template data; got %q, want %q", s, content2) 675 } 676 } 677 678 func TestTaskTemplateManager_Rerender_Signal(t *testing.T) { 679 t.Parallel() 680 // Make a template that renders based on a key in Consul and sends SIGALRM 681 key1 := "foo" 682 content1_1 := "bar" 683 content1_2 := "baz" 684 embedded1 := fmt.Sprintf(`{{key "%s"}}`, key1) 685 file1 := "my.tmpl" 686 template := &structs.Template{ 687 EmbeddedTmpl: embedded1, 688 DestPath: file1, 689 ChangeMode: structs.TemplateChangeModeSignal, 690 ChangeSignal: "SIGALRM", 691 } 692 693 // Make a template that renders based on a key in Consul and sends SIGBUS 694 key2 := "bam" 695 content2_1 := "cat" 696 content2_2 := "dog" 697 embedded2 := fmt.Sprintf(`{{key "%s"}}`, key2) 698 file2 := "my-second.tmpl" 699 template2 := &structs.Template{ 700 EmbeddedTmpl: embedded2, 701 DestPath: file2, 702 ChangeMode: structs.TemplateChangeModeSignal, 703 ChangeSignal: "SIGBUS", 704 } 705 706 // Drop the retry rate 707 testRetryRate = 10 * time.Millisecond 708 709 harness := newTestHarness(t, []*structs.Template{template, template2}, true, false) 710 harness.start(t) 711 defer harness.stop() 712 713 // Ensure no unblock 714 select { 715 case <-harness.mockHooks.UnblockCh: 716 t.Fatalf("Task unblock should have not have been called") 717 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 718 } 719 720 // Write the key to Consul 721 harness.consul.SetKV(t, key1, []byte(content1_1)) 722 harness.consul.SetKV(t, key2, []byte(content2_1)) 723 724 // Wait for the unblock 725 select { 726 case <-harness.mockHooks.UnblockCh: 727 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 728 t.Fatalf("Task unblock should have been called") 729 } 730 731 if len(harness.mockHooks.Signals) != 0 { 732 t.Fatalf("Should not have received any signals: %+v", harness.mockHooks) 733 } 734 735 // Update the keys in Consul 736 harness.consul.SetKV(t, key1, []byte(content1_2)) 737 harness.consul.SetKV(t, key2, []byte(content2_2)) 738 739 // Wait for signals 740 timeout := time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second) 741 OUTER: 742 for { 743 select { 744 case <-harness.mockHooks.RestartCh: 745 t.Fatalf("Restart with signal policy: %+v", harness.mockHooks) 746 case <-harness.mockHooks.SignalCh: 747 if len(harness.mockHooks.Signals) != 2 { 748 continue 749 } 750 break OUTER 751 case <-timeout: 752 t.Fatalf("Should have received two signals: %+v", harness.mockHooks) 753 } 754 } 755 756 // Check the files have been updated 757 path := filepath.Join(harness.taskDir, file1) 758 raw, err := ioutil.ReadFile(path) 759 if err != nil { 760 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 761 } 762 763 if s := string(raw); s != content1_2 { 764 t.Fatalf("Unexpected template data; got %q, want %q", s, content1_2) 765 } 766 767 path = filepath.Join(harness.taskDir, file2) 768 raw, err = ioutil.ReadFile(path) 769 if err != nil { 770 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 771 } 772 773 if s := string(raw); s != content2_2 { 774 t.Fatalf("Unexpected template data; got %q, want %q", s, content2_2) 775 } 776 } 777 778 func TestTaskTemplateManager_Rerender_Restart(t *testing.T) { 779 t.Parallel() 780 // Make a template that renders based on a key in Consul and sends restart 781 key1 := "bam" 782 content1_1 := "cat" 783 content1_2 := "dog" 784 embedded1 := fmt.Sprintf(`{{key "%s"}}`, key1) 785 file1 := "my.tmpl" 786 template := &structs.Template{ 787 EmbeddedTmpl: embedded1, 788 DestPath: file1, 789 ChangeMode: structs.TemplateChangeModeRestart, 790 } 791 792 // Drop the retry rate 793 testRetryRate = 10 * time.Millisecond 794 795 harness := newTestHarness(t, []*structs.Template{template}, true, false) 796 harness.start(t) 797 defer harness.stop() 798 799 // Ensure no unblock 800 select { 801 case <-harness.mockHooks.UnblockCh: 802 t.Fatalf("Task unblock should have not have been called") 803 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 804 } 805 806 // Write the key to Consul 807 harness.consul.SetKV(t, key1, []byte(content1_1)) 808 809 // Wait for the unblock 810 select { 811 case <-harness.mockHooks.UnblockCh: 812 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 813 t.Fatalf("Task unblock should have been called") 814 } 815 816 // Update the keys in Consul 817 harness.consul.SetKV(t, key1, []byte(content1_2)) 818 819 // Wait for restart 820 timeout := time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second) 821 OUTER: 822 for { 823 select { 824 case <-harness.mockHooks.RestartCh: 825 break OUTER 826 case <-harness.mockHooks.SignalCh: 827 t.Fatalf("Signal with restart policy: %+v", harness.mockHooks) 828 case <-timeout: 829 t.Fatalf("Should have received a restart: %+v", harness.mockHooks) 830 } 831 } 832 833 // Check the files have been updated 834 path := filepath.Join(harness.taskDir, file1) 835 raw, err := ioutil.ReadFile(path) 836 if err != nil { 837 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 838 } 839 840 if s := string(raw); s != content1_2 { 841 t.Fatalf("Unexpected template data; got %q, want %q", s, content1_2) 842 } 843 } 844 845 func TestTaskTemplateManager_Interpolate_Destination(t *testing.T) { 846 t.Parallel() 847 // Make a template that will have its destination interpolated 848 content := "hello, world!" 849 file := "${node.unique.id}.tmpl" 850 template := &structs.Template{ 851 EmbeddedTmpl: content, 852 DestPath: file, 853 ChangeMode: structs.TemplateChangeModeNoop, 854 } 855 856 harness := newTestHarness(t, []*structs.Template{template}, false, false) 857 harness.start(t) 858 defer harness.stop() 859 860 // Ensure unblock 861 select { 862 case <-harness.mockHooks.UnblockCh: 863 case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second): 864 t.Fatalf("Task unblock should have been called") 865 } 866 867 // Check the file is there 868 actual := fmt.Sprintf("%s.tmpl", harness.node.ID) 869 path := filepath.Join(harness.taskDir, actual) 870 raw, err := ioutil.ReadFile(path) 871 if err != nil { 872 t.Fatalf("Failed to read rendered template from %q: %v", path, err) 873 } 874 875 if s := string(raw); s != content { 876 t.Fatalf("Unexpected template data; got %q, want %q", s, content) 877 } 878 } 879 880 func TestTaskTemplateManager_Signal_Error(t *testing.T) { 881 t.Parallel() 882 // Make a template that renders based on a key in Consul and sends SIGALRM 883 key1 := "foo" 884 content1 := "bar" 885 content2 := "baz" 886 embedded1 := fmt.Sprintf(`{{key "%s"}}`, key1) 887 file1 := "my.tmpl" 888 template := &structs.Template{ 889 EmbeddedTmpl: embedded1, 890 DestPath: file1, 891 ChangeMode: structs.TemplateChangeModeSignal, 892 ChangeSignal: "SIGALRM", 893 } 894 895 // Drop the retry rate 896 testRetryRate = 10 * time.Millisecond 897 898 harness := newTestHarness(t, []*structs.Template{template}, true, false) 899 harness.start(t) 900 defer harness.stop() 901 902 harness.mockHooks.SignalError = fmt.Errorf("test error") 903 904 // Write the key to Consul 905 harness.consul.SetKV(t, key1, []byte(content1)) 906 907 // Wait a little 908 select { 909 case <-harness.mockHooks.UnblockCh: 910 case <-time.After(time.Duration(2*testutil.TestMultiplier()) * time.Second): 911 t.Fatalf("Should have received unblock: %+v", harness.mockHooks) 912 } 913 914 // Write the key to Consul 915 harness.consul.SetKV(t, key1, []byte(content2)) 916 917 // Wait for kill channel 918 select { 919 case <-harness.mockHooks.KillCh: 920 break 921 case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second): 922 t.Fatalf("Should have received a signals: %+v", harness.mockHooks) 923 } 924 925 if !strings.Contains(harness.mockHooks.KillReason, "Sending signals") { 926 t.Fatalf("Unexpected error: %v", harness.mockHooks.KillReason) 927 } 928 } 929 930 // TestTaskTemplateManager_Env asserts templates with the env flag set are read 931 // into the task's environment. 932 func TestTaskTemplateManager_Env(t *testing.T) { 933 t.Parallel() 934 template := &structs.Template{ 935 EmbeddedTmpl: ` 936 # Comment lines are ok 937 938 FOO=bar 939 foo=123 940 ANYTHING_goes=Spaces are=ok! 941 `, 942 DestPath: "test.env", 943 ChangeMode: structs.TemplateChangeModeNoop, 944 Envvars: true, 945 } 946 harness := newTestHarness(t, []*structs.Template{template}, true, false) 947 harness.start(t) 948 defer harness.stop() 949 950 // Wait a little 951 select { 952 case <-harness.mockHooks.UnblockCh: 953 case <-time.After(time.Duration(2*testutil.TestMultiplier()) * time.Second): 954 t.Fatalf("Should have received unblock: %+v", harness.mockHooks) 955 } 956 957 // Validate environment 958 env := harness.envBuilder.Build().Map() 959 if len(env) < 3 { 960 t.Fatalf("expected at least 3 env vars but found %d:\n%#v\n", len(env), env) 961 } 962 if env["FOO"] != "bar" { 963 t.Errorf("expected FOO=bar but found %q", env["FOO"]) 964 } 965 if env["foo"] != "123" { 966 t.Errorf("expected foo=123 but found %q", env["foo"]) 967 } 968 if env["ANYTHING_goes"] != "Spaces are=ok!" { 969 t.Errorf("expected ANYTHING_GOES='Spaces are ok!' but found %q", env["ANYTHING_goes"]) 970 } 971 } 972 973 // TestTaskTemplateManager_Env_Missing asserts the core env 974 // template processing function returns errors when files don't exist 975 func TestTaskTemplateManager_Env_Missing(t *testing.T) { 976 t.Parallel() 977 d, err := ioutil.TempDir("", "ct_env_missing") 978 if err != nil { 979 t.Fatalf("err: %v", err) 980 } 981 defer os.RemoveAll(d) 982 983 // Fake writing the file so we don't have to run the whole template manager 984 err = ioutil.WriteFile(filepath.Join(d, "exists.env"), []byte("FOO=bar\n"), 0644) 985 if err != nil { 986 t.Fatalf("error writing template file: %v", err) 987 } 988 989 templates := []*structs.Template{ 990 { 991 EmbeddedTmpl: "FOO=bar\n", 992 DestPath: "exists.env", 993 Envvars: true, 994 }, 995 { 996 EmbeddedTmpl: "WHAT=ever\n", 997 DestPath: "missing.env", 998 Envvars: true, 999 }, 1000 } 1001 1002 if vars, err := loadTemplateEnv(templates, d); err == nil { 1003 t.Fatalf("expected an error but instead got env vars: %#v", vars) 1004 } 1005 } 1006 1007 // TestTaskTemplateManager_Env_Multi asserts the core env 1008 // template processing function returns combined env vars from multiple 1009 // templates correctly. 1010 func TestTaskTemplateManager_Env_Multi(t *testing.T) { 1011 t.Parallel() 1012 d, err := ioutil.TempDir("", "ct_env_missing") 1013 if err != nil { 1014 t.Fatalf("err: %v", err) 1015 } 1016 defer os.RemoveAll(d) 1017 1018 // Fake writing the files so we don't have to run the whole template manager 1019 err = ioutil.WriteFile(filepath.Join(d, "zzz.env"), []byte("FOO=bar\nSHARED=nope\n"), 0644) 1020 if err != nil { 1021 t.Fatalf("error writing template file 1: %v", err) 1022 } 1023 err = ioutil.WriteFile(filepath.Join(d, "aaa.env"), []byte("BAR=foo\nSHARED=yup\n"), 0644) 1024 if err != nil { 1025 t.Fatalf("error writing template file 2: %v", err) 1026 } 1027 1028 // Templates will get loaded in order (not alpha sorted) 1029 templates := []*structs.Template{ 1030 { 1031 DestPath: "zzz.env", 1032 Envvars: true, 1033 }, 1034 { 1035 DestPath: "aaa.env", 1036 Envvars: true, 1037 }, 1038 } 1039 1040 vars, err := loadTemplateEnv(templates, d) 1041 if err != nil { 1042 t.Fatalf("expected an error but instead got env vars: %#v", vars) 1043 } 1044 if vars["FOO"] != "bar" { 1045 t.Errorf("expected FOO=bar but found %q", vars["FOO"]) 1046 } 1047 if vars["BAR"] != "foo" { 1048 t.Errorf("expected BAR=foo but found %q", vars["BAR"]) 1049 } 1050 if vars["SHARED"] != "yup" { 1051 t.Errorf("expected FOO=bar but found %q", vars["yup"]) 1052 } 1053 } 1054 1055 // TestTaskTemplateManager_Config_ServerName asserts the tls_server_name 1056 // setting is propogated to consul-template's configuration. See #2776 1057 func TestTaskTemplateManager_Config_ServerName(t *testing.T) { 1058 t.Parallel() 1059 c := config.DefaultConfig() 1060 c.VaultConfig = &sconfig.VaultConfig{ 1061 Enabled: helper.BoolToPtr(true), 1062 Addr: "https://localhost/", 1063 TLSServerName: "notlocalhost", 1064 } 1065 ctconf, err := runnerConfig(c, "token") 1066 if err != nil { 1067 t.Fatalf("unexpected error: %v", err) 1068 } 1069 1070 if *ctconf.Vault.SSL.ServerName != c.VaultConfig.TLSServerName { 1071 t.Fatalf("expected %q but found %q", c.VaultConfig.TLSServerName, *ctconf.Vault.SSL.ServerName) 1072 } 1073 }