func NewTaskTemplateManager(hook TaskHooks, tmpls []*structs.Template, allRendered bool, config *config.Config, vaultToken, taskDir string, taskEnv *env.TaskEnvironment) (*TaskTemplateManager, error) { // Check pre-conditions if hook == nil { return nil, fmt.Errorf("Invalid task hook given") } else if config == nil { return nil, fmt.Errorf("Invalid config given") } else if taskDir == "" { return nil, fmt.Errorf("Invalid task directory given") } else if taskEnv == nil { return nil, fmt.Errorf("Invalid task environment given") } tm := &TaskTemplateManager{ templates: tmpls, allRendered: allRendered, hook: hook, shutdownCh: make(chan struct{}), } // Parse the signals that we need for _, tmpl := range tmpls { if tmpl.ChangeSignal == "" { continue } sig, err := signals.Parse(tmpl.ChangeSignal) if err != nil { return nil, fmt.Errorf("Failed to parse signal %q", tmpl.ChangeSignal) } if tm.signals == nil { tm.signals = make(map[string]os.Signal) } tm.signals[tmpl.ChangeSignal] = sig } // Build the consul-template runner runner, lookup, err := templateRunner(tmpls, config, vaultToken, taskDir, taskEnv) if err != nil { return nil, err } tm.runner = runner tm.lookup = lookup go tm.run() return tm, nil }
// StringToSignalFunc parses a string as a signal based on the signal lookup // table. func StringToSignalFunc() mapstructure.DecodeHookFunc { return func( f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { if f.Kind() != reflect.String { return data, nil } if t.String() != "os.Signal" { return data, nil } if data == nil || data.(string) == "" { return (*os.Signal)(nil), nil } return signals.Parse(data.(string)) } }
// init creates the Runner's underlying data structures and returns an error if // any problems occur. func (r *Runner) init() error { // Ensure we have defaults config := DefaultConfig() config.Merge(r.config) r.config = config // Print the final config for debugging result, err := json.MarshalIndent(r.config, "", " ") if err != nil { return err } log.Printf("[DEBUG] (runner) final config (tokens suppressed):\n\n%s\n\n", result) // Setup the kill signal signal, err := signals.Parse(r.config.KillSignal) if err != nil { return errors.Wrap(err, "runner") } r.killSignal = signal // Create the clientset clients, err := newClientSet(r.config) if err != nil { return fmt.Errorf("runner: %s", err) } // Create the watcher watcher, err := newWatcher(r.config, clients, r.once) if err != nil { return fmt.Errorf("runner: %s", err) } r.watcher = watcher r.data = make(map[string]interface{}) r.configPrefixMap = make(map[string]*ConfigPrefix) r.inStream = os.Stdin r.outStream = os.Stdout r.errStream = os.Stderr r.ErrCh = make(chan error) r.DoneCh = make(chan struct{}) r.ExitCh = make(chan int, 1) // Parse and add consul dependencies for _, p := range r.config.Prefixes { d, err := dep.ParseStoreKeyPrefix(p.Path) if err != nil { return err } r.dependencies = append(r.dependencies, d) r.configPrefixMap[d.HashCode()] = p } // Parse and add vault dependencies - it is important that this come after // consul, because consul should never be permitted to overwrite values from // vault; that would expose a security hole since access to consul is // typically less controlled than access to vault. for _, s := range r.config.Secrets { log.Printf("looking at vault %s", s.Path) d, err := dep.ParseVaultSecret(s.Path) if err != nil { return err } r.dependencies = append(r.dependencies, d) r.configPrefixMap[d.HashCode()] = s } return nil }
// ParseFlags is a helper function for parsing command line flags using Go's // Flag library. This is extracted into a helper to keep the main function // small, but it also makes writing tests for parsing command line arguments // much easier and cleaner. func (cli *CLI) ParseFlags(args []string) (*config.Config, bool, bool, bool, error) { var dry, once, version bool c := config.DefaultConfig() // configPaths stores the list of configuration paths on disk configPaths := make([]string, 0, 6) // Parse the flags and options flags := flag.NewFlagSet(Name, flag.ContinueOnError) flags.SetOutput(cli.errStream) flags.Usage = func() { fmt.Fprintf(cli.errStream, usage, Name) } flags.Var((funcVar)(func(s string) error { configPaths = append(configPaths, s) return nil }), "config", "") flags.Var((funcVar)(func(s string) error { c.Consul.Address = config.String(s) return nil }), "consul-addr", "") flags.Var((funcVar)(func(s string) error { a, err := config.ParseAuthConfig(s) if err != nil { return err } c.Consul.Auth = a return nil }), "consul-auth", "") flags.Var((funcBoolVar)(func(b bool) error { c.Consul.Retry.Enabled = config.Bool(b) return nil }), "consul-retry", "") flags.Var((funcIntVar)(func(i int) error { c.Consul.Retry.Attempts = config.Int(i) return nil }), "consul-retry-attempts", "") flags.Var((funcDurationVar)(func(d time.Duration) error { c.Consul.Retry.Backoff = config.TimeDuration(d) return nil }), "consul-retry-backoff", "") flags.Var((funcBoolVar)(func(b bool) error { c.Consul.SSL.Enabled = config.Bool(b) return nil }), "consul-ssl", "") flags.Var((funcVar)(func(s string) error { c.Consul.SSL.CaCert = config.String(s) return nil }), "consul-ssl-ca-cert", "") flags.Var((funcVar)(func(s string) error { c.Consul.SSL.CaPath = config.String(s) return nil }), "consul-ssl-ca-path", "") flags.Var((funcVar)(func(s string) error { c.Consul.SSL.Cert = config.String(s) return nil }), "consul-ssl-cert", "") flags.Var((funcVar)(func(s string) error { c.Consul.SSL.Key = config.String(s) return nil }), "consul-ssl-key", "") flags.Var((funcVar)(func(s string) error { c.Consul.SSL.ServerName = config.String(s) return nil }), "consul-ssl-server-name", "") flags.Var((funcBoolVar)(func(b bool) error { c.Consul.SSL.Verify = config.Bool(b) return nil }), "consul-ssl-verify", "") flags.Var((funcBoolVar)(func(b bool) error { c.Dedup.Enabled = config.Bool(b) return nil }), "dedup", "") flags.BoolVar(&dry, "dry", false, "") flags.Var((funcVar)(func(s string) error { c.Exec.Enabled = config.Bool(true) c.Exec.Command = config.String(s) return nil }), "exec", "") flags.Var((funcVar)(func(s string) error { sig, err := signals.Parse(s) if err != nil { return err } c.Exec.KillSignal = config.Signal(sig) return nil }), "exec-kill-signal", "") flags.Var((funcDurationVar)(func(d time.Duration) error { c.Exec.KillTimeout = config.TimeDuration(d) return nil }), "exec-kill-timeout", "") flags.Var((funcVar)(func(s string) error { sig, err := signals.Parse(s) if err != nil { return err } c.Exec.ReloadSignal = config.Signal(sig) return nil }), "exec-reload-signal", "") flags.Var((funcDurationVar)(func(d time.Duration) error { c.Exec.Splay = config.TimeDuration(d) return nil }), "exec-splay", "") flags.Var((funcVar)(func(s string) error { sig, err := signals.Parse(s) if err != nil { return err } c.KillSignal = config.Signal(sig) return nil }), "kill-signal", "") flags.Var((funcVar)(func(s string) error { c.LogLevel = config.String(s) return nil }), "log-level", "") flags.Var((funcDurationVar)(func(d time.Duration) error { c.MaxStale = config.TimeDuration(d) return nil }), "max-stale", "") flags.BoolVar(&once, "once", false, "") flags.Var((funcVar)(func(s string) error { c.PidFile = config.String(s) return nil }), "pid-file", "") flags.Var((funcVar)(func(s string) error { sig, err := signals.Parse(s) if err != nil { return err } c.ReloadSignal = config.Signal(sig) return nil }), "reload-signal", "") flags.Var((funcDurationVar)(func(d time.Duration) error { c.Consul.Retry.Backoff = config.TimeDuration(d) return nil }), "retry", "") flags.Var((funcBoolVar)(func(b bool) error { c.Syslog.Enabled = config.Bool(b) return nil }), "syslog", "") flags.Var((funcVar)(func(s string) error { c.Syslog.Facility = config.String(s) return nil }), "syslog-facility", "") flags.Var((funcVar)(func(s string) error { t, err := config.ParseTemplateConfig(s) if err != nil { return err } *c.Templates = append(*c.Templates, t) return nil }), "template", "") flags.Var((funcVar)(func(s string) error { c.Consul.Token = config.String(s) return nil }), "token", "") flags.Var((funcVar)(func(s string) error { c.Vault.Address = config.String(s) return nil }), "vault-addr", "") flags.Var((funcBoolVar)(func(b bool) error { c.Vault.RenewToken = config.Bool(b) return nil }), "vault-renew-token", "") flags.Var((funcBoolVar)(func(b bool) error { c.Vault.Retry.Enabled = config.Bool(b) return nil }), "vault-retry", "") flags.Var((funcIntVar)(func(i int) error { c.Vault.Retry.Attempts = config.Int(i) return nil }), "vault-retry-attempts", "") flags.Var((funcDurationVar)(func(d time.Duration) error { c.Vault.Retry.Backoff = config.TimeDuration(d) return nil }), "vault-retry-backoff", "") flags.Var((funcBoolVar)(func(b bool) error { c.Vault.SSL.Enabled = config.Bool(b) return nil }), "vault-ssl", "") flags.Var((funcVar)(func(s string) error { c.Vault.SSL.CaCert = config.String(s) return nil }), "vault-ssl-ca-cert", "") flags.Var((funcVar)(func(s string) error { c.Vault.SSL.CaPath = config.String(s) return nil }), "vault-ssl-ca-path", "") flags.Var((funcVar)(func(s string) error { c.Vault.SSL.Cert = config.String(s) return nil }), "vault-ssl-cert", "") flags.Var((funcVar)(func(s string) error { c.Vault.SSL.Key = config.String(s) return nil }), "vault-ssl-key", "") flags.Var((funcVar)(func(s string) error { c.Vault.SSL.ServerName = config.String(s) return nil }), "vault-ssl-server-name", "") flags.Var((funcBoolVar)(func(b bool) error { c.Vault.SSL.Verify = config.Bool(b) return nil }), "vault-ssl-verify", "") flags.Var((funcVar)(func(s string) error { c.Vault.Token = config.String(s) return nil }), "vault-token", "") flags.Var((funcBoolVar)(func(b bool) error { c.Vault.UnwrapToken = config.Bool(b) return nil }), "vault-unwrap-token", "") flags.Var((funcVar)(func(s string) error { w, err := config.ParseWaitConfig(s) if err != nil { return err } c.Wait = w return nil }), "wait", "") flags.BoolVar(&version, "v", false, "") flags.BoolVar(&version, "version", false, "") // TODO: Deprecations for i, a := range args { if a == "-auth" || strings.HasPrefix(a, "-auth=") { log.Println("[WARN] -auth has been renamed to -consul-auth") args[i] = strings.Replace(a, "-auth", "-consul-auth", 1) } if a == "-consul" || strings.HasPrefix(a, "-consul=") { log.Println("[WARN] -consul has been renamed to -consul-addr") args[i] = strings.Replace(a, "-consul", "-consul-addr", 1) } if strings.HasPrefix(a, "-ssl") { log.Println("[WARN] -ssl options should be prefixed with -consul") args[i] = strings.Replace(a, "-ssl", "-consul-ssl", 1) } } // If there was a parser error, stop if err := flags.Parse(args); err != nil { return nil, false, false, false, err } // Error if extra arguments are present args = flags.Args() if len(args) > 0 { return nil, false, false, false, fmt.Errorf("cli: extra args: %q", args) } // Create the final configuration finalC := config.DefaultConfig() // Merge all the provided configurations in the order supplied for _, path := range configPaths { c, err := config.FromPath(path) if err != nil { return nil, false, false, false, err } finalC = finalC.Merge(c) } // Add any CLI configuration options, since that's highest precedence finalC = finalC.Merge(c) // Finalize the configuration finalC.Finalize() return finalC, once, dry, version, nil }
// vaultManager should be called in a go-routine and manages the derivation, // renewal and handling of errors with the Vault token. The optional parameter // allows setting the initial Vault token. This is useful when the Vault token // is recovered off disk. func (r *TaskRunner) vaultManager(token string) { // updatedToken lets us store state between loops. If true, a new token // has been retrieved and we need to apply the Vault change mode var updatedToken bool OUTER: for { // Check if we should exit select { case <-r.waitCh: return default: } // Clear the token r.vaultFuture.Clear() // Check if there already is a token which can be the case for // restoring the TaskRunner if token == "" { // Get a token var exit bool token, exit = r.deriveVaultToken() if exit { // Exit the manager return } // Write the token to disk if err := r.writeToken(token); err != nil { e := fmt.Errorf("failed to write Vault token to disk") r.logger.Printf("[ERR] client: %v for task %v on alloc %q: %v", e, r.task.Name, r.alloc.ID, err) r.Kill("vault", e.Error(), true) return } } // Start the renewal process renewCh, err := r.vaultClient.RenewToken(token, 30) // An error returned means the token is not being renewed if err != nil { r.logger.Printf("[ERR] client: failed to start renewal of Vault token for task %v on alloc %q: %v", r.task.Name, r.alloc.ID, err) token = "" goto OUTER } // The Vault token is valid now, so set it r.vaultFuture.Set(token) if updatedToken { switch r.task.Vault.ChangeMode { case structs.VaultChangeModeSignal: s, err := signals.Parse(r.task.Vault.ChangeSignal) if err != nil { e := fmt.Errorf("failed to parse signal: %v", err) r.logger.Printf("[ERR] client: %v", err) r.Kill("vault", e.Error(), true) return } if err := r.Signal("vault", "new Vault token acquired", s); err != nil { r.logger.Printf("[ERR] client: failed to send signal to task %v for alloc %q: %v", r.task.Name, r.alloc.ID, err) r.Kill("vault", fmt.Sprintf("failed to send signal to task: %v", err), true) return } case structs.VaultChangeModeRestart: r.Restart("vault", "new Vault token acquired") case structs.VaultChangeModeNoop: fallthrough default: r.logger.Printf("[ERR] client: Invalid Vault change mode: %q", r.task.Vault.ChangeMode) } // We have handled it updatedToken = false // Call the handler r.updatedTokenHandler() } // Start watching for renewal errors select { case err := <-renewCh: // Clear the token token = "" r.logger.Printf("[ERR] client: failed to renew Vault token for task %v on alloc %q: %v", r.task.Name, r.alloc.ID, err) // Check if we have to do anything if r.task.Vault.ChangeMode != structs.VaultChangeModeNoop { updatedToken = true } case <-r.waitCh: return } } }
// parseFlags is a helper function for parsing command line flags using Go's // Flag library. This is extracted into a helper to keep the main function // small, but it also makes writing tests for parsing command line arguments // much easier and cleaner. func (cli *CLI) parseFlags(args []string) (*Config, bool, bool, bool, error) { var dry, once, version bool config := DefaultConfig() // Parse the flags and options flags := flag.NewFlagSet(Name, flag.ContinueOnError) flags.SetOutput(cli.errStream) flags.Usage = func() { fmt.Fprintf(cli.errStream, usage, Name) } flags.Var((funcVar)(func(s string) error { config.Consul = s config.set("consul") return nil }), "consul", "") flags.Var((funcVar)(func(s string) error { config.Token = s config.set("token") return nil }), "token", "") flags.Var((funcVar)(func(s string) error { config.Auth.Enabled = true config.set("auth.enabled") if strings.Contains(s, ":") { split := strings.SplitN(s, ":", 2) config.Auth.Username = split[0] config.set("auth.username") config.Auth.Password = split[1] config.set("auth.password") } else { config.Auth.Username = s config.set("auth.username") } return nil }), "auth", "") flags.Var((funcBoolVar)(func(b bool) error { config.SSL.Enabled = b config.set("ssl") config.set("ssl.enabled") return nil }), "ssl", "") flags.Var((funcBoolVar)(func(b bool) error { config.SSL.Verify = b config.set("ssl") config.set("ssl.verify") return nil }), "ssl-verify", "") flags.Var((funcVar)(func(s string) error { config.SSL.Cert = s config.set("ssl") config.set("ssl.cert") return nil }), "ssl-cert", "") flags.Var((funcVar)(func(s string) error { config.SSL.Key = s config.set("ssl") config.set("ssl.key") return nil }), "ssl-key", "") flags.Var((funcVar)(func(s string) error { config.SSL.CaCert = s config.set("ssl") config.set("ssl.ca_cert") return nil }), "ssl-ca-cert", "") flags.Var((funcDurationVar)(func(d time.Duration) error { config.MaxStale = d config.set("max_stale") return nil }), "max-stale", "") flags.Var((funcVar)(func(s string) error { t, err := ParseConfigTemplate(s) if err != nil { return err } if config.ConfigTemplates == nil { config.ConfigTemplates = make([]*ConfigTemplate, 0, 1) } config.ConfigTemplates = append(config.ConfigTemplates, t) return nil }), "template", "") flags.Var((funcBoolVar)(func(b bool) error { config.Syslog.Enabled = b config.set("syslog") config.set("syslog.enabled") return nil }), "syslog", "") flags.Var((funcVar)(func(s string) error { config.Syslog.Facility = s config.set("syslog.facility") return nil }), "syslog-facility", "") flags.Var((funcBoolVar)(func(b bool) error { config.Deduplicate.Enabled = b config.set("deduplicate") config.set("deduplicate.enabled") return nil }), "dedup", "") flags.Var((funcVar)(func(s string) error { config.Exec.Command = s config.set("exec") config.set("exec.command") return nil }), "exec", "") flags.Var((funcDurationVar)(func(d time.Duration) error { config.Exec.Splay = d config.set("exec.splay") return nil }), "exec-splay", "") flags.Var((funcVar)(func(s string) error { sig, err := signals.Parse(s) if err != nil { return err } config.Exec.ReloadSignal = sig config.set("exec.reload_signal") return nil }), "exec-reload-signal", "") flags.Var((funcVar)(func(s string) error { sig, err := signals.Parse(s) if err != nil { return err } config.Exec.KillSignal = sig config.set("exec.kill_signal") return nil }), "exec-kill-signal", "") flags.Var((funcDurationVar)(func(d time.Duration) error { config.Exec.KillTimeout = d config.set("exec.kill_timeout") return nil }), "exec-kill-timeout", "") flags.Var((funcVar)(func(s string) error { w, err := watch.ParseWait(s) if err != nil { return err } config.Wait.Min = w.Min config.Wait.Max = w.Max config.set("wait") return nil }), "wait", "") flags.Var((funcDurationVar)(func(d time.Duration) error { config.Retry = d config.set("retry") return nil }), "retry", "") flags.Var((funcVar)(func(s string) error { config.Path = s config.set("path") return nil }), "config", "") flags.Var((funcVar)(func(s string) error { config.PidFile = s config.set("pid_file") return nil }), "pid-file", "") flags.Var((funcVar)(func(s string) error { config.LogLevel = s config.set("log_level") return nil }), "log-level", "") flags.BoolVar(&once, "once", false, "") flags.BoolVar(&dry, "dry", false, "") flags.BoolVar(&version, "v", false, "") flags.BoolVar(&version, "version", false, "") // If there was a parser error, stop if err := flags.Parse(args); err != nil { return nil, false, false, false, err } // Error if extra arguments are present args = flags.Args() if len(args) > 0 { return nil, false, false, false, fmt.Errorf("cli: extra argument(s): %q", args) } return config, once, dry, version, nil }