traefik/provider/provider_test.go
2017-12-05 19:40:04 +01:00

514 lines
12 KiB
Go

package provider
import (
"io/ioutil"
"os"
"strings"
"testing"
"text/template"
"github.com/containous/traefik/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type myProvider struct {
BaseProvider
TLS *types.ClientTLS
}
func (p *myProvider) Foo() string {
return "bar"
}
func TestConfigurationErrors(t *testing.T) {
templateErrorFile, err := ioutil.TempFile("", "provider-configuration-error")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(templateErrorFile.Name())
data := []byte("Not a valid template {{ Bar }}")
err = ioutil.WriteFile(templateErrorFile.Name(), data, 0700)
if err != nil {
t.Fatal(err)
}
templateInvalidTOMLFile, err := ioutil.TempFile("", "provider-configuration-error")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(templateInvalidTOMLFile.Name())
data = []byte(`Hello {{ .Name }}
{{ Foo }}`)
err = ioutil.WriteFile(templateInvalidTOMLFile.Name(), data, 0700)
if err != nil {
t.Fatal(err)
}
invalids := []struct {
provider *myProvider
defaultTemplate string
expectedError string
funcMap template.FuncMap
templateObjects interface{}
}{
{
provider: &myProvider{
BaseProvider{
Filename: "/non/existent/template.tmpl",
},
nil,
},
expectedError: "open /non/existent/template.tmpl: no such file or directory",
},
{
provider: &myProvider{},
defaultTemplate: "non/existent/template.tmpl",
expectedError: "Asset non/existent/template.tmpl not found",
},
{
provider: &myProvider{
BaseProvider{
Filename: templateErrorFile.Name(),
},
nil,
},
expectedError: `function "Bar" not defined`,
},
{
provider: &myProvider{
BaseProvider{
Filename: templateInvalidTOMLFile.Name(),
},
nil,
},
expectedError: "Near line 1 (last key parsed 'Hello'): expected key separator '=', but got '<' instead",
funcMap: template.FuncMap{
"Foo": func() string {
return "bar"
},
},
templateObjects: struct{ Name string }{Name: "bar"},
},
}
for _, invalid := range invalids {
configuration, err := invalid.provider.GetConfiguration(invalid.defaultTemplate, invalid.funcMap, nil)
if err == nil || !strings.Contains(err.Error(), invalid.expectedError) {
t.Fatalf("should have generate an error with %q, got %v", invalid.expectedError, err)
}
if configuration != nil {
t.Fatalf("shouldn't have return a configuration object : %v", configuration)
}
}
}
func TestGetConfiguration(t *testing.T) {
templateFile, err := ioutil.TempFile("", "provider-configuration")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(templateFile.Name())
data := []byte(`[backends]
[backends.backend1]
[backends.backend1.circuitbreaker]
expression = "NetworkErrorRatio() > 0.5"
[backends.backend1.servers.server1]
url = "http://172.17.0.2:80"
weight = 10
[backends.backend1.servers.server2]
url = "http://172.17.0.3:80"
weight = 1
[frontends]
[frontends.frontend1]
backend = "backend1"
passHostHeader = true
[frontends.frontend11.routes.test_2]
rule = "Path"
value = "/test"`)
err = ioutil.WriteFile(templateFile.Name(), data, 0700)
if err != nil {
t.Fatal(err)
}
provider := &myProvider{
BaseProvider{
Filename: templateFile.Name(),
},
nil,
}
configuration, err := provider.GetConfiguration(templateFile.Name(), nil, nil)
if err != nil {
t.Fatalf("Shouldn't have error out, got %v", err)
}
if configuration == nil {
t.Fatal("Configuration should not be nil, but was")
}
}
func TestGetConfigurationReturnsCorrectMaxConnConfiguration(t *testing.T) {
templateFile, err := ioutil.TempFile("", "provider-configuration")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(templateFile.Name())
data := []byte(`[backends]
[backends.backend1]
[backends.backend1.maxconn]
amount = 10
extractorFunc = "request.host"`)
err = ioutil.WriteFile(templateFile.Name(), data, 0700)
if err != nil {
t.Fatal(err)
}
provider := &myProvider{
BaseProvider{
Filename: templateFile.Name(),
},
nil,
}
configuration, err := provider.GetConfiguration(templateFile.Name(), nil, nil)
if err != nil {
t.Fatalf("Shouldn't have error out, got %v", err)
}
if configuration == nil {
t.Fatal("Configuration should not be nil, but was")
}
if configuration.Backends["backend1"].MaxConn.Amount != 10 {
t.Fatal("Configuration did not parse MaxConn.Amount properly")
}
if configuration.Backends["backend1"].MaxConn.ExtractorFunc != "request.host" {
t.Fatal("Configuration did not parse MaxConn.ExtractorFunc properly")
}
}
func TestNilClientTLS(t *testing.T) {
provider := &myProvider{
BaseProvider{
Filename: "",
},
nil,
}
_, err := provider.TLS.CreateTLSConfig()
if err != nil {
t.Fatal("CreateTLSConfig should assume that consumer does not want a TLS configuration if input is nil")
}
}
func TestInsecureSkipVerifyClientTLS(t *testing.T) {
provider := &myProvider{
BaseProvider{
Filename: "",
},
&types.ClientTLS{
InsecureSkipVerify: true,
},
}
config, err := provider.TLS.CreateTLSConfig()
if err != nil {
t.Fatal("CreateTLSConfig should assume that consumer does not want a TLS configuration if input is nil")
}
if !config.InsecureSkipVerify {
t.Fatal("CreateTLSConfig should support setting only InsecureSkipVerify property")
}
}
func TestInsecureSkipVerifyFalseClientTLS(t *testing.T) {
provider := &myProvider{
BaseProvider{
Filename: "",
},
&types.ClientTLS{
InsecureSkipVerify: false,
},
}
_, err := provider.TLS.CreateTLSConfig()
if err == nil {
t.Fatal("CreateTLSConfig should error if consumer does not set a TLS cert or key configuration and not chooses InsecureSkipVerify to be true")
}
t.Log(err)
}
func TestMatchingConstraints(t *testing.T) {
cases := []struct {
constraints types.Constraints
tags []string
expected bool
}{
// simple test: must match
{
constraints: types.Constraints{
{
Key: "tag",
MustMatch: true,
Regex: "us-east-1",
},
},
tags: []string{
"us-east-1",
},
expected: true,
},
// simple test: must match but does not match
{
constraints: types.Constraints{
{
Key: "tag",
MustMatch: true,
Regex: "us-east-1",
},
},
tags: []string{
"us-east-2",
},
expected: false,
},
// simple test: must not match
{
constraints: types.Constraints{
{
Key: "tag",
MustMatch: false,
Regex: "us-east-1",
},
},
tags: []string{
"us-east-1",
},
expected: false,
},
// complex test: globbing
{
constraints: types.Constraints{
{
Key: "tag",
MustMatch: true,
Regex: "us-east-*",
},
},
tags: []string{
"us-east-1",
},
expected: true,
},
// complex test: multiple constraints
{
constraints: types.Constraints{
{
Key: "tag",
MustMatch: true,
Regex: "us-east-*",
},
{
Key: "tag",
MustMatch: false,
Regex: "api",
},
},
tags: []string{
"api",
"us-east-1",
},
expected: false,
},
}
for i, c := range cases {
provider := myProvider{
BaseProvider{
Constraints: c.constraints,
},
nil,
}
actual, _ := provider.MatchConstraints(c.tags)
if actual != c.expected {
t.Fatalf("test #%v: expected %t, got %t, for %#v", i, c.expected, actual, c.constraints)
}
}
}
func TestDefaultFuncMap(t *testing.T) {
templateFile, err := ioutil.TempFile("", "provider-configuration")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(templateFile.Name())
data := []byte(`
[backends]
[backends.{{ "backend-1" | replace "-" "" }}]
[backends.{{ "BACKEND1" | tolower }}.circuitbreaker]
expression = "NetworkErrorRatio() > 0.5"
[backends.servers.server1]
url = "http://172.17.0.2:80"
weight = 10
[backends.backend1.servers.server2]
url = "http://172.17.0.3:80"
weight = 1
[frontends]
[frontends.{{normalize "frontend/1"}}]
{{ $backend := "backend1/test/value" | split "/" }}
{{ $backendid := index $backend 1 }}
{{ if "backend1" | contains "backend" }}
backend = "backend1"
{{end}}
passHostHeader = true
[frontends.frontend-1.routes.test_2]
rule = "Path"
value = "/test"`)
err = ioutil.WriteFile(templateFile.Name(), data, 0700)
if err != nil {
t.Fatal(err)
}
provider := &myProvider{
BaseProvider{
Filename: templateFile.Name(),
},
nil,
}
configuration, err := provider.GetConfiguration(templateFile.Name(), nil, nil)
if err != nil {
t.Fatalf("Shouldn't have error out, got %v", err)
}
if configuration == nil {
t.Fatal("Configuration should not be nil, but was")
}
if _, ok := configuration.Backends["backend1"]; !ok {
t.Fatal("backend1 should exists, but it not")
}
if _, ok := configuration.Frontends["frontend-1"]; !ok {
t.Fatal("Frontend frontend-1 should exists, but it not")
}
}
func TestSprigFunctions(t *testing.T) {
templateFile, err := ioutil.TempFile("", "provider-configuration")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(templateFile.Name())
data := []byte(`
{{$backend_name := trimAll "-" uuidv4}}
[backends]
[backends.{{$backend_name}}]
[backends.{{$backend_name}}.circuitbreaker]
[backends.{{$backend_name}}.servers.server2]
url = "http://172.17.0.3:80"
weight = 1
[frontends]
[frontends.{{normalize "frontend/1"}}]
backend = "{{$backend_name}}"
passHostHeader = true
[frontends.frontend-1.routes.test_2]
rule = "Path"
value = "/test"`)
err = ioutil.WriteFile(templateFile.Name(), data, 0700)
if err != nil {
t.Fatal(err)
}
provider := &myProvider{
BaseProvider{
Filename: templateFile.Name(),
},
nil,
}
configuration, err := provider.GetConfiguration(templateFile.Name(), nil, nil)
if err != nil {
t.Fatalf("Shouldn't have error out, got %v", err)
}
if configuration == nil {
t.Fatal("Configuration should not be nil, but was")
}
if len(configuration.Backends) != 1 {
t.Fatal("one backend should be defined, but it's not")
}
if _, ok := configuration.Frontends["frontend-1"]; !ok {
t.Fatal("Frontend frontend-1 should exists, but it not")
}
}
func TestBaseProvider_GetConfiguration(t *testing.T) {
baseProvider := BaseProvider{}
testCases := []struct {
name string
defaultTemplateFile string
expectedContent string
}{
{
defaultTemplateFile: "templates/docker.tmpl",
expectedContent: readTemplateFile(t, "./../templates/docker.tmpl"),
},
{
defaultTemplateFile: `template content`,
expectedContent: `template content`,
},
}
for _, test := range testCases {
test := test
t.Run(test.name, func(t *testing.T) {
content, err := baseProvider.getTemplateContent(test.defaultTemplateFile)
require.NoError(t, err)
assert.Equal(t, test.expectedContent, content)
})
}
}
func TestNormalize(t *testing.T) {
testCases := []struct {
desc string
name string
expected string
}{
{
desc: "without special chars",
name: "foobar",
expected: "foobar",
},
{
desc: "with special chars",
name: "foo.foo.foo;foo:foo!foo/foo\\foo)foo_123-ç_àéè",
expected: "foo-foo-foo-foo-foo-foo-foo-foo-foo-123-ç-àéè",
},
{
desc: "starts with special chars",
name: ".foo.foo",
expected: "foo-foo",
},
{
desc: "ends with special chars",
name: "foo.foo.",
expected: "foo-foo",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := Normalize(test.name)
assert.Equal(t, test.expected, actual)
})
}
}
func readTemplateFile(t *testing.T, path string) string {
t.Helper()
expectedContent, err := ioutil.ReadFile(path)
if err != nil {
t.Fatal(err)
}
return string(expectedContent)
}