210 lines
6.7 KiB
Go
210 lines
6.7 KiB
Go
package run
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"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"
|
|
k3dToolsImage = "docker.io/iwilltry42/k3d-tools:v0.0.1"
|
|
)
|
|
|
|
func importImage(clusterName string, images []string, noRemove bool) 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
|
|
imageVolume, err := getImageVolume(clusterName)
|
|
if err != nil {
|
|
return fmt.Errorf("ERROR: couldn't get image volume for cluster [%s]\n%+v", clusterName, err)
|
|
}
|
|
|
|
//*** first, save the images using the local docker daemon
|
|
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 tools container to get the tarball into the named volume
|
|
containerConfig := container.Config{
|
|
Hostname: toolsContainerName,
|
|
Image: k3dToolsImage,
|
|
Labels: map[string]string{
|
|
"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),
|
|
},
|
|
}
|
|
|
|
toolsContainerID, err := startContainer(false, &containerConfig, &hostConfig, &network.NetworkingConfig{}, toolsContainerName)
|
|
if err != nil {
|
|
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)
|
|
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
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
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", tarFileName}
|
|
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: 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
|
|
log.Printf("INFO: Importing images %s in container [%s]", images, 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 images %s in all nodes of cluster [%s]", images, clusterName)
|
|
|
|
// remove tarball from inside the server container
|
|
if !noRemove {
|
|
log.Println("INFO: Cleaning up tarball")
|
|
|
|
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")
|
|
|
|
return nil
|
|
}
|