diff options
Diffstat (limited to 'module')
19 files changed, 260 insertions, 38 deletions
diff --git a/module/acme/src/main/resources/META-INF/kmodule.xml b/module/acme/src/main/resources/META-INF/kmodule.xml index 6bcd1db..da435d6 100644 --- a/module/acme/src/main/resources/META-INF/kmodule.xml +++ b/module/acme/src/main/resources/META-INF/kmodule.xml @@ -11,7 +11,7 @@ <ksession name="acme-wireguard" default="true"/> </kbase> - <kbase packages="io.trygvis.acme" name="acme-shared"> + <kbase name="acme-shared" packages="io.trygvis.acme"> <ksession name="acme-shared" default="true"/> </kbase> </kmodule> diff --git a/module/ri-base/src/main/java/io/trygvis/rules/machine/MachineSpecification.java b/module/ri-base/src/main/java/io/trygvis/rules/machine/MachineSpecification.java index 2e17ae5..9d38b2d 100644 --- a/module/ri-base/src/main/java/io/trygvis/rules/machine/MachineSpecification.java +++ b/module/ri-base/src/main/java/io/trygvis/rules/machine/MachineSpecification.java @@ -1,8 +1,10 @@ package io.trygvis.rules.machine; public class MachineSpecification { - public final int cpu; - public final int memory; + public int cpu; + public int memory; + + protected MachineSpecification() {} public MachineSpecification(int cpu, int memory) { this.cpu = cpu; diff --git a/module/ri-base/src/main/resources/io/trygvis/rules/terraform/terraform.drl b/module/ri-base/src/main/resources/io/trygvis/rules/terraform/terraform.drl index 5ebd082..7313998 100644 --- a/module/ri-base/src/main/resources/io/trygvis/rules/terraform/terraform.drl +++ b/module/ri-base/src/main/resources/io/trygvis/rules/terraform/terraform.drl @@ -50,7 +50,7 @@ when $managedZones : ArrayList() from collect(GoogleManagedZoneTerraformExpression()) then String path = "terraform/main-scaleway-machine.tf"; - te.template("terraform-main-scaleway-machine", path, Map.of( + te.template("terraform/main-scaleway-machine", path, Map.of( "managedZones", $managedZones )); end @@ -62,7 +62,7 @@ when $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)); + te.template("terraform/machine", path, Map.of("m", $m, "scw", $scw)); end rule "Terraform for DNS" @@ -73,7 +73,7 @@ when $managedZone : GoogleManagedZoneTerraformExpression() then String path = "terraform/dns-%s.tf".formatted($tf.key); - te.template("terraform-record-set", path, Map.of( + te.template("terraform/record-set", path, Map.of( "entry", $entry, "managedZone", $managedZone, "tf", $tf) diff --git a/module/ri-base/src/main/resources/templates/dba/cluster.j2 b/module/ri-base/src/main/resources/templates/dba/cluster.j2 new file mode 100644 index 0000000..045fbcd --- /dev/null +++ b/module/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/module/ri-base/src/main/resources/templates/platform-ansible.j2 b/module/ri-base/src/main/resources/templates/platform-ansible.j2 new file mode 100644 index 0000000..72e3247 --- /dev/null +++ b/module/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/module/ri-base/src/main/resources/templates/terraform/machine-outputs.j2 b/module/ri-base/src/main/resources/templates/terraform/machine-outputs.j2 new file mode 100644 index 0000000..438fac6 --- /dev/null +++ b/module/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/module/ri-base/src/main/resources/templates/terraform/machine.j2 b/module/ri-base/src/main/resources/templates/terraform/machine.j2 new file mode 100644 index 0000000..0ac8b90 --- /dev/null +++ b/module/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/module/ri-base/src/main/resources/templates/terraform/main-scaleway-machine.j2 b/module/ri-base/src/main/resources/templates/terraform/main-scaleway-machine.j2 new file mode 100644 index 0000000..eab9fac --- /dev/null +++ b/module/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/module/ri-base/src/main/resources/templates/terraform/record-set.j2 b/module/ri-base/src/main/resources/templates/terraform/record-set.j2 new file mode 100644 index 0000000..b89ee00 --- /dev/null +++ b/module/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/module/ri-engine/src/main/java/io/trygvis/rules/engine/DbIo.java b/module/ri-engine/src/main/java/io/trygvis/rules/engine/DbIo.java index d3d309a..e2abd5a 100644 --- a/module/ri-engine/src/main/java/io/trygvis/rules/engine/DbIo.java +++ b/module/ri-engine/src/main/java/io/trygvis/rules/engine/DbIo.java @@ -14,9 +14,10 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; import org.drools.core.common.DefaultFactHandle; import org.drools.core.factmodel.GeneratedFact; import org.kie.api.KieBase; -import org.kie.api.definition.type.FactType; import org.kie.api.runtime.KieContainer; import org.kie.api.runtime.rule.FactHandle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.FileWriter; @@ -27,6 +28,8 @@ import java.util.function.Function; @SuppressWarnings("unchecked") public class DbIo { + private final Logger logger = LoggerFactory.getLogger(getClass()); + private final ObjectMapper mapper; private static final List<String> prioritizedKeys = List.of("key", "name", "fqdn"); @@ -268,9 +271,9 @@ public class DbIo { } } - private static class DbClassLoader extends ClassLoader { - private final KieBase kieBase; + private class DbClassLoader extends ClassLoader { private final KieContainer container; + private final KieBase kieBase; public DbClassLoader(KieContainer container, KieBase kieBase) { this.container = container; @@ -279,35 +282,43 @@ public class DbIo { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { + logger.info("Loading class {}", name); try { - return super.loadClass(name); + var klass = super.loadClass(name); + logger.info("Found class in super classloader"); + return klass; } catch (ClassNotFoundException e) { var i = name.lastIndexOf('.'); - String pkg, klass; + String pkg, simpleName; if (i == -1) { pkg = null; - klass = name; + simpleName = name; } else { pkg = name.substring(0, i); - klass = name.substring(i + 1); + simpleName = name.substring(i + 1); } try { - return container.getClassLoader().loadClass(name); + var klass = container.getClassLoader().loadClass(name); + logger.info("Found class in container's classloader"); + return klass; } catch (ClassNotFoundException ignore) { } - FactType clazz = null; try { - clazz = kieBase.getFactType(pkg, klass); + logger.info("pkg = {}", pkg); + logger.info("simpleName = {}", simpleName); + var clazz = kieBase.getFactType(pkg, simpleName); + if (clazz != null) { + logger.info("Found class as a FactType"); + return clazz.getFactClass(); + } } catch (UnsupportedOperationException ignore) { - System.out.println("AcmeClassLoader.loadClass: " + name); - } - if (clazz == null) { - throw e; } - return clazz.getFactClass(); + logger.warn("Class not found: {}", name); + + throw e; } } } diff --git a/module/ri-engine/src/main/java/io/trygvis/rules/engine/Engine.java b/module/ri-engine/src/main/java/io/trygvis/rules/engine/Engine.java index f73419a..4a49ca2 100644 --- a/module/ri-engine/src/main/java/io/trygvis/rules/engine/Engine.java +++ b/module/ri-engine/src/main/java/io/trygvis/rules/engine/Engine.java @@ -1,6 +1,7 @@ package io.trygvis.rules.engine; import org.drools.core.audit.WorkingMemoryConsoleLogger; +import org.drools.reflective.classloader.ProjectClassLoader; import org.kie.api.KieServices; import org.kie.api.event.rule.AgendaEventListener; import org.kie.api.event.rule.RuleRuntimeEventListener; @@ -12,10 +13,16 @@ import org.slf4j.LoggerFactory; import java.io.Closeable; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import static io.trygvis.rules.engine.TemplateEngine.TemplateLoader; + public class Engine implements Closeable { private final Logger logger = LoggerFactory.getLogger(getClass()); @@ -33,8 +40,10 @@ public class Engine implements Closeable { logger.info("kieRepository.getDefaultReleaseId() = {}", kieRepository.getDefaultReleaseId()); KieContainer container; + TemplateLoader templateLoader; if (modules != null && modules.length > 0) { List<Resource> resources = new ArrayList<>(); + List<URL> files = new ArrayList<>(); for (File path : modules) { if (!path.exists()) { logger.warn("Module path does not exist: {}", path.getAbsolutePath()); @@ -43,6 +52,10 @@ public class Engine implements Closeable { logger.info("New KieBuilder: {}, file={}, directory={}", path, path.isFile(), path.isDirectory()); + if (path.isFile()) { + files.add(path.toURI().toURL()); + } + var resource = services.getResources().newFileSystemResource(path); logger.info("resource.getResourceType() = {}", resource.getResourceType()); resources.add(resource); @@ -54,8 +67,12 @@ public class Engine implements Closeable { logger.info("Creating classpath container, releaseId=" + rId); container = services.newKieContainer(rId); + + templateLoader = new ClasspathTemplateLoader(new URLClassLoader(files.toArray(new URL[0]))); } else { - container = services.getKieClasspathContainer(); + var classLoader = ProjectClassLoader.findParentClassLoader(); + container = services.getKieClasspathContainer(classLoader); + templateLoader = new ClasspathTemplateLoader(classLoader); } logger.info("Creating KieBase \"{}\"", name); @@ -68,11 +85,17 @@ public class Engine implements Closeable { session.addEventListener((AgendaEventListener) l); session.addEventListener((RuleRuntimeEventListener) l); - session.setGlobal("te", new JinjavaTemplateEngine(output)); + session.setGlobal("te", new JinjavaTemplateEngine(templateLoader, output)); logger.info("Loading data"); io = new DbIo(container, kieBase); var objects = io.load(database); + + if (objects.isEmpty()) { + logger.warn("Did not load any objects, something is wrong"); + return; + } + logger.info("Loaded {} objects", objects.size()); for (var object : objects) { @@ -91,4 +114,25 @@ public class Engine implements Closeable { public void close() { session.dispose(); } + + private static class ClasspathTemplateLoader implements TemplateLoader { + private final ClassLoader classLoader; + + private ClasspathTemplateLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + @Override + public String load(String name) throws IOException { + var resource = "templates/" + name + ".j2"; + + try (var inputStream = classLoader.getResourceAsStream(resource)) { + if (inputStream == null) { + throw new FileNotFoundException("Classpath resource: " + resource); + } + + return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + } + } + } } diff --git a/module/ri-engine/src/main/java/io/trygvis/rules/engine/JinjavaTemplateEngine.java b/module/ri-engine/src/main/java/io/trygvis/rules/engine/JinjavaTemplateEngine.java index 42b2127..286029e 100644 --- a/module/ri-engine/src/main/java/io/trygvis/rules/engine/JinjavaTemplateEngine.java +++ b/module/ri-engine/src/main/java/io/trygvis/rules/engine/JinjavaTemplateEngine.java @@ -7,15 +7,19 @@ import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Path; import java.util.Map; +/** + * TODO: cache templates. + */ public class JinjavaTemplateEngine implements TemplateEngine { private final Jinjava jinjava = new Jinjava(); + private final TemplateLoader loader; private final File basedir; - public JinjavaTemplateEngine(File basedir) { + public JinjavaTemplateEngine(TemplateLoader templateLoader, File basedir) { + this.loader = templateLoader; this.basedir = basedir; } @@ -31,7 +35,7 @@ public class JinjavaTemplateEngine implements TemplateEngine { @Override public void template(String name, String output, Map<String, Object> params) throws IOException { - var template = Files.readString(Path.of("j2", name + ".j2")); + var template = loader.load(name); String renderedTemplate = jinjava.render(template, params); var f = new File(basedir, output); FileUtil.createMissingParentDirectories(f); 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 index eafa6e4..a2ae0c2 100644 --- 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 @@ -1,5 +1,6 @@ package io.trygvis.rules.engine; +import java.io.FileNotFoundException; import java.io.IOException; import java.util.Map; @@ -7,4 +8,8 @@ public interface TemplateEngine { void clean(); void template(String name, String output, Map<String, Object> params) throws IOException; + + interface TemplateLoader { + String load(String name) throws IOException; + } } diff --git a/module/ri-engine/src/main/java/io/trygvis/rules/engine/cli/RunCommand.java b/module/ri-engine/src/main/java/io/trygvis/rules/engine/cli/RunCommand.java index 2b016f2..203a9b0 100644 --- a/module/ri-engine/src/main/java/io/trygvis/rules/engine/cli/RunCommand.java +++ b/module/ri-engine/src/main/java/io/trygvis/rules/engine/cli/RunCommand.java @@ -20,14 +20,18 @@ public class RunCommand implements Callable<Integer> { @Option(names = {"--output-state"}) public File outputState; + @Option(names = {"--include"}, split = ",", arity = "1..*") + public String[] includes; + @Option(names = {"--generated-output"}) public File generatedOutput; @Option(names = {"--agenda-groups"}) public String[] agendaGroups; - @Option(names = {"--modules"}, split = ",", arity = "1..*") - public File[] modules; + // TODO: Remove --modules + @Option(names = {"--modules", "--module"}, split = ",", arity = "1..*") + public File[] module; @Override public Integer call() throws Exception { @@ -36,13 +40,39 @@ public class RunCommand implements Callable<Integer> { agendaGroups = new String[]{"init", "generate"}; } - try (var engine = new Engine(name, input, generatedOutput, agendaGroups, modules)) { + try (var engine = new Engine(name, input, generatedOutput, agendaGroups, module)) { engine.io.dump(outputState, engine.session.getFactHandles(), (Object o) -> - o.getClass().getName().contains("Wg") || - o.getClass().getSimpleName().contains("Machine") || - o.getClass().getSimpleName().contains("DnsEntry") || - o.getClass().getSimpleName().contains("Ipv4Cidr") || - o.getClass().getSimpleName().contains("Ipv4Address") + { + if (includes == null || includes.length == 0) { + return true; + } + + var name = o.getClass().getName(); + var simpleName = o.getClass().getSimpleName(); + + for (var i : includes) { + var ok = false; + if (i.startsWith("*")) { + i = i.substring(1); + + if (i.endsWith("*")) { + i = i.substring(1, i.length() - 2); + ok = name.contains(i); + } else { + ok = name.startsWith(i) || simpleName.startsWith(i); + } + } else if (i.endsWith("*")) { + i = i.substring(0, i.length() - 2); + ok = name.startsWith(i) || simpleName.startsWith(i); + } + + if (ok) { + return true; + } + } + + return false; + } ); } diff --git a/module/ri-engine/src/test/java/io/trygvis/rules/engine/WireguardTestMain.java b/module/ri-engine/src/test/java/io/trygvis/rules/engine/AcmeAppsTestMain.java index 20aeffa..17b7950 100644 --- a/module/ri-engine/src/test/java/io/trygvis/rules/engine/WireguardTestMain.java +++ b/module/ri-engine/src/test/java/io/trygvis/rules/engine/AcmeAppsTestMain.java @@ -6,14 +6,14 @@ import java.io.File; import static org.junit.jupiter.api.Assertions.assertEquals; -class WireguardTestMain { +class AcmeAppsTestMain { public static void main(String[] args) throws Exception { var c = new RunCommand(); - c.name = "acme"; + c.name = "acme-apps"; c.input = new File("acme.yaml"); - c.outputState = new File("out/acme/wireguard.yaml"); + c.outputState = new File("out/acme/apps.yaml"); c.agendaGroups = new String[]{"init", "generate"}; - c.generatedOutput = new File("acme-wireguard"); + c.generatedOutput = new File("acme-apps"); assertEquals(0, c.call()); } } diff --git a/module/ri-engine/src/test/java/io/trygvis/rules/engine/AcmeWireguardTestMain.java b/module/ri-engine/src/test/java/io/trygvis/rules/engine/AcmeWireguardTestMain.java new file mode 100644 index 0000000..2bb5513 --- /dev/null +++ b/module/ri-engine/src/test/java/io/trygvis/rules/engine/AcmeWireguardTestMain.java @@ -0,0 +1,27 @@ +package io.trygvis.rules.engine; + +import io.trygvis.rules.engine.cli.RunCommand; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class AcmeWireguardTestMain { + public static void main(String[] args) throws Exception { + var c = new RunCommand(); + c.name = "acme-wireguard"; + c.input = new File("acme.yaml"); + c.outputState = new File("out/acme/wireguard.yaml"); + c.agendaGroups = new String[]{"init", "generate"}; + c.generatedOutput = new File("acme-wireguard"); + c.includes = new String[]{ + "Wg*", + "Machine", + "DnsEntry", + "Ipv4Cidr", + "Ipv4Address", + }; + + assertEquals(0, c.call()); + } +} diff --git a/module/ri-wireguard/src/main/resources/templates/wireguard/ansible-host.j2 b/module/ri-wireguard/src/main/resources/templates/wireguard/ansible-host.j2 new file mode 100644 index 0000000..a3c8c40 --- /dev/null +++ b/module/ri-wireguard/src/main/resources/templates/wireguard/ansible-host.j2 @@ -0,0 +1,8 @@ +# Generated +link_address: {{ host.ip }} +network_cidr: {{ host.networkCidr }} +wireguard_peers: + {{ host.machine.name }}: +{%- for peer in peers %} + - {{ peer.fqdn }} +{%- endfor %} diff --git a/module/ri-wireguard/src/main/resources/templates/wireguard/ansible.j2 b/module/ri-wireguard/src/main/resources/templates/wireguard/ansible.j2 new file mode 100644 index 0000000..82c0ca0 --- /dev/null +++ b/module/ri-wireguard/src/main/resources/templates/wireguard/ansible.j2 @@ -0,0 +1,6 @@ +- hosts: {{ net.name }} + roles: + - name: wireguard + wireguard_if: {{ net.name }} + wireguard_listen_port: 45364 + wireguard_address4: "{{ '{{' }} link_addresses[ansible_hostname] }}" diff --git a/module/ri-wireguard/src/main/resources/templates/wireguard/inventory.j2 b/module/ri-wireguard/src/main/resources/templates/wireguard/inventory.j2 new file mode 100644 index 0000000..0924bb2 --- /dev/null +++ b/module/ri-wireguard/src/main/resources/templates/wireguard/inventory.j2 @@ -0,0 +1,7 @@ +# Generated +all: + hosts: + {%- for host in hosts %} + {{ host.getName() }}: + ansible_host: {{ host.getFqdn() }} + {%- endfor %} |