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  }