Skip to content

Commit 04330fe

Browse files
committed
addHeader & setHeader reconfigure params [fixed #177]
1 parent a73b00c commit 04330fe

File tree

9 files changed

+133
-36
lines changed

9 files changed

+133
-36
lines changed

.travis.yml

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ script:
2020
- docker-compose -f docker-compose-test.yml run --rm staging
2121
- docker-compose -f docker-compose-test.yml down
2222
- docker tag vfarcic/docker-flow-proxy vfarcic/docker-flow-proxy:beta
23-
- docker login -e $DOCKER_EMAIL -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
23+
- docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
2424
- docker push vfarcic/docker-flow-proxy:beta
2525
- HOST_IP=$(ifconfig eth0 | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1') docker-compose -f docker-compose-test.yml run --rm staging-swarm
2626

@@ -42,7 +42,3 @@ after_success:
4242
branches:
4343
only:
4444
- master
45-
46-
notifications:
47-
slack:
48-
secure: h5AN2Q3Ft3LYkBZWsEtQIVTZBmZYc/yGcqBcO7zCts2I4eTYCNPl419nkQiMbPBCL6s7mQv2bY2hA9mhm271ssc3JgoQ8A7yMoR+tlGWrdDwR7vhvs85o+GUiVM27sqCCNNz/pE/HU6F5h5h4vQAEBZzC1WGJkdxKQGNohDh+xWJMxB+SFynC5qtbVjXYiKwGf9EvAa7qWbJ7OgzqvU5QAdpUMa0CptEkNsGxgTF7onvx/6TYJTnDTmmiGlwkeo83895qbROxxUE6Az0lRa/4P8sAKpa4Gc+nxInk41KpZud5XW85lrs6Ncesh2TzIlac/RboE68zhP+MJgobzjDyDZdSnm3tRH8k5vK+2FvaqOtWEFink0H42n96rcuGKVeGe56TJRoHMou5H1qWwc8caIJm4yLRR4kwed+Ao73iQLnSfQeTCEk/WUUm7a4JaChR55EXnRfI5gcd54C+ONCT6JnvCGRhRnPClFiGgQ274D4QUCgm5nLm1+XVReTUo/us9L1fraAQtc/UMTbAsD+1MFddSYRmE8pMh0FZfYWqf8UPlVvmTjtSOPK8g+WoCdi4qqhnBJapmNuSMRrhf7EYxzvFbFG5EBKdSDCgNffYtOMQSwWY09IaufyJFQYjVlCuT+tYi1unzlOJmFWps+qxl/CcoB6guM+YNj0vqFbApw=

actions/reconfigure.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -339,11 +339,7 @@ backend %s{{$.ServiceName}}-be{{.Port}}
339339
tmpl += `
340340
log global`
341341
}
342-
if sr.XForwardedProto {
343-
tmpl += `
344-
http-request add-header X-Forwarded-Proto https if { ssl_fc }`
345-
}
346-
// TODO: Deprecated (dec. 2016).
342+
tmpl += m.getHeaders(sr)
347343
if len(sr.TimeoutServer) > 0 {
348344
tmpl += `
349345
timeout server {{$.TimeoutServer}}s`
@@ -352,6 +348,7 @@ backend %s{{$.ServiceName}}-be{{.Port}}
352348
tmpl += `
353349
timeout tunnel {{$.TimeoutTunnel}}s`
354350
}
351+
// TODO: Deprecated (dec. 2016).
355352
if len(sr.ReqRepSearch) > 0 && len(sr.ReqRepReplace) > 0 {
356353
tmpl += `
357354
reqrep {{$.ReqRepSearch}} {{$.ReqRepReplace}}`
@@ -389,6 +386,27 @@ backend %s{{$.ServiceName}}-be{{.Port}}
389386
return tmpl
390387
}
391388

389+
func (m *Reconfigure) getHeaders(sr *proxy.Service) string {
390+
tmpl := ""
391+
if sr.XForwardedProto {
392+
tmpl += `
393+
http-request add-header X-Forwarded-Proto https if { ssl_fc }`
394+
}
395+
for _, header := range sr.AddHeader {
396+
tmpl += fmt.Sprintf(`
397+
http-request add-header %s`,
398+
header,
399+
)
400+
}
401+
for _, header := range sr.SetHeader {
402+
tmpl += fmt.Sprintf(`
403+
http-request set-header %s`,
404+
header,
405+
)
406+
}
407+
return tmpl
408+
}
409+
392410
func (m *Reconfigure) getUsersList(sr *proxy.Service) string {
393411
if len(sr.Users) > 0 {
394412
return `userlist {{.ServiceName}}Users{{range .Users}}

actions/reconfigure_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,70 @@ backend %s-be%s
657657
s.Equal(expectedData, actualData)
658658
}
659659

660+
func (s ReconfigureTestSuite) Test_Execute_AddsHeader_WhenAddHeaderIsSet() {
661+
s.reconfigure.Mode = "swarm"
662+
s.reconfigure.AddHeader = []string{"header-1", "header-2"}
663+
var actualFilename, actualData string
664+
expectedFilename := fmt.Sprintf("%s/%s-be.cfg", s.TemplatesPath, s.ServiceName)
665+
expectedData := fmt.Sprintf(
666+
`
667+
backend %s-be%s
668+
mode http
669+
http-request add-header header-1
670+
http-request add-header header-2
671+
server %s %s:%s`,
672+
s.ServiceName,
673+
s.reconfigure.ServiceDest[0].Port,
674+
s.ServiceName,
675+
s.ServiceName,
676+
s.reconfigure.ServiceDest[0].Port,
677+
)
678+
writeBeTemplateOrig := writeBeTemplate
679+
defer func() { writeBeTemplate = writeBeTemplateOrig }()
680+
writeBeTemplate = func(filename string, data []byte, perm os.FileMode) error {
681+
actualFilename = filename
682+
actualData = string(data)
683+
return nil
684+
}
685+
686+
s.reconfigure.Execute([]string{})
687+
688+
s.Equal(expectedFilename, actualFilename)
689+
s.Equal(expectedData, actualData)
690+
}
691+
692+
func (s ReconfigureTestSuite) Test_Execute_AddsHeader_WhenSetHeaderIsSet() {
693+
s.reconfigure.Mode = "swarm"
694+
s.reconfigure.SetHeader = []string{"header-1", "header-2"}
695+
var actualFilename, actualData string
696+
expectedFilename := fmt.Sprintf("%s/%s-be.cfg", s.TemplatesPath, s.ServiceName)
697+
expectedData := fmt.Sprintf(
698+
`
699+
backend %s-be%s
700+
mode http
701+
http-request set-header header-1
702+
http-request set-header header-2
703+
server %s %s:%s`,
704+
s.ServiceName,
705+
s.reconfigure.ServiceDest[0].Port,
706+
s.ServiceName,
707+
s.ServiceName,
708+
s.reconfigure.ServiceDest[0].Port,
709+
)
710+
writeBeTemplateOrig := writeBeTemplate
711+
defer func() { writeBeTemplate = writeBeTemplateOrig }()
712+
writeBeTemplate = func(filename string, data []byte, perm os.FileMode) error {
713+
actualFilename = filename
714+
actualData = string(data)
715+
return nil
716+
}
717+
718+
s.reconfigure.Execute([]string{})
719+
720+
s.Equal(expectedFilename, actualFilename)
721+
s.Equal(expectedData, actualData)
722+
}
723+
660724
func (s ReconfigureTestSuite) Test_Execute_DoesNotInvokeRegistrarableCreateConfigs_WhenModeIsService() {
661725
mockObj := getRegistrarableMock("")
662726
registryInstanceOrig := registryInstance

docs/feedback-and-contribution.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Feedback and Contribution
22

3-
The *Docker Flow: Proxy* project welcomes, and depends, on contributions from developers and users in the open source community. Contributions can be made in a number of ways, a few examples are:
3+
The *Docker Flow Proxy* project welcomes, and depends, on contributions from developers and users in the open source community. Contributions can be made in a number of ways, a few examples are:
44

55
* Code patches or new features via pull requests
66
* Documentation improvements
@@ -20,7 +20,7 @@ Please join the [DevOps20](http://slack.devops20toolkit.com/) Slack channel if y
2020

2121
## Contributing To The Project
2222

23-
I encourage you to contribute to the *Docker Flow: Proxy* project.
23+
I encourage you to contribute to the *Docker Flow Proxy* project.
2424

2525
The project is developed using *Test Driven Development* and *Continuous Deployment* process. Test are divided into unit and integration tests. Every code file has an equivalent with tests (e.g. `reconfigure.go` and `reconfigure_test.go`). Ideally, I expect you to write a test that defines that should be developed, run all the unit tests and confirm that the test fails, write just enough code to make the test pass, repeat. If you are new to testing, feel free to create a pull request indicating that tests are missing and I'll help you out.
2626

docs/index.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,9 @@ Since the Docker 1.12 release, *Docker Flow Proxy* supports two modes. The defau
1010

1111
The recommendation is to run *Docker Flow Proxy* inside a Swarm cluster with Automatic Reconfiguration.
1212

13-
*Docker Flow Proxy* examples can be found in the sections that follow.
13+
*Docker Flow Proxy* examples can be found in the *Tutorials* section located in the left-hand menu.
1414

15-
* [Swarm Mode With Automatic Reconfiguration (recommended)](swarm-mode-auto.md)
16-
* [Swarm Mode With Docker Stack](swarm-mode-stack.md)
17-
* [Swarm Mode With Manual Reconfiguration](swarm-mode-manual.md)
18-
* [Standard Mode](standard-mode.md)
19-
20-
Please visit the [config](config.md) and [usage](usage.md) sections for more details.
15+
Please visit the [Configuring Docker Flow Proxy](config.md) and [Usage](usage.md) sections for more details.
2116

2217
[Feedback and contributions](feedback-and-contribution.md) are appreciated.
2318

docs/usage.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ The following query parameters can be used to send a *reconfigure* request to *D
1515
|Query |Description |Required|Default|Example |
1616
|---------------|------------------------------------------------------------------------------------------|--------|-------|-------------|
1717
|aclName |ACLs are ordered alphabetically by their names. If not specified, serviceName is used instead.|No | |05-go-demo-acl|
18+
|addHeader |Additional headers that will be added to the request before forwarding it to the service. Multiple headers should be separated with comma (`,`). Please consult [Add a header to the request](https://www.haproxy.com/doc/aloha/7.0/haproxy/http_rewriting.html#add-a-header-to-the-request) for more info.|No| |X-Forwarded-Port %[dst_port],X-Forwarded-Ssl on if { ssl_fc }|
1819
|httpsPort |The internal HTTPS port of a service that should be reconfigured. The port is used only in the `swarm` mode. If not specified, the `port` parameter will be used instead.|No| |443|
1920
|port |The internal port of a service that should be reconfigured. The port is used only in the `swarm` mode. The parameter can be prefixed with an index thus allowing definition of multiple destinations for a single service (e.g. `port.1`, `port.2`, and so on).|Only in `swarm` mode| |8080|
2021
|reqMode |The request mode. The proxy should be able to work with any mode supported by HAProxy. However, actively supported and tested modes are `http`, `tcp`, and `sni`. The `sni` mode implies TCP with an SNI-based routing.|No|http|tcp|
@@ -23,6 +24,7 @@ The following query parameters can be used to send a *reconfigure* request to *D
2324
|serviceDomain |The domain of the service. If set, the proxy will allow access only to requests coming to that domain. Multiple domains should be separated with comma (`,`).|No| |ecme.com|
2425
|serviceDomainMatchAll|Whether to include subdomains and FDQN domains in the match. If set to false, and, for example, `serviceDomain` is set to `acme.com`, `something.acme.com` would not be considered a match unless this parameter is set to `true`. If this option is used, it is recommended to put any subdomains higher in the list using `aclName`.|No|false|true|
2526
|serviceName |The name of the service. It must match the name of the Swarm service or the one stored in Consul.|Yes| |go-demo |
27+
|setHeader |Additional headers that will be set to the request before forwarding it to the service. If a specified header exists, it will be replaced with the new one. Multiple headers should be separated with comma (`,`). Please consult [Set a header to the request](https://www.haproxy.com/doc/aloha/7.0/haproxy/http_rewriting.html#set-a-header-in-the-request) for more info.|No| |X-Forwarded-Port %[dst_port],X-Forwarded-Ssl on if { ssl_fc }|
2628
|srcPort |The source (entry) port of a service. Useful only when specifying multiple destinations of a single service. The parameter can be prefixed with an index thus allowing definition of multiple destinations for a single service (e.g. `srcPort.1`, `srcPort.2`, and so on).|No| |80|
2729
|timeoutServer |The server timeout in seconds. |No |20 |60 |
2830
|timeoutTunnel |The tunnel timeout in seconds. |No |3600 |1800 |
@@ -95,6 +97,7 @@ The map between the HTTP query parameters and environment variables is as follow
9597
|Query |Environment variable |
9698
|---------------------|------------------------|
9799
|aclName |ACL_NAME |
100+
|addHeader |ADD_HEADER |
98101
|consulTemplateBePath |CONSUL_TEMPLATE_BE_PATH |
99102
|consulTemplateFePath |CONSUL_TEMPLATE_FE_PATH |
100103
|distribute |DISTRIBUTE |
@@ -112,6 +115,7 @@ The map between the HTTP query parameters and environment variables is as follow
112115
|serviceDomainMatchAll|SERVICE_DOMAIN_MATCH_ALL|
113116
|serviceName |SERVICE_NAME |
114117
|servicePath |SERVICE_PATH |
118+
|setHeader |SET_HEADER |
115119
|skipCheck |SKIP_CHECK |
116120
|srcPort |SRC_PORT |
117121
|sslVerifyNone |SSL_VERIFY_NONE |

proxy/types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ type ServiceDest struct {
2020
}
2121

2222
type Service struct {
23+
// Additional headers that will be added to the request before forwarding it to the service. Please consult https://www.haproxy.com/doc/aloha/7.0/haproxy/http_rewriting.html#add-a-header-to-the-request for more info.
24+
AddHeader []string `split_words:"true"`
2325
// ACLs are ordered alphabetically by their names.
2426
// If not specified, serviceName is used instead.
2527
AclName string `split_words:"true"`
@@ -69,6 +71,8 @@ type Service struct {
6971
// The name of the service.
7072
// It must match the name of the Swarm service or the one stored in Consul.
7173
ServiceName string `split_words:"true"`
74+
// Additional headers that will be set to the request before forwarding it to the service. If a specified header exists, it will be replaced with the new one.
75+
SetHeader []string `split_words:"true"`
7276
// Whether to skip adding proxy checks.
7377
// This option is used only in the default mode.
7478
SkipCheck bool `split_words:"true"`

server/server.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ func (m *Serve) GetServiceFromUrl(req *http.Request) *proxy.Service {
7474
if len(req.URL.Query().Get("serviceDomain")) > 0 {
7575
sr.ServiceDomain = strings.Split(req.URL.Query().Get("serviceDomain"), ",")
7676
}
77+
if len(req.URL.Query().Get("addHeader")) > 0 {
78+
sr.AddHeader = strings.Split(req.URL.Query().Get("addHeader"), ",")
79+
}
80+
if len(req.URL.Query().Get("setHeader")) > 0 {
81+
sr.SetHeader = strings.Split(req.URL.Query().Get("setHeader"), ",")
82+
}
7783
globalUsersString := proxy.GetSecretOrEnvVar("USERS", "")
7884
globalUsersEncrypted := strings.EqualFold(proxy.GetSecretOrEnvVar("USERS_PASS_ENCRYPTED", ""), "true")
7985
sr.Users = mergeUsers(

server/server_test.go

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -594,34 +594,36 @@ func (s *ServerTestSuite) Test_RemoveHandler_WritesErrorHeader_WhenRemoveDistrib
594594

595595
func (s *ServerTestSuite) Test_GetServiceFromUrl_ReturnsProxyService() {
596596
expected := proxy.Service{
597-
ServiceName: "serviceName",
598597
AclName: "aclName",
599-
ServiceColor: "serviceColor",
600-
ServiceCert: "serviceCert",
601-
OutboundHostname: "outboundHostname",
598+
AddHeader: []string{"add-header-1", "add-header-2"},
602599
ConsulTemplateFePath: "consulTemplateFePath",
603600
ConsulTemplateBePath: "consulTemplateBePath",
601+
Distribute: true,
602+
HttpsOnly: true,
603+
HttpsPort: 1234,
604+
OutboundHostname: "outboundHostname",
604605
PathType: "pathType",
605-
ReqPathSearch: "reqPathSearch",
606+
RedirectWhenHttpProto: true,
607+
ReqMode: "reqMode",
606608
ReqPathReplace: "reqPathReplace",
607-
TemplateFePath: "templateFePath",
609+
ReqPathSearch: "reqPathSearch",
610+
ServiceCert: "serviceCert",
611+
ServiceColor: "serviceColor",
612+
ServiceDest: []proxy.ServiceDest{{ServicePath: []string{}}},
613+
ServiceDomain: []string{"domain1", "domain2"},
614+
ServiceDomainMatchAll: true,
615+
ServiceName: "serviceName",
616+
SetHeader: []string{"set-header-1", "set-header-2"},
617+
SkipCheck: true,
618+
SslVerifyNone: true,
608619
TemplateBePath: "templateBePath",
620+
TemplateFePath: "templateFePath",
609621
TimeoutServer: "timeoutServer",
610622
TimeoutTunnel: "timeoutTunnel",
611-
ReqMode: "reqMode",
612-
HttpsOnly: true,
613623
XForwardedProto: true,
614-
RedirectWhenHttpProto: true,
615-
HttpsPort: 1234,
616-
ServiceDomain: []string{"domain1", "domain2"},
617-
SkipCheck: true,
618-
Distribute: true,
619-
SslVerifyNone: true,
620-
ServiceDomainMatchAll: true,
621-
ServiceDest: []proxy.ServiceDest{{ServicePath: []string{}}},
622624
}
623625
addr := fmt.Sprintf(
624-
"%s?serviceName=%s&aclName=%s&serviceColor=%s&serviceCert=%s&outboundHostname=%s&consulTemplateFePath=%s&consulTemplateBePath=%s&pathType=%s&reqPathSearch=%s&reqPathReplace=%s&templateFePath=%s&templateBePath=%s&timeoutServer=%s&timeoutTunnel=%s&reqMode=%s&httpsOnly=%t&xForwardedProto=%t&redirectWhenHttpProto=%t&httpsPort=%d&serviceDomain=%s&skipCheck=%t&distribute=%t&sslVerifyNone=%t&serviceDomainMatchAll=%t",
626+
"%s?serviceName=%s&aclName=%s&serviceColor=%s&serviceCert=%s&outboundHostname=%s&consulTemplateFePath=%s&consulTemplateBePath=%s&pathType=%s&reqPathSearch=%s&reqPathReplace=%s&templateFePath=%s&templateBePath=%s&timeoutServer=%s&timeoutTunnel=%s&reqMode=%s&httpsOnly=%t&xForwardedProto=%t&redirectWhenHttpProto=%t&httpsPort=%d&serviceDomain=%s&skipCheck=%t&distribute=%t&sslVerifyNone=%t&serviceDomainMatchAll=%t&addHeader=%s&setHeader=%s",
625627
s.BaseUrl,
626628
expected.ServiceName,
627629
expected.AclName,
@@ -647,6 +649,8 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_ReturnsProxyService() {
647649
expected.Distribute,
648650
expected.SslVerifyNone,
649651
expected.ServiceDomainMatchAll,
652+
strings.Join(expected.AddHeader, ","),
653+
strings.Join(expected.SetHeader, ","),
650654
)
651655
req, _ := http.NewRequest("GET", addr, nil)
652656
srv := Serve{}
@@ -794,6 +798,7 @@ func (s *ServerTestSuite) Test_UsersMerge_AllCases() {
794798
func (s *ServerTestSuite) Test_GetServicesFromEnvVars_ReturnsServices() {
795799
service := proxy.Service{
796800
AclName: "my-AclName",
801+
AddHeader: []string{"add-header-1", "add-header-2"},
797802
ConsulTemplateBePath: "my-ConsulTemplateBePath",
798803
ConsulTemplateFePath: "my-ConsulTemplateFePath",
799804
Distribute: true,
@@ -809,6 +814,7 @@ func (s *ServerTestSuite) Test_GetServicesFromEnvVars_ReturnsServices() {
809814
ServiceDomain: []string{"my-domain-1.com", "my-domain-2.com"},
810815
ServiceDomainMatchAll: true,
811816
ServiceName: "my-ServiceName",
817+
SetHeader: []string{"set-header-1", "set-header-2"},
812818
SkipCheck: true,
813819
SslVerifyNone: true,
814820
TemplateBePath: "my-TemplateBePath",
@@ -821,6 +827,7 @@ func (s *ServerTestSuite) Test_GetServicesFromEnvVars_ReturnsServices() {
821827
},
822828
}
823829
os.Setenv("DFP_SERVICE_ACL_NAME", service.AclName)
830+
os.Setenv("DFP_SERVICE_ADD_HEADER", strings.Join(service.AddHeader, ","))
824831
os.Setenv("DFP_SERVICE_CONSUL_TEMPLATE_FE_PATH", service.ConsulTemplateFePath)
825832
os.Setenv("DFP_SERVICE_CONSUL_TEMPLATE_BE_PATH", service.ConsulTemplateBePath)
826833
os.Setenv("DFP_SERVICE_DISTRIBUTE", strconv.FormatBool(service.Distribute))
@@ -845,10 +852,12 @@ func (s *ServerTestSuite) Test_GetServicesFromEnvVars_ReturnsServices() {
845852
os.Setenv("DFP_SERVICE_X_FORWARDED_PROTO", strconv.FormatBool(service.XForwardedProto))
846853
os.Setenv("DFP_SERVICE_PORT", service.ServiceDest[0].Port)
847854
os.Setenv("DFP_SERVICE_SERVICE_PATH", strings.Join(service.ServiceDest[0].ServicePath, ","))
855+
os.Setenv("DFP_SERVICE_SET_HEADER", strings.Join(service.SetHeader, ","))
848856
os.Setenv("DFP_SERVICE_SRC_PORT", strconv.Itoa(service.ServiceDest[0].SrcPort))
849857

850858
defer func() {
851859
os.Unsetenv("DFP_SERVICE_ACL_NAME")
860+
os.Unsetenv("DFP_SERVICE_ADD_HEADER")
852861
os.Unsetenv("DFP_SERVICE_CONSUL_TEMPLATE_BE_PATH")
853862
os.Unsetenv("DFP_SERVICE_CONSUL_TEMPLATE_FE_PATH")
854863
os.Unsetenv("DFP_SERVICE_DISTRIBUTE")
@@ -866,6 +875,7 @@ func (s *ServerTestSuite) Test_GetServicesFromEnvVars_ReturnsServices() {
866875
os.Unsetenv("DFP_SERVICE_SERVICE_DOMAIN_MATCH_ALL")
867876
os.Unsetenv("DFP_SERVICE_SERVICE_NAME")
868877
os.Unsetenv("DFP_SERVICE_SERVICE_PATH")
878+
os.Unsetenv("DFP_SERVICE_SET_HEADER")
869879
os.Unsetenv("DFP_SERVICE_SKIP_CHECK")
870880
os.Unsetenv("DFP_SERVICE_SRC_PORT")
871881
os.Unsetenv("DFP_SERVICE_SSL_VERIFY_NONE")

0 commit comments

Comments
 (0)