refactor: move all port management code to standalone ports plugin

This change makes interacting with port mappings more clear - folks might previously set the port mapping to the proxy type or vice-versa.

The existing proxy:ports* commands still exist but will show a deprecation warning for a single minor release.
This commit is contained in:
Jose Diaz-Gonzalez
2023-07-02 01:29:01 -04:00
parent a0b84d52ef
commit 64f0f2674d
43 changed files with 894 additions and 553 deletions

7
plugins/ports/.gitignore vendored Normal file
View File

@@ -0,0 +1,7 @@
/commands
/subcommands/*
/triggers/*
/triggers
/install
/post-*
/report

6
plugins/ports/Makefile Normal file
View File

@@ -0,0 +1,6 @@
SUBCOMMANDS = subcommands/list subcommands/add subcommands/clear subcommands/remove subcommands/set subcommands/report
TRIGGERS = triggers/post-certs-remove triggers/post-certs-update triggers/report
BUILD = commands subcommands triggers
PLUGIN_NAME = ports
include ../../common.mk

188
plugins/ports/functions.go Normal file
View File

@@ -0,0 +1,188 @@
package ports
import (
"errors"
"fmt"
"os"
"sort"
"strconv"
"strings"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/config"
"github.com/ryanuber/columnize"
)
func addPortMaps(appName string, portMaps []PortMap) error {
allPortMaps := getPortMaps(appName)
allPortMaps = append(allPortMaps, portMaps...)
return setPortMaps(appName, allPortMaps)
}
func filterAppPortMaps(appName string, scheme string, hostPort int) []PortMap {
var filteredPortMaps []PortMap
for _, portMap := range getPortMaps(appName) {
if portMap.Scheme == scheme && portMap.HostPort == hostPort {
filteredPortMaps = append(filteredPortMaps, portMap)
}
}
return filteredPortMaps
}
func getPortMaps(appName string) []PortMap {
value := config.GetWithDefault(appName, "DOKKU_PROXY_PORT_MAP", "")
portMaps, _ := parsePortMapString(value)
return portMaps
}
func inRange(value int, min int, max int) bool {
return min < value && value < max
}
func listAppPortMaps(appName string) error {
portMaps := getPortMaps(appName)
if len(portMaps) == 0 {
return errors.New("No port mappings configured for app")
}
var lines []string
if os.Getenv("DOKKU_QUIET_OUTPUT") == "" {
lines = append(lines, "-----> scheme:host port:container port")
}
for _, portMap := range portMaps {
lines = append(lines, portMap.String())
}
sort.Strings(lines)
common.LogInfo1Quiet(fmt.Sprintf("Port mappings for %s", appName))
config := columnize.DefaultConfig()
config.Delim = ":"
config.Prefix = " "
config.Empty = ""
fmt.Println(columnize.Format(lines, config))
return nil
}
func parsePortMapString(stringPortMap string) ([]PortMap, error) {
var portMaps []PortMap
for _, v := range strings.Split(strings.TrimSpace(stringPortMap), " ") {
parts := strings.SplitN(v, ":", 3)
if len(parts) == 1 {
hostPort, err := strconv.Atoi(v)
if err != nil {
return portMaps, fmt.Errorf("Invalid port map %s [err=%s]", v, err.Error())
}
if !inRange(hostPort, 0, 65536) {
return portMaps, fmt.Errorf("Invalid port map %s [hostPort=%d]", v, hostPort)
}
portMaps = append(portMaps, PortMap{
HostPort: hostPort,
Scheme: "__internal__",
})
continue
}
if len(parts) != 3 {
return portMaps, fmt.Errorf("Invalid port map %s [len=%d]", v, len(parts))
}
hostPort, err := strconv.Atoi(parts[1])
if err != nil {
return portMaps, fmt.Errorf("Invalid port map %s [err=%s]", v, err.Error())
}
containerPort, err := strconv.Atoi(parts[2])
if err != nil {
return portMaps, fmt.Errorf("Invalid port map %s [err=%s]", v, err.Error())
}
if !inRange(hostPort, 0, 65536) {
return portMaps, fmt.Errorf("Invalid port map %s [hostPort=%d]", v, hostPort)
}
if !inRange(containerPort, 0, 65536) {
return portMaps, fmt.Errorf("Invalid port map %s [containerPort=%d]", v, containerPort)
}
portMaps = append(portMaps, PortMap{
ContainerPort: containerPort,
HostPort: hostPort,
Scheme: parts[0],
})
}
return uniquePortMaps(portMaps), nil
}
func removePortMaps(appName string, portMaps []PortMap) error {
toRemove := map[string]bool{}
toRemoveByPort := map[int]bool{}
for _, portMap := range portMaps {
if portMap.AllowsPersistence() {
toRemoveByPort[portMap.HostPort] = true
continue
}
toRemove[portMap.String()] = true
}
var toSet []PortMap
for _, portMap := range getPortMaps(appName) {
if toRemove[portMap.String()] {
continue
}
if toRemoveByPort[portMap.HostPort] {
continue
}
toSet = append(toSet, portMap)
}
if len(toSet) == 0 {
keys := []string{"DOKKU_PROXY_PORT_MAP"}
return config.UnsetMany(appName, keys, false)
}
return setPortMaps(appName, toSet)
}
func setPortMaps(appName string, portMaps []PortMap) error {
var value []string
for _, portMap := range uniquePortMaps(portMaps) {
if portMap.AllowsPersistence() {
continue
}
value = append(value, portMap.String())
}
sort.Strings(value)
entries := map[string]string{
"DOKKU_PROXY_PORT_MAP": strings.Join(value, " "),
}
return config.SetMany(appName, entries, false)
}
func uniquePortMaps(portMaps []PortMap) []PortMap {
var unique []PortMap
existingPortMaps := map[string]bool{}
for _, portMap := range portMaps {
if existingPortMaps[portMap.String()] {
continue
}
existingPortMaps[portMap.String()] = true
unique = append(unique, portMap)
}
return unique
}

23
plugins/ports/go.mod Normal file
View File

@@ -0,0 +1,23 @@
module github.com/dokku/dokku/plugins/ports
go 1.19
require (
github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000
github.com/dokku/dokku/plugins/config v0.0.0-00010101000000-000000000000
github.com/ryanuber/columnize v2.1.2+incompatible
github.com/spf13/pflag v1.0.5
)
require (
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect
github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27 // indirect
github.com/joho/godotenv v1.2.0 // indirect
github.com/otiai10/copy v1.12.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.8.0 // indirect
)
replace github.com/dokku/dokku/plugins/common => ../common
replace github.com/dokku/dokku/plugins/config => ../config

22
plugins/ports/go.sum Normal file
View File

@@ -0,0 +1,22 @@
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q=
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27 h1:HHUr4P/aKh4quafGxDT9LDasjGdlGkzLbfmmrlng3kA=
github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27/go.mod h1:VQx0hjo2oUeQkQUET7wRwradO6f+fN5jzXgB/zROxxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/joho/godotenv v1.2.0 h1:vGTvz69FzUFp+X4/bAkb0j5BoLC+9bpqTWY8mjhA9pc=
github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
github.com/otiai10/copy v1.12.0 h1:cLMgSQnXBs1eehF0Wy/FAGsgDTDmAqFR7rQylBb1nDY=
github.com/otiai10/copy v1.12.0/go.mod h1:rSaLseMUsZFFbsFGc7wCJnnkTAvdc5L6VWxPE4308Ww=
github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks=
github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk=
github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -0,0 +1,4 @@
[plugin]
description = "dokku core ports plugin"
version = "0.30.8"
[plugin.config]

View File

@@ -4,8 +4,8 @@ source "$PLUGIN_AVAILABLE_PATH/config/functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
trigger-proxy-proxy-configure-ports() {
declare desc="proxy proxy-configure-ports plugin trigger"
trigger-ports-proxy-configure-ports() {
declare desc="ports proxy-configure-ports plugin trigger"
declare trigger="proxy-configure-ports"
declare APP="$1"
local RAW_TCP_PORTS="$(get_app_raw_tcp_ports "$APP")"
@@ -19,7 +19,7 @@ trigger-proxy-proxy-configure-ports() {
if [[ -z "$DOKKU_PROXY_PORT" ]] && [[ -z "$RAW_TCP_PORTS" ]]; then
if [[ "$IS_APP_VHOST_ENABLED" == "false" ]]; then
dokku_log_info1 "No proxy port set, setting to random open high port"
dokku_log_info1 "No port set, setting to random open high port"
local PROXY_PORT=$(get_available_port)
else
local PROXY_PORT=$(config_get --global DOKKU_PROXY_PORT)
@@ -32,7 +32,7 @@ trigger-proxy-proxy-configure-ports() {
local PROXY_SSL_PORT=$(config_get --global DOKKU_PROXY_SSL_PORT)
PROXY_SSL_PORT=${PROXY_SSL_PORT:=443}
if [[ -z "$RAW_TCP_PORTS" ]] && [[ "$IS_APP_VHOST_ENABLED" == "false" ]]; then
dokku_log_info1 "No proxy ssl port set, setting to random open high port"
dokku_log_info1 "No ssl port set, setting to random open high port"
PROXY_SSL_PORT=$(get_available_port)
fi
DOKKU_QUIET_OUTPUT=1 config_set --no-restart "$APP" DOKKU_PROXY_SSL_PORT="$PROXY_SSL_PORT"
@@ -61,4 +61,5 @@ trigger-proxy-proxy-configure-ports() {
fi
fi
}
trigger-proxy-proxy-configure-ports "$@"
trigger-ports-proxy-configure-ports "$@"

21
plugins/ports/proxy.go Normal file
View File

@@ -0,0 +1,21 @@
package ports
import (
"fmt"
)
// PortMap is a struct that contains a scheme:host-port:container-port mapping
type PortMap struct {
ContainerPort int
HostPort int
Scheme string
}
func (p PortMap) String() string {
return fmt.Sprintf("%s:%d:%d", p.Scheme, p.HostPort, p.ContainerPort)
}
// AllowsPersistence returns true if the port map is not to be persisted
func (p PortMap) AllowsPersistence() bool {
return p.Scheme == "__internal__"
}

37
plugins/ports/report.go Normal file
View File

@@ -0,0 +1,37 @@
package ports
import (
"strings"
"github.com/dokku/dokku/plugins/common"
)
// ReportSingleApp is an internal function that displays the ports report for one or more apps
func ReportSingleApp(appName string, format string, infoFlag string) error {
if err := common.VerifyAppName(appName); err != nil {
return err
}
flags := map[string]common.ReportFunc{
"--ports-map": reportPortMap,
}
flagKeys := []string{}
for flagKey := range flags {
flagKeys = append(flagKeys, flagKey)
}
trimPrefix := false
uppercaseFirstCharacter := true
infoFlags := common.CollectReport(appName, infoFlag, flags)
return common.ReportSingleApp("ports", appName, infoFlag, infoFlags, flagKeys, format, trimPrefix, uppercaseFirstCharacter)
}
func reportPortMap(appName string) string {
var portMaps []string
for _, portMap := range getPortMaps(appName) {
portMaps = append(portMaps, portMap.String())
}
return strings.Join(portMaps, " ")
}

View File

@@ -0,0 +1,60 @@
package main
import (
"flag"
"fmt"
"os"
"strconv"
"strings"
"github.com/dokku/dokku/plugins/common"
)
const (
helpHeader = `Usage: dokku ports[:COMMAND]
Manage ports for an app
Additional commands:`
helpContent = `
ports:list <app>, List port mappings for app
ports:add <app> [<scheme>:<host-port>:<container-port>...], Add port mappings to an app
ports:clear <app>, Clear all port mappings for an app
ports:remove <app> [<host-port>|<scheme>:<host-port>:<container-port>...], Remove specific port mappings from an app
ports:set <app> [<scheme>:<host-port>:<container-port>...], Set port mappings for an app
ports:report [<app>] [<flag>], Displays a ports report for one or more apps
`
)
func main() {
flag.Usage = usage
flag.Parse()
cmd := flag.Arg(0)
switch cmd {
case "ports", "ports:help":
usage()
case "help":
command := common.NewShellCmd(fmt.Sprintf("ps -o command= %d", os.Getppid()))
command.ShowOutput = false
output, err := command.Output()
if err == nil && strings.Contains(string(output), "--all") {
fmt.Println(helpContent)
} else {
fmt.Print("\n ports, Manage ports for an app\n")
}
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() {
common.CommandUsage(helpHeader, helpContent)
}

View File

@@ -0,0 +1,65 @@
package main
import (
"fmt"
"os"
"strings"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/ports"
flag "github.com/spf13/pflag"
)
// main entrypoint to all subcommands
func main() {
parts := strings.Split(os.Args[0], "/")
subcommand := parts[len(parts)-1]
var err error
switch subcommand {
case "list":
args := flag.NewFlagSet("ports:list", flag.ExitOnError)
args.Parse(os.Args[2:])
appName := args.Arg(0)
err = ports.CommandList(appName)
case "add":
args := flag.NewFlagSet("ports:add", flag.ExitOnError)
args.Parse(os.Args[2:])
appName := args.Arg(0)
_, portMaps := common.ShiftString(args.Args())
err = ports.CommandAdd(appName, portMaps)
case "clear":
args := flag.NewFlagSet("ports:clear", flag.ExitOnError)
args.Parse(os.Args[2:])
appName := args.Arg(0)
err = ports.CommandClear(appName)
case "remove":
args := flag.NewFlagSet("ports:remove", flag.ExitOnError)
args.Parse(os.Args[2:])
appName := args.Arg(0)
_, portMaps := common.ShiftString(args.Args())
err = ports.CommandRemove(appName, portMaps)
case "set":
args := flag.NewFlagSet("ports:set", flag.ExitOnError)
args.Parse(os.Args[2:])
appName := args.Arg(0)
_, portMaps := common.ShiftString(args.Args())
err = ports.CommandSet(appName, portMaps)
case "report":
args := flag.NewFlagSet("ports:report", flag.ExitOnError)
format := args.String("format", "stdout", "format: [ stdout | json ]")
osArgs, infoFlag, flagErr := common.ParseReportArgs("ports", os.Args[2:])
if flagErr == nil {
args.Parse(osArgs)
appName := args.Arg(0)
err = ports.CommandReport(appName, *format, infoFlag)
}
default:
err = fmt.Errorf("Invalid plugin subcommand call: %s", subcommand)
}
if err != nil {
common.LogFailWithError(err)
}
}

View File

@@ -0,0 +1,37 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/ports"
)
// main entrypoint to all triggers
func main() {
parts := strings.Split(os.Args[0], "/")
trigger := parts[len(parts)-1]
flag.Parse()
var err error
switch trigger {
case "post-certs-remove":
appName := flag.Arg(0)
err = ports.TriggerPostCertsRemove(appName)
case "post-certs-update":
appName := flag.Arg(0)
err = ports.TriggerPostCertsUpdate(appName)
case "report":
appName := flag.Arg(0)
err = ports.ReportSingleApp(appName, "", "")
default:
err = fmt.Errorf("Invalid plugin trigger call: %s", trigger)
}
if err != nil {
common.LogFailWithError(err)
}
}

View File

@@ -0,0 +1,116 @@
package ports
import (
"errors"
"strings"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/config"
)
// CommandList is a cmd wrapper to list port mappings for an app
func CommandList(appName string) error {
if err := common.VerifyAppName(appName); err != nil {
return err
}
return listAppPortMaps(appName)
}
// CommandAdd adds port mappings to an app
func CommandAdd(appName string, portMapStrings []string) error {
if err := common.VerifyAppName(appName); err != nil {
return err
}
if len(portMapStrings) == 0 {
return errors.New("No port mapping specified")
}
portMaps, err := parsePortMapString(strings.Join(portMapStrings, " "))
if err != nil {
return err
}
if err := addPortMaps(appName, portMaps); err != nil {
return err
}
return common.PlugnTrigger("post-proxy-ports-update", []string{appName, "add"}...)
}
// CommandClear clears all port mappings for an app
func CommandClear(appName string) error {
if err := common.VerifyAppName(appName); err != nil {
return err
}
keys := []string{"DOKKU_PROXY_PORT_MAP"}
if err := config.UnsetMany(appName, keys, false); err != nil {
return err
}
return common.PlugnTrigger("post-proxy-ports-update", []string{appName, "clear"}...)
}
// CommandRemove removes specific port mappings from an app
func CommandRemove(appName string, portMapStrings []string) error {
if err := common.VerifyAppName(appName); err != nil {
return err
}
if len(portMapStrings) == 0 {
return errors.New("No port mapping specified")
}
portMaps, err := parsePortMapString(strings.Join(portMapStrings, " "))
if err != nil {
return err
}
if err := removePortMaps(appName, portMaps); err != nil {
return err
}
return common.PlugnTrigger("post-proxy-ports-update", []string{appName, "remove"}...)
}
// CommandSet sets port mappings for an app
func CommandSet(appName string, portMapStrings []string) error {
if err := common.VerifyAppName(appName); err != nil {
return err
}
if len(portMapStrings) == 0 {
return errors.New("No port mapping specified")
}
portMaps, err := parsePortMapString(strings.Join(portMapStrings, " "))
if err != nil {
return err
}
if err := setPortMaps(appName, portMaps); err != nil {
return err
}
return common.PlugnTrigger("post-proxy-ports-update", []string{appName, "set"}...)
}
// CommandReport displays a ports report for one or more apps
func CommandReport(appName string, format string, infoFlag string) error {
if len(appName) == 0 {
apps, err := common.DokkuApps()
if err != nil {
return err
}
for _, appName := range apps {
if err := ReportSingleApp(appName, format, infoFlag); err != nil {
return err
}
}
return nil
}
return ReportSingleApp(appName, format, infoFlag)
}

71
plugins/ports/triggers.go Normal file
View File

@@ -0,0 +1,71 @@
package ports
import (
"github.com/dokku/dokku/plugins/config"
)
// TriggerPostCertsRemove unsets port config vars after SSL cert is added
func TriggerPostCertsRemove(appName string) error {
keys := []string{"DOKKU_PROXY_SSL_PORT"}
if err := config.UnsetMany(appName, keys, false); err != nil {
return err
}
return removePortMaps(appName, filterAppPortMaps(appName, "https", 443))
}
// TriggerPostCertsUpdate sets port config vars after SSL cert is added
func TriggerPostCertsUpdate(appName string) error {
port := config.GetWithDefault(appName, "DOKKU_PROXY_PORT", "")
sslPort := config.GetWithDefault(appName, "DOKKU_PROXY_SSL_PORT", "")
portMaps := getPortMaps(appName)
toUnset := []string{}
if port == "80" {
toUnset = append(toUnset, "DOKKU_PROXY_PORT")
}
if sslPort == "443" {
toUnset = append(toUnset, "DOKKU_PROXY_SSL_PORT")
}
if len(toUnset) > 0 {
if err := config.UnsetMany(appName, toUnset, false); err != nil {
return err
}
}
var http80Ports []PortMap
for _, portMap := range portMaps {
if portMap.Scheme == "http" && portMap.HostPort == 80 {
http80Ports = append(http80Ports, portMap)
}
}
if len(http80Ports) > 0 {
var https443Ports []PortMap
for _, portMap := range portMaps {
if portMap.Scheme == "https" && portMap.HostPort == 443 {
https443Ports = append(https443Ports, portMap)
}
}
if err := removePortMaps(appName, https443Ports); err != nil {
return err
}
var toAdd []PortMap
for _, portMap := range http80Ports {
toAdd = append(toAdd, PortMap{
Scheme: "https",
HostPort: 443,
ContainerPort: portMap.ContainerPort,
})
}
if err := addPortMaps(appName, toAdd); err != nil {
return err
}
}
return nil
}

View File

@@ -6,4 +6,4 @@
/install
/post-*
/report
!/proxy-configure-ports

View File

@@ -1,5 +1,5 @@
SUBCOMMANDS = subcommands/build-config subcommands/clear-config subcommands/disable subcommands/enable subcommands/ports subcommands/ports-add subcommands/ports-clear subcommands/ports-remove subcommands/ports-set subcommands/report subcommands/set
TRIGGERS = triggers/proxy-is-enabled triggers/proxy-type triggers/post-certs-remove triggers/post-certs-update triggers/report
TRIGGERS = triggers/proxy-is-enabled triggers/proxy-type triggers/report
BUILD = commands subcommands triggers
PLUGIN_NAME = proxy

View File

@@ -1,192 +1,9 @@
package proxy
import (
"errors"
"fmt"
"os"
"sort"
"strconv"
"strings"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/config"
"github.com/ryanuber/columnize"
)
func addProxyPorts(appName string, proxyPortMap []PortMap) error {
allPortMaps := getProxyPortMap(appName)
allPortMaps = append(allPortMaps, proxyPortMap...)
return setProxyPorts(appName, allPortMaps)
}
func filterAppProxyPorts(appName string, scheme string, hostPort int) []PortMap {
var filteredProxyMaps []PortMap
for _, portMap := range getProxyPortMap(appName) {
if portMap.Scheme == scheme && portMap.HostPort == hostPort {
filteredProxyMaps = append(filteredProxyMaps, portMap)
}
}
return filteredProxyMaps
}
func getAppProxyType(appName string) string {
return config.GetWithDefault(appName, "DOKKU_APP_PROXY_TYPE", "nginx")
}
func getProxyPortMap(appName string) []PortMap {
value := config.GetWithDefault(appName, "DOKKU_PROXY_PORT_MAP", "")
portMaps, _ := parseProxyPortMapString(value)
return portMaps
}
func inRange(value int, min int, max int) bool {
return min < value && value < max
}
func listAppProxyPorts(appName string) error {
proxyPortMap := getProxyPortMap(appName)
if len(proxyPortMap) == 0 {
return errors.New("No port mappings configured for app")
}
var lines []string
if os.Getenv("DOKKU_QUIET_OUTPUT") == "" {
lines = append(lines, "-----> scheme:host port:container port")
}
for _, portMap := range proxyPortMap {
lines = append(lines, portMap.String())
}
sort.Strings(lines)
common.LogInfo1Quiet(fmt.Sprintf("Port mappings for %s", appName))
config := columnize.DefaultConfig()
config.Delim = ":"
config.Prefix = " "
config.Empty = ""
fmt.Println(columnize.Format(lines, config))
return nil
}
func parseProxyPortMapString(stringPortMap string) ([]PortMap, error) {
var proxyPortMap []PortMap
for _, v := range strings.Split(strings.TrimSpace(stringPortMap), " ") {
parts := strings.SplitN(v, ":", 3)
if len(parts) == 1 {
hostPort, err := strconv.Atoi(v)
if err != nil {
return proxyPortMap, fmt.Errorf("Invalid port map %s [err=%s]", v, err.Error())
}
if !inRange(hostPort, 0, 65536) {
return proxyPortMap, fmt.Errorf("Invalid port map %s [hostPort=%d]", v, hostPort)
}
proxyPortMap = append(proxyPortMap, PortMap{
HostPort: hostPort,
Scheme: "__internal__",
})
continue
}
if len(parts) != 3 {
return proxyPortMap, fmt.Errorf("Invalid port map %s [len=%d]", v, len(parts))
}
hostPort, err := strconv.Atoi(parts[1])
if err != nil {
return proxyPortMap, fmt.Errorf("Invalid port map %s [err=%s]", v, err.Error())
}
containerPort, err := strconv.Atoi(parts[2])
if err != nil {
return proxyPortMap, fmt.Errorf("Invalid port map %s [err=%s]", v, err.Error())
}
if !inRange(hostPort, 0, 65536) {
return proxyPortMap, fmt.Errorf("Invalid port map %s [hostPort=%d]", v, hostPort)
}
if !inRange(containerPort, 0, 65536) {
return proxyPortMap, fmt.Errorf("Invalid port map %s [containerPort=%d]", v, containerPort)
}
proxyPortMap = append(proxyPortMap, PortMap{
ContainerPort: containerPort,
HostPort: hostPort,
Scheme: parts[0],
})
}
return uniqueProxyPortMap(proxyPortMap), nil
}
func removeProxyPorts(appName string, proxyPortMap []PortMap) error {
toRemove := map[string]bool{}
toRemoveByPort := map[int]bool{}
for _, portMap := range proxyPortMap {
if portMap.AllowsPersistence() {
toRemoveByPort[portMap.HostPort] = true
continue
}
toRemove[portMap.String()] = true
}
var toSet []PortMap
for _, portMap := range getProxyPortMap(appName) {
if toRemove[portMap.String()] {
continue
}
if toRemoveByPort[portMap.HostPort] {
continue
}
toSet = append(toSet, portMap)
}
if len(toSet) == 0 {
keys := []string{"DOKKU_PROXY_PORT_MAP"}
return config.UnsetMany(appName, keys, false)
}
return setProxyPorts(appName, toSet)
}
func setProxyPorts(appName string, proxyPortMap []PortMap) error {
var value []string
for _, portMap := range uniqueProxyPortMap(proxyPortMap) {
if portMap.AllowsPersistence() {
continue
}
value = append(value, portMap.String())
}
sort.Strings(value)
entries := map[string]string{
"DOKKU_PROXY_PORT_MAP": strings.Join(value, " "),
}
return config.SetMany(appName, entries, false)
}
func uniqueProxyPortMap(proxyPortMap []PortMap) []PortMap {
var unique []PortMap
existingPortMaps := map[string]bool{}
for _, portMap := range proxyPortMap {
if existingPortMaps[portMap.String()] {
continue
}
existingPortMaps[portMap.String()] = true
unique = append(unique, portMap)
}
return unique
}

View File

@@ -5,7 +5,7 @@ go 1.19
require (
github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000
github.com/dokku/dokku/plugins/config v0.0.0-00010101000000-000000000000
github.com/ryanuber/columnize v2.1.2+incompatible
github.com/dokku/dokku/plugins/ports v0.0.0-00010101000000-000000000000
github.com/spf13/pflag v1.0.5
)
@@ -14,6 +14,7 @@ require (
github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27 // indirect
github.com/joho/godotenv v1.2.0 // indirect
github.com/otiai10/copy v1.12.0 // indirect
github.com/ryanuber/columnize v2.1.2+incompatible // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.10.0 // indirect
)
@@ -21,3 +22,5 @@ require (
replace github.com/dokku/dokku/plugins/common => ../common
replace github.com/dokku/dokku/plugins/config => ../config
replace github.com/dokku/dokku/plugins/ports => ../ports

View File

@@ -1,8 +1,6 @@
package proxy
import (
"fmt"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/config"
)
@@ -11,22 +9,6 @@ import (
// and defaults to -1 (false)
const RunInSerial = 0
// PortMap is a struct that contains a scheme:host-port:container-port mapping
type PortMap struct {
ContainerPort int
HostPort int
Scheme string
}
func (p PortMap) String() string {
return fmt.Sprintf("%s:%d:%d", p.Scheme, p.HostPort, p.ContainerPort)
}
// AllowsPersistence returns true if the port map is not to be persisted
func (p PortMap) AllowsPersistence() bool {
return p.Scheme == "__internal__"
}
// BuildConfig rebuilds the proxy config for the specified app
func BuildConfig(appName string) error {
return common.PlugnTrigger("proxy-build-config", []string{appName}...)

View File

@@ -1,8 +1,6 @@
package proxy
import (
"strings"
"github.com/dokku/dokku/plugins/common"
)
@@ -13,9 +11,8 @@ func ReportSingleApp(appName string, format string, infoFlag string) error {
}
flags := map[string]common.ReportFunc{
"--proxy-enabled": reportEnabled,
"--proxy-type": reportType,
"--proxy-port-map": reportPortMap,
"--proxy-enabled": reportEnabled,
"--proxy-type": reportType,
}
flagKeys := []string{}
@@ -41,12 +38,3 @@ func reportEnabled(appName string) string {
func reportType(appName string) string {
return getAppProxyType(appName)
}
func reportPortMap(appName string) string {
var proxyPortMap []string
for _, portMap := range getProxyPortMap(appName) {
proxyPortMap = append(proxyPortMap, portMap.String())
}
return strings.Join(proxyPortMap, " ")
}

View File

@@ -22,11 +22,6 @@ Additional commands:`
proxy:clear-config [--all|<app>], Clears config for a given app
proxy:disable <app>, Disable proxy for app
proxy:enable <app>, Enable proxy for app
proxy:ports <app>, List proxy port mappings for app
proxy:ports-add <app> [<scheme>:<host-port>:<container-port>...], Add proxy port mappings to an app
proxy:ports-clear <app>, Clear all proxy port mappings for an app
proxy:ports-remove <app> [<host-port>|<scheme>:<host-port>:<container-port>...], Remove specific proxy port mappings from an app
proxy:ports-set <app> [<scheme>:<host-port>:<container-port>...], Set proxy port mappings for an app
proxy:report [<app>] [<flag>], Displays a proxy report for one or more apps
proxy:set <app> <proxy-type>, Set proxy type for app
`

View File

@@ -6,6 +6,7 @@ import (
"strings"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/ports"
"github.com/dokku/dokku/plugins/proxy"
flag "github.com/spf13/pflag"
@@ -49,30 +50,35 @@ func main() {
args := flag.NewFlagSet("proxy:ports", flag.ExitOnError)
args.Parse(os.Args[2:])
appName := args.Arg(0)
err = proxy.CommandPorts(appName)
common.LogWarn("Deprecated: Use 'ports:list' instead")
err = ports.CommandList(appName)
case "ports-add":
args := flag.NewFlagSet("proxy:ports-add", flag.ExitOnError)
args.Parse(os.Args[2:])
appName := args.Arg(0)
_, portMaps := common.ShiftString(args.Args())
err = proxy.CommandPortsAdd(appName, portMaps)
common.LogWarn("Deprecated: Use 'ports:add' instead")
err = ports.CommandAdd(appName, portMaps)
case "ports-clear":
args := flag.NewFlagSet("proxy:ports-clear", flag.ExitOnError)
args.Parse(os.Args[2:])
appName := args.Arg(0)
err = proxy.CommandPortsClear(appName)
common.LogWarn("Deprecated: Use 'ports:clear' instead")
err = ports.CommandClear(appName)
case "ports-remove":
args := flag.NewFlagSet("proxy:ports-remove", flag.ExitOnError)
args.Parse(os.Args[2:])
appName := args.Arg(0)
_, portMaps := common.ShiftString(args.Args())
err = proxy.CommandPortsRemove(appName, portMaps)
common.LogWarn("Deprecated: Use 'ports:remove' instead")
err = ports.CommandRemove(appName, portMaps)
case "ports-set":
args := flag.NewFlagSet("proxy:ports-set", flag.ExitOnError)
args.Parse(os.Args[2:])
appName := args.Arg(0)
_, portMaps := common.ShiftString(args.Args())
err = proxy.CommandPortsSet(appName, portMaps)
common.LogWarn("Deprecated: Use 'ports:set' instead")
err = ports.CommandSet(appName, portMaps)
case "report":
args := flag.NewFlagSet("proxy:report", flag.ExitOnError)
format := args.String("format", "stdout", "format: [ stdout | json ]")

View File

@@ -24,12 +24,6 @@ func main() {
case "proxy-type":
appName := flag.Arg(0)
err = proxy.TriggerProxyType(appName)
case "post-certs-remove":
appName := flag.Arg(0)
err = proxy.TriggerPostCertsRemove(appName)
case "post-certs-update":
appName := flag.Arg(0)
err = proxy.TriggerPostCertsUpdate(appName)
case "report":
appName := flag.Arg(0)
err = proxy.ReportSingleApp(appName, "", "")

View File

@@ -2,7 +2,6 @@ package proxy
import (
"errors"
"strings"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/config"
@@ -60,95 +59,6 @@ func CommandEnable(appName string, allApps bool, parallelCount int) error {
return Enable(appName)
}
// CommandPorts is a cmd wrapper to list proxy port mappings for an app
func CommandPorts(appName string) error {
if err := common.VerifyAppName(appName); err != nil {
return err
}
return listAppProxyPorts(appName)
}
// CommandPortsAdd adds proxy port mappings to an app
func CommandPortsAdd(appName string, portMaps []string) error {
if err := common.VerifyAppName(appName); err != nil {
return err
}
if len(portMaps) == 0 {
return errors.New("No port mapping specified")
}
proxyPortMap, err := parseProxyPortMapString(strings.Join(portMaps, " "))
if err != nil {
return err
}
if err := addProxyPorts(appName, proxyPortMap); err != nil {
return err
}
return common.PlugnTrigger("post-proxy-ports-update", []string{appName, "add"}...)
}
// CommandPortsClear clears all proxy port mappings for an app
func CommandPortsClear(appName string) error {
if err := common.VerifyAppName(appName); err != nil {
return err
}
keys := []string{"DOKKU_PROXY_PORT_MAP"}
if err := config.UnsetMany(appName, keys, false); err != nil {
return err
}
return common.PlugnTrigger("post-proxy-ports-update", []string{appName, "clear"}...)
}
// CommandPortsRemove removes specific proxy port mappings from an app
func CommandPortsRemove(appName string, portMaps []string) error {
if err := common.VerifyAppName(appName); err != nil {
return err
}
if len(portMaps) == 0 {
return errors.New("No port mapping specified")
}
proxyPortMap, err := parseProxyPortMapString(strings.Join(portMaps, " "))
if err != nil {
return err
}
if err := removeProxyPorts(appName, proxyPortMap); err != nil {
return err
}
return common.PlugnTrigger("post-proxy-ports-update", []string{appName, "remove"}...)
}
// CommandPortsSet sets proxy port mappings for an app
func CommandPortsSet(appName string, portMaps []string) error {
if err := common.VerifyAppName(appName); err != nil {
return err
}
if len(portMaps) == 0 {
return errors.New("No port mapping specified")
}
proxyPortMap, err := parseProxyPortMapString(strings.Join(portMaps, " "))
if err != nil {
return err
}
if err := setProxyPorts(appName, proxyPortMap); err != nil {
return err
}
return common.PlugnTrigger("post-proxy-ports-update", []string{appName, "set"}...)
}
// CommandReport displays a proxy report for one or more apps
func CommandReport(appName string, format string, infoFlag string) error {
if len(appName) == 0 {

View File

@@ -2,8 +2,6 @@ package proxy
import (
"fmt"
"github.com/dokku/dokku/plugins/config"
)
// TriggerProxyIsEnabled prints true or false depending on whether the proxy is enabled
@@ -24,69 +22,3 @@ func TriggerProxyType(appName string) error {
return nil
}
// TriggerPostCertsRemove unsets port config vars after SSL cert is added
func TriggerPostCertsRemove(appName string) error {
keys := []string{"DOKKU_PROXY_SSL_PORT"}
if err := config.UnsetMany(appName, keys, false); err != nil {
return err
}
return removeProxyPorts(appName, filterAppProxyPorts(appName, "https", 443))
}
// TriggerPostCertsUpdate sets port config vars after SSL cert is added
func TriggerPostCertsUpdate(appName string) error {
port := config.GetWithDefault(appName, "DOKKU_PROXY_PORT", "")
sslPort := config.GetWithDefault(appName, "DOKKU_PROXY_SSL_PORT", "")
proxyPortMap := getProxyPortMap(appName)
toUnset := []string{}
if port == "80" {
toUnset = append(toUnset, "DOKKU_PROXY_PORT")
}
if sslPort == "443" {
toUnset = append(toUnset, "DOKKU_PROXY_SSL_PORT")
}
if len(toUnset) > 0 {
if err := config.UnsetMany(appName, toUnset, false); err != nil {
return err
}
}
var http80Ports []PortMap
for _, portMap := range proxyPortMap {
if portMap.Scheme == "http" && portMap.HostPort == 80 {
http80Ports = append(http80Ports, portMap)
}
}
if len(http80Ports) > 0 {
var https443Ports []PortMap
for _, portMap := range proxyPortMap {
if portMap.Scheme == "https" && portMap.HostPort == 443 {
https443Ports = append(https443Ports, portMap)
}
}
if err := removeProxyPorts(appName, https443Ports); err != nil {
return err
}
var toAdd []PortMap
for _, portMap := range http80Ports {
toAdd = append(toAdd, PortMap{
Scheme: "https",
HostPort: 443,
ContainerPort: portMap.ContainerPort,
})
}
if err := addProxyPorts(appName, toAdd); err != nil {
return err
}
}
return nil
}

View File

@@ -4,14 +4,12 @@ go 1.19
require (
github.com/dokku/dokku/plugins/common v0.0.0-00010101000000-000000000000
github.com/dokku/dokku/plugins/config v0.0.0-00010101000000-000000000000
github.com/spf13/pflag v1.0.5
)
require (
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect
github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27 // indirect
github.com/joho/godotenv v1.2.0 // indirect
github.com/otiai10/copy v1.12.0 // indirect
github.com/ryanuber/columnize v2.1.2+incompatible // indirect
golang.org/x/sync v0.3.0 // indirect