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