Little helper to run CNCF's k3s in Docker
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
k3d/cli/port.go

203 lines
5.6 KiB

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": {"all", "workers"},
"server": {"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
}