From fbaac5b01cfa53d64aa9c1d51bd603640ee90462 Mon Sep 17 00:00:00 2001 From: Lionel Nicolas Date: Thu, 16 Jan 2020 00:01:08 -0500 Subject: [PATCH] add support for node specifier in labels --- cli/commands.go | 57 ++++++++++------------ cli/container.go | 28 ++++++----- cli/label.go | 121 +++++++++++++++++++++++++++++++++++++++++++++++ cli/port.go | 2 +- cli/types.go | 25 +++++----- main.go | 6 +-- 6 files changed, 180 insertions(+), 59 deletions(-) create mode 100644 cli/label.go diff --git a/cli/commands.go b/cli/commands.go index 82d5be0365..73db130728 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -108,8 +108,10 @@ func CreateCluster(c *cli.Context) error { * Docker container labels that will be added to the k3d node containers */ // labels - labels := []string{} - labels = append(labels, c.StringSlice("label")...) + labelmap, err := mapNodesToLabelSpecs(c.StringSlice("label"), GetAllContainerNames(c.String("name"), DefaultServerCount, c.Int("workers"))) + if err != nil { + log.Fatal(err) + } /* * Arguments passed on to the k3s server and agent, will be filled later @@ -207,17 +209,17 @@ func CreateCluster(c *cli.Context) error { * Defines, with which specifications, the cluster and the nodes inside should be created */ clusterSpec := &ClusterSpec{ - AgentArgs: k3AgentArgs, - APIPort: *apiPort, - AutoRestart: c.Bool("auto-restart"), - ClusterName: c.String("name"), - Env: env, - Labels: labels, - Image: image, - NodeToPortSpecMap: portmap, - PortAutoOffset: c.Int("port-auto-offset"), - ServerArgs: k3sServerArgs, - Volumes: volumesSpec, + AgentArgs: k3AgentArgs, + APIPort: *apiPort, + AutoRestart: c.Bool("auto-restart"), + ClusterName: c.String("name"), + Env: env, + NodeToLabelSpecMap: labelmap, + Image: image, + NodeToPortSpecMap: portmap, + PortAutoOffset: c.Int("port-auto-offset"), + ServerArgs: k3sServerArgs, + Volumes: volumesSpec, } /****************** @@ -485,17 +487,17 @@ func AddNode(c *cli.Context) error { nodeCount := c.Int("count") clusterSpec := &ClusterSpec{ - AgentArgs: nil, - APIPort: apiPort{}, - AutoRestart: false, - ClusterName: clusterName, - Env: nil, - Labels: nil, - Image: "", - NodeToPortSpecMap: nil, - PortAutoOffset: 0, - ServerArgs: nil, - Volumes: &Volumes{}, + AgentArgs: nil, + APIPort: apiPort{}, + AutoRestart: false, + ClusterName: clusterName, + Env: nil, + NodeToLabelSpecMap: nil, + Image: "", + NodeToPortSpecMap: nil, + PortAutoOffset: 0, + ServerArgs: nil, + Volumes: &Volumes{}, } /* (0.1) @@ -569,13 +571,6 @@ func AddNode(c *cli.Context) error { return nil } - /* (0.6) - * --label, -l - * Docker container labels that will be added to the k3d node containers - */ - clusterSpec.Labels = []string{} - clusterSpec.Labels = append(clusterSpec.Labels, c.StringSlice("label")...) - /* * (1) Check cluster */ diff --git a/cli/container.go b/cli/container.go index 5ec4e1f520..755786d696 100644 --- a/cli/container.go +++ b/cli/container.go @@ -75,8 +75,16 @@ func createServer(spec *ClusterSpec) (string, error) { containerName := GetContainerName("server", spec.ClusterName, -1) + // labels to be created to the server belong to roles + // all, server, master or + serverLabels, err := MergeLabelSpecs(spec.NodeToLabelSpecMap, "server", containerName) + if err != nil { + return "", err + } + containerLabels = MergeLabels(containerLabels, serverLabels) + // ports to be assigned to the server belong to roles - // all, server or + // all, worker, agent or serverPorts, err := MergePortSpecs(spec.NodeToPortSpecMap, "server", containerName) if err != nil { return "", err @@ -142,16 +150,6 @@ func createWorker(spec *ClusterSpec, postfix int) (string, error) { containerLabels["created"] = time.Now().Format("2006-01-02 15:04:05") containerLabels["cluster"] = spec.ClusterName - for _, label := range spec.Labels { - labelSlice := strings.SplitN(label, "=", 2) - - if len(labelSlice) > 1 { - containerLabels[labelSlice[0]] = labelSlice[1] - } else { - containerLabels[labelSlice[0]] = "" - } - } - containerName := GetContainerName("worker", spec.ClusterName, postfix) env := spec.Env @@ -166,6 +164,14 @@ func createWorker(spec *ClusterSpec, postfix int) (string, error) { env = append(spec.Env, fmt.Sprintf("K3S_URL=https://k3d-%s-server:%s", spec.ClusterName, spec.APIPort.Port)) } + // labels to be created to the server belong to roles + // all, server or + workerLabels, err := MergeLabelSpecs(spec.NodeToLabelSpecMap, "worker", containerName) + if err != nil { + return "", err + } + containerLabels = MergeLabels(containerLabels, workerLabels) + // ports to be assigned to the server belong to roles // all, server or workerPorts, err := MergePortSpecs(spec.NodeToPortSpecMap, "worker", containerName) diff --git a/cli/label.go b/cli/label.go new file mode 100644 index 0000000000..86dc6371d7 --- /dev/null +++ b/cli/label.go @@ -0,0 +1,121 @@ +package run + +import ( + "regexp" + "strings" + + log "github.com/sirupsen/logrus" +) + +// mapNodesToLabelSpecs maps nodes to labelSpecs +func mapNodesToLabelSpecs(specs []string, createdNodes []string) (map[string][]string, error) { + // check node-specifier possibilitites + possibleNodeSpecifiers := []string{"all", "workers", "agents", "server", "master"} + possibleNodeSpecifiers = append(possibleNodeSpecifiers, createdNodes...) + + nodeToLabelSpecMap := make(map[string][]string) + + for _, spec := range specs { + labelSpec, node := extractLabelNode(spec) + + // 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 + nodeToLabelSpecMap[node] = append(nodeToLabelSpecMap[node], labelSpec) + break + } + } + + // node extraction was a false positive, use full spec with default node + if !nodeFound { + nodeToLabelSpecMap[defaultLabelNodes] = append(nodeToLabelSpecMap[defaultLabelNodes], spec) + } + } + + return nodeToLabelSpecMap, nil +} + +// extractLabelNode separates the node specification from the actual label specs +func extractLabelNode(spec string) (string, string) { + // label defaults to full spec + labelSpec := spec + + // node defaults to "all" + node := defaultLabelNodes + + // only split at the last "@" + re := regexp.MustCompile(`^(.*)@([^@]+)$`) + match := re.FindStringSubmatch(spec) + + if len(match) > 0 { + labelSpec = match[1] + node = match[2] + } + + return labelSpec, node +} + +// splitLabel separates the label key from the label value +func splitLabel(label string) (string, string) { + // split only on first '=' sign (like `docker run` do) + labelSlice := strings.SplitN(label, "=", 2) + + if len(labelSlice) > 1 { + return labelSlice[0], labelSlice[1] + } + + // defaults to label key with empty value (like `docker run` do) + return label, "" +} + +// MergeLabelSpecs merges labels for a given node +func MergeLabelSpecs(nodeToLabelSpecMap map[string][]string, role, name string) ([]string, error) { + labelSpecs := []string{} + + // add portSpecs according to node role + for _, group := range nodeRuleGroupsMap[role] { + for _, v := range nodeToLabelSpecMap[group] { + exists := false + for _, i := range labelSpecs { + if v == i { + exists = true + } + } + if !exists { + labelSpecs = append(labelSpecs, v) + } + } + } + + // add portSpecs according to node name + for _, v := range nodeToLabelSpecMap[name] { + exists := false + for _, i := range labelSpecs { + if v == i { + exists = true + } + } + if !exists { + labelSpecs = append(labelSpecs, v) + } + } + + return labelSpecs, nil +} + +// MergeLabels merges list of labels into a label map +func MergeLabels(labelMap map[string]string, labels []string) map[string]string { + for _, label := range labels { + labelKey, labelValue := splitLabel(label) + + if _, found := labelMap[labelKey]; found { + log.Warningf("Overriding already existing label [%s]", labelKey) + } + + labelMap[labelKey] = labelValue + } + + return labelMap +} diff --git a/cli/port.go b/cli/port.go index 820390ca94..1f1129e840 100644 --- a/cli/port.go +++ b/cli/port.go @@ -93,7 +93,7 @@ func extractNodes(spec string) ([]string, string) { return nodes, portSpec } -// Offset creates a new PublishedPort structure, with all host ports are changed by a fixed 'offset' +// 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)) diff --git a/cli/types.go b/cli/types.go index 2ee604ac23..5c3c4bd8d1 100644 --- a/cli/types.go +++ b/cli/types.go @@ -14,6 +14,9 @@ const ( // defaultNodes describes the type of nodes on which a port should be exposed by default const defaultNodes = "server" +// defaultLabelNodes describes the type of nodes on which a label should be applied by default +const defaultLabelNodes = "all" + // mapping a node role to groups that should be applied to it var nodeRuleGroupsMap = map[string][]string{ "worker": {"all", "workers", "agents"}, @@ -32,17 +35,17 @@ type Cluster struct { // ClusterSpec defines the specs for a cluster that's up for creation type ClusterSpec struct { - AgentArgs []string - APIPort apiPort - AutoRestart bool - ClusterName string - Env []string - Labels []string - Image string - NodeToPortSpecMap map[string][]string - PortAutoOffset int - ServerArgs []string - Volumes *Volumes + AgentArgs []string + APIPort apiPort + AutoRestart bool + ClusterName string + Env []string + NodeToLabelSpecMap map[string][]string + Image string + NodeToPortSpecMap map[string][]string + PortAutoOffset int + ServerArgs []string + Volumes *Volumes } // PublishedPorts is a struct used for exposing container ports on the host system diff --git a/main.go b/main.go index f2e7c3cf94..c042b90ff5 100644 --- a/main.go +++ b/main.go @@ -109,7 +109,7 @@ func main() { }, cli.StringSliceFlag{ Name: "label, l", - Usage: "Add one or more docker labels to every node container of the cluster, using Docker notation `key=value` (new flag per label)", + Usage: "Add a docker label to node container (Format: `key[=value][@node-specifier]`, new flag per label)", }, cli.IntFlag{ Name: "workers, w", @@ -158,10 +158,6 @@ func main() { Name: "env, e", Usage: "Pass an additional environment variable (new flag per variable)", }, - cli.StringSliceFlag{ - Name: "label, l", - Usage: "Add one or more docker labels to every node container of the cluster (Docker notation: `key=value`)", - }, cli.StringSliceFlag{ Name: "volume, v", Usage: "Mount one or more volumes into every created node (Docker notation: `source:destination`)",