PageRenderTime 40ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/provider/provider_test.go

https://bitbucket.org/butbox/traefik
Go | 475 lines | 409 code | 61 blank | 5 comment | 15 complexity | 30fa7e5ed699a28348dcb65d7f4a7c6f MD5 | raw file
Possible License(s): WTFPL, 0BSD, MPL-2.0-no-copyleft-exception, CC-BY-3.0, LGPL-3.0, GPL-2.0, BSD-2-Clause, Apache-2.0, MPL-2.0, MIT, JSON, BSD-3-Clause
  1. package provider
  2. import (
  3. "io/ioutil"
  4. "os"
  5. "strings"
  6. "testing"
  7. "text/template"
  8. "github.com/containous/traefik/types"
  9. "github.com/stretchr/testify/assert"
  10. "github.com/stretchr/testify/require"
  11. )
  12. type myProvider struct {
  13. BaseProvider
  14. TLS *types.ClientTLS
  15. }
  16. func (p *myProvider) Foo() string {
  17. return "bar"
  18. }
  19. func TestConfigurationErrors(t *testing.T) {
  20. templateErrorFile, err := ioutil.TempFile("", "provider-configuration-error")
  21. require.NoError(t, err)
  22. defer os.RemoveAll(templateErrorFile.Name())
  23. data := []byte("Not a valid template {{ Bar }}")
  24. err = ioutil.WriteFile(templateErrorFile.Name(), data, 0700)
  25. require.NoError(t, err)
  26. templateInvalidTOMLFile, err := ioutil.TempFile("", "provider-configuration-error")
  27. require.NoError(t, err)
  28. defer os.RemoveAll(templateInvalidTOMLFile.Name())
  29. data = []byte(`Hello {{ .Name }}
  30. {{ Foo }}`)
  31. err = ioutil.WriteFile(templateInvalidTOMLFile.Name(), data, 0700)
  32. require.NoError(t, err)
  33. invalids := []struct {
  34. provider *myProvider
  35. defaultTemplate string
  36. expectedError string
  37. funcMap template.FuncMap
  38. templateObjects interface{}
  39. }{
  40. {
  41. provider: &myProvider{
  42. BaseProvider: BaseProvider{
  43. Filename: "/non/existent/template.tmpl",
  44. },
  45. },
  46. expectedError: "open /non/existent/template.tmpl: no such file or directory",
  47. },
  48. {
  49. provider: &myProvider{},
  50. defaultTemplate: "non/existent/template.tmpl",
  51. expectedError: "Asset non/existent/template.tmpl not found",
  52. },
  53. {
  54. provider: &myProvider{
  55. BaseProvider: BaseProvider{
  56. Filename: templateErrorFile.Name(),
  57. },
  58. },
  59. expectedError: `function "Bar" not defined`,
  60. },
  61. {
  62. provider: &myProvider{
  63. BaseProvider: BaseProvider{
  64. Filename: templateInvalidTOMLFile.Name(),
  65. },
  66. },
  67. expectedError: "Near line 1 (last key parsed 'Hello'): expected key separator '=', but got '<' instead",
  68. funcMap: template.FuncMap{
  69. "Foo": func() string {
  70. return "bar"
  71. },
  72. },
  73. templateObjects: struct{ Name string }{Name: "bar"},
  74. },
  75. }
  76. for _, invalid := range invalids {
  77. configuration, err := invalid.provider.GetConfiguration(invalid.defaultTemplate, invalid.funcMap, nil)
  78. if err == nil || !strings.Contains(err.Error(), invalid.expectedError) {
  79. t.Fatalf("should have generate an error with %q, got %v", invalid.expectedError, err)
  80. }
  81. assert.Nil(t, configuration)
  82. }
  83. }
  84. func TestGetConfiguration(t *testing.T) {
  85. templateFile, err := ioutil.TempFile("", "provider-configuration")
  86. require.NoError(t, err)
  87. defer os.RemoveAll(templateFile.Name())
  88. data := []byte(`[backends]
  89. [backends.backend1]
  90. [backends.backend1.circuitbreaker]
  91. expression = "NetworkErrorRatio() > 0.5"
  92. [backends.backend1.servers.server1]
  93. url = "http://172.17.0.2:80"
  94. weight = 10
  95. [backends.backend1.servers.server2]
  96. url = "http://172.17.0.3:80"
  97. weight = 1
  98. [frontends]
  99. [frontends.frontend1]
  100. backend = "backend1"
  101. passHostHeader = true
  102. [frontends.frontend11.routes.test_2]
  103. rule = "Path"
  104. value = "/test"`)
  105. err = ioutil.WriteFile(templateFile.Name(), data, 0700)
  106. require.NoError(t, err)
  107. provider := &myProvider{
  108. BaseProvider: BaseProvider{
  109. Filename: templateFile.Name(),
  110. },
  111. }
  112. configuration, err := provider.GetConfiguration(templateFile.Name(), nil, nil)
  113. require.NoError(t, err)
  114. assert.NotNil(t, configuration)
  115. }
  116. func TestGetConfigurationReturnsCorrectMaxConnConfiguration(t *testing.T) {
  117. templateFile, err := ioutil.TempFile("", "provider-configuration")
  118. require.NoError(t, err)
  119. defer os.RemoveAll(templateFile.Name())
  120. data := []byte(`[backends]
  121. [backends.backend1]
  122. [backends.backend1.maxconn]
  123. amount = 10
  124. extractorFunc = "request.host"`)
  125. err = ioutil.WriteFile(templateFile.Name(), data, 0700)
  126. require.NoError(t, err)
  127. provider := &myProvider{
  128. BaseProvider: BaseProvider{
  129. Filename: templateFile.Name(),
  130. },
  131. }
  132. configuration, err := provider.GetConfiguration(templateFile.Name(), nil, nil)
  133. require.NoError(t, err)
  134. require.NotNil(t, configuration)
  135. require.Contains(t, configuration.Backends, "backend1")
  136. assert.EqualValues(t, 10, configuration.Backends["backend1"].MaxConn.Amount)
  137. assert.Equal(t, "request.host", configuration.Backends["backend1"].MaxConn.ExtractorFunc)
  138. }
  139. func TestNilClientTLS(t *testing.T) {
  140. p := &myProvider{
  141. BaseProvider: BaseProvider{
  142. Filename: "",
  143. },
  144. }
  145. _, err := p.TLS.CreateTLSConfig()
  146. require.NoError(t, err, "CreateTLSConfig should assume that consumer does not want a TLS configuration if input is nil")
  147. }
  148. func TestInsecureSkipVerifyClientTLS(t *testing.T) {
  149. p := &myProvider{
  150. BaseProvider: BaseProvider{
  151. Filename: "",
  152. },
  153. TLS: &types.ClientTLS{
  154. InsecureSkipVerify: true,
  155. },
  156. }
  157. config, err := p.TLS.CreateTLSConfig()
  158. require.NoError(t, err, "CreateTLSConfig should assume that consumer does not want a TLS configuration if input is nil")
  159. assert.True(t, config.InsecureSkipVerify, "CreateTLSConfig should support setting only InsecureSkipVerify property")
  160. }
  161. func TestInsecureSkipVerifyFalseClientTLS(t *testing.T) {
  162. p := &myProvider{
  163. BaseProvider: BaseProvider{
  164. Filename: "",
  165. },
  166. TLS: &types.ClientTLS{
  167. InsecureSkipVerify: false,
  168. },
  169. }
  170. _, err := p.TLS.CreateTLSConfig()
  171. assert.Errorf(t, err, "CreateTLSConfig should error if consumer does not set a TLS cert or key configuration and not chooses InsecureSkipVerify to be true")
  172. }
  173. func TestMatchingConstraints(t *testing.T) {
  174. testCases := []struct {
  175. desc string
  176. constraints types.Constraints
  177. tags []string
  178. expected bool
  179. }{
  180. // simple test: must match
  181. {
  182. desc: "tag==us-east-1 with us-east-1",
  183. constraints: types.Constraints{
  184. {
  185. Key: "tag",
  186. MustMatch: true,
  187. Regex: "us-east-1",
  188. },
  189. },
  190. tags: []string{
  191. "us-east-1",
  192. },
  193. expected: true,
  194. },
  195. // simple test: must match but does not match
  196. {
  197. desc: "tag==us-east-1 with us-east-2",
  198. constraints: types.Constraints{
  199. {
  200. Key: "tag",
  201. MustMatch: true,
  202. Regex: "us-east-1",
  203. },
  204. },
  205. tags: []string{
  206. "us-east-2",
  207. },
  208. expected: false,
  209. },
  210. // simple test: must not match
  211. {
  212. desc: "tag!=us-east-1 with us-east-1",
  213. constraints: types.Constraints{
  214. {
  215. Key: "tag",
  216. MustMatch: false,
  217. Regex: "us-east-1",
  218. },
  219. },
  220. tags: []string{
  221. "us-east-1",
  222. },
  223. expected: false,
  224. },
  225. // complex test: globbing
  226. {
  227. desc: "tag!=us-east-* with us-east-1",
  228. constraints: types.Constraints{
  229. {
  230. Key: "tag",
  231. MustMatch: true,
  232. Regex: "us-east-*",
  233. },
  234. },
  235. tags: []string{
  236. "us-east-1",
  237. },
  238. expected: true,
  239. },
  240. // complex test: multiple constraints
  241. {
  242. desc: "tag==us-east-* & tag!=api with us-east-1 & api",
  243. constraints: types.Constraints{
  244. {
  245. Key: "tag",
  246. MustMatch: true,
  247. Regex: "us-east-*",
  248. },
  249. {
  250. Key: "tag",
  251. MustMatch: false,
  252. Regex: "api",
  253. },
  254. },
  255. tags: []string{
  256. "api",
  257. "us-east-1",
  258. },
  259. expected: false,
  260. },
  261. }
  262. for _, test := range testCases {
  263. p := myProvider{
  264. BaseProvider: BaseProvider{
  265. Constraints: test.constraints,
  266. },
  267. }
  268. actual, _ := p.MatchConstraints(test.tags)
  269. assert.Equal(t, test.expected, actual)
  270. }
  271. }
  272. func TestDefaultFuncMap(t *testing.T) {
  273. templateFile, err := ioutil.TempFile("", "provider-configuration")
  274. require.NoError(t, err)
  275. defer os.RemoveAll(templateFile.Name())
  276. data := []byte(`
  277. [backends]
  278. [backends.{{ "backend-1" | replace "-" "" }}]
  279. [backends.{{ "BACKEND1" | tolower }}.circuitbreaker]
  280. expression = "NetworkErrorRatio() > 0.5"
  281. [backends.servers.server1]
  282. url = "http://172.17.0.2:80"
  283. weight = 10
  284. [backends.backend1.servers.server2]
  285. url = "http://172.17.0.3:80"
  286. weight = 1
  287. [frontends]
  288. [frontends.{{normalize "frontend/1"}}]
  289. {{ $backend := "backend1/test/value" | split "/" }}
  290. {{ $backendid := index $backend 1 }}
  291. {{ if "backend1" | contains "backend" }}
  292. backend = "backend1"
  293. {{end}}
  294. passHostHeader = true
  295. [frontends.frontend-1.routes.test_2]
  296. rule = "Path"
  297. value = "/test"`)
  298. err = ioutil.WriteFile(templateFile.Name(), data, 0700)
  299. require.NoError(t, err)
  300. provider := &myProvider{
  301. BaseProvider: BaseProvider{
  302. Filename: templateFile.Name(),
  303. },
  304. }
  305. configuration, err := provider.GetConfiguration(templateFile.Name(), nil, nil)
  306. require.NoError(t, err)
  307. require.NotNil(t, configuration)
  308. assert.Contains(t, configuration.Backends, "backend1")
  309. assert.Contains(t, configuration.Frontends, "frontend-1")
  310. }
  311. func TestSprigFunctions(t *testing.T) {
  312. templateFile, err := ioutil.TempFile("", "provider-configuration")
  313. require.NoError(t, err)
  314. defer os.RemoveAll(templateFile.Name())
  315. data := []byte(`
  316. {{$backend_name := trimAll "-" uuidv4}}
  317. [backends]
  318. [backends.{{$backend_name}}]
  319. [backends.{{$backend_name}}.circuitbreaker]
  320. [backends.{{$backend_name}}.servers.server2]
  321. url = "http://172.17.0.3:80"
  322. weight = 1
  323. [frontends]
  324. [frontends.{{normalize "frontend/1"}}]
  325. backend = "{{$backend_name}}"
  326. passHostHeader = true
  327. [frontends.frontend-1.routes.test_2]
  328. rule = "Path"
  329. value = "/test"`)
  330. err = ioutil.WriteFile(templateFile.Name(), data, 0700)
  331. require.NoError(t, err)
  332. provider := &myProvider{
  333. BaseProvider: BaseProvider{
  334. Filename: templateFile.Name(),
  335. },
  336. }
  337. configuration, err := provider.GetConfiguration(templateFile.Name(), nil, nil)
  338. require.NoError(t, err)
  339. require.NotNil(t, configuration)
  340. assert.Len(t, configuration.Backends, 1)
  341. assert.Contains(t, configuration.Frontends, "frontend-1")
  342. }
  343. func TestBaseProvider_GetConfiguration(t *testing.T) {
  344. baseProvider := BaseProvider{}
  345. testCases := []struct {
  346. name string
  347. defaultTemplateFile string
  348. expectedContent string
  349. }{
  350. {
  351. defaultTemplateFile: "templates/docker.tmpl",
  352. expectedContent: readTemplateFile(t, "./../templates/docker.tmpl"),
  353. },
  354. {
  355. defaultTemplateFile: `template content`,
  356. expectedContent: `template content`,
  357. },
  358. }
  359. for _, test := range testCases {
  360. test := test
  361. t.Run(test.name, func(t *testing.T) {
  362. content, err := baseProvider.getTemplateContent(test.defaultTemplateFile)
  363. require.NoError(t, err)
  364. assert.Equal(t, test.expectedContent, content)
  365. })
  366. }
  367. }
  368. func TestNormalize(t *testing.T) {
  369. testCases := []struct {
  370. desc string
  371. name string
  372. expected string
  373. }{
  374. {
  375. desc: "without special chars",
  376. name: "foobar",
  377. expected: "foobar",
  378. },
  379. {
  380. desc: "with special chars",
  381. name: "foo.foo.foo;foo:foo!foo/foo\\foo)foo_123-ç_àéè",
  382. expected: "foo-foo-foo-foo-foo-foo-foo-foo-foo-123-ç-àéè",
  383. },
  384. {
  385. desc: "starts with special chars",
  386. name: ".foo.foo",
  387. expected: "foo-foo",
  388. },
  389. {
  390. desc: "ends with special chars",
  391. name: "foo.foo.",
  392. expected: "foo-foo",
  393. },
  394. }
  395. for _, test := range testCases {
  396. test := test
  397. t.Run(test.desc, func(t *testing.T) {
  398. t.Parallel()
  399. actual := Normalize(test.name)
  400. assert.Equal(t, test.expected, actual)
  401. })
  402. }
  403. }
  404. func readTemplateFile(t *testing.T, path string) string {
  405. t.Helper()
  406. expectedContent, err := ioutil.ReadFile(path)
  407. if err != nil {
  408. t.Fatal(err)
  409. }
  410. return string(expectedContent)
  411. }