1
0
Fork 0

Dynamic Configuration Refactoring

This commit is contained in:
Ludovic Fernandez 2018-11-14 10:18:03 +01:00 committed by Traefiker Bot
parent d3ae88f108
commit a09dfa3ce1
452 changed files with 21023 additions and 9419 deletions

View file

@ -0,0 +1,44 @@
package types
import (
"fmt"
"strings"
)
// DNSResolvers is a list of DNSes that we will try to resolve the challenged FQDN against
type DNSResolvers []string
// String is the method to format the flag's value, part of the flag.Value interface.
// The String method's output will be used in diagnostics.
func (r *DNSResolvers) String() string {
return strings.Join(*r, ",")
}
// Set is the method to set the flag value, part of the flag.Value interface.
// Set's argument is a string to be parsed to set the flag.
// It's a comma-separated list, so we split it.
func (r *DNSResolvers) Set(value string) error {
entryPoints := strings.Split(value, ",")
if len(entryPoints) == 0 {
return fmt.Errorf("wrong DNSResolvers format: %s", value)
}
for _, entryPoint := range entryPoints {
*r = append(*r, entryPoint)
}
return nil
}
// Get return the DNSResolvers list
func (r *DNSResolvers) Get() interface{} {
return *r
}
// SetValue sets the DNSResolvers list
func (r *DNSResolvers) SetValue(val interface{}) {
*r = val.(DNSResolvers)
}
// Type is type of the struct
func (r *DNSResolvers) Type() string {
return "dnsresolvers"
}

182
old/types/domain_test.go Normal file
View file

@ -0,0 +1,182 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestDomain_ToStrArray(t *testing.T) {
testCases := []struct {
desc string
domain Domain
expected []string
}{
{
desc: "with Main and SANs",
domain: Domain{
Main: "foo.com",
SANs: []string{"bar.foo.com", "bir.foo.com"},
},
expected: []string{"foo.com", "bar.foo.com", "bir.foo.com"},
},
{
desc: "without SANs",
domain: Domain{
Main: "foo.com",
},
expected: []string{"foo.com"},
},
{
desc: "without Main",
domain: Domain{
SANs: []string{"bar.foo.com", "bir.foo.com"},
},
expected: []string{"bar.foo.com", "bir.foo.com"},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
domains := test.domain.ToStrArray()
assert.EqualValues(t, test.expected, domains)
})
}
}
func TestDomain_Set(t *testing.T) {
testCases := []struct {
desc string
rawDomains []string
expected Domain
}{
{
desc: "with 3 domains",
rawDomains: []string{"foo.com", "bar.foo.com", "bir.foo.com"},
expected: Domain{
Main: "foo.com",
SANs: []string{"bar.foo.com", "bir.foo.com"},
},
},
{
desc: "with 1 domain",
rawDomains: []string{"foo.com"},
expected: Domain{
Main: "foo.com",
SANs: []string{},
},
},
{
desc: "",
rawDomains: nil,
expected: Domain{},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
domain := Domain{}
domain.Set(test.rawDomains)
assert.Equal(t, test.expected, domain)
})
}
}
func TestMatchDomain(t *testing.T) {
testCases := []struct {
desc string
certDomain string
domain string
expected bool
}{
{
desc: "exact match",
certDomain: "traefik.wtf",
domain: "traefik.wtf",
expected: true,
},
{
desc: "wildcard and root domain",
certDomain: "*.traefik.wtf",
domain: "traefik.wtf",
expected: false,
},
{
desc: "wildcard and sub domain",
certDomain: "*.traefik.wtf",
domain: "sub.traefik.wtf",
expected: true,
},
{
desc: "wildcard and sub sub domain",
certDomain: "*.traefik.wtf",
domain: "sub.sub.traefik.wtf",
expected: false,
},
{
desc: "double wildcard and sub sub domain",
certDomain: "*.*.traefik.wtf",
domain: "sub.sub.traefik.wtf",
expected: true,
},
{
desc: "sub sub domain and invalid wildcard",
certDomain: "sub.*.traefik.wtf",
domain: "sub.sub.traefik.wtf",
expected: false,
},
{
desc: "sub sub domain and valid wildcard",
certDomain: "*.sub.traefik.wtf",
domain: "sub.sub.traefik.wtf",
expected: true,
},
{
desc: "dot replaced by a cahr",
certDomain: "sub.sub.traefik.wtf",
domain: "sub.sub.traefikiwtf",
expected: false,
},
{
desc: "*",
certDomain: "*",
domain: "sub.sub.traefik.wtf",
expected: false,
},
{
desc: "?",
certDomain: "?",
domain: "sub.sub.traefik.wtf",
expected: false,
},
{
desc: "...................",
certDomain: "...................",
domain: "sub.sub.traefik.wtf",
expected: false,
},
{
desc: "wildcard and *",
certDomain: "*.traefik.wtf",
domain: "*.*.traefik.wtf",
expected: false,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
domains := MatchDomain(test.domain, test.certDomain)
assert.Equal(t, test.expected, domains)
})
}
}

88
old/types/domains.go Normal file
View file

@ -0,0 +1,88 @@
package types
import (
"fmt"
"strings"
)
// Domain holds a domain name with SANs
type Domain struct {
Main string
SANs []string
}
// ToStrArray convert a domain into an array of strings
func (d *Domain) ToStrArray() []string {
var domains []string
if len(d.Main) > 0 {
domains = []string{d.Main}
}
return append(domains, d.SANs...)
}
// Set sets a domains from an array of strings
func (d *Domain) Set(domains []string) {
if len(domains) > 0 {
d.Main = domains[0]
d.SANs = domains[1:]
}
}
// Domains parse []Domain
type Domains []Domain
// Set []Domain
func (ds *Domains) Set(str string) error {
fargs := func(c rune) bool {
return c == ',' || c == ';'
}
// get function
slice := strings.FieldsFunc(str, fargs)
if len(slice) < 1 {
return fmt.Errorf("parse error ACME.Domain. Unable to parse %s", str)
}
d := Domain{
Main: slice[0],
}
if len(slice) > 1 {
d.SANs = slice[1:]
}
*ds = append(*ds, d)
return nil
}
// Get []Domain
func (ds *Domains) Get() interface{} { return []Domain(*ds) }
// String returns []Domain in string
func (ds *Domains) String() string { return fmt.Sprintf("%+v", *ds) }
// SetValue sets []Domain into the parser
func (ds *Domains) SetValue(val interface{}) {
*ds = val.([]Domain)
}
// MatchDomain return true if a domain match the cert domain
func MatchDomain(domain string, certDomain string) bool {
if domain == certDomain {
return true
}
for len(certDomain) > 0 && certDomain[len(certDomain)-1] == '.' {
certDomain = certDomain[:len(certDomain)-1]
}
labels := strings.Split(domain, ".")
for i := range labels {
labels[i] = "*"
candidate := strings.Join(labels, ".")
if certDomain == candidate {
return true
}
}
return false
}

