config_test.go 5.5 KB

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