mirror of
https://github.com/dokku/dokku.git
synced 2025-12-16 03:57:43 +01:00
feat: write auto-detected port mappings during a deploy
During an app build, we now auto-detect ports based on the source code. This is usually http:80:5000, with Dockerfile-based deploys having their ports extracted from the docker image or Dockerfile. Additionally, we add an https:443 mapping for any detected http:80 mapping when there is an ssl certificate, and all http port mappings are transformed to https mappings for Dockerfile-based deploys. While the ports aren't currently consumed, a future refactor will provide the ability to fallback to the new detected ports when there is no user-specified port mapping.
This commit is contained in:
@@ -1292,7 +1292,7 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
- Description: Returns a list of port mappings, newline delimited
|
||||
- Invoked by: Various networking plugins
|
||||
- Arguments `$APP`
|
||||
- Arguments: `$APP`
|
||||
- Example:
|
||||
|
||||
```shell
|
||||
@@ -1307,7 +1307,21 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
- Description: Prints out an available port greater than 1024
|
||||
- Invoked by: Various networking plugins
|
||||
- Arguments `$APP`
|
||||
- Arguments: `$APP`
|
||||
- Example:
|
||||
|
||||
```shell
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
# TODO
|
||||
```
|
||||
|
||||
### `ports-set-detected`
|
||||
- Description: Allows builders to specify detected port mappings for a given app
|
||||
- Invoked by: Builder plugins
|
||||
- Arguments: `$APP [$PORT_MAPPING...]`
|
||||
- Example:
|
||||
|
||||
```shell
|
||||
@@ -1480,7 +1494,7 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
|
||||
|
||||
- Description: This trigger should be used to do stuff to containers after they are created but before they are started. They are explicitely for commands that may involve network traffic, and _not_ for commands that are self-contained, such as chown or tar.
|
||||
- Invoked by: `dokku run`, `dokku ps:rebuild`, `dokku deploy`
|
||||
- Arguments "app|service" "$CONTAINER_ID" "$APP|$SERVICE" "$PHASE"
|
||||
- Arguments: "app|service" "$CONTAINER_ID" "$APP|$SERVICE" "$PHASE"
|
||||
|
||||
```shell
|
||||
#!/usr/bin/env bash
|
||||
|
||||
@@ -212,11 +212,14 @@ dokku ports:report
|
||||
|
||||
```
|
||||
=====> node-js-app ports information
|
||||
Port map: http:80:5000 https:443:5000
|
||||
Port map detected: http:80:5000
|
||||
Port map: http:80:5000 https:443:5000
|
||||
=====> python-sample ports information
|
||||
Port map: http:80:5000
|
||||
Port map detected: http:80:5000
|
||||
Port map: http:80:5000
|
||||
=====> ruby-sample ports information
|
||||
Port map: http:80:5000
|
||||
Port map detected: http:80:5000
|
||||
Port map: http:80:5000
|
||||
```
|
||||
|
||||
You can run the command for a specific app also.
|
||||
@@ -227,7 +230,8 @@ dokku ports:report node-js-app
|
||||
|
||||
```
|
||||
=====> node-js-app ports information
|
||||
Port map: http:80:5000 https:443:5000
|
||||
Port map detected: http:80:5000
|
||||
Port map: http:80:5000 https:443:5000
|
||||
```
|
||||
|
||||
You can pass flags which will output only the value of the specific information you want. For example:
|
||||
|
||||
1
plugins/20_events/ports-set-detected
Symbolic link
1
plugins/20_events/ports-set-detected
Symbolic link
@@ -0,0 +1 @@
|
||||
hook
|
||||
@@ -41,6 +41,10 @@ trigger-builder-dockerfile-builder-build() {
|
||||
eval "$(config_export app "$APP")"
|
||||
"$DOCKER_BIN" image build "${DOCKER_BUILD_LABEL_ARGS[@]}" $DOKKU_GLOBAL_BUILD_ARGS "${ARG_ARRAY[@]}" ${DOKKU_DOCKER_BUILD_OPTS} -t $IMAGE .
|
||||
|
||||
set -x
|
||||
fn-builder-dockerfile-get-detect-port-map "$APP" "$IMAGE" "$SOURCECODE_WORK_DIR/Dockerfile"
|
||||
plugn trigger ports-set-detected "$APP" "$(fn-builder-dockerfile-get-detect-port-map "$APP" "$IMAGE" "$SOURCECODE_WORK_DIR/Dockerfile")"
|
||||
set +x
|
||||
plugn trigger post-build-dockerfile "$APP"
|
||||
}
|
||||
|
||||
|
||||
@@ -88,3 +88,47 @@ fn-builder-dockerfile-dockerfile-path() {
|
||||
|
||||
fn-plugin-property-get-default "builder-dockerfile" "$APP" "dockerfile-path" ""
|
||||
}
|
||||
|
||||
fn-builder-dockerfile-get-ports-from-dockerfile() {
|
||||
declare desc="return all exposed ports from passed file path"
|
||||
declare DOCKERFILE_PATH="$1"
|
||||
|
||||
suppress_output dos2unix "$DOCKERFILE_PATH"
|
||||
local ports="$(grep -E "^EXPOSE " "$DOCKERFILE_PATH" | awk '{ print $2 }' | xargs)" || true
|
||||
echo "$ports"
|
||||
}
|
||||
|
||||
fn-builder-dockerfile-get-ports-from-image() {
|
||||
declare desc="return all exposed ports from passed image name"
|
||||
declare IMAGE="$1"
|
||||
|
||||
local ports="$("$DOCKER_BIN" image inspect --format "{{range $key, $value := .Config.ExposedPorts}}{{$key}} {{end}}" "$IMAGE" | xargs)" || true
|
||||
echo "$ports"
|
||||
}
|
||||
|
||||
fn-builder-dockerfile-get-detect-port-map() {
|
||||
declare desc="extracts and echos a port mapping from the app"
|
||||
declare APP="$1" IMAGE="$2" DOCKERFILE_PATH="$3"
|
||||
|
||||
local detected_ports=$(fn-builder-dockerfile-get-ports-from-dockerfile "$DOCKERFILE_PATH")
|
||||
|
||||
if [[ -z "$detected_ports" ]]; then
|
||||
local detected_ports=$(fn-builder-dockerfile-get-ports-from-image "$IMAGE")
|
||||
fi
|
||||
|
||||
if [[ -n "$detected_ports" ]]; then
|
||||
local port_map=""
|
||||
for p in $detected_ports; do
|
||||
if [[ "$p" =~ .*udp.* ]]; then
|
||||
p=${p//\/udp/}
|
||||
port_map+="udp:$p:$p "
|
||||
else
|
||||
p=${p//\/tcp/}
|
||||
port_map+="http:$p:$p "
|
||||
fi
|
||||
done
|
||||
echo "$port_map" | xargs
|
||||
else
|
||||
echo "http:80:5000"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -91,6 +91,7 @@ trigger-builder-herokuish-builder-build() {
|
||||
|
||||
"$DOCKER_BIN" container commit "${DOCKER_COMMIT_LABEL_ARGS[@]}" "$CID" "$IMAGE" >/dev/null
|
||||
plugn trigger scheduler-register-retired "$APP" "$CID"
|
||||
plugn trigger ports-set-detected "$APP" "http:80:5000"
|
||||
plugn trigger post-build-buildpack "$APP" "$SOURCECODE_WORK_DIR"
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ trigger-builder-lambda-builder-build() {
|
||||
cp "$SOURCECODE_WORK_DIR/Procfile" "${DOKKU_LIB_ROOT}/data/builder-lambda/$APP/$GIT_REV.Procfile"
|
||||
fi
|
||||
|
||||
plugn trigger ports-set-detected "$APP" "http:80:5000"
|
||||
plugn trigger post-build-lambda "$APP"
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ trigger-builder-pack-builder-build() {
|
||||
pack build "$IMAGE" --builder "$DOKKU_CNB_BUILDER" --path "$SOURCECODE_WORK_DIR" --default-process web "${ENV_ARGS[@]}"
|
||||
docker-image-labeler --label=dokku --label=org.label-schema.schema-version=1.0 --label=org.label-schema.vendor=dokku --label=com.dokku.image-stage=build --label=com.dokku.builder-type=pack --label=com.dokku.app-name=$APP "$IMAGE"
|
||||
|
||||
plugn trigger ports-set-detected "$APP" "http:80:5000"
|
||||
plugn trigger post-build-pack "$APP" "$SOURCECODE_WORK_DIR"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
SUBCOMMANDS = subcommands/list subcommands/add subcommands/clear subcommands/remove subcommands/set subcommands/report
|
||||
TRIGGERS = triggers/install triggers/ports-clear triggers/ports-configure triggers/ports-dockerfile-raw-tcp-ports triggers/ports-get triggers/ports-get-available triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-certs-remove triggers/post-certs-update triggers/post-delete triggers/report
|
||||
TRIGGERS = triggers/install triggers/ports-clear triggers/ports-configure triggers/ports-dockerfile-raw-tcp-ports triggers/ports-get triggers/ports-get-available triggers/ports-set-detected triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-certs-remove triggers/post-certs-update triggers/post-delete triggers/report
|
||||
BUILD = commands subcommands triggers
|
||||
PLUGIN_NAME = ports
|
||||
|
||||
|
||||
@@ -63,6 +63,54 @@ func getAvailablePort() int {
|
||||
}
|
||||
}
|
||||
|
||||
func getDetectedPortMaps(appName string) []PortMap {
|
||||
defaultMapping := []PortMap{
|
||||
{
|
||||
ContainerPort: 5000,
|
||||
HostPort: 80,
|
||||
Scheme: "http",
|
||||
},
|
||||
}
|
||||
|
||||
portMaps := []PortMap{}
|
||||
value, err := common.PropertyListGet("ports", appName, "map-detected")
|
||||
if err == nil {
|
||||
portMaps, _ = parsePortMapString(strings.Join(value, " "))
|
||||
}
|
||||
|
||||
if len(portMaps) == 0 {
|
||||
portMaps = defaultMapping
|
||||
}
|
||||
|
||||
if doesCertExist(appName) {
|
||||
setSSLPort := false
|
||||
for _, portMap := range portMaps {
|
||||
if portMap.Scheme != "http" || portMap.HostPort != 80 {
|
||||
continue
|
||||
}
|
||||
|
||||
setSSLPort = true
|
||||
portMaps = append(portMaps, PortMap{
|
||||
ContainerPort: portMap.ContainerPort,
|
||||
HostPort: 443,
|
||||
Scheme: "https",
|
||||
})
|
||||
}
|
||||
|
||||
if !setSSLPort {
|
||||
for i, portMap := range portMaps {
|
||||
if portMap.Scheme != "http" {
|
||||
continue
|
||||
}
|
||||
|
||||
portMaps[i].Scheme = "https"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return portMaps
|
||||
}
|
||||
|
||||
func getDockerfileRawTCPPorts(appName string) []int {
|
||||
b, _ := common.PlugnTriggerOutput("config-get", []string{appName, "DOKKU_DOCKERFILE_PORTS"}...)
|
||||
dockerfilePorts := strings.TrimSpace(string(b[:]))
|
||||
|
||||
@@ -13,7 +13,8 @@ func ReportSingleApp(appName string, format string, infoFlag string) error {
|
||||
}
|
||||
|
||||
flags := map[string]common.ReportFunc{
|
||||
"--ports-map": reportPortMap,
|
||||
"--ports-map": reportPortMap,
|
||||
"--ports-map-detected": reportPortMapDetected,
|
||||
}
|
||||
|
||||
flagKeys := []string{}
|
||||
@@ -35,3 +36,12 @@ func reportPortMap(appName string) string {
|
||||
|
||||
return strings.Join(portMaps, " ")
|
||||
}
|
||||
|
||||
func reportPortMapDetected(appName string) string {
|
||||
var portMaps []string
|
||||
for _, portMap := range getDetectedPortMaps(appName) {
|
||||
portMaps = append(portMaps, portMap.String())
|
||||
}
|
||||
|
||||
return strings.Join(portMaps, " ")
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ func main() {
|
||||
err = ports.TriggerPortsGet(appName)
|
||||
case "ports-get-available":
|
||||
err = ports.TriggerPortsGetAvailable()
|
||||
case "ports-set-detected":
|
||||
appName := flag.Arg(0)
|
||||
appName, portMapString := common.ShiftString(flag.Args())
|
||||
err = ports.TriggerPortsSetDetected(appName, strings.Join(portMapString, " "))
|
||||
case "post-app-clone-setup":
|
||||
oldAppName := flag.Arg(0)
|
||||
newAppName := flag.Arg(1)
|
||||
|
||||
@@ -2,6 +2,7 @@ package ports
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/dokku/dokku/plugins/common"
|
||||
"github.com/dokku/dokku/plugins/config"
|
||||
@@ -101,6 +102,23 @@ func TriggerPortsGetAvailable() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TriggerPortsGetAvailable prints out an available port greater than 1024
|
||||
func TriggerPortsSetDetected(appName string, portMapString string) error {
|
||||
portMaps, _ := parsePortMapString(portMapString)
|
||||
|
||||
var value []string
|
||||
for _, portMap := range uniquePortMaps(portMaps) {
|
||||
if portMap.AllowsPersistence() {
|
||||
continue
|
||||
}
|
||||
|
||||
value = append(value, portMap.String())
|
||||
}
|
||||
|
||||
sort.Strings(value)
|
||||
return common.PropertyListWrite("ports", appName, "map-detected", value)
|
||||
}
|
||||
|
||||
// TriggerPostAppCloneSetup creates new ports files
|
||||
func TriggerPostAppCloneSetup(oldAppName string, newAppName string) error {
|
||||
err := common.PropertyClone("ports", oldAppName, newAppName)
|
||||
|
||||
9
tests/apps/python/expose.Dockerfile
Normal file
9
tests/apps/python/expose.Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
||||
FROM python:3.11.0-buster
|
||||
|
||||
EXPOSE 3001/udp
|
||||
EXPOSE 3000/tcp
|
||||
EXPOSE 3003
|
||||
|
||||
COPY . /app
|
||||
|
||||
WORKDIR /app
|
||||
@@ -90,7 +90,7 @@ teardown() {
|
||||
assert_output "http 80 5000"
|
||||
}
|
||||
|
||||
@test "(ports) ports:add (post-deploy add)" {
|
||||
@test "(ports:add) post-deploy add" {
|
||||
deploy_app
|
||||
run /bin/bash -c "dokku ports:add $TEST_APP http:8080:5000 http:8081:5000"
|
||||
echo "output: $output"
|
||||
@@ -104,3 +104,72 @@ teardown() {
|
||||
assert_http_success "http://$TEST_APP.dokku.me:8080"
|
||||
assert_http_success "http://$TEST_APP.dokku.me:8081"
|
||||
}
|
||||
|
||||
@test "(ports:report) herokuish tls" {
|
||||
run /bin/bash -c "dokku builder-herokuish:set $TEST_APP allowed true"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ports:report $TEST_APP --ports-map"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output ""
|
||||
|
||||
run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output "http:80:5000"
|
||||
|
||||
run setup_test_tls
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run deploy_app
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ports:report $TEST_APP --ports-map"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output "http:80:5000 https:443:5000"
|
||||
|
||||
run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output "http:80:5000 https:443:5000"
|
||||
}
|
||||
|
||||
@test "(ports:report) dockerfile tls" {
|
||||
run deploy_app python dokku@dokku.me:$TEST_APP move_expose_dockerfile_into_place
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ports:report $TEST_APP --ports-map"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output "http:3000:3000 http:3003:3003"
|
||||
|
||||
run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output "http:3000:3000 http:3003:3003 udp:3001:3001"
|
||||
|
||||
run setup_test_tls
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku ports:report $TEST_APP --ports-map"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output "http:3000:3000 http:3003:3003"
|
||||
|
||||
run /bin/bash -c "dokku ports:report $TEST_APP --ports-map-detected"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output "https:3000:3000 https:3003:3003 udp:3001:3001"
|
||||
}
|
||||
|
||||
@@ -529,6 +529,14 @@ move_dockerfile_into_place() {
|
||||
mv "$APP_REPO_DIR/alt.Dockerfile" "$APP_REPO_DIR/Dockerfile"
|
||||
}
|
||||
|
||||
move_expose_dockerfile_into_place() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
[[ -z "$APP" ]] && local APP="$TEST_APP"
|
||||
cat "$APP_REPO_DIR/expose.Dockerfile"
|
||||
mv "$APP_REPO_DIR/expose.Dockerfile" "$APP_REPO_DIR/Dockerfile"
|
||||
}
|
||||
|
||||
add_requirements_txt() {
|
||||
local APP="$1"
|
||||
local APP_REPO_DIR="$2"
|
||||
|
||||
Reference in New Issue
Block a user