From 7c0accc95162b0f15abf16e138f1b5f0349323e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sun, 5 Jan 2025 16:05:09 +0100 Subject: [PATCH 1/3] improve: double bracket in object template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- .../GenericBulkDependentResource.java | 2 +- .../dependent/GenericDependentResource.java | 6 +++--- .../glue/reconciler/glue/GlueReconciler.java | 11 ++++++----- .../operator/GlueOperatorReconciler.java | 5 +++-- .../templating/GenericTemplateHandler.java | 19 ++++++++++++++----- .../GenericTemplateHandlerTest.java | 14 ++++++++++++++ 6 files changed, 41 insertions(+), 16 deletions(-) create mode 100644 src/test/java/io/javaoperatorsdk/operator/glue/templating/GenericTemplateHandlerTest.java diff --git a/src/main/java/io/javaoperatorsdk/operator/glue/dependent/GenericBulkDependentResource.java b/src/main/java/io/javaoperatorsdk/operator/glue/dependent/GenericBulkDependentResource.java index b509eea..a98dbd9 100644 --- a/src/main/java/io/javaoperatorsdk/operator/glue/dependent/GenericBulkDependentResource.java +++ b/src/main/java/io/javaoperatorsdk/operator/glue/dependent/GenericBulkDependentResource.java @@ -29,7 +29,7 @@ public GenericBulkDependentResource(GenericTemplateHandler genericTemplateHandle public Map desiredResources(Glue primary, Context context) { - var res = genericTemplateHandler.processTemplate(desiredTemplate, primary, context); + var res = genericTemplateHandler.processTemplate(desiredTemplate, primary, false, context); var desiredList = Serialization.unmarshal(res, GenericKubernetesResourceList.class).getItems(); desiredList.forEach(r -> { r.getMetadata().getAnnotations() diff --git a/src/main/java/io/javaoperatorsdk/operator/glue/dependent/GenericDependentResource.java b/src/main/java/io/javaoperatorsdk/operator/glue/dependent/GenericDependentResource.java index 9a2b251..a111aac 100644 --- a/src/main/java/io/javaoperatorsdk/operator/glue/dependent/GenericDependentResource.java +++ b/src/main/java/io/javaoperatorsdk/operator/glue/dependent/GenericDependentResource.java @@ -55,10 +55,10 @@ public GenericDependentResource(GenericTemplateHandler genericTemplateHandler, @Override protected GenericKubernetesResource desired(Glue primary, Context context) { + boolean objectTemplate = desired != null; + var template = objectTemplate ? Serialization.asYaml(desired) : desiredTemplate; - var template = desired == null ? desiredTemplate : Serialization.asYaml(desired); - - var res = genericTemplateHandler.processTemplate(template, primary, context); + var res = genericTemplateHandler.processTemplate(template, primary, objectTemplate, context); var resultDesired = Serialization.unmarshal(res, GenericKubernetesResource.class); resultDesired.getMetadata().getAnnotations() diff --git a/src/main/java/io/javaoperatorsdk/operator/glue/reconciler/glue/GlueReconciler.java b/src/main/java/io/javaoperatorsdk/operator/glue/reconciler/glue/GlueReconciler.java index a39a7bc..4521718 100644 --- a/src/main/java/io/javaoperatorsdk/operator/glue/reconciler/glue/GlueReconciler.java +++ b/src/main/java/io/javaoperatorsdk/operator/glue/reconciler/glue/GlueReconciler.java @@ -195,7 +195,7 @@ private void createAndAddDependentToWorkflow(Glue primary, Context context // todo test processing ns as template // name can reference related resources todo doc var targetNamespace = Utils.getNamespace(spec).map(ns -> genericTemplateHandler - .processTemplate(ns, primary, context)); + .processTemplate(ns, primary, false, context)); var resourceInSameNamespaceAsPrimary = targetNamespace.map(n -> n.trim().equals(primary.getMetadata().getNamespace().trim())) .orElse(true); @@ -205,7 +205,7 @@ private void createAndAddDependentToWorkflow(Glue primary, Context context if (!(dr instanceof BulkDependentResource)) { dr.setResourceDiscriminator(new GenericResourceDiscriminator(dr.getGroupVersionKind(), - genericTemplateHandler.processTemplate(Utils.getName(spec), primary, context), + genericTemplateHandler.processTemplate(Utils.getName(spec), primary, false, context), targetNamespace.orElse(null))); } @@ -267,10 +267,11 @@ private void patchRelatedResourcesStatus(Context context, targetRelatedResources.forEach(r -> { var relatedResources = Utils.getRelatedResources(primary, r, context); - var template = r.getStatusPatchTemplate() != null ? r.getStatusPatchTemplate() - : Serialization.asYaml(r.getStatusPatch()); + var objectTemplate = r.getStatusPatch() != null; + var template = + objectTemplate ? Serialization.asYaml(r.getStatusPatch()) : r.getStatusPatchTemplate(); var resultTemplate = - genericTemplateHandler.processTemplate(actualData, template); + genericTemplateHandler.processTemplate(actualData, template, objectTemplate); var statusObjectMap = GenericTemplateHandler.parseTemplateToMapObject(resultTemplate); relatedResources.forEach((n, kr) -> { kr.setAdditionalProperty("status", statusObjectMap); diff --git a/src/main/java/io/javaoperatorsdk/operator/glue/reconciler/operator/GlueOperatorReconciler.java b/src/main/java/io/javaoperatorsdk/operator/glue/reconciler/operator/GlueOperatorReconciler.java index fb17073..d1943a0 100644 --- a/src/main/java/io/javaoperatorsdk/operator/glue/reconciler/operator/GlueOperatorReconciler.java +++ b/src/main/java/io/javaoperatorsdk/operator/glue/reconciler/operator/GlueOperatorReconciler.java @@ -144,9 +144,10 @@ private ObjectMeta glueMetadata(GlueOperator glueOperator, if (glueMeta != null) { // optimize var data = Map.of(PARENT_RELATED_RESOURCE_NAME, parent); - var glueName = genericTemplateHandler.processInputAndTemplate(data, glueMeta.getName()); + var glueName = + genericTemplateHandler.processInputAndTemplate(data, glueMeta.getName(), false); var glueNamespace = - genericTemplateHandler.processInputAndTemplate(data, glueMeta.getNamespace()); + genericTemplateHandler.processInputAndTemplate(data, glueMeta.getNamespace(), false); objectMetaBuilder.withName(glueName); objectMetaBuilder.withNamespace(glueNamespace); } else { diff --git a/src/main/java/io/javaoperatorsdk/operator/glue/templating/GenericTemplateHandler.java b/src/main/java/io/javaoperatorsdk/operator/glue/templating/GenericTemplateHandler.java index c938aea..765d435 100644 --- a/src/main/java/io/javaoperatorsdk/operator/glue/templating/GenericTemplateHandler.java +++ b/src/main/java/io/javaoperatorsdk/operator/glue/templating/GenericTemplateHandler.java @@ -23,22 +23,31 @@ public class GenericTemplateHandler { private static final ObjectMapper objectMapper = new ObjectMapper(); private static final Engine engine = Engine.builder().addDefaults().build(); - public String processTemplate(Map> data, String template) { + public String processTemplate(Map> data, String template, + boolean objectTemplate) { + if (objectTemplate) { + template = handleDoubleCurlyBrackets(template); + } Template parsedTemplate = engine.parse(template); return parsedTemplate.data(data).render(); } + private String handleDoubleCurlyBrackets(String template) { + template = template.replace("\"{{", "{"); + return template.replace("}}\n", "}"); + } + public String processInputAndTemplate(Map data, - String template) { + String template, boolean objectTemplate) { Map> res = genericKubernetesResourceDataToGenericData(data); - return processTemplate(res, template); + return processTemplate(res, template, objectTemplate); } - public String processTemplate(String template, Glue primary, + public String processTemplate(String template, Glue primary, boolean objectTemplate, Context context) { var data = createDataWithResources(primary, context); - return processTemplate(data, template); + return processTemplate(data, template, objectTemplate); } private static Map> genericKubernetesResourceDataToGenericData( diff --git a/src/test/java/io/javaoperatorsdk/operator/glue/templating/GenericTemplateHandlerTest.java b/src/test/java/io/javaoperatorsdk/operator/glue/templating/GenericTemplateHandlerTest.java new file mode 100644 index 0000000..a3c1617 --- /dev/null +++ b/src/test/java/io/javaoperatorsdk/operator/glue/templating/GenericTemplateHandlerTest.java @@ -0,0 +1,14 @@ +package io.javaoperatorsdk.operator.glue.templating; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class GenericTemplateHandlerTest { + + @Test + void testDoubleCurlyBrackets() { + fail(); + } + +} From 1f9d3834cb62d37876e0ce207680cf15b3fcb0ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sun, 5 Jan 2025 20:15:42 +0100 Subject: [PATCH 2/3] improve: double brackets to handle non string values in object templates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- README.md | 4 +-- docs/reference.md | 27 +++++++++++++++++-- .../templating/GenericTemplateHandler.java | 2 +- .../GenericTemplateHandlerTest.java | 26 +++++++++++++++++- .../sample/webpage/webpage.operator.yaml | 4 +-- 5 files changed, 55 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 8f4f770..9e83b9f 100644 --- a/README.md +++ b/README.md @@ -67,8 +67,8 @@ spec: parent: apiVersion: glueoperator.sample/v1 # watches all the custom resource of type WebPage kind: WebPage - statusTemplate: | # update the status of the custom resource at the end of reconciliation - observedGeneration: {parent.metadata.generation} + status: # update the status of the custom resource at the end of reconciliation + observedGeneration: "{{parent.metadata.generation}}" # since it's a non-string value needs double curly brackets childResources: - name: htmlconfigmap resource: diff --git a/docs/reference.md b/docs/reference.md index 0c9101b..19b7c80 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -8,8 +8,31 @@ Although it is limited only to Kubernetes resources it makes it very easy to use ## Generic Notes -- All templates (both object and string based) uses [Qute templating engine](https://quarkus.io/guides/qute-reference). While objects allow only - placeholders, you can use the full power of qute in string templates. + - All templates (both object and string-based) uses [Qute templating engine](https://quarkus.io/guides/qute-reference). While objects allow only + placeholders, you can use the full power of qute in string templates. + + ONLY for object-based templates (thus not string templates) the values can be set using the placeholder notation from Qute: + ```yaml + value: "{string.value.reference}" + ``` + With this standard notation, the result value will be always encoded in double quotes: + ```yaml + value: "1" + ``` + Since there is no simple way to check if the referenced value is a string or other value + (boolean, numeric, etc) for non-string values, user should use double brackets: + ```yaml + value: "{{nonstring.value.reference}}" + ``` + what would result in a value without enclosed double quotes in the produced yaml: + ```yaml + value: 1 + ``` + See sample [here](https://github.com/java-operator-sdk/kubernetes-glue-operator/blob/main/src/test/resources/sample/webpage/webpage.operator.yaml#L10). + Implementation wise, this is a preprocessor that strips the enclosed quotes and additional curly bracket + before it is passed to Qute. + In the future, we might remove such obligation by checking the type + of the target value in the related schema. ## [Glue resource](https://github.com/java-operator-sdk/kubernetes-glue-operator/releases/latest/download/glues.glue-v1.yml) diff --git a/src/main/java/io/javaoperatorsdk/operator/glue/templating/GenericTemplateHandler.java b/src/main/java/io/javaoperatorsdk/operator/glue/templating/GenericTemplateHandler.java index 765d435..babe5ed 100644 --- a/src/main/java/io/javaoperatorsdk/operator/glue/templating/GenericTemplateHandler.java +++ b/src/main/java/io/javaoperatorsdk/operator/glue/templating/GenericTemplateHandler.java @@ -34,7 +34,7 @@ public String processTemplate(Map> data, String template, private String handleDoubleCurlyBrackets(String template) { template = template.replace("\"{{", "{"); - return template.replace("}}\n", "}"); + return template.replace("}}\"", "}"); } public String processInputAndTemplate(Map data, diff --git a/src/test/java/io/javaoperatorsdk/operator/glue/templating/GenericTemplateHandlerTest.java b/src/test/java/io/javaoperatorsdk/operator/glue/templating/GenericTemplateHandlerTest.java index a3c1617..73422c3 100644 --- a/src/test/java/io/javaoperatorsdk/operator/glue/templating/GenericTemplateHandlerTest.java +++ b/src/test/java/io/javaoperatorsdk/operator/glue/templating/GenericTemplateHandlerTest.java @@ -1,14 +1,38 @@ package io.javaoperatorsdk.operator.glue.templating; +import java.util.HashMap; +import java.util.Map; + import org.junit.jupiter.api.Test; +import io.fabric8.kubernetes.client.utils.Serialization; + import static org.junit.jupiter.api.Assertions.*; class GenericTemplateHandlerTest { + GenericTemplateHandler templateHandler = new GenericTemplateHandler(); + @Test void testDoubleCurlyBrackets() { - fail(); + var template = """ + intValue: "{{spec.intvalue}}" + stringValue: "{spec.stringvalue}" + """; + + Map> data = new HashMap<>(); + + Map values = new HashMap(); + values.put("intvalue", 1); + values.put("stringvalue", "value1"); + data.put("spec", values); + + var result = templateHandler.processTemplate(data, template, true); + + Map mapResult = Serialization.unmarshal(result, Map.class); + + assertEquals(1, mapResult.get("intValue")); + assertEquals("value1", mapResult.get("stringValue")); } } diff --git a/src/test/resources/sample/webpage/webpage.operator.yaml b/src/test/resources/sample/webpage/webpage.operator.yaml index 53606fe..5ae8fe8 100644 --- a/src/test/resources/sample/webpage/webpage.operator.yaml +++ b/src/test/resources/sample/webpage/webpage.operator.yaml @@ -6,8 +6,8 @@ spec: parent: apiVersion: glueoperator.sample/v1 kind: WebPage - statusTemplate: | - observedGeneration: {parent.metadata.generation} + status: + observedGeneration: "{{parent.metadata.generation}}" childResources: - name: htmlconfigmap resource: From c895c7c85fbcfd65baea14a0ece2d68603e10da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20M=C3=A9sz=C3=A1ros?= Date: Sun, 5 Jan 2025 20:24:16 +0100 Subject: [PATCH 3/3] wip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Attila Mészáros --- docs/reference.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference.md b/docs/reference.md index 19b7c80..49bec73 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -13,7 +13,7 @@ Although it is limited only to Kubernetes resources it makes it very easy to use ONLY for object-based templates (thus not string templates) the values can be set using the placeholder notation from Qute: ```yaml - value: "{string.value.reference}" + value: "{string.value}" ``` With this standard notation, the result value will be always encoded in double quotes: ```yaml @@ -22,7 +22,7 @@ Although it is limited only to Kubernetes resources it makes it very easy to use Since there is no simple way to check if the referenced value is a string or other value (boolean, numeric, etc) for non-string values, user should use double brackets: ```yaml - value: "{{nonstring.value.reference}}" + value: "{{nonstring.value}}" ``` what would result in a value without enclosed double quotes in the produced yaml: ```yaml