|
|
|
@ -17,39 +17,20 @@ |
|
|
|
package org.springframework.boot.launchscript; |
|
|
|
package org.springframework.boot.launchscript; |
|
|
|
|
|
|
|
|
|
|
|
import java.io.File; |
|
|
|
import java.io.File; |
|
|
|
import java.io.FileInputStream; |
|
|
|
import java.time.Duration; |
|
|
|
import java.io.InputStream; |
|
|
|
|
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.ArrayList; |
|
|
|
import java.util.Arrays; |
|
|
|
|
|
|
|
import java.util.Collections; |
|
|
|
|
|
|
|
import java.util.HashSet; |
|
|
|
|
|
|
|
import java.util.List; |
|
|
|
import java.util.List; |
|
|
|
import java.util.Locale; |
|
|
|
|
|
|
|
import java.util.concurrent.TimeUnit; |
|
|
|
|
|
|
|
import java.util.regex.Pattern; |
|
|
|
import java.util.regex.Pattern; |
|
|
|
|
|
|
|
|
|
|
|
import javax.ws.rs.client.Entity; |
|
|
|
|
|
|
|
import javax.ws.rs.client.WebTarget; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import com.github.dockerjava.api.DockerClient; |
|
|
|
|
|
|
|
import com.github.dockerjava.api.command.DockerCmd; |
|
|
|
|
|
|
|
import com.github.dockerjava.api.exception.DockerClientException; |
|
|
|
|
|
|
|
import com.github.dockerjava.api.model.BuildResponseItem; |
|
|
|
|
|
|
|
import com.github.dockerjava.api.model.Frame; |
|
|
|
|
|
|
|
import com.github.dockerjava.core.DefaultDockerClientConfig; |
|
|
|
|
|
|
|
import com.github.dockerjava.core.DockerClientBuilder; |
|
|
|
|
|
|
|
import com.github.dockerjava.core.DockerClientConfig; |
|
|
|
|
|
|
|
import com.github.dockerjava.core.command.AttachContainerResultCallback; |
|
|
|
|
|
|
|
import com.github.dockerjava.core.command.BuildImageResultCallback; |
|
|
|
|
|
|
|
import com.github.dockerjava.core.command.WaitContainerResultCallback; |
|
|
|
|
|
|
|
import com.github.dockerjava.core.util.CompressArchiveUtil; |
|
|
|
|
|
|
|
import com.github.dockerjava.jaxrs.AbstrSyncDockerCmdExec; |
|
|
|
|
|
|
|
import com.github.dockerjava.jaxrs.JerseyDockerCmdExecFactory; |
|
|
|
|
|
|
|
import org.assertj.core.api.Condition; |
|
|
|
import org.assertj.core.api.Condition; |
|
|
|
import org.junit.Test; |
|
|
|
import org.junit.Test; |
|
|
|
import org.junit.runner.RunWith; |
|
|
|
import org.junit.runner.RunWith; |
|
|
|
import org.junit.runners.Parameterized; |
|
|
|
import org.junit.runners.Parameterized; |
|
|
|
import org.junit.runners.Parameterized.Parameters; |
|
|
|
import org.junit.runners.Parameterized.Parameters; |
|
|
|
|
|
|
|
import org.testcontainers.containers.GenericContainer; |
|
|
|
|
|
|
|
import org.testcontainers.containers.output.ToStringConsumer; |
|
|
|
|
|
|
|
import org.testcontainers.images.builder.ImageFromDockerfile; |
|
|
|
|
|
|
|
import org.testcontainers.utility.MountableFile; |
|
|
|
|
|
|
|
|
|
|
|
import org.springframework.boot.ansi.AnsiColor; |
|
|
|
import org.springframework.boot.ansi.AnsiColor; |
|
|
|
|
|
|
|
|
|
|
|
@ -68,8 +49,6 @@ import static org.junit.Assume.assumeThat; |
|
|
|
@RunWith(Parameterized.class) |
|
|
|
@RunWith(Parameterized.class) |
|
|
|
public class SysVinitLaunchScriptIT { |
|
|
|
public class SysVinitLaunchScriptIT { |
|
|
|
|
|
|
|
|
|
|
|
private final SpringBootDockerCmdExecFactory commandExecFactory = new SpringBootDockerCmdExecFactory(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static final char ESC = 27; |
|
|
|
private static final char ESC = 27; |
|
|
|
|
|
|
|
|
|
|
|
private final String os; |
|
|
|
private final String os; |
|
|
|
@ -270,126 +249,15 @@ public class SysVinitLaunchScriptIT { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private String doTest(String script) throws Exception { |
|
|
|
private String doTest(String script) throws Exception { |
|
|
|
DockerClient docker = createClient(); |
|
|
|
ToStringConsumer consumer = new ToStringConsumer().withRemoveAnsiCodes(false); |
|
|
|
String imageId = buildImage(docker); |
|
|
|
try (LaunchScriptTestContainer container = new LaunchScriptTestContainer(this.os, this.version, script)) { |
|
|
|
String container = createContainer(docker, imageId, script); |
|
|
|
container.withLogConsumer(consumer); |
|
|
|
try { |
|
|
|
container.start(); |
|
|
|
copyFilesToContainer(docker, container, script); |
|
|
|
while (container.isRunning()) { |
|
|
|
docker.startContainerCmd(container).exec(); |
|
|
|
Thread.sleep(100); |
|
|
|
StringBuilder output = new StringBuilder(); |
|
|
|
|
|
|
|
AttachContainerResultCallback resultCallback = docker.attachContainerCmd(container).withStdOut(true) |
|
|
|
|
|
|
|
.withStdErr(true).withFollowStream(true).withLogs(true).exec(new AttachContainerResultCallback() { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public void onNext(Frame item) { |
|
|
|
|
|
|
|
output.append(new String(item.getPayload())); |
|
|
|
|
|
|
|
super.onNext(item); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
resultCallback.awaitCompletion(60, TimeUnit.SECONDS); |
|
|
|
|
|
|
|
WaitContainerResultCallback waitContainerCallback = new WaitContainerResultCallback(); |
|
|
|
|
|
|
|
docker.waitContainerCmd(container).exec(waitContainerCallback); |
|
|
|
|
|
|
|
waitContainerCallback.awaitCompletion(60, TimeUnit.SECONDS); |
|
|
|
|
|
|
|
return output.toString(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
finally { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
docker.removeContainerCmd(container).exec(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
catch (Exception ex) { |
|
|
|
|
|
|
|
// Continue
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private DockerClient createClient() { |
|
|
|
|
|
|
|
DockerClientConfig config = DefaultDockerClientConfig.createDefaultConfigBuilder().withApiVersion("1.19") |
|
|
|
|
|
|
|
.build(); |
|
|
|
|
|
|
|
return DockerClientBuilder.getInstance(config).withDockerCmdExecFactory(this.commandExecFactory).build(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private String buildImage(DockerClient docker) { |
|
|
|
|
|
|
|
String dockerfile = "src/test/resources/conf/" + this.os + "/" + this.version + "/Dockerfile"; |
|
|
|
|
|
|
|
String tag = "spring-boot-it/" + this.os.toLowerCase(Locale.ENGLISH) + ":" + this.version; |
|
|
|
|
|
|
|
BuildImageResultCallback resultCallback = new BuildImageResultCallback() { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private List<BuildResponseItem> items = new ArrayList<>(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public void onNext(BuildResponseItem item) { |
|
|
|
|
|
|
|
super.onNext(item); |
|
|
|
|
|
|
|
this.items.add(item); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public String awaitImageId() { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
awaitCompletion(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
catch (InterruptedException ex) { |
|
|
|
|
|
|
|
throw new DockerClientException("Interrupted while waiting for image id", ex); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return getImageId(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings("deprecation") |
|
|
|
|
|
|
|
private String getImageId() { |
|
|
|
|
|
|
|
if (this.items.isEmpty()) { |
|
|
|
|
|
|
|
throw new DockerClientException("Could not build image"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
String imageId = extractImageId(); |
|
|
|
|
|
|
|
if (imageId == null) { |
|
|
|
|
|
|
|
throw new DockerClientException( |
|
|
|
|
|
|
|
"Could not build image: " + this.items.get(this.items.size() - 1).getError()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return imageId; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private String extractImageId() { |
|
|
|
|
|
|
|
Collections.reverse(this.items); |
|
|
|
|
|
|
|
for (BuildResponseItem item : this.items) { |
|
|
|
|
|
|
|
if (item.isErrorIndicated() || item.getStream() == null) { |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (item.getStream().contains("Successfully built")) { |
|
|
|
|
|
|
|
return item.getStream().replace("Successfully built", "").trim(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
docker.buildImageCmd(new File(dockerfile)).withTags(new HashSet<>(Arrays.asList(tag))).exec(resultCallback); |
|
|
|
|
|
|
|
String imageId = resultCallback.awaitImageId(); |
|
|
|
|
|
|
|
return imageId; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private String createContainer(DockerClient docker, String imageId, String testScript) { |
|
|
|
|
|
|
|
return docker.createContainerCmd(imageId).withTty(false) |
|
|
|
|
|
|
|
.withCmd("/bin/bash", "-c", "chmod +x " + testScript + " && ./" + testScript).exec().getId(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void copyFilesToContainer(DockerClient docker, final String container, String script) { |
|
|
|
|
|
|
|
copyToContainer(docker, container, findApplication()); |
|
|
|
|
|
|
|
copyToContainer(docker, container, new File("src/test/resources/scripts/test-functions.sh")); |
|
|
|
|
|
|
|
copyToContainer(docker, container, new File("src/test/resources/scripts/" + script)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void copyToContainer(DockerClient docker, final String container, final File file) { |
|
|
|
|
|
|
|
this.commandExecFactory.createCopyToContainerCmdExec().exec(new CopyToContainerCmd(container, file)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private File findApplication() { |
|
|
|
|
|
|
|
File targetDir = new File("target"); |
|
|
|
|
|
|
|
for (File file : targetDir.listFiles()) { |
|
|
|
|
|
|
|
if (file.getName().startsWith("spring-boot-launch-script-tests") && file.getName().endsWith(".jar") |
|
|
|
|
|
|
|
&& !file.getName().endsWith("-sources.jar")) { |
|
|
|
|
|
|
|
return file; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
throw new IllegalStateException( |
|
|
|
return consumer.toUtf8String(); |
|
|
|
"Could not find test application in target directory. Have you built it (mvn package)?"); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private Condition<String> coloredString(AnsiColor color, String string) { |
|
|
|
private Condition<String> coloredString(AnsiColor color, String string) { |
|
|
|
@ -417,60 +285,30 @@ public class SysVinitLaunchScriptIT { |
|
|
|
throw new IllegalArgumentException("Failed to extract " + label + " from output: " + output); |
|
|
|
throw new IllegalArgumentException("Failed to extract " + label + " from output: " + output); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private static final class CopyToContainerCmdExec extends AbstrSyncDockerCmdExec<CopyToContainerCmd, Void> { |
|
|
|
private static final class LaunchScriptTestContainer extends GenericContainer<LaunchScriptTestContainer> { |
|
|
|
|
|
|
|
|
|
|
|
private CopyToContainerCmdExec(WebTarget baseResource, DockerClientConfig dockerClientConfig) { |
|
|
|
private LaunchScriptTestContainer(String os, String version, String testScript) { |
|
|
|
super(baseResource, dockerClientConfig); |
|
|
|
super(new ImageFromDockerfile("spring-boot-launch-script/" + os.toLowerCase() + "-" + version) |
|
|
|
|
|
|
|
.withFileFromFile("Dockerfile", |
|
|
|
|
|
|
|
new File("src/test/resources/conf/" + os + "/" + version + "/Dockerfile")) |
|
|
|
|
|
|
|
.withFileFromFile("spring-boot-launch-script-tests.jar", findApplication()) |
|
|
|
|
|
|
|
.withFileFromFile("test-functions.sh", new File("src/test/resources/scripts/test-functions.sh"))); |
|
|
|
|
|
|
|
withCopyFileToContainer(MountableFile.forHostPath("src/test/resources/scripts/" + testScript), |
|
|
|
|
|
|
|
"/" + testScript); |
|
|
|
|
|
|
|
withCommand("/bin/bash", "-c", "chmod +x " + testScript + " && ./" + testScript); |
|
|
|
|
|
|
|
withStartupTimeout(Duration.ofMinutes(5)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
private static File findApplication() { |
|
|
|
protected Void execute(CopyToContainerCmd command) { |
|
|
|
File targetDir = new File("target"); |
|
|
|
try (InputStream streamToUpload = new FileInputStream( |
|
|
|
for (File file : targetDir.listFiles()) { |
|
|
|
CompressArchiveUtil.archiveTARFiles(command.getFile().getParentFile(), |
|
|
|
if (file.getName().startsWith("spring-boot-launch-script-tests") && file.getName().endsWith(".jar") |
|
|
|
Arrays.asList(command.getFile()), command.getFile().getName()))) { |
|
|
|
&& !file.getName().endsWith("-sources.jar")) { |
|
|
|
WebTarget webResource = getBaseResource().path("/containers/{id}/archive").resolveTemplate("id", |
|
|
|
return file; |
|
|
|
command.getContainer()); |
|
|
|
} |
|
|
|
webResource.queryParam("path", ".").queryParam("noOverwriteDirNonDir", false).request() |
|
|
|
|
|
|
|
.put(Entity.entity(streamToUpload, "application/x-tar")).close(); |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
catch (Exception ex) { |
|
|
|
|
|
|
|
throw new RuntimeException(ex); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
throw new IllegalStateException( |
|
|
|
|
|
|
|
"Could not find test application in target directory. Have you built it (mvn package)?"); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static final class CopyToContainerCmd implements DockerCmd<Void> { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private final String container; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private final File file; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private CopyToContainerCmd(String container, File file) { |
|
|
|
|
|
|
|
this.container = container; |
|
|
|
|
|
|
|
this.file = file; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public String getContainer() { |
|
|
|
|
|
|
|
return this.container; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public File getFile() { |
|
|
|
|
|
|
|
return this.file; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
|
|
public void close() { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static final class SpringBootDockerCmdExecFactory extends JerseyDockerCmdExecFactory { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private CopyToContainerCmdExec createCopyToContainerCmdExec() { |
|
|
|
|
|
|
|
return new CopyToContainerCmdExec(getBaseResource(), getDockerClientConfig()); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|