summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorTrygve Laugstøl <trygvis@inamo.no>2021-02-03 16:35:48 +0100
committerTrygve Laugstøl <trygvis@inamo.no>2021-02-03 16:35:48 +0100
commitbafe762ac01d16904c18404283027e426e19bc73 (patch)
treec22688ad186f5ce635f525704c5035f91b983b68 /modules
parent91abd0b04320a9c9d932df195aca38c1cacfcc5a (diff)
downloadrules-sandbox-bafe762ac01d16904c18404283027e426e19bc73.tar.gz
rules-sandbox-bafe762ac01d16904c18404283027e426e19bc73.tar.bz2
rules-sandbox-bafe762ac01d16904c18404283027e426e19bc73.tar.xz
rules-sandbox-bafe762ac01d16904c18404283027e426e19bc73.zip
Code reorganization.
Moving main code to src, keeping modules in modules/
Diffstat (limited to 'modules')
-rw-r--r--modules/ri-base/classpath.txt27
-rw-r--r--modules/ri-base/pom.xml32
-rw-r--r--modules/ri-base/src/main/java/io/trygvis/rules/core/Problem.java11
-rw-r--r--modules/ri-base/src/main/java/io/trygvis/rules/dba/Cluster.java15
-rw-r--r--modules/ri-base/src/main/java/io/trygvis/rules/dba/Container.java59
-rw-r--r--modules/ri-base/src/main/java/io/trygvis/rules/dns/DnsEntry.java27
-rw-r--r--modules/ri-base/src/main/java/io/trygvis/rules/dns/DnsEntryTerraformExpression.java21
-rw-r--r--modules/ri-base/src/main/java/io/trygvis/rules/dns/DnsZone.java9
-rw-r--r--modules/ri-base/src/main/java/io/trygvis/rules/engine/KeyValue.java14
-rw-r--r--modules/ri-base/src/main/java/io/trygvis/rules/machine/Machine.java29
-rw-r--r--modules/ri-base/src/main/java/io/trygvis/rules/machine/MachineSpecification.java21
-rw-r--r--modules/ri-base/src/main/java/io/trygvis/rules/network/IpCalc.java6
-rw-r--r--modules/ri-base/src/main/java/io/trygvis/rules/network/Ipv4Address.java58
-rw-r--r--modules/ri-base/src/main/java/io/trygvis/rules/network/Ipv4Cidr.java150
-rw-r--r--modules/ri-base/src/main/java/io/trygvis/rules/terraform/TerraformInputVariable.java41
-rw-r--r--modules/ri-base/src/main/java/io/trygvis/rules/terraform/TerraformMain.java21
-rw-r--r--modules/ri-base/src/main/java/io/trygvis/rules/terraform/TerraformProvider.java22
-rw-r--r--modules/ri-base/src/main/java/io/trygvis/rules/terraform/TerraformResource.java213
-rw-r--r--modules/ri-base/src/main/resources/META-INF/kmodule.xml12
-rw-r--r--modules/ri-base/src/main/resources/io/trygvis/rules/dba/dba.drl69
-rw-r--r--modules/ri-base/src/main/resources/io/trygvis/rules/engine/init.drl17
-rw-r--r--modules/ri-base/src/main/resources/io/trygvis/rules/machine/machine.drl1
-rw-r--r--modules/ri-base/src/main/resources/io/trygvis/rules/scaleway/terraform.drl81
-rw-r--r--modules/ri-base/src/main/resources/io/trygvis/rules/terraform/terraform-resources.drl28
-rw-r--r--modules/ri-base/src/main/resources/templates/dba/cluster.j219
-rw-r--r--modules/ri-base/src/main/resources/templates/platform-ansible.j26
-rw-r--r--modules/ri-base/src/main/resources/templates/terraform/machine-outputs.j27
-rw-r--r--modules/ri-base/src/main/resources/templates/terraform/machine.j219
-rw-r--r--modules/ri-base/src/main/resources/templates/terraform/main-scaleway-machine.j219
-rw-r--r--modules/ri-base/src/main/resources/templates/terraform/main.j210
-rw-r--r--modules/ri-base/src/main/resources/templates/terraform/record-set.j28
-rw-r--r--modules/ri-base/src/main/resources/templates/terraform/resource.j21
-rw-r--r--modules/ri-base/src/main/resources/templates/terraform/variables.j210
-rw-r--r--modules/ri-base/src/test/java/io/trygvis/rules/network/Ipv4CidrTest.txt37
-rw-r--r--modules/ri-docker/pom.xml25
-rw-r--r--modules/ri-module-api/classpath.txt23
-rw-r--r--modules/ri-module-api/pom.xml16
-rw-r--r--modules/ri-module-api/src/main/java/io/trygvis/rules/engine/TemplateEngine.java10
-rw-r--r--modules/ri-module-parent/classpath.txt23
-rw-r--r--modules/ri-module-parent/pom.xml128
-rw-r--r--modules/ri-wireguard/classpath.txt28
-rw-r--r--modules/ri-wireguard/pom.xml25
-rw-r--r--modules/ri-wireguard/src/main/resources/META-INF/kmodule.xml9
-rw-r--r--modules/ri-wireguard/src/main/resources/io/trygvis/rules/wireguard/wireguard.drl185
-rw-r--r--modules/ri-wireguard/src/main/resources/logback.xml13
-rw-r--r--modules/ri-wireguard/src/main/resources/templates/wireguard/ansible-host.j212
-rw-r--r--modules/ri-wireguard/src/main/resources/templates/wireguard/ansible.j27
-rw-r--r--modules/ri-wireguard/src/main/resources/templates/wireguard/inventory.j213
48 files changed, 1637 insertions, 0 deletions
diff --git a/modules/ri-base/classpath.txt b/modules/ri-base/classpath.txt
new file mode 100644
index 0000000..d5370b2
--- /dev/null
+++ b/modules/ri-base/classpath.txt
@@ -0,0 +1,27 @@
+io.trygvis.rules-sandbox.module:ri-base:1.0-SNAPSHOT:kjar
+com.fasterxml.jackson.core:jackson-annotations:2.12.0:jar
+com.fasterxml.jackson.core:jackson-core:2.12.0:jar
+com.fasterxml.jackson.core:jackson-databind:2.12.0:jar
+com.github.javaparser:javaparser-core:3.13.10:jar
+com.thoughtworks.xstream:xstream:1.4.14:jar
+commons-codec:commons-codec:1.14:jar
+commons-io:commons-io:2.8.0:jar
+io.trygvis.rules-sandbox.module:ri-module-api:1.0-SNAPSHOT:jar
+org.antlr:antlr-runtime:3.5.2:jar
+org.drools:drools-canonical-model:7.48.0.Final:jar
+org.drools:drools-compiler:7.48.0.Final:jar
+org.drools:drools-core:7.48.0.Final:jar
+org.drools:drools-core-dynamic:7.48.0.Final:jar
+org.drools:drools-core-reflective:7.48.0.Final:jar
+org.drools:drools-ecj:7.48.0.Final:jar
+org.drools:drools-model-compiler:7.48.0.Final:jar
+org.drools:drools-mvel-compiler:7.48.0.Final:jar
+org.drools:drools-mvel-parser:7.48.0.Final:jar
+org.kie:kie-api:7.48.0.Final:jar
+org.kie:kie-internal:7.48.0.Final:jar
+org.kie:kie-memory-compiler:7.48.0.Final:jar
+org.kie.soup:kie-soup-maven-support:7.48.0.Final:jar
+org.kie.soup:kie-soup-xstream:7.48.0.Final:jar
+org.slf4j:slf4j-api:1.7.30:jar
+xmlpull:xmlpull:1.1.3.1:jar
+xpp3:xpp3_min:1.1.4c:jar
diff --git a/modules/ri-base/pom.xml b/modules/ri-base/pom.xml
new file mode 100644
index 0000000..119368c
--- /dev/null
+++ b/modules/ri-base/pom.xml
@@ -0,0 +1,32 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>io.trygvis.rules-sandbox.module</groupId>
+ <artifactId>ri-module-parent</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <relativePath>../ri-module-parent/pom.xml</relativePath>
+ </parent>
+
+ <artifactId>ri-base</artifactId>
+ <packaging>kjar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>ri-module-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/modules/ri-base/src/main/java/io/trygvis/rules/core/Problem.java b/modules/ri-base/src/main/java/io/trygvis/rules/core/Problem.java
new file mode 100644
index 0000000..04d1af3
--- /dev/null
+++ b/modules/ri-base/src/main/java/io/trygvis/rules/core/Problem.java
@@ -0,0 +1,11 @@
+package io.trygvis.rules.core;
+
+public class Problem {
+ public final String message;
+ public final Object object;
+
+ public Problem(String message, Object object) {
+ this.message = message;
+ this.object = object;
+ }
+}
diff --git a/modules/ri-base/src/main/java/io/trygvis/rules/dba/Cluster.java b/modules/ri-base/src/main/java/io/trygvis/rules/dba/Cluster.java
new file mode 100644
index 0000000..6b23cdd
--- /dev/null
+++ b/modules/ri-base/src/main/java/io/trygvis/rules/dba/Cluster.java
@@ -0,0 +1,15 @@
+package io.trygvis.rules.dba;
+
+import com.fasterxml.jackson.annotation.JsonIdentityInfo;
+import com.fasterxml.jackson.annotation.ObjectIdGenerators;
+
+@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "name")
+public class Cluster {
+ public String name;
+
+ public Cluster(String name) {
+ this.name = name;
+ }
+
+ protected Cluster() {}
+}
diff --git a/modules/ri-base/src/main/java/io/trygvis/rules/dba/Container.java b/modules/ri-base/src/main/java/io/trygvis/rules/dba/Container.java
new file mode 100644
index 0000000..a420671
--- /dev/null
+++ b/modules/ri-base/src/main/java/io/trygvis/rules/dba/Container.java
@@ -0,0 +1,59 @@
+package io.trygvis.rules.dba;
+
+import io.trygvis.rules.machine.Machine;
+import io.trygvis.rules.machine.MachineSpecification;
+
+//@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
+public class Container {
+ public String id;
+ public Cluster cluster;
+ public String name;
+ public String machineRole;
+ public String image;
+ public String tag;
+
+ private Machine machine;
+ public MachineSpecification machineSpecification;
+
+ public Container(Cluster cluster, String name, String machineRole, String image, String tag,
+ MachineSpecification machineSpecification) {
+ this.id = cluster.name + "-" + name;
+ this.cluster = cluster;
+ this.name = name;
+ this.machineRole = machineRole;
+ this.image = image;
+ this.tag = tag;
+ this.machineSpecification = machineSpecification;
+ }
+
+ protected Container() {
+ }
+
+ public Cluster getCluster() {
+ return cluster;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getMachineRole() {
+ return machineRole;
+ }
+
+ public String getImage() {
+ return image;
+ }
+
+ public String getTag() {
+ return tag;
+ }
+
+ public Machine getMachine() {
+ return machine;
+ }
+
+ public void setMachine(Machine machine) {
+ this.machine = machine;
+ }
+}
diff --git a/modules/ri-base/src/main/java/io/trygvis/rules/dns/DnsEntry.java b/modules/ri-base/src/main/java/io/trygvis/rules/dns/DnsEntry.java
new file mode 100644
index 0000000..105ef79
--- /dev/null
+++ b/modules/ri-base/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/modules/ri-base/src/main/java/io/trygvis/rules/dns/DnsEntryTerraformExpression.java b/modules/ri-base/src/main/java/io/trygvis/rules/dns/DnsEntryTerraformExpression.java
new file mode 100644
index 0000000..79bf934
--- /dev/null
+++ b/modules/ri-base/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/modules/ri-base/src/main/java/io/trygvis/rules/dns/DnsZone.java b/modules/ri-base/src/main/java/io/trygvis/rules/dns/DnsZone.java
new file mode 100644
index 0000000..1af5c8f
--- /dev/null
+++ b/modules/ri-base/src/main/java/io/trygvis/rules/dns/DnsZone.java
@@ -0,0 +1,9 @@
+package io.trygvis.rules.dns;
+
+public class DnsZone {
+ public final String name;
+
+ public DnsZone(String name) {
+ this.name = name;
+ }
+}
diff --git a/modules/ri-base/src/main/java/io/trygvis/rules/engine/KeyValue.java b/modules/ri-base/src/main/java/io/trygvis/rules/engine/KeyValue.java
new file mode 100644
index 0000000..5046169
--- /dev/null
+++ b/modules/ri-base/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/modules/ri-base/src/main/java/io/trygvis/rules/machine/Machine.java b/modules/ri-base/src/main/java/io/trygvis/rules/machine/Machine.java
new file mode 100644
index 0000000..8f162c6
--- /dev/null
+++ b/modules/ri-base/src/main/java/io/trygvis/rules/machine/Machine.java
@@ -0,0 +1,29 @@
+package io.trygvis.rules.machine;
+
+import com.fasterxml.jackson.annotation.JsonIdentityInfo;
+import com.fasterxml.jackson.annotation.ObjectIdGenerators;
+
+@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "name")
+public class Machine {
+ public String name;
+ private String fqdn;
+
+ public Machine() {
+ }
+
+ public Machine(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getFqdn() {
+ return fqdn;
+ }
+
+ public void setFqdn(String fqdn) {
+ this.fqdn = fqdn;
+ }
+}
diff --git a/modules/ri-base/src/main/java/io/trygvis/rules/machine/MachineSpecification.java b/modules/ri-base/src/main/java/io/trygvis/rules/machine/MachineSpecification.java
new file mode 100644
index 0000000..9d38b2d
--- /dev/null
+++ b/modules/ri-base/src/main/java/io/trygvis/rules/machine/MachineSpecification.java
@@ -0,0 +1,21 @@
+package io.trygvis.rules.machine;
+
+public class MachineSpecification {
+ public int cpu;
+ public int memory;
+
+ protected MachineSpecification() {}
+
+ public MachineSpecification(int cpu, int memory) {
+ this.cpu = cpu;
+ this.memory = memory;
+ }
+
+ public int getCpu() {
+ return cpu;
+ }
+
+ public int getMemory() {
+ return memory;
+ }
+}
diff --git a/modules/ri-base/src/main/java/io/trygvis/rules/network/IpCalc.java b/modules/ri-base/src/main/java/io/trygvis/rules/network/IpCalc.java
new file mode 100644
index 0000000..7ec344c
--- /dev/null
+++ b/modules/ri-base/src/main/java/io/trygvis/rules/network/IpCalc.java
@@ -0,0 +1,6 @@
+package io.trygvis.rules.network;
+
+import java.util.regex.Pattern;
+
+public class IpCalc {
+}
diff --git a/modules/ri-base/src/main/java/io/trygvis/rules/network/Ipv4Address.java b/modules/ri-base/src/main/java/io/trygvis/rules/network/Ipv4Address.java
new file mode 100644
index 0000000..9021198
--- /dev/null
+++ b/modules/ri-base/src/main/java/io/trygvis/rules/network/Ipv4Address.java
@@ -0,0 +1,58 @@
+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) {
+ Ipv4Address other = (Ipv4Address) o;
+ 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/modules/ri-base/src/main/java/io/trygvis/rules/network/Ipv4Cidr.java b/modules/ri-base/src/main/java/io/trygvis/rules/network/Ipv4Cidr.java
new file mode 100644
index 0000000..851af95
--- /dev/null
+++ b/modules/ri-base/src/main/java/io/trygvis/rules/network/Ipv4Cidr.java
@@ -0,0 +1,150 @@
+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.List;
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+@JsonSerialize(using = Ipv4Cidr.Serializer.class)
+public class Ipv4Cidr implements Comparable<Ipv4Cidr> {
+ public final int network;
+ public final int bits;
+
+ public Ipv4Cidr(int network, int bits) {
+ if (bits < 0 || bits > 32) {
+ throw new IllegalArgumentException("bits must be [0, 32]");
+ }
+
+ int hostBits = 32 - bits;
+ int netmask = (-1 >> hostBits) << hostBits;
+
+ int x = network & ~netmask;
+
+ if (x != 0) {
+ throw new IllegalArgumentException("The host part of the address must be 0.");
+ }
+
+ this.network = network;
+ 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);
+ }
+
+ private String formatIpv4(int address) {
+ return "%d.%d.%d.%d".formatted(address >> 24 & 0xff, address >> 16 & 0xff, address >> 8 & 0xff, address & 0xff);
+ }
+
+ public List<Ipv4Cidr> partition(int bits) {
+ if (bits <= 0 || bits <= this.bits || bits > 32) {
+ throw new IllegalArgumentException("Invalid new network size");
+ }
+
+ var list = new ArrayList<Ipv4Cidr>();
+
+ int count = 1 << (bits - this.bits);
+ for (int i = 0; i < count; i++) {
+ var network = this.network | (i << (32 - bits));
+
+ list.add(new Ipv4Cidr(network, bits));
+ }
+
+ return list;
+ }
+
+ public List<Ipv4Address> addresses() {
+ int size = 1 << 32 - bits;
+ var end = network + size;
+ var addresses = new ArrayList<Ipv4Address>(size);
+ for (int address = network; address < end; address++) {
+ addresses.add(new Ipv4Address(address));
+ }
+
+ return addresses;
+ }
+
+ @Override
+ public int compareTo(Ipv4Cidr o) {
+ if (this == o) {
+ return 0;
+ }
+
+ var ret = network - o.network;
+ if (ret != 0) {
+ return ret;
+ }
+
+ return bits - o.bits;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Ipv4Cidr ipv4Cidr = (Ipv4Cidr) o;
+ return network == ipv4Cidr.network && bits == ipv4Cidr.bits;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(network, bits);
+ }
+
+ 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 Ipv4Cidr parseCidr(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);
+// System.out.printf("netmask = %08x%n", netmask);
+
+ return new Ipv4Cidr(network, bits);
+ }
+
+ private static int parse(String s) {
+ var i = Integer.parseInt(s);
+ if (i > 255) {
+ throw new IllegalArgumentException("Not a CIDR");
+ }
+
+ return i;
+ }
+
+ 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/modules/ri-base/src/main/java/io/trygvis/rules/terraform/TerraformInputVariable.java b/modules/ri-base/src/main/java/io/trygvis/rules/terraform/TerraformInputVariable.java
new file mode 100644
index 0000000..1415767
--- /dev/null
+++ b/modules/ri-base/src/main/java/io/trygvis/rules/terraform/TerraformInputVariable.java
@@ -0,0 +1,41 @@
+package io.trygvis.rules.terraform;
+
+public class TerraformInputVariable {
+ private String module;
+
+ private String name;
+ private String type;
+ private String default_;
+
+ public TerraformInputVariable(String module) {
+ this.module = module;
+ }
+
+ public String getModule() {
+ return module;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getDefault() {
+ return default_;
+ }
+
+ public void setDefault(String default_) {
+ this.default_ = default_;
+ }
+}
diff --git a/modules/ri-base/src/main/java/io/trygvis/rules/terraform/TerraformMain.java b/modules/ri-base/src/main/java/io/trygvis/rules/terraform/TerraformMain.java
new file mode 100644
index 0000000..82626d2
--- /dev/null
+++ b/modules/ri-base/src/main/java/io/trygvis/rules/terraform/TerraformMain.java
@@ -0,0 +1,21 @@
+package io.trygvis.rules.terraform;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+public class TerraformMain {
+ private String module;
+ private Map<String, TerraformProvider> providers = new TreeMap<String, TerraformProvider>();
+
+ public TerraformMain(String module) {
+ this.module = module;
+ }
+
+ public String getModule() {
+ return module;
+ }
+
+ public Map<String, TerraformProvider> getProviders() {
+ return providers;
+ }
+}
diff --git a/modules/ri-base/src/main/java/io/trygvis/rules/terraform/TerraformProvider.java b/modules/ri-base/src/main/java/io/trygvis/rules/terraform/TerraformProvider.java
new file mode 100644
index 0000000..613d953
--- /dev/null
+++ b/modules/ri-base/src/main/java/io/trygvis/rules/terraform/TerraformProvider.java
@@ -0,0 +1,22 @@
+package io.trygvis.rules.terraform;
+
+public class TerraformProvider {
+ private String source;
+ private String version;
+
+ protected TerraformProvider() {
+ }
+
+ public TerraformProvider(String source, String version) {
+ this.source = source;
+ this.version = version;
+ }
+
+ public String getSource() {
+ return source;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+}
diff --git a/modules/ri-base/src/main/java/io/trygvis/rules/terraform/TerraformResource.java b/modules/ri-base/src/main/java/io/trygvis/rules/terraform/TerraformResource.java
new file mode 100644
index 0000000..351a3e1
--- /dev/null
+++ b/modules/ri-base/src/main/java/io/trygvis/rules/terraform/TerraformResource.java
@@ -0,0 +1,213 @@
+package io.trygvis.rules.terraform;
+
+import com.fasterxml.jackson.annotation.JsonValue;
+
+import java.util.*;
+
+@SuppressWarnings("unused")
+public class TerraformResource {
+ private static final String EOL = System.getProperty("line.separator");
+ private final String output;
+ private final String kind;
+ private final String instance;
+ private final Map<String, TerraformValue> values = new LinkedHashMap<>();
+
+ public TerraformResource(String output, String kind, String instance) {
+ this.output = output;
+ this.kind = kind;
+ this.instance = instance;
+ }
+
+ public String getOutput() {
+ return output;
+ }
+
+ public String getName() {
+ return kind + "." + instance;
+ }
+
+ public String getKind() {
+ return kind;
+ }
+
+ public String getInstance() {
+ return instance;
+ }
+
+ public Map<String, TerraformValue> getValues() {
+ return values;
+ }
+
+ public TerraformResource set(String key, String value) {
+ values.put(key, new StringTerraformValue(value));
+ return this;
+ }
+
+ public TerraformResource set(String key, boolean value) {
+ values.put(key, new BooleanTerraformValue(value));
+ return this;
+ }
+
+ public TerraformResource set(String key, int value) {
+ values.put(key, new IntegerTerraformValue(value));
+ return this;
+ }
+
+ public TerraformResource setExpression(String key, String value) {
+ values.put(key, new ExpressionTerraformValue(value));
+ return this;
+ }
+
+ public ArrayTerraformValue array(String key) {
+ var array = new ArrayTerraformValue();
+ values.put(key, array);
+ return array;
+ }
+
+ public String asString() {
+ var buf = new StringBuilder();
+
+ buf.append("resource ");
+ buf.append(quote(kind));
+ buf.append(" ");
+ buf.append(quote(instance));
+ buf.append(" {");
+ buf.append(EOL);
+
+ for (var entry : values.entrySet()) {
+ buf.append(" ");
+ buf.append(entry.getKey());
+ buf.append(" = ");
+ buf.append(entry.getValue().asString());
+ buf.append(EOL);
+ }
+
+ buf.append("}");
+
+ return buf.toString();
+ }
+
+ public interface TerraformValue {
+ String asString();
+ }
+
+ public static class StringTerraformValue implements TerraformValue {
+ private final String value;
+
+ public StringTerraformValue(String value) {
+ this.value = value;
+ }
+
+ @JsonValue
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String asString() {
+ return quote(value);
+ }
+ }
+
+ public static class BooleanTerraformValue implements TerraformValue {
+ private final boolean value;
+
+ public BooleanTerraformValue(boolean value) {
+ this.value = value;
+ }
+
+ @JsonValue
+ public boolean getValue() {
+ return value;
+ }
+
+ @Override
+ public String asString() {
+ return String.valueOf(value);
+ }
+ }
+
+ public static class IntegerTerraformValue implements TerraformValue {
+ private final int value;
+
+ public IntegerTerraformValue(int value) {
+ this.value = value;
+ }
+
+ @JsonValue
+ public int getValue() {
+ return value;
+ }
+
+ @Override
+ public String asString() {
+ return String.valueOf(value);
+ }
+ }
+
+ public static class ExpressionTerraformValue implements TerraformValue {
+ private final String value;
+
+ public ExpressionTerraformValue(String value) {
+ this.value = value;
+ }
+
+ @JsonValue
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String asString() {
+ return value;
+ }
+ }
+
+ public static class ArrayTerraformValue implements TerraformValue {
+ private final List<TerraformValue> values = new ArrayList<>();
+
+ @JsonValue
+ public List<TerraformValue> getValues() {
+ return values;
+ }
+
+ public ArrayTerraformValue add(String value) {
+ values.add(new StringTerraformValue(value));
+ return this;
+ }
+
+ public ArrayTerraformValue add(int value) {
+ values.add(new IntegerTerraformValue(value));
+ return this;
+ }
+
+ public ArrayTerraformValue add(boolean value) {
+ values.add(new BooleanTerraformValue(value));
+ return this;
+ }
+
+ public ArrayTerraformValue addExpression(String value) {
+ values.add(new ExpressionTerraformValue(value));
+ return this;
+ }
+
+ @Override
+ public String asString() {
+ if (values.isEmpty()) {
+ return "[]";
+ }
+
+ if (values.size() == 1) {
+ return "[" + values.get(0).asString() + "]";
+ }
+
+ var j = new StringJoiner(",\n ", "[\n", "\n ]");
+ values.forEach(value -> j.add(value.asString()));
+ return j.toString();
+ }
+ }
+
+ private static String quote(String value) {
+ return "\"%s\"".formatted(value);
+ }
+}
diff --git a/modules/ri-base/src/main/resources/META-INF/kmodule.xml b/modules/ri-base/src/main/resources/META-INF/kmodule.xml
new file mode 100644
index 0000000..4650977
--- /dev/null
+++ b/modules/ri-base/src/main/resources/META-INF/kmodule.xml
@@ -0,0 +1,12 @@
+<?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 name="all" packages="io.trygvis.rules.*"/>
+ <kbase name="engine" packages="io.trygvis.rules.engine"/>
+ <kbase name="dba" packages="io.trygvis.rules.dba"/>
+ <kbase name="machine" packages="io.trygvis.rules.machine"/>
+ <kbase name="terraform" packages="io.trygvis.rules.terraform"/>
+ <kbase name="scaleway" packages="io.trygvis.rules.scaleway"/>
+</kmodule>
diff --git a/modules/ri-base/src/main/resources/io/trygvis/rules/dba/dba.drl b/modules/ri-base/src/main/resources/io/trygvis/rules/dba/dba.drl
new file mode 100644
index 0000000..c31bc57
--- /dev/null
+++ b/modules/ri-base/src/main/resources/io/trygvis/rules/dba/dba.drl
@@ -0,0 +1,69 @@
+package io.trygvis.rules.dba
+
+import io.trygvis.rules.core.Problem
+import io.trygvis.rules.machine.Machine
+import java.util.ArrayList
+import java.util.Map
+import java.util.HashMap
+import java.util.List
+import java.util.stream.Collectors
+import java.util.Collections
+
+global io.trygvis.rules.engine.TemplateEngine te;
+
+dialect "mvel"
+
+declare DbaMachineRole
+ machine : String
+ roles : String[]
+end
+
+rule "Assign containers to machine"
+when
+ $machine : Machine()
+ $machineRole : DbaMachineRole(machine == $machine.name)
+ $container : Container(machine == null, $machineRole.roles contains machineRole)
+then
+ System.out.println("Assigning container to machine: " + $machine.name);
+ modify ($container) {
+ machine = $machine
+ }
+end
+
+rule "Containers without hosts"
+ agenda-group "generate"
+when
+ $container : Container(machine == null)
+then
+ insert(new Problem("No machine for container", $container))
+end
+
+rule "Generate docker-compose.yaml"
+ agenda-group "generate"
+when
+ $cluster : Cluster()
+ $containers : ArrayList(size > 0) from collect(Container(cluster == $cluster))
+then
+ System.out.println("Docker compose for cluster: " + $cluster.name + " with " + $containers.size() + " containers");
+
+ Map containersByMachine = new HashMap();
+ for (Object o : $containers) {
+ Container c = (Container) o;
+
+ List list = (List) containersByMachine.get(c.getMachine());
+ if (list == null) {
+ list = new ArrayList();
+ containersByMachine.put(c.getMachine(), list);
+ }
+ list.add(c);
+ }
+
+ System.out.println("containersByMachine = " + containersByMachine);
+
+ String path = "ansible/dba/" + $cluster.name + ".yml";
+ te.template("dba/cluster", path, Map.of(
+ "cluster", $cluster,
+ "containers", $containers,
+ "containersByMachine", containersByMachine
+ ));
+end
diff --git a/modules/ri-base/src/main/resources/io/trygvis/rules/engine/init.drl b/modules/ri-base/src/main/resources/io/trygvis/rules/engine/init.drl
new file mode 100644
index 0000000..267cc4a
--- /dev/null
+++ b/modules/ri-base/src/main/resources/io/trygvis/rules/engine/init.drl
@@ -0,0 +1,17 @@
+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
+
+global io.trygvis.rules.engine.TemplateEngine te;
+
+rule "Clean directories"
+ agenda-group "init"
+when
+ not(KeyValue(key == "rm-gen"));
+then
+ te.clean();
+ insert(new KeyValue("rm-gen", null));
+end
diff --git a/modules/ri-base/src/main/resources/io/trygvis/rules/machine/machine.drl b/modules/ri-base/src/main/resources/io/trygvis/rules/machine/machine.drl
new file mode 100644
index 0000000..0250cc6
--- /dev/null
+++ b/modules/ri-base/src/main/resources/io/trygvis/rules/machine/machine.drl
@@ -0,0 +1 @@
+package io.trygvis.rules.machine;
diff --git a/modules/ri-base/src/main/resources/io/trygvis/rules/scaleway/terraform.drl b/modules/ri-base/src/main/resources/io/trygvis/rules/scaleway/terraform.drl
new file mode 100644
index 0000000..7e0ff03
--- /dev/null
+++ b/modules/ri-base/src/main/resources/io/trygvis/rules/scaleway/terraform.drl
@@ -0,0 +1,81 @@
+package io.trygvis.rules.scaleway
+
+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.ArrayList;
+import java.util.Map;
+
+global io.trygvis.rules.engine.TemplateEngine te;
+
+dialect "mvel"
+
+declare ScalewayMachine
+ machine : Machine
+ key : String
+end
+
+declare GoogleManagedZoneTerraformExpression
+ name : 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
+ DnsEntry a = DnsEntry.a($machine.fqdn);
+ insert(a);
+
+ String ipv4 = "scaleway_instance_ip.%s.address".formatted($machine.name);
+ insert(new DnsEntryTerraformExpression(a, $machine.name, ipv4));
+end
+
+rule "main-scaleway-machine.tf"
+ agenda-group "generate"
+when
+ $managedZones : ArrayList() from collect(GoogleManagedZoneTerraformExpression())
+then
+ String path = "terraform/main-scaleway-machine.tf";
+ te.template("terraform/main-scaleway-machine", path, Map.of(
+ "managedZones", $managedZones
+ ));
+end
+
+rule "TF for TerraformMachine"
+ agenda-group "generate"
+when
+ $m: Machine()
+ $scw: ScalewayMachine(machine == $m)
+then
+ String path = "terraform/scaleway-machine-%s.tf".formatted($scw.getKey());
+ te.template("terraform/machine", path, Map.of("m", $m, "scw", $scw));
+end
+
+rule "Terraform for DNS"
+ agenda-group "generate"
+when
+ $entry: DnsEntry()
+ $tf : DnsEntryTerraformExpression(entry == $entry)
+ $managedZone : GoogleManagedZoneTerraformExpression()
+then
+ String path = "terraform/dns-%s.tf".formatted($tf.key);
+ te.template("terraform/record-set", path, Map.of(
+ "entry", $entry,
+ "managedZone", $managedZone,
+ "tf", $tf)
+ );
+end
diff --git a/modules/ri-base/src/main/resources/io/trygvis/rules/terraform/terraform-resources.drl b/modules/ri-base/src/main/resources/io/trygvis/rules/terraform/terraform-resources.drl
new file mode 100644
index 0000000..40a81f0
--- /dev/null
+++ b/modules/ri-base/src/main/resources/io/trygvis/rules/terraform/terraform-resources.drl
@@ -0,0 +1,28 @@
+package io.trygvis.rules.terraform;
+
+import java.util.ArrayList
+import java.util.HashSet
+import java.util.Map
+import java.util.Set
+
+global io.trygvis.rules.engine.TemplateEngine te;
+
+dialect "mvel"
+
+rule "terraform-resources"
+ agenda-group "generate"
+when
+ $r : TerraformResource()
+then
+ te.template("terraform/resource", $r.output, Map.of("resource", $r));
+end
+
+rule "main.tf"
+ agenda-group "generate"
+when
+ $main : TerraformMain()
+ $variables : ArrayList() from collect(TerraformInputVariable(module == $main.module))
+then
+ te.template("terraform/main", $main.module + "/main.tf", Map.of("main", $main));
+ te.template("terraform/variables", $main.module + "/vars.tf", Map.of("variables", $variables));
+end
diff --git a/modules/ri-base/src/main/resources/templates/dba/cluster.j2 b/modules/ri-base/src/main/resources/templates/dba/cluster.j2
new file mode 100644
index 0000000..045fbcd
--- /dev/null
+++ b/modules/ri-base/src/main/resources/templates/dba/cluster.j2
@@ -0,0 +1,19 @@
+# Generated
+
+# cluster: {{ cluster.name }}
+{%- for m, containers in containersByMachine.entrySet() %}
+---
+- host:
+ - {{ m.name }}
+ tasks:
+ import_role:
+ name: docker-service
+ vars:
+ template: |
+ version: "3"
+ services:
+{%- for c in containers %}
+ {{ c.name }}:
+ image: {{ c.image }}:{{ c.tag }}
+{%- endfor %}
+{% endfor %}
diff --git a/modules/ri-base/src/main/resources/templates/platform-ansible.j2 b/modules/ri-base/src/main/resources/templates/platform-ansible.j2
new file mode 100644
index 0000000..72e3247
--- /dev/null
+++ b/modules/ri-base/src/main/resources/templates/platform-ansible.j2
@@ -0,0 +1,6 @@
+# Ansible
+- hosts:
+ - {{ m.key }}
+ tasks:
+ - import_role:
+ name: acme-platform
diff --git a/modules/ri-base/src/main/resources/templates/terraform/machine-outputs.j2 b/modules/ri-base/src/main/resources/templates/terraform/machine-outputs.j2
new file mode 100644
index 0000000..438fac6
--- /dev/null
+++ b/modules/ri-base/src/main/resources/templates/terraform/machine-outputs.j2
@@ -0,0 +1,7 @@
+output "addresses" {
+ value = {
+{%- for m in machines %}
+ {{ m.key }}: scaleway_instance_ip.{{ m.key }}.address,
+{%- endfor %}
+ }
+}
diff --git a/modules/ri-base/src/main/resources/templates/terraform/machine.j2 b/modules/ri-base/src/main/resources/templates/terraform/machine.j2
new file mode 100644
index 0000000..0ac8b90
--- /dev/null
+++ b/modules/ri-base/src/main/resources/templates/terraform/machine.j2
@@ -0,0 +1,19 @@
+resource "scaleway_instance_server" "{{ scw.key }}" {
+ name = "acme-1"
+ type = "DEV1-S"
+ image = "b3042271-d2b1-4f87-b407-aedd3bbd1663"
+ ip_id = scaleway_instance_ip.{{ scw.key }}.id
+ enable_dynamic_ip = false
+ enable_ipv6 = true
+}
+
+resource "scaleway_instance_ip" "{{ scw.key }}" {}
+
+resource "scaleway_instance_ip_reverse_dns" "{{ scw.key }}" {
+ ip_id = scaleway_instance_ip.{{ scw.key }}.id
+ reverse = "{{ m.fqdn }}."
+}
+
+output "{{scw.key}}_public_ip" {
+ value = scaleway_instance_server.{{ scw.key }}.public_ip
+}
diff --git a/modules/ri-base/src/main/resources/templates/terraform/main-scaleway-machine.j2 b/modules/ri-base/src/main/resources/templates/terraform/main-scaleway-machine.j2
new file mode 100644
index 0000000..eab9fac
--- /dev/null
+++ b/modules/ri-base/src/main/resources/templates/terraform/main-scaleway-machine.j2
@@ -0,0 +1,19 @@
+# Generated
+
+terraform {
+ required_providers {
+ scaleway = {
+ source = "scaleway/scaleway"
+ version = "1.17.2"
+ }
+ }
+}
+
+provider "scaleway" {
+}
+
+{% -for z in managedZones %}
+variable "{{z.name}}" {
+ type = string
+}
+{% endfor -%}
diff --git a/modules/ri-base/src/main/resources/templates/terraform/main.j2 b/modules/ri-base/src/main/resources/templates/terraform/main.j2
new file mode 100644
index 0000000..009f4ed
--- /dev/null
+++ b/modules/ri-base/src/main/resources/templates/terraform/main.j2
@@ -0,0 +1,10 @@
+terraform {
+ required_providers {
+{% for alias, provider in main.providers.entrySet() %}
+ {{ alias }} = {
+ version = "{{ provider.version }}"
+ source = "{{ provider.source }}"
+ }
+{%- endfor %}
+ }
+}
diff --git a/modules/ri-base/src/main/resources/templates/terraform/record-set.j2 b/modules/ri-base/src/main/resources/templates/terraform/record-set.j2
new file mode 100644
index 0000000..b89ee00
--- /dev/null
+++ b/modules/ri-base/src/main/resources/templates/terraform/record-set.j2
@@ -0,0 +1,8 @@
+resource "google_dns_record_set" "{{ tf.key }}" {
+ name = "{{ entry.fqdn }}"
+ managed_zone = var.{{ managedZone.name }}
+ type = "{{ entry.type }}"
+ ttl = 300
+
+ rrdatas = [{{ tf.expression }}]
+}
diff --git a/modules/ri-base/src/main/resources/templates/terraform/resource.j2 b/modules/ri-base/src/main/resources/templates/terraform/resource.j2
new file mode 100644
index 0000000..91af481
--- /dev/null
+++ b/modules/ri-base/src/main/resources/templates/terraform/resource.j2
@@ -0,0 +1 @@
+{{ resource.asString() -}}
diff --git a/modules/ri-base/src/main/resources/templates/terraform/variables.j2 b/modules/ri-base/src/main/resources/templates/terraform/variables.j2
new file mode 100644
index 0000000..6c37d99
--- /dev/null
+++ b/modules/ri-base/src/main/resources/templates/terraform/variables.j2
@@ -0,0 +1,10 @@
+{%- for var in variables %}
+variable "{{ var.name }}" {
+{%- if var.type %}
+ type = {{ var.type }}
+{% endif %}
+{%- if var.default %}
+ default = {{ var.default }}
+{% endif %}
+}
+{% endfor %}
diff --git a/modules/ri-base/src/test/java/io/trygvis/rules/network/Ipv4CidrTest.txt b/modules/ri-base/src/test/java/io/trygvis/rules/network/Ipv4CidrTest.txt
new file mode 100644
index 0000000..826f586
--- /dev/null
+++ b/modules/ri-base/src/test/java/io/trygvis/rules/network/Ipv4CidrTest.txt
@@ -0,0 +1,37 @@
+package io.trygvis.rules.network;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static io.trygvis.rules.network.Ipv4Cidr.parseCidr;
+import static org.junit.jupiter.api.Assertions.*;
+
+class Ipv4CidrTest {
+
+ @Test
+ public void basic() {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> parseCidr("192.168.1.1/24").addresses());
+ assertEquals(256, parseCidr("192.168.1.0/24").addresses().size());
+ assertEquals(128, parseCidr("192.168.1.128/25").addresses().size());
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "192.168.1.0/24",
+ "192.168.1.128/25",
+ })
+ public void testParsing(String s) {
+ assertEquals(s, parseCidr(s).toString());
+ }
+
+ @Test
+ public void partition() {
+ var children = parseCidr("192.168.1.0/24").partition(26);
+ assertEquals(1 << 2, children.size());
+ for (var cidr : children) {
+ assertEquals(26, cidr.bits);
+ }
+ }
+}
diff --git a/modules/ri-docker/pom.xml b/modules/ri-docker/pom.xml
new file mode 100644
index 0000000..49729da
--- /dev/null
+++ b/modules/ri-docker/pom.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>io.trygvis.rules-sandbox.module</groupId>
+ <artifactId>ri-module-parent</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <relativePath>../ri-module-parent/pom.xml</relativePath>
+ </parent>
+
+ <artifactId>ri-docker</artifactId>
+ <packaging>kjar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>ri-base</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/modules/ri-module-api/classpath.txt b/modules/ri-module-api/classpath.txt
new file mode 100644
index 0000000..1f0f0a6
--- /dev/null
+++ b/modules/ri-module-api/classpath.txt
@@ -0,0 +1,23 @@
+io.trygvis.rules-sandbox.module:ri-module-api:1.0-SNAPSHOT:jar
+com.github.javaparser:javaparser-core:3.13.10:jar
+com.thoughtworks.xstream:xstream:1.4.14:jar
+commons-codec:commons-codec:1.14:jar
+commons-io:commons-io:2.8.0:jar
+org.antlr:antlr-runtime:3.5.2:jar
+org.drools:drools-canonical-model:7.48.0.Final:jar
+org.drools:drools-compiler:7.48.0.Final:jar
+org.drools:drools-core:7.48.0.Final:jar
+org.drools:drools-core-dynamic:7.48.0.Final:jar
+org.drools:drools-core-reflective:7.48.0.Final:jar
+org.drools:drools-ecj:7.48.0.Final:jar
+org.drools:drools-model-compiler:7.48.0.Final:jar
+org.drools:drools-mvel-compiler:7.48.0.Final:jar
+org.drools:drools-mvel-parser:7.48.0.Final:jar
+org.kie:kie-api:7.48.0.Final:jar
+org.kie:kie-internal:7.48.0.Final:jar
+org.kie:kie-memory-compiler:7.48.0.Final:jar
+org.kie.soup:kie-soup-maven-support:7.48.0.Final:jar
+org.kie.soup:kie-soup-xstream:7.48.0.Final:jar
+org.slf4j:slf4j-api:1.7.30:jar
+xmlpull:xmlpull:1.1.3.1:jar
+xpp3:xpp3_min:1.1.4c:jar
diff --git a/modules/ri-module-api/pom.xml b/modules/ri-module-api/pom.xml
new file mode 100644
index 0000000..118b5ba
--- /dev/null
+++ b/modules/ri-module-api/pom.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>io.trygvis.rules-sandbox.module</groupId>
+ <artifactId>ri-module-parent</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <relativePath>../ri-module-parent/pom.xml</relativePath>
+ </parent>
+
+ <artifactId>ri-module-api</artifactId>
+
+</project>
diff --git a/modules/ri-module-api/src/main/java/io/trygvis/rules/engine/TemplateEngine.java b/modules/ri-module-api/src/main/java/io/trygvis/rules/engine/TemplateEngine.java
new file mode 100644
index 0000000..eafa6e4
--- /dev/null
+++ b/modules/ri-module-api/src/main/java/io/trygvis/rules/engine/TemplateEngine.java
@@ -0,0 +1,10 @@
+package io.trygvis.rules.engine;
+
+import java.io.IOException;
+import java.util.Map;
+
+public interface TemplateEngine {
+ void clean();
+
+ void template(String name, String output, Map<String, Object> params) throws IOException;
+}
diff --git a/modules/ri-module-parent/classpath.txt b/modules/ri-module-parent/classpath.txt
new file mode 100644
index 0000000..8624088
--- /dev/null
+++ b/modules/ri-module-parent/classpath.txt
@@ -0,0 +1,23 @@
+io.trygvis.rules-sandbox.module:ri-module-parent:1.0-SNAPSHOT:pom
+com.github.javaparser:javaparser-core:3.13.10:jar
+com.thoughtworks.xstream:xstream:1.4.14:jar
+commons-codec:commons-codec:1.14:jar
+commons-io:commons-io:2.8.0:jar
+org.antlr:antlr-runtime:3.5.2:jar
+org.drools:drools-canonical-model:7.48.0.Final:jar
+org.drools:drools-compiler:7.48.0.Final:jar
+org.drools:drools-core:7.48.0.Final:jar
+org.drools:drools-core-dynamic:7.48.0.Final:jar
+org.drools:drools-core-reflective:7.48.0.Final:jar
+org.drools:drools-ecj:7.48.0.Final:jar
+org.drools:drools-model-compiler:7.48.0.Final:jar
+org.drools:drools-mvel-compiler:7.48.0.Final:jar
+org.drools:drools-mvel-parser:7.48.0.Final:jar
+org.kie:kie-api:7.48.0.Final:jar
+org.kie:kie-internal:7.48.0.Final:jar
+org.kie:kie-memory-compiler:7.48.0.Final:jar
+org.kie.soup:kie-soup-maven-support:7.48.0.Final:jar
+org.kie.soup:kie-soup-xstream:7.48.0.Final:jar
+org.slf4j:slf4j-api:1.7.30:jar
+xmlpull:xmlpull:1.1.3.1:jar
+xpp3:xpp3_min:1.1.4c:jar
diff --git a/modules/ri-module-parent/pom.xml b/modules/ri-module-parent/pom.xml
new file mode 100644
index 0000000..d593ca8
--- /dev/null
+++ b/modules/ri-module-parent/pom.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>ri-module-parent</artifactId>
+ <groupId>io.trygvis.rules-sandbox.module</groupId>
+ <version>1.0-SNAPSHOT</version>
+ <packaging>pom</packaging>
+
+ <properties>
+ <version.drools>7.48.0.Final</version.drools>
+ <java.version>13</java.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.drools</groupId>
+ <artifactId>drools-model-compiler</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ </dependency>
+ </dependencies>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>io.trygvis.rules-sandbox</groupId>
+ <artifactId>ri-engine</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <build>
+ <finalName>${project.artifactId}</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.kie</groupId>
+ <artifactId>kie-maven-plugin</artifactId>
+ <extensions>true</extensions>
+ </plugin>
+ <plugin>
+ <groupId>eu.nets.oss.maven</groupId>
+ <artifactId>classpath-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>default</id>
+ <goals>
+ <goal>export-classpath</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ <pluginManagement>
+ <plugins>
+ <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
+ <plugin>
+ <groupId>org.eclipse.m2e</groupId>
+ <artifactId>lifecycle-mapping</artifactId>
+ <version>1.0.0</version>
+ <configuration>
+ <lifecycleMappingMetadata>
+ <pluginExecutions>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <groupId>org.kie</groupId>
+ <artifactId>kie-maven-plugin</artifactId>
+ <versionRange>[7.48.0.Final,)</versionRange>
+ <goals>
+ <goal>generatePMMLModel</goal>
+ <goal>generateDMNModel</goal>
+ <goal>build</goal>
+ <goal>generateANC</goal>
+ <goal>generateModel</goal>
+ <goal>validateDMN</goal>
+ <goal>injectreactive</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <ignore></ignore>
+ </action>
+ </pluginExecution>
+ </pluginExecutions>
+ </lifecycleMappingMetadata>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.kie</groupId>
+ <artifactId>kie-maven-plugin</artifactId>
+ <version>${version.drools}</version>
+ <extensions>true</extensions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.8.1</version>
+ <configuration>
+ <encoding>UTF-8</encoding>
+ <release>${java.version}</release>
+ <source>${java.version}</source>
+ <target>${java.version}</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>3.2.0</version>
+ <configuration>
+ <encoding>UTF-8</encoding>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>eu.nets.oss.maven</groupId>
+ <artifactId>classpath-maven-plugin</artifactId>
+ <version>1.0</version>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+
+</project>
diff --git a/modules/ri-wireguard/classpath.txt b/modules/ri-wireguard/classpath.txt
new file mode 100644
index 0000000..ef1b057
--- /dev/null
+++ b/modules/ri-wireguard/classpath.txt
@@ -0,0 +1,28 @@
+io.trygvis.rules-sandbox.module:ri-wireguard:1.0-SNAPSHOT:kjar
+com.fasterxml.jackson.core:jackson-annotations:2.12.0:jar
+com.fasterxml.jackson.core:jackson-core:2.12.0:jar
+com.fasterxml.jackson.core:jackson-databind:2.12.0:jar
+com.github.javaparser:javaparser-core:3.13.10:jar
+com.thoughtworks.xstream:xstream:1.4.14:jar
+commons-codec:commons-codec:1.14:jar
+commons-io:commons-io:2.8.0:jar
+io.trygvis.rules-sandbox.module:ri-base:1.0-SNAPSHOT:jar
+io.trygvis.rules-sandbox.module:ri-module-api:1.0-SNAPSHOT:jar
+org.antlr:antlr-runtime:3.5.2:jar
+org.drools:drools-canonical-model:7.48.0.Final:jar
+org.drools:drools-compiler:7.48.0.Final:jar
+org.drools:drools-core:7.48.0.Final:jar
+org.drools:drools-core-dynamic:7.48.0.Final:jar
+org.drools:drools-core-reflective:7.48.0.Final:jar
+org.drools:drools-ecj:7.48.0.Final:jar
+org.drools:drools-model-compiler:7.48.0.Final:jar
+org.drools:drools-mvel-compiler:7.48.0.Final:jar
+org.drools:drools-mvel-parser:7.48.0.Final:jar
+org.kie:kie-api:7.48.0.Final:jar
+org.kie:kie-internal:7.48.0.Final:jar
+org.kie:kie-memory-compiler:7.48.0.Final:jar
+org.kie.soup:kie-soup-maven-support:7.48.0.Final:jar
+org.kie.soup:kie-soup-xstream:7.48.0.Final:jar
+org.slf4j:slf4j-api:1.7.30:jar
+xmlpull:xmlpull:1.1.3.1:jar
+xpp3:xpp3_min:1.1.4c:jar
diff --git a/modules/ri-wireguard/pom.xml b/modules/ri-wireguard/pom.xml
new file mode 100644
index 0000000..cc569d7
--- /dev/null
+++ b/modules/ri-wireguard/pom.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>io.trygvis.rules-sandbox.module</groupId>
+ <artifactId>ri-module-parent</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ <relativePath>../ri-module-parent/pom.xml</relativePath>
+ </parent>
+
+ <artifactId>ri-wireguard</artifactId>
+ <packaging>kjar</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>ri-base</artifactId>
+ <version>1.0-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/modules/ri-wireguard/src/main/resources/META-INF/kmodule.xml b/modules/ri-wireguard/src/main/resources/META-INF/kmodule.xml
new file mode 100644
index 0000000..de617f7
--- /dev/null
+++ b/modules/ri-wireguard/src/main/resources/META-INF/kmodule.xml
@@ -0,0 +1,9 @@
+<?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 name="wireguard" packages="io.trygvis.rules.wireguard">
+<!-- <ksession name="wireguard"/>-->
+ </kbase>
+</kmodule>
diff --git a/modules/ri-wireguard/src/main/resources/io/trygvis/rules/wireguard/wireguard.drl b/modules/ri-wireguard/src/main/resources/io/trygvis/rules/wireguard/wireguard.drl
new file mode 100644
index 0000000..5630ab6
--- /dev/null
+++ b/modules/ri-wireguard/src/main/resources/io/trygvis/rules/wireguard/wireguard.drl
@@ -0,0 +1,185 @@
+package io.trygvis.rules.wireguard;
+
+import io.trygvis.rules.dns.DnsEntry;
+import io.trygvis.rules.machine.Machine;
+import io.trygvis.rules.network.IpCalc
+import io.trygvis.rules.network.Ipv4Address
+import io.trygvis.rules.network.Ipv4Cidr
+import java.util.ArrayList
+import java.util.List
+import java.util.Map
+
+global io.trygvis.rules.engine.TemplateEngine te;
+
+dialect "mvel"
+
+declare WgNet
+ name : String
+ domain : String
+ port : int
+ linkCidr : String
+ networkCidr : String
+ networkBits : int
+end
+
+declare WgIpPool
+ net : WgNet
+ role : String
+ cidr : Ipv4Cidr
+end
+
+declare WgHost
+ machine : Machine
+ net : WgNet
+ publicName : String
+ publicPort : int
+ ip : String // This host's IP
+ networkCidr : String
+end
+
+declare WgConnection
+ name : String
+ host : WgHost
+ to : WgHost
+end
+
+declare WgIpAllocation
+ host : WgHost
+ role : String
+ ip : Ipv4Address
+end
+
+rule "Create IP pools" when
+ $net : WgNet()
+// not(Ipv4Cidr(network == Ipv4Cidr.parseCidr($net.linkCidr).network))
+then
+ System.out.println("Creating main IP pools");
+ insert(new WgIpPool($net, "link", Ipv4Cidr.parseCidr($net.getLinkCidr())))
+ insert(new WgIpPool($net, "networks", Ipv4Cidr.parseCidr($net.getNetworkCidr())))
+end
+
+rule "WgHost VPN machines"
+when
+ $machine : Machine()
+ $wgNet : WgNet(name == "vpn0")
+ not(WgHost(machine == $machine))
+then
+ WgHost wgHost = new WgHost();
+ wgHost.machine = $machine;
+ wgHost.net = $wgNet;
+ wgHost.publicName = $machine.fqdn;
+ wgHost.publicPort = $wgNet.port;
+ insert(wgHost)
+end
+
+rule "Set public name of WgHost"
+when
+ $host : WgHost(publicName == null)
+ $m : Machine(this == $host.machine, fqdn != null)
+then
+ modify($host) {
+ publicName = $m.fqdn
+ }
+end
+
+rule "Make DNS entries for all VPN hosts"
+when
+ $h : WgHost()
+ not(DnsEntry(fqdn == "%s.%s".formatted($h.machine.name, $h.net.domain), type == "A"))
+then
+ String fqdn = "%s.%s".formatted($h.machine.name, $h.net.domain);
+ insert(DnsEntry.a(fqdn))
+end
+
+rule "Connect VPN nodes"
+ salience -1
+when
+ $h : WgHost()
+ $other : WgHost(publicName != null, this != $h)
+then
+ System.out.printf("VPN connection from %s to %s%n", $h.machine.name, $other.machine.name);
+ WgConnection c = new WgConnection();
+ c.host = $h;
+ c.to = $other;
+ insert(c)
+end
+
+rule "Name connections"
+when
+ $c : WgConnection(name == null, host != null, to != null)
+then
+ String n = $c.host.machine.name + "_x_" + $c.to.machine.name;
+ modify($c) {
+ name = n
+ }
+end
+
+// This and the next rule needs to use .toString(), the specific objects might be generated multiple times,
+// but Drools use identityHashCode() to find equal objects, not equals().
+rule "Assign IP"
+when
+ $pool : WgIpPool(role == "link")
+ $ip : Ipv4Address() from $pool.cidr.addresses()
+ not(WgHost(net == $pool.net, ip == $ip.toString()))
+ $host : WgHost(net == $pool.net, ip == null)
+then
+ System.out.printf("IP: net=%s, pool.role=%s, host=%s, ip=%s%n", $pool.net.name, $pool.role, $host.machine.name, $ip);
+ modify($host) {
+ ip = $ip.toString()
+ }
+end
+
+rule "Assign network CIDR"
+when
+ $net : WgNet()
+ $network : Ipv4Cidr() from Ipv4Cidr.parseCidr($net.networkCidr).partition($net.networkBits)
+ $host : WgHost(net == $net, networkCidr == null)
+ not(WgHost(net == $net, networkCidr == $network.toString()))
+then
+ System.out.printf("Network CIDR: net=%s, host=%s, network=%s%n", $net.name, $host.machine.name, $network);
+ modify($host) {
+ networkCidr = $network.toString()
+ }
+end
+
+rule "Generate per-net files"
+ agenda-group "generate"
+ salience 10
+when
+ $net : WgNet()
+ $hosts : ArrayList() from collect(WgHost(net == $net))
+then
+ te.template("wireguard/ansible", "wireguard-" + $net.name + ".yml", Map.of(
+ "net", $net
+ ));
+
+ List machines = new ArrayList();
+ for (Object o : $hosts) {
+ WgHost m = (WgHost) o;
+ machines.add(m.getMachine());
+ }
+
+ te.template("wireguard/inventory", "inventory.yml", Map.of(
+ "net", $net,
+ "hosts", machines
+ ));
+end
+
+rule "Generate per-net, per-host files"
+ agenda-group "generate"
+ salience 10
+when
+ $net : WgNet()
+ $host : WgHost(net == $net)
+ $peers : ArrayList() from accumulate(WgConnection(host == $host, $to: to), collectList($to))
+then
+ System.out.printf("Generating per-host files: net=%s, host=%s%n", $net.name, $host.machine.name);
+
+ String output = "host_vars/%s/wireguard.yml".formatted($host.machine.name);
+
+ te.template("wireguard/ansible-host", output, Map.of(
+ "net", $net,
+ "host", $host,
+ "peers", $peers
+ ));
+end
diff --git a/modules/ri-wireguard/src/main/resources/logback.xml b/modules/ri-wireguard/src/main/resources/logback.xml
new file mode 100644
index 0000000..66ae905
--- /dev/null
+++ b/modules/ri-wireguard/src/main/resources/logback.xml
@@ -0,0 +1,13 @@
+<configuration>
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+ </encoder>
+ </appender>
+
+ <logger name="org.apache.http" level="WARN"/>
+ <root level="DEBUG">
+ <appender-ref ref="STDOUT" />
+ </root>
+
+</configuration>
diff --git a/modules/ri-wireguard/src/main/resources/templates/wireguard/ansible-host.j2 b/modules/ri-wireguard/src/main/resources/templates/wireguard/ansible-host.j2
new file mode 100644
index 0000000..6cb3a05
--- /dev/null
+++ b/modules/ri-wireguard/src/main/resources/templates/wireguard/ansible-host.j2
@@ -0,0 +1,12 @@
+# Generated
+wireguard_port: {{ host.publicPort }}
+link_address: {{ host.ip }}
+network_cidr: {{ host.networkCidr }}
+wireguard_peers:
+{%- for peer in peers %}
+ {{ peer.machine.name }}:
+ public_address: {{ peer.publicName }}
+ public_port: {{ peer.publicPort }}
+ gateway: {{ peer.ip }}
+ network: {{ peer.networkCidr }}
+{%- endfor %}
diff --git a/modules/ri-wireguard/src/main/resources/templates/wireguard/ansible.j2 b/modules/ri-wireguard/src/main/resources/templates/wireguard/ansible.j2
new file mode 100644
index 0000000..ad4d034
--- /dev/null
+++ b/modules/ri-wireguard/src/main/resources/templates/wireguard/ansible.j2
@@ -0,0 +1,7 @@
+- hosts: wireguard_{{ net.name }}
+ vars:
+ wireguard_if: {{ net.name }}
+ tasks:
+ - name: wireguard
+ import_role:
+ name: wireguard
diff --git a/modules/ri-wireguard/src/main/resources/templates/wireguard/inventory.j2 b/modules/ri-wireguard/src/main/resources/templates/wireguard/inventory.j2
new file mode 100644
index 0000000..64f3b5b
--- /dev/null
+++ b/modules/ri-wireguard/src/main/resources/templates/wireguard/inventory.j2
@@ -0,0 +1,13 @@
+# Generated
+all:
+ hosts:
+ {%- for host in hosts %}
+ {{ host.getName() }}:
+ ansible_host: {{ host.getFqdn() }}
+ {%- endfor %}
+ children:
+ wireguard_{{ net.name }}:
+ hosts:
+{%- for host in hosts %}
+ {{ host.getName() }}:
+{%- endfor %}