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

     1  package taskrunner
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  
     8  	log "github.com/hashicorp/go-hclog"
     9  	"github.com/hashicorp/nomad/client/allocrunner/interfaces"
    10  	ti "github.com/hashicorp/nomad/client/allocrunner/taskrunner/interfaces"
    11  	"github.com/hashicorp/nomad/client/allocrunner/taskrunner/template"
    12  	"github.com/hashicorp/nomad/client/config"
    13  	"github.com/hashicorp/nomad/client/taskenv"
    14  	"github.com/hashicorp/nomad/nomad/structs"
    15  )
    16  
    17  const (
    18  	templateHookName = "template"
    19  )
    20  
    21  type templateHookConfig struct {
    22  	// logger is used to log
    23  	logger log.Logger
    24  
    25  	// lifecycle is used to interact with the task's lifecycle
    26  	lifecycle ti.TaskLifecycle
    27  
    28  	// events is used to emit events
    29  	events ti.EventEmitter
    30  
    31  	// templates is the set of templates we are managing
    32  	templates []*structs.Template
    33  
    34  	// clientConfig is the Nomad Client configuration
    35  	clientConfig *config.Config
    36  
    37  	// envBuilder is the environment variable builder for the task.
    38  	envBuilder *taskenv.Builder
    39  
    40  	// consulNamespace is the current Consul namespace
    41  	consulNamespace string
    42  
    43  	// nomadNamespace is the job's Nomad namespace
    44  	nomadNamespace string
    45  }
    46  
    47  type templateHook struct {
    48  	config *templateHookConfig
    49  
    50  	// logger is used to log
    51  	logger log.Logger
    52  
    53  	// templateManager is used to manage any consul-templates this task may have
    54  	templateManager *template.TaskTemplateManager
    55  	managerLock     sync.Mutex
    56  
    57  	// consulNamespace is the current Consul namespace
    58  	consulNamespace string
    59  
    60  	// vaultToken is the current Vault token
    61  	vaultToken string
    62  
    63  	// vaultNamespace is the current Vault namespace
    64  	vaultNamespace string
    65  
    66  	// nomadToken is the current Nomad token
    67  	nomadToken string
    68  
    69  	// taskDir is the task directory
    70  	taskDir string
    71  }
    72  
    73  func newTemplateHook(config *templateHookConfig) *templateHook {
    74  	return &templateHook{
    75  		config:          config,
    76  		consulNamespace: config.consulNamespace,
    77  		logger:          config.logger.Named(templateHookName),
    78  	}
    79  }
    80  
    81  func (*templateHook) Name() string {
    82  	return templateHookName
    83  }
    84  
    85  func (h *templateHook) Prestart(ctx context.Context, req *interfaces.TaskPrestartRequest, resp *interfaces.TaskPrestartResponse) error {
    86  	h.managerLock.Lock()
    87  	defer h.managerLock.Unlock()
    88  
    89  	// If we have already run prerun before exit early.
    90  	if h.templateManager != nil {
    91  		return nil
    92  	}
    93  
    94  	// Store the current Vault token and the task directory
    95  	h.taskDir = req.TaskDir.Dir
    96  	h.vaultToken = req.VaultToken
    97  	h.nomadToken = req.NomadToken
    98  
    99  	// Set vault namespace if specified
   100  	if req.Task.Vault != nil {
   101  		h.vaultNamespace = req.Task.Vault.Namespace
   102  	}
   103  
   104  	unblockCh, err := h.newManager()
   105  	if err != nil {
   106  		return err
   107  	}
   108  
   109  	// Wait for the template to render
   110  	select {
   111  	case <-ctx.Done():
   112  	case <-unblockCh:
   113  	}
   114  
   115  	return nil
   116  }
   117  
   118  func (h *templateHook) Poststart(ctx context.Context, req *interfaces.TaskPoststartRequest, resp *interfaces.TaskPoststartResponse) error {
   119  	h.managerLock.Lock()
   120  	defer h.managerLock.Unlock()
   121  
   122  	if h.templateManager == nil {
   123  		return nil
   124  	}
   125  
   126  	if req.DriverExec != nil {
   127  		h.templateManager.SetDriverHandle(req.DriverExec)
   128  	} else {
   129  		for _, tmpl := range h.config.templates {
   130  			if tmpl.ChangeMode == structs.TemplateChangeModeScript {
   131  				return fmt.Errorf("template has change mode set to 'script' but the driver it uses does not provide exec capability")
   132  			}
   133  		}
   134  	}
   135  	return nil
   136  }
   137  
   138  func (h *templateHook) newManager() (unblock chan struct{}, err error) {
   139  	unblock = make(chan struct{})
   140  	m, err := template.NewTaskTemplateManager(&template.TaskTemplateManagerConfig{
   141  		UnblockCh:            unblock,
   142  		Lifecycle:            h.config.lifecycle,
   143  		Events:               h.config.events,
   144  		Templates:            h.config.templates,
   145  		ClientConfig:         h.config.clientConfig,
   146  		ConsulNamespace:      h.config.consulNamespace,
   147  		VaultToken:           h.vaultToken,
   148  		VaultNamespace:       h.vaultNamespace,
   149  		TaskDir:              h.taskDir,
   150  		EnvBuilder:           h.config.envBuilder,
   151  		MaxTemplateEventRate: template.DefaultMaxTemplateEventRate,
   152  		NomadNamespace:       h.config.nomadNamespace,
   153  		NomadToken:           h.nomadToken,
   154  	})
   155  	if err != nil {
   156  		h.logger.Error("failed to create template manager", "error", err)
   157  		return nil, err
   158  	}
   159  
   160  	h.templateManager = m
   161  	return unblock, nil
   162  }
   163  
   164  func (h *templateHook) Stop(ctx context.Context, req *interfaces.TaskStopRequest, resp *interfaces.TaskStopResponse) error {
   165  	h.managerLock.Lock()
   166  	defer h.managerLock.Unlock()
   167  
   168  	// Shutdown any created template
   169  	if h.templateManager != nil {
   170  		h.templateManager.Stop()
   171  	}
   172  
   173  	return nil
   174  }
   175  
   176  // Update is used to handle updates to vault and/or nomad tokens.
   177  func (h *templateHook) Update(ctx context.Context, req *interfaces.TaskUpdateRequest, resp *interfaces.TaskUpdateResponse) error {
   178  	h.managerLock.Lock()
   179  	defer h.managerLock.Unlock()
   180  
   181  	// no template manager to manage
   182  	if h.templateManager == nil {
   183  		return nil
   184  	}
   185  
   186  	// neither vault or nomad token has been updated, nothing to do
   187  	if req.VaultToken == h.vaultToken && req.NomadToken == h.nomadToken {
   188  		return nil
   189  	} else {
   190  		h.vaultToken = req.VaultToken
   191  		h.nomadToken = req.NomadToken
   192  	}
   193  
   194  	// shutdown the old template
   195  	h.templateManager.Stop()
   196  	h.templateManager = nil
   197  
   198  	// create the new template
   199  	if _, err := h.newManager(); err != nil {
   200  		err = fmt.Errorf("failed to build template manager: %v", err)
   201  		h.logger.Error("failed to build template manager", "error", err)
   202  		_ = h.config.lifecycle.Kill(context.Background(),
   203  			structs.NewTaskEvent(structs.TaskKilling).
   204  				SetFailsTask().
   205  				SetDisplayMessage(fmt.Sprintf("Template update %v", err)))
   206  	}
   207  
   208  	return nil
   209  }