github.com/manicqin/nomad@v0.9.5/client/allocrunner/taskrunner/template/template_test.go (about)

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