Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<String> 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<String> 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(
Expand Down Expand Up @@ -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());
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
}
}