Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Nginx test
name: Test nginx and haproxy

on: [push, pull_request]

Expand All @@ -18,3 +18,7 @@ jobs:
cd nginx
make test
)
(
cd haproxy/tests
make test
)
1 change: 1 addition & 0 deletions haproxy/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ RUN chmod +x /entrypoint.sh
ADD default_frontend.cfg /usr/local/etc/haproxy
ADD backend.cfg.template /usr/local/etc/haproxy
COPY scripts /usr/local/etc/haproxy/
COPY errors/*.http /usr/local/etc/haproxy/errors/

ENTRYPOINT ["/entrypoint.sh"]
5 changes: 2 additions & 3 deletions haproxy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ In your docker-compose.yml template, the HAproxy container declaration will requ
- HAPROXY_IP: This should be the docker service name, `fqdn`, or set to `0.0.0.0`. If not set, our templates will default this to be a service name of `haproxy`.
*Note*: For Docker Desktop on Mac users, if you wish to expose HAproxy to your host, you will have to set this as `0.0.0.0`, to expose it as `localhost` outside of the docker network.
- HAPROXY_PORT: The port you wish HAProxy to run on. Defaults to 5984.
- COUCHDB1_SERVER: The docker service name or fqdn of the first CouchDB server. Defaults to couchdb-1.local in our templates.
- COUCHDB2_SERVER: The docker service name or fqdn of the second CouchDB server. Defaults to couchdb-2.local in our templates.
- COUCHDB3_SERVER: The docker service name or fqdn of the third CouchDB server. Defaults to couchdb-3.local in our templates.
- COUCHDB_SERVERS: Comma separated list of the docker service name or fqdn of the CouchDB servers. Example: `couchdb-1.local,couchdb-2.local,couchdb-3.local`.
- COUCHDB_USER: The admininstrator that created the couchdb cluster
- COUCHDB_PASSWORD: The above admin's password
- HEALTHCHECK_ADDR: Address to the haproxy healthcheck service
24 changes: 24 additions & 0 deletions haproxy/default_frontend.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,27 @@ global
tune.bufsize 32768
tune.buffers.limit 60000

http-errors json
errorfile 200 /usr/local/etc/haproxy/errors/200-json.http
errorfile 400 /usr/local/etc/haproxy/errors/400-json.http
errorfile 401 /usr/local/etc/haproxy/errors/401-json.http
errorfile 403 /usr/local/etc/haproxy/errors/403-json.http
errorfile 404 /usr/local/etc/haproxy/errors/404-json.http
errorfile 405 /usr/local/etc/haproxy/errors/405-json.http
errorfile 407 /usr/local/etc/haproxy/errors/407-json.http
errorfile 408 /usr/local/etc/haproxy/errors/408-json.http
errorfile 410 /usr/local/etc/haproxy/errors/410-json.http
errorfile 413 /usr/local/etc/haproxy/errors/413-json.http
errorfile 421 /usr/local/etc/haproxy/errors/421-json.http
errorfile 422 /usr/local/etc/haproxy/errors/422-json.http
errorfile 425 /usr/local/etc/haproxy/errors/425-json.http
errorfile 429 /usr/local/etc/haproxy/errors/429-json.http
errorfile 500 /usr/local/etc/haproxy/errors/500-json.http
errorfile 501 /usr/local/etc/haproxy/errors/501-json.http
errorfile 502 /usr/local/etc/haproxy/errors/502-json.http
errorfile 503 /usr/local/etc/haproxy/errors/503-json.http
errorfile 504 /usr/local/etc/haproxy/errors/504-json.http

defaults
mode http
option http-ignore-probes
Expand All @@ -19,6 +40,9 @@ defaults
timeout server 360000000
timeout connect 1500000
timeout http-keep-alive 5m

errorfiles json

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand this correctly, this will always respond with JSON regardless of the Accepts header?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, since HAProxy is not being called directly by browsers or mobile apps adding a conditional can be considered unnecessary logic (feel free to discuss further in #8179)

stats enable
stats refresh 30s
stats auth $COUCHDB_USER:$COUCHDB_PASSWORD
Expand Down
7 changes: 4 additions & 3 deletions haproxy/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ BACKEND="/usr/local/etc/haproxy/backend.cfg"
cp /usr/local/etc/haproxy/backend.cfg.template $BACKEND
for COUCHDB_SERVER in ${COUCHDB_SERVERS//,/ }
do
printf " server $COUCHDB_SERVER $COUCHDB_SERVER:5984 check agent-check agent-inter 5s agent-addr $HEALTHCHECK_ADDR agent-port 5555\n" >> $BACKEND
echo " server $COUCHDB_SERVER $COUCHDB_SERVER:5984 check agent-check agent-inter 5s agent-addr $HEALTHCHECK_ADDR agent-port 5555" >> $BACKEND
done

# Place environment variables into config
Expand All @@ -18,8 +18,9 @@ envsubst < $BACKEND

#Write pw for healthcheck subshell to work
mkdir -p /srv/storage/haproxy/passwd
echo $COUCHDB_USER > /srv/storage/haproxy/passwd/username
echo $COUCHDB_PASSWORD > /srv/storage/haproxy/passwd/admin
echo "$COUCHDB_USER" > /srv/storage/haproxy/passwd/username
echo "$COUCHDB_PASSWORD" > /srv/storage/haproxy/passwd/admin

# Start haproxy
exec /usr/local/bin/docker-entrypoint.sh -f $DEFAULT -f $BACKEND

10 changes: 10 additions & 0 deletions haproxy/errors/200-json.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HTTP/1.1 200 OK
Content-length: 75
Cache-Control: no-cache
Content-Type: application/json

{
"error": "200 OK",
"reason": "Service ready",
"server": "haproxy"
}
11 changes: 11 additions & 0 deletions haproxy/errors/400-json.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
HTTP/1.1 400 Bad request
Content-length: 107
Connection: Close
Cache-Control: no-cache
Content-Type: application/json

{
"error": "400 Bad request",
"reason": "Your browser sent an invalid request",
"server": "haproxy"
}
10 changes: 10 additions & 0 deletions haproxy/errors/401-json.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HTTP/1.1 401 Unauthorized
Content-length: 129
Cache-Control: no-cache
Content-Type: application/json

{
"error": "401 Unauthorized",
"reason": "You need a valid user and password to access this content",
"server": "haproxy"
}
10 changes: 10 additions & 0 deletions haproxy/errors/403-json.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HTTP/1.1 403 Forbidden
Content-length: 110
Cache-Control: no-cache
Content-Type: application/json

{
"error": "403 Forbidden",
"reason": "Request forbidden by administrative rules",
"server": "haproxy"
}
10 changes: 10 additions & 0 deletions haproxy/errors/404-json.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HTTP/1.1 404 Not Found
Content-length: 100
Cache-Control: no-cache
Content-Type: application/json

{
"error": "404 Not Found",
"reason": "The resource could not be found",
"server": "haproxy"
}
10 changes: 10 additions & 0 deletions haproxy/errors/405-json.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HTTP/1.1 405 Method Not Allowed
Content-length: 164
Cache-Control: no-cache
Content-Type: application/json

{
"error": "405 Method Not Allowed",
"reason": "A request was made of a resource using a request method not supported by that resource",
"server": "haproxy"
}
10 changes: 10 additions & 0 deletions haproxy/errors/407-json.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HTTP/1.1 407 Unauthorized
Content-length: 129
Cache-Control: no-cache
Content-Type: application/json

{
"error": "407 Unauthorized",
"reason": "You need a valid user and password to access this content",
"server": "haproxy"
}
11 changes: 11 additions & 0 deletions haproxy/errors/408-json.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
HTTP/1.1 408 Request Time-out
Content-length: 127
Connection: Close
Cache-Control: no-cache
Content-Type: application/json

{
"error": "408 Request Time-out",
"reason": "Your browser didn't send a complete request in time",
"server": "haproxy"
}
10 changes: 10 additions & 0 deletions haproxy/errors/410-json.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HTTP/1.1 410 Gone
Content-length: 131
Cache-Control: no-cache
Content-Type: application/json

{
"error": "410 Gone",
"reason": "The resource is no longer available and will not be available again",
"server": "haproxy"
}
10 changes: 10 additions & 0 deletions haproxy/errors/413-json.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HTTP/1.1 413 Payload Too Large
Content-length: 123
Cache-Control: no-cache
Content-Type: application/json

{
"error": "413 Payload Too Large",
"reason": "The request entity exceeds the maximum allowed",
"server": "haproxy"
}
10 changes: 10 additions & 0 deletions haproxy/errors/421-json.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HTTP/1.1 421 Misdirected Request
Content-length: 121
Cache-Control: no-cache
Content-Type: application/json

{
"error": "421 Misdirected Request",
"reason": "Request sent to a non-authoritative server",
"server": "haproxy"
}
10 changes: 10 additions & 0 deletions haproxy/errors/422-json.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HTTP/1.1 422 Unprocessable Content
Content-length: 133
Cache-Control: no-cache
Content-Type: application/json

{
"error": "422 Unprocessable Content",
"reason": "The server cannot process the contained instructions",
"server": "haproxy"
}
10 changes: 10 additions & 0 deletions haproxy/errors/425-json.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HTTP/1.1 425 Too Early
Content-length: 97
Cache-Control: no-cache
Content-Type: application/json

{
"error": "425 Too Early",
"reason": "Your browser sent early data",
"server": "haproxy"
}
10 changes: 10 additions & 0 deletions haproxy/errors/429-json.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HTTP/1.1 429 Too Many Requests
Content-length: 134
Cache-Control: no-cache
Content-Type: application/json

{
"error": "429 Too Many Requests",
"reason": "You have sent too many requests in a given amount of time",
"server": "haproxy"
}
10 changes: 10 additions & 0 deletions haproxy/errors/500-json.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HTTP/1.1 500 Internal Server Error
Content-length: 114
Cache-Control: no-cache
Content-Type: application/json

{
"error": "500 Internal Server Error",
"reason": "An internal server error occurred",
"server": "haproxy"
}
10 changes: 10 additions & 0 deletions haproxy/errors/501-json.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HTTP/1.1 501 Not Implemented
Content-length: 152
Cache-Control: no-cache
Content-Type: application/json

{
"error": "501 Not Implemented",
"reason": "The server does not support the functionality required to fulfill the request",
"server": "haproxy"
}
10 changes: 10 additions & 0 deletions haproxy/errors/502-json.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HTTP/1.1 502 Bad Gateway
Content-length: 124
Cache-Control: no-cache
Content-Type: application/json

{
"error": "502 Bad Gateway",
"reason": "The server returned an invalid or incomplete response",
"server": "haproxy"
}
10 changes: 10 additions & 0 deletions haproxy/errors/503-json.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HTTP/1.1 503 Service Unavailable
Content-length: 124
Cache-Control: no-cache
Content-Type: application/json

{
"error": "503 Service Unavailable",
"reason": "No server is available to handle this request",
"server": "haproxy"
}
10 changes: 10 additions & 0 deletions haproxy/errors/504-json.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HTTP/1.1 504 Gateway Time-out
Content-length: 109
Cache-Control: no-cache
Content-Type: application/json

{
"error": "504 Gateway Time-out",
"reason": "The server didn't respond in time",
"server": "haproxy"
}
4 changes: 4 additions & 0 deletions haproxy/errors/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# nginx_error_messages

This directory contains error pages for haproxy. Do not update the error pages
manually, instead look in `generate.sh` and the `template.json` file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general I prefer generated code to not be checked in to the repo, and instead generated in CI and published. This means someone doesn't accidentally manually update the output and saves on duplication. What do you think? Is there an advantage to checking in the build output?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see arguments on both sides. The main reason I went with the more explicit pre-generated files is that I expect these will change at most 1-2 times per year and I didn't want to add complexity to the general build process. I imagine debugging an issue with the generated files without having been involved in this PR be more difficult if they're generated on build/start.

What if I add a test to ensure the current files are equal to how they will look when running generate.sh? I prefer that over build step as it doesn't complicate the build process and can be run independently from the build. I added a suggestion to what that could look like in this PR in the file haproxy/tests/errors_generate.bats feel free to review that one.

71 changes: 71 additions & 0 deletions haproxy/errors/generate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/bin/bash
###########################################################
#
# Script to generate static error pages for haproxy
#
# To update: change messages below or the respective template file
# and rerun this script
#
###########################################################
set -eu -o pipefail
unset IFS

DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"

TEMPDIR="$(mktemp -d)"
trap 'rm -rf "$TEMPDIR"' EXIT

declare -ar MESSAGES=(
"200|200 OK|Service ready|"
"400|400 Bad request|Your browser sent an invalid request|Connection: Close"
"401|401 Unauthorized|You need a valid user and password to access this content|"
"403|403 Forbidden|Request forbidden by administrative rules|"
"404|404 Not Found|The resource could not be found|"
"405|405 Method Not Allowed|A request was made of a resource using a request method not supported by that resource|"
"407|407 Unauthorized|You need a valid user and password to access this content|"
"408|408 Request Time-out|Your browser didn't send a complete request in time|Connection: Close"
"410|410 Gone|The resource is no longer available and will not be available again|"
"413|413 Payload Too Large|The request entity exceeds the maximum allowed|"
"421|421 Misdirected Request|Request sent to a non-authoritative server|"
"422|422 Unprocessable Content|The server cannot process the contained instructions|"
"425|425 Too Early|Your browser sent early data|"
"429|429 Too Many Requests|You have sent too many requests in a given amount of time|"
"500|500 Internal Server Error|An internal server error occurred|"
"501|501 Not Implemented|The server does not support the functionality required to fulfill the request|"
"502|502 Bad Gateway|The server returned an invalid or incomplete response|"
"503|503 Service Unavailable|No server is available to handle this request|"
"504|504 Gateway Time-out|The server didn't respond in time|"
)

echo 'http-errors json'
for message in "${MESSAGES[@]}"; do
IFS='|' read -r -a message_parts <<<"$message"
status_code="${message_parts[0]}"
status_message="${message_parts[1]}"
status_reason="${message_parts[2]}"
extra_header="${message_parts[3]:-}"

error_file="$DIR/${status_code}-json.http"

LC_ALL="C" # So content-length/$http_body is >1 for multibyte characters

http_body="$(sed -e "s/__MESSAGE__/$status_message/" -e "s/__REASON__/$status_reason/" <"$DIR/template.json")"

cat <<EOF >"$error_file"
HTTP/1.1 $status_message
Content-length: ${#http_body}
EOF

[[ -n "$extra_header" ]] && echo "$extra_header" >>"$error_file"

cat <<EOF >>"$error_file"
Cache-Control: no-cache
Content-Type: application/json

EOF

echo -n "$http_body" >>"$error_file"

echo " errorfile $status_code /usr/local/etc/haproxy/errors/${status_code}-json.http"

done

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice script! Thanks.

Loading