Skip to content

Commit

Permalink
multiple frontends for consulcatalog
Browse files Browse the repository at this point in the history
  • Loading branch information
hsmade authored and traefiker committed Aug 27, 2018
1 parent a302731 commit f586950
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 4 deletions.
11 changes: 11 additions & 0 deletions docs/configuration/backends/consulcatalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,17 @@ Additional settings can be defined using Consul Catalog tags.
| `<prefix>.frontend.whiteList.sourceRange=RANGE` | Sets a list of IP-Ranges which are allowed to access.<br>An unset or empty list allows all Source-IPs to access. If one of the Net-Specifications are invalid, the whole list is invalid and allows all Source-IPs to access. |
| `<prefix>.frontend.whiteList.useXForwardedFor=true` | Uses `X-Forwarded-For` header as valid source of IP for the white list. |

### Multiple frontends for a single service

If you need to support multiple frontends for a service, for example when having multiple `rules` that can't be combined, specify them as follows:

```
<prefix>.frontends.A.rule=Host:A:PathPrefix:/A
<prefix>.frontends.B.rule=Host:B:PathPrefix:/
```

`A` and `B` here are just arbitrary names, they can be anything. You can use any setting that applies to `<prefix>.frontend` from the table above.

### Custom Headers

!!! note
Expand Down
46 changes: 46 additions & 0 deletions integration/consul_catalog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -674,3 +674,49 @@ func (s *ConsulCatalogSuite) TestMaintenanceMode(c *check.C) {
err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil)
}

func (s *ConsulCatalogSuite) TestMultipleFrontendRule(c *check.C) {
cmd, display := s.traefikCmd(
withConfigFile("fixtures/consul_catalog/simple.toml"),
"--consulCatalog",
"--consulCatalog.endpoint="+s.consulIP+":8500",
"--consulCatalog.domain=consul.localhost")
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer cmd.Process.Kill()

// Wait for Traefik to turn ready.
err = try.GetRequest("http://127.0.0.1:8000/", 2*time.Second, try.StatusCodeIs(http.StatusNotFound))
c.Assert(err, checker.IsNil)

whoami := s.composeProject.Container(c, "whoami1")

err = s.registerService("test", whoami.NetworkSettings.IPAddress, 80,
[]string{
"traefik.frontends.service1.rule=Host:whoami1.consul.localhost",
"traefik.frontends.service2.rule=Host:whoami2.consul.localhost",
})
c.Assert(err, checker.IsNil, check.Commentf("Error registering service"))

req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil)
req.Host = "test.consul.localhost"

err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil)

req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil)
req.Host = "whoami1.consul.localhost"

err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil)

req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
c.Assert(err, checker.IsNil)
req.Host = "whoami2.consul.localhost"

err = try.Request(req, 10*time.Second, try.StatusCodeIs(http.StatusOK), try.HasBody())
c.Assert(err, checker.IsNil)
}
5 changes: 4 additions & 1 deletion provider/consulcatalog/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (p *Provider) buildConfigurationV2(catalog []catalogUpdate) *types.Configur
var services []*serviceUpdate
for _, info := range catalog {
if len(info.Nodes) > 0 {
services = append(services, info.Service)
services = append(services, p.generateFrontends(info.Service)...)
allNodes = append(allNodes, info.Nodes...)
}
}
Expand Down Expand Up @@ -161,6 +161,9 @@ func getCircuitBreaker(labels map[string]string) *types.CircuitBreaker {
}

func getServiceBackendName(service *serviceUpdate) string {
if service.ParentServiceName != "" {
return strings.ToLower(service.ParentServiceName)
}
return strings.ToLower(service.ServiceName)
}

