diff --git a/modules/k3s/src/main/java/org/testcontainers/k3s/K3sContainer.java b/modules/k3s/src/main/java/org/testcontainers/k3s/K3sContainer.java index 6dd6f06d85e..1d99b8dc178 100644 --- a/modules/k3s/src/main/java/org/testcontainers/k3s/K3sContainer.java +++ b/modules/k3s/src/main/java/org/testcontainers/k3s/K3sContainer.java @@ -5,17 +5,26 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.github.dockerjava.api.command.CopyArchiveToContainerCmd; import com.github.dockerjava.api.command.InspectContainerResponse; +import com.github.dockerjava.api.command.SaveImageCmd; import lombok.SneakyThrows; import org.apache.commons.io.IOUtils; import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.Container; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.utility.DockerImageName; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.util.HashMap; import java.util.Map; +import java.util.Set; /** * Testcontainers implementation for K3S @@ -50,6 +59,50 @@ public K3sContainer(DockerImageName dockerImageName) { setWaitStrategy(Wait.forLogMessage(".*Node controller sync successful.*", 1)); } + /** + * import images into the running k3s instance. Images are assumed to be present + * on the host already. + * + * @param images images to be imported into the k3s. + * @throws IOException in case temporary directory/files needed for the import can not be created + * @throws InterruptedException thrown when the thread is interrupted + */ + public void loadImages(Set images) throws IOException, InterruptedException { + if (images != null && !images.isEmpty()) { + // create temporary directory on the host where all tar(s) will reside + File tarsTempFolder = Files.createTempDirectory("").toFile(); + File tarFile = File.createTempFile("images", ".tar", tarsTempFolder); + + for (String image : images) { + try (SaveImageCmd saveImageCmd = getDockerClient().saveImageCmd(image)) { + InputStream imageStream = saveImageCmd.exec(); + // tar file for a single image + Files.copy(imageStream, tarFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + importTar(tarFile, true); + } + } + + // at this point, directory is empty + Files.deleteIfExists(tarsTempFolder.toPath()); + } + } + + /** + * Import this tar file into the running k3s. + * + * @param paths absolute paths of the tar files on the host. + * @throws IOException in case temporary directory/files needed for the import can not be created + * @throws InterruptedException thrown when the thread is interrupted + */ + public void loadImageFromTar(Set paths) throws IOException, InterruptedException { + if (paths != null && !paths.isEmpty()) { + for (String path : paths) { + File tarFile = new File(path); + importTar(tarFile, false); + } + } + } + @Override protected void containerIsStarted(InspectContainerResponse containerInfo) { String rawKubeConfig = copyFileFromContainer( @@ -103,4 +156,20 @@ private String kubeConfigWithServerUrl(String kubeConfigYaml, String serverUrl) return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(kubeConfigObjectNode); } + + private void importTar(File tarFile, boolean deleteAfterCopy) throws IOException, InterruptedException { + try (CopyArchiveToContainerCmd copyCmd = getDockerClient().copyArchiveToContainerCmd(getContainerId())) { + String remotePathName = "/tmp/" + tarFile.getName(); + copyCmd.withHostResource(tarFile.getAbsolutePath()).withRemotePath("/tmp").exec(); + Container.ExecResult result = execInContainer("ctr", "i", "import", remotePathName); + boolean noErrors = result.getStderr() == null || result.getStderr().isEmpty(); + if (!noErrors) { + throw new RuntimeException(result.getStderr()); + } + } finally { + if (deleteAfterCopy) { + Files.deleteIfExists(tarFile.toPath()); + } + } + } } diff --git a/modules/k3s/src/test/java/org/testcontainers/k3s/K3sLoadImagesTest.java b/modules/k3s/src/test/java/org/testcontainers/k3s/K3sLoadImagesTest.java new file mode 100644 index 00000000000..ea68bd739c0 --- /dev/null +++ b/modules/k3s/src/test/java/org/testcontainers/k3s/K3sLoadImagesTest.java @@ -0,0 +1,56 @@ +package org.testcontainers.k3s; + +import com.github.dockerjava.api.command.PullImageCmd; +import com.github.dockerjava.api.command.SaveImageCmd; +import org.junit.ClassRule; +import org.junit.Test; +import org.testcontainers.containers.Container; +import org.testcontainers.utility.DockerImageName; + +import java.io.File; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; + +public class K3sLoadImagesTest { + + @ClassRule + public static K3sContainer k3s = new K3sContainer(DockerImageName.parse("rancher/k3s:v1.21.3-k3s1")); + + @Test + public void testLoadImages() throws Exception { + try (PullImageCmd pullImageCmd = k3s.getDockerClient().pullImageCmd("busybox:1.35")) { + pullImageCmd.start().awaitCompletion(); + + k3s.loadImages(Collections.singleton("busybox:1.35")); + Container.ExecResult result = k3s.execInContainer("crictl", "images"); + assertThat(result.getStdout()).contains("busybox"); + } + } + + @Test + public void testLoadImageFromTar() throws Exception { + try (PullImageCmd pullImageCmd = k3s.getDockerClient().pullImageCmd("busybox:1.35")) { + pullImageCmd.start().awaitCompletion(); + + File tarsTempFolder = Files.createTempDirectory("").toFile(); + File tarFile = File.createTempFile("images", ".tar", tarsTempFolder); + + try (SaveImageCmd saveImageCmd = k3s.getDockerClient().saveImageCmd("busybox:1.35")) { + InputStream imageStream = saveImageCmd.exec(); + Files.copy(imageStream, tarFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + + k3s.loadImageFromTar(Collections.singleton(tarFile.getAbsolutePath())); + + Container.ExecResult result = k3s.execInContainer("crictl", "images"); + assertThat(result.getStdout()).contains("busybox"); + } finally { + Files.delete(tarFile.toPath()); + Files.delete(tarsTempFolder.toPath()); + } + } + } +}