diff --git a/docs/appendices/0.6.0-migration-guide.md b/docs/appendices/0.6.0-migration-guide.md index bb9313828..ed5871238 100644 --- a/docs/appendices/0.6.0-migration-guide.md +++ b/docs/appendices/0.6.0-migration-guide.md @@ -7,3 +7,7 @@ - `DOKKU_CHECKS_DISABLED` is now a thing - The values for the above can be a comma-separated list of process-types or the literal string `_all_` - See the [updated `checks`](/dokku/checks-examples/) docs for more info + +## proxy port mapping +- You can now configure host -> container proxy port mappings + - The UI is handled by the [proxy](/dokku/proxy/) interface plugin by setting the `DOKKU_PROXY_PORT_MAP` config variable in the format of `scheme:host-port:container-port`. The default `nginx-vhosts` proxy plugin supports both the `http` and `https` schemes. diff --git a/docs/development/plugin-triggers.md b/docs/development/plugin-triggers.md index da1683002..4b2933f44 100644 --- a/docs/development/plugin-triggers.md +++ b/docs/development/plugin-triggers.md @@ -400,6 +400,26 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x sudo service haproxy reload ``` +### `post-proxy-ports-update` + +- Description: Allows you to run commands once the proxy port mappings for an application have been updated. It also sends the invoking command. This can be "add", "clear" or "remove". +- Invoked by: `dokku proxy:ports-add`, `dokku proxy:ports-clear`, `dokku proxy:ports-remove` +- Arguments: `$APP` `action name` +- Example: + +```shell +#!/usr/bin/env bash +# Rebuilds haproxy config for our imaginary haproxy plugin +# that replaces the nginx-vhosts plugin + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/haproxy/functions" +APP="$1"; verify_app_name "$APP" + +haproxy-build-config "$APP" +``` + ### `post-release-buildpack` - Description: Allows you to run commands after environment variables are set for the release step of the deploy. Only applies to applications using buildpacks. @@ -602,6 +622,74 @@ verify_app_name "$APP" # TODO ``` +### `pre-disable-vhost` + +- Description: Allows you to run commands before the VHOST feature is disabled +- Invoked by: `dokku domains:disable` +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +APP="$1"; verify_app_name "$APP" + +# TODO +``` + +### `pre-enable-vhost` + +- Description: Allows you to run commands before the VHOST feature is enabled +- Invoked by: `dokku domains:enable` +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +APP="$1"; verify_app_name "$APP" + +# TODO +``` + +### `post-certs-update` + +- Description: Allows you to run commands after a cert is added/updated +- Invoked by: `dokku certs:add`, `dokku certs:update` +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +APP="$1"; verify_app_name "$APP" + +# TODO +``` + +### `post-certs-remove` + +- Description: Allows you to run commands after a cert is removed +- Invoked by: `dokku certs:remove` +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +APP="$1"; verify_app_name "$APP" + +# TODO +``` + ### `receive-app` - Description: Allows you to customize what occurs when an app is received. Normally just triggers an application build. diff --git a/docs/nginx.md b/docs/nginx.md index d7c4bb3e9..4f739ae3f 100644 --- a/docs/nginx.md +++ b/docs/nginx.md @@ -72,6 +72,8 @@ upstream {{ .APP }} { {{ .NGINX_PORT }} Non-SSL nginx listener port (same as `DOKKU_NGINX_PORT` config var) {{ .NGINX_SSL_PORT }} SSL nginx listener port (same as `DOKKU_NGINX_SSL_PORT` config var) {{ .NOSSL_SERVER_NAME }} List of non-SSL VHOSTS +{{ .PROXY_PORT_MAP }} List of port mappings (same as `DOKKU_PROXY_PORT_MAP` config var) +{{ .PROXY_UPSTREAM_PORTS }} List of configured upstream ports (derived from `DOKKU_PROXY_PORT_MAP` config var) {{ .RAW_TCP_PORTS }} List of exposed tcp ports as defined by Dockerfile `EXPOSE` directive (**Dockerfile apps only**) {{ .SSL_INUSE }} Boolean set when an app is SSL-enabled {{ .SSL_SERVER_NAME }} List of SSL VHOSTS @@ -127,6 +129,54 @@ upstream {{ .APP }} { } ``` +### Example using new proxy port mapping +``` +{{ range $port_map := .PROXY_PORT_MAP | split " " }} +{{ $port_map_list := $port_map | split ":" }} +{{ $scheme := index $port_map_list 0 }} +{{ $listen_port := index $port_map_list 1 }} +{{ $upstream_port := index $port_map_list 2 }} + +server { + listen [::]:{{ $listen_port }}; + listen {{ $listen_port }}; + server_name {{ $.NOSSL_SERVER_NAME }}; + access_log /var/log/nginx/{{ $.APP }}-access.log; + error_log /var/log/nginx/{{ $.APP }}-error.log; + + location / { + + gzip on; + gzip_min_length 1100; + gzip_buffers 4 32k; + gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/x-javascript application/json application/xml application/rss+xml font/truetype application/x-font-ttf font/opentype application/vnd.ms-fontobject image/svg+xml; + gzip_vary on; + gzip_comp_level 6; + + proxy_pass http://{{ $.APP }}-{{ $upstream_port }}; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Request-Start $msec; + } + include {{ $.DOKKU_ROOT }}/{{ $.APP }}/nginx.conf.d/*.conf; +} + +{{ range $upstream_port := $.PROXY_UPSTREAM_PORTS | split " " }} +upstream {{ $.APP }}-{{ $upstream_port }} { +{{ range $listeners := $.DOKKU_APP_LISTENERS | split " " }} +{{ $listener_list := $listeners | split ":" }} +{{ $listener_ip := index $listener_list 0 }} +{{ $listener_port := index $listener_list 1 }} + server {{ $listener_ip }}:{{ $upstream_port }};{{ end }} +} +{{ end }} +``` + ### Customizing via configuration files included by the default templates The default nginx.conf template will include everything from your apps `nginx.conf.d/` subdirectory in the main `server {}` block (see above): diff --git a/docs/proxy.md b/docs/proxy.md index 8ca604f7b..2fcbd2256 100644 --- a/docs/proxy.md +++ b/docs/proxy.md @@ -5,9 +5,14 @@ As of dokku 0.5.0, the proxy functionality has been decoupled from the nginx-vhosts plugin into the proxy plugin. In the future this will allow other proxy software (HAproxy for example) to be used instead of nginx. ``` -proxy:disable Disable proxy for app -proxy:enable Enable proxy for app -proxy:set NOT IMPLEMENTED YET!! +proxy Show proxy settings for app +proxy:disable Disable proxy for app +proxy:enable Enable proxy for app +proxy:ports List proxy port mappings for app +proxy:ports-add :: [::...] Set proxy port mappings for app +proxy:ports-clear Clear all proxy port mappings for app +proxy:ports-remove [...] Unset proxy port mappings for app +proxy:set Set proxy type for app ``` ## Container network interface binding diff --git a/plugins/00_dokku-standard/subcommands/deploy b/plugins/00_dokku-standard/subcommands/deploy index f2706b575..68f1a2252 100755 --- a/plugins/00_dokku-standard/subcommands/deploy +++ b/plugins/00_dokku-standard/subcommands/deploy @@ -102,7 +102,7 @@ dokku_deploy_cmd() { kill -9 $$ } - # run checks first, then post-deploy hooks, which switches Nginx traffic + # run checks first, then post-deploy hooks, which switches proxy traffic trap 'kill_new $id' INT TERM EXIT if [[ "$(is_app_proctype_checks_disabled "$APP" "$PROC_TYPE")" == "false" ]]; then dokku_log_info1 "Attempting pre-flight checks" diff --git a/plugins/00_dokku-standard/subcommands/version b/plugins/00_dokku-standard/subcommands/version index 99d2c89d1..c151f94c8 100755 --- a/plugins/00_dokku-standard/subcommands/version +++ b/plugins/00_dokku-standard/subcommands/version @@ -1,10 +1,6 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/checks/functions" -source "$PLUGIN_AVAILABLE_PATH/config/functions" -source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions" -source "$PLUGIN_CORE_AVAILABLE_PATH/proxy/functions" dokku_version_cmd() { declare desc="prints dokku version" diff --git a/plugins/20_events/post-certs-remove b/plugins/20_events/post-certs-remove new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/post-certs-remove @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/post-certs-update b/plugins/20_events/post-certs-update new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/post-certs-update @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/post-config-update b/plugins/20_events/post-config-update new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/post-config-update @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/post-proxy-ports-update b/plugins/20_events/post-proxy-ports-update new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/post-proxy-ports-update @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/pre-disable-vhost b/plugins/20_events/pre-disable-vhost new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/pre-disable-vhost @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/pre-enable-vhost b/plugins/20_events/pre-enable-vhost new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/pre-enable-vhost @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/certs/subcommands/add b/plugins/certs/subcommands/add index 2135ec253..04324b4de 100755 --- a/plugins/certs/subcommands/add +++ b/plugins/certs/subcommands/add @@ -2,7 +2,6 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/certs/functions" -source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions" is_tar_import() { declare desc="determines if we have STDIN open in an attempt to detect a streamed tar import" @@ -70,7 +69,8 @@ certs_set() { chmod 750 "$APP_SSL_PATH" chmod 640 "$APP_SSL_PATH/server.crt" "$APP_SSL_PATH/server.key" cd "$DOKKU_ROOT" - nginx_build_config "$APP" + plugn trigger post-certs-update "$APP" + plugn trigger post-domains-update "$APP" } certs_set "$@" diff --git a/plugins/certs/subcommands/generate b/plugins/certs/subcommands/generate index 8bec9120c..daa2caed8 100755 --- a/plugins/certs/subcommands/generate +++ b/plugins/certs/subcommands/generate @@ -2,7 +2,6 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/domains/functions" -source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions" certs_generate_cmd() { declare desc="generates a self-signed SSL certificate/key combo" @@ -26,7 +25,7 @@ certs_generate_cmd() { mv -f "$CERTS_GENERATE_TMP_WORK_DIR/server.key" "$CERTS_GENERATE_TMP_WORK_DIR/server.crt" "$APP_SSL_PATH" chmod 750 "$APP_SSL_PATH" chmod 640 "$APP_SSL_PATH/server.key" "$APP_SSL_PATH/server.crt" - [[ -n "$DOMAIN" ]] && (domains_add "$APP" "$DOMAIN" || nginx_build_config "$APP") + [[ -n "$DOMAIN" ]] && (domains_add "$APP" "$DOMAIN" || plugn trigger post-domains-update "$APP") dokku_log_info1 "The following is a certificate signing request that can be used" dokku_log_info1 "to generate an 'officially' signed SSL certificate for $APP at $DOMAIN" dokku_log_info1 "by a CA of your choosing." diff --git a/plugins/certs/subcommands/remove b/plugins/certs/subcommands/remove index 5a9c5a7e8..8b38f1f78 100755 --- a/plugins/certs/subcommands/remove +++ b/plugins/certs/subcommands/remove @@ -12,6 +12,7 @@ certs_remove_cmd() { if [[ -d "$APP_SSL_PATH" ]]; then dokku_log_info1 "Removing SSL endpoint from $APP" rm -rf "$APP_SSL_PATH" + plugn trigger post-certs-remove "$APP" plugn trigger post-domains-update "$APP" else dokku_log_fail "An app-specific SSL endpoint is not defined" diff --git a/plugins/common/functions b/plugins/common/functions index bfccfd280..9fb90c6d1 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -573,9 +573,8 @@ disable_app_vhost() { local APP=$1; verify_app_name "$APP" local APP_VHOST_FILE="$DOKKU_ROOT/$APP/VHOST" local APP_URLS_FILE="$DOKKU_ROOT/$APP/URLS" - local DOKKU_NGINX_PORT=$(config_get "$APP" DOKKU_NGINX_PORT) - local DOKKU_NGINX_SSL_PORT=$(config_get "$APP" DOKKU_NGINX_SSL_PORT) + plugn trigger pre-disable-vhost "$APP" if [[ -f "$APP_VHOST_FILE" ]]; then dokku_log_info1 "VHOST support disabled, deleting $APP/VHOST" rm "$APP_VHOST_FILE" @@ -584,12 +583,6 @@ disable_app_vhost() { dokku_log_info1 "VHOST support disabled, deleting $APP/URLS" rm "$APP_URLS_FILE" fi - if [[ "$DOKKU_NGINX_PORT" == "80" ]]; then - config_unset --no-restart "$APP" DOKKU_NGINX_PORT - fi - if [[ "$DOKKU_NGINX_SSL_PORT" == "443" ]]; then - config_unset --no-restart "$APP" DOKKU_NGINX_SSL_PORT - fi [[ "$2" == "--no-restart" ]] && local CONFIG_SET_ARGS=$2 # shellcheck disable=SC2086 @@ -602,7 +595,7 @@ enable_app_vhost() { local APP=$1; verify_app_name "$APP" - config_unset --no-restart "$APP" DOKKU_NGINX_PORT DOKKU_NGINX_SSL_PORT + plugn trigger pre-enable-vhost "$APP" [[ "$2" == "--no-restart" ]] && local CONFIG_SET_ARGS=$2 # shellcheck disable=SC2086 config_set $CONFIG_SET_ARGS "$APP" NO_VHOST=0 @@ -676,36 +669,43 @@ get_container_ports() { get_app_urls() { declare desc="print an app's available urls" source "$PLUGIN_AVAILABLE_PATH/config/functions" + source "$PLUGIN_AVAILABLE_PATH/domains/functions" source "$PLUGIN_AVAILABLE_PATH/proxy/functions" local APP="$2"; verify_app_name "$APP" local RAW_TCP_PORTS="$(get_app_raw_tcp_ports "$APP")" local URLS_FILE="$DOKKU_ROOT/$APP/URLS" + local DOKKU_PROXY_PORT_MAP=$(config_get "$APP" DOKKU_PROXY_PORT_MAP || true) if [[ -s "$URLS_FILE" ]]; then local app_urls="$(egrep -v "^#" "$URLS_FILE")" - if [[ -n "$RAW_TCP_PORTS" ]]; then - for url in ${app_urls[*]}; do - for p in $RAW_TCP_PORTS; do - local port_urls+="$url:$p " + if [[ -n "$DOKKU_PROXY_PORT_MAP" ]]; then + local port_map app_vhost + local app_vhosts=$(get_app_domains "$APP") + for port_map in $DOKKU_PROXY_PORT_MAP; do + local scheme="$(awk -F ':' '{ print $1 }' <<< "$port_map")" + local listen_port="$(awk -F ':' '{ print $2 }' <<< "$port_map")" + for app_vhost in $app_vhosts; do + if [[ "$listen_port" != "80" ]] && [[ "$listen_port" != "443" ]]; then + port_urls+=" $scheme://$app_vhost:$listen_port " + else + port_urls+=" $scheme://$app_vhost " + fi done done - local port_urls="$(echo "$port_urls"| xargs)" fi - local URLS=${port_urls:=$app_urls} + local port_urls="$(echo "$port_urls"| xargs)" + local URLS="$(merge_dedupe_list "$port_urls $app_urls" " ")" case "$1" in url) echo "$URLS" | tr ' ' '\n' | head -n1 ;; urls) - echo "$URLS" | tr ' ' '\n' + echo "$URLS" | tr ' ' '\n' | sort ;; esac else local SCHEME="http"; local SSL="$DOKKU_ROOT/$APP/tls" - local DOKKU_NGINX_PORT=$(config_get "$APP" DOKKU_NGINX_PORT || true) - local DOKKU_NGINX_SSL_PORT=$(config_get "$APP" DOKKU_NGINX_SSL_PORT || true) - if [[ -e "$SSL/server.crt" && -e "$SSL/server.key" ]]; then local SCHEME="https" fi @@ -724,13 +724,13 @@ get_app_urls() { done shopt -u nullglob fi - elif [[ -n "$DOKKU_NGINX_PORT" ]] || [[ -n "$DOKKU_NGINX_SSL_PORT" ]]; then - if [[ -n "$DOKKU_NGINX_PORT" ]];then - echo "http://$(< "$DOKKU_ROOT/HOSTNAME"):$DOKKU_NGINX_PORT ($(get_app_proxy_type "$APP"))" - fi - if [[ -n "$DOKKU_NGINX_SSL_PORT" ]]; then - echo "https://$(< "$DOKKU_ROOT/HOSTNAME"):$DOKKU_NGINX_SSL_PORT ($(get_app_proxy_type "$APP")-ssl)" - fi + elif [[ -n "$DOKKU_PROXY_PORT_MAP" ]]; then + local port_map + for port_map in $DOKKU_PROXY_PORT_MAP; do + local scheme="$(awk -F ':' '{ print $1 }' <<< "$port_map")" + local listen_port="$(awk -F ':' '{ print $2 }' <<< "$port_map")" + echo "$scheme://$(< "$DOKKU_ROOT/HOSTNAME"):$listen_port" + done elif [[ -n "$RAW_TCP_PORTS" ]]; then for p in $RAW_TCP_PORTS; do echo "http://$(< "$DOKKU_ROOT/HOSTNAME"):$p" @@ -812,3 +812,11 @@ add_val_to_list() { list+="${delimiter}$value" echo "$list" } + +merge_dedupe_list() { + declare desc="combine lists ($1) separated by delimiter ($2) (delimiter defaults to comma), dedupe and return list" + local input_lists="$1" delimiter="${2:-,}" + + local merged_list="$(tr "$delimiter" $'\n' <<< "$input_lists" | sort | uniq | xargs)" + echo "$merged_list" +} diff --git a/plugins/nginx-vhosts/functions b/plugins/nginx-vhosts/functions index 86f027faf..1964b45b9 100755 --- a/plugins/nginx-vhosts/functions +++ b/plugins/nginx-vhosts/functions @@ -14,6 +14,12 @@ validate_nginx() { set -e if [[ "$exit_code" -ne "0" ]]; then sudo /usr/sbin/nginx -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" fi } @@ -61,30 +67,51 @@ configure_nginx_ports() { local RAW_TCP_PORTS="$(get_app_raw_tcp_ports "$APP")" local DOKKU_NGINX_PORT=$(config_get "$APP" DOKKU_NGINX_PORT) local DOKKU_NGINX_SSL_PORT=$(config_get "$APP" DOKKU_NGINX_SSL_PORT) + local DOKKU_PROXY_PORT_MAP=$(config_get "$APP" DOKKU_PROXY_PORT_MAP) local IS_APP_VHOST_ENABLED="$(is_app_vhost_enabled "$APP")" + local UPSTREAM_PORT="5000" - if [[ -z "$DOKKU_NGINX_PORT" ]]; then - if [[ -z "$RAW_TCP_PORTS" ]]; then + if [[ -z "$DOKKU_NGINX_PORT" ]] && [[ -z "$RAW_TCP_PORTS" ]]; then + if [[ "$IS_APP_VHOST_ENABLED" == "false" ]]; then + dokku_log_info1 "no nginx port set. setting to random open high port" + local NGINX_PORT=$(get_available_port) + else + local NGINX_PORT=80 + fi + config_set --no-restart "$APP" DOKKU_NGINX_PORT="$NGINX_PORT" + fi + if [[ -z "$DOKKU_NGINX_SSL_PORT" ]]; then + if (is_ssl_enabled "$APP") && [[ -z "$RAW_TCP_PORTS" ]]; then if [[ "$IS_APP_VHOST_ENABLED" == "false" ]]; then - dokku_log_info1 "no nginx port set. setting to random open high port" - local NGINX_PORT=$(get_available_port) + dokku_log_info1 "no nginx ssl port set. setting to random open high port" + local NGINX_SSL_PORT=$(get_available_port) else - local NGINX_PORT=80 + local NGINX_SSL_PORT=443 fi fi - if [[ -n "$NGINX_PORT" ]]; then - config_set --no-restart "$APP" DOKKU_NGINX_PORT="${NGINX_PORT}" - fi + config_set --no-restart "$APP" DOKKU_NGINX_SSL_PORT="$NGINX_SSL_PORT" fi - - if (is_ssl_enabled "$APP") && [[ -z "$DOKKU_NGINX_SSL_PORT" ]] && [[ -z "$RAW_TCP_PORTS" ]]; then - if [[ "$IS_APP_VHOST_ENABLED" == "false" ]]; then - dokku_log_info1 "no nginx ssl port set. setting to random open high port" - local NGINX_SSL_PORT=$(get_available_port) + if [[ -z "$DOKKU_PROXY_PORT_MAP" ]]; then + if [[ -n "$RAW_TCP_PORTS" ]]; then + local RAW_TCP_PORT + for RAW_TCP_PORT in $RAW_TCP_PORTS; do + local PROXY_PORT_MAP+=" http:${RAW_TCP_PORT}:${RAW_TCP_PORT} " + done else - local NGINX_SSL_PORT=443 + local NGINX_PORT=${NGINX_PORT:-$DOKKU_NGINX_PORT} + local NGINX_SSL_PORT=${NGINX_SSL_PORT:-$DOKKU_NGINX_SSL_PORT} + [[ -f "$DOKKU_ROOT/$APP/PORT.web.1" ]] && local UPSTREAM_PORT="$(< "$DOKKU_ROOT/$APP/PORT.web.1")" + if [[ -n "$NGINX_PORT" ]] && [[ -n "$NGINX_SSL_PORT" ]]; then + local PROXY_PORT_MAP+=" http:${NGINX_PORT}:$UPSTREAM_PORT https:${NGINX_SSL_PORT}:$UPSTREAM_PORT " + elif [[ -n "$NGINX_PORT" ]]; then + local PROXY_PORT_MAP+=" http:${NGINX_PORT}:$UPSTREAM_PORT " + fi + fi + if [[ -n "$PROXY_PORT_MAP" ]]; then + local PROXY_PORT_MAP="$(echo "$PROXY_PORT_MAP" | xargs)" + local PROXY_PORT_MAP+=" $(merge_dedupe_list "$(remove_val_from_list "$PORT_MAP" "$DOKKU_PROXY_PORT_MAP" " ")" " ") " + config_set --no-restart "$APP" DOKKU_PROXY_PORT_MAP="$PROXY_PORT_MAP" fi - config_set --no-restart "$APP" DOKKU_NGINX_SSL_PORT="${NGINX_SSL_PORT}" fi } @@ -93,12 +120,12 @@ validate_ssl_domains() { local APP=$1; verify_app_name "$APP" local SSL_HOSTNAME=$(get_ssl_hostnames "$APP") local SSL_HOSTNAME_REGEX=$(echo "$SSL_HOSTNAME" | xargs | sed 's|\.|\\.|g' | sed 's/\*/\[^\.\]\*/g' | sed 's/ /|/g') - local domain if ! (egrep -q "^${SSL_HOSTNAME_REGEX}$" "$VHOST_PATH" &> /dev/null); then dokku_log_info1 "No matching configured domains for $APP found in SSL certificate. Your app will show as insecure in a browser if accessed via SSL" dokku_log_info1 "Please add appropriate domains via the dokku domains command" [[ -n "$NONSSL_VHOSTS" ]] && dokku_log_info1 "Configured domains for app:" + local domain for domain in $(echo "$NONSSL_VHOSTS"| xargs); do dokku_log_info2 "$domain" done @@ -169,19 +196,29 @@ nginx_build_config() { local DOKKU_APP_PORT_FILE="${DOKKU_APP_IP_FILE//IP/PORT}" local DOKKU_APP_LISTENER_IP=$(< "$DOKKU_APP_IP_FILE") local DOKKU_APP_LISTENER_PORT=$(< "$DOKKU_APP_PORT_FILE") - - if [[ -z "$RAW_TCP_PORTS" ]]; then - local DOKKU_APP_LISTENERS+=" $DOKKU_APP_LISTENER_IP:$DOKKU_APP_LISTENER_PORT " - else - local DOKKU_APP_LISTENERS+=" $DOKKU_APP_LISTENER_IP " - fi - local DOKKU_APP_LISTENERS=$(echo "$DOKKU_APP_LISTENERS" | xargs) + local DOKKU_APP_LISTENERS+="$DOKKU_APP_LISTENER_IP:$DOKKU_APP_LISTENER_PORT " done + local DOKKU_APP_LISTENERS="$(echo "$DOKKU_APP_LISTENERS" | xargs)" shopt -u nullglob elif [[ -n "$DOKKU_APP_LISTEN_PORT" ]] && [[ -n "$DOKKU_APP_LISTEN_IP" ]]; then local PASSED_LISTEN_IP_PORT=true fi + # setup nginx listen ports + configure_nginx_ports "$APP" + local NGINX_PORT=$(config_get "$APP" DOKKU_NGINX_PORT) + local NGINX_SSL_PORT=$(config_get "$APP" DOKKU_NGINX_SSL_PORT) + local PROXY_PORT_MAP=$(config_get "$APP" DOKKU_PROXY_PORT_MAP) + + local PORT_MAP + for PORT_MAP in $PROXY_PORT_MAP; do + local PROXY_UPSTREAM_PORT="$(awk -F ':' '{ print $3 }' <<< "$PORT_MAP")" + if [[ "$(is_val_in_list "$PROXY_UPSTREAM_PORT" "$PROXY_UPSTREAM_PORTS" " ")" == "false" ]]; then + local PROXY_UPSTREAM_PORTS+="$PROXY_UPSTREAM_PORT " + fi + done + local PROXY_UPSTREAM_PORTS="$(echo "$PROXY_UPSTREAM_PORTS" | xargs)" + local NGINX_BUILD_CONFIG_TMP_WORK_DIR=$(mktemp -d /tmp/dokku_nginx_template.XXXXX) local NGINX_CONF=$(mktemp --tmpdir="${NGINX_BUILD_CONFIG_TMP_WORK_DIR}" "nginx.conf.XXXXXX") local CUSTOM_NGINX_TEMPLATE="$NGINX_BUILD_CONFIG_TMP_WORK_DIR/$NGINX_TEMPLATE_NAME" @@ -195,11 +232,6 @@ nginx_build_config() { local NGINX_TEMPLATE_SOURCE="app-supplied" fi - # setup nginx listen ports - configure_nginx_ports "$APP" - local NGINX_PORT=$(config_get "$APP" DOKKU_NGINX_PORT) - local NGINX_SSL_PORT=$(config_get "$APP" DOKKU_NGINX_SSL_PORT) - local NONSSL_VHOSTS=$(get_app_domains "$APP") local NOSSL_SERVER_NAME=$(echo "$NONSSL_VHOSTS" | xargs) if is_ssl_enabled "$APP"; then @@ -227,12 +259,13 @@ nginx_build_config() { SPDY_SUPPORTED="$SPDY_SUPPORTED" DOKKU_APP_LISTEN_PORT="$DOKKU_APP_LISTEN_PORT" DOKKU_APP_LISTEN_IP="$DOKKU_APP_LISTEN_IP" APP_SSL_PATH="$APP_SSL_PATH" SSL_INUSE="$SSL_INUSE" SSL_SERVER_NAME="$SSL_SERVER_NAME" - NGINX_PORT="$NGINX_PORT" NGINX_SSL_PORT="$NGINX_SSL_PORT" RAW_TCP_PORTS="$RAW_TCP_PORTS") + NGINX_PORT="$NGINX_PORT" NGINX_SSL_PORT="$NGINX_SSL_PORT" RAW_TCP_PORTS="$RAW_TCP_PORTS" + PROXY_PORT_MAP="$PROXY_PORT_MAP" PROXY_UPSTREAM_PORTS="$PROXY_UPSTREAM_PORTS") # execute sigil template processing xargs -i echo "-----> Configuring {}...(using $NGINX_TEMPLATE_SOURCE template)" <<< "$(echo "${SSL_VHOSTS}" "${NONSSL_VHOSTS}" | tr ' ' '\n' | sort -u)" # echo "sigil ${SIGIL_PARAMS[@]}" - sigil "${SIGIL_PARAMS[@]}" > "$NGINX_CONF" + sigil "${SIGIL_PARAMS[@]}" | cat -s > "$NGINX_CONF" if is_deployed "$APP"; then dokku_log_info1 "Creating $SCHEME nginx.conf" diff --git a/plugins/nginx-vhosts/post-certs-remove b/plugins/nginx-vhosts/post-certs-remove new file mode 100755 index 000000000..fded1290a --- /dev/null +++ b/plugins/nginx-vhosts/post-certs-remove @@ -0,0 +1,16 @@ +#!/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/proxy/functions" + +nginx_post_certs_remove() { + declare desc="unset port config vars after SSL cert is removed" + local trigger="nginx_post_certs_remove" + local APP="$1" + if [[ "$(get_app_proxy_type "$APP")" == "nginx" ]]; then + config_unset --no-restart "$APP" DOKKU_NGINX_PORT DOKKU_NGINX_SSL_PORT DOKKU_PROXY_PORT_MAP + fi +} + +nginx_post_certs_remove "$@" diff --git a/plugins/nginx-vhosts/post-certs-update b/plugins/nginx-vhosts/post-certs-update new file mode 100755 index 000000000..22756ea18 --- /dev/null +++ b/plugins/nginx-vhosts/post-certs-update @@ -0,0 +1,28 @@ +#!/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/proxy/functions" + +nginx_post_certs_update() { + declare desc="unset port config vars after SSL cert is added" + local trigger="nginx_post_certs_update" + local APP="$1" + if [[ "$(get_app_proxy_type "$APP")" == "nginx" ]]; then + local DOKKU_NGINX_PORT=$(config_get "$APP" DOKKU_NGINX_PORT) + local DOKKU_NGINX_SSL_PORT=$(config_get "$APP" DOKKU_NGINX_SSL_PORT) + local DOKKU_PROXY_PORT_MAP=$(config_get "$APP" DOKKU_PROXY_PORT_MAP) + + if [[ "$DOKKU_NGINX_PORT" == "80" ]]; then + config_unset --no-restart "$APP" DOKKU_NGINX_PORT + fi + if [[ "$DOKKU_NGINX_SSL_PORT" == "443" ]]; then + config_unset --no-restart "$APP" DOKKU_NGINX_SSL_PORT + fi + if [[ "$DOKKU_PROXY_PORT_MAP" == *http:80:* ]]; then + config_unset --no-restart "$APP" DOKKU_PROXY_PORT_MAP + fi + fi +} + +nginx_post_certs_update "$@" diff --git a/plugins/nginx-vhosts/post-proxy-ports-update b/plugins/nginx-vhosts/post-proxy-ports-update new file mode 100755 index 000000000..2b05dc3e9 --- /dev/null +++ b/plugins/nginx-vhosts/post-proxy-ports-update @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions" +source "$PLUGIN_AVAILABLE_PATH/proxy/functions" + +nginx_post_proxy_ports_update() { + declare desc="calls nginx build_config when domains are updated" + local trigger="nginx_post_proxy_ports_update" + local APP="$1" ACTION="$2" + if [[ "$(get_app_proxy_type "$APP")" == "nginx" ]]; then + nginx_build_config "$APP" + fi +} + +nginx_post_proxy_ports_update "$@" diff --git a/plugins/nginx-vhosts/pre-disable-vhost b/plugins/nginx-vhosts/pre-disable-vhost new file mode 100755 index 000000000..7a65a58b3 --- /dev/null +++ b/plugins/nginx-vhosts/pre-disable-vhost @@ -0,0 +1,16 @@ +#!/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/proxy/functions" + +nginx_pre_disable_vhost_trigger() { + declare desc="unset port prior to vhosts being disabled" + local trigger="nginx_pre_disable_vhost_trigger" + local APP="$1" + if [[ "$(get_app_proxy_type "$APP")" == "nginx" ]]; then + config_unset --no-restart "$APP" DOKKU_NGINX_PORT DOKKU_NGINX_SSL_PORT DOKKU_PROXY_PORT_MAP + fi +} + +nginx_pre_disable_vhost_trigger "$@" diff --git a/plugins/nginx-vhosts/pre-enable-vhost b/plugins/nginx-vhosts/pre-enable-vhost new file mode 100755 index 000000000..0ef752eb4 --- /dev/null +++ b/plugins/nginx-vhosts/pre-enable-vhost @@ -0,0 +1,16 @@ +#!/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/proxy/functions" + +nginx_pre_enable_vhost_trigger() { + declare desc="unset port prior to vhosts being enabled" + local trigger="nginx_pre_enable_vhost_trigger" + local APP="$1" + if [[ "$(get_app_proxy_type "$APP")" == "nginx" ]]; then + config_unset --no-restart "$APP" DOKKU_NGINX_PORT DOKKU_NGINX_SSL_PORT DOKKU_PROXY_PORT_MAP + fi +} + +nginx_pre_enable_vhost_trigger "$@" diff --git a/plugins/nginx-vhosts/templates/location.config b/plugins/nginx-vhosts/templates/location.config deleted file mode 100644 index 83178d758..000000000 --- a/plugins/nginx-vhosts/templates/location.config +++ /dev/null @@ -1,20 +0,0 @@ - location / { - - gzip on; - gzip_min_length 1100; - gzip_buffers 4 32k; - gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/x-javascript application/json application/xml application/rss+xml font/truetype application/x-font-ttf font/opentype application/vnd.ms-fontobject image/svg+xml; - gzip_vary on; - gzip_comp_level 6; - - proxy_pass http://{{ .APP }}; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_set_header X-Forwarded-Port $server_port; - proxy_set_header X-Request-Start $msec; - } - include {{ .DOKKU_ROOT }}/{{ .APP }}/nginx.conf.d/*.conf; diff --git a/plugins/nginx-vhosts/templates/log.config b/plugins/nginx-vhosts/templates/log.config deleted file mode 100644 index 435ec58b0..000000000 --- a/plugins/nginx-vhosts/templates/log.config +++ /dev/null @@ -1,2 +0,0 @@ - access_log /var/log/nginx/{{ .APP }}-access.log; - error_log /var/log/nginx/{{ .APP }}-error.log; diff --git a/plugins/nginx-vhosts/templates/nginx.conf.sigil b/plugins/nginx-vhosts/templates/nginx.conf.sigil index 0c5b335a0..c67a9564e 100644 --- a/plugins/nginx-vhosts/templates/nginx.conf.sigil +++ b/plugins/nginx-vhosts/templates/nginx.conf.sigil @@ -1,10 +1,86 @@ -{{ if .RAW_TCP_PORTS }} -{{ include "tcp.server.config" . }} -{{ else if .SSL_INUSE }} -{{ include "nossl.server.config" . }} -{{ include "ssl.server.config" . }} -{{ include "upstream.config" . }} +{{ range $port_map := .PROXY_PORT_MAP | split " " }} +{{ $port_map_list := $port_map | split ":" }} +{{ $scheme := index $port_map_list 0 }} +{{ $listen_port := index $port_map_list 1 }} +{{ $upstream_port := index $port_map_list 2 }} + +{{ if eq $scheme "http" }} +server { + listen [::]:{{ $listen_port }}; + listen {{ $listen_port }}; + {{ if $.NOSSL_SERVER_NAME }}server_name {{ $.NOSSL_SERVER_NAME }}; {{ end }} + access_log /var/log/nginx/{{ $.APP }}-access.log; + error_log /var/log/nginx/{{ $.APP }}-error.log; +{{ if (and (eq $listen_port "80") ($.SSL_INUSE)) }} + return 301 https://$host:{{ $.NGINX_SSL_PORT }}$request_uri; {{ else }} -{{ include "nossl.server.config" . }} -{{ include "upstream.config" . }} + location / { + + gzip on; + gzip_min_length 1100; + gzip_buffers 4 32k; + gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/x-javascript application/json application/xml application/rss+xml font/truetype application/x-font-ttf font/opentype application/vnd.ms-fontobject image/svg+xml; + gzip_vary on; + gzip_comp_level 6; + + proxy_pass http://{{ $.APP }}-{{ $upstream_port }}; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Request-Start $msec; + } + include {{ $.DOKKU_ROOT }}/{{ $.APP }}/nginx.conf.d/*.conf; {{ end }} +} +{{ else if eq $scheme "https"}} +server { + listen [::]:{{ $listen_port }} ssl {{ if eq $.SPDY_SUPPORTED "true" }}spdy{{ else }}http2{{ end }}; + listen {{ $listen_port }} ssl {{ if eq $.SPDY_SUPPORTED "true" }}spdy{{ else }}http2{{ end }}; + {{ if $.SSL_SERVER_NAME }}server_name {{ $.SSL_SERVER_NAME }}; {{ end }} + {{ if $.NOSSL_SERVER_NAME }}server_name {{ $.NOSSL_SERVER_NAME }}; {{ end }} + access_log /var/log/nginx/{{ $.APP }}-access.log; + error_log /var/log/nginx/{{ $.APP }}-error.log; + + ssl_certificate {{ $.APP_SSL_PATH }}/server.crt; + ssl_certificate_key {{ $.APP_SSL_PATH }}/server.key; + + keepalive_timeout 70; + {{ if eq $.SPDY_SUPPORTED "true" }}add_header Alternate-Protocol {{ .NGINX_SSL_PORT }}:npn-spdy/2;{{ end }} + + location / { + + gzip on; + gzip_min_length 1100; + gzip_buffers 4 32k; + gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/x-javascript application/json application/xml application/rss+xml font/truetype application/x-font-ttf font/opentype application/vnd.ms-fontobject image/svg+xml; + gzip_vary on; + gzip_comp_level 6; + + proxy_pass http://{{ $.APP }}-{{ $upstream_port }}; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Forwarded-Port $server_port; + proxy_set_header X-Request-Start $msec; + } + include {{ $.DOKKU_ROOT }}/{{ $.APP }}/nginx.conf.d/*.conf; +} +{{ end }}{{ end }} + +{{ if $.DOKKU_APP_LISTENERS }} +{{ range $upstream_port := $.PROXY_UPSTREAM_PORTS | split " " }} +upstream {{ $.APP }}-{{ $upstream_port }} { +{{ range $listeners := $.DOKKU_APP_LISTENERS | split " " }} +{{ $listener_list := $listeners | split ":" }} +{{ $listener_ip := index $listener_list 0 }} +{{ $listener_port := index $listener_list 1 }} + server {{ $listener_ip }}:{{ $upstream_port }};{{ end }} +} +{{ end }}{{ end }} diff --git a/plugins/nginx-vhosts/templates/nossl.server.config b/plugins/nginx-vhosts/templates/nossl.server.config deleted file mode 100644 index 3998232af..000000000 --- a/plugins/nginx-vhosts/templates/nossl.server.config +++ /dev/null @@ -1,11 +0,0 @@ -server { - listen [::]:{{ .NGINX_PORT }}; - listen {{ .NGINX_PORT }}; - {{ if .NOSSL_SERVER_NAME }}server_name {{ .NOSSL_SERVER_NAME }}; {{ end }} -{{ include "log.config" . }} -{{ if .SSL_INUSE }} - return 301 https://$host:{{ .NGINX_SSL_PORT }}$request_uri; -{{ else }} -{{ include "location.config" . }} -{{ end }} -} diff --git a/plugins/nginx-vhosts/templates/ssl.server.config b/plugins/nginx-vhosts/templates/ssl.server.config deleted file mode 100644 index 336be3c9d..000000000 --- a/plugins/nginx-vhosts/templates/ssl.server.config +++ /dev/null @@ -1,13 +0,0 @@ -server { - listen [::]:{{ .NGINX_SSL_PORT }} ssl {{ if eq .SPDY_SUPPORTED "true" }}spdy{{ else }}http2{{ end }}; - listen {{ .NGINX_SSL_PORT }} ssl {{ if eq .SPDY_SUPPORTED "true" }}spdy{{ else }}http2{{ end }}; - {{ if .SSL_SERVER_NAME }}server_name {{ .SSL_SERVER_NAME }}; {{ end }} - {{ if .NOSSL_SERVER_NAME }}server_name {{ .NOSSL_SERVER_NAME }}; {{ end }} -{{ include "log.config" . }} - ssl_certificate {{ .APP_SSL_PATH }}/server.crt; - ssl_certificate_key {{ .APP_SSL_PATH }}/server.key; - - keepalive_timeout 70; - {{ if eq .SPDY_SUPPORTED "true" }}add_header Alternate-Protocol {{ .NGINX_SSL_PORT }}:npn-spdy/2;{{ end }} -{{ include "location.config" . }} -} diff --git a/plugins/nginx-vhosts/templates/tcp.server.config b/plugins/nginx-vhosts/templates/tcp.server.config deleted file mode 100644 index ab557e509..000000000 --- a/plugins/nginx-vhosts/templates/tcp.server.config +++ /dev/null @@ -1,35 +0,0 @@ -{{ range .RAW_TCP_PORTS | split " " }} -{{ $port := . }} -server { - listen [::]:{{ $port }}; - listen {{ $port }}; - {{ if $.NOSSL_SERVER_NAME }}server_name {{ $.NOSSL_SERVER_NAME }}; {{ end }} - access_log /var/log/nginx/{{ $.APP }}-access.log; - error_log /var/log/nginx/{{ $.APP }}-error.log; - - location / { - - gzip on; - gzip_min_length 1100; - gzip_buffers 4 32k; - gzip_types text/css text/javascript text/xml text/plain text/x-component application/javascript application/x-javascript application/json application/xml application/rss+xml font/truetype application/x-font-ttf font/opentype application/vnd.ms-fontobject image/svg+xml; - gzip_vary on; - gzip_comp_level 6; - - proxy_pass http://{{ $.APP }}-{{ $port }}; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $http_host; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-For $remote_addr; - proxy_set_header X-Forwarded-Port $server_port; - proxy_set_header X-Request-Start $msec; - } - include {{ $.DOKKU_ROOT }}/{{ $.APP }}/nginx.conf.d/*.conf; -} -{{ if $.DOKKU_APP_LISTENERS }} -upstream {{ $.APP }}-{{ $port }} { -{{ range $.DOKKU_APP_LISTENERS | split " " }} server {{ . }}:{{ $port }};{{ end }} -} -{{ end }}{{ end }} diff --git a/plugins/nginx-vhosts/templates/upstream.config b/plugins/nginx-vhosts/templates/upstream.config deleted file mode 100644 index 1f9a08e70..000000000 --- a/plugins/nginx-vhosts/templates/upstream.config +++ /dev/null @@ -1,9 +0,0 @@ -{{ if .DOKKU_APP_LISTENERS }} -upstream {{ .APP }} { -{{ range .DOKKU_APP_LISTENERS | split " " }} server {{ . }}; -{{ end }}} -{{ else if .PASSED_LISTEN_IP_PORT }} -upstream {{ .APP }} { - server {{ .DOKKU_APP_LISTEN_IP }}:{{ .DOKKU_APP_LISTEN_PORT }}; -} -{{ end }} diff --git a/plugins/proxy/commands b/plugins/proxy/commands index 318e873f9..a1bcab6e4 100755 --- a/plugins/proxy/commands +++ b/plugins/proxy/commands @@ -7,10 +7,14 @@ case "$1" in help_content_func () { declare desc="return proxy plugin help content" cat<, Show proxy for app + proxy , Show proxy settings for app proxy:enable , Enable proxy for app proxy:disable , Disable proxy for app - proxy:set , NOT IMPLEMENTED YET!! + proxy:ports , List proxy port mappings for app + proxy:ports-clear , Clear all proxy port mappings for app + proxy:ports-add :: [::...], Set proxy port mappings for app + proxy:ports-remove [...], Unset proxy port mappings for app + proxy:set , Set proxy type for app help_content } diff --git a/plugins/proxy/functions b/plugins/proxy/functions index 5f3dce1b6..010136719 100755 --- a/plugins/proxy/functions +++ b/plugins/proxy/functions @@ -18,7 +18,25 @@ is_app_proxy_enabled() { get_app_proxy_type() { declare desc="return app proxy type" local APP="$1"; verify_app_name "$APP" - local APP_PROXY_TYPE="nginx" + local DOKKU_APP_PROXY_TYPE="$(config_get "$APP" DOKKU_APP_PROXY_TYPE || true)" + local APP_PROXY_TYPE="${DOKKU_APP_PROXY_TYPE:-nginx}" - echo $APP_PROXY_TYPE + echo "$APP_PROXY_TYPE" +} + +list_app_proxy_ports() { + declare desc="list proxy port mappings for an app" + local APP="$1"; verify_app_name "$APP" + local PROXY_PORT_MAP="$(config_get "$APP" DOKKU_PROXY_PORT_MAP || true)" + + if [[ -n "$PROXY_PORT_MAP" ]]; then + dokku_log_info1_quiet "Port mappings for $APP" + dokku_col_log_info1_quiet "scheme" "host port" "container port" + local port_map + for port_map in $PROXY_PORT_MAP; do + dokku_col_log_msg "$(cut -d ':' -f1 <<< "$port_map")" "$(cut -d ':' -f2 <<< "$port_map")" "$(cut -d ':' -f3 <<< "$port_map")" + done + else + dokku_log_fail "No port mappings configured for app ($APP)" + fi } diff --git a/plugins/proxy/subcommands/ports b/plugins/proxy/subcommands/ports new file mode 100755 index 000000000..47a1a6cab --- /dev/null +++ b/plugins/proxy/subcommands/ports @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/proxy/functions" +source "$PLUGIN_AVAILABLE_PATH/config/functions" + +proxy_ports_cmd() { + declare desc="cmd wrapper to list proxy port mappings for an app" + local cmd="proxy:ports" + local APP="$2"; verify_app_name "$APP" + + list_app_proxy_ports "$APP" +} + +proxy_ports_cmd "$@" diff --git a/plugins/proxy/subcommands/ports-add b/plugins/proxy/subcommands/ports-add new file mode 100755 index 000000000..911da22b2 --- /dev/null +++ b/plugins/proxy/subcommands/ports-add @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/config/functions" + +proxy_ports_add_cmd() { + declare desc="add proxy port mappings from an app" + local cmd="proxy:add" + local APP="$2"; verify_app_name "$APP" + local PROXY_PORT_MAP="$(config_get "$APP" DOKKU_PROXY_PORT_MAP || true)" + shift 2 + + local INPUT_PORTS="$*" + if [[ -n "$INPUT_PORTS" ]]; then + PROXY_PORT_MAP="$(merge_dedupe_list "$PROXY_PORT_MAP $INPUT_PORTS" " ")" + config_set --no-restart "$APP" DOKKU_PROXY_PORT_MAP="$PROXY_PORT_MAP" + else + dokku proxy:help + dokku_log_fail "No port mapping specified. Exiting..." + fi + plugn trigger post-proxy-ports-update "$APP" "add" +} + +proxy_ports_add_cmd "$@" diff --git a/plugins/proxy/subcommands/ports-clear b/plugins/proxy/subcommands/ports-clear new file mode 100755 index 000000000..3b57abf44 --- /dev/null +++ b/plugins/proxy/subcommands/ports-clear @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/config/functions" + +proxy_ports_clear_cmd() { + declare desc="clear all proxy port mappings for an app" + local cmd="proxy:clear" + local APP="$2"; verify_app_name "$APP" + + config_unset --no-restart "$APP" DOKKU_PROXY_PORT_MAP + plugn trigger post-proxy-ports-update "$APP" "clear" +} + +proxy_ports_clear_cmd "$@" diff --git a/plugins/proxy/subcommands/ports-remove b/plugins/proxy/subcommands/ports-remove new file mode 100755 index 000000000..2c1fe0a90 --- /dev/null +++ b/plugins/proxy/subcommands/ports-remove @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/proxy/functions" +source "$PLUGIN_AVAILABLE_PATH/config/functions" + +proxy_ports_remove_cmd() { + declare desc="remove specific proxy port mappings from an app" + local cmd="proxy:ports-remove" + local APP="$2"; verify_app_name "$APP" + local PROXY_PORT_MAP="$(config_get "$APP" DOKKU_PROXY_PORT_MAP || true)" + shift 2 + + local INPUT_PORTS="$*" + if [[ -n "$INPUT_PORTS" ]]; then + local port + for port in $INPUT_PORTS; do + PROXY_PORT_MAP="$(sed -r "s/[[:alnum:]]+:$port:[[:alnum:]]+//g" <<< "$PROXY_PORT_MAP")" + done + PROXY_PORT_MAP="$(echo "$PROXY_PORT_MAP" | xargs)" + PROXY_PORT_MAP="$(merge_dedupe_list "$PROXY_PORT_MAP" " ")" + if [[ -n "$PROXY_PORT_MAP" ]]; then + config_set --no-restart "$APP" DOKKU_PROXY_PORT_MAP="$PROXY_PORT_MAP" + else + config_unset --no-restart "$APP" DOKKU_PROXY_PORT_MAP + fi + plugn trigger post-proxy-ports-update "$APP" "remove" + else + dokku_log_warn "No port mapping specified. Please supply a valid configured host port" + list_app_proxy_ports "$APP" + dokku_log_fail "exiting..." + fi +} + +proxy_ports_remove_cmd "$@" diff --git a/plugins/proxy/subcommands/set b/plugins/proxy/subcommands/set index ac08a58ec..33d601d62 100755 --- a/plugins/proxy/subcommands/set +++ b/plugins/proxy/subcommands/set @@ -1,6 +1,7 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/config/functions" proxy_set_cmd() { declare desc="enable proxy for app via command line" @@ -8,7 +9,13 @@ proxy_set_cmd() { [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" local APP="$2"; verify_app_name "$APP" - dokku_log_info1 "proxy:set not implemented" + local PROXY_TYPE="$3" + + if [[ -n "$PROXY_TYPE" ]]; then + config_set --no-restart "$APP" DOKKU_APP_PROXY_TYPE="$PROXY_TYPE" + else + dokku_log_fail "Please specify a proxy type!" + fi } proxy_set_cmd "$@" diff --git a/tests/unit/20_nginx-vhosts_1.bats b/tests/unit/20_nginx-vhosts_1.bats index 95d98b553..65d1b959d 100644 --- a/tests/unit/20_nginx-vhosts_1.bats +++ b/tests/unit/20_nginx-vhosts_1.bats @@ -21,8 +21,10 @@ teardown() { HOSTNAME=$(< "$DOKKU_ROOT/HOSTNAME") check_urls http://${HOSTNAME}:[0-9]+ - NGINX_PORT="$(config_get $TEST_APP DOKKU_NGINX_PORT)" - assert_http_success http://${HOSTNAME}:${NGINX_PORT} + URLS=$(dokku --quiet urls "$TEST_APP") + for URL in $URLS; do + assert_http_success $URL + done dokku domains:enable $TEST_APP check_urls http://${TEST_APP}.dokku.me @@ -59,8 +61,10 @@ teardown() { HOSTNAME=$(< "$DOKKU_ROOT/HOSTNAME") check_urls http://${HOSTNAME}:[0-9]+ - NGINX_PORT="$(config_get $TEST_APP DOKKU_NGINX_PORT)" - assert_http_success http://${HOSTNAME}:${NGINX_PORT} + URLS=$(dokku --quiet urls "$TEST_APP") + for URL in $URLS; do + assert_http_success $URL + done } @test "(nginx-vhosts) nginx:build-config (without global VHOST and IPv4 address set as HOSTNAME)" { @@ -71,8 +75,10 @@ teardown() { HOSTNAME=$(< "$DOKKU_ROOT/HOSTNAME") check_urls http://${HOSTNAME}:[0-9]+ - NGINX_PORT="$(config_get $TEST_APP DOKKU_NGINX_PORT)" - assert_http_success http://${HOSTNAME}:${NGINX_PORT} + URLS=$(dokku --quiet urls "$TEST_APP") + for URL in $URLS; do + assert_http_success $URL + done } @test "(nginx-vhosts) nginx:build-config (without global VHOST and IPv6 address set as HOSTNAME)" { @@ -101,11 +107,11 @@ teardown() { } @test "(nginx-vhosts) nginx:build-config (xip.io style hostnames)" { - echo "127.0.0.1.xip.io" > "$DOKKU_ROOT/VHOST" + echo "127.0.0.1.xip.io.dokku.me" > "$DOKKU_ROOT/VHOST" deploy_app - check_urls http://${TEST_APP}.127.0.0.1.xip.io - assert_http_success http://${TEST_APP}.127.0.0.1.xip.io + check_urls http://${TEST_APP}.127.0.0.1.xip.io.dokku.me + assert_http_success http://${TEST_APP}.127.0.0.1.xip.io.dokku.me } @test "(nginx-vhosts) nginx:build-config (dockerfile expose)" { diff --git a/tests/unit/40_core_2.bats b/tests/unit/40_core_2.bats index e8cc44671..b3d860243 100644 --- a/tests/unit/40_core_2.bats +++ b/tests/unit/40_core_2.bats @@ -57,20 +57,20 @@ build_nginx_config() { setup_test_tls assert_urls "https://dokku.me" build_nginx_config - assert_urls "https://${TEST_APP}.dokku.me" + assert_urls "http://${TEST_APP}.dokku.me" "https://${TEST_APP}.dokku.me" add_domain "test.dokku.me" - assert_urls "https://test.dokku.me" "https://${TEST_APP}.dokku.me" + assert_urls "http://test.dokku.me" "http://${TEST_APP}.dokku.me" "https://test.dokku.me" "https://${TEST_APP}.dokku.me" } @test "(core) urls (wildcard ssl)" { setup_test_tls wildcard assert_urls "https://dokku.me" build_nginx_config - assert_urls "https://${TEST_APP}.dokku.me" + assert_urls "http://${TEST_APP}.dokku.me" "https://${TEST_APP}.dokku.me" add_domain "test.dokku.me" - assert_urls "https://test.dokku.me" "https://${TEST_APP}.dokku.me" + assert_urls "http://test.dokku.me" "http://${TEST_APP}.dokku.me" "https://test.dokku.me" "https://${TEST_APP}.dokku.me" add_domain "dokku.example.com" - assert_urls "https://dokku.example.com" "https://test.dokku.me" "https://${TEST_APP}.dokku.me" + assert_urls "http://dokku.example.com" "http://test.dokku.me" "http://${TEST_APP}.dokku.me" "https://dokku.example.com" "https://test.dokku.me" "https://${TEST_APP}.dokku.me" } @test "(core) git-remote (off-port)" { diff --git a/tests/unit/40_nginx-vhosts_2.bats b/tests/unit/40_nginx-vhosts_2.bats index 902e253fa..35d86651c 100644 --- a/tests/unit/40_nginx-vhosts_2.bats +++ b/tests/unit/40_nginx-vhosts_2.bats @@ -91,7 +91,7 @@ assert_error_log() { assert_http_success "https://${CUSTOM_TEMPLATE_SSL_DOMAIN}" } -@test "(nginx-vhosts) nginx:build-config (custom nginx template - default path)" { +@test "(nginx-vhosts) nginx:build-config (custom nginx template - no ssl)" { add_domain "www.test.app.dokku.me" deploy_app nodejs-express dokku@dokku.me:$TEST_APP custom_nginx_template assert_nonssl_domain "www.test.app.dokku.me" diff --git a/tests/unit/40_proxy.bats b/tests/unit/40_proxy.bats index 572d98a4e..99882c890 100644 --- a/tests/unit/40_proxy.bats +++ b/tests/unit/40_proxy.bats @@ -62,3 +62,50 @@ assert_external_port() { assert_external_port $(< $CID_FILE) failure done } + +@test "(proxy) proxy:ports (list/add/remove/clear)" { + run dokku proxy:ports-add $TEST_APP http:8080:5000 https:8443:5000 + echo "output: "$output + echo "status: "$status + assert_success + + run /bin/bash -c "dokku --quiet proxy:ports $TEST_APP | xargs" + echo "output: "$output + echo "status: "$status + assert_output "http 8080 5000 https 8443 5000" + + run /bin/bash -c "dokku proxy:ports-remove $TEST_APP 8080" + echo "output: "$output + echo "status: "$status + assert_success + + run /bin/bash -c "dokku --quiet proxy:ports $TEST_APP | xargs" + echo "output: "$output + echo "status: "$status + assert_output "https 8443 5000" + + run /bin/bash -c "dokku proxy:ports-clear $TEST_APP" + echo "output: "$output + echo "status: "$status + assert_success + + run /bin/bash -c "dokku --quiet proxy:ports $TEST_APP | xargs" + echo "output: "$output + echo "status: "$status + assert_output "http 80 5000" +} + +@test "(proxy) proxy:ports (post-deploy add)" { + deploy_app + run dokku proxy:ports-add $TEST_APP http:8080:5000 http:8081:5000 + echo "output: "$output + echo "status: "$status + assert_success + + URLS="$(dokku --quiet urls "$TEST_APP")" + for URL in $URLS; do + assert_http_success $URL + done + assert_http_success "http://$TEST_APP.dokku.me:8080" + assert_http_success "http://$TEST_APP.dokku.me:8081" +} diff --git a/tests/unit/test_helper.bash b/tests/unit/test_helper.bash index 4d77f3f51..8f518750c 100644 --- a/tests/unit/test_helper.bash +++ b/tests/unit/test_helper.bash @@ -240,24 +240,31 @@ custom_ssl_nginx_template() { echo "injecting custom_ssl_nginx_template -> $APP_REPO_DIR/nginx.conf.sigil" cat< "$APP_REPO_DIR/nginx.conf.sigil" +{{ range \$port_map := .PROXY_PORT_MAP | split " " }} +{{ \$port_map_list := \$port_map | split ":" }} +{{ \$scheme := index \$port_map_list 0 }} +{{ \$listen_port := index \$port_map_list 1 }} +{{ \$upstream_port := index \$port_map_list 2 }} +{{ if eq \$scheme "http" }} server { - listen [::]:{{ .NGINX_PORT }}; - listen {{ .NGINX_PORT }}; - server_name {{ .NOSSL_SERVER_NAME }} $CUSTOM_TEMPLATE_SSL_DOMAIN; - return 301 https://\$host:{{ .NGINX_SSL_PORT }}\$request_uri; + listen [::]:{{ \$listen_port }}; + listen {{ \$listen_port }}; + server_name {{ $.NOSSL_SERVER_NAME }} $CUSTOM_TEMPLATE_SSL_DOMAIN; + return 301 https://\$host:{{ $.NGINX_SSL_PORT }}\$request_uri; } - +{{ else if eq \$scheme "https"}} server { - listen [::]:{{ .NGINX_SSL_PORT }} ssl spdy; - listen {{ .NGINX_SSL_PORT }} ssl spdy; - server_name \$SSL_SERVER_NAME $CUSTOM_TEMPLATE_SSL_DOMAIN; - ssl_certificate {{ .APP_SSL_PATH }}/server.crt; - ssl_certificate_key {{ .APP_SSL_PATH }}/server.key; + listen [::]:{{ $.NGINX_SSL_PORT }} ssl spdy; + listen {{ $.NGINX_SSL_PORT }} ssl spdy; + {{ if $.SSL_SERVER_NAME }}server_name {{ $.SSL_SERVER_NAME }} $CUSTOM_TEMPLATE_SSL_DOMAIN; {{ end }} + {{ if $.NOSSL_SERVER_NAME }}server_name {{ $.NOSSL_SERVER_NAME }} $CUSTOM_TEMPLATE_SSL_DOMAIN; {{ end }} + ssl_certificate {{ $.APP_SSL_PATH }}/server.crt; + ssl_certificate_key {{ $.APP_SSL_PATH }}/server.key; keepalive_timeout 70; - add_header Alternate-Protocol {{ .NGINX_SSL_PORT }}:npn-spdy/2; + add_header Alternate-Protocol {{ \$listen_port }}:npn-spdy/2; location / { - proxy_pass http://{{ .APP }}; + proxy_pass http://{{ $.APP }}-{{ \$upstream_port }}; proxy_http_version 1.1; proxy_set_header Upgrade \$http_upgrade; proxy_set_header Connection "upgrade"; @@ -267,18 +274,22 @@ server { proxy_set_header X-Forwarded-Port \$server_port; proxy_set_header X-Request-Start \$msec; } - include {{ .DOKKU_ROOT }}/{{ .APP }}/nginx.conf.d/*.conf; + include {{ $.DOKKU_ROOT }}/{{ $.APP }}/nginx.conf.d/*.conf; } -{{ if .DOKKU_APP_LISTENERS }} -upstream {{ .APP }} { -{{ range .DOKKU_APP_LISTENERS | split " " }} server {{ . }}; -{{ end }}} -{{ else if .PASSED_LISTEN_IP_PORT }} -upstream {{ .APP }} { - server {{ .DOKKU_APP_LISTEN_IP }}:{{ .DOKKU_APP_LISTEN_PORT }}; +{{ end }}{{ end }} + +{{ if $.DOKKU_APP_LISTENERS }} +{{ range \$upstream_port := $.PROXY_UPSTREAM_PORTS | split " " }} +upstream {{ $.APP }}-{{ \$upstream_port }} { +{{ range \$listeners := $.DOKKU_APP_LISTENERS | split " " }} +{{ \$listener_list := \$listeners | split ":" }} +{{ \$listener_ip := index \$listener_list 0 }} +{{ \$listener_port := index \$listener_list 1 }} + server {{ \$listener_ip }}:{{ \$upstream_port }};{{ end }} } -{{ end }} +{{ end }}{{ end }} EOF +cat "$APP_REPO_DIR/nginx.conf.sigil" } custom_nginx_template() { @@ -288,13 +299,19 @@ custom_nginx_template() { echo "injecting custom_nginx_template -> $APP_REPO_DIR/nginx.conf.sigil" cat< "$APP_REPO_DIR/nginx.conf.sigil" +{{ range \$port_map := .PROXY_PORT_MAP | split " " }} +{{ \$port_map_list := \$port_map | split ":" }} +{{ \$scheme := index \$port_map_list 0 }} +{{ \$listen_port := index \$port_map_list 1 }} +{{ \$upstream_port := index \$port_map_list 2 }} + server { - listen [::]:{{ .NGINX_PORT }}; - listen {{ .NGINX_PORT }}; - server_name {{ .NOSSL_SERVER_NAME }} customtemplate.dokku.me; + listen [::]:{{ \$listen_port }}; + listen {{ \$listen_port }}; + server_name {{ $.NOSSL_SERVER_NAME }} customtemplate.dokku.me; location / { - proxy_pass http://{{ .APP }}; + proxy_pass http://{{ $.APP }}-{{ \$upstream_port }}; proxy_http_version 1.1; proxy_set_header Upgrade \$http_upgrade; proxy_set_header Connection "upgrade"; @@ -304,18 +321,22 @@ server { proxy_set_header X-Forwarded-Port \$server_port; proxy_set_header X-Request-Start \$msec; } - include {{ .DOKKU_ROOT }}/{{ .APP }}/nginx.conf.d/*.conf; -} -{{ if .DOKKU_APP_LISTENERS }} -upstream {{ .APP }} { -{{ range .DOKKU_APP_LISTENERS | split " " }} server {{ . }}; -{{ end }}} -{{ else if .PASSED_LISTEN_IP_PORT }} -upstream {{ .APP }} { - server {{ .DOKKU_APP_LISTEN_IP }}:{{ .DOKKU_APP_LISTEN_PORT }}; + include {{ $.DOKKU_ROOT }}/{{ $.APP }}/nginx.conf.d/*.conf; } {{ end }} + +{{ if $.DOKKU_APP_LISTENERS }} +{{ range \$upstream_port := $.PROXY_UPSTREAM_PORTS | split " " }} +upstream {{ $.APP }}-{{ \$upstream_port }} { +{{ range \$listeners := $.DOKKU_APP_LISTENERS | split " " }} +{{ \$listener_list := \$listeners | split ":" }} +{{ \$listener_ip := index \$listener_list 0 }} +{{ \$listener_port := index \$listener_list 1 }} + server {{ \$listener_ip }}:{{ \$upstream_port }};{{ end }} +} +{{ end }}{{ end }} EOF +cat "$APP_REPO_DIR/nginx.conf.sigil" } bad_custom_nginx_template() {