Expand Down
74 changes: 74 additions & 0 deletions provider/consulcatalog/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,80 @@ func TestProviderBuildConfiguration(t *testing.T) {
},
},
},
{
desc: "Should build config which contains three frontends and one backend",
nodes: []catalogUpdate{
{
Service: &serviceUpdate{
ServiceName: "test",
Attributes: []string{
"random.foo=bar",
label.Prefix + "frontend.rule=Host:A",
label.Prefix + "frontends.test1.rule=Host:B",
label.Prefix + "frontends.test2.rule=Host:C",
},
},
Nodes: []*api.ServiceEntry{
{
Service: &api.AgentService{
Service: "test",
Address: "127.0.0.1",
Port: 80,
Tags: []string{
"random.foo=bar",
},
},
Node: &api.Node{
Node: "localhost",
Address: "127.0.0.1",
},
},
},
},
},
expectedFrontends: map[string]*types.Frontend{
"frontend-test": {
Backend: "backend-test",
PassHostHeader: true,
Routes: map[string]types.Route{
"route-host-test": {
Rule: "Host:A",
},
},
EntryPoints: []string{},
},
"frontend-test-test1": {
Backend: "backend-test",
PassHostHeader: true,
Routes: map[string]types.Route{
"route-host-test-test1": {
Rule: "Host:B",
},
},
EntryPoints: []string{},
},
"frontend-test-test2": {
Backend: "backend-test",
PassHostHeader: true,
Routes: map[string]types.Route{
"route-host-test-test2": {
Rule: "Host:C",
},
},
EntryPoints: []string{},
},
},
expectedBackends: map[string]*types.Backend{
"backend-test": {
Servers: map[string]types.Server{
"test-0-O0Tnh-SwzY69M6SurTKP3wNKkzI": {
URL: "http://127.0.0.1:80",
Weight: 1,
},
},
},
},
},
{
desc: "Should build config with a basic auth with a backward compatibility",
nodes: []catalogUpdate{
Expand Down
61 changes: 58 additions & 3 deletions provider/consulcatalog/consul_catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,15 @@ type Service struct {
}

type serviceUpdate struct {
ServiceName string
Attributes []string
TraefikLabels map[string]string
ServiceName string
ParentServiceName string
Attributes []string
TraefikLabels map[string]string
}

type frontendSegment struct {
Name string
Labels map[string]string
}

type catalogUpdate struct {
Expand Down Expand Up @@ -560,3 +566,52 @@ func (p *Provider) getConstraintTags(tags []string) []string {

return values
}

func (p *Provider) generateFrontends(service *serviceUpdate) []*serviceUpdate {
frontends := make([]*serviceUpdate, 0)
// to support <prefix>.frontend.xxx
frontends = append(frontends, &serviceUpdate{
ServiceName: service.ServiceName,
ParentServiceName: service.ServiceName,
Attributes: service.Attributes,
TraefikLabels: service.TraefikLabels,
})

// loop over children of <prefix>.frontends.*
for _, frontend := range getSegments(p.Prefix+".frontends", p.Prefix, service.TraefikLabels) {
frontends = append(frontends, &serviceUpdate{
ServiceName: service.ServiceName + "-" + frontend.Name,
ParentServiceName: service.ServiceName,
Attributes: service.Attributes,
TraefikLabels: frontend.Labels,
})
}

return frontends
}
func getSegments(path string, prefix string, tree map[string]string) []*frontendSegment {
segments := make([]*frontendSegment, 0)
// find segment names
segmentNames := make(map[string]bool)
for key := range tree {
if strings.HasPrefix(key, path+".") {
segmentNames[strings.SplitN(strings.TrimPrefix(key, path+"."), ".", 2)[0]] = true
}
}

// get labels for each segment found
for segment := range segmentNames {
labels := make(map[string]string)
for key, value := range tree {
if strings.HasPrefix(key, path+"."+segment) {
labels[prefix+".frontend"+strings.TrimPrefix(key, path+"."+segment)] = value
}
}
segments = append(segments, &frontendSegment{
Name: segment,
Labels: labels,
})
}

return segments
}

0 comments on commit f586950

Please sign in to comment.