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

     1  package template
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"math/rand"
     8  	"os"
     9  	"sort"
    10  	"strconv"
    11  	"strings"
    12  	"sync"
    13  	"time"
    14  
    15  	ctconf "github.com/hashicorp/consul-template/config"
    16  	"github.com/hashicorp/consul-template/manager"
    17  	"github.com/hashicorp/consul-template/signals"
    18  	envparse "github.com/hashicorp/go-envparse"
    19  	multierror "github.com/hashicorp/go-multierror"
    20  	"github.com/hashicorp/nomad/client/allocrunner/taskrunner/interfaces"
    21  	"github.com/hashicorp/nomad/client/config"
    22  	"github.com/hashicorp/nomad/client/taskenv"
    23  	"github.com/hashicorp/nomad/helper/pointer"
    24  	"github.com/hashicorp/nomad/nomad/structs"
    25  )
    26  
    27  const (
    28  	// consulTemplateSourceName is the source name when using the TaskHooks.
    29  	consulTemplateSourceName = "Template"
    30  
    31  	// missingDepEventLimit is the number of missing dependencies that will be
    32  	// logged before we switch to showing just the number of missing
    33  	// dependencies.
    34  	missingDepEventLimit = 3
    35  
    36  	// DefaultMaxTemplateEventRate is the default maximum rate at which a
    37  	// template event should be fired.
    38  	DefaultMaxTemplateEventRate = 3 * time.Second
    39  )
    40  
    41  var (
    42  	sourceEscapesErr = errors.New("template source path escapes alloc directory")
    43  	destEscapesErr   = errors.New("template destination path escapes alloc directory")
    44  )
    45  
    46  // TaskTemplateManager is used to run a set of templates for a given task
    47  type TaskTemplateManager struct {
    48  	// config holds the template managers configuration
    49  	config *TaskTemplateManagerConfig
    50  
    51  	// lookup allows looking up the set of Nomad templates by their consul-template ID
    52  	lookup map[string][]*structs.Template
    53  
    54  	// runner is the consul-template runner
    55  	runner *manager.Runner
    56  
    57  	// handle is used to execute scripts
    58  	handle     interfaces.ScriptExecutor
    59  	handleLock sync.Mutex
    60  
    61  	// signals is a lookup map from the string representation of a signal to its
    62  	// actual signal
    63  	signals map[string]os.Signal
    64  
    65  	// shutdownCh is used to signal and started goroutine to shutdown
    66  	shutdownCh chan struct{}
    67  
    68  	// shutdown marks whether the manager has been shutdown
    69  	shutdown     bool
    70  	shutdownLock sync.Mutex
    71  }
    72  
    73  // TaskTemplateManagerConfig is used to configure an instance of the
    74  // TaskTemplateManager
    75  type TaskTemplateManagerConfig struct {
    76  	// UnblockCh is closed when the template has been rendered
    77  	UnblockCh chan struct{}
    78  
    79  	// Lifecycle is used to interact with the task the template manager is being
    80  	// run for
    81  	Lifecycle interfaces.TaskLifecycle
    82  
    83  	// Events is used to emit events for the task
    84  	Events interfaces.EventEmitter
    85  
    86  	// Templates is the set of templates we are managing
    87  	Templates []*structs.Template
    88  
    89  	// ClientConfig is the Nomad Client configuration
    90  	ClientConfig *config.Config
    91  
    92  	// ConsulNamespace is the Consul namespace for the task
    93  	ConsulNamespace string
    94  
    95  	// VaultToken is the Vault token for the task.
    96  	VaultToken string
    97  
    98  	// VaultNamespace is the Vault namespace for the task
    99  	VaultNamespace string
   100  
   101  	// TaskDir is the task's directory
   102  	TaskDir string
   103  
   104  	// EnvBuilder is the environment variable builder for the task.
   105  	EnvBuilder *taskenv.Builder
   106  
   107  	// MaxTemplateEventRate is the maximum rate at which we should emit events.
   108  	MaxTemplateEventRate time.Duration
   109  
   110  	// NomadNamespace is the Nomad namespace for the task
   111  	NomadNamespace string
   112  
   113  	// NomadToken is the Nomad token or identity claim for the task
   114  	NomadToken string
   115  }
   116  
   117  // Validate validates the configuration.
   118  func (c *TaskTemplateManagerConfig) Validate() error {
   119  	if c == nil {
   120  		return fmt.Errorf("Nil config passed")
   121  	} else if c.UnblockCh == nil {
   122  		return fmt.Errorf("Invalid unblock channel given")
   123  	} else if c.Lifecycle == nil {
   124  		return fmt.Errorf("Invalid lifecycle hooks given")
   125  	} else if c.Events == nil {
   126  		return fmt.Errorf("Invalid event hook given")
   127  	} else if c.ClientConfig == nil {
   128  		return fmt.Errorf("Invalid client config given")
   129  	} else if c.TaskDir == "" {
   130  		return fmt.Errorf("Invalid task directory given: %q", c.TaskDir)
   131  	} else if c.EnvBuilder == nil {
   132  		return fmt.Errorf("Invalid task environment given")
   133  	} else if c.MaxTemplateEventRate == 0 {
   134  		return fmt.Errorf("Invalid max template event rate given")
   135  	}
   136  
   137  	return nil
   138  }
   139  
   140  func NewTaskTemplateManager(config *TaskTemplateManagerConfig) (*TaskTemplateManager, error) {
   141  	// Check pre-conditions
   142  	if err := config.Validate(); err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	tm := &TaskTemplateManager{
   147  		config:     config,
   148  		shutdownCh: make(chan struct{}),
   149  	}
   150  
   151  	// Parse the signals that we need
   152  	for _, tmpl := range config.Templates {
   153  		if tmpl.ChangeSignal == "" {
   154  			continue
   155  		}
   156  
   157  		sig, err := signals.Parse(tmpl.ChangeSignal)
   158  		if err != nil {
   159  			return nil, fmt.Errorf("Failed to parse signal %q", tmpl.ChangeSignal)
   160  		}
   161  
   162  		if tm.signals == nil {
   163  			tm.signals = make(map[string]os.Signal)
   164  		}
   165  
   166  		tm.signals[tmpl.ChangeSignal] = sig
   167  	}
   168  
   169  	// Build the consul-template runner
   170  	runner, lookup, err := templateRunner(config)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  	tm.runner = runner
   175  	tm.lookup = lookup
   176  
   177  	go tm.run()
   178  	return tm, nil
   179  }
   180  
   181  // Stop is used to stop the consul-template runner
   182  func (tm *TaskTemplateManager) Stop() {
   183  	tm.shutdownLock.Lock()
   184  	defer tm.shutdownLock.Unlock()
   185  
   186  	if tm.shutdown {
   187  		return
   188  	}
   189  
   190  	close(tm.shutdownCh)
   191  	tm.shutdown = true
   192  
   193  	// Stop the consul-template runner
   194  	if tm.runner != nil {
   195  		tm.runner.Stop()
   196  	}
   197  }
   198  
   199  // SetDriverHandle sets the executor
   200  func (tm *TaskTemplateManager) SetDriverHandle(executor interfaces.ScriptExecutor) {
   201  	tm.handleLock.Lock()
   202  	defer tm.handleLock.Unlock()
   203  	tm.handle = executor
   204  
   205  }
   206  
   207  // run is the long lived loop that handles errors and templates being rendered
   208  func (tm *TaskTemplateManager) run() {
   209  	// Runner is nil if there are no templates
   210  	if tm.runner == nil {
   211  		// Unblock the start if there is nothing to do
   212  		close(tm.config.UnblockCh)
   213  		return
   214  	}
   215  
   216  	// Start the runner
   217  	go tm.runner.Start()
   218  
   219  	// Block till all the templates have been rendered
   220  	tm.handleFirstRender()
   221  
   222  	// Detect if there was a shutdown.
   223  	select {
   224  	case <-tm.shutdownCh:
   225  		return
   226  	default:
   227  	}
   228  
   229  	// Read environment variables from env templates before we unblock
   230  	envMap, err := loadTemplateEnv(tm.config.Templates, tm.config.EnvBuilder.Build())
   231  	if err != nil {
   232  		tm.config.Lifecycle.Kill(context.Background(),
   233  			structs.NewTaskEvent(structs.TaskKilling).
   234  				SetFailsTask().
   235  				SetDisplayMessage(fmt.Sprintf("Template failed to read environment variables: %v", err)))
   236  		return
   237  	}
   238  	tm.config.EnvBuilder.SetTemplateEnv(envMap)
   239  
   240  	// Unblock the task
   241  	close(tm.config.UnblockCh)
   242  
   243  	// If all our templates are change mode no-op, then we can exit here
   244  	if tm.allTemplatesNoop() {
   245  		return
   246  	}
   247  
   248  	// handle all subsequent render events.
   249  	tm.handleTemplateRerenders(time.Now())
   250  }
   251  
   252  // handleFirstRender blocks till all templates have been rendered
   253  func (tm *TaskTemplateManager) handleFirstRender() {
   254  	// missingDependencies is the set of missing dependencies.
   255  	var missingDependencies map[string]struct{}
   256  
   257  	// eventTimer is used to trigger the firing of an event showing the missing
   258  	// dependencies.
   259  	eventTimer := time.NewTimer(tm.config.MaxTemplateEventRate)
   260  	if !eventTimer.Stop() {
   261  		<-eventTimer.C
   262  	}
   263  
   264  	// outstandingEvent tracks whether there is an outstanding event that should
   265  	// be fired.
   266  	outstandingEvent := false
   267  
   268  	// Wait till all the templates have been rendered
   269  WAIT:
   270  	for {
   271  		select {
   272  		case <-tm.shutdownCh:
   273  			return
   274  		case err, ok := <-tm.runner.ErrCh:
   275  			if !ok {
   276  				continue
   277  			}
   278  
   279  			tm.config.Lifecycle.Kill(context.Background(),
   280  				structs.NewTaskEvent(structs.TaskKilling).
   281  					SetFailsTask().
   282  					SetDisplayMessage(fmt.Sprintf("Template failed: %v", err)))
   283  		case <-tm.runner.TemplateRenderedCh():
   284  			// A template has been rendered, figure out what to do
   285  			events := tm.runner.RenderEvents()
   286  
   287  			// Not all templates have been rendered yet
   288  			if len(events) < len(tm.lookup) {
   289  				continue
   290  			}
   291  
   292  			dirty := false
   293  			for _, event := range events {
   294  				// This template hasn't been rendered
   295  				if event.LastWouldRender.IsZero() {
   296  					continue WAIT
   297  				}
   298  				if event.WouldRender && event.DidRender {
   299  					dirty = true
   300  				}
   301  			}
   302  
   303  			// if there's a driver handle then the task is already running and
   304  			// that changes how we want to behave on first render
   305  			if dirty && tm.config.Lifecycle.IsRunning() {
   306  				handledRenders := make(map[string]time.Time, len(tm.config.Templates))
   307  				tm.onTemplateRendered(handledRenders, time.Time{})
   308  			}
   309  
   310  			break WAIT
   311  		case <-tm.runner.RenderEventCh():
   312  			events := tm.runner.RenderEvents()
   313  			joinedSet := make(map[string]struct{})
   314  			for _, event := range events {
   315  				missing := event.MissingDeps
   316  				if missing == nil {
   317  					continue
   318  				}
   319  
   320  				for _, dep := range missing.List() {
   321  					joinedSet[dep.String()] = struct{}{}
   322  				}
   323  			}
   324  
   325  			// Check to see if the new joined set is the same as the old
   326  			different := len(joinedSet) != len(missingDependencies)
   327  			if !different {
   328  				for k := range joinedSet {
   329  					if _, ok := missingDependencies[k]; !ok {
   330  						different = true
   331  						break
   332  					}
   333  				}
   334  			}
   335  
   336  			// Nothing to do
   337  			if !different {
   338  				continue
   339  			}
   340  
   341  			// Update the missing set
   342  			missingDependencies = joinedSet
   343  
   344  			// Update the event timer channel
   345  			if !outstandingEvent {
   346  				// We got new data so reset
   347  				outstandingEvent = true
   348  				eventTimer.Reset(tm.config.MaxTemplateEventRate)
   349  			}
   350  		case <-eventTimer.C:
   351  			if missingDependencies == nil {
   352  				continue
   353  			}
   354  
   355  			// Clear the outstanding event
   356  			outstandingEvent = false
   357  
   358  			// Build the missing set
   359  			missingSlice := make([]string, 0, len(missingDependencies))
   360  			for k := range missingDependencies {
   361  				missingSlice = append(missingSlice, k)
   362  			}
   363  			sort.Strings(missingSlice)
   364  
   365  			if l := len(missingSlice); l > missingDepEventLimit {
   366  				missingSlice[missingDepEventLimit] = fmt.Sprintf("and %d more", l-missingDepEventLimit)
   367  				missingSlice = missingSlice[:missingDepEventLimit+1]
   368  			}
   369  
   370  			missingStr := strings.Join(missingSlice, ", ")
   371  			tm.config.Events.EmitEvent(structs.NewTaskEvent(consulTemplateSourceName).SetDisplayMessage(fmt.Sprintf("Missing: %s", missingStr)))
   372  		}
   373  	}
   374  }
   375  
   376  // handleTemplateRerenders is used to handle template render events after they
   377  // have all rendered. It takes action based on which set of templates re-render.
   378  // The passed allRenderedTime is the time at which all templates have rendered.
   379  // This is used to avoid signaling the task for any render event before hand.
   380  func (tm *TaskTemplateManager) handleTemplateRerenders(allRenderedTime time.Time) {
   381  	// A lookup for the last time the template was handled
   382  	handledRenders := make(map[string]time.Time, len(tm.config.Templates))
   383  
   384  	for {
   385  		select {
   386  		case <-tm.shutdownCh:
   387  			return
   388  		case err, ok := <-tm.runner.ErrCh:
   389  			if !ok {
   390  				continue
   391  			}
   392  
   393  			tm.config.Lifecycle.Kill(context.Background(),
   394  				structs.NewTaskEvent(structs.TaskKilling).
   395  					SetFailsTask().
   396  					SetDisplayMessage(fmt.Sprintf("Template failed: %v", err)))
   397  		case <-tm.runner.TemplateRenderedCh():
   398  			tm.onTemplateRendered(handledRenders, allRenderedTime)
   399  		}
   400  	}
   401  }
   402  
   403  func (tm *TaskTemplateManager) onTemplateRendered(handledRenders map[string]time.Time, allRenderedTime time.Time) {
   404  
   405  	var handling []string
   406  	signals := make(map[string]struct{})
   407  	scripts := []*structs.ChangeScript{}
   408  	restart := false
   409  	var splay time.Duration
   410  
   411  	events := tm.runner.RenderEvents()
   412  	for id, event := range events {
   413  
   414  		// First time through
   415  		if allRenderedTime.After(event.LastDidRender) || allRenderedTime.Equal(event.LastDidRender) {
   416  			handledRenders[id] = allRenderedTime
   417  			continue
   418  		}
   419  
   420  		// We have already handled this one
   421  		if htime := handledRenders[id]; htime.After(event.LastDidRender) || htime.Equal(event.LastDidRender) {
   422  			continue
   423  		}
   424  
   425  		// Lookup the template and determine what to do
   426  		tmpls, ok := tm.lookup[id]
   427  		if !ok {
   428  			tm.config.Lifecycle.Kill(context.Background(),
   429  				structs.NewTaskEvent(structs.TaskKilling).
   430  					SetFailsTask().
   431  					SetDisplayMessage(fmt.Sprintf("Template runner returned unknown template id %q", id)))
   432  			return
   433  		}
   434  
   435  		// Read environment variables from templates
   436  		envMap, err := loadTemplateEnv(tm.config.Templates, tm.config.EnvBuilder.Build())
   437  		if err != nil {
   438  			tm.config.Lifecycle.Kill(context.Background(),
   439  				structs.NewTaskEvent(structs.TaskKilling).
   440  					SetFailsTask().
   441  					SetDisplayMessage(fmt.Sprintf("Template failed to read environment variables: %v", err)))
   442  			return
   443  		}
   444  		tm.config.EnvBuilder.SetTemplateEnv(envMap)
   445  
   446  		for _, tmpl := range tmpls {
   447  			switch tmpl.ChangeMode {
   448  			case structs.TemplateChangeModeSignal:
   449  				signals[tmpl.ChangeSignal] = struct{}{}
   450  			case structs.TemplateChangeModeRestart:
   451  				restart = true
   452  			case structs.TemplateChangeModeScript:
   453  				scripts = append(scripts, tmpl.ChangeScript)
   454  			case structs.TemplateChangeModeNoop:
   455  				continue
   456  			}
   457  
   458  			if tmpl.Splay > splay {
   459  				splay = tmpl.Splay
   460  			}
   461  		}
   462  
   463  		handling = append(handling, id)
   464  	}
   465  
   466  	shouldHandle := restart || len(signals) != 0 || len(scripts) != 0
   467  	if !shouldHandle {
   468  		return
   469  	}
   470  
   471  	// Apply splay timeout to avoid applying change_mode too frequently.
   472  	if splay != 0 {
   473  		ns := splay.Nanoseconds()
   474  		offset := rand.Int63n(ns)
   475  		t := time.Duration(offset)
   476  
   477  		select {
   478  		case <-time.After(t):
   479  		case <-tm.shutdownCh:
   480  			return
   481  		}
   482  	}
   483  
   484  	// Update handle time
   485  	for _, id := range handling {
   486  		handledRenders[id] = events[id].LastDidRender
   487  	}
   488  
   489  	if restart {
   490  		tm.config.Lifecycle.Restart(context.Background(),
   491  			structs.NewTaskEvent(structs.TaskRestartSignal).
   492  				SetDisplayMessage("Template with change_mode restart re-rendered"), false)
   493  	} else {
   494  		// Handle signals and scripts since the task may have multiple
   495  		// templates with mixed change_mode values.
   496  		tm.handleChangeModeSignal(signals)
   497  		tm.handleChangeModeScript(scripts)
   498  	}
   499  }
   500  
   501  func (tm *TaskTemplateManager) handleChangeModeSignal(signals map[string]struct{}) {
   502  	var mErr multierror.Error
   503  	for signal := range signals {
   504  		s := tm.signals[signal]
   505  		event := structs.NewTaskEvent(structs.TaskSignaling).SetTaskSignal(s).SetDisplayMessage("Template re-rendered")
   506  		if err := tm.config.Lifecycle.Signal(event, signal); err != nil {
   507  			_ = multierror.Append(&mErr, err)
   508  		}
   509  	}
   510  
   511  	if err := mErr.ErrorOrNil(); err != nil {
   512  		flat := make([]os.Signal, 0, len(signals))
   513  		for signal := range signals {
   514  			flat = append(flat, tm.signals[signal])
   515  		}
   516  
   517  		tm.config.Lifecycle.Kill(context.Background(),
   518  			structs.NewTaskEvent(structs.TaskKilling).
   519  				SetFailsTask().
   520  				SetDisplayMessage(fmt.Sprintf("Template failed to send signals %v: %v", flat, err)))
   521  	}
   522  }
   523  
   524  func (tm *TaskTemplateManager) handleChangeModeScript(scripts []*structs.ChangeScript) {
   525  	// process script execution concurrently
   526  	var wg sync.WaitGroup
   527  	for _, script := range scripts {
   528  		wg.Add(1)
   529  		go tm.processScript(script, &wg)
   530  	}
   531  	wg.Wait()
   532  }
   533  
   534  // handleScriptError is a helper function that produces a TaskKilling event and
   535  // emits a message
   536  func (tm *TaskTemplateManager) handleScriptError(script *structs.ChangeScript, msg string) {
   537  	ev := structs.NewTaskEvent(structs.TaskHookFailed).SetDisplayMessage(msg)
   538  	tm.config.Events.EmitEvent(ev)
   539  
   540  	if script.FailOnError {
   541  		tm.config.Lifecycle.Kill(context.Background(),
   542  			structs.NewTaskEvent(structs.TaskKilling).
   543  				SetFailsTask().
   544  				SetDisplayMessage("Template script failed, task is being killed"))
   545  	}
   546  }
   547  
   548  // processScript is used for executing change_mode script and handling errors
   549  func (tm *TaskTemplateManager) processScript(script *structs.ChangeScript, wg *sync.WaitGroup) {
   550  	defer wg.Done()
   551  
   552  	if tm.handle == nil {
   553  		failureMsg := fmt.Sprintf(
   554  			"Template failed to run script %v with arguments %v because task driver doesn't support the exec operation",
   555  			script.Command,
   556  			script.Args,
   557  		)
   558  		tm.handleScriptError(script, failureMsg)
   559  		return
   560  	}
   561  	_, exitCode, err := tm.handle.Exec(script.Timeout, script.Command, script.Args)
   562  	if err != nil {
   563  		failureMsg := fmt.Sprintf(
   564  			"Template failed to run script %v with arguments %v on change: %v Exit code: %v",
   565  			script.Command,
   566  			script.Args,
   567  			err,
   568  			exitCode,
   569  		)
   570  		tm.handleScriptError(script, failureMsg)
   571  		return
   572  	}
   573  	if exitCode != 0 {
   574  		failureMsg := fmt.Sprintf(
   575  			"Template ran script %v with arguments %v on change but it exited with code code: %v",
   576  			script.Command,
   577  			script.Args,
   578  			exitCode,
   579  		)
   580  		tm.handleScriptError(script, failureMsg)
   581  		return
   582  	}
   583  	tm.config.Events.EmitEvent(structs.NewTaskEvent(structs.TaskHookMessage).
   584  		SetDisplayMessage(
   585  			fmt.Sprintf(
   586  				"Template successfully ran script %v with arguments: %v. Exit code: %v",
   587  				script.Command,
   588  				script.Args,
   589  				exitCode,
   590  			)))
   591  }
   592  
   593  // allTemplatesNoop returns whether all the managed templates have change mode noop.
   594  func (tm *TaskTemplateManager) allTemplatesNoop() bool {
   595  	for _, tmpl := range tm.config.Templates {
   596  		if tmpl.ChangeMode != structs.TemplateChangeModeNoop {
   597  			return false
   598  		}
   599  	}
   600  
   601  	return true
   602  }
   603  
   604  // templateRunner returns a consul-template runner for the given templates and a
   605  // lookup by destination to the template. If no templates are in the config, a
   606  // nil template runner and lookup is returned.
   607  func templateRunner(config *TaskTemplateManagerConfig) (
   608  	*manager.Runner, map[string][]*structs.Template, error) {
   609  
   610  	if len(config.Templates) == 0 {
   611  		return nil, nil, nil
   612  	}
   613  
   614  	// Parse the templates
   615  	ctmplMapping, err := parseTemplateConfigs(config)
   616  	if err != nil {
   617  		return nil, nil, err
   618  	}
   619  
   620  	// Create the runner configuration.
   621  	runnerConfig, err := newRunnerConfig(config, ctmplMapping)
   622  	if err != nil {
   623  		return nil, nil, err
   624  	}
   625  
   626  	runner, err := manager.NewRunner(runnerConfig, false)
   627  	if err != nil {
   628  		return nil, nil, err
   629  	}
   630  
   631  	// Set Nomad's environment variables.
   632  	// consul-template falls back to the host process environment if a
   633  	// variable isn't explicitly set in the configuration, so we need
   634  	// to mask the environment out to ensure only the task env vars are
   635  	// available.
   636  	runner.Env = maskProcessEnv(config.EnvBuilder.Build().All())
   637  
   638  	// Build the lookup
   639  	idMap := runner.TemplateConfigMapping()
   640  	lookup := make(map[string][]*structs.Template, len(idMap))
   641  	for id, ctmpls := range idMap {
   642  		for _, ctmpl := range ctmpls {
   643  			templates := lookup[id]
   644  			templates = append(templates, ctmplMapping[ctmpl])
   645  			lookup[id] = templates
   646  		}
   647  	}
   648  
   649  	return runner, lookup, nil
   650  }
   651  
   652  // maskProcessEnv masks away any environment variable not found in task env.
   653  // It manipulates the parameter directly and returns it without copying.
   654  func maskProcessEnv(env map[string]string) map[string]string {
   655  	procEnvs := os.Environ()
   656  	for _, e := range procEnvs {
   657  		ekv := strings.SplitN(e, "=", 2)
   658  		if _, ok := env[ekv[0]]; !ok {
   659  			env[ekv[0]] = ""
   660  		}
   661  	}
   662  
   663  	return env
   664  }
   665  
   666  // parseTemplateConfigs converts the tasks templates in the config into
   667  // consul-templates
   668  func parseTemplateConfigs(config *TaskTemplateManagerConfig) (map[*ctconf.TemplateConfig]*structs.Template, error) {
   669  	sandboxEnabled := !config.ClientConfig.TemplateConfig.DisableSandbox
   670  	taskEnv := config.EnvBuilder.Build()
   671  
   672  	ctmpls := make(map[*ctconf.TemplateConfig]*structs.Template, len(config.Templates))
   673  	for _, tmpl := range config.Templates {
   674  		var src, dest string
   675  		if tmpl.SourcePath != "" {
   676  			var escapes bool
   677  			src, escapes = taskEnv.ClientPath(tmpl.SourcePath, false)
   678  			if escapes && sandboxEnabled {
   679  				return nil, sourceEscapesErr
   680  			}
   681  		}
   682  
   683  		if tmpl.DestPath != "" {
   684  			var escapes bool
   685  			dest, escapes = taskEnv.ClientPath(tmpl.DestPath, true)
   686  			if escapes && sandboxEnabled {
   687  				return nil, destEscapesErr
   688  			}
   689  		}
   690  
   691  		ct := ctconf.DefaultTemplateConfig()
   692  		ct.Source = &src
   693  		ct.Destination = &dest
   694  		ct.Contents = &tmpl.EmbeddedTmpl
   695  		ct.LeftDelim = &tmpl.LeftDelim
   696  		ct.RightDelim = &tmpl.RightDelim
   697  		ct.ErrMissingKey = &tmpl.ErrMissingKey
   698  		ct.FunctionDenylist = config.ClientConfig.TemplateConfig.FunctionDenylist
   699  		if sandboxEnabled {
   700  			ct.SandboxPath = &config.TaskDir
   701  		}
   702  
   703  		if tmpl.Wait != nil {
   704  			if err := tmpl.Wait.Validate(); err != nil {
   705  				return nil, err
   706  			}
   707  
   708  			ct.Wait = &ctconf.WaitConfig{
   709  				Enabled: pointer.Of(true),
   710  				Min:     tmpl.Wait.Min,
   711  				Max:     tmpl.Wait.Max,
   712  			}
   713  		}
   714  
   715  		// Set the permissions
   716  		if tmpl.Perms != "" {
   717  			v, err := strconv.ParseUint(tmpl.Perms, 8, 12)
   718  			if err != nil {
   719  				return nil, fmt.Errorf("Failed to parse %q as octal: %v", tmpl.Perms, err)
   720  			}
   721  			m := os.FileMode(v)
   722  			ct.Perms = &m
   723  		}
   724  		// Set ownership
   725  		if tmpl.Uid != nil && *tmpl.Uid >= 0 {
   726  			ct.Uid = tmpl.Uid
   727  		}
   728  		if tmpl.Gid != nil && *tmpl.Gid >= 0 {
   729  			ct.Gid = tmpl.Gid
   730  		}
   731  
   732  		ct.Finalize()
   733  
   734  		ctmpls[ct] = tmpl
   735  	}
   736  
   737  	return ctmpls, nil
   738  }
   739  
   740  // newRunnerConfig returns a consul-template runner configuration, setting the
   741  // Vault and Consul configurations based on the clients configs.
   742  func newRunnerConfig(config *TaskTemplateManagerConfig,
   743  	templateMapping map[*ctconf.TemplateConfig]*structs.Template) (*ctconf.Config, error) {
   744  
   745  	cc := config.ClientConfig
   746  	conf := ctconf.DefaultConfig()
   747  
   748  	// Gather the consul-template templates
   749  	flat := ctconf.TemplateConfigs(make([]*ctconf.TemplateConfig, 0, len(templateMapping)))
   750  	for ctmpl := range templateMapping {
   751  		local := ctmpl
   752  		flat = append(flat, local)
   753  	}
   754  	conf.Templates = &flat
   755  
   756  	// Set the amount of time to do a blocking query for.
   757  	if cc.TemplateConfig.BlockQueryWaitTime != nil {
   758  		conf.BlockQueryWaitTime = cc.TemplateConfig.BlockQueryWaitTime
   759  	}
   760  
   761  	// Set the stale-read threshold to allow queries to be served by followers
   762  	// if the last replicated data is within this bound.
   763  	if cc.TemplateConfig.MaxStale != nil {
   764  		conf.MaxStale = cc.TemplateConfig.MaxStale
   765  	}
   766  
   767  	// Set the minimum and maximum amount of time to wait for the cluster to reach
   768  	// a consistent state before rendering a template.
   769  	if cc.TemplateConfig.Wait != nil {
   770  		// If somehow the WaitConfig wasn't set correctly upstream, return an error.
   771  		var err error
   772  		err = cc.TemplateConfig.Wait.Validate()
   773  		if err != nil {
   774  			return nil, err
   775  		}
   776  		conf.Wait, err = cc.TemplateConfig.Wait.ToConsulTemplate()
   777  		if err != nil {
   778  			return nil, err
   779  		}
   780  	}
   781  
   782  	// Make sure any template specific configuration set by the job author is within
   783  	// the bounds set by the operator.
   784  	if cc.TemplateConfig.WaitBounds != nil {
   785  		// If somehow the WaitBounds weren't set correctly upstream, return an error.
   786  		err := cc.TemplateConfig.WaitBounds.Validate()
   787  		if err != nil {
   788  			return nil, err
   789  		}
   790  
   791  		// Check and override with bounds
   792  		for _, tmpl := range *conf.Templates {
   793  			if tmpl.Wait == nil || !*tmpl.Wait.Enabled {
   794  				continue
   795  			}
   796  			if cc.TemplateConfig.WaitBounds.Min != nil {
   797  				if tmpl.Wait.Min != nil && *tmpl.Wait.Min < *cc.TemplateConfig.WaitBounds.Min {
   798  					tmpl.Wait.Min = &*cc.TemplateConfig.WaitBounds.Min
   799  				}
   800  			}
   801  			if cc.TemplateConfig.WaitBounds.Max != nil {
   802  				if tmpl.Wait.Max != nil && *tmpl.Wait.Max > *cc.TemplateConfig.WaitBounds.Max {
   803  					tmpl.Wait.Max = &*cc.TemplateConfig.WaitBounds.Max
   804  				}
   805  			}
   806  		}
   807  	}
   808  
   809  	// Set up the Consul config
   810  	if cc.ConsulConfig != nil {
   811  		conf.Consul.Address = &cc.ConsulConfig.Addr
   812  		conf.Consul.Token = &cc.ConsulConfig.Token
   813  
   814  		// Get the Consul namespace from agent config. This is the lower level
   815  		// of precedence (beyond default).
   816  		if cc.ConsulConfig.Namespace != "" {
   817  			conf.Consul.Namespace = &cc.ConsulConfig.Namespace
   818  		}
   819  
   820  		if cc.ConsulConfig.EnableSSL != nil && *cc.ConsulConfig.EnableSSL {
   821  			verify := cc.ConsulConfig.VerifySSL != nil && *cc.ConsulConfig.VerifySSL
   822  			conf.Consul.SSL = &ctconf.SSLConfig{
   823  				Enabled: pointer.Of(true),
   824  				Verify:  &verify,
   825  				Cert:    &cc.ConsulConfig.CertFile,
   826  				Key:     &cc.ConsulConfig.KeyFile,
   827  				CaCert:  &cc.ConsulConfig.CAFile,
   828  			}
   829  		}
   830  
   831  		if cc.ConsulConfig.Auth != "" {
   832  			parts := strings.SplitN(cc.ConsulConfig.Auth, ":", 2)
   833  			if len(parts) != 2 {
   834  				return nil, fmt.Errorf("Failed to parse Consul Auth config")
   835  			}
   836  
   837  			conf.Consul.Auth = &ctconf.AuthConfig{
   838  				Enabled:  pointer.Of(true),
   839  				Username: &parts[0],
   840  				Password: &parts[1],
   841  			}
   842  		}
   843  
   844  		// Set the user-specified Consul RetryConfig
   845  		if cc.TemplateConfig.ConsulRetry != nil {
   846  			var err error
   847  			err = cc.TemplateConfig.ConsulRetry.Validate()
   848  			if err != nil {
   849  				return nil, err
   850  			}
   851  			conf.Consul.Retry, err = cc.TemplateConfig.ConsulRetry.ToConsulTemplate()
   852  			if err != nil {
   853  				return nil, err
   854  			}
   855  		}
   856  	}
   857  
   858  	// Get the Consul namespace from job/group config. This is the higher level
   859  	// of precedence if set (above agent config).
   860  	if config.ConsulNamespace != "" {
   861  		conf.Consul.Namespace = &config.ConsulNamespace
   862  	}
   863  
   864  	// Set up the Vault config
   865  	// Always set these to ensure nothing is picked up from the environment
   866  	emptyStr := ""
   867  	conf.Vault.RenewToken = pointer.Of(false)
   868  	conf.Vault.Token = &emptyStr
   869  	if cc.VaultConfig != nil && cc.VaultConfig.IsEnabled() {
   870  		conf.Vault.Address = &cc.VaultConfig.Addr
   871  		conf.Vault.Token = &config.VaultToken
   872  
   873  		// Set the Vault Namespace. Passed in Task config has
   874  		// highest precedence.
   875  		if config.ClientConfig.VaultConfig.Namespace != "" {
   876  			conf.Vault.Namespace = &config.ClientConfig.VaultConfig.Namespace
   877  		}
   878  		if config.VaultNamespace != "" {
   879  			conf.Vault.Namespace = &config.VaultNamespace
   880  		}
   881  
   882  		if strings.HasPrefix(cc.VaultConfig.Addr, "https") || cc.VaultConfig.TLSCertFile != "" {
   883  			skipVerify := cc.VaultConfig.TLSSkipVerify != nil && *cc.VaultConfig.TLSSkipVerify
   884  			verify := !skipVerify
   885  			conf.Vault.SSL = &ctconf.SSLConfig{
   886  				Enabled:    pointer.Of(true),
   887  				Verify:     &verify,
   888  				Cert:       &cc.VaultConfig.TLSCertFile,
   889  				Key:        &cc.VaultConfig.TLSKeyFile,
   890  				CaCert:     &cc.VaultConfig.TLSCaFile,
   891  				CaPath:     &cc.VaultConfig.TLSCaPath,
   892  				ServerName: &cc.VaultConfig.TLSServerName,
   893  			}
   894  		} else {
   895  			conf.Vault.SSL = &ctconf.SSLConfig{
   896  				Enabled:    pointer.Of(false),
   897  				Verify:     pointer.Of(false),
   898  				Cert:       &emptyStr,
   899  				Key:        &emptyStr,
   900  				CaCert:     &emptyStr,
   901  				CaPath:     &emptyStr,
   902  				ServerName: &emptyStr,
   903  			}
   904  		}
   905  
   906  		// Set the user-specified Vault RetryConfig
   907  		if cc.TemplateConfig.VaultRetry != nil {
   908  			var err error
   909  			if err = cc.TemplateConfig.VaultRetry.Validate(); err != nil {
   910  				return nil, err
   911  			}
   912  			conf.Vault.Retry, err = cc.TemplateConfig.VaultRetry.ToConsulTemplate()
   913  			if err != nil {
   914  				return nil, err
   915  			}
   916  		}
   917  	}
   918  
   919  	// Set up Nomad
   920  	conf.Nomad.Namespace = &config.NomadNamespace
   921  	conf.Nomad.Transport.CustomDialer = cc.TemplateDialer
   922  	conf.Nomad.Token = &config.NomadToken
   923  	if cc.TemplateConfig != nil && cc.TemplateConfig.NomadRetry != nil {
   924  		// Set the user-specified Nomad RetryConfig
   925  		var err error
   926  		if err = cc.TemplateConfig.NomadRetry.Validate(); err != nil {
   927  			return nil, err
   928  		}
   929  		conf.Nomad.Retry, err = cc.TemplateConfig.NomadRetry.ToConsulTemplate()
   930  		if err != nil {
   931  			return nil, err
   932  		}
   933  	}
   934  
   935  	conf.Finalize()
   936  	return conf, nil
   937  }
   938  
   939  // loadTemplateEnv loads task environment variables from all templates.
   940  func loadTemplateEnv(tmpls []*structs.Template, taskEnv *taskenv.TaskEnv) (map[string]string, error) {
   941  	all := make(map[string]string, 50)
   942  	for _, t := range tmpls {
   943  		if !t.Envvars {
   944  			continue
   945  		}
   946  
   947  		// we checked escape before we rendered the file
   948  		dest, _ := taskEnv.ClientPath(t.DestPath, true)
   949  		f, err := os.Open(dest)
   950  		if err != nil {
   951  			return nil, fmt.Errorf("error opening env template: %v", err)
   952  		}
   953  		defer f.Close()
   954  
   955  		// Parse environment fil
   956  		vars, err := envparse.Parse(f)
   957  		if err != nil {
   958  			return nil, fmt.Errorf("error parsing env template %q: %v", dest, err)
   959  		}
   960  		for k, v := range vars {
   961  			all[k] = v
   962  		}
   963  	}
   964  	return all, nil
   965  }