diff --git a/README.md b/README.md index 7198d8b..cba4490 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,10 @@ Certain actions can only be performed on a container if the container is in a ce * `stop`: (Default if no pre- or post-action is set) The container will be stopped before the backup is performed. (Ignored if container is already stopped.) * `pause`: The container will be paused before the backup is performed. (Ignored if container is already paused or stopped.) * `salvage.command.pre` and `salvage.command.post`: Commands that will be executed before and after the backup within the container, similar to `docker exec`. Will not be executed if the container is stopped or paused. +* `salvage.command.exitcode`: Defines how different exit codes should be handled. Possible values are: + * `ignore`: The exit code will be ignored. + * `stop`: The backup will not be performed. (Default) + * `custom`: Special handlingg. Instead of using the `custom` value, you are expected to provide a comma-separated list of exit codes that should be handled as `stop`. You can define ranges or single exit codes. For example `1,3-5,7-9`. * `salvage.user`: User that will be used to execute the backup command. (Default is container's user) # salvage Crane Interface diff --git a/src/main/java/de/chrisliebaer/salvage/StateTransaction.java b/src/main/java/de/chrisliebaer/salvage/StateTransaction.java index d45b207..ca1653d 100644 --- a/src/main/java/de/chrisliebaer/salvage/StateTransaction.java +++ b/src/main/java/de/chrisliebaer/salvage/StateTransaction.java @@ -76,12 +76,19 @@ public void prepare(SalvageContainer container) throws InterruptedException { if (container.commandPre().isPresent() && state.getRunning() && !state.getPaused()) { var command = container.commandPre().get(); log.debug("running preperation command '{}' on container {}", command, container.name()); + long exitCode; try { - command.run(docker, container); + exitCode = command.run(docker, container); } catch (Throwable e) { throw new IllegalStateException("preperation command '" + command + "' failed on container '" + container.name() + "'", e); } + if (container.exitCodeBehaviour().check(exitCode)) { + log.debug("preperation command '{}' on container {} exited with code {}", command, container.name(), exitCode); + } else { + throw new IllegalStateException("preperation command '" + command + "' on container '" + container.name() + "' exited with code " + exitCode); + } + preCommandRun = true; } @@ -133,7 +140,13 @@ public void restore(SalvageContainer container) throws Throwable { if (affected.preCommandRun() && container.commandPost().isPresent()) { var command = container.commandPost().get(); log.debug("running post command '{}' on container {}", command, container.name()); - command.run(docker, container); + var exitCode = command.run(docker, container); + if (container.exitCodeBehaviour().check(exitCode)) { + log.debug("post command '{}' on container {} exited with code {}", command, container.name(), exitCode); + } else { + throw new IllegalStateException("post command '" + command + "' on container '" + container.name() + "' exited with code " + exitCode); + + } } } diff --git a/src/main/java/de/chrisliebaer/salvage/entity/ContainerCommand.java b/src/main/java/de/chrisliebaer/salvage/entity/ContainerCommand.java index 5f54eec..95746c3 100644 --- a/src/main/java/de/chrisliebaer/salvage/entity/ContainerCommand.java +++ b/src/main/java/de/chrisliebaer/salvage/entity/ContainerCommand.java @@ -44,8 +44,6 @@ public long run(DockerClient client, SalvageContainer container) throws Throwabl var exitCode = execInspect.getExitCodeLong(); if (exitCode == null) throw new IllegalStateException("execution of command in container " + container.name() + " failed"); - if (exitCode != 0) - log.warn("execution of command in container {} failed with exit code {}", container.name(), exitCode); return exitCode; } diff --git a/src/main/java/de/chrisliebaer/salvage/entity/ExitCodeBehaviour.java b/src/main/java/de/chrisliebaer/salvage/entity/ExitCodeBehaviour.java new file mode 100644 index 0000000..25cdc20 --- /dev/null +++ b/src/main/java/de/chrisliebaer/salvage/entity/ExitCodeBehaviour.java @@ -0,0 +1,136 @@ +package de.chrisliebaer.salvage.entity; + +import com.google.common.collect.Range; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + + +/** + * Represents the behaviour of a command in response to its exit code. + *
+ * The interface defines a single method, check, which takes a long value representing an exit code and returns a boolean.
+ */
+public sealed interface ExitCodeBehaviour permits ExitCodeBehaviour.Ignore, ExitCodeBehaviour.FailIfNonZero, ExitCodeBehaviour.Custom {
+
+ static ExitCodeBehaviour fromString(String value) {
+ // first check for built-in behaviours, if none matches, parse custom behaviour, if that fails, throw exception1
+
+ if ("ignore".equals(value)) {
+ return new Ignore();
+ }
+ if ("fail".equals(value)) {
+ return new FailIfNonZero();
+ }
+ return Custom.fromString(value);
+ }
+
+ boolean check(long exitCode);
+
+ /**
+ * Describes behaviour which ignores the exit code of the command and continues with the backup regardless.
+ */
+ record Ignore() implements ExitCodeBehaviour {
+
+ @Override
+ public boolean check(long exitCode) {
+ return true;
+ }
+ }
+
+ /**
+ * Describes behaviour which stops the backup if the command exits with a non-zero exit code.
+ */
+ record FailIfNonZero() implements ExitCodeBehaviour {
+
+ @Override
+ public boolean check(long exitCode) {
+ return exitCode == 0;
+ }
+ }
+
+ /**
+ * Describes behaviour which reacts to the exit code in a custom way.
+ */
+ record Custom(List Examples of valid ranges:
+ * The string is expected to be a comma-separated list of ranges, where each range is either a single number or a range of numbers separated by a hyphen.
+ *
+ *
+ */
+ private static final Pattern RANGE_PATTERN = Pattern.compile("(?