|
| 1 | +// Package confd provides basic Confd support. |
| 2 | +// |
| 3 | +// Right now, this library is highly specific to the needs of the present |
| 4 | +// builder. Because the confd library is not all public, we don't use it directly. |
| 5 | +// Instead, we invoke the CLI. |
| 6 | +package confd |
| 7 | + |
| 8 | +import ( |
| 9 | + "fmt" |
| 10 | + "os" |
| 11 | + "os/exec" |
| 12 | + "strconv" |
| 13 | + "time" |
| 14 | + |
| 15 | + "github.com/Masterminds/cookoo" |
| 16 | + "github.com/Masterminds/cookoo/log" |
| 17 | + "github.com/Masterminds/cookoo/safely" |
| 18 | +) |
| 19 | + |
| 20 | +// defaultEtcd is the default Etcd host. |
| 21 | +const defaultEtcd = "127.0.0.1:4001" |
| 22 | + |
| 23 | +// RunOnce runs the equivalent of `confd --onetime`. |
| 24 | +// |
| 25 | +// This may run the process repeatedly until either we time out (~20 minutes) or |
| 26 | +// the templates are successfully built. |
| 27 | +// |
| 28 | +// Importantly, this blocks until the run is complete. |
| 29 | +// |
| 30 | +// Params: |
| 31 | +// - node (string): The etcd node to use. (Only etcd is currently supported) |
| 32 | +// |
| 33 | +// Returns: |
| 34 | +// - The []bytes from stdout and stderr when running the program. |
| 35 | +// |
| 36 | +func RunOnce(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { |
| 37 | + node := p.Get("node", defaultEtcd).(string) |
| 38 | + |
| 39 | + dargs := []string{"-onetime", "-node", node, "-log-level", "error"} |
| 40 | + |
| 41 | + log.Info(c, "Building confd templates. This may take a moment.") |
| 42 | + |
| 43 | + limit := 1200 |
| 44 | + timeout := time.Second * 3 |
| 45 | + var lasterr error |
| 46 | + start := time.Now() |
| 47 | + for i := 0; i < limit; i++ { |
| 48 | + if out, err := exec.Command("confd", dargs...).CombinedOutput(); err == nil { |
| 49 | + log.Infof(c, "Templates generated for %s on run %d", node, i) |
| 50 | + return out, nil |
| 51 | + } else { |
| 52 | + log.Debugf(c, "Recoverable error: %s", err) |
| 53 | + log.Debugf(c, "Output: %q", out) |
| 54 | + lasterr = err |
| 55 | + } |
| 56 | + |
| 57 | + time.Sleep(timeout) |
| 58 | + log.Infof(c, "Re-trying template build. (Elapsed time: %d)", time.Now().Sub(start)/time.Second) |
| 59 | + } |
| 60 | + |
| 61 | + return nil, fmt.Errorf("Could not build confd templates before timeout. Last error: %s", lasterr) |
| 62 | +} |
| 63 | + |
| 64 | +// Run starts confd and runs it in the background. |
| 65 | +// |
| 66 | +// If the command fails immediately on startup, an error is immediately |
| 67 | +// returned. But from that point, a goroutine watches the command and |
| 68 | +// reports if the command dies. |
| 69 | +// |
| 70 | +// Params: |
| 71 | +// - node (string): The etcd node to use. (Only etcd is currently supported) |
| 72 | +// - interval (int, default:5): The rebuilding interval. |
| 73 | +// |
| 74 | +// Returns |
| 75 | +// bool true if this succeeded. |
| 76 | +func Run(c cookoo.Context, p *cookoo.Params) (interface{}, cookoo.Interrupt) { |
| 77 | + node := p.Get("node", defaultEtcd).(string) |
| 78 | + interval := strconv.Itoa(p.Get("interval", 5).(int)) |
| 79 | + |
| 80 | + cmd := exec.Command("confd", "-log-level", "error", "-node", node, "-interval", interval) |
| 81 | + if err := cmd.Start(); err != nil { |
| 82 | + return false, err |
| 83 | + } |
| 84 | + |
| 85 | + log.Infof(c, "Watching confd.") |
| 86 | + safely.Go(func() { |
| 87 | + if err := cmd.Wait(); err != nil { |
| 88 | + // If confd exits, builder will stop functioning as intended. So |
| 89 | + // we stop builder and let the environment restart. |
| 90 | + log.Errf(c, "Stopping builder. confd exited with error: %s", err) |
| 91 | + os.Exit(37) |
| 92 | + } |
| 93 | + }) |
| 94 | + |
| 95 | + return true, nil |
| 96 | +} |
0 commit comments