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