View file

@ -0,0 +1,10 @@
package types
import (
"github.com/containous/mux"
)
// InternalRouter router used by server to register internal routes (/api, /ping ...)
type InternalRouter interface {
AddRoutes(systemRouter *mux.Router)
}

200
old/types/logs.go Normal file
View file

@ -0,0 +1,200 @@
package types
import (
"fmt"
"strings"
"github.com/containous/flaeg/parse"
)
const (
// AccessLogKeep is the keep string value
AccessLogKeep = "keep"
// AccessLogDrop is the drop string value
AccessLogDrop = "drop"
// AccessLogRedact is the redact string value
AccessLogRedact = "redact"
)
// TraefikLog holds the configuration settings for the traefik logger.
type TraefikLog struct {
FilePath string `json:"file,omitempty" description:"Traefik log file path. Stdout is used when omitted or empty"`
Format string `json:"format,omitempty" description:"Traefik log format: json | common"`
}
// AccessLog holds the configuration settings for the access logger (middlewares/accesslog).
type AccessLog struct {
FilePath string `json:"file,omitempty" description:"Access log file path. Stdout is used when omitted or empty" export:"true"`
Format string `json:"format,omitempty" description:"Access log format: json | common" export:"true"`
Filters *AccessLogFilters `json:"filters,omitempty" description:"Access log filters, used to keep only specific access logs" export:"true"`
Fields *AccessLogFields `json:"fields,omitempty" description:"AccessLogFields" export:"true"`
BufferingSize int64 `json:"bufferingSize,omitempty" description:"Number of access log lines to process in a buffered way. Default 0." export:"true"`
}
// AccessLogFilters holds filters configuration
type AccessLogFilters struct {
StatusCodes StatusCodes `json:"statusCodes,omitempty" description:"Keep access logs with status codes in the specified range" export:"true"`
RetryAttempts bool `json:"retryAttempts,omitempty" description:"Keep access logs when at least one retry happened" export:"true"`
MinDuration parse.Duration `json:"duration,omitempty" description:"Keep access logs when request took longer than the specified duration" export:"true"`
}
// FieldHeaders holds configuration for access log headers
type FieldHeaders struct {
DefaultMode string `json:"defaultMode,omitempty" description:"Default mode for fields: keep | drop | redact" export:"true"`
Names FieldHeaderNames `json:"names,omitempty" description:"Override mode for headers" export:"true"`
}
// StatusCodes holds status codes ranges to filter access log
type StatusCodes []string
// Set adds strings elem into the the parser
// it splits str on , and ;
func (s *StatusCodes) Set(str string) error {
fargs := func(c rune) bool {
return c == ',' || c == ';'
}
// get function
slice := strings.FieldsFunc(str, fargs)
*s = append(*s, slice...)
return nil
}
// Get StatusCodes
func (s *StatusCodes) Get() interface{} { return *s }
// String return slice in a string
func (s *StatusCodes) String() string { return fmt.Sprintf("%v", *s) }
// SetValue sets StatusCodes into the parser
func (s *StatusCodes) SetValue(val interface{}) {
*s = val.(StatusCodes)
}
// FieldNames holds maps of fields with specific mode
type FieldNames map[string]string
// String is the method to format the flag's value, part of the flag.Value interface.
// The String method's output will be used in diagnostics.
func (f *FieldNames) String() string {
return fmt.Sprintf("%+v", *f)
}
// Get return the FieldNames map
func (f *FieldNames) Get() interface{} {
return *f
}
// Set is the method to set the flag value, part of the flag.Value interface.
// Set's argument is a string to be parsed to set the flag.
// It's a space-separated list, so we split it.
func (f *FieldNames) Set(value string) error {
// When arguments are passed through YAML, escaped double quotes
// might be added to this string, and they would break the last
// key/value pair. This ensures the string is clean.
value = strings.Trim(value, "\"")
fields := strings.Fields(value)
for _, field := range fields {
n := strings.SplitN(field, "=", 2)
if len(n) == 2 {
(*f)[n[0]] = n[1]
}
}
return nil
}
// SetValue sets the FieldNames map with val
func (f *FieldNames) SetValue(val interface{}) {
*f = val.(FieldNames)
}
// FieldHeaderNames holds maps of fields with specific mode
type FieldHeaderNames map[string]string
// String is the method to format the flag's value, part of the flag.Value interface.
// The String method's output will be used in diagnostics.
func (f *FieldHeaderNames) String() string {
return fmt.Sprintf("%+v", *f)
}
// Get return the FieldHeaderNames map
func (f *FieldHeaderNames) Get() interface{} {
return *f
}
// Set is the method to set the flag value, part of the flag.Value interface.
// Set's argument is a string to be parsed to set the flag.
// It's a space-separated list, so we split it.
func (f *FieldHeaderNames) Set(value string) error {
// When arguments are passed through YAML, escaped double quotes
// might be added to this string, and they would break the last
// key/value pair. This ensures the string is clean.
value = strings.Trim(value, "\"")
fields := strings.Fields(value)
for _, field := range fields {
n := strings.SplitN(field, "=", 2)
(*f)[n[0]] = n[1]
}
return nil
}
// SetValue sets the FieldHeaderNames map with val
func (f *FieldHeaderNames) SetValue(val interface{}) {
*f = val.(FieldHeaderNames)
}
// AccessLogFields holds configuration for access log fields
type AccessLogFields struct {
DefaultMode string `json:"defaultMode,omitempty" description:"Default mode for fields: keep | drop" export:"true"`
Names FieldNames `json:"names,omitempty" description:"Override mode for fields" export:"true"`
Headers *FieldHeaders `json:"headers,omitempty" description:"Headers to keep, drop or redact" export:"true"`
}
// Keep check if the field need to be kept or dropped
func (f *AccessLogFields) Keep(field string) bool {
defaultKeep := true
if f != nil {
defaultKeep = checkFieldValue(f.DefaultMode, defaultKeep)
if v, ok := f.Names[field]; ok {
return checkFieldValue(v, defaultKeep)
}
}
return defaultKeep
}
// KeepHeader checks if the headers need to be kept, dropped or redacted and returns the status
func (f *AccessLogFields) KeepHeader(header string) string {
defaultValue := AccessLogKeep
if f != nil && f.Headers != nil {
defaultValue = checkFieldHeaderValue(f.Headers.DefaultMode, defaultValue)
if v, ok := f.Headers.Names[header]; ok {
return checkFieldHeaderValue(v, defaultValue)
}
}
return defaultValue
}
func checkFieldValue(value string, defaultKeep bool) bool {
switch value {
case AccessLogKeep:
return true
case AccessLogDrop:
return false
default:
return defaultKeep
}
}
func checkFieldHeaderValue(value string, defaultValue string) string {
if value == AccessLogKeep || value == AccessLogDrop || value == AccessLogRedact {
return value
}
return defaultValue
}

