github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/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  	"reflect"
    11  	"regexp"
    12  	"sort"
    13  	"strconv"
    14  	"strings"
    15  	"sync"
    16  	"testing"
    17  	"time"
    18  
    19  	ctestutil "github.com/hashicorp/consul/sdk/testutil"
    20  	"github.com/hashicorp/nomad/client/allocdir"
    21  	"github.com/hashicorp/nomad/client/config"
    22  	"github.com/hashicorp/nomad/client/taskenv"
    23  	"github.com/hashicorp/nomad/helper"
    24  	"github.com/hashicorp/nomad/helper/testlog"
    25  	"github.com/hashicorp/nomad/helper/uuid"
    26  	"github.com/hashicorp/nomad/nomad/mock"
    27  	"github.com/hashicorp/nomad/nomad/structs"
    28  	sconfig "github.com/hashicorp/nomad/nomad/structs/config"
    29  	"github.com/hashicorp/nomad/testutil"
    30  	"github.com/kr/pretty"
    31  	"github.com/stretchr/testify/assert"
    32  	"github.com/stretchr/testify/require"
    33  )
    34  
    35  const (
    36  	// TestTaskName is the name of the injected task. It should appear in the
    37  	// environment variable $NOMAD_TASK_NAME
    38  	TestTaskName = "test-task"
    39  )
    40  
    41  // MockTaskHooks is a mock of the TaskHooks interface useful for testing
    42  type MockTaskHooks struct {
    43  	Restarts  int
    44  	RestartCh chan struct{}
    45  
    46  	Signals    []string
    47  	SignalCh   chan struct{}
    48  	signalLock sync.Mutex
    49  
    50  	// SignalError is returned when Signal is called on the mock hook
    51  	SignalError error
    52  
    53  	UnblockCh chan struct{}
    54  
    55  	KillEvent *structs.TaskEvent
    56  	KillCh    chan struct{}
    57  
    58  	Events      []*structs.TaskEvent
    59  	EmitEventCh chan *structs.TaskEvent
    60  
    61  	// hasHandle can be set to simulate restoring a task after client restart
    62  	hasHandle bool
    63  }
    64  
    65  func NewMockTaskHooks() *MockTaskHooks {
    66  	return &MockTaskHooks{
    67  		UnblockCh:   make(chan struct{}, 1),
    68  		RestartCh:   make(chan struct{}, 1),
    69  		SignalCh:    make(chan struct{}, 1),
    70  		KillCh:      make(chan struct{}, 1),
    71  		EmitEventCh: make(chan *structs.TaskEvent, 1),
    72  	}
    73  }
    74  func (m *MockTaskHooks) Restart(ctx context.Context, event *structs.TaskEvent, failure bool) error {
    75  	m.Restarts++
    76  	select {
    77  	case m.RestartCh <- struct{}{}:
    78  	default:
    79  	}
    80  	return nil
    81  }
    82  
    83  func (m *MockTaskHooks) Signal(event *structs.TaskEvent, s string) error {
    84  	m.signalLock.Lock()
    85  	m.Signals = append(m.Signals, s)
    86  	m.signalLock.Unlock()
    87  	select {
    88  	case m.SignalCh <- struct{}{}:
    89  	default:
    90  	}
    91  
    92  	return m.SignalError
    93  }
    94  
    95  func (m *MockTaskHooks) Kill(ctx context.Context, event *structs.TaskEvent) error {
    96  	m.KillEvent = event
    97  	select {
    98  	case m.KillCh <- struct{}{}:
    99  	default:
   100  	}
   101  	return nil
   102  }
   103  
   104  func (m *MockTaskHooks) IsRunning() bool {
   105  	return m.hasHandle
   106  }
   107  
   108  func (m *MockTaskHooks) EmitEvent(event *structs.TaskEvent) {
   109  	m.Events = append(m.Events, event)
   110  	select {
   111  	case m.EmitEventCh <- event:
   112  	case <-m.EmitEventCh:
   113  		m.EmitEventCh <- event
   114  	}
   115  }
   116  
   117  func (m *MockTaskHooks) SetState(state string, event *structs.TaskEvent) {}
   118  
   119  // testHarness is used to test the TaskTemplateManager by spinning up
   120  // Consul/Vault as needed
   121  type testHarness struct {
   122  	manager    *TaskTemplateManager
   123  	mockHooks  *MockTaskHooks
   124  	templates  []*structs.Template
   125  	envBuilder *taskenv.Builder
   126  	node       *structs.Node
   127  	config     *config.Config
   128  	vaultToken string
   129  	taskDir    string
   130  	vault      *testutil.TestVault
   131  	consul     *ctestutil.TestServer
   132  	emitRate   time.Duration
   133  }
   134  
   135  // newTestHarness returns a harness starting a dev consul and vault server,
   136  // building the appropriate config and creating a TaskTemplateManager
   137  func newTestHarness(t *testing.T, templates []*structs.Template, consul, vault bool) *testHarness {
   138  	region := "global"
   139  	harness := &testHarness{
   140  		mockHooks: NewMockTaskHooks(),
   141  		templates: templates,
   142  		node:      mock.Node(),
   143  		config: &config.Config{
   144  			Region: region,
   145  			TemplateConfig: &config.ClientTemplateConfig{
   146  				FunctionDenylist: []string{"plugin"},
   147  				DisableSandbox:   false,
   148  			}},
   149  		emitRate: DefaultMaxTemplateEventRate,
   150  	}
   151  
   152  	// Build the task environment
   153  	a := mock.Alloc()
   154  	task := a.Job.TaskGroups[0].Tasks[0]
   155  	task.Name = TestTaskName
   156  	harness.envBuilder = taskenv.NewBuilder(harness.node, a, task, region)
   157  
   158  	// Make a tempdir
   159  	d, err := ioutil.TempDir("", "ct_test")
   160  	if err != nil {
   161  		t.Fatalf("Failed to make tmpdir: %v", err)
   162  	}
   163  	harness.taskDir = d
   164  	harness.envBuilder.SetClientTaskRoot(harness.taskDir)
   165  
   166  	if consul {
   167  		harness.consul, err = ctestutil.NewTestServerConfigT(t, func(c *ctestutil.TestServerConfig) {
   168  			// defaults
   169  		})
   170  		if err != nil {
   171  			t.Fatalf("error starting test Consul server: %v", err)
   172  		}
   173  		harness.config.ConsulConfig = &sconfig.ConsulConfig{
   174  			Addr: harness.consul.HTTPAddr,
   175  		}
   176  	}
   177  
   178  	if vault {
   179  		harness.vault = testutil.NewTestVault(t)
   180  		harness.config.VaultConfig = harness.vault.Config
   181  		harness.vaultToken = harness.vault.RootToken
   182  	}
   183  
   184  	return harness
   185  }
   186  
   187  func (h *testHarness) start(t *testing.T) {
   188  	if err := h.startWithErr(); err != nil {
   189  		t.Fatalf("failed to build task template manager: %v", err)
   190  	}
   191  }
   192  
   193  func (h *testHarness) startWithErr() error {
   194  	var err error
   195  	h.manager, err = NewTaskTemplateManager(&TaskTemplateManagerConfig{
   196  		UnblockCh:            h.mockHooks.UnblockCh,
   197  		Lifecycle:            h.mockHooks,
   198  		Events:               h.mockHooks,
   199  		Templates:            h.templates,
   200  		ClientConfig:         h.config,
   201  		VaultToken:           h.vaultToken,
   202  		TaskDir:              h.taskDir,
   203  		EnvBuilder:           h.envBuilder,
   204  		MaxTemplateEventRate: h.emitRate,
   205  		retryRate:            10 * time.Millisecond,
   206  	})
   207  
   208  	return err
   209  }
   210  
   211  func (h *testHarness) setEmitRate(d time.Duration) {
   212  	h.emitRate = d
   213  }
   214  
   215  // stop is used to stop any running Vault or Consul server plus the task manager
   216  func (h *testHarness) stop() {
   217  	if h.vault != nil {
   218  		h.vault.Stop()
   219  	}
   220  	if h.consul != nil {
   221  		h.consul.Stop()
   222  	}
   223  	if h.manager != nil {
   224  		h.manager.Stop()
   225  	}
   226  	if h.taskDir != "" {
   227  		os.RemoveAll(h.taskDir)
   228  	}
   229  }
   230  
   231  func TestTaskTemplateManager_InvalidConfig(t *testing.T) {
   232  	t.Parallel()
   233  	hooks := NewMockTaskHooks()
   234  	clientConfig := &config.Config{Region: "global"}
   235  	taskDir := "foo"
   236  	a := mock.Alloc()
   237  	envBuilder := taskenv.NewBuilder(mock.Node(), a, a.Job.TaskGroups[0].Tasks[0], clientConfig.Region)
   238  
   239  	cases := []struct {
   240  		name        string
   241  		config      *TaskTemplateManagerConfig
   242  		expectedErr string
   243  	}{
   244  		{
   245  			name:        "nil config",
   246  			config:      nil,
   247  			expectedErr: "Nil config passed",
   248  		},
   249  		{
   250  			name: "bad lifecycle hooks",
   251  			config: &TaskTemplateManagerConfig{
   252  				UnblockCh:            hooks.UnblockCh,
   253  				Events:               hooks,
   254  				ClientConfig:         clientConfig,
   255  				TaskDir:              taskDir,
   256  				EnvBuilder:           envBuilder,
   257  				MaxTemplateEventRate: DefaultMaxTemplateEventRate,
   258  			},
   259  			expectedErr: "lifecycle hooks",
   260  		},
   261  		{
   262  			name: "bad event hooks",
   263  			config: &TaskTemplateManagerConfig{
   264  				UnblockCh:            hooks.UnblockCh,
   265  				Lifecycle:            hooks,
   266  				ClientConfig:         clientConfig,
   267  				TaskDir:              taskDir,
   268  				EnvBuilder:           envBuilder,
   269  				MaxTemplateEventRate: DefaultMaxTemplateEventRate,
   270  			},
   271  			expectedErr: "event hook",
   272  		},
   273  		{
   274  			name: "bad client config",
   275  			config: &TaskTemplateManagerConfig{
   276  				UnblockCh:            hooks.UnblockCh,
   277  				Lifecycle:            hooks,
   278  				Events:               hooks,
   279  				TaskDir:              taskDir,
   280  				EnvBuilder:           envBuilder,
   281  				MaxTemplateEventRate: DefaultMaxTemplateEventRate,
   282  			},
   283  			expectedErr: "client config",
   284  		},
   285  		{
   286  			name: "bad task dir",
   287  			config: &TaskTemplateManagerConfig{
   288  				UnblockCh:            hooks.UnblockCh,
   289  				ClientConfig:         clientConfig,
   290  				Lifecycle:            hooks,
   291  				Events:               hooks,
   292  				EnvBuilder:           envBuilder,
   293  				MaxTemplateEventRate: DefaultMaxTemplateEventRate,
   294  			},
   295  			expectedErr: "task directory",
   296  		},
   297  		{
   298  			name: "bad env builder",
   299  			config: &TaskTemplateManagerConfig{
   300  				UnblockCh:            hooks.UnblockCh,
   301  				ClientConfig:         clientConfig,
   302  				Lifecycle:            hooks,
   303  				Events:               hooks,
   304  				TaskDir:              taskDir,
   305  				MaxTemplateEventRate: DefaultMaxTemplateEventRate,
   306  			},
   307  			expectedErr: "task environment",
   308  		},
   309  		{
   310  			name: "bad max event rate",
   311  			config: &TaskTemplateManagerConfig{
   312  				UnblockCh:    hooks.UnblockCh,
   313  				ClientConfig: clientConfig,
   314  				Lifecycle:    hooks,
   315  				Events:       hooks,
   316  				TaskDir:      taskDir,
   317  				EnvBuilder:   envBuilder,
   318  			},
   319  			expectedErr: "template event rate",
   320  		},
   321  		{
   322  			name: "valid",
   323  			config: &TaskTemplateManagerConfig{
   324  				UnblockCh:            hooks.UnblockCh,
   325  				ClientConfig:         clientConfig,
   326  				Lifecycle:            hooks,
   327  				Events:               hooks,
   328  				TaskDir:              taskDir,
   329  				EnvBuilder:           envBuilder,
   330  				MaxTemplateEventRate: DefaultMaxTemplateEventRate,
   331  			},
   332  		},
   333  		{
   334  			name: "invalid signal",
   335  			config: &TaskTemplateManagerConfig{
   336  				UnblockCh: hooks.UnblockCh,
   337  				Templates: []*structs.Template{
   338  					{
   339  						DestPath:     "foo",
   340  						EmbeddedTmpl: "hello, world",
   341  						ChangeMode:   structs.TemplateChangeModeSignal,
   342  						ChangeSignal: "foobarbaz",
   343  					},
   344  				},
   345  				ClientConfig:         clientConfig,
   346  				Lifecycle:            hooks,
   347  				Events:               hooks,
   348  				TaskDir:              taskDir,
   349  				EnvBuilder:           envBuilder,
   350  				MaxTemplateEventRate: DefaultMaxTemplateEventRate,
   351  			},
   352  			expectedErr: "parse signal",
   353  		},
   354  	}
   355  
   356  	for _, c := range cases {
   357  		t.Run(c.name, func(t *testing.T) {
   358  			_, err := NewTaskTemplateManager(c.config)
   359  			if err != nil {
   360  				if c.expectedErr == "" {
   361  					t.Fatalf("unexpected error: %v", err)
   362  				} else if !strings.Contains(err.Error(), c.expectedErr) {
   363  					t.Fatalf("expected error to contain %q; got %q", c.expectedErr, err.Error())
   364  				}
   365  			} else if c.expectedErr != "" {
   366  				t.Fatalf("expected an error to contain %q", c.expectedErr)
   367  			}
   368  		})
   369  	}
   370  }
   371  
   372  func TestTaskTemplateManager_HostPath(t *testing.T) {
   373  	t.Parallel()
   374  	// Make a template that will render immediately and write it to a tmp file
   375  	f, err := ioutil.TempFile("", "")
   376  	if err != nil {
   377  		t.Fatalf("Bad: %v", err)
   378  	}
   379  	defer f.Close()
   380  	defer os.Remove(f.Name())
   381  
   382  	content := "hello, world!"
   383  	if _, err := io.WriteString(f, content); err != nil {
   384  		t.Fatalf("Bad: %v", err)
   385  	}
   386  
   387  	file := "my.tmpl"
   388  	template := &structs.Template{
   389  		SourcePath: f.Name(),
   390  		DestPath:   file,
   391  		ChangeMode: structs.TemplateChangeModeNoop,
   392  	}
   393  
   394  	harness := newTestHarness(t, []*structs.Template{template}, false, false)
   395  	harness.config.TemplateConfig.DisableSandbox = true
   396  	err = harness.startWithErr()
   397  	if err != nil {
   398  		t.Fatalf("couldn't setup initial harness: %v", err)
   399  	}
   400  	defer harness.stop()
   401  
   402  	// Wait for the unblock
   403  	select {
   404  	case <-harness.mockHooks.UnblockCh:
   405  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   406  		t.Fatalf("Task unblock should have been called")
   407  	}
   408  
   409  	// Check the file is there
   410  	path := filepath.Join(harness.taskDir, file)
   411  	raw, err := ioutil.ReadFile(path)
   412  	if err != nil {
   413  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
   414  	}
   415  
   416  	if s := string(raw); s != content {
   417  		t.Fatalf("Unexpected template data; got %q, want %q", s, content)
   418  	}
   419  
   420  	// Change the config to disallow host sources
   421  	harness = newTestHarness(t, []*structs.Template{template}, false, false)
   422  	err = harness.startWithErr()
   423  	if err == nil || !strings.Contains(err.Error(), "escapes alloc directory") {
   424  		t.Fatalf("Expected absolute template path disallowed for %q: %v",
   425  			template.SourcePath, err)
   426  	}
   427  
   428  	template.SourcePath = "../../../../../../" + file
   429  	harness = newTestHarness(t, []*structs.Template{template}, false, false)
   430  	err = harness.startWithErr()
   431  	if err == nil || !strings.Contains(err.Error(), "escapes alloc directory") {
   432  		t.Fatalf("Expected directory traversal out of %q disallowed for %q: %v",
   433  			harness.taskDir, template.SourcePath, err)
   434  	}
   435  
   436  	// Build a new task environment
   437  	a := mock.Alloc()
   438  	task := a.Job.TaskGroups[0].Tasks[0]
   439  	task.Name = TestTaskName
   440  	task.Meta = map[string]string{"ESCAPE": "../"}
   441  
   442  	template.SourcePath = "${NOMAD_META_ESCAPE}${NOMAD_META_ESCAPE}${NOMAD_META_ESCAPE}${NOMAD_META_ESCAPE}${NOMAD_META_ESCAPE}${NOMAD_META_ESCAPE}" + file
   443  	harness = newTestHarness(t, []*structs.Template{template}, false, false)
   444  	harness.envBuilder = taskenv.NewBuilder(harness.node, a, task, "global")
   445  	err = harness.startWithErr()
   446  	if err == nil || !strings.Contains(err.Error(), "escapes alloc directory") {
   447  		t.Fatalf("Expected directory traversal out of %q via interpolation disallowed for %q: %v",
   448  			harness.taskDir, template.SourcePath, err)
   449  	}
   450  
   451  	// Test with desination too
   452  	template.SourcePath = f.Name()
   453  	template.DestPath = "../../../../../../" + file
   454  	harness = newTestHarness(t, []*structs.Template{template}, false, false)
   455  	harness.envBuilder = taskenv.NewBuilder(harness.node, a, task, "global")
   456  	err = harness.startWithErr()
   457  	if err == nil || !strings.Contains(err.Error(), "escapes alloc directory") {
   458  		t.Fatalf("Expected directory traversal out of %q via interpolation disallowed for %q: %v",
   459  			harness.taskDir, template.SourcePath, err)
   460  	}
   461  
   462  }
   463  
   464  func TestTaskTemplateManager_Unblock_Static(t *testing.T) {
   465  	t.Parallel()
   466  	// Make a template that will render immediately
   467  	content := "hello, world!"
   468  	file := "my.tmpl"
   469  	template := &structs.Template{
   470  		EmbeddedTmpl: content,
   471  		DestPath:     file,
   472  		ChangeMode:   structs.TemplateChangeModeNoop,
   473  	}
   474  
   475  	harness := newTestHarness(t, []*structs.Template{template}, false, false)
   476  	harness.start(t)
   477  	defer harness.stop()
   478  
   479  	// Wait for the unblock
   480  	select {
   481  	case <-harness.mockHooks.UnblockCh:
   482  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   483  		t.Fatalf("Task unblock should have been called")
   484  	}
   485  
   486  	// Check the file is there
   487  	path := filepath.Join(harness.taskDir, file)
   488  	raw, err := ioutil.ReadFile(path)
   489  	if err != nil {
   490  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
   491  	}
   492  
   493  	if s := string(raw); s != content {
   494  		t.Fatalf("Unexpected template data; got %q, want %q", s, content)
   495  	}
   496  }
   497  
   498  func TestTaskTemplateManager_Permissions(t *testing.T) {
   499  	t.Parallel()
   500  	// Make a template that will render immediately
   501  	content := "hello, world!"
   502  	file := "my.tmpl"
   503  	template := &structs.Template{
   504  		EmbeddedTmpl: content,
   505  		DestPath:     file,
   506  		ChangeMode:   structs.TemplateChangeModeNoop,
   507  		Perms:        "777",
   508  	}
   509  
   510  	harness := newTestHarness(t, []*structs.Template{template}, false, false)
   511  	harness.start(t)
   512  	defer harness.stop()
   513  
   514  	// Wait for the unblock
   515  	select {
   516  	case <-harness.mockHooks.UnblockCh:
   517  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   518  		t.Fatalf("Task unblock should have been called")
   519  	}
   520  
   521  	// Check the file is there
   522  	path := filepath.Join(harness.taskDir, file)
   523  	fi, err := os.Stat(path)
   524  	if err != nil {
   525  		t.Fatalf("Failed to stat file: %v", err)
   526  	}
   527  
   528  	if m := fi.Mode(); m != os.ModePerm {
   529  		t.Fatalf("Got mode %v; want %v", m, os.ModePerm)
   530  	}
   531  }
   532  
   533  func TestTaskTemplateManager_Unblock_Static_NomadEnv(t *testing.T) {
   534  	t.Parallel()
   535  	// Make a template that will render immediately
   536  	content := `Hello Nomad Task: {{env "NOMAD_TASK_NAME"}}`
   537  	expected := fmt.Sprintf("Hello Nomad Task: %s", TestTaskName)
   538  	file := "my.tmpl"
   539  	template := &structs.Template{
   540  		EmbeddedTmpl: content,
   541  		DestPath:     file,
   542  		ChangeMode:   structs.TemplateChangeModeNoop,
   543  	}
   544  
   545  	harness := newTestHarness(t, []*structs.Template{template}, false, false)
   546  	harness.start(t)
   547  	defer harness.stop()
   548  
   549  	// Wait for the unblock
   550  	select {
   551  	case <-harness.mockHooks.UnblockCh:
   552  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   553  		t.Fatalf("Task unblock should have been called")
   554  	}
   555  
   556  	// Check the file is there
   557  	path := filepath.Join(harness.taskDir, file)
   558  	raw, err := ioutil.ReadFile(path)
   559  	if err != nil {
   560  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
   561  	}
   562  
   563  	if s := string(raw); s != expected {
   564  		t.Fatalf("Unexpected template data; got %q, want %q", s, expected)
   565  	}
   566  }
   567  
   568  func TestTaskTemplateManager_Unblock_Static_AlreadyRendered(t *testing.T) {
   569  	t.Parallel()
   570  	// Make a template that will render immediately
   571  	content := "hello, world!"
   572  	file := "my.tmpl"
   573  	template := &structs.Template{
   574  		EmbeddedTmpl: content,
   575  		DestPath:     file,
   576  		ChangeMode:   structs.TemplateChangeModeNoop,
   577  	}
   578  
   579  	harness := newTestHarness(t, []*structs.Template{template}, false, false)
   580  
   581  	// Write the contents
   582  	path := filepath.Join(harness.taskDir, file)
   583  	if err := ioutil.WriteFile(path, []byte(content), 0777); err != nil {
   584  		t.Fatalf("Failed to write data: %v", err)
   585  	}
   586  
   587  	harness.start(t)
   588  	defer harness.stop()
   589  
   590  	// Wait for the unblock
   591  	select {
   592  	case <-harness.mockHooks.UnblockCh:
   593  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   594  		t.Fatalf("Task unblock should have been called")
   595  	}
   596  
   597  	// Check the file is there
   598  	path = filepath.Join(harness.taskDir, file)
   599  	raw, err := ioutil.ReadFile(path)
   600  	if err != nil {
   601  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
   602  	}
   603  
   604  	if s := string(raw); s != content {
   605  		t.Fatalf("Unexpected template data; got %q, want %q", s, content)
   606  	}
   607  }
   608  
   609  func TestTaskTemplateManager_Unblock_Consul(t *testing.T) {
   610  	t.Parallel()
   611  	// Make a template that will render based on a key in Consul
   612  	key := "foo"
   613  	content := "barbaz"
   614  	embedded := fmt.Sprintf(`{{key "%s"}}`, key)
   615  	file := "my.tmpl"
   616  	template := &structs.Template{
   617  		EmbeddedTmpl: embedded,
   618  		DestPath:     file,
   619  		ChangeMode:   structs.TemplateChangeModeNoop,
   620  	}
   621  
   622  	harness := newTestHarness(t, []*structs.Template{template}, true, false)
   623  	harness.start(t)
   624  	defer harness.stop()
   625  
   626  	// Ensure no unblock
   627  	select {
   628  	case <-harness.mockHooks.UnblockCh:
   629  		t.Fatalf("Task unblock should have not have been called")
   630  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
   631  	}
   632  
   633  	// Write the key to Consul
   634  	harness.consul.SetKV(t, key, []byte(content))
   635  
   636  	// Wait for the unblock
   637  	select {
   638  	case <-harness.mockHooks.UnblockCh:
   639  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   640  		t.Fatalf("Task unblock should have been called")
   641  	}
   642  
   643  	// Check the file is there
   644  	path := filepath.Join(harness.taskDir, file)
   645  	raw, err := ioutil.ReadFile(path)
   646  	if err != nil {
   647  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
   648  	}
   649  
   650  	if s := string(raw); s != content {
   651  		t.Fatalf("Unexpected template data; got %q, want %q", s, content)
   652  	}
   653  }
   654  
   655  func TestTaskTemplateManager_Unblock_Vault(t *testing.T) {
   656  	t.Parallel()
   657  	require := require.New(t)
   658  	// Make a template that will render based on a key in Vault
   659  	vaultPath := "secret/data/password"
   660  	key := "password"
   661  	content := "barbaz"
   662  	embedded := fmt.Sprintf(`{{with secret "%s"}}{{.Data.data.%s}}{{end}}`, vaultPath, key)
   663  	file := "my.tmpl"
   664  	template := &structs.Template{
   665  		EmbeddedTmpl: embedded,
   666  		DestPath:     file,
   667  		ChangeMode:   structs.TemplateChangeModeNoop,
   668  	}
   669  
   670  	harness := newTestHarness(t, []*structs.Template{template}, false, true)
   671  	harness.start(t)
   672  	defer harness.stop()
   673  
   674  	// Ensure no unblock
   675  	select {
   676  	case <-harness.mockHooks.UnblockCh:
   677  		t.Fatalf("Task unblock should not have been called")
   678  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
   679  	}
   680  
   681  	// Write the secret to Vault
   682  	logical := harness.vault.Client.Logical()
   683  	_, err := logical.Write(vaultPath, map[string]interface{}{"data": map[string]interface{}{key: content}})
   684  	require.NoError(err)
   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 file is there
   694  	path := filepath.Join(harness.taskDir, file)
   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 != content {
   701  		t.Fatalf("Unexpected template data; got %q, want %q", s, content)
   702  	}
   703  }
   704  
   705  func TestTaskTemplateManager_Unblock_Multi_Template(t *testing.T) {
   706  	t.Parallel()
   707  	// Make a template that will render immediately
   708  	staticContent := "hello, world!"
   709  	staticFile := "my.tmpl"
   710  	template := &structs.Template{
   711  		EmbeddedTmpl: staticContent,
   712  		DestPath:     staticFile,
   713  		ChangeMode:   structs.TemplateChangeModeNoop,
   714  	}
   715  
   716  	// Make a template that will render based on a key in Consul
   717  	consulKey := "foo"
   718  	consulContent := "barbaz"
   719  	consulEmbedded := fmt.Sprintf(`{{key "%s"}}`, consulKey)
   720  	consulFile := "consul.tmpl"
   721  	template2 := &structs.Template{
   722  		EmbeddedTmpl: consulEmbedded,
   723  		DestPath:     consulFile,
   724  		ChangeMode:   structs.TemplateChangeModeNoop,
   725  	}
   726  
   727  	harness := newTestHarness(t, []*structs.Template{template, template2}, true, false)
   728  	harness.start(t)
   729  	defer harness.stop()
   730  
   731  	// Ensure no unblock
   732  	select {
   733  	case <-harness.mockHooks.UnblockCh:
   734  		t.Fatalf("Task unblock should have not have been called")
   735  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
   736  	}
   737  
   738  	// Check that the static file has been rendered
   739  	path := filepath.Join(harness.taskDir, staticFile)
   740  	raw, err := ioutil.ReadFile(path)
   741  	if err != nil {
   742  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
   743  	}
   744  
   745  	if s := string(raw); s != staticContent {
   746  		t.Fatalf("Unexpected template data; got %q, want %q", s, staticContent)
   747  	}
   748  
   749  	// Write the key to Consul
   750  	harness.consul.SetKV(t, consulKey, []byte(consulContent))
   751  
   752  	// Wait for the unblock
   753  	select {
   754  	case <-harness.mockHooks.UnblockCh:
   755  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   756  		t.Fatalf("Task unblock should have been called")
   757  	}
   758  
   759  	// Check the consul file is there
   760  	path = filepath.Join(harness.taskDir, consulFile)
   761  	raw, err = ioutil.ReadFile(path)
   762  	if err != nil {
   763  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
   764  	}
   765  
   766  	if s := string(raw); s != consulContent {
   767  		t.Fatalf("Unexpected template data; got %q, want %q", s, consulContent)
   768  	}
   769  }
   770  
   771  // TestTaskTemplateManager_FirstRender_Restored tests that a task that's been
   772  // restored renders and triggers its change mode if the template has changed
   773  func TestTaskTemplateManager_FirstRender_Restored(t *testing.T) {
   774  	t.Parallel()
   775  	require := require.New(t)
   776  	// Make a template that will render based on a key in Vault
   777  	vaultPath := "secret/data/password"
   778  	key := "password"
   779  	content := "barbaz"
   780  	embedded := fmt.Sprintf(`{{with secret "%s"}}{{.Data.data.%s}}{{end}}`, vaultPath, key)
   781  	file := "my.tmpl"
   782  	template := &structs.Template{
   783  		EmbeddedTmpl: embedded,
   784  		DestPath:     file,
   785  		ChangeMode:   structs.TemplateChangeModeRestart,
   786  	}
   787  
   788  	harness := newTestHarness(t, []*structs.Template{template}, false, true)
   789  	harness.start(t)
   790  	defer harness.stop()
   791  
   792  	// Ensure no unblock
   793  	select {
   794  	case <-harness.mockHooks.UnblockCh:
   795  		require.Fail("Task unblock should not have been called")
   796  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
   797  	}
   798  
   799  	// Write the secret to Vault
   800  	logical := harness.vault.Client.Logical()
   801  	_, err := logical.Write(vaultPath, map[string]interface{}{"data": map[string]interface{}{key: content}})
   802  	require.NoError(err)
   803  
   804  	// Wait for the unblock
   805  	select {
   806  	case <-harness.mockHooks.UnblockCh:
   807  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   808  		require.Fail("Task unblock should have been called")
   809  	}
   810  
   811  	// Check the file is there
   812  	path := filepath.Join(harness.taskDir, file)
   813  	raw, err := ioutil.ReadFile(path)
   814  	require.NoError(err, "Failed to read rendered template from %q", path)
   815  	require.Equal(content, string(raw), "Unexpected template data; got %s, want %q", raw, content)
   816  
   817  	// task is now running
   818  	harness.mockHooks.hasHandle = true
   819  
   820  	// simulate a client restart
   821  	harness.manager.Stop()
   822  	harness.mockHooks.UnblockCh = make(chan struct{}, 1)
   823  	harness.start(t)
   824  
   825  	// Wait for the unblock
   826  	select {
   827  	case <-harness.mockHooks.UnblockCh:
   828  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   829  		require.Fail("Task unblock should have been called")
   830  	}
   831  
   832  	select {
   833  	case <-harness.mockHooks.RestartCh:
   834  		require.Fail("should not have restarted", harness.mockHooks)
   835  	case <-harness.mockHooks.SignalCh:
   836  		require.Fail("should not have restarted", harness.mockHooks)
   837  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
   838  	}
   839  
   840  	// simulate a client restart and TTL expiry
   841  	harness.manager.Stop()
   842  	content = "bazbar"
   843  	_, err = logical.Write(vaultPath, map[string]interface{}{"data": map[string]interface{}{key: content}})
   844  	require.NoError(err)
   845  	harness.mockHooks.UnblockCh = make(chan struct{}, 1)
   846  	harness.start(t)
   847  
   848  	// Wait for the unblock
   849  	select {
   850  	case <-harness.mockHooks.UnblockCh:
   851  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   852  		require.Fail("Task unblock should have been called")
   853  	}
   854  
   855  	// Wait for restart
   856  	timeout := time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second)
   857  OUTER:
   858  	for {
   859  		select {
   860  		case <-harness.mockHooks.RestartCh:
   861  			break OUTER
   862  		case <-harness.mockHooks.SignalCh:
   863  			require.Fail("Signal with restart policy", harness.mockHooks)
   864  		case <-timeout:
   865  			require.Fail("Should have received a restart", harness.mockHooks)
   866  		}
   867  	}
   868  }
   869  
   870  func TestTaskTemplateManager_Rerender_Noop(t *testing.T) {
   871  	t.Parallel()
   872  	// Make a template that will render based on a key in Consul
   873  	key := "foo"
   874  	content1 := "bar"
   875  	content2 := "baz"
   876  	embedded := fmt.Sprintf(`{{key "%s"}}`, key)
   877  	file := "my.tmpl"
   878  	template := &structs.Template{
   879  		EmbeddedTmpl: embedded,
   880  		DestPath:     file,
   881  		ChangeMode:   structs.TemplateChangeModeNoop,
   882  	}
   883  
   884  	harness := newTestHarness(t, []*structs.Template{template}, true, false)
   885  	harness.start(t)
   886  	defer harness.stop()
   887  
   888  	// Ensure no unblock
   889  	select {
   890  	case <-harness.mockHooks.UnblockCh:
   891  		t.Fatalf("Task unblock should have not have been called")
   892  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
   893  	}
   894  
   895  	// Write the key to Consul
   896  	harness.consul.SetKV(t, key, []byte(content1))
   897  
   898  	// Wait for the unblock
   899  	select {
   900  	case <-harness.mockHooks.UnblockCh:
   901  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   902  		t.Fatalf("Task unblock should have been called")
   903  	}
   904  
   905  	// Check the file is there
   906  	path := filepath.Join(harness.taskDir, file)
   907  	raw, err := ioutil.ReadFile(path)
   908  	if err != nil {
   909  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
   910  	}
   911  
   912  	if s := string(raw); s != content1 {
   913  		t.Fatalf("Unexpected template data; got %q, want %q", s, content1)
   914  	}
   915  
   916  	// Update the key in Consul
   917  	harness.consul.SetKV(t, key, []byte(content2))
   918  
   919  	select {
   920  	case <-harness.mockHooks.RestartCh:
   921  		t.Fatalf("Noop ignored: %+v", harness.mockHooks)
   922  	case <-harness.mockHooks.SignalCh:
   923  		t.Fatalf("Noop ignored: %+v", harness.mockHooks)
   924  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
   925  	}
   926  
   927  	// Check the file has been updated
   928  	path = filepath.Join(harness.taskDir, file)
   929  	raw, err = ioutil.ReadFile(path)
   930  	if err != nil {
   931  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
   932  	}
   933  
   934  	if s := string(raw); s != content2 {
   935  		t.Fatalf("Unexpected template data; got %q, want %q", s, content2)
   936  	}
   937  }
   938  
   939  func TestTaskTemplateManager_Rerender_Signal(t *testing.T) {
   940  	t.Parallel()
   941  	// Make a template that renders based on a key in Consul and sends SIGALRM
   942  	key1 := "foo"
   943  	content1_1 := "bar"
   944  	content1_2 := "baz"
   945  	embedded1 := fmt.Sprintf(`{{key "%s"}}`, key1)
   946  	file1 := "my.tmpl"
   947  	template := &structs.Template{
   948  		EmbeddedTmpl: embedded1,
   949  		DestPath:     file1,
   950  		ChangeMode:   structs.TemplateChangeModeSignal,
   951  		ChangeSignal: "SIGALRM",
   952  	}
   953  
   954  	// Make a template that renders based on a key in Consul and sends SIGBUS
   955  	key2 := "bam"
   956  	content2_1 := "cat"
   957  	content2_2 := "dog"
   958  	embedded2 := fmt.Sprintf(`{{key "%s"}}`, key2)
   959  	file2 := "my-second.tmpl"
   960  	template2 := &structs.Template{
   961  		EmbeddedTmpl: embedded2,
   962  		DestPath:     file2,
   963  		ChangeMode:   structs.TemplateChangeModeSignal,
   964  		ChangeSignal: "SIGBUS",
   965  	}
   966  
   967  	harness := newTestHarness(t, []*structs.Template{template, template2}, true, false)
   968  	harness.start(t)
   969  	defer harness.stop()
   970  
   971  	// Ensure no unblock
   972  	select {
   973  	case <-harness.mockHooks.UnblockCh:
   974  		t.Fatalf("Task unblock should have not have been called")
   975  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
   976  	}
   977  
   978  	// Write the key to Consul
   979  	harness.consul.SetKV(t, key1, []byte(content1_1))
   980  	harness.consul.SetKV(t, key2, []byte(content2_1))
   981  
   982  	// Wait for the unblock
   983  	select {
   984  	case <-harness.mockHooks.UnblockCh:
   985  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   986  		t.Fatalf("Task unblock should have been called")
   987  	}
   988  
   989  	if len(harness.mockHooks.Signals) != 0 {
   990  		t.Fatalf("Should not have received any signals: %+v", harness.mockHooks)
   991  	}
   992  
   993  	// Update the keys in Consul
   994  	harness.consul.SetKV(t, key1, []byte(content1_2))
   995  	harness.consul.SetKV(t, key2, []byte(content2_2))
   996  
   997  	// Wait for signals
   998  	timeout := time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second)
   999  OUTER:
  1000  	for {
  1001  		select {
  1002  		case <-harness.mockHooks.RestartCh:
  1003  			t.Fatalf("Restart with signal policy: %+v", harness.mockHooks)
  1004  		case <-harness.mockHooks.SignalCh:
  1005  			harness.mockHooks.signalLock.Lock()
  1006  			s := harness.mockHooks.Signals
  1007  			harness.mockHooks.signalLock.Unlock()
  1008  			if len(s) != 2 {
  1009  				continue
  1010  			}
  1011  			break OUTER
  1012  		case <-timeout:
  1013  			t.Fatalf("Should have received two signals: %+v", harness.mockHooks)
  1014  		}
  1015  	}
  1016  
  1017  	// Check the files have  been updated
  1018  	path := filepath.Join(harness.taskDir, file1)
  1019  	raw, err := ioutil.ReadFile(path)
  1020  	if err != nil {
  1021  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
  1022  	}
  1023  
  1024  	if s := string(raw); s != content1_2 {
  1025  		t.Fatalf("Unexpected template data; got %q, want %q", s, content1_2)
  1026  	}
  1027  
  1028  	path = filepath.Join(harness.taskDir, file2)
  1029  	raw, err = ioutil.ReadFile(path)
  1030  	if err != nil {
  1031  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
  1032  	}
  1033  
  1034  	if s := string(raw); s != content2_2 {
  1035  		t.Fatalf("Unexpected template data; got %q, want %q", s, content2_2)
  1036  	}
  1037  }
  1038  
  1039  func TestTaskTemplateManager_Rerender_Restart(t *testing.T) {
  1040  	t.Parallel()
  1041  	// Make a template that renders based on a key in Consul and sends restart
  1042  	key1 := "bam"
  1043  	content1_1 := "cat"
  1044  	content1_2 := "dog"
  1045  	embedded1 := fmt.Sprintf(`{{key "%s"}}`, key1)
  1046  	file1 := "my.tmpl"
  1047  	template := &structs.Template{
  1048  		EmbeddedTmpl: embedded1,
  1049  		DestPath:     file1,
  1050  		ChangeMode:   structs.TemplateChangeModeRestart,
  1051  	}
  1052  
  1053  	harness := newTestHarness(t, []*structs.Template{template}, true, false)
  1054  	harness.start(t)
  1055  	defer harness.stop()
  1056  
  1057  	// Ensure no unblock
  1058  	select {
  1059  	case <-harness.mockHooks.UnblockCh:
  1060  		t.Fatalf("Task unblock should have not have been called")
  1061  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
  1062  	}
  1063  
  1064  	// Write the key to Consul
  1065  	harness.consul.SetKV(t, key1, []byte(content1_1))
  1066  
  1067  	// Wait for the unblock
  1068  	select {
  1069  	case <-harness.mockHooks.UnblockCh:
  1070  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
  1071  		t.Fatalf("Task unblock should have been called")
  1072  	}
  1073  
  1074  	// Update the keys in Consul
  1075  	harness.consul.SetKV(t, key1, []byte(content1_2))
  1076  
  1077  	// Wait for restart
  1078  	timeout := time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second)
  1079  OUTER:
  1080  	for {
  1081  		select {
  1082  		case <-harness.mockHooks.RestartCh:
  1083  			break OUTER
  1084  		case <-harness.mockHooks.SignalCh:
  1085  			t.Fatalf("Signal with restart policy: %+v", harness.mockHooks)
  1086  		case <-timeout:
  1087  			t.Fatalf("Should have received a restart: %+v", harness.mockHooks)
  1088  		}
  1089  	}
  1090  
  1091  	// Check the files have  been updated
  1092  	path := filepath.Join(harness.taskDir, file1)
  1093  	raw, err := ioutil.ReadFile(path)
  1094  	if err != nil {
  1095  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
  1096  	}
  1097  
  1098  	if s := string(raw); s != content1_2 {
  1099  		t.Fatalf("Unexpected template data; got %q, want %q", s, content1_2)
  1100  	}
  1101  }
  1102  
  1103  func TestTaskTemplateManager_Interpolate_Destination(t *testing.T) {
  1104  	t.Parallel()
  1105  	// Make a template that will have its destination interpolated
  1106  	content := "hello, world!"
  1107  	file := "${node.unique.id}.tmpl"
  1108  	template := &structs.Template{
  1109  		EmbeddedTmpl: content,
  1110  		DestPath:     file,
  1111  		ChangeMode:   structs.TemplateChangeModeNoop,
  1112  	}
  1113  
  1114  	harness := newTestHarness(t, []*structs.Template{template}, false, false)
  1115  	harness.start(t)
  1116  	defer harness.stop()
  1117  
  1118  	// Ensure unblock
  1119  	select {
  1120  	case <-harness.mockHooks.UnblockCh:
  1121  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
  1122  		t.Fatalf("Task unblock should have been called")
  1123  	}
  1124  
  1125  	// Check the file is there
  1126  	actual := fmt.Sprintf("%s.tmpl", harness.node.ID)
  1127  	path := filepath.Join(harness.taskDir, actual)
  1128  	raw, err := ioutil.ReadFile(path)
  1129  	if err != nil {
  1130  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
  1131  	}
  1132  
  1133  	if s := string(raw); s != content {
  1134  		t.Fatalf("Unexpected template data; got %q, want %q", s, content)
  1135  	}
  1136  }
  1137  
  1138  func TestTaskTemplateManager_Signal_Error(t *testing.T) {
  1139  	t.Parallel()
  1140  	require := require.New(t)
  1141  
  1142  	// Make a template that renders based on a key in Consul and sends SIGALRM
  1143  	key1 := "foo"
  1144  	content1 := "bar"
  1145  	content2 := "baz"
  1146  	embedded1 := fmt.Sprintf(`{{key "%s"}}`, key1)
  1147  	file1 := "my.tmpl"
  1148  	template := &structs.Template{
  1149  		EmbeddedTmpl: embedded1,
  1150  		DestPath:     file1,
  1151  		ChangeMode:   structs.TemplateChangeModeSignal,
  1152  		ChangeSignal: "SIGALRM",
  1153  	}
  1154  
  1155  	harness := newTestHarness(t, []*structs.Template{template}, true, false)
  1156  	harness.start(t)
  1157  	defer harness.stop()
  1158  
  1159  	harness.mockHooks.SignalError = fmt.Errorf("test error")
  1160  
  1161  	// Write the key to Consul
  1162  	harness.consul.SetKV(t, key1, []byte(content1))
  1163  
  1164  	// Wait a little
  1165  	select {
  1166  	case <-harness.mockHooks.UnblockCh:
  1167  	case <-time.After(time.Duration(2*testutil.TestMultiplier()) * time.Second):
  1168  		t.Fatalf("Should have received unblock: %+v", harness.mockHooks)
  1169  	}
  1170  
  1171  	// Write the key to Consul
  1172  	harness.consul.SetKV(t, key1, []byte(content2))
  1173  
  1174  	// Wait for kill channel
  1175  	select {
  1176  	case <-harness.mockHooks.KillCh:
  1177  		break
  1178  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
  1179  		t.Fatalf("Should have received a signals: %+v", harness.mockHooks)
  1180  	}
  1181  
  1182  	require.NotNil(harness.mockHooks.KillEvent)
  1183  	require.Contains(harness.mockHooks.KillEvent.DisplayMessage, "failed to send signals")
  1184  }
  1185  
  1186  // TestTaskTemplateManager_FiltersProcessEnvVars asserts that we only render
  1187  // environment variables found in task env-vars and not read the nomad host
  1188  // process environment variables.  nomad host process environment variables
  1189  // are to be treated the same as not found environment variables.
  1190  func TestTaskTemplateManager_FiltersEnvVars(t *testing.T) {
  1191  	t.Parallel()
  1192  
  1193  	defer os.Setenv("NOMAD_TASK_NAME", os.Getenv("NOMAD_TASK_NAME"))
  1194  	os.Setenv("NOMAD_TASK_NAME", "should be overridden by task")
  1195  
  1196  	testenv := "TESTENV_" + strings.ReplaceAll(uuid.Generate(), "-", "")
  1197  	os.Setenv(testenv, "MY_TEST_VALUE")
  1198  	defer os.Unsetenv(testenv)
  1199  
  1200  	// Make a template that will render immediately
  1201  	content := `Hello Nomad Task: {{env "NOMAD_TASK_NAME"}}
  1202  TEST_ENV: {{ env "` + testenv + `" }}
  1203  TEST_ENV_NOT_FOUND: {{env "` + testenv + `_NOTFOUND" }}`
  1204  	expected := fmt.Sprintf("Hello Nomad Task: %s\nTEST_ENV: \nTEST_ENV_NOT_FOUND: ", TestTaskName)
  1205  
  1206  	file := "my.tmpl"
  1207  	template := &structs.Template{
  1208  		EmbeddedTmpl: content,
  1209  		DestPath:     file,
  1210  		ChangeMode:   structs.TemplateChangeModeNoop,
  1211  	}
  1212  
  1213  	harness := newTestHarness(t, []*structs.Template{template}, false, false)
  1214  	harness.start(t)
  1215  	defer harness.stop()
  1216  
  1217  	// Wait for the unblock
  1218  	select {
  1219  	case <-harness.mockHooks.UnblockCh:
  1220  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
  1221  		require.Fail(t, "Task unblock should have been called")
  1222  	}
  1223  
  1224  	// Check the file is there
  1225  	path := filepath.Join(harness.taskDir, file)
  1226  	raw, err := ioutil.ReadFile(path)
  1227  	require.NoError(t, err)
  1228  
  1229  	require.Equal(t, expected, string(raw))
  1230  }
  1231  
  1232  // TestTaskTemplateManager_Env asserts templates with the env flag set are read
  1233  // into the task's environment.
  1234  func TestTaskTemplateManager_Env(t *testing.T) {
  1235  	t.Parallel()
  1236  	template := &structs.Template{
  1237  		EmbeddedTmpl: `
  1238  # Comment lines are ok
  1239  
  1240  FOO=bar
  1241  foo=123
  1242  ANYTHING_goes=Spaces are=ok!
  1243  `,
  1244  		DestPath:   "test.env",
  1245  		ChangeMode: structs.TemplateChangeModeNoop,
  1246  		Envvars:    true,
  1247  	}
  1248  	harness := newTestHarness(t, []*structs.Template{template}, true, false)
  1249  	harness.start(t)
  1250  	defer harness.stop()
  1251  
  1252  	// Wait a little
  1253  	select {
  1254  	case <-harness.mockHooks.UnblockCh:
  1255  	case <-time.After(time.Duration(2*testutil.TestMultiplier()) * time.Second):
  1256  		t.Fatalf("Should have received unblock: %+v", harness.mockHooks)
  1257  	}
  1258  
  1259  	// Validate environment
  1260  	env := harness.envBuilder.Build().Map()
  1261  	if len(env) < 3 {
  1262  		t.Fatalf("expected at least 3 env vars but found %d:\n%#v\n", len(env), env)
  1263  	}
  1264  	if env["FOO"] != "bar" {
  1265  		t.Errorf("expected FOO=bar but found %q", env["FOO"])
  1266  	}
  1267  	if env["foo"] != "123" {
  1268  		t.Errorf("expected foo=123 but found %q", env["foo"])
  1269  	}
  1270  	if env["ANYTHING_goes"] != "Spaces are=ok!" {
  1271  		t.Errorf("expected ANYTHING_GOES='Spaces are ok!' but found %q", env["ANYTHING_goes"])
  1272  	}
  1273  }
  1274  
  1275  // TestTaskTemplateManager_Env_Missing asserts the core env
  1276  // template processing function returns errors when files don't exist
  1277  func TestTaskTemplateManager_Env_Missing(t *testing.T) {
  1278  	t.Parallel()
  1279  	d, err := ioutil.TempDir("", "ct_env_missing")
  1280  	if err != nil {
  1281  		t.Fatalf("err: %v", err)
  1282  	}
  1283  	defer os.RemoveAll(d)
  1284  
  1285  	// Fake writing the file so we don't have to run the whole template manager
  1286  	err = ioutil.WriteFile(filepath.Join(d, "exists.env"), []byte("FOO=bar\n"), 0644)
  1287  	if err != nil {
  1288  		t.Fatalf("error writing template file: %v", err)
  1289  	}
  1290  
  1291  	templates := []*structs.Template{
  1292  		{
  1293  			EmbeddedTmpl: "FOO=bar\n",
  1294  			DestPath:     "exists.env",
  1295  			Envvars:      true,
  1296  		},
  1297  		{
  1298  			EmbeddedTmpl: "WHAT=ever\n",
  1299  			DestPath:     "missing.env",
  1300  			Envvars:      true,
  1301  		},
  1302  	}
  1303  
  1304  	taskEnv := taskenv.NewEmptyBuilder().SetClientTaskRoot(d).Build()
  1305  	if vars, err := loadTemplateEnv(templates, taskEnv); err == nil {
  1306  		t.Fatalf("expected an error but instead got env vars: %#v", vars)
  1307  	}
  1308  }
  1309  
  1310  // TestTaskTemplateManager_Env_InterpolatedDest asserts the core env
  1311  // template processing function handles interpolated destinations
  1312  func TestTaskTemplateManager_Env_InterpolatedDest(t *testing.T) {
  1313  	t.Parallel()
  1314  	require := require.New(t)
  1315  
  1316  	d, err := ioutil.TempDir("", "ct_env_interpolated")
  1317  	if err != nil {
  1318  		t.Fatalf("err: %v", err)
  1319  	}
  1320  	defer os.RemoveAll(d)
  1321  
  1322  	// Fake writing the file so we don't have to run the whole template manager
  1323  	err = ioutil.WriteFile(filepath.Join(d, "exists.env"), []byte("FOO=bar\n"), 0644)
  1324  	if err != nil {
  1325  		t.Fatalf("error writing template file: %v", err)
  1326  	}
  1327  
  1328  	templates := []*structs.Template{
  1329  		{
  1330  			EmbeddedTmpl: "FOO=bar\n",
  1331  			DestPath:     "${NOMAD_META_path}.env",
  1332  			Envvars:      true,
  1333  		},
  1334  	}
  1335  
  1336  	// Build the env
  1337  	taskEnv := taskenv.NewTaskEnv(
  1338  		map[string]string{"NOMAD_META_path": "exists"},
  1339  		map[string]string{"NOMAD_META_path": "exists"},
  1340  		map[string]string{},
  1341  		map[string]string{},
  1342  		d, "")
  1343  
  1344  	vars, err := loadTemplateEnv(templates, taskEnv)
  1345  	require.NoError(err)
  1346  	require.Contains(vars, "FOO")
  1347  	require.Equal(vars["FOO"], "bar")
  1348  }
  1349  
  1350  // TestTaskTemplateManager_Env_Multi asserts the core env
  1351  // template processing function returns combined env vars from multiple
  1352  // templates correctly.
  1353  func TestTaskTemplateManager_Env_Multi(t *testing.T) {
  1354  	t.Parallel()
  1355  	d, err := ioutil.TempDir("", "ct_env_missing")
  1356  	if err != nil {
  1357  		t.Fatalf("err: %v", err)
  1358  	}
  1359  	defer os.RemoveAll(d)
  1360  
  1361  	// Fake writing the files so we don't have to run the whole template manager
  1362  	err = ioutil.WriteFile(filepath.Join(d, "zzz.env"), []byte("FOO=bar\nSHARED=nope\n"), 0644)
  1363  	if err != nil {
  1364  		t.Fatalf("error writing template file 1: %v", err)
  1365  	}
  1366  	err = ioutil.WriteFile(filepath.Join(d, "aaa.env"), []byte("BAR=foo\nSHARED=yup\n"), 0644)
  1367  	if err != nil {
  1368  		t.Fatalf("error writing template file 2: %v", err)
  1369  	}
  1370  
  1371  	// Templates will get loaded in order (not alpha sorted)
  1372  	templates := []*structs.Template{
  1373  		{
  1374  			DestPath: "zzz.env",
  1375  			Envvars:  true,
  1376  		},
  1377  		{
  1378  			DestPath: "aaa.env",
  1379  			Envvars:  true,
  1380  		},
  1381  	}
  1382  
  1383  	taskEnv := taskenv.NewEmptyBuilder().SetClientTaskRoot(d).Build()
  1384  	vars, err := loadTemplateEnv(templates, taskEnv)
  1385  	if err != nil {
  1386  		t.Fatalf("expected no error: %v", err)
  1387  	}
  1388  	if vars["FOO"] != "bar" {
  1389  		t.Errorf("expected FOO=bar but found %q", vars["FOO"])
  1390  	}
  1391  	if vars["BAR"] != "foo" {
  1392  		t.Errorf("expected BAR=foo but found %q", vars["BAR"])
  1393  	}
  1394  	if vars["SHARED"] != "yup" {
  1395  		t.Errorf("expected FOO=bar but found %q", vars["yup"])
  1396  	}
  1397  }
  1398  
  1399  func TestTaskTemplateManager_Rerender_Env(t *testing.T) {
  1400  	t.Parallel()
  1401  	// Make a template that renders based on a key in Consul and sends restart
  1402  	key1 := "bam"
  1403  	key2 := "bar"
  1404  	content1_1 := "cat"
  1405  	content1_2 := "dog"
  1406  	t1 := &structs.Template{
  1407  		EmbeddedTmpl: `
  1408  FOO={{key "bam"}}
  1409  `,
  1410  		DestPath:   "test.env",
  1411  		ChangeMode: structs.TemplateChangeModeRestart,
  1412  		Envvars:    true,
  1413  	}
  1414  	t2 := &structs.Template{
  1415  		EmbeddedTmpl: `
  1416  BAR={{key "bar"}}
  1417  `,
  1418  		DestPath:   "test2.env",
  1419  		ChangeMode: structs.TemplateChangeModeRestart,
  1420  		Envvars:    true,
  1421  	}
  1422  
  1423  	harness := newTestHarness(t, []*structs.Template{t1, t2}, true, false)
  1424  	harness.start(t)
  1425  	defer harness.stop()
  1426  
  1427  	// Ensure no unblock
  1428  	select {
  1429  	case <-harness.mockHooks.UnblockCh:
  1430  		t.Fatalf("Task unblock should have not have been called")
  1431  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
  1432  	}
  1433  
  1434  	// Write the key to Consul
  1435  	harness.consul.SetKV(t, key1, []byte(content1_1))
  1436  	harness.consul.SetKV(t, key2, []byte(content1_1))
  1437  
  1438  	// Wait for the unblock
  1439  	select {
  1440  	case <-harness.mockHooks.UnblockCh:
  1441  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
  1442  		t.Fatalf("Task unblock should have been called")
  1443  	}
  1444  
  1445  	env := harness.envBuilder.Build().Map()
  1446  	if v, ok := env["FOO"]; !ok || v != content1_1 {
  1447  		t.Fatalf("Bad env for FOO: %v %v", v, ok)
  1448  	}
  1449  	if v, ok := env["BAR"]; !ok || v != content1_1 {
  1450  		t.Fatalf("Bad env for BAR: %v %v", v, ok)
  1451  	}
  1452  
  1453  	// Update the keys in Consul
  1454  	harness.consul.SetKV(t, key1, []byte(content1_2))
  1455  
  1456  	// Wait for restart
  1457  	timeout := time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second)
  1458  OUTER:
  1459  	for {
  1460  		select {
  1461  		case <-harness.mockHooks.RestartCh:
  1462  			break OUTER
  1463  		case <-harness.mockHooks.SignalCh:
  1464  			t.Fatalf("Signal with restart policy: %+v", harness.mockHooks)
  1465  		case <-timeout:
  1466  			t.Fatalf("Should have received a restart: %+v", harness.mockHooks)
  1467  		}
  1468  	}
  1469  
  1470  	env = harness.envBuilder.Build().Map()
  1471  	if v, ok := env["FOO"]; !ok || v != content1_2 {
  1472  		t.Fatalf("Bad env for FOO: %v %v", v, ok)
  1473  	}
  1474  	if v, ok := env["BAR"]; !ok || v != content1_1 {
  1475  		t.Fatalf("Bad env for BAR: %v %v", v, ok)
  1476  	}
  1477  }
  1478  
  1479  // TestTaskTemplateManager_Config_ServerName asserts the tls_server_name
  1480  // setting is propagated to consul-template's configuration. See #2776
  1481  func TestTaskTemplateManager_Config_ServerName(t *testing.T) {
  1482  	t.Parallel()
  1483  	c := config.DefaultConfig()
  1484  	c.VaultConfig = &sconfig.VaultConfig{
  1485  		Enabled:       helper.BoolToPtr(true),
  1486  		Addr:          "https://localhost/",
  1487  		TLSServerName: "notlocalhost",
  1488  	}
  1489  	config := &TaskTemplateManagerConfig{
  1490  		ClientConfig: c,
  1491  		VaultToken:   "token",
  1492  	}
  1493  	ctconf, err := newRunnerConfig(config, nil)
  1494  	if err != nil {
  1495  		t.Fatalf("unexpected error: %v", err)
  1496  	}
  1497  
  1498  	if *ctconf.Vault.SSL.ServerName != c.VaultConfig.TLSServerName {
  1499  		t.Fatalf("expected %q but found %q", c.VaultConfig.TLSServerName, *ctconf.Vault.SSL.ServerName)
  1500  	}
  1501  }
  1502  
  1503  // TestTaskTemplateManager_Config_VaultNamespace asserts the Vault namespace setting is
  1504  // propagated to consul-template's configuration.
  1505  func TestTaskTemplateManager_Config_VaultNamespace(t *testing.T) {
  1506  	t.Parallel()
  1507  	assert := assert.New(t)
  1508  
  1509  	testNS := "test-namespace"
  1510  	c := config.DefaultConfig()
  1511  	c.Node = mock.Node()
  1512  	c.VaultConfig = &sconfig.VaultConfig{
  1513  		Enabled:       helper.BoolToPtr(true),
  1514  		Addr:          "https://localhost/",
  1515  		TLSServerName: "notlocalhost",
  1516  		Namespace:     testNS,
  1517  	}
  1518  
  1519  	alloc := mock.Alloc()
  1520  	config := &TaskTemplateManagerConfig{
  1521  		ClientConfig: c,
  1522  		VaultToken:   "token",
  1523  		EnvBuilder:   taskenv.NewBuilder(c.Node, alloc, alloc.Job.TaskGroups[0].Tasks[0], c.Region),
  1524  	}
  1525  
  1526  	ctmplMapping, err := parseTemplateConfigs(config)
  1527  	assert.Nil(err, "Parsing Templates")
  1528  
  1529  	ctconf, err := newRunnerConfig(config, ctmplMapping)
  1530  	assert.Nil(err, "Building Runner Config")
  1531  	assert.Equal(testNS, *ctconf.Vault.Namespace, "Vault Namespace Value")
  1532  }
  1533  
  1534  // TestTaskTemplateManager_Config_VaultNamespace asserts the Vault namespace setting is
  1535  // propagated to consul-template's configuration.
  1536  func TestTaskTemplateManager_Config_VaultNamespace_TaskOverride(t *testing.T) {
  1537  	t.Parallel()
  1538  	assert := assert.New(t)
  1539  
  1540  	testNS := "test-namespace"
  1541  	c := config.DefaultConfig()
  1542  	c.Node = mock.Node()
  1543  	c.VaultConfig = &sconfig.VaultConfig{
  1544  		Enabled:       helper.BoolToPtr(true),
  1545  		Addr:          "https://localhost/",
  1546  		TLSServerName: "notlocalhost",
  1547  		Namespace:     testNS,
  1548  	}
  1549  
  1550  	alloc := mock.Alloc()
  1551  	overriddenNS := "new-namespace"
  1552  
  1553  	// Set the template manager config vault namespace
  1554  	config := &TaskTemplateManagerConfig{
  1555  		ClientConfig:   c,
  1556  		VaultToken:     "token",
  1557  		VaultNamespace: overriddenNS,
  1558  		EnvBuilder:     taskenv.NewBuilder(c.Node, alloc, alloc.Job.TaskGroups[0].Tasks[0], c.Region),
  1559  	}
  1560  
  1561  	ctmplMapping, err := parseTemplateConfigs(config)
  1562  	assert.Nil(err, "Parsing Templates")
  1563  
  1564  	ctconf, err := newRunnerConfig(config, ctmplMapping)
  1565  	assert.Nil(err, "Building Runner Config")
  1566  	assert.Equal(overriddenNS, *ctconf.Vault.Namespace, "Vault Namespace Value")
  1567  }
  1568  
  1569  // TestTaskTemplateManager_Escapes asserts that when sandboxing is enabled
  1570  // interpolated paths are not incorrectly treated as escaping the alloc dir.
  1571  func TestTaskTemplateManager_Escapes(t *testing.T) {
  1572  	t.Parallel()
  1573  
  1574  	clientConf := config.DefaultConfig()
  1575  	require.False(t, clientConf.TemplateConfig.DisableSandbox, "expected sandbox to be disabled")
  1576  
  1577  	// Set a fake alloc dir to make test output more realistic
  1578  	clientConf.AllocDir = "/fake/allocdir"
  1579  
  1580  	clientConf.Node = mock.Node()
  1581  	alloc := mock.Alloc()
  1582  	task := alloc.Job.TaskGroups[0].Tasks[0]
  1583  	logger := testlog.HCLogger(t)
  1584  	allocDir := allocdir.NewAllocDir(logger, filepath.Join(clientConf.AllocDir, alloc.ID))
  1585  	taskDir := allocDir.NewTaskDir(task.Name)
  1586  
  1587  	containerEnv := func() *taskenv.Builder {
  1588  		// To emulate a Docker or exec tasks we must copy the
  1589  		// Set{Alloc,Task,Secrets}Dir logic in taskrunner/task_dir_hook.go
  1590  		b := taskenv.NewBuilder(clientConf.Node, alloc, task, clientConf.Region)
  1591  		b.SetAllocDir(allocdir.SharedAllocContainerPath)
  1592  		b.SetTaskLocalDir(allocdir.TaskLocalContainerPath)
  1593  		b.SetSecretsDir(allocdir.TaskSecretsContainerPath)
  1594  		b.SetClientTaskRoot(taskDir.Dir)
  1595  		b.SetClientSharedAllocDir(taskDir.SharedAllocDir)
  1596  		b.SetClientTaskLocalDir(taskDir.LocalDir)
  1597  		b.SetClientTaskSecretsDir(taskDir.SecretsDir)
  1598  		return b
  1599  	}
  1600  
  1601  	rawExecEnv := func() *taskenv.Builder {
  1602  		// To emulate a unisolated tasks we must copy the
  1603  		// Set{Alloc,Task,Secrets}Dir logic in taskrunner/task_dir_hook.go
  1604  		b := taskenv.NewBuilder(clientConf.Node, alloc, task, clientConf.Region)
  1605  		b.SetAllocDir(taskDir.SharedAllocDir)
  1606  		b.SetTaskLocalDir(taskDir.LocalDir)
  1607  		b.SetSecretsDir(taskDir.SecretsDir)
  1608  		b.SetClientTaskRoot(taskDir.Dir)
  1609  		b.SetClientSharedAllocDir(taskDir.SharedAllocDir)
  1610  		b.SetClientTaskLocalDir(taskDir.LocalDir)
  1611  		b.SetClientTaskSecretsDir(taskDir.SecretsDir)
  1612  		return b
  1613  	}
  1614  
  1615  	cases := []struct {
  1616  		Name   string
  1617  		Config func() *TaskTemplateManagerConfig
  1618  
  1619  		// Expected paths to be returned if Err is nil
  1620  		SourcePath string
  1621  		DestPath   string
  1622  
  1623  		// Err is the expected error to be returned or nil
  1624  		Err error
  1625  	}{
  1626  		{
  1627  			Name: "ContainerOk",
  1628  			Config: func() *TaskTemplateManagerConfig {
  1629  				return &TaskTemplateManagerConfig{
  1630  					ClientConfig: clientConf,
  1631  					TaskDir:      taskDir.Dir,
  1632  					EnvBuilder:   containerEnv(),
  1633  					Templates: []*structs.Template{
  1634  						{
  1635  							SourcePath: "${NOMAD_TASK_DIR}/src",
  1636  							DestPath:   "${NOMAD_SECRETS_DIR}/dst",
  1637  						},
  1638  					},
  1639  				}
  1640  			},
  1641  			SourcePath: filepath.Join(taskDir.Dir, "local/src"),
  1642  			DestPath:   filepath.Join(taskDir.Dir, "secrets/dst"),
  1643  		},
  1644  		{
  1645  			Name: "ContainerSrcEscapesErr",
  1646  			Config: func() *TaskTemplateManagerConfig {
  1647  				return &TaskTemplateManagerConfig{
  1648  					ClientConfig: clientConf,
  1649  					TaskDir:      taskDir.Dir,
  1650  					EnvBuilder:   containerEnv(),
  1651  					Templates: []*structs.Template{
  1652  						{
  1653  							SourcePath: "/etc/src_escapes",
  1654  							DestPath:   "${NOMAD_SECRETS_DIR}/dst",
  1655  						},
  1656  					},
  1657  				}
  1658  			},
  1659  			Err: sourceEscapesErr,
  1660  		},
  1661  		{
  1662  			Name: "ContainerSrcEscapesOk",
  1663  			Config: func() *TaskTemplateManagerConfig {
  1664  				unsafeConf := clientConf.Copy()
  1665  				unsafeConf.TemplateConfig.DisableSandbox = true
  1666  				return &TaskTemplateManagerConfig{
  1667  					ClientConfig: unsafeConf,
  1668  					TaskDir:      taskDir.Dir,
  1669  					EnvBuilder:   containerEnv(),
  1670  					Templates: []*structs.Template{
  1671  						{
  1672  							SourcePath: "/etc/src_escapes_ok",
  1673  							DestPath:   "${NOMAD_SECRETS_DIR}/dst",
  1674  						},
  1675  					},
  1676  				}
  1677  			},
  1678  			SourcePath: "/etc/src_escapes_ok",
  1679  			DestPath:   filepath.Join(taskDir.Dir, "secrets/dst"),
  1680  		},
  1681  		{
  1682  			Name: "ContainerDstAbsoluteOk",
  1683  			Config: func() *TaskTemplateManagerConfig {
  1684  				return &TaskTemplateManagerConfig{
  1685  					ClientConfig: clientConf,
  1686  					TaskDir:      taskDir.Dir,
  1687  					EnvBuilder:   containerEnv(),
  1688  					Templates: []*structs.Template{
  1689  						{
  1690  							SourcePath: "${NOMAD_TASK_DIR}/src",
  1691  							DestPath:   "/etc/absolutely_relative",
  1692  						},
  1693  					},
  1694  				}
  1695  			},
  1696  			SourcePath: filepath.Join(taskDir.Dir, "local/src"),
  1697  			DestPath:   filepath.Join(taskDir.Dir, "etc/absolutely_relative"),
  1698  		},
  1699  		{
  1700  			Name: "ContainerDstAbsoluteEscapesErr",
  1701  			Config: func() *TaskTemplateManagerConfig {
  1702  				return &TaskTemplateManagerConfig{
  1703  					ClientConfig: clientConf,
  1704  					TaskDir:      taskDir.Dir,
  1705  					EnvBuilder:   containerEnv(),
  1706  					Templates: []*structs.Template{
  1707  						{
  1708  							SourcePath: "${NOMAD_TASK_DIR}/src",
  1709  							DestPath:   "../escapes",
  1710  						},
  1711  					},
  1712  				}
  1713  			},
  1714  			Err: destEscapesErr,
  1715  		},
  1716  		{
  1717  			Name: "ContainerDstAbsoluteEscapesOk",
  1718  			Config: func() *TaskTemplateManagerConfig {
  1719  				unsafeConf := clientConf.Copy()
  1720  				unsafeConf.TemplateConfig.DisableSandbox = true
  1721  				return &TaskTemplateManagerConfig{
  1722  					ClientConfig: unsafeConf,
  1723  					TaskDir:      taskDir.Dir,
  1724  					EnvBuilder:   containerEnv(),
  1725  					Templates: []*structs.Template{
  1726  						{
  1727  							SourcePath: "${NOMAD_TASK_DIR}/src",
  1728  							DestPath:   "../escapes",
  1729  						},
  1730  					},
  1731  				}
  1732  			},
  1733  			SourcePath: filepath.Join(taskDir.Dir, "local/src"),
  1734  			DestPath:   filepath.Join(taskDir.Dir, "..", "escapes"),
  1735  		},
  1736  		//TODO: Fix this test. I *think* it should pass. The double
  1737  		//      joining of the task dir onto the destination seems like
  1738  		//      a bug. https://github.com/hashicorp/nomad/issues/9389
  1739  		{
  1740  			Name: "RawExecOk",
  1741  			Config: func() *TaskTemplateManagerConfig {
  1742  				return &TaskTemplateManagerConfig{
  1743  					ClientConfig: clientConf,
  1744  					TaskDir:      taskDir.Dir,
  1745  					EnvBuilder:   rawExecEnv(),
  1746  					Templates: []*structs.Template{
  1747  						{
  1748  							SourcePath: "${NOMAD_TASK_DIR}/src",
  1749  							DestPath:   "${NOMAD_SECRETS_DIR}/dst",
  1750  						},
  1751  					},
  1752  				}
  1753  			},
  1754  			SourcePath: filepath.Join(taskDir.Dir, "local/src"),
  1755  			DestPath:   filepath.Join(taskDir.Dir, "secrets/dst"),
  1756  		},
  1757  		{
  1758  			Name: "RawExecSrcEscapesErr",
  1759  			Config: func() *TaskTemplateManagerConfig {
  1760  				return &TaskTemplateManagerConfig{
  1761  					ClientConfig: clientConf,
  1762  					TaskDir:      taskDir.Dir,
  1763  					EnvBuilder:   rawExecEnv(),
  1764  					Templates: []*structs.Template{
  1765  						{
  1766  							SourcePath: "/etc/src_escapes",
  1767  							DestPath:   "${NOMAD_SECRETS_DIR}/dst",
  1768  						},
  1769  					},
  1770  				}
  1771  			},
  1772  			Err: sourceEscapesErr,
  1773  		},
  1774  		{
  1775  			Name: "RawExecDstAbsoluteOk",
  1776  			Config: func() *TaskTemplateManagerConfig {
  1777  				return &TaskTemplateManagerConfig{
  1778  					ClientConfig: clientConf,
  1779  					TaskDir:      taskDir.Dir,
  1780  					EnvBuilder:   rawExecEnv(),
  1781  					Templates: []*structs.Template{
  1782  						{
  1783  							SourcePath: "${NOMAD_TASK_DIR}/src",
  1784  							DestPath:   "/etc/absolutely_relative",
  1785  						},
  1786  					},
  1787  				}
  1788  			},
  1789  			SourcePath: filepath.Join(taskDir.Dir, "local/src"),
  1790  			DestPath:   filepath.Join(taskDir.Dir, "etc/absolutely_relative"),
  1791  		},
  1792  	}
  1793  
  1794  	for i := range cases {
  1795  		tc := cases[i]
  1796  		t.Run(tc.Name, func(t *testing.T) {
  1797  			config := tc.Config()
  1798  			mapping, err := parseTemplateConfigs(config)
  1799  			if tc.Err == nil {
  1800  				// Ok path
  1801  				require.NoError(t, err)
  1802  				require.NotNil(t, mapping)
  1803  				require.Len(t, mapping, 1)
  1804  				for k := range mapping {
  1805  					require.Equal(t, tc.SourcePath, *k.Source)
  1806  					require.Equal(t, tc.DestPath, *k.Destination)
  1807  					t.Logf("Rendering %s => %s", *k.Source, *k.Destination)
  1808  				}
  1809  			} else {
  1810  				// Err path
  1811  				assert.EqualError(t, err, tc.Err.Error())
  1812  				require.Nil(t, mapping)
  1813  			}
  1814  
  1815  		})
  1816  	}
  1817  }
  1818  
  1819  func TestTaskTemplateManager_BlockedEvents(t *testing.T) {
  1820  	// The tests sets a template that need keys 0, 1, 2, 3, 4,
  1821  	// then subsequently sets 0, 1, 2 keys
  1822  	// then asserts that templates are still blocked on 3 and 4,
  1823  	// and check that we got the relevant task events
  1824  	t.Parallel()
  1825  	require := require.New(t)
  1826  
  1827  	// Make a template that will render based on a key in Consul
  1828  	var embedded string
  1829  	for i := 0; i < 5; i++ {
  1830  		embedded += fmt.Sprintf(`{{key "%d"}}`, i)
  1831  	}
  1832  
  1833  	file := "my.tmpl"
  1834  	template := &structs.Template{
  1835  		EmbeddedTmpl: embedded,
  1836  		DestPath:     file,
  1837  		ChangeMode:   structs.TemplateChangeModeNoop,
  1838  	}
  1839  
  1840  	harness := newTestHarness(t, []*structs.Template{template}, true, false)
  1841  	harness.setEmitRate(100 * time.Millisecond)
  1842  	harness.start(t)
  1843  	defer harness.stop()
  1844  
  1845  	missingKeys := func(e *structs.TaskEvent) ([]string, int) {
  1846  		missingRexp := regexp.MustCompile(`kv.block\(([0-9]*)\)`)
  1847  		moreRexp := regexp.MustCompile(`and ([0-9]*) more`)
  1848  
  1849  		missingMatch := missingRexp.FindAllStringSubmatch(e.DisplayMessage, -1)
  1850  		moreMatch := moreRexp.FindAllStringSubmatch(e.DisplayMessage, -1)
  1851  
  1852  		missing := make([]string, len(missingMatch))
  1853  		for i, v := range missingMatch {
  1854  			missing[i] = v[1]
  1855  		}
  1856  		sort.Strings(missing)
  1857  
  1858  		more := 0
  1859  		if len(moreMatch) != 0 {
  1860  			more, _ = strconv.Atoi(moreMatch[0][1])
  1861  		}
  1862  		return missing, more
  1863  
  1864  	}
  1865  
  1866  	// Ensure that we get a blocked event
  1867  	select {
  1868  	case <-harness.mockHooks.UnblockCh:
  1869  		t.Fatalf("Task unblock should have not have been called")
  1870  	case <-harness.mockHooks.EmitEventCh:
  1871  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
  1872  		t.Fatalf("timeout")
  1873  	}
  1874  
  1875  	// Check to see we got a correct message
  1876  	// assert that all 0-4 keys are missing
  1877  	require.Len(harness.mockHooks.Events, 1)
  1878  	t.Logf("first message: %v", harness.mockHooks.Events[0])
  1879  	missing, more := missingKeys(harness.mockHooks.Events[0])
  1880  	require.Equal(5, len(missing)+more)
  1881  	require.Contains(harness.mockHooks.Events[0].DisplayMessage, "and 2 more")
  1882  
  1883  	// Write 0-2 keys to Consul
  1884  	for i := 0; i < 3; i++ {
  1885  		harness.consul.SetKV(t, fmt.Sprintf("%d", i), []byte{0xa})
  1886  	}
  1887  
  1888  	// Ensure that we get a blocked event
  1889  	isExpectedFinalEvent := func(e *structs.TaskEvent) bool {
  1890  		missing, more := missingKeys(e)
  1891  		return reflect.DeepEqual(missing, []string{"3", "4"}) && more == 0
  1892  	}
  1893  	timeout := time.After(time.Second * time.Duration(testutil.TestMultiplier()))
  1894  WAIT_LOOP:
  1895  	for {
  1896  		select {
  1897  		case <-harness.mockHooks.UnblockCh:
  1898  			t.Errorf("Task unblock should have not have been called")
  1899  		case e := <-harness.mockHooks.EmitEventCh:
  1900  			t.Logf("received event: %v", e.DisplayMessage)
  1901  			if isExpectedFinalEvent(e) {
  1902  				break WAIT_LOOP
  1903  			}
  1904  		case <-timeout:
  1905  			t.Errorf("timeout")
  1906  		}
  1907  	}
  1908  
  1909  	// Check to see we got a correct message
  1910  	event := harness.mockHooks.Events[len(harness.mockHooks.Events)-1]
  1911  	if !isExpectedFinalEvent(event) {
  1912  		t.Logf("received all events: %v", pretty.Sprint(harness.mockHooks.Events))
  1913  
  1914  		t.Fatalf("bad event, expected only 3 and 5 blocked got: %q", event.DisplayMessage)
  1915  	}
  1916  }