Files
dokku/plugins/network/network.go
Jose Diaz-Gonzalez 9ecfa843f0 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
2021-08-06 01:29:25 -04:00

245 lines
6.4 KiB
Go

package network
import (
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/dokku/dokku/plugins/common"
"github.com/dokku/dokku/plugins/config"
sh "github.com/codeskyblue/go-sh"
)
var (
// DefaultProperties is a map of all valid network properties with corresponding default property values
DefaultProperties = map[string]string{
"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{
"attach-post-create": true,
"attach-post-deploy": true,
"bind-all-interfaces": true,
"initial-network": true,
"tld": true,
}
)
// BuildConfig builds network config files
func BuildConfig(appName string) error {
if !common.IsDeployed(appName) {
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 {
return err
}
scale, err := common.ParseScaleOutput(s)
if err != nil {
return err
}
if len(scale) == 0 {
return nil
}
image := common.GetAppImageName(appName, "", "")
isHerokuishContainer := common.IsImageHerokuishBased(image, appName)
common.LogInfo1(fmt.Sprintf("Ensuring network configuration is in sync for %s", appName))
for processType, procCount := range scale {
containerIndex := 0
for containerIndex < procCount {
containerIndex++
containerIndexString := strconv.Itoa(containerIndex)
containerIDFile := fmt.Sprintf("%v/CONTAINER.%v.%v", appRoot, processType, containerIndex)
containerID := common.ReadFirstLine(containerIDFile)
if containerID == "" || !common.ContainerIsRunning(containerID) {
continue
}
ipAddress := GetContainerIpaddress(appName, processType, containerID)
port := GetContainerPort(appName, processType, containerID, isHerokuishContainer)
if ipAddress != "" {
args := []string{appName, processType, containerIndexString, ipAddress}
_, err := common.PlugnTriggerOutput("network-write-ipaddr", args...)
if err != nil {
common.LogWarn(err.Error())
}
}
if port != "" {
args := []string{appName, processType, containerIndexString, port}
_, err := common.PlugnTriggerOutput("network-write-port", args...)
if err != nil {
common.LogWarn(err.Error())
}
}
}
}
return nil
}
// 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"
}
}
initialNetwork := reportComputedInitialNetwork(appName)
if initialNetwork == "" {
initialNetwork = "bridge"
}
b, err := common.DockerInspect(containerID, fmt.Sprintf("{{ $network := index .NetworkSettings.Networks \"%s\" }}{{ $network.IPAddress}}", initialNetwork))
if err != nil || len(b) == 0 {
// Deprecated: docker < 1.9 compatibility
b, err = common.DockerInspect(containerID, "{{ .NetworkSettings.IPAddress }}")
}
if err == nil {
return string(b[:])
}
return
}
// 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", "")
if configValue != "" {
dockerfilePorts = strings.Split(configValue, " ")
}
}
if len(dockerfilePorts) > 0 {
for _, p := range dockerfilePorts {
if strings.HasSuffix(p, "/udp") {
continue
}
port = strings.TrimSuffix(p, "/tcp")
if port != "" {
break
}
}
cmd := sh.Command(common.DockerBin(), "container", "port", containerID, port)
cmd.Stderr = ioutil.Discard
b, err := cmd.Output()
if err == nil {
port = strings.Split(string(b[:]), ":")[1]
}
} else {
port = "5000"
}
return
}
// 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)
portPrefix := fmt.Sprintf("/PORT.%s.", processType)
files, _ := filepath.Glob(appRoot + ipPrefix + "*")
var listeners []string
for _, ipfile := range files {
portfile := strings.Replace(ipfile, ipPrefix, portPrefix, 1)
ipAddress := common.ReadFirstLine(ipfile)
port := common.ReadFirstLine(portfile)
listeners = append(listeners, fmt.Sprintf("%s:%s", ipAddress, port))
}
return listeners
}
// HasNetworkConfig returns whether the network configuration for a given app exists
func HasNetworkConfig(appName string) bool {
appRoot := common.AppRoot(appName)
ipfile := fmt.Sprintf("%v/IP.web.1", appRoot)
portfile := fmt.Sprintf("%v/PORT.web.1", appRoot)
return common.FileExists(ipfile) && common.FileExists(portfile)
}
// ClearNetworkConfig removes old IP and PORT files for a newly cloned app
func ClearNetworkConfig(appName string) bool {
appRoot := common.AppRoot(appName)
success := true
ipFiles, _ := filepath.Glob(appRoot + "/IP.*")
for _, file := range ipFiles {
if err := os.Remove(file); err != nil {
common.LogWarn(fmt.Sprintf("Unable to remove file %s", file))
success = false
}
}
portFiles, _ := filepath.Glob(appRoot + "/PORT.*")
for _, file := range portFiles {
if err := os.Remove(file); err != nil {
common.LogWarn(fmt.Sprintf("Unable to remove file %s", file))
success = false
}
}
return success
}