Skip to content

Commit

Permalink
Copy config parsing from Consul Template
Browse files Browse the repository at this point in the history
Fixes GH-21
Fixes GH-32
Closes GH-42
  • Loading branch information
sethvargo committed May 8, 2016
1 parent 868d974 commit fbc467c
Show file tree
Hide file tree
Showing 8 changed files with 1,010 additions and 673 deletions.
313 changes: 218 additions & 95 deletions cli.go

Large diffs are not rendered by default.

389 changes: 199 additions & 190 deletions cli_test.go

Large diffs are not rendered by default.

432 changes: 319 additions & 113 deletions config.go

Large diffs are not rendered by default.

354 changes: 166 additions & 188 deletions config_test.go

Large diffs are not rendered by default.

77 changes: 28 additions & 49 deletions flags.go
Original file line number Diff line number Diff line change
@@ -1,63 +1,42 @@
package main

import (
"fmt"
"strings"
"strconv"
"time"
)

// prefixVar implements the Flag.Value interface and allows the user
// to specify multiple -prefix keys in the CLI where each option is parsed
// as a dependency.
type prefixVar []*Prefix
// funcVar is a type of flag that accepts a function that is the string given
// by the user.
type funcVar func(s string) error

func (pv *prefixVar) Set(value string) error {
prefix, err := ParsePrefix(value)
if err != nil {
return err
}
func (f funcVar) Set(s string) error { return f(s) }
func (f funcVar) String() string { return "" }
func (f funcVar) IsBoolFlag() bool { return false }

if *pv == nil {
*pv = make([]*Prefix, 0, 1)
}
*pv = append(*pv, prefix)
// funcBoolVar is a type of flag that accepts a function, converts the user's
// value to a bool, and then calls the given function.
type funcBoolVar func(b bool) error

return nil
}

