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