package org.jenkinsci.plugins.dockerbuildstep.action;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.command.AttachContainerCmd;
import com.github.dockerjava.api.model.Frame;
import com.github.dockerjava.core.command.AttachContainerResultCallback;
import com.google.common.base.Charsets;
import com.jcraft.jzlib.GZIPInputStream;
import hudson.console.AnnotatedLargeText;
import hudson.model.*;
import hudson.security.ACL;
import hudson.security.Permission;
import jenkins.model.Jenkins;
import org.apache.commons.io.IOUtils;
import org.apache.commons.jelly.XMLOutput;
import org.jenkinsci.plugins.dockerbuildstep.DockerBuilder;
import org.jenkinsci.plugins.dockerbuildstep.log.container.DockerLogStreamReader;
import java.io.*;
/**
* Jenkins action to add a 'Console Output' like page for the Docker container output. Container output is gathered
* using the {@link AttachContainerCmd}.
*/
public class DockerContainerConsoleAction extends TaskAction implements Serializable {
private static final long serialVersionUID = 1L;
private final AbstractBuild<?, ?> build;
private final String containerId;
private String containerName;
public DockerContainerConsoleAction(AbstractBuild<?, ?> build, String containerId) {
super();
this.build = build;
this.containerId = containerId;
}
public String getIconFileName() {
return Jenkins.RESOURCE_PATH + "/plugin/docker-build-step/icons/docker-icon-20x20.png";
}
public String getDisplayName() {
if (containerName != null && !isSingleContainerBuild()) {
return (containerName.startsWith("/") ? containerName.substring(1) : containerName) + " Output";
}
return "Container Output";
}
private boolean isSingleContainerBuild() {
return build.getActions(DockerContainerConsoleAction.class).size() == 1;
}
public String getFullDisplayName() {
return build.getFullDisplayName() + ' ' + getDisplayName();
}
public String getUrlName() {
return "dockerconsole_" + containerId;
}
public AbstractBuild<?, ?> getOwner() {
return this.build;
}
@Override
protected Permission getPermission() {
return Item.READ;
}
@Override
protected ACL getACL() {
return build.getACL();
}
public String getBuildStatusUrl() {
return build.getIconColor().getImage();
}
public void setContainerName(String containerName) {
this.containerName = containerName;
}
public File getLogFile() {
return new File(build.getRootDir(), "docker_" + containerId + ".log");
}
@Override
public AnnotatedLargeText obtainLog() {
return new AnnotatedLargeText(getLogFile(), Charsets.UTF_8, !isLogUpdated(), this);
}
public boolean isLogUpdated() {
return (workerThread != null) && build.isLogUpdated();
}
public InputStream getLogInputStream() throws IOException {
File logFile = getLogFile();
if (logFile != null && logFile.exists()) {
// Checking if a ".gz" file was return
FileInputStream fis = new FileInputStream(logFile);
if (logFile.getName().endsWith(".gz")) {
return new GZIPInputStream(fis);
} else {
return fis;
}
}
String message = "No such file: " + logFile;
return new ByteArrayInputStream(message.getBytes(Charsets.UTF_8));
}
public void writeLogTo(long offset, XMLOutput out) throws IOException {
try {
obtainLog().writeHtmlTo(offset, out.asWriter());
} catch (IOException e) {
// try to fall back to the old getLogInputStream()
// mainly to support .gz compressed files
// In this case, console annotation handling will be turned off.
InputStream input = getLogInputStream();
try {
IOUtils.copy(input, out.asWriter());
} finally {
IOUtils.closeQuietly(input);
}
}
}
public DockerContainerConsoleAction start() throws IOException {
workerThread = new DockerLogWorkerThread(getLogFile());
workerThread.start();
return this;
}
public void stop() {
workerThread.interrupt();
workerThread = null;
}
public final class DockerLogWorkerThread extends TaskThread {
protected DockerLogWorkerThread(File logFile) throws IOException {
super(DockerContainerConsoleAction.this, ListenerAndText
.forFile(logFile, DockerContainerConsoleAction.this));
}
@Override
protected void perform(final TaskListener listener) throws Exception {
DockerLogStreamReader reader = null;
OutputStreamWriter writer = null;
try {
writer = new OutputStreamWriter(listener.getLogger(), Charsets.UTF_8);
final OutputStreamWriter finalWriter = writer;
AttachContainerResultCallback callback = new AttachContainerResultCallback() {
@Override
public void onNext(Frame item) {
try {
finalWriter.append(item.toString());
finalWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
super.onNext(item);
}
@Override
public void onError(Throwable throwable) {
try {
finalWriter.append(throwable.getMessage());
finalWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
super.onError(throwable);
}
};
DockerClient client = ((DockerBuilder.DescriptorImpl) Jenkins.getInstance().getDescriptor(
DockerBuilder.class)).getDockerClient(build, null);
client.attachContainerCmd(containerId).withFollowStream(true).withStdOut(true).withStdErr(true).exec(callback).awaitCompletion();
} finally {
if (writer != null) {
writer.close();
}
if (reader != null) {
reader.close();
}
workerThread = null;
}
}
}
}