Skip to content

improve: double bracket in object template #166

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
27 changes: 25 additions & 2 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
```
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}}"
```
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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public GenericBulkDependentResource(GenericTemplateHandler genericTemplateHandle
public Map<String, GenericKubernetesResource> desiredResources(Glue primary,
Context<Glue> 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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ public GenericDependentResource(GenericTemplateHandler genericTemplateHandler,
@Override
protected GenericKubernetesResource desired(Glue primary,
Context<Glue> 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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ private void createAndAddDependentToWorkflow(Glue primary, Context<Glue> 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);
Expand All @@ -205,7 +205,7 @@ private void createAndAddDependentToWorkflow(Glue primary, Context<Glue> 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)));
}

Expand Down Expand Up @@ -267,10 +267,11 @@ private void patchRelatedResourcesStatus(Context<Glue> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Map<?, ?>> data, String template) {
public String processTemplate(Map<String, 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("}}\"", "}");
}

public String processInputAndTemplate(Map<String, GenericKubernetesResource> data,
String template) {
String template, boolean objectTemplate) {
Map<String, 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<Glue> context) {
var data = createDataWithResources(primary, context);
return processTemplate(data, template);
return processTemplate(data, template, objectTemplate);
}

private static Map<String, Map<?, ?>> genericKubernetesResourceDataToGenericData(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +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() {
var template = """
intValue: "{{spec.intvalue}}"
stringValue: "{spec.stringvalue}"
""";

Map<String, 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"));
}

}
4 changes: 2 additions & 2 deletions src/test/resources/sample/webpage/webpage.operator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Loading