mirror of https://github.com/k3d-io/k3d
Merge pull request #43 from rancher/feature/enhanced-port-mapping
[Feature] Enhanced port mapping (node-specifiers, optional offset, ...)pull/46/head v1.2.0-beta.2
commit
8d70dd261c
@ -0,0 +1,203 @@ |
||||
package run |
||||
|
||||
import ( |
||||
"fmt" |
||||
"log" |
||||
"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 = "server" |
||||
|
||||
// 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, createdNodes []string) (map[string][]string, error) { |
||||
|
||||
if err := validatePortSpecs(specs); err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
// check node-specifier possibilitites
|
||||
possibleNodeSpecifiers := []string{"all", "workers", "server", "master"} |
||||
possibleNodeSpecifiers = append(possibleNodeSpecifiers, createdNodes...) |
||||
|
||||
nodeToPortSpecMap := make(map[string][]string) |
||||
|
||||
for _, spec := range specs { |
||||
nodes, portSpec := extractNodes(spec) |
||||
|
||||
if len(nodes) == 0 { |
||||
nodes = append(nodes, defaultNodes) |
||||
} |
||||
|
||||
for _, node := range nodes { |
||||
// check if node-specifier is valid (either a role or a name) and append to list if matches
|
||||
nodeFound := false |
||||
for _, name := range possibleNodeSpecifiers { |
||||
if node == name { |
||||
nodeFound = true |
||||
nodeToPortSpecMap[node] = append(nodeToPortSpecMap[node], portSpec) |
||||
break |
||||
} |
||||
} |
||||
if !nodeFound { |
||||
log.Printf("WARNING: Unknown node-specifier [%s] in port mapping entry [%s]", node, spec) |
||||
} |
||||
} |
||||
} |
||||
|
||||
return nodeToPortSpecMap, nil |
||||
} |
||||
|
||||
// CreatePublishedPorts is 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 { |
||||
for _, spec := range specs { |
||||
atSplit := strings.Split(spec, "@") |
||||
_, err := nat.ParsePortSpec(atSplit[0]) |
||||
if err != nil { |
||||
return fmt.Errorf("ERROR: Invalid port specification [%s] in port mapping [%s]\n%+v", atSplit[0], spec, err) |
||||
} |
||||
if len(atSplit) > 0 { |
||||
for i := 1; i < len(atSplit); i++ { |
||||
if err := ValidateHostname(atSplit[i]); err != nil { |
||||
return fmt.Errorf("ERROR: Invalid node-specifier [%s] in port mapping [%s]\n%+v", atSplit[i], spec, err) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
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 |
||||
} |
Loading…
Reference in new issue