summaryrefslogtreecommitdiff
path: root/module/ri-engine/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'module/ri-engine/src/main')
-rw-r--r--module/ri-engine/src/main/java/io/trygvis/rules/acme/AcmeIo.java260
-rw-r--r--module/ri-engine/src/main/java/io/trygvis/rules/acme/AcmeMyApp.java6
-rw-r--r--module/ri-engine/src/main/java/io/trygvis/rules/acme/AcmeObject.java16
-rw-r--r--module/ri-engine/src/main/java/io/trygvis/rules/acme/AcmeOps.java4
-rw-r--r--module/ri-engine/src/main/java/io/trygvis/rules/acme/IpCalc.java67
-rw-r--r--module/ri-engine/src/main/java/io/trygvis/rules/dba/Cluster.java9
-rw-r--r--module/ri-engine/src/main/java/io/trygvis/rules/dba/Container.java17
-rw-r--r--module/ri-engine/src/main/java/io/trygvis/rules/dns/DnsEntry.java27
-rw-r--r--module/ri-engine/src/main/java/io/trygvis/rules/dns/DnsEntryTerraformExpression.java21
-rw-r--r--module/ri-engine/src/main/java/io/trygvis/rules/engine/KeyValue.java14
-rw-r--r--module/ri-engine/src/main/java/io/trygvis/rules/engine/Main.java53
-rw-r--r--module/ri-engine/src/main/java/io/trygvis/rules/engine/TemplateEngine.java30
-rw-r--r--module/ri-engine/src/main/java/io/trygvis/rules/machine/Machine.java13
-rw-r--r--module/ri-engine/src/main/java/io/trygvis/rules/network/Ipv4Address.java57
-rw-r--r--module/ri-engine/src/main/java/io/trygvis/rules/network/Ipv4Cidr.java54
-rw-r--r--module/ri-engine/src/main/resources/META-INF/kmodule.xml11
-rw-r--r--module/ri-engine/src/main/resources/io/trygvis/rules/acme/acme.drl62
-rw-r--r--module/ri-engine/src/main/resources/io/trygvis/rules/dba/dba.drl1
-rw-r--r--module/ri-engine/src/main/resources/io/trygvis/rules/engine/default.drl15
-rw-r--r--module/ri-engine/src/main/resources/io/trygvis/rules/machine/machine.drl6
-rw-r--r--module/ri-engine/src/main/resources/io/trygvis/rules/terraform/terraform.drl60
21 files changed, 803 insertions, 0 deletions
diff --git a/module/ri-engine/src/main/java/io/trygvis/rules/acme/AcmeIo.java b/module/ri-engine/src/main/java/io/trygvis/rules/acme/AcmeIo.java
new file mode 100644
index 0000000..456195d
--- /dev/null
+++ b/module/ri-engine/src/main/java/io/trygvis/rules/acme/AcmeIo.java
@@ -0,0 +1,260 @@
+package io.trygvis.rules.acme;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
+import org.apache.commons.collections4.OrderedMap;
+import org.drools.core.common.DefaultFactHandle;
+import org.kie.api.KieBase;
+import org.kie.api.runtime.rule.FactHandle;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.*;
+import java.util.function.Function;
+
+@SuppressWarnings("unchecked")
+public class AcmeIo {
+ private final ObjectMapper mapper;
+
+ public AcmeIo(KieBase kieBase) {
+ var factory = new YAMLFactory();
+ factory.enable(YAMLGenerator.Feature.USE_NATIVE_TYPE_ID);
+ factory.enable(YAMLGenerator.Feature.USE_NATIVE_OBJECT_ID);
+ mapper = new ObjectMapper(factory);
+ var typeFactory = TypeFactory.defaultInstance()
+ .withClassLoader(new AcmeClassLoader(kieBase));
+ mapper.setTypeFactory(typeFactory);
+ mapper.findAndRegisterModules();
+ }
+
+ public List<Object> load(String file) throws IOException {
+ var parser = mapper.getFactory().createParser(new File(file));
+
+ var objects = mapper.readValues(parser, AcmeObject.class).readAll(new ArrayList<>());
+
+ List<Object> items = new ArrayList<>(objects.size());
+ for (AcmeObject object : objects) {
+ try {
+ var type = mapper.getTypeFactory().findClass(object.type);
+ var x = mapper.treeToValue(object.data, type);
+ items.add(x);
+ } catch (ClassNotFoundException e) {
+ throw new IOException(e);
+ }
+ }
+
+ return items;
+ }
+
+ public void dump(String s, Collection<FactHandle> factHandles) throws IOException {
+ dump(s, factHandles, (o) -> true);
+ }
+
+ // This should just sort by all getters instead.
+ static class FactCollection<T> {
+ public final Class<T> type;
+ public final List<T> values;
+
+ public FactCollection(Class<T> type) {
+ this.type = type;
+ this.values = new ArrayList<>();
+ }
+
+ public void sort() {
+ var comparator = comparable(type);
+
+ this.values.sort(comparator);
+ }
+ }
+
+ private static final Map<Class<?>, Comparator> comparators = new HashMap<>();
+
+ private static <A, T extends Comparable<T>> Comparator comparable(Class<A> klass) {
+ var comparator = comparators.get(klass);
+ if (comparator != null) {
+ return comparator;
+ }
+
+ // TODO: check if klass is a Comparable directly.
+
+ var prioritizedKeys = List.of("key", "name", "fqdn");
+
+ var discoveredFieldsP1 = new LinkedHashMap<String, Function<Object, Object>>();
+ var discoveredFieldsP2 = new LinkedHashMap<String, Function<Object, Object>>();
+
+ var prioritizedTypes = List.of(String.class, int.class, Number.class);
+
+ for (var f : klass.getDeclaredFields()) {
+ if (f.getDeclaringClass() == Object.class) {
+ continue;
+ }
+
+ if (!f.trySetAccessible()) {
+ continue;
+ }
+
+ var collection = discoveredFieldsP2;
+
+ if (prioritizedTypes.contains(f.getType())) {
+ collection = discoveredFieldsP1;
+ }
+
+ collection.put(f.getName(), (Object o) -> {
+ try {
+ return f.get(o);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
+
+// for (var m : klass.getFields()) {
+// if (m.getParameterCount() != 0) {
+// continue;
+// }
+//
+// var name = m.getName();
+//
+// if (name.startsWith("get") && name.length() > 3 && Character.isUpperCase(name.charAt(4))) {
+// name = name.substring(3, 3).toLowerCase() + name.substring(4);
+// } else {
+// continue;
+// }
+//
+// if (!m.isAccessible()) {
+// if (!m.trySetAccessible())
+// return null;
+// }
+//
+// discoveredFields.put(name, m);
+// }
+
+// System.out.printf("Sorting %s by:%n", klass.getName());
+
+ var discoveredFields = new LinkedHashMap<>(discoveredFieldsP1);
+ discoveredFields.putAll(discoveredFieldsP2);
+
+ List<Function<Object, Object>> accessors = new ArrayList<>();
+ for (String prioritizedKey : prioritizedKeys) {
+ var m = discoveredFields.remove(prioritizedKey);
+ if (m == null) {
+ continue;
+ }
+
+ accessors.add(m);
+// System.out.println(" + " + prioritizedKey);
+ }
+ accessors.addAll(discoveredFields.values());
+// discoveredFields.keySet().forEach((s)-> System.out.println(" - " + s));
+
+ comparator = (a, b) -> {
+// if (klass.getName().contains("AcmeServer")) {
+// System.out.println("AcmeIo.comparable");
+// }
+
+ for (var method : accessors) {
+ var x = method.apply(a);
+ var y = method.apply(b);
+
+ if (x == null && y == null) {
+ continue;
+ }
+
+ if (x == null) {
+ return -1;
+ } else if (y == null) {
+ return 1;
+ } else {
+ var res = x.toString().compareTo(y.toString());
+ if (res != 0) {
+ return res;
+ }
+ }
+ }
+
+ return 0;
+ };
+
+ comparators.put(klass, comparator);
+
+ return comparator;
+ }
+
+ public void dump(String s, Collection<FactHandle> factHandles, Function<Object, Boolean> filter) throws IOException {
+ var out = new File("out");
+
+ if (!out.isDirectory()) {
+ if (!out.mkdirs()) {
+ throw new IOException("Could not create directory: " + out);
+ }
+ }
+
+ var facts = new TreeMap<Class<?>, FactCollection<Object>>(Comparator.comparing(Class::getName));
+ for (var handle : factHandles) {
+ if (handle instanceof DefaultFactHandle h) {
+ var obj = h.getObject();
+ if (!filter.apply(obj)) {
+ continue;
+ }
+
+ Class<?> type = obj.getClass();
+ var collection = facts.get(type);
+
+ if (collection == null) {
+ collection = new FactCollection(type);
+ facts.put(type, collection);
+ }
+
+ collection.values.add(obj);
+ }
+ }
+
+ var factory = mapper.getFactory();
+ try (var writer = new FileWriter(new File(out, s + ".yaml"));
+ var g = factory.createGenerator(writer)) {
+ for (var e : facts.entrySet()) {
+ var name = e.getKey().getName();
+
+ var collection = e.getValue();
+ collection.sort();
+ for (var fact : collection.values) {
+ g.writeObject(new AcmeObject(name, mapper.valueToTree(fact)));
+ }
+ }
+ }
+ }
+
+ private static class AcmeClassLoader extends ClassLoader {
+ private final KieBase kieBase;
+
+ public AcmeClassLoader(KieBase kieBase) {
+ this.kieBase = kieBase;
+ }
+
+ @Override
+ public Class<?> loadClass(String name) throws ClassNotFoundException {
+ try {
+ return super.loadClass(name);
+ } catch (ClassNotFoundException e) {
+ var i = name.lastIndexOf('.');
+ String pkg, klass;
+ if (i == -1) {
+ pkg = null;
+ klass = name;
+ } else {
+ pkg = name.substring(0, i);
+ klass = name.substring(i + 1);
+ }
+ var clazz = kieBase.getFactType(pkg, klass);
+ if (clazz == null) {
+ throw e;
+ }
+
+ return clazz.getFactClass();
+ }
+ }
+ }
+}
diff --git a/module/ri-engine/src/main/java/io/trygvis/rules/acme/AcmeMyApp.java b/module/ri-engine/src/main/java/io/trygvis/rules/acme/AcmeMyApp.java
new file mode 100644
index 0000000..a1340e7
--- /dev/null
+++ b/module/ri-engine/src/main/java/io/trygvis/rules/acme/AcmeMyApp.java
@@ -0,0 +1,6 @@
+package io.trygvis.rules.acme;
+
+public class AcmeMyApp {
+ public String environment;
+ public String dockerTag;
+}
diff --git a/module/ri-engine/src/main/java/io/trygvis/rules/acme/AcmeObject.java b/module/ri-engine/src/main/java/io/trygvis/rules/acme/AcmeObject.java
new file mode 100644
index 0000000..a75c4ba
--- /dev/null
+++ b/module/ri-engine/src/main/java/io/trygvis/rules/acme/AcmeObject.java
@@ -0,0 +1,16 @@
+package io.trygvis.rules.acme;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+public final class AcmeObject {
+ public String type;
+ public ObjectNode data;
+
+ public AcmeObject() {
+ }
+
+ public AcmeObject(String type, ObjectNode data) {
+ this.type = type;
+ this.data = data;
+ }
+}
diff --git a/module/ri-engine/src/main/java/io/trygvis/rules/acme/AcmeOps.java b/module/ri-engine/src/main/java/io/trygvis/rules/acme/AcmeOps.java
new file mode 100644
index 0000000..147fcfa
--- /dev/null
+++ b/module/ri-engine/src/main/java/io/trygvis/rules/acme/AcmeOps.java
@@ -0,0 +1,4 @@
+package io.trygvis.rules.acme;
+
+public class AcmeOps {
+}
diff --git a/module/ri-engine/src/main/java/io/trygvis/rules/acme/IpCalc.java b/module/ri-engine/src/main/java/io/trygvis/rules/acme/IpCalc.java
new file mode 100644
index 0000000..5369d62
--- /dev/null
+++ b/module/ri-engine/src/main/java/io/trygvis/rules/acme/IpCalc.java
@@ -0,0 +1,67 @@
+package io.trygvis.rules.acme;
+
+import io.trygvis.rules.network.Ipv4Cidr;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class IpCalc {
+ private static final Pattern pattern = Pattern.compile("([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})/([0-9]{1,3})");
+
+ public static class FirstLast {
+ public final int first;
+ public final int last;
+
+ public FirstLast(int first, int last) {
+ this.first = first;
+ this.last = last;
+ }
+ }
+
+ public static Ipv4Cidr cidr(String cidr) {
+ var matcher = pattern.matcher(cidr);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Not a CIDR: " + cidr);
+ }
+
+ var b1 = matcher.group(1);
+ var b2 = matcher.group(2);
+ var b3 = matcher.group(3);
+ var b4 = matcher.group(4);
+
+ int network = parse(b1) << 24 |
+ parse(b2) << 16 |
+ parse(b3) << 8 |
+ parse(b4);
+
+// System.out.printf("network = %x%n", network);
+
+ var l = matcher.group(5);
+ var bits = Integer.parseInt(l);
+ var hostBits = 32 - bits;
+ int size = 1 << hostBits;
+
+ int netmask = (-1 >> hostBits) << hostBits;
+// System.out.printf("netmask = %08x%n", netmask);
+
+ int x = network & ~netmask;
+
+ if (x != 0) {
+ throw new IllegalArgumentException("Not a CIDR: " + cidr);
+ }
+
+ return new Ipv4Cidr(network, netmask, size, bits);
+ }
+
+ private static int parse(String s) {
+ var i = Integer.parseInt(s);
+ if (i > 255) {
+ throw new IllegalArgumentException("Not a CIDR");
+ }
+
+ return i;
+ }
+}
diff --git a/module/ri-engine/src/main/java/io/trygvis/rules/dba/Cluster.java b/module/ri-engine/src/main/java/io/trygvis/rules/dba/Cluster.java
new file mode 100644
index 0000000..949d9ae
--- /dev/null
+++ b/module/ri-engine/src/main/java/io/trygvis/rules/dba/Cluster.java
@@ -0,0 +1,9 @@
+package io.trygvis.rules.dba;
+
+public class Cluster {
+ public String name;
+
+ public Cluster(String name) {
+ this.name = name;
+ }
+}
diff --git a/module/ri-engine/src/main/java/io/trygvis/rules/dba/Container.java b/module/ri-engine/src/main/java/io/trygvis/rules/dba/Container.java
new file mode 100644
index 0000000..6df939d
--- /dev/null
+++ b/module/ri-engine/src/main/java/io/trygvis/rules/dba/Container.java
@@ -0,0 +1,17 @@
+package io.trygvis.rules.dba;
+
+public class Container {
+ public Cluster cluster;
+ public String name;
+ public String machineRole;
+ public String image;
+ public String tag;
+
+ public Container(Cluster cluster, String name, String machineRole, String image, String tag) {
+ this.cluster = cluster;
+ this.name = name;
+ this.machineRole = machineRole;
+ this.image = image;
+ this.tag = tag;
+ }
+}
diff --git a/module/ri-engine/src/main/java/io/trygvis/rules/dns/DnsEntry.java b/module/ri-engine/src/main/java/io/trygvis/rules/dns/DnsEntry.java
new file mode 100644
index 0000000..105ef79
--- /dev/null
+++ b/module/ri-engine/src/main/java/io/trygvis/rules/dns/DnsEntry.java
@@ -0,0 +1,27 @@
+package io.trygvis.rules.dns;
+
+public class DnsEntry {
+ public String fqdn;
+ public String type;
+
+ public DnsEntry(String fqdn, String type) {
+ this.fqdn = fqdn;
+ this.type = type;
+ }
+
+ public static DnsEntry a(String fqdn) {
+ return new DnsEntry(fqdn, "A");
+ }
+
+ public static DnsEntry aaaa(String fqdn) {
+ return new DnsEntry(fqdn, "AAAA");
+ }
+
+ public String getFqdn() {
+ return fqdn;
+ }
+
+ public String getType() {
+ return type;
+ }
+}
diff --git a/module/ri-engine/src/main/java/io/trygvis/rules/dns/DnsEntryTerraformExpression.java b/module/ri-engine/src/main/java/io/trygvis/rules/dns/DnsEntryTerraformExpression.java
new file mode 100644
index 0000000..79bf934
--- /dev/null
+++ b/module/ri-engine/src/main/java/io/trygvis/rules/dns/DnsEntryTerraformExpression.java
@@ -0,0 +1,21 @@
+package io.trygvis.rules.dns;
+
+public class DnsEntryTerraformExpression {
+ public DnsEntry entry;
+ public String key;
+ public String expression;
+
+ public DnsEntryTerraformExpression(DnsEntry entry, String key, String expression) {
+ this.entry = entry;
+ this.key = key;
+ this.expression = expression;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getExpression() {
+ return expression;
+ }
+}
diff --git a/module/ri-engine/src/main/java/io/trygvis/rules/engine/KeyValue.java b/module/ri-engine/src/main/java/io/trygvis/rules/engine/KeyValue.java
new file mode 100644
index 0000000..5046169
--- /dev/null
+++ b/module/ri-engine/src/main/java/io/trygvis/rules/engine/KeyValue.java
@@ -0,0 +1,14 @@
+package io.trygvis.rules.engine;
+
+public class KeyValue {
+ public String key;
+ public String value;
+
+ public KeyValue() {
+ }
+
+ public KeyValue(String key, String value) {
+ this.key = key;
+ this.value = value;
+ }
+}
diff --git a/module/ri-engine/src/main/java/io/trygvis/rules/engine/Main.java b/module/ri-engine/src/main/java/io/trygvis/rules/engine/Main.java
new file mode 100644
index 0000000..7c96be6
--- /dev/null
+++ b/module/ri-engine/src/main/java/io/trygvis/rules/engine/Main.java
@@ -0,0 +1,53 @@
+package io.trygvis.rules.engine;
+
+import io.trygvis.rules.acme.AcmeIo;
+import io.trygvis.rules.dns.DnsEntry;
+import io.trygvis.rules.machine.Machine;
+import io.trygvis.rules.network.Ipv4Address;
+import io.trygvis.rules.network.Ipv4Cidr;
+import org.drools.core.audit.WorkingMemoryConsoleLogger;
+import org.kie.api.KieServices;
+import org.kie.api.event.rule.AgendaEventListener;
+import org.kie.api.event.rule.RuleRuntimeEventListener;
+
+import java.io.IOException;
+
+public class Main {
+ public static void main(String[] args) throws IOException {
+ var services = KieServices.Factory.get();
+ var container = services.getKieClasspathContainer();
+ var kieBase = container.getKieBase();
+
+ var io = new AcmeIo(kieBase);
+ var objects = io.load("acme.yaml");
+
+ var session = container.newKieSession();
+
+ session.setGlobal("te", new TemplateEngine());
+
+ for (var object : objects) {
+ System.out.println("object = " + object);
+ session.insert(object);
+ }
+
+ var logger = new WorkingMemoryConsoleLogger(session);
+ session.addEventListener((AgendaEventListener) logger);
+ session.addEventListener((RuleRuntimeEventListener) logger);
+
+ session.getAgenda().getAgendaGroup("init").setFocus();
+
+ session.fireAllRules();
+
+ io.dump("phase-1", session.getFactHandles());
+
+ io.dump("vpn0", session.getFactHandles(), (Object o) ->
+ o.getClass().getName().contains("Wg")
+ || o instanceof Machine
+ || o instanceof DnsEntry
+ || o instanceof Ipv4Cidr
+ || o instanceof Ipv4Address
+ );
+
+ session.dispose();
+ }
+}
diff --git a/module/ri-engine/src/main/java/io/trygvis/rules/engine/TemplateEngine.java b/module/ri-engine/src/main/java/io/trygvis/rules/engine/TemplateEngine.java
new file mode 100644
index 0000000..ace7aaf
--- /dev/null
+++ b/module/ri-engine/src/main/java/io/trygvis/rules/engine/TemplateEngine.java
@@ -0,0 +1,30 @@
+package io.trygvis.rules.engine;
+
+import ch.qos.logback.core.util.FileUtil;
+import com.hubspot.jinjava.Jinjava;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+
+public class TemplateEngine {
+ private final Jinjava jinjava = new Jinjava();
+
+ public void template(String name, String output, Map<String, Object> params) throws IOException {
+ var template = Files.readString(Path.of("j2", name + ".j2"));
+ String renderedTemplate = jinjava.render(template, params);
+// System.out.println("-----------------------------------");
+// for (var item : params.entrySet()) {
+// System.out.printf(" %s : %s%n", item.getKey(), item.getValue());
+// }
+ System.out.println("-----------------------------------");
+ System.out.println(renderedTemplate);
+ System.out.println("-----------------------------------");
+
+ var f = new File("gen", output);
+ FileUtil.createMissingParentDirectories(f);
+ Files.writeString(f.toPath(), renderedTemplate);
+ }
+}
diff --git a/module/ri-engine/src/main/java/io/trygvis/rules/machine/Machine.java b/module/ri-engine/src/main/java/io/trygvis/rules/machine/Machine.java
new file mode 100644
index 0000000..cc3f2ed
--- /dev/null
+++ b/module/ri-engine/src/main/java/io/trygvis/rules/machine/Machine.java
@@ -0,0 +1,13 @@
+package io.trygvis.rules.machine;
+
+public class Machine {
+ public String name;
+ public String fqdn;
+
+ public Machine() {
+ }
+
+ public Machine(String name) {
+ this.name = name;
+ }
+}
diff --git a/module/ri-engine/src/main/java/io/trygvis/rules/network/Ipv4Address.java b/module/ri-engine/src/main/java/io/trygvis/rules/network/Ipv4Address.java
new file mode 100644
index 0000000..7ca9ca5
--- /dev/null
+++ b/module/ri-engine/src/main/java/io/trygvis/rules/network/Ipv4Address.java
@@ -0,0 +1,57 @@
+package io.trygvis.rules.network;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+import java.io.IOException;
+import java.util.Objects;
+
+@JsonSerialize(using = Ipv4Address.Serializer.class)
+public class Ipv4Address implements Comparable<Ipv4Address> {
+ public final int address;
+
+ public Ipv4Address(int address) {
+ this.address = address;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+
+ if (o instanceof Ipv4Address other) {
+ return address == other.address;
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(address);
+ }
+
+ @Override
+ public int compareTo(Ipv4Address o) {
+ return address - o.address;
+ }
+
+ @Override
+ public String toString() {
+ return "%d.%d.%d.%d".formatted(
+ address >> 24 & 0xff,
+ address >> 16 & 0xff,
+ address >> 8 & 0xff,
+ address & 0xff);
+ }
+
+ public static class Serializer extends JsonSerializer<Ipv4Address> {
+ @Override
+ public void serialize(Ipv4Address value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+ gen.writeStartObject();
+ gen.writeObjectField("value", value.toString());
+ gen.writeEndObject();
+ }
+ }
+}
diff --git a/module/ri-engine/src/main/java/io/trygvis/rules/network/Ipv4Cidr.java b/module/ri-engine/src/main/java/io/trygvis/rules/network/Ipv4Cidr.java
new file mode 100644
index 0000000..e69cd02
--- /dev/null
+++ b/module/ri-engine/src/main/java/io/trygvis/rules/network/Ipv4Cidr.java
@@ -0,0 +1,54 @@
+package io.trygvis.rules.network;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+
+@JsonSerialize(using = Ipv4Cidr.Serializer.class)
+public class Ipv4Cidr {
+ public final int network;
+ public final int netmask;
+ public final int size;
+ public final int bits;
+
+ public Ipv4Cidr(int network, int netmask, int size, int bits) {
+ this.network = network;
+ this.netmask = netmask;
+ this.size = size;
+ this.bits = bits;
+ }
+
+ @Override
+ public String toString() {
+ return "%d.%d.%d.%d/%d".formatted(
+ network >> 24 & 0xff,
+ network >> 16 & 0xff,
+ network >> 8 & 0xff,
+ network & 0xff,
+ bits);
+ }
+
+ public Collection<Ipv4Address> addresses() {
+ var end = network + size;
+ var addresses = new ArrayList<Ipv4Address>(size);
+ for (int address = network; address < end; address++) {
+ addresses.add(new Ipv4Address(address));
+ }
+
+ return addresses;
+ }
+
+ public static class Serializer extends JsonSerializer<Ipv4Cidr> {
+ @Override
+ public void serialize(Ipv4Cidr value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
+ gen.writeStartObject();
+ gen.writeObjectField("value", value.toString());
+ gen.writeEndObject();
+ }
+ }
+}
diff --git a/module/ri-engine/src/main/resources/META-INF/kmodule.xml b/module/ri-engine/src/main/resources/META-INF/kmodule.xml
new file mode 100644
index 0000000..7a46b1a
--- /dev/null
+++ b/module/ri-engine/src/main/resources/META-INF/kmodule.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<kmodule xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="http://www.drools.org/xsd/kmodule"
+ xsi:schemaLocation="http://www.drools.org/xsd/kmodule https://www.drools.org/xsd/kmodule_7_1.xsd">
+
+ <kbase packages="io.trygvis.rules,io.trygvis.rules.acme,io.trygvis.rules.dba,io.trygvis.rules.engine,io.trygvis.rules.machine,io.trygvis.rules.terraform"
+ default="true" includes="wireguard">
+ <ksession name="Default" default="true">
+ </ksession>
+ </kbase>
+</kmodule>
diff --git a/module/ri-engine/src/main/resources/io/trygvis/rules/acme/acme.drl b/module/ri-engine/src/main/resources/io/trygvis/rules/acme/acme.drl
new file mode 100644
index 0000000..0465343
--- /dev/null
+++ b/module/ri-engine/src/main/resources/io/trygvis/rules/acme/acme.drl
@@ -0,0 +1,62 @@
+package io.trygvis.rules.acme;
+
+import io.trygvis.rules.machine.Machine;
+import io.trygvis.rules.dba.Cluster;
+import io.trygvis.rules.dba.Container;
+
+declare AcmeServer
+ name : String
+ machine : Machine
+end
+
+//declare MachinePublicName
+// machine : Machine
+// fqdn : String
+//end
+
+rule "Ops"
+when
+ $ops: AcmeOps()
+then
+ var cluster = new Cluster("acme-ops");
+ insert(cluster);
+ insert(new Container(cluster, "app", "pdb", "postgresql", "11"));
+ insert(new Container(cluster, "app", "n8n", "n8n", "0.84.1"));
+end
+
+rule "MyApp"
+when
+ $app: AcmeMyApp()
+then
+ var cluster = new Cluster("acme-" + $app.environment);
+ insert(cluster);
+
+ var tag = $app.dockerTag;
+ insert(new Container(cluster, "app", "statera", "statera", tag));
+ insert(new Container(cluster, "app", "statera-console", "statera-console", tag));
+ insert(new Container(cluster, "app", "4tune-web", "4tune-web", tag));
+ insert(new Container(cluster, "app", "4tune-api", "4tune-api", tag));
+ insert(new Container(cluster, "db", "pdb", "postgresql", "13"));
+ insert(new Container(cluster, "db", "mdb", "mongodb", "3.2"));
+end
+
+rule "Create Acme servers"
+when
+ $m : Machine(name.startsWith("acme-"))
+ not(AcmeServer(name == $m.name))
+then
+ var s = new AcmeServer();
+ s.name = $m.name;
+ s.machine = $m;
+ insert(s);
+end
+
+rule "Set public domain for ACME servers"
+when
+ $m : Machine(fqdn == null)
+ $s : AcmeServer(machine == $m)
+then
+ var fqdn = "%s.machine.acme.com".formatted($s.machine.name);
+ $s.machine.fqdn = fqdn;
+ update($s.machine)
+end
diff --git a/module/ri-engine/src/main/resources/io/trygvis/rules/dba/dba.drl b/module/ri-engine/src/main/resources/io/trygvis/rules/dba/dba.drl
new file mode 100644
index 0000000..7beceef
--- /dev/null
+++ b/module/ri-engine/src/main/resources/io/trygvis/rules/dba/dba.drl
@@ -0,0 +1 @@
+package io.trygvis.rules.dba;
diff --git a/module/ri-engine/src/main/resources/io/trygvis/rules/engine/default.drl b/module/ri-engine/src/main/resources/io/trygvis/rules/engine/default.drl
new file mode 100644
index 0000000..090fddf
--- /dev/null
+++ b/module/ri-engine/src/main/resources/io/trygvis/rules/engine/default.drl
@@ -0,0 +1,15 @@
+package io.trygvis.rules.engine;
+import java.util.Map
+import org.apache.commons.io.FileSystem
+import org.apache.commons.io.FileUtils
+import java.io.File
+
+rule "Clean directories"
+ agenda-group "init"
+when
+ not(KeyValue(key == "rm-gen"));
+then
+ System.out.println("Cleaning gen!");
+ FileUtils.deleteDirectory(new File("gen"));
+ insert(new KeyValue("rm-gen", null));
+end
diff --git a/module/ri-engine/src/main/resources/io/trygvis/rules/machine/machine.drl b/module/ri-engine/src/main/resources/io/trygvis/rules/machine/machine.drl
new file mode 100644
index 0000000..a9a379f
--- /dev/null
+++ b/module/ri-engine/src/main/resources/io/trygvis/rules/machine/machine.drl
@@ -0,0 +1,6 @@
+package io.trygvis.rules.machine;
+
+import io.trygvis.rules.dba.Cluster;
+import io.trygvis.rules.dba.Container;
+import io.trygvis.rules.machine.Machine;
+import io.trygvis.rules.dns.DnsEntry;
diff --git a/module/ri-engine/src/main/resources/io/trygvis/rules/terraform/terraform.drl b/module/ri-engine/src/main/resources/io/trygvis/rules/terraform/terraform.drl
new file mode 100644
index 0000000..40b9f7c
--- /dev/null
+++ b/module/ri-engine/src/main/resources/io/trygvis/rules/terraform/terraform.drl
@@ -0,0 +1,60 @@
+package io.trygvis.rules.terraform
+
+import io.trygvis.rules.dba.Cluster
+import io.trygvis.rules.dba.Container
+import io.trygvis.rules.machine.Machine
+import io.trygvis.rules.dns.DnsEntry
+import io.trygvis.rules.dns.DnsEntryTerraformExpression
+import java.util.Map;
+
+global io.trygvis.rules.engine.TemplateEngine te;
+
+declare ScalewayMachine
+ machine : Machine
+ key : String
+end
+
+rule "Terraform for Machine"
+when
+ $machine: Machine()
+then
+ ScalewayMachine scw = new ScalewayMachine();
+
+ scw.setKey($machine.name);
+ scw.setMachine($machine);
+
+ insert(scw);
+end
+
+rule "Create DNS entry for Terraform Machine"
+when
+ $machine : Machine(fqdn != null)
+ not(DnsEntry(fqdn == $machine.fqdn))
+then
+ var a = DnsEntry.a($machine.fqdn);
+ insert(a);
+
+ var ipv4 = "scaleway_instance_ip.%s.address".formatted($machine.name);
+ insert(new DnsEntryTerraformExpression(a, $machine.name, ipv4));
+end
+
+rule "TF for TerraformMachine"
+when
+ $m: Machine()
+ $scw: ScalewayMachine(machine == $m)
+then
+ var path = "platform/terraform/%s.tf".formatted($scw.getKey());
+ te.template("terraform-machine", path, Map.of("m", $m, "scw", $scw));
+end
+
+rule "Terraform for DNS"
+when
+ $entry: DnsEntry()
+ $tf : DnsEntryTerraformExpression(entry == $entry)
+then
+ var path = "dns/%s.tf".formatted($tf.key);
+ te.template("terraform-record-set", path, Map.of(
+ "entry", $entry,
+ "tf", $tf)
+ );
+end