419
old/types/logs_test.go Normal file
View file

@ -0,0 +1,419 @@
package types
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestStatusCodesSet(t *testing.T) {
testCases := []struct {
desc string
value string
expected StatusCodes
}{
{
desc: "One value should return StatusCodes of size 1",
value: "200",
expected: StatusCodes{"200"},
},
{
desc: "Two values separated by comma should return StatusCodes of size 2",
value: "200,400",
expected: StatusCodes{"200", "400"},
},
{
desc: "Two values separated by semicolon should return StatusCodes of size 2",
value: "200;400",
expected: StatusCodes{"200", "400"},
},
{
desc: "Three values separated by comma and semicolon should return StatusCodes of size 3",
value: "200,400;500",
expected: StatusCodes{"200", "400", "500"},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
var statusCodes StatusCodes
err := statusCodes.Set(test.value)
assert.Nil(t, err)
assert.Equal(t, test.expected, statusCodes)
})
}
}
func TestStatusCodesGet(t *testing.T) {
testCases := []struct {
desc string
values StatusCodes
expected StatusCodes
}{
{
desc: "Should return 1 value",
values: StatusCodes{"200"},
expected: StatusCodes{"200"},
},
{
desc: "Should return 2 values",
values: StatusCodes{"200", "400"},
expected: StatusCodes{"200", "400"},
},
{
desc: "Should return 3 values",
values: StatusCodes{"200", "400", "500"},
expected: StatusCodes{"200", "400", "500"},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := test.values.Get()
assert.Equal(t, test.expected, actual)
})
}
}
func TestStatusCodesString(t *testing.T) {
testCases := []struct {
desc string
values StatusCodes
expected string
}{
{
desc: "Should return 1 value",
values: StatusCodes{"200"},
expected: "[200]",
},
{
desc: "Should return 2 values",
values: StatusCodes{"200", "400"},
expected: "[200 400]",
},
{
desc: "Should return 3 values",
values: StatusCodes{"200", "400", "500"},
expected: "[200 400 500]",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := test.values.String()
assert.Equal(t, test.expected, actual)
})
}
}
func TestStatusCodesSetValue(t *testing.T) {
testCases := []struct {
desc string
values StatusCodes
expected StatusCodes
}{
{
desc: "Should return 1 value",
values: StatusCodes{"200"},
expected: StatusCodes{"200"},
},
{
desc: "Should return 2 values",
values: StatusCodes{"200", "400"},
expected: StatusCodes{"200", "400"},
},
{
desc: "Should return 3 values",
values: StatusCodes{"200", "400", "500"},
expected: StatusCodes{"200", "400", "500"},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
var slice StatusCodes
slice.SetValue(test.values)
assert.Equal(t, test.expected, slice)
})
}
}
func TestFieldsNamesSet(t *testing.T) {
testCases := []struct {
desc string
value string
expected *FieldNames
}{
{
desc: "One value should return FieldNames of size 1",
value: "field-1=foo",
expected: &FieldNames{
"field-1": "foo",
},
},
{
desc: "Two values separated by space should return FieldNames of size 2",
value: "field-1=foo field-2=bar",
expected: &FieldNames{
"field-1": "foo",
"field-2": "bar",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
fieldsNames := &FieldNames{}
err := fieldsNames.Set(test.value)
assert.NoError(t, err)
assert.Equal(t, test.expected, fieldsNames)
})
}
}
func TestFieldsNamesGet(t *testing.T) {
testCases := []struct {
desc string
values FieldNames
expected FieldNames
}{
{
desc: "Should return 1 value",
values: FieldNames{"field-1": "foo"},
expected: FieldNames{"field-1": "foo"},
},
{
desc: "Should return 2 values",
values: FieldNames{"field-1": "foo", "field-2": "bar"},
expected: FieldNames{"field-1": "foo", "field-2": "bar"},
},
{
desc: "Should return 3 values",
values: FieldNames{"field-1": "foo", "field-2": "bar", "field-3": "powpow"},
expected: FieldNames{"field-1": "foo", "field-2": "bar", "field-3": "powpow"},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := test.values.Get()
assert.Equal(t, test.expected, actual)
})
}
}
func TestFieldsNamesString(t *testing.T) {
testCases := []struct {
desc string
values FieldNames
expected string
}{
{
desc: "Should return 1 value",
values: FieldNames{"field-1": "foo"},
expected: "map[field-1:foo]",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := test.values.String()
assert.Equal(t, test.expected, actual)
})
}
}
func TestFieldsNamesSetValue(t *testing.T) {
testCases := []struct {
desc string
values FieldNames
expected *FieldNames
}{
{
desc: "Should return 1 value",
values: FieldNames{"field-1": "foo"},
expected: &FieldNames{"field-1": "foo"},
},
{
desc: "Should return 2 values",
values: FieldNames{"field-1": "foo", "field-2": "bar"},
expected: &FieldNames{"field-1": "foo", "field-2": "bar"},
},
{
desc: "Should return 3 values",
values: FieldNames{"field-1": "foo", "field-2": "bar", "field-3": "powpow"},
expected: &FieldNames{"field-1": "foo", "field-2": "bar", "field-3": "powpow"},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
fieldsNames := &FieldNames{}
fieldsNames.SetValue(test.values)
assert.Equal(t, test.expected, fieldsNames)
})
}
}
func TestFieldsHeadersNamesSet(t *testing.T) {
testCases := []struct {
desc string
value string
expected *FieldHeaderNames
}{
{
desc: "One value should return FieldNames of size 1",
value: "X-HEADER-1=foo",
expected: &FieldHeaderNames{
"X-HEADER-1": "foo",
},
},
{
desc: "Two values separated by space should return FieldNames of size 2",
value: "X-HEADER-1=foo X-HEADER-2=bar",
expected: &FieldHeaderNames{
"X-HEADER-1": "foo",
"X-HEADER-2": "bar",
},
},
{
desc: "Two values separated by space with escaped double quotes should return FieldNames of size 2",
value: "\"X-HEADER-1=foo X-HEADER-2=bar\"",
expected: &FieldHeaderNames{
"X-HEADER-1": "foo",
"X-HEADER-2": "bar",
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
headersNames := &FieldHeaderNames{}
err := headersNames.Set(test.value)
assert.NoError(t, err)
assert.Equal(t, test.expected, headersNames)
})
}
}
func TestFieldsHeadersNamesGet(t *testing.T) {
testCases := []struct {
desc string
values FieldHeaderNames
expected FieldHeaderNames
}{
{
desc: "Should return 1 value",
values: FieldHeaderNames{"X-HEADER-1": "foo"},
expected: FieldHeaderNames{"X-HEADER-1": "foo"},
},
{
desc: "Should return 2 values",
values: FieldHeaderNames{"X-HEADER-1": "foo", "X-HEADER-2": "bar"},
expected: FieldHeaderNames{"X-HEADER-1": "foo", "X-HEADER-2": "bar"},
},
{
desc: "Should return 3 values",
values: FieldHeaderNames{"X-HEADER-1": "foo", "X-HEADER-2": "bar", "X-HEADER-3": "powpow"},
expected: FieldHeaderNames{"X-HEADER-1": "foo", "X-HEADER-2": "bar", "X-HEADER-3": "powpow"},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := test.values.Get()
assert.Equal(t, test.expected, actual)
})
}
}
func TestFieldsHeadersNamesString(t *testing.T) {
testCases := []struct {
desc string
values FieldHeaderNames
expected string
}{
{
desc: "Should return 1 value",
values: FieldHeaderNames{"X-HEADER-1": "foo"},
expected: "map[X-HEADER-1:foo]",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual := test.values.String()
assert.Equal(t, test.expected, actual)
})
}
}
func TestFieldsHeadersNamesSetValue(t *testing.T) {
testCases := []struct {
desc string
values FieldHeaderNames
expected *FieldHeaderNames
}{
{
desc: "Should return 1 value",
values: FieldHeaderNames{"X-HEADER-1": "foo"},
expected: &FieldHeaderNames{"X-HEADER-1": "foo"},
},
{
desc: "Should return 2 values",
values: FieldHeaderNames{"X-HEADER-1": "foo", "X-HEADER-2": "bar"},
expected: &FieldHeaderNames{"X-HEADER-1": "foo", "X-HEADER-2": "bar"},
},
{
desc: "Should return 3 values",
values: FieldHeaderNames{"X-HEADER-1": "foo", "X-HEADER-2": "bar", "X-HEADER-3": "powpow"},
expected: &FieldHeaderNames{"X-HEADER-1": "foo", "X-HEADER-2": "bar", "X-HEADER-3": "powpow"},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
headersNames := &FieldHeaderNames{}
headersNames.SetValue(test.values)
assert.Equal(t, test.expected, headersNames)
})
}
}

