config_test.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. package config
  2. import (
  3. "io/ioutil"
  4. "os"
  5. "path/filepath"
  6. "testing"
  7. "time"
  8. "github.com/imdario/mergo"
  9. "github.com/slackhq/nebula/test"
  10. "github.com/stretchr/testify/assert"
  11. "github.com/stretchr/testify/require"
  12. "gopkg.in/yaml.v2"
  13. )
  14. func TestConfig_Load(t *testing.T) {
  15. l := test.NewLogger()
  16. dir, err := ioutil.TempDir("", "config-test")
  17. // invalid yaml
  18. c := NewC(l)
  19. ioutil.WriteFile(filepath.Join(dir, "01.yaml"), []byte(" invalid yaml"), 0644)
  20. assert.EqualError(t, c.Load(dir), "yaml: unmarshal errors:\n line 1: cannot unmarshal !!str `invalid...` into map[interface {}]interface {}")
  21. // simple multi config merge
  22. c = NewC(l)
  23. os.RemoveAll(dir)
  24. os.Mkdir(dir, 0755)
  25. assert.Nil(t, err)
  26. ioutil.WriteFile(filepath.Join(dir, "01.yaml"), []byte("outer:\n inner: hi"), 0644)
  27. ioutil.WriteFile(filepath.Join(dir, "02.yml"), []byte("outer:\n inner: override\nnew: hi"), 0644)
  28. assert.Nil(t, c.Load(dir))
  29. expected := map[interface{}]interface{}{
  30. "outer": map[interface{}]interface{}{
  31. "inner": "override",
  32. },
  33. "new": "hi",
  34. }
  35. assert.Equal(t, expected, c.Settings)
  36. //TODO: test symlinked file
  37. //TODO: test symlinked directory
  38. }
  39. func TestConfig_Get(t *testing.T) {
  40. l := test.NewLogger()
  41. // test simple type
  42. c := NewC(l)
  43. c.Settings["firewall"] = map[interface{}]interface{}{"outbound": "hi"}
  44. assert.Equal(t, "hi", c.Get("firewall.outbound"))
  45. // test complex type
  46. inner := []map[interface{}]interface{}{{"port": "1", "code": "2"}}
  47. c.Settings["firewall"] = map[interface{}]interface{}{"outbound": inner}
  48. assert.EqualValues(t, inner, c.Get("firewall.outbound"))
  49. // test missing
  50. assert.Nil(t, c.Get("firewall.nope"))
  51. }
  52. func TestConfig_GetStringSlice(t *testing.T) {
  53. l := test.NewLogger()
  54. c := NewC(l)
  55. c.Settings["slice"] = []interface{}{"one", "two"}
  56. assert.Equal(t, []string{"one", "two"}, c.GetStringSlice("slice", []string{}))
  57. }
  58. func TestConfig_GetBool(t *testing.T) {
  59. l := test.NewLogger()
  60. c := NewC(l)
  61. c.Settings["bool"] = true
  62. assert.Equal(t, true, c.GetBool("bool", false))
  63. c.Settings["bool"] = "true"
  64. assert.Equal(t, true, c.GetBool("bool", false))
  65. c.Settings["bool"] = false
  66. assert.Equal(t, false, c.GetBool("bool", true))
  67. c.Settings["bool"] = "false"
  68. assert.Equal(t, false, c.GetBool("bool", true))
  69. c.Settings["bool"] = "Y"
  70. assert.Equal(t, true, c.GetBool("bool", false))
  71. c.Settings["bool"] = "yEs"
  72. assert.Equal(t, true, c.GetBool("bool", false))
  73. c.Settings["bool"] = "N"
  74. assert.Equal(t, false, c.GetBool("bool", true))
  75. c.Settings["bool"] = "nO"
  76. assert.Equal(t, false, c.GetBool("bool", true))
  77. }
  78. func TestConfig_HasChanged(t *testing.T) {
  79. l := test.NewLogger()
  80. // No reload has occurred, return false
  81. c := NewC(l)
  82. c.Settings["test"] = "hi"
  83. assert.False(t, c.HasChanged(""))
  84. // Test key change
  85. c = NewC(l)
  86. c.Settings["test"] = "hi"
  87. c.oldSettings = map[interface{}]interface{}{"test": "no"}
  88. assert.True(t, c.HasChanged("test"))
  89. assert.True(t, c.HasChanged(""))
  90. // No key change
  91. c = NewC(l)
  92. c.Settings["test"] = "hi"
  93. c.oldSettings = map[interface{}]interface{}{"test": "hi"}
  94. assert.False(t, c.HasChanged("test"))
  95. assert.False(t, c.HasChanged(""))
  96. }
  97. func TestConfig_ReloadConfig(t *testing.T) {
  98. l := test.NewLogger()
  99. done := make(chan bool, 1)
  100. dir, err := ioutil.TempDir("", "config-test")
  101. assert.Nil(t, err)
  102. ioutil.WriteFile(filepath.Join(dir, "01.yaml"), []byte("outer:\n inner: hi"), 0644)
  103. c := NewC(l)
  104. assert.Nil(t, c.Load(dir))
  105. assert.False(t, c.HasChanged("outer.inner"))
  106. assert.False(t, c.HasChanged("outer"))
  107. assert.False(t, c.HasChanged(""))
  108. ioutil.WriteFile(filepath.Join(dir, "01.yaml"), []byte("outer:\n inner: ho"), 0644)
  109. c.RegisterReloadCallback(func(c *C) {
  110. done <- true
  111. })
  112. c.ReloadConfig()
  113. assert.True(t, c.HasChanged("outer.inner"))
  114. assert.True(t, c.HasChanged("outer"))
  115. assert.True(t, c.HasChanged(""))
  116. // Make sure we call the callbacks
  117. select {
  118. case <-done:
  119. case <-time.After(1 * time.Second):
  120. panic("timeout")
  121. }
  122. }
  123. // Ensure mergo merges are done the way we expect.
  124. // This is needed to test for potential regressions, like:
  125. // - https://github.com/imdario/mergo/issues/187
  126. func TestConfig_MergoMerge(t *testing.T) {
  127. configs := [][]byte{
  128. []byte(`
  129. listen:
  130. port: 1234
  131. `),
  132. []byte(`
  133. firewall:
  134. inbound:
  135. - port: 443
  136. proto: tcp
  137. groups:
  138. - server
  139. - port: 443
  140. proto: tcp
  141. groups:
  142. - webapp
  143. `),
  144. []byte(`
  145. listen:
  146. host: 0.0.0.0
  147. port: 4242
  148. firewall:
  149. outbound:
  150. - port: any
  151. proto: any
  152. host: any
  153. inbound:
  154. - port: any
  155. proto: icmp
  156. host: any
  157. `),
  158. }
  159. var m map[any]any
  160. // merge the same way config.parse() merges
  161. for _, b := range configs {
  162. var nm map[any]any
  163. err := yaml.Unmarshal(b, &nm)
  164. require.NoError(t, err)
  165. // We need to use WithAppendSlice so that firewall rules in separate
  166. // files are appended together
  167. err = mergo.Merge(&nm, m, mergo.WithAppendSlice)
  168. m = nm
  169. require.NoError(t, err)
  170. }
  171. t.Logf("Merged Config: %#v", m)
  172. mYaml, err := yaml.Marshal(m)
  173. require.NoError(t, err)
  174. t.Logf("Merged Config as YAML:\n%s", mYaml)
  175. // If a bug is present, some items might be replaced instead of merged like we expect
  176. expected := map[any]any{
  177. "firewall": map[any]any{
  178. "inbound": []any{
  179. map[any]any{"host": "any", "port": "any", "proto": "icmp"},
  180. map[any]any{"groups": []any{"server"}, "port": 443, "proto": "tcp"},
  181. map[any]any{"groups": []any{"webapp"}, "port": 443, "proto": "tcp"}},
  182. "outbound": []any{
  183. map[any]any{"host": "any", "port": "any", "proto": "any"}}},
  184. "listen": map[any]any{
  185. "host": "0.0.0.0",
  186. "port": 4242,
  187. },
  188. }
  189. assert.Equal(t, expected, m)
  190. }