Skip to content

Commit

Permalink
[descriptors][templating] experimental handlebar support
Browse files Browse the repository at this point in the history
  • Loading branch information
rmannibucau committed Mar 29, 2024
1 parent 0152958 commit 1b97290
Show file tree
Hide file tree
Showing 13 changed files with 354 additions and 33 deletions.
16 changes: 10 additions & 6 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Clone
uses: actions/checkout@v1
uses: actions/checkout@v4
- name: Cache Maven Repository
uses: actions/cache@v2
with:
Expand All @@ -36,9 +36,11 @@ jobs:
/home/runner/work/bundlebee/bundlebee/bundlebee-documentation/src/main/minisite/assets/generated/kubernetes
key: m2_repository
- name: Set up JDK
uses: actions/setup-java@v1
uses: actions/setup-java@v4
with:
java-version: '11.0.21'
distribution: 'zulu'
java-version: '17'
cache: 'maven'
- name: Build
run: mvn install -Pnative -Dm2.location=/home/runner/work/bundlebee/bundlebee/.m2
- name: Remove Snapshots Before Caching
Expand All @@ -48,16 +50,18 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Clone
uses: actions/checkout@v1
uses: actions/checkout@v4
- name: Cache Maven Repository
uses: actions/cache@v1
with:
path: /home/runner/work/bundlebee/bundlebee/.m2
key: m2_repository
- name: Set up JDK
uses: actions/setup-java@v1
uses: actions/setup-java@v4
with:
java-version: '11.0.21'
distribution: 'zulu'
java-version: '17'
cache: 'maven'
- name: Build
run: mvn install -DskipTests ossindex:audit
- name: Remove Snapshots Before Caching
Expand Down
24 changes: 24 additions & 0 deletions bundlebee-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,17 +93,41 @@
<version>${yupiik-tools.version}</version>
</dependency>

