diff --git a/jkube-kit/config/image/src/main/java/org/eclipse/jkube/kit/config/image/build/DockerFileBuilder.java b/jkube-kit/config/image/src/main/java/org/eclipse/jkube/kit/config/image/build/DockerFileBuilder.java index 67c1f1bbdb..341c5c275c 100644 --- a/jkube-kit/config/image/src/main/java/org/eclipse/jkube/kit/config/image/build/DockerFileBuilder.java +++ b/jkube-kit/config/image/src/main/java/org/eclipse/jkube/kit/config/image/build/DockerFileBuilder.java @@ -39,472 +39,535 @@ */ public class DockerFileBuilder { - private static final String DEFAULT_BASE_IMAGE = "busybox"; + private static final String DEFAULT_BASE_IMAGE = "busybox"; - // Base image to use as from - private String baseImage; + // Base image to use as from + private String baseImage; - // Maintainer of this image - private String maintainer; - // Workdir - private String workdir = null; + //List of stages + private List stages = new ArrayList<>(); + // Maintainer of this image + private String maintainer; - // Basedir to be export - private String basedir = "/maven"; + // Workdir + private String workdir = null; - private Arguments entryPoint; - private Arguments cmd; + // Basedir to be export + private String basedir = "/maven"; - private Boolean exportTargetDir = null; + private Arguments entryPoint; + private Arguments cmd; - // User under which the files should be added - private String assemblyUser; + private Boolean exportTargetDir = null; - // User to run as - private String user; + // User under which the files should be added + private String assemblyUser; - private HealthCheckConfiguration healthCheck; + // User to run as + private String user; - // List of files to add. Source and destination follow except that destination - // in interpreted as a relative path to the exportDir - // See also http://docs.docker.io/reference/builder/#copy - private List copyEntries = new ArrayList<>(); + private HealthCheckConfiguration healthCheck; - // list of ports to expose and environments to use - private List ports = new ArrayList<>(); + // List of files to add. Source and destination follow except that destination + // in interpreted as a relative path to the exportDir + // See also http://docs.docker.io/reference/builder/#copy + private List copyEntries = new ArrayList<>(); - // list of RUN Commands to run along with image build see issue #191 on github - private List runCmds = new ArrayList<>(); + // list of ports to expose and environments to use + private List ports = new ArrayList<>(); - // environment - private Map envEntries = new LinkedHashMap<>(); + // list of RUN Commands to run along with image build see issue #191 on github + private List runCmds = new ArrayList<>(); + + // environment + private Map envEntries = new LinkedHashMap<>(); - // image labels - private Map labels = new LinkedHashMap<>(); + // image labels + private Map labels = new LinkedHashMap<>(); - // exposed volumes - private List volumes = new ArrayList<>(); + // exposed volumes + private List volumes = new ArrayList<>(); - // whether the Dockerfile should be optimised. i.e. compressing run statements into a single statement - private boolean shouldOptimise = false; + // whether the Dockerfile should be optimised. i.e. compressing run statements into a single statement + private boolean shouldOptimise = false; - /** - * Create a DockerFile in the given directory - * @param destDir directory where to store the dockerfile - * @return the full path to the docker file - * @throws IOException if writing fails - */ - public File write(File destDir) throws IOException { - File target = new File(destDir, "Dockerfile"); - FileUtils.write(target, content(), Charset.defaultCharset()); - return target; - } - - /** - * Create a Dockerfile following the format described in the - * Docker reference manual - * - * @return the dockerfile create - */ - public String content() { - - StringBuilder b = new StringBuilder(); - - DockerFileKeyword.FROM.addTo(b, baseImage != null ? baseImage : DEFAULT_BASE_IMAGE); - if (maintainer != null) { - DockerFileKeyword.MAINTAINER.addTo(b, maintainer); - } - - addOptimisation(); - addEnv(b); - addLabels(b); - addPorts(b); - - addCopy(b); - addWorkdir(b); - addRun(b); - addVolumes(b); - - addHealthCheck(b); - - addCmd(b); - addEntryPoint(b); - - addUser(b); - - return b.toString(); - } - - private void addUser(StringBuilder b) { - if (user != null) { - DockerFileKeyword.USER.addTo(b, user); - } - } - - private void addHealthCheck(StringBuilder b) { - if (healthCheck != null) { - StringBuilder healthString = new StringBuilder(); - - switch (healthCheck.getMode()) { - case cmd: - buildOption(healthString, DockerFileOption.HEALTHCHECK_INTERVAL, healthCheck.getInterval()); - buildOption(healthString, DockerFileOption.HEALTHCHECK_TIMEOUT, healthCheck.getTimeout()); - buildOption(healthString, DockerFileOption.HEALTHCHECK_START_PERIOD, healthCheck.getStartPeriod()); - buildOption(healthString, DockerFileOption.HEALTHCHECK_RETRIES, healthCheck.getRetries()); - buildArguments(healthString, DockerFileKeyword.CMD, false, healthCheck.getCmd()); - break; - case none: - DockerFileKeyword.NONE.addTo(healthString, false); - break; - default: - throw new IllegalArgumentException("Unsupported health check mode: " + healthCheck.getMode()); - } - - DockerFileKeyword.HEALTHCHECK.addTo(b, healthString.toString()); - } - } - - private void addWorkdir(StringBuilder b) { - if (workdir != null) { - DockerFileKeyword.WORKDIR.addTo(b, workdir); - } - } - - private void addEntryPoint(StringBuilder b){ - if (entryPoint != null) { - buildArguments(b, DockerFileKeyword.ENTRYPOINT, true, entryPoint); - } - } - - private void addCmd(StringBuilder b){ - if (cmd != null) { - buildArguments(b, DockerFileKeyword.CMD, true, cmd); - } - } - - private static void buildArguments(StringBuilder b, DockerFileKeyword key, boolean newline, Arguments arguments) { - String arg; - if (arguments.getShell() != null) { - arg = arguments.getShell(); - } else { - arg = "[\"" + String.join("\",\"",arguments.getExec()) + "\"]"; - } - key.addTo(b, newline, arg); - } + /** + * Create a DockerFile in the given directory + * + * @param destDir directory where to store the dockerfile + * @return the full path to the docker file + * @throws IOException if writing fails + */ + public File write(File destDir) throws IOException { + File target = new File(destDir, "Dockerfile"); + FileUtils.write(target, content(), Charset.defaultCharset()); + return target; + } - private static void buildOption(StringBuilder b, DockerFileOption option, Object value) { - if (value != null) { - option.addTo(b, value); - } - } + /** + * Create a Dockerfile following the format described in the + * Docker reference manual + * + * @return the dockerfile create + */ + public String content() { - private void addCopy(StringBuilder b) { - if (assemblyUser != null) { - String tmpDir = createTempDir(); - addCopyEntries(b, tmpDir); - - String[] userParts = StringUtils.split(assemblyUser, ":"); - String userArg = userParts.length > 1 ? userParts[0] + ":" + userParts[1] : userParts[0]; - String chmod = "chown -R " + userArg + " " + tmpDir + " && cp -rp " + tmpDir + "/* / && rm -rf " + tmpDir; - if (userParts.length > 2) { - DockerFileKeyword.USER.addTo(b, "root"); - DockerFileKeyword.RUN.addTo(b, chmod); - DockerFileKeyword.USER.addTo(b, userParts[2]); - } else { - DockerFileKeyword.RUN.addTo(b, chmod); - } - } else { - addCopyEntries(b, ""); - } - } + StringBuilder b = new StringBuilder(); - private String createTempDir() { - return "/tmp/" + UUID.randomUUID().toString(); + DockerFileKeyword.FROM.addTo(b, baseImage != null ? baseImage : DEFAULT_BASE_IMAGE); + if (maintainer != null) { + DockerFileKeyword.MAINTAINER.addTo(b, maintainer); } - private void addCopyEntries(StringBuilder b, String topLevelDir) { - for (CopyEntry entry : copyEntries) { - String dest = topLevelDir + (basedir.equals("/") ? "" : basedir) + "/" + entry.destination; - DockerFileKeyword.COPY.addTo(b, entry.source, dest); - } - } + addOptimisation(); + addEnv(b); + addLabels(b); + addPorts(b); - private void addEnv(StringBuilder b) { - addMap(b, DockerFileKeyword.ENV, envEntries); - } + addCopy(b); + addWorkdir(b); + addRun(b); + addVolumes(b); - private void addLabels(StringBuilder b) { - addMap(b, DockerFileKeyword.LABEL, labels); - } + addHealthCheck(b); - private void addMap(StringBuilder b, DockerFileKeyword keyword, Map map) { - if (map != null && map.size() > 0) { - final String[] entries = new String[map.size()]; - int i = 0; - for (Map.Entry entry : map.entrySet()) { - entries[i++] = createKeyValue(entry.getKey(), entry.getValue()); - } - keyword.addTo(b, entries); - } - } + addCmd(b); + addEntryPoint(b); + + addUser(b); + + addStage(b); - /** - * Escape any slashes, quotes, and newlines int the value. If any escaping occurred, quote the value. - * @param key The key - * @param value The value - * @return Escaped and quoted key="value" - */ - private String createKeyValue(String key, String value) { - StringBuilder sb = new StringBuilder(); - // no quoting the key; "Keys are alphanumeric strings which may contain periods (.) and hyphens (-)" - sb.append(key).append('='); - if (value == null || value.isEmpty()) { - return sb.append("\"\"").toString(); - } - StringBuilder valBuf = new StringBuilder(); - boolean toBeQuoted = false; - for (int i = 0; i < value.length(); ++i) { - char c = value.charAt(i); - switch (c) { - case '"': - case '\n': - case '\\': - // escape the character - valBuf.append('\\'); - case ' ': - // space needs quotes, too - toBeQuoted = true; - default: - // always append - valBuf.append(c); - } - } - if (toBeQuoted) { - // need to keep quotes - sb.append('"').append(valBuf.toString()).append('"'); - } else { - sb.append(value); - } - return sb.toString(); - } - - private void addPorts(StringBuilder b) { - if (!ports.isEmpty()) { - String[] portsS = new String[ports.size()]; - int i = 0; - for(String port : ports) { - portsS[i++] = validatePortExposure(port); - } - DockerFileKeyword.EXPOSE.addTo(b, portsS); - } - } - - private String validatePortExposure(String input) { - try { - Matcher matcher = Pattern.compile("(.*?)(?:/(tcp|udp))?$", Pattern.CASE_INSENSITIVE).matcher(input); - // Matches always. If there is a tcp/udp protocol, should end up in the second group - // and get factored out. If it's something invalid, it should get stuck to the first group. - matcher.matches(); - Integer.valueOf(matcher.group(1)); - return input.toLowerCase(); - } catch (NumberFormatException exp) { - throw new IllegalArgumentException("\nInvalid port mapping '" + input + "'\n" + - "Required format: '(/tcp|udp)'\n" + - "See the reference manual for more details"); - } - } - - private void addOptimisation() { - if (runCmds != null && !runCmds.isEmpty() && shouldOptimise) { - String optimisedRunCmd = StringUtils.join(runCmds.iterator(), " && "); - runCmds.clear(); - runCmds.add(optimisedRunCmd); - } - } - - private void addRun(StringBuilder b) { - for (String run : runCmds) { - DockerFileKeyword.RUN.addTo(b, run); - } - } - - private void addVolumes(StringBuilder b) { - if (exportTargetDir != null ? exportTargetDir : baseImage == null) { - addVolume(b, basedir); - } - - for (String volume : volumes) { - addVolume(b, volume); - } - } - - private void addVolume(StringBuilder buffer, String volume) { - while (volume.endsWith("/")) { - volume = volume.substring(0, volume.length() - 1); - } - // don't export '/' - if (volume.length() > 0) { - DockerFileKeyword.VOLUME.addTo(buffer, "[\"" + volume + "\"]"); - } - } - - // ========================================================================== - // Builder stuff .... - public DockerFileBuilder() {} - - public DockerFileBuilder baseImage(String baseImage) { - if (baseImage != null) { - this.baseImage = baseImage; - } - return this; - } - - public DockerFileBuilder maintainer(String maintainer) { - this.maintainer = maintainer; - return this; - } - - public DockerFileBuilder workdir(String workdir) { - this.workdir = workdir; - return this; - } - - public DockerFileBuilder basedir(String dir) { - if (dir != null) { - if (!dir.startsWith("/")) { - throw new IllegalArgumentException("'basedir' must be an *nix absolute path starting with / (and not " + - "'" + basedir + "')"); - } - basedir = dir; - } - return this; - } - - public DockerFileBuilder cmd(Arguments cmd) { - this.cmd = cmd; - return this; - } - - public DockerFileBuilder entryPoint(Arguments entryPoint) { - this.entryPoint = entryPoint; - return this; - } - - public DockerFileBuilder assemblyUser(String assemblyUser) { - this.assemblyUser = assemblyUser; - return this; - } - - public DockerFileBuilder user(String user) { - this.user = user; - return this; - } - - public DockerFileBuilder healthCheck(HealthCheckConfiguration healthCheck) { - this.healthCheck = healthCheck; - return this; - } - - public DockerFileBuilder add(String source, String destination) { - this.copyEntries.add(new CopyEntry(source, destination)); - return this; - } - - public DockerFileBuilder expose(List ports) { - if (ports != null) { - this.ports.addAll(ports); - } - return this; - } - - /** - * Adds the RUN Commands within the build image section - * @param runCmds run commands - * @return DockerFileBuilder docker file builder - */ - public DockerFileBuilder run(List runCmds) { - Optional.ofNullable(runCmds).orElse(Collections.emptyList()).stream() - .filter(StringUtils::isNotEmpty) - .forEach(this.runCmds::add); - return this; - } - - public DockerFileBuilder exportTargetDir(Boolean exportTargetDir) { - this.exportTargetDir = exportTargetDir; - return this; - } - - public DockerFileBuilder env(Map values) { - if (values != null) { - this.envEntries.putAll(values); - validateMap(envEntries); - } - return this; - } - - public DockerFileBuilder labels(Map values) { - if (values != null) { - this.labels.putAll(values); - } - return this; - } - - public DockerFileBuilder volumes(List volumes) { - if (volumes != null) { - this.volumes.addAll(volumes); - } - return this; - } - - public DockerFileBuilder optimise() { - this.shouldOptimise = true; - return this; - } - - private void validateMap(Map env) { - for (Map.Entry entry : env.entrySet()) { - if (entry.getValue() == null || entry.getValue().length() == 0) { - throw new IllegalArgumentException("Environment variable '" + - entry.getKey() + "' must not be null or empty if building an image"); - } - } - } - - // All entries required, destination is relative to exportDir - private static final class CopyEntry { - private String source; - private String destination; - - private CopyEntry(String src, String dest) { - source = src; - - // Strip leading slashes - destination = dest; - - // squeeze slashes - while (destination.startsWith("/")) { - destination = destination.substring(1); - } - } - } + + return b.toString(); + } - // =========================================================================== - - public static List extractLines(File dockerFile, - String keyword, - Function interpolator) throws IOException { - List ret = new ArrayList<>(); - try (BufferedReader reader = new BufferedReader(new FileReader(dockerFile))) { - String line; - while ((line = reader.readLine()) != null) { - String lineInterpolated = interpolator.apply(line); - String[] lineParts = lineInterpolated.split("\\s+"); - if (lineParts.length > 0 && lineParts[0].equalsIgnoreCase(keyword)) { - ret.add(lineParts); - } - } + private void addStage(StringBuilder b) { + + if (this.stages != null) { + + this.stages.forEach(f -> { + b.append("\n"); + b.append( f.content()); + DockerFileKeyword.STAGE.addTo(b, false); + }); + + } + } + + private void addUser(StringBuilder b) { + if (user != null) { + DockerFileKeyword.USER.addTo(b, user); + } + } + + private void addHealthCheck(StringBuilder b) { + if (healthCheck != null) { + StringBuilder healthString = new StringBuilder(); + + switch (healthCheck.getMode()) { + case cmd: + buildOption(healthString, DockerFileOption.HEALTHCHECK_INTERVAL, healthCheck.getInterval()); + buildOption(healthString, DockerFileOption.HEALTHCHECK_TIMEOUT, healthCheck.getTimeout()); + buildOption(healthString, DockerFileOption.HEALTHCHECK_START_PERIOD, healthCheck.getStartPeriod()); + buildOption(healthString, DockerFileOption.HEALTHCHECK_RETRIES, healthCheck.getRetries()); + buildArguments(healthString, DockerFileKeyword.CMD, false, healthCheck.getCmd()); + break; + case none: + DockerFileKeyword.NONE.addTo(healthString, false); + break; + default: + throw new IllegalArgumentException("Unsupported health check mode: " + healthCheck.getMode()); + } + + DockerFileKeyword.HEALTHCHECK.addTo(b, healthString.toString()); + } + } + + private void addWorkdir(StringBuilder b) { + if (workdir != null) { + DockerFileKeyword.WORKDIR.addTo(b, workdir); + } + } + + private void addEntryPoint(StringBuilder b) { + if (entryPoint != null) { + buildArguments(b, DockerFileKeyword.ENTRYPOINT, true, entryPoint); + } + } + + private void addCmd(StringBuilder b) { + if (cmd != null) { + buildArguments(b, DockerFileKeyword.CMD, true, cmd); + } + } + + private static void buildArguments(StringBuilder b, DockerFileKeyword key, boolean newline, Arguments arguments) { + String arg; + if (arguments.getShell() != null) { + arg = arguments.getShell(); + } else { + arg = "[\"" + String.join("\",\"", arguments.getExec()) + "\"]"; + } + key.addTo(b, newline, arg); + } + + private static void buildOption(StringBuilder b, DockerFileOption option, Object value) { + if (value != null) { + option.addTo(b, value); + } + } + + private void addCopy(StringBuilder b) { + if (assemblyUser != null) { + String tmpDir = createTempDir(); + addCopyEntries(b, tmpDir); + + String[] userParts = StringUtils.split(assemblyUser, ":"); + String userArg = userParts.length > 1 ? userParts[0] + ":" + userParts[1] : userParts[0]; + String chmod = "chown -R " + userArg + " " + tmpDir + " && cp -rp " + tmpDir + "/* / && rm -rf " + tmpDir; + if (userParts.length > 2) { + DockerFileKeyword.USER.addTo(b, "root"); + DockerFileKeyword.RUN.addTo(b, chmod); + DockerFileKeyword.USER.addTo(b, userParts[2]); + } else { + DockerFileKeyword.RUN.addTo(b, chmod); + } + } else { + addCopyEntries(b, ""); + } + } + + private String createTempDir() { + return "/tmp/" + UUID.randomUUID().toString(); + } + + private void addCopyEntries(StringBuilder b, String topLevelDir) { + for (CopyEntry entry : copyEntries) { + if (entry.stage!=(null)){ + + entry.source = "--from=" + entry.stage + " " +entry.source ; + } + String dest = topLevelDir + (basedir.equals("/") ? "" : basedir) + "/" + entry.destination; + DockerFileKeyword.COPY.addTo(b, entry.source, dest); + } + } + + private void addEnv(StringBuilder b) { + addMap(b, DockerFileKeyword.ENV, envEntries); + } + + private void addLabels(StringBuilder b) { + addMap(b, DockerFileKeyword.LABEL, labels); + } + + private void addMap(StringBuilder b, DockerFileKeyword keyword, Map map) { + if (map != null && map.size() > 0) { + final String[] entries = new String[map.size()]; + int i = 0; + for (Map.Entry entry : map.entrySet()) { + entries[i++] = createKeyValue(entry.getKey(), entry.getValue()); + } + keyword.addTo(b, entries); + } + } + + /** + * Escape any slashes, quotes, and newlines int the value. If any escaping occurred, quote the value. + * + * @param key The key + * @param value The value + * @return Escaped and quoted key="value" + */ + private String createKeyValue(String key, String value) { + StringBuilder sb = new StringBuilder(); + // no quoting the key; "Keys are alphanumeric strings which may contain periods (.) and hyphens (-)" + sb.append(key).append('='); + if (value == null || value.isEmpty()) { + return sb.append("\"\"").toString(); + } + StringBuilder valBuf = new StringBuilder(); + boolean toBeQuoted = false; + for (int i = 0; i < value.length(); ++i) { + char c = value.charAt(i); + switch (c) { + case '"': + case '\n': + case '\\': + // escape the character + valBuf.append('\\'); + case ' ': + // space needs quotes, too + toBeQuoted = true; + default: + // always append + valBuf.append(c); + } + } + if (toBeQuoted) { + // need to keep quotes + sb.append('"').append(valBuf.toString()).append('"'); + } else { + sb.append(value); + } + return sb.toString(); + } + + private void addPorts(StringBuilder b) { + if (!ports.isEmpty()) { + String[] portsS = new String[ports.size()]; + int i = 0; + for (String port : ports) { + portsS[i++] = validatePortExposure(port); + } + DockerFileKeyword.EXPOSE.addTo(b, portsS); + } + } + + private String validatePortExposure(String input) { + try { + Matcher matcher = Pattern.compile("(.*?)(?:/(tcp|udp))?$", Pattern.CASE_INSENSITIVE).matcher(input); + // Matches always. If there is a tcp/udp protocol, should end up in the second group + // and get factored out. If it's something invalid, it should get stuck to the first group. + matcher.matches(); + Integer.valueOf(matcher.group(1)); + return input.toLowerCase(); + } catch (NumberFormatException exp) { + throw new IllegalArgumentException("\nInvalid port mapping '" + input + "'\n" + + "Required format: '(/tcp|udp)'\n" + + "See the reference manual for more details"); + } + } + + private void addOptimisation() { + if (runCmds != null && !runCmds.isEmpty() && shouldOptimise) { + String optimisedRunCmd = StringUtils.join(runCmds.iterator(), " && "); + runCmds.clear(); + runCmds.add(optimisedRunCmd); + } + } + + private void addRun(StringBuilder b) { + for (String run : runCmds) { + DockerFileKeyword.RUN.addTo(b, run); + } + } + + private void addVolumes(StringBuilder b) { + if (exportTargetDir != null ? exportTargetDir : baseImage == null) { + addVolume(b, basedir); + } + + for (String volume : volumes) { + addVolume(b, volume); + } + } + + private void addVolume(StringBuilder buffer, String volume) { + while (volume.endsWith("/")) { + volume = volume.substring(0, volume.length() - 1); + } + // don't export '/' + if (volume.length() > 0) { + DockerFileKeyword.VOLUME.addTo(buffer, "[\"" + volume + "\"]"); + } + } + + // ========================================================================== + // Builder stuff .... + public DockerFileBuilder() {} + + public DockerFileBuilder alias(String alias) { + if (alias != null) { + + this.baseImage = this.baseImage + " AS " + alias; + } + return this; + } + + public DockerFileBuilder addStages(DockerFileBuilder dockerStage) { + if (dockerStage != null) { + this.stages.add(dockerStage); + + } + return this; + } + + public DockerFileBuilder baseImage(String baseImage) { + if (baseImage != null) { + this.baseImage = baseImage; + } + return this; + } + + public DockerFileBuilder maintainer(String maintainer) { + this.maintainer = maintainer; + return this; + } + + public DockerFileBuilder workdir(String workdir) { + this.workdir = workdir; + return this; + } + + public DockerFileBuilder basedir(String dir) { + if (dir != null) { + if (!dir.startsWith("/")) { + throw new IllegalArgumentException("'basedir' must be an *nix absolute path starting with / (and not " + + "'" + basedir + "')"); + } + basedir = dir; + } + return this; + } + + public DockerFileBuilder cmd(Arguments cmd) { + this.cmd = cmd; + return this; + } + + public DockerFileBuilder entryPoint(Arguments entryPoint) { + this.entryPoint = entryPoint; + return this; + } + + public DockerFileBuilder assemblyUser(String assemblyUser) { + this.assemblyUser = assemblyUser; + return this; + } + + public DockerFileBuilder user(String user) { + this.user = user; + return this; + } + + public DockerFileBuilder healthCheck(HealthCheckConfiguration healthCheck) { + this.healthCheck = healthCheck; + return this; + } + + public DockerFileBuilder add(String source, String destination) { + this.copyEntries.add(new CopyEntry(source, destination)); + return this; + } + + public DockerFileBuilder addFromStage(String source, String destination,String stage) { + if (stage!=null) + this.copyEntries.add(new CopyEntry(source, destination, stage)); + return this; + } + + public DockerFileBuilder expose(List ports) { + if (ports != null) { + this.ports.addAll(ports); + } + return this; + } + + /** + * Adds the RUN Commands within the build image section + * + * @param runCmds run commands + * @return DockerFileBuilder docker file builder + */ + public DockerFileBuilder run(List runCmds) { + Optional.ofNullable(runCmds).orElse(Collections.emptyList()).stream() + .filter(StringUtils::isNotEmpty) + .forEach(this.runCmds::add); + return this; + } + + public DockerFileBuilder exportTargetDir(Boolean exportTargetDir) { + this.exportTargetDir = exportTargetDir; + return this; + } + + public DockerFileBuilder env(Map values) { + if (values != null) { + this.envEntries.putAll(values); + validateMap(envEntries); + } + return this; + } + + public DockerFileBuilder labels(Map values) { + if (values != null) { + this.labels.putAll(values); + } + return this; + } + + public DockerFileBuilder volumes(List volumes) { + if (volumes != null) { + this.volumes.addAll(volumes); + } + return this; + } + + public DockerFileBuilder optimise() { + this.shouldOptimise = true; + return this; + } + + private void validateMap(Map env) { + for (Map.Entry entry : env.entrySet()) { + if (entry.getValue() == null || entry.getValue().length() == 0) { + throw new IllegalArgumentException("Environment variable '" + + entry.getKey() + "' must not be null or empty if building an image"); + } + } + } + + // All entries required, destination is relative to exportDir + private static final class CopyEntry { + private String source; + private String destination; + private String stage ; + + private CopyEntry(String src, String dest) { + source = src; + + // Strip leading slashes + destination = dest; + + // squeeze slashes + while (destination.startsWith("/")) { + destination = destination.substring(1); + } + } + + private CopyEntry(String src, String dest,String stage) { + source = src; + + // Strip leading slashes + destination = dest; + if (stage!=null) { + this.stage = stage ; + } + // squeeze slashes + while (destination.startsWith("/")) { + destination = destination.substring(1); + } + } + } + + // =========================================================================== + + public static List extractLines(File dockerFile, + String keyword, + Function interpolator) throws IOException { + List ret = new ArrayList<>(); + try (BufferedReader reader = new BufferedReader(new FileReader(dockerFile))) { + String line; + while ((line = reader.readLine()) != null) { + String lineInterpolated = interpolator.apply(line); + String[] lineParts = lineInterpolated.split("\\s+"); + if (lineParts.length > 0 && lineParts[0].equalsIgnoreCase(keyword)) { + ret.add(lineParts); } - return ret; + } } + return ret; + } } diff --git a/jkube-kit/config/image/src/main/java/org/eclipse/jkube/kit/config/image/build/DockerFileKeyword.java b/jkube-kit/config/image/src/main/java/org/eclipse/jkube/kit/config/image/build/DockerFileKeyword.java index 23d7a463a0..a30fe5a247 100644 --- a/jkube-kit/config/image/src/main/java/org/eclipse/jkube/kit/config/image/build/DockerFileKeyword.java +++ b/jkube-kit/config/image/src/main/java/org/eclipse/jkube/kit/config/image/build/DockerFileKeyword.java @@ -14,53 +14,57 @@ package org.eclipse.jkube.kit.config.image.build; /** - * Fields for a dockerfile + * Fields for a dockerfile + * * @author Paris Apostolopoulos ;<javapapo@mac.com;> * @author Christian Fischer ;<sw-dev@computerlyrik.de;> * @since 13.06.05 */ -public enum DockerFileKeyword -{ - MAINTAINER, - EXPOSE, - FROM, - RUN, - WORKDIR, - ENTRYPOINT, - CMD, - USER, - ENV, - ARG, - LABEL, - COPY, - VOLUME, - HEALTHCHECK, - NONE; +public enum DockerFileKeyword { + MAINTAINER, + EXPOSE, + FROM, + RUN, + WORKDIR, + ENTRYPOINT, + CMD, + USER, + ENV, + ARG, + LABEL, + COPY, + VOLUME, + HEALTHCHECK, + STAGE, + NONE; - /** - * Append this keyword + optionally some args to a {@link StringBuilder} plus a trailing newline. - * - * @param sb stringbuilder to add to - * @param args args added (space separated) - */ - public void addTo(StringBuilder sb, String... args) { - addTo(sb, true, args); - } + /** + * Append this keyword + optionally some args to a {@link StringBuilder} plus a trailing newline. + * + * @param sb stringbuilder to add to + * @param args args added (space separated) + */ + public void addTo(StringBuilder sb, String... args) { + addTo(sb, true, args); + } - /** - * Append this keyword + optionally some args to a {@link StringBuilder} and a optional trailing newline. - * - * @param sb stringbuilder to add to - * @param newline flag indicating whether a new line should be added - * @param args args added (space separated) - */ - public void addTo(StringBuilder sb, boolean newline, String... args) { - sb.append(name()); - for (String arg : args) { - sb.append(" ").append(arg); - } - if (newline) { - sb.append("\n"); - } + /** + * Append this keyword + optionally some args to a {@link StringBuilder} and a optional trailing newline. + * + * @param sb stringbuilder to add to + * @param newline flag indicating whether a new line should be added + * @param args args added (space separated) + */ + public void addTo(StringBuilder sb, boolean newline, String... args) { + if (!name().equals("STAGE")) { + sb.append(name()); + } + for (String arg : args) { + sb.append(" ").append(arg); } + if (newline) { + sb.append("\n"); + } + } + } diff --git a/jkube-kit/config/image/src/test/java/org/eclipse/jkube/kit/config/image/build/DockerFileBuilderTest.java b/jkube-kit/config/image/src/test/java/org/eclipse/jkube/kit/config/image/build/DockerFileBuilderTest.java index b2b366bd23..8aa343a3d7 100644 --- a/jkube-kit/config/image/src/test/java/org/eclipse/jkube/kit/config/image/build/DockerFileBuilderTest.java +++ b/jkube-kit/config/image/src/test/java/org/eclipse/jkube/kit/config/image/build/DockerFileBuilderTest.java @@ -25,6 +25,7 @@ import java.util.regex.Pattern; import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -32,268 +33,312 @@ class DockerFileBuilderTest { - @Test - void testBuildDockerFile() throws Exception { - Arguments a = Arguments.builder().execArgument("c1").execArgument("c2").build(); - String dockerfileContent = new DockerFileBuilder().add("/src", "/dest") - .baseImage("image") - .cmd(a) - .env(Collections.singletonMap("foo", "bar")) - .basedir("/export") - .expose(Collections.singletonList("8080")) - .maintainer("maintainer@example.com") - .workdir("/tmp") - .labels(Collections.singletonMap("com.acme.foobar", "How are \"you\" ?")) - .volumes(Collections.singletonList("/vol1")) - .run(Arrays.asList("echo something", "echo second")) - .content(); - String expected = loadFile("docker/Dockerfile.test"); - assertThat(stripCR(dockerfileContent)).isEqualTo(expected); - } + @Test + void testBuildDockerFile() throws Exception { + Arguments a = Arguments.builder().execArgument("c1").execArgument("c2").build(); + String dockerfileContent = new DockerFileBuilder().add("/src", "/dest") + .baseImage("image") + .cmd(a) + .env(Collections.singletonMap("foo", "bar")) + .basedir("/export") + .expose(Collections.singletonList("8080")) + .maintainer("maintainer@example.com") + .workdir("/tmp") + .labels(Collections.singletonMap("com.acme.foobar", "How are \"you\" ?")) + .volumes(Collections.singletonList("/vol1")) + .run(Arrays.asList("echo something", "echo second")) + .content(); + String expected = loadFile("docker/Dockerfile.test"); + assertThat(stripCR(dockerfileContent)).isEqualTo(expected); + } - @Test - void testBuildDockerFileMultilineLabel() throws Exception { - Arguments a = Arguments.builder().execArgument("c1").execArgument("c2").build(); - String dockerfileContent = new DockerFileBuilder() - .add("/src", "/dest") - .baseImage("image") - .cmd(a) - .labels(new LinkedHashMap() {{ - put("key","unquoted"); - put("flag",""); - put("with_space","1.fc nuremberg"); - put("some-json","{\n \"key\": \"value\"\n}\n"); - }}) - .content(); - String expected = loadFile("docker/Dockerfile.multiline_label.test"); - assertThat(stripCR(dockerfileContent)).isEqualTo(expected); - } + @Test + void testBuildDockerFileMultilineLabel() throws Exception { + Arguments a = Arguments.builder().execArgument("c1").execArgument("c2").build(); + String dockerfileContent = new DockerFileBuilder() + .add("/src", "/dest") + .baseImage("image") + .cmd(a) + .labels(new LinkedHashMap() { + { + put("key", "unquoted"); + put("flag", ""); + put("with_space", "1.fc nuremberg"); + put("some-json", "{\n \"key\": \"value\"\n}\n"); + } + }) + .content(); + String expected = loadFile("docker/Dockerfile.multiline_label.test"); + assertThat(stripCR(dockerfileContent)).isEqualTo(expected); + } - @Test - void testBuildLabelWithSpace() { - String dockerfileContent = new DockerFileBuilder() - .labels(Collections.singletonMap("key", "label with space")) - .content(); - assertThat(stripCR(dockerfileContent)).contains("LABEL key=\"label with space\""); - } + @Test + void testBuildLabelWithSpace() { + String dockerfileContent = new DockerFileBuilder() + .labels(Collections.singletonMap("key", "label with space")) + .content(); + assertThat(stripCR(dockerfileContent)).contains("LABEL key=\"label with space\""); + } - @Test - void testBuildDockerFileUDPPort() throws Exception { - Arguments a = Arguments.builder().execArgument("c1").execArgument("c2").build(); - String dockerfileContent = new DockerFileBuilder().add("/src", "/dest") - .baseImage("image") - .cmd(a) - .basedir("/export") - .expose(Collections.singletonList("8080/udp")) - .maintainer("maintainer@example.com") - .workdir("/tmp") - .volumes(Collections.singletonList("/vol1")) - .run(Arrays.asList("echo something", "echo second")) - .content(); - String expected = loadFile("docker/Dockerfile_udp.test"); - assertThat(stripCR(dockerfileContent)).isEqualTo(expected); - } + @Test + void testBuildDockerFileUDPPort() throws Exception { + Arguments a = Arguments.builder().execArgument("c1").execArgument("c2").build(); + String dockerfileContent = new DockerFileBuilder().add("/src", "/dest") + .baseImage("image") + .cmd(a) + .basedir("/export") + .expose(Collections.singletonList("8080/udp")) + .maintainer("maintainer@example.com") + .workdir("/tmp") + .volumes(Collections.singletonList("/vol1")) + .run(Arrays.asList("echo something", "echo second")) + .content(); + String expected = loadFile("docker/Dockerfile_udp.test"); + assertThat(stripCR(dockerfileContent)).isEqualTo(expected); + } - @Test - void testBuildDockerFileExplicitTCPPort() throws Exception { - Arguments a = Arguments.builder().execArgument("c1").execArgument("c2").build(); - String dockerfileContent = new DockerFileBuilder().add("/src", "/dest") - .baseImage("image") - .cmd(a) - .basedir("/export") - .expose(Collections.singletonList("8080/tcp")) - .maintainer("maintainer@example.com") - .workdir("/tmp") - .volumes(Collections.singletonList("/vol1")) - .run(Arrays.asList("echo something", "echo second")) - .content(); - String expected = loadFile("docker/Dockerfile_tcp.test"); - assertThat(stripCR(dockerfileContent)).isEqualTo(expected); - } + @Test + void testBuildDockerFileExplicitTCPPort() throws Exception { + Arguments a = Arguments.builder().execArgument("c1").execArgument("c2").build(); + String dockerfileContent = new DockerFileBuilder().add("/src", "/dest") + .baseImage("image") + .cmd(a) + .basedir("/export") + .expose(Collections.singletonList("8080/tcp")) + .maintainer("maintainer@example.com") + .workdir("/tmp") + .volumes(Collections.singletonList("/vol1")) + .run(Arrays.asList("echo something", "echo second")) + .content(); + String expected = loadFile("docker/Dockerfile_tcp.test"); + assertThat(stripCR(dockerfileContent)).isEqualTo(expected); + } - @Test - void testBuildDockerFileBadPort() { - Arguments a = Arguments.builder().execArgument("c1").execArgument("c2").build(); - DockerFileBuilder fileBuilder = new DockerFileBuilder().add("/src", "/dest") - .baseImage("image") - .cmd(a) - .env(Collections.singletonMap("foo", "bar")) - .basedir("/export") - .expose(Collections.singletonList("8080aaa/udp")) - .maintainer("maintainer@example.com") - .workdir("/tmp") - .labels(Collections.singletonMap("com.acme.foobar", "How are \"you\" ?")) - .volumes(Collections.singletonList("/vol1")) - .run(Arrays.asList("echo something", "echo second")); - assertThatIllegalArgumentException().isThrownBy(fileBuilder::content); - } + @Test + void testBuildDockerFileBadPort() { + Arguments a = Arguments.builder().execArgument("c1").execArgument("c2").build(); + DockerFileBuilder fileBuilder = new DockerFileBuilder().add("/src", "/dest") + .baseImage("image") + .cmd(a) + .env(Collections.singletonMap("foo", "bar")) + .basedir("/export") + .expose(Collections.singletonList("8080aaa/udp")) + .maintainer("maintainer@example.com") + .workdir("/tmp") + .labels(Collections.singletonMap("com.acme.foobar", "How are \"you\" ?")) + .volumes(Collections.singletonList("/vol1")) + .run(Arrays.asList("echo something", "echo second")); + assertThatIllegalArgumentException().isThrownBy(fileBuilder::content); + } - @Test - void testBuildDockerFileBadProtocol() { - Arguments a = Arguments.builder().execArgument("c1").execArgument("c2").build(); - DockerFileBuilder fileBuilder = new DockerFileBuilder().add("/src", "/dest") - .baseImage("image") - .cmd(a) - .env(Collections.singletonMap("foo", "bar")) - .basedir("/export") - .expose(Collections.singletonList("8080/bogusdatagram")) - .maintainer("maintainer@example.com") - .workdir("/tmp") - .labels(Collections.singletonMap("com.acme.foobar", "How are \"you\" ?")) - .volumes(Collections.singletonList("/vol1")) - .run(Arrays.asList("echo something", "echo second")); - assertThatIllegalArgumentException().isThrownBy(fileBuilder::content); - } + @Test + void testBuildDockerFileBadProtocol() { + Arguments a = Arguments.builder().execArgument("c1").execArgument("c2").build(); + DockerFileBuilder fileBuilder = new DockerFileBuilder().add("/src", "/dest") + .baseImage("image") + .cmd(a) + .env(Collections.singletonMap("foo", "bar")) + .basedir("/export") + .expose(Collections.singletonList("8080/bogusdatagram")) + .maintainer("maintainer@example.com") + .workdir("/tmp") + .labels(Collections.singletonMap("com.acme.foobar", "How are \"you\" ?")) + .volumes(Collections.singletonList("/vol1")) + .run(Arrays.asList("echo something", "echo second")); + assertThatIllegalArgumentException().isThrownBy(fileBuilder::content); + } - @Test - void testDockerFileOptimisation() throws Exception { - Arguments a = Arguments.builder().execArgument("c1").execArgument("c2").build(); - String dockerfileContent = new DockerFileBuilder().add("/src", "/dest") - .baseImage("image") - .cmd(a) - .env(Collections.singletonMap("foo", "bar")) - .basedir("/export") - .expose(Collections.singletonList("8080")) - .maintainer("maintainer@example.com") - .workdir("/tmp") - .labels(Collections.singletonMap("com.acme.foobar", "How are \"you\" ?")) - .volumes(Collections.singletonList("/vol1")) - .run(Arrays.asList("echo something", "echo second", "echo third", "echo fourth", "echo fifth")) - .optimise() - .content(); - String expected = loadFile("docker/Dockerfile_optimised.test"); - assertThat(stripCR(dockerfileContent)).isEqualTo(expected); - } + @Test + void testDockerFileOptimisation() throws Exception { + Arguments a = Arguments.builder().execArgument("c1").execArgument("c2").build(); + String dockerfileContent = new DockerFileBuilder().add("/src", "/dest") + .baseImage("image") + .cmd(a) + .env(Collections.singletonMap("foo", "bar")) + .basedir("/export") + .expose(Collections.singletonList("8080")) + .maintainer("maintainer@example.com") + .workdir("/tmp") + .labels(Collections.singletonMap("com.acme.foobar", "How are \"you\" ?")) + .volumes(Collections.singletonList("/vol1")) + .run(Arrays.asList("echo something", "echo second", "echo third", "echo fourth", "echo fifth")) + .optimise() + .content(); + String expected = loadFile("docker/Dockerfile_optimised.test"); + assertThat(stripCR(dockerfileContent)).isEqualTo(expected); + } - @Test - void testMaintainer() { - String dockerfileContent = new DockerFileBuilder().maintainer("maintainer@example.com").content(); - assertThat(dockerfileToMap(dockerfileContent)).containsEntry("MAINTAINER", "maintainer@example.com"); - } + @Test + void testMaintainer() { + String dockerfileContent = new DockerFileBuilder().maintainer("maintainer@example.com").content(); + assertThat(dockerfileToMap(dockerfileContent)).containsEntry("MAINTAINER", "maintainer@example.com"); + } - @Test - void testOptimise() { - String dockerfileContent = new DockerFileBuilder().optimise().run(Arrays.asList("echo something", "echo two")).content(); - assertThat(dockerfileToMap(dockerfileContent)).containsEntry("RUN", "echo something && echo two"); - } + @Test + void testOptimise() { + String dockerfileContent = new DockerFileBuilder().optimise().run(Arrays.asList("echo something", "echo two")).content(); + assertThat(dockerfileToMap(dockerfileContent)).containsEntry("RUN", "echo something && echo two"); + } - @Test - void testOptimiseOnEmptyRunCommandListDoesNotThrowException() { - final String result = new DockerFileBuilder().optimise().content(); - assertThat(result).isNotNull(); - } + @Test + void testOptimiseOnEmptyRunCommandListDoesNotThrowException() { + final String result = new DockerFileBuilder().optimise().content(); + assertThat(result).isNotNull(); + } - @Test - void testEntryPointShell() { - Arguments a = Arguments.builder().shell("java -jar /my-app-1.1.1.jar server").build(); - String dockerfileContent = new DockerFileBuilder().entryPoint(a).content(); - assertThat(dockerfileToMap(dockerfileContent)).containsEntry("ENTRYPOINT", "java -jar /my-app-1.1.1.jar server"); - } + @Test + void testEntryPointShell() { + Arguments a = Arguments.builder().shell("java -jar /my-app-1.1.1.jar server").build(); + String dockerfileContent = new DockerFileBuilder().entryPoint(a).content(); + assertThat(dockerfileToMap(dockerfileContent)).containsEntry("ENTRYPOINT", "java -jar /my-app-1.1.1.jar server"); + } - @Test - void testEntryPointParams() { - Arguments a = Arguments.builder().execArgument("java").execArgument("-jar").execArgument("/my-app-1.1.1.jar").execArgument("server").build(); - String dockerfileContent = new DockerFileBuilder().entryPoint(a).content(); - assertThat(dockerfileToMap(dockerfileContent)).containsEntry("ENTRYPOINT", "[\"java\",\"-jar\",\"/my-app-1.1.1.jar\",\"server\"]"); - } + @Test + void testEntryPointParams() { + Arguments a = Arguments.builder().execArgument("java").execArgument("-jar").execArgument("/my-app-1.1.1.jar") + .execArgument("server").build(); + String dockerfileContent = new DockerFileBuilder().entryPoint(a).content(); + assertThat(dockerfileToMap(dockerfileContent)).containsEntry("ENTRYPOINT", + "[\"java\",\"-jar\",\"/my-app-1.1.1.jar\",\"server\"]"); + } - @Test - void testHealthCheckCmdParams() { - HealthCheckConfiguration hc = HealthCheckConfiguration.builder() - .cmd(Arguments.builder().shell("echo hello").build()) - .interval("5s").timeout("3s") - .startPeriod("30s") - .retries(4) - .build(); - String dockerfileContent = new DockerFileBuilder().healthCheck(hc).content(); - assertThat(dockerfileToMap(dockerfileContent)).containsEntry("HEALTHCHECK", "--interval=5s --timeout=3s --start-period=30s --retries=4 CMD echo hello"); - } + @Test + void testHealthCheckCmdParams() { + HealthCheckConfiguration hc = HealthCheckConfiguration.builder() + .cmd(Arguments.builder().shell("echo hello").build()) + .interval("5s").timeout("3s") + .startPeriod("30s") + .retries(4) + .build(); + String dockerfileContent = new DockerFileBuilder().healthCheck(hc).content(); + assertThat(dockerfileToMap(dockerfileContent)).containsEntry("HEALTHCHECK", + "--interval=5s --timeout=3s --start-period=30s --retries=4 CMD echo hello"); + } - @Test - void testHealthCheckNone() { - HealthCheckConfiguration hc = HealthCheckConfiguration.builder().mode(HealthCheckMode.none).build(); - String dockerfileContent = new DockerFileBuilder().healthCheck(hc).content(); - assertThat(dockerfileToMap(dockerfileContent)).containsEntry("HEALTHCHECK", "NONE"); - } + @Test + void testHealthCheckNone() { + HealthCheckConfiguration hc = HealthCheckConfiguration.builder().mode(HealthCheckMode.none).build(); + String dockerfileContent = new DockerFileBuilder().healthCheck(hc).content(); + assertThat(dockerfileToMap(dockerfileContent)).containsEntry("HEALTHCHECK", "NONE"); + } - @Test - void testNoRootExport() { - assertThat(new DockerFileBuilder().add("/src", "/dest").basedir("/").content()).doesNotContain("VOLUME"); - } + @Test + void testNoRootExport() { + assertThat(new DockerFileBuilder().add("/src", "/dest").basedir("/").content()).doesNotContain("VOLUME"); + } - @Test - void illegalNonAbsoluteBaseDir() { - assertThatIllegalArgumentException().isThrownBy(() -> new DockerFileBuilder().basedir("blub").content()); - } + @Test + void illegalNonAbsoluteBaseDir() { + assertThatIllegalArgumentException().isThrownBy(() -> new DockerFileBuilder().basedir("blub").content()); + } - @Test - void testAssemblyUserWithChown() { - String dockerFile = new DockerFileBuilder().assemblyUser("jboss:jboss:jboss") - .add("a","a/nested").add("b","b/deeper/nested").content(); - String EXPECTED_REGEXP = "chown\\s+-R\\s+jboss:jboss\\s+([^\\s]+)" - + "\\s+&&\\s+cp\\s+-rp\\s+\\1/\\*\\s+/\\s+&&\\s+rm\\s+-rf\\s+\\1"; - Pattern pattern = Pattern.compile(EXPECTED_REGEXP); - assertThat(pattern.matcher(dockerFile).find()).isTrue(); - } + @Test + void testAssemblyUserWithChown() { + String dockerFile = new DockerFileBuilder().assemblyUser("jboss:jboss:jboss") + .add("a", "a/nested").add("b", "b/deeper/nested").content(); + String EXPECTED_REGEXP = "chown\\s+-R\\s+jboss:jboss\\s+([^\\s]+)" + + "\\s+&&\\s+cp\\s+-rp\\s+\\1/\\*\\s+/\\s+&&\\s+rm\\s+-rf\\s+\\1"; + Pattern pattern = Pattern.compile(EXPECTED_REGEXP); + assertThat(pattern.matcher(dockerFile).find()).isTrue(); + } - @Test - void testUser() { - String dockerFile = new DockerFileBuilder().assemblyUser("jboss:jboss:jboss").user("bob") - .add("a","a/nested").add("b","b/deeper/nested").content(); - String EXPECTED_REGEXP = "USER bob$"; - Pattern pattern = Pattern.compile(EXPECTED_REGEXP); - assertThat(pattern.matcher(dockerFile).find()).isTrue(); - } + @Test + void testUser() { + String dockerFile = new DockerFileBuilder().assemblyUser("jboss:jboss:jboss").user("bob") + .add("a", "a/nested").add("b", "b/deeper/nested").content(); + String EXPECTED_REGEXP = "USER bob$"; + Pattern pattern = Pattern.compile(EXPECTED_REGEXP); + assertThat(pattern.matcher(dockerFile).find()).isTrue(); + } + @Test + void testExportBaseDir() { + assertThat(new DockerFileBuilder().basedir("/export").content()).contains("/export"); + assertThat(new DockerFileBuilder().baseImage("java").basedir("/export").content()).doesNotContain("/export"); + assertThat(new DockerFileBuilder().baseImage("java").exportTargetDir(true).basedir("/export").content()) + .contains("/export"); + assertThat(new DockerFileBuilder().baseImage("java").exportTargetDir(false).basedir("/export").content()) + .doesNotContain("/export"); + } - @Test - void testExportBaseDir() { - assertThat(new DockerFileBuilder().basedir("/export").content()).contains("/export"); - assertThat(new DockerFileBuilder().baseImage("java").basedir("/export").content()).doesNotContain("/export"); - assertThat(new DockerFileBuilder().baseImage("java").exportTargetDir(true).basedir("/export").content()) - .contains("/export"); - assertThat(new DockerFileBuilder().baseImage("java").exportTargetDir(false).basedir("/export").content()) - .doesNotContain("/export"); - } + @Test + void testDockerFileKeywords() { + StringBuilder b = new StringBuilder(); + DockerFileKeyword.RUN.addTo(b, "apt-get", "update"); + assertThat(b).hasToString("RUN apt-get update\n"); - @Test - void testDockerFileKeywords() { - StringBuilder b = new StringBuilder(); - DockerFileKeyword.RUN.addTo(b, "apt-get", "update"); - assertThat(b).hasToString("RUN apt-get update\n"); + b = new StringBuilder(); + DockerFileKeyword.EXPOSE.addTo(b, "1010", "2020"); + assertThat(b).hasToString("EXPOSE 1010 2020\n"); - b = new StringBuilder(); - DockerFileKeyword.EXPOSE.addTo(b, "1010", "2020"); - assertThat(b).hasToString("EXPOSE 1010 2020\n"); + b = new StringBuilder(); + DockerFileKeyword.USER.addTo(b, "roland"); + assertThat(b).hasToString("USER roland\n"); + } - b = new StringBuilder(); - DockerFileKeyword.USER.addTo(b, "roland"); - assertThat(b).hasToString("USER roland\n"); - } + + @DisplayName("Test Multistage DockerFile, expected ok") + @Test + void testBuildDockerFileMultiStage() throws Exception { + Arguments a = Arguments.builder().execArgument("c1").execArgument("c2").build(); + DockerFileBuilder dockerfileStage2 = new DockerFileBuilder() + .baseImage("image2") + .cmd(a) + .basedir("/export") + .expose(Collections.singletonList("8080/udp")) + .maintainer("maintainer@example.com") - private String stripCR(String input){ - return input.replaceAll("\r", ""); - } + .volumes(Collections.singletonList("/vol1")) + .run(Arrays.asList("echo something", "echo second")) - private String loadFile(String fileName) throws IOException { - return stripCR(IOUtils.toString(Objects.requireNonNull(getClass().getClassLoader().getResource(fileName)), Charset.defaultCharset())); - } + .addFromStage("/src", "/dest", "stage1") + .workdir("/tmp") + + ; + String dockerfileFinalFile = new DockerFileBuilder() + .baseImage("image").alias("stage1") + .cmd(a) + .basedir("/export") + .expose(Collections.singletonList("8080/udp")) + .add("/src", "/dest") + .workdir("/tmp") + .maintainer("maintainer@example.com") + .volumes(Collections.singletonList("/vol1")) + .run(Arrays.asList("echo something", "echo second")) + .addStages(dockerfileStage2) + .content() + + ; + + String expected = loadFile("docker/Dockerfile_multistage.test"); + + assertThat(stripCR(dockerfileFinalFile)).isEqualTo(expected); + } + + private String stripCR(String input) { + return input.replaceAll("\r", ""); + } + + private String loadFile(String fileName) throws IOException { + return stripCR( + IOUtils.toString(Objects.requireNonNull(getClass().getClassLoader().getResource(fileName)), Charset.defaultCharset())); + } - private static Map dockerfileToMap(String dockerFile) { - final Map dockerfileMap = new HashMap<>(); - final Scanner scanner = new Scanner(dockerFile); - while (scanner.hasNextLine()) { - String line = scanner.nextLine(); - if (line.trim().length() == 0) { - continue; - } - String[] commandAndArguments = line.trim().split("\\s+", 2); - if (commandAndArguments.length < 2) { - continue; - } - dockerfileMap.put(commandAndArguments[0], commandAndArguments[1]); - } - scanner.close(); - return dockerfileMap; + private static Map dockerfileToMap(String dockerFile) { + final Map dockerfileMap = new HashMap<>(); + final Scanner scanner = new Scanner(dockerFile); + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + if (line.trim().length() == 0) { + continue; + } + String[] commandAndArguments = line.trim().split("\\s+", 2); + if (commandAndArguments.length < 2) { + continue; + } + dockerfileMap.put(commandAndArguments[0], commandAndArguments[1]); } + scanner.close(); + return dockerfileMap; + } } diff --git a/jkube-kit/config/image/src/test/resources/docker/Dockerfile_multistage.test b/jkube-kit/config/image/src/test/resources/docker/Dockerfile_multistage.test new file mode 100644 index 0000000000..50d4fb5a42 --- /dev/null +++ b/jkube-kit/config/image/src/test/resources/docker/Dockerfile_multistage.test @@ -0,0 +1,19 @@ +FROM image AS stage1 +MAINTAINER maintainer@example.com +EXPOSE 8080/udp +COPY /src /export/dest +WORKDIR /tmp +RUN echo something +RUN echo second +VOLUME ["/vol1"] +CMD ["c1","c2"] + +FROM image2 +MAINTAINER maintainer@example.com +EXPOSE 8080/udp +COPY --from=stage1 /src /export/dest +WORKDIR /tmp +RUN echo something +RUN echo second +VOLUME ["/vol1"] +CMD ["c1","c2"]