From 7b6f147544161398a00b9dbcae22f1d141dbb123 Mon Sep 17 00:00:00 2001 From: Jurrie Overgoor <1213142+Jurrie@users.noreply.github.com> Date: Fri, 16 Feb 2024 16:43:43 +0100 Subject: [PATCH 1/3] Introduce unmarshal methods that take java.nio.Path Signed-off-by: Jurrie Overgoor --- .../jkube/kit/common/util/Serialization.java | 24 +++++++++++++------ .../kit/resource/helm/HelmServiceTest.java | 8 +++---- .../maven/plugin/mojo/build/HelmMojoTest.java | 2 +- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/Serialization.java b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/Serialization.java index 9394a8236f..dea97ca82e 100644 --- a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/Serialization.java +++ b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/Serialization.java @@ -31,6 +31,7 @@ import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; public class Serialization { @@ -65,19 +66,28 @@ public static T unmarshal(File file) throws IOException { } public static T unmarshal(File file, Class clazz) throws IOException { - try (InputStream fis = Files.newInputStream(file.toPath())) { + return unmarshal(file.toPath(), clazz); + } + + public static T unmarshal(File file, TypeReference type) throws IOException { + return unmarshal(file.toPath(), type); + } + + public static T unmarshal(Path file, Class clazz) throws IOException { + try (InputStream fis = Files.newInputStream(file)) { return unmarshal(fis, clazz); } } - public static T unmarshal(File file, TypeReference type) throws IOException { - try (InputStream fis = Files.newInputStream(file.toPath())) { + public static T unmarshal(Path file, TypeReference type) throws IOException { + try (InputStream fis = Files.newInputStream(file)) { return unmarshal(fis, type); } } - public static T unmarshal(URL url, Class type) throws IOException { + + public static T unmarshal(URL url, Class clazz) throws IOException { try (InputStream is = url.openStream()){ - return unmarshal(is, type); + return unmarshal(is, clazz); } } @@ -95,8 +105,8 @@ public static T unmarshal(InputStream is, TypeReference type) { return KUBERNETES_SERIALIZATION.unmarshal(is, type); } - public static T unmarshal(String string, Class type) { - return KUBERNETES_SERIALIZATION.unmarshal(string, type); + public static T unmarshal(String string, Class clazz) { + return KUBERNETES_SERIALIZATION.unmarshal(string, clazz); } public static T unmarshal(String string, TypeReference type) { diff --git a/jkube-kit/helm/src/test/java/org/eclipse/jkube/kit/resource/helm/HelmServiceTest.java b/jkube-kit/helm/src/test/java/org/eclipse/jkube/kit/resource/helm/HelmServiceTest.java index 7a200c75c4..d7df3633ea 100644 --- a/jkube-kit/helm/src/test/java/org/eclipse/jkube/kit/resource/helm/HelmServiceTest.java +++ b/jkube-kit/helm/src/test/java/org/eclipse/jkube/kit/resource/helm/HelmServiceTest.java @@ -101,7 +101,7 @@ void generateHelmCharts() throws IOException { helmService.generateHelmCharts(helmConfig.build()); // Then final Map chartYaml = Serialization.unmarshal( - helmOutputDirectory.resolve("kubernetes").resolve("Chart.yaml").toFile(), + helmOutputDirectory.resolve("kubernetes").resolve("Chart.yaml"), new TypeReference>() {}); assertThat(chartYaml) .contains( @@ -164,7 +164,7 @@ void generateHelmCharts_withValidChartYamlFragment_usesMergedChart() throws Exce new HelmService(jKubeConfiguration, resourceServiceConfig, new KitLogger.SilentLogger()) .generateHelmCharts(helmConfig.build()); // Then - final Map savedChart = Serialization.unmarshal(helmOutputDirectory.resolve("kubernetes").resolve("Chart.yaml").toFile(), Map.class); + final Map savedChart = Serialization.unmarshal(helmOutputDirectory.resolve("kubernetes").resolve("Chart.yaml"), Map.class); assertThat(savedChart) .hasFieldOrPropertyWithValue("apiVersion", "v1") .hasFieldOrPropertyWithValue("name", "name-from-fragment") @@ -199,7 +199,7 @@ void generateHelmCharts_withValidValuesYamlFragment_usesMergedValues() throws Ex new HelmService(jKubeConfiguration, resourceServiceConfig, new KitLogger.SilentLogger()) .generateHelmCharts(helmConfig.build()); // Then - final Map savedValues = Serialization.unmarshal(helmOutputDirectory.resolve("kubernetes").resolve("values.yaml").toFile(), Map.class); + final Map savedValues = Serialization.unmarshal(helmOutputDirectory.resolve("kubernetes").resolve("values.yaml"), Map.class); assertThat(savedValues) .hasFieldOrPropertyWithValue("replaceableProperty", "the-value") .hasFieldOrPropertyWithValue("replicaCount", 1) @@ -270,7 +270,7 @@ void createChartYamlWithDependencies() throws Exception { helmService.generateHelmCharts(helmConfig.build()); // Then final Map chartYaml = Serialization.unmarshal( - helmOutputDirectory.resolve("kubernetes").resolve("Chart.yaml").toFile(), + helmOutputDirectory.resolve("kubernetes").resolve("Chart.yaml"), new TypeReference>() {}); assertThat(chartYaml) .contains( diff --git a/kubernetes-maven-plugin/plugin/src/test/java/org/eclipse/jkube/maven/plugin/mojo/build/HelmMojoTest.java b/kubernetes-maven-plugin/plugin/src/test/java/org/eclipse/jkube/maven/plugin/mojo/build/HelmMojoTest.java index 7bd498bbba..cfe32f3f3f 100644 --- a/kubernetes-maven-plugin/plugin/src/test/java/org/eclipse/jkube/maven/plugin/mojo/build/HelmMojoTest.java +++ b/kubernetes-maven-plugin/plugin/src/test/java/org/eclipse/jkube/maven/plugin/mojo/build/HelmMojoTest.java @@ -135,7 +135,7 @@ void executeInternal_findTemplatesFromProvidedFile() throws Exception { .hasFieldOrPropertyWithValue("metadata.name", "the-template-for-params"); final Map savedChart = Serialization.unmarshal( projectDir.resolve("target").resolve("jkube").resolve("helm").resolve("empty-project") - .resolve("kubernetes").resolve("values.yaml").toFile(), new TypeReference>() {}); + .resolve("kubernetes").resolve("values.yaml"), new TypeReference>() {}); assertThat(savedChart).containsEntry("key", "value"); } From 0bfaf28969ae184764b164bc42fa5727b1814070 Mon Sep 17 00:00:00 2001 From: Jurrie Overgoor <1213142+Jurrie@users.noreply.github.com> Date: Fri, 16 Feb 2024 15:45:29 +0100 Subject: [PATCH 2/3] Better way to escape Helm (also unescape afterwards) Signed-off-by: Jurrie Overgoor --- .../jkube/kit/common/util/Serialization.java | 33 +++- .../jkube/kit/common/util/TemplateUtil.java | 171 ++++++++++++++---- .../kit/common/util/TemplateUtilTest.java | 80 ++++++-- .../jkube/kit/resource/helm/HelmService.java | 8 +- .../kit/resource/helm/HelmServiceIT.java | 14 +- .../kubernetes/templates/kubernetes.yaml | 15 +- .../it/expected/kubernetes/values.yaml | 2 +- .../openshift/templates/openshift.yaml | 10 +- .../it/expected/openshift/values.yaml | 2 +- .../it/sources/kubernetes/kubernetes.yml | 15 +- .../it/sources/openshift/openshift.yml | 10 +- 11 files changed, 275 insertions(+), 85 deletions(-) diff --git a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/Serialization.java b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/Serialization.java index dea97ca82e..539cb9799c 100644 --- a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/Serialization.java +++ b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/Serialization.java @@ -24,14 +24,17 @@ import io.fabric8.kubernetes.api.model.KubernetesResource; import io.fabric8.kubernetes.client.utils.KubernetesSerialization; +import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.stream.Collectors; public class Serialization { @@ -97,20 +100,22 @@ public static T unmarshal(URL url, TypeReference type) throws IOException } } - public static T unmarshal(InputStream is, Class clazz) { - return KUBERNETES_SERIALIZATION.unmarshal(is, clazz); + public static T unmarshal(final InputStream is, final Class clazz) { + return unmarshal(inputStreamToString(is), clazz); } - public static T unmarshal(InputStream is, TypeReference type) { - return KUBERNETES_SERIALIZATION.unmarshal(is, type); + public static T unmarshal(final InputStream is, final TypeReference type) { + return unmarshal(inputStreamToString(is), type); } public static T unmarshal(String string, Class clazz) { - return KUBERNETES_SERIALIZATION.unmarshal(string, clazz); + final byte[] yaml = TemplateUtil.escapeYamlTemplate(string).getBytes(StandardCharsets.UTF_8); + return KUBERNETES_SERIALIZATION.unmarshal(new ByteArrayInputStream(yaml), clazz); } - public static T unmarshal(String string, TypeReference type) { - return unmarshal(new ByteArrayInputStream(string.getBytes(StandardCharsets.UTF_8)), type); + public static T unmarshal(final String string, final TypeReference type) { + final byte[] yaml = TemplateUtil.escapeYamlTemplate(string).getBytes(StandardCharsets.UTF_8); + return KUBERNETES_SERIALIZATION.unmarshal(new ByteArrayInputStream(yaml), type); } public static T merge(T original, T overrides) throws IOException { @@ -131,7 +136,8 @@ public static ObjectWriter jsonWriter() { } public static String asYaml(Object object) { - return KUBERNETES_SERIALIZATION.asYaml(object); + final String yamlString = KUBERNETES_SERIALIZATION.asYaml(object); + return TemplateUtil.unescapeYamlTemplate(yamlString); } public static void saveJson(File resultFile, Object value) throws IOException { @@ -139,6 +145,15 @@ public static void saveJson(File resultFile, Object value) throws IOException { } public static void saveYaml(File resultFile, Object value) throws IOException { - YAML_MAPPER.writeValue(resultFile, value); + String yamlString = YAML_MAPPER.writeValueAsString(value); + yamlString = TemplateUtil.unescapeYamlTemplate(yamlString); + Files.write(resultFile.toPath(), yamlString.getBytes(StandardCharsets.UTF_8)); + } + + private static String inputStreamToString(final InputStream is) { + return new BufferedReader( + new InputStreamReader(is, StandardCharsets.UTF_8)) + .lines() + .collect(Collectors.joining("\n")); } } diff --git a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/TemplateUtil.java b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/TemplateUtil.java index 1aba31c50d..138ba51fae 100644 --- a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/TemplateUtil.java +++ b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/TemplateUtil.java @@ -13,49 +13,152 @@ */ package org.eclipse.jkube.kit.common.util; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + public class TemplateUtil { + private static final String HELM_DIRECTIVE_REGEX = "\\{\\{.*\\}\\}"; private TemplateUtil() { } /** - * Ported from https://github.com/fabric8io/fabric8-maven-plugin/commit/d6bdaa37e06863677bc01cefa31f7d23c6d5f0f9 + * This function will replace all Helm directives with a valid Yaml line containing the base64 encoded Helm directive. + * + * Helm lines that are by themselves will be converted like so: + *
+ * Input: + * + *
+   * {{- $myDate := .Value.date }}
+   * {{ include "something" . }}
+   * someKey: {{ a bit of Helm }}
+   * someOtherKey: {{ another bit of Helm }}
+   * 
+ * + * Output: + * + *
+   * escapedHelm0: BASE64STRINGOFCHARACTERS=
+   * escapedHelm1: ANOTHERBASE64STRING=
+   * someKey: escapedHelmValueBASE64STRING==
+   * someOtherKey: escapedHelmValueBASE64STRING
+   * 
+ * + * The escapedHelm and escapedHelmValue flags are needed for unescaping. + * + * @param yaml the input Yaml with Helm directives to be escaped + * @return the same Yaml, only with Helm directives converted to valid Yaml + * @see #unescapeYamlTemplate(String) + */ + public static String escapeYamlTemplate(final String yaml) { + return escapeYamlTemplateLines(escapeYamlTemplateValues(yaml)); + } + + /** + * This function will replace all escaped Helm directives by {@link #escapeYamlTemplate(String)} back to actual Helm. + *
+ * This function promises to be the opposite of {@link #escapeYamlTemplate(String)}. + * + * @param template the input Yaml that was returned by a call to {@link #escapeYamlTemplate(String)} + * @return the Yaml that was originally provided to {@link #escapeYamlTemplate(String)} + * @see #escapeYamlTemplate(String) + */ + public static String unescapeYamlTemplate(final String template) { + return unescapeYamlTemplateLines(unescapeYamlTemplateValues(template)); + } + + /** + * This function is responsible for escaping the Helm directives that are stand-alone. + * For example: + * + *
+   * {{ include "something" . }}
+   * 
* - * @param template String to escape - * @return the escaped Yaml template + * @see #unescapeYamlTemplateLines(String) */ - public static String escapeYamlTemplate(String template) { - StringBuilder answer = new StringBuilder(); - int count = 0; - char last = 0; - for (int i = 0, size = template.length(); i < size; i++) { - char ch = template.charAt(i); - if (ch == '{' || ch == '}') { - if (count == 0) { - last = ch; - count = 1; - } else { - if (ch == last) { - answer.append(ch == '{' ? "{{\"{{\"}}" : "{{\"}}\"}}"); - } else { - answer.append(last); - answer.append(ch); - } - count = 0; - last = 0; - } - } else { - if (count > 0) { - answer.append(last); - } - answer.append(ch); - count = 0; - last = 0; - } + private static String escapeYamlTemplateLines(String template) { + long escapedHelmIndex = 0; + final Pattern compile = Pattern.compile("^( *-? *)(" + HELM_DIRECTIVE_REGEX + ".*)$", Pattern.MULTILINE); + Matcher matcher = compile.matcher(template); + while (matcher.find()) { + final String indentation = matcher.group(1); + final String base64Line = Base64Util.encodeToString(matcher.group(2)); + template = matcher.replaceFirst(indentation + "escapedHelm" + escapedHelmIndex + ": " + base64Line); + matcher = compile.matcher(template); + escapedHelmIndex++; } - if (count > 0) { - answer.append(last); + return template; + } + + /** + * This function is responsible for reinstating the stand-alone Helm directives. + * For example: + * + *
+   * BASE64STRINGOFCHARACTERS=
+   * 
+ * + * It is the opposite of {@link #escapeYamlTemplateLines(String)}. + * + * @see #escapeYamlTemplateLines(String) + */ + private static String unescapeYamlTemplateLines(String template) { + final Pattern compile = Pattern.compile("^( *-? *)escapedHelm[\\d]+: \"?(.*?)\"?$", Pattern.MULTILINE); + Matcher matcher = compile.matcher(template); + while (matcher.find()) { + final String indentation = matcher.group(1); + final String helmLine = Base64Util.decodeToString(matcher.group(2)); + template = matcher.replaceFirst(indentation + helmLine.replace("$", "\\$")); + matcher = compile.matcher(template); + } + return template; + } + + /** + * This function is responsible for escaping the Helm directives that are Yaml values. + * For example: + * + *
+   * someKey: {{ a bit of Helm }}
+   * 
+ * + * @see #unescapeYamlTemplateValues(String) + */ + private static String escapeYamlTemplateValues(String template) { + final Pattern compile = Pattern.compile("^( *[^ ]+ *): *(" + HELM_DIRECTIVE_REGEX + ".*)$", Pattern.MULTILINE); + Matcher matcher = compile.matcher(template); + while (matcher.find()) { + final String indentation = matcher.group(1); + final String base64Value = Base64Util.encodeToString(matcher.group(2)); + template = matcher.replaceFirst(indentation + ": escapedHelmValue" + base64Value); + matcher = compile.matcher(template); + } + return template; + } + + /** + * This function is responsible for reinstating the Helm directives that were Yaml values. + * For example: + * + *
+   * someKey: escapedHelmValueBASE64STRING==
+   * 
+ * + * It is the opposite of {@link #escapeYamlTemplateValues(String)}. + * + * @see #escapeYamlTemplateValues(String) + */ + private static String unescapeYamlTemplateValues(String template) { + final Pattern compile = Pattern.compile("^( *[^ ]+ *): *\"?escapedHelmValue(.*?)\"?$", Pattern.MULTILINE); + Matcher matcher = compile.matcher(template); + while (matcher.find()) { + final String indentation = matcher.group(1); + final String helmValue = Base64Util.decodeToString(matcher.group(2)); + template = matcher.replaceFirst(indentation + ": " + helmValue.replace("$", "\\$")); + matcher = compile.matcher(template); } - return answer.toString(); + return template; } } diff --git a/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/TemplateUtilTest.java b/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/TemplateUtilTest.java index 8ee952061a..93087937e8 100644 --- a/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/TemplateUtilTest.java +++ b/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/TemplateUtilTest.java @@ -20,21 +20,73 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.jkube.kit.common.util.TemplateUtil.escapeYamlTemplate; +import static org.eclipse.jkube.kit.common.util.TemplateUtil.unescapeYamlTemplate; class TemplateUtilTest { - public static Stream data() { - return Stream.of(new Object[][]{ - {"abcd", "abcd"}, - {"abc{de}f}", "abc{de}f}"}, - {"abc{{de}f", "abc{{\"{{\"}}de}f"}, - {"abc{{de}f}}", "abc{{\"{{\"}}de}f{{\"}}\"}}"} - }); - } - - @ParameterizedTest(name = "{0} is {1}") - @MethodSource("data") - void escapeYamlTemplateTest(String input, String expected) { - assertThat(escapeYamlTemplate(input)).isEqualTo(expected); - } + public static Stream data() { + return Stream.of(new Object[][] { + // No Helm directive + { "abcd", "abcd" }, + + // When the Helm directive is not the first on the line + { "abc{de}f}", "abc{de}f}" }, + { "abc{{de}f", "abc{{de}f" }, + { "abc{{$def}}", "abc{{$def}}" }, + { "abc{{de}}f", "abc{{de}}f" }, + { "abc{{de}f}}", "abc{{de}f}}" }, + { "abc{{def}}ghi{{jkl}}mno", "abc{{def}}ghi{{jkl}}mno" }, + + // When the Helm directive is the first on the line + { "{de}f}", "{de}f}" }, + { "{{de}f", "{{de}f" }, + { "{{$def}}", "escapedHelm0: " + Base64Util.encodeToString("{{$def}}") }, + { "{{de}}f", "escapedHelm0: " + Base64Util.encodeToString("{{de}}f") }, + { "{{de}f}}", "escapedHelm0: " + Base64Util.encodeToString("{{de}f}}") }, + { "{{def}}ghi{{jkl}}mno", "escapedHelm0: " + Base64Util.encodeToString("{{def}}ghi{{jkl}}mno") }, + { "hello\n{{def}}\nworld", "hello\nescapedHelm0: " + Base64Util.encodeToString("{{def}}") + "\nworld" }, + { "- hello\n- {{def}}\n- world", "- hello\n- escapedHelm0: " + Base64Util.encodeToString("{{def}}") + "\n- world" }, + { "{{multiple}}\n{{helm}}\n{{lines}}", + "escapedHelm0: " + Base64Util.encodeToString("{{multiple}}") + "\n" + + "escapedHelm1: " + Base64Util.encodeToString("{{helm}}") + "\n" + + "escapedHelm2: " + Base64Util.encodeToString("{{lines}}") }, + + // When the Helm directive is the first on the line, but indented + { " {de}f}", " {de}f}" }, + { " {{de}f", " {{de}f" }, + { " {{$def}}", " escapedHelm0: " + Base64Util.encodeToString("{{$def}}") }, + { " {{de}}f", " escapedHelm0: " + Base64Util.encodeToString("{{de}}f") }, + { " {{de}f}}", " escapedHelm0: " + Base64Util.encodeToString("{{de}f}}") }, + { " {{def}}ghi{{jkl}}mno", " escapedHelm0: " + Base64Util.encodeToString("{{def}}ghi{{jkl}}mno") }, + { "hello:\n {{def}}\n world", "hello:\n escapedHelm0: " + Base64Util.encodeToString("{{def}}") + "\n world" }, + { "hello:\n - {{def}}\n - world", + "hello:\n - escapedHelm0: " + Base64Util.encodeToString("{{def}}") + "\n - world" }, + + // When the Helm directive is a value + { "key: {de}f}", "key: {de}f}" }, + { "key: {{de}f", "key: {{de}f" }, + { "key: {{$def}}", "key: escapedHelmValue" + Base64Util.encodeToString("{{$def}}") }, + { "key: {{de}}f", "key: escapedHelmValue" + Base64Util.encodeToString("{{de}}f") }, + { "key: {{de}f}}", "key: escapedHelmValue" + Base64Util.encodeToString("{{de}f}}") }, + { "key: {{def}}ghi{{jkl}}mno", "key: escapedHelmValue" + Base64Util.encodeToString("{{def}}ghi{{jkl}}mno") }, + + // When the Helm directive is a value, but indented + { " key: {de}f}", " key: {de}f}" }, + { " key: {{de}f", " key: {{de}f" }, + { " key: {{$def}}", " key: escapedHelmValue" + Base64Util.encodeToString("{{$def}}") }, + { " key: {{de}}f", " key: escapedHelmValue" + Base64Util.encodeToString("{{de}}f") }, + { " key: {{de}f}}", " key: escapedHelmValue" + Base64Util.encodeToString("{{de}f}}") }, + { " key: {{def}}ghi{{jkl}}mno", " key: escapedHelmValue" + Base64Util.encodeToString("{{def}}ghi{{jkl}}mno") }, + }); + } + + @ParameterizedTest(name = "{0} → {1}") + @MethodSource("data") + void escapeYamlTemplateTest(final String input, final String expected) { + final String escapedYaml = escapeYamlTemplate(input); + assertThat(escapedYaml).isEqualTo(expected); + + final String unescapedYaml = unescapeYamlTemplate(escapedYaml); + assertThat(input).isEqualTo(unescapedYaml); + } } \ No newline at end of file diff --git a/jkube-kit/helm/src/main/java/org/eclipse/jkube/kit/resource/helm/HelmService.java b/jkube-kit/helm/src/main/java/org/eclipse/jkube/kit/resource/helm/HelmService.java index 634d4ab1c6..49ab7ba310 100644 --- a/jkube-kit/helm/src/main/java/org/eclipse/jkube/kit/resource/helm/HelmService.java +++ b/jkube-kit/helm/src/main/java/org/eclipse/jkube/kit/resource/helm/HelmService.java @@ -16,7 +16,6 @@ import java.io.File; import java.io.IOException; import java.nio.charset.Charset; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -63,7 +62,6 @@ import static org.eclipse.jkube.kit.common.JKubeFileInterpolator.DEFAULT_FILTER; import static org.eclipse.jkube.kit.common.JKubeFileInterpolator.interpolate; import static org.eclipse.jkube.kit.common.util.MapUtil.getNestedMap; -import static org.eclipse.jkube.kit.common.util.TemplateUtil.escapeYamlTemplate; import static org.eclipse.jkube.kit.common.util.YamlUtil.listYamls; import static org.eclipse.jkube.kit.resource.helm.HelmServiceUtil.isRepositoryValid; import static org.eclipse.jkube.kit.resource.helm.HelmServiceUtil.selectHelmRepository; @@ -253,11 +251,7 @@ private static void processSourceFiles(File sourceDir, File templatesDir) throws splitAndSaveTemplate((Template) dto, templatesDir); } else { final String fileName = FileUtil.stripPostfix(FileUtil.stripPostfix(file.getName(), ".yml"), YAML_EXTENSION) + YAML_EXTENSION; - File targetFile = new File(templatesDir, fileName); - // lets escape any {{ or }} characters to avoid creating invalid templates - String text = FileUtils.readFileToString(file, Charset.defaultCharset()); - text = escapeYamlTemplate(text); - FileUtils.write(targetFile, text, Charset.defaultCharset()); + FileUtil.copy(file, new File(templatesDir, fileName)); } } } diff --git a/jkube-kit/helm/src/test/java/org/eclipse/jkube/kit/resource/helm/HelmServiceIT.java b/jkube-kit/helm/src/test/java/org/eclipse/jkube/kit/resource/helm/HelmServiceIT.java index 580cd6473e..592aa30770 100644 --- a/jkube-kit/helm/src/test/java/org/eclipse/jkube/kit/resource/helm/HelmServiceIT.java +++ b/jkube-kit/helm/src/test/java/org/eclipse/jkube/kit/resource/helm/HelmServiceIT.java @@ -15,7 +15,6 @@ import java.io.File; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; @@ -32,7 +31,6 @@ import org.eclipse.jkube.kit.common.assertj.ArchiveAssertions; import io.fabric8.openshift.api.model.Template; -import org.apache.commons.io.FileUtils; import org.eclipse.jkube.kit.common.util.Serialization; import org.eclipse.jkube.kit.config.resource.ResourceServiceConfig; import org.junit.jupiter.api.BeforeEach; @@ -174,16 +172,10 @@ private void assertYamls() throws Exception { final Path expectations = new File(HelmServiceIT.class.getResource("/it/expected").toURI()).toPath(); final Path generatedYamls = helmOutputDir.toPath(); for (Path expected : Files.walk(expectations).filter(Files::isRegularFile).collect(Collectors.toList())) { - final Map expectedContent = Serialization.unmarshal(replacePlaceholders(expected), Map.class); - final Map actualContent = Serialization.unmarshal( - replacePlaceholders(generatedYamls.resolve(expectations.relativize(expected))), Map.class); + + final Map expectedContent = Serialization.unmarshal(expected, Map.class); + final Map actualContent = Serialization.unmarshal(generatedYamls.resolve(expectations.relativize(expected)), Map.class); assertThat(actualContent).isEqualTo(expectedContent); } } - - private static String replacePlaceholders(final Path yamlWithJsonPlaceholders) throws IOException { - return FileUtils.readFileToString(yamlWithJsonPlaceholders.toFile(), StandardCharsets.UTF_8) - .replace("\"{{\"{{\"}}", "").replace("{{\"}}\"}}", "") - .replace("{", "(").replace("}", ")"); - } } diff --git a/jkube-kit/helm/src/test/resources/it/expected/kubernetes/templates/kubernetes.yaml b/jkube-kit/helm/src/test/resources/it/expected/kubernetes/templates/kubernetes.yaml index e110adca10..a0a8823df0 100644 --- a/jkube-kit/helm/src/test/resources/it/expected/kubernetes/templates/kubernetes.yaml +++ b/jkube-kit/helm/src/test/resources/it/expected/kubernetes/templates/kubernetes.yaml @@ -27,11 +27,14 @@ items: - apiVersion: v1 kind: Service metadata: + {{ if .Values.addAnnotations }} annotations: jkube/it: This is a test helm-variable: {{ required "A valid .Values.GLOBAL_TEMPLATE_ENV_VAR entry required!" .Values.GLOBAL_TEMPLATE_ENV_VAR }} - escape-test: "{{"{{"}} {{"}}"}} should be escaped to prevent helm errors" + value-that-looks-like-helm-test: "{{ $The term 'helm' finds its roots in Old English }}" + value-that-is-a-helm-expression: {{ .Values.abandonShip }} helm-golang-expression: {{ .Values.unused_value | upper | quote }} + {{ end }} labels: app: test provider: jkube @@ -51,12 +54,15 @@ items: - apiVersion: apps/v1 kind: Deployment metadata: + {{ if .Values.addAnnotations }} annotations: jkube/it: This is a test helm-variable: {{ required "A valid .Values.GLOBAL_TEMPLATE_ENV_VAR entry required!" .Values.GLOBAL_TEMPLATE_ENV_VAR }} - escape-test: "{{"{{"}} {{"}}"}} should be escaped to prevent helm errors" + value-that-looks-like-helm-test: "{{ $The term 'helm' finds its roots in Old English }}" + value-that-is-a-helm-expression: {{ .Values.abandonShip }} annotation-from-config: {{ .Chart.Name | upper }} annotation-from-config/dotted: {{ .Chart.Name }} + {{ end }} labels: app: test provider: jkube @@ -72,10 +78,13 @@ items: group: org.eclipse.jkube.quickstarts.maven template: metadata: + {{ if .Values.addAnnotations }} annotations: jkube/it: This is a test helm-variable: {{ required "A valid .Values.GLOBAL_TEMPLATE_ENV_VAR entry required!" .Values.GLOBAL_TEMPLATE_ENV_VAR }} - escape-test: "{{"{{"}} {{"}}"}} should be escaped to prevent helm errors" + value-that-looks-like-helm-test: "{{ $The term 'helm' finds its roots in Old English }}" + value-that-is-a-helm-expression: {{ .Values.abandonShip }} + {{ end }} labels: app: test provider: jkube diff --git a/jkube-kit/helm/src/test/resources/it/expected/kubernetes/values.yaml b/jkube-kit/helm/src/test/resources/it/expected/kubernetes/values.yaml index 95318ff9f8..b979c5b831 100644 --- a/jkube-kit/helm/src/test/resources/it/expected/kubernetes/values.yaml +++ b/jkube-kit/helm/src/test/resources/it/expected/kubernetes/values.yaml @@ -13,9 +13,9 @@ # deployment: - replicas: 1 limits: memory: 512Mi + replicas: 1 GLOBAL_TEMPLATE_ENV_VAR: This is a sample unused_value: the-value requests_memory: 256Mi diff --git a/jkube-kit/helm/src/test/resources/it/expected/openshift/templates/openshift.yaml b/jkube-kit/helm/src/test/resources/it/expected/openshift/templates/openshift.yaml index ea9641ca3a..a471f1748a 100644 --- a/jkube-kit/helm/src/test/resources/it/expected/openshift/templates/openshift.yaml +++ b/jkube-kit/helm/src/test/resources/it/expected/openshift/templates/openshift.yaml @@ -13,6 +13,8 @@ # --- +{{- $shouldDeploy := .Values.shouldSetSail -}} +{{ if eq $shouldDeploy true }} apiVersion: v1 kind: List items: @@ -32,5 +34,11 @@ items: provider: jkube version: 1337 helm-variable: {{ required "A valid .Values.GLOBAL_TEMPLATE_ENV_VAR entry required!" .Values.GLOBAL_TEMPLATE_ENV_VAR }} - escape-test: "{{"{{"}} {{"}}"}} should be escaped to prevent helm errors" + value-that-looks-like-helm-test: "{{ $The term 'helm' finds its roots in Old English }}" + value-that-is-a-helm-expression: {{ .Values.abandonShip }} + toppings: |- + {{- range $index, $topping := .Values.pizzaToppings }} + {{ $index }}: {{ $topping }} + {{- end }} name: test +{{ end }} \ No newline at end of file diff --git a/jkube-kit/helm/src/test/resources/it/expected/openshift/values.yaml b/jkube-kit/helm/src/test/resources/it/expected/openshift/values.yaml index 81a42482c0..6dbac326ee 100644 --- a/jkube-kit/helm/src/test/resources/it/expected/openshift/values.yaml +++ b/jkube-kit/helm/src/test/resources/it/expected/openshift/values.yaml @@ -13,9 +13,9 @@ # deployment: - replicas: 1 limits: memory: "512Mi" + replicas: 1 GLOBAL_TEMPLATE_ENV_VAR: This is a sample unused_value: the-value requests_memory: 256Mi diff --git a/jkube-kit/helm/src/test/resources/it/sources/kubernetes/kubernetes.yml b/jkube-kit/helm/src/test/resources/it/sources/kubernetes/kubernetes.yml index 54d8b1a139..1e43391e9e 100644 --- a/jkube-kit/helm/src/test/resources/it/sources/kubernetes/kubernetes.yml +++ b/jkube-kit/helm/src/test/resources/it/sources/kubernetes/kubernetes.yml @@ -27,11 +27,14 @@ items: - apiVersion: v1 kind: Service metadata: + {{ if .Values.addAnnotations }} annotations: jkube/it: This is a test helm-variable: ${GLOBAL_TEMPLATE_ENV_VAR} - escape-test: "{{ }} should be escaped to prevent helm errors" + value-that-looks-like-helm-test: "{{ $The term 'helm' finds its roots in Old English }}" + value-that-is-a-helm-expression: {{ .Values.abandonShip }} helm-golang-expression: ${golang_expression} + {{ end }} labels: app: test provider: jkube @@ -51,12 +54,15 @@ items: - apiVersion: apps/v1 kind: Deployment metadata: + {{ if .Values.addAnnotations }} annotations: jkube/it: This is a test helm-variable: ${GLOBAL_TEMPLATE_ENV_VAR} - escape-test: "{{ }} should be escaped to prevent helm errors" + value-that-looks-like-helm-test: "{{ $The term 'helm' finds its roots in Old English }}" + value-that-is-a-helm-expression: {{ .Values.abandonShip }} annotation-from-config: ${annotation_from_config} annotation-from-config/dotted: ${annotation.from.config.dotted} + {{ end }} labels: app: test provider: jkube @@ -72,10 +78,13 @@ items: group: org.eclipse.jkube.quickstarts.maven template: metadata: + {{ if .Values.addAnnotations }} annotations: jkube/it: This is a test helm-variable: ${GLOBAL_TEMPLATE_ENV_VAR} - escape-test: "{{ }} should be escaped to prevent helm errors" + value-that-looks-like-helm-test: "{{ $The term 'helm' finds its roots in Old English }}" + value-that-is-a-helm-expression: {{ .Values.abandonShip }} + {{ end }} labels: app: test provider: jkube diff --git a/jkube-kit/helm/src/test/resources/it/sources/openshift/openshift.yml b/jkube-kit/helm/src/test/resources/it/sources/openshift/openshift.yml index cd9f8955e8..bd0be30ed8 100644 --- a/jkube-kit/helm/src/test/resources/it/sources/openshift/openshift.yml +++ b/jkube-kit/helm/src/test/resources/it/sources/openshift/openshift.yml @@ -13,6 +13,8 @@ # --- +{{- $shouldDeploy := .Values.shouldSetSail -}} +{{ if eq $shouldDeploy true }} apiVersion: v1 kind: List items: @@ -32,5 +34,11 @@ items: provider: jkube version: 1337 helm-variable: ${GLOBAL_TEMPLATE_ENV_VAR} - escape-test: "{{ }} should be escaped to prevent helm errors" + value-that-looks-like-helm-test: "{{ $The term 'helm' finds its roots in Old English }}" + value-that-is-a-helm-expression: {{ .Values.abandonShip }} + toppings: |- + {{- range $index, $topping := .Values.pizzaToppings }} + {{ $index }}: {{ $topping }} + {{- end }} name: test +{{ end }} \ No newline at end of file From 0087d30f37415931b4eff746ca6bb8c996b91732 Mon Sep 17 00:00:00 2001 From: Jurrie Overgoor <1213142+Jurrie@users.noreply.github.com> Date: Fri, 23 Feb 2024 12:10:24 +0100 Subject: [PATCH 3/3] Rewrote the escaping logic - hopefully better readable Signed-off-by: Jurrie Overgoor --- .../jkube/kit/common/util/TemplateUtil.java | 218 ++++++++++-------- .../kit/common/util/TemplateUtilTest.java | 105 ++++----- .../kubernetes/templates/kubernetes.yaml | 5 +- .../it/sources/kubernetes/kubernetes.yml | 5 +- 4 files changed, 182 insertions(+), 151 deletions(-) diff --git a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/TemplateUtil.java b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/TemplateUtil.java index 138ba51fae..5dc935cd4b 100644 --- a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/TemplateUtil.java +++ b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/TemplateUtil.java @@ -13,17 +13,32 @@ */ package org.eclipse.jkube.kit.common.util; +import java.lang.invoke.MethodHandles; +import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + public class TemplateUtil { - private static final String HELM_DIRECTIVE_REGEX = "\\{\\{.*\\}\\}"; + private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); + + private static final String HELM_END_TAG = "}}"; + private static final String HELM_START_TAG = "{{"; + + private static final String YAML_HELM_ESCAPE_KEY = "escapedHelm"; + private static final String YAML_LIST_TAG = "-"; + private static final String YAML_KEY_VALUE_SEPARATOR = ": "; private TemplateUtil() { } /** - * This function will replace all Helm directives with a valid Yaml line containing the base64 encoded Helm directive. + * This function will replace all single line Helm directives with a valid Yaml line containing the base64 encoded Helm + * directive. + * Helm directives that are values (so not a full line) will be left alone. * * Helm lines that are by themselves will be converted like so: *
@@ -32,17 +47,17 @@ private TemplateUtil() { *
    * {{- $myDate := .Value.date }}
    * {{ include "something" . }}
+   * myKey: "Regular line"
    * someKey: {{ a bit of Helm }}
-   * someOtherKey: {{ another bit of Helm }}
    * 
* * Output: * *
-   * escapedHelm0: BASE64STRINGOFCHARACTERS=
-   * escapedHelm1: ANOTHERBASE64STRING=
-   * someKey: escapedHelmValueBASE64STRING==
-   * someOtherKey: escapedHelmValueBASE64STRING
+   * escapedHelm0: e3stICA6PSAuVmFsdWUuZGF0ZSB9fQ==
+   * escapedHelm1: e3sgaW5jbHVkZSBzb21ldGhpbmcgLiB9fQ==
+   * myKey: "Regular line"
+   * someKey: {{ a bit of Helm }}
    * 
* * The escapedHelm and escapedHelmValue flags are needed for unescaping. @@ -52,7 +67,58 @@ private TemplateUtil() { * @see #unescapeYamlTemplate(String) */ public static String escapeYamlTemplate(final String yaml) { - return escapeYamlTemplateLines(escapeYamlTemplateValues(yaml)); + final AtomicLong helmEscapeIndex = new AtomicLong(); + return iterateOverLines(yaml, + (line, lineEnding, lineNumber) -> escapeYamlLine(line, lineNumber, helmEscapeIndex) + lineEnding); + } + + private static String escapeYamlLine(final String line, final long lineNumber, final AtomicLong helmEscapeIndex) { + if (line.trim().startsWith(HELM_START_TAG)) { + // Line starts with optional indenting and then '{{', so replace whole line + checkHelmStartAndEndTags(line, lineNumber); + + final int startOfHelm = line.indexOf(HELM_START_TAG); + final String indentation = line.substring(0, startOfHelm); + final String base64EncodedLine = Base64Util.encodeToString(line.substring(startOfHelm)); + final long currentHelmEscapeIndex = helmEscapeIndex.getAndIncrement(); + return indentation + YAML_HELM_ESCAPE_KEY + currentHelmEscapeIndex + YAML_KEY_VALUE_SEPARATOR + base64EncodedLine; + } else if (line.trim().startsWith(YAML_LIST_TAG)) { + // Line starts with optional indenting and then '-'. Strip the '-', parse again, readd the '-'. + final int startOfRestOfLine = line.indexOf(YAML_LIST_TAG) + YAML_LIST_TAG.length(); + final String prefix = line.substring(0, startOfRestOfLine); + final String restOfLine = line.substring(startOfRestOfLine); + return prefix + escapeYamlLine(restOfLine, lineNumber, helmEscapeIndex); + } else if (line.contains(YAML_KEY_VALUE_SEPARATOR)) { + // Line is a "key: value" line. Strip the key, parse again, readd the key. + final int startOfRestOfLine = line.indexOf(YAML_KEY_VALUE_SEPARATOR) + YAML_KEY_VALUE_SEPARATOR.length(); + final String key = line.substring(0, startOfRestOfLine); + final String value = line.substring(startOfRestOfLine); + + if (value.trim().startsWith(HELM_START_TAG)) { + checkHelmStartAndEndTags(value, lineNumber); + + final String base64EncodedLine = Base64Util.encodeToString(value); + return key + YAML_HELM_ESCAPE_KEY + base64EncodedLine; + } else { + // Value was a regular value + return line; + } + } else { + // Line was a regular line + return line; + } + } + + private static void checkHelmStartAndEndTags(final String line, final long lineNumber) { + final int startCount = StringUtils.countMatches(line, HELM_START_TAG); + final int endCount = StringUtils.countMatches(line, HELM_END_TAG); + if (startCount != endCount) { + LOG.warn("Found {} Helm start tag{} ('{}') but {} end tag{} ('{}') on line {}. " + + "Expected this to be equal! Note that multi-line Helm directives are not supported.", + startCount, startCount == 1 ? "" : "s", HELM_START_TAG, + endCount, endCount == 1 ? "" : "s", HELM_END_TAG, + lineNumber); + } } /** @@ -64,101 +130,63 @@ public static String escapeYamlTemplate(final String yaml) { * @return the Yaml that was originally provided to {@link #escapeYamlTemplate(String)} * @see #escapeYamlTemplate(String) */ - public static String unescapeYamlTemplate(final String template) { - return unescapeYamlTemplateLines(unescapeYamlTemplateValues(template)); + public static String unescapeYamlTemplate(final String yaml) { + return iterateOverLines(yaml, (line, lineEnding, lineNumber) -> unescapeYamlLine(line) + lineEnding); } - /** - * This function is responsible for escaping the Helm directives that are stand-alone. - * For example: - * - *
-   * {{ include "something" . }}
-   * 
- * - * @see #unescapeYamlTemplateLines(String) - */ - private static String escapeYamlTemplateLines(String template) { - long escapedHelmIndex = 0; - final Pattern compile = Pattern.compile("^( *-? *)(" + HELM_DIRECTIVE_REGEX + ".*)$", Pattern.MULTILINE); - Matcher matcher = compile.matcher(template); - while (matcher.find()) { - final String indentation = matcher.group(1); - final String base64Line = Base64Util.encodeToString(matcher.group(2)); - template = matcher.replaceFirst(indentation + "escapedHelm" + escapedHelmIndex + ": " + base64Line); - matcher = compile.matcher(template); - escapedHelmIndex++; + private static String unescapeYamlLine(final String line) { + if (line.trim().startsWith(YAML_HELM_ESCAPE_KEY)) { + // Line starts with optional indenting and then 'escapedHelm', so replace whole line + final int endOfIndentation = line.indexOf(YAML_HELM_ESCAPE_KEY); + final int startOfBase64Helm = line.indexOf(YAML_KEY_VALUE_SEPARATOR) + YAML_KEY_VALUE_SEPARATOR.length(); + final String indentation = line.substring(0, endOfIndentation); + final String base64DecodedLine = Base64Util.decodeToString(line.substring(startOfBase64Helm)); + return indentation + base64DecodedLine; + } else if (line.trim().startsWith(YAML_LIST_TAG)) { + // Line starts with optional indenting and then '-'. Strip the '-', parse again, readd the '-'. + final int startOfRestOfLine = line.indexOf(YAML_LIST_TAG) + YAML_LIST_TAG.length(); + final String prefix = line.substring(0, startOfRestOfLine); + final String restOfLine = line.substring(startOfRestOfLine); + return prefix + unescapeYamlLine(restOfLine); + } else if (line.contains(YAML_KEY_VALUE_SEPARATOR)) { + // Line is a "key: value" line. Strip the key, parse again, readd the key. + final int startOfRestOfLine = line.indexOf(YAML_KEY_VALUE_SEPARATOR) + YAML_KEY_VALUE_SEPARATOR.length(); + final String key = line.substring(0, startOfRestOfLine); + final String value = line.substring(startOfRestOfLine); + if (value.trim().startsWith(YAML_HELM_ESCAPE_KEY)) { + final String base64DecodedLine = Base64Util.decodeToString(value.substring(YAML_HELM_ESCAPE_KEY.length())); + return key + base64DecodedLine; + } else { + // Value was a regular value + return line; + } + } else { + // Line was a regular line + return line; } - return template; } - /** - * This function is responsible for reinstating the stand-alone Helm directives. - * For example: - * - *
-   * BASE64STRINGOFCHARACTERS=
-   * 
- * - * It is the opposite of {@link #escapeYamlTemplateLines(String)}. - * - * @see #escapeYamlTemplateLines(String) - */ - private static String unescapeYamlTemplateLines(String template) { - final Pattern compile = Pattern.compile("^( *-? *)escapedHelm[\\d]+: \"?(.*?)\"?$", Pattern.MULTILINE); - Matcher matcher = compile.matcher(template); - while (matcher.find()) { - final String indentation = matcher.group(1); - final String helmLine = Base64Util.decodeToString(matcher.group(2)); - template = matcher.replaceFirst(indentation + helmLine.replace("$", "\\$")); - matcher = compile.matcher(template); - } - return template; - } + private static String iterateOverLines(final String yaml, final IterateOverLinesCallback iterator) { + final Matcher matcher = Pattern.compile("(.*)(\\R|$)").matcher(yaml); + final StringBuilder result = new StringBuilder(); + int index = 0; + long lineNumber = 0; + while (matcher.find(index) && matcher.start() != matcher.end()) { + final String line = matcher.group(1); + final String lineEnding = matcher.group(2); - /** - * This function is responsible for escaping the Helm directives that are Yaml values. - * For example: - * - *
-   * someKey: {{ a bit of Helm }}
-   * 
- * - * @see #unescapeYamlTemplateValues(String) - */ - private static String escapeYamlTemplateValues(String template) { - final Pattern compile = Pattern.compile("^( *[^ ]+ *): *(" + HELM_DIRECTIVE_REGEX + ".*)$", Pattern.MULTILINE); - Matcher matcher = compile.matcher(template); - while (matcher.find()) { - final String indentation = matcher.group(1); - final String base64Value = Base64Util.encodeToString(matcher.group(2)); - template = matcher.replaceFirst(indentation + ": escapedHelmValue" + base64Value); - matcher = compile.matcher(template); + final String escapedYamlLine = iterator.onLine(line, lineEnding, lineNumber); + lineNumber++; + + result.append(escapedYamlLine); + + index = matcher.end(); } - return template; + return result.toString(); } - /** - * This function is responsible for reinstating the Helm directives that were Yaml values. - * For example: - * - *
-   * someKey: escapedHelmValueBASE64STRING==
-   * 
- * - * It is the opposite of {@link #escapeYamlTemplateValues(String)}. - * - * @see #escapeYamlTemplateValues(String) - */ - private static String unescapeYamlTemplateValues(String template) { - final Pattern compile = Pattern.compile("^( *[^ ]+ *): *\"?escapedHelmValue(.*?)\"?$", Pattern.MULTILINE); - Matcher matcher = compile.matcher(template); - while (matcher.find()) { - final String indentation = matcher.group(1); - final String helmValue = Base64Util.decodeToString(matcher.group(2)); - template = matcher.replaceFirst(indentation + ": " + helmValue.replace("$", "\\$")); - matcher = compile.matcher(template); - } - return template; + @FunctionalInterface + private interface IterateOverLinesCallback { + String onLine(String input, String lineEnding, long lineNumber); } } diff --git a/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/TemplateUtilTest.java b/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/TemplateUtilTest.java index 93087937e8..de9ab45cc0 100644 --- a/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/TemplateUtilTest.java +++ b/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/TemplateUtilTest.java @@ -13,70 +13,71 @@ */ package org.eclipse.jkube.kit.common.util; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.stream.Stream; - import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.jkube.kit.common.util.TemplateUtil.escapeYamlTemplate; import static org.eclipse.jkube.kit.common.util.TemplateUtil.unescapeYamlTemplate; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + class TemplateUtilTest { public static Stream data() { return Stream.of(new Object[][] { - // No Helm directive - { "abcd", "abcd" }, + // No Helm directive + { "abcd", "abcd" }, - // When the Helm directive is not the first on the line - { "abc{de}f}", "abc{de}f}" }, - { "abc{{de}f", "abc{{de}f" }, - { "abc{{$def}}", "abc{{$def}}" }, - { "abc{{de}}f", "abc{{de}}f" }, - { "abc{{de}f}}", "abc{{de}f}}" }, - { "abc{{def}}ghi{{jkl}}mno", "abc{{def}}ghi{{jkl}}mno" }, + // When the Helm directive is not the first on the line + { "abc{de}f}", "abc{de}f}" }, + { "abc{{de}f", "abc{{de}f" }, + { "abc{{$def}}", "abc{{$def}}" }, + { "abc{{de}}f", "abc{{de}}f" }, + { "abc{{de}f}}", "abc{{de}f}}" }, + { "abc{{def}}ghi{{jkl}}mno", "abc{{def}}ghi{{jkl}}mno" }, - // When the Helm directive is the first on the line - { "{de}f}", "{de}f}" }, - { "{{de}f", "{{de}f" }, - { "{{$def}}", "escapedHelm0: " + Base64Util.encodeToString("{{$def}}") }, - { "{{de}}f", "escapedHelm0: " + Base64Util.encodeToString("{{de}}f") }, - { "{{de}f}}", "escapedHelm0: " + Base64Util.encodeToString("{{de}f}}") }, - { "{{def}}ghi{{jkl}}mno", "escapedHelm0: " + Base64Util.encodeToString("{{def}}ghi{{jkl}}mno") }, - { "hello\n{{def}}\nworld", "hello\nescapedHelm0: " + Base64Util.encodeToString("{{def}}") + "\nworld" }, - { "- hello\n- {{def}}\n- world", "- hello\n- escapedHelm0: " + Base64Util.encodeToString("{{def}}") + "\n- world" }, - { "{{multiple}}\n{{helm}}\n{{lines}}", - "escapedHelm0: " + Base64Util.encodeToString("{{multiple}}") + "\n" + - "escapedHelm1: " + Base64Util.encodeToString("{{helm}}") + "\n" + - "escapedHelm2: " + Base64Util.encodeToString("{{lines}}") }, + // When the Helm directive is the first on the line + { "{de}f}", "{de}f}" }, + { "{{de}f", "escapedHelm0: " + Base64Util.encodeToString("{{de}f") }, + { "{{$def}}", "escapedHelm0: " + Base64Util.encodeToString("{{$def}}") }, + { "{{de}}f", "escapedHelm0: " + Base64Util.encodeToString("{{de}}f") }, + { "{{de}f}}", "escapedHelm0: " + Base64Util.encodeToString("{{de}f}}") }, + { "{{def}}ghi{{jkl}}mno", "escapedHelm0: " + Base64Util.encodeToString("{{def}}ghi{{jkl}}mno") }, + { "abc{{def}}ghi{{jkl}}mno", "abc{{def}}ghi{{jkl}}mno" }, + { "hello\n{{def}}\nworld", "hello\nescapedHelm0: " + Base64Util.encodeToString("{{def}}") + "\nworld" }, + { "{{multiple}}\n{{helm}}\n{{lines}}", + "escapedHelm0: " + Base64Util.encodeToString("{{multiple}}") + "\n" + + "escapedHelm1: " + Base64Util.encodeToString("{{helm}}") + "\n" + + "escapedHelm2: " + Base64Util.encodeToString("{{lines}}") }, + { "{{multiple\nhelm\nlines}}", + "escapedHelm0: " + Base64Util.encodeToString("{{multiple") + "\nhelm\nlines}}" }, + { "{{ include \"}}something{{}}}}{{\" . }}", + "escapedHelm0: " + Base64Util.encodeToString("{{ include \"}}something{{}}}}{{\" . }}") }, - // When the Helm directive is the first on the line, but indented - { " {de}f}", " {de}f}" }, - { " {{de}f", " {{de}f" }, - { " {{$def}}", " escapedHelm0: " + Base64Util.encodeToString("{{$def}}") }, - { " {{de}}f", " escapedHelm0: " + Base64Util.encodeToString("{{de}}f") }, - { " {{de}f}}", " escapedHelm0: " + Base64Util.encodeToString("{{de}f}}") }, - { " {{def}}ghi{{jkl}}mno", " escapedHelm0: " + Base64Util.encodeToString("{{def}}ghi{{jkl}}mno") }, - { "hello:\n {{def}}\n world", "hello:\n escapedHelm0: " + Base64Util.encodeToString("{{def}}") + "\n world" }, - { "hello:\n - {{def}}\n - world", - "hello:\n - escapedHelm0: " + Base64Util.encodeToString("{{def}}") + "\n - world" }, + // When the Helm directive is the first on the line, but indented + { " {de}f}", " {de}f}" }, + { " {{de}f", " escapedHelm0: " + Base64Util.encodeToString("{{de}f") }, + { " {{$def}}", " escapedHelm0: " + Base64Util.encodeToString("{{$def}}") }, + { " {{de}}f", " escapedHelm0: " + Base64Util.encodeToString("{{de}}f") }, + { " {{de}f}}", " escapedHelm0: " + Base64Util.encodeToString("{{de}f}}") }, + { " {{def}}ghi{{jkl}}mno", " escapedHelm0: " + Base64Util.encodeToString("{{def}}ghi{{jkl}}mno") }, + { "hello:\n {{def}}\n world", "hello:\n escapedHelm0: " + Base64Util.encodeToString("{{def}}") + "\n world" }, - // When the Helm directive is a value - { "key: {de}f}", "key: {de}f}" }, - { "key: {{de}f", "key: {{de}f" }, - { "key: {{$def}}", "key: escapedHelmValue" + Base64Util.encodeToString("{{$def}}") }, - { "key: {{de}}f", "key: escapedHelmValue" + Base64Util.encodeToString("{{de}}f") }, - { "key: {{de}f}}", "key: escapedHelmValue" + Base64Util.encodeToString("{{de}f}}") }, - { "key: {{def}}ghi{{jkl}}mno", "key: escapedHelmValue" + Base64Util.encodeToString("{{def}}ghi{{jkl}}mno") }, + // When Helm is used in a list + { "- hello\n- {{def}}\n- world {{ helm }}", + "- hello\n- escapedHelm0: " + Base64Util.encodeToString("{{def}}") + "\n- world {{ helm }}" }, + { "hello:\n - {{def}}\n - world\n - hello {{ helm }}", + "hello:\n - escapedHelm0: " + Base64Util.encodeToString("{{def}}") + "\n - world\n - hello {{ helm }}" }, - // When the Helm directive is a value, but indented - { " key: {de}f}", " key: {de}f}" }, - { " key: {{de}f", " key: {{de}f" }, - { " key: {{$def}}", " key: escapedHelmValue" + Base64Util.encodeToString("{{$def}}") }, - { " key: {{de}}f", " key: escapedHelmValue" + Base64Util.encodeToString("{{de}}f") }, - { " key: {{de}f}}", " key: escapedHelmValue" + Base64Util.encodeToString("{{de}f}}") }, - { " key: {{def}}ghi{{jkl}}mno", " key: escapedHelmValue" + Base64Util.encodeToString("{{def}}ghi{{jkl}}mno") }, + // When the Helm directive is a value + { "key: {de}f}", "key: {de}f}" }, + { "key: {{de}f", "key: escapedHelm" + Base64Util.encodeToString("{{de}f") }, + { "key: {{$def}}", "key: escapedHelm" + Base64Util.encodeToString("{{$def}}") }, + { "key: {{de}}f", "key: escapedHelm" + Base64Util.encodeToString("{{de}}f") }, + { " key: {{$def}}", " key: escapedHelm" + Base64Util.encodeToString("{{$def}}") }, + { "key: {{de}f}}", "key: escapedHelm" + Base64Util.encodeToString("{{de}f}}") }, + { "key: {{def}}ghi{{jkl}}mno", "key: escapedHelm" + Base64Util.encodeToString("{{def}}ghi{{jkl}}mno") }, }); } @@ -87,6 +88,6 @@ void escapeYamlTemplateTest(final String input, final String expected) { assertThat(escapedYaml).isEqualTo(expected); final String unescapedYaml = unescapeYamlTemplate(escapedYaml); - assertThat(input).isEqualTo(unescapedYaml); + assertThat(unescapedYaml).isEqualTo(input); } } \ No newline at end of file diff --git a/jkube-kit/helm/src/test/resources/it/expected/kubernetes/templates/kubernetes.yaml b/jkube-kit/helm/src/test/resources/it/expected/kubernetes/templates/kubernetes.yaml index a0a8823df0..9cf7820cb7 100644 --- a/jkube-kit/helm/src/test/resources/it/expected/kubernetes/templates/kubernetes.yaml +++ b/jkube-kit/helm/src/test/resources/it/expected/kubernetes/templates/kubernetes.yaml @@ -33,6 +33,7 @@ items: helm-variable: {{ required "A valid .Values.GLOBAL_TEMPLATE_ENV_VAR entry required!" .Values.GLOBAL_TEMPLATE_ENV_VAR }} value-that-looks-like-helm-test: "{{ $The term 'helm' finds its roots in Old English }}" value-that-is-a-helm-expression: {{ .Values.abandonShip }} + value-with-helm-expression-inside: So does {{ print "this" }} work? helm-golang-expression: {{ .Values.unused_value | upper | quote }} {{ end }} labels: @@ -42,7 +43,7 @@ items: name: test spec: ports: - - name: http + - {{ print "name: http" }} port: 8080 protocol: TCP targetPort: 8080 @@ -103,7 +104,7 @@ items: - containerPort: 8080 name: http protocol: TCP - - containerPort: 9779 + - containerPort: {{ .Values.prometheusPort }} name: prometheus protocol: TCP - containerPort: 8778 diff --git a/jkube-kit/helm/src/test/resources/it/sources/kubernetes/kubernetes.yml b/jkube-kit/helm/src/test/resources/it/sources/kubernetes/kubernetes.yml index 1e43391e9e..9f8bd75a94 100644 --- a/jkube-kit/helm/src/test/resources/it/sources/kubernetes/kubernetes.yml +++ b/jkube-kit/helm/src/test/resources/it/sources/kubernetes/kubernetes.yml @@ -33,6 +33,7 @@ items: helm-variable: ${GLOBAL_TEMPLATE_ENV_VAR} value-that-looks-like-helm-test: "{{ $The term 'helm' finds its roots in Old English }}" value-that-is-a-helm-expression: {{ .Values.abandonShip }} + value-with-helm-expression-inside: So does {{ print "this" }} work? helm-golang-expression: ${golang_expression} {{ end }} labels: @@ -42,7 +43,7 @@ items: name: test spec: ports: - - name: http + - {{ print "name: http" }} port: 8080 protocol: TCP targetPort: 8080 @@ -103,7 +104,7 @@ items: - containerPort: 8080 name: http protocol: TCP - - containerPort: 9779 + - containerPort: {{ .Values.prometheusPort }} name: prometheus protocol: TCP - containerPort: 8778