From ab2c54ca1d0f735042ea32bd955051520aaee626 Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Sun, 30 Jun 2019 16:51:20 +0200 Subject: [PATCH 1/9] add import-image command for importing single image --- cli/cluster.go | 4 ++ cli/commands.go | 19 ++++--- cli/container.go | 28 +++++++--- cli/image.go | 131 +++++++++++++++++++++++++++++++++++++++++++++++ cli/util.go | 6 +-- main.go | 17 ++++++ 6 files changed, 188 insertions(+), 17 deletions(-) create mode 100644 cli/image.go diff --git a/cli/cluster.go b/cli/cluster.go index aec873a14..b5797e2a1 100644 --- a/cli/cluster.go +++ b/cli/cluster.go @@ -68,6 +68,10 @@ func createClusterDir(name string) { if err := createDirIfNotExists(clusterPath); err != nil { log.Fatalf("ERROR: couldn't create cluster directory [%s] -> %+v", clusterPath, err) } + // create subdir for sharing container images + if err := createDirIfNotExists(clusterPath + "/images"); err != nil { + log.Fatalf("ERROR: couldn't create cluster sub-directory [%s] -> %+v", clusterPath+"/images", err) + } } // deleteClusterDir contrary to createClusterDir, this deletes the cluster directory under $HOME/.config/k3d/ diff --git a/cli/commands.go b/cli/commands.go index 6d759a292..4fe0c04f1 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -100,7 +100,7 @@ func CreateCluster(c *cli.Context) error { if c.IsSet("port") { log.Println("INFO: As of v2.0.0 --port will be used for arbitrary port mapping. Please use --api-port/-a instead for configuring the Api Port") } - apiPort, err := parseApiPort(c.String("api-port")) + apiPort, err := parseAPIPort(c.String("api-port")) if err != nil { return err } @@ -111,7 +111,7 @@ func CreateCluster(c *cli.Context) error { if apiPort.Host == "" { apiPort.Host, err = getDockerMachineIp() // IP address is the same as the host - apiPort.HostIp = apiPort.Host + apiPort.HostIP = apiPort.Host // In case of error, Log a warning message, and continue on. Since it more likely caused by a miss configured // DOCKER_MACHINE_NAME environment variable. if err != nil { @@ -137,7 +137,7 @@ func CreateCluster(c *cli.Context) error { clusterSpec := &ClusterSpec{ AgentArgs: []string{}, - ApiPort: *apiPort, + APIPort: *apiPort, AutoRestart: c.Bool("auto-restart"), ClusterName: c.String("name"), Env: env, @@ -151,6 +151,10 @@ func CreateCluster(c *cli.Context) error { // create the server log.Printf("Creating cluster [%s]", c.String("name")) + + // create the directory where we will put the kubeconfig file by default (when running `k3d get-config`) + createClusterDir(c.String("name")) + dockerID, err := createServer(clusterSpec) if err != nil { deleteCluster() @@ -192,10 +196,6 @@ func CreateCluster(c *cli.Context) error { time.Sleep(1 * time.Second) } - // create the directory where we will put the kubeconfig file by default (when running `k3d get-config`) - // TODO: this can probably be moved to `k3d get-config` or be removed in a different approach - createClusterDir(c.String("name")) - // spin up the worker nodes // TODO: do this concurrently in different goroutines if c.Int("workers") > 0 { @@ -362,3 +362,8 @@ func GetKubeConfig(c *cli.Context) error { func Shell(c *cli.Context) error { return subShell(c.String("name"), c.String("shell"), c.String("command")) } + +// ImportImage saves an image locally and imports it into the k3d containers +func ImportImage(c *cli.Context) error { + return importImage(c.String("name"), c.String("image")) +} diff --git a/cli/container.go b/cli/container.go index db3a1c818..5dc09763f 100644 --- a/cli/container.go +++ b/cli/container.go @@ -22,7 +22,7 @@ import ( type ClusterSpec struct { AgentArgs []string - ApiPort apiPort + APIPort apiPort AutoRestart bool ClusterName string Env []string @@ -94,14 +94,14 @@ func createServer(spec *ClusterSpec) (string, error) { return "", err } - hostIp := "0.0.0.0" + hostIP := "0.0.0.0" containerLabels["apihost"] = "localhost" - if spec.ApiPort.Host != "" { - hostIp = spec.ApiPort.HostIp - containerLabels["apihost"] = spec.ApiPort.Host + if spec.APIPort.Host != "" { + hostIP = spec.APIPort.HostIP + containerLabels["apihost"] = spec.APIPort.Host } - apiPortSpec := fmt.Sprintf("%s:%s:%s/tcp", hostIp, spec.ApiPort.Port, spec.ApiPort.Port) + apiPortSpec := fmt.Sprintf("%s:%s:%s/tcp", hostIP, spec.APIPort.Port, spec.APIPort.Port) serverPorts = append(serverPorts, apiPortSpec) @@ -123,6 +123,13 @@ func createServer(spec *ClusterSpec) (string, error) { hostConfig.Binds = spec.Volumes } + // we need to mount the clusterDir subdirectory `clusterDir/images` to enable importing images without the need for `docker cp` + clusterDir, err := getClusterDir(spec.ClusterName) + if err != nil { + return "", fmt.Errorf("ERROR: couldn't get cluster dir for mounting\n%+v", err) + } + hostConfig.Binds = append(hostConfig.Binds, fmt.Sprintf("%s:/images", clusterDir+"/images")) + networkingConfig := &network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{ k3dNetworkName(spec.ClusterName): { @@ -157,7 +164,7 @@ func createWorker(spec *ClusterSpec, postfix int) (string, error) { containerName := GetContainerName("worker", spec.ClusterName, postfix) - env := append(spec.Env, fmt.Sprintf("K3S_URL=https://k3d-%s-server:%s", spec.ClusterName, spec.ApiPort.Port)) + env := append(spec.Env, fmt.Sprintf("K3S_URL=https://k3d-%s-server:%s", spec.ClusterName, spec.APIPort.Port)) // ports to be assigned to the server belong to roles // all, server or @@ -192,6 +199,13 @@ func createWorker(spec *ClusterSpec, postfix int) (string, error) { hostConfig.Binds = spec.Volumes } + // we need to mount the clusterDir subdirectory `clusterDir/images` to enable importing images without the need for `docker cp` + clusterDir, err := getClusterDir(spec.ClusterName) + if err != nil { + return "", fmt.Errorf("ERROR: couldn't get cluster dir for mounting\n%+v", err) + } + hostConfig.Binds = append(hostConfig.Binds, fmt.Sprintf("%s:/images", clusterDir+"/images")) + networkingConfig := &network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{ k3dNetworkName(spec.ClusterName): { diff --git a/cli/image.go b/cli/image.go new file mode 100644 index 000000000..f80b27482 --- /dev/null +++ b/cli/image.go @@ -0,0 +1,131 @@ +package run + +import ( + "context" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "strings" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/client" +) + +const imageBasePathRemote = "/images/" + +func importImage(clusterName, image string) error { + // get a docker client + ctx := context.Background() + docker, err := client.NewEnvClient() + if err != nil { + return fmt.Errorf("ERROR: couldn't create docker client\n%+v", err) + } + + // get cluster directory to temporarily save the image tarball there + imageBasePathLocal, err := getClusterDir(clusterName) + imageBasePathLocal = imageBasePathLocal + "/images/" + if err != nil { + return fmt.Errorf("ERROR: couldn't get cluster directory for cluster [%s]\n%+v", clusterName, err) + } + + // TODO: extend to enable importing a list of images + imageList := []string{image} + + //*** first, save the images using the local docker daemon + log.Printf("INFO: Saving image [%s] from local docker daemon...", image) + imageReader, err := docker.ImageSave(ctx, imageList) + if err != nil { + return fmt.Errorf("ERROR: failed to save image [%s] locally\n%+v", image, err) + } + + // create tarball + imageTarName := strings.ReplaceAll(strings.ReplaceAll(image, ":", "_"), "/", "_") + ".tar" + imageTar, err := os.Create(imageBasePathLocal + imageTarName) + if err != nil { + return err + } + defer imageTar.Close() + + _, err = io.Copy(imageTar, imageReader) + if err != nil { + return fmt.Errorf("ERROR: couldn't save image [%s] to file [%s]\n%+v", image, imageTar.Name(), err) + } + + // TODO: get correct container ID by cluster name + clusters, err := getClusters(false, clusterName) + if err != nil { + return fmt.Errorf("ERROR: couldn't get cluster by name [%s]\n%+v", clusterName, err) + } + containerList := []types.Container{clusters[clusterName].server} + containerList = append(containerList, clusters[clusterName].workers...) + + // *** second, import the images using ctr in the k3d nodes + + // create exec configuration + cmd := []string{"ctr", "image", "import", imageBasePathRemote + imageTarName} + execConfig := types.ExecConfig{ + AttachStderr: true, + AttachStdout: true, + Cmd: cmd, + Tty: true, + Detach: true, + } + + execAttachConfig := types.ExecConfig{ + Tty: true, + } + + execStartConfig := types.ExecStartCheck{ + Tty: true, + } + + // import in each node separately + // TODO: create a shared image cache volume, so we don't need to import it separately + for _, container := range containerList { + + containerName := container.Names[0][1:] // trimming the leading "/" from name + log.Printf("INFO: Importing image [%s] in container [%s]", image, containerName) + + // create exec configuration + execResponse, err := docker.ContainerExecCreate(ctx, container.ID, execConfig) + if err != nil { + return fmt.Errorf("ERROR: Failed to create exec command for container [%s]\n%+v", containerName, err) + } + + // attach to exec process in container + containerConnection, err := docker.ContainerExecAttach(ctx, execResponse.ID, execAttachConfig) + if err != nil { + return fmt.Errorf("ERROR: couldn't attach to container [%s]\n%+v", containerName, err) + } + defer containerConnection.Close() + + // start exec + err = docker.ContainerExecStart(ctx, execResponse.ID, execStartConfig) + if err != nil { + return fmt.Errorf("ERROR: couldn't execute command in container [%s]\n%+v", containerName, err) + } + + // get output from container + content, err := ioutil.ReadAll(containerConnection.Reader) + if err != nil { + return fmt.Errorf("ERROR: couldn't read output from container [%s]\n%+v", containerName, err) + } + + // example output "unpacking image........ ...done" + if !strings.Contains(string(content), "done") { + return fmt.Errorf("ERROR: seems like something went wrong using `ctr image import` in container [%s]. Full output below:\n%s", containerName, string(content)) + } + } + + log.Printf("INFO: Successfully imported image [%s] in all nodes of cluster [%s]", image, clusterName) + + log.Println("INFO: Cleaning up tarball...") + if err := os.Remove(imageBasePathLocal + imageTarName); err != nil { + return fmt.Errorf("ERROR: Couldn't remove tarball [%s]\n%+v", imageBasePathLocal+imageTarName, err) + } + log.Println("INFO: ...Done") + + return nil +} diff --git a/cli/util.go b/cli/util.go index 51f1b18e7..6608056ba 100644 --- a/cli/util.go +++ b/cli/util.go @@ -11,7 +11,7 @@ import ( type apiPort struct { Host string - HostIp string + HostIP string Port string } @@ -90,7 +90,7 @@ func ValidateHostname(name string) error { return nil } -func parseApiPort(portSpec string) (*apiPort, error) { +func parseAPIPort(portSpec string) (*apiPort, error) { var port *apiPort split := strings.Split(portSpec, ":") if len(split) > 2 { @@ -105,7 +105,7 @@ func parseApiPort(portSpec string) (*apiPort, error) { if err != nil { return nil, err } - port = &apiPort{Host: split[0], HostIp: addrs[0], Port: split[1]} + port = &apiPort{Host: split[0], HostIP: addrs[0], Port: split[1]} } // Verify 'port' is an integer and within port ranges diff --git a/main.go b/main.go index 1f94ee606..30c79244a 100644 --- a/main.go +++ b/main.go @@ -214,6 +214,23 @@ func main() { }, Action: run.GetKubeConfig, }, + { + // get-kubeconfig grabs the kubeconfig from the cluster and prints the path to it + Name: "import-image", + Usage: "Import a container image from your local docker daemon into the cluster", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "name, n", + Value: defaultK3sClusterName, + Usage: "Name of the cluster", + }, + cli.StringFlag{ + Name: "image, i", + Usage: "Name of the image that you want to import, e.g. `nginx:local`", + }, + }, + Action: run.ImportImage, + }, } // Global flags From deccb0122ab0e6545e1292172fa1d59dabdb01a1 Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Mon, 1 Jul 2019 12:17:03 +0200 Subject: [PATCH 2/9] enable importing multiple images at once --- cli/commands.go | 8 +++++++- cli/image.go | 22 ++++++++++------------ main.go | 11 ++++------- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/cli/commands.go b/cli/commands.go index 4fe0c04f1..4dbe60e05 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -365,5 +365,11 @@ func Shell(c *cli.Context) error { // ImportImage saves an image locally and imports it into the k3d containers func ImportImage(c *cli.Context) error { - return importImage(c.String("name"), c.String("image")) + images := make([]string, 0) + if strings.Contains(c.Args().First(), ",") { + images = append(images, strings.Split(c.Args().First(), ",")...) + } else { + images = append(images, c.Args()...) + } + return importImage(c.String("name"), images) } diff --git a/cli/image.go b/cli/image.go index f80b27482..f34fb6574 100644 --- a/cli/image.go +++ b/cli/image.go @@ -8,6 +8,7 @@ import ( "log" "os" "strings" + "time" "github.com/docker/docker/api/types" "github.com/docker/docker/client" @@ -15,7 +16,7 @@ import ( const imageBasePathRemote = "/images/" -func importImage(clusterName, image string) error { +func importImage(clusterName string, images []string) error { // get a docker client ctx := context.Background() docker, err := client.NewEnvClient() @@ -30,18 +31,15 @@ func importImage(clusterName, image string) error { return fmt.Errorf("ERROR: couldn't get cluster directory for cluster [%s]\n%+v", clusterName, err) } - // TODO: extend to enable importing a list of images - imageList := []string{image} - //*** first, save the images using the local docker daemon - log.Printf("INFO: Saving image [%s] from local docker daemon...", image) - imageReader, err := docker.ImageSave(ctx, imageList) + log.Printf("INFO: Saving images [%s] from local docker daemon...", images) + imageReader, err := docker.ImageSave(ctx, images) if err != nil { - return fmt.Errorf("ERROR: failed to save image [%s] locally\n%+v", image, err) + return fmt.Errorf("ERROR: failed to save images [%s] locally\n%+v", images, err) } // create tarball - imageTarName := strings.ReplaceAll(strings.ReplaceAll(image, ":", "_"), "/", "_") + ".tar" + imageTarName := "k3d-" + clusterName + "-images-" + time.Now().Format("20060102150405") + ".tar" imageTar, err := os.Create(imageBasePathLocal + imageTarName) if err != nil { return err @@ -50,10 +48,10 @@ func importImage(clusterName, image string) error { _, err = io.Copy(imageTar, imageReader) if err != nil { - return fmt.Errorf("ERROR: couldn't save image [%s] to file [%s]\n%+v", image, imageTar.Name(), err) + return fmt.Errorf("ERROR: couldn't save image [%s] to file [%s]\n%+v", images, imageTar.Name(), err) } - // TODO: get correct container ID by cluster name + // Get the container IDs for all containers in the cluster clusters, err := getClusters(false, clusterName) if err != nil { return fmt.Errorf("ERROR: couldn't get cluster by name [%s]\n%+v", clusterName, err) @@ -86,7 +84,7 @@ func importImage(clusterName, image string) error { for _, container := range containerList { containerName := container.Names[0][1:] // trimming the leading "/" from name - log.Printf("INFO: Importing image [%s] in container [%s]", image, containerName) + log.Printf("INFO: Importing image [%s] in container [%s]", images, containerName) // create exec configuration execResponse, err := docker.ContainerExecCreate(ctx, container.ID, execConfig) @@ -119,7 +117,7 @@ func importImage(clusterName, image string) error { } } - log.Printf("INFO: Successfully imported image [%s] in all nodes of cluster [%s]", image, clusterName) + log.Printf("INFO: Successfully imported image [%s] in all nodes of cluster [%s]", images, clusterName) log.Println("INFO: Cleaning up tarball...") if err := os.Remove(imageBasePathLocal + imageTarName); err != nil { diff --git a/main.go b/main.go index 30c79244a..c01dedbac 100644 --- a/main.go +++ b/main.go @@ -216,18 +216,15 @@ func main() { }, { // get-kubeconfig grabs the kubeconfig from the cluster and prints the path to it - Name: "import-image", - Usage: "Import a container image from your local docker daemon into the cluster", + Name: "import-images", + Aliases: []string{"i"}, + Usage: "Import a comma- or space-separated list of container images from your local docker daemon into the cluster", Flags: []cli.Flag{ cli.StringFlag{ - Name: "name, n", + Name: "name, n, cluster, c", Value: defaultK3sClusterName, Usage: "Name of the cluster", }, - cli.StringFlag{ - Name: "image, i", - Usage: "Name of the image that you want to import, e.g. `nginx:local`", - }, }, Action: run.ImportImage, }, From cbdeea3bfa55c86a919199233d5c69268c280b25 Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Tue, 2 Jul 2019 14:48:28 +0200 Subject: [PATCH 3/9] create named volume --- cli/commands.go | 18 ++++++++++++++++-- cli/container.go | 14 -------------- cli/image.go | 6 +++--- vendor/modules.txt | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/cli/commands.go b/cli/commands.go index 4dbe60e05..8fb042cb1 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -135,6 +135,15 @@ func CreateCluster(c *cli.Context) error { log.Fatal(err) } + // create a docker volume for sharing image tarballs with the cluster + imageVolume, err := createImageVolume(c.String("name")) + log.Println("Created docker volume ", imageVolume.Name) + if err != nil { + return err + } + volumes := c.StringSlice("volume") + volumes = append(volumes, fmt.Sprintf("%s:/images", imageVolume.Name)) + clusterSpec := &ClusterSpec{ AgentArgs: []string{}, APIPort: *apiPort, @@ -146,7 +155,7 @@ func CreateCluster(c *cli.Context) error { PortAutoOffset: c.Int("port-auto-offset"), ServerArgs: k3sServerArgs, Verbose: c.GlobalBool("verbose"), - Volumes: c.StringSlice("volume"), + Volumes: volumes, } // create the server @@ -241,8 +250,8 @@ func DeleteCluster(c *cli.Context) error { } } } - log.Println("...Removing server") deleteClusterDir(cluster.name) + log.Println("...Removing server") if err := removeContainer(cluster.server.ID); err != nil { return fmt.Errorf("ERROR: Couldn't remove server for cluster %s\n%+v", cluster.name, err) } @@ -251,6 +260,11 @@ func DeleteCluster(c *cli.Context) error { log.Printf("WARNING: couldn't delete cluster network for cluster %s\n%+v", cluster.name, err) } + log.Println("...Removing docker image volume") + if err := deleteImageVolume(cluster.name); err != nil { + log.Printf("WARNING: couldn't delete image docker volume for cluster %s\n%+v", cluster.name, err) + } + log.Printf("SUCCESS: removed cluster [%s]", cluster.name) } diff --git a/cli/container.go b/cli/container.go index 5dc09763f..275b42db6 100644 --- a/cli/container.go +++ b/cli/container.go @@ -123,13 +123,6 @@ func createServer(spec *ClusterSpec) (string, error) { hostConfig.Binds = spec.Volumes } - // we need to mount the clusterDir subdirectory `clusterDir/images` to enable importing images without the need for `docker cp` - clusterDir, err := getClusterDir(spec.ClusterName) - if err != nil { - return "", fmt.Errorf("ERROR: couldn't get cluster dir for mounting\n%+v", err) - } - hostConfig.Binds = append(hostConfig.Binds, fmt.Sprintf("%s:/images", clusterDir+"/images")) - networkingConfig := &network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{ k3dNetworkName(spec.ClusterName): { @@ -199,13 +192,6 @@ func createWorker(spec *ClusterSpec, postfix int) (string, error) { hostConfig.Binds = spec.Volumes } - // we need to mount the clusterDir subdirectory `clusterDir/images` to enable importing images without the need for `docker cp` - clusterDir, err := getClusterDir(spec.ClusterName) - if err != nil { - return "", fmt.Errorf("ERROR: couldn't get cluster dir for mounting\n%+v", err) - } - hostConfig.Binds = append(hostConfig.Binds, fmt.Sprintf("%s:/images", clusterDir+"/images")) - networkingConfig := &network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{ k3dNetworkName(spec.ClusterName): { diff --git a/cli/image.go b/cli/image.go index f34fb6574..37630625d 100644 --- a/cli/image.go +++ b/cli/image.go @@ -25,11 +25,11 @@ func importImage(clusterName string, images []string) error { } // get cluster directory to temporarily save the image tarball there - imageBasePathLocal, err := getClusterDir(clusterName) - imageBasePathLocal = imageBasePathLocal + "/images/" + imageVolume, err := getImageVolume(clusterName) if err != nil { - return fmt.Errorf("ERROR: couldn't get cluster directory for cluster [%s]\n%+v", clusterName, err) + return fmt.Errorf("ERROR: couldn't get image volume for cluster [%s]\n%+v", clusterName, err) } + imageBasePathLocal := imageVolume.Mountpoint + "/" //*** first, save the images using the local docker daemon log.Printf("INFO: Saving images [%s] from local docker daemon...", images) diff --git a/vendor/modules.txt b/vendor/modules.txt index 9a44086bf..395bfcee3 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -8,6 +8,7 @@ github.com/docker/docker/api/types github.com/docker/docker/api/types/container github.com/docker/docker/api/types/filters github.com/docker/docker/api/types/network +github.com/docker/docker/api/types/volume github.com/docker/docker/client github.com/docker/docker/api/types/mount github.com/docker/docker/api/types/registry @@ -18,7 +19,6 @@ github.com/docker/docker/api/types/versions github.com/docker/docker/api/types/events github.com/docker/docker/api/types/reference github.com/docker/docker/api/types/time -github.com/docker/docker/api/types/volume github.com/docker/docker/pkg/tlsconfig # github.com/docker/go-connections v0.4.0 github.com/docker/go-connections/nat From 93fa5630ac1aee452f8cbd1844863a14ddc5d865 Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Tue, 2 Jul 2019 14:48:28 +0200 Subject: [PATCH 4/9] create named volume --- cli/commands.go | 18 ++++++++- cli/container.go | 14 ------- cli/image.go | 6 +-- cli/volume.go | 92 ++++++++++++++++++++++++++++++++++++++++++++++ vendor/modules.txt | 2 +- 5 files changed, 112 insertions(+), 20 deletions(-) create mode 100644 cli/volume.go diff --git a/cli/commands.go b/cli/commands.go index 4dbe60e05..8fb042cb1 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -135,6 +135,15 @@ func CreateCluster(c *cli.Context) error { log.Fatal(err) } + // create a docker volume for sharing image tarballs with the cluster + imageVolume, err := createImageVolume(c.String("name")) + log.Println("Created docker volume ", imageVolume.Name) + if err != nil { + return err + } + volumes := c.StringSlice("volume") + volumes = append(volumes, fmt.Sprintf("%s:/images", imageVolume.Name)) + clusterSpec := &ClusterSpec{ AgentArgs: []string{}, APIPort: *apiPort, @@ -146,7 +155,7 @@ func CreateCluster(c *cli.Context) error { PortAutoOffset: c.Int("port-auto-offset"), ServerArgs: k3sServerArgs, Verbose: c.GlobalBool("verbose"), - Volumes: c.StringSlice("volume"), + Volumes: volumes, } // create the server @@ -241,8 +250,8 @@ func DeleteCluster(c *cli.Context) error { } } } - log.Println("...Removing server") deleteClusterDir(cluster.name) + log.Println("...Removing server") if err := removeContainer(cluster.server.ID); err != nil { return fmt.Errorf("ERROR: Couldn't remove server for cluster %s\n%+v", cluster.name, err) } @@ -251,6 +260,11 @@ func DeleteCluster(c *cli.Context) error { log.Printf("WARNING: couldn't delete cluster network for cluster %s\n%+v", cluster.name, err) } + log.Println("...Removing docker image volume") + if err := deleteImageVolume(cluster.name); err != nil { + log.Printf("WARNING: couldn't delete image docker volume for cluster %s\n%+v", cluster.name, err) + } + log.Printf("SUCCESS: removed cluster [%s]", cluster.name) } diff --git a/cli/container.go b/cli/container.go index 5dc09763f..275b42db6 100644 --- a/cli/container.go +++ b/cli/container.go @@ -123,13 +123,6 @@ func createServer(spec *ClusterSpec) (string, error) { hostConfig.Binds = spec.Volumes } - // we need to mount the clusterDir subdirectory `clusterDir/images` to enable importing images without the need for `docker cp` - clusterDir, err := getClusterDir(spec.ClusterName) - if err != nil { - return "", fmt.Errorf("ERROR: couldn't get cluster dir for mounting\n%+v", err) - } - hostConfig.Binds = append(hostConfig.Binds, fmt.Sprintf("%s:/images", clusterDir+"/images")) - networkingConfig := &network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{ k3dNetworkName(spec.ClusterName): { @@ -199,13 +192,6 @@ func createWorker(spec *ClusterSpec, postfix int) (string, error) { hostConfig.Binds = spec.Volumes } - // we need to mount the clusterDir subdirectory `clusterDir/images` to enable importing images without the need for `docker cp` - clusterDir, err := getClusterDir(spec.ClusterName) - if err != nil { - return "", fmt.Errorf("ERROR: couldn't get cluster dir for mounting\n%+v", err) - } - hostConfig.Binds = append(hostConfig.Binds, fmt.Sprintf("%s:/images", clusterDir+"/images")) - networkingConfig := &network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{ k3dNetworkName(spec.ClusterName): { diff --git a/cli/image.go b/cli/image.go index f34fb6574..37630625d 100644 --- a/cli/image.go +++ b/cli/image.go @@ -25,11 +25,11 @@ func importImage(clusterName string, images []string) error { } // get cluster directory to temporarily save the image tarball there - imageBasePathLocal, err := getClusterDir(clusterName) - imageBasePathLocal = imageBasePathLocal + "/images/" + imageVolume, err := getImageVolume(clusterName) if err != nil { - return fmt.Errorf("ERROR: couldn't get cluster directory for cluster [%s]\n%+v", clusterName, err) + return fmt.Errorf("ERROR: couldn't get image volume for cluster [%s]\n%+v", clusterName, err) } + imageBasePathLocal := imageVolume.Mountpoint + "/" //*** first, save the images using the local docker daemon log.Printf("INFO: Saving images [%s] from local docker daemon...", images) diff --git a/cli/volume.go b/cli/volume.go new file mode 100644 index 000000000..aa93a66e6 --- /dev/null +++ b/cli/volume.go @@ -0,0 +1,92 @@ +package run + +import ( + "context" + "fmt" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/volume" + "github.com/docker/docker/client" +) + +// createImageVolume will create a new docker volume used for storing image tarballs that can be loaded into the clusters +func createImageVolume(clusterName string) (types.Volume, error) { + + var vol types.Volume + + ctx := context.Background() + docker, err := client.NewEnvClient() + if err != nil { + return vol, fmt.Errorf("ERROR: couldn't create docker client\n%+v", err) + } + + volName := fmt.Sprintf("k3d-%s-images", clusterName) + + volumeCreateOptions := volume.VolumesCreateBody{ + Name: volName, + Labels: map[string]string{ + "app": "k3d", + "cluster": clusterName, + }, + Driver: "local", //TODO: allow setting driver + opts + DriverOpts: map[string]string{}, + } + vol, err = docker.VolumeCreate(ctx, volumeCreateOptions) + if err != nil { + return vol, fmt.Errorf("ERROR: failed to create image volume [%s] for cluster [%s]\n%+v", volName, clusterName, err) + } + + return vol, nil +} + +// deleteImageVolume will delete the volume we created for sharing images with this cluster +func deleteImageVolume(clusterName string) error { + + ctx := context.Background() + docker, err := client.NewEnvClient() + if err != nil { + return fmt.Errorf("ERROR: couldn't create docker client\n%+v", err) + } + + volName := fmt.Sprintf("k3d-%s-images", clusterName) + + if err = docker.VolumeRemove(ctx, volName, true); err != nil { + return fmt.Errorf("ERROR: couldn't remove volume [%s] for cluster [%s]\n%+v", volName, clusterName, err) + } + + return nil +} + +// getImageVolume returns the docker volume object representing the imagevolume for the cluster +func getImageVolume(clusterName string) (types.Volume, error) { + var vol types.Volume + volName := fmt.Sprintf("k3d-%s-images", clusterName) + + ctx := context.Background() + docker, err := client.NewEnvClient() + if err != nil { + return vol, fmt.Errorf("ERROR: couldn't create docker client\n%+v", err) + } + + filters := filters.NewArgs() + filters.Add("label", "app=k3d") + filters.Add("label", fmt.Sprintf("cluster=%s", clusterName)) + volumeList, err := docker.VolumeList(ctx, filters) + if err != nil { + return vol, fmt.Errorf("ERROR: couldn't get volumes for cluster [%s]\n%+v ", clusterName, err) + } + volFound := false + for _, volume := range volumeList.Volumes { + if volume.Name == volName { + vol = *volume + volFound = true + break + } + } + if !volFound { + return vol, fmt.Errorf("ERROR: didn't find volume [%s] in list of volumes returned for cluster [%s]", volName, clusterName) + } + + return vol, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 9a44086bf..395bfcee3 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -8,6 +8,7 @@ github.com/docker/docker/api/types github.com/docker/docker/api/types/container github.com/docker/docker/api/types/filters github.com/docker/docker/api/types/network +github.com/docker/docker/api/types/volume github.com/docker/docker/client github.com/docker/docker/api/types/mount github.com/docker/docker/api/types/registry @@ -18,7 +19,6 @@ github.com/docker/docker/api/types/versions github.com/docker/docker/api/types/events github.com/docker/docker/api/types/reference github.com/docker/docker/api/types/time -github.com/docker/docker/api/types/volume github.com/docker/docker/pkg/tlsconfig # github.com/docker/go-connections v0.4.0 github.com/docker/go-connections/nat From f670dde640db1eb4f4ae8a0c415b0b9edf3308ba Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Mon, 8 Jul 2019 09:58:26 +0200 Subject: [PATCH 5/9] test --- cli/cluster.go | 3 +-- cli/image.go | 58 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/cli/cluster.go b/cli/cluster.go index b5797e2a1..825e28155 100644 --- a/cli/cluster.go +++ b/cli/cluster.go @@ -14,8 +14,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/client" - - "github.com/mitchellh/go-homedir" + homedir "github.com/mitchellh/go-homedir" "github.com/olekukonko/tablewriter" ) diff --git a/cli/image.go b/cli/image.go index 37630625d..7829a5c3b 100644 --- a/cli/image.go +++ b/cli/image.go @@ -6,15 +6,15 @@ import ( "io" "io/ioutil" "log" - "os" "strings" - "time" "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" "github.com/docker/docker/client" ) -const imageBasePathRemote = "/images/" +const imageBasePathRemote = "/images" func importImage(clusterName string, images []string) error { // get a docker client @@ -29,7 +29,6 @@ func importImage(clusterName string, images []string) error { if err != nil { return fmt.Errorf("ERROR: couldn't get image volume for cluster [%s]\n%+v", clusterName, err) } - imageBasePathLocal := imageVolume.Mountpoint + "/" //*** first, save the images using the local docker daemon log.Printf("INFO: Saving images [%s] from local docker daemon...", images) @@ -38,17 +37,44 @@ func importImage(clusterName string, images []string) error { return fmt.Errorf("ERROR: failed to save images [%s] locally\n%+v", images, err) } - // create tarball - imageTarName := "k3d-" + clusterName + "-images-" + time.Now().Format("20060102150405") + ".tar" - imageTar, err := os.Create(imageBasePathLocal + imageTarName) + // TODO: create tar from stream + tmpFile, err := ioutil.TempFile("", "*.tar") if err != nil { - return err + return fmt.Errorf("ERROR: couldn't create temp file to cache tarball\n%+v", err) + } + defer tmpFile.Close() + if _, err = io.Copy(tmpFile, imageReader); err != nil { + return fmt.Errorf("ERROR: couldn't write image stream to tar [%s]\n%+v", tmpFile.Name(), err) } - defer imageTar.Close() - _, err = io.Copy(imageTar, imageReader) + // create a dummy container to get the tarball into the named volume + containerConfig := container.Config{ + Hostname: "k3d-dummy", // TODO: change details here + Image: "rancher/k3s:v0.7.0-rc2", + Labels: map[string]string{ + "app": "k3d", + "cluster": "test", + }, + } + hostConfig := container.HostConfig{ + Binds: []string{ + fmt.Sprintf("%s:%s:rw", imageVolume.Name, imageBasePathRemote), + }, + } + dummyContainer, err := docker.ContainerCreate(ctx, &containerConfig, &hostConfig, &network.NetworkingConfig{}, "k3d-dummy") if err != nil { - return fmt.Errorf("ERROR: couldn't save image [%s] to file [%s]\n%+v", images, imageTar.Name(), err) + return fmt.Errorf("ERROR: couldn't create dummy container\n%+v", err) + } + + fmt.Println(ioutil.ReadAll(imageReader)) + if err = docker.CopyToContainer(ctx, dummyContainer.ID, "/images", imageReader, types.CopyToContainerOptions{}); err != nil { + return fmt.Errorf("ERROR: couldn't copy tarball to dummy container\n%+v", err) + } + + if err = docker.ContainerRemove(ctx, "k3d-dummy", types.ContainerRemoveOptions{ + Force: true, + }); err != nil { + return fmt.Errorf("ERROR: couldn't remove dummy container\n%+v", err) } // Get the container IDs for all containers in the cluster @@ -62,7 +88,8 @@ func importImage(clusterName string, images []string) error { // *** second, import the images using ctr in the k3d nodes // create exec configuration - cmd := []string{"ctr", "image", "import", imageBasePathRemote + imageTarName} + command := fmt.Sprintf("ctr image import %s", imageBasePathRemote+"/test.tar") + cmd := []string{"sh", "-c", command} execConfig := types.ExecConfig{ AttachStderr: true, AttachStdout: true, @@ -120,9 +147,12 @@ func importImage(clusterName string, images []string) error { log.Printf("INFO: Successfully imported image [%s] in all nodes of cluster [%s]", images, clusterName) log.Println("INFO: Cleaning up tarball...") - if err := os.Remove(imageBasePathLocal + imageTarName); err != nil { - return fmt.Errorf("ERROR: Couldn't remove tarball [%s]\n%+v", imageBasePathLocal+imageTarName, err) + /*if err := tmpFile.Close(); err != nil { + return fmt.Errorf("ERROR: Couldn't close tarfile [%s]\n%+v", tmpFile.Name(), err) } + if err = os.Remove(tmpFile.Name()); err != nil { + return fmt.Errorf("ERROR: Couldn't remove tarball [%s]\n%+v", tmpFile.Name(), err) + }*/ log.Println("INFO: ...Done") return nil From 07e458d6f10b1ba4ebb435aec37e1235e235863a Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Mon, 8 Jul 2019 09:59:01 +0200 Subject: [PATCH 6/9] test --- cli/image.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cli/image.go b/cli/image.go index c4760e182..7829a5c3b 100644 --- a/cli/image.go +++ b/cli/image.go @@ -29,7 +29,6 @@ func importImage(clusterName string, images []string) error { if err != nil { return fmt.Errorf("ERROR: couldn't get image volume for cluster [%s]\n%+v", clusterName, err) } - imageBasePathLocal := imageVolume.Mountpoint + "/" //*** first, save the images using the local docker daemon log.Printf("INFO: Saving images [%s] from local docker daemon...", images) From a4c75c6568242135b614d28edc8b99f61fc7c242 Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Wed, 10 Jul 2019 18:23:47 +0200 Subject: [PATCH 7/9] use a tools container --- cli/image.go | 98 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 58 insertions(+), 40 deletions(-) diff --git a/cli/image.go b/cli/image.go index 7829a5c3b..325129141 100644 --- a/cli/image.go +++ b/cli/image.go @@ -3,10 +3,10 @@ package run import ( "context" "fmt" - "io" "io/ioutil" "log" "strings" + "time" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" @@ -14,7 +14,10 @@ import ( "github.com/docker/docker/client" ) -const imageBasePathRemote = "/images" +const ( + imageBasePathRemote = "/images" + k3dToolsImage = "iwilltry42/k3d-tools:v0.0.1" +) func importImage(clusterName string, images []string) error { // get a docker client @@ -31,50 +34,69 @@ func importImage(clusterName string, images []string) error { } //*** first, save the images using the local docker daemon - log.Printf("INFO: Saving images [%s] from local docker daemon...", images) - imageReader, err := docker.ImageSave(ctx, images) - if err != nil { - return fmt.Errorf("ERROR: failed to save images [%s] locally\n%+v", images, err) - } - - // TODO: create tar from stream - tmpFile, err := ioutil.TempFile("", "*.tar") - if err != nil { - return fmt.Errorf("ERROR: couldn't create temp file to cache tarball\n%+v", err) - } - defer tmpFile.Close() - if _, err = io.Copy(tmpFile, imageReader); err != nil { - return fmt.Errorf("ERROR: couldn't write image stream to tar [%s]\n%+v", tmpFile.Name(), err) - } + log.Printf("INFO: Saving images %s from local docker daemon...", images) + toolsContainerName := fmt.Sprintf("k3d-%s-tools", clusterName) + tarFileName := fmt.Sprintf("%s/k3d-%s-images-%s.tar", imageBasePathRemote, clusterName, time.Now().Format("20060102150405")) - // create a dummy container to get the tarball into the named volume + // create a tools container to get the tarball into the named volume containerConfig := container.Config{ - Hostname: "k3d-dummy", // TODO: change details here - Image: "rancher/k3s:v0.7.0-rc2", + Hostname: toolsContainerName, + Image: k3dToolsImage, Labels: map[string]string{ - "app": "k3d", - "cluster": "test", + "app": "k3d", + "cluster": clusterName, + "component": "tools", }, + Cmd: append([]string{"save-image", "-d", tarFileName}, images...), + AttachStdout: true, + AttachStderr: true, } hostConfig := container.HostConfig{ Binds: []string{ + "/var/run/docker.sock:/var/run/docker.sock", fmt.Sprintf("%s:%s:rw", imageVolume.Name, imageBasePathRemote), }, } - dummyContainer, err := docker.ContainerCreate(ctx, &containerConfig, &hostConfig, &network.NetworkingConfig{}, "k3d-dummy") + + toolsContainerID, err := startContainer(false, &containerConfig, &hostConfig, &network.NetworkingConfig{}, toolsContainerName) if err != nil { - return fmt.Errorf("ERROR: couldn't create dummy container\n%+v", err) + return err } - fmt.Println(ioutil.ReadAll(imageReader)) - if err = docker.CopyToContainer(ctx, dummyContainer.ID, "/images", imageReader, types.CopyToContainerOptions{}); err != nil { - return fmt.Errorf("ERROR: couldn't copy tarball to dummy container\n%+v", err) + // loop to wait for tools container to exit (failed or successfully saved images) + for { + cont, err := docker.ContainerInspect(ctx, toolsContainerID) + if err != nil { + return fmt.Errorf("ERROR: couldn't get helper container's exit code\n%+v", err) + } + if !cont.State.Running { // container finished... + if cont.State.ExitCode == 0 { // ...successfully + log.Println("INFO: saved images to shared docker volume") + break + } else if cont.State.ExitCode != 0 { // ...failed + errTxt := "ERROR: helper container failed to save images" + logReader, err := docker.ContainerLogs(ctx, toolsContainerID, types.ContainerLogsOptions{ + ShowStdout: true, + ShowStderr: true, + }) + if err != nil { + return fmt.Errorf("%s\n> couldn't get logs from helper container\n%+v", errTxt, err) + } + logs, err := ioutil.ReadAll(logReader) // let's show somw logs indicating what happened + if err != nil { + return fmt.Errorf("%s\n> couldn't get logs from helper container\n%+v", errTxt, err) + } + return fmt.Errorf("%s -> Logs from [%s]:\n>>>>>>\n%s\n<<<<<<", errTxt, toolsContainerName, string(logs)) + } + } + time.Sleep(time.Second / 2) // wait for half a second so we don't spam the docker API too much } - if err = docker.ContainerRemove(ctx, "k3d-dummy", types.ContainerRemoveOptions{ + // clean up tools container + if err = docker.ContainerRemove(ctx, toolsContainerID, types.ContainerRemoveOptions{ Force: true, }); err != nil { - return fmt.Errorf("ERROR: couldn't remove dummy container\n%+v", err) + return fmt.Errorf("ERROR: couldn't remove helper container\n%+v", err) } // Get the container IDs for all containers in the cluster @@ -88,8 +110,7 @@ func importImage(clusterName string, images []string) error { // *** second, import the images using ctr in the k3d nodes // create exec configuration - command := fmt.Sprintf("ctr image import %s", imageBasePathRemote+"/test.tar") - cmd := []string{"sh", "-c", command} + cmd := []string{"ctr", "image", "import", tarFileName} execConfig := types.ExecConfig{ AttachStderr: true, AttachStdout: true, @@ -111,7 +132,7 @@ func importImage(clusterName string, images []string) error { for _, container := range containerList { containerName := container.Names[0][1:] // trimming the leading "/" from name - log.Printf("INFO: Importing image [%s] in container [%s]", images, containerName) + log.Printf("INFO: Importing images %s in container [%s]", images, containerName) // create exec configuration execResponse, err := docker.ContainerExecCreate(ctx, container.ID, execConfig) @@ -144,15 +165,12 @@ func importImage(clusterName string, images []string) error { } } - log.Printf("INFO: Successfully imported image [%s] in all nodes of cluster [%s]", images, clusterName) + log.Printf("INFO: Successfully imported images %s in all nodes of cluster [%s]", images, clusterName) + + // log.Println("INFO: Cleaning up tarball...") + + // TODO: clean up tarball (if --rm flag was passed) and then remove the tools container - log.Println("INFO: Cleaning up tarball...") - /*if err := tmpFile.Close(); err != nil { - return fmt.Errorf("ERROR: Couldn't close tarfile [%s]\n%+v", tmpFile.Name(), err) - } - if err = os.Remove(tmpFile.Name()); err != nil { - return fmt.Errorf("ERROR: Couldn't remove tarball [%s]\n%+v", tmpFile.Name(), err) - }*/ log.Println("INFO: ...Done") return nil From 081ec611bc29a05e74173a514578792d06574d57 Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Thu, 11 Jul 2019 15:43:49 +0200 Subject: [PATCH 8/9] --keep flag to not remove the tarball after import --- cli/commands.go | 2 +- cli/image.go | 43 +++++++++++++++++++++++++++++++++++++------ main.go | 4 ++++ 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/cli/commands.go b/cli/commands.go index 8fb042cb1..22c5417e2 100644 --- a/cli/commands.go +++ b/cli/commands.go @@ -385,5 +385,5 @@ func ImportImage(c *cli.Context) error { } else { images = append(images, c.Args()...) } - return importImage(c.String("name"), images) + return importImage(c.String("name"), images, c.Bool("no-remove")) } diff --git a/cli/image.go b/cli/image.go index 325129141..6ae32bb97 100644 --- a/cli/image.go +++ b/cli/image.go @@ -16,10 +16,10 @@ import ( const ( imageBasePathRemote = "/images" - k3dToolsImage = "iwilltry42/k3d-tools:v0.0.1" + k3dToolsImage = "docker.io/iwilltry42/k3d-tools:v0.0.1" ) -func importImage(clusterName string, images []string) error { +func importImage(clusterName string, images []string, noRemove bool) error { // get a docker client ctx := context.Background() docker, err := client.NewEnvClient() @@ -96,7 +96,7 @@ func importImage(clusterName string, images []string) error { if err = docker.ContainerRemove(ctx, toolsContainerID, types.ContainerRemoveOptions{ Force: true, }); err != nil { - return fmt.Errorf("ERROR: couldn't remove helper container\n%+v", err) + return fmt.Errorf("ERROR: couldn't remove tools container\n%+v", err) } // Get the container IDs for all containers in the cluster @@ -128,7 +128,7 @@ func importImage(clusterName string, images []string) error { } // import in each node separately - // TODO: create a shared image cache volume, so we don't need to import it separately + // TODO: import concurrently using goroutines or find a way to share the image cache for _, container := range containerList { containerName := container.Names[0][1:] // trimming the leading "/" from name @@ -167,9 +167,40 @@ func importImage(clusterName string, images []string) error { log.Printf("INFO: Successfully imported images %s in all nodes of cluster [%s]", images, clusterName) - // log.Println("INFO: Cleaning up tarball...") + // remove tarball from inside the server container + if !noRemove { + log.Println("INFO: Cleaning up tarball") - // TODO: clean up tarball (if --rm flag was passed) and then remove the tools container + execID, err := docker.ContainerExecCreate(ctx, clusters[clusterName].server.ID, types.ExecConfig{ + Cmd: []string{"rm", "-f", tarFileName}, + }) + if err != nil { + log.Printf("WARN: failed to delete tarball: couldn't create remove in container [%s]\n%+v", clusters[clusterName].server.ID, err) + } + err = docker.ContainerExecStart(ctx, execID.ID, types.ExecStartCheck{ + Detach: true, + }) + if err != nil { + log.Printf("WARN: couldn't start tarball deletion action\n%+v", err) + } + + for { + execInspect, err := docker.ContainerExecInspect(ctx, execID.ID) + if err != nil { + log.Printf("WARN: couldn't verify deletion of tarball\n%+v", err) + } + + if !execInspect.Running { + if execInspect.ExitCode == 0 { + log.Println("INFO: deleted tarball") + break + } else { + log.Println("WARN: failed to delete tarball") + break + } + } + } + } log.Println("INFO: ...Done") diff --git a/main.go b/main.go index c01dedbac..5b9b24ff9 100644 --- a/main.go +++ b/main.go @@ -225,6 +225,10 @@ func main() { Value: defaultK3sClusterName, Usage: "Name of the cluster", }, + cli.BoolFlag{ + Name: "no-remove, no-rm, keep, k", + Usage: "Disable automatic removal of the tarball", + }, }, Action: run.ImportImage, }, From 96c883a10aed6652dd75723ce3c4b17a820709ad Mon Sep 17 00:00:00 2001 From: iwilltry42 Date: Wed, 17 Jul 2019 09:32:46 +0200 Subject: [PATCH 9/9] cleanup tools container using defer --- cli/container.go | 2 +- cli/image.go | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/cli/container.go b/cli/container.go index 275b42db6..2eaa89000 100644 --- a/cli/container.go +++ b/cli/container.go @@ -230,7 +230,7 @@ func removeContainer(ID string) error { } if err := docker.ContainerRemove(ctx, ID, options); err != nil { - return fmt.Errorf("FAILURE: couldn't delete container [%s] -> %+v", ID, err) + return fmt.Errorf("ERROR: couldn't delete container [%s] -> %+v", ID, err) } return nil } diff --git a/cli/image.go b/cli/image.go index 6ae32bb97..5ab514466 100644 --- a/cli/image.go +++ b/cli/image.go @@ -63,6 +63,14 @@ func importImage(clusterName string, images []string, noRemove bool) error { return err } + defer func() { + if err = docker.ContainerRemove(ctx, toolsContainerID, types.ContainerRemoveOptions{ + Force: true, + }); err != nil { + log.Println(fmt.Errorf("WARN: couldn't remove tools container\n%+v", err)) + } + }() + // loop to wait for tools container to exit (failed or successfully saved images) for { cont, err := docker.ContainerInspect(ctx, toolsContainerID) @@ -92,13 +100,6 @@ func importImage(clusterName string, images []string, noRemove bool) error { time.Sleep(time.Second / 2) // wait for half a second so we don't spam the docker API too much } - // clean up tools container - if err = docker.ContainerRemove(ctx, toolsContainerID, types.ContainerRemoveOptions{ - Force: true, - }); err != nil { - return fmt.Errorf("ERROR: couldn't remove tools container\n%+v", err) - } - // Get the container IDs for all containers in the cluster clusters, err := getClusters(false, clusterName) if err != nil {