package com.github.dockerjava.core.dockerfile; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.UUID; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.filefilter.TrueFileFilter; import com.github.dockerjava.api.exception.DockerClientException; import com.github.dockerjava.core.GoLangFileMatch; import com.github.dockerjava.core.exception.GoLangFileMatchException; import com.github.dockerjava.core.util.CompressArchiveUtil; import com.github.dockerjava.core.util.FilePathUtil; import com.google.common.base.Function; import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.collect.Collections2; /** * Parse a Dockerfile. */ public class Dockerfile { public final File dockerFile; private final File baseDirectory; public Dockerfile(File dockerFile, File baseDirectory) { if (!dockerFile.exists()) { throw new IllegalStateException(String.format("Dockerfile %s does not exist", dockerFile.getAbsolutePath())); } if (!dockerFile.isFile()) { throw new IllegalStateException(String.format("Dockerfile %s is not a file", dockerFile.getAbsolutePath())); } this.dockerFile = dockerFile; if (!baseDirectory.exists()) { throw new IllegalStateException(String.format("Base directory %s does not exist", baseDirectory.getAbsolutePath())); } if (!baseDirectory.isDirectory()) { throw new IllegalStateException(String.format("Base directory %s is not a directory", baseDirectory.getAbsolutePath())); } this.baseDirectory = baseDirectory; } private static class LineTransformer implements Function<String, Optional<? extends DockerfileStatement>> { private int line = 0; @Override public Optional<? extends DockerfileStatement> apply(String input) { try { line++; return DockerfileStatement.createFromLine(input); } catch (Exception ex) { throw new DockerClientException("Error on dockerfile line " + line); } } } public Iterable<DockerfileStatement> getStatements() throws IOException { Collection<String> dockerFileContent = FileUtils.readLines(dockerFile); if (dockerFileContent.size() <= 0) { throw new DockerClientException(String.format("Dockerfile %s is empty", dockerFile)); } Collection<Optional<? extends DockerfileStatement>> optionals = Collections2.transform(dockerFileContent, new LineTransformer()); return Optional.presentInstances(optionals); } public List<String> getIgnores() throws IOException { List<String> ignores = new ArrayList<String>(); File dockerIgnoreFile = new File(baseDirectory, ".dockerignore"); if (dockerIgnoreFile.exists()) { int lineNumber = 0; List<String> dockerIgnoreFileContent = FileUtils.readLines(dockerIgnoreFile); for (String pattern : dockerIgnoreFileContent) { lineNumber++; pattern = pattern.trim(); if (pattern.isEmpty()) { continue; // skip empty lines } pattern = FilenameUtils.normalize(pattern); try { ignores.add(pattern); } catch (GoLangFileMatchException e) { throw new DockerClientException(String.format( "Invalid pattern '%s' on line %s in .dockerignore file", pattern, lineNumber)); } } } return ignores; } public ScannedResult parse() throws IOException { return new ScannedResult(); } /** * Result of scanning / parsing a docker file. */ public class ScannedResult { final List<String> ignores; final List<File> filesToAdd = new ArrayList<File>(); public InputStream buildDockerFolderTar() { return buildDockerFolderTar(baseDirectory); } public InputStream buildDockerFolderTar(File directory) { File dockerFolderTar = null; try { final String archiveNameWithOutExtension = UUID.randomUUID().toString(); dockerFolderTar = CompressArchiveUtil.archiveTARFiles(directory, filesToAdd, archiveNameWithOutExtension); long length = dockerFolderTar.length(); final FileInputStream tarInputStream = FileUtils.openInputStream(dockerFolderTar); final File tarFile = dockerFolderTar; return new InputStream() { @Override public int available() throws IOException { return tarInputStream.available(); } @Override public int read() throws IOException { return tarInputStream.read(); } @Override public int read(byte[] buff, int offset, int len) throws IOException { return tarInputStream.read(buff, offset, len); } @Override public void close() throws IOException { IOUtils.closeQuietly(tarInputStream); FileUtils.deleteQuietly(tarFile); } }; } catch (IOException ex) { FileUtils.deleteQuietly(dockerFolderTar); throw new DockerClientException("Error occurred while preparing Docker context folder.", ex); } } @Override public String toString() { return Objects.toStringHelper(this).add("ignores", ignores).add("filesToAdd", filesToAdd).toString(); } public ScannedResult() throws IOException { ignores = getIgnores(); String matchingIgnorePattern = effectiveMatchingIgnorePattern(dockerFile); if (matchingIgnorePattern != null) { throw new DockerClientException(String.format( "Dockerfile is excluded by pattern '%s' in .dockerignore file", matchingIgnorePattern)); } Collection<File> filesInBuildContext = FileUtils.listFiles(baseDirectory, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE); for (File f : filesInBuildContext) { if (effectiveMatchingIgnorePattern(f) == null) { filesToAdd.add(f); } } } /** * Returns all matching ignore patterns for the given file name. */ private List<String> matchingIgnorePatterns(String fileName) { List<String> matches = new ArrayList<String>(); int lineNumber = 0; for (String pattern : ignores) { String goLangPattern = pattern.startsWith("!") ? pattern.substring(1) : pattern; lineNumber++; try { if (GoLangFileMatch.match(goLangPattern, fileName)) { matches.add(pattern); } } catch (GoLangFileMatchException e) { throw new DockerClientException(String.format( "Invalid pattern '%s' on line %s in .dockerignore file", pattern, lineNumber)); } } return matches; } /** * Returns the matching ignore pattern for the given file or null if it should NOT be ignored. Exception rules like "!Dockerfile" * will be respected. */ private String effectiveMatchingIgnorePattern(File file) { String relativeFilename = FilePathUtil.relativize(baseDirectory, file); List<String> matchingPattern = matchingIgnorePatterns(relativeFilename); if (matchingPattern.isEmpty()) { return null; } String lastMatchingPattern = matchingPattern.get(matchingPattern.size() - 1); return !lastMatchingPattern.startsWith("!") ? lastMatchingPattern : null; } } }