From c061104b5327a8a8300b9f668e104c1a39e578a4 Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Mon, 11 May 2020 08:42:44 +0200 Subject: [PATCH] createNode: copy as many details as possible from existing - we now use the full containerJSON details when getting a node - we now use as many details as possible to copy k3d settings from an existing node when adding a new node to a running cluster --- docs/usage/.pages | 1 + go.mod | 2 +- pkg/cluster/node.go | 30 +++++-------- pkg/runtimes/docker/node.go | 31 +++++++++++-- pkg/runtimes/docker/translate.go | 77 ++++++++++++++++++++++++++++++++ 5 files changed, 119 insertions(+), 22 deletions(-) diff --git a/docs/usage/.pages b/docs/usage/.pages index d77f82fb..db03ec11 100644 --- a/docs/usage/.pages +++ b/docs/usage/.pages @@ -2,4 +2,5 @@ title: Usage arrange: - commands.md - kubeconfig.md + - multimaster.md - guides \ No newline at end of file diff --git a/go.mod b/go.mod index cedc5082..66eb91ef 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/gogo/protobuf v1.3.1 // indirect github.com/golang/protobuf v1.4.0 // indirect github.com/heroku/docker-registry-client v0.0.0-20190909225348-afc9e1acc3d5 - github.com/imdario/mergo v0.3.9 // indirect + github.com/imdario/mergo v0.3.9 github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de github.com/mitchellh/go-homedir v1.1.0 github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect diff --git a/pkg/cluster/node.go b/pkg/cluster/node.go index 0fe10def..9ce2443b 100644 --- a/pkg/cluster/node.go +++ b/pkg/cluster/node.go @@ -25,10 +25,10 @@ package cluster import ( "bytes" "context" - "fmt" "strings" "time" + "github.com/imdario/mergo" "github.com/rancher/k3d/pkg/runtimes" k3d "github.com/rancher/k3d/pkg/types" log "github.com/sirupsen/logrus" @@ -70,27 +70,21 @@ func AddNodeToCluster(runtime runtimes.Runtime, node *k3d.Node, cluster *k3d.Clu } } + // get node details + chosenNode, err = GetNode(chosenNode, runtime) + if err != nil { + return err + } + log.Debugf("Copying configuration from existing node %+v", chosenNode) - // get config from labels - for k, v := range chosenNode.Labels { - if strings.HasPrefix(k, "k3d") { - node.Labels[k] = v - } - if k == "k3d.cluster.url" { - node.Env = append(node.Env, fmt.Sprintf("K3S_URL=%s", v)) - } - if k == "k3d.cluster.secret" { - node.Env = append(node.Env, fmt.Sprintf("K3S_TOKEN=%s", v)) - } + // merge node config of new node into existing node config + if err := mergo.MergeWithOverwrite(chosenNode, *node); err != nil { + log.Errorln("Failed to merge new node config into existing node config") + return err } - // backup: get config from environment variables - for _, env := range chosenNode.Env { - if strings.HasPrefix(env, "K3S_") { - node.Env = append(node.Env, env) - } - } + node = chosenNode log.Debugf("Resulting node %+v", node) diff --git a/pkg/runtimes/docker/node.go b/pkg/runtimes/docker/node.go index db6426e7..840be3ef 100644 --- a/pkg/runtimes/docker/node.go +++ b/pkg/runtimes/docker/node.go @@ -38,7 +38,6 @@ import ( // CreateNode creates a new container func (d Docker) CreateNode(node *k3d.Node) error { - log.Debugln("docker.CreateNode...") // translate node spec to docker container specs dockerNode, err := TranslateNodeToContainer(node) @@ -172,9 +171,30 @@ func getContainersByLabel(labels map[string]string) ([]types.Container, error) { log.Errorln("Failed to list containers") return nil, err } + return containers, nil } +// getContainer details returns the containerjson with more details +func getContainerDetails(containerID string) (types.ContainerJSON, error) { + // (0) create docker client + ctx := context.Background() + docker, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + return types.ContainerJSON{}, fmt.Errorf("Failed to create docker client. %+v", err) + } + defer docker.Close() + + containerDetails, err := docker.ContainerInspect(ctx, containerID) + if err != nil { + log.Errorln("Failed to get details for container '%s'", containerID) + return types.ContainerJSON{}, err + } + + return containerDetails, nil + +} + // GetNode tries to get a node container by its name func (d Docker) GetNode(node *k3d.Node) (*k3d.Node, error) { container, err := getNodeContainer(node) @@ -183,9 +203,14 @@ func (d Docker) GetNode(node *k3d.Node) (*k3d.Node, error) { return nil, err } - node, err = TranslateContainerToNode(container) + containerDetails, err := getContainerDetails(container.ID) + if err != nil { + return nil, err + } + + node, err = TranslateContainerDetailsToNode(containerDetails) if err != nil { - log.Errorf("Failed to translate container for node '%s' to node object", node.Name) + log.Errorf("Failed to translate container details for node '%s' to node object", node.Name) return nil, err } diff --git a/pkg/runtimes/docker/translate.go b/pkg/runtimes/docker/translate.go index d1b6a019..0020b14b 100644 --- a/pkg/runtimes/docker/translate.go +++ b/pkg/runtimes/docker/translate.go @@ -23,6 +23,7 @@ THE SOFTWARE. package docker import ( + "fmt" "strings" "github.com/docker/docker/api/types" @@ -117,3 +118,79 @@ func TranslateContainerToNode(cont *types.Container) (*k3d.Node, error) { } return node, nil } + +// TranslateContainerDetailsToNode translates a docker containerJSON object into a k3d node representation +func TranslateContainerDetailsToNode(containerDetails types.ContainerJSON) (*k3d.Node, error) { + + // translate portMap to string representation + ports := []string{} + for containerPort, portBindingList := range containerDetails.HostConfig.PortBindings { + for _, hostInfo := range portBindingList { + ports = append(ports, fmt.Sprintf("%s:%s:%s", hostInfo.HostIP, hostInfo.HostPort, containerPort)) + } + } + + // restart -> we only set 'unless-stopped' upon cluster creation + restart := false + if containerDetails.HostConfig.RestartPolicy.IsAlways() || containerDetails.HostConfig.RestartPolicy.IsUnlessStopped() { + restart = true + } + + // get the clusterNetwork + clusterNetwork := "" + for networkName := range containerDetails.NetworkSettings.Networks { + if strings.HasPrefix(networkName, fmt.Sprintf("%s-%s", k3d.DefaultObjectNamePrefix, containerDetails.Config.Labels["k3d.cluster"])) { // FIXME: catch error if label 'k3d.cluster' does not exist, but this should also never be the case + clusterNetwork = networkName + } + } + + // masterOpts + masterOpts := k3d.MasterOpts{IsInit: false} + for k, v := range containerDetails.Config.Labels { + /* + node.Labels["k3d.master.api.hostIP"] = node.MasterOpts.ExposeAPI.HostIP // TODO: maybe get docker machine IP here + node.Labels["k3d.master.api.host"] = node.MasterOpts.ExposeAPI.Host + node.Labels["k3d.master.api.port"] = node.MasterOpts.ExposeAPI.Port + */ + if k == "k3d.master.api.hostIP" { + masterOpts.ExposeAPI.HostIP = v + } else if k == "k3d.master.api.host" { + masterOpts.ExposeAPI.Host = v + } else if k == "k3d.master.api.port" { + masterOpts.ExposeAPI.Port = v + } + } + + // env vars: only copy K3S_* and K3D_* // FIXME: should we really do this? Might be unexpected, if user has e.g. HTTP_PROXY vars + env := []string{} + for _, envVar := range containerDetails.Config.Env { + if strings.HasPrefix(envVar, "K3D_") || strings.HasPrefix(envVar, "K3S_") { + env = append(env, envVar) + } + } + + // labels: only copy k3d.* labels + labels := map[string]string{} + for k, v := range containerDetails.Config.Labels { + if strings.HasPrefix(k, "k3d") { + labels[k] = v + } + } + + node := &k3d.Node{ + Name: strings.TrimPrefix(containerDetails.Name, "/"), // container name with leading '/' cut off + Role: k3d.NodeRoles[containerDetails.Config.Labels["k3d.role"]], + Image: containerDetails.Image, + Volumes: containerDetails.HostConfig.Binds, + Env: env, + Cmd: containerDetails.Config.Cmd, + Args: []string{}, // empty, since Cmd already contains flags + Ports: ports, + Restart: restart, + Labels: labels, + Network: clusterNetwork, + MasterOpts: masterOpts, + WorkerOpts: k3d.WorkerOpts{}, + } + return node, nil +}