mirror of
https://github.com/dokku/dokku.git
synced 2026-02-23 19:50:34 +01:00
feat: add support for routing an app to a specified host:port
This is useful when there is a service not managed by Dokku but should be exposed via the Dokku routing layer. As an example, some binaries (consul, nomad, vault) expose web uis, and are traditionally run on the host directly vs in a container. Closes #4665
This commit is contained in:
15
docs/deployment/builders/null.md
Normal file
15
docs/deployment/builders/null.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Null Builder
|
||||
|
||||
> New as of 0.25.0
|
||||
|
||||
The `null` builder does nothing, and is useful for routing to services not managed by Dokku. It should not be used in normal operation. Please see the [network documentation](/docs/networking/network.md#routing-an-app-to-a-known-ip:port-combination) for more information on the aforementioned use case.
|
||||
|
||||
## Usage
|
||||
|
||||
### Detection
|
||||
|
||||
This builder is _never_ auto-detected. The builder _must_ be specified via the `builder:set` command:
|
||||
|
||||
```shell
|
||||
dokku builder:set node-js-app selected null
|
||||
```
|
||||
15
docs/deployment/schedulers/null.md
Normal file
15
docs/deployment/schedulers/null.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Null Scheduler
|
||||
|
||||
> New as of 0.25.0
|
||||
|
||||
The `null` scheduler does nothing, and is useful for routing to services not managed by Dokku. It should not be used in normal operation. Please see the [network documentation](/docs/networking/network.md#routing-an-app-to-a-known-ip:port-combination) for more information on the aforementioned use case.
|
||||
|
||||
## Usage
|
||||
|
||||
### Detection
|
||||
|
||||
This scheduler is _never_ auto-detected. The scheduler _must_ be specified via the `config:set` command:
|
||||
|
||||
```shell
|
||||
dokku config:set node-js-app DOCKER_SCHEDULER=null
|
||||
```
|
||||
@@ -940,6 +940,21 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
|
||||
# TODO
|
||||
```
|
||||
|
||||
### `network-get-static-listeners`
|
||||
|
||||
- Description: Return the network value for an app's property
|
||||
- Invoked by: `internally triggered by proxy-build-config`
|
||||
- Arguments: `$APP $PROCESS_TYPE`
|
||||
- Example:
|
||||
|
||||
```shell
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
# TODO
|
||||
```
|
||||
|
||||
### `network-write-ipaddr`
|
||||
|
||||
- Description: Write the ipaddr for a given app index
|
||||
|
||||
@@ -123,6 +123,40 @@ dokku network:info test-network
|
||||
// TODO
|
||||
```
|
||||
|
||||
### Routing an app to a known ip:port combination
|
||||
|
||||
> New as of 0.25.0
|
||||
|
||||
In some cases, it may be necessary to route an app to an existing `$IP:$PORT` combination. This is particularly the case for internal admin tools or services that aren't run by Dokku but have a web ui that would benefit from being exposed by Dokku. This can be done by setting a value for `static-web-lister` and running a few other commands when creating an app.
|
||||
|
||||
```shell
|
||||
# for a service listening on:
|
||||
# - ip address: 127.0.0.1
|
||||
# - port: 8080
|
||||
|
||||
# create the app
|
||||
dokku apps:create local-app
|
||||
|
||||
# set the builder to the null builder, which does nothing
|
||||
dokku builder:set local-app selected null
|
||||
|
||||
# set the scheduler to the null scheduler, which does nothing
|
||||
dokku config:set local-app DOKKU_SCHEDULER=null
|
||||
|
||||
# set the static-web-listener network property to the ip:port combination for your app.
|
||||
dokku network:set local-app static-web-listener 127.0.0.1:8080
|
||||
|
||||
# set the port map as desired for the port specified in your static-web-listener
|
||||
dokku proxy:ports-set local-app http:80:8080
|
||||
|
||||
# set the domains desired
|
||||
dokku domains:set local-app local-app.dokku.me
|
||||
|
||||
dokku proxy:build-config local-app
|
||||
```
|
||||
|
||||
Only a single `$IP:$PORT` combination can be routed to for a given app, and that `$IP:$PORT` combination _must_ be accessible to the proxy, or requests to the app may not resolve.
|
||||
|
||||
### Attaching an app to a network
|
||||
|
||||
> New as of 0.20.0, Requires Docker 1.21+
|
||||
|
||||
16
plugins/builder-null/commands
Executable file
16
plugins/builder-null/commands
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
[[ " help builder-null:help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT"
|
||||
source "$PLUGIN_AVAILABLE_PATH/builder-null/help-functions"
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
case "$1" in
|
||||
help | builder-null:help)
|
||||
cmd-builder-null-help "$@"
|
||||
;;
|
||||
|
||||
*)
|
||||
exit "$DOKKU_NOT_IMPLEMENTED_EXIT"
|
||||
;;
|
||||
|
||||
esac
|
||||
23
plugins/builder-null/help-functions
Executable file
23
plugins/builder-null/help-functions
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
cmd-builder-null-help() {
|
||||
declare desc="help command"
|
||||
declare CMD="$1"
|
||||
local plugin_name="builder-null"
|
||||
local plugin_description="No-op builder plugin"
|
||||
|
||||
if [[ "$CMD" == "${plugin_name}:help" ]]; then
|
||||
echo -e "Usage: dokku ${plugin_name}[:COMMAND]"
|
||||
echo ''
|
||||
echo "$plugin_description"
|
||||
echo ''
|
||||
elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then
|
||||
true
|
||||
else
|
||||
cat <<help_desc
|
||||
$plugin_name, $plugin_description
|
||||
help_desc
|
||||
fi
|
||||
}
|
||||
4
plugins/builder-null/plugin.toml
Normal file
4
plugins/builder-null/plugin.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[plugin]
|
||||
description = "dokku core builder-null plugin"
|
||||
version = "0.24.10"
|
||||
[plugin.config]
|
||||
@@ -1,5 +1,5 @@
|
||||
SUBCOMMANDS = subcommands/create subcommands/destroy subcommands/exists subcommands/info subcommands/list subcommands/rebuild subcommands/rebuildall subcommands/report subcommands/set
|
||||
TRIGGERS = triggers/core-post-deploy triggers/docker-args-process-build triggers/docker-args-process-deploy triggers/docker-args-process-run triggers/install triggers/network-build-config triggers/network-compute-ports triggers/network-config-exists triggers/network-get-ipaddr triggers/network-get-listeners triggers/network-get-port triggers/network-get-property triggers/network-write-ipaddr triggers/network-write-port triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-container-create triggers/post-create triggers/post-delete triggers/report
|
||||
TRIGGERS = triggers/core-post-deploy triggers/docker-args-process-build triggers/docker-args-process-deploy triggers/docker-args-process-run triggers/install triggers/network-build-config triggers/network-compute-ports triggers/network-config-exists triggers/network-get-ipaddr triggers/network-get-listeners triggers/network-get-port triggers/network-get-property triggers/network-get-static-listeners triggers/network-write-ipaddr triggers/network-write-port triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-container-create triggers/post-create triggers/post-delete triggers/report
|
||||
BUILD = commands subcommands triggers
|
||||
PLUGIN_NAME = network
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package network
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
@@ -17,18 +18,19 @@ import (
|
||||
var (
|
||||
// DefaultProperties is a map of all valid network properties with corresponding default property values
|
||||
DefaultProperties = map[string]string{
|
||||
"bind-all-interfaces": "",
|
||||
"attach-post-create": "",
|
||||
"attach-post-deploy": "",
|
||||
"bind-all-interfaces": "",
|
||||
"initial-network": "",
|
||||
"static-web-listener": "",
|
||||
"tld": "",
|
||||
}
|
||||
|
||||
// GlobalProperties is a map of all valid global network properties
|
||||
GlobalProperties = map[string]bool{
|
||||
"bind-all-interfaces": true,
|
||||
"attach-post-create": true,
|
||||
"attach-post-deploy": true,
|
||||
"bind-all-interfaces": true,
|
||||
"initial-network": true,
|
||||
"tld": true,
|
||||
}
|
||||
@@ -40,6 +42,10 @@ func BuildConfig(appName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if staticWebListener := reportStaticWebListener(appName); staticWebListener != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
appRoot := common.AppRoot(appName)
|
||||
s, err := common.PlugnTriggerOutput("ps-current-scale", []string{appName}...)
|
||||
if err != nil {
|
||||
@@ -97,6 +103,22 @@ func BuildConfig(appName string) error {
|
||||
|
||||
// GetContainerIpaddress returns the ipaddr for a given app container
|
||||
func GetContainerIpaddress(appName, processType, containerID string) (ipAddr string) {
|
||||
if processType == "web" {
|
||||
if staticWebListener := reportStaticWebListener(appName); staticWebListener != "" {
|
||||
ip, _, err := net.SplitHostPort(staticWebListener)
|
||||
if err == nil {
|
||||
return ip
|
||||
}
|
||||
|
||||
ip2 := net.ParseIP(staticWebListener)
|
||||
if ip2 != nil {
|
||||
return ip2.String()
|
||||
}
|
||||
|
||||
return "127.0.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
if b, err := common.DockerInspect(containerID, "{{ .HostConfig.NetworkMode }}"); err == nil {
|
||||
if string(b[:]) == "host" {
|
||||
return "127.0.0.1"
|
||||
@@ -123,6 +145,17 @@ func GetContainerIpaddress(appName, processType, containerID string) (ipAddr str
|
||||
|
||||
// GetContainerPort returns the port for a given app container
|
||||
func GetContainerPort(appName, processType string, containerID string, isHerokuishContainer bool) (port string) {
|
||||
if processType == "web" {
|
||||
if staticWebListener := reportStaticWebListener(appName); staticWebListener != "" {
|
||||
_, port, err := net.SplitHostPort(staticWebListener)
|
||||
if err == nil {
|
||||
return port
|
||||
}
|
||||
|
||||
return "80"
|
||||
}
|
||||
}
|
||||
|
||||
dockerfilePorts := make([]string, 0)
|
||||
if !isHerokuishContainer {
|
||||
configValue := config.GetWithDefault(appName, "DOKKU_DOCKERFILE_PORTS", "")
|
||||
@@ -156,6 +189,12 @@ func GetContainerPort(appName, processType string, containerID string, isHerokui
|
||||
|
||||
// GetListeners returns a string array of app listeners
|
||||
func GetListeners(appName string, processType string) []string {
|
||||
if processType == "web" {
|
||||
if staticWebListener := reportStaticWebListener(appName); staticWebListener != "" {
|
||||
return []string{staticWebListener}
|
||||
}
|
||||
}
|
||||
|
||||
appRoot := common.AppRoot(appName)
|
||||
|
||||
ipPrefix := fmt.Sprintf("/IP.%s.", processType)
|
||||
|
||||
@@ -27,6 +27,7 @@ func ReportSingleApp(appName string, format string, infoFlag string) error {
|
||||
"--network-global-initial-network": reportGlobalInitialNetwork,
|
||||
"--network-global-tld": reportGlobalTld,
|
||||
"--network-initial-network": reportInitialNetwork,
|
||||
"--network-static-web-listener": reportStaticWebListener,
|
||||
"--network-tld": reportTld,
|
||||
"--network-web-listeners": reportWebListeners,
|
||||
}
|
||||
@@ -123,6 +124,10 @@ func reportInitialNetwork(appName string) string {
|
||||
return common.PropertyGet("network", appName, "initial-network")
|
||||
}
|
||||
|
||||
func reportStaticWebListener(appName string) string {
|
||||
return common.PropertyGet("network", appName, "static-web-listener")
|
||||
}
|
||||
|
||||
func reportTld(appName string) string {
|
||||
return common.PropertyGet("network", appName, "tld")
|
||||
}
|
||||
|
||||
@@ -59,6 +59,10 @@ func main() {
|
||||
appName := flag.Arg(0)
|
||||
property := flag.Arg(1)
|
||||
err = network.TriggerNetworkGetProperty(appName, property)
|
||||
case "network-get-static-listeners":
|
||||
appName := flag.Arg(0)
|
||||
processType := flag.Arg(1)
|
||||
err = network.TriggerNetworkGetStaticListeners(appName, processType)
|
||||
case "network-write-ipaddr":
|
||||
appName := flag.Arg(0)
|
||||
processType := flag.Arg(1)
|
||||
|
||||
@@ -140,6 +140,13 @@ func TriggerNetworkGetProperty(appName string, property string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TriggerNetworkGetStaticListeners fetches the static listener for the specified app/processType combination
|
||||
func TriggerNetworkGetStaticListeners(appName string, processType string) error {
|
||||
staticWebListener := reportStaticWebListener(appName)
|
||||
fmt.Println(staticWebListener)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TriggerNetworkWriteIpaddr writes the ip to disk
|
||||
func TriggerNetworkWriteIpaddr(appName string, processType string, containerIndex string, ip string) error {
|
||||
appRoot := common.AppRoot(appName)
|
||||
|
||||
@@ -447,10 +447,12 @@ nginx_build_config() {
|
||||
if [[ -z "$DOKKU_APP_LISTENERS" ]]; then
|
||||
dokku_log_warn_quiet "No web listeners specified for $APP"
|
||||
elif (is_deployed "$APP"); then
|
||||
local IMAGE_TAG=$(get_running_image_tag "$APP")
|
||||
local IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG" 2>/dev/null)
|
||||
if ! verify_image "$IMAGE" 2>/dev/null; then
|
||||
dokku_log_fail "Missing image for app"
|
||||
if [[ "$(plugn trigger network-get-static-listeners "$APP" "web")" == "" ]]; then
|
||||
local IMAGE_TAG=$(get_running_image_tag "$APP")
|
||||
local IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG" 2>/dev/null)
|
||||
if ! verify_image "$IMAGE" 2>/dev/null; then
|
||||
dokku_log_fail "Missing image for app"
|
||||
fi
|
||||
fi
|
||||
|
||||
local NGINX_BUILD_CONFIG_TMP_WORK_DIR=$(mktemp -d "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX")
|
||||
|
||||
16
plugins/scheduler-null/commands
Executable file
16
plugins/scheduler-null/commands
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
[[ " help scheduler-null:help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT"
|
||||
source "$PLUGIN_AVAILABLE_PATH/scheduler-null/help-functions"
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
case "$1" in
|
||||
help | scheduler-null:help)
|
||||
cmd-scheduler-null-help "$@"
|
||||
;;
|
||||
|
||||
*)
|
||||
exit "$DOKKU_NOT_IMPLEMENTED_EXIT"
|
||||
;;
|
||||
|
||||
esac
|
||||
23
plugins/scheduler-null/help-functions
Executable file
23
plugins/scheduler-null/help-functions
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
cmd-scheduler-null-help() {
|
||||
declare desc="help command"
|
||||
declare CMD="$1"
|
||||
local plugin_name="scheduler-null"
|
||||
local plugin_description="No-op scheduler plugin"
|
||||
|
||||
if [[ "$CMD" == "${plugin_name}:help" ]]; then
|
||||
echo -e "Usage: dokku ${plugin_name}[:COMMAND]"
|
||||
echo ''
|
||||
echo "$plugin_description"
|
||||
echo ''
|
||||
elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then
|
||||
true
|
||||
else
|
||||
cat <<help_desc
|
||||
$plugin_name, $plugin_description
|
||||
help_desc
|
||||
fi
|
||||
}
|
||||
4
plugins/scheduler-null/plugin.toml
Normal file
4
plugins/scheduler-null/plugin.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[plugin]
|
||||
description = "dokku core scheduler-null plugin"
|
||||
version = "0.24.10"
|
||||
[plugin.config]
|
||||
17
plugins/scheduler-null/scheduler-is-deployed
Executable file
17
plugins/scheduler-null/scheduler-is-deployed
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
[[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
trigger-scheduler-null-scheduler-is-deployed() {
|
||||
declare desc="checks if an app is deployed"
|
||||
declare trigger="scheduler-is-deployed"
|
||||
declare DOKKU_SCHEDULER="$1" APP="$2"
|
||||
|
||||
if [[ "$DOKKU_SCHEDULER" != "null" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
trigger-scheduler-null-scheduler-is-deployed "$@"
|
||||
52
tests/unit/proxied-app.bats
Normal file
52
tests/unit/proxied-app.bats
Normal file
@@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
setup() {
|
||||
global_setup
|
||||
create_app
|
||||
}
|
||||
|
||||
teardown() {
|
||||
destroy_app
|
||||
global_teardown
|
||||
}
|
||||
|
||||
# covers https://github.com/dokku/dokku/issues/4665
|
||||
@test "(proxied-app) default" {
|
||||
run /bin/bash -c "dokku builder:set $TEST_APP selected null"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku config:set $TEST_APP DOKKU_SCHEDULER=null"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku network:set $TEST_APP static-web-listener 127.0.0.1:8080"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku proxy:ports-set $TEST_APP http:80:8080"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku domains:set $TEST_APP $TEST_APP.dokku.me"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku proxy:build-config $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku nginx:show-config $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "server 127.0.0.1:8080;"
|
||||
}
|
||||
Reference in New Issue
Block a user