<dependency> <!-- for now experimental (we bridged the plain placeholder to hb) -->
<groupId>io.yupiik.fusion</groupId>
<artifactId>fusion-handlebars</artifactId>
<version>1.0.16</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>io.yupiik.fusion</groupId>
<artifactId>fusion-api</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.apache.openwebbeans</groupId>
<artifactId>openwebbeans-junit5</artifactId>
<version>${owb.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.nashorn</groupId>
<artifactId>nashorn-core</artifactId>
<version>15.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.talend.sdk.component</groupId>
<artifactId>component-runtime-http-junit</artifactId>
<scope>test</scope>
</dependency>
<dependency> <!-- for http-junit self seigned cert generation on java 17 -->
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<scope>test</scope>
</dependency>
<dependency> <!-- for talend mock http server -->
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright (c) 2021 - present - Yupiik SAS - https://www.yupiik.com
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package io.yupiik.bundlebee.core.handlebars;

import io.yupiik.bundlebee.core.descriptor.Manifest;
import io.yupiik.bundlebee.core.service.AlveolusHandler;
import io.yupiik.fusion.framework.handlebars.HandlebarsCompiler;
import io.yupiik.fusion.framework.handlebars.compiler.accessor.MapAccessor;

import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toMap;

public class HandlebarsInterpolator {
private final Manifest.Alveolus alveolus;
private final AlveolusHandler.LoadedDescriptor descriptor;
private final String id;
private final Map<String, Function<Object, String>> helpers;
private final BiFunction<String, String, String> defaultLookup;

public HandlebarsInterpolator(final Manifest.Alveolus alveolus,
final AlveolusHandler.LoadedDescriptor descriptor,
final String id,
final Map<String, Function<Object, String>> customHelpers,
final BiFunction<String, String, String> defaultLookup) {
this.alveolus = alveolus;
this.descriptor = descriptor;
this.id = id;
this.helpers = customHelpers;
this.defaultLookup = defaultLookup;
}

public String apply(final String template) {
final Map<String, Function<Object, String>> specificHelpers = helpers.entrySet().stream()
.collect(toMap(
Map.Entry::getKey,
// inject the id as last param
p -> o -> p.getValue().apply(new Object[]{o, id})));
specificHelpers.put("base64", a -> Base64.getEncoder().encodeToString(a.toString().getBytes(UTF_8)));
specificHelpers.put("base64url", a -> Base64.getUrlEncoder().withoutPadding().encodeToString(a.toString().getBytes(UTF_8)));

final var rootData = Map.of(
"alveolus", alveolus,
"descriptor", descriptor,
"executionId", id == null ? "" : id);
return new HandlebarsCompiler(
new MapAccessor() {
@Override
public Object find(final Object data, final String name) {
if (data instanceof String) { // standard placeholder?
return doDefaultLookup(data + "." + name);
}

final var found = super.find(data, name);
if (found == null) {
return doDefaultLookup(name);
}

if (found instanceof Manifest.Alveolus) {
return asMap((Manifest.Alveolus) found);
}
if (found instanceof AlveolusHandler.LoadedDescriptor) {
return asMap((AlveolusHandler.LoadedDescriptor) found);
}
return found;
}
})
.compile(new HandlebarsCompiler.CompilationContext(new HandlebarsCompiler.Settings().helpers(specificHelpers), template))
.render(rootData);
}

private String doDefaultLookup(final String name) {
final var value = defaultLookup.apply("{{" + name + "}}", id);
if (value == null || "null".equals(value)) {
return name;
}
return value;
}

private Map<Object, Object> asMap(final Manifest.Alveolus alveolus) {
final var map = new HashMap<>();
map.put("name", alveolus.getName());
if (alveolus.getVersion() != null) {
map.put("version", alveolus.getVersion());
}
if (alveolus.getPlaceholders() != null) {
map.putAll(alveolus.getPlaceholders());
}
return map;
}

private Map<Object, Object> asMap(final AlveolusHandler.LoadedDescriptor descriptor) {
final var map = new HashMap<>();
map.put("name", descriptor.getConfiguration().getName());
if (descriptor.getConfiguration().getLocation() != null) {
map.put("location", descriptor.getConfiguration().getLocation());
}
if (descriptor.getConfiguration().getType() != null) {
map.put("type", descriptor.getConfiguration().getType());
}
return map;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@
*/
package io.yupiik.bundlebee.core.lang;

import io.yupiik.bundlebee.core.descriptor.Manifest;
import io.yupiik.bundlebee.core.handlebars.HandlebarsInterpolator;
import io.yupiik.bundlebee.core.service.AlveolusHandler;

import javax.enterprise.inject.Vetoed;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;

Expand Down Expand Up @@ -49,21 +54,18 @@ public String replace(final String source) {
return replace(source, null);
}

public String replace(final Manifest.Alveolus alveolus,
final AlveolusHandler.LoadedDescriptor desc,
final String source, final String id) {
return doReplace(alveolus, desc, source, id);
}

public String replace(final String source, final String id) {
if (source == null) {
return null;
}
String current = source;
do {
final var previous = current;
current = substitute(current, 0, id);
if (previous.equals(current)) {
return previous.replace(ESCAPE + PREFIX, PREFIX);
}
} while (true);
return replace(null, null, source, id);
}

private String substitute(final String input, int iteration, final String id) {
private String substitute(final Manifest.Alveolus alveolus, AlveolusHandler.LoadedDescriptor descriptor,
final String input, int iteration, final String id) {
if (iteration > maxIterations) {
return input;
}
Expand All @@ -90,7 +92,7 @@ private String substitute(final String input, int iteration, final String id) {
final var nested = key.indexOf(PREFIX);
if (nested >= 0 && !(nested > 0 && key.charAt(nested - 1) == ESCAPE)) {
final var nestedPlaceholder = key + SUFFIX;
final var newKey = substitute(nestedPlaceholder, iteration + 1, id);
final var newKey = substitute(alveolus, descriptor, nestedPlaceholder, iteration + 1, id);
return input.replace(nestedPlaceholder, newKey);
}

Expand All @@ -101,16 +103,62 @@ private String substitute(final String input, int iteration, final String id) {
if (sep > 0) {
final var actualKey = key.substring(0, sep);
final var fallback = key.substring(sep + VALUE_DELIMITER.length());
return startOfString + getOrDefault(actualKey, fallback, id) + endOfString;
return startOfString + doGetOrDefault(alveolus, descriptor, actualKey, fallback, id) + endOfString;
}
return startOfString + doGetOrDefault(alveolus, descriptor, key, null, id) + endOfString;
}

protected Map<String, Function<Object, String>> handlebarsHelpers() {
return Map.of();
}

private String handlebars(final Manifest.Alveolus alveolus, final AlveolusHandler.LoadedDescriptor desc,
final String source, final String id) {
return new HandlebarsInterpolator(alveolus, desc, id, handlebarsHelpers(), this::replace).apply(source);
}

private String doReplace(final Manifest.Alveolus alveolus, final AlveolusHandler.LoadedDescriptor desc, final String source, final String id) {
if (source == null) {
return null;
}

if (desc != null && desc.getExtension() != null && ("hb".equals(desc.getExtension()) || "handlebars".equals(desc.getExtension()))) {
return handlebars(alveolus, desc, source, id);
}
return startOfString + getOrDefault(key, null, id) + endOfString;

String current = source;
do {
final var previous = current;
current = substitute(alveolus, desc, current, 0, id);
if (previous.equals(current)) {
return previous.replace(ESCAPE + PREFIX, PREFIX);
}
} while (true);
}

@Deprecated
protected String getOrDefault(final String varName, final String varDefaultValue) {
return getOrDefault(varName, varDefaultValue, null);
}

protected String doGetOrDefault(final Manifest.Alveolus alveolus,
final AlveolusHandler.LoadedDescriptor descriptor,
final String varName, final String varDefaultValue, final String id) {
if ("executionId".equals(varName)) {
return id == null ? "" : id;
}
if ("descriptor.name".equals(varName)) {
return descriptor == null ? "" : descriptor.getConfiguration().getName();
}
if ("alveolus.name".equals(varName)) {
return alveolus == null ? "" : alveolus.getName();
}
if ("alveolus.version".equals(varName)) {
return alveolus == null || alveolus.getVersion() == null ? "" : alveolus.getVersion();
}
return getOrDefault(varName, varDefaultValue, id);
}

protected String getOrDefault(final String varName, final String varDefaultValue, final String id) {
try {
return ofNullable(lookup.apply(varName, varDefaultValue)).orElse(varDefaultValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Spliterator;
import java.util.Spliterators;
Expand Down Expand Up @@ -108,7 +110,13 @@ public Substitutor substitutor(final Config config) {
() -> getClass().getDeclaredMethod("doSubstitute", AtomicReference.class, Config.class, String.class).getDeclaringClass() != SubstitutorProducer.class);
idHolder = hasOldOnPlaceholderExtensionPoint || hasOldSubstitute ? new ThreadLocal<>() : null;

final var helpers = new HashMap<String, Function<Object, String>>();
final var ref = new Substitutor(it -> doSubstitute(self, config, it, null)) {
@Override
protected Map<String, Function<Object, String>> handlebarsHelpers() {
return helpers;
}

@Override // mainly handle backward compat and id propagation, could be ~2-3 lines without that
protected String getOrDefault(final String varName, final String varDefaultValue, final String id) {
String value;
Expand Down Expand Up @@ -147,6 +155,14 @@ protected String getOrDefault(final String varName, final String varDefaultValue
}
};
self.set(ref);

// handlebars helpers
// - placeholder helper enables to get the standard placeholder behavior from a string
helpers.put("placeholder", args -> {
final Object[] casted = (Object[]) args; // (key, execution id injected)
return self.get().replace(casted[0].toString(), casted[1].toString());
});

return ref;
}

Expand Down Expand Up @@ -176,11 +192,13 @@ protected void onPlaceholder(final String varName, final String varDefaultValue,
/**
* @deprecated ensure to pass use the flavor with an id as parameter otherwise some commands will be broken.
*/
@Deprecated
protected String doSubstitute(final AtomicReference<Substitutor> self, final Config config, final String placeholder) {
return doSubstitute(self, config, placeholder, idHolder == null ? null : idHolder.get());
}

protected String doSubstitute(final AtomicReference<Substitutor> self, final Config config, final String placeholder, final String id) {
protected String doSubstitute(final AtomicReference<Substitutor> self, final Config config,
final String placeholder, final String id) {
try {
if (placeholder.equals("bundlebee-kubernetes-namespace")) {
return httpKubeClient.getNamespace();
Expand Down

0 comments on commit 1b97290

Please sign in to comment.