From 5927fe90b2644835468a6f48e7e657ad53303531 Mon Sep 17 00:00:00 2001 From: Stephan Schnabel Date: Fri, 25 Oct 2024 14:36:22 +0200 Subject: [PATCH] Add timeouts for docker save and copy. --- docs/goal/apply.md | 1 + docs/goal/image.md | 7 ++- docs/goal/restart.md | 1 + docs/goal/rm.md | 1 + docs/goal/run.md | 1 + .../io/kokuwa/maven/k3s/mojo/ApplyMojo.java | 2 +- .../io/kokuwa/maven/k3s/mojo/ImageMojo.java | 30 +++++++++- .../io/kokuwa/maven/k3s/mojo/K3sMojo.java | 18 +++++- .../io/kokuwa/maven/k3s/mojo/RestartMojo.java | 4 +- .../java/io/kokuwa/maven/k3s/util/Docker.java | 59 +++++++++---------- .../java/io/kokuwa/maven/k3s/util/Task.java | 22 +++---- .../io/kokuwa/maven/k3s/mojo/RunMojoTest.java | 7 ++- .../kokuwa/maven/k3s/test/MojoExtension.java | 3 +- .../io/kokuwa/maven/k3s/util/DockerTest.java | 4 +- 14 files changed, 99 insertions(+), 61 deletions(-) diff --git a/docs/goal/apply.md b/docs/goal/apply.md index 4f29f913..384f0fb1 100644 --- a/docs/goal/apply.md +++ b/docs/goal/apply.md @@ -9,3 +9,4 @@ Runs kubectl to apply manifests. | `timeout` | `k3s.timeout` | Timeout in seconds to wait for resources getting ready. | 300 | | `skipApply` | `k3s.skipApply` | Skip applying kubectl manifests. | false | | `debug` | `k3s.debug` | Stream logs of docker and kubectl. | false | +| `taskTimeout` | `k3s.taskTimeout` | Default timeout for docker tasks in seconds. | 30 | diff --git a/docs/goal/image.md b/docs/goal/image.md index 764962f6..e3b54817 100644 --- a/docs/goal/image.md +++ b/docs/goal/image.md @@ -3,11 +3,14 @@ Import images into k3s containerd. | Name | User Property | Description | Default | -| -----| ------------- | ----------- | ------- | +| ---- | ---------------------- | ----------- | ------- | | `ctrImages` | `k3s.ctrImages` | Download given images via `ctr image pull` inside k3s container. | [] | | `tarFiles` | `k3s.tarFiles` | Import given tar files via `ctr image import` inside k3s container. | [] | | `dockerImages` | `k3s.dockerImages` | Copy given images from docker deamon via `ctr image import` inside k3s container. | [] | | `dockerPullAlways` | `k3s.dockerPullAlways` | Always pull docker images or only if not present. | false | -| `pullTimeout` | `k3s.pullTimeout` | Timout for `ctr image pull` or `docker pull` in seconds. | 1200 | +| `pullTimeout` | `k3s.pullTimeout` | Timout for `ctr image pull` or `docker pull` in seconds. | 1200 | +| `copyTimeout` | `k3s.copyToContainerTimeout` | Timout for `docker cp` in seconds. | 120 | +| `saveTimeout` | `k3s.saveImageTimeout` | Timout for `docker save` in seconds. | 120 | | `skipImage` | `k3s.skipImage` | Skip image handling. | false | | `debug` | `k3s.debug` | Stream logs of docker and kubectl. | false | +| `taskTimeout` | `k3s.taskTimeout` | Default timeout for docker tasks in seconds. | 30 | diff --git a/docs/goal/restart.md b/docs/goal/restart.md index 6256a15b..cde8b75d 100644 --- a/docs/goal/restart.md +++ b/docs/goal/restart.md @@ -8,3 +8,4 @@ Restart selected resources. Usefull for local development and restarting service | `timeout` | `k3s.timeout` | Timeout in seconds to wait for resources getting ready. | 300 | | `skipRestart` | `k3s.skipImage` | Skip image handling. | false | | `debug` | `k3s.debug` | Stream logs of docker and kubectl. | false | +| `taskTimeout` | `k3s.taskTimeout` | Default timeout for docker tasks in seconds. | 30 | diff --git a/docs/goal/rm.md b/docs/goal/rm.md index 4c31b97a..5f20b52e 100644 --- a/docs/goal/rm.md +++ b/docs/goal/rm.md @@ -7,3 +7,4 @@ Stop and remove k3s container. | `includeCache` | `k3s.includeCache` | Include cache directory with downloaded images. | false | | `skipRm` | `k3s.skipRm` | Skip removing k3s container. | false | | `debug` | `k3s.debug` | Stream logs of docker and kubectl. | false | +| `taskTimeout` | `k3s.taskTimeout` | Default timeout for docker tasks in seconds. | 30 | diff --git a/docs/goal/run.md b/docs/goal/run.md index c9eb9ff4..98d75313 100644 --- a/docs/goal/run.md +++ b/docs/goal/run.md @@ -27,3 +27,4 @@ Start and run k3s container. | `registries` | `k3s.registries` | Path to "registry.yaml" to mount to "/etc/rancher/k3s/registries.yaml". | `null` | | `skipRun` | `skipRun` | Skip running of k3s. | false | | `debug` | `k3s.debug` | Stream logs of docker and kubectl. | false | +| `taskTimeout` | `k3s.taskTimeout` | Default timeout for docker tasks in seconds. | 30 | diff --git a/src/main/java/io/kokuwa/maven/k3s/mojo/ApplyMojo.java b/src/main/java/io/kokuwa/maven/k3s/mojo/ApplyMojo.java index 6f2b9ee4..b9787d71 100644 --- a/src/main/java/io/kokuwa/maven/k3s/mojo/ApplyMojo.java +++ b/src/main/java/io/kokuwa/maven/k3s/mojo/ApplyMojo.java @@ -87,7 +87,7 @@ public void execute() throws MojoExecutionException { if (getDocker().getContainer().isEmpty()) { throw new MojoExecutionException("No k3s container found"); } - getDocker().copyToContainer(manifests, toLinuxPath(path)); + getDocker().copyToContainer(manifests, toLinuxPath(path), timeout); // wait for service account, see https://github.com/kubernetes/kubernetes/issues/66689 diff --git a/src/main/java/io/kokuwa/maven/k3s/mojo/ImageMojo.java b/src/main/java/io/kokuwa/maven/k3s/mojo/ImageMojo.java index cc4cb071..405e4042 100644 --- a/src/main/java/io/kokuwa/maven/k3s/mojo/ImageMojo.java +++ b/src/main/java/io/kokuwa/maven/k3s/mojo/ImageMojo.java @@ -74,6 +74,22 @@ public class ImageMojo extends K3sMojo { @Parameter(property = "k3s.pullTimeout", defaultValue = "1200") private Duration pullTimeout; + /** + * Timeout for "docker cp" in seconds. + * + * @since 1.5.0 + */ + @Parameter(property = "k3s.copyToContainerTimeout", defaultValue = "120") + private Duration copyTimeout; + + /** + * Timeout for "docker save" in seconds. + * + * @since 1.5.0 + */ + @Parameter(property = "k3s.saveTimeout", defaultValue = "120") + private Duration saveTimeout; + /** * Skip starting of k3s container. * @@ -154,7 +170,7 @@ private boolean tar(Map> existingImages, Path tarFile) { var destination = "/tmp/" + tarFile.getFileName() + "_" + System.nanoTime(); var outputPattern = Pattern.compile("^unpacking (?.*) \\(sha256:[0-9a-f]{64}\\).*$"); - getDocker().copyToContainer(tarFile, destination); + getDocker().copyToContainer(tarFile, destination, copyTimeout); for (var output : getDocker().exec(pullTimeout, "ctr", "image", "import", destination.toString())) { var matcher = outputPattern.matcher(output); if (matcher.matches()) { @@ -232,8 +248,8 @@ private boolean docker(Map> existingImages, String image) var source = Paths.get(System.getProperty("java.io.tmpdir")).resolve(filename); var destination = "/tmp/" + filename; try { - getDocker().saveImage(image, source); - getDocker().copyToContainer(source, destination); + getDocker().saveImage(image, source, saveTimeout); + getDocker().copyToContainer(source, destination, copyTimeout); getDocker().exec(pullTimeout, "ctr", "image", "import", destination.toString()); getDocker().exec("ctr", "image", "label", normalizedImage, label + "=" + digest); } catch (MojoExecutionException e) { @@ -290,6 +306,14 @@ public void setPullTimeout(int pullTimeout) { this.pullTimeout = Duration.ofSeconds(pullTimeout); } + public void setCopyTimeout(int copyTimeout) { + this.copyTimeout = Duration.ofSeconds(copyTimeout); + } + + public void setSaveTimeout(int saveTimeout) { + this.saveTimeout = Duration.ofSeconds(saveTimeout); + } + public void setSkipImage(boolean skipImage) { this.skipImage = skipImage; } diff --git a/src/main/java/io/kokuwa/maven/k3s/mojo/K3sMojo.java b/src/main/java/io/kokuwa/maven/k3s/mojo/K3sMojo.java index e838f51a..ccb71828 100644 --- a/src/main/java/io/kokuwa/maven/k3s/mojo/K3sMojo.java +++ b/src/main/java/io/kokuwa/maven/k3s/mojo/K3sMojo.java @@ -2,6 +2,7 @@ import java.io.File; import java.nio.file.Path; +import java.time.Duration; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.logging.Log; @@ -21,13 +22,21 @@ public abstract class K3sMojo extends AbstractMojo { /** - * Enable debuging of docker and k3s logs. + * Enable debugging of docker and k3s logs. * * @since 1.0.0 */ @Parameter(property = "k3s.debug", defaultValue = "false") private boolean debug; + /** + * Default timeout for docker tasks in seconds. + * + * @since 1.5.0 + */ + @Parameter(property = "k3s.taskTimeout", defaultValue = "30") + private Duration taskTimeout; + /** * Skip plugin. * @@ -68,7 +77,8 @@ public Log getLog() { } public Docker getDocker() { - return docker == null ? docker = new Docker(containerName, volumeName, log) : docker; + if (docker == null && taskTimeout == null) throw new NullPointerException(); + return docker == null ? docker = new Docker(containerName, volumeName, log, taskTimeout) : docker; } public String toLinuxPath(Path path) { @@ -86,6 +96,10 @@ public void setDebug(boolean debug) { this.debug = debug; } + public void setTaskTimeout(int taskTimeout) { + this.taskTimeout = Duration.ofSeconds(taskTimeout); + } + public void setSkip(boolean skip) { this.skip = skip; } diff --git a/src/main/java/io/kokuwa/maven/k3s/mojo/RestartMojo.java b/src/main/java/io/kokuwa/maven/k3s/mojo/RestartMojo.java index 84bc98f6..9ac3a382 100644 --- a/src/main/java/io/kokuwa/maven/k3s/mojo/RestartMojo.java +++ b/src/main/java/io/kokuwa/maven/k3s/mojo/RestartMojo.java @@ -120,8 +120,8 @@ private Callable restart(String resoure) { try { getDocker().exec("kubectl", "rollout", "restart", kind, name, "--namespace=" + namespace); log.info("{} {}/{} restarted", kind, namespace, name); - getDocker().exec("kubectl", "rollout", "status", kind, name, "--namespace=" + namespace, - "--timeout=" + timeout.getSeconds() + "s"); + getDocker().exec(timeout.plusSeconds(5), "kubectl", "rollout", "status", kind, name, + "--namespace=" + namespace, "--timeout=" + timeout.getSeconds() + "s"); log.info("{} {}/{} restart finished", kind, namespace, name); return true; } catch (MojoExecutionException e) { diff --git a/src/main/java/io/kokuwa/maven/k3s/util/Docker.java b/src/main/java/io/kokuwa/maven/k3s/util/Docker.java index f1c03ad8..61e273b2 100644 --- a/src/main/java/io/kokuwa/maven/k3s/util/Docker.java +++ b/src/main/java/io/kokuwa/maven/k3s/util/Docker.java @@ -31,16 +31,19 @@ public class Docker { private final String containerName; private final String volumeName; private final Logger log; + private final Duration timeout; - public Docker(String containerName, String volumeName, Logger log) { + public Docker(String containerName, String volumeName, Logger log, Duration timeout) { this.containerName = containerName; this.volumeName = volumeName; this.log = log; + this.timeout = timeout; } public Optional getContainer() throws MojoExecutionException { return Task - .of(log, "docker", "container", "ls", "--all", "--filter=name=" + containerName, "--format={{json .}}") + .of(log, timeout, "docker", "container", "ls", "--all", "--filter=name=" + containerName, + "--format={{json .}}") .run().stream() .map(output -> readValue(Container.class, output)) .filter(container -> containerName.equals(container.name)) @@ -62,66 +65,62 @@ public void createContainer(String image, List ports, List k3s, ports.stream().map(port -> "--publish=" + port).forEach(command::add); command.add(image); command.addAll(k3s); - Task.of(log, command).run(); + Task.of(log, timeout, command).run(); } public void startContainer() throws MojoExecutionException { - Task.of(log, "docker", "start", containerName).run(); + Task.of(log, timeout, "docker", "start", containerName).run(); } public void removeContainer() throws MojoExecutionException { - Task.of(log, "docker", "rm", containerName, "--force", "--volumes").run(); + Task.of(log, timeout, "docker", "rm", containerName, "--force", "--volumes").run(); } public void copyFromContainer(String source, Path destination) throws MojoExecutionException { - Task.of(log, "docker", "cp", containerName + ":" + source, destination.toString()).run(); + Task.of(log, timeout, "docker", "cp", containerName + ":" + source, destination.toString()).run(); } - public void copyToContainer(Path source, String destination) throws MojoExecutionException { + public void copyToContainer(Path source, String destination, Duration copyTimeout) throws MojoExecutionException { // suffix directories with '/.', see https://docs.docker.com/engine/reference/commandline/cp/#description var sourceString = Files.isDirectory(source) ? source + File.separator + "." : source.toString(); - Task.of(log, "docker", "cp", sourceString, containerName + ":" + destination).run(); + Task.of(log, copyTimeout, "docker", "cp", sourceString, containerName + ":" + destination).run(); } public void waitForLog(Await await, Function, Boolean> checker) throws MojoExecutionException { - var process = Task.of(log, "docker", "logs", containerName, "--follow").timeout(Duration.ofHours(1)).start(); + var process = Task.of(log, Duration.ofHours(1), "docker", "logs", containerName, "--follow").start(); await.onTimeout(() -> process.output().forEach(log::warn)).until(() -> process.output(), checker); process.close(); } public List exec(String... commands) throws MojoExecutionException { - return exec(null, commands); + return exec(timeout, commands); } - public List exec(Duration timeout, String... commands) throws MojoExecutionException { - return exec(timeout, List.of(commands)); + public List exec(Duration execTimeout, String... commands) throws MojoExecutionException { + return exec(execTimeout, List.of(commands)); } - public List exec(Duration timeout, List commands) throws MojoExecutionException { - return execWithoutVerify(timeout, commands).verify().output(); + public List exec(Duration execTimeout, List commands) throws MojoExecutionException { + return execWithoutVerify(execTimeout, commands).verify().output(); } public Task execWithoutVerify(List commands) throws MojoExecutionException { - return execWithoutVerify(null, commands); + return execWithoutVerify(timeout, commands); } - public Task execWithoutVerify(Duration timeout, List commands) throws MojoExecutionException { + public Task execWithoutVerify(Duration execTimeout, List commands) throws MojoExecutionException { var command = new ArrayList(); command.add("docker"); command.add("exec"); command.add(containerName); command.addAll(commands); - var task = Task.of(log, command); - if (timeout != null) { - task.timeout(timeout); - } - return task.start().waitFor(); + return Task.of(log, execTimeout, command).start().waitFor(); } // volume public Optional getVolume() throws MojoExecutionException { - return Task.of(log, "docker", "volume", "ls", "--filter=name=" + volumeName, "--format={{json .}}") + return Task.of(log, timeout, "docker", "volume", "ls", "--filter=name=" + volumeName, "--format={{json .}}") .run().stream() .map(output -> readValue(ContainerVolume.class, output)) .filter(volume -> volumeName.equals(volume.name)) @@ -129,11 +128,11 @@ public Optional getVolume() throws MojoExecutionException { } public void createVolume() throws MojoExecutionException { - Task.of(log, "docker", "volume", "create", volumeName).run(); + Task.of(log, timeout, "docker", "volume", "create", volumeName).run(); } public void removeVolume() throws MojoExecutionException { - Task.of(log, "docker", "volume", "rm", volumeName, "--force").run(); + Task.of(log, timeout, "docker", "volume", "rm", volumeName, "--force").run(); } // images @@ -157,22 +156,22 @@ public String normalizeImage(String image) { } public Optional getImage(String image) throws MojoExecutionException { - var task = Task.of(log, "docker", "image", "inspect", image, "--format={{json .}}").start().waitFor(); + var task = Task.of(log, timeout, "docker", "image", "inspect", image, "--format={{json .}}").start().waitFor(); return task.exitCode() == 0 ? task.output().stream().map(output -> readValue(ContainerImage.class, output)).findAny() : Optional.empty(); } - public void pullImage(String image, Duration timeout) throws MojoExecutionException { - Task.of(log, "docker", "image", "pull", "--quiet", image).timeout(timeout).run(); + public void pullImage(String image, Duration pullTimeout) throws MojoExecutionException { + Task.of(log, pullTimeout, "docker", "image", "pull", "--quiet", image).run(); } - public void saveImage(String image, Path path) throws MojoExecutionException { - Task.of(log, "docker", "image", "save", "--output=" + path, image).run(); + public void saveImage(String image, Path path, Duration saveTimeout) throws MojoExecutionException { + Task.of(log, saveTimeout, "docker", "image", "save", "--output=" + path, image).run(); } public void removeImage(String image) throws MojoExecutionException { - Task.of(log, "docker", "image", "rm", "--force", image).run(); + Task.of(log, timeout, "docker", "image", "rm", "--force", image).run(); } // internal diff --git a/src/main/java/io/kokuwa/maven/k3s/util/Task.java b/src/main/java/io/kokuwa/maven/k3s/util/Task.java index 6a198775..6ddde51b 100644 --- a/src/main/java/io/kokuwa/maven/k3s/util/Task.java +++ b/src/main/java/io/kokuwa/maven/k3s/util/Task.java @@ -24,40 +24,32 @@ public class Task { private final Logger log; private final List command; - private Duration timeout = Duration.ofSeconds(30); + private Duration timeout; private Process process; private final List output = new ArrayList<>(); private final List threads = new ArrayList<>(); - private Task(Logger log, List command) { + private Task(Logger log, Duration timeout, List command) { this.log = log; this.command = command; + this.timeout = timeout; } // config - public static Task of(Logger log, String... command) { - return new Task(log, List.of(command)); + public static Task of(Logger log, Duration timeout, String... command) { + return new Task(log, timeout, List.of(command)); } - public static Task of(Logger log, List command) { - return new Task(log, command); + public static Task of(Logger log, Duration timeout, List command) { + return new Task(log, timeout, command); } public List command() { return command; } - public Duration timeout() { - return timeout; - } - - public Task timeout(Duration newTimeout) { - this.timeout = newTimeout; - return this; - } - @Override public String toString() { return command.stream().collect(Collectors.joining(" ")) + " (timeout: " + timeout + ")"; diff --git a/src/test/java/io/kokuwa/maven/k3s/mojo/RunMojoTest.java b/src/test/java/io/kokuwa/maven/k3s/mojo/RunMojoTest.java index 73de461a..b3b017c1 100644 --- a/src/test/java/io/kokuwa/maven/k3s/mojo/RunMojoTest.java +++ b/src/test/java/io/kokuwa/maven/k3s/mojo/RunMojoTest.java @@ -9,6 +9,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; +import java.time.Duration; import java.util.List; import org.apache.maven.plugin.MojoExecutionException; @@ -67,7 +68,7 @@ void withFailIfExistsStopped(RunMojo runMojo, Logger log) throws MojoExecutionEx runMojo.setFailIfExists(true); assertDoesNotThrow(runMojo::execute); assertTrue(runMojo.getMarker().consumeStarted(), "started marker expected"); - Task.of(log, "docker", "stop", "k3s-maven-plugin").run(); + Task.of(log, Duration.ofSeconds(30), "docker", "stop", "k3s-maven-plugin").run(); var expectedMessage = "Container with id '" + docker.getContainer().get().id + "' found. Please remove that container or set 'k3s.failIfExists' to false."; var actualMessage = assertThrows(MojoExecutionException.class, runMojo::execute).getMessage(); @@ -97,7 +98,7 @@ void withReplaceIfExistsStopped(RunMojo runMojo, Logger log) throws MojoExecutio assertDoesNotThrow(runMojo::execute); assertTrue(runMojo.getMarker().consumeStarted(), "started marker expected"); var containerBefore = docker.getContainer().orElseThrow(); - Task.of(log, "docker", "stop", "k3s-maven-plugin").run(); + Task.of(log, Duration.ofSeconds(30), "docker", "stop", "k3s-maven-plugin").run(); assertDoesNotThrow(runMojo::execute); var containerAfter = docker.getContainer().orElseThrow(); assertNotEquals(containerBefore.id, containerAfter.id, "container was not replaced"); @@ -126,7 +127,7 @@ void withoutFailIfExistsStopped(RunMojo runMojo, Logger log, ApplyMojo applyMojo assertDoesNotThrow(runMojo::execute); assertTrue(runMojo.getMarker().consumeStarted(), "started marker expected"); var containerBefore = docker.getContainer().orElseThrow(); - Task.of(log, "docker", "stop", "k3s-maven-plugin").run(); + Task.of(log, Duration.ofSeconds(30), "docker", "stop", "k3s-maven-plugin").run(); assertDoesNotThrow(runMojo::execute); assertTrue(runMojo.getMarker().consumeStarted(), "started marker expected"); assertDoesNotThrow(applyMojo::execute); diff --git a/src/test/java/io/kokuwa/maven/k3s/test/MojoExtension.java b/src/test/java/io/kokuwa/maven/k3s/test/MojoExtension.java index c3f22779..eb9cbcc8 100644 --- a/src/test/java/io/kokuwa/maven/k3s/test/MojoExtension.java +++ b/src/test/java/io/kokuwa/maven/k3s/test/MojoExtension.java @@ -5,6 +5,7 @@ import java.io.File; import java.io.StringReader; +import java.time.Duration; import java.util.HashSet; import java.util.Set; import java.util.stream.Stream; @@ -33,7 +34,7 @@ public class MojoExtension implements ParameterResolver, BeforeAllCallback { private static final String containerName = "k3s-maven-plugin"; private static final String volumeName = "k3s-maven-plugin-junit"; private static final Logger log = LoggerFactory.getLogger(MojoExtension.class); - private static final Docker docker = new Docker(containerName, volumeName, log); + private static final Docker docker = new Docker(containerName, volumeName, log, Duration.ofSeconds(30)); private static final Set mojos = new HashSet<>(); @Override diff --git a/src/test/java/io/kokuwa/maven/k3s/util/DockerTest.java b/src/test/java/io/kokuwa/maven/k3s/util/DockerTest.java index 195ef3d2..0c3c4dbd 100644 --- a/src/test/java/io/kokuwa/maven/k3s/util/DockerTest.java +++ b/src/test/java/io/kokuwa/maven/k3s/util/DockerTest.java @@ -126,7 +126,7 @@ void copy() throws MojoExecutionException, IOException { Files.deleteIfExists(sourceFile); Files.deleteIfExists(returnFile); Files.write(sourceFile, initialContent.toString().getBytes()); - docker.copyToContainer(sourceDir, containerDir.toString()); + docker.copyToContainer(sourceDir, containerDir.toString(), Duration.ofSeconds(30)); docker.copyFromContainer(containerDir.toString(), returnDir); assertEquals(initialContent, Files.readString(returnFile)); @@ -136,7 +136,7 @@ void copy() throws MojoExecutionException, IOException { Files.deleteIfExists(sourceFile); Files.deleteIfExists(returnFile); Files.write(sourceFile, changedContent.toString().getBytes()); - docker.copyToContainer(sourceDir, containerDir.toString()); + docker.copyToContainer(sourceDir, containerDir.toString(), Duration.ofSeconds(30)); docker.copyFromContainer(containerDir.toString(), returnDir); assertEquals(changedContent, Files.readString(returnFile)); }