diff --git a/docs/configuration/nginx.md b/docs/configuration/nginx.md index c9092685a..ebdc9d830 100644 --- a/docs/configuration/nginx.md +++ b/docs/configuration/nginx.md @@ -6,6 +6,7 @@ Dokku uses nginx as its server for routing requests to specific applications. By nginx:access-logs [-t] # Show the nginx access logs for an application (-t follows) nginx:build-config # (Re)builds nginx config for given app nginx:error-logs [-t] # Show the nginx error logs for an application (-t follows) +nginx:validate [] [--clean] # Validates and optionally cleans up invalid nginx configurations ``` ## Checking access logs @@ -44,6 +45,30 @@ In certain cases, your app nginx configs may drift from the correct config for y dokku nginx:build-config node-js-app ``` +## Validating nginx configs + +It may be desired to validate an nginx config outside of the deployment process. To do so, run the `nginx:validate` command. With no arguments, this will validate all app nginx configs, one at a time. A minimal wrapper nginx config is generated for each app's nginx config, upon which `nginx -t` will be run. + +```shell +dokku nginx:validate +``` + +As app nginx configs are actually executed within a shared context, it is possible for an individual config to be invalid when being validated standalone but _also_ be valid within the global server context. As such, the exit code for the `nginx:validate` command is the exit code of `nginx -t` against the server's real nginx config. + +The `nginx:validate` command also takes an optional `--clean` flag. If specified, invalid nginx configs will be removed. + +> Warning: Invalid app nginx config's will be removed _even if_ the config is valid in the global server context. + +```shell +dokku nginx:validate --clean +``` + +The `--clean` flag may also be specified for a given app: + +```shell +dokku nginx:validate node-js-app --clean +``` + ## Customizing the nginx configuration > New as of 0.5.0 diff --git a/plugins/nginx-vhosts/functions b/plugins/nginx-vhosts/functions index 4f57c7aaf..2a9773a6d 100755 --- a/plugins/nginx-vhosts/functions +++ b/plugins/nginx-vhosts/functions @@ -6,6 +6,7 @@ source "$PLUGIN_AVAILABLE_PATH/config/functions" source "$PLUGIN_AVAILABLE_PATH/domains/functions" source "$PLUGIN_AVAILABLE_PATH/proxy/functions" source "$PLUGIN_AVAILABLE_PATH/ps/functions" +source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/internal-functions" get_nginx_location() { declare desc="check that nginx is at the expected location and return it" @@ -25,26 +26,45 @@ get_nginx_location() { validate_nginx() { declare desc="validate entire nginx config" - local NGINX_LOCATION + declare APP="${1:-}" FLAG="${2:-}" + local NGINX_LOCATION EXIT_CODE NGINX_LOCATION=$(get_nginx_location) if [[ -z "$NGINX_LOCATION" ]]; then exit 1; fi + if [[ "$APP" == "--clean" ]]; then + APP="" + FLAG="--clean" + fi + set +e sudo "$NGINX_LOCATION" -t > /dev/null 2>&1 - local exit_code=$? + EXIT_CODE=$? set -e - if [[ "$exit_code" -ne "0" ]]; then - sudo "$NGINX_LOCATION" -t - shopt -s nullglob - local conf_file - for conf_file in $DOKKU_ROOT/*/nginx.conf; do - dokku_log_verbose "validate_nginx failed. contents of $conf_file below..." - cat "$conf_file" - done - exit "$exit_code" + if [[ "$EXIT_CODE" -eq "0" ]]; then + return fi + + if [[ -n "$APP" ]]; then + nginx_vhosts_validate_single_func "$APP" "$FLAG" + else + for app in $(dokku_apps); do + nginx_vhosts_validate_single_func "$app" "$FLAG" + done + fi + + set +e + sudo "$NGINX_LOCATION" -t > /dev/null 2>&1 + EXIT_CODE=$? + set -e + if [[ "$EXIT_CODE" -eq "0" ]]; then + return + fi + + + sudo "$NGINX_LOCATION" -t + exit $? } restart_nginx() { @@ -320,7 +340,7 @@ nginx_build_config() { PROXY_PORT_MAP=$(echo "$PROXY_PORT_MAP" | xargs) # trailing spaces mess up default template eval "$(config_export app "$APP")" - local SIGIL_PARAMS=(-f $NGINX_TEMPLATE APP="$APP" DOKKU_ROOT="$DOKKU_ROOT" + local SIGIL_PARAMS=(-f "$NGINX_TEMPLATE" APP="$APP" DOKKU_ROOT="$DOKKU_ROOT" NOSSL_SERVER_NAME="$NOSSL_SERVER_NAME" DOKKU_APP_LISTENERS="$DOKKU_APP_LISTENERS" DOKKU_LIB_ROOT="$DOKKU_LIB_ROOT" diff --git a/plugins/nginx-vhosts/install b/plugins/nginx-vhosts/install index 62f31fc28..9a601776a 100755 --- a/plugins/nginx-vhosts/install +++ b/plugins/nginx-vhosts/install @@ -5,23 +5,23 @@ source "$PLUGIN_AVAILABLE_PATH/config/functions" case "$DOKKU_DISTRO" in debian) - echo "%dokku ALL=(ALL) NOPASSWD:/usr/sbin/invoke-rc.d nginx reload, /usr/sbin/nginx -t" > /etc/sudoers.d/dokku-nginx + echo "%dokku ALL=(ALL) NOPASSWD:/usr/sbin/invoke-rc.d nginx reload, /usr/sbin/nginx -t, /usr/sbin/nginx -t -c *" > /etc/sudoers.d/dokku-nginx ;; ubuntu) - echo "%dokku ALL=(ALL) NOPASSWD:/etc/init.d/nginx reload, /usr/sbin/nginx -t" > /etc/sudoers.d/dokku-nginx + echo "%dokku ALL=(ALL) NOPASSWD:/etc/init.d/nginx reload, /usr/sbin/nginx -t, /usr/sbin/nginx -t -c *" > /etc/sudoers.d/dokku-nginx ;; opensuse) - echo "%dokku ALL=(ALL) NOPASSWD:/sbin/service nginx reload, /usr/sbin/nginx -t" > /etc/sudoers.d/dokku-nginx + echo "%dokku ALL=(ALL) NOPASSWD:/sbin/service nginx reload, /usr/sbin/nginx -t, /usr/sbin/nginx -t -c *" > /etc/sudoers.d/dokku-nginx ;; arch) - echo "%dokku ALL=(ALL) NOPASSWD:/usr/bin/systemctl reload nginx, /usr/sbin/nginx -t" > /etc/sudoers.d/dokku-nginx + echo "%dokku ALL=(ALL) NOPASSWD:/usr/bin/systemctl reload nginx, /usr/sbin/nginx -t, /usr/sbin/nginx -t -c *" > /etc/sudoers.d/dokku-nginx ;; centos|rhel) - echo "%dokku ALL=(ALL) NOPASSWD:/usr/bin/systemctl reload nginx, /usr/sbin/nginx -t" > /etc/sudoers.d/dokku-nginx + echo "%dokku ALL=(ALL) NOPASSWD:/usr/bin/systemctl reload nginx, /usr/sbin/nginx -t, /usr/sbin/nginx -t -c *" > /etc/sudoers.d/dokku-nginx echo "Defaults:dokku !requiretty" >> /etc/sudoers.d/dokku-nginx ;; esac diff --git a/plugins/nginx-vhosts/internal-functions b/plugins/nginx-vhosts/internal-functions index edf212502..cc27fbf3f 100755 --- a/plugins/nginx-vhosts/internal-functions +++ b/plugins/nginx-vhosts/internal-functions @@ -1,12 +1,52 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +nginx_vhosts_validate_single_func() { + declare APP="$1" FLAG="$2" + local NGINX_CONF="$DOKKU_ROOT/$APP/nginx.conf" + + if [[ ! -f "$NGINX_CONF" ]]; then + dokku_log_warn_quiet "No nginx config found for ${APP}" + return + fi + + if nginx_vhosts_is_valid_nginx_config_func "$APP"; then + return + fi + + dokku_log_warn "Failed to validate nginx config for ${APP}. Contents below..." + cat "$NGINX_CONF" + + if [[ "$FLAG" == "--clean" ]]; then + nginx_vhosts_conf_clean_func "$APP" + fi +} + +nginx_vhosts_is_valid_nginx_config_func() { + declare desc="checks if an app has a valid nginx config" + declare APP="$1" + local VALIDATE_TEMPLATE="$PLUGIN_AVAILABLE_PATH/nginx-vhosts/templates/validate.conf.sigil" + local TMP_OUTPUT=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") + trap 'rm -rf "$TMP_OUTPUT" > /dev/null' RETURN INT TERM EXIT + + sigil -f "$VALIDATE_TEMPLATE" NGINX_CONF="$DOKKU_ROOT/$APP/nginx.conf" | cat -s > "$TMP_OUTPUT" + sudo "$NGINX_LOCATION" -t -c "$TMP_OUTPUT" 2> /dev/null +} + +nginx_vhosts_conf_clean_func() { + declare APP="$1" + local NGINX_CONF="$DOKKU_ROOT/$APP/nginx.conf" + dokku_log_warn "Removing invalid nginx file" + rm -f "$NGINX_CONF" +} + nginx_vhosts_help_content_func() { declare desc="return nginx plugin help content" cat<, (Re)builds nginx config for given app nginx:access-logs [-t], Show the nginx access logs for an application (-t follows) nginx:error-logs [-t], Show the nginx error logs for an application (-t follows) + nginx:validate [] [--clean], Validates and optionally cleans up invalid nginx configurations help_content } diff --git a/plugins/nginx-vhosts/subcommands/validate b/plugins/nginx-vhosts/subcommands/validate new file mode 100755 index 000000000..63f677682 --- /dev/null +++ b/plugins/nginx-vhosts/subcommands/validate @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/config/functions" +source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions" + +nginx_validate_cmd() { + declare desc="validates and optionally cleans up invalid nginx configurations" + declare cmd="nginx:validate" argv=("$@"); [[ ${argv[0]} == "$cmd" ]] && shift 1 + validate_nginx "$@" +} + +nginx_validate_cmd "$@" diff --git a/plugins/nginx-vhosts/templates/validate.conf.sigil b/plugins/nginx-vhosts/templates/validate.conf.sigil new file mode 100644 index 000000000..232fd6d51 --- /dev/null +++ b/plugins/nginx-vhosts/templates/validate.conf.sigil @@ -0,0 +1,2 @@ +events { worker_connections 768; } +http { include {{ $.NGINX_CONF }}; } diff --git a/tests/unit/40_nginx-vhosts_2.bats b/tests/unit/40_nginx-vhosts_2.bats index 40b041585..3be7ab2cd 100644 --- a/tests/unit/40_nginx-vhosts_2.bats +++ b/tests/unit/40_nginx-vhosts_2.bats @@ -106,3 +106,50 @@ assert_error_log() { echo "status: $status" assert_failure } + +@test "(nginx-vhosts) nginx:validate" { + deploy_app + run /bin/bash -c "dokku nginx:validate" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:validate $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_success + + echo "invalid config" > "/home/dokku/${TEST_APP}/nginx.conf" + + run /bin/bash -c "dokku nginx:validate" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku nginx:validate $TEST_APP" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku nginx:validate --clean" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:validate" + echo "output: $output" + echo "status: $status" + assert_success + + echo "invalid config" > "/home/dokku/${TEST_APP}/nginx.conf" + + run /bin/bash -c "dokku nginx:validate $TEST_APP --clean" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku nginx:validate" + echo "output: $output" + echo "status: $status" + assert_success +}