diff --git a/.gitignore b/.gitignore index 3d47f986c41db29ec6dc0d5036bf760b3a1cf366..8d47cace3a5bd898da9fb12bed716d60838191a4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ target/ .settings/ *.iml .project -.classpath \ No newline at end of file +.classpath +**/.DS_Store \ No newline at end of file diff --git a/pom.xml b/pom.xml index ba54d56baa570ae6cd51be5be46ed0b0016eea31..35b09fe05ce5040d4f8622428b6c27adb769530d 100644 --- a/pom.xml +++ b/pom.xml @@ -6,20 +6,20 @@ <!-- Inherited Icy Parent POM --> <parent> - <artifactId>pom-icy</artifactId> <groupId>org.bioimageanalysis.icy</groupId> - <version>2.0.0</version> - </parent> + <artifactId>pom-icy</artifactId> + <version>2.2.0</version> + </parent> <!-- Project Information --> <artifactId>docker4icy</artifactId> - <version>3.2.7</version> + <version>4.0.0</version> <packaging>jar</packaging> <name>Docker for Icy</name> <description>Yes, it is finally here. A user-friendly interface to Docker, letting you run containers within your plug-ins in no time.</description> - <url>http://icy.bioimageanalysis.org/plugin/docker-for-icy/</url> + <url>https://icy.bioimageanalysis.org/plugin/docker-for-icy/</url> <inceptionYear>2020</inceptionYear> <organization> @@ -55,31 +55,15 @@ <!-- Project properties --> <properties> - <artifact-to-include>docker-java</artifact-to-include> + <artifact-to-extract>docker-java</artifact-to-extract> </properties> - <profiles> - <profile> - <id>icy-plugin-extract-library</id> - <activation> - <activeByDefault>true</activeByDefault> - </activation> - </profile> - </profiles> - <!-- List of project's dependencies --> <dependencies> - <!-- The core of Icy --> - <dependency> - <groupId>org.bioimageanalysis.icy</groupId> - <artifactId>icy-kernel</artifactId> - <version>${icy-kernel.version}</version> - </dependency> - <dependency> <groupId>com.github.docker-java</groupId> <artifactId>docker-java</artifactId> - <version>3.2.7</version> + <version>3.3.0</version> </dependency> </dependencies> diff --git a/src/main/java/plugins/adufour/docker/Docker4Icy.java b/src/main/java/plugins/adufour/docker/Docker4Icy.java index 35839ed783d07ebbd0f78587e478bde0afb037e2..82ca5f947b10e0d2598bb7aba51fd46585300bda 100644 --- a/src/main/java/plugins/adufour/docker/Docker4Icy.java +++ b/src/main/java/plugins/adufour/docker/Docker4Icy.java @@ -15,62 +15,62 @@ import icy.util.JarUtil; * Icy interface to <a href="https://www.docker.com/what-docker">Docker</a>. This daemon loads up * necessary 3rd-party libraries into the class path at startup (and every time the plug-in list is * reloaded). To interact with Docker, see the {@link DockerUtil} class. - * + * * @author Alexandre Dufour */ -public class Docker4Icy extends Plugin implements PluginDaemon -{ - /** The temporary folder where the 3rd party libraries will be extracted */ +public class Docker4Icy extends Plugin implements PluginDaemon { + /** + * The temporary folder where the 3rd party libraries will be extracted + */ private static final String libFolder = FileUtil.getTempDirectory() + "/Docker4Icy/"; - + @Override - public void init() - { - try - { + public void init() { + try { // 1) Unpack and load 3rd-party JAR libraries via a temporary folder URL url = DockerUtil.class.getResource("lib"); - if (url != null) - { + if (url != null) { String[] jarFiles = new File(url.getFile()).list(); - for (String jarFile : jarFiles) - { - String fileName = FileUtil.getFileName(jarFile); - extractResource(libFolder + fileName, DockerUtil.class.getResource("lib" + File.separator + fileName)); - ((JarClassLoader) PluginLoader.getLoader()).add(new URL("file://" + libFolder + fileName)); + if (jarFiles != null) { + for (String jarFile : jarFiles) { + String fileName = FileUtil.getFileName(jarFile); + URL urlLib = DockerUtil.class.getResource("lib" + File.separator + fileName); + if (urlLib != null) { + extractResourceTo(libFolder + fileName, urlLib); + ((JarClassLoader) PluginLoader.getLoader()).add(new URL("file://" + libFolder + fileName)); + } + } } } - else - { + else { String jarPath = FileUtil.getApplicationDirectory() + "/" + getDescriptor().getJarFilename(); for (String jarFile : JarUtil.getAllFiles(jarPath, false, false)) - if (jarFile.endsWith(".jar")) - { + if (jarFile.endsWith(".jar")) { String fileName = FileUtil.getFileName(jarFile); - extractResource(libFolder + fileName, DockerUtil.class.getResource("lib/" + fileName)); - ((JarClassLoader) PluginLoader.getLoader()).add(libFolder + fileName); + URL urlLib = DockerUtil.class.getResource("lib/" + fileName); + if (urlLib != null) { + extractResourceTo(libFolder + fileName, urlLib); + ((JarClassLoader) PluginLoader.getLoader()).add(libFolder + fileName); + } } } } - catch (IOException e) - { + catch (IOException e) { e.printStackTrace(); } } - + @Override - public void run() - { + public void run() { // Don't run. I'm too fast for you anyway. } - + @Override - public void stop() - { + public void stop() { // Delete the temporary folder: // 1) for the beauty of saving space until the next run... // 2) To prevent potential conflicts after upgrades FileUtil.delete(new File(libFolder), true); } - + } diff --git a/src/main/java/plugins/adufour/docker/DockerUtil.java b/src/main/java/plugins/adufour/docker/DockerUtil.java index db7dd252e70b381d13283faefd79e3ac3599e526..8db1b2082fd26cfe4b852423f077a43766b288e1 100644 --- a/src/main/java/plugins/adufour/docker/DockerUtil.java +++ b/src/main/java/plugins/adufour/docker/DockerUtil.java @@ -6,163 +6,142 @@ import java.util.Map; import java.util.Map.Entry; import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.async.ResultCallback; import com.github.dockerjava.api.command.CreateContainerCmd; import com.github.dockerjava.api.command.CreateContainerResponse; +import com.github.dockerjava.api.command.PullImageResultCallback; import com.github.dockerjava.api.model.AccessMode; import com.github.dockerjava.api.model.Bind; import com.github.dockerjava.api.model.Volume; import com.github.dockerjava.core.DefaultDockerClientConfig; import com.github.dockerjava.core.DockerClientBuilder; -import com.github.dockerjava.core.command.ExecStartResultCallback; -import com.github.dockerjava.core.command.PullImageResultCallback; /** * Utility class to easily interface with (and run) * <a href="https://www.docker.com/what-docker">Docker</a> within Icy, using the official * <a href="https://github.com/docker-java/docker-java">docker-java API</a>. - * + * * @author Alexandre Dufour */ -public class DockerUtil -{ - private static DockerClient docker = DockerClientBuilder.getInstance(DefaultDockerClientConfig.createDefaultConfigBuilder().build()).build(); - +public class DockerUtil { + private static final DockerClient docker = DockerClientBuilder.getInstance(DefaultDockerClientConfig.createDefaultConfigBuilder().build()).build(); + /** * Starts a container with the specified image - * - * @param image - * the image to run (and download if necessary) + * + * @param image the image to run (and download if necessary) * @return the container ID * @throws InterruptedException */ - public static String startContainer(String image) throws InterruptedException - { + public static String startContainer(String image) throws InterruptedException { return startContainer(image, (List<Bind>) null, null); } - + /** * Starts a container with the specified image and virtual folder binding(s) - * - * @param image - * the image to run (and download if necessary) - * @param bindings - * Virtual bindings between host and container folders, e.g. - * <code>{"/host/a", "/docker/b"}</code>. NB: by default, these mappings are set to - * "read-write" mode. For further control, use - * {@link #startContainer(String, List, String)} + * + * @param image the image to run (and download if necessary) + * @param bindings Virtual bindings between host and container folders, e.g. + * <code>{"/host/a", "/docker/b"}</code>. NB: by default, these mappings are set to + * "read-write" mode. For further control, use + * {@link #startContainer(String, List, String)} * @return the container ID * @throws InterruptedException */ - public static String startContainer(String image, Map<String, String> bindings) throws InterruptedException - { + public static String startContainer(String image, Map<String, String> bindings) throws InterruptedException { return startContainer(image, bindings, null); } - + /** * Starts a container with the specified image and virtual folder binding(s) - * - * @param image - * the image to run (and download if necessary) - * @param bindings - * Virtual bindings between host and container folders, e.g. - * <code>new Bind("/host/a", "/docker/b", AccessMode.rw)</code> + * + * @param image the image to run (and download if necessary) + * @param bindings Virtual bindings between host and container folders, e.g. + * <code>new Bind("/host/a", "/docker/b", AccessMode.rw)</code> * @return the container ID * @throws InterruptedException */ - public static String startContainer(String image, List<Bind> bindings) throws InterruptedException - { + public static String startContainer(String image, List<Bind> bindings) throws InterruptedException { return startContainer(image, bindings, null); } - + /** * Starts a container with the specified image and virtual folder binding(s) - * - * @param image - * the image to run (and download if necessary) - * @param bindings - * Virtual bindings between host and container folders, e.g. - * <code>{"/host/a", "/docker/b"}</code>. NB: by default, these mappings are set to - * "read-write" mode. For further control, use - * {@link #startContainer(String, List, String)} - * @param workingDirectory - * the directory where the container should start from (e.g. - * <code>"/docker/b/subdir"</code>). This is equivalent to (but faster than) starting - * the container and then calling <code>"cd /docker/b/subdir"</code> + * + * @param image the image to run (and download if necessary) + * @param bindings Virtual bindings between host and container folders, e.g. + * <code>{"/host/a", "/docker/b"}</code>. NB: by default, these mappings are set to + * "read-write" mode. For further control, use + * {@link #startContainer(String, List, String)} + * @param workingDirectory the directory where the container should start from (e.g. + * <code>"/docker/b/subdir"</code>). This is equivalent to (but faster than) starting + * the container and then calling <code>"cd /docker/b/subdir"</code> * @return the container ID * @throws InterruptedException */ - public static String startContainer(String image, Map<String, String> bindings, String workingDirectory) throws InterruptedException - { + public static String startContainer(String image, Map<String, String> bindings, String workingDirectory) throws InterruptedException { List<Bind> bindingList = null; - - if (bindings != null) - { + + if (bindings != null) { // Convert user mappings to Docker bindings - bindingList = new ArrayList<Bind>(bindings.size()); + bindingList = new ArrayList<>(bindings.size()); for (Entry<String, String> binding : bindings.entrySet()) bindingList.add(new Bind(binding.getKey(), new Volume(binding.getValue()), AccessMode.rw)); } - + return startContainer(image, bindingList, workingDirectory); } - + /** * Starts a container with the specified image and virtual folder binding(s) - * - * @param image - * the image to run (and download if necessary) - * @param bindings - * Virtual bindings between host and container folders, e.g. - * <code>new Bind("/host/a", "/docker/b", AccessMode.rw)</code> - * @param workingDirectory - * the directory where the container should start from (e.g. - * <code>"/docker/b/subdir"</code>). This is equivalent to (but faster than) starting - * the container and then calling <code>"cd /docker/b/subdir"</code> + * + * @param image the image to run (and download if necessary) + * @param bindings Virtual bindings between host and container folders, e.g. + * <code>new Bind("/host/a", "/docker/b", AccessMode.rw)</code> + * @param workingDirectory the directory where the container should start from (e.g. + * <code>"/docker/b/subdir"</code>). This is equivalent to (but faster than) starting + * the container and then calling <code>"cd /docker/b/subdir"</code> * @return the container ID * @throws InterruptedException */ - public static String startContainer(String image, List<Bind> bindings, String workingDirectory) throws InterruptedException - { + public static String startContainer(String image, List<Bind> bindings, String workingDirectory) throws InterruptedException { System.out.println("Fetching image " + image); docker.pullImageCmd(image).exec(new PullImageResultCallback()).awaitCompletion(); - + System.out.println("Starting container using " + image); - - CreateContainerCmd createCommand = docker.createContainerCmd(image); - - if (bindings != null) createCommand = createCommand.withBinds(bindings); - - if (workingDirectory != null) createCommand = createCommand.withWorkingDir(workingDirectory); - + + final CreateContainerCmd createCommand = docker.createContainerCmd(image); + + if (bindings != null && createCommand.getHostConfig() != null) + createCommand.getHostConfig().withBinds(bindings); + + if (workingDirectory != null) + createCommand.withWorkingDir(workingDirectory); + // Emulate a TTY and attach output and error streams - createCommand = createCommand.withTty(true).withAttachStdout(true).withAttachStderr(true); - + createCommand.withTty(true).withAttachStdout(true).withAttachStderr(true); + // Finally, start the container CreateContainerResponse container = createCommand.exec(); String containerID = container.getId(); docker.startContainerCmd(containerID).exec(); - + return containerID; } - - public static void stopContainer(String containerID) - { + + public static void stopContainer(String containerID) { docker.stopContainerCmd(containerID).exec(); } - + /** * Runs the specified command in the given container and awaits completion - * - * @param containerID - * the ID of the container where the command should run - * @param command - * the command to run (e.g. <code>"echo hello Icy world!"</code> - * @throws InterruptedException - * if the calling thread was interrupted before completion of the command + * + * @param containerID the ID of the container where the command should run + * @param command the command to run (e.g. <code>"echo hello Icy world!"</code> + * @throws InterruptedException if the calling thread was interrupted before completion of the command */ - public static void runCommand(String containerID, String command) throws InterruptedException - { + public static void runCommand(String containerID, String command) throws InterruptedException { String cmd = docker.execCreateCmd(containerID).withAttachStdout(true).withAttachStderr(true).withTty(true).withCmd(command.split(" ")).exec().getId(); - docker.execStartCmd(cmd).exec(new ExecStartResultCallback(System.out, System.err)).awaitCompletion(); + docker.execStartCmd(cmd).exec(new ResultCallback.Adapter<>()).awaitCompletion(); } }