mirror of
https://github.com/kemko/nomad.git
synced 2026-01-02 16:35:44 +03:00
311 lines
8.6 KiB
Go
311 lines
8.6 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package taskrunner
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
log "github.com/hashicorp/go-hclog"
|
|
"github.com/hashicorp/nomad/client/allocrunner/interfaces"
|
|
ti "github.com/hashicorp/nomad/client/allocrunner/taskrunner/interfaces"
|
|
"github.com/hashicorp/nomad/client/allocrunner/taskrunner/template"
|
|
"github.com/hashicorp/nomad/client/config"
|
|
cstructs "github.com/hashicorp/nomad/client/structs"
|
|
"github.com/hashicorp/nomad/client/taskenv"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
)
|
|
|
|
const (
|
|
templateHookName = "template"
|
|
)
|
|
|
|
type templateHookConfig struct {
|
|
// the allocation
|
|
alloc *structs.Allocation
|
|
|
|
// logger is used to log
|
|
logger log.Logger
|
|
|
|
// lifecycle is used to interact with the task's lifecycle
|
|
lifecycle ti.TaskLifecycle
|
|
|
|
// events is used to emit events
|
|
events ti.EventEmitter
|
|
|
|
// templates is the set of templates we are managing
|
|
templates []*structs.Template
|
|
|
|
// clientConfig is the Nomad Client configuration
|
|
clientConfig *config.Config
|
|
|
|
// envBuilder is the environment variable builder for the task.
|
|
envBuilder *taskenv.Builder
|
|
|
|
// consulNamespace is the current Consul namespace
|
|
consulNamespace string
|
|
|
|
// nomadNamespace is the job's Nomad namespace
|
|
nomadNamespace string
|
|
|
|
// renderOnTaskRestart is flag to explicitly render templates on task restart
|
|
renderOnTaskRestart bool
|
|
|
|
// hookResources are used to fetch Consul tokens
|
|
hookResources *cstructs.AllocHookResources
|
|
}
|
|
|
|
type templateHook struct {
|
|
config *templateHookConfig
|
|
|
|
// logger is used to log
|
|
logger log.Logger
|
|
|
|
// templateManager is used to manage any consul-templates this task may have
|
|
templateManager *template.TaskTemplateManager
|
|
managerLock sync.Mutex
|
|
|
|
// consulNamespace is the current Consul namespace
|
|
consulNamespace string
|
|
|
|
// vaultToken is the current Vault token
|
|
vaultToken string
|
|
|
|
// vaultNamespace is the current Vault namespace
|
|
vaultNamespace string
|
|
|
|
// nomadToken is the current Nomad token
|
|
nomadToken string
|
|
|
|
// consulToken is the Consul ACL token obtained from consul_hook via
|
|
// workload identity
|
|
consulToken string
|
|
|
|
// task is the task that defines these templates
|
|
task *structs.Task
|
|
|
|
// taskDir is the task directory
|
|
taskDir string
|
|
|
|
// taskID is a unique identifier for this templateHook, for use in
|
|
// downstream platform-specific template runner consumers
|
|
taskID string
|
|
}
|
|
|
|
func newTemplateHook(config *templateHookConfig) *templateHook {
|
|
return &templateHook{
|
|
config: config,
|
|
consulNamespace: config.consulNamespace,
|
|
logger: config.logger.Named(templateHookName),
|
|
}
|
|
}
|
|
|
|
func (*templateHook) Name() string {
|
|
return templateHookName
|
|
}
|
|
|
|
func (h *templateHook) Prestart(ctx context.Context, req *interfaces.TaskPrestartRequest, resp *interfaces.TaskPrestartResponse) error {
|
|
h.managerLock.Lock()
|
|
defer h.managerLock.Unlock()
|
|
|
|
// If we have already run prerun before exit early.
|
|
if h.templateManager != nil {
|
|
if !h.config.renderOnTaskRestart {
|
|
return nil
|
|
}
|
|
h.logger.Info("re-rendering templates on task restart")
|
|
h.templateManager.Stop()
|
|
h.templateManager = nil
|
|
}
|
|
|
|
// Store request information so they can be used in other hooks.
|
|
h.task = req.Task
|
|
h.taskDir = req.TaskDir.Dir
|
|
h.vaultToken = req.VaultToken
|
|
h.nomadToken = req.NomadToken
|
|
h.taskID = req.Alloc.ID + "-" + req.Task.Name
|
|
|
|
// Set the consul token if the task uses WI.
|
|
tg := h.config.alloc.Job.LookupTaskGroup(h.config.alloc.TaskGroup)
|
|
consulBlock := tg.Consul
|
|
if req.Task.Consul != nil {
|
|
consulBlock = req.Task.Consul
|
|
}
|
|
consulWIDName := consulBlock.IdentityName()
|
|
|
|
// Check if task has an identity for Consul and assume WI flow if it does.
|
|
// COMPAT simplify this logic and assume WI flow in 1.9+
|
|
hasConsulIdentity := false
|
|
for _, wid := range req.Task.Identities {
|
|
if wid.Name == consulWIDName {
|
|
hasConsulIdentity = true
|
|
break
|
|
}
|
|
}
|
|
if hasConsulIdentity {
|
|
consulCluster := req.Task.GetConsulClusterName(tg)
|
|
consulTokens := h.config.hookResources.GetConsulTokens()
|
|
clusterTokens := consulTokens[consulCluster]
|
|
|
|
if clusterTokens == nil {
|
|
return fmt.Errorf(
|
|
"consul tokens for cluster %s requested by task %s not found",
|
|
consulCluster, req.Task.Name,
|
|
)
|
|
}
|
|
|
|
consulToken := clusterTokens[consulWIDName+"/"+req.Task.Name]
|
|
if consulToken == nil {
|
|
return fmt.Errorf(
|
|
"consul tokens for cluster %s and identity %s requested by task %s not found",
|
|
consulCluster, consulWIDName, req.Task.Name,
|
|
)
|
|
}
|
|
|
|
h.consulToken = consulToken.SecretID
|
|
}
|
|
|
|
// Set vault namespace if specified
|
|
if req.Task.Vault != nil {
|
|
h.vaultNamespace = req.Task.Vault.Namespace
|
|
}
|
|
|
|
once, watch := []*structs.Template{}, []*structs.Template{}
|
|
for _, tmpl := range h.config.templates {
|
|
if tmpl.Once {
|
|
once = append(once, tmpl)
|
|
} else {
|
|
watch = append(watch, tmpl)
|
|
}
|
|
}
|
|
|
|
return h.renderTemplates(ctx, once, watch)
|
|
}
|
|
|
|
func (h *templateHook) newManager(tmpls []*structs.Template) (manager *template.TaskTemplateManager, unblock chan struct{}, err error) {
|
|
vaultCluster := h.task.GetVaultClusterName()
|
|
vaultConfig := h.config.clientConfig.GetVaultConfigs(h.logger)[vaultCluster]
|
|
|
|
// Fail if task has a vault block but no client config was found.
|
|
if h.task.Vault != nil && vaultConfig == nil {
|
|
return nil, nil, fmt.Errorf("Vault cluster %q is disabled or not configured", vaultCluster)
|
|
}
|
|
|
|
tg := h.config.alloc.Job.LookupTaskGroup(h.config.alloc.TaskGroup)
|
|
consulCluster := h.task.GetConsulClusterName(tg)
|
|
consulConfig := h.config.clientConfig.GetConsulConfigs(h.logger)[consulCluster]
|
|
|
|
unblock = make(chan struct{})
|
|
m, err := template.NewTaskTemplateManager(&template.TaskTemplateManagerConfig{
|
|
UnblockCh: unblock,
|
|
Lifecycle: h.config.lifecycle,
|
|
Events: h.config.events,
|
|
Templates: tmpls,
|
|
ClientConfig: h.config.clientConfig,
|
|
ConsulNamespace: h.config.consulNamespace,
|
|
ConsulToken: h.consulToken,
|
|
ConsulConfig: consulConfig,
|
|
VaultToken: h.vaultToken,
|
|
VaultConfig: vaultConfig,
|
|
VaultNamespace: h.vaultNamespace,
|
|
TaskDir: h.taskDir,
|
|
EnvBuilder: h.config.envBuilder,
|
|
MaxTemplateEventRate: template.DefaultMaxTemplateEventRate,
|
|
NomadNamespace: h.config.nomadNamespace,
|
|
NomadToken: h.nomadToken,
|
|
TaskID: h.taskID,
|
|
Logger: h.logger,
|
|
})
|
|
if err != nil {
|
|
h.logger.Error("failed to create template manager", "error", err)
|
|
return nil, nil, err
|
|
}
|
|
|
|
return m, unblock, nil
|
|
}
|
|
|
|
func (h *templateHook) Stop(_ context.Context, req *interfaces.TaskStopRequest, resp *interfaces.TaskStopResponse) error {
|
|
h.managerLock.Lock()
|
|
defer h.managerLock.Unlock()
|
|
|
|
// Shutdown any created template
|
|
if h.templateManager != nil {
|
|
h.templateManager.Stop()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Update is used to handle updates to vault and/or nomad tokens.
|
|
func (h *templateHook) Update(ctx context.Context, req *interfaces.TaskUpdateRequest, resp *interfaces.TaskUpdateResponse) error {
|
|
h.managerLock.Lock()
|
|
defer h.managerLock.Unlock()
|
|
|
|
// no template manager to manage
|
|
if h.templateManager == nil {
|
|
return nil
|
|
}
|
|
|
|
// neither vault or nomad token has been updated, nothing to do
|
|
if req.VaultToken == h.vaultToken && req.NomadToken == h.nomadToken {
|
|
return nil
|
|
} else {
|
|
h.vaultToken = req.VaultToken
|
|
h.nomadToken = req.NomadToken
|
|
}
|
|
|
|
tmpls := h.templateManager.Templates()
|
|
|
|
// shutdown the old template
|
|
h.templateManager.Stop()
|
|
h.templateManager = nil
|
|
|
|
err := h.renderTemplates(ctx, nil, tmpls)
|
|
if err != nil {
|
|
err = fmt.Errorf("failed to build template manager: %v", err)
|
|
h.logger.Error("failed to build template manager", "error", err)
|
|
_ = h.config.lifecycle.Kill(context.Background(),
|
|
structs.NewTaskEvent(structs.TaskKilling).
|
|
SetFailsTask().
|
|
SetDisplayMessage(fmt.Sprintf("Template update %v", err)))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// renderTemplates creates the template managers and waits until each template has rendered, setting the watch
|
|
// templateManger on the hook when complete so it can be referenced during token updates.
|
|
func (h *templateHook) renderTemplates(ctx context.Context, once []*structs.Template, watch []*structs.Template) error {
|
|
onceMgr, unblockOne, err := h.newManager(once)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
watchMgr, unblockWatch, err := h.newManager(watch)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
go onceMgr.Run()
|
|
go watchMgr.Run()
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
onceMgr.Stop()
|
|
case <-unblockOne:
|
|
}
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
watchMgr.Stop()
|
|
case <-unblockWatch:
|
|
}
|
|
|
|
// The template hook only needs to manage "watched" templates.
|
|
// We can ignore the "once" manager after it's templates render.
|
|
h.templateManager = watchMgr
|
|
return nil
|
|
}
|