From 6b6a84977ce8b1c0d439cc66e2d045eb69ea0897 Mon Sep 17 00:00:00 2001 From: JFronny Date: Thu, 20 Oct 2022 21:57:43 +0200 Subject: [PATCH] Initial commit --- .gitignore | 33 +++++++++ README.md | 2 + build.gradle.kts | 32 +++++++++ settings.gradle.kts | 1 + .../jfronny/woodpecker/include/Main.java | 46 ++++++++++++ .../woodpecker/include/PipelineUnpacker.java | 70 +++++++++++++++++++ .../woodpecker/include/model/Build.java | 36 ++++++++++ .../woodpecker/include/model/Pipeline.java | 8 +++ .../woodpecker/include/model/Repo.java | 34 +++++++++ .../include/model/RequestModel.java | 9 +++ .../include/model/ResponseModel.java | 14 ++++ .../include/util/FilteringYamlMapping.java | 32 +++++++++ .../include/util/YamlTypeAdapter.java | 21 ++++++ 13 files changed, 338 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 build.gradle.kts create mode 100644 settings.gradle.kts create mode 100644 src/main/java/io/gitlab/jfronny/woodpecker/include/Main.java create mode 100644 src/main/java/io/gitlab/jfronny/woodpecker/include/PipelineUnpacker.java create mode 100644 src/main/java/io/gitlab/jfronny/woodpecker/include/model/Build.java create mode 100644 src/main/java/io/gitlab/jfronny/woodpecker/include/model/Pipeline.java create mode 100644 src/main/java/io/gitlab/jfronny/woodpecker/include/model/Repo.java create mode 100644 src/main/java/io/gitlab/jfronny/woodpecker/include/model/RequestModel.java create mode 100644 src/main/java/io/gitlab/jfronny/woodpecker/include/model/ResponseModel.java create mode 100644 src/main/java/io/gitlab/jfronny/woodpecker/include/util/FilteringYamlMapping.java create mode 100644 src/main/java/io/gitlab/jfronny/woodpecker/include/util/YamlTypeAdapter.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fc7603 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2e558c3 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Woodpecker-Include +A microservice to be called from woodpecker to enable including pipeline configurations diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..1fd9dad --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,32 @@ +plugins { + java + application + id("com.github.johnrengelman.shadow") version "7.1.2" +} + +group = "io.gitlab.jfronny" +version = "1.0-SNAPSHOT" + +application { + mainClass.set("io.gitlab.jfronny.woodpecker.include.Main") +} + +repositories { + mavenCentral() + maven("https://gitlab.com/api/v4/projects/35745143/packages/maven") +} + +dependencies { + implementation("net.freeutils:jlhttp:2.6") + implementation("com.amihaiemil.web:eo-yaml:6.1.0") + implementation("io.gitlab.jfronny:commons:2022.9.26+17-56-16") + implementation("io.gitlab.jfronny:commons-gson:2022.9.26+17-56-16") +} + +tasks.runShadow { + args("8002") +} + +tasks.compileJava { + options.compilerArgs.add("--enable-preview") +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..45ad95d --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "woodpecker-include" diff --git a/src/main/java/io/gitlab/jfronny/woodpecker/include/Main.java b/src/main/java/io/gitlab/jfronny/woodpecker/include/Main.java new file mode 100644 index 0000000..9ffe528 --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/woodpecker/include/Main.java @@ -0,0 +1,46 @@ +package io.gitlab.jfronny.woodpecker.include; + +import com.amihaiemil.eoyaml.*; +import io.gitlab.jfronny.commons.HttpUtils; +import io.gitlab.jfronny.commons.serialize.Serializer; +import io.gitlab.jfronny.commons.serialize.gson.api.v1.GsonHolders; +import io.gitlab.jfronny.woodpecker.include.model.*; +import io.gitlab.jfronny.woodpecker.include.util.YamlTypeAdapter; +import net.freeutils.httpserver.HTTPServer; + +import java.io.*; +import java.util.concurrent.atomic.AtomicBoolean; + +public class Main { + public static void main(String[] args) throws IOException { + if (args.length != 1) { + System.err.println("Usage: woodpecker-include "); + return; + } + HTTPServer server = new HTTPServer(Integer.parseInt(args[0])); + HTTPServer.VirtualHost host = server.getVirtualHost(null); + GsonHolders.registerSerializer(); + GsonHolders.registerTypeAdapter(YamlMapping.class, new YamlTypeAdapter()); + HttpUtils.setUserAgent("Woodpecker-Include/1.0"); + host.addContext("/ciconfig", (req, resp) -> { + try { + RequestModel request; + try (var isr = new InputStreamReader(req.getBody())) { + request = Serializer.getInstance().deserialize(isr, RequestModel.class); + } + AtomicBoolean changed = new AtomicBoolean(false); + ResponseModel response = new ResponseModel(request.configs.stream().mapMulti(new PipelineUnpacker(changed)).toList()); + if (changed.get()) { + try (OutputStreamWriter writer = new OutputStreamWriter(resp.getBody())) { + Serializer.getInstance().serialize(response, writer); + } + return 0; + } else return 204; + } catch (UncheckedIOException e) { + throw e.getCause(); + } + }, "POST"); + server.start(); + System.out.println("Running Woodpecker-Include"); + } +} diff --git a/src/main/java/io/gitlab/jfronny/woodpecker/include/PipelineUnpacker.java b/src/main/java/io/gitlab/jfronny/woodpecker/include/PipelineUnpacker.java new file mode 100644 index 0000000..75c100b --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/woodpecker/include/PipelineUnpacker.java @@ -0,0 +1,70 @@ +package io.gitlab.jfronny.woodpecker.include; + +import com.amihaiemil.eoyaml.*; +import io.gitlab.jfronny.commons.HttpUtils; +import io.gitlab.jfronny.woodpecker.include.model.Pipeline; +import io.gitlab.jfronny.woodpecker.include.util.FilteringYamlMapping; + +import java.io.*; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Paths; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +record PipelineUnpacker(AtomicBoolean changed) implements BiConsumer> { + @Override + public void accept(Pipeline pipeline, Consumer pipelineConsumer) { + try { + processPipeline(pipeline, pipelineConsumer); + } catch (URISyntaxException e) { + throw new UncheckedIOException(new IOException("Could not find URL", e)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private void processPipeline(Pipeline pipeline, Consumer pipelineConsumer) throws URISyntaxException, IOException { + YamlNode include = pipeline.data.value("include"); + if (include == null) { + // Has no includes + pipelineConsumer.accept(pipeline); + return; + } + changed.set(true); + // Send included + switch (include) { + case Scalar str -> download(str.value(), null, pipelineConsumer); + case YamlMapping map -> { + for (YamlNode key : map.keys()) { + download(map.string(key), key.asScalar().value(), pipelineConsumer); + } + } + case YamlSequence seq -> { + for (YamlNode node : seq) { + download(node.asScalar().value(), null, pipelineConsumer); + } + } + default -> throw new IllegalStateException("Unexpected value: " + include); + } + // Remove include from original + Pipeline filtered = new Pipeline(); + filtered.name = pipeline.name; + pipeline.data = new FilteringYamlMapping(pipeline.data, s -> !s.asScalar().value().equals("include")); + pipelineConsumer.accept(filtered); + } + + private void download(String url, String fileName, Consumer pipelineConsumer) throws URISyntaxException, IOException { + URI uri = new URI(url.replace(" ", "%20")); + if (!"http".equalsIgnoreCase(uri.getScheme()) && !"https".equalsIgnoreCase(uri.getScheme())) { + throw new URISyntaxException(url, "Could not find scheme"); + } + try (InputStream is = HttpUtils.get(url).sendInputStream()) { + Pipeline pipeline = new Pipeline(); + pipeline.name = fileName != null ? fileName : Paths.get(new URI(url.replace(" ", "%20")).getPath()).getFileName().toString(); + pipeline.data = Yaml.createYamlInput(is).readYamlMapping(); + processPipeline(pipeline, pipelineConsumer); + } + } +} diff --git a/src/main/java/io/gitlab/jfronny/woodpecker/include/model/Build.java b/src/main/java/io/gitlab/jfronny/woodpecker/include/model/Build.java new file mode 100644 index 0000000..1dcb7b4 --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/woodpecker/include/model/Build.java @@ -0,0 +1,36 @@ +package io.gitlab.jfronny.woodpecker.include.model; + +import java.util.List; + +public class Build { + public String author; + public String author_avatar; + public String author_email; + public String branch; + public List changed_files; + public String commit; + public Long created_at; + public String deploy_to; + public Long enqueued_at; + public String error; + public String event; + public Long finished_at; + public Long id; + public String link_url; + public String message; + public Long number; + public Long parent; + public String ref; + public String refspec; + public String remote; + public Long reviewed_at; + public String reviewed_by; + public String sender; + public Boolean signed; + public Long started_at; + public String status; + public Long timestamp; + public String title; + public Long updated_at; + public Boolean verified; +} diff --git a/src/main/java/io/gitlab/jfronny/woodpecker/include/model/Pipeline.java b/src/main/java/io/gitlab/jfronny/woodpecker/include/model/Pipeline.java new file mode 100644 index 0000000..bd13663 --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/woodpecker/include/model/Pipeline.java @@ -0,0 +1,8 @@ +package io.gitlab.jfronny.woodpecker.include.model; + +import com.amihaiemil.eoyaml.YamlMapping; + +public class Pipeline { + public String name; + public YamlMapping data; +} diff --git a/src/main/java/io/gitlab/jfronny/woodpecker/include/model/Repo.java b/src/main/java/io/gitlab/jfronny/woodpecker/include/model/Repo.java new file mode 100644 index 0000000..d53b69c --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/woodpecker/include/model/Repo.java @@ -0,0 +1,34 @@ +package io.gitlab.jfronny.woodpecker.include.model; + +import io.gitlab.jfronny.gson.annotations.SerializedName; + +public class Repo { + public Long id; + public String uid; + public Long user_id; + public String namespace; + public String name; + public String slug; + public String scm; + public String git_http_url; + public String git_ssh_url; + public String link; + public String default_branch; + @SerializedName("private") + public Boolean private$; + public String visibility; + public Boolean active; + public String config; + public Boolean trusted; + @SerializedName("protected") + public Boolean protected$; + public Boolean ignore_forks; + public Boolean ignore_pulls; + public Boolean cancel_pulls; + public Long timeout; + public Long counter; + public Long synced; + public Long created; + public Long updated; + public Long version; +} diff --git a/src/main/java/io/gitlab/jfronny/woodpecker/include/model/RequestModel.java b/src/main/java/io/gitlab/jfronny/woodpecker/include/model/RequestModel.java new file mode 100644 index 0000000..6b8d87f --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/woodpecker/include/model/RequestModel.java @@ -0,0 +1,9 @@ +package io.gitlab.jfronny.woodpecker.include.model; + +import java.util.List; + +public class RequestModel { + public Repo repo; + public Build build; + public List configs; +} diff --git a/src/main/java/io/gitlab/jfronny/woodpecker/include/model/ResponseModel.java b/src/main/java/io/gitlab/jfronny/woodpecker/include/model/ResponseModel.java new file mode 100644 index 0000000..e0a6f5b --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/woodpecker/include/model/ResponseModel.java @@ -0,0 +1,14 @@ +package io.gitlab.jfronny.woodpecker.include.model; + +import java.util.List; + +public class ResponseModel { + public List configs; + + public ResponseModel() { + } + + public ResponseModel(List configs) { + this.configs = List.copyOf(configs); + } +} diff --git a/src/main/java/io/gitlab/jfronny/woodpecker/include/util/FilteringYamlMapping.java b/src/main/java/io/gitlab/jfronny/woodpecker/include/util/FilteringYamlMapping.java new file mode 100644 index 0000000..768bec7 --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/woodpecker/include/util/FilteringYamlMapping.java @@ -0,0 +1,32 @@ +package io.gitlab.jfronny.woodpecker.include.util; + +import com.amihaiemil.eoyaml.*; + +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class FilteringYamlMapping extends BaseYamlMapping { + private final YamlMapping base; + private final Predicate filter; + + public FilteringYamlMapping(YamlMapping base, Predicate filter) { + this.base = base; + this.filter = filter; + } + + @Override + public Set keys() { + return base.keys().stream().filter(filter).collect(Collectors.toSet()); + } + + @Override + public YamlNode value(YamlNode key) { + return filter.test(key) ? base.value(key) : null; + } + + @Override + public Comment comment() { + return base.comment(); + } +} diff --git a/src/main/java/io/gitlab/jfronny/woodpecker/include/util/YamlTypeAdapter.java b/src/main/java/io/gitlab/jfronny/woodpecker/include/util/YamlTypeAdapter.java new file mode 100644 index 0000000..5fdb711 --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/woodpecker/include/util/YamlTypeAdapter.java @@ -0,0 +1,21 @@ +package io.gitlab.jfronny.woodpecker.include.util; + +import com.amihaiemil.eoyaml.Yaml; +import com.amihaiemil.eoyaml.YamlMapping; +import io.gitlab.jfronny.gson.TypeAdapter; +import io.gitlab.jfronny.gson.stream.JsonReader; +import io.gitlab.jfronny.gson.stream.JsonWriter; + +import java.io.IOException; + +public class YamlTypeAdapter extends TypeAdapter { + @Override + public void write(JsonWriter jsonWriter, YamlMapping yamlMapping) throws IOException { + jsonWriter.value(yamlMapping.toString()); + } + + @Override + public YamlMapping read(JsonReader jsonReader) throws IOException { + return Yaml.createYamlInput(jsonReader.nextString()).readYamlMapping(); + } +}