1220 lines
27 KiB
Go
1220 lines
27 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
dep "github.com/hashicorp/consul-template/dependency"
|
|
"github.com/hashicorp/consul-template/test"
|
|
"github.com/hashicorp/consul/testutil"
|
|
"github.com/hashicorp/go-gatedio"
|
|
)
|
|
|
|
func TestNewRunner_initialize(t *testing.T) {
|
|
in1 := test.CreateTempfile(nil, t)
|
|
defer test.DeleteTempfile(in1, t)
|
|
|
|
in2 := test.CreateTempfile(nil, t)
|
|
defer test.DeleteTempfile(in2, t)
|
|
|
|
in3 := test.CreateTempfile(nil, t)
|
|
defer test.DeleteTempfile(in3, t)
|
|
|
|
dry, once := true, true
|
|
config := DefaultConfig()
|
|
config.Merge(&Config{
|
|
ConfigTemplates: []*ConfigTemplate{
|
|
&ConfigTemplate{Source: in1.Name(), Command: "1"},
|
|
&ConfigTemplate{Source: in1.Name(), Command: "1.1"},
|
|
&ConfigTemplate{Source: in2.Name(), Command: "2"},
|
|
&ConfigTemplate{Source: in3.Name(), Command: "3"},
|
|
},
|
|
})
|
|
|
|
runner, err := NewRunner(config, dry, once, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if runner.dry != dry {
|
|
t.Errorf("expected %#v to be %#v", runner.dry, dry)
|
|
}
|
|
|
|
if runner.once != once {
|
|
t.Errorf("expected %#v to be %#v", runner.once, once)
|
|
}
|
|
|
|
if runner.watcher == nil {
|
|
t.Errorf("expected %#v to be %#v", runner.watcher, nil)
|
|
}
|
|
|
|
if num := len(runner.templates); num != 3 {
|
|
t.Errorf("expected %d to be %d", len(runner.templates), 3)
|
|
}
|
|
|
|
if runner.renderedTemplates == nil {
|
|
t.Errorf("expected %#v to be %#v", runner.renderedTemplates, nil)
|
|
}
|
|
|
|
if num := len(runner.ctemplatesMap); num != 3 {
|
|
t.Errorf("expected %d to be %d", len(runner.ctemplatesMap), 3)
|
|
}
|
|
|
|
ctemplates := runner.ctemplatesMap[in1.Name()]
|
|
if num := len(ctemplates); num != 2 {
|
|
t.Errorf("expected %d to be %d", len(ctemplates), 2)
|
|
}
|
|
|
|
if runner.outStream != os.Stdout {
|
|
t.Errorf("expected %#v to be %#v", runner.outStream, os.Stdout)
|
|
}
|
|
|
|
if runner.errStream != os.Stderr {
|
|
t.Errorf("expected %#v to be %#v", runner.errStream, os.Stderr)
|
|
}
|
|
|
|
brain := NewBrain()
|
|
if !reflect.DeepEqual(runner.brain, brain) {
|
|
t.Errorf("expected %#v to be %#v", runner.brain, brain)
|
|
}
|
|
|
|
if runner.ErrCh == nil {
|
|
t.Errorf("expected %#v to be %#v", runner.ErrCh, nil)
|
|
}
|
|
|
|
if runner.DoneCh == nil {
|
|
t.Errorf("expected %#v to be %#v", runner.DoneCh, nil)
|
|
}
|
|
}
|
|
|
|
func TestNewRunner_badTemplate(t *testing.T) {
|
|
config := DefaultConfig()
|
|
config.Merge(&Config{
|
|
ConfigTemplates: []*ConfigTemplate{
|
|
&ConfigTemplate{Source: "/not/a/real/path"},
|
|
},
|
|
})
|
|
|
|
if _, err := NewRunner(config, false, false, &sync.RWMutex{}); err == nil {
|
|
t.Fatal("expected error, but nothing was returned")
|
|
}
|
|
}
|
|
|
|
func TestReceive_addsToBrain(t *testing.T) {
|
|
runner, err := NewRunner(DefaultConfig(), false, false, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
d, err := dep.ParseStoreKey("foo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data := "some value"
|
|
runner.dependencies[d.HashCode()] = d
|
|
runner.Receive(d, data)
|
|
|
|
value, ok := runner.brain.Recall(d)
|
|
if !ok {
|
|
t.Fatalf("expected brain to have data")
|
|
}
|
|
if data != value {
|
|
t.Errorf("expected %q to be %q", data, value)
|
|
}
|
|
}
|
|
|
|
func TestReceive_storesBrain(t *testing.T) {
|
|
runner, err := NewRunner(DefaultConfig(), false, false, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
d, data := &dep.File{}, "this is some data"
|
|
runner.dependencies[d.HashCode()] = d
|
|
runner.Receive(d, data)
|
|
|
|
if _, ok := runner.brain.Recall(d); !ok {
|
|
t.Errorf("expected brain to have data")
|
|
}
|
|
}
|
|
|
|
func TestReceive_doesNotStoreIfNotWatching(t *testing.T) {
|
|
runner, err := NewRunner(DefaultConfig(), false, false, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
d, data := &dep.File{}, "this is some data"
|
|
runner.Receive(d, data)
|
|
|
|
if _, ok := runner.brain.Recall(d); ok {
|
|
t.Errorf("expected brain to not have data")
|
|
}
|
|
}
|
|
|
|
func TestRun_noopIfMissingData(t *testing.T) {
|
|
in := test.CreateTempfile([]byte(`
|
|
{{ range service "consul@nyc1" }}{{ end }}
|
|
`), t)
|
|
defer test.DeleteTempfile(in, t)
|
|
|
|
config := DefaultConfig()
|
|
config.Merge(&Config{
|
|
ConfigTemplates: []*ConfigTemplate{
|
|
&ConfigTemplate{Source: in.Name()},
|
|
},
|
|
})
|
|
|
|
runner, err := NewRunner(config, false, false, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
buff := gatedio.NewByteBuffer()
|
|
runner.outStream, runner.errStream = buff, buff
|
|
|
|
if err := runner.Run(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if num := len(buff.Bytes()); num != 0 {
|
|
t.Errorf("expected %d to be %d", num, 0)
|
|
}
|
|
}
|
|
|
|
func TestRun_dry(t *testing.T) {
|
|
in := test.CreateTempfile([]byte(`
|
|
{{ range service "consul@nyc1" }}{{.Node}}{{ end }}
|
|
`), t)
|
|
defer test.DeleteTempfile(in, t)
|
|
|
|
config := DefaultConfig()
|
|
config.Merge(&Config{
|
|
ConfigTemplates: []*ConfigTemplate{
|
|
&ConfigTemplate{
|
|
Source: in.Name(),
|
|
Destination: "/out/file.txt",
|
|
},
|
|
},
|
|
})
|
|
|
|
runner, err := NewRunner(config, true, false, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
d, err := dep.ParseHealthServices("consul@nyc1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data := []*dep.HealthService{
|
|
&dep.HealthService{Node: "consul1"},
|
|
&dep.HealthService{Node: "consul2"},
|
|
}
|
|
runner.dependencies[d.HashCode()] = d
|
|
runner.watcher.ForceWatching(d, true)
|
|
runner.Receive(d, data)
|
|
|
|
buff := gatedio.NewByteBuffer()
|
|
runner.outStream, runner.errStream = buff, buff
|
|
|
|
if err := runner.Run(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
actual := bytes.TrimSpace(buff.Bytes())
|
|
expected := bytes.TrimSpace([]byte(`
|
|
> /out/file.txt
|
|
|
|
consul1consul2
|
|
`))
|
|
if !bytes.Equal(actual, expected) {
|
|
t.Errorf("expected \n%q\n to equal \n%q\n", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestRun_singlePass(t *testing.T) {
|
|
in := test.CreateTempfile([]byte(`
|
|
{{ range service "consul@nyc1"}}{{ end }}
|
|
{{ range service "consul@nyc2"}}{{ end }}
|
|
{{ range service "consul@nyc3"}}{{ end }}
|
|
`), t)
|
|
defer test.DeleteTempfile(in, t)
|
|
|
|
config := DefaultConfig()
|
|
config.Merge(&Config{
|
|
ConfigTemplates: []*ConfigTemplate{
|
|
&ConfigTemplate{Source: in.Name()},
|
|
},
|
|
})
|
|
|
|
runner, err := NewRunner(config, true, false, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(runner.dependencies) != 0 {
|
|
t.Errorf("expected %d to be %d", len(runner.dependencies), 0)
|
|
}
|
|
|
|
if err := runner.Run(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(runner.dependencies) != 3 {
|
|
t.Errorf("expected %d to be %d", len(runner.dependencies), 3)
|
|
}
|
|
}
|
|
|
|
func TestRun_singlePassDuplicates(t *testing.T) {
|
|
in := test.CreateTempfile([]byte(`
|
|
{{ range service "consul@nyc1"}}{{ end }}
|
|
{{ range service "consul@nyc1"}}{{ end }}
|
|
{{ range service "consul@nyc1"}}{{ end }}
|
|
{{ range service "consul@nyc2"}}{{ end }}
|
|
{{ range service "consul@nyc2"}}{{ end }}
|
|
{{ range service "consul@nyc3"}}{{ end }}
|
|
{{ range service "consul@nyc3"}}{{ end }}
|
|
`), t)
|
|
defer test.DeleteTempfile(in, t)
|
|
|
|
config := DefaultConfig()
|
|
config.Merge(&Config{
|
|
ConfigTemplates: []*ConfigTemplate{
|
|
&ConfigTemplate{Source: in.Name()},
|
|
},
|
|
})
|
|
|
|
runner, err := NewRunner(config, true, false, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(runner.dependencies) != 0 {
|
|
t.Errorf("expected %d to be %d", len(runner.dependencies), 0)
|
|
}
|
|
|
|
if err := runner.Run(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(runner.dependencies) != 3 {
|
|
t.Errorf("expected %d to be %d", len(runner.dependencies), 3)
|
|
}
|
|
}
|
|
|
|
func TestRun_doublePass(t *testing.T) {
|
|
in := test.CreateTempfile([]byte(`
|
|
{{ range ls "services" }}
|
|
{{ range service .Key }}
|
|
{{.Node}} {{.Address}}:{{.Port}}
|
|
{{ end }}
|
|
{{ end }}
|
|
`), t)
|
|
defer test.DeleteTempfile(in, t)
|
|
|
|
config := DefaultConfig()
|
|
config.Merge(&Config{
|
|
ConfigTemplates: []*ConfigTemplate{
|
|
&ConfigTemplate{Source: in.Name()},
|
|
},
|
|
})
|
|
|
|
runner, err := NewRunner(config, true, false, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(runner.dependencies) != 0 {
|
|
t.Errorf("expected %d to be %d", len(runner.dependencies), 0)
|
|
}
|
|
|
|
if err := runner.Run(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(runner.dependencies) != 1 {
|
|
t.Errorf("expected %d to be %d", len(runner.dependencies), 1)
|
|
}
|
|
|
|
d, err := dep.ParseStoreKeyPrefix("services")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data := []*dep.KeyPair{
|
|
&dep.KeyPair{Key: "service1"},
|
|
&dep.KeyPair{Key: "service2"},
|
|
&dep.KeyPair{Key: "service3"},
|
|
}
|
|
runner.Receive(d, data)
|
|
|
|
if err := runner.Run(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(runner.dependencies) != 4 {
|
|
t.Errorf("expected %d to be %d", len(runner.dependencies), 4)
|
|
}
|
|
}
|
|
|
|
func TestRun_removesUnusedDependencies(t *testing.T) {
|
|
in := test.CreateTempfile([]byte(nil), t)
|
|
defer test.DeleteTempfile(in, t)
|
|
|
|
config := DefaultConfig()
|
|
config.Merge(&Config{
|
|
ConfigTemplates: []*ConfigTemplate{
|
|
&ConfigTemplate{Source: in.Name()},
|
|
},
|
|
})
|
|
|
|
runner, err := NewRunner(config, true, false, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
d, err := dep.ParseHealthServices("consul@nyc2")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
runner.dependencies = map[string]dep.Dependency{"consul@nyc2": d}
|
|
|
|
if err := runner.Run(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if len(runner.dependencies) != 0 {
|
|
t.Errorf("expected %d to be %d", len(runner.dependencies), 0)
|
|
}
|
|
|
|
if runner.watcher.Watching(d) {
|
|
t.Errorf("expected watcher to stop watching dependency")
|
|
}
|
|
|
|
if _, ok := runner.brain.Recall(d); ok {
|
|
t.Errorf("expected brain to forget dependency")
|
|
}
|
|
}
|
|
|
|
func TestRun_multipleTemplatesRunsCommands(t *testing.T) {
|
|
in1 := test.CreateTempfile([]byte(`
|
|
{{ range service "consul@nyc1" }}{{.Node}}{{ end }}
|
|
`), t)
|
|
defer test.DeleteTempfile(in1, t)
|
|
|
|
in2 := test.CreateTempfile([]byte(`
|
|
{{range service "consul@nyc2"}}{{.Node}}{{ end }}
|
|
`), t)
|
|
defer test.DeleteTempfile(in2, t)
|
|
|
|
out1 := test.CreateTempfile(nil, t)
|
|
test.DeleteTempfile(out1, t)
|
|
|
|
out2 := test.CreateTempfile(nil, t)
|
|
test.DeleteTempfile(out2, t)
|
|
|
|
touch1, err := ioutil.TempFile(os.TempDir(), "touch1-")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
os.Remove(touch1.Name())
|
|
defer os.Remove(touch1.Name())
|
|
|
|
touch2, err := ioutil.TempFile(os.TempDir(), "touch2-")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
os.Remove(touch2.Name())
|
|
defer os.Remove(touch2.Name())
|
|
|
|
config := DefaultConfig()
|
|
config.Merge(&Config{
|
|
ConfigTemplates: []*ConfigTemplate{
|
|
&ConfigTemplate{
|
|
Source: in1.Name(),
|
|
Destination: out1.Name(),
|
|
Command: fmt.Sprintf("touch %s", touch1.Name()),
|
|
},
|
|
&ConfigTemplate{
|
|
Source: in2.Name(),
|
|
Destination: out2.Name(),
|
|
Command: fmt.Sprintf("touch %s", touch2.Name()),
|
|
},
|
|
},
|
|
})
|
|
|
|
runner, err := NewRunner(config, false, false, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
d, err := dep.ParseHealthServices("consul@nyc1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data := []*dep.HealthService{
|
|
&dep.HealthService{Node: "consul1"},
|
|
&dep.HealthService{Node: "consul2"},
|
|
}
|
|
runner.dependencies[d.HashCode()] = d
|
|
runner.watcher.ForceWatching(d, true)
|
|
runner.Receive(d, data)
|
|
|
|
if err := runner.Run(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err := os.Stat(touch1.Name()); err != nil {
|
|
t.Errorf("expected first command to run, but did not: %s", err)
|
|
}
|
|
|
|
if _, err := os.Stat(touch2.Name()); err == nil {
|
|
t.Errorf("expected second command to not run, but touch exists")
|
|
}
|
|
}
|
|
|
|
// Warning: this is a super fragile and time-dependent test. If it's failing,
|
|
// check the demo Consul cluster and your own sanity before you assume your
|
|
// code broke something...
|
|
func TestRunner_quiescence(t *testing.T) {
|
|
consul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
|
|
c.Stdout = ioutil.Discard
|
|
c.Stderr = ioutil.Discard
|
|
})
|
|
defer consul.Stop()
|
|
|
|
in := test.CreateTempfile([]byte(`
|
|
{{ range service "consul" "any" }}{{.Node}}{{ end }}
|
|
`), t)
|
|
defer test.DeleteTempfile(in, t)
|
|
|
|
out := test.CreateTempfile(nil, t)
|
|
test.DeleteTempfile(out, t)
|
|
|
|
config := testConfig(fmt.Sprintf(`
|
|
consul = "%s"
|
|
wait = "50ms:200ms"
|
|
|
|
template {
|
|
source = "%s"
|
|
destination = "%s"
|
|
}
|
|
`, consul.HTTPAddr, in.Name(), out.Name()), t)
|
|
|
|
runner, err := NewRunner(config, false, false, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
go runner.Start()
|
|
defer runner.Stop()
|
|
|
|
min := time.After(10 * time.Millisecond)
|
|
max := time.After(500 * time.Millisecond)
|
|
for {
|
|
select {
|
|
case <-min:
|
|
if _, err = os.Stat(out.Name()); !os.IsNotExist(err) {
|
|
t.Errorf("expected quiescence timer to not fire for yet")
|
|
}
|
|
continue
|
|
case <-max:
|
|
if _, err = os.Stat(out.Name()); os.IsNotExist(err) {
|
|
t.Errorf("expected template to be rendered by now")
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRender_sameContentsDoesNotExecuteCommand(t *testing.T) {
|
|
outFile := test.CreateTempfile(nil, t)
|
|
os.Remove(outFile.Name())
|
|
defer os.Remove(outFile.Name())
|
|
|
|
inTemplate := test.CreateTempfile([]byte(`
|
|
{{ range service "consul@nyc1" }}{{.Node}}{{ end }}
|
|
`), t)
|
|
defer test.DeleteTempfile(inTemplate, t)
|
|
|
|
outTemplate := test.CreateTempfile([]byte(`
|
|
consul1consul2
|
|
`), t)
|
|
defer test.DeleteTempfile(outTemplate, t)
|
|
|
|
config := DefaultConfig()
|
|
config.Merge(&Config{
|
|
ConfigTemplates: []*ConfigTemplate{
|
|
&ConfigTemplate{
|
|
Source: inTemplate.Name(),
|
|
Destination: outTemplate.Name(),
|
|
Command: fmt.Sprintf("echo 'foo' > %s", outFile.Name()),
|
|
},
|
|
},
|
|
})
|
|
|
|
runner, err := NewRunner(config, false, false, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
d, err := dep.ParseHealthServices("consul@nyc1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data := []*dep.HealthService{
|
|
&dep.HealthService{Node: "consul1"},
|
|
&dep.HealthService{Node: "consul2"},
|
|
}
|
|
runner.Receive(d, data)
|
|
|
|
if err := runner.Run(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = os.Stat(outFile.Name())
|
|
if !os.IsNotExist(err) {
|
|
t.Fatalf("expected command to not be run")
|
|
}
|
|
}
|
|
|
|
func TestAtomicWrite_parentFolderMissing(t *testing.T) {
|
|
// Create a TempDir and a TempFile in that TempDir, then remove them to
|
|
// "simulate" a non-existent folder
|
|
outDir, err := ioutil.TempDir(os.TempDir(), "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(outDir)
|
|
outFile, err := ioutil.TempFile(outDir, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.RemoveAll(outDir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := atomicWrite(outFile.Name(), nil, 0644, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if _, err := os.Stat(outFile.Name()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestAtomicWrite_retainsPermissions(t *testing.T) {
|
|
outDir, err := ioutil.TempDir(os.TempDir(), "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(outDir)
|
|
outFile, err := ioutil.TempFile(outDir, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
os.Chmod(outFile.Name(), 0644)
|
|
|
|
if err := atomicWrite(outFile.Name(), nil, 0644, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
stat, err := os.Stat(outFile.Name())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
expected := os.FileMode(0644)
|
|
if stat.Mode() != expected {
|
|
t.Errorf("expected %q to be %q", stat.Mode(), expected)
|
|
}
|
|
}
|
|
|
|
func TestAtomicWrite_nonExistent(t *testing.T) {
|
|
// Create a temp dir
|
|
outDir, err := ioutil.TempDir(os.TempDir(), "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(outDir)
|
|
|
|
// Try atomicWrite to a file that doesn't exist yet
|
|
file := filepath.Join(outDir, "nope")
|
|
if err := atomicWrite(file, nil, 0644, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// File was created
|
|
if _, err := os.Stat(file); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestAtomicWrite_backup(t *testing.T) {
|
|
outDir, err := ioutil.TempDir(os.TempDir(), "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(outDir)
|
|
outFile, err := ioutil.TempFile(outDir, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Chmod(outFile.Name(), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := outFile.Write([]byte("before")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := atomicWrite(outFile.Name(), []byte("after"), 0644, true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
f, err := ioutil.ReadFile(outFile.Name() + ".bak")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !bytes.Equal(f, []byte("before")) {
|
|
t.Fatal("expected %q to be %q", f, []byte("before"))
|
|
}
|
|
|
|
if stat, err := os.Stat(outFile.Name() + ".bak"); err != nil {
|
|
t.Fatal(err)
|
|
} else {
|
|
if stat.Mode() != 0600 {
|
|
t.Fatal("expected %d to be %d", stat.Mode(), 0600)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAtomicWrite_backupNoExist(t *testing.T) {
|
|
outDir, err := ioutil.TempDir(os.TempDir(), "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer os.RemoveAll(outDir)
|
|
outFile, err := ioutil.TempFile(outDir, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Remove(outFile.Name()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := atomicWrite(outFile.Name(), nil, 0644, true); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Shouldn't have a backup file, since the original file didn't exist
|
|
if _, err := os.Stat(outFile.Name() + ".bak"); err == nil {
|
|
t.Fatal("expected error")
|
|
} else {
|
|
if !os.IsNotExist(err) {
|
|
t.Fatal("bad error: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRun_doesNotExecuteCommandMissingDependencies(t *testing.T) {
|
|
outFile := test.CreateTempfile(nil, t)
|
|
os.Remove(outFile.Name())
|
|
defer os.Remove(outFile.Name())
|
|
|
|
inTemplate := test.CreateTempfile([]byte(`
|
|
{{ range service "consul@nyc1"}}{{ end }}
|
|
`), t)
|
|
defer test.DeleteTempfile(inTemplate, t)
|
|
|
|
outTemplate := test.CreateTempfile(nil, t)
|
|
defer test.DeleteTempfile(outTemplate, t)
|
|
|
|
config := DefaultConfig()
|
|
config.Merge(&Config{
|
|
ConfigTemplates: []*ConfigTemplate{
|
|
&ConfigTemplate{
|
|
Source: inTemplate.Name(),
|
|
Destination: outTemplate.Name(),
|
|
Command: fmt.Sprintf("echo 'foo' > %s", outFile.Name()),
|
|
},
|
|
},
|
|
})
|
|
|
|
runner, err := NewRunner(config, false, false, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := runner.Run(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = os.Stat(outFile.Name())
|
|
if !os.IsNotExist(err) {
|
|
t.Fatalf("expected command to not be run")
|
|
}
|
|
}
|
|
|
|
func TestRun_executesCommand(t *testing.T) {
|
|
outFile := test.CreateTempfile(nil, t)
|
|
os.Remove(outFile.Name())
|
|
defer os.Remove(outFile.Name())
|
|
|
|
inTemplate := test.CreateTempfile([]byte(`
|
|
{{ range service "consul@nyc1"}}{{ end }}
|
|
`), t)
|
|
defer test.DeleteTempfile(inTemplate, t)
|
|
|
|
outTemplate := test.CreateTempfile(nil, t)
|
|
defer test.DeleteTempfile(outTemplate, t)
|
|
|
|
config := DefaultConfig()
|
|
config.Merge(&Config{
|
|
ConfigTemplates: []*ConfigTemplate{
|
|
&ConfigTemplate{
|
|
Source: inTemplate.Name(),
|
|
Destination: outTemplate.Name(),
|
|
Command: fmt.Sprintf("echo 'foo' > %s", outFile.Name()),
|
|
},
|
|
},
|
|
})
|
|
|
|
runner, err := NewRunner(config, false, false, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
d, err := dep.ParseHealthServices("consul@nyc1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data := []*dep.HealthService{
|
|
&dep.HealthService{
|
|
Node: "consul",
|
|
Address: "1.2.3.4",
|
|
ID: "consul@nyc1",
|
|
Name: "consul",
|
|
},
|
|
}
|
|
runner.dependencies[d.HashCode()] = d
|
|
runner.watcher.ForceWatching(d, true)
|
|
runner.Receive(d, data)
|
|
|
|
if err := runner.Run(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = os.Stat(outFile.Name())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestRun_doesNotExecuteCommandMoreThanOnce(t *testing.T) {
|
|
outFile := test.CreateTempfile(nil, t)
|
|
os.Remove(outFile.Name())
|
|
defer os.Remove(outFile.Name())
|
|
|
|
inTemplate := test.CreateTempfile([]byte(`
|
|
{{ range service "consul@nyc1"}}{{ end }}
|
|
`), t)
|
|
defer test.DeleteTempfile(inTemplate, t)
|
|
|
|
outTemplateA := test.CreateTempfile(nil, t)
|
|
defer test.DeleteTempfile(outTemplateA, t)
|
|
|
|
outTemplateB := test.CreateTempfile(nil, t)
|
|
defer test.DeleteTempfile(outTemplateB, t)
|
|
|
|
config := DefaultConfig()
|
|
config.Merge(&Config{
|
|
ConfigTemplates: []*ConfigTemplate{
|
|
&ConfigTemplate{
|
|
Source: inTemplate.Name(),
|
|
Destination: outTemplateA.Name(),
|
|
Command: fmt.Sprintf("echo 'foo' >> %s", outFile.Name()),
|
|
},
|
|
&ConfigTemplate{
|
|
Source: inTemplate.Name(),
|
|
Destination: outTemplateB.Name(),
|
|
Command: fmt.Sprintf("echo 'foo' >> %s", outFile.Name()),
|
|
},
|
|
},
|
|
})
|
|
|
|
runner, err := NewRunner(config, false, false, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
d, err := dep.ParseHealthServices("consul@nyc1")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data := []*dep.HealthService{
|
|
&dep.HealthService{
|
|
Node: "consul",
|
|
Address: "1.2.3.4",
|
|
ID: "consul@nyc1",
|
|
Name: "consul",
|
|
},
|
|
}
|
|
runner.dependencies[d.HashCode()] = d
|
|
runner.watcher.ForceWatching(d, true)
|
|
runner.Receive(d, data)
|
|
|
|
if err := runner.Run(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = os.Stat(outFile.Name())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
output, err := ioutil.ReadFile(outFile.Name())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if strings.Count(string(output), "foo") > 1 {
|
|
t.Fatalf("expected command to be run once.")
|
|
}
|
|
}
|
|
|
|
func TestRunner_pidCreate(t *testing.T) {
|
|
pidfile := test.CreateTempfile(nil, t)
|
|
os.Remove(pidfile.Name())
|
|
defer os.Remove(pidfile.Name())
|
|
|
|
config := testConfig(fmt.Sprintf(`
|
|
pid_file = "%s"
|
|
`, pidfile.Name()), t)
|
|
|
|
runner, err := NewRunner(config, false, false, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
go runner.Start()
|
|
defer runner.Stop()
|
|
|
|
select {
|
|
case err := <-runner.ErrCh:
|
|
t.Fatal(err)
|
|
case <-time.After(100 * time.Millisecond):
|
|
}
|
|
|
|
_, err = os.Stat(pidfile.Name())
|
|
if err != nil {
|
|
t.Fatal("expected pidfile to exist")
|
|
}
|
|
}
|
|
|
|
func TestRunner_pidDelete(t *testing.T) {
|
|
pidfile := test.CreateTempfile(nil, t)
|
|
os.Remove(pidfile.Name())
|
|
defer os.Remove(pidfile.Name())
|
|
|
|
config := testConfig(fmt.Sprintf(`
|
|
pid_file = "%s"
|
|
`, pidfile.Name()), t)
|
|
|
|
runner, err := NewRunner(config, false, false, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
go runner.Start()
|
|
|
|
select {
|
|
case err := <-runner.ErrCh:
|
|
t.Fatal(err)
|
|
case <-time.After(100 * time.Millisecond):
|
|
}
|
|
|
|
_, err = os.Stat(pidfile.Name())
|
|
if err != nil {
|
|
t.Fatal("expected pidfile to exist")
|
|
}
|
|
|
|
runner.Stop()
|
|
select {
|
|
case err := <-runner.ErrCh:
|
|
t.Fatal(err)
|
|
case <-runner.DoneCh:
|
|
}
|
|
|
|
_, err = os.Stat(pidfile.Name())
|
|
if err == nil {
|
|
t.Fatal("expected pidfile to be cleaned up")
|
|
}
|
|
if !os.IsNotExist(err) {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestRunner_onceAlreadyRenderedDoesNotHangOrRunCommands(t *testing.T) {
|
|
outFile := test.CreateTempfile(nil, t)
|
|
os.Remove(outFile.Name())
|
|
defer os.Remove(outFile.Name())
|
|
|
|
out := test.CreateTempfile([]byte("redis"), t)
|
|
defer os.Remove(out.Name())
|
|
|
|
in := test.CreateTempfile([]byte(`{{ key "service_name"}}`), t)
|
|
defer test.DeleteTempfile(in, t)
|
|
|
|
outTemplateA := test.CreateTempfile(nil, t)
|
|
defer test.DeleteTempfile(outTemplateA, t)
|
|
|
|
config := DefaultConfig()
|
|
config.Merge(&Config{
|
|
ConfigTemplates: []*ConfigTemplate{
|
|
&ConfigTemplate{
|
|
Source: in.Name(),
|
|
Destination: out.Name(),
|
|
Command: fmt.Sprintf("echo 'foo' >> %s", outFile.Name()),
|
|
},
|
|
},
|
|
})
|
|
|
|
runner, err := NewRunner(config, false, true, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
d, err := dep.ParseStoreKey("service_name")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data := "redis"
|
|
runner.dependencies[d.HashCode()] = d
|
|
runner.watcher.ForceWatching(d, true)
|
|
runner.Receive(d, data)
|
|
|
|
go runner.Start()
|
|
|
|
select {
|
|
case err := <-runner.ErrCh:
|
|
t.Fatal(err)
|
|
case <-runner.DoneCh:
|
|
case <-time.After(5 * time.Millisecond):
|
|
t.Fatal("runner should have stopped")
|
|
runner.Stop()
|
|
}
|
|
|
|
_, err = os.Stat(outFile.Name())
|
|
if err == nil {
|
|
t.Fatal("expected command to not be run")
|
|
}
|
|
if !os.IsNotExist(err) {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestExecute_setsEnv(t *testing.T) {
|
|
tmpfile := test.CreateTempfile(nil, t)
|
|
defer test.DeleteTempfile(tmpfile, t)
|
|
|
|
config := testConfig(`
|
|
consul = "1.2.3.4:5678"
|
|
token = "abcd1234"
|
|
|
|
vault {
|
|
address = "5.6.7.8:1234"
|
|
ssl {
|
|
verify = false
|
|
}
|
|
}
|
|
|
|
auth {
|
|
username = "username"
|
|
password = "password"
|
|
}
|
|
|
|
ssl {
|
|
enabled = true
|
|
verify = false
|
|
}
|
|
`, t)
|
|
|
|
runner, err := NewRunner(config, false, false, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
command := fmt.Sprintf("env > %s", tmpfile.Name())
|
|
if err := runner.execute(command, 1*time.Second); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bytes, err := ioutil.ReadFile(tmpfile.Name())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
contents := string(bytes)
|
|
|
|
if !strings.Contains(contents, "CONSUL_HTTP_ADDR=1.2.3.4:5678") {
|
|
t.Errorf("expected env to contain CONSUL_HTTP_ADDR")
|
|
}
|
|
|
|
if !strings.Contains(contents, "CONSUL_HTTP_TOKEN=abcd1234") {
|
|
t.Errorf("expected env to contain CONSUL_HTTP_TOKEN")
|
|
}
|
|
|
|
if !strings.Contains(contents, "CONSUL_HTTP_AUTH=username:password") {
|
|
t.Errorf("expected env to contain CONSUL_HTTP_AUTH")
|
|
}
|
|
|
|
if !strings.Contains(contents, "CONSUL_HTTP_SSL=true") {
|
|
t.Errorf("expected env to contain CONSUL_HTTP_SSL")
|
|
}
|
|
|
|
if !strings.Contains(contents, "CONSUL_HTTP_SSL_VERIFY=false") {
|
|
t.Errorf("expected env to contain CONSUL_HTTP_SSL_VERIFY")
|
|
}
|
|
|
|
if !strings.Contains(contents, "VAULT_ADDR=5.6.7.8:1234") {
|
|
t.Errorf("expected env to contain VAULT_ADDR")
|
|
}
|
|
|
|
if !strings.Contains(contents, "VAULT_SKIP_VERIFY=true") {
|
|
t.Errorf("expected env to contain VAULT_SKIP_VERIFY")
|
|
}
|
|
}
|
|
|
|
func TestExecute_timeout(t *testing.T) {
|
|
tmpfile := test.CreateTempfile(nil, t)
|
|
defer test.DeleteTempfile(tmpfile, t)
|
|
|
|
config := testConfig(`
|
|
consul = "1.2.3.4:5678"
|
|
token = "abcd1234"
|
|
|
|
auth {
|
|
username = "username"
|
|
password = "password"
|
|
}
|
|
|
|
ssl {
|
|
enabled = true
|
|
verify = false
|
|
}
|
|
`, t)
|
|
|
|
runner, err := NewRunner(config, false, false, &sync.RWMutex{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = runner.execute("sleep 10", 100*time.Millisecond)
|
|
if err == nil {
|
|
t.Fatal("expected error, but nothing was returned")
|
|
}
|
|
|
|
expected := "did not return for 100ms"
|
|
if !strings.Contains(err.Error(), expected) {
|
|
t.Errorf("expected %q to include %q", err.Error(), expected)
|
|
}
|
|
}
|
|
|
|
func TestRunner_dedup(t *testing.T) {
|
|
// Create a template
|
|
in := test.CreateTempfile([]byte(`
|
|
{{ range service "consul" }}{{.Node}}{{ end }}
|
|
`), t)
|
|
defer test.DeleteTempfile(in, t)
|
|
|
|
out1 := test.CreateTempfile(nil, t)
|
|
defer test.DeleteTempfile(out1, t)
|
|
|
|
out2 := test.CreateTempfile(nil, t)
|
|
defer test.DeleteTempfile(out2, t)
|
|
|
|
// Start consul
|
|
consul := testutil.NewTestServerConfig(t, func(c *testutil.TestServerConfig) {
|
|
c.Stdout = ioutil.Discard
|
|
c.Stderr = ioutil.Discard
|
|
})
|
|
defer consul.Stop()
|
|
|
|
// Setup the runner config
|
|
config := DefaultConfig()
|
|
config.Merge(&Config{
|
|
ConfigTemplates: []*ConfigTemplate{
|
|
&ConfigTemplate{Source: in.Name(), Destination: out1.Name()},
|
|
},
|
|
})
|
|
config.Deduplicate.Enabled = true
|
|
config.Consul = consul.HTTPAddr
|
|
config.set("consul")
|
|
config.set("deduplicate")
|
|
config.set("deduplicate.enabled")
|
|
|
|
config2 := DefaultConfig()
|
|
config2.Merge(&Config{
|
|
ConfigTemplates: []*ConfigTemplate{
|
|
&ConfigTemplate{Source: in.Name(), Destination: out2.Name()},
|
|
},
|
|
})
|
|
config2.Deduplicate.Enabled = true
|
|
config2.Consul = consul.HTTPAddr
|
|
config2.set("consul")
|
|
config2.set("deduplicate")
|
|
config2.set("deduplicate.enabled")
|
|
|
|
// Create the runners
|
|
var reapLock sync.RWMutex
|
|
r1, err := NewRunner(config, false, false, &reapLock)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
go r1.Start()
|
|
defer r1.Stop()
|
|
|
|
r2, err := NewRunner(config2, false, false, &reapLock)
|
|
if err != nil {
|
|
t.Fatalf("err: %v", err)
|
|
}
|
|
go r2.Start()
|
|
defer r2.Stop()
|
|
|
|
// Wait until the output file exists
|
|
testutil.WaitForResult(func() (bool, error) {
|
|
_, err := os.Stat(out1.Name())
|
|
if err != nil {
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
}, func(err error) {
|
|
t.Fatalf("err: %v", err)
|
|
})
|
|
|
|
// Wait until the output file exists
|
|
testutil.WaitForResult(func() (bool, error) {
|
|
_, err := os.Stat(out2.Name())
|
|
if err != nil {
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
}, func(err error) {
|
|
t.Fatalf("err: %v", err)
|
|
})
|
|
|
|
// Should only be a single total watcher
|
|
total := r1.watcher.Size() + r2.watcher.Size()
|
|
if total > 1 {
|
|
t.Fatalf("too many watchers: %d", total)
|
|
}
|
|
}
|