diff --git a/docs/networking/proxies/haproxy.md b/docs/networking/proxies/haproxy.md index 55726f948..248306938 100644 --- a/docs/networking/proxies/haproxy.md +++ b/docs/networking/proxies/haproxy.md @@ -113,6 +113,16 @@ dokku haproxy:set --global log-level DEBUG After modifying, the Haproxy container will need to be restarted. +### Changing the Haproxy refresh interval + +Haproxy polls the Docker API for label changes every `10` seconds by default. The interval may be changed by setting the `refresh-conf` property with the `--global` flag: + +```shell +dokku haproxy:set --global refresh-conf 5 +``` + +The `refresh-conf` property is global-only and cannot be set on a per-app basis. Setting an empty value will reset it to the default. After modifying, the Haproxy container will need to be restarted. + ### Label Management The Haproxy plugin allows you to add custom container labels to apps. These labels are injected into containers during deployment and can be used to configure Haproxy behavior beyond what the plugin provides by default. diff --git a/plugins/haproxy-vhosts/command-functions b/plugins/haproxy-vhosts/command-functions index 9644f5356..3477abca1 100755 --- a/plugins/haproxy-vhosts/command-functions +++ b/plugins/haproxy-vhosts/command-functions @@ -72,6 +72,7 @@ cmd-haproxy-report-single() { "--haproxy-letsencrypt-email: $(fn-haproxy-letsencrypt-email)" "--haproxy-letsencrypt-server: $(fn-haproxy-letsencrypt-server)" "--haproxy-log-level: $(fn-haproxy-log-level)" + "--haproxy-refresh-conf: $(fn-haproxy-refresh-conf)" ) fn-report-validate-format "$FORMAT" "$INFO_FLAG" diff --git a/plugins/haproxy-vhosts/internal-functions b/plugins/haproxy-vhosts/internal-functions index 0e94364e6..c4b8d80ac 100755 --- a/plugins/haproxy-vhosts/internal-functions +++ b/plugins/haproxy-vhosts/internal-functions @@ -38,7 +38,8 @@ fn-haproxy-template-compose-file() { local SIGIL_PARAMS=(HAPROXY_IMAGE="$(fn-haproxy-image)" HAPROXY_LOG_LEVEL="$(fn-haproxy-log-level)" HAPROXY_LETSENCRYPT_EMAIL="$(fn-haproxy-letsencrypt-email)" - HAPROXY_LETSENCRYPT_SERVER="$(fn-haproxy-letsencrypt-server)") + HAPROXY_LETSENCRYPT_SERVER="$(fn-haproxy-letsencrypt-server)" + HAPROXY_REFRESH_CONF="$(fn-haproxy-refresh-conf)") sigil -f "$COMPOSE_TEMPLATE" "${SIGIL_PARAMS[@]}" | cat -s >"$OUTPUT_PATH" } @@ -60,3 +61,7 @@ fn-haproxy-letsencrypt-email() { fn-haproxy-letsencrypt-server() { fn-plugin-property-get-default "haproxy" "--global" "letsencrypt-server" "https://acme-v02.api.letsencrypt.org/directory" } + +fn-haproxy-refresh-conf() { + fn-plugin-property-get-default "haproxy" "--global" "refresh-conf" "10" +} diff --git a/plugins/haproxy-vhosts/subcommands/set b/plugins/haproxy-vhosts/subcommands/set index 6406ea025..9789378d5 100755 --- a/plugins/haproxy-vhosts/subcommands/set +++ b/plugins/haproxy-vhosts/subcommands/set @@ -9,13 +9,14 @@ cmd-haproxy-set() { declare cmd="haproxy:set" [[ "$1" == "$cmd" ]] && shift 1 declare APP="$1" KEY="$2" VALUE="$3" - local VALID_KEYS=("image" "log-level" "letsencrypt-email" "letsencrypt-server") - local GLOBAL_KEYS=("image" "log-level" "letsencrypt-email" "letsencrypt-server") + local VALID_KEYS=("image" "log-level" "letsencrypt-email" "letsencrypt-server" "refresh-conf") + local GLOBAL_KEYS=("image" "log-level" "letsencrypt-email" "letsencrypt-server" "refresh-conf") + local GLOBAL_ONLY_KEYS=("refresh-conf") [[ -z "$KEY" ]] && dokku_log_fail "No key specified" if ! fn-in-array "$KEY" "${VALID_KEYS[@]}"; then - dokku_log_fail "Invalid key specified, valid keys include: image log-level letsencrypt-email letsencrypt-server" + dokku_log_fail "Invalid key specified, valid keys include: image log-level letsencrypt-email letsencrypt-server refresh-conf" fi if ! fn-in-array "$KEY" "${GLOBAL_KEYS[@]}"; then @@ -25,6 +26,10 @@ cmd-haproxy-set() { verify_app_name "$APP" fi + if fn-in-array "$KEY" "${GLOBAL_ONLY_KEYS[@]}" && [[ "$APP" != "--global" ]]; then + dokku_log_fail "The key '$KEY' can only be set globally" + fi + if [[ -n "$VALUE" ]]; then dokku_log_info2_quiet "Setting ${KEY} to ${VALUE}" fn-plugin-property-write "haproxy" "$APP" "$KEY" "$VALUE" diff --git a/plugins/haproxy-vhosts/templates/compose.yml.sigil b/plugins/haproxy-vhosts/templates/compose.yml.sigil index be884d5be..49146677c 100644 --- a/plugins/haproxy-vhosts/templates/compose.yml.sigil +++ b/plugins/haproxy-vhosts/templates/compose.yml.sigil @@ -7,6 +7,7 @@ services: - EASYHAPROXY_DISCOVER=docker - EASYHAPROXY_LABEL_PREFIX=haproxy - EASYHAPROXY_LOG_LEVEL={{ $.HAPROXY_LOG_LEVEL }} + - EASYHAPROXY_REFRESH_CONF={{ $.HAPROXY_REFRESH_CONF }} - CERTBOT_LOG_LEVEL={{ $.HAPROXY_LOG_LEVEL }} - HAPROXY_LOG_LEVEL={{ $.HAPROXY_LOG_LEVEL }} {{ if $.HAPROXY_LETSENCRYPT_EMAIL }} diff --git a/tests/unit/haproxy.bats b/tests/unit/haproxy.bats index 89f749044..2717b2bc5 100644 --- a/tests/unit/haproxy.bats +++ b/tests/unit/haproxy.bats @@ -7,6 +7,7 @@ setup() { dokku nginx:stop dokku haproxy:set --global letsencrypt-server https://acme-staging-v02.api.letsencrypt.org/directory dokku haproxy:set --global letsencrypt-email + dokku haproxy:set --global refresh-conf 2 dokku haproxy:start create_app } @@ -15,6 +16,7 @@ teardown() { global_teardown destroy_app dokku haproxy:stop + dokku haproxy:set --global refresh-conf dokku nginx:start } @@ -32,6 +34,42 @@ teardown() { assert_output "$help_output" } +@test "(haproxy) refresh-conf" { + run /bin/bash -c "dokku haproxy:set $TEST_APP refresh-conf 2" + echo "output: $output" + echo "status: $status" + assert_failure + assert_output_contains "can only be set globally" + + run /bin/bash -c "dokku haproxy:set --global refresh-conf 5" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku haproxy:report --global --haproxy-refresh-conf" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "5" + + run /bin/bash -c "dokku haproxy:show-config" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_contains "EASYHAPROXY_REFRESH_CONF=5" + + run /bin/bash -c "dokku haproxy:set --global refresh-conf" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku haproxy:report --global --haproxy-refresh-conf" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "10" +} + @test "(haproxy) log-level" { run /bin/bash -c "dokku haproxy:set --global log-level DEBUG" echo "output: $output" @@ -60,11 +98,7 @@ teardown() { echo "status: $status" assert_success - run /bin/bash -c "curl --silent $(dokku url $TEST_APP)" - echo "output: $output" - echo "status: $status" - assert_success - assert_output_contains "python/http.server" + assert_http_localhost_response_contains "http" "$TEST_APP.$DOKKU_DOMAIN" "80" "/" "python/http.server" } @test "(haproxy) multiple domains" { @@ -88,8 +122,6 @@ teardown() { echo "status: $status" assert_success - sleep 5 - run /bin/bash -c "dokku logs $TEST_APP" echo "output: $output" echo "status: $status" diff --git a/tests/unit/test_helper.bash b/tests/unit/test_helper.bash index 3b581565a..0cbd174ec 100644 --- a/tests/unit/test_helper.bash +++ b/tests/unit/test_helper.bash @@ -249,10 +249,17 @@ assert_http_success() { assert_http_localhost_response() { local scheme="$1" domain="$2" port="${3:-80}" path="${4:-}" content="${5:-}" status_code="${6:-200}" - run curl --connect-to "$domain:$port:localhost:$port" -kSso /dev/null -w "%{http_code}" "$scheme://$domain:$port$path" + local retries="${HTTP_ASSERT_RETRIES:-30}" attempt=1 + while [[ "$attempt" -lt "$retries" ]]; do + run curl --connect-to "$domain:$port:localhost:$port" -kSso /dev/null -w "%{http_code}" "$scheme://$domain:$port$path" + [[ "$output" == "$status_code" ]] && break + sleep 1 + attempt=$((attempt + 1)) + done echo "curl: curl --connect-to $domain:$port:localhost:$port -kSso /dev/null -w %{http_code} $scheme://$domain:$port$path" echo "output: $output" echo "status: $status" + echo "attempts: $attempt" assert_output "$status_code" if [[ -n "$content" ]]; then @@ -265,10 +272,17 @@ assert_http_localhost_response() { assert_http_localhost_response_contains() { local scheme="$1" domain="$2" port="${3:-80}" path="${4:-}" content="${5:-}" status_code="${6:-200}" - run curl --connect-to "$domain:$port:localhost:$port" -kSso /dev/null -w "%{http_code}" "$scheme://$domain:$port$path" + local retries="${HTTP_ASSERT_RETRIES:-30}" attempt=1 + while [[ "$attempt" -lt "$retries" ]]; do + run curl --connect-to "$domain:$port:localhost:$port" -kSso /dev/null -w "%{http_code}" "$scheme://$domain:$port$path" + [[ "$output" == "$status_code" ]] && break + sleep 1 + attempt=$((attempt + 1)) + done echo "curl: curl --connect-to $domain:$port:localhost:$port -kSso /dev/null -w %{http_code} $scheme://$domain:$port$path" echo "output: $output" echo "status: $status" + echo "attempts: $attempt" assert_output "$status_code" if [[ -n "$content" ]]; then