|
|
|
package run
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/docker/go-connections/nat"
|
|
|
|
)
|
|
|
|
|
|
|
|
// PublishedPorts is a struct used for exposing container ports on the host system
|
|
|
|
type PublishedPorts struct {
|
|
|
|
ExposedPorts map[nat.Port]struct{}
|
|
|
|
PortBindings map[nat.Port][]nat.PortBinding
|
|
|
|
}
|
|
|
|
|
|
|
|
// defaultNodes describes the type of nodes on which a port should be exposed by default
|
|
|
|
const defaultNodes = "all"
|
|
|
|
|
|
|
|
// mapping a node role to groups that should be applied to it
|
|
|
|
var nodeRuleGroupsMap = map[string][]string{
|
|
|
|
"worker": []string{"all", "workers"},
|
|
|
|
"server": []string{"all", "server", "master"},
|
|
|
|
}
|
|
|
|
|
|
|
|
// mapNodesToPortSpecs maps nodes to portSpecs
|
|
|
|
func mapNodesToPortSpecs(specs []string) (map[string][]string, error) {
|
|
|
|
|
|
|
|
if err := validatePortSpecs(specs); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
nodeToPortSpecMap := make(map[string][]string)
|
|
|
|
|
|
|
|
for _, spec := range specs {
|
|
|
|
nodes, portSpec := extractNodes(spec)
|
|
|
|
|
|
|
|
for _, node := range nodes {
|
|
|
|
nodeToPortSpecMap[node] = append(nodeToPortSpecMap[node], portSpec)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Printf("nodeToPortSpecMap: %+v\n", nodeToPortSpecMap)
|
|
|
|
|
|
|
|
return nodeToPortSpecMap, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// The factory function for PublishedPorts
|
|
|
|
func CreatePublishedPorts(specs []string) (*PublishedPorts, error) {
|
|
|
|
if len(specs) == 0 {
|
|
|
|
var newExposedPorts = make(map[nat.Port]struct{}, 1)
|
|
|
|
var newPortBindings = make(map[nat.Port][]nat.PortBinding, 1)
|
|
|
|
return &PublishedPorts{ExposedPorts: newExposedPorts, PortBindings: newPortBindings}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
newExposedPorts, newPortBindings, err := nat.ParsePortSpecs(specs)
|
|
|
|
return &PublishedPorts{ExposedPorts: newExposedPorts, PortBindings: newPortBindings}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// validatePortSpecs matches the provided port specs against a set of rules to enable early exit if something is wrong
|
|
|
|
func validatePortSpecs(specs []string) error {
|
|
|
|
// regex matching (no sophisticated IP/Hostname matching at the moment)
|
|
|
|
regex := regexp.MustCompile(`^(((?P<host>[\w\.]+)?:)?((?P<hostPort>[0-9]{0,6}):)?(?P<containerPort>[0-9]{1,6}))((/(?P<protocol>udp|tcp))?(?P<nodes>(@(?P<node>[\w-]+))*))$`)
|
|
|
|
for _, spec := range specs {
|
|
|
|
if !regex.MatchString(spec) {
|
|
|
|
return fmt.Errorf("[ERROR] Provided port spec [%s] didn't match format specification", spec)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// extractNodes separates the node specification from the actual port specs
|
|
|
|
func extractNodes(spec string) ([]string, string) {
|
|
|
|
// extract nodes
|
|
|
|
nodes := []string{}
|
|
|
|
atSplit := strings.Split(spec, "@")
|
|
|
|
portSpec := atSplit[0]
|
|
|
|
if len(atSplit) > 1 {
|
|
|
|
nodes = atSplit[1:]
|
|
|
|
}
|
|
|
|
if len(nodes) == 0 {
|
|
|
|
nodes = append(nodes, defaultNodes)
|
|
|
|
}
|
|
|
|
return nodes, portSpec
|
|
|
|
}
|
|
|
|
|
|
|
|
// Offset creates a new PublishedPort structure, with all host ports are changed by a fixed 'offset'
|
|
|
|
func (p PublishedPorts) Offset(offset int) *PublishedPorts {
|
|
|
|
var newExposedPorts = make(map[nat.Port]struct{}, len(p.ExposedPorts))
|
|
|
|
var newPortBindings = make(map[nat.Port][]nat.PortBinding, len(p.PortBindings))
|
|
|
|
|
|
|
|
for k, v := range p.ExposedPorts {
|
|
|
|
newExposedPorts[k] = v
|
|
|
|
}
|
|
|
|
|
|
|
|
for k, v := range p.PortBindings {
|
|
|
|
bindings := make([]nat.PortBinding, len(v))
|
|
|
|
for i, b := range v {
|
|
|
|
port, _ := nat.ParsePort(b.HostPort)
|
|
|
|
bindings[i].HostIP = b.HostIP
|
|
|
|
bindings[i].HostPort = fmt.Sprintf("%d", port+offset)
|
|
|
|
}
|
|
|
|
newPortBindings[k] = bindings
|
|
|
|
}
|
|
|
|
|
|
|
|
return &PublishedPorts{ExposedPorts: newExposedPorts, PortBindings: newPortBindings}
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddPort creates a new PublishedPort struct with one more port, based on 'portSpec'
|
|
|
|
func (p *PublishedPorts) AddPort(portSpec string) (*PublishedPorts, error) {
|
|
|
|
portMappings, err := nat.ParsePortSpec(portSpec)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var newExposedPorts = make(map[nat.Port]struct{}, len(p.ExposedPorts)+1)
|
|
|
|
var newPortBindings = make(map[nat.Port][]nat.PortBinding, len(p.PortBindings)+1)
|
|
|
|
|
|
|
|
// Populate the new maps
|
|
|
|
for k, v := range p.ExposedPorts {
|
|
|
|
newExposedPorts[k] = v
|
|
|
|
}
|
|
|
|
|
|
|
|
for k, v := range p.PortBindings {
|
|
|
|
newPortBindings[k] = v
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add new ports
|
|
|
|
for _, portMapping := range portMappings {
|
|
|
|
port := portMapping.Port
|
|
|
|
if _, exists := newExposedPorts[port]; !exists {
|
|
|
|
newExposedPorts[port] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
bslice, exists := newPortBindings[port]
|
|
|
|
if !exists {
|
|
|
|
bslice = []nat.PortBinding{}
|
|
|
|
}
|
|
|
|
newPortBindings[port] = append(bslice, portMapping.Binding)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &PublishedPorts{ExposedPorts: newExposedPorts, PortBindings: newPortBindings}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// MergePortSpecs merges published ports for a given node
|
|
|
|
func MergePortSpecs(nodeToPortSpecMap map[string][]string, role, name string) ([]string, error) {
|
|
|
|
|
|
|
|
portSpecs := []string{}
|
|
|
|
|
|
|
|
// add portSpecs according to node role
|
|
|
|
for _, group := range nodeRuleGroupsMap[role] {
|
|
|
|
for _, v := range nodeToPortSpecMap[group] {
|
|
|
|
exists := false
|
|
|
|
for _, i := range portSpecs {
|
|
|
|
if v == i {
|
|
|
|
exists = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !exists {
|
|
|
|
portSpecs = append(portSpecs, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// add portSpecs according to node name
|
|
|
|
for _, v := range nodeToPortSpecMap[name] {
|
|
|
|
exists := false
|
|
|
|
for _, i := range portSpecs {
|
|
|
|
if v == i {
|
|
|
|
exists = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !exists {
|
|
|
|
portSpecs = append(portSpecs, v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return portSpecs, nil
|
|
|
|
}
|