github.com/zhizhiboom/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/client/allocrunner/taskrunner/consul_template_test.go (about)

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