From acde3d60a719d53d46213fbf0cc86fad39e84a0d Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sun, 3 Sep 2017 20:36:09 -0400 Subject: [PATCH] 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