github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/client/allocrunner/taskrunner/template/template_test.go (about)

     1  package template
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  	"reflect"
    11  	"regexp"
    12  	"runtime"
    13  	"sort"
    14  	"strconv"
    15  	"strings"
    16  	"sync"
    17  	"syscall"
    18  	"testing"
    19  	"time"
    20  
    21  	templateconfig "github.com/hashicorp/consul-template/config"
    22  	ctestutil "github.com/hashicorp/consul/sdk/testutil"
    23  	"github.com/hashicorp/nomad/ci"
    24  	"github.com/hashicorp/nomad/client/allocdir"
    25  	"github.com/hashicorp/nomad/client/config"
    26  	"github.com/hashicorp/nomad/client/taskenv"
    27  	clienttestutil "github.com/hashicorp/nomad/client/testutil"
    28  	"github.com/hashicorp/nomad/helper/pointer"
    29  	"github.com/hashicorp/nomad/helper/testlog"
    30  	"github.com/hashicorp/nomad/helper/users"
    31  	"github.com/hashicorp/nomad/helper/uuid"
    32  	"github.com/hashicorp/nomad/nomad/mock"
    33  	"github.com/hashicorp/nomad/nomad/structs"
    34  	sconfig "github.com/hashicorp/nomad/nomad/structs/config"
    35  	"github.com/hashicorp/nomad/testutil"
    36  	"github.com/kr/pretty"
    37  	"github.com/shoenig/test/must"
    38  	"github.com/stretchr/testify/assert"
    39  	"github.com/stretchr/testify/require"
    40  )
    41  
    42  const (
    43  	// TestTaskName is the name of the injected task. It should appear in the
    44  	// environment variable $NOMAD_TASK_NAME
    45  	TestTaskName = "test-task"
    46  )
    47  
    48  // MockTaskHooks is a mock of the TaskHooks interface useful for testing
    49  type MockTaskHooks struct {
    50  	Restarts  int
    51  	RestartCh chan struct{}
    52  
    53  	Signals    []string
    54  	SignalCh   chan struct{}
    55  	signalLock sync.Mutex
    56  
    57  	// SignalError is returned when Signal is called on the mock hook
    58  	SignalError error
    59  
    60  	UnblockCh chan struct{}
    61  
    62  	KillEvent *structs.TaskEvent
    63  	KillCh    chan *structs.TaskEvent
    64  
    65  	Events      []*structs.TaskEvent
    66  	EmitEventCh chan *structs.TaskEvent
    67  
    68  	// hasHandle can be set to simulate restoring a task after client restart
    69  	hasHandle bool
    70  }
    71  
    72  func NewMockTaskHooks() *MockTaskHooks {
    73  	return &MockTaskHooks{
    74  		UnblockCh:   make(chan struct{}, 1),
    75  		RestartCh:   make(chan struct{}, 1),
    76  		SignalCh:    make(chan struct{}, 1),
    77  		KillCh:      make(chan *structs.TaskEvent, 1),
    78  		EmitEventCh: make(chan *structs.TaskEvent, 1),
    79  	}
    80  }
    81  func (m *MockTaskHooks) Restart(ctx context.Context, event *structs.TaskEvent, failure bool) error {
    82  	m.Restarts++
    83  	select {
    84  	case m.RestartCh <- struct{}{}:
    85  	default:
    86  	}
    87  	return nil
    88  }
    89  
    90  func (m *MockTaskHooks) Signal(event *structs.TaskEvent, s string) error {
    91  	m.signalLock.Lock()
    92  	m.Signals = append(m.Signals, s)
    93  	m.signalLock.Unlock()
    94  	select {
    95  	case m.SignalCh <- struct{}{}:
    96  	default:
    97  	}
    98  
    99  	return m.SignalError
   100  }
   101  
   102  func (m *MockTaskHooks) Kill(ctx context.Context, event *structs.TaskEvent) error {
   103  	m.KillEvent = event
   104  	select {
   105  	case m.KillCh <- event:
   106  	default:
   107  	}
   108  	return nil
   109  }
   110  
   111  func (m *MockTaskHooks) IsRunning() bool {
   112  	return m.hasHandle
   113  }
   114  
   115  func (m *MockTaskHooks) EmitEvent(event *structs.TaskEvent) {
   116  	m.Events = append(m.Events, event)
   117  	select {
   118  	case m.EmitEventCh <- event:
   119  	case <-m.EmitEventCh:
   120  		m.EmitEventCh <- event
   121  	}
   122  }
   123  
   124  func (m *MockTaskHooks) SetState(state string, event *structs.TaskEvent) {}
   125  
   126  // mockExecutor implements script executor interface
   127  type mockExecutor struct {
   128  	DesiredExit int
   129  	DesiredErr  error
   130  }
   131  
   132  func (m *mockExecutor) Exec(timeout time.Duration, cmd string, args []string) ([]byte, int, error) {
   133  	return []byte{}, m.DesiredExit, m.DesiredErr
   134  }
   135  
   136  // testHarness is used to test the TaskTemplateManager by spinning up
   137  // Consul/Vault as needed
   138  type testHarness struct {
   139  	manager        *TaskTemplateManager
   140  	mockHooks      *MockTaskHooks
   141  	templates      []*structs.Template
   142  	envBuilder     *taskenv.Builder
   143  	node           *structs.Node
   144  	config         *config.Config
   145  	vaultToken     string
   146  	taskDir        string
   147  	vault          *testutil.TestVault
   148  	consul         *ctestutil.TestServer
   149  	emitRate       time.Duration
   150  	nomadNamespace string
   151  }
   152  
   153  // newTestHarness returns a harness starting a dev consul and vault server,
   154  // building the appropriate config and creating a TaskTemplateManager
   155  func newTestHarness(t *testing.T, templates []*structs.Template, consul, vault bool) *testHarness {
   156  	region := "global"
   157  	mockNode := mock.Node()
   158  
   159  	harness := &testHarness{
   160  		mockHooks: NewMockTaskHooks(),
   161  		templates: templates,
   162  		node:      mockNode,
   163  		config: &config.Config{
   164  			Node:   mockNode,
   165  			Region: region,
   166  			TemplateConfig: &config.ClientTemplateConfig{
   167  				FunctionDenylist: config.DefaultTemplateFunctionDenylist,
   168  				DisableSandbox:   false,
   169  				ConsulRetry:      &config.RetryConfig{Backoff: pointer.Of(10 * time.Millisecond)},
   170  			}},
   171  		emitRate: DefaultMaxTemplateEventRate,
   172  	}
   173  
   174  	// Build the task environment
   175  	a := mock.Alloc()
   176  	task := a.Job.TaskGroups[0].Tasks[0]
   177  	task.Name = TestTaskName
   178  	harness.envBuilder = taskenv.NewBuilder(harness.node, a, task, region)
   179  	harness.nomadNamespace = a.Namespace
   180  
   181  	// Make a tempdir
   182  	harness.taskDir = t.TempDir()
   183  	harness.envBuilder.SetClientTaskRoot(harness.taskDir)
   184  
   185  	if consul {
   186  		var err error
   187  		harness.consul, err = ctestutil.NewTestServerConfigT(t, func(c *ctestutil.TestServerConfig) {
   188  			c.Peering = nil // fix for older versions of Consul (<1.13.0) that don't support peering
   189  		})
   190  		if err != nil {
   191  			t.Fatalf("error starting test Consul server: %v", err)
   192  		}
   193  		harness.config.ConsulConfig = &sconfig.ConsulConfig{
   194  			Addr: harness.consul.HTTPAddr,
   195  		}
   196  	}
   197  
   198  	if vault {
   199  		harness.vault = testutil.NewTestVault(t)
   200  		harness.config.VaultConfig = harness.vault.Config
   201  		harness.vaultToken = harness.vault.RootToken
   202  	}
   203  
   204  	return harness
   205  }
   206  
   207  func (h *testHarness) start(t *testing.T) {
   208  	if err := h.startWithErr(); err != nil {
   209  		t.Fatalf("failed to build task template manager: %v", err)
   210  	}
   211  }
   212  
   213  func (h *testHarness) startWithErr() error {
   214  	var err error
   215  	h.manager, err = NewTaskTemplateManager(&TaskTemplateManagerConfig{
   216  		UnblockCh:            h.mockHooks.UnblockCh,
   217  		Lifecycle:            h.mockHooks,
   218  		Events:               h.mockHooks,
   219  		Templates:            h.templates,
   220  		ClientConfig:         h.config,
   221  		VaultToken:           h.vaultToken,
   222  		TaskDir:              h.taskDir,
   223  		EnvBuilder:           h.envBuilder,
   224  		MaxTemplateEventRate: h.emitRate,
   225  	})
   226  	return err
   227  }
   228  
   229  func (h *testHarness) setEmitRate(d time.Duration) {
   230  	h.emitRate = d
   231  }
   232  
   233  // stop is used to stop any running Vault or Consul server plus the task manager
   234  func (h *testHarness) stop() {
   235  	if h.vault != nil {
   236  		h.vault.Stop()
   237  	}
   238  	if h.consul != nil {
   239  		h.consul.Stop()
   240  	}
   241  	if h.manager != nil {
   242  		h.manager.Stop()
   243  	}
   244  	if h.taskDir != "" {
   245  		os.RemoveAll(h.taskDir)
   246  	}
   247  }
   248  
   249  func TestTaskTemplateManager_InvalidConfig(t *testing.T) {
   250  	ci.Parallel(t)
   251  	hooks := NewMockTaskHooks()
   252  	clientConfig := &config.Config{Region: "global"}
   253  	taskDir := "foo"
   254  	a := mock.Alloc()
   255  	envBuilder := taskenv.NewBuilder(mock.Node(), a, a.Job.TaskGroups[0].Tasks[0], clientConfig.Region)
   256  
   257  	cases := []struct {
   258  		name        string
   259  		config      *TaskTemplateManagerConfig
   260  		expectedErr string
   261  	}{
   262  		{
   263  			name:        "nil config",
   264  			config:      nil,
   265  			expectedErr: "Nil config passed",
   266  		},
   267  		{
   268  			name: "bad lifecycle hooks",
   269  			config: &TaskTemplateManagerConfig{
   270  				UnblockCh:            hooks.UnblockCh,
   271  				Events:               hooks,
   272  				ClientConfig:         clientConfig,
   273  				TaskDir:              taskDir,
   274  				EnvBuilder:           envBuilder,
   275  				MaxTemplateEventRate: DefaultMaxTemplateEventRate,
   276  			},
   277  			expectedErr: "lifecycle hooks",
   278  		},
   279  		{
   280  			name: "bad event hooks",
   281  			config: &TaskTemplateManagerConfig{
   282  				UnblockCh:            hooks.UnblockCh,
   283  				Lifecycle:            hooks,
   284  				ClientConfig:         clientConfig,
   285  				TaskDir:              taskDir,
   286  				EnvBuilder:           envBuilder,
   287  				MaxTemplateEventRate: DefaultMaxTemplateEventRate,
   288  			},
   289  			expectedErr: "event hook",
   290  		},
   291  		{
   292  			name: "bad client config",
   293  			config: &TaskTemplateManagerConfig{
   294  				UnblockCh:            hooks.UnblockCh,
   295  				Lifecycle:            hooks,
   296  				Events:               hooks,
   297  				TaskDir:              taskDir,
   298  				EnvBuilder:           envBuilder,
   299  				MaxTemplateEventRate: DefaultMaxTemplateEventRate,
   300  			},
   301  			expectedErr: "client config",
   302  		},
   303  		{
   304  			name: "bad task dir",
   305  			config: &TaskTemplateManagerConfig{
   306  				UnblockCh:            hooks.UnblockCh,
   307  				ClientConfig:         clientConfig,
   308  				Lifecycle:            hooks,
   309  				Events:               hooks,
   310  				EnvBuilder:           envBuilder,
   311  				MaxTemplateEventRate: DefaultMaxTemplateEventRate,
   312  			},
   313  			expectedErr: "task directory",
   314  		},
   315  		{
   316  			name: "bad env builder",
   317  			config: &TaskTemplateManagerConfig{
   318  				UnblockCh:            hooks.UnblockCh,
   319  				ClientConfig:         clientConfig,
   320  				Lifecycle:            hooks,
   321  				Events:               hooks,
   322  				TaskDir:              taskDir,
   323  				MaxTemplateEventRate: DefaultMaxTemplateEventRate,
   324  			},
   325  			expectedErr: "task environment",
   326  		},
   327  		{
   328  			name: "bad max event rate",
   329  			config: &TaskTemplateManagerConfig{
   330  				UnblockCh:    hooks.UnblockCh,
   331  				ClientConfig: clientConfig,
   332  				Lifecycle:    hooks,
   333  				Events:       hooks,
   334  				TaskDir:      taskDir,
   335  				EnvBuilder:   envBuilder,
   336  			},
   337  			expectedErr: "template event rate",
   338  		},
   339  		{
   340  			name: "valid",
   341  			config: &TaskTemplateManagerConfig{
   342  				UnblockCh:            hooks.UnblockCh,
   343  				ClientConfig:         clientConfig,
   344  				Lifecycle:            hooks,
   345  				Events:               hooks,
   346  				TaskDir:              taskDir,
   347  				EnvBuilder:           envBuilder,
   348  				MaxTemplateEventRate: DefaultMaxTemplateEventRate,
   349  			},
   350  		},
   351  		{
   352  			name: "invalid signal",
   353  			config: &TaskTemplateManagerConfig{
   354  				UnblockCh: hooks.UnblockCh,
   355  				Templates: []*structs.Template{
   356  					{
   357  						DestPath:     "foo",
   358  						EmbeddedTmpl: "hello, world",
   359  						ChangeMode:   structs.TemplateChangeModeSignal,
   360  						ChangeSignal: "foobarbaz",
   361  					},
   362  				},
   363  				ClientConfig:         clientConfig,
   364  				Lifecycle:            hooks,
   365  				Events:               hooks,
   366  				TaskDir:              taskDir,
   367  				EnvBuilder:           envBuilder,
   368  				MaxTemplateEventRate: DefaultMaxTemplateEventRate,
   369  			},
   370  			expectedErr: "parse signal",
   371  		},
   372  	}
   373  
   374  	for _, c := range cases {
   375  		t.Run(c.name, func(t *testing.T) {
   376  			_, err := NewTaskTemplateManager(c.config)
   377  			if err != nil {
   378  				if c.expectedErr == "" {
   379  					t.Fatalf("unexpected error: %v", err)
   380  				} else if !strings.Contains(err.Error(), c.expectedErr) {
   381  					t.Fatalf("expected error to contain %q; got %q", c.expectedErr, err.Error())
   382  				}
   383  			} else if c.expectedErr != "" {
   384  				t.Fatalf("expected an error to contain %q", c.expectedErr)
   385  			}
   386  		})
   387  	}
   388  }
   389  
   390  func TestTaskTemplateManager_HostPath(t *testing.T) {
   391  	ci.Parallel(t)
   392  	// Make a template that will render immediately and write it to a tmp file
   393  	f, err := os.CreateTemp("", "")
   394  	if err != nil {
   395  		t.Fatalf("Bad: %v", err)
   396  	}
   397  	defer f.Close()
   398  	defer os.Remove(f.Name())
   399  
   400  	content := "hello, world!"
   401  	if _, err := io.WriteString(f, content); err != nil {
   402  		t.Fatalf("Bad: %v", err)
   403  	}
   404  
   405  	file := "my.tmpl"
   406  	template := &structs.Template{
   407  		SourcePath: f.Name(),
   408  		DestPath:   file,
   409  		ChangeMode: structs.TemplateChangeModeNoop,
   410  	}
   411  
   412  	harness := newTestHarness(t, []*structs.Template{template}, false, false)
   413  	harness.config.TemplateConfig.DisableSandbox = true
   414  	err = harness.startWithErr()
   415  	if err != nil {
   416  		t.Fatalf("couldn't setup initial harness: %v", err)
   417  	}
   418  	defer harness.stop()
   419  
   420  	// Wait for the unblock
   421  	select {
   422  	case <-harness.mockHooks.UnblockCh:
   423  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   424  		t.Fatalf("Task unblock should have been called")
   425  	}
   426  
   427  	// Check the file is there
   428  	path := filepath.Join(harness.taskDir, file)
   429  	raw, err := os.ReadFile(path)
   430  	if err != nil {
   431  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
   432  	}
   433  
   434  	if s := string(raw); s != content {
   435  		t.Fatalf("Unexpected template data; got %q, want %q", s, content)
   436  	}
   437  
   438  	// Change the config to disallow host sources
   439  	harness = newTestHarness(t, []*structs.Template{template}, false, false)
   440  	err = harness.startWithErr()
   441  	if err == nil || !strings.Contains(err.Error(), "escapes alloc directory") {
   442  		t.Fatalf("Expected absolute template path disallowed for %q: %v",
   443  			template.SourcePath, err)
   444  	}
   445  
   446  	template.SourcePath = "../../../../../../" + file
   447  	harness = newTestHarness(t, []*structs.Template{template}, false, false)
   448  	err = harness.startWithErr()
   449  	if err == nil || !strings.Contains(err.Error(), "escapes alloc directory") {
   450  		t.Fatalf("Expected directory traversal out of %q disallowed for %q: %v",
   451  			harness.taskDir, template.SourcePath, err)
   452  	}
   453  
   454  	// Build a new task environment
   455  	a := mock.Alloc()
   456  	task := a.Job.TaskGroups[0].Tasks[0]
   457  	task.Name = TestTaskName
   458  	task.Meta = map[string]string{"ESCAPE": "../"}
   459  
   460  	template.SourcePath = "${NOMAD_META_ESCAPE}${NOMAD_META_ESCAPE}${NOMAD_META_ESCAPE}${NOMAD_META_ESCAPE}${NOMAD_META_ESCAPE}${NOMAD_META_ESCAPE}" + file
   461  	harness = newTestHarness(t, []*structs.Template{template}, false, false)
   462  	harness.envBuilder = taskenv.NewBuilder(harness.node, a, task, "global")
   463  	err = harness.startWithErr()
   464  	if err == nil || !strings.Contains(err.Error(), "escapes alloc directory") {
   465  		t.Fatalf("Expected directory traversal out of %q via interpolation disallowed for %q: %v",
   466  			harness.taskDir, template.SourcePath, err)
   467  	}
   468  
   469  	// Test with desination too
   470  	template.SourcePath = f.Name()
   471  	template.DestPath = "../../../../../../" + file
   472  	harness = newTestHarness(t, []*structs.Template{template}, false, false)
   473  	harness.envBuilder = taskenv.NewBuilder(harness.node, a, task, "global")
   474  	err = harness.startWithErr()
   475  	if err == nil || !strings.Contains(err.Error(), "escapes alloc directory") {
   476  		t.Fatalf("Expected directory traversal out of %q via interpolation disallowed for %q: %v",
   477  			harness.taskDir, template.SourcePath, err)
   478  	}
   479  
   480  }
   481  
   482  func TestTaskTemplateManager_Unblock_Static(t *testing.T) {
   483  	ci.Parallel(t)
   484  	// Make a template that will render immediately
   485  	content := "hello, world!"
   486  	file := "my.tmpl"
   487  	template := &structs.Template{
   488  		EmbeddedTmpl: content,
   489  		DestPath:     file,
   490  		ChangeMode:   structs.TemplateChangeModeNoop,
   491  	}
   492  
   493  	harness := newTestHarness(t, []*structs.Template{template}, false, false)
   494  	harness.start(t)
   495  	defer harness.stop()
   496  
   497  	// Wait for the unblock
   498  	select {
   499  	case <-harness.mockHooks.UnblockCh:
   500  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   501  		t.Fatalf("Task unblock should have been called")
   502  	}
   503  
   504  	// Check the file is there
   505  	path := filepath.Join(harness.taskDir, file)
   506  	raw, err := os.ReadFile(path)
   507  	if err != nil {
   508  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
   509  	}
   510  
   511  	if s := string(raw); s != content {
   512  		t.Fatalf("Unexpected template data; got %q, want %q", s, content)
   513  	}
   514  }
   515  
   516  func TestTaskTemplateManager_Permissions(t *testing.T) {
   517  	clienttestutil.RequireRoot(t)
   518  	ci.Parallel(t)
   519  	// Make a template that will render immediately
   520  	content := "hello, world!"
   521  	file := "my.tmpl"
   522  	template := &structs.Template{
   523  		EmbeddedTmpl: content,
   524  		DestPath:     file,
   525  		ChangeMode:   structs.TemplateChangeModeNoop,
   526  		Perms:        "777",
   527  		Uid:          pointer.Of(503),
   528  		Gid:          pointer.Of(20),
   529  	}
   530  
   531  	harness := newTestHarness(t, []*structs.Template{template}, false, false)
   532  	harness.start(t)
   533  	defer harness.stop()
   534  
   535  	// Wait for the unblock
   536  	select {
   537  	case <-harness.mockHooks.UnblockCh:
   538  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   539  		t.Fatalf("Task unblock should have been called")
   540  	}
   541  
   542  	// Check the file is there
   543  	path := filepath.Join(harness.taskDir, file)
   544  	fi, err := os.Stat(path)
   545  	if err != nil {
   546  		t.Fatalf("Failed to stat file: %v", err)
   547  	}
   548  
   549  	if m := fi.Mode(); m != os.ModePerm {
   550  		t.Fatalf("Got mode %v; want %v", m, os.ModePerm)
   551  	}
   552  
   553  	sys := fi.Sys()
   554  	uid := pointer.Of(int(sys.(*syscall.Stat_t).Uid))
   555  	gid := pointer.Of(int(sys.(*syscall.Stat_t).Gid))
   556  
   557  	must.Eq(t, template.Uid, uid)
   558  	must.Eq(t, template.Gid, gid)
   559  }
   560  
   561  func TestTaskTemplateManager_Unblock_Static_NomadEnv(t *testing.T) {
   562  	ci.Parallel(t)
   563  	// Make a template that will render immediately
   564  	content := `Hello Nomad Task: {{env "NOMAD_TASK_NAME"}}`
   565  	expected := fmt.Sprintf("Hello Nomad Task: %s", TestTaskName)
   566  	file := "my.tmpl"
   567  	template := &structs.Template{
   568  		EmbeddedTmpl: content,
   569  		DestPath:     file,
   570  		ChangeMode:   structs.TemplateChangeModeNoop,
   571  	}
   572  
   573  	harness := newTestHarness(t, []*structs.Template{template}, false, false)
   574  	harness.start(t)
   575  	defer harness.stop()
   576  
   577  	// Wait for the unblock
   578  	select {
   579  	case <-harness.mockHooks.UnblockCh:
   580  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   581  		t.Fatalf("Task unblock should have been called")
   582  	}
   583  
   584  	// Check the file is there
   585  	path := filepath.Join(harness.taskDir, file)
   586  	raw, err := os.ReadFile(path)
   587  	if err != nil {
   588  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
   589  	}
   590  
   591  	if s := string(raw); s != expected {
   592  		t.Fatalf("Unexpected template data; got %q, want %q", s, expected)
   593  	}
   594  }
   595  
   596  func TestTaskTemplateManager_Unblock_Static_AlreadyRendered(t *testing.T) {
   597  	ci.Parallel(t)
   598  	// Make a template that will render immediately
   599  	content := "hello, world!"
   600  	file := "my.tmpl"
   601  	template := &structs.Template{
   602  		EmbeddedTmpl: content,
   603  		DestPath:     file,
   604  		ChangeMode:   structs.TemplateChangeModeNoop,
   605  	}
   606  
   607  	harness := newTestHarness(t, []*structs.Template{template}, false, false)
   608  
   609  	// Write the contents
   610  	path := filepath.Join(harness.taskDir, file)
   611  	if err := os.WriteFile(path, []byte(content), 0777); err != nil {
   612  		t.Fatalf("Failed to write data: %v", err)
   613  	}
   614  
   615  	harness.start(t)
   616  	defer harness.stop()
   617  
   618  	// Wait for the unblock
   619  	select {
   620  	case <-harness.mockHooks.UnblockCh:
   621  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   622  		t.Fatalf("Task unblock should have been called")
   623  	}
   624  
   625  	// Check the file is there
   626  	path = filepath.Join(harness.taskDir, file)
   627  	raw, err := os.ReadFile(path)
   628  	if err != nil {
   629  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
   630  	}
   631  
   632  	if s := string(raw); s != content {
   633  		t.Fatalf("Unexpected template data; got %q, want %q", s, content)
   634  	}
   635  }
   636  
   637  func TestTaskTemplateManager_Unblock_Consul(t *testing.T) {
   638  	ci.Parallel(t)
   639  	// Make a template that will render based on a key in Consul
   640  	key := "foo"
   641  	content := "barbaz"
   642  	embedded := fmt.Sprintf(`{{key "%s"}}`, key)
   643  	file := "my.tmpl"
   644  	template := &structs.Template{
   645  		EmbeddedTmpl: embedded,
   646  		DestPath:     file,
   647  		ChangeMode:   structs.TemplateChangeModeNoop,
   648  	}
   649  
   650  	harness := newTestHarness(t, []*structs.Template{template}, true, false)
   651  	harness.start(t)
   652  	defer harness.stop()
   653  
   654  	// Ensure no unblock
   655  	select {
   656  	case <-harness.mockHooks.UnblockCh:
   657  		t.Fatalf("Task unblock should have not have been called")
   658  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
   659  	}
   660  
   661  	// Write the key to Consul
   662  	harness.consul.SetKV(t, key, []byte(content))
   663  
   664  	// Wait for the unblock
   665  	select {
   666  	case <-harness.mockHooks.UnblockCh:
   667  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   668  		t.Fatalf("Task unblock should have been called")
   669  	}
   670  
   671  	// Check the file is there
   672  	path := filepath.Join(harness.taskDir, file)
   673  	raw, err := os.ReadFile(path)
   674  	if err != nil {
   675  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
   676  	}
   677  
   678  	if s := string(raw); s != content {
   679  		t.Fatalf("Unexpected template data; got %q, want %q", s, content)
   680  	}
   681  }
   682  
   683  func TestTaskTemplateManager_Unblock_Vault(t *testing.T) {
   684  	ci.Parallel(t)
   685  	require := require.New(t)
   686  	// Make a template that will render based on a key in Vault
   687  	vaultPath := "secret/data/password"
   688  	key := "password"
   689  	content := "barbaz"
   690  	embedded := fmt.Sprintf(`{{with secret "%s"}}{{.Data.data.%s}}{{end}}`, vaultPath, key)
   691  	file := "my.tmpl"
   692  	template := &structs.Template{
   693  		EmbeddedTmpl: embedded,
   694  		DestPath:     file,
   695  		ChangeMode:   structs.TemplateChangeModeNoop,
   696  	}
   697  
   698  	harness := newTestHarness(t, []*structs.Template{template}, false, true)
   699  	harness.start(t)
   700  	defer harness.stop()
   701  
   702  	// Ensure no unblock
   703  	select {
   704  	case <-harness.mockHooks.UnblockCh:
   705  		t.Fatalf("Task unblock should not have been called")
   706  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
   707  	}
   708  
   709  	// Write the secret to Vault
   710  	logical := harness.vault.Client.Logical()
   711  	_, err := logical.Write(vaultPath, map[string]interface{}{"data": map[string]interface{}{key: content}})
   712  	require.NoError(err)
   713  
   714  	// Wait for the unblock
   715  	select {
   716  	case <-harness.mockHooks.UnblockCh:
   717  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   718  		t.Fatalf("Task unblock should have been called")
   719  	}
   720  
   721  	// Check the file is there
   722  	path := filepath.Join(harness.taskDir, file)
   723  	raw, err := os.ReadFile(path)
   724  	if err != nil {
   725  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
   726  	}
   727  
   728  	if s := string(raw); s != content {
   729  		t.Fatalf("Unexpected template data; got %q, want %q", s, content)
   730  	}
   731  }
   732  
   733  func TestTaskTemplateManager_Unblock_Multi_Template(t *testing.T) {
   734  	ci.Parallel(t)
   735  	// Make a template that will render immediately
   736  	staticContent := "hello, world!"
   737  	staticFile := "my.tmpl"
   738  	template := &structs.Template{
   739  		EmbeddedTmpl: staticContent,
   740  		DestPath:     staticFile,
   741  		ChangeMode:   structs.TemplateChangeModeNoop,
   742  	}
   743  
   744  	// Make a template that will render based on a key in Consul
   745  	consulKey := "foo"
   746  	consulContent := "barbaz"
   747  	consulEmbedded := fmt.Sprintf(`{{key "%s"}}`, consulKey)
   748  	consulFile := "consul.tmpl"
   749  	template2 := &structs.Template{
   750  		EmbeddedTmpl: consulEmbedded,
   751  		DestPath:     consulFile,
   752  		ChangeMode:   structs.TemplateChangeModeNoop,
   753  	}
   754  
   755  	harness := newTestHarness(t, []*structs.Template{template, template2}, true, false)
   756  	harness.start(t)
   757  	defer harness.stop()
   758  
   759  	// Ensure no unblock
   760  	select {
   761  	case <-harness.mockHooks.UnblockCh:
   762  		t.Fatalf("Task unblock should have not have been called")
   763  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
   764  	}
   765  
   766  	// Check that the static file has been rendered
   767  	path := filepath.Join(harness.taskDir, staticFile)
   768  	raw, err := os.ReadFile(path)
   769  	if err != nil {
   770  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
   771  	}
   772  
   773  	if s := string(raw); s != staticContent {
   774  		t.Fatalf("Unexpected template data; got %q, want %q", s, staticContent)
   775  	}
   776  
   777  	// Write the key to Consul
   778  	harness.consul.SetKV(t, consulKey, []byte(consulContent))
   779  
   780  	// Wait for the unblock
   781  	select {
   782  	case <-harness.mockHooks.UnblockCh:
   783  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   784  		t.Fatalf("Task unblock should have been called")
   785  	}
   786  
   787  	// Check the consul file is there
   788  	path = filepath.Join(harness.taskDir, consulFile)
   789  	raw, err = os.ReadFile(path)
   790  	if err != nil {
   791  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
   792  	}
   793  
   794  	if s := string(raw); s != consulContent {
   795  		t.Fatalf("Unexpected template data; got %q, want %q", s, consulContent)
   796  	}
   797  }
   798  
   799  // TestTaskTemplateManager_FirstRender_Restored tests that a task that's been
   800  // restored renders and triggers its change mode if the template has changed
   801  func TestTaskTemplateManager_FirstRender_Restored(t *testing.T) {
   802  	ci.Parallel(t)
   803  	require := require.New(t)
   804  	// Make a template that will render based on a key in Vault
   805  	vaultPath := "secret/data/password"
   806  	key := "password"
   807  	content := "barbaz"
   808  	embedded := fmt.Sprintf(`{{with secret "%s"}}{{.Data.data.%s}}{{end}}`, vaultPath, key)
   809  	file := "my.tmpl"
   810  	template := &structs.Template{
   811  		EmbeddedTmpl: embedded,
   812  		DestPath:     file,
   813  		ChangeMode:   structs.TemplateChangeModeRestart,
   814  	}
   815  
   816  	harness := newTestHarness(t, []*structs.Template{template}, false, true)
   817  	harness.start(t)
   818  	defer harness.stop()
   819  
   820  	// Ensure no unblock
   821  	select {
   822  	case <-harness.mockHooks.UnblockCh:
   823  		require.Fail("Task unblock should not have been called")
   824  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
   825  	}
   826  
   827  	// Write the secret to Vault
   828  	logical := harness.vault.Client.Logical()
   829  	_, err := logical.Write(vaultPath, map[string]interface{}{"data": map[string]interface{}{key: content}})
   830  	require.NoError(err)
   831  
   832  	// Wait for the unblock
   833  	select {
   834  	case <-harness.mockHooks.UnblockCh:
   835  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   836  		require.Fail("Task unblock should have been called")
   837  	}
   838  
   839  	// Check the file is there
   840  	path := filepath.Join(harness.taskDir, file)
   841  	raw, err := os.ReadFile(path)
   842  	require.NoError(err, "Failed to read rendered template from %q", path)
   843  	require.Equal(content, string(raw), "Unexpected template data; got %s, want %q", raw, content)
   844  
   845  	// task is now running
   846  	harness.mockHooks.hasHandle = true
   847  
   848  	// simulate a client restart
   849  	harness.manager.Stop()
   850  	harness.mockHooks.UnblockCh = make(chan struct{}, 1)
   851  	harness.start(t)
   852  
   853  	// Wait for the unblock
   854  	select {
   855  	case <-harness.mockHooks.UnblockCh:
   856  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   857  		require.Fail("Task unblock should have been called")
   858  	}
   859  
   860  	select {
   861  	case <-harness.mockHooks.RestartCh:
   862  		require.Fail("should not have restarted", harness.mockHooks)
   863  	case <-harness.mockHooks.SignalCh:
   864  		require.Fail("should not have restarted", harness.mockHooks)
   865  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
   866  	}
   867  
   868  	// simulate a client restart and TTL expiry
   869  	harness.manager.Stop()
   870  	content = "bazbar"
   871  	_, err = logical.Write(vaultPath, map[string]interface{}{"data": map[string]interface{}{key: content}})
   872  	require.NoError(err)
   873  	harness.mockHooks.UnblockCh = make(chan struct{}, 1)
   874  	harness.start(t)
   875  
   876  	// Wait for the unblock
   877  	select {
   878  	case <-harness.mockHooks.UnblockCh:
   879  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   880  		require.Fail("Task unblock should have been called")
   881  	}
   882  
   883  	// Wait for restart
   884  	timeout := time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second)
   885  OUTER:
   886  	for {
   887  		select {
   888  		case <-harness.mockHooks.RestartCh:
   889  			break OUTER
   890  		case <-harness.mockHooks.SignalCh:
   891  			require.Fail("Signal with restart policy", harness.mockHooks)
   892  		case <-timeout:
   893  			require.Fail("Should have received a restart", harness.mockHooks)
   894  		}
   895  	}
   896  }
   897  
   898  func TestTaskTemplateManager_Rerender_Noop(t *testing.T) {
   899  	ci.Parallel(t)
   900  	// Make a template that will render based on a key in Consul
   901  	key := "foo"
   902  	content1 := "bar"
   903  	content2 := "baz"
   904  	embedded := fmt.Sprintf(`{{key "%s"}}`, key)
   905  	file := "my.tmpl"
   906  	template := &structs.Template{
   907  		EmbeddedTmpl: embedded,
   908  		DestPath:     file,
   909  		ChangeMode:   structs.TemplateChangeModeNoop,
   910  	}
   911  
   912  	harness := newTestHarness(t, []*structs.Template{template}, true, false)
   913  	harness.start(t)
   914  	defer harness.stop()
   915  
   916  	// Ensure no unblock
   917  	select {
   918  	case <-harness.mockHooks.UnblockCh:
   919  		t.Fatalf("Task unblock should have not have been called")
   920  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
   921  	}
   922  
   923  	// Write the key to Consul
   924  	harness.consul.SetKV(t, key, []byte(content1))
   925  
   926  	// Wait for the unblock
   927  	select {
   928  	case <-harness.mockHooks.UnblockCh:
   929  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
   930  		t.Fatalf("Task unblock should have been called")
   931  	}
   932  
   933  	// Check the file is there
   934  	path := filepath.Join(harness.taskDir, file)
   935  	raw, err := os.ReadFile(path)
   936  	if err != nil {
   937  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
   938  	}
   939  
   940  	if s := string(raw); s != content1 {
   941  		t.Fatalf("Unexpected template data; got %q, want %q", s, content1)
   942  	}
   943  
   944  	// Update the key in Consul
   945  	harness.consul.SetKV(t, key, []byte(content2))
   946  
   947  	select {
   948  	case <-harness.mockHooks.RestartCh:
   949  		t.Fatalf("Noop ignored: %+v", harness.mockHooks)
   950  	case <-harness.mockHooks.SignalCh:
   951  		t.Fatalf("Noop ignored: %+v", harness.mockHooks)
   952  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
   953  	}
   954  
   955  	// Check the file has been updated
   956  	path = filepath.Join(harness.taskDir, file)
   957  	raw, err = os.ReadFile(path)
   958  	if err != nil {
   959  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
   960  	}
   961  
   962  	if s := string(raw); s != content2 {
   963  		t.Fatalf("Unexpected template data; got %q, want %q", s, content2)
   964  	}
   965  }
   966  
   967  func TestTaskTemplateManager_Rerender_Signal(t *testing.T) {
   968  	ci.Parallel(t)
   969  	// Make a template that renders based on a key in Consul and sends SIGALRM
   970  	key1 := "foo"
   971  	content1_1 := "bar"
   972  	content1_2 := "baz"
   973  	embedded1 := fmt.Sprintf(`{{key "%s"}}`, key1)
   974  	file1 := "my.tmpl"
   975  	template := &structs.Template{
   976  		EmbeddedTmpl: embedded1,
   977  		DestPath:     file1,
   978  		ChangeMode:   structs.TemplateChangeModeSignal,
   979  		ChangeSignal: "SIGALRM",
   980  	}
   981  
   982  	// Make a template that renders based on a key in Consul and sends SIGBUS
   983  	key2 := "bam"
   984  	content2_1 := "cat"
   985  	content2_2 := "dog"
   986  	embedded2 := fmt.Sprintf(`{{key "%s"}}`, key2)
   987  	file2 := "my-second.tmpl"
   988  	template2 := &structs.Template{
   989  		EmbeddedTmpl: embedded2,
   990  		DestPath:     file2,
   991  		ChangeMode:   structs.TemplateChangeModeSignal,
   992  		ChangeSignal: "SIGBUS",
   993  	}
   994  
   995  	harness := newTestHarness(t, []*structs.Template{template, template2}, true, false)
   996  	harness.start(t)
   997  	defer harness.stop()
   998  
   999  	// Ensure no unblock
  1000  	select {
  1001  	case <-harness.mockHooks.UnblockCh:
  1002  		t.Fatalf("Task unblock should have not have been called")
  1003  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
  1004  	}
  1005  
  1006  	// Write the key to Consul
  1007  	harness.consul.SetKV(t, key1, []byte(content1_1))
  1008  	harness.consul.SetKV(t, key2, []byte(content2_1))
  1009  
  1010  	// Wait for the unblock
  1011  	select {
  1012  	case <-harness.mockHooks.UnblockCh:
  1013  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
  1014  		t.Fatalf("Task unblock should have been called")
  1015  	}
  1016  
  1017  	if len(harness.mockHooks.Signals) != 0 {
  1018  		t.Fatalf("Should not have received any signals: %+v", harness.mockHooks)
  1019  	}
  1020  
  1021  	// Update the keys in Consul
  1022  	harness.consul.SetKV(t, key1, []byte(content1_2))
  1023  	harness.consul.SetKV(t, key2, []byte(content2_2))
  1024  
  1025  	// Wait for signals
  1026  	timeout := time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second)
  1027  OUTER:
  1028  	for {
  1029  		select {
  1030  		case <-harness.mockHooks.RestartCh:
  1031  			t.Fatalf("Restart with signal policy: %+v", harness.mockHooks)
  1032  		case <-harness.mockHooks.SignalCh:
  1033  			harness.mockHooks.signalLock.Lock()
  1034  			s := harness.mockHooks.Signals
  1035  			harness.mockHooks.signalLock.Unlock()
  1036  			if len(s) != 2 {
  1037  				continue
  1038  			}
  1039  			break OUTER
  1040  		case <-timeout:
  1041  			t.Fatalf("Should have received two signals: %+v", harness.mockHooks)
  1042  		}
  1043  	}
  1044  
  1045  	// Check the files have  been updated
  1046  	path := filepath.Join(harness.taskDir, file1)
  1047  	raw, err := os.ReadFile(path)
  1048  	if err != nil {
  1049  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
  1050  	}
  1051  
  1052  	if s := string(raw); s != content1_2 {
  1053  		t.Fatalf("Unexpected template data; got %q, want %q", s, content1_2)
  1054  	}
  1055  
  1056  	path = filepath.Join(harness.taskDir, file2)
  1057  	raw, err = os.ReadFile(path)
  1058  	if err != nil {
  1059  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
  1060  	}
  1061  
  1062  	if s := string(raw); s != content2_2 {
  1063  		t.Fatalf("Unexpected template data; got %q, want %q", s, content2_2)
  1064  	}
  1065  }
  1066  
  1067  func TestTaskTemplateManager_Rerender_Restart(t *testing.T) {
  1068  	ci.Parallel(t)
  1069  	// Make a template that renders based on a key in Consul and sends restart
  1070  	key1 := "bam"
  1071  	content1_1 := "cat"
  1072  	content1_2 := "dog"
  1073  	embedded1 := fmt.Sprintf(`{{key "%s"}}`, key1)
  1074  	file1 := "my.tmpl"
  1075  	template := &structs.Template{
  1076  		EmbeddedTmpl: embedded1,
  1077  		DestPath:     file1,
  1078  		ChangeMode:   structs.TemplateChangeModeRestart,
  1079  	}
  1080  
  1081  	harness := newTestHarness(t, []*structs.Template{template}, true, false)
  1082  	harness.start(t)
  1083  	defer harness.stop()
  1084  
  1085  	// Ensure no unblock
  1086  	select {
  1087  	case <-harness.mockHooks.UnblockCh:
  1088  		t.Fatalf("Task unblock should have not have been called")
  1089  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
  1090  	}
  1091  
  1092  	// Write the key to Consul
  1093  	harness.consul.SetKV(t, key1, []byte(content1_1))
  1094  
  1095  	// Wait for the unblock
  1096  	select {
  1097  	case <-harness.mockHooks.UnblockCh:
  1098  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
  1099  		t.Fatalf("Task unblock should have been called")
  1100  	}
  1101  
  1102  	// Update the keys in Consul
  1103  	harness.consul.SetKV(t, key1, []byte(content1_2))
  1104  
  1105  	// Wait for restart
  1106  	timeout := time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second)
  1107  OUTER:
  1108  	for {
  1109  		select {
  1110  		case <-harness.mockHooks.RestartCh:
  1111  			break OUTER
  1112  		case <-harness.mockHooks.SignalCh:
  1113  			t.Fatalf("Signal with restart policy: %+v", harness.mockHooks)
  1114  		case <-timeout:
  1115  			t.Fatalf("Should have received a restart: %+v", harness.mockHooks)
  1116  		}
  1117  	}
  1118  
  1119  	// Check the files have  been updated
  1120  	path := filepath.Join(harness.taskDir, file1)
  1121  	raw, err := os.ReadFile(path)
  1122  	if err != nil {
  1123  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
  1124  	}
  1125  
  1126  	if s := string(raw); s != content1_2 {
  1127  		t.Fatalf("Unexpected template data; got %q, want %q", s, content1_2)
  1128  	}
  1129  }
  1130  
  1131  func TestTaskTemplateManager_Interpolate_Destination(t *testing.T) {
  1132  	ci.Parallel(t)
  1133  	// Make a template that will have its destination interpolated
  1134  	content := "hello, world!"
  1135  	file := "${node.unique.id}.tmpl"
  1136  	template := &structs.Template{
  1137  		EmbeddedTmpl: content,
  1138  		DestPath:     file,
  1139  		ChangeMode:   structs.TemplateChangeModeNoop,
  1140  	}
  1141  
  1142  	harness := newTestHarness(t, []*structs.Template{template}, false, false)
  1143  	harness.start(t)
  1144  	defer harness.stop()
  1145  
  1146  	// Ensure unblock
  1147  	select {
  1148  	case <-harness.mockHooks.UnblockCh:
  1149  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
  1150  		t.Fatalf("Task unblock should have been called")
  1151  	}
  1152  
  1153  	// Check the file is there
  1154  	actual := fmt.Sprintf("%s.tmpl", harness.node.ID)
  1155  	path := filepath.Join(harness.taskDir, actual)
  1156  	raw, err := os.ReadFile(path)
  1157  	if err != nil {
  1158  		t.Fatalf("Failed to read rendered template from %q: %v", path, err)
  1159  	}
  1160  
  1161  	if s := string(raw); s != content {
  1162  		t.Fatalf("Unexpected template data; got %q, want %q", s, content)
  1163  	}
  1164  }
  1165  
  1166  func TestTaskTemplateManager_Signal_Error(t *testing.T) {
  1167  	ci.Parallel(t)
  1168  	require := require.New(t)
  1169  
  1170  	// Make a template that renders based on a key in Consul and sends SIGALRM
  1171  	key1 := "foo"
  1172  	content1 := "bar"
  1173  	content2 := "baz"
  1174  	embedded1 := fmt.Sprintf(`{{key "%s"}}`, key1)
  1175  	file1 := "my.tmpl"
  1176  	template := &structs.Template{
  1177  		EmbeddedTmpl: embedded1,
  1178  		DestPath:     file1,
  1179  		ChangeMode:   structs.TemplateChangeModeSignal,
  1180  		ChangeSignal: "SIGALRM",
  1181  	}
  1182  
  1183  	harness := newTestHarness(t, []*structs.Template{template}, true, false)
  1184  	harness.start(t)
  1185  	defer harness.stop()
  1186  
  1187  	harness.mockHooks.SignalError = fmt.Errorf("test error")
  1188  
  1189  	// Write the key to Consul
  1190  	harness.consul.SetKV(t, key1, []byte(content1))
  1191  
  1192  	// Wait a little
  1193  	select {
  1194  	case <-harness.mockHooks.UnblockCh:
  1195  	case <-time.After(time.Duration(2*testutil.TestMultiplier()) * time.Second):
  1196  		t.Fatalf("Should have received unblock: %+v", harness.mockHooks)
  1197  	}
  1198  
  1199  	// Write the key to Consul
  1200  	harness.consul.SetKV(t, key1, []byte(content2))
  1201  
  1202  	// Wait for kill channel
  1203  	select {
  1204  	case <-harness.mockHooks.KillCh:
  1205  		break
  1206  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
  1207  		t.Fatalf("Should have received a signals: %+v", harness.mockHooks)
  1208  	}
  1209  
  1210  	require.NotNil(harness.mockHooks.KillEvent)
  1211  	require.Contains(harness.mockHooks.KillEvent.DisplayMessage, "failed to send signals")
  1212  }
  1213  
  1214  func TestTaskTemplateManager_ScriptExecution(t *testing.T) {
  1215  	ci.Parallel(t)
  1216  
  1217  	// Make a template that renders based on a key in Consul and triggers script
  1218  	key1 := "bam"
  1219  	key2 := "bar"
  1220  	content1_1 := "cat"
  1221  	content1_2 := "dog"
  1222  	t1 := &structs.Template{
  1223  		EmbeddedTmpl: `
  1224  FOO={{key "bam"}}
  1225  `,
  1226  		DestPath:   "test.env",
  1227  		ChangeMode: structs.TemplateChangeModeScript,
  1228  		ChangeScript: &structs.ChangeScript{
  1229  			Command:     "/bin/foo",
  1230  			Args:        []string{},
  1231  			Timeout:     5 * time.Second,
  1232  			FailOnError: false,
  1233  		},
  1234  		Envvars: true,
  1235  	}
  1236  	t2 := &structs.Template{
  1237  		EmbeddedTmpl: `
  1238  BAR={{key "bar"}}
  1239  `,
  1240  		DestPath:   "test2.env",
  1241  		ChangeMode: structs.TemplateChangeModeScript,
  1242  		ChangeScript: &structs.ChangeScript{
  1243  			Command:     "/bin/foo",
  1244  			Args:        []string{},
  1245  			Timeout:     5 * time.Second,
  1246  			FailOnError: false,
  1247  		},
  1248  		Envvars: true,
  1249  	}
  1250  
  1251  	me := mockExecutor{DesiredExit: 0, DesiredErr: nil}
  1252  	harness := newTestHarness(t, []*structs.Template{t1, t2}, true, false)
  1253  	harness.start(t)
  1254  	harness.manager.SetDriverHandle(&me)
  1255  	defer harness.stop()
  1256  
  1257  	// Ensure no unblock
  1258  	select {
  1259  	case <-harness.mockHooks.UnblockCh:
  1260  		require.Fail(t, "Task unblock should not have been called")
  1261  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
  1262  	}
  1263  
  1264  	// Write the key to Consul
  1265  	harness.consul.SetKV(t, key1, []byte(content1_1))
  1266  	harness.consul.SetKV(t, key2, []byte(content1_1))
  1267  
  1268  	// Wait for the unblock
  1269  	select {
  1270  	case <-harness.mockHooks.UnblockCh:
  1271  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
  1272  		require.Fail(t, "Task unblock should have been called")
  1273  	}
  1274  
  1275  	// Update the keys in Consul
  1276  	harness.consul.SetKV(t, key1, []byte(content1_2))
  1277  
  1278  	// Wait for script execution
  1279  	timeout := time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second)
  1280  OUTER:
  1281  	for {
  1282  		select {
  1283  		case <-harness.mockHooks.RestartCh:
  1284  			require.Fail(t, "restart not expected")
  1285  		case ev := <-harness.mockHooks.EmitEventCh:
  1286  			if strings.Contains(ev.DisplayMessage, t1.ChangeScript.Command) {
  1287  				break OUTER
  1288  			}
  1289  		case <-harness.mockHooks.SignalCh:
  1290  			require.Fail(t, "signal not expected")
  1291  		case <-timeout:
  1292  			require.Fail(t, "should have received an event")
  1293  		}
  1294  	}
  1295  }
  1296  
  1297  // TestTaskTemplateManager_ScriptExecutionFailTask tests whether we fail the
  1298  // task upon script execution failure if that's how it's configured.
  1299  func TestTaskTemplateManager_ScriptExecutionFailTask(t *testing.T) {
  1300  	ci.Parallel(t)
  1301  	require := require.New(t)
  1302  
  1303  	// Make a template that renders based on a key in Consul and triggers script
  1304  	key1 := "bam"
  1305  	key2 := "bar"
  1306  	content1_1 := "cat"
  1307  	content1_2 := "dog"
  1308  	t1 := &structs.Template{
  1309  		EmbeddedTmpl: `
  1310  FOO={{key "bam"}}
  1311  `,
  1312  		DestPath:   "test.env",
  1313  		ChangeMode: structs.TemplateChangeModeScript,
  1314  		ChangeScript: &structs.ChangeScript{
  1315  			Command:     "/bin/foo",
  1316  			Args:        []string{},
  1317  			Timeout:     5 * time.Second,
  1318  			FailOnError: true,
  1319  		},
  1320  		Envvars: true,
  1321  	}
  1322  	t2 := &structs.Template{
  1323  		EmbeddedTmpl: `
  1324  BAR={{key "bar"}}
  1325  `,
  1326  		DestPath:   "test2.env",
  1327  		ChangeMode: structs.TemplateChangeModeScript,
  1328  		ChangeScript: &structs.ChangeScript{
  1329  			Command:     "/bin/foo",
  1330  			Args:        []string{},
  1331  			Timeout:     5 * time.Second,
  1332  			FailOnError: false,
  1333  		},
  1334  		Envvars: true,
  1335  	}
  1336  
  1337  	me := mockExecutor{DesiredExit: 1, DesiredErr: fmt.Errorf("Script failed")}
  1338  	harness := newTestHarness(t, []*structs.Template{t1, t2}, true, false)
  1339  	harness.start(t)
  1340  	harness.manager.SetDriverHandle(&me)
  1341  	defer harness.stop()
  1342  
  1343  	// Ensure no unblock
  1344  	select {
  1345  	case <-harness.mockHooks.UnblockCh:
  1346  		require.Fail("Task unblock should not have been called")
  1347  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
  1348  	}
  1349  
  1350  	// Write the key to Consul
  1351  	harness.consul.SetKV(t, key1, []byte(content1_1))
  1352  	harness.consul.SetKV(t, key2, []byte(content1_1))
  1353  
  1354  	// Wait for the unblock
  1355  	select {
  1356  	case <-harness.mockHooks.UnblockCh:
  1357  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
  1358  		require.Fail("Task unblock should have been called")
  1359  	}
  1360  
  1361  	// Update the keys in Consul
  1362  	harness.consul.SetKV(t, key1, []byte(content1_2))
  1363  
  1364  	// Wait for kill channel
  1365  	select {
  1366  	case <-harness.mockHooks.KillCh:
  1367  		break
  1368  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
  1369  		require.Fail("Should have received a signals: %+v", harness.mockHooks)
  1370  	}
  1371  
  1372  	require.NotNil(harness.mockHooks.KillEvent)
  1373  	require.Contains(harness.mockHooks.KillEvent.DisplayMessage, "task is being killed")
  1374  }
  1375  
  1376  func TestTaskTemplateManager_ChangeModeMixed(t *testing.T) {
  1377  	ci.Parallel(t)
  1378  
  1379  	templateRestart := &structs.Template{
  1380  		EmbeddedTmpl: `
  1381  RESTART={{key "restart"}}
  1382  COMMON={{key "common"}}
  1383  `,
  1384  		DestPath:   "restart",
  1385  		ChangeMode: structs.TemplateChangeModeRestart,
  1386  	}
  1387  	templateSignal := &structs.Template{
  1388  		EmbeddedTmpl: `
  1389  SIGNAL={{key "signal"}}
  1390  COMMON={{key "common"}}
  1391  `,
  1392  		DestPath:     "signal",
  1393  		ChangeMode:   structs.TemplateChangeModeSignal,
  1394  		ChangeSignal: "SIGALRM",
  1395  	}
  1396  	templateScript := &structs.Template{
  1397  		EmbeddedTmpl: `
  1398  SCRIPT={{key "script"}}
  1399  COMMON={{key "common"}}
  1400  `,
  1401  		DestPath:   "script",
  1402  		ChangeMode: structs.TemplateChangeModeScript,
  1403  		ChangeScript: &structs.ChangeScript{
  1404  			Command:     "/bin/foo",
  1405  			Args:        []string{},
  1406  			Timeout:     5 * time.Second,
  1407  			FailOnError: true,
  1408  		},
  1409  	}
  1410  	templates := []*structs.Template{
  1411  		templateRestart,
  1412  		templateSignal,
  1413  		templateScript,
  1414  	}
  1415  
  1416  	me := mockExecutor{DesiredExit: 0, DesiredErr: nil}
  1417  	harness := newTestHarness(t, templates, true, false)
  1418  	harness.start(t)
  1419  	harness.manager.SetDriverHandle(&me)
  1420  	defer harness.stop()
  1421  
  1422  	// Ensure no unblock
  1423  	select {
  1424  	case <-harness.mockHooks.UnblockCh:
  1425  		require.Fail(t, "Task unblock should not have been called")
  1426  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
  1427  	}
  1428  
  1429  	// Write the key to Consul
  1430  	harness.consul.SetKV(t, "common", []byte(fmt.Sprintf("%v", time.Now())))
  1431  	harness.consul.SetKV(t, "restart", []byte(fmt.Sprintf("%v", time.Now())))
  1432  	harness.consul.SetKV(t, "signal", []byte(fmt.Sprintf("%v", time.Now())))
  1433  	harness.consul.SetKV(t, "script", []byte(fmt.Sprintf("%v", time.Now())))
  1434  
  1435  	// Wait for the unblock
  1436  	select {
  1437  	case <-harness.mockHooks.UnblockCh:
  1438  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
  1439  		require.Fail(t, "Task unblock should have been called")
  1440  	}
  1441  
  1442  	t.Run("restart takes precedence", func(t *testing.T) {
  1443  		// Update the common Consul key.
  1444  		harness.consul.SetKV(t, "common", []byte(fmt.Sprintf("%v", time.Now())))
  1445  
  1446  		// Collect some events.
  1447  		timeout := time.After(time.Duration(3*testutil.TestMultiplier()) * time.Second)
  1448  		events := []*structs.TaskEvent{}
  1449  	OUTER:
  1450  		for {
  1451  			select {
  1452  			case <-harness.mockHooks.RestartCh:
  1453  				// Consume restarts so the channel is clean for other tests.
  1454  			case <-harness.mockHooks.SignalCh:
  1455  				require.Fail(t, "signal not expected")
  1456  			case ev := <-harness.mockHooks.EmitEventCh:
  1457  				events = append(events, ev)
  1458  			case <-timeout:
  1459  				break OUTER
  1460  			}
  1461  		}
  1462  
  1463  		for _, ev := range events {
  1464  			require.NotContains(t, ev.DisplayMessage, templateScript.ChangeScript.Command)
  1465  			require.NotContains(t, ev.Type, structs.TaskSignaling)
  1466  		}
  1467  	})
  1468  
  1469  	t.Run("signal and script", func(t *testing.T) {
  1470  		// Update the signal and script Consul keys.
  1471  		harness.consul.SetKV(t, "signal", []byte(fmt.Sprintf("%v", time.Now())))
  1472  		harness.consul.SetKV(t, "script", []byte(fmt.Sprintf("%v", time.Now())))
  1473  
  1474  		// Wait for a events.
  1475  		var gotSignal, gotScript bool
  1476  		timeout := time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second)
  1477  		for {
  1478  			select {
  1479  			case <-harness.mockHooks.RestartCh:
  1480  				require.Fail(t, "restart not expected")
  1481  			case ev := <-harness.mockHooks.EmitEventCh:
  1482  				if strings.Contains(ev.DisplayMessage, templateScript.ChangeScript.Command) {
  1483  					// Make sure we only run script once.
  1484  					require.False(t, gotScript)
  1485  					gotScript = true
  1486  				}
  1487  			case <-harness.mockHooks.SignalCh:
  1488  				// Make sure we only signal once.
  1489  				require.False(t, gotSignal)
  1490  				gotSignal = true
  1491  			case <-timeout:
  1492  				require.Fail(t, "timeout waiting for script and signal")
  1493  			}
  1494  
  1495  			if gotScript && gotSignal {
  1496  				break
  1497  			}
  1498  		}
  1499  	})
  1500  }
  1501  
  1502  // TestTaskTemplateManager_FiltersProcessEnvVars asserts that we only render
  1503  // environment variables found in task env-vars and not read the nomad host
  1504  // process environment variables.  nomad host process environment variables
  1505  // are to be treated the same as not found environment variables.
  1506  func TestTaskTemplateManager_FiltersEnvVars(t *testing.T) {
  1507  
  1508  	t.Setenv("NOMAD_TASK_NAME", "should be overridden by task")
  1509  
  1510  	testenv := "TESTENV_" + strings.ReplaceAll(uuid.Generate(), "-", "")
  1511  	t.Setenv(testenv, "MY_TEST_VALUE")
  1512  
  1513  	// Make a template that will render immediately
  1514  	content := `Hello Nomad Task: {{env "NOMAD_TASK_NAME"}}
  1515  TEST_ENV: {{ env "` + testenv + `" }}
  1516  TEST_ENV_NOT_FOUND: {{env "` + testenv + `_NOTFOUND" }}`
  1517  	expected := fmt.Sprintf("Hello Nomad Task: %s\nTEST_ENV: \nTEST_ENV_NOT_FOUND: ", TestTaskName)
  1518  
  1519  	file := "my.tmpl"
  1520  	template := &structs.Template{
  1521  		EmbeddedTmpl: content,
  1522  		DestPath:     file,
  1523  		ChangeMode:   structs.TemplateChangeModeNoop,
  1524  	}
  1525  
  1526  	harness := newTestHarness(t, []*structs.Template{template}, false, false)
  1527  	harness.start(t)
  1528  	defer harness.stop()
  1529  
  1530  	// Wait for the unblock
  1531  	select {
  1532  	case <-harness.mockHooks.UnblockCh:
  1533  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
  1534  		require.Fail(t, "Task unblock should have been called")
  1535  	}
  1536  
  1537  	// Check the file is there
  1538  	path := filepath.Join(harness.taskDir, file)
  1539  	raw, err := os.ReadFile(path)
  1540  	require.NoError(t, err)
  1541  
  1542  	require.Equal(t, expected, string(raw))
  1543  }
  1544  
  1545  // TestTaskTemplateManager_Env asserts templates with the env flag set are read
  1546  // into the task's environment.
  1547  func TestTaskTemplateManager_Env(t *testing.T) {
  1548  	ci.Parallel(t)
  1549  	template := &structs.Template{
  1550  		EmbeddedTmpl: `
  1551  # Comment lines are ok
  1552  
  1553  FOO=bar
  1554  foo=123
  1555  ANYTHING_goes=Spaces are=ok!
  1556  `,
  1557  		DestPath:   "test.env",
  1558  		ChangeMode: structs.TemplateChangeModeNoop,
  1559  		Envvars:    true,
  1560  	}
  1561  	harness := newTestHarness(t, []*structs.Template{template}, true, false)
  1562  	harness.start(t)
  1563  	defer harness.stop()
  1564  
  1565  	// Wait a little
  1566  	select {
  1567  	case <-harness.mockHooks.UnblockCh:
  1568  	case <-time.After(time.Duration(2*testutil.TestMultiplier()) * time.Second):
  1569  		t.Fatalf("Should have received unblock: %+v", harness.mockHooks)
  1570  	}
  1571  
  1572  	// Validate environment
  1573  	env := harness.envBuilder.Build().Map()
  1574  	if len(env) < 3 {
  1575  		t.Fatalf("expected at least 3 env vars but found %d:\n%#v\n", len(env), env)
  1576  	}
  1577  	if env["FOO"] != "bar" {
  1578  		t.Errorf("expected FOO=bar but found %q", env["FOO"])
  1579  	}
  1580  	if env["foo"] != "123" {
  1581  		t.Errorf("expected foo=123 but found %q", env["foo"])
  1582  	}
  1583  	if env["ANYTHING_goes"] != "Spaces are=ok!" {
  1584  		t.Errorf("expected ANYTHING_GOES='Spaces are ok!' but found %q", env["ANYTHING_goes"])
  1585  	}
  1586  }
  1587  
  1588  // TestTaskTemplateManager_Env_Missing asserts the core env
  1589  // template processing function returns errors when files don't exist
  1590  func TestTaskTemplateManager_Env_Missing(t *testing.T) {
  1591  	ci.Parallel(t)
  1592  	d := t.TempDir()
  1593  
  1594  	// Fake writing the file so we don't have to run the whole template manager
  1595  	err := os.WriteFile(filepath.Join(d, "exists.env"), []byte("FOO=bar\n"), 0644)
  1596  	if err != nil {
  1597  		t.Fatalf("error writing template file: %v", err)
  1598  	}
  1599  
  1600  	templates := []*structs.Template{
  1601  		{
  1602  			EmbeddedTmpl: "FOO=bar\n",
  1603  			DestPath:     "exists.env",
  1604  			Envvars:      true,
  1605  		},
  1606  		{
  1607  			EmbeddedTmpl: "WHAT=ever\n",
  1608  			DestPath:     "missing.env",
  1609  			Envvars:      true,
  1610  		},
  1611  	}
  1612  
  1613  	taskEnv := taskenv.NewEmptyBuilder().SetClientTaskRoot(d).Build()
  1614  	if vars, err := loadTemplateEnv(templates, taskEnv); err == nil {
  1615  		t.Fatalf("expected an error but instead got env vars: %#v", vars)
  1616  	}
  1617  }
  1618  
  1619  // TestTaskTemplateManager_Env_InterpolatedDest asserts the core env
  1620  // template processing function handles interpolated destinations
  1621  func TestTaskTemplateManager_Env_InterpolatedDest(t *testing.T) {
  1622  	ci.Parallel(t)
  1623  	require := require.New(t)
  1624  
  1625  	d := t.TempDir()
  1626  
  1627  	// Fake writing the file so we don't have to run the whole template manager
  1628  	err := os.WriteFile(filepath.Join(d, "exists.env"), []byte("FOO=bar\n"), 0644)
  1629  	if err != nil {
  1630  		t.Fatalf("error writing template file: %v", err)
  1631  	}
  1632  
  1633  	templates := []*structs.Template{
  1634  		{
  1635  			EmbeddedTmpl: "FOO=bar\n",
  1636  			DestPath:     "${NOMAD_META_path}.env",
  1637  			Envvars:      true,
  1638  		},
  1639  	}
  1640  
  1641  	// Build the env
  1642  	taskEnv := taskenv.NewTaskEnv(
  1643  		map[string]string{"NOMAD_META_path": "exists"},
  1644  		map[string]string{"NOMAD_META_path": "exists"},
  1645  		map[string]string{},
  1646  		map[string]string{},
  1647  		d, "")
  1648  
  1649  	vars, err := loadTemplateEnv(templates, taskEnv)
  1650  	require.NoError(err)
  1651  	require.Contains(vars, "FOO")
  1652  	require.Equal(vars["FOO"], "bar")
  1653  }
  1654  
  1655  // TestTaskTemplateManager_Env_Multi asserts the core env
  1656  // template processing function returns combined env vars from multiple
  1657  // templates correctly.
  1658  func TestTaskTemplateManager_Env_Multi(t *testing.T) {
  1659  	ci.Parallel(t)
  1660  	d := t.TempDir()
  1661  
  1662  	// Fake writing the files so we don't have to run the whole template manager
  1663  	err := os.WriteFile(filepath.Join(d, "zzz.env"), []byte("FOO=bar\nSHARED=nope\n"), 0644)
  1664  	if err != nil {
  1665  		t.Fatalf("error writing template file 1: %v", err)
  1666  	}
  1667  	err = os.WriteFile(filepath.Join(d, "aaa.env"), []byte("BAR=foo\nSHARED=yup\n"), 0644)
  1668  	if err != nil {
  1669  		t.Fatalf("error writing template file 2: %v", err)
  1670  	}
  1671  
  1672  	// Templates will get loaded in order (not alpha sorted)
  1673  	templates := []*structs.Template{
  1674  		{
  1675  			DestPath: "zzz.env",
  1676  			Envvars:  true,
  1677  		},
  1678  		{
  1679  			DestPath: "aaa.env",
  1680  			Envvars:  true,
  1681  		},
  1682  	}
  1683  
  1684  	taskEnv := taskenv.NewEmptyBuilder().SetClientTaskRoot(d).Build()
  1685  	vars, err := loadTemplateEnv(templates, taskEnv)
  1686  	if err != nil {
  1687  		t.Fatalf("expected no error: %v", err)
  1688  	}
  1689  	if vars["FOO"] != "bar" {
  1690  		t.Errorf("expected FOO=bar but found %q", vars["FOO"])
  1691  	}
  1692  	if vars["BAR"] != "foo" {
  1693  		t.Errorf("expected BAR=foo but found %q", vars["BAR"])
  1694  	}
  1695  	if vars["SHARED"] != "yup" {
  1696  		t.Errorf("expected FOO=bar but found %q", vars["yup"])
  1697  	}
  1698  }
  1699  
  1700  func TestTaskTemplateManager_Rerender_Env(t *testing.T) {
  1701  	ci.Parallel(t)
  1702  	// Make a template that renders based on a key in Consul and sends restart
  1703  	key1 := "bam"
  1704  	key2 := "bar"
  1705  	content1_1 := "cat"
  1706  	content1_2 := "dog"
  1707  	t1 := &structs.Template{
  1708  		EmbeddedTmpl: `
  1709  FOO={{key "bam"}}
  1710  `,
  1711  		DestPath:   "test.env",
  1712  		ChangeMode: structs.TemplateChangeModeRestart,
  1713  		Envvars:    true,
  1714  	}
  1715  	t2 := &structs.Template{
  1716  		EmbeddedTmpl: `
  1717  BAR={{key "bar"}}
  1718  `,
  1719  		DestPath:   "test2.env",
  1720  		ChangeMode: structs.TemplateChangeModeRestart,
  1721  		Envvars:    true,
  1722  	}
  1723  
  1724  	harness := newTestHarness(t, []*structs.Template{t1, t2}, true, false)
  1725  	harness.start(t)
  1726  	defer harness.stop()
  1727  
  1728  	// Ensure no unblock
  1729  	select {
  1730  	case <-harness.mockHooks.UnblockCh:
  1731  		t.Fatalf("Task unblock should have not have been called")
  1732  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
  1733  	}
  1734  
  1735  	// Write the key to Consul
  1736  	harness.consul.SetKV(t, key1, []byte(content1_1))
  1737  	harness.consul.SetKV(t, key2, []byte(content1_1))
  1738  
  1739  	// Wait for the unblock
  1740  	select {
  1741  	case <-harness.mockHooks.UnblockCh:
  1742  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
  1743  		t.Fatalf("Task unblock should have been called")
  1744  	}
  1745  
  1746  	env := harness.envBuilder.Build().Map()
  1747  	if v, ok := env["FOO"]; !ok || v != content1_1 {
  1748  		t.Fatalf("Bad env for FOO: %v %v", v, ok)
  1749  	}
  1750  	if v, ok := env["BAR"]; !ok || v != content1_1 {
  1751  		t.Fatalf("Bad env for BAR: %v %v", v, ok)
  1752  	}
  1753  
  1754  	// Update the keys in Consul
  1755  	harness.consul.SetKV(t, key1, []byte(content1_2))
  1756  
  1757  	// Wait for restart
  1758  	timeout := time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second)
  1759  OUTER:
  1760  	for {
  1761  		select {
  1762  		case <-harness.mockHooks.RestartCh:
  1763  			break OUTER
  1764  		case <-harness.mockHooks.SignalCh:
  1765  			t.Fatalf("Signal with restart policy: %+v", harness.mockHooks)
  1766  		case <-timeout:
  1767  			t.Fatalf("Should have received a restart: %+v", harness.mockHooks)
  1768  		}
  1769  	}
  1770  
  1771  	env = harness.envBuilder.Build().Map()
  1772  	if v, ok := env["FOO"]; !ok || v != content1_2 {
  1773  		t.Fatalf("Bad env for FOO: %v %v", v, ok)
  1774  	}
  1775  	if v, ok := env["BAR"]; !ok || v != content1_1 {
  1776  		t.Fatalf("Bad env for BAR: %v %v", v, ok)
  1777  	}
  1778  }
  1779  
  1780  // TestTaskTemplateManager_Config_ServerName asserts the tls_server_name
  1781  // setting is propagated to consul-template's configuration. See #2776
  1782  func TestTaskTemplateManager_Config_ServerName(t *testing.T) {
  1783  	ci.Parallel(t)
  1784  	c := config.DefaultConfig()
  1785  	c.Node = mock.Node()
  1786  	c.VaultConfig = &sconfig.VaultConfig{
  1787  		Enabled:       pointer.Of(true),
  1788  		Addr:          "https://localhost/",
  1789  		TLSServerName: "notlocalhost",
  1790  	}
  1791  	config := &TaskTemplateManagerConfig{
  1792  		ClientConfig: c,
  1793  		VaultToken:   "token",
  1794  	}
  1795  	ctconf, err := newRunnerConfig(config, nil)
  1796  	if err != nil {
  1797  		t.Fatalf("unexpected error: %v", err)
  1798  	}
  1799  
  1800  	if *ctconf.Vault.SSL.ServerName != c.VaultConfig.TLSServerName {
  1801  		t.Fatalf("expected %q but found %q", c.VaultConfig.TLSServerName, *ctconf.Vault.SSL.ServerName)
  1802  	}
  1803  }
  1804  
  1805  // TestTaskTemplateManager_Config_VaultNamespace asserts the Vault namespace setting is
  1806  // propagated to consul-template's configuration.
  1807  func TestTaskTemplateManager_Config_VaultNamespace(t *testing.T) {
  1808  	ci.Parallel(t)
  1809  	assert := assert.New(t)
  1810  
  1811  	testNS := "test-namespace"
  1812  	c := config.DefaultConfig()
  1813  	c.Node = mock.Node()
  1814  	c.VaultConfig = &sconfig.VaultConfig{
  1815  		Enabled:       pointer.Of(true),
  1816  		Addr:          "https://localhost/",
  1817  		TLSServerName: "notlocalhost",
  1818  		Namespace:     testNS,
  1819  	}
  1820  
  1821  	alloc := mock.Alloc()
  1822  	config := &TaskTemplateManagerConfig{
  1823  		ClientConfig: c,
  1824  		VaultToken:   "token",
  1825  		EnvBuilder:   taskenv.NewBuilder(c.Node, alloc, alloc.Job.TaskGroups[0].Tasks[0], c.Region),
  1826  	}
  1827  
  1828  	ctmplMapping, err := parseTemplateConfigs(config)
  1829  	assert.Nil(err, "Parsing Templates")
  1830  
  1831  	ctconf, err := newRunnerConfig(config, ctmplMapping)
  1832  	assert.Nil(err, "Building Runner Config")
  1833  	assert.Equal(testNS, *ctconf.Vault.Namespace, "Vault Namespace Value")
  1834  }
  1835  
  1836  // TestTaskTemplateManager_Config_VaultNamespace asserts the Vault namespace setting is
  1837  // propagated to consul-template's configuration.
  1838  func TestTaskTemplateManager_Config_VaultNamespace_TaskOverride(t *testing.T) {
  1839  	ci.Parallel(t)
  1840  	assert := assert.New(t)
  1841  
  1842  	testNS := "test-namespace"
  1843  	c := config.DefaultConfig()
  1844  	c.Node = mock.Node()
  1845  	c.VaultConfig = &sconfig.VaultConfig{
  1846  		Enabled:       pointer.Of(true),
  1847  		Addr:          "https://localhost/",
  1848  		TLSServerName: "notlocalhost",
  1849  		Namespace:     testNS,
  1850  	}
  1851  
  1852  	alloc := mock.Alloc()
  1853  	overriddenNS := "new-namespace"
  1854  
  1855  	// Set the template manager config vault namespace
  1856  	config := &TaskTemplateManagerConfig{
  1857  		ClientConfig:   c,
  1858  		VaultToken:     "token",
  1859  		VaultNamespace: overriddenNS,
  1860  		EnvBuilder:     taskenv.NewBuilder(c.Node, alloc, alloc.Job.TaskGroups[0].Tasks[0], c.Region),
  1861  	}
  1862  
  1863  	ctmplMapping, err := parseTemplateConfigs(config)
  1864  	assert.Nil(err, "Parsing Templates")
  1865  
  1866  	ctconf, err := newRunnerConfig(config, ctmplMapping)
  1867  	assert.Nil(err, "Building Runner Config")
  1868  	assert.Equal(overriddenNS, *ctconf.Vault.Namespace, "Vault Namespace Value")
  1869  }
  1870  
  1871  // TestTaskTemplateManager_Escapes asserts that when sandboxing is enabled
  1872  // interpolated paths are not incorrectly treated as escaping the alloc dir.
  1873  func TestTaskTemplateManager_Escapes(t *testing.T) {
  1874  	ci.Parallel(t)
  1875  
  1876  	clientConf := config.DefaultConfig()
  1877  	require.False(t, clientConf.TemplateConfig.DisableSandbox, "expected sandbox to be disabled")
  1878  
  1879  	// Set a fake alloc dir to make test output more realistic
  1880  	clientConf.AllocDir = "/fake/allocdir"
  1881  
  1882  	clientConf.Node = mock.Node()
  1883  	alloc := mock.Alloc()
  1884  	task := alloc.Job.TaskGroups[0].Tasks[0]
  1885  	logger := testlog.HCLogger(t)
  1886  	allocDir := allocdir.NewAllocDir(logger, clientConf.AllocDir, alloc.ID)
  1887  	taskDir := allocDir.NewTaskDir(task.Name)
  1888  
  1889  	containerEnv := func() *taskenv.Builder {
  1890  		// To emulate a Docker or exec tasks we must copy the
  1891  		// Set{Alloc,Task,Secrets}Dir logic in taskrunner/task_dir_hook.go
  1892  		b := taskenv.NewBuilder(clientConf.Node, alloc, task, clientConf.Region)
  1893  		b.SetAllocDir(allocdir.SharedAllocContainerPath)
  1894  		b.SetTaskLocalDir(allocdir.TaskLocalContainerPath)
  1895  		b.SetSecretsDir(allocdir.TaskSecretsContainerPath)
  1896  		b.SetClientTaskRoot(taskDir.Dir)
  1897  		b.SetClientSharedAllocDir(taskDir.SharedAllocDir)
  1898  		b.SetClientTaskLocalDir(taskDir.LocalDir)
  1899  		b.SetClientTaskSecretsDir(taskDir.SecretsDir)
  1900  		return b
  1901  	}
  1902  
  1903  	rawExecEnv := func() *taskenv.Builder {
  1904  		// To emulate a unisolated tasks we must copy the
  1905  		// Set{Alloc,Task,Secrets}Dir logic in taskrunner/task_dir_hook.go
  1906  		b := taskenv.NewBuilder(clientConf.Node, alloc, task, clientConf.Region)
  1907  		b.SetAllocDir(taskDir.SharedAllocDir)
  1908  		b.SetTaskLocalDir(taskDir.LocalDir)
  1909  		b.SetSecretsDir(taskDir.SecretsDir)
  1910  		b.SetClientTaskRoot(taskDir.Dir)
  1911  		b.SetClientSharedAllocDir(taskDir.SharedAllocDir)
  1912  		b.SetClientTaskLocalDir(taskDir.LocalDir)
  1913  		b.SetClientTaskSecretsDir(taskDir.SecretsDir)
  1914  		return b
  1915  	}
  1916  
  1917  	cases := []struct {
  1918  		Name   string
  1919  		Config func() *TaskTemplateManagerConfig
  1920  
  1921  		// Expected paths to be returned if Err is nil
  1922  		SourcePath string
  1923  		DestPath   string
  1924  
  1925  		// Err is the expected error to be returned or nil
  1926  		Err error
  1927  	}{
  1928  		{
  1929  			Name: "ContainerOk",
  1930  			Config: func() *TaskTemplateManagerConfig {
  1931  				return &TaskTemplateManagerConfig{
  1932  					ClientConfig: clientConf,
  1933  					TaskDir:      taskDir.Dir,
  1934  					EnvBuilder:   containerEnv(),
  1935  					Templates: []*structs.Template{
  1936  						{
  1937  							SourcePath: "${NOMAD_TASK_DIR}/src",
  1938  							DestPath:   "${NOMAD_SECRETS_DIR}/dst",
  1939  						},
  1940  					},
  1941  				}
  1942  			},
  1943  			SourcePath: filepath.Join(taskDir.Dir, "local/src"),
  1944  			DestPath:   filepath.Join(taskDir.Dir, "secrets/dst"),
  1945  		},
  1946  		{
  1947  			Name: "ContainerSrcEscapesErr",
  1948  			Config: func() *TaskTemplateManagerConfig {
  1949  				return &TaskTemplateManagerConfig{
  1950  					ClientConfig: clientConf,
  1951  					TaskDir:      taskDir.Dir,
  1952  					EnvBuilder:   containerEnv(),
  1953  					Templates: []*structs.Template{
  1954  						{
  1955  							SourcePath: "/etc/src_escapes",
  1956  							DestPath:   "${NOMAD_SECRETS_DIR}/dst",
  1957  						},
  1958  					},
  1959  				}
  1960  			},
  1961  			Err: sourceEscapesErr,
  1962  		},
  1963  		{
  1964  			Name: "ContainerSrcEscapesOk",
  1965  			Config: func() *TaskTemplateManagerConfig {
  1966  				unsafeConf := clientConf.Copy()
  1967  				unsafeConf.TemplateConfig.DisableSandbox = true
  1968  				return &TaskTemplateManagerConfig{
  1969  					ClientConfig: unsafeConf,
  1970  					TaskDir:      taskDir.Dir,
  1971  					EnvBuilder:   containerEnv(),
  1972  					Templates: []*structs.Template{
  1973  						{
  1974  							SourcePath: "/etc/src_escapes_ok",
  1975  							DestPath:   "${NOMAD_SECRETS_DIR}/dst",
  1976  						},
  1977  					},
  1978  				}
  1979  			},
  1980  			SourcePath: "/etc/src_escapes_ok",
  1981  			DestPath:   filepath.Join(taskDir.Dir, "secrets/dst"),
  1982  		},
  1983  		{
  1984  			Name: "ContainerDstAbsoluteOk",
  1985  			Config: func() *TaskTemplateManagerConfig {
  1986  				return &TaskTemplateManagerConfig{
  1987  					ClientConfig: clientConf,
  1988  					TaskDir:      taskDir.Dir,
  1989  					EnvBuilder:   containerEnv(),
  1990  					Templates: []*structs.Template{
  1991  						{
  1992  							SourcePath: "${NOMAD_TASK_DIR}/src",
  1993  							DestPath:   "/etc/absolutely_relative",
  1994  						},
  1995  					},
  1996  				}
  1997  			},
  1998  			SourcePath: filepath.Join(taskDir.Dir, "local/src"),
  1999  			DestPath:   filepath.Join(taskDir.Dir, "etc/absolutely_relative"),
  2000  		},
  2001  		{
  2002  			Name: "ContainerDstAbsoluteEscapesErr",
  2003  			Config: func() *TaskTemplateManagerConfig {
  2004  				return &TaskTemplateManagerConfig{
  2005  					ClientConfig: clientConf,
  2006  					TaskDir:      taskDir.Dir,
  2007  					EnvBuilder:   containerEnv(),
  2008  					Templates: []*structs.Template{
  2009  						{
  2010  							SourcePath: "${NOMAD_TASK_DIR}/src",
  2011  							DestPath:   "../escapes",
  2012  						},
  2013  					},
  2014  				}
  2015  			},
  2016  			Err: destEscapesErr,
  2017  		},
  2018  		{
  2019  			Name: "ContainerDstAbsoluteEscapesOk",
  2020  			Config: func() *TaskTemplateManagerConfig {
  2021  				unsafeConf := clientConf.Copy()
  2022  				unsafeConf.TemplateConfig.DisableSandbox = true
  2023  				return &TaskTemplateManagerConfig{
  2024  					ClientConfig: unsafeConf,
  2025  					TaskDir:      taskDir.Dir,
  2026  					EnvBuilder:   containerEnv(),
  2027  					Templates: []*structs.Template{
  2028  						{
  2029  							SourcePath: "${NOMAD_TASK_DIR}/src",
  2030  							DestPath:   "../escapes",
  2031  						},
  2032  					},
  2033  				}
  2034  			},
  2035  			SourcePath: filepath.Join(taskDir.Dir, "local/src"),
  2036  			DestPath:   filepath.Join(taskDir.Dir, "..", "escapes"),
  2037  		},
  2038  		//TODO: Fix this test. I *think* it should pass. The double
  2039  		//      joining of the task dir onto the destination seems like
  2040  		//      a bug. https://github.com/hashicorp/nomad/issues/9389
  2041  		{
  2042  			Name: "RawExecOk",
  2043  			Config: func() *TaskTemplateManagerConfig {
  2044  				return &TaskTemplateManagerConfig{
  2045  					ClientConfig: clientConf,
  2046  					TaskDir:      taskDir.Dir,
  2047  					EnvBuilder:   rawExecEnv(),
  2048  					Templates: []*structs.Template{
  2049  						{
  2050  							SourcePath: "${NOMAD_TASK_DIR}/src",
  2051  							DestPath:   "${NOMAD_SECRETS_DIR}/dst",
  2052  						},
  2053  					},
  2054  				}
  2055  			},
  2056  			SourcePath: filepath.Join(taskDir.Dir, "local/src"),
  2057  			DestPath:   filepath.Join(taskDir.Dir, "secrets/dst"),
  2058  		},
  2059  		{
  2060  			Name: "RawExecSrcEscapesErr",
  2061  			Config: func() *TaskTemplateManagerConfig {
  2062  				return &TaskTemplateManagerConfig{
  2063  					ClientConfig: clientConf,
  2064  					TaskDir:      taskDir.Dir,
  2065  					EnvBuilder:   rawExecEnv(),
  2066  					Templates: []*structs.Template{
  2067  						{
  2068  							SourcePath: "/etc/src_escapes",
  2069  							DestPath:   "${NOMAD_SECRETS_DIR}/dst",
  2070  						},
  2071  					},
  2072  				}
  2073  			},
  2074  			Err: sourceEscapesErr,
  2075  		},
  2076  		{
  2077  			Name: "RawExecDstAbsoluteOk",
  2078  			Config: func() *TaskTemplateManagerConfig {
  2079  				return &TaskTemplateManagerConfig{
  2080  					ClientConfig: clientConf,
  2081  					TaskDir:      taskDir.Dir,
  2082  					EnvBuilder:   rawExecEnv(),
  2083  					Templates: []*structs.Template{
  2084  						{
  2085  							SourcePath: "${NOMAD_TASK_DIR}/src",
  2086  							DestPath:   "/etc/absolutely_relative",
  2087  						},
  2088  					},
  2089  				}
  2090  			},
  2091  			SourcePath: filepath.Join(taskDir.Dir, "local/src"),
  2092  			DestPath:   filepath.Join(taskDir.Dir, "etc/absolutely_relative"),
  2093  		},
  2094  	}
  2095  
  2096  	for i := range cases {
  2097  		tc := cases[i]
  2098  		t.Run(tc.Name, func(t *testing.T) {
  2099  			config := tc.Config()
  2100  			mapping, err := parseTemplateConfigs(config)
  2101  			if tc.Err == nil {
  2102  				// Ok path
  2103  				require.NoError(t, err)
  2104  				require.NotNil(t, mapping)
  2105  				require.Len(t, mapping, 1)
  2106  				for k := range mapping {
  2107  					require.Equal(t, tc.SourcePath, *k.Source)
  2108  					require.Equal(t, tc.DestPath, *k.Destination)
  2109  					t.Logf("Rendering %s => %s", *k.Source, *k.Destination)
  2110  				}
  2111  			} else {
  2112  				// Err path
  2113  				assert.EqualError(t, err, tc.Err.Error())
  2114  				require.Nil(t, mapping)
  2115  			}
  2116  
  2117  		})
  2118  	}
  2119  }
  2120  
  2121  func TestTaskTemplateManager_BlockedEvents(t *testing.T) {
  2122  	// The tests sets a template that need keys 0, 1, 2, 3, 4,
  2123  	// then subsequently sets 0, 1, 2 keys
  2124  	// then asserts that templates are still blocked on 3 and 4,
  2125  	// and check that we got the relevant task events
  2126  	ci.Parallel(t)
  2127  	require := require.New(t)
  2128  
  2129  	// Make a template that will render based on a key in Consul
  2130  	var embedded string
  2131  	for i := 0; i < 5; i++ {
  2132  		embedded += fmt.Sprintf(`{{key "%d"}}`, i)
  2133  	}
  2134  
  2135  	file := "my.tmpl"
  2136  	template := &structs.Template{
  2137  		EmbeddedTmpl: embedded,
  2138  		DestPath:     file,
  2139  		ChangeMode:   structs.TemplateChangeModeNoop,
  2140  	}
  2141  
  2142  	harness := newTestHarness(t, []*structs.Template{template}, true, false)
  2143  	harness.setEmitRate(100 * time.Millisecond)
  2144  	harness.start(t)
  2145  	defer harness.stop()
  2146  
  2147  	missingKeys := func(e *structs.TaskEvent) ([]string, int) {
  2148  		missingRexp := regexp.MustCompile(`kv.block\(([0-9]*)\)`)
  2149  		moreRexp := regexp.MustCompile(`and ([0-9]*) more`)
  2150  
  2151  		missingMatch := missingRexp.FindAllStringSubmatch(e.DisplayMessage, -1)
  2152  		moreMatch := moreRexp.FindAllStringSubmatch(e.DisplayMessage, -1)
  2153  
  2154  		missing := make([]string, len(missingMatch))
  2155  		for i, v := range missingMatch {
  2156  			missing[i] = v[1]
  2157  		}
  2158  		sort.Strings(missing)
  2159  
  2160  		more := 0
  2161  		if len(moreMatch) != 0 {
  2162  			more, _ = strconv.Atoi(moreMatch[0][1])
  2163  		}
  2164  		return missing, more
  2165  
  2166  	}
  2167  
  2168  	// Ensure that we get a blocked event
  2169  	select {
  2170  	case <-harness.mockHooks.UnblockCh:
  2171  		t.Fatalf("Task unblock should have not have been called")
  2172  	case <-harness.mockHooks.EmitEventCh:
  2173  	case <-time.After(time.Duration(1*testutil.TestMultiplier()) * time.Second):
  2174  		t.Fatalf("timeout")
  2175  	}
  2176  
  2177  	// Check to see we got a correct message
  2178  	// assert that all 0-4 keys are missing
  2179  	require.Len(harness.mockHooks.Events, 1)
  2180  	t.Logf("first message: %v", harness.mockHooks.Events[0])
  2181  	missing, more := missingKeys(harness.mockHooks.Events[0])
  2182  	require.Equal(5, len(missing)+more)
  2183  	require.Contains(harness.mockHooks.Events[0].DisplayMessage, "and 2 more")
  2184  
  2185  	// Write 0-2 keys to Consul
  2186  	for i := 0; i < 3; i++ {
  2187  		harness.consul.SetKV(t, fmt.Sprintf("%d", i), []byte{0xa})
  2188  	}
  2189  
  2190  	// Ensure that we get a blocked event
  2191  	isExpectedFinalEvent := func(e *structs.TaskEvent) bool {
  2192  		missing, more := missingKeys(e)
  2193  		return reflect.DeepEqual(missing, []string{"3", "4"}) && more == 0
  2194  	}
  2195  	timeout := time.After(time.Second * time.Duration(testutil.TestMultiplier()))
  2196  WAIT_LOOP:
  2197  	for {
  2198  		select {
  2199  		case <-harness.mockHooks.UnblockCh:
  2200  			t.Errorf("Task unblock should have not have been called")
  2201  		case e := <-harness.mockHooks.EmitEventCh:
  2202  			t.Logf("received event: %v", e.DisplayMessage)
  2203  			if isExpectedFinalEvent(e) {
  2204  				break WAIT_LOOP
  2205  			}
  2206  		case <-timeout:
  2207  			t.Errorf("timeout")
  2208  		}
  2209  	}
  2210  
  2211  	// Check to see we got a correct message
  2212  	event := harness.mockHooks.Events[len(harness.mockHooks.Events)-1]
  2213  	if !isExpectedFinalEvent(event) {
  2214  		t.Logf("received all events: %v", pretty.Sprint(harness.mockHooks.Events))
  2215  
  2216  		t.Fatalf("bad event, expected only 3 and 5 blocked got: %q", event.DisplayMessage)
  2217  	}
  2218  }
  2219  
  2220  // TestTaskTemplateManager_ClientTemplateConfig_Set asserts that all client level
  2221  // configuration is accurately mapped from the client to the TaskTemplateManager
  2222  // and that any operator defined boundaries are enforced.
  2223  func TestTaskTemplateManager_ClientTemplateConfig_Set(t *testing.T) {
  2224  	ci.Parallel(t)
  2225  
  2226  	testNS := "test-namespace"
  2227  
  2228  	clientConfig := config.DefaultConfig()
  2229  	clientConfig.Node = mock.Node()
  2230  
  2231  	clientConfig.VaultConfig = &sconfig.VaultConfig{
  2232  		Enabled:   pointer.Of(true),
  2233  		Namespace: testNS,
  2234  	}
  2235  
  2236  	clientConfig.ConsulConfig = &sconfig.ConsulConfig{
  2237  		Namespace: testNS,
  2238  	}
  2239  
  2240  	// helper to reduce boilerplate
  2241  	waitConfig := &config.WaitConfig{
  2242  		Min: pointer.Of(5 * time.Second),
  2243  		Max: pointer.Of(10 * time.Second),
  2244  	}
  2245  	// helper to reduce boilerplate
  2246  	retryConfig := &config.RetryConfig{
  2247  		Attempts:   pointer.Of(5),
  2248  		Backoff:    pointer.Of(5 * time.Second),
  2249  		MaxBackoff: pointer.Of(20 * time.Second),
  2250  	}
  2251  
  2252  	clientConfig.TemplateConfig.MaxStale = pointer.Of(5 * time.Second)
  2253  	clientConfig.TemplateConfig.BlockQueryWaitTime = pointer.Of(60 * time.Second)
  2254  	clientConfig.TemplateConfig.Wait = waitConfig.Copy()
  2255  	clientConfig.TemplateConfig.ConsulRetry = retryConfig.Copy()
  2256  	clientConfig.TemplateConfig.VaultRetry = retryConfig.Copy()
  2257  	clientConfig.TemplateConfig.NomadRetry = retryConfig.Copy()
  2258  
  2259  	alloc := mock.Alloc()
  2260  	allocWithOverride := mock.Alloc()
  2261  	allocWithOverride.Job.TaskGroups[0].Tasks[0].Templates = []*structs.Template{
  2262  		{
  2263  			Wait: &structs.WaitConfig{
  2264  				Min: pointer.Of(2 * time.Second),
  2265  				Max: pointer.Of(12 * time.Second),
  2266  			},
  2267  		},
  2268  	}
  2269  
  2270  	cases := []struct {
  2271  		Name                   string
  2272  		ClientTemplateConfig   *config.ClientTemplateConfig
  2273  		TTMConfig              *TaskTemplateManagerConfig
  2274  		ExpectedRunnerConfig   *config.Config
  2275  		ExpectedTemplateConfig *templateconfig.TemplateConfig
  2276  	}{
  2277  		{
  2278  			"basic-wait-config",
  2279  			&config.ClientTemplateConfig{
  2280  				MaxStale:           pointer.Of(5 * time.Second),
  2281  				BlockQueryWaitTime: pointer.Of(60 * time.Second),
  2282  				Wait:               waitConfig.Copy(),
  2283  				ConsulRetry:        retryConfig.Copy(),
  2284  				VaultRetry:         retryConfig.Copy(),
  2285  				NomadRetry:         retryConfig.Copy(),
  2286  			},
  2287  			&TaskTemplateManagerConfig{
  2288  				ClientConfig: clientConfig,
  2289  				VaultToken:   "token",
  2290  				EnvBuilder:   taskenv.NewBuilder(clientConfig.Node, alloc, alloc.Job.TaskGroups[0].Tasks[0], clientConfig.Region),
  2291  			},
  2292  			&config.Config{
  2293  				TemplateConfig: &config.ClientTemplateConfig{
  2294  					MaxStale:           pointer.Of(5 * time.Second),
  2295  					BlockQueryWaitTime: pointer.Of(60 * time.Second),
  2296  					Wait:               waitConfig.Copy(),
  2297  					ConsulRetry:        retryConfig.Copy(),
  2298  					VaultRetry:         retryConfig.Copy(),
  2299  					NomadRetry:         retryConfig.Copy(),
  2300  				},
  2301  			},
  2302  			&templateconfig.TemplateConfig{
  2303  				Wait: &templateconfig.WaitConfig{
  2304  					Enabled: pointer.Of(true),
  2305  					Min:     pointer.Of(5 * time.Second),
  2306  					Max:     pointer.Of(10 * time.Second),
  2307  				},
  2308  			},
  2309  		},
  2310  		{
  2311  			"template-override",
  2312  			&config.ClientTemplateConfig{
  2313  				MaxStale:           pointer.Of(5 * time.Second),
  2314  				BlockQueryWaitTime: pointer.Of(60 * time.Second),
  2315  				Wait:               waitConfig.Copy(),
  2316  				ConsulRetry:        retryConfig.Copy(),
  2317  				VaultRetry:         retryConfig.Copy(),
  2318  				NomadRetry:         retryConfig.Copy(),
  2319  			},
  2320  			&TaskTemplateManagerConfig{
  2321  				ClientConfig: clientConfig,
  2322  				VaultToken:   "token",
  2323  				EnvBuilder:   taskenv.NewBuilder(clientConfig.Node, allocWithOverride, allocWithOverride.Job.TaskGroups[0].Tasks[0], clientConfig.Region),
  2324  			},
  2325  			&config.Config{
  2326  				TemplateConfig: &config.ClientTemplateConfig{
  2327  					MaxStale:           pointer.Of(5 * time.Second),
  2328  					BlockQueryWaitTime: pointer.Of(60 * time.Second),
  2329  					Wait:               waitConfig.Copy(),
  2330  					ConsulRetry:        retryConfig.Copy(),
  2331  					VaultRetry:         retryConfig.Copy(),
  2332  					NomadRetry:         retryConfig.Copy(),
  2333  				},
  2334  			},
  2335  			&templateconfig.TemplateConfig{
  2336  				Wait: &templateconfig.WaitConfig{
  2337  					Enabled: pointer.Of(true),
  2338  					Min:     pointer.Of(2 * time.Second),
  2339  					Max:     pointer.Of(12 * time.Second),
  2340  				},
  2341  			},
  2342  		},
  2343  		{
  2344  			"bounds-override",
  2345  			&config.ClientTemplateConfig{
  2346  				MaxStale:           pointer.Of(5 * time.Second),
  2347  				BlockQueryWaitTime: pointer.Of(60 * time.Second),
  2348  				Wait:               waitConfig.Copy(),
  2349  				WaitBounds: &config.WaitConfig{
  2350  					Min: pointer.Of(3 * time.Second),
  2351  					Max: pointer.Of(11 * time.Second),
  2352  				},
  2353  				ConsulRetry: retryConfig.Copy(),
  2354  				VaultRetry:  retryConfig.Copy(),
  2355  				NomadRetry:  retryConfig.Copy(),
  2356  			},
  2357  			&TaskTemplateManagerConfig{
  2358  				ClientConfig: clientConfig,
  2359  				VaultToken:   "token",
  2360  				EnvBuilder:   taskenv.NewBuilder(clientConfig.Node, allocWithOverride, allocWithOverride.Job.TaskGroups[0].Tasks[0], clientConfig.Region),
  2361  				Templates: []*structs.Template{
  2362  					{
  2363  						Wait: &structs.WaitConfig{
  2364  							Min: pointer.Of(2 * time.Second),
  2365  							Max: pointer.Of(12 * time.Second),
  2366  						},
  2367  					},
  2368  				},
  2369  			},
  2370  			&config.Config{
  2371  				TemplateConfig: &config.ClientTemplateConfig{
  2372  					MaxStale:           pointer.Of(5 * time.Second),
  2373  					BlockQueryWaitTime: pointer.Of(60 * time.Second),
  2374  					Wait:               waitConfig.Copy(),
  2375  					WaitBounds: &config.WaitConfig{
  2376  						Min: pointer.Of(3 * time.Second),
  2377  						Max: pointer.Of(11 * time.Second),
  2378  					},
  2379  					ConsulRetry: retryConfig.Copy(),
  2380  					VaultRetry:  retryConfig.Copy(),
  2381  					NomadRetry:  retryConfig.Copy(),
  2382  				},
  2383  			},
  2384  			&templateconfig.TemplateConfig{
  2385  				Wait: &templateconfig.WaitConfig{
  2386  					Enabled: pointer.Of(true),
  2387  					Min:     pointer.Of(3 * time.Second),
  2388  					Max:     pointer.Of(11 * time.Second),
  2389  				},
  2390  			},
  2391  		},
  2392  	}
  2393  
  2394  	for _, _case := range cases {
  2395  		t.Run(_case.Name, func(t *testing.T) {
  2396  			// monkey patch the client config with the version of the ClientTemplateConfig we want to test.
  2397  			_case.TTMConfig.ClientConfig.TemplateConfig = _case.ClientTemplateConfig
  2398  			templateMapping, err := parseTemplateConfigs(_case.TTMConfig)
  2399  			require.NoError(t, err)
  2400  
  2401  			runnerConfig, err := newRunnerConfig(_case.TTMConfig, templateMapping)
  2402  			require.NoError(t, err)
  2403  
  2404  			// Direct properties
  2405  			require.Equal(t, *_case.ExpectedRunnerConfig.TemplateConfig.MaxStale, *runnerConfig.MaxStale)
  2406  			require.Equal(t, *_case.ExpectedRunnerConfig.TemplateConfig.BlockQueryWaitTime, *runnerConfig.BlockQueryWaitTime)
  2407  			// WaitConfig
  2408  			require.Equal(t, *_case.ExpectedRunnerConfig.TemplateConfig.Wait.Min, *runnerConfig.Wait.Min)
  2409  			require.Equal(t, *_case.ExpectedRunnerConfig.TemplateConfig.Wait.Max, *runnerConfig.Wait.Max)
  2410  			// Consul Retry
  2411  			require.NotNil(t, runnerConfig.Consul)
  2412  			require.NotNil(t, runnerConfig.Consul.Retry)
  2413  			require.Equal(t, *_case.ExpectedRunnerConfig.TemplateConfig.ConsulRetry.Attempts, *runnerConfig.Consul.Retry.Attempts)
  2414  			require.Equal(t, *_case.ExpectedRunnerConfig.TemplateConfig.ConsulRetry.Backoff, *runnerConfig.Consul.Retry.Backoff)
  2415  			require.Equal(t, *_case.ExpectedRunnerConfig.TemplateConfig.ConsulRetry.MaxBackoff, *runnerConfig.Consul.Retry.MaxBackoff)
  2416  			// Vault Retry
  2417  			require.NotNil(t, runnerConfig.Vault)
  2418  			require.NotNil(t, runnerConfig.Vault.Retry)
  2419  			require.Equal(t, *_case.ExpectedRunnerConfig.TemplateConfig.VaultRetry.Attempts, *runnerConfig.Vault.Retry.Attempts)
  2420  			require.Equal(t, *_case.ExpectedRunnerConfig.TemplateConfig.VaultRetry.Backoff, *runnerConfig.Vault.Retry.Backoff)
  2421  			require.Equal(t, *_case.ExpectedRunnerConfig.TemplateConfig.VaultRetry.MaxBackoff, *runnerConfig.Vault.Retry.MaxBackoff)
  2422  			// Nomad Retry
  2423  			require.NotNil(t, runnerConfig.Nomad)
  2424  			require.NotNil(t, runnerConfig.Nomad.Retry)
  2425  			require.Equal(t, *_case.ExpectedRunnerConfig.TemplateConfig.NomadRetry.Attempts, *runnerConfig.Nomad.Retry.Attempts)
  2426  			require.Equal(t, *_case.ExpectedRunnerConfig.TemplateConfig.NomadRetry.Backoff, *runnerConfig.Nomad.Retry.Backoff)
  2427  			require.Equal(t, *_case.ExpectedRunnerConfig.TemplateConfig.NomadRetry.MaxBackoff, *runnerConfig.Nomad.Retry.MaxBackoff)
  2428  
  2429  			// Test that wait_bounds are enforced
  2430  			for _, tmpl := range *runnerConfig.Templates {
  2431  				require.Equal(t, *_case.ExpectedTemplateConfig.Wait.Enabled, *tmpl.Wait.Enabled)
  2432  				require.Equal(t, *_case.ExpectedTemplateConfig.Wait.Min, *tmpl.Wait.Min)
  2433  				require.Equal(t, *_case.ExpectedTemplateConfig.Wait.Max, *tmpl.Wait.Max)
  2434  			}
  2435  		})
  2436  	}
  2437  }
  2438  
  2439  // TestTaskTemplateManager_Template_Wait_Set asserts that all template level
  2440  // configuration is accurately mapped from the template to the TaskTemplateManager's
  2441  // template config.
  2442  func TestTaskTemplateManager_Template_Wait_Set(t *testing.T) {
  2443  	ci.Parallel(t)
  2444  
  2445  	c := config.DefaultConfig()
  2446  	c.Node = mock.Node()
  2447  
  2448  	alloc := mock.Alloc()
  2449  
  2450  	ttmConfig := &TaskTemplateManagerConfig{
  2451  		ClientConfig: c,
  2452  		VaultToken:   "token",
  2453  		EnvBuilder:   taskenv.NewBuilder(c.Node, alloc, alloc.Job.TaskGroups[0].Tasks[0], c.Region),
  2454  		Templates: []*structs.Template{
  2455  			{
  2456  				Wait: &structs.WaitConfig{
  2457  					Min: pointer.Of(5 * time.Second),
  2458  					Max: pointer.Of(10 * time.Second),
  2459  				},
  2460  			},
  2461  		},
  2462  	}
  2463  
  2464  	templateMapping, err := parseTemplateConfigs(ttmConfig)
  2465  	require.NoError(t, err)
  2466  
  2467  	for k, _ := range templateMapping {
  2468  		require.True(t, *k.Wait.Enabled)
  2469  		require.Equal(t, 5*time.Second, *k.Wait.Min)
  2470  		require.Equal(t, 10*time.Second, *k.Wait.Max)
  2471  	}
  2472  }
  2473  
  2474  // TestTaskTemplateManager_Template_ErrMissingKey_Set asserts that all template level
  2475  // configuration is accurately mapped from the template to the TaskTemplateManager's
  2476  // template config.
  2477  func TestTaskTemplateManager_Template_ErrMissingKey_Set(t *testing.T) {
  2478  	ci.Parallel(t)
  2479  
  2480  	c := config.DefaultConfig()
  2481  	c.Node = mock.Node()
  2482  
  2483  	alloc := mock.Alloc()
  2484  
  2485  	ttmConfig := &TaskTemplateManagerConfig{
  2486  		ClientConfig: c,
  2487  		VaultToken:   "token",
  2488  		EnvBuilder:   taskenv.NewBuilder(c.Node, alloc, alloc.Job.TaskGroups[0].Tasks[0], c.Region),
  2489  		Templates: []*structs.Template{
  2490  			{
  2491  				EmbeddedTmpl:  "test-false",
  2492  				ErrMissingKey: false,
  2493  			},
  2494  			{
  2495  				EmbeddedTmpl:  "test-true",
  2496  				ErrMissingKey: true,
  2497  			},
  2498  		},
  2499  	}
  2500  
  2501  	templateMapping, err := parseTemplateConfigs(ttmConfig)
  2502  	require.NoError(t, err)
  2503  
  2504  	for k, tmpl := range templateMapping {
  2505  		if tmpl.EmbeddedTmpl == "test-false" {
  2506  			require.False(t, *k.ErrMissingKey)
  2507  		}
  2508  		if tmpl.EmbeddedTmpl == "test-true" {
  2509  			require.True(t, *k.ErrMissingKey)
  2510  		}
  2511  	}
  2512  }
  2513  
  2514  // TestTaskTemplateManager_writeToFile_Disabled asserts the consul-template function
  2515  // writeToFile is disabled by default.
  2516  func TestTaskTemplateManager_writeToFile_Disabled(t *testing.T) {
  2517  	ci.Parallel(t)
  2518  
  2519  	file := "my.tmpl"
  2520  	template := &structs.Template{
  2521  		EmbeddedTmpl: `Testing writeToFile...
  2522  {{ "if i exist writeToFile is enabled" | writeToFile "/tmp/NOMAD-TEST-SHOULD-NOT-EXIST" "" "" "0644" }}
  2523  ...done
  2524  `,
  2525  		DestPath:   file,
  2526  		ChangeMode: structs.TemplateChangeModeNoop,
  2527  	}
  2528  
  2529  	harness := newTestHarness(t, []*structs.Template{template}, false, false)
  2530  	require.NoError(t, harness.startWithErr(), "couldn't setup initial harness")
  2531  	defer harness.stop()
  2532  
  2533  	// Using writeToFile should cause a kill
  2534  	select {
  2535  	case <-harness.mockHooks.UnblockCh:
  2536  		t.Fatalf("Task unblock should have not have been called")
  2537  	case <-harness.mockHooks.EmitEventCh:
  2538  		t.Fatalf("Task event should not have been emitted")
  2539  	case e := <-harness.mockHooks.KillCh:
  2540  		require.Contains(t, e.DisplayMessage, "writeToFile: function is disabled")
  2541  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
  2542  		t.Fatalf("timeout")
  2543  	}
  2544  
  2545  	// Check the file is not there
  2546  	path := filepath.Join(harness.taskDir, file)
  2547  	_, err := os.ReadFile(path)
  2548  	require.Error(t, err)
  2549  }
  2550  
  2551  // TestTaskTemplateManager_writeToFile asserts the consul-template function
  2552  // writeToFile can be enabled.
  2553  func TestTaskTemplateManager_writeToFile(t *testing.T) {
  2554  	if runtime.GOOS != "linux" {
  2555  		t.Skip("username and group lookup assume linux platform")
  2556  	}
  2557  
  2558  	ci.Parallel(t)
  2559  
  2560  	cu, err := users.Current()
  2561  	require.NoError(t, err)
  2562  
  2563  	cg, err := users.LookupGroupId(cu.Gid)
  2564  	require.NoError(t, err)
  2565  
  2566  	file := "my.tmpl"
  2567  	template := &structs.Template{
  2568  		// EmbeddedTmpl set below as it needs the taskDir
  2569  		DestPath:   file,
  2570  		ChangeMode: structs.TemplateChangeModeNoop,
  2571  	}
  2572  
  2573  	harness := newTestHarness(t, []*structs.Template{template}, false, false)
  2574  
  2575  	// Add template now that we know the taskDir
  2576  	harness.templates[0].EmbeddedTmpl = fmt.Sprintf(`Testing writeToFile...
  2577  {{ "hello" | writeToFile "%s" "`+cu.Username+`" "`+cg.Name+`" "0644" }}
  2578  ...done
  2579  `, filepath.Join(harness.taskDir, "writetofile.out"))
  2580  
  2581  	// Enable all funcs
  2582  	harness.config.TemplateConfig.FunctionDenylist = []string{}
  2583  
  2584  	require.NoError(t, harness.startWithErr(), "couldn't setup initial harness")
  2585  	defer harness.stop()
  2586  
  2587  	// Using writeToFile should not cause a kill
  2588  	select {
  2589  	case <-harness.mockHooks.UnblockCh:
  2590  	case <-harness.mockHooks.EmitEventCh:
  2591  		t.Fatalf("Task event should not have been emitted")
  2592  	case e := <-harness.mockHooks.KillCh:
  2593  		t.Fatalf("Task should not have been killed: %v", e.DisplayMessage)
  2594  	case <-time.After(time.Duration(5*testutil.TestMultiplier()) * time.Second):
  2595  		t.Fatalf("timeout")
  2596  	}
  2597  
  2598  	// Check the templated file is there
  2599  	path := filepath.Join(harness.taskDir, file)
  2600  	r, err := os.ReadFile(path)
  2601  	require.NoError(t, err)
  2602  	require.True(t, bytes.HasSuffix(r, []byte("...done\n")), string(r))
  2603  
  2604  	// Check that writeToFile was allowed
  2605  	path = filepath.Join(harness.taskDir, "writetofile.out")
  2606  	r, err = os.ReadFile(path)
  2607  	require.NoError(t, err)
  2608  	require.Equal(t, "hello", string(r))
  2609  }