test(constraint): unit tests + integration tests + make validate
This commit is contained in:
parent
cd2100ed84
commit
f46accc74d
7 changed files with 362 additions and 40 deletions
211
integration/constraint_test.go
Normal file
211
integration/constraint_test.go
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
//"io/ioutil"
|
||||||
|
//"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os/exec"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-check/check"
|
||||||
|
"github.com/hashicorp/consul/api"
|
||||||
|
|
||||||
|
checker "github.com/vdemeester/shakers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constraint test suite
|
||||||
|
type ConstraintSuite struct {
|
||||||
|
BaseSuite
|
||||||
|
consulIP string
|
||||||
|
consulClient *api.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) SetUpSuite(c *check.C) {
|
||||||
|
|
||||||
|
s.createComposeProject(c, "constraints")
|
||||||
|
s.composeProject.Start(c)
|
||||||
|
|
||||||
|
consul := s.composeProject.Container(c, "consul")
|
||||||
|
|
||||||
|
s.consulIP = consul.NetworkSettings.IPAddress
|
||||||
|
config := api.DefaultConfig()
|
||||||
|
config.Address = s.consulIP + ":8500"
|
||||||
|
consulClient, err := api.NewClient(config)
|
||||||
|
if err != nil {
|
||||||
|
c.Fatalf("Error creating consul client")
|
||||||
|
}
|
||||||
|
s.consulClient = consulClient
|
||||||
|
|
||||||
|
// Wait for consul to elect itself leader
|
||||||
|
time.Sleep(2000 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) registerService(name string, address string, port int, tags []string) error {
|
||||||
|
catalog := s.consulClient.Catalog()
|
||||||
|
_, err := catalog.Register(
|
||||||
|
&api.CatalogRegistration{
|
||||||
|
Node: address,
|
||||||
|
Address: address,
|
||||||
|
Service: &api.AgentService{
|
||||||
|
ID: name,
|
||||||
|
Service: name,
|
||||||
|
Address: address,
|
||||||
|
Port: port,
|
||||||
|
Tags: tags,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&api.WriteOptions{},
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) deregisterService(name string, address string) error {
|
||||||
|
catalog := s.consulClient.Catalog()
|
||||||
|
_, err := catalog.Deregister(
|
||||||
|
&api.CatalogDeregistration{
|
||||||
|
Node: address,
|
||||||
|
Address: address,
|
||||||
|
ServiceID: name,
|
||||||
|
},
|
||||||
|
&api.WriteOptions{},
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) TestMatchConstraintGlobal(c *check.C) {
|
||||||
|
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--constraints=tag==api")
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
nginx := s.composeProject.Container(c, "nginx")
|
||||||
|
|
||||||
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api"})
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
time.Sleep(5000 * time.Millisecond)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.consul.localhost"
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) TestDoesNotMatchConstraintGlobal(c *check.C) {
|
||||||
|
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--constraints=tag==api")
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
nginx := s.composeProject.Container(c, "nginx")
|
||||||
|
|
||||||
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
time.Sleep(5000 * time.Millisecond)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.consul.localhost"
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) TestMatchConstraintProvider(c *check.C) {
|
||||||
|
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api")
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
nginx := s.composeProject.Container(c, "nginx")
|
||||||
|
|
||||||
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api"})
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
time.Sleep(5000 * time.Millisecond)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.consul.localhost"
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) TestDoesNotMatchConstraintProvider(c *check.C) {
|
||||||
|
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api")
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
nginx := s.composeProject.Container(c, "nginx")
|
||||||
|
|
||||||
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{})
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
time.Sleep(5000 * time.Millisecond)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.consul.localhost"
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) TestMatchMultipleConstraint(c *check.C) {
|
||||||
|
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api", "--constraints=tag!=us-*")
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
nginx := s.composeProject.Container(c, "nginx")
|
||||||
|
|
||||||
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api", "traefik.tags=eu-1"})
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
time.Sleep(5000 * time.Millisecond)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.consul.localhost"
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(resp.StatusCode, checker.Equals, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ConstraintSuite) TestDoesNotMatchMultipleConstraint(c *check.C) {
|
||||||
|
cmd := exec.Command(traefikBinary, "--consulCatalog", "--consulCatalog.endpoint="+s.consulIP+":8500", "--consulCatalog.domain=consul.localhost", "--configFile=fixtures/consul_catalog/simple.toml", "--consulCatalog.constraints=tag==api", "--constraints=tag!=us-*")
|
||||||
|
err := cmd.Start()
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
defer cmd.Process.Kill()
|
||||||
|
|
||||||
|
nginx := s.composeProject.Container(c, "nginx")
|
||||||
|
|
||||||
|
err = s.registerService("test", nginx.NetworkSettings.IPAddress, 80, []string{"traefik.tags=api", "traefik.tags=us-1"})
|
||||||
|
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))
|
||||||
|
defer s.deregisterService("test", nginx.NetworkSettings.IPAddress)
|
||||||
|
|
||||||
|
time.Sleep(5000 * time.Millisecond)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", "http://127.0.0.1:8000/", nil)
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
req.Host = "test.consul.localhost"
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
c.Assert(err, checker.IsNil)
|
||||||
|
c.Assert(resp.StatusCode, checker.Equals, 404)
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ func init() {
|
||||||
check.Suite(&ConsulCatalogSuite{})
|
check.Suite(&ConsulCatalogSuite{})
|
||||||
check.Suite(&EtcdSuite{})
|
check.Suite(&EtcdSuite{})
|
||||||
check.Suite(&MarathonSuite{})
|
check.Suite(&MarathonSuite{})
|
||||||
|
check.Suite(&ConstraintSuite{})
|
||||||
}
|
}
|
||||||
|
|
||||||
var traefikBinary = "../dist/traefik"
|
var traefikBinary = "../dist/traefik"
|
||||||
|
|
17
integration/resources/compose/constraints.yml
Normal file
17
integration/resources/compose/constraints.yml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
consul:
|
||||||
|
image: progrium/consul
|
||||||
|
command: -server -bootstrap -log-level debug -ui-dir /ui
|
||||||
|
ports:
|
||||||
|
- "8400:8400"
|
||||||
|
- "8500:8500"
|
||||||
|
- "8600:53/udp"
|
||||||
|
expose:
|
||||||
|
- "8300"
|
||||||
|
- "8301"
|
||||||
|
- "8301/udp"
|
||||||
|
- "8302"
|
||||||
|
- "8302/udp"
|
||||||
|
nginx:
|
||||||
|
image: nginx
|
||||||
|
ports:
|
||||||
|
- "8881:80"
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/ty/fun"
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/cenkalti/backoff"
|
"github.com/cenkalti/backoff"
|
||||||
"github.com/containous/traefik/safe"
|
"github.com/containous/traefik/safe"
|
||||||
|
@ -88,28 +89,22 @@ func (provider *ConsulCatalog) healthyNodes(service string) (catalogUpdate, erro
|
||||||
return catalogUpdate{}, err
|
return catalogUpdate{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
set := map[string]bool{}
|
nodes := fun.Filter(func(node *api.ServiceEntry) bool {
|
||||||
tags := []string{}
|
|
||||||
nodes := []*api.ServiceEntry{}
|
|
||||||
for _, node := range data {
|
|
||||||
constraintTags := provider.getContraintTags(node.Service.Tags)
|
constraintTags := provider.getContraintTags(node.Service.Tags)
|
||||||
if ok, failingConstraint, err := provider.MatchConstraints(constraintTags); err != nil {
|
ok, failingConstraint := provider.MatchConstraints(constraintTags)
|
||||||
return catalogUpdate{}, err
|
if ok == false && failingConstraint != nil {
|
||||||
} else if ok == true {
|
|
||||||
nodes = append(nodes, node)
|
|
||||||
// merge tags of every nodes in a single slice
|
|
||||||
// only if node match constraint
|
|
||||||
for _, tag := range node.Service.Tags {
|
|
||||||
if _, ok := set[tag]; ok == false {
|
|
||||||
set[tag] = true
|
|
||||||
tags = append(tags, tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if ok == false && failingConstraint != nil {
|
|
||||||
log.Debugf("Service %v pruned by '%v' constraint", service, failingConstraint.String())
|
log.Debugf("Service %v pruned by '%v' constraint", service, failingConstraint.String())
|
||||||
}
|
}
|
||||||
|
return ok
|
||||||
|
}, data).([]*api.ServiceEntry)
|
||||||
|
|
||||||
}
|
//Merge tags of nodes matching constraints, in a single slice.
|
||||||
|
tags := fun.Foldl(func(node *api.ServiceEntry, set []string) []string {
|
||||||
|
return fun.Keys(fun.Union(
|
||||||
|
fun.Set(set),
|
||||||
|
fun.Set(node.Service.Tags),
|
||||||
|
).(map[string]bool)).([]string)
|
||||||
|
}, []string{}, nodes).([]string)
|
||||||
|
|
||||||
return catalogUpdate{
|
return catalogUpdate{
|
||||||
Service: &serviceUpdate{
|
Service: &serviceUpdate{
|
||||||
|
|
|
@ -29,22 +29,21 @@ type BaseProvider struct {
|
||||||
|
|
||||||
// MatchConstraints must match with EVERY single contraint
|
// MatchConstraints must match with EVERY single contraint
|
||||||
// returns first constraint that do not match or nil
|
// returns first constraint that do not match or nil
|
||||||
// returns errors for future use (regex)
|
func (p *BaseProvider) MatchConstraints(tags []string) (bool, *types.Constraint) {
|
||||||
func (p *BaseProvider) MatchConstraints(tags []string) (bool, *types.Constraint, error) {
|
|
||||||
// if there is no tags and no contraints, filtering is disabled
|
// if there is no tags and no contraints, filtering is disabled
|
||||||
if len(tags) == 0 && len(p.Constraints) == 0 {
|
if len(tags) == 0 && len(p.Constraints) == 0 {
|
||||||
return true, nil, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, constraint := range p.Constraints {
|
for _, constraint := range p.Constraints {
|
||||||
if ok := constraint.MatchConstraintWithAtLeastOneTag(tags); xor(ok == true, constraint.MustMatch == true) {
|
// xor: if ok and constraint.MustMatch are equal, then no tag is currently matching with the constraint
|
||||||
return false, constraint, nil
|
if ok := constraint.MatchConstraintWithAtLeastOneTag(tags); ok != constraint.MustMatch {
|
||||||
|
return false, constraint
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no constraint or every constraints matching
|
// If no constraint or every constraints matching
|
||||||
return true, nil, nil
|
return true, nil
|
||||||
>>>>>>> e844462... feat(constraints): Implementation of constraints (cmd + toml + matching functions), implementation proposal with consul
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *BaseProvider) getConfiguration(defaultTemplateFile string, funcMap template.FuncMap, templateObjects interface{}) (*types.Configuration, error) {
|
func (p *BaseProvider) getConfiguration(defaultTemplateFile string, funcMap template.FuncMap, templateObjects interface{}) (*types.Configuration, error) {
|
||||||
|
@ -98,8 +97,3 @@ func normalize(name string) string {
|
||||||
// get function
|
// get function
|
||||||
return strings.Join(strings.FieldsFunc(name, fargs), "-")
|
return strings.Join(strings.FieldsFunc(name, fargs), "-")
|
||||||
}
|
}
|
||||||
|
|
||||||
// golang does not support ^ operator
|
|
||||||
func xor(cond1 bool, cond2 bool) bool {
|
|
||||||
return cond1 != cond2
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/containous/traefik/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type myProvider struct {
|
type myProvider struct {
|
||||||
|
@ -206,3 +208,100 @@ func TestGetConfigurationReturnsCorrectMaxConnConfiguration(t *testing.T) {
|
||||||
t.Fatalf("Configuration did not parse MaxConn.ExtractorFunc properly")
|
t.Fatalf("Configuration did not parse MaxConn.ExtractorFunc properly")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMatchingConstraints(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
constraints []*types.Constraint
|
||||||
|
tags []string
|
||||||
|
expected bool
|
||||||
|
}{
|
||||||
|
// simple test: must match
|
||||||
|
{
|
||||||
|
constraints: []*types.Constraint{
|
||||||
|
{
|
||||||
|
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.Constraint{
|
||||||
|
{
|
||||||
|
Key: "tag",
|
||||||
|
MustMatch: true,
|
||||||
|
Regex: "us-east-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: []string{
|
||||||
|
"us-east-2",
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
// simple test: must not match
|
||||||
|
{
|
||||||
|
constraints: []*types.Constraint{
|
||||||
|
{
|
||||||
|
Key: "tag",
|
||||||
|
MustMatch: false,
|
||||||
|
Regex: "us-east-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: []string{
|
||||||
|
"us-east-1",
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
// complex test: globbing
|
||||||
|
{
|
||||||
|
constraints: []*types.Constraint{
|
||||||
|
{
|
||||||
|
Key: "tag",
|
||||||
|
MustMatch: true,
|
||||||
|
Regex: "us-east-*",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: []string{
|
||||||
|
"us-east-1",
|
||||||
|
},
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
// complex test: multiple constraints
|
||||||
|
{
|
||||||
|
constraints: []*types.Constraint{
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
actual, _ := provider.MatchConstraints(c.tags)
|
||||||
|
if actual != c.expected {
|
||||||
|
t.Fatalf("test #%v: expected %q, got %q, for %q", i, c.expected, actual, c.constraints)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -106,6 +106,7 @@ type Constraint struct {
|
||||||
Regex string
|
Regex string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewConstraint receive a string and return a *Constraint, after checking syntax and parsing the constraint expression
|
||||||
func NewConstraint(exp string) (*Constraint, error) {
|
func NewConstraint(exp string) (*Constraint, error) {
|
||||||
sep := ""
|
sep := ""
|
||||||
constraint := &Constraint{}
|
constraint := &Constraint{}
|
||||||
|
@ -142,6 +143,7 @@ func (c *Constraint) String() string {
|
||||||
return c.Key + "!=" + c.Regex
|
return c.Key + "!=" + c.Regex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MatchConstraintWithAtLeastOneTag tests a constraint for one single service
|
||||||
func (c *Constraint) MatchConstraintWithAtLeastOneTag(tags []string) bool {
|
func (c *Constraint) MatchConstraintWithAtLeastOneTag(tags []string) bool {
|
||||||
for _, tag := range tags {
|
for _, tag := range tags {
|
||||||
if glob.Glob(c.Regex, tag) {
|
if glob.Glob(c.Regex, tag) {
|
||||||
|
@ -165,41 +167,44 @@ func StringToConstraintHookFunc() mapstructure.DecodeHookFunc {
|
||||||
return data, nil
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if constraint, err := NewConstraint(data.(string)); err != nil {
|
constraint, err := NewConstraint(data.(string))
|
||||||
|
if err != nil {
|
||||||
return data, err
|
return data, err
|
||||||
} else {
|
|
||||||
return constraint, nil
|
|
||||||
}
|
}
|
||||||
|
return constraint, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Constraints own a pointer on globalConfiguration.Constraints and supports a Set() method (not possible on a slice)
|
||||||
|
// interface:
|
||||||
type Constraints struct {
|
type Constraints struct {
|
||||||
value *[]*Constraint
|
value *[]*Constraint
|
||||||
changed bool
|
changed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command line
|
// Set receive a cli argument and add it to globalConfiguration
|
||||||
func (cs *Constraints) Set(value string) error {
|
func (cs *Constraints) Set(value string) error {
|
||||||
exps := strings.Split(value, ",")
|
exps := strings.Split(value, ",")
|
||||||
if len(exps) == 0 {
|
if len(exps) == 0 {
|
||||||
return errors.New("Bad Constraint format: " + value)
|
return errors.New("Bad Constraint format: " + value)
|
||||||
}
|
}
|
||||||
for _, exp := range exps {
|
for _, exp := range exps {
|
||||||
if constraint, err := NewConstraint(exp); err != nil {
|
constraint, err := NewConstraint(exp)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
|
||||||
*cs.value = append(*cs.value, constraint)
|
|
||||||
}
|
}
|
||||||
|
*cs.value = append(*cs.value, constraint)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Constraints) Type() string {
|
// Type exports the Constraints type as a string
|
||||||
|
func (cs *Constraints) Type() string {
|
||||||
return "constraints"
|
return "constraints"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Constraints) String() string {
|
func (cs *Constraints) String() string {
|
||||||
return fmt.Sprintln("%v", *c.value)
|
return fmt.Sprintln("%v", *cs.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConstraintSliceValue make an alias of []*Constraint to Constraints for the command line
|
// NewConstraintSliceValue make an alias of []*Constraint to Constraints for the command line
|
||||||
|
|
Loading…
Reference in a new issue