github.com/smithx10/nomad@v0.9.1-rc1/client/allocrunner/taskrunner/template/template_test.go (about)

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