From ec9b8be5f8676c2662937d3b69241dab7a38dd0b Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 21 Apr 2017 01:51:32 -0600 Subject: [PATCH 01/66] feat: implement first-pass at networking plugin Implements help, `rebuild`, and `rebuildall` command. Refs #2403 --- docs/networking/network.md | 32 ++++++ plugins/network/commands | 15 +++ plugins/network/functions | 132 +++++++++++++++++++++++++ plugins/network/internal-functions | 27 +++++ plugins/network/plugin.toml | 4 + plugins/network/subcommands/default | 5 + plugins/network/subcommands/rebuild | 13 +++ plugins/network/subcommands/rebuildall | 14 +++ 8 files changed, 242 insertions(+) create mode 100644 docs/networking/network.md create mode 100755 plugins/network/commands create mode 100755 plugins/network/functions create mode 100755 plugins/network/internal-functions create mode 100644 plugins/network/plugin.toml create mode 100755 plugins/network/subcommands/default create mode 100755 plugins/network/subcommands/rebuild create mode 100755 plugins/network/subcommands/rebuildall diff --git a/docs/networking/network.md b/docs/networking/network.md new file mode 100644 index 000000000..aa737dc15 --- /dev/null +++ b/docs/networking/network.md @@ -0,0 +1,32 @@ +# Network Management + +> New as of 0.10.0 + +``` +network:rebuild # Rebuilds network settings for an app +network:rebuildall # Rebuild network settings for all apps +``` + +The Network plugin allows developers to abstract the concept of container network management, allowing developers to both change what networks a given container is attached to as well as rebuild the configuration on the fly. + +## Usage + +### Rebuilding network settings + +There are cases where you may need to rebuild the network configuration for an app, such as on app boot or container restart. In these cases, you can use the `network:rebuild` command: + +```shell +dokku network:rebuild node-js-app +``` + +> This command will exit a non-zero number that depends on the number of containers for which configuration could not be built + +### Rebuilding all network settings + +In some cases, a docker upgrade may reset container IPs or Ports. In both cases, you can quickly rewrite those files by using the `network:rebuildall` command: + +```shell +dokku network:rebuildall +``` + +> This command will exit a non-zero number that depends on the number of containers for which configuration could not be built diff --git a/plugins/network/commands b/plugins/network/commands new file mode 100755 index 000000000..099603de0 --- /dev/null +++ b/plugins/network/commands @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +[[ " help network:help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT" +source "$PLUGIN_AVAILABLE_PATH/network/internal-functions" +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +case "$1" in + help | network:help) + network_help_cmd "$@" + ;; + + *) + exit "$DOKKU_NOT_IMPLEMENTED_EXIT" + ;; + +esac diff --git a/plugins/network/functions b/plugins/network/functions new file mode 100755 index 000000000..e10e07323 --- /dev/null +++ b/plugins/network/functions @@ -0,0 +1,132 @@ +#!/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" + +network-write-config() { + declare desc="writes the network config files out" + declare APP="$1"; verify_app_name "$APP" + local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" + + dokku_log_info1 "Ensuring network configuration is in sync for ${APP}" + local line; local PROC_TYPE; local PROC_COUNT; local CONTAINER_INDEX + while read -r line || [[ -n "$line" ]]; do + [[ "$line" =~ ^#.* ]] && continue + line="$(strip_inline_comments "$line")" + PROC_TYPE=${line%%=*} + PROC_COUNT=${line#*=} + CONTAINER_INDEX=1 + while [[ $CONTAINER_INDEX -le $PROC_COUNT ]]; do + local CID="" p="" port="" ipaddr="" CONTAINER_STATUS + local DOKKU_CONTAINER_ID_FILE="$DOKKU_ROOT/$APP/CONTAINER.$PROC_TYPE.$CONTAINER_INDEX" + local DOKKU_IP_FILE="$DOKKU_ROOT/$APP/IP.$PROC_TYPE.$CONTAINER_INDEX" + local DOKKU_PORT_FILE="$DOKKU_ROOT/$APP/PORT.$PROC_TYPE.$CONTAINER_INDEX" + local CONTAINER_INDEX=$(( CONTAINER_INDEX + 1 )) + + [[ -f "$DOKKU_CONTAINER_ID_FILE" ]] || continue + + CID="$(cat "$DOKKU_CONTAINER_ID_FILE")" + [[ -n "$CID" ]] || continue + CONTAINER_RUNNING="$(docker inspect -f '{{.State.Running}}' "$CID" 2>/dev/null || true)" + [[ "$CONTAINER_RUNNING" == "true" ]] || continue + + ipaddr=$(network-get-container-ipaddr "$APP" "PROC_TYPE" "$CID") + port=$(network-get-container-port "$APP" "PROC_TYPE" "$CID") + + [[ -n "$ipaddr" ]] && echo "$ipaddr" > "$DOKKU_IP_FILE" + [[ -n "$port" ]] && echo "$port" > "$DOKKU_PORT_FILE" + done + done < "$DOKKU_SCALE_FILE" +} + +network-compute-container-ports() { + declare desc="Return the ipaddr for a given app container" + declare APP="$1" PROC_TYPE="$2" + local PORTS + + if [[ "$PROC_TYPE" != "web" ]]; thren + return + fi + + is_image_herokuish_based "$IMAGE" && local DOKKU_HEROKUISH=true + + if [[ -z "$DOKKU_HEROKUISH" ]]; then + local DOKKU_DOCKERFILE_PORTS=($(config_get "$APP" DOKKU_DOCKERFILE_PORTS || true)) + fi + + if [[ -z "${DOKKU_DOCKERFILE_PORTS[*]}" ]]; then + local PORTS=5000 + else + local p + for p in ${DOKKU_DOCKERFILE_PORTS[*]};do + if [[ ! "$p" =~ .*udp.* ]]; then + # set port to first non-udp port + local p=${p//\/tcp} + local PORT=${PORT:="$p"} + fi + PORTS+=" $PORT " + done + fi + + echo "$PORTS" +} + + +network-get-container-ipaddr() { + declare desc="Return the ipaddr for a given app container" + declare APP="$1" PROC_TYPE="$2" CONTAINER_ID="$3" + local DOKKU_IS_APP_PROXY_ENABLED + local IP_ADDRESS=127.0.0.1 + + if [[ "$PROC_TYPE" != "web" ]]; thren + return + fi + + local DOKKU_IS_APP_PROXY_ENABLED="$(is_app_proxy_enabled "$APP")" + if [[ "$DOKKU_IS_APP_PROXY_ENABLED" == "true" ]]; then + IP_ADDRESS="$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$CID")" + # Docker < 1.9 compatibility + if [[ -z $ipaddr ]]; then + IP_ADDRESS=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' "$CID") + fi + fi + + echo "$IP_ADDRESS" +} + +network-get-container-port() { + declare desc="Return the ipaddr for a given app container" + declare APP="$1" PROC_TYPE="$2" CID="$3" + local DOKKU_IS_APP_PROXY_ENABLED + local PORT=5000 + + if [[ "$PROC_TYPE" != "web" ]]; thren + return + fi + + local IMAGE_TAG="$(get_running_image_tag "$APP")" + local IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG") + + is_image_herokuish_based "$IMAGE" && local DOKKU_HEROKUISH=true + + if [[ -z "$DOKKU_HEROKUISH" ]]; then + local DOKKU_DOCKERFILE_PORTS=($(config_get "$APP" DOKKU_DOCKERFILE_PORTS || true)) + fi + + if [[ -n "${DOKKU_DOCKERFILE_PORTS[*]}" ]]; then + for p in ${DOKKU_DOCKERFILE_PORTS[*]};do + if [[ ! "$p" =~ .*udp.* ]]; then + # set port to first non-udp port + p=${p//\/tcp} + PORT=${port:="$p"} + fi + done + fi + + if [[ "$DOKKU_IS_APP_PROXY_ENABLED" != "true" ]]; then + PORT="$(docker port "$CID" "$PORT" | sed 's/[0-9.]*://')" + fi + + echo "$PORT" +} diff --git a/plugins/network/internal-functions b/plugins/network/internal-functions new file mode 100755 index 000000000..31d4adf57 --- /dev/null +++ b/plugins/network/internal-functions @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +network_help_content_func() { + declare desc="return network plugin help content" + cat<, Rebuilds network settings for an app + network:rebuildall, Rebuild network settings for all apps +help_content +} + +network_help_cmd() { + if [[ $1 = "network:help" ]] ; then + echo -e 'Usage: dokku network[:COMMAND]' + echo '' + echo 'Rebuilds network settings for an app.' + echo '' + echo 'Additional commands:' + network_help_content_func | sort | column -c2 -t -s, + elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then + network_help_content_func + else + cat< Date: Fri, 21 Apr 2017 02:19:25 -0600 Subject: [PATCH 02/66] feat: refactor to use network plugin - Add proxy-build-config hook to rebuild network and proxy settings for an application - Scope nginx:build-config to work only when it is the configured proxy for an application - Refactor deploy code to interact with network plugin for computed and actual ipaddr/port settings - Ensure the proxy-build-config plugin trigger is called on ps:restore - Expose a few plugin triggers for retrieving networking information for a given APP/PROC_TYPE/CONTAINER_ID combination --- docs/development/plugin-triggers.md | 125 ++++++++++++++++-- plugins/common/functions | 38 ++---- plugins/network/functions | 9 +- plugins/network/network-build-config | 5 + plugins/network/network-compute-ports | 5 + plugins/network/network-get-ipaddr | 5 + plugins/network/network-get-port | 5 + plugins/nginx-vhosts/proxy-build-config | 18 +++ plugins/nginx-vhosts/subcommands/build-config | 14 +- plugins/ps/subcommands/restore | 2 + 10 files changed, 182 insertions(+), 44 deletions(-) create mode 100755 plugins/network/network-build-config create mode 100755 plugins/network/network-compute-ports create mode 100755 plugins/network/network-get-ipaddr create mode 100755 plugins/network/network-get-port create mode 100755 plugins/nginx-vhosts/proxy-build-config diff --git a/docs/development/plugin-triggers.md b/docs/development/plugin-triggers.md index 4222a9fb7..ebf47d9ce 100644 --- a/docs/development/plugin-triggers.md +++ b/docs/development/plugin-triggers.md @@ -344,6 +344,66 @@ if [[ ! -f "$DOKKU_ROOT/HOSTNAME" ]]; then fi ``` +### `network-build-config` + +- Description: Rebuilds network configuration +- Invoked by: `internally triggered by proxy-build-config within proxy implementations` +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `network-compute-ports` + +- Description: Computes the ports for a given app container +- Invoked by: `internally triggered by proxy-build-config within proxy implementations` +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `network-get-ipaddr` + +- Description: Return the ipaddr for a given app container +- Invoked by: `internally triggered by a deploy` +- Arguments: `$APP $PROC_TYPE $CONTAINER_ID` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `network-get-port` + +- Description: Return the port for a given app container +- Invoked by: `internally triggered by a deploy` +- Arguments: `$APP $PROC_TYPE $CONTAINER_ID` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `nginx-hostname` - Description: Allows you to customize the hostname for a given application. @@ -410,6 +470,40 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # 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 +``` + +### `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-create` - Description: Can be used to run commands after an application is created. @@ -763,10 +857,10 @@ APP="$1"; verify_app_name "$APP" # TODO ``` -### `post-certs-update` +### `proxy-build-config` -- Description: Allows you to run commands after a cert is added/updated -- Invoked by: `dokku certs:add`, `dokku certs:update` +- Description: Builds the proxy implementation configuration for a given app +- Invoked by: `internally triggered by ps:restore` - Arguments: `$APP` - Example: @@ -774,16 +868,29 @@ APP="$1"; verify_app_name "$APP" #!/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` +### `proxy-enable` -- Description: Allows you to run commands after a cert is removed -- Invoked by: `dokku certs:remove` +- Description: Enables the configured proxy implementation for an app +- Invoked by: `internally triggered by ps:restore` +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `proxy-disable` + +- Description: Disables the configured proxy implementation for an app +- Invoked by: `internally triggered by ps:restore` - Arguments: `$APP` - Example: @@ -791,8 +898,6 @@ APP="$1"; verify_app_name "$APP" #!/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 ``` diff --git a/plugins/common/functions b/plugins/common/functions index c9f28250a..816e59391 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -574,8 +574,6 @@ dokku_deploy_cmd() { while [[ $CONTAINER_INDEX -le $PROC_COUNT ]]; do local id=""; local port=""; local ipaddr="" local DOKKU_CONTAINER_ID_FILE="$DOKKU_ROOT/$APP/CONTAINER.$PROC_TYPE.$CONTAINER_INDEX" - local DOKKU_IP_FILE="$DOKKU_ROOT/$APP/IP.$PROC_TYPE.$CONTAINER_INDEX" - local DOKKU_PORT_FILE="$DOKKU_ROOT/$APP/PORT.$PROC_TYPE.$CONTAINER_INDEX" # start the app local DOCKER_ARGS @@ -593,39 +591,29 @@ dokku_deploy_cmd() { fi if [[ "$PROC_TYPE" == "web" ]]; then - if [[ -z "${DOKKU_DOCKERFILE_PORTS[*]}" ]]; then - local port=5000 - local DOKKU_DOCKER_PORT_ARGS+="-p $port" - else - local p - for p in ${DOKKU_DOCKERFILE_PORTS[*]};do - if [[ ! "$p" =~ .*udp.* ]]; then - # set port to first non-udp port - local p=${p//\/tcp} - local port=${port:="$p"} - fi - local DOKKU_DOCKER_PORT_ARGS+=" -p $p " - done - fi + ports="$(plugn trigger network-compute-ports "$APP" "$PROC_TYPE")" + local DOKKU_DOCKER_PORT_ARGS="" + local DOKKU_PORT="" + for port in "${ports[@]}"; do + DOKKU_DOCKER_PORT_ARGS+=" -p $port " + PORTS="$port" + done + if [[ "$DOKKU_IS_APP_PROXY_ENABLED" == "true" ]]; then # shellcheck disable=SC2086 local id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -e PORT=$port $DOCKER_ARGS $IMAGE $START_CMD) - local ipaddr=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$id") - # Docker < 1.9 compatibility - if [[ -z $ipaddr ]]; then - local ipaddr=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' "$id") - fi else # shellcheck disable=SC2086 local id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOKKU_DOCKER_PORT_ARGS -e PORT=$port $DOCKER_ARGS $IMAGE $START_CMD) - local port=$(docker port "$id" "$port" | sed 's/[0-9.]*://') - local ipaddr=127.0.0.1 fi else # shellcheck disable=SC2086 local id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOCKER_ARGS $IMAGE $START_CMD) fi + ipaddr=$(plugn trigger network-get-ipaddr "$APP" "PROC_TYPE" "$id") + port=$(plugn trigger network-get-port "$APP" "PROC_TYPE" "$id") + kill_new() { declare desc="wrapper function to kill newly started app container" local id="$1" @@ -644,8 +632,8 @@ dokku_deploy_cmd() { # now using the new container [[ -n "$id" ]] && echo "$id" > "$DOKKU_CONTAINER_ID_FILE" - [[ -n "$ipaddr" ]] && echo "$ipaddr" > "$DOKKU_IP_FILE" - [[ -n "$port" ]] && echo "$port" > "$DOKKU_PORT_FILE" + [[ -n "$ipaddr" ]] && network-write-container-ipaddr "$APP" "$PROC_TYPE" "$CONTAINER_INDEX" "$ipaddr" + [[ -n "$port" ]] && network-write-container-port "$APP" "$PROC_TYPE" "$CONTAINER_INDEX" "$port" # cleanup pre-migration files rm -f "$DOKKU_ROOT/$APP/CONTAINER" "$DOKKU_ROOT/$APP/IP" "$DOKKU_ROOT/$APP/PORT" diff --git a/plugins/network/functions b/plugins/network/functions index e10e07323..106dfdd28 100755 --- a/plugins/network/functions +++ b/plugins/network/functions @@ -4,8 +4,8 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" source "$PLUGIN_AVAILABLE_PATH/proxy/functions" -network-write-config() { - declare desc="writes the network config files out" +network-build-config() { + declare desc="builds network config files" declare APP="$1"; verify_app_name "$APP" local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" @@ -41,7 +41,7 @@ network-write-config() { } network-compute-container-ports() { - declare desc="Return the ipaddr for a given app container" + declare desc="Computes the ports for a given app container" declare APP="$1" PROC_TYPE="$2" local PORTS @@ -72,7 +72,6 @@ network-compute-container-ports() { echo "$PORTS" } - network-get-container-ipaddr() { declare desc="Return the ipaddr for a given app container" declare APP="$1" PROC_TYPE="$2" CONTAINER_ID="$3" @@ -96,7 +95,7 @@ network-get-container-ipaddr() { } network-get-container-port() { - declare desc="Return the ipaddr for a given app container" + declare desc="Return the port for a given app container" declare APP="$1" PROC_TYPE="$2" CID="$3" local DOKKU_IS_APP_PROXY_ENABLED local PORT=5000 diff --git a/plugins/network/network-build-config b/plugins/network/network-build-config new file mode 100755 index 000000000..235aa1806 --- /dev/null +++ b/plugins/network/network-build-config @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_AVAILABLE_PATH/network/functions" + +network-build-config "$@" diff --git a/plugins/network/network-compute-ports b/plugins/network/network-compute-ports new file mode 100755 index 000000000..04d91debe --- /dev/null +++ b/plugins/network/network-compute-ports @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_AVAILABLE_PATH/network/functions" + +network-compute-container-ports "$@" diff --git a/plugins/network/network-get-ipaddr b/plugins/network/network-get-ipaddr new file mode 100755 index 000000000..254840d02 --- /dev/null +++ b/plugins/network/network-get-ipaddr @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_AVAILABLE_PATH/network/functions" + +network-get-container-ipaddr "$@" diff --git a/plugins/network/network-get-port b/plugins/network/network-get-port new file mode 100755 index 000000000..cc2ac7ddf --- /dev/null +++ b/plugins/network/network-get-port @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_AVAILABLE_PATH/network/functions" + +network-get-container-port "$@" diff --git a/plugins/nginx-vhosts/proxy-build-config b/plugins/nginx-vhosts/proxy-build-config new file mode 100755 index 000000000..7aa867b20 --- /dev/null +++ b/plugins/nginx-vhosts/proxy-build-config @@ -0,0 +1,18 @@ +#!/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_build_config() { + declare desc="build nginx config to proxy app containers from command line" + declare APP="$1" + [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" + + if [[ "$(get_app_proxy_type "$1")" = "nginx" ]]; then + plugn trigger network-build-config "$APP" + nginx_build_config "$APP" + fi +} + +nginx_build_config_cmd "$@" diff --git a/plugins/nginx-vhosts/subcommands/build-config b/plugins/nginx-vhosts/subcommands/build-config index 74b118da2..581b2b36b 100755 --- a/plugins/nginx-vhosts/subcommands/build-config +++ b/plugins/nginx-vhosts/subcommands/build-config @@ -1,14 +1,20 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/config/functions" -source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions" nginx_build_config_cmd() { declare desc="build nginx config to proxy app containers from command line" + declare APP="$2" local cmd="nginx:build-config" - [[ -z $2 ]] && dokku_log_fail "Please specify an app to run the command on" - nginx_build_config "$2" + [[ -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")" + + if [[ "$PROXY_APP_TYPE" == "nginx" ]]; then + plugn trigger proxy-build-config "$APP" + else + dokku_log_fail "Configured proxy for ${APP} is ${PROXY_APP_TYPE}" + fi } nginx_build_config_cmd "$@" diff --git a/plugins/ps/subcommands/restore b/plugins/ps/subcommands/restore index 818c3d980..db501f751 100755 --- a/plugins/ps/subcommands/restore +++ b/plugins/ps/subcommands/restore @@ -10,6 +10,7 @@ ps_restore_cmd() { if which parallel > /dev/null 2>&1; then dokku_apps | parallel dokku ps:start + dokku_apps | parallel plugn trigger proxy-build-config return fi @@ -18,6 +19,7 @@ ps_restore_cmd() { if [[ $DOKKU_APP_RESTORE != 0 ]]; then echo "Restoring app $app ..." if ps_start "$app"; then + plugn trigger proxy-build-config "$app" continue fi dokku_log_warn "dokku ps:restore ${app} failed" From f668bec210bba35137edfd6b7e0a189aed910064 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 21 Apr 2017 02:19:54 -0600 Subject: [PATCH 03/66] docs: move dns and proxy management to new networking section --- docs/{configuration => networking}/dns.md | 0 docs/{advanced-usage => networking}/proxy-management.md | 0 docs/template.html | 8 ++++++-- docs/viewdocs.json | 7 +++++-- 4 files changed, 11 insertions(+), 4 deletions(-) rename docs/{configuration => networking}/dns.md (100%) rename docs/{advanced-usage => networking}/proxy-management.md (100%) diff --git a/docs/configuration/dns.md b/docs/networking/dns.md similarity index 100% rename from docs/configuration/dns.md rename to docs/networking/dns.md diff --git a/docs/advanced-usage/proxy-management.md b/docs/networking/proxy-management.md similarity index 100% rename from docs/advanced-usage/proxy-management.md rename to docs/networking/proxy-management.md diff --git a/docs/template.html b/docs/template.html index 46dd4be7e..81ced6701 100644 --- a/docs/template.html +++ b/docs/template.html @@ -136,11 +136,16 @@ Configuration Environment Variables - DNS Configuration Domain Configuration Nginx Configuration SSL Configuration + Network Management + + DNS Configuration + Network Management + Proxy and Port Management + Advanced Usage Backup and Recovery @@ -149,7 +154,6 @@ Event Logs Persistent Storage Plugin Management - Proxy and Port Management Repository Management Community Contributions diff --git a/docs/viewdocs.json b/docs/viewdocs.json index 873566feb..e37e59f74 100644 --- a/docs/viewdocs.json +++ b/docs/viewdocs.json @@ -15,16 +15,19 @@ "deployment/images": "deployment/methods/images/", "configuration-management": "configuration/environment-variables/", "deployment/ssl-configuration": "configuration/ssl/", - "dns": "configuration/dns/", "nginx": "configuration/nginx/", + "dns": "networking/dns/", + "configuration/dns": "networking/dns/", + "proxy": "networking/proxy-management/", + "advanced-usage/proxy-management": "networking/proxy-management/", + "backup-recovery": "advanced-usage/backup-recovery/", "deployment-tasks": "advanced-usage/deployment-tasks/", "deployment/deployment-tasks": "advanced-usage/deployment-tasks/", "docker-options": "advanced-usage/docker-options/", "dokku-events-logs": "advanced-usage/event-logs/", "dokku-storage": "advanced-usage/persistent-storage/", - "proxy": "advanced-usage/proxy-management/", "plugins": "community/plugins/" } From a9e0f8b7beadf1bf47fcc7f2a9e530433d6b7302 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 21 Apr 2017 02:35:35 -0600 Subject: [PATCH 04/66] fix: ensure we write the ipaddr and port --- plugins/common/functions | 4 ++-- plugins/network/network-write-ipaddr | 11 +++++++++++ plugins/network/network-write-port | 11 +++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100755 plugins/network/network-write-ipaddr create mode 100755 plugins/network/network-write-port diff --git a/plugins/common/functions b/plugins/common/functions index 816e59391..74aa7015b 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -632,8 +632,8 @@ dokku_deploy_cmd() { # now using the new container [[ -n "$id" ]] && echo "$id" > "$DOKKU_CONTAINER_ID_FILE" - [[ -n "$ipaddr" ]] && network-write-container-ipaddr "$APP" "$PROC_TYPE" "$CONTAINER_INDEX" "$ipaddr" - [[ -n "$port" ]] && network-write-container-port "$APP" "$PROC_TYPE" "$CONTAINER_INDEX" "$port" + [[ -n "$ipaddr" ]] && plugn trigger network-write-ipaddr "$APP" "$PROC_TYPE" "$CONTAINER_INDEX" "$ipaddr" + [[ -n "$port" ]] && plugn trigger network-write-port "$APP" "$PROC_TYPE" "$CONTAINER_INDEX" "$port" # cleanup pre-migration files rm -f "$DOKKU_ROOT/$APP/CONTAINER" "$DOKKU_ROOT/$APP/IP" "$DOKKU_ROOT/$APP/PORT" diff --git a/plugins/network/network-write-ipaddr b/plugins/network/network-write-ipaddr new file mode 100755 index 000000000..9f544f73e --- /dev/null +++ b/plugins/network/network-write-ipaddr @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +network-write-ipaddr() { + declare desc="Writes the ipaddr to disk" + declare APP="$1" PROC_TYPE="$2" CONTAINER_INDEX="$3" IP_ADDRESS="$4" + local DOKKU_IP_FILE="$DOKKU_ROOT/$APP/IP.$PROC_TYPE.$CONTAINER_INDEX" + echo "$IP_ADDRESS" > "$DOKKU_IP_FILE" +} + +network-write-ipaddr "$@" diff --git a/plugins/network/network-write-port b/plugins/network/network-write-port new file mode 100755 index 000000000..f43a1b784 --- /dev/null +++ b/plugins/network/network-write-port @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +network-write-port() { + declare desc="Writes the ipaddr to disk" + declare APP="$1" PROC_TYPE="$2" CONTAINER_INDEX="$3" PORT="$4" + local DOKKU_PORT_FILE="$DOKKU_ROOT/$APP/PORT.$PROC_TYPE.$CONTAINER_INDEX" + echo "$PORT" > "$DOKKU_PORT_FILE" +} + +network-write-port "$@" From d7d5d373c9b598415741e37e1eb238cbb82c50f5 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 21 Apr 2017 02:44:25 -0600 Subject: [PATCH 05/66] feat: refactor to retrieve listeners from network plugin --- plugins/network/functions | 15 +++++++++++++++ plugins/network/network-get-listeners | 5 +++++ plugins/nginx-vhosts/functions | 8 +------- 3 files changed, 21 insertions(+), 7 deletions(-) create mode 100755 plugins/network/network-get-listeners diff --git a/plugins/network/functions b/plugins/network/functions index 106dfdd28..75393c391 100755 --- a/plugins/network/functions +++ b/plugins/network/functions @@ -94,6 +94,21 @@ network-get-container-ipaddr() { echo "$IP_ADDRESS" } +network-get-container-listeners() { + declare desc="Return the listeners (host:port combinations) for a given app container" + declare APP="$1" + + local IP_FILE LISTENER_IP LISTENER_PORT LISTENERS PORT_FILE + for IP_FILE in $DOKKU_ROOT/$APP/IP.web.*; do + local PORT_FILE="${IP_FILE//IP/PORT}" + local LISTENER_IP=$(< "$IP_FILE") + local LISTENER_PORT=$(< "$PORT_FILE") + local LISTENERS+="$LISTENER_IP:$LISTENER_PORT " + done + + echo "$LISTENERS" +} + network-get-container-port() { declare desc="Return the port for a given app container" declare APP="$1" PROC_TYPE="$2" CID="$3" diff --git a/plugins/network/network-get-listeners b/plugins/network/network-get-listeners new file mode 100755 index 000000000..9d71e0d11 --- /dev/null +++ b/plugins/network/network-get-listeners @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_AVAILABLE_PATH/network/functions" + +network-get-container-listeners "$@" diff --git a/plugins/nginx-vhosts/functions b/plugins/nginx-vhosts/functions index 4792550df..82e3ab0bb 100755 --- a/plugins/nginx-vhosts/functions +++ b/plugins/nginx-vhosts/functions @@ -229,13 +229,7 @@ nginx_build_config() { if [[ -z "$DOKKU_DISABLE_PROXY" ]]; then if [[ -z "$DOKKU_APP_LISTEN_PORT" ]] && [[ -z "$DOKKU_APP_LISTEN_IP" ]]; then shopt -s nullglob - local DOKKU_APP_IP_FILE - for DOKKU_APP_IP_FILE in $DOKKU_ROOT/$APP/IP.web.*; do - 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") - local DOKKU_APP_LISTENERS+="$DOKKU_APP_LISTENER_IP:$DOKKU_APP_LISTENER_PORT " - done + local DOKKU_APP_LISTENERS="$(plugn trigger network-get-listeners "$APP")" local DOKKU_APP_LISTENERS="$(echo "$DOKKU_APP_LISTENERS" | xargs)" shopt -u nullglob elif [[ -n "$DOKKU_APP_LISTEN_PORT" ]] && [[ -n "$DOKKU_APP_LISTEN_IP" ]]; then From 6fbd148597b9cf679c40af5a5bd391418d75a3a9 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 21 Apr 2017 02:44:50 -0600 Subject: [PATCH 06/66] fix: properly name variable to avoid reference issues --- plugins/network/functions | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/plugins/network/functions b/plugins/network/functions index 75393c391..8e241386e 100755 --- a/plugins/network/functions +++ b/plugins/network/functions @@ -18,7 +18,7 @@ network-build-config() { PROC_COUNT=${line#*=} CONTAINER_INDEX=1 while [[ $CONTAINER_INDEX -le $PROC_COUNT ]]; do - local CID="" p="" port="" ipaddr="" CONTAINER_STATUS + local CONTAINER_ID="" p="" port="" ipaddr="" CONTAINER_STATUS local DOKKU_CONTAINER_ID_FILE="$DOKKU_ROOT/$APP/CONTAINER.$PROC_TYPE.$CONTAINER_INDEX" local DOKKU_IP_FILE="$DOKKU_ROOT/$APP/IP.$PROC_TYPE.$CONTAINER_INDEX" local DOKKU_PORT_FILE="$DOKKU_ROOT/$APP/PORT.$PROC_TYPE.$CONTAINER_INDEX" @@ -26,13 +26,13 @@ network-build-config() { [[ -f "$DOKKU_CONTAINER_ID_FILE" ]] || continue - CID="$(cat "$DOKKU_CONTAINER_ID_FILE")" - [[ -n "$CID" ]] || continue - CONTAINER_RUNNING="$(docker inspect -f '{{.State.Running}}' "$CID" 2>/dev/null || true)" + CONTAINER_ID="$(cat "$DOKKU_CONTAINER_ID_FILE")" + [[ -n "$CONTAINER_ID" ]] || continue + CONTAINER_RUNNING="$(docker inspect -f '{{.State.Running}}' "$CONTAINER_ID" 2>/dev/null || true)" [[ "$CONTAINER_RUNNING" == "true" ]] || continue - ipaddr=$(network-get-container-ipaddr "$APP" "PROC_TYPE" "$CID") - port=$(network-get-container-port "$APP" "PROC_TYPE" "$CID") + ipaddr=$(network-get-container-ipaddr "$APP" "PROC_TYPE" "$CONTAINER_ID") + port=$(network-get-container-port "$APP" "PROC_TYPE" "$CONTAINER_ID") [[ -n "$ipaddr" ]] && echo "$ipaddr" > "$DOKKU_IP_FILE" [[ -n "$port" ]] && echo "$port" > "$DOKKU_PORT_FILE" @@ -84,10 +84,10 @@ network-get-container-ipaddr() { local DOKKU_IS_APP_PROXY_ENABLED="$(is_app_proxy_enabled "$APP")" if [[ "$DOKKU_IS_APP_PROXY_ENABLED" == "true" ]]; then - IP_ADDRESS="$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$CID")" + IP_ADDRESS="$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$CONTAINER_ID")" # Docker < 1.9 compatibility if [[ -z $ipaddr ]]; then - IP_ADDRESS=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' "$CID") + IP_ADDRESS=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' "$CONTAINER_ID") fi fi @@ -111,7 +111,7 @@ network-get-container-listeners() { network-get-container-port() { declare desc="Return the port for a given app container" - declare APP="$1" PROC_TYPE="$2" CID="$3" + declare APP="$1" PROC_TYPE="$2" CONTAINER_ID="$3" local DOKKU_IS_APP_PROXY_ENABLED local PORT=5000 @@ -139,7 +139,7 @@ network-get-container-port() { fi if [[ "$DOKKU_IS_APP_PROXY_ENABLED" != "true" ]]; then - PORT="$(docker port "$CID" "$PORT" | sed 's/[0-9.]*://')" + PORT="$(docker port "$CONTAINER_ID" "$PORT" | sed 's/[0-9.]*://')" fi echo "$PORT" From 901ec7c21395deeaee0b0493a54acf9b6971e265 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 21 Apr 2017 02:49:39 -0600 Subject: [PATCH 07/66] refactor: retrieve ip and port for checks from the network plugin triggers --- plugins/checks/subcommands/run | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/checks/subcommands/run b/plugins/checks/subcommands/run index 9f9b9f2f9..a97758043 100755 --- a/plugins/checks/subcommands/run +++ b/plugins/checks/subcommands/run @@ -46,8 +46,6 @@ check_process_type() { check_process() { local APP="$1" PROC_TYPE="$2" CONTAINER_INDEX="$3" local DOKKU_CONTAINER_ID_FILE="$DOKKU_ROOT/$APP/CONTAINER.$PROC_TYPE.$CONTAINER_INDEX" - local DOKKU_IP_FILE="$DOKKU_ROOT/$APP/IP.$PROC_TYPE.$CONTAINER_INDEX" - local DOKKU_PORT_FILE="$DOKKU_ROOT/$APP/PORT.$PROC_TYPE.$CONTAINER_INDEX" if [[ ! -f "$DOKKU_CONTAINER_ID_FILE" ]]; then dokku_log_fail "Invalid container index specified ($APP.$PROC_TYPE.$CONTAINER_INDEX)" @@ -55,8 +53,8 @@ check_process() { dokku_log_info1 "Running checks for app ($APP.$PROC_TYPE.$CONTAINER_INDEX)" local CONTAINER_ID=$(< "$DOKKU_CONTAINER_ID_FILE") - local IP=$(< "$DOKKU_IP_FILE") - local PORT=$(< "$DOKKU_PORT_FILE") + local IP="$(plugn trigger network-get-ipaddr "$APP" "PROC_TYPE" "$CONTAINER_ID")" + local PORT="$(plugn trigger network-get-port "$APP" "PROC_TYPE" "$CONTAINER_ID")" plugn trigger check-deploy "$APP" "$CONTAINER_ID" "$PROC_TYPE" "$PORT" "$IP" } From 054a1328f78e32b3186e60bf9840f30edafb09ff Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 21 Apr 2017 02:53:28 -0600 Subject: [PATCH 08/66] fix: thren => then --- plugins/network/functions | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/network/functions b/plugins/network/functions index 8e241386e..c8ba53288 100755 --- a/plugins/network/functions +++ b/plugins/network/functions @@ -45,7 +45,7 @@ network-compute-container-ports() { declare APP="$1" PROC_TYPE="$2" local PORTS - if [[ "$PROC_TYPE" != "web" ]]; thren + if [[ "$PROC_TYPE" != "web" ]]; then return fi @@ -78,7 +78,7 @@ network-get-container-ipaddr() { local DOKKU_IS_APP_PROXY_ENABLED local IP_ADDRESS=127.0.0.1 - if [[ "$PROC_TYPE" != "web" ]]; thren + if [[ "$PROC_TYPE" != "web" ]]; then return fi @@ -115,7 +115,7 @@ network-get-container-port() { local DOKKU_IS_APP_PROXY_ENABLED local PORT=5000 - if [[ "$PROC_TYPE" != "web" ]]; thren + if [[ "$PROC_TYPE" != "web" ]]; then return fi From 1a2ed28e5b67a87c82291a9f6ecf9905905b6754 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 21 Apr 2017 02:58:33 -0600 Subject: [PATCH 09/66] docs: add documentation for all new triggers --- docs/development/plugin-triggers.md | 45 +++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/docs/development/plugin-triggers.md b/docs/development/plugin-triggers.md index ebf47d9ce..6a29e92b7 100644 --- a/docs/development/plugin-triggers.md +++ b/docs/development/plugin-triggers.md @@ -389,6 +389,21 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +### `network-get-listeners` + +- Description: Return the listeners (host:port combinations) for a given app container +- Invoked by: `internally triggered by a deploy` +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `network-get-port` - Description: Return the port for a given app container @@ -404,6 +419,36 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +### `network-write-ipaddr` + +- Description: Write the ipaddr for a given app index +- Invoked by: `internally triggered by a deploy` +- Arguments: `$APP $PROC_TYPE $CONTAINER_INDEX $IP_ADDRESS` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + +### `network-write-port` + +- Description: Write the port for a given app index +- Invoked by: `internally triggered by a deploy` +- Arguments: `$APP $PROC_TYPE $CONTAINER_INDEX $PORT` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `nginx-hostname` - Description: Allows you to customize the hostname for a given application. From dfad37ba236950f64c60acc55951ed59a0f1c7a4 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 21 Apr 2017 16:28:56 -0600 Subject: [PATCH 10/66] fix: cleanup issues retrieving the port and ipaddr for a container --- plugins/common/functions | 5 ++--- plugins/network/functions | 8 ++------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/plugins/common/functions b/plugins/common/functions index 74aa7015b..e0d020c3c 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -584,7 +584,6 @@ dokku_deploy_cmd() { [[ -n "$DOKKU_HEROKUISH" ]] && local START_CMD="/start $PROC_TYPE" if [[ -z "$DOKKU_HEROKUISH" ]]; then - local DOKKU_DOCKERFILE_PORTS=($(config_get "$APP" DOKKU_DOCKERFILE_PORTS || true)) local DOKKU_DOCKERFILE_START_CMD=$(config_get "$APP" DOKKU_DOCKERFILE_START_CMD || true) local DOKKU_PROCFILE_START_CMD=$(get_cmd_from_procfile "$APP" "$PROC_TYPE") local START_CMD=${DOKKU_DOCKERFILE_START_CMD:-$DOKKU_PROCFILE_START_CMD} @@ -611,8 +610,8 @@ dokku_deploy_cmd() { local id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOCKER_ARGS $IMAGE $START_CMD) fi - ipaddr=$(plugn trigger network-get-ipaddr "$APP" "PROC_TYPE" "$id") - port=$(plugn trigger network-get-port "$APP" "PROC_TYPE" "$id") + ipaddr=$(plugn trigger network-get-ipaddr "$APP" "$PROC_TYPE" "$id") + port=$(plugn trigger network-get-port "$APP" "$PROC_TYPE" "$id") kill_new() { declare desc="wrapper function to kill newly started app container" diff --git a/plugins/network/functions b/plugins/network/functions index c8ba53288..ba46fd5ad 100755 --- a/plugins/network/functions +++ b/plugins/network/functions @@ -111,7 +111,7 @@ network-get-container-listeners() { network-get-container-port() { declare desc="Return the port for a given app container" - declare APP="$1" PROC_TYPE="$2" CONTAINER_ID="$3" + declare APP="$1" PROC_TYPE="$2" CONTAINER_ID="$3" DOKKU_HEROKUISH="$4" local DOKKU_IS_APP_PROXY_ENABLED local PORT=5000 @@ -119,11 +119,6 @@ network-get-container-port() { return fi - local IMAGE_TAG="$(get_running_image_tag "$APP")" - local IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG") - - is_image_herokuish_based "$IMAGE" && local DOKKU_HEROKUISH=true - if [[ -z "$DOKKU_HEROKUISH" ]]; then local DOKKU_DOCKERFILE_PORTS=($(config_get "$APP" DOKKU_DOCKERFILE_PORTS || true)) fi @@ -138,6 +133,7 @@ network-get-container-port() { done fi + DOKKU_IS_APP_PROXY_ENABLED="$(is_app_proxy_enabled "$APP")" if [[ "$DOKKU_IS_APP_PROXY_ENABLED" != "true" ]]; then PORT="$(docker port "$CONTAINER_ID" "$PORT" | sed 's/[0-9.]*://')" fi From f71d147a8f78022cc24825b05380a6102e751b64 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Fri, 21 Apr 2017 16:31:02 -0600 Subject: [PATCH 11/66] fix: add missing argument --- docs/development/plugin-triggers.md | 2 +- plugins/common/functions | 2 +- plugins/network/functions | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/development/plugin-triggers.md b/docs/development/plugin-triggers.md index 6a29e92b7..0fba2a33a 100644 --- a/docs/development/plugin-triggers.md +++ b/docs/development/plugin-triggers.md @@ -408,7 +408,7 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x - Description: Return the port for a given app container - Invoked by: `internally triggered by a deploy` -- Arguments: `$APP $PROC_TYPE $CONTAINER_ID` +- Arguments: `$APP $PROC_TYPE $CONTAINER_ID $IS_HEROKUISH_CONTAINER` - Example: ```shell diff --git a/plugins/common/functions b/plugins/common/functions index e0d020c3c..5a8b5ed28 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -611,7 +611,7 @@ dokku_deploy_cmd() { fi ipaddr=$(plugn trigger network-get-ipaddr "$APP" "$PROC_TYPE" "$id") - port=$(plugn trigger network-get-port "$APP" "$PROC_TYPE" "$id") + port=$(plugn trigger network-get-port "$APP" "$PROC_TYPE" "$id" "$DOKKU_HEROKUISH") kill_new() { declare desc="wrapper function to kill newly started app container" diff --git a/plugins/network/functions b/plugins/network/functions index ba46fd5ad..ee195b774 100755 --- a/plugins/network/functions +++ b/plugins/network/functions @@ -111,7 +111,7 @@ network-get-container-listeners() { network-get-container-port() { declare desc="Return the port for a given app container" - declare APP="$1" PROC_TYPE="$2" CONTAINER_ID="$3" DOKKU_HEROKUISH="$4" + declare APP="$1" PROC_TYPE="$2" CONTAINER_ID="$3" IS_HEROKUISH_CONTAINER="$4" local DOKKU_IS_APP_PROXY_ENABLED local PORT=5000 @@ -119,7 +119,7 @@ network-get-container-port() { return fi - if [[ -z "$DOKKU_HEROKUISH" ]]; then + if [[ -z "$IS_HEROKUISH_CONTAINER" ]]; then local DOKKU_DOCKERFILE_PORTS=($(config_get "$APP" DOKKU_DOCKERFILE_PORTS || true)) fi From aee88d081d0ecf9afd619f68284d6c90defe1c7c Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 22 Apr 2017 13:48:04 -0600 Subject: [PATCH 12/66] fix: add missing proxy/functions source call --- plugins/nginx-vhosts/subcommands/build-config | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/nginx-vhosts/subcommands/build-config b/plugins/nginx-vhosts/subcommands/build-config index 581b2b36b..88b3b6af5 100755 --- a/plugins/nginx-vhosts/subcommands/build-config +++ b/plugins/nginx-vhosts/subcommands/build-config @@ -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/proxy/functions" nginx_build_config_cmd() { declare desc="build nginx config to proxy app containers from command line" From 06a7cb2a3dd2420e253b7e27bc34569a7ee6b140 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 22 Apr 2017 14:39:20 -0600 Subject: [PATCH 13/66] fix: check correct variable --- plugins/network/functions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/network/functions b/plugins/network/functions index ee195b774..483775662 100755 --- a/plugins/network/functions +++ b/plugins/network/functions @@ -86,7 +86,7 @@ network-get-container-ipaddr() { if [[ "$DOKKU_IS_APP_PROXY_ENABLED" == "true" ]]; then IP_ADDRESS="$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$CONTAINER_ID")" # Docker < 1.9 compatibility - if [[ -z $ipaddr ]]; then + if [[ -z "$IP_ADDRESS" ]]; then IP_ADDRESS=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' "$CONTAINER_ID") fi fi From 4949d1e33e75703c47d67f53b7b4707bca307ff5 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 22 Apr 2017 14:40:10 -0600 Subject: [PATCH 14/66] fix: always pass in IS_HEROKUISH_CONTAINER --- plugins/common/functions | 6 +++--- plugins/network/functions | 36 +++++++++++++++++++----------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/plugins/common/functions b/plugins/common/functions index 5a8b5ed28..506e6e707 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -590,7 +590,7 @@ dokku_deploy_cmd() { fi if [[ "$PROC_TYPE" == "web" ]]; then - ports="$(plugn trigger network-compute-ports "$APP" "$PROC_TYPE")" + ports="$(plugn trigger network-compute-ports "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH")" local DOKKU_DOCKER_PORT_ARGS="" local DOKKU_PORT="" for port in "${ports[@]}"; do @@ -610,8 +610,8 @@ dokku_deploy_cmd() { local id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOCKER_ARGS $IMAGE $START_CMD) fi - ipaddr=$(plugn trigger network-get-ipaddr "$APP" "$PROC_TYPE" "$id") - port=$(plugn trigger network-get-port "$APP" "$PROC_TYPE" "$id" "$DOKKU_HEROKUISH") + ipaddr=$(plugn trigger network-get-ipaddr "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH" "$id") + port=$(plugn trigger network-get-port "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH" "$id") kill_new() { declare desc="wrapper function to kill newly started app container" diff --git a/plugins/network/functions b/plugins/network/functions index 483775662..934742e68 100755 --- a/plugins/network/functions +++ b/plugins/network/functions @@ -7,15 +7,19 @@ source "$PLUGIN_AVAILABLE_PATH/proxy/functions" network-build-config() { declare desc="builds network config files" declare APP="$1"; verify_app_name "$APP" - local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" + local CONTAINER_INDEX DOKKU_SCALE IMAGE IS_HEROKUISH_CONTAINER PROC_COUNT PROC_LINE PROC_TYPE + + DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" + IS_HEROKUISH_CONTAINER=false + IMAGE=$(get_app_image_name "$APP") + is_image_herokuish_based "$IMAGE" && IS_HEROKUISH_CONTAINER=true dokku_log_info1 "Ensuring network configuration is in sync for ${APP}" - local line; local PROC_TYPE; local PROC_COUNT; local CONTAINER_INDEX - while read -r line || [[ -n "$line" ]]; do - [[ "$line" =~ ^#.* ]] && continue - line="$(strip_inline_comments "$line")" - PROC_TYPE=${line%%=*} - PROC_COUNT=${line#*=} + while read -r PROC_LINE || [[ -n "$PROC_LINE" ]]; do + [[ "$PROC_LINE" =~ ^#.* ]] && continue + PROC_LINE="$(strip_inline_comments "$PROC_LINE")" + PROC_TYPE=${PROC_LINE%%=*} + PROC_COUNT=${PROC_LINE#*=} CONTAINER_INDEX=1 while [[ $CONTAINER_INDEX -le $PROC_COUNT ]]; do local CONTAINER_ID="" p="" port="" ipaddr="" CONTAINER_STATUS @@ -31,8 +35,8 @@ network-build-config() { CONTAINER_RUNNING="$(docker inspect -f '{{.State.Running}}' "$CONTAINER_ID" 2>/dev/null || true)" [[ "$CONTAINER_RUNNING" == "true" ]] || continue - ipaddr=$(network-get-container-ipaddr "$APP" "PROC_TYPE" "$CONTAINER_ID") - port=$(network-get-container-port "$APP" "PROC_TYPE" "$CONTAINER_ID") + ipaddr=$(network-get-container-ipaddr "$APP" "PROC_TYPE" "$IS_HEROKUISH_CONTAINER" "$CONTAINER_ID") + port=$(network-get-container-port "$APP" "PROC_TYPE" "$IS_HEROKUISH_CONTAINER" "$CONTAINER_ID") [[ -n "$ipaddr" ]] && echo "$ipaddr" > "$DOKKU_IP_FILE" [[ -n "$port" ]] && echo "$port" > "$DOKKU_PORT_FILE" @@ -42,17 +46,15 @@ network-build-config() { network-compute-container-ports() { declare desc="Computes the ports for a given app container" - declare APP="$1" PROC_TYPE="$2" - local PORTS + declare APP="$1" PROC_TYPE="$2" IS_HEROKUISH_CONTAINER="$3" + local DOKKU_DOCKERFILE_PORTS PORTS if [[ "$PROC_TYPE" != "web" ]]; then return fi - is_image_herokuish_based "$IMAGE" && local DOKKU_HEROKUISH=true - - if [[ -z "$DOKKU_HEROKUISH" ]]; then - local DOKKU_DOCKERFILE_PORTS=($(config_get "$APP" DOKKU_DOCKERFILE_PORTS || true)) + if [[ -z "$IS_HEROKUISH_CONTAINER" ]]; then + DOKKU_DOCKERFILE_PORTS=($(config_get "$APP" DOKKU_DOCKERFILE_PORTS || true)) fi if [[ -z "${DOKKU_DOCKERFILE_PORTS[*]}" ]]; then @@ -74,7 +76,7 @@ network-compute-container-ports() { network-get-container-ipaddr() { declare desc="Return the ipaddr for a given app container" - declare APP="$1" PROC_TYPE="$2" CONTAINER_ID="$3" + declare APP="$1" PROC_TYPE="$2" IS_HEROKUISH_CONTAINER="$3" CONTAINER_ID="$4" local DOKKU_IS_APP_PROXY_ENABLED local IP_ADDRESS=127.0.0.1 @@ -111,7 +113,7 @@ network-get-container-listeners() { network-get-container-port() { declare desc="Return the port for a given app container" - declare APP="$1" PROC_TYPE="$2" CONTAINER_ID="$3" IS_HEROKUISH_CONTAINER="$4" + declare APP="$1" PROC_TYPE="$2" IS_HEROKUISH_CONTAINER="$3" CONTAINER_ID="$4" local DOKKU_IS_APP_PROXY_ENABLED local PORT=5000 From b8a4152e10b79a7212ee1ab59939cde699ea2747 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 22 Apr 2017 15:50:13 -0600 Subject: [PATCH 15/66] fix: call the correct method in the proxy-build-config hook --- plugins/nginx-vhosts/proxy-build-config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/nginx-vhosts/proxy-build-config b/plugins/nginx-vhosts/proxy-build-config index 7aa867b20..358fa1997 100755 --- a/plugins/nginx-vhosts/proxy-build-config +++ b/plugins/nginx-vhosts/proxy-build-config @@ -4,7 +4,7 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/nginx-vhosts/functions" source "$PLUGIN_AVAILABLE_PATH/proxy/functions" -nginx_build_config() { +nginx_proxy_build_config() { declare desc="build nginx config to proxy app containers from command line" declare APP="$1" [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" @@ -15,4 +15,4 @@ nginx_build_config() { fi } -nginx_build_config_cmd "$@" +nginx_proxy_build_config "$@" From b0afa778fbdf9ea631ec05e3317d6cd1a77a3aef Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 22 Apr 2017 15:52:08 -0600 Subject: [PATCH 16/66] chore: minor cleanup --- plugins/nginx-vhosts/post-domains-update | 9 ++++++--- plugins/nginx-vhosts/proxy-build-config | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/nginx-vhosts/post-domains-update b/plugins/nginx-vhosts/post-domains-update index 6201175b7..29a590963 100755 --- a/plugins/nginx-vhosts/post-domains-update +++ b/plugins/nginx-vhosts/post-domains-update @@ -6,9 +6,12 @@ source "$PLUGIN_AVAILABLE_PATH/proxy/functions" nginx_post_domains_update() { declare desc="calls nginx build_config when domains are updated" - local trigger="nginx_post_domains_update" - if [[ "$(get_app_proxy_type "$1")" == "nginx" ]]; then - nginx_build_config "$1" + declare trigger="nginx_post_domains_update" + 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 + nginx_build_config "$APP" fi } diff --git a/plugins/nginx-vhosts/proxy-build-config b/plugins/nginx-vhosts/proxy-build-config index 358fa1997..0253f7181 100755 --- a/plugins/nginx-vhosts/proxy-build-config +++ b/plugins/nginx-vhosts/proxy-build-config @@ -6,10 +6,11 @@ source "$PLUGIN_AVAILABLE_PATH/proxy/functions" nginx_proxy_build_config() { declare desc="build nginx config to proxy app containers from command line" + declare trigger="nginx_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 "$1")" = "nginx" ]]; then + if [[ "$(get_app_proxy_type "$APP")" = "nginx" ]]; then plugn trigger network-build-config "$APP" nginx_build_config "$APP" fi From b409af602a11bd2e32c81ebfbd73b089044aa78b Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 22 Apr 2017 20:35:25 -0600 Subject: [PATCH 17/66] fix: call network-get-ipaddr correctly --- plugins/checks/subcommands/run | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/checks/subcommands/run b/plugins/checks/subcommands/run index a97758043..5be88e38f 100755 --- a/plugins/checks/subcommands/run +++ b/plugins/checks/subcommands/run @@ -45,16 +45,20 @@ check_process_type() { check_process() { local APP="$1" PROC_TYPE="$2" CONTAINER_INDEX="$3" - local DOKKU_CONTAINER_ID_FILE="$DOKKU_ROOT/$APP/CONTAINER.$PROC_TYPE.$CONTAINER_INDEX" + local CONTAINER_ID DOKKU_CONTAINER_ID_FILE IMAGE IP IS_HEROKUISH_CONTAINER PORT + DOKKU_CONTAINER_ID_FILE="$DOKKU_ROOT/$APP/CONTAINER.$PROC_TYPE.$CONTAINER_INDEX" if [[ ! -f "$DOKKU_CONTAINER_ID_FILE" ]]; then dokku_log_fail "Invalid container index specified ($APP.$PROC_TYPE.$CONTAINER_INDEX)" fi + IMAGE=$(get_app_image_name "$APP") + is_image_herokuish_based "$IMAGE" && IS_HEROKUISH_CONTAINER=true + dokku_log_info1 "Running checks for app ($APP.$PROC_TYPE.$CONTAINER_INDEX)" - local CONTAINER_ID=$(< "$DOKKU_CONTAINER_ID_FILE") - local IP="$(plugn trigger network-get-ipaddr "$APP" "PROC_TYPE" "$CONTAINER_ID")" - local PORT="$(plugn trigger network-get-port "$APP" "PROC_TYPE" "$CONTAINER_ID")" + CONTAINER_ID=$(< "$DOKKU_CONTAINER_ID_FILE") + IP="$(plugn trigger network-get-ipaddr "$APP" "$PROC_TYPE" "$IS_HEROKUISH_CONTAINER" "$CONTAINER_ID")" + PORT="$(plugn trigger network-get-port "$APP" "$PROC_TYPE" "$IS_HEROKUISH_CONTAINER" "$CONTAINER_ID")" plugn trigger check-deploy "$APP" "$CONTAINER_ID" "$PROC_TYPE" "$PORT" "$IP" } From 0cc9e42c5946f3a442c45a2d2de619d6117261b1 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 22 Apr 2017 21:09:27 -0600 Subject: [PATCH 18/66] fix: check IS_HEROKUISH_CONTAINER value check --- plugins/common/functions | 11 +++++++---- plugins/network/functions | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/plugins/common/functions b/plugins/common/functions index 506e6e707..550e08692 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -539,11 +539,14 @@ dokku_deploy_cmd() { source "$PLUGIN_AVAILABLE_PATH/proxy/functions" [[ -z $1 ]] && dokku_log_fail "Please specify an app to run the command on" - local APP="$1"; local IMAGE_TAG="$2"; local IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG") + local APP="$1" IMAGE_TAG="$2" + local DOKKU_HEROKUISH IMAGE + DOKKU_HEROKUISH=false + IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG") verify_app_name "$APP" plugn trigger pre-deploy "$APP" "$IMAGE_TAG" - is_image_herokuish_based "$IMAGE" && local DOKKU_HEROKUISH=true + is_image_herokuish_based "$IMAGE" && DOKKU_HEROKUISH=true local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" local oldids=$(get_app_container_ids "$APP") @@ -581,9 +584,9 @@ dokku_deploy_cmd() { DOCKER_ARGS+=" -e DYNO=$PROC_TYPE.$CONTAINER_INDEX " [[ "$DOKKU_TRACE" ]] && DOCKER_ARGS+=" -e TRACE=true " - [[ -n "$DOKKU_HEROKUISH" ]] && local START_CMD="/start $PROC_TYPE" + [[ "$DOKKU_HEROKUISH" == "true" ]] && local START_CMD="/start $PROC_TYPE" - if [[ -z "$DOKKU_HEROKUISH" ]]; then + if [[ "$DOKKU_HEROKUISH" == "false" ]]; then local DOKKU_DOCKERFILE_START_CMD=$(config_get "$APP" DOKKU_DOCKERFILE_START_CMD || true) local DOKKU_PROCFILE_START_CMD=$(get_cmd_from_procfile "$APP" "$PROC_TYPE") local START_CMD=${DOKKU_DOCKERFILE_START_CMD:-$DOKKU_PROCFILE_START_CMD} diff --git a/plugins/network/functions b/plugins/network/functions index 934742e68..0eb97d12c 100755 --- a/plugins/network/functions +++ b/plugins/network/functions @@ -53,7 +53,7 @@ network-compute-container-ports() { return fi - if [[ -z "$IS_HEROKUISH_CONTAINER" ]]; then + if [[ "$IS_HEROKUISH_CONTAINER" == "false" ]]; then DOKKU_DOCKERFILE_PORTS=($(config_get "$APP" DOKKU_DOCKERFILE_PORTS || true)) fi @@ -121,7 +121,7 @@ network-get-container-port() { return fi - if [[ -z "$IS_HEROKUISH_CONTAINER" ]]; then + if [[ "$IS_HEROKUISH_CONTAINER" == "false" ]]; then local DOKKU_DOCKERFILE_PORTS=($(config_get "$APP" DOKKU_DOCKERFILE_PORTS || true)) fi From 2c5190bf6337fbf2f87fdea7a4d0a96307a32428 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 22 Apr 2017 21:11:49 -0600 Subject: [PATCH 19/66] fix: set IS_HEROKUISH_CONTAINER=false to false by default --- plugins/checks/subcommands/run | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/checks/subcommands/run b/plugins/checks/subcommands/run index 5be88e38f..dd9c6f935 100755 --- a/plugins/checks/subcommands/run +++ b/plugins/checks/subcommands/run @@ -52,6 +52,7 @@ check_process() { dokku_log_fail "Invalid container index specified ($APP.$PROC_TYPE.$CONTAINER_INDEX)" fi + IS_HEROKUISH_CONTAINER=false IMAGE=$(get_app_image_name "$APP") is_image_herokuish_based "$IMAGE" && IS_HEROKUISH_CONTAINER=true From 006eb452f1b56cbba1f0ddaa04f23a510dfcd900 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 22 Apr 2017 21:54:08 -0600 Subject: [PATCH 20/66] fix: do not generate network config if there is no generated scale file --- plugins/network/functions | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/network/functions b/plugins/network/functions index 0eb97d12c..e5d567f5d 100755 --- a/plugins/network/functions +++ b/plugins/network/functions @@ -7,9 +7,13 @@ source "$PLUGIN_AVAILABLE_PATH/proxy/functions" network-build-config() { declare desc="builds network config files" declare APP="$1"; verify_app_name "$APP" - local CONTAINER_INDEX DOKKU_SCALE IMAGE IS_HEROKUISH_CONTAINER PROC_COUNT PROC_LINE PROC_TYPE - + local CONTAINER_INDEX DOKKU_SCALE_FILE IMAGE IS_HEROKUISH_CONTAINER PROC_COUNT PROC_LINE PROC_TYPE DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" + + if [[ ! -f "$DOKKU_SCALE_FILE" ]]; then + return + fi + IS_HEROKUISH_CONTAINER=false IMAGE=$(get_app_image_name "$APP") is_image_herokuish_based "$IMAGE" && IS_HEROKUISH_CONTAINER=true From 7fd32fb3e8dda5b23c70089bfe9a05479a0aea5b Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 23 Apr 2017 12:41:41 -0600 Subject: [PATCH 21/66] fix: properly retrieve ports list --- plugins/common/functions | 12 +++++++----- plugins/network/functions | 5 ++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/plugins/common/functions b/plugins/common/functions index 550e08692..238c81a02 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -596,17 +596,19 @@ dokku_deploy_cmd() { ports="$(plugn trigger network-compute-ports "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH")" local DOKKU_DOCKER_PORT_ARGS="" local DOKKU_PORT="" - for port in "${ports[@]}"; do - DOKKU_DOCKER_PORT_ARGS+=" -p $port " - PORTS="$port" + for p in "${ports[@]}"; do + if [[ ! "$p" =~ .*udp.* ]]; then + DOKKU_PORT=${DOKKU_PORT:="$p"} + fi + DOKKU_DOCKER_PORT_ARGS+=" -p $p " done if [[ "$DOKKU_IS_APP_PROXY_ENABLED" == "true" ]]; then # shellcheck disable=SC2086 - local id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -e PORT=$port $DOCKER_ARGS $IMAGE $START_CMD) + local id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -e PORT=$DOKKU_PORT $DOCKER_ARGS $IMAGE $START_CMD) else # shellcheck disable=SC2086 - local id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOKKU_DOCKER_PORT_ARGS -e PORT=$port $DOCKER_ARGS $IMAGE $START_CMD) + local id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOKKU_DOCKER_PORT_ARGS -e PORT=$DOKKU_PORT $DOCKER_ARGS $IMAGE $START_CMD) fi else # shellcheck disable=SC2086 diff --git a/plugins/network/functions b/plugins/network/functions index e5d567f5d..114dabf0f 100755 --- a/plugins/network/functions +++ b/plugins/network/functions @@ -68,10 +68,9 @@ network-compute-container-ports() { for p in ${DOKKU_DOCKERFILE_PORTS[*]};do if [[ ! "$p" =~ .*udp.* ]]; then # set port to first non-udp port - local p=${p//\/tcp} - local PORT=${PORT:="$p"} + p=${p//\/tcp} fi - PORTS+=" $PORT " + PORTS+="$p " done fi From 94149c844580f612e525191d28bb0713d99d37d2 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 23 Apr 2017 12:51:40 -0600 Subject: [PATCH 22/66] fix: properly pass PROC_TYPE --- plugins/network/functions | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/network/functions b/plugins/network/functions index 114dabf0f..a720b9d5d 100755 --- a/plugins/network/functions +++ b/plugins/network/functions @@ -39,8 +39,8 @@ network-build-config() { CONTAINER_RUNNING="$(docker inspect -f '{{.State.Running}}' "$CONTAINER_ID" 2>/dev/null || true)" [[ "$CONTAINER_RUNNING" == "true" ]] || continue - ipaddr=$(network-get-container-ipaddr "$APP" "PROC_TYPE" "$IS_HEROKUISH_CONTAINER" "$CONTAINER_ID") - port=$(network-get-container-port "$APP" "PROC_TYPE" "$IS_HEROKUISH_CONTAINER" "$CONTAINER_ID") + ipaddr=$(network-get-container-ipaddr "$APP" "$PROC_TYPE" "$IS_HEROKUISH_CONTAINER" "$CONTAINER_ID") + port=$(network-get-container-port "$APP" "$PROC_TYPE" "$IS_HEROKUISH_CONTAINER" "$CONTAINER_ID") [[ -n "$ipaddr" ]] && echo "$ipaddr" > "$DOKKU_IP_FILE" [[ -n "$port" ]] && echo "$port" > "$DOKKU_PORT_FILE" From c43a3edfc688fec239e27b79ecca79f79863d6fd Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 23 Apr 2017 13:18:06 -0600 Subject: [PATCH 23/66] fix: ensure ports are set to an array --- plugins/common/functions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/common/functions b/plugins/common/functions index 238c81a02..de06c03c5 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -593,7 +593,7 @@ dokku_deploy_cmd() { fi if [[ "$PROC_TYPE" == "web" ]]; then - ports="$(plugn trigger network-compute-ports "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH")" + ports=($(plugn trigger network-compute-ports "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH")) local DOKKU_DOCKER_PORT_ARGS="" local DOKKU_PORT="" for p in "${ports[@]}"; do From 5b592575e709af9c63a9a48948e30eb1af3f3c00 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 23 Apr 2017 16:27:34 -0600 Subject: [PATCH 24/66] fix: call correct command --- plugins/network/subcommands/rebuild | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/network/subcommands/rebuild b/plugins/network/subcommands/rebuild index e45e4cdea..b4a173e1c 100755 --- a/plugins/network/subcommands/rebuild +++ b/plugins/network/subcommands/rebuild @@ -7,7 +7,7 @@ network_rebuild_cmd() { local cmd="network:rebuild" local APP="$2" - dokku_write_network_config "$APP" + network-build-config "$APP" } network_rebuild_cmd "$@" From e74346f0dc48b1c51eabca21cd45992216f5d1b5 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 23 Apr 2017 16:28:01 -0600 Subject: [PATCH 25/66] fix: call correct command --- plugins/network/subcommands/rebuildall | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/network/subcommands/rebuildall b/plugins/network/subcommands/rebuildall index b8c0c3b06..b3f77232f 100755 --- a/plugins/network/subcommands/rebuildall +++ b/plugins/network/subcommands/rebuildall @@ -7,7 +7,7 @@ network_rebuildall_cmd() { declare desc="rebuilds network settings for all apps" local cmd="network:rebuildall" for app in $(dokku_apps); do - (is_deployed "$app") && dokku_write_network_config "$app" + (is_deployed "$app") && network-build-config "$app" done } From c788659b6326e139cd0c15ca7b3057fae9182095 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Mon, 24 Apr 2017 09:03:30 -0600 Subject: [PATCH 26/66] refactor: rewrite network plugin in golang --- .editorconfig | 2 +- plugins/common/common.go | 232 ++++++++++++- plugins/config/config.go | 30 ++ plugins/network/.gitignore | 3 + plugins/network/Makefile | 47 +++ plugins/network/commands | 15 - plugins/network/functions | 147 --------- plugins/network/glide.yaml | 4 + plugins/network/internal-functions | 27 -- plugins/network/network-build-config | 5 - plugins/network/network-compute-ports | 5 - plugins/network/network-get-ipaddr | 5 - plugins/network/network-get-listeners | 5 - plugins/network/network-get-port | 5 - plugins/network/network-write-ipaddr | 11 - plugins/network/network-write-port | 11 - plugins/network/network.go | 165 ++++++++++ plugins/network/src/commands/commands.go | 54 ++++ plugins/network/src/glide.lock | 6 + plugins/network/src/glide.yaml | 3 + .../src/subcommands/rebuild/rebuild.go | 15 + .../src/subcommands/rebuildall/rebuildall.go | 17 + .../network-build-config.go | 15 + .../network-compute-ports.go | 42 +++ .../network-get-ipaddr/network-get-ipaddr.go | 22 ++ .../network-get-listeners.go | 32 ++ .../network-get-port/network-get-port.go | 22 ++ .../network-write-ipaddr.go | 40 +++ .../network-write-port/network-write-port.go | 40 +++ .../github.com/ryanuber/columnize/.travis.yml | 3 + .../github.com/ryanuber/columnize/LICENSE | 20 ++ .../github.com/ryanuber/columnize/README.md | 69 ++++ .../ryanuber/columnize/columnize.go | 178 ++++++++++ .../ryanuber/columnize/columnize_test.go | 306 ++++++++++++++++++ plugins/network/subcommands/default | 5 - plugins/network/subcommands/rebuild | 13 - plugins/network/subcommands/rebuildall | 14 - .../github.com/codegangsta/inject/.gitignore | 2 + .../github.com/codegangsta/inject/LICENSE | 20 ++ .../github.com/codegangsta/inject/README.md | 92 ++++++ .../github.com/codegangsta/inject/inject.go | 187 +++++++++++ .../codegangsta/inject/inject_test.go | 159 +++++++++ .../inject/translations/README_zh_cn.md | 85 +++++ .../codegangsta/inject/update_readme.sh | 3 + .../github.com/codeskyblue/go-sh/LICENSE | 202 ++++++++++++ .../codeskyblue/go-sh/OLD_README.md | 69 ++++ .../github.com/codeskyblue/go-sh/README.md | 85 +++++ .../codeskyblue/go-sh/example/example1.go | 41 +++ .../codeskyblue/go-sh/example/less/less.go | 7 + .../codeskyblue/go-sh/example/tail/tailf.go | 17 + .../go-sh/example/timeout/timeout.go | 23 ++ .../codeskyblue/go-sh/example_test.go | 28 ++ .../github.com/codeskyblue/go-sh/pipe.go | 148 +++++++++ .../github.com/codeskyblue/go-sh/pipe_test.go | 126 ++++++++ .../vendor/github.com/codeskyblue/go-sh/sh.go | 200 ++++++++++++ .../github.com/codeskyblue/go-sh/sh_test.go | 106 ++++++ .../github.com/codeskyblue/go-sh/test.go | 64 ++++ .../github.com/codeskyblue/go-sh/test_test.go | 58 ++++ .../codeskyblue/go-sh/testdata/executable | 0 .../codeskyblue/go-sh/testdata/hello.txt | 0 .../codeskyblue/go-sh/testdata/linkfile | 0 .../github.com/codeskyblue/go-sh/wercker.yml | 28 ++ .../github.com/ryanuber/columnize/.travis.yml | 3 + .../github.com/ryanuber/columnize/LICENSE | 20 ++ .../github.com/ryanuber/columnize/README.md | 69 ++++ .../ryanuber/columnize/columnize.go | 178 ++++++++++ .../ryanuber/columnize/columnize_test.go | 306 ++++++++++++++++++ plugins/proxy/proxy.go | 21 ++ plugins/repo/src/commands/commands.go | 2 +- 69 files changed, 3710 insertions(+), 274 deletions(-) create mode 100644 plugins/config/config.go create mode 100644 plugins/network/.gitignore create mode 100644 plugins/network/Makefile delete mode 100755 plugins/network/commands delete mode 100755 plugins/network/functions create mode 100644 plugins/network/glide.yaml delete mode 100755 plugins/network/internal-functions delete mode 100755 plugins/network/network-build-config delete mode 100755 plugins/network/network-compute-ports delete mode 100755 plugins/network/network-get-ipaddr delete mode 100755 plugins/network/network-get-listeners delete mode 100755 plugins/network/network-get-port delete mode 100755 plugins/network/network-write-ipaddr delete mode 100755 plugins/network/network-write-port create mode 100644 plugins/network/network.go create mode 100644 plugins/network/src/commands/commands.go create mode 100644 plugins/network/src/glide.lock create mode 100644 plugins/network/src/glide.yaml create mode 100644 plugins/network/src/subcommands/rebuild/rebuild.go create mode 100644 plugins/network/src/subcommands/rebuildall/rebuildall.go create mode 100644 plugins/network/src/triggers/network-build-config/network-build-config.go create mode 100644 plugins/network/src/triggers/network-compute-ports/network-compute-ports.go create mode 100644 plugins/network/src/triggers/network-get-ipaddr/network-get-ipaddr.go create mode 100644 plugins/network/src/triggers/network-get-listeners/network-get-listeners.go create mode 100644 plugins/network/src/triggers/network-get-port/network-get-port.go create mode 100644 plugins/network/src/triggers/network-write-ipaddr/network-write-ipaddr.go create mode 100644 plugins/network/src/triggers/network-write-port/network-write-port.go create mode 100644 plugins/network/src/vendor/github.com/ryanuber/columnize/.travis.yml create mode 100644 plugins/network/src/vendor/github.com/ryanuber/columnize/LICENSE create mode 100644 plugins/network/src/vendor/github.com/ryanuber/columnize/README.md create mode 100644 plugins/network/src/vendor/github.com/ryanuber/columnize/columnize.go create mode 100644 plugins/network/src/vendor/github.com/ryanuber/columnize/columnize_test.go delete mode 100755 plugins/network/subcommands/default delete mode 100755 plugins/network/subcommands/rebuild delete mode 100755 plugins/network/subcommands/rebuildall create mode 100644 plugins/network/vendor/github.com/codegangsta/inject/.gitignore create mode 100644 plugins/network/vendor/github.com/codegangsta/inject/LICENSE create mode 100644 plugins/network/vendor/github.com/codegangsta/inject/README.md create mode 100644 plugins/network/vendor/github.com/codegangsta/inject/inject.go create mode 100644 plugins/network/vendor/github.com/codegangsta/inject/inject_test.go create mode 100644 plugins/network/vendor/github.com/codegangsta/inject/translations/README_zh_cn.md create mode 100644 plugins/network/vendor/github.com/codegangsta/inject/update_readme.sh create mode 100644 plugins/network/vendor/github.com/codeskyblue/go-sh/LICENSE create mode 100644 plugins/network/vendor/github.com/codeskyblue/go-sh/OLD_README.md create mode 100644 plugins/network/vendor/github.com/codeskyblue/go-sh/README.md create mode 100644 plugins/network/vendor/github.com/codeskyblue/go-sh/example/example1.go create mode 100644 plugins/network/vendor/github.com/codeskyblue/go-sh/example/less/less.go create mode 100644 plugins/network/vendor/github.com/codeskyblue/go-sh/example/tail/tailf.go create mode 100644 plugins/network/vendor/github.com/codeskyblue/go-sh/example/timeout/timeout.go create mode 100644 plugins/network/vendor/github.com/codeskyblue/go-sh/example_test.go create mode 100644 plugins/network/vendor/github.com/codeskyblue/go-sh/pipe.go create mode 100644 plugins/network/vendor/github.com/codeskyblue/go-sh/pipe_test.go create mode 100644 plugins/network/vendor/github.com/codeskyblue/go-sh/sh.go create mode 100644 plugins/network/vendor/github.com/codeskyblue/go-sh/sh_test.go create mode 100644 plugins/network/vendor/github.com/codeskyblue/go-sh/test.go create mode 100644 plugins/network/vendor/github.com/codeskyblue/go-sh/test_test.go create mode 100755 plugins/network/vendor/github.com/codeskyblue/go-sh/testdata/executable create mode 100644 plugins/network/vendor/github.com/codeskyblue/go-sh/testdata/hello.txt create mode 100644 plugins/network/vendor/github.com/codeskyblue/go-sh/testdata/linkfile create mode 100644 plugins/network/vendor/github.com/codeskyblue/go-sh/wercker.yml create mode 100644 plugins/network/vendor/github.com/ryanuber/columnize/.travis.yml create mode 100644 plugins/network/vendor/github.com/ryanuber/columnize/LICENSE create mode 100644 plugins/network/vendor/github.com/ryanuber/columnize/README.md create mode 100644 plugins/network/vendor/github.com/ryanuber/columnize/columnize.go create mode 100644 plugins/network/vendor/github.com/ryanuber/columnize/columnize_test.go create mode 100644 plugins/proxy/proxy.go diff --git a/.editorconfig b/.editorconfig index 0643f4310..2c518168b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -17,5 +17,5 @@ indent_size = 4 [*.go] insert_final_newline = true -indent_style = space +indent_style = tab indent_size = 4 diff --git a/plugins/common/common.go b/plugins/common/common.go index 31060a760..8bc3309d5 100644 --- a/plugins/common/common.go +++ b/plugins/common/common.go @@ -1,7 +1,10 @@ package common import ( + "bufio" + "errors" "fmt" + "io/ioutil" "os" "os/exec" "regexp" @@ -50,12 +53,28 @@ func (sc *ShellCmd) Execute() bool { return true } +// Output is a lightweight wrapper around exec.Command.Output() +func (sc *ShellCmd) Output() ([]byte, error) { + env := os.Environ() + for k, v := range sc.Env { + env = append(env, fmt.Sprintf("%s=%s", k, v)) + } + sc.Command.Env = env + if sc.ShowOutput { + sc.Command.Stdout = os.Stdout + sc.Command.Stderr = os.Stderr + } + return sc.Command.Output() +} + // VerifyAppName verifies app name format and app existence" func VerifyAppName(appName string) (err error) { + if appName == "" { + return fmt.Errorf("App name must not be null") + } dokkuRoot := MustGetEnv("DOKKU_ROOT") appRoot := strings.Join([]string{dokkuRoot, appName}, "/") - _, err = os.Stat(appRoot) - if os.IsNotExist(err) { + if !DirectoryExists(appRoot) { return fmt.Errorf("App %s does not exist: %v\n", appName, err) } r, _ := regexp.Compile("^[a-z].*") @@ -134,8 +153,213 @@ func GetAppImageRepo(appName string) string { func VerifyImage(image string) bool { imageCmd := NewShellCmd(strings.Join([]string{"docker inspect", image}, " ")) imageCmd.ShowOutput = false - if imageCmd.Execute() { - return true + return imageCmd.Execute() +} + +// ContainerIsRunning checks to see if a container is running +func ContainerIsRunning(containerId string) bool { + b, err := sh.Command("docker", "inspect", "--format", "'{{.State.Running}}'", containerId).Output() + if err != nil { + return false } + return string(b[:]) == "true" +} + +// DirectoryExists returns if a path exists and is a directory +func DirectoryExists(filePath string) bool { + fi, err := os.Stat(filePath) + if err != nil { + return false + } + + return fi.IsDir() +} + +// DokkuApps returns a list of all local apps +func DokkuApps() ([]string, error) { + var apps []string + + dokkuRoot := MustGetEnv("DOKKU_ROOT") + files, err := ioutil.ReadDir(dokkuRoot) + if err != nil { + return apps, errors.New("You haven't deployed any applications yet") + } + + for _, f := range files { + appRoot := strings.Join([]string{dokkuRoot, f.Name()}, "/") + if !DirectoryExists(appRoot) { + continue + } + if f.Name() == "tls" || strings.HasPrefix(f.Name(), ".") { + continue + } + apps = append(apps, f.Name()) + } + + if len(apps) == 0 { + return apps, errors.New("You haven't deployed any applications yet") + } + + return apps, nil +} + +// FileToSlice reads in all the lines from a file into a string slice +func FileToSlice(filePath string) ([]string, error) { + var lines []string + f, err := os.Open(filePath) + if err != nil { + return lines, err + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + text := strings.TrimSpace(scanner.Text()) + if text == "" { + continue + } + lines = append(lines, text) + } + err = scanner.Err() + return lines, err +} + +// FileExists returns if a path exists and is a file +func FileExists(filePath string) bool { + fi, err := os.Stat(filePath) + if err != nil { + return false + } + + return fi.Mode().IsRegular() +} + +// GetAppImageName returnS image identifier for a given app, tag tuple. validate if tag is presented +func GetAppImageName(appName, imageTag, imageRepo string) (imageName string) { + err := VerifyAppName(appName) + if err != nil { + LogFail(err.Error()) + } + + if imageRepo == "" { + imageRepo = GetAppImageRepo(appName) + } + + if imageTag == "" { + imageName = fmt.Sprintf("%v:latest", imageRepo) + } else { + imageName = fmt.Sprintf("%v:%v", imageRepo, imageTag) + if !VerifyImage(imageName) { + LogFail(fmt.Sprintf("app image (%s) not found", imageName)) + } + } + return +} + +// return true if given app has a running container +func IsDeployed(appName string) bool { + dokkuRoot := MustGetEnv("DOKKU_ROOT") + appRoot := strings.Join([]string{dokkuRoot, appName}, "/") + files, err := ioutil.ReadDir(appRoot) + if err != nil { + return false + } + + for _, f := range files { + if f.Name() == "CONTAINER" || strings.HasPrefix(f.Name(), "CONTAINER.") { + return true + } + } + return false } + +// IsImageHerokuishBased returns true if app image is based on herokuish +func IsImageHerokuishBased(image string) bool { + // circleci can't support --rm as they run lxc in lxc + dockerArgs := "" + if !FileExists("/home/ubuntu/.circlerc") { + dockerArgs = "--rm" + } + + dockerGlobalArgs := os.Getenv("DOKKU_GLOBAL_RUN_ARGS") + parts := []string{"docker", "run", dockerGlobalArgs, "--entrypoint=\"/bin/sh\"", dockerArgs, image, "-c", "\"test -f /exec\""} + + var dockerCmdParts []string + for _, str := range parts { + if str != "" { + dockerCmdParts = append(dockerCmdParts, str) + } + } + + dockerCmd := NewShellCmd(strings.Join(dockerCmdParts, " ")) + dockerCmd.ShowOutput = false + return dockerCmd.Execute() +} + +// LogInfo1 is the info1 header formatter +func LogInfo1(text string) { + fmt.Fprintln(os.Stdout, fmt.Sprintf("-----> %s", text)) +} + +// LogInfo2Quiet is the info1 header formatter (with quiet option) +func LogInfo1Quiet(text string) { + if os.Getenv("DOKKU_QUIET_OUTPUT") != "" { + LogInfo1(text) + } +} + +// LogInfo2 is the info2 header formatter +func LogInfo2(text string) { + fmt.Fprintln(os.Stdout, fmt.Sprintf("=====> %s", text)) +} + +// LogInfo2Quiet is the info2 header formatter (with quiet option) +func LogInfo2Quiet(text string) { + if os.Getenv("DOKKU_QUIET_OUTPUT") != "" { + LogInfo2(text) + } +} + +// LogWarn is the warning log formatter +func LogWarn(text string) { + fmt.Fprintln(os.Stderr, fmt.Sprintf(" ! %s", text)) +} + +// ReadFirstLine gets the first line of a file that has contents and returns it +// if there are no contents, an empty string is returned +// will also return an empty string if the file does not exist +func ReadFirstLine(filename string) string { + if !FileExists(filename) { + return "" + } + f, err := os.Open(filename) + if err != nil { + return "" + } + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + text := strings.TrimSpace(scanner.Text()) + if text == "" { + continue + } + return text + } + return "" + +} + +// StripInlineComments removes bash-style comment from input line +func StripInlineComments(text string) string { + var bytes = []byte(text) + re := regexp.MustCompile("(?s)#.*") + bytes = re.ReplaceAll(bytes, nil) + return strings.TrimSpace(string(bytes)) +} + +// ToBool returns a bool value for a given string +func ToBool(s string) bool { + return s == "true" +} diff --git a/plugins/config/config.go b/plugins/config/config.go new file mode 100644 index 000000000..821862113 --- /dev/null +++ b/plugins/config/config.go @@ -0,0 +1,30 @@ +package config + +import ( + "fmt" + "strings" + + common "github.com/dokku/dokku/plugins/common" +) + +func GetWithDefault(appName string, key string, defaultValue string) string { + envFile := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName, "ENV"}, "/") + lines, err := common.FileToSlice(envFile) + if err != nil { + return defaultValue + } + + value := defaultValue + prefix := fmt.Sprintf("export %v=", key) + for _, line := range lines { + if !strings.HasPrefix(line, prefix) { + continue + } + value = strings.TrimPrefix(line, prefix) + if strings.HasPrefix(line, "'") && strings.HasSuffix(line, "'") { + value = strings.TrimPrefix(strings.TrimSuffix(value, "'"), "'") + } + } + + return value +} diff --git a/plugins/network/.gitignore b/plugins/network/.gitignore new file mode 100644 index 000000000..9ff165fe8 --- /dev/null +++ b/plugins/network/.gitignore @@ -0,0 +1,3 @@ +/commands +/subcommands/* +/network-* diff --git a/plugins/network/Makefile b/plugins/network/Makefile new file mode 100644 index 000000000..0eab0fd09 --- /dev/null +++ b/plugins/network/Makefile @@ -0,0 +1,47 @@ +include ../../common.mk + +build-in-docker: clean + docker run --rm \ + -v $$PWD/../..:$(GO_REPO_ROOT) \ + -w $(GO_REPO_ROOT)/plugins/network \ + $(BUILD_IMAGE) \ + bash -c "make build" || exit $$? + +build: commands subcommands triggers +subcommands: subcommands/rebuild subcommands/rebuildall +triggers: network-build-config network-compute-ports network-get-ipaddr network-get-listeners network-get-port network-write-ipaddr network-write-port +commands: **/**/commands.go + go build -a -o commands src/commands/commands.go + +subcommands/rebuild: **/**/**/rebuild.go + go build -a -o subcommands/rebuild src/subcommands/rebuild/rebuild.go + +subcommands/rebuildall: **/**/**/rebuildall.go + go build -a -o subcommands/rebuildall src/subcommands/rebuildall/rebuildall.go + +network-build-config: **/**/**/network-build-config.go + go build -a -o network-build-config src/triggers/network-build-config/network-build-config.go + +network-compute-ports: **/**/**/network-compute-ports.go + go build -a -o network-compute-ports src/triggers/network-compute-ports/network-compute-ports.go + +network-get-ipaddr: **/**/**/network-get-ipaddr.go + go build -a -o network-get-ipaddr src/triggers/network-get-ipaddr/network-get-ipaddr.go + +network-get-listeners: **/**/**/network-get-listeners.go + go build -a -o network-get-listeners src/triggers/network-get-listeners/network-get-listeners.go + +network-get-port: **/**/**/network-get-port.go + go build -a -o network-get-port src/triggers/network-get-port/network-get-port.go + +network-write-ipaddr: **/**/**/network-write-ipaddr.go + go build -a -o network-write-ipaddr src/triggers/network-write-ipaddr/network-write-ipaddr.go + +network-write-port: **/**/**/network-write-port.go + go build -a -o network-write-port src/triggers/network-write-port/network-write-port.go + +clean: + rm -rf commands subcommands network-* + +src-clean: + rm -rf .gitignore src vendor Makefile diff --git a/plugins/network/commands b/plugins/network/commands deleted file mode 100755 index 099603de0..000000000 --- a/plugins/network/commands +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -[[ " help network:help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT" -source "$PLUGIN_AVAILABLE_PATH/network/internal-functions" -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x - -case "$1" in - help | network:help) - network_help_cmd "$@" - ;; - - *) - exit "$DOKKU_NOT_IMPLEMENTED_EXIT" - ;; - -esac diff --git a/plugins/network/functions b/plugins/network/functions deleted file mode 100755 index a720b9d5d..000000000 --- a/plugins/network/functions +++ /dev/null @@ -1,147 +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" - -network-build-config() { - declare desc="builds network config files" - declare APP="$1"; verify_app_name "$APP" - local CONTAINER_INDEX DOKKU_SCALE_FILE IMAGE IS_HEROKUISH_CONTAINER PROC_COUNT PROC_LINE PROC_TYPE - DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" - - if [[ ! -f "$DOKKU_SCALE_FILE" ]]; then - return - fi - - IS_HEROKUISH_CONTAINER=false - IMAGE=$(get_app_image_name "$APP") - is_image_herokuish_based "$IMAGE" && IS_HEROKUISH_CONTAINER=true - - dokku_log_info1 "Ensuring network configuration is in sync for ${APP}" - while read -r PROC_LINE || [[ -n "$PROC_LINE" ]]; do - [[ "$PROC_LINE" =~ ^#.* ]] && continue - PROC_LINE="$(strip_inline_comments "$PROC_LINE")" - PROC_TYPE=${PROC_LINE%%=*} - PROC_COUNT=${PROC_LINE#*=} - CONTAINER_INDEX=1 - while [[ $CONTAINER_INDEX -le $PROC_COUNT ]]; do - local CONTAINER_ID="" p="" port="" ipaddr="" CONTAINER_STATUS - local DOKKU_CONTAINER_ID_FILE="$DOKKU_ROOT/$APP/CONTAINER.$PROC_TYPE.$CONTAINER_INDEX" - local DOKKU_IP_FILE="$DOKKU_ROOT/$APP/IP.$PROC_TYPE.$CONTAINER_INDEX" - local DOKKU_PORT_FILE="$DOKKU_ROOT/$APP/PORT.$PROC_TYPE.$CONTAINER_INDEX" - local CONTAINER_INDEX=$(( CONTAINER_INDEX + 1 )) - - [[ -f "$DOKKU_CONTAINER_ID_FILE" ]] || continue - - CONTAINER_ID="$(cat "$DOKKU_CONTAINER_ID_FILE")" - [[ -n "$CONTAINER_ID" ]] || continue - CONTAINER_RUNNING="$(docker inspect -f '{{.State.Running}}' "$CONTAINER_ID" 2>/dev/null || true)" - [[ "$CONTAINER_RUNNING" == "true" ]] || continue - - ipaddr=$(network-get-container-ipaddr "$APP" "$PROC_TYPE" "$IS_HEROKUISH_CONTAINER" "$CONTAINER_ID") - port=$(network-get-container-port "$APP" "$PROC_TYPE" "$IS_HEROKUISH_CONTAINER" "$CONTAINER_ID") - - [[ -n "$ipaddr" ]] && echo "$ipaddr" > "$DOKKU_IP_FILE" - [[ -n "$port" ]] && echo "$port" > "$DOKKU_PORT_FILE" - done - done < "$DOKKU_SCALE_FILE" -} - -network-compute-container-ports() { - declare desc="Computes the ports for a given app container" - declare APP="$1" PROC_TYPE="$2" IS_HEROKUISH_CONTAINER="$3" - local DOKKU_DOCKERFILE_PORTS PORTS - - if [[ "$PROC_TYPE" != "web" ]]; then - return - fi - - if [[ "$IS_HEROKUISH_CONTAINER" == "false" ]]; then - DOKKU_DOCKERFILE_PORTS=($(config_get "$APP" DOKKU_DOCKERFILE_PORTS || true)) - fi - - if [[ -z "${DOKKU_DOCKERFILE_PORTS[*]}" ]]; then - local PORTS=5000 - else - local p - for p in ${DOKKU_DOCKERFILE_PORTS[*]};do - if [[ ! "$p" =~ .*udp.* ]]; then - # set port to first non-udp port - p=${p//\/tcp} - fi - PORTS+="$p " - done - fi - - echo "$PORTS" -} - -network-get-container-ipaddr() { - declare desc="Return the ipaddr for a given app container" - declare APP="$1" PROC_TYPE="$2" IS_HEROKUISH_CONTAINER="$3" CONTAINER_ID="$4" - local DOKKU_IS_APP_PROXY_ENABLED - local IP_ADDRESS=127.0.0.1 - - if [[ "$PROC_TYPE" != "web" ]]; then - return - fi - - local DOKKU_IS_APP_PROXY_ENABLED="$(is_app_proxy_enabled "$APP")" - if [[ "$DOKKU_IS_APP_PROXY_ENABLED" == "true" ]]; then - IP_ADDRESS="$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$CONTAINER_ID")" - # Docker < 1.9 compatibility - if [[ -z "$IP_ADDRESS" ]]; then - IP_ADDRESS=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' "$CONTAINER_ID") - fi - fi - - echo "$IP_ADDRESS" -} - -network-get-container-listeners() { - declare desc="Return the listeners (host:port combinations) for a given app container" - declare APP="$1" - - local IP_FILE LISTENER_IP LISTENER_PORT LISTENERS PORT_FILE - for IP_FILE in $DOKKU_ROOT/$APP/IP.web.*; do - local PORT_FILE="${IP_FILE//IP/PORT}" - local LISTENER_IP=$(< "$IP_FILE") - local LISTENER_PORT=$(< "$PORT_FILE") - local LISTENERS+="$LISTENER_IP:$LISTENER_PORT " - done - - echo "$LISTENERS" -} - -network-get-container-port() { - declare desc="Return the port for a given app container" - declare APP="$1" PROC_TYPE="$2" IS_HEROKUISH_CONTAINER="$3" CONTAINER_ID="$4" - local DOKKU_IS_APP_PROXY_ENABLED - local PORT=5000 - - if [[ "$PROC_TYPE" != "web" ]]; then - return - fi - - if [[ "$IS_HEROKUISH_CONTAINER" == "false" ]]; then - local DOKKU_DOCKERFILE_PORTS=($(config_get "$APP" DOKKU_DOCKERFILE_PORTS || true)) - fi - - if [[ -n "${DOKKU_DOCKERFILE_PORTS[*]}" ]]; then - for p in ${DOKKU_DOCKERFILE_PORTS[*]};do - if [[ ! "$p" =~ .*udp.* ]]; then - # set port to first non-udp port - p=${p//\/tcp} - PORT=${port:="$p"} - fi - done - fi - - DOKKU_IS_APP_PROXY_ENABLED="$(is_app_proxy_enabled "$APP")" - if [[ "$DOKKU_IS_APP_PROXY_ENABLED" != "true" ]]; then - PORT="$(docker port "$CONTAINER_ID" "$PORT" | sed 's/[0-9.]*://')" - fi - - echo "$PORT" -} diff --git a/plugins/network/glide.yaml b/plugins/network/glide.yaml new file mode 100644 index 000000000..a720d6c11 --- /dev/null +++ b/plugins/network/glide.yaml @@ -0,0 +1,4 @@ +package: . +import: +- package: github.com/codeskyblue/go-sh +- package: github.com/ryanuber/columnize diff --git a/plugins/network/internal-functions b/plugins/network/internal-functions deleted file mode 100755 index 31d4adf57..000000000 --- a/plugins/network/internal-functions +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x - -network_help_content_func() { - declare desc="return network plugin help content" - cat<, Rebuilds network settings for an app - network:rebuildall, Rebuild network settings for all apps -help_content -} - -network_help_cmd() { - if [[ $1 = "network:help" ]] ; then - echo -e 'Usage: dokku network[:COMMAND]' - echo '' - echo 'Rebuilds network settings for an app.' - echo '' - echo 'Additional commands:' - network_help_content_func | sort | column -c2 -t -s, - elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then - network_help_content_func - else - cat< "$DOKKU_IP_FILE" -} - -network-write-ipaddr "$@" diff --git a/plugins/network/network-write-port b/plugins/network/network-write-port deleted file mode 100755 index f43a1b784..000000000 --- a/plugins/network/network-write-port +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x - -network-write-port() { - declare desc="Writes the ipaddr to disk" - declare APP="$1" PROC_TYPE="$2" CONTAINER_INDEX="$3" PORT="$4" - local DOKKU_PORT_FILE="$DOKKU_ROOT/$APP/PORT.$PROC_TYPE.$CONTAINER_INDEX" - echo "$PORT" > "$DOKKU_PORT_FILE" -} - -network-write-port "$@" diff --git a/plugins/network/network.go b/plugins/network/network.go new file mode 100644 index 000000000..6ef28c3d0 --- /dev/null +++ b/plugins/network/network.go @@ -0,0 +1,165 @@ +package network + +import ( + "fmt" + "strconv" + "strings" + + common "github.com/dokku/dokku/plugins/common" + config "github.com/dokku/dokku/plugins/config" + proxy "github.com/dokku/dokku/plugins/proxy" + + sh "github.com/codeskyblue/go-sh" +) + +// return the ipaddr for a given app container +func GetContainerIpaddress(appName string, procType string, isHerokuishContainer bool, containerId string) string { + if procType != "web" { + return "" + } + + ipAddress := "127.0.0.1" + if !proxy.IsAppProxyEnabled(appName) { + return ipAddress + } + + imageCmd := common.NewShellCmd(strings.Join([]string{ + "docker", + "inspect", + "--format", + "'{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'", + containerId, + }, " ")) + + b, err := imageCmd.Output() + if err != nil || len(b) == 0 { + // docker < .19 compatibility + imageCmd = common.NewShellCmd(strings.Join([]string{ + "docker", + "inspect", + "--format", + "'{{ .NetworkSettings.IPAddress }}'", + containerId, + }, " ")) + b, err = imageCmd.Output() + } + + if err == nil { + return string(b[:]) + } + + return "" +} + +// return the port for a given app container +func GetContainerPort(appName string, procType string, isHerokuishContainer bool, containerId string) string { + if procType != "web" { + return "" + } + + dockerfilePorts := make([]string, 0) + port := "" + + if isHerokuishContainer { + dockerfilePorts = strings.Split(config.GetWithDefault(appName, "DOKKU_DOCKERFILE_PORTS", ""), " ") + } + + if len(dockerfilePorts) > 0 { + for _, p := range dockerfilePorts { + if strings.HasSuffix(p, "/udp") { + continue + } + port = strings.TrimSuffix(p, "/tcp") + if port != "" { + break + } + } + } else { + port = "5000" + } + + if !proxy.IsAppProxyEnabled(appName) { + portCmd := common.NewShellCmd(strings.Join([]string{ + "docker", + "port", + containerId, + port, + }, " ")) + b, err := portCmd.Output() + if err == nil { + port = strings.Split(string(b[:]), ":")[1] + } + } + + return port +} + +// builds network config files +func BuildConfig(appName string) { + err := common.VerifyAppName(appName) + if err != nil { + common.LogFail(err.Error()) + } + + if !common.IsDeployed(appName) { + return + } + + appRoot := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName}, "/") + scaleFile := strings.Join([]string{appRoot, "DOKKU_SCALE"}, "/") + if !common.FileExists(scaleFile) { + return + } + + image := common.GetAppImageName(appName, "", "") + isHerokuishContainer := common.IsImageHerokuishBased(image) + + common.LogInfo1(fmt.Sprintf("Ensuring network configuration is in sync for %s", appName)) + lines, err := common.FileToSlice(scaleFile) + if err != nil { + return + } + for _, line := range lines { + if line == "" || strings.HasPrefix(line, "#") { + continue + } + + procParts := strings.SplitAfterN(line, ":", 2) + if len(procParts) != 2 { + continue + } + procType := procParts[0] + procCount, err := strconv.Atoi(procParts[1]) + if err != nil { + continue + } + + containerIndex := 0 + for containerIndex <= procCount { + containerIndex += 1 + containerIdFile := fmt.Sprintf("%v/CONTAINER.%v.%v", appRoot, procType, containerIndex) + + containerId := common.ReadFirstLine(containerIdFile) + if containerId == "" || !common.ContainerIsRunning(containerId) { + continue + } + + ipAddress := GetContainerIpaddress(appName, procType, isHerokuishContainer, containerId) + port := GetContainerPort(appName, procType, isHerokuishContainer, containerId) + + if ipAddress != "" { + _, err := sh.Command("plugn", "trigger", "network-write-ipaddr", appName, procType, containerIndex, ipAddress).Output() + if err != nil { + common.LogWarn(err.Error()) + } + } + + if port != "" { + _, err := sh.Command("plugn", "trigger", "network-write-port", appName, procType, containerIndex, port).Output() + if err != nil { + common.LogWarn(err.Error()) + } + } + } + } +} diff --git a/plugins/network/src/commands/commands.go b/plugins/network/src/commands/commands.go new file mode 100644 index 000000000..48a7a91c8 --- /dev/null +++ b/plugins/network/src/commands/commands.go @@ -0,0 +1,54 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strconv" + "strings" + + columnize "github.com/ryanuber/columnize" +) + +const ( + helpHeader = `Usage: dokku network[:COMMAND] + +Manages network settings for an app + +Additional commands:` + + helpContent = ` + network:rebuild , Rebuilds network settings for an app + network:rebuildall, Rebuild network settings for all apps +` +) + +func main() { + flag.Usage = usage + flag.Parse() + + cmd := flag.Arg(0) + switch cmd { + case "network", "network:help": + usage() + case "help": + fmt.Print(helpContent) + 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 = "\t" + config.Empty = "" + content := strings.Split(helpContent, "\n")[1:] + fmt.Println(helpHeader) + fmt.Println(columnize.Format(content, config)) +} diff --git a/plugins/network/src/glide.lock b/plugins/network/src/glide.lock new file mode 100644 index 000000000..fe4a98f07 --- /dev/null +++ b/plugins/network/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/network/src/glide.yaml b/plugins/network/src/glide.yaml new file mode 100644 index 000000000..3f7a96657 --- /dev/null +++ b/plugins/network/src/glide.yaml @@ -0,0 +1,3 @@ +package: . +import: +- package: github.com/ryanuber/columnize diff --git a/plugins/network/src/subcommands/rebuild/rebuild.go b/plugins/network/src/subcommands/rebuild/rebuild.go new file mode 100644 index 000000000..b608e2190 --- /dev/null +++ b/plugins/network/src/subcommands/rebuild/rebuild.go @@ -0,0 +1,15 @@ +package main + +import ( + "flag" + + network "github.com/dokku/dokku/plugins/network" +) + +// rebuilds network settings for an app +func main() { + flag.Parse() + appName := flag.Arg(1) + + network.BuildConfig(appName) +} diff --git a/plugins/network/src/subcommands/rebuildall/rebuildall.go b/plugins/network/src/subcommands/rebuildall/rebuildall.go new file mode 100644 index 000000000..c55b50f73 --- /dev/null +++ b/plugins/network/src/subcommands/rebuildall/rebuildall.go @@ -0,0 +1,17 @@ +package main + +import ( + common "github.com/dokku/dokku/plugins/common" + network "github.com/dokku/dokku/plugins/network" +) + +// rebuilds network settings for all apps +func main() { + apps, err := common.DokkuApps() + if err != nil { + common.LogFail(err.Error()) + } + for _, appName := range apps { + network.BuildConfig(appName) + } +} diff --git a/plugins/network/src/triggers/network-build-config/network-build-config.go b/plugins/network/src/triggers/network-build-config/network-build-config.go new file mode 100644 index 000000000..b608e2190 --- /dev/null +++ b/plugins/network/src/triggers/network-build-config/network-build-config.go @@ -0,0 +1,15 @@ +package main + +import ( + "flag" + + network "github.com/dokku/dokku/plugins/network" +) + +// rebuilds network settings for an app +func main() { + flag.Parse() + appName := flag.Arg(1) + + network.BuildConfig(appName) +} diff --git a/plugins/network/src/triggers/network-compute-ports/network-compute-ports.go b/plugins/network/src/triggers/network-compute-ports/network-compute-ports.go new file mode 100644 index 000000000..845d5daf3 --- /dev/null +++ b/plugins/network/src/triggers/network-compute-ports/network-compute-ports.go @@ -0,0 +1,42 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + common "github.com/dokku/dokku/plugins/common" + config "github.com/dokku/dokku/plugins/config" +) + +// computes the ports for a given app container +func main() { + flag.Parse() + appName := flag.Arg(1) + procType := flag.Arg(2) + isHerokuishContainer := common.ToBool(flag.Arg(3)) + + if procType != "web" { + return + } + + var dockerfilePorts []string + if !isHerokuishContainer { + dockerfilePorts = strings.Split(config.GetWithDefault(appName, "DOKKU_DOCKERFILE_PORTS", ""), " ") + } + + var ports []string + if len(dockerfilePorts) == 0 { + ports = append(ports, "5000") + } else { + for _, port := range dockerfilePorts { + port = strings.TrimSuffix(strings.TrimSpace(port), "/tcp") + if port == "" || strings.HasSuffix(port, "/udp") { + continue + } + ports = append(ports, port) + } + } + fmt.Fprint(os.Stdout, strings.Join(ports, " ")) +} diff --git a/plugins/network/src/triggers/network-get-ipaddr/network-get-ipaddr.go b/plugins/network/src/triggers/network-get-ipaddr/network-get-ipaddr.go new file mode 100644 index 000000000..ee8b97665 --- /dev/null +++ b/plugins/network/src/triggers/network-get-ipaddr/network-get-ipaddr.go @@ -0,0 +1,22 @@ +package main + +import ( + "flag" + "fmt" + "os" + + common "github.com/dokku/dokku/plugins/common" + network "github.com/dokku/dokku/plugins/network" +) + +// write the ipaddress to stdout for a given app container +func main() { + flag.Parse() + appName := flag.Arg(1) + procType := flag.Arg(2) + isHerokuishContainer := common.ToBool(flag.Arg(3)) + containerId := flag.Arg(4) + + ipAddress := network.GetContainerIpaddress(appName, procType, isHerokuishContainer, containerId) + fmt.Fprintln(os.Stdout, ipAddress) +} diff --git a/plugins/network/src/triggers/network-get-listeners/network-get-listeners.go b/plugins/network/src/triggers/network-get-listeners/network-get-listeners.go new file mode 100644 index 000000000..f7155c720 --- /dev/null +++ b/plugins/network/src/triggers/network-get-listeners/network-get-listeners.go @@ -0,0 +1,32 @@ +package main + +import ( + "path/filepath" + "flag" + "fmt" + "os" + "strings" + + common "github.com/dokku/dokku/plugins/common" +) + +// returns the listeners (host:port combinations) for a given app container +func main() { + flag.Parse() + appName := flag.Arg(1) + + dokkuRoot := common.MustGetEnv("DOKKU_ROOT") + appRoot := strings.Join([]string{dokkuRoot, appName}, "/") + + files, _ := filepath.Glob(appRoot + "/IP.web.*") + + var listeners []string + for _, ipfile := range files { + portfile := strings.Replace(ipfile, "/IP.web.", "/PORT.web.", 1) + ipAddress := common.ReadFirstLine(ipfile) + port := common.ReadFirstLine(portfile) + listeners = append(listeners, fmt.Sprintf("%s:%s", ipAddress, port)) + } + + fmt.Fprint(os.Stdout, strings.Join(listeners, " ")) +} diff --git a/plugins/network/src/triggers/network-get-port/network-get-port.go b/plugins/network/src/triggers/network-get-port/network-get-port.go new file mode 100644 index 000000000..5ec64ef52 --- /dev/null +++ b/plugins/network/src/triggers/network-get-port/network-get-port.go @@ -0,0 +1,22 @@ +package main + +import ( + "flag" + "fmt" + "os" + + common "github.com/dokku/dokku/plugins/common" + network "github.com/dokku/dokku/plugins/network" +) + +// write the port to stdout for a given app container +func main() { + flag.Parse() + appName := flag.Arg(1) + procType := flag.Arg(2) + isHerokuishContainer := common.ToBool(flag.Arg(3)) + containerId := flag.Arg(4) + + port := network.GetContainerPort(appName, procType, isHerokuishContainer, containerId) + fmt.Fprintln(os.Stdout, port) +} diff --git a/plugins/network/src/triggers/network-write-ipaddr/network-write-ipaddr.go b/plugins/network/src/triggers/network-write-ipaddr/network-write-ipaddr.go new file mode 100644 index 000000000..9ba30e635 --- /dev/null +++ b/plugins/network/src/triggers/network-write-ipaddr/network-write-ipaddr.go @@ -0,0 +1,40 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + common "github.com/dokku/dokku/plugins/common" +) + +// writes the ip to disk +func main() { + flag.Parse() + appName := flag.Arg(1) + procType := flag.Arg(2) + containerIndex := flag.Arg(3) + ip := flag.Arg(4) + + if appName == "" { + common.LogFail("Please specify an app to run the command on") + } + err := common.VerifyAppName(appName) + if err != nil { + common.LogFail(err.Error()) + } + + appRoot := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName}, "/") + filename := fmt.Sprintf("%v/IP.%v.%v", appRoot, procType, containerIndex) + f, err := os.Create(filename) + if err != nil { + common.LogFail(err.Error()) + } + + ipBytes := []byte(ip) + _, err = f.Write(ipBytes) + if err != nil { + common.LogFail(err.Error()) + } +} diff --git a/plugins/network/src/triggers/network-write-port/network-write-port.go b/plugins/network/src/triggers/network-write-port/network-write-port.go new file mode 100644 index 000000000..df79e809b --- /dev/null +++ b/plugins/network/src/triggers/network-write-port/network-write-port.go @@ -0,0 +1,40 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + common "github.com/dokku/dokku/plugins/common" +) + +// writes the port to disk +func main() { + flag.Parse() + appName := flag.Arg(1) + procType := flag.Arg(2) + containerIndex := flag.Arg(3) + port := flag.Arg(4) + + if appName == "" { + common.LogFail("Please specify an app to run the command on") + } + err := common.VerifyAppName(appName) + if err != nil { + common.LogFail(err.Error()) + } + + appRoot := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName}, "/") + filename := fmt.Sprintf("%v/PORT.%v.%v", appRoot, procType, containerIndex) + f, err := os.Create(filename) + if err != nil { + common.LogFail(err.Error()) + } + + portBytes := []byte(port) + _, err = f.Write(portBytes) + if err != nil { + common.LogFail(err.Error()) + } +} diff --git a/plugins/network/src/vendor/github.com/ryanuber/columnize/.travis.yml b/plugins/network/src/vendor/github.com/ryanuber/columnize/.travis.yml new file mode 100644 index 000000000..1a0bbea6c --- /dev/null +++ b/plugins/network/src/vendor/github.com/ryanuber/columnize/.travis.yml @@ -0,0 +1,3 @@ +language: go +go: + - tip diff --git a/plugins/network/src/vendor/github.com/ryanuber/columnize/LICENSE b/plugins/network/src/vendor/github.com/ryanuber/columnize/LICENSE new file mode 100644 index 000000000..b9c0e2b68 --- /dev/null +++ b/plugins/network/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/network/src/vendor/github.com/ryanuber/columnize/README.md b/plugins/network/src/vendor/github.com/ryanuber/columnize/README.md new file mode 100644 index 000000000..e47634fc6 --- /dev/null +++ b/plugins/network/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/network/src/vendor/github.com/ryanuber/columnize/columnize.go b/plugins/network/src/vendor/github.com/ryanuber/columnize/columnize.go new file mode 100644 index 000000000..915716a10 --- /dev/null +++ b/plugins/network/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/network/src/vendor/github.com/ryanuber/columnize/columnize_test.go b/plugins/network/src/vendor/github.com/ryanuber/columnize/columnize_test.go new file mode 100644 index 000000000..89dabaa38 --- /dev/null +++ b/plugins/network/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/network/subcommands/default b/plugins/network/subcommands/default deleted file mode 100755 index 352d511a7..000000000 --- a/plugins/network/subcommands/default +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_AVAILABLE_PATH/network/internal-functions" - -network_help_cmd "network:help" diff --git a/plugins/network/subcommands/rebuild b/plugins/network/subcommands/rebuild deleted file mode 100755 index b4a173e1c..000000000 --- a/plugins/network/subcommands/rebuild +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_AVAILABLE_PATH/network/functions" - -network_rebuild_cmd() { - declare desc="rebuilds network settings for an app" - local cmd="network:rebuild" - local APP="$2" - - network-build-config "$APP" -} - -network_rebuild_cmd "$@" diff --git a/plugins/network/subcommands/rebuildall b/plugins/network/subcommands/rebuildall deleted file mode 100755 index b3f77232f..000000000 --- a/plugins/network/subcommands/rebuildall +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_AVAILABLE_PATH/network/functions" - -network_rebuildall_cmd() { - declare desc="rebuilds network settings for all apps" - local cmd="network:rebuildall" - for app in $(dokku_apps); do - (is_deployed "$app") && network-build-config "$app" - done -} - -network_rebuildall_cmd "$@" diff --git a/plugins/network/vendor/github.com/codegangsta/inject/.gitignore b/plugins/network/vendor/github.com/codegangsta/inject/.gitignore new file mode 100644 index 000000000..df3df8a90 --- /dev/null +++ b/plugins/network/vendor/github.com/codegangsta/inject/.gitignore @@ -0,0 +1,2 @@ +inject +inject.test diff --git a/plugins/network/vendor/github.com/codegangsta/inject/LICENSE b/plugins/network/vendor/github.com/codegangsta/inject/LICENSE new file mode 100644 index 000000000..eb68a0e05 --- /dev/null +++ b/plugins/network/vendor/github.com/codegangsta/inject/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Jeremy Saenz + +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/network/vendor/github.com/codegangsta/inject/README.md b/plugins/network/vendor/github.com/codegangsta/inject/README.md new file mode 100644 index 000000000..679abe01a --- /dev/null +++ b/plugins/network/vendor/github.com/codegangsta/inject/README.md @@ -0,0 +1,92 @@ +# inject +-- + import "github.com/codegangsta/inject" + +Package inject provides utilities for mapping and injecting dependencies in +various ways. + +Language Translations: +* [简体中文](translations/README_zh_cn.md) + +## Usage + +#### func InterfaceOf + +```go +func InterfaceOf(value interface{}) reflect.Type +``` +InterfaceOf dereferences a pointer to an Interface type. It panics if value is +not an pointer to an interface. + +#### type Applicator + +```go +type Applicator interface { + // Maps dependencies in the Type map to each field in the struct + // that is tagged with 'inject'. Returns an error if the injection + // fails. + Apply(interface{}) error +} +``` + +Applicator represents an interface for mapping dependencies to a struct. + +#### type Injector + +```go +type Injector interface { + Applicator + Invoker + TypeMapper + // SetParent sets the parent of the injector. If the injector cannot find a + // dependency in its Type map it will check its parent before returning an + // error. + SetParent(Injector) +} +``` + +Injector represents an interface for mapping and injecting dependencies into +structs and function arguments. + +#### func New + +```go +func New() Injector +``` +New returns a new Injector. + +#### type Invoker + +```go +type Invoker interface { + // Invoke attempts to call the interface{} provided as a function, + // providing dependencies for function arguments based on Type. Returns + // a slice of reflect.Value representing the returned values of the function. + // Returns an error if the injection fails. + Invoke(interface{}) ([]reflect.Value, error) +} +``` + +Invoker represents an interface for calling functions via reflection. + +#### type TypeMapper + +```go +type TypeMapper interface { + // Maps the interface{} value based on its immediate type from reflect.TypeOf. + Map(interface{}) TypeMapper + // Maps the interface{} value based on the pointer of an Interface provided. + // This is really only useful for mapping a value as an interface, as interfaces + // cannot at this time be referenced directly without a pointer. + MapTo(interface{}, interface{}) TypeMapper + // Provides a possibility to directly insert a mapping based on type and value. + // This makes it possible to directly map type arguments not possible to instantiate + // with reflect like unidirectional channels. + Set(reflect.Type, reflect.Value) TypeMapper + // Returns the Value that is mapped to the current type. Returns a zeroed Value if + // the Type has not been mapped. + Get(reflect.Type) reflect.Value +} +``` + +TypeMapper represents an interface for mapping interface{} values based on type. diff --git a/plugins/network/vendor/github.com/codegangsta/inject/inject.go b/plugins/network/vendor/github.com/codegangsta/inject/inject.go new file mode 100644 index 000000000..3ff713c8a --- /dev/null +++ b/plugins/network/vendor/github.com/codegangsta/inject/inject.go @@ -0,0 +1,187 @@ +// Package inject provides utilities for mapping and injecting dependencies in various ways. +package inject + +import ( + "fmt" + "reflect" +) + +// Injector represents an interface for mapping and injecting dependencies into structs +// and function arguments. +type Injector interface { + Applicator + Invoker + TypeMapper + // SetParent sets the parent of the injector. If the injector cannot find a + // dependency in its Type map it will check its parent before returning an + // error. + SetParent(Injector) +} + +// Applicator represents an interface for mapping dependencies to a struct. +type Applicator interface { + // Maps dependencies in the Type map to each field in the struct + // that is tagged with 'inject'. Returns an error if the injection + // fails. + Apply(interface{}) error +} + +// Invoker represents an interface for calling functions via reflection. +type Invoker interface { + // Invoke attempts to call the interface{} provided as a function, + // providing dependencies for function arguments based on Type. Returns + // a slice of reflect.Value representing the returned values of the function. + // Returns an error if the injection fails. + Invoke(interface{}) ([]reflect.Value, error) +} + +// TypeMapper represents an interface for mapping interface{} values based on type. +type TypeMapper interface { + // Maps the interface{} value based on its immediate type from reflect.TypeOf. + Map(interface{}) TypeMapper + // Maps the interface{} value based on the pointer of an Interface provided. + // This is really only useful for mapping a value as an interface, as interfaces + // cannot at this time be referenced directly without a pointer. + MapTo(interface{}, interface{}) TypeMapper + // Provides a possibility to directly insert a mapping based on type and value. + // This makes it possible to directly map type arguments not possible to instantiate + // with reflect like unidirectional channels. + Set(reflect.Type, reflect.Value) TypeMapper + // Returns the Value that is mapped to the current type. Returns a zeroed Value if + // the Type has not been mapped. + Get(reflect.Type) reflect.Value +} + +type injector struct { + values map[reflect.Type]reflect.Value + parent Injector +} + +// InterfaceOf dereferences a pointer to an Interface type. +// It panics if value is not an pointer to an interface. +func InterfaceOf(value interface{}) reflect.Type { + t := reflect.TypeOf(value) + + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + + if t.Kind() != reflect.Interface { + panic("Called inject.InterfaceOf with a value that is not a pointer to an interface. (*MyInterface)(nil)") + } + + return t +} + +// New returns a new Injector. +func New() Injector { + return &injector{ + values: make(map[reflect.Type]reflect.Value), + } +} + +// Invoke attempts to call the interface{} provided as a function, +// providing dependencies for function arguments based on Type. +// Returns a slice of reflect.Value representing the returned values of the function. +// Returns an error if the injection fails. +// It panics if f is not a function +func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) { + t := reflect.TypeOf(f) + + var in = make([]reflect.Value, t.NumIn()) //Panic if t is not kind of Func + for i := 0; i < t.NumIn(); i++ { + argType := t.In(i) + val := inj.Get(argType) + if !val.IsValid() { + return nil, fmt.Errorf("Value not found for type %v", argType) + } + + in[i] = val + } + + return reflect.ValueOf(f).Call(in), nil +} + +// Maps dependencies in the Type map to each field in the struct +// that is tagged with 'inject'. +// Returns an error if the injection fails. +func (inj *injector) Apply(val interface{}) error { + v := reflect.ValueOf(val) + + for v.Kind() == reflect.Ptr { + v = v.Elem() + } + + if v.Kind() != reflect.Struct { + return nil // Should not panic here ? + } + + t := v.Type() + + for i := 0; i < v.NumField(); i++ { + f := v.Field(i) + structField := t.Field(i) + if f.CanSet() && (structField.Tag == "inject" || structField.Tag.Get("inject") != "") { + ft := f.Type() + v := inj.Get(ft) + if !v.IsValid() { + return fmt.Errorf("Value not found for type %v", ft) + } + + f.Set(v) + } + + } + + return nil +} + +// Maps the concrete value of val to its dynamic type using reflect.TypeOf, +// It returns the TypeMapper registered in. +func (i *injector) Map(val interface{}) TypeMapper { + i.values[reflect.TypeOf(val)] = reflect.ValueOf(val) + return i +} + +func (i *injector) MapTo(val interface{}, ifacePtr interface{}) TypeMapper { + i.values[InterfaceOf(ifacePtr)] = reflect.ValueOf(val) + return i +} + +// Maps the given reflect.Type to the given reflect.Value and returns +// the Typemapper the mapping has been registered in. +func (i *injector) Set(typ reflect.Type, val reflect.Value) TypeMapper { + i.values[typ] = val + return i +} + +func (i *injector) Get(t reflect.Type) reflect.Value { + val := i.values[t] + + if val.IsValid() { + return val + } + + // no concrete types found, try to find implementors + // if t is an interface + if t.Kind() == reflect.Interface { + for k, v := range i.values { + if k.Implements(t) { + val = v + break + } + } + } + + // Still no type found, try to look it up on the parent + if !val.IsValid() && i.parent != nil { + val = i.parent.Get(t) + } + + return val + +} + +func (i *injector) SetParent(parent Injector) { + i.parent = parent +} diff --git a/plugins/network/vendor/github.com/codegangsta/inject/inject_test.go b/plugins/network/vendor/github.com/codegangsta/inject/inject_test.go new file mode 100644 index 000000000..eb94471d3 --- /dev/null +++ b/plugins/network/vendor/github.com/codegangsta/inject/inject_test.go @@ -0,0 +1,159 @@ +package inject_test + +import ( + "fmt" + "github.com/codegangsta/inject" + "reflect" + "testing" +) + +type SpecialString interface { +} + +type TestStruct struct { + Dep1 string `inject:"t" json:"-"` + Dep2 SpecialString `inject` + Dep3 string +} + +type Greeter struct { + Name string +} + +func (g *Greeter) String() string { + return "Hello, My name is" + g.Name +} + +/* Test Helpers */ +func expect(t *testing.T, a interface{}, b interface{}) { + if a != b { + t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) + } +} + +func refute(t *testing.T, a interface{}, b interface{}) { + if a == b { + t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) + } +} + +func Test_InjectorInvoke(t *testing.T) { + injector := inject.New() + expect(t, injector == nil, false) + + dep := "some dependency" + injector.Map(dep) + dep2 := "another dep" + injector.MapTo(dep2, (*SpecialString)(nil)) + dep3 := make(chan *SpecialString) + dep4 := make(chan *SpecialString) + typRecv := reflect.ChanOf(reflect.RecvDir, reflect.TypeOf(dep3).Elem()) + typSend := reflect.ChanOf(reflect.SendDir, reflect.TypeOf(dep4).Elem()) + injector.Set(typRecv, reflect.ValueOf(dep3)) + injector.Set(typSend, reflect.ValueOf(dep4)) + + _, err := injector.Invoke(func(d1 string, d2 SpecialString, d3 <-chan *SpecialString, d4 chan<- *SpecialString) { + expect(t, d1, dep) + expect(t, d2, dep2) + expect(t, reflect.TypeOf(d3).Elem(), reflect.TypeOf(dep3).Elem()) + expect(t, reflect.TypeOf(d4).Elem(), reflect.TypeOf(dep4).Elem()) + expect(t, reflect.TypeOf(d3).ChanDir(), reflect.RecvDir) + expect(t, reflect.TypeOf(d4).ChanDir(), reflect.SendDir) + }) + + expect(t, err, nil) +} + +func Test_InjectorInvokeReturnValues(t *testing.T) { + injector := inject.New() + expect(t, injector == nil, false) + + dep := "some dependency" + injector.Map(dep) + dep2 := "another dep" + injector.MapTo(dep2, (*SpecialString)(nil)) + + result, err := injector.Invoke(func(d1 string, d2 SpecialString) string { + expect(t, d1, dep) + expect(t, d2, dep2) + return "Hello world" + }) + + expect(t, result[0].String(), "Hello world") + expect(t, err, nil) +} + +func Test_InjectorApply(t *testing.T) { + injector := inject.New() + + injector.Map("a dep").MapTo("another dep", (*SpecialString)(nil)) + + s := TestStruct{} + err := injector.Apply(&s) + expect(t, err, nil) + + expect(t, s.Dep1, "a dep") + expect(t, s.Dep2, "another dep") + expect(t, s.Dep3, "") +} + +func Test_InterfaceOf(t *testing.T) { + iType := inject.InterfaceOf((*SpecialString)(nil)) + expect(t, iType.Kind(), reflect.Interface) + + iType = inject.InterfaceOf((**SpecialString)(nil)) + expect(t, iType.Kind(), reflect.Interface) + + // Expecting nil + defer func() { + rec := recover() + refute(t, rec, nil) + }() + iType = inject.InterfaceOf((*testing.T)(nil)) +} + +func Test_InjectorSet(t *testing.T) { + injector := inject.New() + typ := reflect.TypeOf("string") + typSend := reflect.ChanOf(reflect.SendDir, typ) + typRecv := reflect.ChanOf(reflect.RecvDir, typ) + + // instantiating unidirectional channels is not possible using reflect + // http://golang.org/src/pkg/reflect/value.go?s=60463:60504#L2064 + chanRecv := reflect.MakeChan(reflect.ChanOf(reflect.BothDir, typ), 0) + chanSend := reflect.MakeChan(reflect.ChanOf(reflect.BothDir, typ), 0) + + injector.Set(typSend, chanSend) + injector.Set(typRecv, chanRecv) + + expect(t, injector.Get(typSend).IsValid(), true) + expect(t, injector.Get(typRecv).IsValid(), true) + expect(t, injector.Get(chanSend.Type()).IsValid(), false) +} + +func Test_InjectorGet(t *testing.T) { + injector := inject.New() + + injector.Map("some dependency") + + expect(t, injector.Get(reflect.TypeOf("string")).IsValid(), true) + expect(t, injector.Get(reflect.TypeOf(11)).IsValid(), false) +} + +func Test_InjectorSetParent(t *testing.T) { + injector := inject.New() + injector.MapTo("another dep", (*SpecialString)(nil)) + + injector2 := inject.New() + injector2.SetParent(injector) + + expect(t, injector2.Get(inject.InterfaceOf((*SpecialString)(nil))).IsValid(), true) +} + +func TestInjectImplementors(t *testing.T) { + injector := inject.New() + g := &Greeter{"Jeremy"} + injector.Map(g) + + expect(t, injector.Get(inject.InterfaceOf((*fmt.Stringer)(nil))).IsValid(), true) +} diff --git a/plugins/network/vendor/github.com/codegangsta/inject/translations/README_zh_cn.md b/plugins/network/vendor/github.com/codegangsta/inject/translations/README_zh_cn.md new file mode 100644 index 000000000..0ac3d3f55 --- /dev/null +++ b/plugins/network/vendor/github.com/codegangsta/inject/translations/README_zh_cn.md @@ -0,0 +1,85 @@ +# inject +-- + import "github.com/codegangsta/inject" + +inject包提供了多种对实体的映射和依赖注入方式。 + +## 用法 + +#### func InterfaceOf + +```go +func InterfaceOf(value interface{}) reflect.Type +``` +函数InterfaceOf返回指向接口类型的指针。如果传入的value值不是指向接口的指针,将抛出一个panic异常。 + +#### type Applicator + +```go +type Applicator interface { + // 在Type map中维持对结构体中每个域的引用并用'inject'来标记 + // 如果注入失败将会返回一个error. + Apply(interface{}) error +} +``` + +Applicator接口表示到结构体的依赖映射关系。 + +#### type Injector + +```go +type Injector interface { + Applicator + Invoker + TypeMapper + // SetParent用来设置父injector. 如果在当前injector的Type map中找不到依赖, + // 将会继续从它的父injector中找,直到返回error. + SetParent(Injector) +} +``` + +Injector接口表示对结构体、函数参数的映射和依赖注入。 + +#### func New + +```go +func New() Injector +``` +New创建并返回一个Injector. + +#### type Invoker + +```go +type Invoker interface { + // Invoke尝试将interface{}作为一个函数来调用,并基于Type为函数提供参数。 + // 它将返回reflect.Value的切片,其中存放原函数的返回值。 + // 如果注入失败则返回error. + Invoke(interface{}) ([]reflect.Value, error) +} +``` + +Invoker接口表示通过反射进行函数调用。 + +#### type TypeMapper + +```go +type TypeMapper interface { + // 基于调用reflect.TypeOf得到的类型映射interface{}的值。 + Map(interface{}) TypeMapper + // 基于提供的接口的指针映射interface{}的值。 + // 该函数仅用来将一个值映射为接口,因为接口无法不通过指针而直接引用到。 + MapTo(interface{}, interface{}) TypeMapper + // 为直接插入基于类型和值的map提供一种可能性。 + // 它使得这一类直接映射成为可能:无法通过反射直接实例化的类型参数,如单向管道。 + Set(reflect.Type, reflect.Value) TypeMapper + // 返回映射到当前类型的Value. 如果Type没被映射,将返回对应的零值。 + Get(reflect.Type) reflect.Value +} +``` + +TypeMapper接口用来表示基于类型到接口值的映射。 + + +## 译者 + +张强 (qqbunny@yeah.net) \ No newline at end of file diff --git a/plugins/network/vendor/github.com/codegangsta/inject/update_readme.sh b/plugins/network/vendor/github.com/codegangsta/inject/update_readme.sh new file mode 100644 index 000000000..497f9a577 --- /dev/null +++ b/plugins/network/vendor/github.com/codegangsta/inject/update_readme.sh @@ -0,0 +1,3 @@ +#!/bin/bash +go get github.com/robertkrimen/godocdown/godocdown +godocdown > README.md diff --git a/plugins/network/vendor/github.com/codeskyblue/go-sh/LICENSE b/plugins/network/vendor/github.com/codeskyblue/go-sh/LICENSE new file mode 100644 index 000000000..e06d20818 --- /dev/null +++ b/plugins/network/vendor/github.com/codeskyblue/go-sh/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/plugins/network/vendor/github.com/codeskyblue/go-sh/OLD_README.md b/plugins/network/vendor/github.com/codeskyblue/go-sh/OLD_README.md new file mode 100644 index 000000000..7e899402d --- /dev/null +++ b/plugins/network/vendor/github.com/codeskyblue/go-sh/OLD_README.md @@ -0,0 +1,69 @@ +## OLD README +First give you a full example, I will explain every command below. + + session := sh.NewSession() + session.Env["PATH"] = "/usr/bin:/bin" + session.Stdout = os.Stdout + session.Stderr = os.Stderr + session.Alias("ll", "ls", "-l") + session.ShowCMD = true // enable for debug + var err error + err = session.Call("ll", "/") + if err != nil { + log.Fatal(err) + } + ret, err := session.Capture("pwd", sh.Dir("/home")) # wraper of session.Call + if err != nil { + log.Fatal(err) + } + # ret is "/home\n" + fmt.Println(ret) + +create a new Session + + session := sh.NewSession() + +use alias like this + + session.Alias("ll", "ls", "-l") # like alias ll='ls -l' + +set current env like this + + session.Env["BUILD_ID"] = "123" # like export BUILD_ID=123 + +set current directory + + session.Set(sh.Dir("/")) # like cd / + +pipe is also supported + + session.Command("echo", "hello\tworld").Command("cut", "-f2") + // output should be "world" + session.Run() + +test, the build in command support + + session.Test("d", "dir") // test dir + session.Test("f", "file) // test regular file + +with `Alias Env Set Call Capture Command` a shell scripts can be easily converted into golang program. below is a shell script. + + #!/bin/bash - + # + export PATH=/usr/bin:/bin + alias ll='ls -l' + cd /usr + if test -d "local" + then + ll local | awk '{print $1, $NF}' + fi + +convert to golang, will be + + s := sh.NewSession() + s.Env["PATH"] = "/usr/bin:/bin" + s.Set(sh.Dir("/usr")) + s.Alias("ll", "ls", "-l") + if s.Test("d", "local") { + s.Command("ll", "local").Command("awk", "{print $1, $NF}").Run() + } diff --git a/plugins/network/vendor/github.com/codeskyblue/go-sh/README.md b/plugins/network/vendor/github.com/codeskyblue/go-sh/README.md new file mode 100644 index 000000000..28614708d --- /dev/null +++ b/plugins/network/vendor/github.com/codeskyblue/go-sh/README.md @@ -0,0 +1,85 @@ +## go-sh +[![wercker status](https://app.wercker.com/status/009acbd4f00ccc6de7e2554e12a50d84/s "wercker status")](https://app.wercker.com/project/bykey/009acbd4f00ccc6de7e2554e12a50d84) +[![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/codeskyblue/go-sh) + +*If you depend on the old api, see tag: v.0.1* + +install: `go get github.com/codeskyblue/go-sh` + +Pipe Example: + + package main + + import "github.com/codeskyblue/go-sh" + + func main() { + sh.Command("echo", "hello\tworld").Command("cut", "-f2").Run() + } + +Because I like os/exec, `go-sh` is very much modelled after it. However, `go-sh` provides a better experience. + +These are some of its features: + +* keep the variable environment (e.g. export) +* alias support (e.g. alias in shell) +* remember current dir +* pipe command +* shell build-in commands echo & test +* timeout support + +Examples are important: + + sh: echo hello + go: sh.Command("echo", "hello").Run() + + sh: export BUILD_ID=123 + go: s = sh.NewSession().SetEnv("BUILD_ID", "123") + + sh: alias ll='ls -l' + go: s = sh.NewSession().Alias('ll', 'ls', '-l') + + sh: (cd /; pwd) + go: sh.Command("pwd", sh.Dir("/")).Run() + + sh: test -d data || mkdir data + go: if ! sh.Test("dir", "data") { sh.Command("mkdir", "data").Run() } + + sh: cat first second | awk '{print $1}' + go: sh.Command("cat", "first", "second").Command("awk", "{print $1}").Run() + + sh: count=$(echo "one two three" | wc -w) + go: count, err := sh.Echo("one two three").Command("wc", "-w").Output() + + sh(in ubuntu): timeout 1s sleep 3 + go: c := sh.Command("sleep", "3"); c.Start(); c.WaitTimeout(time.Second) # default SIGKILL + go: out, err := sh.Command("sleep", "3").SetTimeout(time.Second).Output() # set session timeout and get output) + + sh: echo hello | cat + go: out, err := sh.Command("cat").SetInput("hello").Output() + + sh: cat # read from stdin + go: out, err := sh.Command("cat").SetStdin(os.Stdin).Output() + +If you need to keep env and dir, it is better to create a session + + session := sh.NewSession() + session.SetEnv("BUILD_ID", "123") + session.SetDir("/") + # then call cmd + session.Command("echo", "hello").Run() + # set ShowCMD to true for easily debug + session.ShowCMD = true + +for more information, it better to see docs. +[![Go Walker](http://gowalker.org/api/v1/badge)](http://gowalker.org/github.com/codeskyblue/go-sh) + +### contribute +If you love this project, starring it will encourage the coder. Pull requests are welcome. + +support the author: [alipay](https://me.alipay.com/goskyblue) + +### thanks +this project is based on . thanks for the author. + +# the reason to use Go shell +Sometimes we need to write shell scripts, but shell scripts are not good at working cross platform, Go, on the other hand, is good at that. Is there a good way to use Go to write shell like scripts? Using go-sh we can do this now. diff --git a/plugins/network/vendor/github.com/codeskyblue/go-sh/example/example1.go b/plugins/network/vendor/github.com/codeskyblue/go-sh/example/example1.go new file mode 100644 index 000000000..cdc7f6c39 --- /dev/null +++ b/plugins/network/vendor/github.com/codeskyblue/go-sh/example/example1.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + "log" + + "github.com/codeskyblue/go-sh" +) + +func main() { + sh.Command("echo", "hello").Run() + out, err := sh.Command("echo", "hello").Output() + if err != nil { + log.Fatal(err) + } + fmt.Println("output is", string(out)) + + var a int + sh.Command("echo", "2").UnmarshalJSON(&a) + fmt.Println("a =", a) + + s := sh.NewSession() + s.Alias("hi", "echo", "hi") + s.Command("hi", "boy").Run() + + fmt.Print("pwd = ") + s.Command("pwd", sh.Dir("/")).Run() + + if !sh.Test("dir", "data") { + sh.Command("echo", "mkdir", "data").Run() + } + + sh.Command("echo", "hello", "world"). + Command("awk", `{print "second arg is "$2}`).Run() + s.ShowCMD = true + s.Command("echo", "hello", "world"). + Command("awk", `{print "second arg is "$2}`).Run() + + s.SetEnv("BUILD_ID", "123").Command("bash", "-c", "echo $BUILD_ID").Run() + s.Command("bash", "-c", "echo current shell is $SHELL").Run() +} diff --git a/plugins/network/vendor/github.com/codeskyblue/go-sh/example/less/less.go b/plugins/network/vendor/github.com/codeskyblue/go-sh/example/less/less.go new file mode 100644 index 000000000..ffced2e81 --- /dev/null +++ b/plugins/network/vendor/github.com/codeskyblue/go-sh/example/less/less.go @@ -0,0 +1,7 @@ +package main + +import "github.com/codeskyblue/go-sh" + +func main() { + sh.Command("less", "less.go").Run() +} diff --git a/plugins/network/vendor/github.com/codeskyblue/go-sh/example/tail/tailf.go b/plugins/network/vendor/github.com/codeskyblue/go-sh/example/tail/tailf.go new file mode 100644 index 000000000..b7ecd3bed --- /dev/null +++ b/plugins/network/vendor/github.com/codeskyblue/go-sh/example/tail/tailf.go @@ -0,0 +1,17 @@ +package main + +import ( + "flag" + "fmt" + + "github.com/codeskyblue/go-sh" +) + +func main() { + flag.Parse() + if flag.NArg() != 1 { + fmt.Println("Usage: PROGRAM ") + return + } + sh.Command("tail", "-f", flag.Arg(0)).Run() +} diff --git a/plugins/network/vendor/github.com/codeskyblue/go-sh/example/timeout/timeout.go b/plugins/network/vendor/github.com/codeskyblue/go-sh/example/timeout/timeout.go new file mode 100644 index 000000000..852908a47 --- /dev/null +++ b/plugins/network/vendor/github.com/codeskyblue/go-sh/example/timeout/timeout.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "time" + + sh "github.com/codeskyblue/go-sh" +) + +func main() { + c := sh.Command("sleep", "3") + c.Start() + err := c.WaitTimeout(time.Second * 1) + if err != nil { + fmt.Printf("timeout should happend: %v\n", err) + } + // timeout should be a session + out, err := sh.Command("sleep", "2").SetTimeout(time.Second).Output() + fmt.Printf("output:(%s), err(%v)\n", string(out), err) + + out, err = sh.Command("echo", "hello").SetTimeout(time.Second).Output() + fmt.Printf("output:(%s), err(%v)\n", string(out), err) +} diff --git a/plugins/network/vendor/github.com/codeskyblue/go-sh/example_test.go b/plugins/network/vendor/github.com/codeskyblue/go-sh/example_test.go new file mode 100644 index 000000000..5abfed64d --- /dev/null +++ b/plugins/network/vendor/github.com/codeskyblue/go-sh/example_test.go @@ -0,0 +1,28 @@ +package sh_test + +import ( + "fmt" + + "github.com/codeskyblue/go-sh" +) + +func ExampleCommand() { + out, err := sh.Command("echo", "hello").Output() + fmt.Println(string(out), err) +} + +func ExampleCommandPipe() { + out, err := sh.Command("echo", "-n", "hi").Command("wc", "-c").Output() + fmt.Println(string(out), err) +} + +func ExampleCommandSetDir() { + out, err := sh.Command("pwd", sh.Dir("/")).Output() + fmt.Println(string(out), err) +} + +func ExampleTest() { + if sh.Test("dir", "mydir") { + fmt.Println("mydir exists") + } +} diff --git a/plugins/network/vendor/github.com/codeskyblue/go-sh/pipe.go b/plugins/network/vendor/github.com/codeskyblue/go-sh/pipe.go new file mode 100644 index 000000000..c35f4e2a0 --- /dev/null +++ b/plugins/network/vendor/github.com/codeskyblue/go-sh/pipe.go @@ -0,0 +1,148 @@ +package sh + +import ( + "bytes" + "encoding/json" + "encoding/xml" + "errors" + "io" + "os" + "strings" + "syscall" + "time" +) + +var ErrExecTimeout = errors.New("execute timeout") + +// unmarshal shell output to decode json +func (s *Session) UnmarshalJSON(data interface{}) (err error) { + bufrw := bytes.NewBuffer(nil) + s.Stdout = bufrw + if err = s.Run(); err != nil { + return + } + return json.NewDecoder(bufrw).Decode(data) +} + +// unmarshal command output into xml +func (s *Session) UnmarshalXML(data interface{}) (err error) { + bufrw := bytes.NewBuffer(nil) + s.Stdout = bufrw + if err = s.Run(); err != nil { + return + } + return xml.NewDecoder(bufrw).Decode(data) +} + +// start command +func (s *Session) Start() (err error) { + s.started = true + var rd *io.PipeReader + var wr *io.PipeWriter + var length = len(s.cmds) + if s.ShowCMD { + var cmds = make([]string, 0, 4) + for _, cmd := range s.cmds { + cmds = append(cmds, strings.Join(cmd.Args, " ")) + } + s.writePrompt(strings.Join(cmds, " | ")) + } + for index, cmd := range s.cmds { + if index == 0 { + cmd.Stdin = s.Stdin + } else { + cmd.Stdin = rd + } + if index != length { + rd, wr = io.Pipe() // create pipe + cmd.Stdout = wr + cmd.Stderr = os.Stderr + } + if index == length-1 { + cmd.Stdout = s.Stdout + cmd.Stderr = s.Stderr + } + err = cmd.Start() + if err != nil { + return + } + } + return +} + +// Should be call after Start() +// only catch the last command error +func (s *Session) Wait() (err error) { + for _, cmd := range s.cmds { + err = cmd.Wait() + wr, ok := cmd.Stdout.(*io.PipeWriter) + if ok { + wr.Close() + } + } + return err +} + +func (s *Session) Kill(sig os.Signal) { + for _, cmd := range s.cmds { + if cmd.Process != nil { + cmd.Process.Signal(sig) + } + } +} + +func (s *Session) WaitTimeout(timeout time.Duration) (err error) { + select { + case <-time.After(timeout): + s.Kill(syscall.SIGKILL) + return ErrExecTimeout + case err = <-Go(s.Wait): + return err + } +} + +func Go(f func() error) chan error { + ch := make(chan error) + go func() { + ch <- f() + }() + return ch +} + +func (s *Session) Run() (err error) { + if err = s.Start(); err != nil { + return + } + if s.timeout != time.Duration(0) { + return s.WaitTimeout(s.timeout) + } + return s.Wait() +} + +func (s *Session) Output() (out []byte, err error) { + oldout := s.Stdout + defer func() { + s.Stdout = oldout + }() + stdout := bytes.NewBuffer(nil) + s.Stdout = stdout + err = s.Run() + out = stdout.Bytes() + return +} + +func (s *Session) CombinedOutput() (out []byte, err error) { + oldout := s.Stdout + olderr := s.Stderr + defer func() { + s.Stdout = oldout + s.Stderr = olderr + }() + stdout := bytes.NewBuffer(nil) + s.Stdout = stdout + s.Stderr = stdout + + err = s.Run() + out = stdout.Bytes() + return +} diff --git a/plugins/network/vendor/github.com/codeskyblue/go-sh/pipe_test.go b/plugins/network/vendor/github.com/codeskyblue/go-sh/pipe_test.go new file mode 100644 index 000000000..8d0e22c0c --- /dev/null +++ b/plugins/network/vendor/github.com/codeskyblue/go-sh/pipe_test.go @@ -0,0 +1,126 @@ +package sh + +import ( + "encoding/xml" + "io" + "os" + "os/exec" + "strings" + "testing" + "time" +) + +func TestUnmarshalJSON(t *testing.T) { + var a int + s := NewSession() + s.ShowCMD = true + err := s.Command("echo", []string{"1"}).UnmarshalJSON(&a) + if err != nil { + t.Error(err) + } + if a != 1 { + t.Errorf("expect a tobe 1, but got %d", a) + } +} + +func TestUnmarshalXML(t *testing.T) { + s := NewSession() + xmlSample := ` +` + type server struct { + XMLName xml.Name `xml:"server"` + Version string `xml:"version,attr"` + } + data := &server{} + s.Command("echo", xmlSample).UnmarshalXML(data) + if data.Version != "1" { + t.Error(data) + } +} + +func TestPipe(t *testing.T) { + s := NewSession() + s.ShowCMD = true + s.Call("echo", "hello") + err := s.Command("echo", "hi").Command("cat", "-n").Start() + if err != nil { + t.Error(err) + } + err = s.Wait() + if err != nil { + t.Error(err) + } + out, err := s.Command("echo", []string{"hello"}).Output() + if err != nil { + t.Error(err) + } + if string(out) != "hello\n" { + t.Error("capture wrong output:", out) + } + s.Command("echo", []string{"hello\tworld"}).Command("cut", []string{"-f2"}).Run() +} + +func TestPipeCommand(t *testing.T) { + c1 := exec.Command("echo", "good") + rd, wr := io.Pipe() + c1.Stdout = wr + c2 := exec.Command("cat", "-n") + c2.Stdout = os.Stdout + c2.Stdin = rd + c1.Start() + c2.Start() + + c1.Wait() + wc, ok := c1.Stdout.(io.WriteCloser) + if ok { + wc.Close() + } + c2.Wait() +} + +func TestPipeInput(t *testing.T) { + s := NewSession() + s.ShowCMD = true + s.SetInput("first line\nsecond line\n") + out, err := s.Command("grep", "second").Output() + if err != nil { + t.Error(err) + } + if string(out) != "second line\n" { + t.Error("capture wrong output:", out) + } +} + +func TestTimeout(t *testing.T) { + s := NewSession() + err := s.Command("sleep", "2").Start() + if err != nil { + t.Fatal(err) + } + err = s.WaitTimeout(time.Second) + if err != ErrExecTimeout { + t.Fatal(err) + } +} + +func TestSetTimeout(t *testing.T) { + s := NewSession() + s.SetTimeout(time.Second) + defer s.SetTimeout(0) + err := s.Command("sleep", "2").Run() + if err != ErrExecTimeout { + t.Fatal(err) + } +} + +func TestCombinedOutput(t *testing.T) { + s := NewSession() + bytes, err := s.Command("sh", "-c", "echo stderr >&2 ; echo stdout").CombinedOutput() + if err != nil { + t.Error(err) + } + stringOutput := string(bytes) + if !(strings.Contains(stringOutput, "stdout") && strings.Contains(stringOutput, "stderr")) { + t.Errorf("expect output from both output streams, got '%s'", strings.TrimSpace(stringOutput)) + } +} diff --git a/plugins/network/vendor/github.com/codeskyblue/go-sh/sh.go b/plugins/network/vendor/github.com/codeskyblue/go-sh/sh.go new file mode 100644 index 000000000..15f1b85d5 --- /dev/null +++ b/plugins/network/vendor/github.com/codeskyblue/go-sh/sh.go @@ -0,0 +1,200 @@ +/* +Package go-sh is intented to make shell call with golang more easily. +Some usage is more similar to os/exec, eg: Run(), Output(), Command(name, args...) + +But with these similar function, pipe is added in and this package also got shell-session support. + +Why I love golang so much, because the usage of golang is simple, but the power is unlimited. I want to make this pakcage got the sample style like golang. + + // just like os/exec + sh.Command("echo", "hello").Run() + + // support pipe + sh.Command("echo", "hello").Command("wc", "-c").Run() + + // create a session to store dir and env + sh.NewSession().SetDir("/").Command("pwd") + + // shell buildin command - "test" + sh.Test("dir", "mydir") + + // like shell call: (cd /; pwd) + sh.Command("pwd", sh.Dir("/")) same with sh.Command(sh.Dir("/"), "pwd") + + // output to json and xml easily + v := map[string] int {} + err = sh.Command("echo", `{"number": 1}`).UnmarshalJSON(&v) +*/ +package sh + +import ( + "fmt" + "io" + "os" + "os/exec" + "reflect" + "strings" + "time" + + "github.com/codegangsta/inject" +) + +type Dir string + +type Session struct { + inj inject.Injector + alias map[string][]string + cmds []*exec.Cmd + dir Dir + started bool + Env map[string]string + Stdin io.Reader + Stdout io.Writer + Stderr io.Writer + ShowCMD bool // enable for debug + timeout time.Duration +} + +func (s *Session) writePrompt(args ...interface{}) { + var ps1 = fmt.Sprintf("[golang-sh]$") + args = append([]interface{}{ps1}, args...) + fmt.Fprintln(s.Stderr, args...) +} + +func NewSession() *Session { + env := make(map[string]string) + for _, key := range []string{"PATH"} { + env[key] = os.Getenv(key) + } + s := &Session{ + inj: inject.New(), + alias: make(map[string][]string), + dir: Dir(""), + Stdin: strings.NewReader(""), + Stdout: os.Stdout, + Stderr: os.Stderr, + Env: env, + } + return s +} + +func InteractiveSession() *Session { + s := NewSession() + s.SetStdin(os.Stdin) + return s +} + +func Command(name string, a ...interface{}) *Session { + s := NewSession() + return s.Command(name, a...) +} + +func Echo(in string) *Session { + s := NewSession() + return s.SetInput(in) +} + +func (s *Session) Alias(alias, cmd string, args ...string) { + v := []string{cmd} + v = append(v, args...) + s.alias[alias] = v +} + +func (s *Session) Command(name string, a ...interface{}) *Session { + var args = make([]string, 0) + var sType = reflect.TypeOf("") + + // init cmd, args, dir, envs + // if not init, program may panic + s.inj.Map(name).Map(args).Map(s.dir).Map(map[string]string{}) + for _, v := range a { + switch reflect.TypeOf(v) { + case sType: + args = append(args, v.(string)) + default: + s.inj.Map(v) + } + } + if len(args) != 0 { + s.inj.Map(args) + } + s.inj.Invoke(s.appendCmd) + return s +} + +// combine Command and Run +func (s *Session) Call(name string, a ...interface{}) error { + return s.Command(name, a...).Run() +} + +/* +func (s *Session) Exec(cmd string, args ...string) error { + return s.Call(cmd, args) +} +*/ + +func (s *Session) SetEnv(key, value string) *Session { + s.Env[key] = value + return s +} + +func (s *Session) SetDir(dir string) *Session { + s.dir = Dir(dir) + return s +} + +func (s *Session) SetInput(in string) *Session { + s.Stdin = strings.NewReader(in) + return s +} + +func (s *Session) SetStdin(r io.Reader) *Session { + s.Stdin = r + return s +} + +func (s *Session) SetTimeout(d time.Duration) *Session { + s.timeout = d + return s +} + +func newEnviron(env map[string]string, inherit bool) []string { //map[string]string { + environ := make([]string, 0, len(env)) + if inherit { + for _, line := range os.Environ() { + for k, _ := range env { + if strings.HasPrefix(line, k+"=") { + goto CONTINUE + } + } + environ = append(environ, line) + CONTINUE: + } + } + for k, v := range env { + environ = append(environ, k+"="+v) + } + return environ +} + +func (s *Session) appendCmd(cmd string, args []string, cwd Dir, env map[string]string) { + if s.started { + s.started = false + s.cmds = make([]*exec.Cmd, 0) + } + for k, v := range s.Env { + if _, ok := env[k]; !ok { + env[k] = v + } + } + environ := newEnviron(s.Env, true) // true: inherit sys-env + v, ok := s.alias[cmd] + if ok { + cmd = v[0] + args = append(v[1:], args...) + } + c := exec.Command(cmd, args...) + c.Env = environ + c.Dir = string(cwd) + s.cmds = append(s.cmds, c) +} diff --git a/plugins/network/vendor/github.com/codeskyblue/go-sh/sh_test.go b/plugins/network/vendor/github.com/codeskyblue/go-sh/sh_test.go new file mode 100644 index 000000000..be5c27998 --- /dev/null +++ b/plugins/network/vendor/github.com/codeskyblue/go-sh/sh_test.go @@ -0,0 +1,106 @@ +package sh + +import ( + "fmt" + "log" + "runtime" + "strings" + "testing" +) + +func TestAlias(t *testing.T) { + s := NewSession() + s.Alias("gr", "echo", "hi") + out, err := s.Command("gr", "sky").Output() + if err != nil { + t.Error(err) + } + if string(out) != "hi sky\n" { + t.Errorf("expect 'hi sky' but got:%s", string(out)) + } +} + +func ExampleSession_Command() { + s := NewSession() + out, err := s.Command("echo", "hello").Output() + if err != nil { + log.Fatal(err) + } + fmt.Println(string(out)) + // Output: hello +} + +func ExampleSession_Command_pipe() { + s := NewSession() + out, err := s.Command("echo", "hello", "world").Command("awk", "{print $2}").Output() + if err != nil { + log.Fatal(err) + } + fmt.Println(string(out)) + // Output: world +} + +func ExampleSession_Alias() { + s := NewSession() + s.Alias("alias_echo_hello", "echo", "hello") + out, err := s.Command("alias_echo_hello", "world").Output() + if err != nil { + log.Fatal(err) + } + fmt.Println(string(out)) + // Output: hello world +} + +func TestEcho(t *testing.T) { + out, err := Echo("one two three").Command("wc", "-w").Output() + if err != nil { + t.Error(err) + } + if strings.TrimSpace(string(out)) != "3" { + t.Errorf("expect '3' but got:%s", string(out)) + } +} + +func TestSession(t *testing.T) { + if runtime.GOOS == "windows" { + t.Log("ignore test on windows") + return + } + session := NewSession() + session.ShowCMD = true + err := session.Call("pwd") + if err != nil { + t.Error(err) + } + out, err := session.SetDir("/").Command("pwd").Output() + if err != nil { + t.Error(err) + } + if string(out) != "/\n" { + t.Errorf("expect /, but got %s", string(out)) + } +} + +/* + #!/bin/bash - + # + export PATH=/usr/bin:/bin + alias ll='ls -l' + cd /usr + if test -d "local" + then + ll local | awk '{print $1, $NF}' | grep bin + fi +*/ +func Example(t *testing.T) { + s := NewSession() + //s.ShowCMD = true + s.Env["PATH"] = "/usr/bin:/bin" + s.SetDir("/bin") + s.Alias("ll", "ls", "-l") + + if s.Test("d", "local") { + //s.Command("ll", []string{"local"}).Command("awk", []string{"{print $1, $NF}"}).Command("grep", []string{"bin"}).Run() + s.Command("ll", "local").Command("awk", "{print $1, $NF}").Command("grep", "bin").Run() + } +} diff --git a/plugins/network/vendor/github.com/codeskyblue/go-sh/test.go b/plugins/network/vendor/github.com/codeskyblue/go-sh/test.go new file mode 100644 index 000000000..b9d310527 --- /dev/null +++ b/plugins/network/vendor/github.com/codeskyblue/go-sh/test.go @@ -0,0 +1,64 @@ +package sh + +import ( + "os" + "path/filepath" +) + +func filetest(name string, modemask os.FileMode) (match bool, err error) { + fi, err := os.Stat(name) + if err != nil { + return + } + match = (fi.Mode() & modemask) == modemask + return +} + +func (s *Session) pwd() string { + dir := string(s.dir) + if dir == "" { + dir, _ = os.Getwd() + } + return dir +} + +func (s *Session) abspath(name string) string { + if filepath.IsAbs(name) { + return name + } + return filepath.Join(s.pwd(), name) +} + +func init() { + //log.SetFlags(log.Lshortfile | log.LstdFlags) +} + +// expression can be dir, file, link +func (s *Session) Test(expression string, argument string) bool { + var err error + var fi os.FileInfo + fi, err = os.Lstat(s.abspath(argument)) + switch expression { + case "d", "dir": + return err == nil && fi.IsDir() + case "f", "file": + return err == nil && fi.Mode().IsRegular() + case "x", "executable": + /* + fmt.Println(expression, argument) + if err == nil { + fmt.Println(fi.Mode()) + } + */ + return err == nil && fi.Mode()&os.FileMode(0100) != 0 + case "L", "link": + return err == nil && fi.Mode()&os.ModeSymlink != 0 + } + return false +} + +// expression can be d,dir, f,file, link +func Test(exp string, arg string) bool { + s := NewSession() + return s.Test(exp, arg) +} diff --git a/plugins/network/vendor/github.com/codeskyblue/go-sh/test_test.go b/plugins/network/vendor/github.com/codeskyblue/go-sh/test_test.go new file mode 100644 index 000000000..15225aa9a --- /dev/null +++ b/plugins/network/vendor/github.com/codeskyblue/go-sh/test_test.go @@ -0,0 +1,58 @@ +package sh_test + +import ( + "testing" + + "github.com/codeskyblue/go-sh" +) + +var s = sh.NewSession() + +type T struct{ *testing.T } + +func NewT(t *testing.T) *T { + return &T{t} +} + +func (t *T) checkTest(exp string, arg string, result bool) { + r := s.Test(exp, arg) + if r != result { + t.Errorf("test -%s %s, %v != %v", exp, arg, r, result) + } +} + +func TestTest(i *testing.T) { + t := NewT(i) + t.checkTest("d", "../go-sh", true) + t.checkTest("d", "./yymm", false) + + // file test + t.checkTest("f", "testdata/hello.txt", true) + t.checkTest("f", "testdata/xxxxx", false) + t.checkTest("f", "testdata/yymm", false) + + // link test + t.checkTest("link", "testdata/linkfile", true) + t.checkTest("link", "testdata/xxxxxlinkfile", false) + t.checkTest("link", "testdata/hello.txt", false) + + // executable test + t.checkTest("x", "testdata/executable", true) + t.checkTest("x", "testdata/xxxxx", false) + t.checkTest("x", "testdata/hello.txt", false) +} + +func ExampleShellTest(t *testing.T) { + // test -L + sh.Test("link", "testdata/linkfile") + sh.Test("L", "testdata/linkfile") + // test -f + sh.Test("file", "testdata/file") + sh.Test("f", "testdata/file") + // test -x + sh.Test("executable", "testdata/binfile") + sh.Test("x", "testdata/binfile") + // test -d + sh.Test("dir", "testdata/dir") + sh.Test("d", "testdata/dir") +} diff --git a/plugins/network/vendor/github.com/codeskyblue/go-sh/testdata/executable b/plugins/network/vendor/github.com/codeskyblue/go-sh/testdata/executable new file mode 100755 index 000000000..e69de29bb diff --git a/plugins/network/vendor/github.com/codeskyblue/go-sh/testdata/hello.txt b/plugins/network/vendor/github.com/codeskyblue/go-sh/testdata/hello.txt new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/network/vendor/github.com/codeskyblue/go-sh/testdata/linkfile b/plugins/network/vendor/github.com/codeskyblue/go-sh/testdata/linkfile new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/network/vendor/github.com/codeskyblue/go-sh/wercker.yml b/plugins/network/vendor/github.com/codeskyblue/go-sh/wercker.yml new file mode 100644 index 000000000..72c47020f --- /dev/null +++ b/plugins/network/vendor/github.com/codeskyblue/go-sh/wercker.yml @@ -0,0 +1,28 @@ +box: wercker/golang +# Build definition +build: + # The steps that will be executed on build + steps: + # Sets the go workspace and places you package + # at the right place in the workspace tree + - setup-go-workspace + + # Gets the dependencies + - script: + name: go get + code: | + cd $WERCKER_SOURCE_DIR + go version + go get -t . + + # Build the project + - script: + name: go build + code: | + go build . + + # Test the project + - script: + name: go test + code: | + go test -v ./... diff --git a/plugins/network/vendor/github.com/ryanuber/columnize/.travis.yml b/plugins/network/vendor/github.com/ryanuber/columnize/.travis.yml new file mode 100644 index 000000000..1a0bbea6c --- /dev/null +++ b/plugins/network/vendor/github.com/ryanuber/columnize/.travis.yml @@ -0,0 +1,3 @@ +language: go +go: + - tip diff --git a/plugins/network/vendor/github.com/ryanuber/columnize/LICENSE b/plugins/network/vendor/github.com/ryanuber/columnize/LICENSE new file mode 100644 index 000000000..b9c0e2b68 --- /dev/null +++ b/plugins/network/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/network/vendor/github.com/ryanuber/columnize/README.md b/plugins/network/vendor/github.com/ryanuber/columnize/README.md new file mode 100644 index 000000000..e47634fc6 --- /dev/null +++ b/plugins/network/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/network/vendor/github.com/ryanuber/columnize/columnize.go b/plugins/network/vendor/github.com/ryanuber/columnize/columnize.go new file mode 100644 index 000000000..915716a10 --- /dev/null +++ b/plugins/network/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/network/vendor/github.com/ryanuber/columnize/columnize_test.go b/plugins/network/vendor/github.com/ryanuber/columnize/columnize_test.go new file mode 100644 index 000000000..89dabaa38 --- /dev/null +++ b/plugins/network/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/proxy.go b/plugins/proxy/proxy.go new file mode 100644 index 000000000..2fcf2372a --- /dev/null +++ b/plugins/proxy/proxy.go @@ -0,0 +1,21 @@ +package proxy + +import ( + common "github.com/dokku/dokku/plugins/common" + config "github.com/dokku/dokku/plugins/config" +) + +// IsAppProxyEnabled returns true if proxy is enabled; otherwise return false +func IsAppProxyEnabled(appName string) bool { + err := common.VerifyAppName(appName) + if err != nil { + common.LogFail(err.Error()) + } + + proxyEnabled := true + disableProxy := config.GetWithDefault(appName, "DOKKU_DISABLE_PROXY", "") + if disableProxy != "" { + proxyEnabled = false + } + return proxyEnabled +} diff --git a/plugins/repo/src/commands/commands.go b/plugins/repo/src/commands/commands.go index a01d546d5..e0d3862b4 100644 --- a/plugins/repo/src/commands/commands.go +++ b/plugins/repo/src/commands/commands.go @@ -29,7 +29,7 @@ func main() { cmd := flag.Arg(0) switch cmd { - case "repo:help": + case "repo", "repo:help": usage() case "help": fmt.Print(helpContent) From 1e8831d9a0c5fe4536b570314940a2f967a2a0de Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Mon, 24 Apr 2017 09:06:55 -0600 Subject: [PATCH 27/66] chore: remove unused nullglob --- plugins/nginx-vhosts/functions | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/nginx-vhosts/functions b/plugins/nginx-vhosts/functions index 82e3ab0bb..20950e2c8 100755 --- a/plugins/nginx-vhosts/functions +++ b/plugins/nginx-vhosts/functions @@ -228,10 +228,8 @@ nginx_build_config() { if [[ -z "$DOKKU_DISABLE_PROXY" ]]; then if [[ -z "$DOKKU_APP_LISTEN_PORT" ]] && [[ -z "$DOKKU_APP_LISTEN_IP" ]]; then - shopt -s nullglob local DOKKU_APP_LISTENERS="$(plugn trigger network-get-listeners "$APP")" 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 From 72eb8a06f0f447e93f2d3771a233420f3627cc4c Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Wed, 26 Apr 2017 18:49:19 -0600 Subject: [PATCH 28/66] fix: respond to all review comments --- plugins/common/common.go | 6 +++--- plugins/network/network.go | 6 +++--- .../triggers/network-write-ipaddr/network-write-ipaddr.go | 1 + .../src/triggers/network-write-port/network-write-port.go | 1 + 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/plugins/common/common.go b/plugins/common/common.go index 8bc3309d5..26825054b 100644 --- a/plugins/common/common.go +++ b/plugins/common/common.go @@ -162,7 +162,7 @@ func ContainerIsRunning(containerId string) bool { if err != nil { return false } - return string(b[:]) == "true" + return strings.TrimSpace(string(b[:])) == "true" } // DirectoryExists returns if a path exists and is a directory @@ -234,7 +234,7 @@ func FileExists(filePath string) bool { return fi.Mode().IsRegular() } -// GetAppImageName returnS image identifier for a given app, tag tuple. validate if tag is presented +// GetAppImageName returns image identifier for a given app, tag tuple. validate if tag is presented func GetAppImageName(appName, imageTag, imageRepo string) (imageName string) { err := VerifyAppName(appName) if err != nil { @@ -302,7 +302,7 @@ func LogInfo1(text string) { fmt.Fprintln(os.Stdout, fmt.Sprintf("-----> %s", text)) } -// LogInfo2Quiet is the info1 header formatter (with quiet option) +// LogInfo1Quiet is the info1 header formatter (with quiet option) func LogInfo1Quiet(text string) { if os.Getenv("DOKKU_QUIET_OUTPUT") != "" { LogInfo1(text) diff --git a/plugins/network/network.go b/plugins/network/network.go index 6ef28c3d0..cacb79001 100644 --- a/plugins/network/network.go +++ b/plugins/network/network.go @@ -33,7 +33,7 @@ func GetContainerIpaddress(appName string, procType string, isHerokuishContainer b, err := imageCmd.Output() if err != nil || len(b) == 0 { - // docker < .19 compatibility + // docker < 1.9 compatibility imageCmd = common.NewShellCmd(strings.Join([]string{ "docker", "inspect", @@ -124,7 +124,7 @@ func BuildConfig(appName string) { continue } - procParts := strings.SplitAfterN(line, ":", 2) + procParts := strings.SplitN(line, "=", 2) if len(procParts) != 2 { continue } @@ -135,7 +135,7 @@ func BuildConfig(appName string) { } containerIndex := 0 - for containerIndex <= procCount { + for containerIndex < procCount { containerIndex += 1 containerIdFile := fmt.Sprintf("%v/CONTAINER.%v.%v", appRoot, procType, containerIndex) diff --git a/plugins/network/src/triggers/network-write-ipaddr/network-write-ipaddr.go b/plugins/network/src/triggers/network-write-ipaddr/network-write-ipaddr.go index 9ba30e635..effc303ce 100644 --- a/plugins/network/src/triggers/network-write-ipaddr/network-write-ipaddr.go +++ b/plugins/network/src/triggers/network-write-ipaddr/network-write-ipaddr.go @@ -31,6 +31,7 @@ func main() { if err != nil { common.LogFail(err.Error()) } + defer f.Close() ipBytes := []byte(ip) _, err = f.Write(ipBytes) diff --git a/plugins/network/src/triggers/network-write-port/network-write-port.go b/plugins/network/src/triggers/network-write-port/network-write-port.go index df79e809b..eef3ad488 100644 --- a/plugins/network/src/triggers/network-write-port/network-write-port.go +++ b/plugins/network/src/triggers/network-write-port/network-write-port.go @@ -31,6 +31,7 @@ func main() { if err != nil { common.LogFail(err.Error()) } + defer f.Close() portBytes := []byte(port) _, err = f.Write(portBytes) From 25759bcbb48104ba5902b2f17fdcc10192cee143 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Mon, 1 May 2017 19:15:40 -0600 Subject: [PATCH 29/66] fix: use correct flag positional argument --- .../network-compute-ports/network-compute-ports.go | 6 +++--- .../src/triggers/network-get-ipaddr/network-get-ipaddr.go | 8 ++++---- .../network-get-listeners/network-get-listeners.go | 2 +- .../src/triggers/network-get-port/network-get-port.go | 8 ++++---- .../triggers/network-write-ipaddr/network-write-ipaddr.go | 8 ++++---- .../src/triggers/network-write-port/network-write-port.go | 8 ++++---- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/plugins/network/src/triggers/network-compute-ports/network-compute-ports.go b/plugins/network/src/triggers/network-compute-ports/network-compute-ports.go index 845d5daf3..baad620c3 100644 --- a/plugins/network/src/triggers/network-compute-ports/network-compute-ports.go +++ b/plugins/network/src/triggers/network-compute-ports/network-compute-ports.go @@ -13,9 +13,9 @@ import ( // computes the ports for a given app container func main() { flag.Parse() - appName := flag.Arg(1) - procType := flag.Arg(2) - isHerokuishContainer := common.ToBool(flag.Arg(3)) + appName := flag.Arg(0) + procType := flag.Arg(1) + isHerokuishContainer := common.ToBool(flag.Arg(2)) if procType != "web" { return diff --git a/plugins/network/src/triggers/network-get-ipaddr/network-get-ipaddr.go b/plugins/network/src/triggers/network-get-ipaddr/network-get-ipaddr.go index ee8b97665..270a208a1 100644 --- a/plugins/network/src/triggers/network-get-ipaddr/network-get-ipaddr.go +++ b/plugins/network/src/triggers/network-get-ipaddr/network-get-ipaddr.go @@ -12,10 +12,10 @@ import ( // write the ipaddress to stdout for a given app container func main() { flag.Parse() - appName := flag.Arg(1) - procType := flag.Arg(2) - isHerokuishContainer := common.ToBool(flag.Arg(3)) - containerId := flag.Arg(4) + appName := flag.Arg(0) + procType := flag.Arg(1) + isHerokuishContainer := common.ToBool(flag.Arg(2)) + containerId := flag.Arg(3) ipAddress := network.GetContainerIpaddress(appName, procType, isHerokuishContainer, containerId) fmt.Fprintln(os.Stdout, ipAddress) diff --git a/plugins/network/src/triggers/network-get-listeners/network-get-listeners.go b/plugins/network/src/triggers/network-get-listeners/network-get-listeners.go index f7155c720..9260a8447 100644 --- a/plugins/network/src/triggers/network-get-listeners/network-get-listeners.go +++ b/plugins/network/src/triggers/network-get-listeners/network-get-listeners.go @@ -13,7 +13,7 @@ import ( // returns the listeners (host:port combinations) for a given app container func main() { flag.Parse() - appName := flag.Arg(1) + appName := flag.Arg(0) dokkuRoot := common.MustGetEnv("DOKKU_ROOT") appRoot := strings.Join([]string{dokkuRoot, appName}, "/") diff --git a/plugins/network/src/triggers/network-get-port/network-get-port.go b/plugins/network/src/triggers/network-get-port/network-get-port.go index 5ec64ef52..bf695d34f 100644 --- a/plugins/network/src/triggers/network-get-port/network-get-port.go +++ b/plugins/network/src/triggers/network-get-port/network-get-port.go @@ -12,10 +12,10 @@ import ( // write the port to stdout for a given app container func main() { flag.Parse() - appName := flag.Arg(1) - procType := flag.Arg(2) - isHerokuishContainer := common.ToBool(flag.Arg(3)) - containerId := flag.Arg(4) + appName := flag.Arg(0) + procType := flag.Arg(1) + isHerokuishContainer := common.ToBool(flag.Arg(2)) + containerId := flag.Arg(3) port := network.GetContainerPort(appName, procType, isHerokuishContainer, containerId) fmt.Fprintln(os.Stdout, port) diff --git a/plugins/network/src/triggers/network-write-ipaddr/network-write-ipaddr.go b/plugins/network/src/triggers/network-write-ipaddr/network-write-ipaddr.go index effc303ce..32fa1fb9e 100644 --- a/plugins/network/src/triggers/network-write-ipaddr/network-write-ipaddr.go +++ b/plugins/network/src/triggers/network-write-ipaddr/network-write-ipaddr.go @@ -12,10 +12,10 @@ import ( // writes the ip to disk func main() { flag.Parse() - appName := flag.Arg(1) - procType := flag.Arg(2) - containerIndex := flag.Arg(3) - ip := flag.Arg(4) + appName := flag.Arg(0) + procType := flag.Arg(1) + containerIndex := flag.Arg(2) + ip := flag.Arg(3) if appName == "" { common.LogFail("Please specify an app to run the command on") diff --git a/plugins/network/src/triggers/network-write-port/network-write-port.go b/plugins/network/src/triggers/network-write-port/network-write-port.go index eef3ad488..97490246d 100644 --- a/plugins/network/src/triggers/network-write-port/network-write-port.go +++ b/plugins/network/src/triggers/network-write-port/network-write-port.go @@ -12,10 +12,10 @@ import ( // writes the port to disk func main() { flag.Parse() - appName := flag.Arg(1) - procType := flag.Arg(2) - containerIndex := flag.Arg(3) - port := flag.Arg(4) + appName := flag.Arg(0) + procType := flag.Arg(1) + containerIndex := flag.Arg(2) + port := flag.Arg(3) if appName == "" { common.LogFail("Please specify an app to run the command on") From 31333452c8d7c3ab768fe150067837d533e8c085 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Mon, 1 May 2017 20:55:16 -0600 Subject: [PATCH 30/66] fix: refactor retrieval of network and docker inspect information --- plugins/common/common.go | 16 +++++++++++++++- plugins/network/network.go | 32 +++++++------------------------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/plugins/common/common.go b/plugins/common/common.go index 26825054b..97a7b2e1e 100644 --- a/plugins/common/common.go +++ b/plugins/common/common.go @@ -158,7 +158,7 @@ func VerifyImage(image string) bool { // ContainerIsRunning checks to see if a container is running func ContainerIsRunning(containerId string) bool { - b, err := sh.Command("docker", "inspect", "--format", "'{{.State.Running}}'", containerId).Output() + b, err := DockerInspect(containerId, "'{{.State.Running}}'") if err != nil { return false } @@ -175,6 +175,20 @@ func DirectoryExists(filePath string) bool { return fi.IsDir() } +// DockerInspect runs an inspect command with a given format against a container id +func DockerInspect(containerId, format string) (string, error) { + b, err := sh.Command("docker", "inspect", "--format", format, containerId).Output() + if err != nil { + return "", err + } + output := strings.TrimSpace(string(b[:])) + if strings.HasPrefix(output, "'") && strings.HasSuffix(output, "'") { + output = strings.TrimSuffix(strings.TrimPrefix(output, "'"), "'") + } + return output, err + +} + // DokkuApps returns a list of all local apps func DokkuApps() ([]string, error) { var apps []string diff --git a/plugins/network/network.go b/plugins/network/network.go index cacb79001..832a8d25e 100644 --- a/plugins/network/network.go +++ b/plugins/network/network.go @@ -23,25 +23,10 @@ func GetContainerIpaddress(appName string, procType string, isHerokuishContainer return ipAddress } - imageCmd := common.NewShellCmd(strings.Join([]string{ - "docker", - "inspect", - "--format", - "'{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'", - containerId, - }, " ")) - - b, err := imageCmd.Output() + b, err := common.DockerInspect(containerId, "'{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'") if err != nil || len(b) == 0 { // docker < 1.9 compatibility - imageCmd = common.NewShellCmd(strings.Join([]string{ - "docker", - "inspect", - "--format", - "'{{ .NetworkSettings.IPAddress }}'", - containerId, - }, " ")) - b, err = imageCmd.Output() + b, err = common.DockerInspect(containerId, "'{{ .NetworkSettings.IPAddress }}'") } if err == nil { @@ -61,7 +46,10 @@ func GetContainerPort(appName string, procType string, isHerokuishContainer bool port := "" if isHerokuishContainer { - dockerfilePorts = strings.Split(config.GetWithDefault(appName, "DOKKU_DOCKERFILE_PORTS", ""), " ") + configValue := config.GetWithDefault(appName, "DOKKU_DOCKERFILE_PORTS", "") + if configValue != "" { + dockerfilePorts = strings.Split(configValue, " ") + } } if len(dockerfilePorts) > 0 { @@ -79,13 +67,7 @@ func GetContainerPort(appName string, procType string, isHerokuishContainer bool } if !proxy.IsAppProxyEnabled(appName) { - portCmd := common.NewShellCmd(strings.Join([]string{ - "docker", - "port", - containerId, - port, - }, " ")) - b, err := portCmd.Output() + b, err := sh.Command("docker", "port", containerId, port).Output() if err == nil { port = strings.Split(string(b[:]), ":")[1] } From 6e76831b5d11b5366ad7653632483ca38c25d27f Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Mon, 1 May 2017 21:09:36 -0600 Subject: [PATCH 31/66] fix: use correct flag parameter for app name --- .../src/triggers/network-build-config/network-build-config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/network/src/triggers/network-build-config/network-build-config.go b/plugins/network/src/triggers/network-build-config/network-build-config.go index b608e2190..51279ec5f 100644 --- a/plugins/network/src/triggers/network-build-config/network-build-config.go +++ b/plugins/network/src/triggers/network-build-config/network-build-config.go @@ -9,7 +9,7 @@ import ( // rebuilds network settings for an app func main() { flag.Parse() - appName := flag.Arg(1) + appName := flag.Arg(0) network.BuildConfig(appName) } From 6f008d6643bc340dc3f5864c8dabc5eb1ea9d771 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 2 May 2017 00:32:39 -0600 Subject: [PATCH 32/66] fix: use correct conditional check for isHerokuishContainer --- plugins/network/network.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/network/network.go b/plugins/network/network.go index 832a8d25e..e195ecf97 100644 --- a/plugins/network/network.go +++ b/plugins/network/network.go @@ -45,7 +45,7 @@ func GetContainerPort(appName string, procType string, isHerokuishContainer bool dockerfilePorts := make([]string, 0) port := "" - if isHerokuishContainer { + if !isHerokuishContainer { configValue := config.GetWithDefault(appName, "DOKKU_DOCKERFILE_PORTS", "") if configValue != "" { dockerfilePorts = strings.Split(configValue, " ") From 350efd8d5374924a245fb3bdb0e5da624a960d30 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Tue, 2 May 2017 01:44:05 -0600 Subject: [PATCH 33/66] chore: remove use of deprecated method --- tests/unit/10_ps-herokuish.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/10_ps-herokuish.bats b/tests/unit/10_ps-herokuish.bats index d3c00c001..6be9d85fb 100644 --- a/tests/unit/10_ps-herokuish.bats +++ b/tests/unit/10_ps-herokuish.bats @@ -137,7 +137,7 @@ teardown() { echo "status: "$status assert_success - run bash -c "dokku apps" + run bash -c "dokku apps:list" echo "output: "$output echo "status: "$status assert_success From 92d6e76ea4580ec291516931a3c8dfcc63ad209d Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 22 Jul 2017 15:25:58 -0600 Subject: [PATCH 34/66] fix: ignore vendor directories --- .stickler.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.stickler.yml b/.stickler.yml index bb59453ca..0304acb21 100644 --- a/.stickler.yml +++ b/.stickler.yml @@ -7,3 +7,4 @@ linters: files: ignore: - './debian/*' + - '*/vendor/*' From 3b9f4ba63f06e946145aadd127fd2d38457b9e07 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 22 Jul 2017 15:26:23 -0600 Subject: [PATCH 35/66] fix: correct lint errors --- plugins/config/config.go | 1 + plugins/network/network.go | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/plugins/config/config.go b/plugins/config/config.go index 821862113..b5366390e 100644 --- a/plugins/config/config.go +++ b/plugins/config/config.go @@ -7,6 +7,7 @@ import ( common "github.com/dokku/dokku/plugins/common" ) +// GetWithDefault returns the value set for a given key, returning defaultValue if none found func GetWithDefault(appName string, key string, defaultValue string) string { envFile := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName, "ENV"}, "/") lines, err := common.FileToSlice(envFile) diff --git a/plugins/network/network.go b/plugins/network/network.go index e195ecf97..244c1e52f 100644 --- a/plugins/network/network.go +++ b/plugins/network/network.go @@ -12,8 +12,8 @@ import ( sh "github.com/codeskyblue/go-sh" ) -// return the ipaddr for a given app container -func GetContainerIpaddress(appName string, procType string, isHerokuishContainer bool, containerId string) string { +// GetContainerIpaddress returns the ipaddr for a given app container +func GetContainerIpaddress(appName string, procType string, isHerokuishContainer bool, containerID string) string { if procType != "web" { return "" } @@ -23,10 +23,10 @@ func GetContainerIpaddress(appName string, procType string, isHerokuishContainer return ipAddress } - b, err := common.DockerInspect(containerId, "'{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'") + b, err := common.DockerInspect(containerID, "'{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'") if err != nil || len(b) == 0 { // docker < 1.9 compatibility - b, err = common.DockerInspect(containerId, "'{{ .NetworkSettings.IPAddress }}'") + b, err = common.DockerInspect(containerID, "'{{ .NetworkSettings.IPAddress }}'") } if err == nil { @@ -36,8 +36,8 @@ func GetContainerIpaddress(appName string, procType string, isHerokuishContainer return "" } -// return the port for a given app container -func GetContainerPort(appName string, procType string, isHerokuishContainer bool, containerId string) string { +// GetContainerPort returns the port for a given app container +func GetContainerPort(appName string, procType string, isHerokuishContainer bool, containerID string) string { if procType != "web" { return "" } @@ -67,7 +67,7 @@ func GetContainerPort(appName string, procType string, isHerokuishContainer bool } if !proxy.IsAppProxyEnabled(appName) { - b, err := sh.Command("docker", "port", containerId, port).Output() + b, err := sh.Command("docker", "port", containerID, port).Output() if err == nil { port = strings.Split(string(b[:]), ":")[1] } @@ -76,7 +76,7 @@ func GetContainerPort(appName string, procType string, isHerokuishContainer bool return port } -// builds network config files +// BuildConfig builds network config files func BuildConfig(appName string) { err := common.VerifyAppName(appName) if err != nil { @@ -118,16 +118,16 @@ func BuildConfig(appName string) { containerIndex := 0 for containerIndex < procCount { - containerIndex += 1 - containerIdFile := fmt.Sprintf("%v/CONTAINER.%v.%v", appRoot, procType, containerIndex) + containerIndex++ + containerIDFile := fmt.Sprintf("%v/CONTAINER.%v.%v", appRoot, procType, containerIndex) - containerId := common.ReadFirstLine(containerIdFile) - if containerId == "" || !common.ContainerIsRunning(containerId) { + containerID := common.ReadFirstLine(containerIDFile) + if containerID == "" || !common.ContainerIsRunning(containerID) { continue } - ipAddress := GetContainerIpaddress(appName, procType, isHerokuishContainer, containerId) - port := GetContainerPort(appName, procType, isHerokuishContainer, containerId) + ipAddress := GetContainerIpaddress(appName, procType, isHerokuishContainer, containerID) + port := GetContainerPort(appName, procType, isHerokuishContainer, containerID) if ipAddress != "" { _, err := sh.Command("plugn", "trigger", "network-write-ipaddr", appName, procType, containerIndex, ipAddress).Output() From a3abf8016b6ff1afabf2fd5b35994cb81a9de9a7 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 22 Jul 2017 15:32:14 -0600 Subject: [PATCH 36/66] fix: correct lint errors --- plugins/common/common.go | 12 ++++++------ .../network-get-ipaddr/network-get-ipaddr.go | 4 ++-- .../triggers/network-get-port/network-get-port.go | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/plugins/common/common.go b/plugins/common/common.go index 97a7b2e1e..eae0e0df0 100644 --- a/plugins/common/common.go +++ b/plugins/common/common.go @@ -157,8 +157,8 @@ func VerifyImage(image string) bool { } // ContainerIsRunning checks to see if a container is running -func ContainerIsRunning(containerId string) bool { - b, err := DockerInspect(containerId, "'{{.State.Running}}'") +func ContainerIsRunning(containerID string) bool { + b, err := DockerInspect(containerID, "'{{.State.Running}}'") if err != nil { return false } @@ -176,13 +176,13 @@ func DirectoryExists(filePath string) bool { } // DockerInspect runs an inspect command with a given format against a container id -func DockerInspect(containerId, format string) (string, error) { - b, err := sh.Command("docker", "inspect", "--format", format, containerId).Output() +func DockerInspect(containerID, format string) (string, error) { + b, err := sh.Command("docker", "inspect", "--format", format, containerID).Output() if err != nil { return "", err } output := strings.TrimSpace(string(b[:])) - if strings.HasPrefix(output, "'") && strings.HasSuffix(output, "'") { + if strings.HasPrefix(output, "'") && strings.HasSuffix(output, "'") { output = strings.TrimSuffix(strings.TrimPrefix(output, "'"), "'") } return output, err @@ -270,7 +270,7 @@ func GetAppImageName(appName, imageTag, imageRepo string) (imageName string) { return } -// return true if given app has a running container +// IsDeployed returns true if given app has a running container func IsDeployed(appName string) bool { dokkuRoot := MustGetEnv("DOKKU_ROOT") appRoot := strings.Join([]string{dokkuRoot, appName}, "/") diff --git a/plugins/network/src/triggers/network-get-ipaddr/network-get-ipaddr.go b/plugins/network/src/triggers/network-get-ipaddr/network-get-ipaddr.go index 270a208a1..46010bbb0 100644 --- a/plugins/network/src/triggers/network-get-ipaddr/network-get-ipaddr.go +++ b/plugins/network/src/triggers/network-get-ipaddr/network-get-ipaddr.go @@ -15,8 +15,8 @@ func main() { appName := flag.Arg(0) procType := flag.Arg(1) isHerokuishContainer := common.ToBool(flag.Arg(2)) - containerId := flag.Arg(3) + containerID := flag.Arg(3) - ipAddress := network.GetContainerIpaddress(appName, procType, isHerokuishContainer, containerId) + ipAddress := network.GetContainerIpaddress(appName, procType, isHerokuishContainer, containerID) fmt.Fprintln(os.Stdout, ipAddress) } diff --git a/plugins/network/src/triggers/network-get-port/network-get-port.go b/plugins/network/src/triggers/network-get-port/network-get-port.go index bf695d34f..9ac850788 100644 --- a/plugins/network/src/triggers/network-get-port/network-get-port.go +++ b/plugins/network/src/triggers/network-get-port/network-get-port.go @@ -15,8 +15,8 @@ func main() { appName := flag.Arg(0) procType := flag.Arg(1) isHerokuishContainer := common.ToBool(flag.Arg(2)) - containerId := flag.Arg(3) + containerID := flag.Arg(3) - port := network.GetContainerPort(appName, procType, isHerokuishContainer, containerId) + port := network.GetContainerPort(appName, procType, isHerokuishContainer, containerID) fmt.Fprintln(os.Stdout, port) } From 27f247eabd9d42ea11e95363284f59fabeb4398f Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 22 Jul 2017 15:35:34 -0600 Subject: [PATCH 37/66] fix: do not shadow id variable --- plugins/common/functions | 48 ++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/plugins/common/functions b/plugins/common/functions index de06c03c5..57f231825 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -450,19 +450,19 @@ dokku_build() { case "$IMAGE_SOURCE_TYPE" in herokuish) DOKKU_IMAGE="$(config_get "$APP" DOKKU_IMAGE || echo "$DOKKU_IMAGE")" - local id=$(tar -c . | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$DOKKU_IMAGE" /bin/bash -c "mkdir -p /app && tar -xC /app") - test "$(docker wait "$id")" -eq 0 - docker commit "$id" "$IMAGE" > /dev/null + local cid=$(tar -c . | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$DOKKU_IMAGE" /bin/bash -c "mkdir -p /app && tar -xC /app") + test "$(docker wait "$cid")" -eq 0 + docker commit "$cid" "$IMAGE" > /dev/null [[ -d $CACHE_DIR ]] || mkdir -p "$CACHE_DIR" plugn trigger pre-build-buildpack "$APP" local DOCKER_ARGS=$(: | plugn trigger docker-args-build "$APP" "$IMAGE_SOURCE_TYPE") [[ "$DOKKU_TRACE" ]] && DOCKER_ARGS+=" -e TRACE=true " # shellcheck disable=SC2086 - local id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -v $CACHE_DIR:/cache -e CACHE_PATH=/cache $DOCKER_ARGS $IMAGE /build) - docker attach "$id" - test "$(docker wait "$id")" -eq 0 - docker commit "$id" "$IMAGE" > /dev/null + local cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -v $CACHE_DIR:/cache -e CACHE_PATH=/cache $DOCKER_ARGS $IMAGE /build) + docker attach "$cid" + test "$(docker wait "$cid")" -eq 0 + docker commit "$cid" "$IMAGE" > /dev/null plugn trigger post-build-buildpack "$APP" ;; @@ -507,14 +507,14 @@ dokku_release() { herokuish) plugn trigger pre-release-buildpack "$APP" "$IMAGE_TAG" if [[ -n $(config_export global) ]]; then - local id=$(config_export global | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$IMAGE" /bin/bash -c "mkdir -p /app/.profile.d && cat > /app/.profile.d/00-global-env.sh") - test "$(docker wait "$id")" -eq 0 - docker commit "$id" "$IMAGE" > /dev/null + local cid=$(config_export global | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$IMAGE" /bin/bash -c "mkdir -p /app/.profile.d && cat > /app/.profile.d/00-global-env.sh") + test "$(docker wait "$cid")" -eq 0 + docker commit "$cid" "$IMAGE" > /dev/null fi if [[ -n $(config_export app "$APP") ]]; then - local id=$(config_export app "$APP" | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$IMAGE" /bin/bash -c "mkdir -p /app/.profile.d && cat > /app/.profile.d/01-app-env.sh") - test "$(docker wait "$id")" -eq 0 - docker commit "$id" "$IMAGE" > /dev/null + local cid=$(config_export app "$APP" | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$IMAGE" /bin/bash -c "mkdir -p /app/.profile.d && cat > /app/.profile.d/01-app-env.sh") + test "$(docker wait "$cid")" -eq 0 + docker commit "$cid" "$IMAGE" > /dev/null fi plugn trigger post-release-buildpack "$APP" "$IMAGE_TAG" ;; @@ -575,7 +575,7 @@ dokku_deploy_cmd() { fi while [[ $CONTAINER_INDEX -le $PROC_COUNT ]]; do - local id=""; local port=""; local ipaddr="" + local cid=""; local port=""; local ipaddr="" local DOKKU_CONTAINER_ID_FILE="$DOKKU_ROOT/$APP/CONTAINER.$PROC_TYPE.$CONTAINER_INDEX" # start the app @@ -605,37 +605,37 @@ dokku_deploy_cmd() { if [[ "$DOKKU_IS_APP_PROXY_ENABLED" == "true" ]]; then # shellcheck disable=SC2086 - local id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -e PORT=$DOKKU_PORT $DOCKER_ARGS $IMAGE $START_CMD) + local cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -e PORT=$DOKKU_PORT $DOCKER_ARGS $IMAGE $START_CMD) else # shellcheck disable=SC2086 - local id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOKKU_DOCKER_PORT_ARGS -e PORT=$DOKKU_PORT $DOCKER_ARGS $IMAGE $START_CMD) + local cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOKKU_DOCKER_PORT_ARGS -e PORT=$DOKKU_PORT $DOCKER_ARGS $IMAGE $START_CMD) fi else # shellcheck disable=SC2086 - local id=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOCKER_ARGS $IMAGE $START_CMD) + local cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOCKER_ARGS $IMAGE $START_CMD) fi - ipaddr=$(plugn trigger network-get-ipaddr "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH" "$id") - port=$(plugn trigger network-get-port "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH" "$id") + ipaddr=$(plugn trigger network-get-ipaddr "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH" "$cid") + port=$(plugn trigger network-get-port "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH" "$cid") kill_new() { declare desc="wrapper function to kill newly started app container" - local id="$1" - docker inspect "$id" &> /dev/null && docker stop "$id" > /dev/null && docker kill "$id" &> /dev/null + local cid="$1" + docker inspect "$cid" &> /dev/null && docker stop "$cid" > /dev/null && docker kill "$cid" &> /dev/null trap - INT TERM EXIT kill -9 $$ } # run checks first, then post-deploy hooks, which switches proxy traffic - trap 'kill_new $id' INT TERM EXIT + trap 'kill_new $cid' INT TERM EXIT if [[ "$(is_app_proctype_checks_disabled "$APP" "$PROC_TYPE")" == "false" ]]; then dokku_log_info1 "Attempting pre-flight checks" - plugn trigger check-deploy "$APP" "$id" "$PROC_TYPE" "$port" "$ipaddr" + plugn trigger check-deploy "$APP" "$cid" "$PROC_TYPE" "$port" "$ipaddr" fi trap - INT TERM EXIT # now using the new container - [[ -n "$id" ]] && echo "$id" > "$DOKKU_CONTAINER_ID_FILE" + [[ -n "$cid" ]] && echo "$cid" > "$DOKKU_CONTAINER_ID_FILE" [[ -n "$ipaddr" ]] && plugn trigger network-write-ipaddr "$APP" "$PROC_TYPE" "$CONTAINER_INDEX" "$ipaddr" [[ -n "$port" ]] && plugn trigger network-write-port "$APP" "$PROC_TYPE" "$CONTAINER_INDEX" "$port" From 1372522258d7c75169659c3b73565b6fd47c5431 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 22 Jul 2017 15:36:43 -0600 Subject: [PATCH 38/66] fix: Declare and assign separately to avoid masking return values. --- plugins/common/functions | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/plugins/common/functions b/plugins/common/functions index 57f231825..8647ab403 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -440,6 +440,7 @@ dokku_build() { source "$PLUGIN_AVAILABLE_PATH/config/functions" local APP="$1"; local IMAGE_SOURCE_TYPE="$2"; local TMP_WORK_DIR="$3"; local IMAGE=$(get_app_image_name "$APP") + local cid verify_app_name "$APP" local CACHE_DIR="$DOKKU_ROOT/$APP/cache" @@ -450,7 +451,7 @@ dokku_build() { case "$IMAGE_SOURCE_TYPE" in herokuish) DOKKU_IMAGE="$(config_get "$APP" DOKKU_IMAGE || echo "$DOKKU_IMAGE")" - local cid=$(tar -c . | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$DOKKU_IMAGE" /bin/bash -c "mkdir -p /app && tar -xC /app") + cid=$(tar -c . | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$DOKKU_IMAGE" /bin/bash -c "mkdir -p /app && tar -xC /app") test "$(docker wait "$cid")" -eq 0 docker commit "$cid" "$IMAGE" > /dev/null [[ -d $CACHE_DIR ]] || mkdir -p "$CACHE_DIR" @@ -459,7 +460,7 @@ dokku_build() { local DOCKER_ARGS=$(: | plugn trigger docker-args-build "$APP" "$IMAGE_SOURCE_TYPE") [[ "$DOKKU_TRACE" ]] && DOCKER_ARGS+=" -e TRACE=true " # shellcheck disable=SC2086 - local cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -v $CACHE_DIR:/cache -e CACHE_PATH=/cache $DOCKER_ARGS $IMAGE /build) + cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -v $CACHE_DIR:/cache -e CACHE_PATH=/cache $DOCKER_ARGS $IMAGE /build) docker attach "$cid" test "$(docker wait "$cid")" -eq 0 docker commit "$cid" "$IMAGE" > /dev/null @@ -501,18 +502,19 @@ dokku_release() { source "$PLUGIN_AVAILABLE_PATH/config/functions" local APP="$1"; local IMAGE_SOURCE_TYPE="$2"; local IMAGE_TAG="$3"; local IMAGE=$(get_app_image_name "$APP" "$IMAGE_TAG") + local cid verify_app_name "$APP" case "$IMAGE_SOURCE_TYPE" in herokuish) plugn trigger pre-release-buildpack "$APP" "$IMAGE_TAG" if [[ -n $(config_export global) ]]; then - local cid=$(config_export global | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$IMAGE" /bin/bash -c "mkdir -p /app/.profile.d && cat > /app/.profile.d/00-global-env.sh") + cid=$(config_export global | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$IMAGE" /bin/bash -c "mkdir -p /app/.profile.d && cat > /app/.profile.d/00-global-env.sh") test "$(docker wait "$cid")" -eq 0 docker commit "$cid" "$IMAGE" > /dev/null fi if [[ -n $(config_export app "$APP") ]]; then - local cid=$(config_export app "$APP" | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$IMAGE" /bin/bash -c "mkdir -p /app/.profile.d && cat > /app/.profile.d/01-app-env.sh") + cid=$(config_export app "$APP" | docker run "$DOKKU_GLOBAL_RUN_ARGS" -i -a stdin "$IMAGE" /bin/bash -c "mkdir -p /app/.profile.d && cat > /app/.profile.d/01-app-env.sh") test "$(docker wait "$cid")" -eq 0 docker commit "$cid" "$IMAGE" > /dev/null fi @@ -605,14 +607,14 @@ dokku_deploy_cmd() { if [[ "$DOKKU_IS_APP_PROXY_ENABLED" == "true" ]]; then # shellcheck disable=SC2086 - local cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -e PORT=$DOKKU_PORT $DOCKER_ARGS $IMAGE $START_CMD) + cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -e PORT=$DOKKU_PORT $DOCKER_ARGS $IMAGE $START_CMD) else # shellcheck disable=SC2086 - local cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOKKU_DOCKER_PORT_ARGS -e PORT=$DOKKU_PORT $DOCKER_ARGS $IMAGE $START_CMD) + cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOKKU_DOCKER_PORT_ARGS -e PORT=$DOKKU_PORT $DOCKER_ARGS $IMAGE $START_CMD) fi else # shellcheck disable=SC2086 - local cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOCKER_ARGS $IMAGE $START_CMD) + cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOCKER_ARGS $IMAGE $START_CMD) fi ipaddr=$(plugn trigger network-get-ipaddr "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH" "$cid") From 44282fd857ef8f42a3e85ba67553490e6437b36a Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 22 Jul 2017 15:38:32 -0600 Subject: [PATCH 39/66] fix: Declare and assign separately to avoid masking return values. --- plugins/nginx-vhosts/functions | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/nginx-vhosts/functions b/plugins/nginx-vhosts/functions index 20950e2c8..da6af4c29 100755 --- a/plugins/nginx-vhosts/functions +++ b/plugins/nginx-vhosts/functions @@ -222,14 +222,15 @@ nginx_build_config() { local NGINX_TEMPLATE="$DEFAULT_NGINX_TEMPLATE"; local SCHEME=http local NGINX_TEMPLATE_SOURCE="built-in"; local APP_SSL_PATH="$DOKKU_ROOT/$APP/tls" local RAW_TCP_PORTS="$(get_app_raw_tcp_ports "$APP")" + local DOKKU_APP_LISTENERS local DOKKU_DISABLE_PROXY=$(config_get "$APP" DOKKU_DISABLE_PROXY) local IS_APP_VHOST_ENABLED=$(is_app_vhost_enabled "$APP") if [[ -z "$DOKKU_DISABLE_PROXY" ]]; then if [[ -z "$DOKKU_APP_LISTEN_PORT" ]] && [[ -z "$DOKKU_APP_LISTEN_IP" ]]; then - local DOKKU_APP_LISTENERS="$(plugn trigger network-get-listeners "$APP")" - local DOKKU_APP_LISTENERS="$(echo "$DOKKU_APP_LISTENERS" | xargs)" + DOKKU_APP_LISTENERS="$(plugn trigger network-get-listeners "$APP")" + DOKKU_APP_LISTENERS="$(echo "$DOKKU_APP_LISTENERS" | xargs)" elif [[ -n "$DOKKU_APP_LISTEN_PORT" ]] && [[ -n "$DOKKU_APP_LISTEN_IP" ]]; then local PASSED_LISTEN_IP_PORT=true fi From 647efab2dda3f851c53f7217f5f3e03868542824 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 22 Jul 2017 16:54:55 -0600 Subject: [PATCH 40/66] fix: ensure we always call proxy-build-config when calling ps_start --- plugins/ps/functions | 1 + plugins/ps/subcommands/restore | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/ps/functions b/plugins/ps/functions index f9f094df8..25f6ce401 100755 --- a/plugins/ps/functions +++ b/plugins/ps/functions @@ -98,6 +98,7 @@ ps_start() { else echo "App $APP already running" fi + plugn trigger proxy-build-config "$APP" else echo "App $APP has not been deployed" fi diff --git a/plugins/ps/subcommands/restore b/plugins/ps/subcommands/restore index db501f751..818c3d980 100755 --- a/plugins/ps/subcommands/restore +++ b/plugins/ps/subcommands/restore @@ -10,7 +10,6 @@ ps_restore_cmd() { if which parallel > /dev/null 2>&1; then dokku_apps | parallel dokku ps:start - dokku_apps | parallel plugn trigger proxy-build-config return fi @@ -19,7 +18,6 @@ ps_restore_cmd() { if [[ $DOKKU_APP_RESTORE != 0 ]]; then echo "Restoring app $app ..." if ps_start "$app"; then - plugn trigger proxy-build-config "$app" continue fi dokku_log_warn "dokku ps:restore ${app} failed" From 20dca9fd46bf9dbecd7ccfcde5a473e162c5f1cb Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 3 Sep 2017 00:11:34 -0400 Subject: [PATCH 41/66] fix: convert the containerIndex to a string to ensure it is properly passed to the plugn trigger --- plugins/network/network.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/network/network.go b/plugins/network/network.go index 244c1e52f..80bb3eaec 100644 --- a/plugins/network/network.go +++ b/plugins/network/network.go @@ -119,6 +119,7 @@ func BuildConfig(appName string) { containerIndex := 0 for containerIndex < procCount { containerIndex++ + containerIndexString := strconv.Itoa(containerIndex) containerIDFile := fmt.Sprintf("%v/CONTAINER.%v.%v", appRoot, procType, containerIndex) containerID := common.ReadFirstLine(containerIDFile) @@ -130,14 +131,14 @@ func BuildConfig(appName string) { port := GetContainerPort(appName, procType, isHerokuishContainer, containerID) if ipAddress != "" { - _, err := sh.Command("plugn", "trigger", "network-write-ipaddr", appName, procType, containerIndex, ipAddress).Output() + _, err := sh.Command("plugn", "trigger", "network-write-ipaddr", appName, procType, containerIndexString, ipAddress).Output() if err != nil { common.LogWarn(err.Error()) } } if port != "" { - _, err := sh.Command("plugn", "trigger", "network-write-port", appName, procType, containerIndex, port).Output() + _, err := sh.Command("plugn", "trigger", "network-write-port", appName, procType, containerIndexString, port).Output() if err != nil { common.LogWarn(err.Error()) } From 7fe2f8593c9ceac0e10c0b057b79d7e7ec09b442 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 3 Sep 2017 00:48:03 -0400 Subject: [PATCH 42/66] refactor: remove unused IS_HEROKUISH_CONTAINER argument --- plugins/checks/subcommands/run | 2 +- plugins/common/functions | 2 +- plugins/network/network.go | 4 ++-- .../src/triggers/network-get-ipaddr/network-get-ipaddr.go | 6 ++---- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/plugins/checks/subcommands/run b/plugins/checks/subcommands/run index dd9c6f935..d1f9bdfc7 100755 --- a/plugins/checks/subcommands/run +++ b/plugins/checks/subcommands/run @@ -58,7 +58,7 @@ check_process() { dokku_log_info1 "Running checks for app ($APP.$PROC_TYPE.$CONTAINER_INDEX)" CONTAINER_ID=$(< "$DOKKU_CONTAINER_ID_FILE") - IP="$(plugn trigger network-get-ipaddr "$APP" "$PROC_TYPE" "$IS_HEROKUISH_CONTAINER" "$CONTAINER_ID")" + IP="$(plugn trigger network-get-ipaddr "$APP" "$PROC_TYPE" "$CONTAINER_ID")" PORT="$(plugn trigger network-get-port "$APP" "$PROC_TYPE" "$IS_HEROKUISH_CONTAINER" "$CONTAINER_ID")" plugn trigger check-deploy "$APP" "$CONTAINER_ID" "$PROC_TYPE" "$PORT" "$IP" } diff --git a/plugins/common/functions b/plugins/common/functions index 8647ab403..000c73f77 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -617,7 +617,7 @@ dokku_deploy_cmd() { cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d $DOCKER_ARGS $IMAGE $START_CMD) fi - ipaddr=$(plugn trigger network-get-ipaddr "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH" "$cid") + ipaddr=$(plugn trigger network-get-ipaddr "$APP" "$PROC_TYPE" "$cid") port=$(plugn trigger network-get-port "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH" "$cid") kill_new() { diff --git a/plugins/network/network.go b/plugins/network/network.go index 80bb3eaec..69979c1d1 100644 --- a/plugins/network/network.go +++ b/plugins/network/network.go @@ -13,7 +13,7 @@ import ( ) // GetContainerIpaddress returns the ipaddr for a given app container -func GetContainerIpaddress(appName string, procType string, isHerokuishContainer bool, containerID string) string { +func GetContainerIpaddress(appName string, procType string, containerID string) string { if procType != "web" { return "" } @@ -127,7 +127,7 @@ func BuildConfig(appName string) { continue } - ipAddress := GetContainerIpaddress(appName, procType, isHerokuishContainer, containerID) + ipAddress := GetContainerIpaddress(appName, procType, containerID) port := GetContainerPort(appName, procType, isHerokuishContainer, containerID) if ipAddress != "" { diff --git a/plugins/network/src/triggers/network-get-ipaddr/network-get-ipaddr.go b/plugins/network/src/triggers/network-get-ipaddr/network-get-ipaddr.go index 46010bbb0..5d06c9330 100644 --- a/plugins/network/src/triggers/network-get-ipaddr/network-get-ipaddr.go +++ b/plugins/network/src/triggers/network-get-ipaddr/network-get-ipaddr.go @@ -5,7 +5,6 @@ import ( "fmt" "os" - common "github.com/dokku/dokku/plugins/common" network "github.com/dokku/dokku/plugins/network" ) @@ -14,9 +13,8 @@ func main() { flag.Parse() appName := flag.Arg(0) procType := flag.Arg(1) - isHerokuishContainer := common.ToBool(flag.Arg(2)) - containerID := flag.Arg(3) + containerID := flag.Arg(2) - ipAddress := network.GetContainerIpaddress(appName, procType, isHerokuishContainer, containerID) + ipAddress := network.GetContainerIpaddress(appName, procType, containerID) fmt.Fprintln(os.Stdout, ipAddress) } From dc00542b6f36779f7ddef867518e3d25d41e92ac Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 3 Sep 2017 03:07:24 -0400 Subject: [PATCH 43/66] refactor: remove use of IP and PORT files from core-post-deploy plugin --- docs/development/plugin-triggers.md | 15 +++++++++++++ plugins/network/Makefile | 5 ++++- plugins/network/network.go | 9 ++++++++ .../network-config-exists.go | 22 +++++++++++++++++++ plugins/nginx-vhosts/core-post-deploy | 5 ++++- 5 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 plugins/network/src/triggers/network-config-exists/network-config-exists.go diff --git a/docs/development/plugin-triggers.md b/docs/development/plugin-triggers.md index 0fba2a33a..5b560f956 100644 --- a/docs/development/plugin-triggers.md +++ b/docs/development/plugin-triggers.md @@ -374,6 +374,21 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +### `network-config-exists` + +- Description: Returns whether the network configuration for a given app exists +- Invoked by: `internally triggered by core-post-deploy within proxy implementations` +- Arguments: `$APP` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `network-get-ipaddr` - Description: Return the ipaddr for a given app container diff --git a/plugins/network/Makefile b/plugins/network/Makefile index 0eab0fd09..5a8a6f690 100644 --- a/plugins/network/Makefile +++ b/plugins/network/Makefile @@ -9,7 +9,7 @@ build-in-docker: clean build: commands subcommands triggers subcommands: subcommands/rebuild subcommands/rebuildall -triggers: network-build-config network-compute-ports network-get-ipaddr network-get-listeners network-get-port network-write-ipaddr network-write-port +triggers: network-build-config network-compute-ports network-config-exists network-get-ipaddr network-get-listeners network-get-port network-write-ipaddr network-write-port commands: **/**/commands.go go build -a -o commands src/commands/commands.go @@ -25,6 +25,9 @@ network-build-config: **/**/**/network-build-config.go network-compute-ports: **/**/**/network-compute-ports.go go build -a -o network-compute-ports src/triggers/network-compute-ports/network-compute-ports.go +network-config-exists: **/**/**/network-config-exists.go + go build -a -o network-config-exists src/triggers/network-config-exists/network-config-exists.go + network-get-ipaddr: **/**/**/network-get-ipaddr.go go build -a -o network-get-ipaddr src/triggers/network-get-ipaddr/network-get-ipaddr.go diff --git a/plugins/network/network.go b/plugins/network/network.go index 69979c1d1..4e48f3f7a 100644 --- a/plugins/network/network.go +++ b/plugins/network/network.go @@ -76,6 +76,15 @@ func GetContainerPort(appName string, procType string, isHerokuishContainer bool return port } +// HasNetworkConfig returns whether the network configuration for a given app exists +func HasNetworkConfig(appName string) bool { + appRoot := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName}, "/") + ipfile := fmt.Sprintf("%v/IP.web.1", appRoot) + portfile := fmt.Sprintf("%v/PORT.web.1", appRoot) + + return common.FileExists(ipfile) && common.FileExists(portfile) +} + // BuildConfig builds network config files func BuildConfig(appName string) { err := common.VerifyAppName(appName) diff --git a/plugins/network/src/triggers/network-config-exists/network-config-exists.go b/plugins/network/src/triggers/network-config-exists/network-config-exists.go new file mode 100644 index 000000000..43efedc95 --- /dev/null +++ b/plugins/network/src/triggers/network-config-exists/network-config-exists.go @@ -0,0 +1,22 @@ +package main + +import ( + "flag" + "fmt" + "os" + + network "github.com/dokku/dokku/plugins/network" +) + +// write the ipaddress to stdout for a given app container +func main() { + flag.Parse() + appName := flag.Arg(0) + + if network.HasNetworkConfig(appName) { + fmt.Fprintln(os.Stdout, "true") + return + } + + fmt.Fprintln(os.Stdout, "false") +} diff --git a/plugins/nginx-vhosts/core-post-deploy b/plugins/nginx-vhosts/core-post-deploy index 7bb7db877..493fd2da2 100755 --- a/plugins/nginx-vhosts/core-post-deploy +++ b/plugins/nginx-vhosts/core-post-deploy @@ -9,7 +9,10 @@ nginx_core_post_deploy() { declare desc="nginx-vhosts core-post-deploy plugin trigger" local trigger="nginx_core_post_deploy" local APP="$1" - if [[ -f "$DOKKU_ROOT/$APP/IP.web.1" ]] && [[ -f "$DOKKU_ROOT/$APP/PORT.web.1" ]]; then + local HAS_NETWORK_CONFIG + + HAS_NETWORK_CONFIG="$(plugn trigger network-config-exists "$APP")" + if [[ "$HAS_NETWORK_CONFIG" == "true" ]]; then if [[ "$(is_app_vhost_enabled "$APP")" == "false" ]]; then dokku_log_info1 "VHOST support disabled. Skipping domains setup" elif [[ ! -f "$DOKKU_ROOT/$APP/VHOST" ]]; then From 9ef7c385d4a959afcde8ef5b0120e1922a3b00a1 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 3 Sep 2017 03:22:17 -0400 Subject: [PATCH 44/66] refactor: drop extra sub-shell --- plugins/nginx-vhosts/functions | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/nginx-vhosts/functions b/plugins/nginx-vhosts/functions index da6af4c29..06c662be6 100755 --- a/plugins/nginx-vhosts/functions +++ b/plugins/nginx-vhosts/functions @@ -229,8 +229,7 @@ nginx_build_config() { if [[ -z "$DOKKU_DISABLE_PROXY" ]]; then if [[ -z "$DOKKU_APP_LISTEN_PORT" ]] && [[ -z "$DOKKU_APP_LISTEN_IP" ]]; then - DOKKU_APP_LISTENERS="$(plugn trigger network-get-listeners "$APP")" - DOKKU_APP_LISTENERS="$(echo "$DOKKU_APP_LISTENERS" | xargs)" + DOKKU_APP_LISTENERS="$(plugn trigger network-get-listeners "$APP" | xargs)" elif [[ -n "$DOKKU_APP_LISTEN_PORT" ]] && [[ -n "$DOKKU_APP_LISTEN_IP" ]]; then local PASSED_LISTEN_IP_PORT=true fi From 81ddef04ffda3f28c395401762680772efa4efd4 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 3 Sep 2017 03:45:11 -0400 Subject: [PATCH 45/66] refactor: drop direct access to PORT files --- plugins/common/functions | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/common/functions b/plugins/common/functions index 000c73f77..1e63395e4 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -1040,9 +1040,11 @@ get_app_urls() { echo "$(< "$DOKKU_ROOT/HOSTNAME"):$app_port (container)" done else - shopt -s nullglob - for PORT_FILE in $DOKKU_ROOT/$APP/PORT.*; do - echo "$SCHEME://$(< "$DOKKU_ROOT/HOSTNAME"):$(< "$PORT_FILE") (container)" + local DOKKU_APP_LISTENERS PORT + DOKKU_APP_LISTENERS="$(plugn trigger network-get-listeners "$APP" | xargs)" + for DOKKU_APP_LISTENER in $DOKKU_APP_LISTENERS; do + PORT="$(echo "$DOKKU_APP_LISTENER" | cut -d ':' -f2)" + echo "$SCHEME://$(< "$DOKKU_ROOT/HOSTNAME"):$PORT (container)" done shopt -u nullglob fi From af13d1247f4bd0176fe2162c9012c4e4c83aa06a Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 3 Sep 2017 05:30:47 -0400 Subject: [PATCH 46/66] refactor: consolidate DOKKU_DISABLE_PROXY usage into the proxy plugin --- plugins/nginx-vhosts/functions | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/nginx-vhosts/functions b/plugins/nginx-vhosts/functions index 06c662be6..8fda14a13 100755 --- a/plugins/nginx-vhosts/functions +++ b/plugins/nginx-vhosts/functions @@ -4,6 +4,7 @@ source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" source "$PLUGIN_AVAILABLE_PATH/certs/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" source "$PLUGIN_AVAILABLE_PATH/domains/functions" +source "$PLUGIN_AVAILABLE_PATH/proxy/functions" source "$PLUGIN_AVAILABLE_PATH/ps/functions" get_nginx_location() { @@ -224,10 +225,9 @@ nginx_build_config() { local RAW_TCP_PORTS="$(get_app_raw_tcp_ports "$APP")" local DOKKU_APP_LISTENERS - local DOKKU_DISABLE_PROXY=$(config_get "$APP" DOKKU_DISABLE_PROXY) local IS_APP_VHOST_ENABLED=$(is_app_vhost_enabled "$APP") - if [[ -z "$DOKKU_DISABLE_PROXY" ]]; then + if [[ "$(is_app_proxy_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 @@ -333,7 +333,7 @@ nginx_build_config() { xargs -i echo "$SCHEME://{}" <<< "$(echo "${SSL_VHOSTS}" "${NONSSL_VHOSTS}" | tr ' ' '\n' | sort -u)" >> "$URLS_PATH" fi else - # note because this clause is long. if $DOKKU_DISABLE_PROXY is set: + # note because this clause is long. if the proxy is disabled: dokku_log_info1 "nginx support is disabled for app ($APP)." if [[ -f "$DOKKU_ROOT/$APP/nginx.conf" ]]; then dokku_log_info1 "deleting nginx.conf" From 9d287d0a5d244c4a3f2814bb394b026aca46a6ca Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 3 Sep 2017 17:31:14 -0400 Subject: [PATCH 47/66] refactor: alphabetize functions --- plugins/network/network.go | 142 ++++++++++++++++++------------------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/plugins/network/network.go b/plugins/network/network.go index 4e48f3f7a..cbe66400c 100644 --- a/plugins/network/network.go +++ b/plugins/network/network.go @@ -12,6 +12,77 @@ import ( sh "github.com/codeskyblue/go-sh" ) +// BuildConfig builds network config files +func BuildConfig(appName string) { + err := common.VerifyAppName(appName) + if err != nil { + common.LogFail(err.Error()) + } + + if !common.IsDeployed(appName) { + return + } + + appRoot := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName}, "/") + scaleFile := strings.Join([]string{appRoot, "DOKKU_SCALE"}, "/") + if !common.FileExists(scaleFile) { + return + } + + image := common.GetAppImageName(appName, "", "") + isHerokuishContainer := common.IsImageHerokuishBased(image) + + common.LogInfo1(fmt.Sprintf("Ensuring network configuration is in sync for %s", appName)) + lines, err := common.FileToSlice(scaleFile) + if err != nil { + return + } + for _, line := range lines { + if line == "" || strings.HasPrefix(line, "#") { + continue + } + + procParts := strings.SplitN(line, "=", 2) + if len(procParts) != 2 { + continue + } + procType := procParts[0] + procCount, err := strconv.Atoi(procParts[1]) + if err != nil { + continue + } + + containerIndex := 0 + for containerIndex < procCount { + containerIndex++ + containerIndexString := strconv.Itoa(containerIndex) + containerIDFile := fmt.Sprintf("%v/CONTAINER.%v.%v", appRoot, procType, containerIndex) + + containerID := common.ReadFirstLine(containerIDFile) + if containerID == "" || !common.ContainerIsRunning(containerID) { + continue + } + + ipAddress := GetContainerIpaddress(appName, procType, containerID) + port := GetContainerPort(appName, procType, isHerokuishContainer, containerID) + + if ipAddress != "" { + _, err := sh.Command("plugn", "trigger", "network-write-ipaddr", appName, procType, containerIndexString, ipAddress).Output() + if err != nil { + common.LogWarn(err.Error()) + } + } + + if port != "" { + _, err := sh.Command("plugn", "trigger", "network-write-port", appName, procType, containerIndexString, port).Output() + if err != nil { + common.LogWarn(err.Error()) + } + } + } + } +} + // GetContainerIpaddress returns the ipaddr for a given app container func GetContainerIpaddress(appName string, procType string, containerID string) string { if procType != "web" { @@ -84,74 +155,3 @@ func HasNetworkConfig(appName string) bool { return common.FileExists(ipfile) && common.FileExists(portfile) } - -// BuildConfig builds network config files -func BuildConfig(appName string) { - err := common.VerifyAppName(appName) - if err != nil { - common.LogFail(err.Error()) - } - - if !common.IsDeployed(appName) { - return - } - - appRoot := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName}, "/") - scaleFile := strings.Join([]string{appRoot, "DOKKU_SCALE"}, "/") - if !common.FileExists(scaleFile) { - return - } - - image := common.GetAppImageName(appName, "", "") - isHerokuishContainer := common.IsImageHerokuishBased(image) - - common.LogInfo1(fmt.Sprintf("Ensuring network configuration is in sync for %s", appName)) - lines, err := common.FileToSlice(scaleFile) - if err != nil { - return - } - for _, line := range lines { - if line == "" || strings.HasPrefix(line, "#") { - continue - } - - procParts := strings.SplitN(line, "=", 2) - if len(procParts) != 2 { - continue - } - procType := procParts[0] - procCount, err := strconv.Atoi(procParts[1]) - if err != nil { - continue - } - - containerIndex := 0 - for containerIndex < procCount { - containerIndex++ - containerIndexString := strconv.Itoa(containerIndex) - containerIDFile := fmt.Sprintf("%v/CONTAINER.%v.%v", appRoot, procType, containerIndex) - - containerID := common.ReadFirstLine(containerIDFile) - if containerID == "" || !common.ContainerIsRunning(containerID) { - continue - } - - ipAddress := GetContainerIpaddress(appName, procType, containerID) - port := GetContainerPort(appName, procType, isHerokuishContainer, containerID) - - if ipAddress != "" { - _, err := sh.Command("plugn", "trigger", "network-write-ipaddr", appName, procType, containerIndexString, ipAddress).Output() - if err != nil { - common.LogWarn(err.Error()) - } - } - - if port != "" { - _, err := sh.Command("plugn", "trigger", "network-write-port", appName, procType, containerIndexString, port).Output() - if err != nil { - common.LogWarn(err.Error()) - } - } - } - } -} From 04cd5eeb91389eef2ed85b0451505304e776d8a3 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 3 Sep 2017 19:19:28 -0400 Subject: [PATCH 48/66] refactor: move all logging functions to their own file --- plugins/common/common.go | 37 ----------------------------------- plugins/common/log.go | 42 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 37 deletions(-) create mode 100644 plugins/common/log.go diff --git a/plugins/common/common.go b/plugins/common/common.go index eae0e0df0..d9cba64ef 100644 --- a/plugins/common/common.go +++ b/plugins/common/common.go @@ -93,13 +93,6 @@ func MustGetEnv(key string) string { return value } -// LogFail is the failure log formatter -// prints text to stderr and exits with status 1 -func LogFail(text string) { - fmt.Fprintln(os.Stderr, fmt.Sprintf("FAILED: %s", text)) - os.Exit(1) -} - // GetDeployingAppImageName returns deploying image identifier for a given app, tag tuple. validate if tag is presented func GetDeployingAppImageName(appName, imageTag, imageRepo string) (imageName string) { if appName == "" { @@ -311,35 +304,6 @@ func IsImageHerokuishBased(image string) bool { return dockerCmd.Execute() } -// LogInfo1 is the info1 header formatter -func LogInfo1(text string) { - fmt.Fprintln(os.Stdout, fmt.Sprintf("-----> %s", text)) -} - -// LogInfo1Quiet is the info1 header formatter (with quiet option) -func LogInfo1Quiet(text string) { - if os.Getenv("DOKKU_QUIET_OUTPUT") != "" { - LogInfo1(text) - } -} - -// LogInfo2 is the info2 header formatter -func LogInfo2(text string) { - fmt.Fprintln(os.Stdout, fmt.Sprintf("=====> %s", text)) -} - -// LogInfo2Quiet is the info2 header formatter (with quiet option) -func LogInfo2Quiet(text string) { - if os.Getenv("DOKKU_QUIET_OUTPUT") != "" { - LogInfo2(text) - } -} - -// LogWarn is the warning log formatter -func LogWarn(text string) { - fmt.Fprintln(os.Stderr, fmt.Sprintf(" ! %s", text)) -} - // ReadFirstLine gets the first line of a file that has contents and returns it // if there are no contents, an empty string is returned // will also return an empty string if the file does not exist @@ -362,7 +326,6 @@ func ReadFirstLine(filename string) string { return text } return "" - } // StripInlineComments removes bash-style comment from input line diff --git a/plugins/common/log.go b/plugins/common/log.go new file mode 100644 index 000000000..132822328 --- /dev/null +++ b/plugins/common/log.go @@ -0,0 +1,42 @@ +package common + +import ( + "fmt" + "os" +) + +// LogFail is the failure log formatter +// prints text to stderr and exits with status 1 +func LogFail(text string) { + fmt.Fprintln(os.Stderr, fmt.Sprintf("FAILED: %s", text)) + os.Exit(1) +} + +// LogInfo1 is the info1 header formatter +func LogInfo1(text string) { + fmt.Fprintln(os.Stdout, fmt.Sprintf("-----> %s", text)) +} + +// LogInfo1Quiet is the info1 header formatter (with quiet option) +func LogInfo1Quiet(text string) { + if os.Getenv("DOKKU_QUIET_OUTPUT") != "" { + LogInfo1(text) + } +} + +// LogInfo2 is the info2 header formatter +func LogInfo2(text string) { + fmt.Fprintln(os.Stdout, fmt.Sprintf("=====> %s", text)) +} + +// LogInfo2Quiet is the info2 header formatter (with quiet option) +func LogInfo2Quiet(text string) { + if os.Getenv("DOKKU_QUIET_OUTPUT") != "" { + LogInfo2(text) + } +} + +// LogWarn is the warning log formatter +func LogWarn(text string) { + fmt.Fprintln(os.Stderr, fmt.Sprintf(" ! %s", text)) +} From 096ba83f41cd480f65d450838b3e999f152ad231 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 3 Sep 2017 19:34:44 -0400 Subject: [PATCH 49/66] refactor: alphabetize more functions --- plugins/common/common.go | 66 ++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/plugins/common/common.go b/plugins/common/common.go index d9cba64ef..94021f2fe 100644 --- a/plugins/common/common.go +++ b/plugins/common/common.go @@ -67,32 +67,6 @@ func (sc *ShellCmd) Output() ([]byte, error) { return sc.Command.Output() } -// VerifyAppName verifies app name format and app existence" -func VerifyAppName(appName string) (err error) { - if appName == "" { - return fmt.Errorf("App name must not be null") - } - dokkuRoot := MustGetEnv("DOKKU_ROOT") - appRoot := strings.Join([]string{dokkuRoot, appName}, "/") - if !DirectoryExists(appRoot) { - return fmt.Errorf("App %s does not exist: %v\n", appName, err) - } - r, _ := regexp.Compile("^[a-z].*") - if !r.MatchString(appName) { - return fmt.Errorf("App name (%s) must begin with lowercase alphanumeric character\n", appName) - } - return err -} - -// MustGetEnv returns env variable or fails if it's not set -func MustGetEnv(key string) string { - value := os.Getenv(key) - if value == "" { - LogFail(fmt.Sprintf("%s not set!", key)) - } - return value -} - // GetDeployingAppImageName returns deploying image identifier for a given app, tag tuple. validate if tag is presented func GetDeployingAppImageName(appName, imageTag, imageRepo string) (imageName string) { if appName == "" { @@ -142,13 +116,6 @@ func GetAppImageRepo(appName string) string { return strings.Join([]string{"dokku", appName}, "/") } -// VerifyImage returns true if docker image exists in local repo -func VerifyImage(image string) bool { - imageCmd := NewShellCmd(strings.Join([]string{"docker inspect", image}, " ")) - imageCmd.ShowOutput = false - return imageCmd.Execute() -} - // ContainerIsRunning checks to see if a container is running func ContainerIsRunning(containerID string) bool { b, err := DockerInspect(containerID, "'{{.State.Running}}'") @@ -304,6 +271,15 @@ func IsImageHerokuishBased(image string) bool { return dockerCmd.Execute() } +// MustGetEnv returns env variable or fails if it's not set +func MustGetEnv(key string) string { + value := os.Getenv(key) + if value == "" { + LogFail(fmt.Sprintf("%s not set!", key)) + } + return value +} + // ReadFirstLine gets the first line of a file that has contents and returns it // if there are no contents, an empty string is returned // will also return an empty string if the file does not exist @@ -340,3 +316,27 @@ func StripInlineComments(text string) string { func ToBool(s string) bool { return s == "true" } + +// VerifyAppName verifies app name format and app existence" +func VerifyAppName(appName string) (err error) { + if appName == "" { + return fmt.Errorf("App name must not be null") + } + dokkuRoot := MustGetEnv("DOKKU_ROOT") + appRoot := strings.Join([]string{dokkuRoot, appName}, "/") + if !DirectoryExists(appRoot) { + return fmt.Errorf("App %s does not exist: %v\n", appName, err) + } + r, _ := regexp.Compile("^[a-z].*") + if !r.MatchString(appName) { + return fmt.Errorf("App name (%s) must begin with lowercase alphanumeric character\n", appName) + } + return err +} + +// VerifyImage returns true if docker image exists in local repo +func VerifyImage(image string) bool { + imageCmd := NewShellCmd(strings.Join([]string{"docker inspect", image}, " ")) + imageCmd.ShowOutput = false + return imageCmd.Execute() +} From 7a4464a8aee97d6ad7b2f36d6e3816c96600d98c Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 3 Sep 2017 20:05:15 -0400 Subject: [PATCH 50/66] feat: more loggers --- plugins/common/log.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/common/log.go b/plugins/common/log.go index 132822328..31d83a9b4 100644 --- a/plugins/common/log.go +++ b/plugins/common/log.go @@ -36,6 +36,16 @@ func LogInfo2Quiet(text string) { } } +func LogVerbose(text string) { + fmt.Fprintln(os.Stdout, fmt.Sprintf(" %s", text)) +} + +func LogVerboseQuiet(text string) { + if os.Getenv("DOKKU_QUIET_OUTPUT") != "" { + LogVerbose(text) + } +} + // LogWarn is the warning log formatter func LogWarn(text string) { fmt.Fprintln(os.Stderr, fmt.Sprintf(" ! %s", text)) From acde3d60a719d53d46213fbf0cc86fad39e84a0d Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 3 Sep 2017 20:36:09 -0400 Subject: [PATCH 51/66] feat: allow users to bind to all interfaces when a proxy is enabled - use a plugin trigger to see whether we should bind to all interfaces - create a generic way of setting properties for a plugin - migrate proxy-enabled to the new network property "bind-all-interfaces" - add network:set subcommand --- docs/development/plugin-triggers.md | 15 ++ docs/networking/network.md | 55 +++++- docs/networking/proxy-management.md | 41 +--- plugins/common/common.go | 4 +- plugins/common/functions | 9 +- plugins/common/log.go | 4 + plugins/common/properties.go | 181 ++++++++++++++++++ plugins/config/config.go | 2 +- plugins/network/.gitignore | 3 + plugins/network/Makefile | 23 ++- plugins/network/network.go | 18 ++ plugins/network/src/commands/commands.go | 1 + plugins/network/src/subcommands/set/set.go | 22 +++ .../network/src/triggers/install/install.go | 33 ++++ .../network-get-property.go | 18 ++ .../src/triggers/post-create/post-create.go | 15 ++ .../src/triggers/post-delete/post-delete.go | 15 ++ tests/unit/20_network.bats | 69 +++++++ tests/unit/40_proxy.bats | 2 +- 19 files changed, 477 insertions(+), 53 deletions(-) create mode 100644 plugins/common/properties.go create mode 100644 plugins/network/src/subcommands/set/set.go create mode 100644 plugins/network/src/triggers/install/install.go create mode 100644 plugins/network/src/triggers/network-get-property/network-get-property.go create mode 100644 plugins/network/src/triggers/post-create/post-create.go create mode 100644 plugins/network/src/triggers/post-delete/post-delete.go create mode 100644 tests/unit/20_network.bats diff --git a/docs/development/plugin-triggers.md b/docs/development/plugin-triggers.md index 5b560f956..4179f1c85 100644 --- a/docs/development/plugin-triggers.md +++ b/docs/development/plugin-triggers.md @@ -419,6 +419,21 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x # TODO ``` +### `network-get-property` + +- Description: Return the network value for an application's property +- Invoked by: `internally triggered by a deploy` +- Arguments: `$APP $KEY` +- Example: + +```shell +#!/usr/bin/env bash + +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +# TODO +``` + ### `network-get-port` - Description: Return the port for a given app container diff --git a/docs/networking/network.md b/docs/networking/network.md index aa737dc15..a1fea00ed 100644 --- a/docs/networking/network.md +++ b/docs/networking/network.md @@ -1,10 +1,11 @@ # Network Management -> New as of 0.10.0 +> New as of 0.11.0 ``` network:rebuild # Rebuilds network settings for an app network:rebuildall # Rebuild network settings for all apps +network:set () # Set or clear a network property for an app ``` The Network plugin allows developers to abstract the concept of container network management, allowing developers to both change what networks a given container is attached to as well as rebuild the configuration on the fly. @@ -30,3 +31,55 @@ dokku network:rebuildall ``` > This command will exit a non-zero number that depends on the number of containers for which configuration could not be built + +### Container network interface binding + +> This functionality does not control the `--network` docker flag. Please use the [docker-options plugin](docs/advanced-usage/docker-options.md) to manage this flag. + +By default, an application will only bind to the internal interface. This behavior can be modified per app by changing the `bind-all-interfaces` network property. + +```shell +# bind to the default docker interface (`docker0`) with a random internal ip +# this is the default behavior +dokku network:set node-js-app bind-all-interfaces false + +# bind to all interfaces (`0.0.0.0`) on a random port for each upstream port +# this will make the app container directly accessible by other hosts on your network +# ports are randomized for every deploy, e.g. `0.0.0.0:32771->5000/tcp`. +dokku network:set node-js-app bind-all-interfaces true +``` + +By way of example, in the default case, each container is bound to the docker interface: + +```shell +docker ps +``` + +``` +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +1b88d8aec3d1 dokku/node-js-app:latest "/bin/bash -c '/star About a minute ago Up About a minute node-js-app.web.1 +``` + +As such, the container's IP address will be an internal IP, and thus it is only accessible on the host itself: + +``` +docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' node-js-app.web.1 +``` + +``` +172.17.0.6 +``` + +However, you can disable the internal proxying via the `network:set` command so that it will listen on the host's IP address: + +```shell +dokku network:set node-js-app bind-all-interfaces true + +# container bound to all interfaces +docker ps +``` + +``` +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +d6499edb0edb dokku/node-js-app:latest "/bin/bash -c '/star About a minute ago Up About a minute 0.0.0.0:49153->5000/tcp node-js-app.web.1 +``` diff --git a/docs/networking/proxy-management.md b/docs/networking/proxy-management.md index 09f88c0c4..f4f02ef0e 100644 --- a/docs/networking/proxy-management.md +++ b/docs/networking/proxy-management.md @@ -19,46 +19,9 @@ In Dokku 0.5.0, port proxying was decoupled from the `nginx-vhosts` plugin into ### Container network interface binding -> New as of 0.5.0 +> Changed as of 0.11.0 -By default, the deployed docker container running your app's web process will bind to the internal docker network interface (i.e. `docker inspect --format '{{ .NetworkSettings.IPAddress }}' $CONTAINER_ID`). This behavior can be modified per app by disabling the proxy (i.e. `dokku proxy:disable `). In this case, the container will bind to an external interface (i.e. `0.0.0.0`) and your app container will be directly accessible by other hosts on your network. - -> If a proxy is disabled, Dokku will bind your container's port to a random port on the host for every deploy, e.g. `0.0.0.0:32771->5000/tcp`. - -By way of example, in the default case, each container is bound to the docker interface: - -```shell -docker ps -``` - -``` -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -1b88d8aec3d1 dokku/node-js-app:latest "/bin/bash -c '/star About a minute ago Up About a minute node-js-app.web.1 -``` - -As such, the container's IP address will be an internal IP, and thus it is only accessible on the host itself: - -``` -docker inspect --format '{{ .NetworkSettings.IPAddress }}' node-js-app.web.1 -``` - -``` -172.17.0.6 -``` - -However, you can disable the internal proxying via the `proxy:disable` command so that it will listen on the host's IP address: - -```shell -dokku proxy:disable node-js-app - -# container bound to all interfaces -docker ps -``` - -``` -CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -d6499edb0edb dokku/node-js-app:latest "/bin/bash -c '/star About a minute ago Up About a minute 0.0.0.0:49153->5000/tcp node-js-app.web.1 -``` +From Dokku versions `0.5.0` until `0.11.0`, enabling or disabling an application's proxy would **also** control whether or not the application was bound to all interfaces - e.g. `0.0.0.0`. As of `0.10.0`, this is now controlled by the network plugin. Please see the [network documentation](/docs/networking/network.md#container-network-interface-binding) for more information. ### Displaying proxy reports about an app diff --git a/plugins/common/common.go b/plugins/common/common.go index 94021f2fe..408b40a23 100644 --- a/plugins/common/common.go +++ b/plugins/common/common.go @@ -325,11 +325,11 @@ func VerifyAppName(appName string) (err error) { dokkuRoot := MustGetEnv("DOKKU_ROOT") appRoot := strings.Join([]string{dokkuRoot, appName}, "/") if !DirectoryExists(appRoot) { - return fmt.Errorf("App %s does not exist: %v\n", appName, err) + return fmt.Errorf("app %s does not exist: %v", appName, err) } r, _ := regexp.Compile("^[a-z].*") if !r.MatchString(appName) { - return fmt.Errorf("App name (%s) must begin with lowercase alphanumeric character\n", appName) + return fmt.Errorf("app name (%s) must begin with lowercase alphanumeric character", appName) } return err } diff --git a/plugins/common/functions b/plugins/common/functions index 1e63395e4..07563d647 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -538,11 +538,10 @@ dokku_deploy_cmd() { local cmd="deploy" source "$PLUGIN_AVAILABLE_PATH/checks/functions" source "$PLUGIN_AVAILABLE_PATH/config/functions" - source "$PLUGIN_AVAILABLE_PATH/proxy/functions" [[ -z $1 ]] && dokku_log_fail "Please specify an app to run the command on" local APP="$1" IMAGE_TAG="$2" - local DOKKU_HEROKUISH IMAGE + local DOKKU_DOCKER_STOP_TIMEOUT DOKKU_HEROKUISH DOKKU_NETWORK_BIND_ALL IMAGE DOKKU_HEROKUISH=false IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG") verify_app_name "$APP" @@ -552,8 +551,8 @@ dokku_deploy_cmd() { local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" local oldids=$(get_app_container_ids "$APP") - local DOKKU_IS_APP_PROXY_ENABLED="$(is_app_proxy_enabled "$APP")" - local DOKKU_DOCKER_STOP_TIMEOUT="$(config_get "$APP" DOKKU_DOCKER_STOP_TIMEOUT || true)" + DOKKU_NETWORK_BIND_ALL="$(plugn trigger network-get-property "$APP" bind-all-interfaces)" + DOKKU_DOCKER_STOP_TIMEOUT="$(config_get "$APP" DOKKU_DOCKER_STOP_TIMEOUT || true)" [[ $DOKKU_DOCKER_STOP_TIMEOUT ]] && DOCKER_STOP_TIME_ARG="--time=${DOKKU_DOCKER_STOP_TIMEOUT}" local line; local PROC_TYPE; local PROC_COUNT; local CONTAINER_INDEX @@ -605,7 +604,7 @@ dokku_deploy_cmd() { DOKKU_DOCKER_PORT_ARGS+=" -p $p " done - if [[ "$DOKKU_IS_APP_PROXY_ENABLED" == "true" ]]; then + if [[ "$DOKKU_NETWORK_BIND_ALL" == "false" ]]; then # shellcheck disable=SC2086 cid=$(docker run $DOKKU_GLOBAL_RUN_ARGS -d -e PORT=$DOKKU_PORT $DOCKER_ARGS $IMAGE $START_CMD) else diff --git a/plugins/common/log.go b/plugins/common/log.go index 31d83a9b4..f91ee0c69 100644 --- a/plugins/common/log.go +++ b/plugins/common/log.go @@ -36,10 +36,14 @@ func LogInfo2Quiet(text string) { } } +// LogVerbose is the verbose log formatter +// prints indented text to stdout func LogVerbose(text string) { fmt.Fprintln(os.Stdout, fmt.Sprintf(" %s", text)) } +// LogVerboseQuiet is the verbose log formatter +// prints indented text to stdout (with quiet option) func LogVerboseQuiet(text string) { if os.Getenv("DOKKU_QUIET_OUTPUT") != "" { LogVerbose(text) diff --git a/plugins/common/properties.go b/plugins/common/properties.go new file mode 100644 index 000000000..565679a6c --- /dev/null +++ b/plugins/common/properties.go @@ -0,0 +1,181 @@ +package common + +import ( + "fmt" + "io/ioutil" + "os" + "os/user" + "reflect" + "strconv" + "strings" +) + +// CommandPropertySet is a generic function that will set a property for a given plugin/app combination +func CommandPropertySet(pluginName string, appName string, property string, value string, validProperties map[string]bool) { + err := VerifyAppName(appName) + if err != nil { + LogFail(err.Error()) + } + + if property == "" { + LogFail("No property specified") + } + + if !isValidProperty(validProperties, property) { + properties := reflect.ValueOf(validProperties).MapKeys() + validPropertyList := make([]string, len(properties)) + for i := 0; i < len(properties); i++ { + validPropertyList[i] = properties[i].String() + } + + LogFail(fmt.Sprintf("Invalid property specified, valid properties include: %s", strings.Join(validPropertyList, ", "))) + } + + if value != "" { + LogInfo2Quiet(fmt.Sprintf("Setting %s to %s", property, value)) + PropertyWrite(pluginName, appName, property, value) + } else { + LogInfo2Quiet(fmt.Sprintf("Unsetting %s", property)) + PropertyDelete(pluginName, appName, property) + } +} + +// PropertyDelete deletes a property from the plugin properties for an app +func PropertyDelete(pluginName string, appName string, property string) { + pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName) + propertyPath := strings.Join([]string{pluginAppConfigRoot, property}, "/") + err := os.Remove(propertyPath) + if err != nil { + LogFail(fmt.Sprintf("Unable to remove %s property %s.%s", pluginName, appName, property)) + } +} + +// PropertyDestroy destroys the plugin properties for an app +func PropertyDestroy(pluginName string, appName string) { + if appName == "_all_" { + pluginConfigPath := getPluginConfigPath(pluginName) + os.RemoveAll(pluginConfigPath) + } else { + pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName) + os.RemoveAll(pluginAppConfigRoot) + } +} + +// PropertyExists returns whether a property exists or not +func PropertyExists(pluginName string, appName string, property string) bool { + pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName) + propertyPath := strings.Join([]string{pluginAppConfigRoot, property}, "/") + _, err := os.Stat(propertyPath) + return !os.IsNotExist(err) +} + +// PropertyGet returns the value for a given property +func PropertyGet(pluginName string, appName string, property string) string { + return PropertyGetDefault(pluginName, appName, property, "") +} + +// PropertyGetDefault returns the value for a given property with a specified default value +func PropertyGetDefault(pluginName string, appName string, property string, defaultValue string) string { + if !PropertyExists(pluginName, appName, property) { + return "" + } + + pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName) + propertyPath := strings.Join([]string{pluginAppConfigRoot, property}, "/") + + b, err := ioutil.ReadFile(propertyPath) + if err != nil { + LogWarn(fmt.Sprintf("Unable to read %s property %s.%s", pluginName, appName, property)) + return "" + } + + return string(b) +} + +// PropertyWrite writes a value for a given application property +func PropertyWrite(pluginName string, appName string, property string, value string) { + err := makePropertyPath(pluginName, appName) + if err != nil { + LogFail(fmt.Sprintf("Unable to create %s config directory for %s: %s", pluginName, appName, err.Error())) + } + + pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName) + propertyPath := strings.Join([]string{pluginAppConfigRoot, property}, "/") + file, err := os.Create(propertyPath) + if err != nil { + LogFail(fmt.Sprintf("Unable to write %s config value %s.%s: %s", pluginName, appName, property, err.Error)) + } + defer file.Close() + + fmt.Fprintf(file, value) + file.Chmod(0600) + setPermissions(propertyPath, 0600) +} + +// PropertySetup creates the plugin config root +func PropertySetup(pluginName string) error { + pluginConfigRoot := getPluginConfigPath(pluginName) + err := os.MkdirAll(pluginConfigRoot, 0755) + if err != nil { + return err + } + return setPermissions(pluginConfigRoot, 0755) +} + +// isValidProperty returns whether a property is a valid property or not +func isValidProperty(validProperties map[string]bool, property string) bool { + return validProperties[property] +} + +// getPluginAppPropertyPath returns the plugin property path for a given plugin/app combination +func getPluginAppPropertyPath(pluginName string, appName string) string { + return strings.Join([]string{getPluginConfigPath(pluginName), appName}, "/") +} + +// getPluginConfigPath returns the plugin property path for a given plugin +func getPluginConfigPath(pluginName string) string { + return strings.Join([]string{MustGetEnv("DOKKU_LIB_ROOT"), "config", pluginName}, "/") +} + +// makePropertyPath ensures that a property path exists +func makePropertyPath(pluginName string, appName string) error { + pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName) + err := os.MkdirAll(pluginAppConfigRoot, 0755) + if err != nil { + return err + } + return setPermissions(pluginAppConfigRoot, 0755) +} + +// setPermissions sets the proper owner and filemode for a given file +func setPermissions(path string, fileMode os.FileMode) error { + err := os.Chmod(path, fileMode) + if err != nil { + return err + } + + group, err := user.LookupGroup("dokku") + if err != nil { + return err + } + user, err := user.Lookup("dokku") + if err != nil { + return err + } + + uid, err := strconv.Atoi(user.Uid) + if err != nil { + return err + } + + gid, err := strconv.Atoi(group.Gid) + if err != nil { + return err + } + + err = os.Chown(path, uid, gid) + if err != nil { + return err + } + return nil +} diff --git a/plugins/config/config.go b/plugins/config/config.go index b5366390e..8ef087a35 100644 --- a/plugins/config/config.go +++ b/plugins/config/config.go @@ -22,7 +22,7 @@ func GetWithDefault(appName string, key string, defaultValue string) string { continue } value = strings.TrimPrefix(line, prefix) - if strings.HasPrefix(line, "'") && strings.HasSuffix(line, "'") { + if strings.HasPrefix(value, "'") && strings.HasSuffix(value, "'") { value = strings.TrimPrefix(strings.TrimSuffix(value, "'"), "'") } } diff --git a/plugins/network/.gitignore b/plugins/network/.gitignore index 9ff165fe8..14fca6773 100644 --- a/plugins/network/.gitignore +++ b/plugins/network/.gitignore @@ -1,3 +1,6 @@ /commands /subcommands/* /network-* +/install +/post-create +/post-delete diff --git a/plugins/network/Makefile b/plugins/network/Makefile index 5a8a6f690..c435e1d26 100644 --- a/plugins/network/Makefile +++ b/plugins/network/Makefile @@ -8,17 +8,23 @@ build-in-docker: clean bash -c "make build" || exit $$? build: commands subcommands triggers -subcommands: subcommands/rebuild subcommands/rebuildall -triggers: network-build-config network-compute-ports network-config-exists network-get-ipaddr network-get-listeners network-get-port network-write-ipaddr network-write-port +subcommands: subcommands/rebuild subcommands/rebuildall subcommands/set +triggers: install network-build-config network-compute-ports network-config-exists network-get-ipaddr network-get-listeners network-get-port network-get-property network-write-ipaddr network-write-port post-create post-delete commands: **/**/commands.go go build -a -o commands src/commands/commands.go subcommands/rebuild: **/**/**/rebuild.go - go build -a -o subcommands/rebuild src/subcommands/rebuild/rebuild.go + go build -a -o subcommands/rebuild src/subcommands/rebuild/rebuild.go subcommands/rebuildall: **/**/**/rebuildall.go go build -a -o subcommands/rebuildall src/subcommands/rebuildall/rebuildall.go +subcommands/set: **/**/**/set.go + go build -a -o subcommands/set src/subcommands/set/set.go + +install: **/**/**/install.go + go build -a -o install src/triggers/install/install.go + network-build-config: **/**/**/network-build-config.go go build -a -o network-build-config src/triggers/network-build-config/network-build-config.go @@ -37,14 +43,23 @@ network-get-listeners: **/**/**/network-get-listeners.go network-get-port: **/**/**/network-get-port.go go build -a -o network-get-port src/triggers/network-get-port/network-get-port.go +network-get-property: **/**/**/network-get-property.go + go build -a -o network-get-property src/triggers/network-get-property/network-get-property.go + network-write-ipaddr: **/**/**/network-write-ipaddr.go go build -a -o network-write-ipaddr src/triggers/network-write-ipaddr/network-write-ipaddr.go network-write-port: **/**/**/network-write-port.go go build -a -o network-write-port src/triggers/network-write-port/network-write-port.go +post-create: **/**/**/post-create.go + go build -a -o post-create src/triggers/post-create/post-create.go + +post-delete: **/**/**/post-delete.go + go build -a -o post-delete src/triggers/post-delete/post-delete.go + clean: - rm -rf commands subcommands network-* + rm -rf commands subcommands network-* install post-create post-delete src-clean: rm -rf .gitignore src vendor Makefile diff --git a/plugins/network/network.go b/plugins/network/network.go index cbe66400c..d3a91ddd7 100644 --- a/plugins/network/network.go +++ b/plugins/network/network.go @@ -12,6 +12,16 @@ import ( sh "github.com/codeskyblue/go-sh" ) +// ValidProperties is a map of all valid network properties +var ValidProperties = map[string]bool{ + "bind-all-interfaces": true, +} + +// DefaultPropertyValues is a map of all valid network properties with corresponding default property values +var DefaultPropertyValues = map[string]string{ + "bind-all-interfaces": "false", +} + // BuildConfig builds network config files func BuildConfig(appName string) { err := common.VerifyAppName(appName) @@ -147,6 +157,14 @@ func GetContainerPort(appName string, procType string, isHerokuishContainer bool return port } +// GetDefaultValue returns the default value for a given property +func GetDefaultValue(property string) string { + if ValidProperties[property] { + return DefaultPropertyValues[property] + } + return "" +} + // HasNetworkConfig returns whether the network configuration for a given app exists func HasNetworkConfig(appName string) bool { appRoot := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName}, "/") diff --git a/plugins/network/src/commands/commands.go b/plugins/network/src/commands/commands.go index 48a7a91c8..308fab652 100644 --- a/plugins/network/src/commands/commands.go +++ b/plugins/network/src/commands/commands.go @@ -20,6 +20,7 @@ Additional commands:` helpContent = ` network:rebuild , Rebuilds network settings for an app network:rebuildall, Rebuild network settings for all apps + network:set (), Set or clear a network property for an app ` ) diff --git a/plugins/network/src/subcommands/set/set.go b/plugins/network/src/subcommands/set/set.go new file mode 100644 index 000000000..119707111 --- /dev/null +++ b/plugins/network/src/subcommands/set/set.go @@ -0,0 +1,22 @@ +package main + +import ( + "flag" + + common "github.com/dokku/dokku/plugins/common" + network "github.com/dokku/dokku/plugins/network" +) + +// set or clear a network property for an app +func main() { + flag.Parse() + appName := flag.Arg(1) + property := flag.Arg(2) + value := flag.Arg(3) + + if property == "bind-all-interfaces" && value == "" { + value = "false" + } + + common.CommandPropertySet("network", appName, property, value, network.ValidProperties) +} diff --git a/plugins/network/src/triggers/install/install.go b/plugins/network/src/triggers/install/install.go new file mode 100644 index 000000000..85c2229a9 --- /dev/null +++ b/plugins/network/src/triggers/install/install.go @@ -0,0 +1,33 @@ +package main + +import ( + "fmt" + + common "github.com/dokku/dokku/plugins/common" + proxy "github.com/dokku/dokku/plugins/proxy" +) + +// runs the install step for the network plugin +func main() { + err := common.PropertySetup("network") + if err != nil { + common.LogFail(fmt.Sprintf("Unable to install the network plugin: %s", err.Error())) + } + + apps, err := common.DokkuApps() + if err != nil { + return + } + for _, appName := range apps { + if common.PropertyExists("network", appName, "bind-all-interfaces") { + continue + } + if proxy.IsAppProxyEnabled(appName) { + common.LogVerboseQuiet("Setting %s network property 'bind-all-interfaces' to false") + common.PropertyWrite("network", appName, "bind-all-interfaces", "false") + } else { + common.LogVerboseQuiet("Setting %s network property 'bind-all-interfaces' to true") + common.PropertyWrite("network", appName, "bind-all-interfaces", "true") + } + } +} diff --git a/plugins/network/src/triggers/network-get-property/network-get-property.go b/plugins/network/src/triggers/network-get-property/network-get-property.go new file mode 100644 index 000000000..d6e973fd3 --- /dev/null +++ b/plugins/network/src/triggers/network-get-property/network-get-property.go @@ -0,0 +1,18 @@ +package main + +import ( + "flag" + + common "github.com/dokku/dokku/plugins/common" + network "github.com/dokku/dokku/plugins/network" +) + +// write the port to stdout for a given app container +func main() { + flag.Parse() + appName := flag.Arg(0) + property := flag.Arg(1) + + defaultValue := network.GetDefaultValue(property) + common.PropertyGetDefault("network", appName, property, defaultValue) +} diff --git a/plugins/network/src/triggers/post-create/post-create.go b/plugins/network/src/triggers/post-create/post-create.go new file mode 100644 index 000000000..0ddcc7e0e --- /dev/null +++ b/plugins/network/src/triggers/post-create/post-create.go @@ -0,0 +1,15 @@ +package main + +import ( + "flag" + + common "github.com/dokku/dokku/plugins/common" +) + +// set bind-all-interfaces to false by default +func main() { + flag.Parse() + appName := flag.Arg(0) + + common.PropertyWrite("network", appName, "bind-all-interfaces", "false") +} diff --git a/plugins/network/src/triggers/post-delete/post-delete.go b/plugins/network/src/triggers/post-delete/post-delete.go new file mode 100644 index 000000000..3127b5d9b --- /dev/null +++ b/plugins/network/src/triggers/post-delete/post-delete.go @@ -0,0 +1,15 @@ +package main + +import ( + "flag" + + common "github.com/dokku/dokku/plugins/common" +) + +// write the port to stdout for a given app container +func main() { + flag.Parse() + appName := flag.Arg(0) + + common.PropertyDestroy("network", appName) +} diff --git a/tests/unit/20_network.bats b/tests/unit/20_network.bats new file mode 100644 index 000000000..80e35203e --- /dev/null +++ b/tests/unit/20_network.bats @@ -0,0 +1,69 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + global_setup + [[ -f "$DOKKU_ROOT/VHOST" ]] && cp -fp "$DOKKU_ROOT/VHOST" "$DOKKU_ROOT/VHOST.bak" + [[ -f "$DOKKU_ROOT/HOSTNAME" ]] && cp -fp "$DOKKU_ROOT/HOSTNAME" "$DOKKU_ROOT/HOSTNAME.bak" + create_app +} + +teardown() { + destroy_app 0 $TEST_APP + [[ -f "$DOKKU_ROOT/VHOST.bak" ]] && mv "$DOKKU_ROOT/VHOST.bak" "$DOKKU_ROOT/VHOST" && chown dokku:dokku "$DOKKU_ROOT/VHOST" + [[ -f "$DOKKU_ROOT/HOSTNAME.bak" ]] && mv "$DOKKU_ROOT/HOSTNAME.bak" "$DOKKU_ROOT/HOSTNAME" && chown dokku:dokku "$DOKKU_ROOT/HOSTNAME" + global_teardown +} + +assert_nonssl_domain() { + local domain=$1 + assert_app_domain "${domain}" + assert_http_success "http://${domain}" +} + +assert_app_domain() { + local domain=$1 + run /bin/bash -c "dokku domains $TEST_APP 2> /dev/null | grep -xF ${domain}" + echo "output: "$output + echo "status: "$status + assert_output "${domain}" +} + +assert_external_port() { + local CID="$1"; local exit_status="$2" + local EXTERNAL_PORT_COUNT=$(docker port $CID | wc -l) + run /bin/bash -c "[[ $EXTERNAL_PORT_COUNT -gt 0 ]]" + if [[ "$exit_status" == "success" ]]; then + assert_success + else + assert_failure + fi +} + +@test "(proxy) network:set bind-all-interfaces" { + deploy_app + assert_nonssl_domain "${TEST_APP}.dokku.me" + + run dokku network:set $TEST_APP bind-all-interfaces true + run dokku ps:rebuild $TEST_APP + echo "output: "$output + echo "status: "$status + assert_success + assert_http_success "${TEST_APP}.dokku.me" + + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do + assert_external_port $(< $CID_FILE) success + done + + run dokku network:set $TEST_APP bind-all-interfaces false + run dokku ps:rebuild $TEST_APP + echo "output: "$output + echo "status: "$status + assert_success + assert_http_success "${TEST_APP}.dokku.me" + + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do + assert_external_port $(< $CID_FILE) failure + done +} diff --git a/tests/unit/40_proxy.bats b/tests/unit/40_proxy.bats index 60ea5037f..fc733fe9d 100644 --- a/tests/unit/40_proxy.bats +++ b/tests/unit/40_proxy.bats @@ -51,7 +51,7 @@ assert_external_port() { assert_success for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do - assert_external_port $(< $CID_FILE) success + assert_external_port $(< $CID_FILE) failure done run dokku proxy:enable $TEST_APP From 3ca5503b74892b85fec66773d2371514a8639efd Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 1 Oct 2017 17:16:31 -0400 Subject: [PATCH 52/66] fix: actually echo the property --- .../triggers/network-get-property/network-get-property.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/network/src/triggers/network-get-property/network-get-property.go b/plugins/network/src/triggers/network-get-property/network-get-property.go index d6e973fd3..da78d3ff9 100644 --- a/plugins/network/src/triggers/network-get-property/network-get-property.go +++ b/plugins/network/src/triggers/network-get-property/network-get-property.go @@ -2,6 +2,8 @@ package main import ( "flag" + "fmt" + "os" common "github.com/dokku/dokku/plugins/common" network "github.com/dokku/dokku/plugins/network" @@ -14,5 +16,6 @@ func main() { property := flag.Arg(1) defaultValue := network.GetDefaultValue(property) - common.PropertyGetDefault("network", appName, property, defaultValue) + value := common.PropertyGetDefault("network", appName, property, defaultValue) + fmt.Fprintln(os.Stdout, value) } From 419d687c502005337609c9ae426970d9119de3bd Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 1 Oct 2017 17:45:30 -0400 Subject: [PATCH 53/66] fix: remove proxy enabled checks when retrieving a container's ip and port --- plugins/network/network.go | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/plugins/network/network.go b/plugins/network/network.go index d3a91ddd7..f99290520 100644 --- a/plugins/network/network.go +++ b/plugins/network/network.go @@ -7,7 +7,6 @@ import ( common "github.com/dokku/dokku/plugins/common" config "github.com/dokku/dokku/plugins/config" - proxy "github.com/dokku/dokku/plugins/proxy" sh "github.com/codeskyblue/go-sh" ) @@ -99,11 +98,6 @@ func GetContainerIpaddress(appName string, procType string, containerID string) return "" } - ipAddress := "127.0.0.1" - if !proxy.IsAppProxyEnabled(appName) { - return ipAddress - } - b, err := common.DockerInspect(containerID, "'{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'") if err != nil || len(b) == 0 { // docker < 1.9 compatibility @@ -143,15 +137,12 @@ func GetContainerPort(appName string, procType string, isHerokuishContainer bool break } } - } else { - port = "5000" - } - - if !proxy.IsAppProxyEnabled(appName) { b, err := sh.Command("docker", "port", containerID, port).Output() if err == nil { port = strings.Split(string(b[:]), ":")[1] } + } else { + port = "5000" } return port From c2ee6e99c61a9568bf9f5b7f54423f88823e9a4a Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 1 Oct 2017 18:46:44 -0400 Subject: [PATCH 54/66] fix: update unconfigured client test regex --- tests/unit/30_client.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/30_client.bats b/tests/unit/30_client.bats index 7f1d97f00..4e4a7c609 100644 --- a/tests/unit/30_client.bats +++ b/tests/unit/30_client.bats @@ -23,7 +23,7 @@ teardown() { } @test "(client) no args should print help" { - run /bin/bash -c "./contrib/dokku_client.sh | head -1 | egrep -q '^Usage: dokku \[.+\] COMMAND .*'" + run /bin/bash -c "./contrib/dokku_client.sh | head -1 | egrep -q '^Usage: dokku \[.+\] COMMAND \[.+\]'" echo "output: "$output echo "status: "$status assert_success From edfec35177fce0605fa074b87b2f58dc7f5b164b Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 1 Oct 2017 18:52:48 -0400 Subject: [PATCH 55/66] feat: add symlinks to all network and proxy hooks --- plugins/20_events/network-build-config | 1 + plugins/20_events/network-compute-ports | 1 + plugins/20_events/network-config-exists | 1 + plugins/20_events/network-get-ipaddr | 1 + plugins/20_events/network-get-listeners | 1 + plugins/20_events/network-get-port | 1 + plugins/20_events/network-get-property | 1 + plugins/20_events/network-write-ipaddr | 1 + plugins/20_events/network-write-port | 1 + plugins/20_events/proxy-build-config | 1 + plugins/20_events/proxy-disable | 1 + plugins/20_events/proxy-enable | 1 + 12 files changed, 12 insertions(+) create mode 120000 plugins/20_events/network-build-config create mode 120000 plugins/20_events/network-compute-ports create mode 120000 plugins/20_events/network-config-exists create mode 120000 plugins/20_events/network-get-ipaddr create mode 120000 plugins/20_events/network-get-listeners create mode 120000 plugins/20_events/network-get-port create mode 120000 plugins/20_events/network-get-property create mode 120000 plugins/20_events/network-write-ipaddr create mode 120000 plugins/20_events/network-write-port create mode 120000 plugins/20_events/proxy-build-config create mode 120000 plugins/20_events/proxy-disable create mode 120000 plugins/20_events/proxy-enable diff --git a/plugins/20_events/network-build-config b/plugins/20_events/network-build-config new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/network-build-config @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/network-compute-ports b/plugins/20_events/network-compute-ports new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/network-compute-ports @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/network-config-exists b/plugins/20_events/network-config-exists new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/network-config-exists @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/network-get-ipaddr b/plugins/20_events/network-get-ipaddr new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/network-get-ipaddr @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/network-get-listeners b/plugins/20_events/network-get-listeners new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/network-get-listeners @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/network-get-port b/plugins/20_events/network-get-port new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/network-get-port @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/network-get-property b/plugins/20_events/network-get-property new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/network-get-property @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/network-write-ipaddr b/plugins/20_events/network-write-ipaddr new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/network-write-ipaddr @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/network-write-port b/plugins/20_events/network-write-port new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/network-write-port @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/proxy-build-config b/plugins/20_events/proxy-build-config new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/proxy-build-config @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/proxy-disable b/plugins/20_events/proxy-disable new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/proxy-disable @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/20_events/proxy-enable b/plugins/20_events/proxy-enable new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/proxy-enable @@ -0,0 +1 @@ +hook \ No newline at end of file From 7d73577952ab79ab2a127410f18a431903cb5edf Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 1 Oct 2017 19:17:03 -0400 Subject: [PATCH 56/66] refactor: use grep -E instead of egrep --- tests/unit/30_client.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/30_client.bats b/tests/unit/30_client.bats index 4e4a7c609..126206222 100644 --- a/tests/unit/30_client.bats +++ b/tests/unit/30_client.bats @@ -23,7 +23,7 @@ teardown() { } @test "(client) no args should print help" { - run /bin/bash -c "./contrib/dokku_client.sh | head -1 | egrep -q '^Usage: dokku \[.+\] COMMAND \[.+\]'" + run /bin/bash -c "./contrib/dokku_client.sh | head -1 | grep -E '^Usage: dokku \[.+\] COMMAND \[.+\]'" echo "output: "$output echo "status: "$status assert_success From 690c32ceb8dcb3441b824bc13a7042161dff6b5d Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 1 Oct 2017 19:47:40 -0400 Subject: [PATCH 57/66] fix: use the same method to trigger the client --- tests/unit/30_client.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/30_client.bats b/tests/unit/30_client.bats index 126206222..f2670a330 100644 --- a/tests/unit/30_client.bats +++ b/tests/unit/30_client.bats @@ -23,7 +23,7 @@ teardown() { } @test "(client) no args should print help" { - run /bin/bash -c "./contrib/dokku_client.sh | head -1 | grep -E '^Usage: dokku \[.+\] COMMAND \[.+\]'" + run bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh | head -1 | grep -E '^Usage: dokku \[.+\] COMMAND \[.+\]'" echo "output: "$output echo "status: "$status assert_success From 5e32f9a89d384d6d46627b4dc9dbb1c69e084403 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 1 Oct 2017 20:40:05 -0400 Subject: [PATCH 58/66] refactor: go back to the old regex --- tests/unit/30_client.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/30_client.bats b/tests/unit/30_client.bats index f2670a330..6eabb2571 100644 --- a/tests/unit/30_client.bats +++ b/tests/unit/30_client.bats @@ -23,7 +23,7 @@ teardown() { } @test "(client) no args should print help" { - run bash -c "${BATS_TEST_DIRNAME}/../../contrib/dokku_client.sh | head -1 | grep -E '^Usage: dokku \[.+\] COMMAND \[.+\]'" + run /bin/bash -c "./contrib/dokku_client.sh | head -1 | grep -E '^Usage: dokku \[.+\] COMMAND .*'" echo "output: "$output echo "status: "$status assert_success From fbf90770758be066729664299822f73a843a10e5 Mon Sep 17 00:00:00 2001 From: Michael Hobbs Date: Mon, 2 Oct 2017 13:24:32 -0700 Subject: [PATCH 59/66] make test more flexible --- tests/unit/30_client.bats | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/30_client.bats b/tests/unit/30_client.bats index 6eabb2571..2914688e1 100644 --- a/tests/unit/30_client.bats +++ b/tests/unit/30_client.bats @@ -23,7 +23,7 @@ teardown() { } @test "(client) no args should print help" { - run /bin/bash -c "./contrib/dokku_client.sh | head -1 | grep -E '^Usage: dokku \[.+\] COMMAND .*'" + run /bin/bash -c "./contrib/dokku_client.sh | head -1 | grep -E 'Usage: dokku \[.+\] COMMAND .*'" echo "output: "$output echo "status: "$status assert_success From c9ea884e5dbc8bf9f5348dde21cead567c77a60a Mon Sep 17 00:00:00 2001 From: Michael Hobbs Date: Mon, 2 Oct 2017 16:36:12 -0700 Subject: [PATCH 60/66] move coverage command to only run on ci --- tests.mk | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests.mk b/tests.mk index 2c325f064..f67502c91 100644 --- a/tests.mk +++ b/tests.mk @@ -81,7 +81,9 @@ ci-go-coverage: -v $$PWD:$(GO_REPO_ROOT) \ -w $(GO_REPO_ROOT) \ $(BUILD_IMAGE) \ - bash -c "go get github.com/schrej/godacov && godacov -t $$CODACY_TOKEN -r ./coverage.out -c $$CIRCLE_SHA1" || exit $$? + bash -c "go get github.com/schrej/godacov github.com/haya14busa/goverage && \ + go list ./... | egrep -v '/vendor/|/tests/apps/' | xargs goverage -v -coverprofile=coverage.out && \ + godacov -t $$CODACY_TOKEN -r ./coverage.out -c $$CIRCLE_SHA1" || exit $$? go-tests: @echo running go unit tests... @@ -90,9 +92,8 @@ go-tests: -v $$PWD:$(GO_REPO_ROOT) \ -w $(GO_REPO_ROOT) \ $(BUILD_IMAGE) \ - bash -c "go get github.com/onsi/gomega github.com/haya14busa/goverage && \ - go list ./... | egrep -v '/vendor/|/tests/apps/' | xargs go test -v -p 1 -race && \ - go list ./... | egrep -v '/vendor/|/tests/apps/' | xargs goverage -v -coverprofile=coverage.out" || exit $$? + bash -c "go get github.com/onsi/gomega && \ + go list ./... | egrep -v '/vendor/|/tests/apps/' | xargs go test -v -p 1 -race" || exit $$? unit-tests: go-tests @echo running bats unit tests... From b099d2c3895860e063642b17cb37763fed108dab Mon Sep 17 00:00:00 2001 From: Michael Hobbs Date: Mon, 2 Oct 2017 16:49:54 -0700 Subject: [PATCH 61/66] add more tests --- plugins/common/common_test.go | 88 +++++++++++++++++++++++++++++---- plugins/config/config_test.go | 36 ++++++++++++++ plugins/network/network_test.go | 12 +++++ 3 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 plugins/config/config_test.go create mode 100644 plugins/network/network_test.go diff --git a/plugins/common/common_test.go b/plugins/common/common_test.go index 9d9a9a5b1..535ba0f01 100644 --- a/plugins/common/common_test.go +++ b/plugins/common/common_test.go @@ -1,38 +1,106 @@ package common import ( + "io/ioutil" "os" + "strings" "testing" . "github.com/onsi/gomega" ) -func TestGetEnv(t *testing.T) { +var ( + testAppName = "test-app-1" + testAppDir = strings.Join([]string{"/home/dokku/", testAppName}, "") + testEnvFile = strings.Join([]string{testAppDir, "/ENV"}, "") + testEnvLine = "export testKey=TESTING" +) + +func setupTestApp() (err error) { + Expect(os.MkdirAll(testAppDir, 0644)).To(Succeed()) + b := []byte(testEnvLine + "\n") + if err = ioutil.WriteFile(testEnvFile, b, 0644); err != nil { + return + } + return +} + +func teardownTestApp() { + os.RemoveAll(testAppDir) +} + +func TestCommonGetEnv(t *testing.T) { RegisterTestingT(t) Expect(MustGetEnv("DOKKU_ROOT")).To(Equal("/home/dokku")) } -func TestGetAppImageRepo(t *testing.T) { +func TestCommonGetAppImageRepo(t *testing.T) { RegisterTestingT(t) Expect(GetAppImageRepo("testapp")).To(Equal("dokku/testapp")) } -func TestVerifyImageInvalid(t *testing.T) { +func TestCommonVerifyImageInvalid(t *testing.T) { RegisterTestingT(t) Expect(VerifyImage("testapp")).To(Equal(false)) } -func TestVerifyAppNameInvalid(t *testing.T) { +func TestCommonVerifyAppNameInvalid(t *testing.T) { RegisterTestingT(t) err := VerifyAppName("1994testApp") Expect(err).To(HaveOccurred()) } -func TestVerifyAppName(t *testing.T) { +func TestCommonVerifyAppName(t *testing.T) { RegisterTestingT(t) - dir := "/home/dokku/testApp" - os.MkdirAll(dir, 0644) - err := VerifyAppName("testApp") - Expect(err).NotTo(HaveOccurred()) - os.RemoveAll(dir) + Expect(setupTestApp()).To(Succeed()) + Expect(VerifyAppName(testAppName)).To(Succeed()) + teardownTestApp() +} + +func TestCommonDokkuAppsError(t *testing.T) { + RegisterTestingT(t) + _, err := DokkuApps() + Expect(err).To(HaveOccurred()) +} + +func TestCommonDokkuApps(t *testing.T) { + RegisterTestingT(t) + Expect(setupTestApp()).To(Succeed()) + apps, err := DokkuApps() + Expect(err).NotTo(HaveOccurred()) + Expect(apps).To(HaveLen(1)) + Expect(apps[0]).To(Equal(testAppName)) + teardownTestApp() +} + +func TestCommonFileToSlice(t *testing.T) { + RegisterTestingT(t) + Expect(setupTestApp()).To(Succeed()) + lines, err := FileToSlice(testEnvFile) + Expect(err).NotTo(HaveOccurred()) + Expect(lines).To(Equal([]string{testEnvLine})) + teardownTestApp() +} + +func TestCommonFileExists(t *testing.T) { + RegisterTestingT(t) + Expect(setupTestApp()).To(Succeed()) + Expect(FileExists(testEnvFile)).To(BeTrue()) + teardownTestApp() +} + +func TestCommonReadFirstLine(t *testing.T) { + RegisterTestingT(t) + line := ReadFirstLine(testEnvFile) + Expect(line).To(Equal("")) + Expect(setupTestApp()).To(Succeed()) + line = ReadFirstLine(testEnvFile) + Expect(line).To(Equal(testEnvLine)) + teardownTestApp() +} + +func TestCommonStripInlineComments(t *testing.T) { + RegisterTestingT(t) + text := StripInlineComments(strings.Join([]string{testEnvLine, "# testing comment"}, " ")) + Expect(text).To(Equal(testEnvLine)) } diff --git a/plugins/config/config_test.go b/plugins/config/config_test.go new file mode 100644 index 000000000..dbe660065 --- /dev/null +++ b/plugins/config/config_test.go @@ -0,0 +1,36 @@ +package config + +import ( + "io/ioutil" + "os" + "strings" + "testing" + + . "github.com/onsi/gomega" +) + +var ( + testAppName = "test-app-1" + testAppDir = strings.Join([]string{"/home/dokku/", testAppName}, "") +) + +func setupTestApp() (err error) { + Expect(os.MkdirAll(testAppDir, 0644)).To(Succeed()) + b := []byte("export testKey=TESTING\n") + if err = ioutil.WriteFile(strings.Join([]string{testAppDir, "/ENV"}, ""), b, 0644); err != nil { + return + } + return +} + +func teardownTestApp() { + os.RemoveAll(testAppDir) +} + +func TestConfigGetWithDefault(t *testing.T) { + RegisterTestingT(t) + Expect(setupTestApp()).To(Succeed()) + Expect(GetWithDefault(testAppName, "unknownKey", "UNKNOWN")).To(Equal("UNKNOWN")) + Expect(GetWithDefault(testAppName, "testKey", "testKey")).To(Equal("TESTING")) + teardownTestApp() +} diff --git a/plugins/network/network_test.go b/plugins/network/network_test.go new file mode 100644 index 000000000..9aed75050 --- /dev/null +++ b/plugins/network/network_test.go @@ -0,0 +1,12 @@ +package network + +import ( + "testing" + + . "github.com/onsi/gomega" +) + +func TestNetworkGetDefaultValue(t *testing.T) { + RegisterTestingT(t) + Expect(GetDefaultValue("bind-all-interfaces")).To(Equal("false")) +} From 865478313178b2b6c2fdf7d82f30add56d692522 Mon Sep 17 00:00:00 2001 From: Michael Hobbs Date: Mon, 2 Oct 2017 16:50:05 -0700 Subject: [PATCH 62/66] style tweaks --- plugins/common/common.go | 54 +++++++-------- plugins/common/properties.go | 67 +++++++------------ plugins/config/config.go | 13 ++-- plugins/network/network.go | 48 ++++++------- .../src/subcommands/rebuild/rebuild.go | 2 +- .../src/subcommands/rebuildall/rebuildall.go | 4 +- plugins/network/src/subcommands/set/set.go | 6 +- .../network/src/triggers/install/install.go | 7 +- .../network-build-config.go | 2 +- .../network-compute-ports.go | 4 +- .../network-config-exists.go | 2 +- .../network-get-ipaddr/network-get-ipaddr.go | 2 +- .../network-get-listeners.go | 4 +- .../network-get-port/network-get-port.go | 2 +- .../network-get-property.go | 2 +- .../network-write-ipaddr.go | 2 +- .../network-write-port/network-write-port.go | 2 +- .../src/triggers/post-create/post-create.go | 2 +- .../src/triggers/post-delete/post-delete.go | 2 +- plugins/proxy/proxy.go | 4 +- plugins/repo/src/subcommands/gc/gc.go | 2 +- .../subcommands/purge-cache/purge-cache.go | 2 +- 22 files changed, 101 insertions(+), 134 deletions(-) diff --git a/plugins/common/common.go b/plugins/common/common.go index 408b40a23..fcaceadaa 100644 --- a/plugins/common/common.go +++ b/plugins/common/common.go @@ -2,7 +2,6 @@ package common import ( "bufio" - "errors" "fmt" "io/ioutil" "os" @@ -46,8 +45,7 @@ func (sc *ShellCmd) Execute() bool { sc.Command.Stdout = os.Stdout sc.Command.Stderr = os.Stderr } - err := sc.Command.Run() - if err != nil { + if err := sc.Command.Run(); err != nil { return false } return true @@ -136,27 +134,25 @@ func DirectoryExists(filePath string) bool { } // DockerInspect runs an inspect command with a given format against a container id -func DockerInspect(containerID, format string) (string, error) { +func DockerInspect(containerID, format string) (output string, err error) { b, err := sh.Command("docker", "inspect", "--format", format, containerID).Output() if err != nil { return "", err } - output := strings.TrimSpace(string(b[:])) + output = strings.TrimSpace(string(b[:])) if strings.HasPrefix(output, "'") && strings.HasSuffix(output, "'") { output = strings.TrimSuffix(strings.TrimPrefix(output, "'"), "'") } - return output, err - + return } // DokkuApps returns a list of all local apps -func DokkuApps() ([]string, error) { - var apps []string - +func DokkuApps() (apps []string, err error) { dokkuRoot := MustGetEnv("DOKKU_ROOT") files, err := ioutil.ReadDir(dokkuRoot) if err != nil { - return apps, errors.New("You haven't deployed any applications yet") + err = fmt.Errorf("You haven't deployed any applications yet") + return } for _, f := range files { @@ -171,18 +167,18 @@ func DokkuApps() ([]string, error) { } if len(apps) == 0 { - return apps, errors.New("You haven't deployed any applications yet") + err = fmt.Errorf("You haven't deployed any applications yet") + return } - return apps, nil + return } // FileToSlice reads in all the lines from a file into a string slice -func FileToSlice(filePath string) ([]string, error) { - var lines []string +func FileToSlice(filePath string) (lines []string, err error) { f, err := os.Open(filePath) if err != nil { - return lines, err + return } defer f.Close() @@ -195,7 +191,7 @@ func FileToSlice(filePath string) ([]string, error) { lines = append(lines, text) } err = scanner.Err() - return lines, err + return } // FileExists returns if a path exists and is a file @@ -244,7 +240,6 @@ func IsDeployed(appName string) bool { return true } } - return false } @@ -272,41 +267,40 @@ func IsImageHerokuishBased(image string) bool { } // MustGetEnv returns env variable or fails if it's not set -func MustGetEnv(key string) string { - value := os.Getenv(key) - if value == "" { +func MustGetEnv(key string) (val string) { + val = os.Getenv(key) + if val == "" { LogFail(fmt.Sprintf("%s not set!", key)) } - return value + return } // ReadFirstLine gets the first line of a file that has contents and returns it // if there are no contents, an empty string is returned // will also return an empty string if the file does not exist -func ReadFirstLine(filename string) string { +func ReadFirstLine(filename string) (text string) { if !FileExists(filename) { - return "" + return } f, err := os.Open(filename) if err != nil { - return "" + return } defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { - text := strings.TrimSpace(scanner.Text()) - if text == "" { + if text = strings.TrimSpace(scanner.Text()); text == "" { continue } - return text + return } - return "" + return } // StripInlineComments removes bash-style comment from input line func StripInlineComments(text string) string { - var bytes = []byte(text) + bytes := []byte(text) re := regexp.MustCompile("(?s)#.*") bytes = re.ReplaceAll(bytes, nil) return strings.TrimSpace(string(bytes)) diff --git a/plugins/common/properties.go b/plugins/common/properties.go index 565679a6c..88717fcc2 100644 --- a/plugins/common/properties.go +++ b/plugins/common/properties.go @@ -11,18 +11,16 @@ import ( ) // CommandPropertySet is a generic function that will set a property for a given plugin/app combination -func CommandPropertySet(pluginName string, appName string, property string, value string, validProperties map[string]bool) { - err := VerifyAppName(appName) - if err != nil { +func CommandPropertySet(pluginName, appName, property, value string, properties map[string]string) { + if err := VerifyAppName(appName); err != nil { LogFail(err.Error()) } - if property == "" { LogFail("No property specified") } - if !isValidProperty(validProperties, property) { - properties := reflect.ValueOf(validProperties).MapKeys() + if _, ok := properties[property]; !ok { + properties := reflect.ValueOf(properties).MapKeys() validPropertyList := make([]string, len(properties)) for i := 0; i < len(properties); i++ { validPropertyList[i] = properties[i].String() @@ -44,8 +42,7 @@ func CommandPropertySet(pluginName string, appName string, property string, valu func PropertyDelete(pluginName string, appName string, property string) { pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName) propertyPath := strings.Join([]string{pluginAppConfigRoot, property}, "/") - err := os.Remove(propertyPath) - if err != nil { + if err := os.Remove(propertyPath); err != nil { LogFail(fmt.Sprintf("Unable to remove %s property %s.%s", pluginName, appName, property)) } } @@ -75,9 +72,9 @@ func PropertyGet(pluginName string, appName string, property string) string { } // PropertyGetDefault returns the value for a given property with a specified default value -func PropertyGetDefault(pluginName string, appName string, property string, defaultValue string) string { +func PropertyGetDefault(pluginName, appName, property, defaultValue string) (val string) { if !PropertyExists(pluginName, appName, property) { - return "" + return } pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName) @@ -86,16 +83,15 @@ func PropertyGetDefault(pluginName string, appName string, property string, defa b, err := ioutil.ReadFile(propertyPath) if err != nil { LogWarn(fmt.Sprintf("Unable to read %s property %s.%s", pluginName, appName, property)) - return "" + return } - - return string(b) + val = string(b) + return } // PropertyWrite writes a value for a given application property func PropertyWrite(pluginName string, appName string, property string, value string) { - err := makePropertyPath(pluginName, appName) - if err != nil { + if err := makePropertyPath(pluginName, appName); err != nil { LogFail(fmt.Sprintf("Unable to create %s config directory for %s: %s", pluginName, appName, err.Error())) } @@ -103,7 +99,7 @@ func PropertyWrite(pluginName string, appName string, property string, value str propertyPath := strings.Join([]string{pluginAppConfigRoot, property}, "/") file, err := os.Create(propertyPath) if err != nil { - LogFail(fmt.Sprintf("Unable to write %s config value %s.%s: %s", pluginName, appName, property, err.Error)) + LogFail(fmt.Sprintf("Unable to write %s config value %s.%s: %s", pluginName, appName, property, err.Error())) } defer file.Close() @@ -113,20 +109,14 @@ func PropertyWrite(pluginName string, appName string, property string, value str } // PropertySetup creates the plugin config root -func PropertySetup(pluginName string) error { +func PropertySetup(pluginName string) (err error) { pluginConfigRoot := getPluginConfigPath(pluginName) - err := os.MkdirAll(pluginConfigRoot, 0755) - if err != nil { - return err + if err = os.MkdirAll(pluginConfigRoot, 0755); err != nil { + return } return setPermissions(pluginConfigRoot, 0755) } -// isValidProperty returns whether a property is a valid property or not -func isValidProperty(validProperties map[string]bool, property string) bool { - return validProperties[property] -} - // getPluginAppPropertyPath returns the plugin property path for a given plugin/app combination func getPluginAppPropertyPath(pluginName string, appName string) string { return strings.Join([]string{getPluginConfigPath(pluginName), appName}, "/") @@ -138,44 +128,37 @@ func getPluginConfigPath(pluginName string) string { } // makePropertyPath ensures that a property path exists -func makePropertyPath(pluginName string, appName string) error { +func makePropertyPath(pluginName string, appName string) (err error) { pluginAppConfigRoot := getPluginAppPropertyPath(pluginName, appName) - err := os.MkdirAll(pluginAppConfigRoot, 0755) - if err != nil { - return err + if err = os.MkdirAll(pluginAppConfigRoot, 0755); err != nil { + return } return setPermissions(pluginAppConfigRoot, 0755) } // setPermissions sets the proper owner and filemode for a given file -func setPermissions(path string, fileMode os.FileMode) error { - err := os.Chmod(path, fileMode) - if err != nil { +func setPermissions(path string, fileMode os.FileMode) (err error) { + if err = os.Chmod(path, fileMode); err != nil { return err } group, err := user.LookupGroup("dokku") if err != nil { - return err + return } user, err := user.Lookup("dokku") if err != nil { - return err + return } uid, err := strconv.Atoi(user.Uid) if err != nil { - return err + return } gid, err := strconv.Atoi(group.Gid) if err != nil { - return err + return } - - err = os.Chown(path, uid, gid) - if err != nil { - return err - } - return nil + return os.Chown(path, uid, gid) } diff --git a/plugins/config/config.go b/plugins/config/config.go index 8ef087a35..328cccaca 100644 --- a/plugins/config/config.go +++ b/plugins/config/config.go @@ -4,18 +4,18 @@ import ( "fmt" "strings" - common "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/common" ) // GetWithDefault returns the value set for a given key, returning defaultValue if none found -func GetWithDefault(appName string, key string, defaultValue string) string { +func GetWithDefault(appName string, key string, defaultValue string) (value string) { + value = defaultValue + envFile := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName, "ENV"}, "/") lines, err := common.FileToSlice(envFile) if err != nil { - return defaultValue + return } - - value := defaultValue prefix := fmt.Sprintf("export %v=", key) for _, line := range lines { if !strings.HasPrefix(line, prefix) { @@ -26,6 +26,5 @@ func GetWithDefault(appName string, key string, defaultValue string) string { value = strings.TrimPrefix(strings.TrimSuffix(value, "'"), "'") } } - - return value + return } diff --git a/plugins/network/network.go b/plugins/network/network.go index f99290520..c5e117c54 100644 --- a/plugins/network/network.go +++ b/plugins/network/network.go @@ -5,33 +5,27 @@ import ( "strconv" "strings" - common "github.com/dokku/dokku/plugins/common" - config "github.com/dokku/dokku/plugins/config" + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/config" sh "github.com/codeskyblue/go-sh" ) -// ValidProperties is a map of all valid network properties -var ValidProperties = map[string]bool{ - "bind-all-interfaces": true, -} - -// DefaultPropertyValues is a map of all valid network properties with corresponding default property values -var DefaultPropertyValues = map[string]string{ - "bind-all-interfaces": "false", -} +var ( + // DefaultProperties is a map of all valid network properties with corresponding default property values + DefaultProperties = map[string]string{ + "bind-all-interfaces": "false", + } +) // BuildConfig builds network config files func BuildConfig(appName string) { - err := common.VerifyAppName(appName) - if err != nil { + if err := common.VerifyAppName(appName); err != nil { common.LogFail(err.Error()) } - if !common.IsDeployed(appName) { return } - appRoot := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName}, "/") scaleFile := strings.Join([]string{appRoot, "DOKKU_SCALE"}, "/") if !common.FileExists(scaleFile) { @@ -40,7 +34,6 @@ func BuildConfig(appName string) { image := common.GetAppImageName(appName, "", "") isHerokuishContainer := common.IsImageHerokuishBased(image) - common.LogInfo1(fmt.Sprintf("Ensuring network configuration is in sync for %s", appName)) lines, err := common.FileToSlice(scaleFile) if err != nil { @@ -93,9 +86,9 @@ func BuildConfig(appName string) { } // GetContainerIpaddress returns the ipaddr for a given app container -func GetContainerIpaddress(appName string, procType string, containerID string) string { +func GetContainerIpaddress(appName, procType, containerID string) (ipAddr string) { if procType != "web" { - return "" + return } b, err := common.DockerInspect(containerID, "'{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'") @@ -108,18 +101,16 @@ func GetContainerIpaddress(appName string, procType string, containerID string) return string(b[:]) } - return "" + return } // GetContainerPort returns the port for a given app container -func GetContainerPort(appName string, procType string, isHerokuishContainer bool, containerID string) string { +func GetContainerPort(appName, procType string, isHerokuishContainer bool, containerID string) (port string) { if procType != "web" { - return "" + return } dockerfilePorts := make([]string, 0) - port := "" - if !isHerokuishContainer { configValue := config.GetWithDefault(appName, "DOKKU_DOCKERFILE_PORTS", "") if configValue != "" { @@ -145,15 +136,16 @@ func GetContainerPort(appName string, procType string, isHerokuishContainer bool port = "5000" } - return port + return } // GetDefaultValue returns the default value for a given property -func GetDefaultValue(property string) string { - if ValidProperties[property] { - return DefaultPropertyValues[property] +func GetDefaultValue(property string) (value string) { + value, ok := DefaultProperties[property] + if ok { + return } - return "" + return } // HasNetworkConfig returns whether the network configuration for a given app exists diff --git a/plugins/network/src/subcommands/rebuild/rebuild.go b/plugins/network/src/subcommands/rebuild/rebuild.go index b608e2190..b2d129532 100644 --- a/plugins/network/src/subcommands/rebuild/rebuild.go +++ b/plugins/network/src/subcommands/rebuild/rebuild.go @@ -3,7 +3,7 @@ package main import ( "flag" - network "github.com/dokku/dokku/plugins/network" + "github.com/dokku/dokku/plugins/network" ) // rebuilds network settings for an app diff --git a/plugins/network/src/subcommands/rebuildall/rebuildall.go b/plugins/network/src/subcommands/rebuildall/rebuildall.go index c55b50f73..01c09c670 100644 --- a/plugins/network/src/subcommands/rebuildall/rebuildall.go +++ b/plugins/network/src/subcommands/rebuildall/rebuildall.go @@ -1,8 +1,8 @@ package main import ( - common "github.com/dokku/dokku/plugins/common" - network "github.com/dokku/dokku/plugins/network" + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/network" ) // rebuilds network settings for all apps diff --git a/plugins/network/src/subcommands/set/set.go b/plugins/network/src/subcommands/set/set.go index 119707111..cd8eb73ef 100644 --- a/plugins/network/src/subcommands/set/set.go +++ b/plugins/network/src/subcommands/set/set.go @@ -3,8 +3,8 @@ package main import ( "flag" - common "github.com/dokku/dokku/plugins/common" - network "github.com/dokku/dokku/plugins/network" + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/network" ) // set or clear a network property for an app @@ -18,5 +18,5 @@ func main() { value = "false" } - common.CommandPropertySet("network", appName, property, value, network.ValidProperties) + common.CommandPropertySet("network", appName, property, value, network.DefaultProperties) } diff --git a/plugins/network/src/triggers/install/install.go b/plugins/network/src/triggers/install/install.go index 85c2229a9..f78133512 100644 --- a/plugins/network/src/triggers/install/install.go +++ b/plugins/network/src/triggers/install/install.go @@ -3,14 +3,13 @@ package main import ( "fmt" - common "github.com/dokku/dokku/plugins/common" - proxy "github.com/dokku/dokku/plugins/proxy" + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/proxy" ) // runs the install step for the network plugin func main() { - err := common.PropertySetup("network") - if err != nil { + if err := common.PropertySetup("network"); err != nil { common.LogFail(fmt.Sprintf("Unable to install the network plugin: %s", err.Error())) } diff --git a/plugins/network/src/triggers/network-build-config/network-build-config.go b/plugins/network/src/triggers/network-build-config/network-build-config.go index 51279ec5f..6c2bc2af8 100644 --- a/plugins/network/src/triggers/network-build-config/network-build-config.go +++ b/plugins/network/src/triggers/network-build-config/network-build-config.go @@ -3,7 +3,7 @@ package main import ( "flag" - network "github.com/dokku/dokku/plugins/network" + "github.com/dokku/dokku/plugins/network" ) // rebuilds network settings for an app diff --git a/plugins/network/src/triggers/network-compute-ports/network-compute-ports.go b/plugins/network/src/triggers/network-compute-ports/network-compute-ports.go index baad620c3..b2edbbbde 100644 --- a/plugins/network/src/triggers/network-compute-ports/network-compute-ports.go +++ b/plugins/network/src/triggers/network-compute-ports/network-compute-ports.go @@ -6,8 +6,8 @@ import ( "os" "strings" - common "github.com/dokku/dokku/plugins/common" - config "github.com/dokku/dokku/plugins/config" + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/config" ) // computes the ports for a given app container diff --git a/plugins/network/src/triggers/network-config-exists/network-config-exists.go b/plugins/network/src/triggers/network-config-exists/network-config-exists.go index 43efedc95..6ba8cd737 100644 --- a/plugins/network/src/triggers/network-config-exists/network-config-exists.go +++ b/plugins/network/src/triggers/network-config-exists/network-config-exists.go @@ -5,7 +5,7 @@ import ( "fmt" "os" - network "github.com/dokku/dokku/plugins/network" + "github.com/dokku/dokku/plugins/network" ) // write the ipaddress to stdout for a given app container diff --git a/plugins/network/src/triggers/network-get-ipaddr/network-get-ipaddr.go b/plugins/network/src/triggers/network-get-ipaddr/network-get-ipaddr.go index 5d06c9330..cfdb3bbfd 100644 --- a/plugins/network/src/triggers/network-get-ipaddr/network-get-ipaddr.go +++ b/plugins/network/src/triggers/network-get-ipaddr/network-get-ipaddr.go @@ -5,7 +5,7 @@ import ( "fmt" "os" - network "github.com/dokku/dokku/plugins/network" + "github.com/dokku/dokku/plugins/network" ) // write the ipaddress to stdout for a given app container diff --git a/plugins/network/src/triggers/network-get-listeners/network-get-listeners.go b/plugins/network/src/triggers/network-get-listeners/network-get-listeners.go index 9260a8447..6ef0508aa 100644 --- a/plugins/network/src/triggers/network-get-listeners/network-get-listeners.go +++ b/plugins/network/src/triggers/network-get-listeners/network-get-listeners.go @@ -1,13 +1,13 @@ package main import ( - "path/filepath" "flag" "fmt" "os" + "path/filepath" "strings" - common "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/common" ) // returns the listeners (host:port combinations) for a given app container diff --git a/plugins/network/src/triggers/network-get-port/network-get-port.go b/plugins/network/src/triggers/network-get-port/network-get-port.go index 9ac850788..801bc88ff 100644 --- a/plugins/network/src/triggers/network-get-port/network-get-port.go +++ b/plugins/network/src/triggers/network-get-port/network-get-port.go @@ -5,7 +5,7 @@ import ( "fmt" "os" - common "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/common" network "github.com/dokku/dokku/plugins/network" ) diff --git a/plugins/network/src/triggers/network-get-property/network-get-property.go b/plugins/network/src/triggers/network-get-property/network-get-property.go index da78d3ff9..f7486b93a 100644 --- a/plugins/network/src/triggers/network-get-property/network-get-property.go +++ b/plugins/network/src/triggers/network-get-property/network-get-property.go @@ -5,7 +5,7 @@ import ( "fmt" "os" - common "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/common" network "github.com/dokku/dokku/plugins/network" ) diff --git a/plugins/network/src/triggers/network-write-ipaddr/network-write-ipaddr.go b/plugins/network/src/triggers/network-write-ipaddr/network-write-ipaddr.go index 32fa1fb9e..b76ceb3af 100644 --- a/plugins/network/src/triggers/network-write-ipaddr/network-write-ipaddr.go +++ b/plugins/network/src/triggers/network-write-ipaddr/network-write-ipaddr.go @@ -6,7 +6,7 @@ import ( "os" "strings" - common "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/common" ) // writes the ip to disk diff --git a/plugins/network/src/triggers/network-write-port/network-write-port.go b/plugins/network/src/triggers/network-write-port/network-write-port.go index 97490246d..2a8d45dff 100644 --- a/plugins/network/src/triggers/network-write-port/network-write-port.go +++ b/plugins/network/src/triggers/network-write-port/network-write-port.go @@ -6,7 +6,7 @@ import ( "os" "strings" - common "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/common" ) // writes the port to disk diff --git a/plugins/network/src/triggers/post-create/post-create.go b/plugins/network/src/triggers/post-create/post-create.go index 0ddcc7e0e..4d03f0df3 100644 --- a/plugins/network/src/triggers/post-create/post-create.go +++ b/plugins/network/src/triggers/post-create/post-create.go @@ -3,7 +3,7 @@ package main import ( "flag" - common "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/common" ) // set bind-all-interfaces to false by default diff --git a/plugins/network/src/triggers/post-delete/post-delete.go b/plugins/network/src/triggers/post-delete/post-delete.go index 3127b5d9b..bd620ceb8 100644 --- a/plugins/network/src/triggers/post-delete/post-delete.go +++ b/plugins/network/src/triggers/post-delete/post-delete.go @@ -3,7 +3,7 @@ package main import ( "flag" - common "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/common" ) // write the port to stdout for a given app container diff --git a/plugins/proxy/proxy.go b/plugins/proxy/proxy.go index 2fcf2372a..423e09f5a 100644 --- a/plugins/proxy/proxy.go +++ b/plugins/proxy/proxy.go @@ -1,8 +1,8 @@ package proxy import ( - common "github.com/dokku/dokku/plugins/common" - config "github.com/dokku/dokku/plugins/config" + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/config" ) // IsAppProxyEnabled returns true if proxy is enabled; otherwise return false diff --git a/plugins/repo/src/subcommands/gc/gc.go b/plugins/repo/src/subcommands/gc/gc.go index 7787385b8..1edcc741a 100644 --- a/plugins/repo/src/subcommands/gc/gc.go +++ b/plugins/repo/src/subcommands/gc/gc.go @@ -4,7 +4,7 @@ import ( "flag" "strings" - common "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/common" ) // runs 'git gc --aggressive' against the application's repo diff --git a/plugins/repo/src/subcommands/purge-cache/purge-cache.go b/plugins/repo/src/subcommands/purge-cache/purge-cache.go index 863d4d46c..29b04cd75 100644 --- a/plugins/repo/src/subcommands/purge-cache/purge-cache.go +++ b/plugins/repo/src/subcommands/purge-cache/purge-cache.go @@ -5,7 +5,7 @@ import ( "os" "strings" - common "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/common" ) // deletes the contents of the build cache stored in the repository From fdbab2c291e14a5dfc8948a45f9814fc3c6bd7b4 Mon Sep 17 00:00:00 2001 From: Michael Hobbs Date: Mon, 2 Oct 2017 16:57:45 -0700 Subject: [PATCH 63/66] fix test coverage deps --- tests.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests.mk b/tests.mk index f67502c91..6a62d9cdd 100644 --- a/tests.mk +++ b/tests.mk @@ -81,7 +81,7 @@ ci-go-coverage: -v $$PWD:$(GO_REPO_ROOT) \ -w $(GO_REPO_ROOT) \ $(BUILD_IMAGE) \ - bash -c "go get github.com/schrej/godacov github.com/haya14busa/goverage && \ + bash -c "go get github.com/onsi/gomega github.com/schrej/godacov github.com/haya14busa/goverage && \ go list ./... | egrep -v '/vendor/|/tests/apps/' | xargs goverage -v -coverprofile=coverage.out && \ godacov -t $$CODACY_TOKEN -r ./coverage.out -c $$CIRCLE_SHA1" || exit $$? From ad732bd42bd898f2d1da378c4dd0e9ae1492ed62 Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Wed, 4 Oct 2017 00:48:02 -0400 Subject: [PATCH 64/66] feat: add network:report command --- docs/networking/network.md | 39 +++++++++++ plugins/common/common.go | 9 +++ plugins/common/log.go | 2 +- plugins/network/Makefile | 5 +- plugins/network/network.go | 18 +++++ plugins/network/src/commands/commands.go | 1 + .../network/src/subcommands/report/report.go | 67 +++++++++++++++++++ .../network-get-listeners.go | 17 +---- 8 files changed, 141 insertions(+), 17 deletions(-) create mode 100644 plugins/network/src/subcommands/report/report.go diff --git a/docs/networking/network.md b/docs/networking/network.md index a1fea00ed..810b39ecc 100644 --- a/docs/networking/network.md +++ b/docs/networking/network.md @@ -3,6 +3,7 @@ > New as of 0.11.0 ``` +network:report [] [] # Displays a network report for one or more apps network:rebuild # Rebuilds network settings for an app network:rebuildall # Rebuild network settings for all apps network:set () # Set or clear a network property for an app @@ -83,3 +84,41 @@ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d6499edb0edb dokku/node-js-app:latest "/bin/bash -c '/star About a minute ago Up About a minute 0.0.0.0:49153->5000/tcp node-js-app.web.1 ``` + +### Displaying network reports about an app + +You can get a report about the app's network status using the `network:report` command: + +```shell +dokku network:report +``` + +``` +=====> node-js-app network information + Network bind all interfaces: false + Network listeners: 172.17.0.1:5000 +=====> python-sample network information + Network bind all interfaces: false + Network listeners: 172.17.0.2:5000 +=====> ruby-sample network information + Network bind all interfaces: true + Network listeners: +``` + +You can run the command for a specific app also. + +```shell +dokku network:report node-js-app +``` + +``` +=====> node-js-app network information + Network bind all interfaces: false + Network listeners: 172.17.0.1:5000 +``` + +You can pass flags which will output only the value of the specific information you want. For example: + +```shell +dokku network:report node-js-app --network-bind-all-interfaces +``` diff --git a/plugins/common/common.go b/plugins/common/common.go index fcaceadaa..d8faf3510 100644 --- a/plugins/common/common.go +++ b/plugins/common/common.go @@ -8,6 +8,7 @@ import ( "os/exec" "regexp" "strings" + "unicode" sh "github.com/codeskyblue/go-sh" ) @@ -311,6 +312,14 @@ func ToBool(s string) bool { return s == "true" } +// UcFirst uppercases the first character in a string +func UcFirst(str string) string { + for i, v := range str { + return string(unicode.ToUpper(v)) + str[i+1:] + } + return "" +} + // VerifyAppName verifies app name format and app existence" func VerifyAppName(appName string) (err error) { if appName == "" { diff --git a/plugins/common/log.go b/plugins/common/log.go index f91ee0c69..dd3992e83 100644 --- a/plugins/common/log.go +++ b/plugins/common/log.go @@ -31,7 +31,7 @@ func LogInfo2(text string) { // LogInfo2Quiet is the info2 header formatter (with quiet option) func LogInfo2Quiet(text string) { - if os.Getenv("DOKKU_QUIET_OUTPUT") != "" { + if os.Getenv("DOKKU_QUIET_OUTPUT") == "" { LogInfo2(text) } } diff --git a/plugins/network/Makefile b/plugins/network/Makefile index c435e1d26..c3017156f 100644 --- a/plugins/network/Makefile +++ b/plugins/network/Makefile @@ -8,7 +8,7 @@ build-in-docker: clean bash -c "make build" || exit $$? build: commands subcommands triggers -subcommands: subcommands/rebuild subcommands/rebuildall subcommands/set +subcommands: subcommands/rebuild subcommands/rebuildall subcommands/report subcommands/set triggers: install network-build-config network-compute-ports network-config-exists network-get-ipaddr network-get-listeners network-get-port network-get-property network-write-ipaddr network-write-port post-create post-delete commands: **/**/commands.go go build -a -o commands src/commands/commands.go @@ -19,6 +19,9 @@ subcommands/rebuild: **/**/**/rebuild.go subcommands/rebuildall: **/**/**/rebuildall.go go build -a -o subcommands/rebuildall src/subcommands/rebuildall/rebuildall.go +subcommands/report: **/**/**/report.go + go build -a -o subcommands/report src/subcommands/report/report.go + subcommands/set: **/**/**/set.go go build -a -o subcommands/set src/subcommands/set/set.go diff --git a/plugins/network/network.go b/plugins/network/network.go index c5e117c54..08a8d21e1 100644 --- a/plugins/network/network.go +++ b/plugins/network/network.go @@ -2,6 +2,7 @@ package network import ( "fmt" + "path/filepath" "strconv" "strings" @@ -148,6 +149,23 @@ func GetDefaultValue(property string) (value string) { return } +// GetListeners returns a string array of app listeners +func GetListeners(appName string) []string { + dokkuRoot := common.MustGetEnv("DOKKU_ROOT") + appRoot := strings.Join([]string{dokkuRoot, appName}, "/") + + files, _ := filepath.Glob(appRoot + "/IP.web.*") + + var listeners []string + for _, ipfile := range files { + portfile := strings.Replace(ipfile, "/IP.web.", "/PORT.web.", 1) + ipAddress := common.ReadFirstLine(ipfile) + port := common.ReadFirstLine(portfile) + listeners = append(listeners, fmt.Sprintf("%s:%s", ipAddress, port)) + } + return listeners +} + // HasNetworkConfig returns whether the network configuration for a given app exists func HasNetworkConfig(appName string) bool { appRoot := strings.Join([]string{common.MustGetEnv("DOKKU_ROOT"), appName}, "/") diff --git a/plugins/network/src/commands/commands.go b/plugins/network/src/commands/commands.go index 308fab652..61ec0efba 100644 --- a/plugins/network/src/commands/commands.go +++ b/plugins/network/src/commands/commands.go @@ -18,6 +18,7 @@ Manages network settings for an app Additional commands:` helpContent = ` + network:report [] [], Displays a network report for one or more apps network:rebuild , Rebuilds network settings for an app network:rebuildall, Rebuild network settings for all apps network:set (), Set or clear a network property for an app diff --git a/plugins/network/src/subcommands/report/report.go b/plugins/network/src/subcommands/report/report.go new file mode 100644 index 000000000..382245cb8 --- /dev/null +++ b/plugins/network/src/subcommands/report/report.go @@ -0,0 +1,67 @@ +package main + +import ( + "flag" + "fmt" + "os" + "reflect" + "strings" + + "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/network" +) + +func reportSingleApp(appName, infoFlag string) { + infoFlags := map[string]string{ + "--network-bind-all-interfaces": common.PropertyGet("network", appName, "bind-all-interfaces"), + "--network-listeners": strings.Join(network.GetListeners(appName), " "), + } + + if len(infoFlag) == 0 { + common.LogInfo2Quiet(fmt.Sprintf("%s network information", appName)) + for k, v := range infoFlags { + key := common.UcFirst(strings.Replace(strings.TrimPrefix(k, "--"), "-", " ", -1)) + common.LogVerbose(fmt.Sprintf("%s: %s", key, v)) + } + return + } + + for k, v := range infoFlags { + if infoFlag == k { + fmt.Fprintln(os.Stdout, v) + return + } + } + + keys := reflect.ValueOf(infoFlags).MapKeys() + strkeys := make([]string, len(keys)) + for i := 0; i < len(keys); i++ { + strkeys[i] = keys[i].String() + } + common.LogFail(fmt.Sprintf("Invalid flag passed, valid flags: %s", strings.Join(strkeys, ", "))) +} + +// set or clear a network property for an app +func main() { + flag.Parse() + appName := flag.Arg(1) + infoFlag := flag.Arg(2) + + if strings.HasPrefix(appName, "--") { + infoFlag = appName + appName = "" + } + + if len(appName) == 0 { + apps, err := common.DokkuApps() + if err != nil { + return + } + for _, appName := range apps { + reportSingleApp(appName, infoFlag) + } + return + } + + reportSingleApp(appName, infoFlag) +} diff --git a/plugins/network/src/triggers/network-get-listeners/network-get-listeners.go b/plugins/network/src/triggers/network-get-listeners/network-get-listeners.go index 6ef0508aa..9c999fe4e 100644 --- a/plugins/network/src/triggers/network-get-listeners/network-get-listeners.go +++ b/plugins/network/src/triggers/network-get-listeners/network-get-listeners.go @@ -4,10 +4,9 @@ import ( "flag" "fmt" "os" - "path/filepath" "strings" - "github.com/dokku/dokku/plugins/common" + "github.com/dokku/dokku/plugins/network" ) // returns the listeners (host:port combinations) for a given app container @@ -15,18 +14,6 @@ func main() { flag.Parse() appName := flag.Arg(0) - dokkuRoot := common.MustGetEnv("DOKKU_ROOT") - appRoot := strings.Join([]string{dokkuRoot, appName}, "/") - - files, _ := filepath.Glob(appRoot + "/IP.web.*") - - var listeners []string - for _, ipfile := range files { - portfile := strings.Replace(ipfile, "/IP.web.", "/PORT.web.", 1) - ipAddress := common.ReadFirstLine(ipfile) - port := common.ReadFirstLine(portfile) - listeners = append(listeners, fmt.Sprintf("%s:%s", ipAddress, port)) - } - + listeners := network.GetListeners(appName) fmt.Fprint(os.Stdout, strings.Join(listeners, " ")) } From 722f22e7f002c6421f2d9949d39424b4f422aabf Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Wed, 4 Oct 2017 01:06:17 -0400 Subject: [PATCH 65/66] fix: avoid old gpg key error --- tests.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/tests.mk b/tests.mk index 6a62d9cdd..166407476 100644 --- a/tests.mk +++ b/tests.mk @@ -5,6 +5,7 @@ ifneq ($(shell shellcheck --version > /dev/null 2>&1 ; echo $$?),0) ifeq ($(SYSTEM),Darwin) brew install shellcheck else + sudo apt-key adv --keyserver pgp.mit.edu --recv-keys 5072E1F5 sudo add-apt-repository 'deb http://archive.ubuntu.com/ubuntu trusty-backports main restricted universe multiverse' sudo apt-get update -qq && sudo apt-get install -qq -y shellcheck endif From adedd3af5d9b3566d7ebdaf90eed8fa88fc368dc Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Wed, 4 Oct 2017 01:09:26 -0400 Subject: [PATCH 66/66] fix: avoid Hash Sum mismatch errors --- tests.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/tests.mk b/tests.mk index 166407476..4db82d12a 100644 --- a/tests.mk +++ b/tests.mk @@ -7,6 +7,7 @@ ifeq ($(SYSTEM),Darwin) else sudo apt-key adv --keyserver pgp.mit.edu --recv-keys 5072E1F5 sudo add-apt-repository 'deb http://archive.ubuntu.com/ubuntu trusty-backports main restricted universe multiverse' + sudo rm -rf /var/lib/apt/lists/* && sudo apt-get clean sudo apt-get update -qq && sudo apt-get install -qq -y shellcheck endif endif