diff --git a/docs/development/plugin-triggers.md b/docs/development/plugin-triggers.md index a3ddf285f..4afe73354 100644 --- a/docs/development/plugin-triggers.md +++ b/docs/development/plugin-triggers.md @@ -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 diff --git a/docs/networking/port-management.md b/docs/networking/port-management.md index 66b8e6dd9..f7a770de8 100644 --- a/docs/networking/port-management.md +++ b/docs/networking/port-management.md @@ -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: diff --git a/plugins/20_events/ports-set-detected b/plugins/20_events/ports-set-detected new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/ports-set-detected @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/builder-dockerfile/builder-build b/plugins/builder-dockerfile/builder-build index c733c4480..0ff7d1f6b 100755 --- a/plugins/builder-dockerfile/builder-build +++ b/plugins/builder-dockerfile/builder-build @@ -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" } diff --git a/plugins/builder-dockerfile/internal-functions b/plugins/builder-dockerfile/internal-functions index a230f67b3..02fe4dee1 100755 --- a/plugins/builder-dockerfile/internal-functions +++ b/plugins/builder-dockerfile/internal-functions @@ -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 +} diff --git a/plugins/builder-herokuish/builder-build b/plugins/builder-herokuish/builder-build index 8ebc672bf..d09f6df28 100755 --- a/plugins/builder-herokuish/builder-build +++ b/plugins/builder-herokuish/builder-build @@ -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" } diff --git a/plugins/builder-lambda/builder-build b/plugins/builder-lambda/builder-build index 2c1b7a107..64beb30a0 100755 --- a/plugins/builder-lambda/builder-build +++ b/plugins/builder-lambda/builder-build @@ -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" } diff --git a/plugins/builder-pack/builder-build b/plugins/builder-pack/builder-build index 773ebfd80..8a2f865b1 100755 --- a/plugins/builder-pack/builder-build +++ b/plugins/builder-pack/builder-build @@ -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" } diff --git a/plugins/ports/Makefile b/plugins/ports/Makefile index ac8759b6e..f1d13cfbd 100644 --- a/plugins/ports/Makefile +++ b/plugins/ports/Makefile @@ -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 diff --git a/plugins/ports/functions.go b/plugins/ports/functions.go index f13c049c4..ba9358b20 100644 --- a/plugins/ports/functions.go +++ b/plugins/ports/functions.go @@ -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[:])) diff --git a/plugins/ports/report.go b/plugins/ports/report.go index bd820554d..8031478b7 100644 --- a/plugins/ports/report.go +++ b/plugins/ports/report.go @@ -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, " ") +} diff --git a/plugins/ports/src/triggers/triggers.go b/plugins/ports/src/triggers/triggers.go index f97571120..3adc5c27a 100644 --- a/plugins/ports/src/triggers/triggers.go +++ b/plugins/ports/src/triggers/triggers.go @@ -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) diff --git a/plugins/ports/triggers.go b/plugins/ports/triggers.go index 53f01fb2b..0bb59ddf6 100644 --- a/plugins/ports/triggers.go +++ b/plugins/ports/triggers.go @@ -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) diff --git a/tests/apps/python/expose.Dockerfile b/tests/apps/python/expose.Dockerfile new file mode 100644 index 000000000..130cbb2fc --- /dev/null +++ b/tests/apps/python/expose.Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.11.0-buster + +EXPOSE 3001/udp +EXPOSE 3000/tcp +EXPOSE 3003 + +COPY . /app + +WORKDIR /app diff --git a/tests/unit/ports.bats b/tests/unit/ports.bats index 2bef9aa78..772deca21 100644 --- a/tests/unit/ports.bats +++ b/tests/unit/ports.bats @@ -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" +} diff --git a/tests/unit/test_helper.bash b/tests/unit/test_helper.bash index fdcd3cb3a..76b261000 100644 --- a/tests/unit/test_helper.bash +++ b/tests/unit/test_helper.bash @@ -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"