package haproxy import ( "bytes" "context" "crypto/sha256" "fmt" "io" "io/ioutil" "os" "os/exec" "sync" "text/template" ) type ServiceManager interface { Reload(context.Context) error Status(context.Context) error Start(context.Context) error Stop(context.Context) error } type HAProxyManager struct { sync.Mutex configFile string svc ServiceManager template *template.Template hash []byte } func NewHAProxyManager(configFile, service string) (ha *HAProxyManager, err error) { t, err := template.New("haproxy").Parse(haproxyConfigTemplate) if err != nil { return } ha = &HAProxyManager{ configFile: configFile, svc: NewSystemdManager(service), template: t, } return } func (ha *HAProxyManager) checkConfig(ctx context.Context, lbs Config) (hash []byte, err error) { tmp, err := ioutil.TempFile(os.TempDir(), "haproxy_") if err != nil { return } defer tmp.Close() defer os.Remove(tmp.Name()) h := sha256.New() w := io.MultiWriter(tmp, h) err = ha.template.Execute(w, lbs) if err != nil { return } hash = h.Sum(nil) out, err := exec.CommandContext(ctx, "haproxy", "-c", "-f", tmp.Name()).CombinedOutput() if err != nil { err = fmt.Errorf("%s:%w", string(out), err) } return hash, err } func (ha *HAProxyManager) updateConfig(ctx context.Context, lbs Config) error { hash, err := ha.checkConfig(ctx, lbs) if err != nil { return err } if bytes.Equal(ha.hash, hash) { return nil } file, err := os.OpenFile(ha.configFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { return err } err = ha.template.Execute(file, lbs) if err != nil { file.Close() return err } err = file.Close() ha.hash = hash return err } func (ha *HAProxyManager) UpdateConfig(ctx context.Context, lbs Config) error { ha.Lock() defer ha.Unlock() err := ha.updateConfig(ctx, lbs) if err != nil { return err } return ha.svc.Reload(ctx) } func (ha *HAProxyManager) Start(ctx context.Context) error { err := ha.svc.Status(ctx) if err != nil { err = ha.updateConfig(ctx, Config{}) if err != nil { return err } err = ha.svc.Start(ctx) } return err } func (ha *HAProxyManager) Stop(ctx context.Context) error { return ha.svc.Stop(ctx) }