Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ This default can be changed with labels:
- `reproxy.auth` - require basic auth for the route with comma-separated `user:bcrypt_hash` pairs (generated by `htpasswd -nbB`)
- `reproxy.assets` - set assets mapping as `web-root:location`, for example `reproxy.assets=/web:/var/www`
- `reproxy.keep-host` - keep host header as is (`yes`, `true`, `1`) or replace with destination host (`no`, `false`, `0`)
- `reproxy.forward-health-checks` - forward `/ping` and `/health` requests to the backend instead of reproxy handling them (`yes`, `true`, `1`). Useful when the backend has its own health check endpoints with application-specific responses.
- `reproxy.enabled` - enable (`yes`, `true`, `1`) or disable (`no`, `false`, `0`) container from reproxy destinations.

Pls note: without `--docker.auto` the destination container has to have at least one of `reproxy.*` labels to be considered as a potential destination.
Expand Down Expand Up @@ -172,6 +173,7 @@ This default can be changed with tags:
- `reproxy.remote` - restrict access to the route with a list of comma-separated subnets or ips
- `reproxy.auth` - require basic auth for the route with comma-separated `user:bcrypt_hash` pairs (generated by `htpasswd -nbB`)
- `reproxy.ping` - ping path for the destination service.
- `reproxy.forward-health-checks` - forward `/ping` and `/health` requests to the backend (`true`, `yes`, `1`).
- `reproxy.enabled` - enable (`yes`, `true`, `1`) or disable (`any different value`) service from reproxy destinations.

