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 matching at the moment) regex := regexp.MustCompile(`^(((?P[\d\.]+)?:)?((?P[0-9]{0,6}):)?(?P[0-9]{1,6}))((/(?Pudp|tcp))?(?P(@(?P[\w-]+))+))$`) for _, spec := range specs { if !regex.MatchString(spec) { return fmt.Errorf("[ERROR] Provided port spec [%s] didn't match format specification (`[ip:][host-port:]container-port[/protocol]@node-specifier`)", 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 }