diff --git a/docs/appendices/0.20.0-migration-guide.md b/docs/appendices/0.20.0-migration-guide.md index 9d537d009..11267c418 100644 --- a/docs/appendices/0.20.0-migration-guide.md +++ b/docs/appendices/0.20.0-migration-guide.md @@ -15,6 +15,19 @@ The following commands were previously deprecated and were removed in this relea - `trace`: Use `trace:on` or `trace:off` instead. - `ps`: Use `enter` with a container ID instead to run `ps auxww` within a container. +The refactor of the proxy plugin removes the following functions from being sourced in plugins: + +- `proxy#get_app_proxy_port_map()` +- `proxy#list_app_proxy_ports()` +- `proxy#filter_app_proxy_ports()` +- `proxy#add_proxy_ports()` +- `proxy#remove_proxy_ports()` +- `proxy#set_proxy_ports()` + +The informal policy is to allow removal of functions within a `functions` file if they are not used in OSS plugins. Should you be affected by any such removals, please file an issue to expose the functionality via a plugin trigger. + ## Deprecations - `nginx:show-conf` has been deprecated in favor of `nginx:show-config`. +- `proxy#is_app_proxy_enabled()` is deprecated in favor of `plugn trigger proxy-is-enabled` +- `proxy#get_app_proxy_type()` is deprecated in favor of `plugn trigger proxy-type` diff --git a/docs/development/plugin-triggers.md b/docs/development/plugin-triggers.md index 77fe0945e..af37aac05 100644 --- a/docs/development/plugin-triggers.md +++ b/docs/development/plugin-triggers.md @@ -1295,6 +1295,36 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +### `proxy-is-enabled` + +- Description: Checks if there is a proxy enabled for the app +- Invoked by: +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `proxy-type` + +- Description: Returns the proxy type for an app +- Invoked by: +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `receive-app` - Description: Allows you to customize what occurs when an app is received. Normally just triggers an app build. diff --git a/plugins/20_events/proxy-is-enabled b/plugins/20_events/proxy-is-enabled new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/proxy-is-enabled @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/proxy-type b/plugins/20_events/proxy-type new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/proxy-type @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/common/functions b/plugins/common/functions index fc822ca53..fca53a7d4 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -895,7 +895,7 @@ internal_get_app_urls() { local SCHEME="https" fi - if [[ "$(is_app_proxy_enabled "$APP")" == "false" ]]; then + if [[ "$(plugn trigger proxy-is-enabled "$APP")" == "false" ]]; then if [[ -n "$RAW_TCP_PORTS" ]]; then local APP_CONTAINER_PORTS="$(get_container_ports "$APP")" local app_port diff --git a/plugins/nginx-vhosts/core-post-deploy b/plugins/nginx-vhosts/core-post-deploy index ea4293174..a716959fe 100755 --- a/plugins/nginx-vhosts/core-post-deploy +++ b/plugins/nginx-vhosts/core-post-deploy @@ -12,7 +12,7 @@ trigger-nginx-vhosts-core-post-deploy() { declare APP="$1" local HAS_NETWORK_CONFIG - if [[ "$(get_app_proxy_type "$1")" != "nginx" ]]; then + if [[ "$(plugn trigger proxy-type "$APP")" != "nginx" ]]; then return fi diff --git a/plugins/nginx-vhosts/functions b/plugins/nginx-vhosts/functions index 3a2a49470..ba1514dc2 100755 --- a/plugins/nginx-vhosts/functions +++ b/plugins/nginx-vhosts/functions @@ -295,7 +295,7 @@ nginx_build_config() { local IS_APP_VHOST_ENABLED=$(is_app_vhost_enabled "$APP") - if [[ "$(is_app_proxy_enabled "$APP")" == "true" ]]; then + if [[ "$(plugn trigger proxy-is-enabled "$APP")" == "true" ]]; then if [[ -z "$DOKKU_APP_LISTEN_PORT" ]] && [[ -z "$DOKKU_APP_LISTEN_IP" ]]; then DOKKU_APP_LISTENERS="$(plugn trigger network-get-listeners "$APP" | xargs)" elif [[ -n "$DOKKU_APP_LISTEN_PORT" ]] && [[ -n "$DOKKU_APP_LISTEN_IP" ]]; then diff --git a/plugins/nginx-vhosts/post-certs-update b/plugins/nginx-vhosts/post-certs-update deleted file mode 100755 index 308a75ed0..000000000 --- a/plugins/nginx-vhosts/post-certs-update +++ /dev/null @@ -1,35 +0,0 @@ -#!/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" - -trigger-nginx-vhosts-post-certs-update() { - declare desc="unset port config vars after SSL cert is added" - declare trigger="post-certs-update" - declare APP="$1" - - if [[ "$(get_app_proxy_type "$APP")" == "nginx" ]]; then - local DOKKU_PROXY_PORT=$(config_get "$APP" DOKKU_PROXY_PORT) - local DOKKU_PROXY_SSL_PORT=$(config_get "$APP" DOKKU_PROXY_SSL_PORT) - local DOKKU_PROXY_PORT_MAP=$(config_get "$APP" DOKKU_PROXY_PORT_MAP) - - if [[ "$DOKKU_PROXY_PORT" == "80" ]]; then - DOKKU_QUIET_OUTPUT=1 config_unset --no-restart "$APP" DOKKU_PROXY_PORT - fi - if [[ "$DOKKU_PROXY_SSL_PORT" == "443" ]]; then - DOKKU_QUIET_OUTPUT=1 config_unset --no-restart "$APP" DOKKU_PROXY_SSL_PORT - fi - if [[ "$DOKKU_PROXY_PORT_MAP" == *http:80:* ]]; then - # shellcheck disable=SC2046 - remove_proxy_ports "$APP" $(filter_app_proxy_ports "$APP" "https" "443") - # shellcheck disable=SC2046 - for port_map in $(filter_app_proxy_ports "$APP" "http" "80"); do - add_proxy_ports "$APP" "https:443:$(cut -d ':' -f3 <<<"$port_map")" - done - fi - fi -} - -trigger-nginx-vhosts-post-certs-update "$@" diff --git a/plugins/nginx-vhosts/post-domains-update b/plugins/nginx-vhosts/post-domains-update index 0a1e82b2f..c80e23096 100755 --- a/plugins/nginx-vhosts/post-domains-update +++ b/plugins/nginx-vhosts/post-domains-update @@ -12,7 +12,7 @@ trigger-nginx-vhosts-post-domains-update() { [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" - if [[ "$(get_app_proxy_type "$APP")" == "nginx" ]]; then + if [[ "$(plugn trigger proxy-type "$APP")" == "nginx" ]]; then nginx_build_config "$APP" fi } diff --git a/plugins/nginx-vhosts/post-proxy-ports-update b/plugins/nginx-vhosts/post-proxy-ports-update index e6135d8b8..7f5514f13 100755 --- a/plugins/nginx-vhosts/post-proxy-ports-update +++ b/plugins/nginx-vhosts/post-proxy-ports-update @@ -10,7 +10,7 @@ trigger-nginx-vhosts-post-proxy-ports-update() { declare trigger="post-proxy-ports-update" declare APP="$1" ACTION="$2" - if [[ "$(get_app_proxy_type "$APP")" == "nginx" ]]; then + if [[ "$(plugn trigger proxy-type "$APP")" == "nginx" ]]; then nginx_build_config "$APP" fi } diff --git a/plugins/nginx-vhosts/pre-disable-vhost b/plugins/nginx-vhosts/pre-disable-vhost index 3ddbd6444..a2bf0ea2c 100755 --- a/plugins/nginx-vhosts/pre-disable-vhost +++ b/plugins/nginx-vhosts/pre-disable-vhost @@ -10,7 +10,7 @@ trigger-nginx-vhosts-pre-disable-vhost() { declare trigger="pre-disable-vhost" declare APP="$1" - if [[ "$(get_app_proxy_type "$APP")" == "nginx" ]]; then + if [[ "$(plugn trigger proxy-type "$APP")" == "nginx" ]]; then DOKKU_QUIET_OUTPUT=1 config_unset --no-restart "$APP" DOKKU_PROXY_PORT DOKKU_PROXY_SSL_PORT DOKKU_PROXY_PORT_MAP fi } diff --git a/plugins/nginx-vhosts/pre-enable-vhost b/plugins/nginx-vhosts/pre-enable-vhost index 13c7584e3..e68f47f6e 100755 --- a/plugins/nginx-vhosts/pre-enable-vhost +++ b/plugins/nginx-vhosts/pre-enable-vhost @@ -10,7 +10,7 @@ trigger-nginx-vhosts-pre-enable-vhost() { declare trigger="pre-enable-vhost" declare APP="$1" - if [[ "$(get_app_proxy_type "$APP")" == "nginx" ]]; then + if [[ "$(plugn trigger proxy-type "$APP")" == "nginx" ]]; then DOKKU_QUIET_OUTPUT=1 config_unset --no-restart "$APP" DOKKU_PROXY_PORT DOKKU_PROXY_SSL_PORT DOKKU_PROXY_PORT_MAP fi } diff --git a/plugins/nginx-vhosts/proxy-build-config b/plugins/nginx-vhosts/proxy-build-config index 0abb97320..408f4b16f 100755 --- a/plugins/nginx-vhosts/proxy-build-config +++ b/plugins/nginx-vhosts/proxy-build-config @@ -11,7 +11,7 @@ trigger-nginx-vhosts-proxy-build-config() { declare APP="$1" [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" - if [[ "$(get_app_proxy_type "$APP")" == "nginx" ]]; then + if [[ "$(plugn trigger proxy-type "$APP")" == "nginx" ]]; then plugn trigger network-build-config "$APP" nginx_build_config "$APP" fi diff --git a/plugins/nginx-vhosts/proxy-clear-config b/plugins/nginx-vhosts/proxy-clear-config index d9daad61a..5cf0c3e6e 100755 --- a/plugins/nginx-vhosts/proxy-clear-config +++ b/plugins/nginx-vhosts/proxy-clear-config @@ -11,7 +11,7 @@ trigger-nginx-vhosts-proxy-clear-config() { declare APP="$1" [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" - if [[ "$(get_app_proxy_type "$APP")" == "nginx" ]]; then + if [[ "$(plugn trigger proxy-type "$APP")" == "nginx" ]]; then plugn trigger network-clear-config "$APP" nginx_clear_config "$APP" fi diff --git a/plugins/nginx-vhosts/proxy-disable b/plugins/nginx-vhosts/proxy-disable index e0ce5f7f0..8c5296aed 100755 --- a/plugins/nginx-vhosts/proxy-disable +++ b/plugins/nginx-vhosts/proxy-disable @@ -12,7 +12,7 @@ trigger-nginx-vhosts-proxy-disable() { declare APP="$1" verify_app_name "$APP" - if [[ "$(get_app_proxy_type "$APP")" == "nginx" ]]; then + if [[ "$(plugn trigger proxy-type "$APP")" == "nginx" ]]; then disable_app_vhost "$APP" --no-restart ps_restart "$APP" fi diff --git a/plugins/nginx-vhosts/proxy-enable b/plugins/nginx-vhosts/proxy-enable index 65340ee05..a5b20938d 100755 --- a/plugins/nginx-vhosts/proxy-enable +++ b/plugins/nginx-vhosts/proxy-enable @@ -12,7 +12,7 @@ trigger-nginx-vhosts-proxy-enable() { declare APP="$1" verify_app_name "$APP" - if [[ "$(get_app_proxy_type "$APP")" == "nginx" ]]; then + if [[ "$(plugn trigger proxy-type "$APP")" == "nginx" ]]; then enable_app_vhost "$APP" --no-restart ps_restart "$APP" fi diff --git a/plugins/nginx-vhosts/subcommands/build-config b/plugins/nginx-vhosts/subcommands/build-config index 322e21d13..7f7bab1cc 100755 --- a/plugins/nginx-vhosts/subcommands/build-config +++ b/plugins/nginx-vhosts/subcommands/build-config @@ -12,7 +12,7 @@ cmd-nginx-build-config() { [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" local PROXY_APP_TYPE - PROXY_APP_TYPE="$(get_app_proxy_type "$APP")" + PROXY_APP_TYPE="$(plugn trigger proxy-type "$APP")" if [[ "$PROXY_APP_TYPE" == "nginx" ]]; then plugn trigger proxy-build-config "$APP" diff --git a/plugins/proxy/.gitignore b/plugins/proxy/.gitignore new file mode 100644 index 000000000..ce994fee0 --- /dev/null +++ b/plugins/proxy/.gitignore @@ -0,0 +1,8 @@ +/commands +/subcommands/* +/triggers/* +/triggers +/proxy-* +/install +/post-* +/report diff --git a/plugins/proxy/Makefile b/plugins/proxy/Makefile new file mode 100644 index 000000000..0376ee9b4 --- /dev/null +++ b/plugins/proxy/Makefile @@ -0,0 +1,6 @@ +SUBCOMMANDS = subcommands/disable subcommands/enable subcommands/ports subcommands/ports-add subcommands/ports-clear subcommands/ports-remove subcommands/ports-set subcommands/report subcommands/set +TRIGGERS = triggers/proxy-is-enabled triggers/proxy-type triggers/post-certs-remove triggers/post-certs-update triggers/report +BUILD = commands subcommands triggers +PLUGIN_NAME = proxy + +include ../../common.mk diff --git a/plugins/proxy/commands b/plugins/proxy/commands deleted file mode 100755 index 742fe187d..000000000 --- a/plugins/proxy/commands +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash -[[ " help proxy:help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT" -source "$PLUGIN_AVAILABLE_PATH/proxy/help-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -case "$1" in - help | proxy:help) - cmd-proxy-help "$@" - ;; - - *) - exit "$DOKKU_NOT_IMPLEMENTED_EXIT" - ;; - -esac diff --git a/plugins/proxy/functions b/plugins/proxy/functions index 206e12719..ce7222fa6 100755 --- a/plugins/proxy/functions +++ b/plugins/proxy/functions @@ -6,124 +6,18 @@ source "$PLUGIN_AVAILABLE_PATH/config/functions" is_app_proxy_enabled() { declare desc="return true if proxy is enabled; otherwise return false" - local APP="$1" - verify_app_name "$APP" - local APP_PROXY_ENABLED=true + declare APP="$1" + declare deprecated=true + dokku_log_warn "Deprecated: plugn#proxy-is-enabled" - local DOKKU_DISABLE_PROXY=$(config_get "$APP" DOKKU_DISABLE_PROXY) - if [[ -n "$DOKKU_DISABLE_PROXY" ]]; then - local APP_PROXY_ENABLED=false - fi - echo $APP_PROXY_ENABLED + plugn trigger proxy-is-enabled "$APP" } get_app_proxy_type() { declare desc="return app proxy type" - local APP="$1" - verify_app_name "$APP" - local DOKKU_APP_PROXY_TYPE="$(config_get "$APP" DOKKU_APP_PROXY_TYPE || true)" - local APP_PROXY_TYPE="${DOKKU_APP_PROXY_TYPE:-nginx}" + declare APP="$1" + declare deprecated=true + dokku_log_warn "Deprecated: plugn#proxy-type" - echo "$APP_PROXY_TYPE" -} - -get_app_proxy_port_map() { - declare desc="return a list of configured port mappings" - local APP="$1" - local PROXY_PORT_MAP="$(config_get "$APP" DOKKU_PROXY_PORT_MAP || true)" - echo "$PROXY_PORT_MAP" -} - -list_app_proxy_ports() { - declare desc="list proxy port mappings for an app" - local APP="$1" - verify_app_name "$APP" - local PROXY_PORT_MAP="$(get_app_proxy_port_map "$APP")" - - 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 -} - -filter_app_proxy_ports() { - declare desc="list proxy port mappings for an app that start with a particular scheme and a host port" - local APP="$1" - verify_app_name "$APP" - local SCHEME="$2" HOST_PORT="$3" - local PROXY_PORT_MAP="$(get_app_proxy_port_map "$APP")" - - if [[ -n "$PROXY_PORT_MAP" ]]; then - local port_map - for port_map in $PROXY_PORT_MAP; do - local scheme="$(cut -d ':' -f1 <<<"$port_map")" - local host_port="$(cut -d ':' -f2 <<<"$port_map")" - if [[ "$scheme" == "$SCHEME" ]] && [[ "$host_port" == "$HOST_PORT" ]]; then - echo "$port_map" - fi - done - fi -} - -add_proxy_ports() { - declare desc="add proxy port mappings for an app" - local APP="$1" - local PROXY_PORT_MAP="$(get_app_proxy_port_map "$APP")" - shift 1 - - local INPUT_PORTS="$*" - if [[ -n "$INPUT_PORTS" ]]; then - PROXY_PORT_MAP="$(merge_dedupe_list "$PROXY_PORT_MAP $INPUT_PORTS" " ")" - DOKKU_QUIET_OUTPUT=1 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 -} - -remove_proxy_ports() { - declare desc="remove specific proxy port mappings for an app" - local APP="$1" - local PROXY_PORT_MAP="$(get_app_proxy_port_map "$APP")" - local RE_PORT='^[0-9]+$' - shift 1 - - local INPUT_PORTS="$*" - local port - PROXY_PORT_MAP=" $PROXY_PORT_MAP " - for port in $INPUT_PORTS; do - if [[ "$port" =~ $RE_PORT ]]; then - PROXY_PORT_MAP="$(sed -r "s/[[:alnum:]]+:$port:[[:alnum:]]+//g" <<<"$PROXY_PORT_MAP")" - else - PROXY_PORT_MAP="$(sed -r "s/ $port / /g" <<<"$PROXY_PORT_MAP")" - fi - done - PROXY_PORT_MAP="$(echo "$PROXY_PORT_MAP" | xargs)" - PROXY_PORT_MAP="$(merge_dedupe_list "$PROXY_PORT_MAP" " ")" - if [[ -n "$PROXY_PORT_MAP" ]]; then - DOKKU_QUIET_OUTPUT=1 config_set --no-restart "$APP" DOKKU_PROXY_PORT_MAP="$PROXY_PORT_MAP" - else - DOKKU_QUIET_OUTPUT=1 config_unset --no-restart "$APP" DOKKU_PROXY_PORT_MAP - fi -} - -set_proxy_ports() { - declare desc="SET proxy port mappings for an app" - local APP="$1" - shift 1 - - local INPUT_PORTS="$*" - if [[ -n "$INPUT_PORTS" ]]; then - PROXY_PORT_MAP="$(merge_dedupe_list "$INPUT_PORTS" " ")" - DOKKU_QUIET_OUTPUT=1 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 proxy-type "$APP" } diff --git a/plugins/proxy/glide.yaml b/plugins/proxy/glide.yaml new file mode 100644 index 000000000..3f7a96657 --- /dev/null +++ b/plugins/proxy/glide.yaml @@ -0,0 +1,3 @@ +package: . +import: +- package: github.com/ryanuber/columnize diff --git a/plugins/proxy/help-functions b/plugins/proxy/help-functions deleted file mode 100755 index 82237dad2..000000000 --- a/plugins/proxy/help-functions +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-proxy-help() { - declare desc="help command" - declare CMD="$1" - local plugin_name="proxy" - local plugin_description="Manage the proxy integration for an app" - - if [[ "$CMD" == "$plugin_name:help" ]]; then - echo -e "Usage: dokku $plugin_name[:COMMAND]" - echo '' - echo "$plugin_description" - echo '' - echo 'Additional commands:' - fn-help-content | sort | column -c2 -t -s, - elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then - fn-help-content - else - cat <, Disable proxy for app - proxy:enable , Enable proxy for app - proxy:ports , List proxy port mappings for app - proxy:ports-add [::...], Add proxy port mappings to an app - proxy:ports-clear , Clear all proxy port mappings for an app - proxy:ports-remove [|::...], Remove specific proxy port mappings from an app - proxy:ports-set [::...], Set proxy port mappings for an app - proxy:report [] [], Displays a proxy report for one or more apps - proxy:set , Set proxy type for app -help_content -} diff --git a/plugins/proxy/internal-functions b/plugins/proxy/internal-functions deleted file mode 100755 index 43f8e29b1..000000000 --- a/plugins/proxy/internal-functions +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/proxy/functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-proxy-report() { - declare desc="displays a proxy report for one or more apps" - declare cmd="proxy:report" - [[ "$1" == "$cmd" ]] && shift 1 - local INSTALLED_APPS=$(dokku_apps) - local APP="$1" INFO_FLAG="$2" - - if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then - INFO_FLAG="$APP" - APP="" - fi - - if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then - INFO_FLAG="true" - fi - - if [[ -z "$APP" ]]; then - for app in $INSTALLED_APPS; do - cmd-proxy-report-single "$app" "$INFO_FLAG" | tee || true - done - else - cmd-proxy-report-single "$APP" "$INFO_FLAG" - fi -} - -cmd-proxy-report-single() { - declare APP="$1" INFO_FLAG="$2" - if [[ "$INFO_FLAG" == "true" ]]; then - INFO_FLAG="" - fi - verify_app_name "$APP" - local flag_map=( - "--proxy-enabled: $(fn-proxy-enabled "$APP")" - "--proxy-type: $(get_app_proxy_type "$APP")" - "--proxy-port-map: $(get_app_proxy_port_map "$APP")" - ) - - if [[ -z "$INFO_FLAG" ]]; then - dokku_log_info2_quiet "$APP proxy information" - for flag in "${flag_map[@]}"; do - key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')" - dokku_log_verbose "$(printf "%-30s %-25s" "${key^}" "${flag#*: }")" - done - else - local match=false - local value_exists=false - for flag in "${flag_map[@]}"; do - valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)" - if [[ "$flag" == "${INFO_FLAG}:"* ]]; then - value=${flag#*: } - size="${#value}" - if [[ "$size" -ne 0 ]]; then - echo "$value" && match=true && value_exists=true - else - match=true - fi - fi - done - [[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}" - [[ "$value_exists" == "true" ]] || dokku_log_fail "not deployed" - fi -} - -fn-proxy-enabled() { - declare APP="$1" - local PROXY_ENABLED=false - - if [[ "$(is_app_proxy_enabled "$APP")" == "true" ]]; then - PROXY_ENABLED=true - fi - echo "$PROXY_ENABLED" -} diff --git a/plugins/proxy/post-certs-remove b/plugins/proxy/post-certs-remove deleted file mode 100755 index 07d3fe9d2..000000000 --- a/plugins/proxy/post-certs-remove +++ /dev/null @@ -1,19 +0,0 @@ -#!/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" - -trigger-proxy-post-certs-remove() { - declare desc="unset port config vars after SSL cert is removed" - declare trigger="post-certs-remove" - declare APP="$1" - - DOKKU_QUIET_OUTPUT=1 config_unset --no-restart "$APP" DOKKU_PROXY_SSL_PORT - - # shellcheck disable=SC2046 - remove_proxy_ports "$APP" $(filter_app_proxy_ports "$APP" "https" "443") -} - -trigger-proxy-post-certs-remove "$@" diff --git a/plugins/proxy/proxy.go b/plugins/proxy/proxy.go index 423e09f5a..aeab9736e 100644 --- a/plugins/proxy/proxy.go +++ b/plugins/proxy/proxy.go @@ -1,10 +1,27 @@ package proxy import ( + "errors" + "fmt" + "os" + "strconv" + "strings" + "github.com/dokku/dokku/plugins/common" "github.com/dokku/dokku/plugins/config" + columnize "github.com/ryanuber/columnize" ) +type PortMap struct { + ContainerPort int + HostPort int + Scheme string +} + +func (p PortMap) String() string { + return fmt.Sprintf("%s:%d:%d", p.Scheme, p.HostPort, p.ContainerPort) +} + // IsAppProxyEnabled returns true if proxy is enabled; otherwise return false func IsAppProxyEnabled(appName string) bool { err := common.VerifyAppName(appName) @@ -19,3 +36,176 @@ func IsAppProxyEnabled(appName string) bool { } return proxyEnabled } + +// ReportSingleApp is an internal function that displays the app report for one or more apps +func ReportSingleApp(appName string, infoFlag string) error { + if err := common.VerifyAppName(appName); err != nil { + return err + } + + proxyEnabled := "false" + if IsAppProxyEnabled(appName) { + proxyEnabled = "true" + } + + var proxyPortMap []string + for _, portMap := range getProxyPortMap(appName) { + proxyPortMap = append(proxyPortMap, portMap.String()) + } + + infoFlags := map[string]string{ + "--proxy-enabled": proxyEnabled, + "--proxy-type": getAppProxyType(appName), + "--proxy-port-map": strings.Join(proxyPortMap, " "), + } + + trimPrefix := false + uppercaseFirstCharacter := true + return common.ReportSingleApp("proxy", appName, infoFlag, infoFlags, trimPrefix, uppercaseFirstCharacter) +} + +func addProxyPorts(appName string, proxyPortMap []PortMap) error { + allPortMaps := getProxyPortMap(appName) + allPortMaps = append(allPortMaps, proxyPortMap...) + + return setProxyPorts(appName, allPortMaps) +} + +func filterAppProxyPorts(appName string, scheme string, hostPort int) []PortMap { + var filteredProxyMaps []PortMap + proxyPortMap := getProxyPortMap(appName) + for _, portMap := range proxyPortMap { + if portMap.Scheme == scheme && portMap.HostPort == hostPort { + filteredProxyMaps = append(filteredProxyMaps, portMap) + } + } + + return filteredProxyMaps +} + +func getAppName(args []string) (appName string, err error) { + if len(args) >= 1 { + appName = args[0] + } else { + err = errors.New("Please specify an app to run the command on") + } + + return +} + +func getAppProxyType(appName string) string { + return config.GetWithDefault(appName, "DOKKU_APP_PROXY_TYPE", "nginx") +} + +func getProxyPortMap(appName string) []PortMap { + value := config.GetWithDefault(appName, "DOKKU_PROXY_PORT_MAP", "") + return parseProxyPortMapString(value) +} + +func listAppProxyPorts(appName string) error { + proxyPortMap := getProxyPortMap(appName) + + if len(proxyPortMap) == 0 { + return errors.New("No port mappings configured for app") + } + + var lines []string + if os.Getenv("DOKKU_QUIET_OUTPUT") == "" { + lines = append(lines, "-----> scheme:host port:container port") + } + + for _, portMap := range proxyPortMap { + lines = append(lines, portMap.String()) + } + + common.LogInfo1Quiet(fmt.Sprintf("Port mappings for %s", appName)) + config := columnize.DefaultConfig() + config.Delim = ":" + config.Prefix = " " + config.Empty = "" + fmt.Println(columnize.Format(lines, config)) + return nil +} + +func setProxyPorts(appName string, proxyPortMap []PortMap) error { + var value []string + for _, portMap := range uniqueProxyPortMap(proxyPortMap) { + value = append(value, portMap.String()) + } + + entries := map[string]string{ + "DOKKU_PROXY_PORT_MAP": strings.Join(value, " "), + } + return config.SetMany(appName, entries, false) +} + +func removeProxyPorts(appName string, proxyPortMap []PortMap) error { + var toRemove map[string]bool + + for _, portMap := range proxyPortMap { + toRemove[portMap.String()] = true + } + + var toSet []PortMap + existingPortMaps := getProxyPortMap(appName) + for _, portMap := range existingPortMaps { + if toRemove[portMap.String()] { + continue + } + + toSet = append(toSet, portMap) + } + + if len(toSet) == 0 { + keys := []string{"DOKKU_PROXY_PORT_MAP"} + return config.UnsetMany(appName, keys, false) + } + + return setProxyPorts(appName, toSet) +} + +func parseProxyPortMapString(stringPortMap string) []PortMap { + var proxyPortMap []PortMap + + for _, v := range strings.Split(strings.TrimSpace(stringPortMap), "") { + parts := strings.SplitN(v, ":", 3) + if len(parts) != 3 { + common.LogWarn(fmt.Sprintf("Invalid port map %s", v)) + continue + } + + hostPort, err := strconv.Atoi(parts[1]) + if err != nil { + common.LogWarn(fmt.Sprintf("Invalid port map %s", v)) + } + + containerPort, err := strconv.Atoi(parts[2]) + if err != nil { + common.LogWarn(fmt.Sprintf("Invalid port map %s", v)) + } + + proxyPortMap = append(proxyPortMap, PortMap{ + ContainerPort: containerPort, + HostPort: hostPort, + Scheme: parts[0], + }) + } + + return uniqueProxyPortMap(proxyPortMap) +} + +func uniqueProxyPortMap(proxyPortMap []PortMap) []PortMap { + var unique []PortMap + var existingPortMaps map[string]bool + + for _, portMap := range proxyPortMap { + if existingPortMaps[portMap.String()] { + continue + } + + existingPortMaps[portMap.String()] = true + unique = append(unique, portMap) + } + + return unique +} diff --git a/plugins/proxy/report b/plugins/proxy/report deleted file mode 100755 index a087dd2c7..000000000 --- a/plugins/proxy/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/proxy/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-proxy-report-single "$@" diff --git a/plugins/proxy/src/commands/commands.go b/plugins/proxy/src/commands/commands.go new file mode 100644 index 000000000..e4f76ce0d --- /dev/null +++ b/plugins/proxy/src/commands/commands.go @@ -0,0 +1,70 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strconv" + "strings" + + "github.com/dokku/dokku/plugins/common" + columnize "github.com/ryanuber/columnize" +) + +const ( + helpHeader = `Usage: dokku proxy[:COMMAND] + +Manage the proxy integration for an app + +Additional commands:` + + helpContent = ` + proxy:disable , Disable proxy for app + proxy:enable , Enable proxy for app + proxy:ports , List proxy port mappings for app + proxy:ports-add [::...], Add proxy port mappings to an app + proxy:ports-clear , Clear all proxy port mappings for an app + proxy:ports-remove [|::...], Remove specific proxy port mappings from an app + proxy:ports-set [::...], Set proxy port mappings for an app + proxy:report [] [], Displays a proxy report for one or more apps + proxy:set , Set proxy type for app +` +) + +func main() { + flag.Usage = usage + flag.Parse() + + cmd := flag.Arg(0) + switch cmd { + case "proxy", "proxy:help": + usage() + case "help": + command := common.NewShellCmd(fmt.Sprintf("ps -o command= %d", os.Getppid())) + command.ShowOutput = false + output, err := command.Output() + + if err == nil && strings.Contains(string(output), "--all") { + fmt.Println(helpContent) + } else { + fmt.Print("\n proxy, Manage the proxy integration for an app\n") + } + default: + dokkuNotImplementExitCode, err := strconv.Atoi(os.Getenv("DOKKU_NOT_IMPLEMENTED_EXIT")) + if err != nil { + fmt.Println("failed to retrieve DOKKU_NOT_IMPLEMENTED_EXIT environment variable") + dokkuNotImplementExitCode = 10 + } + os.Exit(dokkuNotImplementExitCode) + } +} + +func usage() { + config := columnize.DefaultConfig() + config.Delim = "," + config.Prefix = " " + config.Empty = "" + content := strings.Split(helpContent, "\n")[1:] + fmt.Println(helpHeader) + fmt.Println(columnize.Format(content, config)) +} diff --git a/plugins/proxy/src/glide.lock b/plugins/proxy/src/glide.lock new file mode 100644 index 000000000..fe4a98f07 --- /dev/null +++ b/plugins/proxy/src/glide.lock @@ -0,0 +1,6 @@ +hash: 1ddab5de41d1514c2722bd7e24758ad4b60bf6956eb5b9b925fa071a1427f149 +updated: 2017-01-03T17:16:50.97156327-08:00 +imports: +- name: github.com/ryanuber/columnize + version: 0fbbb3f0e3fbdc5bae7c6cd5f6c1887ebfb76360 +testImports: [] diff --git a/plugins/proxy/src/glide.yaml b/plugins/proxy/src/glide.yaml new file mode 100644 index 000000000..3f7a96657 --- /dev/null +++ b/plugins/proxy/src/glide.yaml @@ -0,0 +1,3 @@ +package: . +import: +- package: github.com/ryanuber/columnize diff --git a/plugins/proxy/src/subcommands/subcommands.go b/plugins/proxy/src/subcommands/subcommands.go new file mode 100644 index 000000000..e15730b8d --- /dev/null +++ b/plugins/proxy/src/subcommands/subcommands.go @@ -0,0 +1,63 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/proxy" +) + +// main entrypoint to all subcommands +func main() { + parts := strings.Split(os.Args[0], "/") + subcommand := parts[len(parts)-1] + + var err error + switch subcommand { + case "disable": + args := flag.NewFlagSet("proxy:disable", flag.ExitOnError) + args.Parse(os.Args[2:]) + err = proxy.CommandDisable(args.Args()) + case "enable": + args := flag.NewFlagSet("proxy:enable", flag.ExitOnError) + args.Parse(os.Args[2:]) + err = proxy.CommandEnable(args.Args()) + case "ports": + args := flag.NewFlagSet("proxy:ports", flag.ExitOnError) + args.Parse(os.Args[2:]) + err = proxy.CommandPorts(args.Args()) + case "ports-add": + args := flag.NewFlagSet("proxy:ports-add", flag.ExitOnError) + args.Parse(os.Args[2:]) + err = proxy.CommandPortsAdd(args.Args()) + case "ports-clear": + args := flag.NewFlagSet("proxy:ports-clear", flag.ExitOnError) + args.Parse(os.Args[2:]) + err = proxy.CommandPortsClear(args.Args()) + case "ports-remove": + args := flag.NewFlagSet("proxy:ports-remove", flag.ExitOnError) + args.Parse(os.Args[2:]) + err = proxy.CommandPortsRemove(args.Args()) + case "ports-set": + args := flag.NewFlagSet("proxy:ports-set", flag.ExitOnError) + args.Parse(os.Args[2:]) + err = proxy.CommandPortsSet(args.Args()) + case "report": + args := flag.NewFlagSet("proxy:report", flag.ExitOnError) + args.Parse(os.Args[2:]) + err = proxy.CommandReport(args.Args()) + case "set": + args := flag.NewFlagSet("proxy:set", flag.ExitOnError) + args.Parse(os.Args[2:]) + err = proxy.CommandSet(args.Args()) + default: + common.LogFail(fmt.Sprintf("Invalid plugin subcommand call: %s", subcommand)) + } + + if err != nil { + common.LogFail(err.Error()) + } +} diff --git a/plugins/proxy/src/triggers/triggers.go b/plugins/proxy/src/triggers/triggers.go new file mode 100644 index 000000000..b3af91f84 --- /dev/null +++ b/plugins/proxy/src/triggers/triggers.go @@ -0,0 +1,43 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/proxy" +) + +// main entrypoint to all triggers +func main() { + parts := strings.Split(os.Args[0], "/") + trigger := parts[len(parts)-1] + flag.Parse() + + var err error + switch trigger { + case "proxy-is-enabled": + appName := flag.Arg(0) + proxy.TriggerProxyIsEnabled(appName) + case "proxy-type": + appName := flag.Arg(0) + proxy.TriggerProxyType(appName) + case "post-certs-remove": + appName := flag.Arg(0) + proxy.TriggerPostCertsRemove(appName) + case "post-certs-update": + appName := flag.Arg(0) + proxy.TriggerPostCertsUpdate(appName) + case "report": + appName := flag.Arg(0) + err = proxy.ReportSingleApp(appName, "") + default: + common.LogFail(fmt.Sprintf("Invalid plugin trigger call: %s", trigger)) + } + + if err != nil { + common.LogFail(err.Error()) + } +} diff --git a/plugins/proxy/src/vendor/github.com/ryanuber/columnize/.travis.yml b/plugins/proxy/src/vendor/github.com/ryanuber/columnize/.travis.yml new file mode 100644 index 000000000..1a0bbea6c --- /dev/null +++ b/plugins/proxy/src/vendor/github.com/ryanuber/columnize/.travis.yml @@ -0,0 +1,3 @@ +language: go +go: + - tip diff --git a/plugins/proxy/src/vendor/github.com/ryanuber/columnize/LICENSE b/plugins/proxy/src/vendor/github.com/ryanuber/columnize/LICENSE new file mode 100644 index 000000000..b9c0e2b68 --- /dev/null +++ b/plugins/proxy/src/vendor/github.com/ryanuber/columnize/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2016 Ryan Uber + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/proxy/src/vendor/github.com/ryanuber/columnize/README.md b/plugins/proxy/src/vendor/github.com/ryanuber/columnize/README.md new file mode 100644 index 000000000..e47634fc6 --- /dev/null +++ b/plugins/proxy/src/vendor/github.com/ryanuber/columnize/README.md @@ -0,0 +1,69 @@ +Columnize +========= + +Easy column-formatted output for golang + +[![Build Status](https://travis-ci.org/ryanuber/columnize.svg)](https://travis-ci.org/ryanuber/columnize) +[![GoDoc](https://godoc.org/github.com/ryanuber/columnize?status.svg)](https://godoc.org/github.com/ryanuber/columnize) + +Columnize is a really small Go package that makes building CLI's a little bit +easier. In some CLI designs, you want to output a number similar items in a +human-readable way with nicely aligned columns. However, figuring out how wide +to make each column is a boring problem to solve and eats your valuable time. + +Here is an example: + +```go +package main + +import ( + "fmt" + "github.com/ryanuber/columnize" +) + +func main() { + output := []string{ + "Name | Gender | Age", + "Bob | Male | 38", + "Sally | Female | 26", + } + result := columnize.SimpleFormat(output) + fmt.Println(result) +} +``` + +As you can see, you just pass in a list of strings. And the result: + +``` +Name Gender Age +Bob Male 38 +Sally Female 26 +``` + +Columnize is tolerant of missing or empty fields, or even empty lines, so +passing in extra lines for spacing should show up as you would expect. + +Configuration +============= + +Columnize is configured using a `Config`, which can be obtained by calling the +`DefaultConfig()` method. You can then tweak the settings in the resulting +`Config`: + +``` +config := columnize.DefaultConfig() +config.Delim = "|" +config.Glue = " " +config.Prefix = "" +config.Empty = "" +``` + +* `Delim` is the string by which columns of **input** are delimited +* `Glue` is the string by which columns of **output** are delimited +* `Prefix` is a string by which each line of **output** is prefixed +* `Empty` is a string used to replace blank values found in output + +You can then pass the `Config` in using the `Format` method (signature below) to +have text formatted to your liking. + +See the [godoc](https://godoc.org/github.com/ryanuber/columnize) page for usage. diff --git a/plugins/proxy/src/vendor/github.com/ryanuber/columnize/columnize.go b/plugins/proxy/src/vendor/github.com/ryanuber/columnize/columnize.go new file mode 100644 index 000000000..915716a10 --- /dev/null +++ b/plugins/proxy/src/vendor/github.com/ryanuber/columnize/columnize.go @@ -0,0 +1,178 @@ +package columnize + +import ( + "bytes" + "fmt" + "strings" +) + +// Config can be used to tune certain parameters which affect the way +// in which Columnize will format output text. +type Config struct { + // The string by which the lines of input will be split. + Delim string + + // The string by which columns of output will be separated. + Glue string + + // The string by which columns of output will be prefixed. + Prefix string + + // A replacement string to replace empty fields + Empty string +} + +// DefaultConfig returns a *Config with default values. +func DefaultConfig() *Config { + return &Config{ + Delim: "|", + Glue: " ", + Prefix: "", + Empty: "", + } +} + +// MergeConfig merges two config objects together and returns the resulting +// configuration. Values from the right take precedence over the left side. +func MergeConfig(a, b *Config) *Config { + var result Config = *a + + // Return quickly if either side was nil + if a == nil || b == nil { + return &result + } + + if b.Delim != "" { + result.Delim = b.Delim + } + if b.Glue != "" { + result.Glue = b.Glue + } + if b.Prefix != "" { + result.Prefix = b.Prefix + } + if b.Empty != "" { + result.Empty = b.Empty + } + + return &result +} + +// stringFormat, given a set of column widths and the number of columns in +// the current line, returns a sprintf-style format string which can be used +// to print output aligned properly with other lines using the same widths set. +func stringFormat(c *Config, widths []int, columns int) string { + // Create the buffer with an estimate of the length + buf := bytes.NewBuffer(make([]byte, 0, (6+len(c.Glue))*columns)) + + // Start with the prefix, if any was given. The buffer will not return an + // error so it does not need to be handled + buf.WriteString(c.Prefix) + + // Create the format string from the discovered widths + for i := 0; i < columns && i < len(widths); i++ { + if i == columns-1 { + buf.WriteString("%s\n") + } else { + fmt.Fprintf(buf, "%%-%ds%s", widths[i], c.Glue) + } + } + return buf.String() +} + +// elementsFromLine returns a list of elements, each representing a single +// item which will belong to a column of output. +func elementsFromLine(config *Config, line string) []interface{} { + seperated := strings.Split(line, config.Delim) + elements := make([]interface{}, len(seperated)) + for i, field := range seperated { + value := strings.TrimSpace(field) + + // Apply the empty value, if configured. + if value == "" && config.Empty != "" { + value = config.Empty + } + elements[i] = value + } + return elements +} + +// runeLen calculates the number of visible "characters" in a string +func runeLen(s string) int { + l := 0 + for _ = range s { + l++ + } + return l +} + +// widthsFromLines examines a list of strings and determines how wide each +// column should be considering all of the elements that need to be printed +// within it. +func widthsFromLines(config *Config, lines []string) []int { + widths := make([]int, 0, 8) + + for _, line := range lines { + elems := elementsFromLine(config, line) + for i := 0; i < len(elems); i++ { + l := runeLen(elems[i].(string)) + if len(widths) <= i { + widths = append(widths, l) + } else if widths[i] < l { + widths[i] = l + } + } + } + return widths +} + +// Format is the public-facing interface that takes a list of strings and +// returns nicely aligned column-formatted text. +func Format(lines []string, config *Config) string { + conf := MergeConfig(DefaultConfig(), config) + widths := widthsFromLines(conf, lines) + + // Estimate the buffer size + glueSize := len(conf.Glue) + var size int + for _, w := range widths { + size += w + glueSize + } + size *= len(lines) + + // Create the buffer + buf := bytes.NewBuffer(make([]byte, 0, size)) + + // Create a cache for the string formats + fmtCache := make(map[int]string, 16) + + // Create the formatted output using the format string + for _, line := range lines { + elems := elementsFromLine(conf, line) + + // Get the string format using cache + numElems := len(elems) + stringfmt, ok := fmtCache[numElems] + if !ok { + stringfmt = stringFormat(conf, widths, numElems) + fmtCache[numElems] = stringfmt + } + + fmt.Fprintf(buf, stringfmt, elems...) + } + + // Get the string result + result := buf.String() + + // Remove trailing newline without removing leading/trailing space + if n := len(result); n > 0 && result[n-1] == '\n' { + result = result[:n-1] + } + + return result +} + +// SimpleFormat is a convenience function to format text with the defaults. +func SimpleFormat(lines []string) string { + return Format(lines, nil) +} diff --git a/plugins/proxy/src/vendor/github.com/ryanuber/columnize/columnize_test.go b/plugins/proxy/src/vendor/github.com/ryanuber/columnize/columnize_test.go new file mode 100644 index 000000000..89dabaa38 --- /dev/null +++ b/plugins/proxy/src/vendor/github.com/ryanuber/columnize/columnize_test.go @@ -0,0 +1,306 @@ +package columnize + +import ( + "fmt" + "testing" + + crand "crypto/rand" +) + +func TestListOfStringsInput(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "x | y | z", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestEmptyLinesOutput(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "", + "x | y | z", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestLeadingSpacePreserved(t *testing.T) { + input := []string{ + "| Column B | Column C", + "x | y | z", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := " Column B Column C\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestColumnWidthCalculator(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "Longer than A | Longer than B | Longer than C", + "short | short | short", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "Longer than A Longer than B Longer than C\n" + expected += "short short short" + + if output != expected { + printableProof := fmt.Sprintf("\nGot: %+q", output) + printableProof += fmt.Sprintf("\nExpected: %+q", expected) + t.Fatalf("\n%s", printableProof) + } +} + +func TestColumnWidthCalculatorNonASCII(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "⌘⌘⌘⌘⌘⌘⌘⌘ | Longer than B | Longer than C", + "short | short | short", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "⌘⌘⌘⌘⌘⌘⌘⌘ Longer than B Longer than C\n" + expected += "short short short" + + if output != expected { + printableProof := fmt.Sprintf("\nGot: %+q", output) + printableProof += fmt.Sprintf("\nExpected: %+q", expected) + t.Fatalf("\n%s", printableProof) + } +} + +func BenchmarkColumnWidthCalculator(b *testing.B) { + // Generate the input + input := []string{ + "UUID A | UUID B | UUID C | Column D | Column E", + } + + format := "%s|%s|%s|%s" + short := "short" + + uuid := func() string { + buf := make([]byte, 16) + if _, err := crand.Read(buf); err != nil { + panic(fmt.Errorf("failed to read random bytes: %v", err)) + } + + return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x", + buf[0:4], + buf[4:6], + buf[6:8], + buf[8:10], + buf[10:16]) + } + + for i := 0; i < 1000; i++ { + l := fmt.Sprintf(format, uuid()[:8], uuid()[:12], uuid(), short, short) + input = append(input, l) + } + + config := DefaultConfig() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + Format(input, config) + } +} + +func TestVariedInputSpacing(t *testing.T) { + input := []string{ + "Column A |Column B| Column C", + "x|y| z", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestUnmatchedColumnCounts(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "Value A | Value B", + "Value A | Value B | Value C | Value D", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "Value A Value B\n" + expected += "Value A Value B Value C Value D" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestAlternateDelimiter(t *testing.T) { + input := []string{ + "Column | A % Column | B % Column | C", + "Value A % Value B % Value C", + } + + config := DefaultConfig() + config.Delim = "%" + output := Format(input, config) + + expected := "Column | A Column | B Column | C\n" + expected += "Value A Value B Value C" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestAlternateSpacingString(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "x | y | z", + } + + config := DefaultConfig() + config.Glue = " " + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestSimpleFormat(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "x | y | z", + } + + output := SimpleFormat(input) + + expected := "Column A Column B Column C\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestAlternatePrefixString(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "x | y | z", + } + + config := DefaultConfig() + config.Prefix = " " + output := Format(input, config) + + expected := " Column A Column B Column C\n" + expected += " x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestEmptyFieldReplacement(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "x | | z", + } + + config := DefaultConfig() + config.Empty = "" + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "x z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestEmptyConfigValues(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "x | y | z", + } + + config := Config{} + output := Format(input, &config) + + expected := "Column A Column B Column C\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestMergeConfig(t *testing.T) { + conf1 := &Config{Delim: "a", Glue: "a", Prefix: "a", Empty: "a"} + conf2 := &Config{Delim: "b", Glue: "b", Prefix: "b", Empty: "b"} + conf3 := &Config{Delim: "c", Prefix: "c"} + + m := MergeConfig(conf1, conf2) + if m.Delim != "b" || m.Glue != "b" || m.Prefix != "b" || m.Empty != "b" { + t.Fatalf("bad: %#v", m) + } + + m = MergeConfig(conf1, conf3) + if m.Delim != "c" || m.Glue != "a" || m.Prefix != "c" || m.Empty != "a" { + t.Fatalf("bad: %#v", m) + } + + m = MergeConfig(conf1, nil) + if m.Delim != "a" || m.Glue != "a" || m.Prefix != "a" || m.Empty != "a" { + t.Fatalf("bad: %#v", m) + } + + m = MergeConfig(conf1, &Config{}) + if m.Delim != "a" || m.Glue != "a" || m.Prefix != "a" || m.Empty != "a" { + t.Fatalf("bad: %#v", m) + } +} diff --git a/plugins/proxy/subcommands.go b/plugins/proxy/subcommands.go new file mode 100644 index 000000000..89fbc35ee --- /dev/null +++ b/plugins/proxy/subcommands.go @@ -0,0 +1,201 @@ +package proxy + +import ( + "errors" + "strings" + + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/config" +) + +// CommandDisable disables the proxy for app via command line +func CommandDisable(args []string) error { + appName, err := getAppName(args) + if err != nil { + return err + } + + if !IsAppProxyEnabled(appName) { + common.LogInfo1("Proxy is already disable for app") + return nil + } + + common.LogInfo1("Disabling proxy for app") + restart := true + if len(args) >= 2 && args[1] == "--no-restart" { + restart = false + } + + entries := map[string]string{ + "DOKKU_DISABLE_PROXY": "1", + } + + if err := config.SetMany(appName, entries, restart); err != nil { + return err + } + + return common.PlugnTrigger("proxy-disable", []string{appName}...) +} + +// CommandEnable enables the proxy for app via command line +func CommandEnable(args []string) error { + appName, err := getAppName(args) + if err != nil { + return err + } + + if IsAppProxyEnabled(appName) { + common.LogInfo1("Proxy is already enabled for app") + return nil + } + + common.LogInfo1("Enabling proxy for app") + restart := true + if len(args) >= 2 && args[1] == "--no-restart" { + restart = false + } + + keys := []string{"DOKKU_DISABLE_PROXY"} + if err := config.UnsetMany(appName, keys, restart); err != nil { + return err + } + + return common.PlugnTrigger("proxy-enable", []string{appName}...) +} + +// CommandPorts is a cmd wrapper to list proxy port mappings for an app +func CommandPorts(args []string) error { + appName, err := getAppName(args) + if err != nil { + return err + } + + return listAppProxyPorts(appName) +} + +// CommandPortsAdd adds proxy port mappings to an app +func CommandPortsAdd(args []string) error { + appName, err := getAppName(args) + if err != nil { + return err + } + + if len(args) == 1 { + return errors.New("No port mapping specified") + } + + stringPortMap := strings.Join(args[1:], " ") + proxyPortMap := parseProxyPortMapString(stringPortMap) + if err := addProxyPorts(appName, proxyPortMap); err != nil { + return err + } + + return common.PlugnTrigger("post-proxy-ports-update", []string{appName, "add"}...) +} + +// CommandPortsClear clears all proxy port mappings for an app +func CommandPortsClear(args []string) error { + appName, err := getAppName(args) + if err != nil { + return err + } + + keys := []string{"DOKKU_PROXY_PORT_MAP"} + err = config.UnsetMany(appName, keys, false) + if err != nil { + return err + } + + return common.PlugnTrigger("post-proxy-ports-update", []string{appName, "clear"}...) +} + +// CommandPortsRemove removes specific proxy port mappings from an app +func CommandPortsRemove(args []string) error { + appName, err := getAppName(args) + if err != nil { + return err + } + + if len(args) == 1 { + return errors.New("No port mapping specified") + } + + stringPortMap := strings.Join(args[1:], " ") + proxyPortMap := parseProxyPortMapString(stringPortMap) + if err := removeProxyPorts(appName, proxyPortMap); err != nil { + return err + } + + return common.PlugnTrigger("post-proxy-ports-update", []string{appName, "remove"}...) +} + +// CommandPortsSet sets proxy port mappings for an app +func CommandPortsSet(args []string) error { + appName, err := getAppName(args) + if err != nil { + return err + } + + if len(args) == 1 { + return errors.New("No port mapping specified") + } + + stringPortMap := strings.Join(args[1:], " ") + proxyPortMap := parseProxyPortMapString(stringPortMap) + if err := setProxyPorts(appName, proxyPortMap); err != nil { + return err + } + + return common.PlugnTrigger("post-proxy-ports-update", []string{appName, "set"}...) +} + +// CommandReport displays a proxy report for one or more apps +func CommandReport(args []string) error { + appName, err := getAppName(args) + if err != nil { + return err + } + + infoFlag := "" + if len(args) > 1 { + infoFlag = args[1] + } + + if strings.HasPrefix(appName, "--") { + infoFlag = appName + appName = "" + } + + if len(appName) == 0 { + apps, err := common.DokkuApps() + if err != nil { + return err + } + for _, appName := range apps { + if err := ReportSingleApp(appName, infoFlag); err != nil { + return err + } + } + return nil + } + + return ReportSingleApp(appName, infoFlag) +} + +// CommandSet sets a proxy for an app +func CommandSet(args []string) error { + appName, err := getAppName(args) + if err != nil { + return err + } + + if len(appName) < 2 { + return errors.New("Please specify a proxy type!") + } + + proxyType := args[1] + entries := map[string]string{ + "DOKKU_APP_PROXY_TYPE": proxyType, + } + return config.SetMany(appName, entries, false) +} diff --git a/plugins/proxy/subcommands/default b/plugins/proxy/subcommands/default deleted file mode 100755 index b2ed70e70..000000000 --- a/plugins/proxy/subcommands/default +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_AVAILABLE_PATH/proxy/help-functions" - -cmd-proxy-help "proxy:help" diff --git a/plugins/proxy/subcommands/disable b/plugins/proxy/subcommands/disable deleted file mode 100755 index bf2be158c..000000000 --- a/plugins/proxy/subcommands/disable +++ /dev/null @@ -1,28 +0,0 @@ -#!/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" - -cmd-proxy-disable() { - declare desc="disable proxy for app via command line" - declare cmd="proxy:disable" - [[ "$1" == "$cmd" ]] && shift 1 - declare APP="$1" - - [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" - verify_app_name "$APP" - - if [[ "$(is_app_proxy_enabled "$APP")" == "true" ]]; then - dokku_log_info1 "Disabling proxy for app ($APP)" - [[ "$2" == "--no-restart" ]] && local CONFIG_SET_ARGS=$2 - # shellcheck disable=SC2086 - DOKKU_QUIET_OUTPUT=1 config_set $CONFIG_SET_ARGS $APP DOKKU_DISABLE_PROXY=1 - plugn trigger proxy-disable "$APP" - else - dokku_log_info1 "proxy is already disable for app ($APP)" - fi -} - -cmd-proxy-disable "$2" --no-restart diff --git a/plugins/proxy/subcommands/enable b/plugins/proxy/subcommands/enable deleted file mode 100755 index bc28e8ae4..000000000 --- a/plugins/proxy/subcommands/enable +++ /dev/null @@ -1,28 +0,0 @@ -#!/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" - -cmd-proxy-enable() { - declare desc="enable proxy for app via command line" - declare cmd="proxy:enable" - [[ "$1" == "$cmd" ]] && shift 1 - declare APP="$1" - - [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" - verify_app_name "$APP" - - if [[ "$(is_app_proxy_enabled "$APP")" == "false" ]]; then - dokku_log_info1 "Enabling proxy for app ($APP)" - [[ "$2" == "--no-restart" ]] && local CONFIG_SET_ARGS=$2 - # shellcheck disable=SC2086 - DOKKU_QUIET_OUTPUT=1 config_unset $CONFIG_SET_ARGS $APP DOKKU_DISABLE_PROXY - plugn trigger proxy-enable "$APP" - else - dokku_log_info1 "proxy is already enabled for app ($APP)" - fi -} - -cmd-proxy-enable "$2" --no-restart diff --git a/plugins/proxy/subcommands/ports b/plugins/proxy/subcommands/ports deleted file mode 100755 index ae7b25e89..000000000 --- a/plugins/proxy/subcommands/ports +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/proxy/functions" -source "$PLUGIN_AVAILABLE_PATH/config/functions" - -cmd-proxy-ports() { - declare desc="cmd wrapper to list proxy port mappings for an app" - declare cmd="proxy:ports" - [[ "$1" == "$cmd" ]] && shift 1 - declare APP="$1" - - verify_app_name "$APP" - list_app_proxy_ports "$APP" -} - -cmd-proxy-ports "$@" diff --git a/plugins/proxy/subcommands/ports-add b/plugins/proxy/subcommands/ports-add deleted file mode 100755 index 7be477df2..000000000 --- a/plugins/proxy/subcommands/ports-add +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/proxy/functions" - -cmd-proxy-ports-add() { - declare desc="add proxy port mappings to an app" - declare cmd="proxy:ports-add" - [[ "$1" == "$cmd" ]] && shift 1 - declare APP="$1" - - verify_app_name "$APP" - shift 1 - - add_proxy_ports "$APP" "$@" - plugn trigger post-proxy-ports-update "$APP" "add" -} - -cmd-proxy-ports-add "$@" diff --git a/plugins/proxy/subcommands/ports-clear b/plugins/proxy/subcommands/ports-clear deleted file mode 100755 index aaecac1b4..000000000 --- a/plugins/proxy/subcommands/ports-clear +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/config/functions" - -cmd-proxy-ports-clear() { - declare desc="clear all proxy port mappings for an app" - declare cmd="proxy:ports-clear" - [[ "$1" == "$cmd" ]] && shift 1 - declare APP="$1" - - verify_app_name "$APP" - DOKKU_QUIET_OUTPUT=1 config_unset --no-restart "$APP" DOKKU_PROXY_PORT_MAP - plugn trigger post-proxy-ports-update "$APP" "clear" -} - -cmd-proxy-ports-clear "$@" diff --git a/plugins/proxy/subcommands/ports-remove b/plugins/proxy/subcommands/ports-remove deleted file mode 100755 index 4514bf7c1..000000000 --- a/plugins/proxy/subcommands/ports-remove +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/proxy/functions" - -cmd-proxy-ports-remove() { - declare desc="remove specific proxy port mappings from an app" - declare cmd="proxy:ports-remove" - [[ "$1" == "$cmd" ]] && shift 1 - declare APP="$1" - - verify_app_name "$APP" - shift 1 - - local INPUT_PORTS="$*" - if [[ -z "$INPUT_PORTS" ]]; then - dokku_log_warn "No port mapping specified. Please supply a valid configured host port" - list_app_proxy_ports "$APP" - dokku_log_fail "exiting..." - fi - - remove_proxy_ports "$APP" "$@" - plugn trigger post-proxy-ports-update "$APP" "remove" -} - -cmd-proxy-ports-remove "$@" diff --git a/plugins/proxy/subcommands/ports-set b/plugins/proxy/subcommands/ports-set deleted file mode 100755 index fa116713d..000000000 --- a/plugins/proxy/subcommands/ports-set +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/proxy/functions" - -cmd-proxy-ports-set() { - declare desc="set proxy port mappings for an app" - declare cmd="proxy:ports-set" - [[ "$1" == "$cmd" ]] && shift 1 - declare APP="$1" - - verify_app_name "$APP" - shift 1 - - set_proxy_ports "$APP" "$@" - plugn trigger post-proxy-ports-update "$APP" "set" -} - -cmd-proxy-ports-set "$@" diff --git a/plugins/proxy/subcommands/report b/plugins/proxy/subcommands/report deleted file mode 100755 index 709e11b00..000000000 --- a/plugins/proxy/subcommands/report +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash -source "$PLUGIN_AVAILABLE_PATH/proxy/internal-functions" -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x - -cmd-proxy-report "$@" diff --git a/plugins/proxy/subcommands/set b/plugins/proxy/subcommands/set deleted file mode 100755 index 9ee1a779e..000000000 --- a/plugins/proxy/subcommands/set +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail -[[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/config/functions" - -cmd-proxy-set() { - declare desc="enable proxy for app via command line" - declare cmd="proxy:set" - [[ "$1" == "$cmd" ]] && shift 1 - declare APP="$1" PROXY_TYPE="$2" - - [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" - verify_app_name "$APP" - - if [[ -n "$PROXY_TYPE" ]]; then - DOKKU_QUIET_OUTPUT=1 config_set --no-restart "$APP" DOKKU_APP_PROXY_TYPE="$PROXY_TYPE" - else - dokku_log_fail "Please specify a proxy type!" - fi -} - -cmd-proxy-set "$@" diff --git a/plugins/proxy/triggers.go b/plugins/proxy/triggers.go new file mode 100644 index 000000000..cc82dff84 --- /dev/null +++ b/plugins/proxy/triggers.go @@ -0,0 +1,89 @@ +package proxy + +import ( + "fmt" + + "github.com/dokku/dokku/plugins/config" +) + +func TriggerProxyIsEnabled(appName string) error { + if IsAppProxyEnabled(appName) { + fmt.Println("true") + } else { + fmt.Println("false") + } + + return nil +} + +func TriggerProxyType(appName string) error { + proxyType := getAppProxyType(appName) + fmt.Println(proxyType) + + return nil +} + +func TriggerPostCertsRemove(appName string) error { + keys := []string{"DOKKU_PROXY_SSL_PORT"} + if err := config.UnsetMany(appName, keys, false); err != nil { + return err + } + + return removeProxyPorts(appName, filterAppProxyPorts(appName, "https", 443)) +} + +// TriggerPostCertsUpdate unsets port config vars after SSL cert is added +func TriggerPostCertsUpdate(appName string) error { + port := config.GetWithDefault(appName, "DOKKU_PROXY_PORT", "") + sslPort := config.GetWithDefault(appName, "DOKKU_PROXY_SSL_PORT", "") + proxyPortMap := getProxyPortMap(appName) + + toUnset := []string{} + if port == "80" { + toUnset = append(toUnset, "DOKKU_PROXY_PORT") + } + if sslPort == "443" { + toUnset = append(toUnset, "DOKKU_PROXY_SSL_PORT") + } + + if len(toUnset) > 0 { + if err := config.UnsetMany(appName, toUnset, false); err != nil { + return err + } + } + + var http80Ports []PortMap + for _, portMap := range proxyPortMap { + if portMap.Scheme == "http" && portMap.HostPort == 80 { + http80Ports = append(http80Ports, portMap) + } + } + + if len(http80Ports) > 0 { + var https443Ports []PortMap + for _, portMap := range proxyPortMap { + if portMap.Scheme == "https" && portMap.HostPort == 443 { + https443Ports = append(https443Ports, portMap) + } + } + + if err := removeProxyPorts(appName, https443Ports); err != nil { + return err + } + + var toAdd []PortMap + for _, portMap := range http80Ports { + toAdd = append(toAdd, PortMap{ + Scheme: "https", + HostPort: 443, + ContainerPort: portMap.ContainerPort, + }) + } + + if err := addProxyPorts(appName, toAdd); err != nil { + return err + } + } + + return nil +} diff --git a/plugins/proxy/vendor/github.com/ryanuber/columnize/.travis.yml b/plugins/proxy/vendor/github.com/ryanuber/columnize/.travis.yml new file mode 100644 index 000000000..1a0bbea6c --- /dev/null +++ b/plugins/proxy/vendor/github.com/ryanuber/columnize/.travis.yml @@ -0,0 +1,3 @@ +language: go +go: + - tip diff --git a/plugins/proxy/vendor/github.com/ryanuber/columnize/LICENSE b/plugins/proxy/vendor/github.com/ryanuber/columnize/LICENSE new file mode 100644 index 000000000..b9c0e2b68 --- /dev/null +++ b/plugins/proxy/vendor/github.com/ryanuber/columnize/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2016 Ryan Uber + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/proxy/vendor/github.com/ryanuber/columnize/README.md b/plugins/proxy/vendor/github.com/ryanuber/columnize/README.md new file mode 100644 index 000000000..e47634fc6 --- /dev/null +++ b/plugins/proxy/vendor/github.com/ryanuber/columnize/README.md @@ -0,0 +1,69 @@ +Columnize +========= + +Easy column-formatted output for golang + +[![Build Status](https://travis-ci.org/ryanuber/columnize.svg)](https://travis-ci.org/ryanuber/columnize) +[![GoDoc](https://godoc.org/github.com/ryanuber/columnize?status.svg)](https://godoc.org/github.com/ryanuber/columnize) + +Columnize is a really small Go package that makes building CLI's a little bit +easier. In some CLI designs, you want to output a number similar items in a +human-readable way with nicely aligned columns. However, figuring out how wide +to make each column is a boring problem to solve and eats your valuable time. + +Here is an example: + +```go +package main + +import ( + "fmt" + "github.com/ryanuber/columnize" +) + +func main() { + output := []string{ + "Name | Gender | Age", + "Bob | Male | 38", + "Sally | Female | 26", + } + result := columnize.SimpleFormat(output) + fmt.Println(result) +} +``` + +As you can see, you just pass in a list of strings. And the result: + +``` +Name Gender Age +Bob Male 38 +Sally Female 26 +``` + +Columnize is tolerant of missing or empty fields, or even empty lines, so +passing in extra lines for spacing should show up as you would expect. + +Configuration +============= + +Columnize is configured using a `Config`, which can be obtained by calling the +`DefaultConfig()` method. You can then tweak the settings in the resulting +`Config`: + +``` +config := columnize.DefaultConfig() +config.Delim = "|" +config.Glue = " " +config.Prefix = "" +config.Empty = "" +``` + +* `Delim` is the string by which columns of **input** are delimited +* `Glue` is the string by which columns of **output** are delimited +* `Prefix` is a string by which each line of **output** is prefixed +* `Empty` is a string used to replace blank values found in output + +You can then pass the `Config` in using the `Format` method (signature below) to +have text formatted to your liking. + +See the [godoc](https://godoc.org/github.com/ryanuber/columnize) page for usage. diff --git a/plugins/proxy/vendor/github.com/ryanuber/columnize/columnize.go b/plugins/proxy/vendor/github.com/ryanuber/columnize/columnize.go new file mode 100644 index 000000000..915716a10 --- /dev/null +++ b/plugins/proxy/vendor/github.com/ryanuber/columnize/columnize.go @@ -0,0 +1,178 @@ +package columnize + +import ( + "bytes" + "fmt" + "strings" +) + +// Config can be used to tune certain parameters which affect the way +// in which Columnize will format output text. +type Config struct { + // The string by which the lines of input will be split. + Delim string + + // The string by which columns of output will be separated. + Glue string + + // The string by which columns of output will be prefixed. + Prefix string + + // A replacement string to replace empty fields + Empty string +} + +// DefaultConfig returns a *Config with default values. +func DefaultConfig() *Config { + return &Config{ + Delim: "|", + Glue: " ", + Prefix: "", + Empty: "", + } +} + +// MergeConfig merges two config objects together and returns the resulting +// configuration. Values from the right take precedence over the left side. +func MergeConfig(a, b *Config) *Config { + var result Config = *a + + // Return quickly if either side was nil + if a == nil || b == nil { + return &result + } + + if b.Delim != "" { + result.Delim = b.Delim + } + if b.Glue != "" { + result.Glue = b.Glue + } + if b.Prefix != "" { + result.Prefix = b.Prefix + } + if b.Empty != "" { + result.Empty = b.Empty + } + + return &result +} + +// stringFormat, given a set of column widths and the number of columns in +// the current line, returns a sprintf-style format string which can be used +// to print output aligned properly with other lines using the same widths set. +func stringFormat(c *Config, widths []int, columns int) string { + // Create the buffer with an estimate of the length + buf := bytes.NewBuffer(make([]byte, 0, (6+len(c.Glue))*columns)) + + // Start with the prefix, if any was given. The buffer will not return an + // error so it does not need to be handled + buf.WriteString(c.Prefix) + + // Create the format string from the discovered widths + for i := 0; i < columns && i < len(widths); i++ { + if i == columns-1 { + buf.WriteString("%s\n") + } else { + fmt.Fprintf(buf, "%%-%ds%s", widths[i], c.Glue) + } + } + return buf.String() +} + +// elementsFromLine returns a list of elements, each representing a single +// item which will belong to a column of output. +func elementsFromLine(config *Config, line string) []interface{} { + seperated := strings.Split(line, config.Delim) + elements := make([]interface{}, len(seperated)) + for i, field := range seperated { + value := strings.TrimSpace(field) + + // Apply the empty value, if configured. + if value == "" && config.Empty != "" { + value = config.Empty + } + elements[i] = value + } + return elements +} + +// runeLen calculates the number of visible "characters" in a string +func runeLen(s string) int { + l := 0 + for _ = range s { + l++ + } + return l +} + +// widthsFromLines examines a list of strings and determines how wide each +// column should be considering all of the elements that need to be printed +// within it. +func widthsFromLines(config *Config, lines []string) []int { + widths := make([]int, 0, 8) + + for _, line := range lines { + elems := elementsFromLine(config, line) + for i := 0; i < len(elems); i++ { + l := runeLen(elems[i].(string)) + if len(widths) <= i { + widths = append(widths, l) + } else if widths[i] < l { + widths[i] = l + } + } + } + return widths +} + +// Format is the public-facing interface that takes a list of strings and +// returns nicely aligned column-formatted text. +func Format(lines []string, config *Config) string { + conf := MergeConfig(DefaultConfig(), config) + widths := widthsFromLines(conf, lines) + + // Estimate the buffer size + glueSize := len(conf.Glue) + var size int + for _, w := range widths { + size += w + glueSize + } + size *= len(lines) + + // Create the buffer + buf := bytes.NewBuffer(make([]byte, 0, size)) + + // Create a cache for the string formats + fmtCache := make(map[int]string, 16) + + // Create the formatted output using the format string + for _, line := range lines { + elems := elementsFromLine(conf, line) + + // Get the string format using cache + numElems := len(elems) + stringfmt, ok := fmtCache[numElems] + if !ok { + stringfmt = stringFormat(conf, widths, numElems) + fmtCache[numElems] = stringfmt + } + + fmt.Fprintf(buf, stringfmt, elems...) + } + + // Get the string result + result := buf.String() + + // Remove trailing newline without removing leading/trailing space + if n := len(result); n > 0 && result[n-1] == '\n' { + result = result[:n-1] + } + + return result +} + +// SimpleFormat is a convenience function to format text with the defaults. +func SimpleFormat(lines []string) string { + return Format(lines, nil) +} diff --git a/plugins/proxy/vendor/github.com/ryanuber/columnize/columnize_test.go b/plugins/proxy/vendor/github.com/ryanuber/columnize/columnize_test.go new file mode 100644 index 000000000..89dabaa38 --- /dev/null +++ b/plugins/proxy/vendor/github.com/ryanuber/columnize/columnize_test.go @@ -0,0 +1,306 @@ +package columnize + +import ( + "fmt" + "testing" + + crand "crypto/rand" +) + +func TestListOfStringsInput(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "x | y | z", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestEmptyLinesOutput(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "", + "x | y | z", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestLeadingSpacePreserved(t *testing.T) { + input := []string{ + "| Column B | Column C", + "x | y | z", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := " Column B Column C\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestColumnWidthCalculator(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "Longer than A | Longer than B | Longer than C", + "short | short | short", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "Longer than A Longer than B Longer than C\n" + expected += "short short short" + + if output != expected { + printableProof := fmt.Sprintf("\nGot: %+q", output) + printableProof += fmt.Sprintf("\nExpected: %+q", expected) + t.Fatalf("\n%s", printableProof) + } +} + +func TestColumnWidthCalculatorNonASCII(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "⌘⌘⌘⌘⌘⌘⌘⌘ | Longer than B | Longer than C", + "short | short | short", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "⌘⌘⌘⌘⌘⌘⌘⌘ Longer than B Longer than C\n" + expected += "short short short" + + if output != expected { + printableProof := fmt.Sprintf("\nGot: %+q", output) + printableProof += fmt.Sprintf("\nExpected: %+q", expected) + t.Fatalf("\n%s", printableProof) + } +} + +func BenchmarkColumnWidthCalculator(b *testing.B) { + // Generate the input + input := []string{ + "UUID A | UUID B | UUID C | Column D | Column E", + } + + format := "%s|%s|%s|%s" + short := "short" + + uuid := func() string { + buf := make([]byte, 16) + if _, err := crand.Read(buf); err != nil { + panic(fmt.Errorf("failed to read random bytes: %v", err)) + } + + return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x", + buf[0:4], + buf[4:6], + buf[6:8], + buf[8:10], + buf[10:16]) + } + + for i := 0; i < 1000; i++ { + l := fmt.Sprintf(format, uuid()[:8], uuid()[:12], uuid(), short, short) + input = append(input, l) + } + + config := DefaultConfig() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + Format(input, config) + } +} + +func TestVariedInputSpacing(t *testing.T) { + input := []string{ + "Column A |Column B| Column C", + "x|y| z", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestUnmatchedColumnCounts(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "Value A | Value B", + "Value A | Value B | Value C | Value D", + } + + config := DefaultConfig() + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "Value A Value B\n" + expected += "Value A Value B Value C Value D" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestAlternateDelimiter(t *testing.T) { + input := []string{ + "Column | A % Column | B % Column | C", + "Value A % Value B % Value C", + } + + config := DefaultConfig() + config.Delim = "%" + output := Format(input, config) + + expected := "Column | A Column | B Column | C\n" + expected += "Value A Value B Value C" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestAlternateSpacingString(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "x | y | z", + } + + config := DefaultConfig() + config.Glue = " " + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestSimpleFormat(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "x | y | z", + } + + output := SimpleFormat(input) + + expected := "Column A Column B Column C\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestAlternatePrefixString(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "x | y | z", + } + + config := DefaultConfig() + config.Prefix = " " + output := Format(input, config) + + expected := " Column A Column B Column C\n" + expected += " x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestEmptyFieldReplacement(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "x | | z", + } + + config := DefaultConfig() + config.Empty = "" + output := Format(input, config) + + expected := "Column A Column B Column C\n" + expected += "x z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestEmptyConfigValues(t *testing.T) { + input := []string{ + "Column A | Column B | Column C", + "x | y | z", + } + + config := Config{} + output := Format(input, &config) + + expected := "Column A Column B Column C\n" + expected += "x y z" + + if output != expected { + t.Fatalf("\nexpected:\n%s\n\ngot:\n%s", expected, output) + } +} + +func TestMergeConfig(t *testing.T) { + conf1 := &Config{Delim: "a", Glue: "a", Prefix: "a", Empty: "a"} + conf2 := &Config{Delim: "b", Glue: "b", Prefix: "b", Empty: "b"} + conf3 := &Config{Delim: "c", Prefix: "c"} + + m := MergeConfig(conf1, conf2) + if m.Delim != "b" || m.Glue != "b" || m.Prefix != "b" || m.Empty != "b" { + t.Fatalf("bad: %#v", m) + } + + m = MergeConfig(conf1, conf3) + if m.Delim != "c" || m.Glue != "a" || m.Prefix != "c" || m.Empty != "a" { + t.Fatalf("bad: %#v", m) + } + + m = MergeConfig(conf1, nil) + if m.Delim != "a" || m.Glue != "a" || m.Prefix != "a" || m.Empty != "a" { + t.Fatalf("bad: %#v", m) + } + + m = MergeConfig(conf1, &Config{}) + if m.Delim != "a" || m.Glue != "a" || m.Prefix != "a" || m.Empty != "a" { + t.Fatalf("bad: %#v", m) + } +}