### Compose-specific details
Expand Down
32 changes: 17 additions & 15 deletions app/discovery/discovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ type URLMapper struct {
PingURL string
MatchType MatchType
RedirectType RedirectType
KeepHost *bool
OnlyFromIPs []string
AuthUsers []string // basic auth credentials as user:bcrypt_hash pairs
KeepHost *bool
ForwardHealthChecks bool
OnlyFromIPs []string
AuthUsers []string // basic auth credentials as user:bcrypt_hash pairs

AssetsLocation string // local FS root location
AssetsWebRoot string // web root location
Expand Down Expand Up @@ -460,18 +461,19 @@ func (s *Service) extendMapper(m URLMapper) URLMapper {
}

res := URLMapper{
Server: m.Server,
Dst: strings.TrimSuffix(m.Dst, "/") + "/$1",
ProviderID: m.ProviderID,
PingURL: m.PingURL,
MatchType: m.MatchType,
AssetsWebRoot: m.AssetsWebRoot,
AssetsLocation: m.AssetsLocation,
AssetsSPA: m.AssetsSPA,
RedirectType: m.RedirectType,
KeepHost: m.KeepHost,
OnlyFromIPs: m.OnlyFromIPs,
AuthUsers: m.AuthUsers,
Server: m.Server,
Dst: strings.TrimSuffix(m.Dst, "/") + "/$1",
ProviderID: m.ProviderID,
PingURL: m.PingURL,
MatchType: m.MatchType,
AssetsWebRoot: m.AssetsWebRoot,
AssetsLocation: m.AssetsLocation,
AssetsSPA: m.AssetsSPA,
RedirectType: m.RedirectType,
KeepHost: m.KeepHost,
ForwardHealthChecks: m.ForwardHealthChecks,
OnlyFromIPs: m.OnlyFromIPs,
AuthUsers: m.AuthUsers,
}
rx, err := regexp.Compile("^" + strings.TrimSuffix(src, "/") + "/(.*)")
if err != nil {
Expand Down
8 changes: 7 additions & 1 deletion app/discovery/provider/consulcatalog/consulcatalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ func (cc *ConsulCatalog) List() ([]discovery.URLMapper, error) {
pingURL := fmt.Sprintf("http://%s:%d/ping", c.ServiceAddress, c.ServicePort)
server := "*"
var keepHost *bool
forwardHealthChecks := false
onlyFrom := []string{}

if v, ok := c.Labels["reproxy.enabled"]; ok && (v == "true" || v == "yes" || v == "1") {
Expand Down Expand Up @@ -191,6 +192,10 @@ func (cc *ConsulCatalog) List() ([]discovery.URLMapper, error) {
}
}

if v, ok := c.Labels["reproxy.forward-health-checks"]; ok && (v == "true" || v == "yes" || v == "1") {
forwardHealthChecks = true
}

if !enabled {
log.Printf("[DEBUG] service %s disabled", c.ServiceID)
continue
Expand All @@ -204,7 +209,8 @@ func (cc *ConsulCatalog) List() ([]discovery.URLMapper, error) {
// server label may have multiple, comma separated servers
for srv := range strings.SplitSeq(server, ",") {
res = append(res, discovery.URLMapper{Server: strings.TrimSpace(srv), SrcMatch: *srcRegex, Dst: destURL,
PingURL: pingURL, ProviderID: discovery.PIConsulCatalog, KeepHost: keepHost, OnlyFromIPs: onlyFrom, AuthUsers: authUsers})
PingURL: pingURL, ProviderID: discovery.PIConsulCatalog, KeepHost: keepHost,
ForwardHealthChecks: forwardHealthChecks, OnlyFromIPs: onlyFrom, AuthUsers: authUsers})
}
}

Expand Down
43 changes: 43 additions & 0 deletions app/discovery/provider/consulcatalog/consulcatalog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,49 @@ func TestConsulCatalog_List(t *testing.T) {
assert.Equal(t, "*", res[6].Server)
assert.Equal(t, &fa, res[6].KeepHost)
assert.Equal(t, []string{}, res[6].AuthUsers)

for i := range 7 {
assert.False(t, res[i].ForwardHealthChecks, "route %d should not have forward-health-checks", i)
}
}

func TestConsulCatalog_ListForwardHealthChecks(t *testing.T) {
clientMock := &ConsulClientMock{GetFunc: func() ([]consulService, error) {
return []consulService{
{
ServiceID: "fhc1",
ServiceName: "fhcService",
ServiceAddress: "addr-fhc",
ServicePort: 9000,
Labels: map[string]string{
"reproxy.enabled": "true",
"reproxy.server": "fhc.example.com",
"reproxy.forward-health-checks": "true",
},
},
{
ServiceID: "nofhc",
ServiceName: "noFhcService",
ServiceAddress: "addr-nofhc",
ServicePort: 9001,
Labels: map[string]string{
"reproxy.enabled": "true",
},
},
}, nil
}}

cc := &ConsulCatalog{client: clientMock}
res, err := cc.List()
require.NoError(t, err)
require.Len(t, res, 2)

fhcByServer := map[string]bool{}
for _, r := range res {
fhcByServer[r.Server] = r.ForwardHealthChecks
}
assert.True(t, fhcByServer["fhc.example.com"])
assert.False(t, fhcByServer["*"])
}

func TestConsulCatalog_serviceListWasChanged(t *testing.T) {
Expand Down
7 changes: 6 additions & 1 deletion app/discovery/provider/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ func (d *Docker) parseContainerInfo(c containerInfo) (res []discovery.URLMapper)
// defaults
destURL, pingURL, server := fmt.Sprintf("http://%s:%d/$1", c.IP, port), fmt.Sprintf("http://%s:%d/ping", c.IP, port), "*"
assetsWebRoot, assetsLocation, assetsSPA := "", "", false
forwardHealthChecks := false
onlyFrom := []string{}

if d.AutoAPI && n == 0 {
Expand Down Expand Up @@ -172,6 +173,10 @@ func (d *Docker) parseContainerInfo(c containerInfo) (res []discovery.URLMapper)

keepHost := d.getKeepHostValue(c.Labels, n)

if _, ok := d.labelN(c.Labels, n, "forward-health-checks"); ok {
forwardHealthChecks = true
}

if !enabled {
continue
}
Expand All @@ -186,7 +191,7 @@ func (d *Docker) parseContainerInfo(c containerInfo) (res []discovery.URLMapper)
for srv := range strings.SplitSeq(server, ",") {
mp := discovery.URLMapper{Server: strings.TrimSpace(srv), SrcMatch: *srcRegex, Dst: destURL,
PingURL: pingURL, ProviderID: discovery.PIDocker, MatchType: discovery.MTProxy,
KeepHost: keepHost, OnlyFromIPs: onlyFrom, AuthUsers: authUsers}
KeepHost: keepHost, ForwardHealthChecks: forwardHealthChecks, OnlyFromIPs: onlyFrom, AuthUsers: authUsers}

// for assets we add the second proxy mapping only if explicitly requested
if assetsWebRoot != "" && explicit {
Expand Down
59 changes: 59 additions & 0 deletions app/discovery/provider/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,65 @@ func TestDocker_ListMulti(t *testing.T) {

assert.Equal(t, "^/kn/", res[7].SrcMatch.String())
assert.False(t, *res[7].KeepHost)

for i := range 8 {
assert.False(t, res[i].ForwardHealthChecks, "route %d should not have forward-health-checks", i)
}
}

func TestDocker_ListForwardHealthChecks(t *testing.T) {
dclient := &DockerClientMock{
ListContainersFunc: func() ([]containerInfo, error) {
return []containerInfo{
{
Name: "abs", State: "running", IP: "127.0.0.10", Ports: []int{8080},
Labels: map[string]string{
"reproxy.server": "abs.example.com",
"reproxy.route": "^/(.*)",
"reproxy.dest": "/$1",
"reproxy.forward-health-checks": "yes",
},
},
{
Name: "normal", State: "running", IP: "127.0.0.11", Ports: []int{8080},
Labels: map[string]string{
"reproxy.server": "normal.example.com",
"reproxy.route": "^/(.*)",
"reproxy.dest": "/$1",
},
},
{
Name: "multi", State: "running", IP: "127.0.0.12", Ports: []int{8080},
Labels: map[string]string{
"reproxy.0.route": "^/api/(.*)",
"reproxy.0.dest": "/api/$1",
"reproxy.1.route": "^/web/(.*)",
"reproxy.1.dest": "/web/$1",
"reproxy.1.forward-health-checks": "true",
},
},
}, nil
},
}

d := Docker{DockerClient: dclient}
res, err := d.List()
require.NoError(t, err)
require.Len(t, res, 4)

fhcByServer := map[string]bool{}
for _, r := range res {
fhcByServer[r.Server] = r.ForwardHealthChecks
}
assert.True(t, fhcByServer["abs.example.com"], "abs.example.com should have forward-health-checks")
assert.False(t, fhcByServer["normal.example.com"], "normal.example.com should not have forward-health-checks")

fhcByRoute := map[string]bool{}
for _, r := range res {
fhcByRoute[r.SrcMatch.String()] = r.ForwardHealthChecks
}
assert.False(t, fhcByRoute["^/api/(.*)"], "multi route 0 should not have forward-health-checks")
assert.True(t, fhcByRoute["^/web/(.*)"], "multi route 1 should have forward-health-checks")
}

func TestDocker_ListMultiFallBack(t *testing.T) {
Expand Down
32 changes: 17 additions & 15 deletions app/discovery/provider/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,13 @@ func (d *File) List() (res []discovery.URLMapper, err error) {
var fileConf map[string][]struct {
SourceRoute string `yaml:"route"`
Dest string `yaml:"dest"`
Ping string `yaml:"ping"`
AssetsEnabled bool `yaml:"assets"`
AssetsSPA bool `yaml:"spa"`
KeepHost *bool `yaml:"keep-host,omitempty"`
OnlyFrom string `yaml:"remote"`
Auth string `yaml:"auth"`
Ping string `yaml:"ping"`
AssetsEnabled bool `yaml:"assets"`
AssetsSPA bool `yaml:"spa"`
KeepHost *bool `yaml:"keep-host,omitempty"`
ForwardHealthChecks bool `yaml:"forward-health-checks"`
OnlyFrom string `yaml:"remote"`
Auth string `yaml:"auth"`
}
fh, err := os.Open(d.FileName)
if err != nil {
Expand All @@ -109,15 +110,16 @@ func (d *File) List() (res []discovery.URLMapper, err error) {
srv = "*"
}
mapper := discovery.URLMapper{
Server: srv,
SrcMatch: *rx,
Dst: f.Dest,
PingURL: f.Ping,
KeepHost: f.KeepHost,
ProviderID: discovery.PIFile,
MatchType: discovery.MTProxy,
OnlyFromIPs: discovery.ParseOnlyFrom(f.OnlyFrom),
AuthUsers: discovery.ParseAuth(f.Auth),
Server: srv,
SrcMatch: *rx,
Dst: f.Dest,
PingURL: f.Ping,
KeepHost: f.KeepHost,
ForwardHealthChecks: f.ForwardHealthChecks,
ProviderID: discovery.PIFile,
MatchType: discovery.MTProxy,
OnlyFromIPs: discovery.ParseOnlyFrom(f.OnlyFrom),
AuthUsers: discovery.ParseAuth(f.Auth),
}
if f.AssetsEnabled || f.AssetsSPA {
mapper.MatchType = discovery.MTStatic
Expand Down
Loading