Skip to content
This repository has been archived by the owner on Mar 21, 2022. It is now read-only.

Issue 185 : Allow one to log process messages to a file #186

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
49 changes: 47 additions & 2 deletions src/main/java/com/spotify/docker/BuildMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.spotify.docker.client.AnsiProgressHandler;
import com.spotify.docker.client.DockerClient;
import com.spotify.docker.client.DockerException;
import com.spotify.docker.client.ProgressHandler;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigFactory;
Expand All @@ -52,6 +53,7 @@
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand Down Expand Up @@ -92,6 +94,12 @@ public class BuildMojo extends AbstractDockerMojo {
*/
private static final char WINDOWS_SEPARATOR = '\\';

/**
* File to log output
*/
@Parameter(property = "logOutput")
private String logOutput;

/**
* Directory containing the Dockerfile. If the value is not set, the plugin will generate a
* Dockerfile using the required baseImage value, plus the optional entryPoint, cmd and maintainer
Expand Down Expand Up @@ -328,7 +336,11 @@ protected void execute(final DockerClient docker)
copyResources(destination);
}

buildImage(docker, destination, buildParams());
if (logOutput == null) {
buildImage(docker, destination, buildParams());
} else {
buildImage(docker, destination, new File(logOutput), buildParams());
}
tagImage(docker, forceTags);

final DockerBuildInformation buildInfo = new DockerBuildInformation(imageName, getLog());
Expand Down Expand Up @@ -540,12 +552,45 @@ private void validateParameters() throws MojoExecutionException {
}

private void buildImage(final DockerClient docker, final String buildDir,
final ProgressHandler progressHandler,
final DockerClient.BuildParam... buildParams)
throws MojoExecutionException, DockerException, IOException, InterruptedException {
getLog().info("Building image " + imageName);
docker.build(Paths.get(buildDir), imageName, new AnsiProgressHandler(), buildParams);
docker.build(Paths.get(buildDir), imageName, progressHandler, buildParams);
getLog().info("Built " + imageName);
}

private void buildImage(final DockerClient docker, final String buildDir,
final File output,
final DockerClient.BuildParam... buildParams)
throws MojoExecutionException, DockerException, IOException, InterruptedException {

if (output.isDirectory() || (output.exists() && !output.canWrite())) {
throw new MojoExecutionException("The specified output file is a directory or cannot "
+ "be written");
}

File parent = output.getParentFile();
if (parent.isFile()) {
throw new MojoExecutionException("The specified output file's parent is a file");
}

if (!parent.exists() && !parent.mkdirs()) {
throw new MojoExecutionException("The specified output file's parent cannot be made a"
+ " directory");
}

try (PrintStream printStream =
new PrintStream(new FileOutputStream(output, true), true, "UTF-8")) {
buildImage(docker, buildDir, new AnsiProgressHandler(printStream), buildParams);
}
}

private void buildImage(final DockerClient docker, final String buildDir,
final DockerClient.BuildParam... buildParams)
throws MojoExecutionException, DockerException, IOException, InterruptedException {
buildImage(docker, buildDir, new AnsiProgressHandler(), buildParams);
}

private void tagImage(final DockerClient docker, boolean forceTags)
throws DockerException, InterruptedException, MojoExecutionException {
Expand Down
111 changes: 111 additions & 0 deletions src/test/java/com/spotify/docker/BuildMojoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,17 @@
import com.spotify.docker.client.AnsiProgressHandler;
import com.spotify.docker.client.DockerClient;
import com.spotify.docker.client.DockerClient.BuildParam;
import com.spotify.docker.client.DockerException;
import com.spotify.docker.client.ProgressHandler;
import com.spotify.docker.client.messages.ProgressMessage;

import org.apache.commons.lang.StringUtils;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.testing.AbstractMojoTestCase;
import org.apache.maven.project.MavenProject;
import org.mockito.ArgumentMatcher;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

Expand All @@ -56,10 +59,12 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class BuildMojoTest extends AbstractMojoTestCase {

Expand Down Expand Up @@ -448,6 +453,112 @@ public void testNoCache() throws Exception {
eq(BuildParam.noCache()));
}

public void testLogOutputToFileButParentIsFile() throws Exception {
testLogOutputToFileButFileCannotBeWritten(false);
}

public void testLogOutputToFileButFileIsDirectory() throws Exception {
testLogOutputToFileButFileCannotBeWritten(true);
}

public void testLogOutputToFileButFileCannotBeWritten(boolean dir) throws Exception {
final File pom = getTestFile("src/test/resources/pom-build-log-output.xml");
assertNotNull("Null pom.xml", pom);
assertTrue("pom.xml does not exist", pom.exists());

// Make sure initially the file to be logged does not exist
final String outputFileName = "target/docker/outputDir/file-to-log-output.log";
final File outputFile = getTestFile(outputFileName);
assertNotNull("Null output file", outputFile);
assertFalse("output file already exists", outputFile.exists());

if (dir) {
// Force it being a directory
assertTrue("Cannot create directory ", outputFile.mkdirs());
} else {
// Force parent is be a file
File parent = outputFile.getParentFile();
parent.getParentFile().mkdirs();
assertTrue("Cannot create parent file ", parent.createNewFile());
}

final BuildMojo mojo = setupMojo(pom);
final DockerClient docker = mock(DockerClient.class);
try {
mojo.execute(docker);
fail("mojo should have thrown exception because output file cannot be written to");
} catch (MojoExecutionException e) {
final String message;
if (dir) {
message = "The specified output file is a directory or cannot be written";
} else {
message = "The specified output file's parent is a file";
}
assertTrue(String.format("Exception message should have contained '%s'", message),
e.getMessage().contains(message));
}
}

public void testLogOutputToNewFile() throws Exception {
testLogOutputToFile(true);
}

public void testLogOutputToExistingFile() throws Exception {
testLogOutputToFile(false);
}

private void testLogOutputToFile(boolean newFile) throws Exception {
final File pom = getTestFile("src/test/resources/pom-build-log-output.xml");
assertNotNull("Null pom.xml", pom);
assertTrue("pom.xml does not exist", pom.exists());

// Make sure initially the file to be logged does not exist
final String outputFileName = "target/docker/outputDir/file-to-log-output.log";
final File outputFile = getTestFile(outputFileName);
assertNotNull("Null output file", outputFile);
assertFalse("output file already exists", outputFile.exists());

final BuildMojo mojo = setupMojo(pom);
final DockerClient docker = mock(DockerClient.class);

// A matcher that grabs the instantiated AnsiProgressHandler and logs a message
final String testMessage = "Testing progress is logged to file";
ArgumentMatcher<AnsiProgressHandler> matcher = new ArgumentMatcher<AnsiProgressHandler>() {

@Override
public boolean matches(Object argument) {
assertTrue(AnsiProgressHandler.class.isInstance(argument));
AnsiProgressHandler handler = AnsiProgressHandler.class.cast(argument);
ProgressMessage message = new ProgressMessage();
message.status(testMessage);
try {
handler.progress(message);
} catch (DockerException e) {
fail("Unexpected error");
}
return true;
}

};

if (! newFile) {
File parent = outputFile.getParentFile();
assertTrue("Cannot create parent directory", parent.exists() || parent.mkdirs());
assertTrue("Cannot create output file", outputFile.createNewFile());
}

when(docker.build(eq(Paths.get("target/docker")), eq("busybox"), argThat(matcher))).thenReturn(StringUtils.EMPTY);

mojo.execute(docker);

verify(docker).build(eq(Paths.get("target/docker")), eq("busybox"), any(AnsiProgressHandler.class));

// Make sure output file exists and message is logged
assertFileExists(outputFileName);
byte[] encoded = Files.readAllBytes(Paths.get(outputFileName));
assertEquals(testMessage + System.lineSeparator(), new String(encoded, "UTF-8"));
}

private BuildMojo setupMojo(final File pom) throws Exception {
final MavenProject project = new ProjectStub(pom);
final MavenSession session = newMavenSession(project);
Expand Down
31 changes: 31 additions & 0 deletions src/test/resources/pom-build-log-output.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<name>Docker Maven Plugin Test Pom</name>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<build>
<plugins>
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.1-SNAPSHOT</version>
<configuration>
<!-- we have to supply values for all params because unit testing framework -->
<!-- doesn't set default values even though the are set in mojo-->
<dockerHost>http://host:2375</dockerHost>
<dockerDirectory>src/test/resources/dockerDirectory</dockerDirectory>
<imageName>busybox</imageName>
<!-- test base image is pulled -->
<logOutput>target/docker/outputDir/file-to-log-output.log</logOutput>
</configuration>
</plugin>
</plugins>
</build>
</project>