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 }