func TestKeyFunc_hasData(t *testing.T) { d, err := dep.ParseStoreKey("existing") if err != nil { t.Fatal(err) } brain := NewBrain() brain.Remember(d, "contents") used := make(map[string]dep.Dependency) missing := make(map[string]dep.Dependency) f := keyFunc(brain, used, missing) result, err := f("existing") if err != nil { t.Fatal(err) } if result != "contents" { t.Errorf("expected %q to be %q", result, "contents") } if len(missing) != 0 { t.Errorf("expected missing to have 0 elements, but had %d", len(missing)) } if _, ok := used[d.HashCode()]; !ok { t.Errorf("expected dep to be used") } }
func TestKeyFunc_missingData(t *testing.T) { d, err := dep.ParseStoreKey("non-existing") if err != nil { t.Fatal(err) } brain := NewBrain() used := make(map[string]dep.Dependency) missing := make(map[string]dep.Dependency) f := keyFunc(brain, used, missing) result, err := f("non-existing") if err != nil { t.Fatal(err) } if result != "" { t.Errorf("expected %q to be %q", result, "") } if _, ok := used[d.HashCode()]; !ok { t.Errorf("expected dep to be used") } if _, ok := missing[d.HashCode()]; !ok { t.Errorf("expected dep to be missing") } }
// keyWithDefaultFunc returns or accumulates key dependencies that have a // default value. func keyWithDefaultFunc(brain *Brain, used, missing map[string]dep.Dependency) func(string, string) (string, error) { return func(s, def string) (string, error) { if len(s) == 0 { return def, nil } d, err := dep.ParseStoreKey(s) if err != nil { return "", err } d.SetDefault(def) addDependency(used, d) if value, ok := brain.Recall(d); ok { if value == nil { return def, nil } return value.(string), nil } addDependency(missing, d) return def, nil } }
// keyFunc returns or accumulates key dependencies. func keyFunc(brain *Brain, used, missing map[string]dep.Dependency) func(string) (string, error) { return func(s string) (string, error) { if len(s) == 0 { return "", nil } d, err := dep.ParseStoreKey(s) if err != nil { return "", err } addDependency(used, d) if value, ok := brain.Recall(d); ok { if value == nil { return "", nil } else { return value.(string), nil } } addDependency(missing, d) return "", nil } }
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()), Wait: &watch.Wait{}, }, }, }) runner, err := NewRunner(config, false, true) 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_missingDependencies(t *testing.T) { contents := []byte(`{{key "foo"}}`) in := test.CreateTempfile(contents, t) defer test.DeleteTempfile(in, t) tmpl, err := NewTemplate(in.Name()) if err != nil { t.Fatal(err) } brain := NewBrain() used, missing, result, err := tmpl.Execute(brain) if err != nil { t.Fatal(err) } if num := len(used); num != 1 { t.Fatalf("expected 1 used, got: %d", num) } if num := len(missing); num != 1 { t.Fatalf("expected 1 missing, got: %d", num) } expected, err := dep.ParseStoreKey("foo") if err != nil { t.Fatal(err) } if !reflect.DeepEqual(missing[0], expected) { t.Errorf("expected %q to be %q", missing[0], expected) } if num := len(used); num != 1 { t.Fatalf("expected 1 used, got %d", num) } if !reflect.DeepEqual(used[0], expected) { t.Errorf("expected %q to be %q", used[0], expected) } expectedResult := []byte("") if !bytes.Equal(result, expectedResult) { t.Errorf("expected %q to be %q", result, expectedResult) } }
func TestReceive_addsToBrain(t *testing.T) { runner, err := NewRunner(DefaultConfig(), false, false) 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) } }
// keyExistsFunc returns true if a key exists, false otherwise. func keyExistsFunc(brain *Brain, used, missing map[string]dep.Dependency) func(string) (bool, error) { return func(s string) (bool, error) { if len(s) == 0 { return false, nil } d, err := dep.ParseStoreKey(s) if err != nil { return false, err } d.SetExistenceCheck(true) addDependency(used, d) if value, ok := brain.Recall(d); ok { return value.(bool), nil } addDependency(missing, d) return false, nil } }
func TestExecute_renders(t *testing.T) { // Stub out the time. now = func() time.Time { return time.Unix(0, 0).UTC() } in := test.CreateTempfile([]byte(` API Functions ------------- datacenters:{{ range datacenters }} {{.}}{{ end }} file: {{ file "/path/to/file" }} key: {{ key "config/redis/maxconns" }} ls:{{ range ls "config/redis" }} {{.Key}}={{.Value}}{{ end }} node:{{ with node }} {{.Node.Node}}{{ range .Services}} {{.Service}}{{ end }}{{ end }} nodes:{{ range nodes }} {{.Node}}{{ end }} service:{{ range service "webapp" }} {{.Address}}{{ end }} service (any):{{ range service "webapp" "any" }} {{.Address}}{{ end }} service (tag.Contains):{{ range service "webapp" }}{{ if .Tags.Contains "production" }} {{.Node}}{{ end }}{{ end }} services:{{ range services }} {{.Name}}{{ end }} tree:{{ range tree "config/redis" }} {{.Key}}={{.Value}}{{ end }} vault: {{ with vault "secret/foo/bar" }}{{.Data.zip}}{{ end }} Helper Functions ---------------- byKey:{{ range $key, $pairs := tree "config/redis" | byKey }} {{$key}}:{{ range $pairs }} {{.Key}}={{.Value}}{{ end }}{{ end }} byTag (health service):{{ range $tag, $services := service "webapp" | byTag }} {{$tag}}:{{ range $services }} {{.Address}}{{ end }}{{ end }} byTag (catalog services):{{ range $tag, $services := services | byTag }} {{$tag}}:{{ range $services }} {{.Name}}{{ end }}{{ end }} contains:{{ range service "webapp" }}{{ if .Tags | contains "production" }} {{.Node}}{{ end }}{{ end }} env: {{ env "foo" }} explode:{{ range $k, $v := tree "config/redis" | explode }} {{$k}}{{$v}}{{ end }} in:{{ range service "webapp" }}{{ if in .Tags "production" }} {{.Node}}{{ end }}{{ end }} loop:{{ range loop 3 }} test{{ end }} loop(i):{{ range $i := loop 5 8 }} test{{$i}}{{ end }} join: {{ "a,b,c" | split "," | join ";" }} parseBool: {{"true" | parseBool}} parseFloat: {{"1.2" | parseFloat}} parseInt: {{"-1" | parseInt}} parseJSON (string):{{ range $key, $value := "{\"foo\": \"bar\"}" | parseJSON }} {{$key}}={{$value}}{{ end }} parseJSON (file):{{ range $key, $value := file "/path/to/json/file" | parseJSON }} {{$key}}={{$value}}{{ end }} parseJSON (env):{{ range $key, $value := env "json" | parseJSON }} {{$key}}={{$value}}{{ end }} parseUint: {{"1" | parseUint}} plugin: {{ file "/path/to/json/file" | plugin "echo" }} timestamp: {{ timestamp }} timestamp (formatted): {{ timestamp "2006-01-02" }} regexMatch: {{ file "/path/to/file" | regexMatch ".*[cont][a-z]+" }} regexMatch: {{ file "/path/to/file" | regexMatch "v[0-9]*" }} regexReplaceAll: {{ file "/path/to/file" | regexReplaceAll "\\w" "x" }} replaceAll: {{ file "/path/to/file" | replaceAll "some" "this" }} split:{{ range "a,b,c" | split "," }} {{.}}{{end}} toLower: {{ file "/path/to/file" | toLower }} toJSON: {{ tree "config/redis" | explode | toJSON }} toJSONPretty: {{ tree "config/redis" | explode | toJSONPretty }} toTitle: {{ file "/path/to/file" | toTitle }} toUpper: {{ file "/path/to/file" | toUpper }} toYAML: {{ tree "config/redis" | explode | toYAML }} Math Functions -------------- add:{{ 2 | add 2 }} subtract:{{ 2 | subtract 2 }} multiply:{{ 2 | multiply 2 }} divide:{{ 2 | divide 2 }} `), t) defer test.DeleteTempfile(in, t) tmpl, err := NewTemplate(in.Name()) if err != nil { t.Fatal(err) } brain := NewBrain() var d dep.Dependency d, err = dep.ParseDatacenters() if err != nil { t.Fatal(err) } brain.Remember(d, []string{"dc1", "dc2"}) d, err = dep.ParseFile("/path/to/file") if err != nil { t.Fatal(err) } brain.Remember(d, "some content") d, err = dep.ParseStoreKey("config/redis/maxconns") if err != nil { t.Fatal(err) } brain.Remember(d, "5") d, err = dep.ParseStoreKeyPrefix("config/redis") if err != nil { t.Fatal(err) } brain.Remember(d, []*dep.KeyPair{ &dep.KeyPair{Key: "", Value: ""}, &dep.KeyPair{Key: "admin/port", Value: "1134"}, &dep.KeyPair{Key: "maxconns", Value: "5"}, &dep.KeyPair{Key: "minconns", Value: "2"}, }) d, err = dep.ParseCatalogNode() if err != nil { t.Fatal(err) } brain.Remember(d, &dep.NodeDetail{ Node: &dep.Node{Node: "node1"}, Services: dep.NodeServiceList([]*dep.NodeService{ &dep.NodeService{ Service: "service1", }, }), }) d, err = dep.ParseCatalogNodes("") if err != nil { t.Fatal(err) } brain.Remember(d, []*dep.Node{ &dep.Node{Node: "node1"}, &dep.Node{Node: "node2"}, }) d, err = dep.ParseHealthServices("webapp") if err != nil { t.Fatal(err) } brain.Remember(d, []*dep.HealthService{ &dep.HealthService{ Node: "node1", Address: "1.2.3.4", Tags: []string{"release"}, }, &dep.HealthService{ Node: "node2", Address: "5.6.7.8", Tags: []string{"release", "production"}, }, &dep.HealthService{ Node: "node3", Address: "9.10.11.12", Tags: []string{"production"}, }, }) d, err = dep.ParseHealthServices("webapp", "any") if err != nil { t.Fatal(err) } brain.Remember(d, []*dep.HealthService{ &dep.HealthService{Node: "node1", Address: "1.2.3.4"}, &dep.HealthService{Node: "node2", Address: "5.6.7.8"}, }) d, err = dep.ParseCatalogServices("") if err != nil { t.Fatal(err) } brain.Remember(d, []*dep.CatalogService{ &dep.CatalogService{ Name: "service1", Tags: []string{"production"}, }, &dep.CatalogService{ Name: "service2", Tags: []string{"release", "production"}, }, }) d, err = dep.ParseVaultSecret("secret/foo/bar") if err != nil { t.Fatal(err) } brain.Remember(d, &dep.Secret{ LeaseID: "abcd1234", LeaseDuration: 120, Renewable: true, Data: map[string]interface{}{"zip": "zap"}, }) if err := os.Setenv("foo", "bar"); err != nil { t.Fatal(err) } d, err = dep.ParseFile("/path/to/json/file") if err != nil { t.Fatal(err) } brain.Remember(d, `{"foo": "bar"}`) if err := os.Setenv("json", `{"foo": "bar"}`); err != nil { t.Fatal(err) } _, _, result, err := tmpl.Execute(brain) if err != nil { t.Fatal(err) } expected := []byte(` API Functions ------------- datacenters: dc1 dc2 file: some content key: 5 ls: maxconns=5 minconns=2 node: node1 service1 nodes: node1 node2 service: 1.2.3.4 5.6.7.8 9.10.11.12 service (any): 1.2.3.4 5.6.7.8 service (tag.Contains): node2 node3 services: service1 service2 tree: admin/port=1134 maxconns=5 minconns=2 vault: zap Helper Functions ---------------- byKey: admin: port=1134 byTag (health service): production: 5.6.7.8 9.10.11.12 release: 1.2.3.4 5.6.7.8 byTag (catalog services): production: service1 service2 release: service2 contains: node2 node3 env: bar explode: adminmap[port:1134] maxconns5 minconns2 in: node2 node3 loop: test test test loop(i): test5 test6 test7 join: a;b;c parseBool: true parseFloat: 1.2 parseInt: -1 parseJSON (string): foo=bar parseJSON (file): foo=bar parseJSON (env): foo=bar parseUint: 1 plugin: {"foo": "bar"} timestamp: 1970-01-01T00:00:00Z timestamp (formatted): 1970-01-01 regexMatch: true regexMatch: false regexReplaceAll: xxxx xxxxxxx replaceAll: this content split: a b c toLower: some content toJSON: {"admin":{"port":"1134"},"maxconns":"5","minconns":"2"} toJSONPretty: { "admin": { "port": "1134" }, "maxconns": "5", "minconns": "2" } toTitle: Some Content toUpper: SOME CONTENT toYAML: admin: port: "1134" maxconns: "5" minconns: "2" Math Functions -------------- add:4 subtract:0 multiply:4 divide:1 `) if !bytes.Equal(result, expected) { t.Errorf("expected %s to be %s", result, expected) } }