682
old/types/types.go Normal file
View file

@ -0,0 +1,682 @@
package types
import (
"crypto/tls"
"crypto/x509"
"encoding"
"errors"
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"github.com/abronan/valkeyrie/store"
"github.com/containous/flaeg/parse"
"github.com/containous/mux"
"github.com/containous/traefik/ip"
"github.com/containous/traefik/old/log"
traefiktls "github.com/containous/traefik/tls"
"github.com/mitchellh/hashstructure"
"github.com/ryanuber/go-glob"
)
// Backend holds backend configuration.
type Backend struct {
Servers map[string]Server `json:"servers,omitempty"`
CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty"`
LoadBalancer *LoadBalancer `json:"loadBalancer,omitempty"`
MaxConn *MaxConn `json:"maxConn,omitempty"`
HealthCheck *HealthCheck `json:"healthCheck,omitempty"`
Buffering *Buffering `json:"buffering,omitempty"`
ResponseForwarding *ResponseForwarding `json:"forwardingResponse,omitempty"`
}
// ResponseForwarding holds configuration for the forward of the response
type ResponseForwarding struct {
FlushInterval string `json:"flushInterval,omitempty"`
}
// MaxConn holds maximum connection configuration
type MaxConn struct {
Amount int64 `json:"amount,omitempty"`
ExtractorFunc string `json:"extractorFunc,omitempty"`
}
// LoadBalancer holds load balancing configuration.
type LoadBalancer struct {
Method string `json:"method,omitempty"`
Stickiness *Stickiness `json:"stickiness,omitempty"`
}
// Stickiness holds sticky session configuration.
type Stickiness struct {
CookieName string `json:"cookieName,omitempty"`
}
// CircuitBreaker holds circuit breaker configuration.
type CircuitBreaker struct {
Expression string `json:"expression,omitempty"`
}
// Buffering holds request/response buffering configuration/
type Buffering struct {
MaxRequestBodyBytes int64 `json:"maxRequestBodyBytes,omitempty"`
MemRequestBodyBytes int64 `json:"memRequestBodyBytes,omitempty"`
MaxResponseBodyBytes int64 `json:"maxResponseBodyBytes,omitempty"`
MemResponseBodyBytes int64 `json:"memResponseBodyBytes,omitempty"`
RetryExpression string `json:"retryExpression,omitempty"`
}
// WhiteList contains white list configuration.
type WhiteList struct {
SourceRange []string `json:"sourceRange,omitempty"`
IPStrategy *IPStrategy `json:"ipStrategy,omitempty"`
}
// HealthCheck holds HealthCheck configuration
type HealthCheck struct {
Scheme string `json:"scheme,omitempty"`
Path string `json:"path,omitempty"`
Port int `json:"port,omitempty"`
Interval string `json:"interval,omitempty"`
Timeout string `json:"timeout,omitempty"`
Hostname string `json:"hostname,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
}
// Server holds server configuration.
type Server struct {
URL string `json:"url,omitempty"`
Weight int `json:"weight"`
}
// Route holds route configuration.
type Route struct {
Rule string `json:"rule,omitempty"`
}
// ServerRoute holds ServerRoute configuration.
type ServerRoute struct {
Route *mux.Route
StripPrefixes []string
StripPrefixesRegex []string
AddPrefix string
ReplacePath string
ReplacePathRegex string
}
// ErrorPage holds custom error page configuration
type ErrorPage struct {
Status []string `json:"status,omitempty"`
Backend string `json:"backend,omitempty"`
Query string `json:"query,omitempty"`
}
// Rate holds a rate limiting configuration for a specific time period
type Rate struct {
Period parse.Duration `json:"period,omitempty"`
Average int64 `json:"average,omitempty"`
Burst int64 `json:"burst,omitempty"`
}
// RateLimit holds a rate limiting configuration for a given frontend
type RateLimit struct {
RateSet map[string]*Rate `json:"rateset,omitempty"`
ExtractorFunc string `json:"extractorFunc,omitempty"`
}
// Headers holds the custom header configuration
type Headers struct {
CustomRequestHeaders map[string]string `json:"customRequestHeaders,omitempty"`
CustomResponseHeaders map[string]string `json:"customResponseHeaders,omitempty"`
AllowedHosts []string `json:"allowedHosts,omitempty"`
HostsProxyHeaders []string `json:"hostsProxyHeaders,omitempty"`
SSLRedirect bool `json:"sslRedirect,omitempty"`
SSLTemporaryRedirect bool `json:"sslTemporaryRedirect,omitempty"`
SSLHost string `json:"sslHost,omitempty"`
SSLProxyHeaders map[string]string `json:"sslProxyHeaders,omitempty"`
SSLForceHost bool `json:"sslForceHost,omitempty"`
STSSeconds int64 `json:"stsSeconds,omitempty"`
STSIncludeSubdomains bool `json:"stsIncludeSubdomains,omitempty"`
STSPreload bool `json:"stsPreload,omitempty"`
ForceSTSHeader bool `json:"forceSTSHeader,omitempty"`
FrameDeny bool `json:"frameDeny,omitempty"`
CustomFrameOptionsValue string `json:"customFrameOptionsValue,omitempty"`
ContentTypeNosniff bool `json:"contentTypeNosniff,omitempty"`
BrowserXSSFilter bool `json:"browserXssFilter,omitempty"`
CustomBrowserXSSValue string `json:"customBrowserXSSValue,omitempty"`
ContentSecurityPolicy string `json:"contentSecurityPolicy,omitempty"`
PublicKey string `json:"publicKey,omitempty"`
ReferrerPolicy string `json:"referrerPolicy,omitempty"`
IsDevelopment bool `json:"isDevelopment,omitempty"`
}
// HasCustomHeadersDefined checks to see if any of the custom header elements have been set
func (h *Headers) HasCustomHeadersDefined() bool {
return h != nil && (len(h.CustomResponseHeaders) != 0 ||
len(h.CustomRequestHeaders) != 0)
}
// HasSecureHeadersDefined checks to see if any of the secure header elements have been set
func (h *Headers) HasSecureHeadersDefined() bool {
return h != nil && (len(h.AllowedHosts) != 0 ||
len(h.HostsProxyHeaders) != 0 ||
h.SSLRedirect ||
h.SSLTemporaryRedirect ||
h.SSLForceHost ||
h.SSLHost != "" ||
len(h.SSLProxyHeaders) != 0 ||
h.STSSeconds != 0 ||
h.STSIncludeSubdomains ||
h.STSPreload ||
h.ForceSTSHeader ||
h.FrameDeny ||
h.CustomFrameOptionsValue != "" ||
h.ContentTypeNosniff ||
h.BrowserXSSFilter ||
h.CustomBrowserXSSValue != "" ||
h.ContentSecurityPolicy != "" ||
h.PublicKey != "" ||
h.ReferrerPolicy != "" ||
h.IsDevelopment)
}
// Frontend holds frontend configuration.
type Frontend struct {
EntryPoints []string `json:"entryPoints,omitempty" hash:"ignore"`
Backend string `json:"backend,omitempty"`
Routes map[string]Route `json:"routes,omitempty" hash:"ignore"`
PassHostHeader bool `json:"passHostHeader,omitempty"`
PassTLSCert bool `json:"passTLSCert,omitempty"` // Deprecated use PassTLSClientCert instead
PassTLSClientCert *TLSClientHeaders `json:"passTLSClientCert,omitempty"`
Priority int `json:"priority"`
WhiteList *WhiteList `json:"whiteList,omitempty"`
Headers *Headers `json:"headers,omitempty"`
Errors map[string]*ErrorPage `json:"errors,omitempty"`
RateLimit *RateLimit `json:"ratelimit,omitempty"`
Redirect *Redirect `json:"redirect,omitempty"`
Auth *Auth `json:"auth,omitempty"`
}
// Hash returns the hash value of a Frontend struct.
func (f *Frontend) Hash() (string, error) {
hash, err := hashstructure.Hash(f, nil)
if err != nil {
return "", err
}
return strconv.FormatUint(hash, 10), nil
}
// Redirect configures a redirection of an entry point to another, or to an URL
type Redirect struct {
EntryPoint string `json:"entryPoint,omitempty"`
Regex string `json:"regex,omitempty"`
Replacement string `json:"replacement,omitempty"`
Permanent bool `json:"permanent,omitempty"`
}
// LoadBalancerMethod holds the method of load balancing to use.
type LoadBalancerMethod uint8
const (
// Wrr (default) = Weighted Round Robin
Wrr LoadBalancerMethod = iota
// Drr = Dynamic Round Robin
Drr
)
var loadBalancerMethodNames = []string{
"Wrr",
"Drr",
}
// NewLoadBalancerMethod create a new LoadBalancerMethod from a given LoadBalancer.
func NewLoadBalancerMethod(loadBalancer *LoadBalancer) (LoadBalancerMethod, error) {
if loadBalancer == nil {
return Wrr, errors.New("no load-balancer defined, fallback to 'wrr' method")
}
if len(loadBalancer.Method) == 0 {
return Wrr, errors.New("no load-balancing method defined, fallback to 'wrr' method")
}
for i, name := range loadBalancerMethodNames {
if strings.EqualFold(name, loadBalancer.Method) {
return LoadBalancerMethod(i), nil
}
}
return Wrr, fmt.Errorf("invalid load-balancing method %q, fallback to 'wrr' method", loadBalancer.Method)
}
// Configurations is for currentConfigurations Map
type Configurations map[string]*Configuration
// Configuration of a provider.
type Configuration struct {
Backends map[string]*Backend `json:"backends,omitempty"`
Frontends map[string]*Frontend `json:"frontends,omitempty"`
TLS []*traefiktls.Configuration `json:"-"`
}
// ConfigMessage hold configuration information exchanged between parts of traefik.
type ConfigMessage struct {
ProviderName string
Configuration *Configuration
}
// Constraint hold a parsed constraint expression
type Constraint struct {
Key string `export:"true"`
// MustMatch is true if operator is "==" or false if operator is "!="
MustMatch bool `export:"true"`
// TODO: support regex
Regex string `export:"true"`
}
// NewConstraint receive a string and return a *Constraint, after checking syntax and parsing the constraint expression
func NewConstraint(exp string) (*Constraint, error) {
sep := ""
constraint := &Constraint{}
if strings.Contains(exp, "==") {
sep = "=="
constraint.MustMatch = true
} else if strings.Contains(exp, "!=") {
sep = "!="
constraint.MustMatch = false
} else {
return nil, errors.New("constraint expression missing valid operator: '==' or '!='")
}
kv := strings.SplitN(exp, sep, 2)
if len(kv) == 2 {
// At the moment, it only supports tags
if kv[0] != "tag" {
return nil, errors.New("constraint must be tag-based. Syntax: tag==us-*")
}
constraint.Key = kv[0]
constraint.Regex = kv[1]
return constraint, nil
}
return nil, fmt.Errorf("incorrect constraint expression: %s", exp)
}
func (c *Constraint) String() string {
if c.MustMatch {
return c.Key + "==" + c.Regex
}
return c.Key + "!=" + c.Regex
}
var _ encoding.TextUnmarshaler = (*Constraint)(nil)
// UnmarshalText define how unmarshal in TOML parsing
func (c *Constraint) UnmarshalText(text []byte) error {
constraint, err := NewConstraint(string(text))
if err != nil {
return err
}
c.Key = constraint.Key
c.MustMatch = constraint.MustMatch
c.Regex = constraint.Regex
return nil
}
var _ encoding.TextMarshaler = (*Constraint)(nil)
// MarshalText encodes the receiver into UTF-8-encoded text and returns the result.
func (c *Constraint) MarshalText() (text []byte, err error) {
return []byte(c.String()), nil
}
// MatchConstraintWithAtLeastOneTag tests a constraint for one single service
func (c *Constraint) MatchConstraintWithAtLeastOneTag(tags []string) bool {
for _, tag := range tags {
if glob.Glob(c.Regex, tag) {
return true
}
}
return false
}
// Set []*Constraint
func (cs *Constraints) Set(str string) error {
exps := strings.Split(str, ",")
if len(exps) == 0 {
return fmt.Errorf("bad Constraint format: %s", str)
}
for _, exp := range exps {
constraint, err := NewConstraint(exp)
if err != nil {
return err
}
*cs = append(*cs, constraint)
}
return nil
}
// Constraints holds a Constraint parser
type Constraints []*Constraint
// Get []*Constraint
func (cs *Constraints) Get() interface{} { return []*Constraint(*cs) }
// String returns []*Constraint in string
func (cs *Constraints) String() string { return fmt.Sprintf("%+v", *cs) }
// SetValue sets []*Constraint into the parser
func (cs *Constraints) SetValue(val interface{}) {
*cs = val.(Constraints)
}
// Type exports the Constraints type as a string
func (cs *Constraints) Type() string {
return "constraint"
}
// Store holds KV store cluster config
type Store struct {
store.Store
// like this "prefix" (without the /)
Prefix string `export:"true"`
}
// Cluster holds cluster config
type Cluster struct {
Node string `description:"Node name" export:"true"`
Store *Store `export:"true"`
}
// Auth holds authentication configuration (BASIC, DIGEST, users)
type Auth struct {
Basic *Basic `json:"basic,omitempty" export:"true"`
Digest *Digest `json:"digest,omitempty" export:"true"`
Forward *Forward `json:"forward,omitempty" export:"true"`
HeaderField string `json:"headerField,omitempty" export:"true"`
}
// Users authentication users
type Users []string
// Basic HTTP basic authentication
type Basic struct {
Realm string `json:"realm,omitempty"`
Users `json:"users,omitempty" mapstructure:","`
UsersFile string `json:"usersFile,omitempty"`
RemoveHeader bool `json:"removeHeader,omitempty"`
}
// Digest HTTP authentication
type Digest struct {
Users `json:"users,omitempty" mapstructure:","`
UsersFile string `json:"usersFile,omitempty"`
RemoveHeader bool `json:"removeHeader,omitempty"`
}
// Forward authentication
type Forward struct {
Address string `description:"Authentication server address" json:"address,omitempty"`
TLS *ClientTLS `description:"Enable TLS support" json:"tls,omitempty" export:"true"`
TrustForwardHeader bool `description:"Trust X-Forwarded-* headers" json:"trustForwardHeader,omitempty" export:"true"`
AuthResponseHeaders []string `description:"Headers to be forwarded from auth response" json:"authResponseHeaders,omitempty"`
}
// CanonicalDomain returns a lower case domain with trim space
func CanonicalDomain(domain string) string {
return strings.ToLower(strings.TrimSpace(domain))
}
// Statistics provides options for monitoring request and response stats
type Statistics struct {
RecentErrors int `description:"Number of recent errors logged" export:"true"`
}
// Metrics provides options to expose and send Traefik metrics to different third party monitoring systems
type Metrics struct {
Prometheus *Prometheus `description:"Prometheus metrics exporter type" export:"true"`
Datadog *Datadog `description:"DataDog metrics exporter type" export:"true"`
StatsD *Statsd `description:"StatsD metrics exporter type" export:"true"`
InfluxDB *InfluxDB `description:"InfluxDB metrics exporter type"`
}
// Prometheus can contain specific configuration used by the Prometheus Metrics exporter
type Prometheus struct {
Buckets Buckets `description:"Buckets for latency metrics" export:"true"`
EntryPoint string `description:"EntryPoint" export:"true"`
}
// Datadog contains address and metrics pushing interval configuration
type Datadog struct {
Address string `description:"DataDog's address"`
PushInterval string `description:"DataDog push interval" export:"true"`
}
// Statsd contains address and metrics pushing interval configuration
type Statsd struct {
Address string `description:"StatsD address"`
PushInterval string `description:"StatsD push interval" export:"true"`
}
// InfluxDB contains address, login and metrics pushing interval configuration
type InfluxDB struct {
Address string `description:"InfluxDB address"`
Protocol string `description:"InfluxDB address protocol (udp or http)"`
PushInterval string `description:"InfluxDB push interval" export:"true"`
Database string `description:"InfluxDB database used when protocol is http" export:"true"`
RetentionPolicy string `description:"InfluxDB retention policy used when protocol is http" export:"true"`
Username string `description:"InfluxDB username (only with http)" export:"true"`
Password string `description:"InfluxDB password (only with http)" export:"true"`
}
// Buckets holds Prometheus Buckets
type Buckets []float64
// Set adds strings elem into the the parser
// it splits str on "," and ";" and apply ParseFloat to string
func (b *Buckets) Set(str string) error {
fargs := func(c rune) bool {
return c == ',' || c == ';'
}
// get function
slice := strings.FieldsFunc(str, fargs)
for _, bucket := range slice {
bu, err := strconv.ParseFloat(bucket, 64)
if err != nil {
return err
}
*b = append(*b, bu)
}
return nil
}
// Get []float64
func (b *Buckets) Get() interface{} { return *b }
// String return slice in a string
func (b *Buckets) String() string { return fmt.Sprintf("%v", *b) }
// SetValue sets []float64 into the parser
func (b *Buckets) SetValue(val interface{}) {
*b = val.(Buckets)
}
// ClientTLS holds TLS specific configurations as client
// CA, Cert and Key can be either path or file contents
type ClientTLS struct {
CA string `description:"TLS CA" json:"ca,omitempty"`
CAOptional bool `description:"TLS CA.Optional" json:"caOptional,omitempty"`
Cert string `description:"TLS cert" json:"cert,omitempty"`
Key string `description:"TLS key" json:"key,omitempty"`
InsecureSkipVerify bool `description:"TLS insecure skip verify" json:"insecureSkipVerify,omitempty"`
}
// CreateTLSConfig creates a TLS config from ClientTLS structures
func (clientTLS *ClientTLS) CreateTLSConfig() (*tls.Config, error) {
var err error
if clientTLS == nil {
log.Warnf("clientTLS is nil")
return nil, nil
}
caPool := x509.NewCertPool()
clientAuth := tls.NoClientCert
if clientTLS.CA != "" {
var ca []byte
if _, errCA := os.Stat(clientTLS.CA); errCA == nil {
ca, err = ioutil.ReadFile(clientTLS.CA)
if err != nil {
return nil, fmt.Errorf("failed to read CA. %s", err)
}
} else {
ca = []byte(clientTLS.CA)
}
if !caPool.AppendCertsFromPEM(ca) {
return nil, fmt.Errorf("failed to parse CA")
}
if clientTLS.CAOptional {
clientAuth = tls.VerifyClientCertIfGiven
} else {
clientAuth = tls.RequireAndVerifyClientCert
}
}
cert := tls.Certificate{}
_, errKeyIsFile := os.Stat(clientTLS.Key)
if !clientTLS.InsecureSkipVerify && (len(clientTLS.Cert) == 0 || len(clientTLS.Key) == 0) {
return nil, fmt.Errorf("TLS Certificate or Key file must be set when TLS configuration is created")
}
if len(clientTLS.Cert) > 0 && len(clientTLS.Key) > 0 {
if _, errCertIsFile := os.Stat(clientTLS.Cert); errCertIsFile == nil {
if errKeyIsFile == nil {
cert, err = tls.LoadX509KeyPair(clientTLS.Cert, clientTLS.Key)
if err != nil {
return nil, fmt.Errorf("failed to load TLS keypair: %v", err)
}
} else {
return nil, fmt.Errorf("tls cert is a file, but tls key is not")
}
} else {
if errKeyIsFile != nil {
cert, err = tls.X509KeyPair([]byte(clientTLS.Cert), []byte(clientTLS.Key))
if err != nil {
return nil, fmt.Errorf("failed to load TLS keypair: %v", err)
}
} else {
return nil, fmt.Errorf("TLS key is a file, but tls cert is not")
}
}
}
TLSConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caPool,
InsecureSkipVerify: clientTLS.InsecureSkipVerify,
ClientAuth: clientAuth,
}
return TLSConfig, nil
}
// HTTPCodeRanges holds HTTP code ranges
type HTTPCodeRanges [][2]int
// NewHTTPCodeRanges creates HTTPCodeRanges from a given []string.
// Break out the http status code ranges into a low int and high int
// for ease of use at runtime
func NewHTTPCodeRanges(strBlocks []string) (HTTPCodeRanges, error) {
var blocks HTTPCodeRanges
for _, block := range strBlocks {
codes := strings.Split(block, "-")
// if only a single HTTP code was configured, assume the best and create the correct configuration on the user's behalf
if len(codes) == 1 {
codes = append(codes, codes[0])
}
lowCode, err := strconv.Atoi(codes[0])
if err != nil {
return nil, err
}
highCode, err := strconv.Atoi(codes[1])
if err != nil {
return nil, err
}
blocks = append(blocks, [2]int{lowCode, highCode})
}
return blocks, nil
}
// Contains tests whether the passed status code is within
// one of its HTTP code ranges.
func (h HTTPCodeRanges) Contains(statusCode int) bool {
for _, block := range h {
if statusCode >= block[0] && statusCode <= block[1] {
return true
}
}
return false
}
// IPStrategy Configuration to choose the IP selection strategy.
type IPStrategy struct {
Depth int `json:"depth,omitempty" export:"true"`
ExcludedIPs []string `json:"excludedIPs,omitempty"`
}
// Get an IP selection strategy
// if nil return the RemoteAddr strategy
// else return a strategy base on the configuration using the X-Forwarded-For Header.
// Depth override the ExcludedIPs
func (s *IPStrategy) Get() (ip.Strategy, error) {
if s == nil {
return &ip.RemoteAddrStrategy{}, nil
}
if s.Depth > 0 {
return &ip.DepthStrategy{
Depth: s.Depth,
}, nil
}
if len(s.ExcludedIPs) > 0 {
checker, err := ip.NewChecker(s.ExcludedIPs)
if err != nil {
return nil, err
}
return &ip.CheckerStrategy{
Checker: checker,
}, nil
}
return &ip.RemoteAddrStrategy{}, nil
}
// TLSClientHeaders holds the TLS client cert headers configuration.
type TLSClientHeaders struct {
PEM bool `description:"Enable header with escaped client pem" json:"pem"`
Infos *TLSClientCertificateInfos `description:"Enable header with configured client cert infos" json:"infos,omitempty"`
}
// TLSClientCertificateInfos holds the client TLS certificate infos configuration
type TLSClientCertificateInfos struct {
NotAfter bool `description:"Add NotAfter info in header" json:"notAfter"`
NotBefore bool `description:"Add NotBefore info in header" json:"notBefore"`
Subject *TLSCLientCertificateSubjectInfos `description:"Add Subject info in header" json:"subject,omitempty"`
Sans bool `description:"Add Sans info in header" json:"sans"`
}
// TLSCLientCertificateSubjectInfos holds the client TLS certificate subject infos configuration
type TLSCLientCertificateSubjectInfos struct {
Country bool `description:"Add Country info in header" json:"country"`
Province bool `description:"Add Province info in header" json:"province"`
Locality bool `description:"Add Locality info in header" json:"locality"`
Organization bool `description:"Add Organization info in header" json:"organization"`
CommonName bool `description:"Add CommonName info in header" json:"commonName"`
SerialNumber bool `description:"Add SerialNumber info in header" json:"serialNumber"`
}

186
old/types/types_test.go Normal file
View file

@ -0,0 +1,186 @@
package types
import (
"fmt"
"testing"
"github.com/containous/traefik/ip"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestHeaders_ShouldReturnFalseWhenNotHasCustomHeadersDefined(t *testing.T) {
headers := Headers{}
assert.False(t, headers.HasCustomHeadersDefined())
}
func TestHeaders_ShouldReturnTrueWhenHasCustomHeadersDefined(t *testing.T) {
headers := Headers{}
headers.CustomRequestHeaders = map[string]string{
"foo": "bar",
}
assert.True(t, headers.HasCustomHeadersDefined())
}
func TestHeaders_ShouldReturnFalseWhenNotHasSecureHeadersDefined(t *testing.T) {
headers := Headers{}
assert.False(t, headers.HasSecureHeadersDefined())
}
func TestHeaders_ShouldReturnTrueWhenHasSecureHeadersDefined(t *testing.T) {
headers := Headers{}
headers.SSLRedirect = true
assert.True(t, headers.HasSecureHeadersDefined())
}
func TestNewHTTPCodeRanges(t *testing.T) {
testCases := []struct {
desc string
strBlocks []string
expected HTTPCodeRanges
errExpected bool
}{
{
desc: "Should return 2 code range",
strBlocks: []string{
"200-500",
"502",
},
expected: HTTPCodeRanges{[2]int{200, 500}, [2]int{502, 502}},
errExpected: false,
},
{
desc: "Should return 2 code range",
strBlocks: []string{
"200-500",
"205",
},
expected: HTTPCodeRanges{[2]int{200, 500}, [2]int{205, 205}},
errExpected: false,
},
{
desc: "invalid code range",
strBlocks: []string{
"200-500",
"aaa",
},
expected: nil,
errExpected: true,
},
{
desc: "invalid code range nil",
strBlocks: nil,
expected: nil,
errExpected: false,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
actual, err := NewHTTPCodeRanges(test.strBlocks)
assert.Equal(t, test.expected, actual)
if test.errExpected {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestHTTPCodeRanges_Contains(t *testing.T) {
testCases := []struct {
strBlocks []string
statusCode int
contains bool
}{
{
strBlocks: []string{"200-299"},
statusCode: 200,
contains: true,
},
{
strBlocks: []string{"200"},
statusCode: 200,
contains: true,
},
{
strBlocks: []string{"201"},
statusCode: 200,
contains: false,
},
{
strBlocks: []string{"200-299", "500-599"},
statusCode: 400,
contains: false,
},
}
for _, test := range testCases {
test := test
testName := fmt.Sprintf("%q contains %d", test.strBlocks, test.statusCode)
t.Run(testName, func(t *testing.T) {
t.Parallel()
httpCodeRanges, err := NewHTTPCodeRanges(test.strBlocks)
assert.NoError(t, err)
assert.Equal(t, test.contains, httpCodeRanges.Contains(test.statusCode))
})
}
}
func TestIPStrategy_Get(t *testing.T) {
testCases := []struct {
desc string
ipStrategy *IPStrategy
expected ip.Strategy
}{
{
desc: "IPStrategy is nil",
expected: &ip.RemoteAddrStrategy{},
},
{
desc: "IPStrategy is not nil but with no values",
ipStrategy: &IPStrategy{},
expected: &ip.RemoteAddrStrategy{},
},
{
desc: "IPStrategy with Depth",
ipStrategy: &IPStrategy{Depth: 3},
expected: &ip.DepthStrategy{},
},
{
desc: "IPStrategy with ExcludedIPs",
ipStrategy: &IPStrategy{ExcludedIPs: []string{"10.0.0.1"}},
expected: &ip.CheckerStrategy{},
},
{
desc: "IPStrategy with ExcludedIPs and Depth",
ipStrategy: &IPStrategy{Depth: 4, ExcludedIPs: []string{"10.0.0.1"}},
expected: &ip.DepthStrategy{},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
strategy, err := test.ipStrategy.Get()
require.NoError(t, err)
assert.IsType(t, test.expected, strategy)
})
}
}