From d35f12a77e6d452bef88cfb6ac8faf4c35902192 Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Wed, 8 May 2019 13:37:24 +0200 Subject: [PATCH] init --- cli/commands.go | 12 +++- cli/container.go | 104 +++++--------------------------- cli/port.go | 153 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+), 91 deletions(-) create mode 100644 cli/port.go diff --git a/cli/commands.go b/cli/commands.go index 812dfa77..07f128b1 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -71,8 +71,18 @@ func CreateCluster(c *cli.Context) error { k3sServerArgs = append(k3sServerArgs, c.StringSlice("server-arg")...) } + fmt.Println("==========") + pm, err := createPortMap(c.StringSlice("publish")) + fmt.Printf("pm: %+v \n err: %+v \n", pm, err) + for _, x := range *pm { + fmt.Printf("x: %+v\n -> Ports: %+v\n", x, x.Ports) + } + fmt.Println("==========") publishedPorts, err := createPublishedPorts(c.StringSlice("publish")) - if (err != nil) { + fmt.Printf("pm: %+v \n err: %+v \n", publishedPorts, err) + fmt.Println("==========") + + if err != nil { log.Fatalf("ERROR: failed to parse the publish parameter.\n%+v", err) } diff --git a/cli/container.go b/cli/container.go index 843a6ce8..8db2df26 100644 --- a/cli/container.go +++ b/cli/container.go @@ -18,84 +18,8 @@ import ( "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" "github.com/docker/docker/client" - "github.com/docker/go-connections/nat" ) -type PublishedPorts struct { - ExposedPorts map[nat.Port]struct{} - PortBindings map[nat.Port][]nat.PortBinding -} - -// 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 -} - -// Create 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} -} - -// Create 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 -} - func startContainer(verbose bool, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (string, error) { ctx := context.Background() @@ -139,7 +63,7 @@ func startContainer(verbose bool, config *container.Config, hostConfig *containe } func createServer(verbose bool, image string, port string, args []string, env []string, - name string, volumes []string, pPorts *PublishedPorts) (string, error) { + name string, volumes []string, pPorts *PublishedPorts) (string, error) { log.Printf("Creating server using %s...\n", image) containerLabels := make(map[string]string) @@ -152,13 +76,13 @@ func createServer(verbose bool, image string, port string, args []string, env [] apiPortSpec := fmt.Sprintf("0.0.0.0:%s:%s/tcp", port, port) serverPublishedPorts, err := pPorts.AddPort(apiPortSpec) - if (err != nil) { + if err != nil { log.Fatalf("Error: failed to parse API port spec %s \n%+v", apiPortSpec, err) } hostConfig := &container.HostConfig{ PortBindings: serverPublishedPorts.PortBindings, - Privileged: true, + Privileged: true, } if len(volumes) > 0 && volumes[0] != "" { @@ -174,12 +98,12 @@ func createServer(verbose bool, image string, port string, args []string, env [] } config := &container.Config{ - Hostname: containerName, - Image: image, - Cmd: append([]string{"server"}, args...), + Hostname: containerName, + Image: image, + Cmd: append([]string{"server"}, args...), ExposedPorts: serverPublishedPorts.ExposedPorts, - Env: env, - Labels: containerLabels, + Env: env, + Labels: containerLabels, } id, err := startContainer(verbose, config, hostConfig, networkingConfig, containerName) if err != nil { @@ -191,7 +115,7 @@ func createServer(verbose bool, image string, port string, args []string, env [] // createWorker creates/starts a k3s agent node that connects to the server func createWorker(verbose bool, image string, args []string, env []string, name string, volumes []string, - postfix int, serverPort string, pPorts *PublishedPorts) (string, error) { + postfix int, serverPort string, pPorts *PublishedPorts) (string, error) { containerLabels := make(map[string]string) containerLabels["app"] = "k3d" containerLabels["component"] = "worker" @@ -210,7 +134,7 @@ func createWorker(verbose bool, image string, args []string, env []string, name "/var/run": "", }, PortBindings: workerPublishedPorts.PortBindings, - Privileged: true, + Privileged: true, } if len(volumes) > 0 && volumes[0] != "" { @@ -226,10 +150,10 @@ func createWorker(verbose bool, image string, args []string, env []string, name } config := &container.Config{ - Hostname: containerName, - Image: image, - Env: env, - Labels: containerLabels, + Hostname: containerName, + Image: image, + Env: env, + Labels: containerLabels, ExposedPorts: workerPublishedPorts.ExposedPorts, } diff --git a/cli/port.go b/cli/port.go new file mode 100644 index 00000000..465e7ae1 --- /dev/null +++ b/cli/port.go @@ -0,0 +1,153 @@ +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 +} + +// Portmap maps node roles/names to a set of PublishedPorts +type Portmap struct { + Node string + Ports *PublishedPorts +} + +// defaultNodes describes the type of nodes on which a port should be exposed by default +const defaultNodes = "all" + +// createPortMap creates a list of portmaps that map nodes (roles or names) to a list of published ports +func createPortMap(specs []string) (*[]Portmap, 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) + } + } + + portmaps := []Portmap{} + for node, portSpecs := range nodeToPortSpecMap { + ports, err := createPublishedPorts(portSpecs) + if err != nil { + return nil, err + } + newPortMap := Portmap{ + Node: node, + Ports: ports, + } + portmaps = append(portmaps, newPortMap) + } + return &portmaps, 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[\w\.]+)?:)?((?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", 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 +}