func (pv *prefixVar) String() string {
list := make([]string, 0, len(*pv))
for _, prefix := range *pv {
list = append(list, fmt.Sprintf("%s:%s", prefix.SourceRaw, prefix.Destination))
func (f funcBoolVar) Set(s string) error {
v, err := strconv.ParseBool(s)
if err != nil {
return err
}
return strings.Join(list, ", ")
return f(v)
}
func (f funcBoolVar) String() string { return "" }
func (f funcBoolVar) IsBoolFlag() bool { return true }

/// ------------------------- ///

// authVar implements the Flag.Value interface and allows the user to specify
// authentication in the username[:password] form.
type authVar Auth
// funcDurationVar is a type of flag that accepts a function, converts the
// user's value to a duration, and then calls the given function.
type funcDurationVar func(d time.Duration) error

// Set sets the value for this authentication.
func (a *authVar) Set(value string) error {
a.Enabled = true

if strings.Contains(value, ":") {
split := strings.SplitN(value, ":", 2)
a.Username = split[0]
a.Password = split[1]
} else {
a.Username = value
}

return nil
}

// String returns the string representation of this authentication.
func (a *authVar) String() string {
if a.Password == "" {
return a.Username
func (f funcDurationVar) Set(s string) error {
v, err := time.ParseDuration(s)
if err != nil {
return err
}

return fmt.Sprintf("%s:%s", a.Username, a.Password)
return f(v)
}
func (f funcDurationVar) String() string { return "" }
func (f funcDurationVar) IsBoolFlag() bool { return false }
27 changes: 23 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
package main // import "github.com/hashicorp/consul-replicate"

import (
"bytes"
"fmt"
"os"
)

// Name is the exported name of this application.
const Name = "consul-replicate"
// The git commit that was compiled. This will be filled in by the compiler.
var GitCommit string

// Version is the current version of this application.
const Version = "0.2.0.dev"
const Name = "consul-replicate"
const Version = "0.2.0"
const VersionPrerelease = "dev"

func main() {
cli := NewCLI(os.Stdout, os.Stderr)
os.Exit(cli.Run(os.Args))
}

// formattedVersion returns a formatted version string which includes the git
// commit and development information.
func formattedVersion() string {
var versionString bytes.Buffer
fmt.Fprintf(&versionString, "%s v%s", Name, Version)

if VersionPrerelease != "" {
fmt.Fprintf(&versionString, "-%s", VersionPrerelease)

if GitCommit != "" {
fmt.Fprintf(&versionString, " (%s)", GitCommit)
}
}
return versionString.String()
}
57 changes: 57 additions & 0 deletions runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,12 @@ func NewRunner(config *Config, once bool) (*Runner, error) {
func (r *Runner) Start() {
log.Printf("[INFO] (runner) starting")

// Create the pid before doing anything.
if err := r.storePid(); err != nil {
r.ErrCh <- err
return
}

// Add the dependencies to the watcher
for _, prefix := range r.config.Prefixes {
r.watcher.Add(prefix.Source)
Expand Down Expand Up @@ -162,6 +168,10 @@ func (r *Runner) Start() {
func (r *Runner) Stop() {
log.Printf("[INFO] (runner) stopping")
r.watcher.Stop()
if err := r.deletePid(); err != nil {
log.Printf("[WARN] (runner) could not remove pid at %q: %s",
r.config.PidFile, err)
}
close(r.DoneCh)
}

Expand Down Expand Up @@ -403,6 +413,53 @@ func (r *Runner) statusPath(prefix *Prefix) string {
return filepath.Join(r.config.StatusDir, enc)
}

// storePid is used to write out a PID file to disk.
func (r *Runner) storePid() error {
path := r.config.PidFile
if path == "" {
return nil
}

log.Printf("[INFO] creating pid file at %q", path)

f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
if err != nil {
return fmt.Errorf("runner: could not open pid file: %s", err)
}
defer f.Close()

pid := os.Getpid()
_, err = f.WriteString(fmt.Sprintf("%d", pid))
if err != nil {
return fmt.Errorf("runner: could not write to pid file: %s", err)
}
return nil
}

// deletePid is used to remove the PID on exit.
func (r *Runner) deletePid() error {
path := r.config.PidFile
if path == "" {
return nil
}

log.Printf("[DEBUG] removing pid file at %q", path)

stat, err := os.Stat(path)
if err != nil {
return fmt.Errorf("runner: could not remove pid file: %s", err)
}
if stat.IsDir() {
return fmt.Errorf("runner: specified pid file path is directory")
}

err = os.Remove(path)
if err != nil {
return fmt.Errorf("runner: could not remove pid file: %s", err)
}
return nil
}

// newAPIClient creates a new API client from the given config and
func newAPIClient(config *Config) (*api.Client, error) {
log.Printf("[INFO] (runner) creating consul/api client")
Expand Down
34 changes: 0 additions & 34 deletions runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main
import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
Expand Down Expand Up @@ -65,19 +64,6 @@ func TestNewRunner_initialize(t *testing.T) {
}
}

func TestConfigDefaultOverrides(t *testing.T) {
expected := "test/statuses"

config := &Config{
StatusDir: expected,
}

r, _ := NewRunner(config, true)
if r.config.StatusDir != expected {
t.Errorf("expected StatusDir %q to be %q", r.config.StatusDir, expected)
}
}

func TestBuildConfig_singleFile(t *testing.T) {
configFile := test.CreateTempfile([]byte(`
consul = "127.0.0.1"
Expand Down Expand Up @@ -136,23 +122,3 @@ func TestBuildConfig_EmptyDirectory(t *testing.T) {
t.Fatalf("expected %q to contain %q", err.Error(), expected)
}
}

func TestBuildConfig_BadConfigs(t *testing.T) {
configFile := test.CreateTempfile([]byte(`
totally not a vaild config
`), t)
defer test.DeleteTempfile(configFile, t)

configDir := filepath.Dir(configFile.Name())

config := new(Config)
err := buildConfig(config, configDir)
if err == nil {
t.Fatalf("expected error, but nothing was returned")
}

expected := "1 error(s) occurred"
if !strings.Contains(err.Error(), expected) {
t.Fatalf("expected %q to contain %q", err.Error(), expected)
}
}

0 comments on commit fbc467c

Please sign in to comment.