1313 */
1414package org .eclipse .jkube .kit .common .util ;
1515
16+ import java .lang .invoke .MethodHandles ;
17+ import java .util .concurrent .atomic .AtomicLong ;
1618import java .util .regex .Matcher ;
1719import java .util .regex .Pattern ;
1820
21+ import org .apache .commons .lang3 .StringUtils ;
22+ import org .slf4j .Logger ;
23+ import org .slf4j .LoggerFactory ;
24+
1925public class TemplateUtil {
20- private static final String HELM_DIRECTIVE_REGEX = "\\ {\\ {.*\\ }\\ }" ;
26+ private static final Logger LOG = LoggerFactory .getLogger (MethodHandles .lookup ().lookupClass ());
27+
28+ private static final String HELM_END_TAG = "}}" ;
29+ private static final String HELM_START_TAG = "{{" ;
30+
31+ private static final String YAML_HELM_ESCAPE_KEY = "escapedHelm" ;
32+ private static final String YAML_LIST_TAG = "-" ;
33+ private static final String YAML_KEY_VALUE_SEPARATOR = ": " ;
2134
2235 private TemplateUtil () {
2336 }
2437
2538 /**
26- * This function will replace all Helm directives with a valid Yaml line containing the base64 encoded Helm directive.
39+ * This function will replace all single line Helm directives with a valid Yaml line containing the base64 encoded Helm
40+ * directive.
41+ * Helm directives that are values (so not a full line) will be left alone.
2742 *
2843 * Helm lines that are by themselves will be converted like so:
2944 * <br/>
@@ -32,17 +47,17 @@ private TemplateUtil() {
3247 * <pre>
3348 * {{- $myDate := .Value.date }}
3449 * {{ include "something" . }}
50+ * myKey: "Regular line"
3551 * someKey: {{ a bit of Helm }}
36- * someOtherKey: {{ another bit of Helm }}
3752 * </pre>
3853 *
3954 * Output:
4055 *
4156 * <pre>
42- * escapedHelm0: BASE64STRINGOFCHARACTERS =
43- * escapedHelm1: ANOTHERBASE64STRING =
44- * someKey: escapedHelmValueBASE64STRING==
45- * someOtherKey: escapedHelmValueBASE64STRING
57+ * escapedHelm0: e3stICA6PSAuVmFsdWUuZGF0ZSB9fQ= =
58+ * escapedHelm1: e3sgaW5jbHVkZSBzb21ldGhpbmcgLiB9fQ= =
59+ * myKey: "Regular line"
60+ * someKey: {{ a bit of Helm }}
4661 * </pre>
4762 *
4863 * The <strong>escapedHelm</strong> and <strong>escapedHelmValue</strong> flags are needed for unescaping.
@@ -52,7 +67,39 @@ private TemplateUtil() {
5267 * @see #unescapeYamlTemplate(String)
5368 */
5469 public static String escapeYamlTemplate (final String yaml ) {
55- return escapeYamlTemplateLines (escapeYamlTemplateValues (yaml ));
70+ final AtomicLong helmEscapeIndex = new AtomicLong ();
71+ return iterateOverLines (yaml ,
72+ (line , lineEnding , lineNumber ) -> escapeYamlLine (line , lineNumber , helmEscapeIndex ) + lineEnding );
73+ }
74+
75+ private static String escapeYamlLine (final String line , final long lineNumber , final AtomicLong helmEscapeIndex ) {
76+ if (line .trim ().startsWith (HELM_START_TAG )) {
77+ // Line starts with optional indenting and then '{{', so replace whole line
78+ final int helmStartCount = StringUtils .countMatches (line , HELM_START_TAG );
79+ final int helmEndCount = StringUtils .countMatches (line , HELM_END_TAG );
80+ if (helmStartCount != helmEndCount ) {
81+ LOG .warn ("Found {} Helm start tag{} ('{}') but {} end tag{} ('{}') on line {}. "
82+ + "Expected this to be equal! Note that multi-line Helm directives are not supported." ,
83+ helmStartCount , helmStartCount == 1 ? "" : "s" , HELM_START_TAG ,
84+ helmEndCount , helmEndCount == 1 ? "" : "s" , HELM_END_TAG ,
85+ lineNumber );
86+ }
87+
88+ final int startOfHelm = line .indexOf (HELM_START_TAG );
89+ final String indentation = line .substring (0 , startOfHelm );
90+ final String base64EncodedLine = Base64Util .encodeToString (line .substring (startOfHelm ));
91+ final long currentHelmEscapeIndex = helmEscapeIndex .getAndIncrement ();
92+ return indentation + YAML_HELM_ESCAPE_KEY + currentHelmEscapeIndex + YAML_KEY_VALUE_SEPARATOR + base64EncodedLine ;
93+ } else if (line .trim ().startsWith (YAML_LIST_TAG )) {
94+ // Line starts with optional indenting and then '-'. Strip the '-', parse again, readd the '-'.
95+ final int startOfRestOfLine = line .indexOf (YAML_LIST_TAG ) + YAML_LIST_TAG .length ();
96+ final String prefix = line .substring (0 , startOfRestOfLine );
97+ final String restOfLine = line .substring (startOfRestOfLine );
98+ return prefix + escapeYamlLine (restOfLine , lineNumber , helmEscapeIndex );
99+ } else {
100+ // Line was a regular line
101+ return line ;
102+ }
56103 }
57104
58105 /**
@@ -64,101 +111,51 @@ public static String escapeYamlTemplate(final String yaml) {
64111 * @return the Yaml that was originally provided to {@link #escapeYamlTemplate(String)}
65112 * @see #escapeYamlTemplate(String)
66113 */
67- public static String unescapeYamlTemplate (final String template ) {
68- return unescapeYamlTemplateLines ( unescapeYamlTemplateValues ( template ) );
114+ public static String unescapeYamlTemplate (final String yaml ) {
115+ return iterateOverLines ( yaml , ( line , lineEnding , lineNumber ) -> unescapeYamlLine ( line ) + lineEnding );
69116 }
70117
71- /**
72- * This function is responsible for escaping the Helm directives that are stand-alone.
73- * For example:
74- *
75- * <pre>
76- * {{ include "something" . }}
77- * </pre>
78- *
79- * @see #unescapeYamlTemplateLines(String)
80- */
81- private static String escapeYamlTemplateLines (String template ) {
82- long escapedHelmIndex = 0 ;
83- final Pattern compile = Pattern .compile ("^( *-? *)(" + HELM_DIRECTIVE_REGEX + ".*)$" , Pattern .MULTILINE );
84- Matcher matcher = compile .matcher (template );
85- while (matcher .find ()) {
86- final String indentation = matcher .group (1 );
87- final String base64Line = Base64Util .encodeToString (matcher .group (2 ));
88- template = matcher .replaceFirst (indentation + "escapedHelm" + escapedHelmIndex + ": " + base64Line );
89- matcher = compile .matcher (template );
90- escapedHelmIndex ++;
118+ private static String unescapeYamlLine (final String line ) {
119+ if (line .trim ().startsWith (YAML_HELM_ESCAPE_KEY )) {
120+ // Line starts with optional indenting and then 'escapedHelm', so replace whole line
121+ final int endOfIndentation = line .indexOf (YAML_HELM_ESCAPE_KEY );
122+ final int startOfBase64Helm = line .indexOf (YAML_KEY_VALUE_SEPARATOR ) + YAML_KEY_VALUE_SEPARATOR .length ();
123+ final String indentation = line .substring (0 , endOfIndentation );
124+ final String base64DecodedLine = Base64Util .decodeToString (line .substring (startOfBase64Helm ));
125+ return indentation + base64DecodedLine ;
126+ } else if (line .trim ().startsWith (YAML_LIST_TAG )) {
127+ // Line starts with optional indenting and then '-'. Strip the '-', parse again, readd the '-'.
128+ final int startOfRestOfLine = line .indexOf (YAML_LIST_TAG ) + YAML_LIST_TAG .length ();
129+ final String prefix = line .substring (0 , startOfRestOfLine );
130+ final String restOfLine = line .substring (startOfRestOfLine );
131+ return prefix + unescapeYamlLine (restOfLine );
132+ } else {
133+ // Line was a regular line
134+ return line ;
91135 }
92- return template ;
93136 }
94137
95- /**
96- * This function is responsible for reinstating the stand-alone Helm directives.
97- * For example:
98- *
99- * <pre>
100- * BASE64STRINGOFCHARACTERS=
101- * </pre>
102- *
103- * It is the opposite of {@link #escapeYamlTemplateLines(String)}.
104- *
105- * @see #escapeYamlTemplateLines(String)
106- */
107- private static String unescapeYamlTemplateLines (String template ) {
108- final Pattern compile = Pattern .compile ("^( *-? *)escapedHelm[\\ d]+: \" ?(.*?)\" ?$" , Pattern .MULTILINE );
109- Matcher matcher = compile .matcher (template );
110- while (matcher .find ()) {
111- final String indentation = matcher .group (1 );
112- final String helmLine = Base64Util .decodeToString (matcher .group (2 ));
113- template = matcher .replaceFirst (indentation + helmLine .replace ("$" , "\\ $" ));
114- matcher = compile .matcher (template );
115- }
116- return template ;
117- }
138+ private static String iterateOverLines (final String yaml , final IterateOverLinesCallback iterator ) {
139+ final Matcher matcher = Pattern .compile ("(.*)(\\ R|$)" ).matcher (yaml );
140+ final StringBuilder result = new StringBuilder ();
141+ int index = 0 ;
142+ long lineNumber = 0 ;
143+ while (matcher .find (index ) && matcher .start () != matcher .end ()) {
144+ final String line = matcher .group (1 );
145+ final String lineEnding = matcher .group (2 );
118146
119- /**
120- * This function is responsible for escaping the Helm directives that are Yaml values.
121- * For example:
122- *
123- * <pre>
124- * someKey: {{ a bit of Helm }}
125- * </pre>
126- *
127- * @see #unescapeYamlTemplateValues(String)
128- */
129- private static String escapeYamlTemplateValues (String template ) {
130- final Pattern compile = Pattern .compile ("^( *[^ ]+ *): *(" + HELM_DIRECTIVE_REGEX + ".*)$" , Pattern .MULTILINE );
131- Matcher matcher = compile .matcher (template );
132- while (matcher .find ()) {
133- final String indentation = matcher .group (1 );
134- final String base64Value = Base64Util .encodeToString (matcher .group (2 ));
135- template = matcher .replaceFirst (indentation + ": escapedHelmValue" + base64Value );
136- matcher = compile .matcher (template );
147+ final String escapedYamlLine = iterator .onLine (line , lineEnding , lineNumber );
148+ lineNumber ++;
149+
150+ result .append (escapedYamlLine );
151+
152+ index = matcher .end ();
137153 }
138- return template ;
154+ return result . toString () ;
139155 }
140156
141- /**
142- * This function is responsible for reinstating the Helm directives that were Yaml values.
143- * For example:
144- *
145- * <pre>
146- * someKey: escapedHelmValueBASE64STRING==
147- * </pre>
148- *
149- * It is the opposite of {@link #escapeYamlTemplateValues(String)}.
150- *
151- * @see #escapeYamlTemplateValues(String)
152- */
153- private static String unescapeYamlTemplateValues (String template ) {
154- final Pattern compile = Pattern .compile ("^( *[^ ]+ *): *\" ?escapedHelmValue(.*?)\" ?$" , Pattern .MULTILINE );
155- Matcher matcher = compile .matcher (template );
156- while (matcher .find ()) {
157- final String indentation = matcher .group (1 );
158- final String helmValue = Base64Util .decodeToString (matcher .group (2 ));
159- template = matcher .replaceFirst (indentation + ": " + helmValue .replace ("$" , "\\ $" ));
160- matcher = compile .matcher (template );
161- }
162- return template ;
157+ @ FunctionalInterface
158+ private interface IterateOverLinesCallback {
159+ String onLine (String input , String lineEnding , long lineNumber );
163160 }
164161}
0 commit comments