From 91fd894b39d000142c5ef5589cb406455933fbf6 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 18 May 2022 01:26:19 +0200 Subject: build: Use correct group for Gradle modules --- build.gradle.kts | 1 - opendc-common/build.gradle.kts | 1 + .../src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index ea1a256a..87a338b9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -27,6 +27,5 @@ plugins { } allprojects { - group = "org.opendc" version = "2.1-rc1" } diff --git a/opendc-common/build.gradle.kts b/opendc-common/build.gradle.kts index 7da937c4..f1c22f61 100644 --- a/opendc-common/build.gradle.kts +++ b/opendc-common/build.gradle.kts @@ -20,6 +20,7 @@ * SOFTWARE. */ +group = "org.opendc" description = "Common functionality used across OpenDC modules" /* Build configuration */ diff --git a/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java b/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java index 983bb852..54782ace 100644 --- a/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java +++ b/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java @@ -58,7 +58,7 @@ import java.util.regex.Pattern; public class OpenDCUiProcessor { private static final String FEATURE = "opendc-ui"; - private static final GACT OPENDC_UI_WEBJAR_ARTIFACT_KEY = new GACT("org.opendc", "opendc-web-ui", null, "jar"); + private static final GACT OPENDC_UI_WEBJAR_ARTIFACT_KEY = new GACT("org.opendc.web", "opendc-web-ui", null, "jar"); private static final String OPENDC_UI_WEBJAR_STATIC_RESOURCES_PATH = "META-INF/resources/opendc-web-ui"; private static final Pattern PATH_PARAM_PATTERN = Pattern.compile("\\[(\\w+)]"); -- cgit v1.2.3 From 3c4afb14452f2ae28d66b349f080b5dea8b1366b Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Tue, 17 May 2022 23:35:11 +0200 Subject: feat(web/client): Support client construction without AuthController This change updates the `OpenDCClient` and `OpenDCRunnerClient` to support connecting to an API that is not protected by authorization. This is useful in a local development context where authorization is explicitly disabled. --- .../kotlin/org/opendc/web/client/OpenDCClient.kt | 2 +- .../opendc/web/client/runner/OpenDCRunnerClient.kt | 2 +- .../web/client/transport/HttpTransportClient.kt | 46 ++++++++++++++++------ 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/OpenDCClient.kt b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/OpenDCClient.kt index 33f2b41e..a34c7864 100644 --- a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/OpenDCClient.kt +++ b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/OpenDCClient.kt @@ -39,7 +39,7 @@ public class OpenDCClient(client: TransportClient) { * @param baseUrl The base url of the API. * @param auth Helper class for managing authentication. */ - public constructor(baseUrl: URI, auth: AuthController) : this(HttpTransportClient(baseUrl, auth)) + public constructor(baseUrl: URI, auth: AuthController? = null) : this(HttpTransportClient(baseUrl, auth)) /** * A resource for the available projects. diff --git a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/runner/OpenDCRunnerClient.kt b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/runner/OpenDCRunnerClient.kt index a3cff6c3..e2112b8c 100644 --- a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/runner/OpenDCRunnerClient.kt +++ b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/runner/OpenDCRunnerClient.kt @@ -40,7 +40,7 @@ public class OpenDCRunnerClient(client: TransportClient) { * @param baseUrl The base url of the API. * @param auth Helper class for managing authentication. */ - public constructor(baseUrl: URI, auth: AuthController) : this(HttpTransportClient(baseUrl, auth)) + public constructor(baseUrl: URI, auth: AuthController? = null) : this(HttpTransportClient(baseUrl, auth)) /** * A resource for the available simulation jobs. diff --git a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/transport/HttpTransportClient.kt b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/transport/HttpTransportClient.kt index 03b3945f..e407380b 100644 --- a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/transport/HttpTransportClient.kt +++ b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/transport/HttpTransportClient.kt @@ -42,7 +42,7 @@ import java.nio.file.Paths */ public class HttpTransportClient( private val baseUrl: URI, - private val auth: AuthController, + private val auth: AuthController?, private val client: HttpClient = HttpClient.newHttpClient() ) : TransportClient { /** @@ -58,15 +58,20 @@ public class HttpTransportClient( override fun get(path: String, targetType: TypeReference): T? { val request = HttpRequest.newBuilder(buildUri(path)) .GET() - .also { auth.injectToken(it) } + .also { auth?.injectToken(it) } .build() val response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()) return when (val code = response.statusCode()) { in 200..299 -> mapper.readValue(response.body(), targetType) 401 -> { - auth.refreshToken() - get(path, targetType) + val auth = auth + if (auth != null) { + auth.refreshToken() + get(path, targetType) + } else { + throw IllegalStateException("Authorization required") + } } 404 -> null else -> throw IllegalStateException("Invalid response $code") @@ -80,15 +85,20 @@ public class HttpTransportClient( val request = HttpRequest.newBuilder(buildUri(path)) .POST(HttpRequest.BodyPublishers.ofByteArray(mapper.writeValueAsBytes(body))) .header("Content-Type", "application/json") - .also { auth.injectToken(it) } + .also { auth?.injectToken(it) } .build() val response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()) return when (val code = response.statusCode()) { in 200..299 -> mapper.readValue(response.body(), targetType) 401 -> { - auth.refreshToken() - post(path, body, targetType) + val auth = auth + if (auth != null) { + auth.refreshToken() + post(path, body, targetType) + } else { + throw IllegalStateException("Authorization required") + } } 404 -> null else -> throw IllegalStateException("Invalid response $code") @@ -102,15 +112,20 @@ public class HttpTransportClient( val request = HttpRequest.newBuilder(buildUri(path)) .PUT(HttpRequest.BodyPublishers.ofByteArray(mapper.writeValueAsBytes(body))) .header("Content-Type", "application/json") - .also { auth.injectToken(it) } + .also { auth?.injectToken(it) } .build() val response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()) return when (val code = response.statusCode()) { in 200..299 -> mapper.readValue(response.body(), targetType) 401 -> { - auth.refreshToken() - put(path, body, targetType) + val auth = auth + if (auth != null) { + auth.refreshToken() + put(path, body, targetType) + } else { + throw IllegalStateException("Authorization required") + } } 404 -> null else -> throw IllegalStateException("Invalid response $code") @@ -123,15 +138,20 @@ public class HttpTransportClient( override fun delete(path: String, targetType: TypeReference): T? { val request = HttpRequest.newBuilder(buildUri(path)) .DELETE() - .also { auth.injectToken(it) } + .also { auth?.injectToken(it) } .build() val response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()) return when (val code = response.statusCode()) { in 200..299 -> mapper.readValue(response.body(), targetType) 401 -> { - auth.refreshToken() - delete(path, targetType) + val auth = auth + if (auth != null) { + auth.refreshToken() + delete(path, targetType) + } else { + throw IllegalStateException("Authorization required") + } } 404 -> null else -> throw IllegalStateException("Invalid response $code") -- cgit v1.2.3 From 689c873ff68b6c8fd66740603dad6efa3bf8ab47 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 18 May 2022 00:36:15 +0200 Subject: refactor(web/runner): Move runner CLI into separate configuration This change splits the command line interface from the OpenDC web runner into a separate configuration. We plan to re-use the runner code for a Quarkus extension that integrates the runner in development mode. --- opendc-trace/opendc-trace-parquet/build.gradle.kts | 1 + opendc-web/opendc-web-runner/Dockerfile | 2 +- opendc-web/opendc-web-runner/build.gradle.kts | 64 +++++++++-- .../src/cli/kotlin/org/opendc/web/runner/Main.kt | 127 +++++++++++++++++++++ .../src/main/kotlin/org/opendc/web/runner/Main.kt | 127 --------------------- settings.gradle.kts | 2 +- 6 files changed, 182 insertions(+), 141 deletions(-) create mode 100644 opendc-web/opendc-web-runner/src/cli/kotlin/org/opendc/web/runner/Main.kt delete mode 100644 opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/Main.kt diff --git a/opendc-trace/opendc-trace-parquet/build.gradle.kts b/opendc-trace/opendc-trace-parquet/build.gradle.kts index 794dd655..9b1e1273 100644 --- a/opendc-trace/opendc-trace-parquet/build.gradle.kts +++ b/opendc-trace/opendc-trace-parquet/build.gradle.kts @@ -43,6 +43,7 @@ dependencies { exclude(group = "org.apache.htrace") exclude(group = "commons-cli") exclude(group = "javax.servlet") + exclude(group = "javax.servlet.jsp") exclude(group = "org.eclipse.jetty") exclude(group = "com.sun.jersey") exclude(group = "com.jcraft") diff --git a/opendc-web/opendc-web-runner/Dockerfile b/opendc-web/opendc-web-runner/Dockerfile index b72f8a7f..bb606f4e 100644 --- a/opendc-web/opendc-web-runner/Dockerfile +++ b/opendc-web/opendc-web-runner/Dockerfile @@ -1,4 +1,4 @@ -FROM openjdk:17-slim +FROM openjdk:18-slim MAINTAINER OpenDC Maintainers # Obtain (cache) Gradle wrapper diff --git a/opendc-web/opendc-web-runner/build.gradle.kts b/opendc-web/opendc-web-runner/build.gradle.kts index c1e3b976..a5723994 100644 --- a/opendc-web/opendc-web-runner/build.gradle.kts +++ b/opendc-web/opendc-web-runner/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 AtLarge Research + * Copyright (c) 2022 AtLarge Research * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,27 +24,67 @@ description = "Experiment runner for OpenDC" /* Build configuration */ plugins { - `kotlin-conventions` - `testing-conventions` - application + `kotlin-library-conventions` + distribution } -application { - mainClass.set("org.opendc.web.runner.MainKt") +val cli: SourceSet by sourceSets.creating { + compileClasspath += sourceSets["main"].output + runtimeClasspath += sourceSets["main"].output +} + +val cliImplementation: Configuration by configurations.getting { + extendsFrom(configurations["implementation"]) +} +val cliRuntimeOnly: Configuration by configurations.getting +val cliRuntimeClasspath: Configuration by configurations.getting { + extendsFrom(configurations["runtimeClasspath"]) +} + +val cliJar by tasks.creating(Jar::class) { + from(cli.output) + + archiveBaseName.set("${project.name}-cli") } dependencies { + api(projects.opendcWeb.opendcWebClient) implementation(projects.opendcCompute.opendcComputeSimulator) implementation(projects.opendcCompute.opendcComputeWorkload) implementation(projects.opendcSimulator.opendcSimulatorCore) implementation(projects.opendcTrace.opendcTraceApi) - implementation(projects.opendcWeb.opendcWebClient) implementation(libs.kotlin.logging) - implementation(libs.clikt) - implementation(libs.sentry.log4j2) - implementation(kotlin("reflect")) - runtimeOnly(projects.opendcTrace.opendcTraceOpendc) - runtimeOnly(libs.log4j.slf4j) + runtimeOnly(projects.opendcTrace.opendcTraceBitbrains) + + cliImplementation(libs.clikt) + cliImplementation(libs.sentry.log4j2) + + cliRuntimeOnly(projects.opendcTrace.opendcTraceOpendc) + cliRuntimeOnly(libs.log4j.slf4j) +} + +val createCli by tasks.creating(CreateStartScripts::class) { + dependsOn(cliJar) + + applicationName = "opendc-runner" + mainClass.set("org.opendc.web.runner.cli.MainKt") + classpath = cliJar.outputs.files + cliRuntimeClasspath + outputDir = project.buildDir.resolve("scripts") +} + +distributions { + main { + contents { + into("bin") { + from(createCli) + } + + into("lib") { + from(cliJar) + from(cliRuntimeClasspath) // Also includes main classpath + } + } + } } diff --git a/opendc-web/opendc-web-runner/src/cli/kotlin/org/opendc/web/runner/Main.kt b/opendc-web/opendc-web-runner/src/cli/kotlin/org/opendc/web/runner/Main.kt new file mode 100644 index 00000000..348a838c --- /dev/null +++ b/opendc-web/opendc-web-runner/src/cli/kotlin/org/opendc/web/runner/Main.kt @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.runner + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.* +import com.github.ajalt.clikt.parameters.types.file +import com.github.ajalt.clikt.parameters.types.int +import mu.KotlinLogging +import org.opendc.web.client.auth.OpenIdAuthController +import org.opendc.web.client.runner.OpenDCRunnerClient +import java.io.File +import java.net.URI + +private val logger = KotlinLogging.logger {} + +/** + * Represents the CLI command for starting the OpenDC web runner. + */ +class RunnerCli : CliktCommand(name = "opendc-runner") { + /** + * The URL to the OpenDC API. + */ + private val apiUrl by option( + "--api-url", + help = "url to the OpenDC API", + envvar = "OPENDC_API_URL" + ) + .convert { URI(it) } + .default(URI("https://api.opendc.org/v2")) + + /** + * The auth domain to use. + */ + private val authDomain by option( + "--auth-domain", + help = "auth domain of the OpenDC API", + envvar = "AUTH0_DOMAIN" + ) + .required() + + /** + * The auth domain to use. + */ + private val authAudience by option( + "--auth-audience", + help = "auth audience of the OpenDC API", + envvar = "AUTH0_AUDIENCE" + ) + .required() + + /** + * The auth client ID to use. + */ + private val authClientId by option( + "--auth-id", + help = "auth client id of the OpenDC API", + envvar = "AUTH0_CLIENT_ID" + ) + .required() + + /** + * The auth client secret to use. + */ + private val authClientSecret by option( + "--auth-secret", + help = "auth client secret of the OpenDC API", + envvar = "AUTH0_CLIENT_SECRET" + ) + .required() + + /** + * The path to the traces directory. + */ + private val tracePath by option( + "--traces", + help = "path to the directory containing the traces", + envvar = "OPENDC_TRACES" + ) + .file(canBeFile = false) + .defaultLazy { File("traces/") } + + /** + * The number of threads used for simulations.. + */ + private val parallelism by option( + "--parallelism", + help = "maximum number of threads for simulations", + ) + .int() + .default(Runtime.getRuntime().availableProcessors() - 1) + + override fun run() { + logger.info { "Starting OpenDC web runner" } + + val client = OpenDCRunnerClient(baseUrl = apiUrl, OpenIdAuthController(authDomain, authClientId, authClientSecret, authAudience)) + val runner = OpenDCRunner(client, tracePath, parallelism = parallelism) + + logger.info { "Watching for queued scenarios" } + runner.run() + } +} + +/** + * Main entry point of the runner. + */ +fun main(args: Array): Unit = RunnerCli().main(args) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/Main.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/Main.kt deleted file mode 100644 index 7bf7e220..00000000 --- a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/Main.kt +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.runner - -import com.github.ajalt.clikt.core.CliktCommand -import com.github.ajalt.clikt.parameters.options.* -import com.github.ajalt.clikt.parameters.types.file -import com.github.ajalt.clikt.parameters.types.int -import mu.KotlinLogging -import org.opendc.web.client.auth.OpenIdAuthController -import org.opendc.web.client.runner.OpenDCRunnerClient -import java.io.File -import java.net.URI - -private val logger = KotlinLogging.logger {} - -/** - * Represents the CLI command for starting the OpenDC web runner. - */ -class RunnerCli : CliktCommand(name = "opendc-runner") { - /** - * The URL to the OpenDC API. - */ - private val apiUrl by option( - "--api-url", - help = "url to the OpenDC API", - envvar = "OPENDC_API_URL" - ) - .convert { URI(it) } - .default(URI("https://api.opendc.org/v2")) - - /** - * The auth domain to use. - */ - private val authDomain by option( - "--auth-domain", - help = "auth domain of the OpenDC API", - envvar = "AUTH0_DOMAIN" - ) - .required() - - /** - * The auth domain to use. - */ - private val authAudience by option( - "--auth-audience", - help = "auth audience of the OpenDC API", - envvar = "AUTH0_AUDIENCE" - ) - .required() - - /** - * The auth client ID to use. - */ - private val authClientId by option( - "--auth-id", - help = "auth client id of the OpenDC API", - envvar = "AUTH0_CLIENT_ID" - ) - .required() - - /** - * The auth client secret to use. - */ - private val authClientSecret by option( - "--auth-secret", - help = "auth client secret of the OpenDC API", - envvar = "AUTH0_CLIENT_SECRET" - ) - .required() - - /** - * The path to the traces directory. - */ - private val tracePath by option( - "--traces", - help = "path to the directory containing the traces", - envvar = "OPENDC_TRACES" - ) - .file(canBeFile = false) - .defaultLazy { File("traces/") } - - /** - * The number of threads used for simulations.. - */ - private val parallelism by option( - "--parallelism", - help = "maximum number of threads for simulations", - ) - .int() - .default(Runtime.getRuntime().availableProcessors() - 1) - - override fun run() { - logger.info { "Starting OpenDC web runner" } - - val client = OpenDCRunnerClient(baseUrl = apiUrl, OpenIdAuthController(authDomain, authClientId, authClientSecret, authAudience)) - val runner = OpenDCRunner(client, tracePath, parallelism = parallelism) - - logger.info { "Watching for queued scenarios" } - runner.run() - } -} - -/** - * Main entry point of the runner. - */ -fun main(args: Array): Unit = RunnerCli().main(args) diff --git a/settings.gradle.kts b/settings.gradle.kts index 805e8364..d3f3dc9d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -41,7 +41,7 @@ include(":opendc-web:opendc-web-client") include(":opendc-web:opendc-web-ui") include(":opendc-web:opendc-web-ui-quarkus:deployment") include(":opendc-web:opendc-web-ui-quarkus:runtime") -include(":opendc-web:opendc-web-runner") +include(":opendc-web:opendc-web-runner:opendc-web-runner") include(":opendc-simulator:opendc-simulator-core") include(":opendc-simulator:opendc-simulator-flow") include(":opendc-simulator:opendc-simulator-power") -- cgit v1.2.3 From 8ec4bd7584ad67b4aebd2a88a1e33902923a5375 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 18 May 2022 13:36:14 +0200 Subject: refactor(web/ui): Remove module nesting in Quarkus extension This change updates the OpenDC web UI Quarkus extension to live completely in the `opendc-web` directory, as opposed to adding another level of nesting. This also allows us to properly name the artifacts of the Quarkus extension modules. --- opendc-web/opendc-web-api/build.gradle.kts | 4 +- .../build.gradle.kts | 39 +++ .../web/ui/deployment/AuthConfiguration.java | 52 ++++ .../opendc/web/ui/deployment/OpenDCUiConfig.java | 63 +++++ .../web/ui/deployment/OpenDCUiProcessor.java | 314 +++++++++++++++++++++ .../ui/deployment/OpenDCUiRoutingBuildItem.java | 119 ++++++++ opendc-web/opendc-web-ui-quarkus/build.gradle.kts | 19 ++ .../deployment/build.gradle.kts | 39 --- .../web/ui/deployment/AuthConfiguration.java | 52 ---- .../opendc/web/ui/deployment/OpenDCUiConfig.java | 63 ----- .../web/ui/deployment/OpenDCUiProcessor.java | 314 --------------------- .../ui/deployment/OpenDCUiRoutingBuildItem.java | 119 -------- .../opendc-web-ui-quarkus/runtime/build.gradle.kts | 36 --- .../opendc/web/ui/runtime/OpenDCUiRecorder.java | 107 ------- .../web/ui/runtime/OpenDCUiRuntimeConfig.java | 39 --- .../main/resources/META-INF/quarkus-extension.yaml | 5 - .../opendc/web/ui/runtime/OpenDCUiRecorder.java | 107 +++++++ .../web/ui/runtime/OpenDCUiRuntimeConfig.java | 39 +++ .../main/resources/META-INF/quarkus-extension.yaml | 5 + settings.gradle.kts | 4 +- 20 files changed, 761 insertions(+), 778 deletions(-) create mode 100644 opendc-web/opendc-web-ui-quarkus-deployment/build.gradle.kts create mode 100644 opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/AuthConfiguration.java create mode 100644 opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiConfig.java create mode 100644 opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java create mode 100644 opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiRoutingBuildItem.java delete mode 100644 opendc-web/opendc-web-ui-quarkus/deployment/build.gradle.kts delete mode 100644 opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/AuthConfiguration.java delete mode 100644 opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiConfig.java delete mode 100644 opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java delete mode 100644 opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiRoutingBuildItem.java delete mode 100644 opendc-web/opendc-web-ui-quarkus/runtime/build.gradle.kts delete mode 100644 opendc-web/opendc-web-ui-quarkus/runtime/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRecorder.java delete mode 100644 opendc-web/opendc-web-ui-quarkus/runtime/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRuntimeConfig.java delete mode 100644 opendc-web/opendc-web-ui-quarkus/runtime/src/main/resources/META-INF/quarkus-extension.yaml create mode 100644 opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRecorder.java create mode 100644 opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRuntimeConfig.java create mode 100644 opendc-web/opendc-web-ui-quarkus/src/main/resources/META-INF/quarkus-extension.yaml diff --git a/opendc-web/opendc-web-api/build.gradle.kts b/opendc-web/opendc-web-api/build.gradle.kts index 5ef6009f..fe493f1d 100644 --- a/opendc-web/opendc-web-api/build.gradle.kts +++ b/opendc-web/opendc-web-api/build.gradle.kts @@ -31,8 +31,8 @@ dependencies { implementation(enforcedPlatform(libs.quarkus.bom)) implementation(projects.opendcWeb.opendcWebProto) - compileOnly(projects.opendcWeb.opendcWebUiQuarkus.deployment) /* Temporary fix for Quarkus/Gradle issues */ - implementation(projects.opendcWeb.opendcWebUiQuarkus.runtime) + compileOnly(projects.opendcWeb.opendcWebUiQuarkusDeployment) /* Temporary fix for Quarkus/Gradle issues */ + implementation(projects.opendcWeb.opendcWebUiQuarkus) implementation(libs.quarkus.kotlin) implementation(libs.quarkus.resteasy.core) diff --git a/opendc-web/opendc-web-ui-quarkus-deployment/build.gradle.kts b/opendc-web/opendc-web-ui-quarkus-deployment/build.gradle.kts new file mode 100644 index 00000000..5a42aaea --- /dev/null +++ b/opendc-web/opendc-web-ui-quarkus-deployment/build.gradle.kts @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Quarkus extension for serving OpenDC web interface" + +/* Build configuration */ +plugins { + `java-library-conventions` +} + +dependencies { + implementation(platform(libs.quarkus.bom)) + + implementation(projects.opendcWeb.opendcWebUi) + implementation(projects.opendcWeb.opendcWebUiQuarkus) + + implementation(libs.quarkus.core.deployment) + implementation(libs.quarkus.vertx.http.deployment) + implementation(libs.quarkus.arc.deployment) +} diff --git a/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/AuthConfiguration.java b/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/AuthConfiguration.java new file mode 100644 index 00000000..2e4d9198 --- /dev/null +++ b/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/AuthConfiguration.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.ui.deployment; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; + +import java.util.Optional; + +/** + * Auth configuration for the OpenDC UI extension. + */ +@ConfigGroup +public class AuthConfiguration { + /** + * The authentication domain. + */ + @ConfigItem + Optional domain; + + /** + * The client identifier used by the OpenDC web ui. + */ + @ConfigItem + Optional clientId; + + /** + * The audience of the OpenDC API. + */ + @ConfigItem + Optional audience; +} diff --git a/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiConfig.java b/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiConfig.java new file mode 100644 index 00000000..50c1fbe3 --- /dev/null +++ b/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiConfig.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.ui.deployment; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +import java.util.Optional; + +/** + * Build-time configuration for the OpenDC UI extension. + */ +@ConfigRoot(name = "opendc-ui") +public class OpenDCUiConfig { + /** + * A flag to include the OpenDC UI extension into the build. + */ + @ConfigItem(defaultValue = "true") + boolean include; + + /** + * The path where the OpenDC UI is available. + */ + @ConfigItem(defaultValue = "/") + String path; + + /** + * The base URL of the OpenDC API. + */ + @ConfigItem(defaultValue = "/api") + String apiBaseUrl; + + /** + * Configuration properties for web UI authentication. + */ + AuthConfiguration auth; + + /** + * Sentry DSN. + */ + @ConfigItem + Optional sentryDsn; +} diff --git a/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java b/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java new file mode 100644 index 00000000..54782ace --- /dev/null +++ b/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.ui.deployment; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.ShutdownContextBuildItem; +import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; +import io.quarkus.maven.dependency.GACT; +import io.quarkus.maven.dependency.ResolvedDependency; +import io.quarkus.paths.PathVisit; +import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; +import io.quarkus.vertx.http.deployment.RouteBuildItem; +import io.quarkus.vertx.http.deployment.webjar.WebJarBuildItem; +import io.quarkus.vertx.http.deployment.webjar.WebJarResourcesFilter; +import io.quarkus.vertx.http.deployment.webjar.WebJarResultsBuildItem; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; +import org.opendc.web.ui.runtime.OpenDCUiRecorder; +import org.opendc.web.ui.runtime.OpenDCUiRuntimeConfig; + +import java.io.*; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.function.BooleanSupplier; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Build processor for the OpenDC web UI Quarkus extension. + */ +public class OpenDCUiProcessor { + + private static final String FEATURE = "opendc-ui"; + private static final GACT OPENDC_UI_WEBJAR_ARTIFACT_KEY = new GACT("org.opendc.web", "opendc-web-ui", null, "jar"); + private static final String OPENDC_UI_WEBJAR_STATIC_RESOURCES_PATH = "META-INF/resources/opendc-web-ui"; + private static final Pattern PATH_PARAM_PATTERN = Pattern.compile("\\[(\\w+)]"); + + private final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * Provide the {@link FeatureBuildItem} for this Quarkus extension. + */ + @BuildStep(onlyIf = IsIncluded.class) + public FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } + + /** + * Build the WebJar that is used to serve the Next.js resources. + */ + @BuildStep(onlyIf = IsIncluded.class) + public WebJarBuildItem buildWebJar(OpenDCUiConfig config, + HttpRootPathBuildItem httpRootPathBuildItem) { + return WebJarBuildItem.builder() + .artifactKey(OPENDC_UI_WEBJAR_ARTIFACT_KEY) + .root(OPENDC_UI_WEBJAR_STATIC_RESOURCES_PATH) + .onlyCopyNonArtifactFiles(false) + .useDefaultQuarkusBranding(false) + .filter(new InsertVariablesResourcesFilter(config, httpRootPathBuildItem)) + .build(); + } + + + /** + * Build the Next.js routes based on the route manifest generated by it. + */ + @BuildStep(onlyIf = IsIncluded.class) + public OpenDCUiRoutingBuildItem buildRoutes(CurateOutcomeBuildItem curateOutcomeBuildItem) throws IOException { + ResolvedDependency dependency = getAppArtifact(curateOutcomeBuildItem, OPENDC_UI_WEBJAR_ARTIFACT_KEY); + PathVisit visit = dependency.getContentTree().apply(OPENDC_UI_WEBJAR_STATIC_RESOURCES_PATH + "/routes-manifest.json", v -> v); + + if (visit == null) { + throw new FileNotFoundException("Cannot find routes-manifest.json"); + } + + JsonNode routeManifest = objectMapper.readTree(visit.getUrl()); + + var pages = new ArrayList(); + for (Iterator it = routeManifest.get("staticRoutes").elements(); it.hasNext();) { + JsonNode route = it.next(); + + String page = route.get("page").asText(); + + // Static routes do not have any path parameters + pages.add(new OpenDCUiRoutingBuildItem.Page(page, page)); + } + + for (Iterator it = routeManifest.get("dynamicRoutes").elements(); it.hasNext();) { + JsonNode route = it.next(); + + String page = route.get("page").asText(); + String path = PATH_PARAM_PATTERN.matcher(page).replaceAll(r -> ":" + r.group(1)); + + pages.add(new OpenDCUiRoutingBuildItem.Page(path, page)); + } + + var redirects = new ArrayList(); + for (Iterator it = routeManifest.get("redirects").elements(); it.hasNext();) { + JsonNode redirect = it.next(); + if (redirect.has("internal")) { + continue; + } + + int statusCode = redirect.get("statusCode").asInt(); + String path = redirect.get("source").asText().replaceAll("/%%NEXT_BASE_PATH%%", ""); + String destination = redirect.get("destination").asText().replaceAll("/%%NEXT_BASE_PATH%%", ""); + + if (path.isEmpty()) { + path = "/"; + } + + redirects.add(new OpenDCUiRoutingBuildItem.Redirect(path, destination, statusCode)); + } + + var custom404 = routeManifest.get("pages404").asBoolean(); + return new OpenDCUiRoutingBuildItem(pages, redirects, custom404); + } + + /** + * Register the HTTP handles for the OpenDC web UI. + */ + @BuildStep(onlyIf = IsIncluded.class) + @Record(ExecutionTime.RUNTIME_INIT) + public void registerOpenDCUiHandler(OpenDCUiRecorder recorder, + BuildProducer routes, + HttpRootPathBuildItem httpRootPathBuildItem, + WebJarResultsBuildItem webJarResultsBuildItem, + OpenDCUiRoutingBuildItem openDCUiBuildItem, + OpenDCUiRuntimeConfig runtimeConfig, + OpenDCUiConfig buildConfig, + ShutdownContextBuildItem shutdownContext) { + + WebJarResultsBuildItem.WebJarResult result = webJarResultsBuildItem.byArtifactKey(OPENDC_UI_WEBJAR_ARTIFACT_KEY); + if (result == null) { + return; + } + + String basePath = httpRootPathBuildItem.resolvePath(buildConfig.path); + String finalDestination = result.getFinalDestination(); + + /* Construct dynamic routes */ + for (var redirect : openDCUiBuildItem.getRedirects()) { + String destination = basePath.equals("/") ? redirect.getDestination() : basePath + redirect.getDestination(); + + routes.produce(httpRootPathBuildItem.routeBuilder() + .route(basePath + redirect.getPath()) + .handler(recorder.redirectHandler(destination, redirect.getStatusCode(), runtimeConfig)) + .build()); + } + + for (var page : openDCUiBuildItem.getPages()) { + routes.produce(httpRootPathBuildItem.routeBuilder() + .route(basePath + page.getPath()) + .handler(recorder.pageHandler(finalDestination, page.getName(), runtimeConfig)) + .build()); + } + + /* Construct static routes */ + Handler staticHandler = recorder.staticHandler( + finalDestination, + basePath, + result.getWebRootConfigurations(), + runtimeConfig, + shutdownContext + ); + + routes.produce(httpRootPathBuildItem.routeBuilder() + .route(buildConfig.path) + .displayOnNotFoundPage("OpenDC UI") + .routeConfigKey("quarkus.opendc-ui.path") + .handler(staticHandler) + .build()); + + routes.produce(httpRootPathBuildItem.routeBuilder() + .route(buildConfig.path + "*") + .handler(staticHandler) + .build()); + } + + /** + * A {@link WebJarResourcesFilter} that instantiates the variables in the web jar resource. + */ + private static class InsertVariablesResourcesFilter implements WebJarResourcesFilter { + + private static final String HTML = ".html"; + private static final String CSS = ".css"; + private static final String JS = ".js"; + + private final OpenDCUiConfig config; + private final HttpRootPathBuildItem httpRootPathBuildItem; + + + public InsertVariablesResourcesFilter(OpenDCUiConfig config, HttpRootPathBuildItem httpRootPathBuildItem) { + this.config = config; + this.httpRootPathBuildItem = httpRootPathBuildItem; + } + + @Override + public FilterResult apply(String fileName, InputStream stream) throws IOException { + if (stream == null) { + return new FilterResult(null, false); + } + + // Allow replacement of variables in HTML, CSS, and JavaScript files + if (fileName.endsWith(HTML) || fileName.endsWith(CSS) || fileName.endsWith(JS)) { + byte[] oldContentBytes = stream.readAllBytes(); + String oldContents = new String(oldContentBytes); + String contents = substituteVariables(oldContents, this::substitute); + + boolean changed = contents.length() != oldContents.length() || !contents.equals(oldContents); + if (changed) { + return new FilterResult(new ByteArrayInputStream(contents.getBytes()), true); + } else { + return new FilterResult(new ByteArrayInputStream(oldContentBytes), false); + } + } + + return new FilterResult(stream, false); + } + + private String substitute(String var) { + switch (var) { + case "NEXT_BASE_PATH": + String basePath = httpRootPathBuildItem.resolvePath(config.path); + return basePath.equals("/") ? "" : basePath; // Base path must not end with trailing slash + case "NEXT_PUBLIC_API_BASE_URL": + return config.apiBaseUrl; + case "NEXT_PUBLIC_SENTRY_DSN": + return config.sentryDsn.orElse(""); + case "NEXT_PUBLIC_AUTH0_DOMAIN": + return config.auth.domain.orElse(""); + case "NEXT_PUBLIC_AUTH0_CLIENT_ID": + return config.auth.clientId.orElse(""); + case "NEXT_PUBLIC_AUTH0_AUDIENCE": + return config.auth.audience.orElse(""); + default: + return null; + } + } + + /** + * Pattern to match variables in the OpenDC web UI sources specified using the following format: "%%VAR_NAME%%". + *

+ * Be aware that to properly handle Next.js base path, we need to also match a possible forward slash in front + * of the variable. + */ + private static final Pattern PATTERN = Pattern.compile("/?%%(\\w+)%%"); + + /** + * Helper method to substitute variables in the OpenDC web UI. + */ + private static String substituteVariables(String contents, Function substitute) { + Matcher m = PATTERN.matcher(contents); + StringBuilder sb = new StringBuilder(); + + while (m.find()) { + String group = m.group(1); + String val = substitute.apply(group); + m.appendReplacement(sb, val != null ? val : group); + } + + m.appendTail(sb); + return sb.toString(); + } + } + + /** + * A {@link BooleanSupplier} to determine if the OpenDC web UI extension should be included. + */ + private static class IsIncluded implements BooleanSupplier { + OpenDCUiConfig config; + + @Override + public boolean getAsBoolean() { + return config.include; + } + } + + private static ResolvedDependency getAppArtifact(CurateOutcomeBuildItem curateOutcomeBuildItem, GACT artifactKey) { + for (ResolvedDependency dep : curateOutcomeBuildItem.getApplicationModel().getDependencies()) { + if (dep.getKey().equals(artifactKey)) { + return dep; + } + } + throw new RuntimeException("Could not find artifact " + artifactKey + " among the application dependencies"); + } +} diff --git a/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiRoutingBuildItem.java b/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiRoutingBuildItem.java new file mode 100644 index 00000000..7e0f9408 --- /dev/null +++ b/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiRoutingBuildItem.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.ui.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; + +import java.util.List; + +/** + * Build item containing the routes for the OpenDC web UI. + */ +public final class OpenDCUiRoutingBuildItem extends SimpleBuildItem { + + private final boolean custom404; + private final List pages; + private final List redirects; + + /** + * Construct a {@link OpenDCUiRoutingBuildItem} instance. + * + * @param routes The routes defined by Next.js. + * @param redirects The redirects that have been defined by Next.js. + * @param custom404 A flag to indicate that custom 404 pages are enabled. + */ + public OpenDCUiRoutingBuildItem(List routes, List redirects, boolean custom404) { + this.custom404 = custom404; + this.pages = routes; + this.redirects = redirects; + } + + public List getPages() { + return pages; + } + + public List getRedirects() { + return redirects; + } + + public boolean hasCustom404() { + return this.custom404; + } + + /** + * A redirect defined by the Next.js routes manifest. + */ + public static final class Redirect { + + private final String path; + private final String destination; + private final int statusCode; + + /** + * Construct a {@link Redirect} route. + * + * @param path The path that should result in a redirect. + * @param destination The destination of the redirect. + * @param statusCode The status code of the redirect. + */ + public Redirect(String path, String destination, int statusCode) { + this.statusCode = statusCode; + this.path = path; + this.destination = destination; + } + + public String getPath() { + return path; + } + + public String getDestination() { + return destination; + } + + public int getStatusCode() { + return statusCode; + } + } + + /** + * A page defined by the Next.js routes manifest. + */ + public static final class Page { + + private final String path; + private final String name; + + public Page(String path, String page) { + this.path = path; + this.name = page; + } + + public String getPath() { + return path; + } + + public String getName() { + return name; + } + } +} diff --git a/opendc-web/opendc-web-ui-quarkus/build.gradle.kts b/opendc-web/opendc-web-ui-quarkus/build.gradle.kts index cbec021c..7f2fad20 100644 --- a/opendc-web/opendc-web-ui-quarkus/build.gradle.kts +++ b/opendc-web/opendc-web-ui-quarkus/build.gradle.kts @@ -21,3 +21,22 @@ */ description = "Quarkus extension for serving OpenDC web interface" + +plugins { + `java-library-conventions` + id("io.quarkus.extension") +} + +quarkusExtension { + deploymentModule = "opendc-web-ui-quarkus-deployment" +} + +dependencies { + implementation(platform(libs.quarkus.bom)) + + implementation(libs.quarkus.core.runtime) + implementation(libs.quarkus.vertx.http.runtime) + implementation(libs.quarkus.arc.runtime) +} + +evaluationDependsOn(projects.opendcWeb.opendcWebUiQuarkusDeployment.dependencyProject.path) diff --git a/opendc-web/opendc-web-ui-quarkus/deployment/build.gradle.kts b/opendc-web/opendc-web-ui-quarkus/deployment/build.gradle.kts deleted file mode 100644 index 0f3ae8ce..00000000 --- a/opendc-web/opendc-web-ui-quarkus/deployment/build.gradle.kts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -description = "Quarkus extension for serving OpenDC web interface" - -/* Build configuration */ -plugins { - `java-library-conventions` -} - -dependencies { - implementation(enforcedPlatform(libs.quarkus.bom)) - - implementation(projects.opendcWeb.opendcWebUi) - implementation(projects.opendcWeb.opendcWebUiQuarkus.runtime) - - implementation(libs.quarkus.core.deployment) - implementation(libs.quarkus.vertx.http.deployment) - implementation(libs.quarkus.arc.deployment) -} diff --git a/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/AuthConfiguration.java b/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/AuthConfiguration.java deleted file mode 100644 index 2e4d9198..00000000 --- a/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/AuthConfiguration.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.ui.deployment; - -import io.quarkus.runtime.annotations.ConfigGroup; -import io.quarkus.runtime.annotations.ConfigItem; - -import java.util.Optional; - -/** - * Auth configuration for the OpenDC UI extension. - */ -@ConfigGroup -public class AuthConfiguration { - /** - * The authentication domain. - */ - @ConfigItem - Optional domain; - - /** - * The client identifier used by the OpenDC web ui. - */ - @ConfigItem - Optional clientId; - - /** - * The audience of the OpenDC API. - */ - @ConfigItem - Optional audience; -} diff --git a/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiConfig.java b/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiConfig.java deleted file mode 100644 index 50c1fbe3..00000000 --- a/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiConfig.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.ui.deployment; - -import io.quarkus.runtime.annotations.ConfigItem; -import io.quarkus.runtime.annotations.ConfigRoot; - -import java.util.Optional; - -/** - * Build-time configuration for the OpenDC UI extension. - */ -@ConfigRoot(name = "opendc-ui") -public class OpenDCUiConfig { - /** - * A flag to include the OpenDC UI extension into the build. - */ - @ConfigItem(defaultValue = "true") - boolean include; - - /** - * The path where the OpenDC UI is available. - */ - @ConfigItem(defaultValue = "/") - String path; - - /** - * The base URL of the OpenDC API. - */ - @ConfigItem(defaultValue = "/api") - String apiBaseUrl; - - /** - * Configuration properties for web UI authentication. - */ - AuthConfiguration auth; - - /** - * Sentry DSN. - */ - @ConfigItem - Optional sentryDsn; -} diff --git a/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java b/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java deleted file mode 100644 index 54782ace..00000000 --- a/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.ui.deployment; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.quarkus.deployment.annotations.BuildProducer; -import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.annotations.ExecutionTime; -import io.quarkus.deployment.annotations.Record; -import io.quarkus.deployment.builditem.FeatureBuildItem; -import io.quarkus.deployment.builditem.ShutdownContextBuildItem; -import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; -import io.quarkus.maven.dependency.GACT; -import io.quarkus.maven.dependency.ResolvedDependency; -import io.quarkus.paths.PathVisit; -import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; -import io.quarkus.vertx.http.deployment.RouteBuildItem; -import io.quarkus.vertx.http.deployment.webjar.WebJarBuildItem; -import io.quarkus.vertx.http.deployment.webjar.WebJarResourcesFilter; -import io.quarkus.vertx.http.deployment.webjar.WebJarResultsBuildItem; -import io.vertx.core.Handler; -import io.vertx.ext.web.RoutingContext; -import org.opendc.web.ui.runtime.OpenDCUiRecorder; -import org.opendc.web.ui.runtime.OpenDCUiRuntimeConfig; - -import java.io.*; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.function.BooleanSupplier; -import java.util.function.Function; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Build processor for the OpenDC web UI Quarkus extension. - */ -public class OpenDCUiProcessor { - - private static final String FEATURE = "opendc-ui"; - private static final GACT OPENDC_UI_WEBJAR_ARTIFACT_KEY = new GACT("org.opendc.web", "opendc-web-ui", null, "jar"); - private static final String OPENDC_UI_WEBJAR_STATIC_RESOURCES_PATH = "META-INF/resources/opendc-web-ui"; - private static final Pattern PATH_PARAM_PATTERN = Pattern.compile("\\[(\\w+)]"); - - private final ObjectMapper objectMapper = new ObjectMapper(); - - /** - * Provide the {@link FeatureBuildItem} for this Quarkus extension. - */ - @BuildStep(onlyIf = IsIncluded.class) - public FeatureBuildItem feature() { - return new FeatureBuildItem(FEATURE); - } - - /** - * Build the WebJar that is used to serve the Next.js resources. - */ - @BuildStep(onlyIf = IsIncluded.class) - public WebJarBuildItem buildWebJar(OpenDCUiConfig config, - HttpRootPathBuildItem httpRootPathBuildItem) { - return WebJarBuildItem.builder() - .artifactKey(OPENDC_UI_WEBJAR_ARTIFACT_KEY) - .root(OPENDC_UI_WEBJAR_STATIC_RESOURCES_PATH) - .onlyCopyNonArtifactFiles(false) - .useDefaultQuarkusBranding(false) - .filter(new InsertVariablesResourcesFilter(config, httpRootPathBuildItem)) - .build(); - } - - - /** - * Build the Next.js routes based on the route manifest generated by it. - */ - @BuildStep(onlyIf = IsIncluded.class) - public OpenDCUiRoutingBuildItem buildRoutes(CurateOutcomeBuildItem curateOutcomeBuildItem) throws IOException { - ResolvedDependency dependency = getAppArtifact(curateOutcomeBuildItem, OPENDC_UI_WEBJAR_ARTIFACT_KEY); - PathVisit visit = dependency.getContentTree().apply(OPENDC_UI_WEBJAR_STATIC_RESOURCES_PATH + "/routes-manifest.json", v -> v); - - if (visit == null) { - throw new FileNotFoundException("Cannot find routes-manifest.json"); - } - - JsonNode routeManifest = objectMapper.readTree(visit.getUrl()); - - var pages = new ArrayList(); - for (Iterator it = routeManifest.get("staticRoutes").elements(); it.hasNext();) { - JsonNode route = it.next(); - - String page = route.get("page").asText(); - - // Static routes do not have any path parameters - pages.add(new OpenDCUiRoutingBuildItem.Page(page, page)); - } - - for (Iterator it = routeManifest.get("dynamicRoutes").elements(); it.hasNext();) { - JsonNode route = it.next(); - - String page = route.get("page").asText(); - String path = PATH_PARAM_PATTERN.matcher(page).replaceAll(r -> ":" + r.group(1)); - - pages.add(new OpenDCUiRoutingBuildItem.Page(path, page)); - } - - var redirects = new ArrayList(); - for (Iterator it = routeManifest.get("redirects").elements(); it.hasNext();) { - JsonNode redirect = it.next(); - if (redirect.has("internal")) { - continue; - } - - int statusCode = redirect.get("statusCode").asInt(); - String path = redirect.get("source").asText().replaceAll("/%%NEXT_BASE_PATH%%", ""); - String destination = redirect.get("destination").asText().replaceAll("/%%NEXT_BASE_PATH%%", ""); - - if (path.isEmpty()) { - path = "/"; - } - - redirects.add(new OpenDCUiRoutingBuildItem.Redirect(path, destination, statusCode)); - } - - var custom404 = routeManifest.get("pages404").asBoolean(); - return new OpenDCUiRoutingBuildItem(pages, redirects, custom404); - } - - /** - * Register the HTTP handles for the OpenDC web UI. - */ - @BuildStep(onlyIf = IsIncluded.class) - @Record(ExecutionTime.RUNTIME_INIT) - public void registerOpenDCUiHandler(OpenDCUiRecorder recorder, - BuildProducer routes, - HttpRootPathBuildItem httpRootPathBuildItem, - WebJarResultsBuildItem webJarResultsBuildItem, - OpenDCUiRoutingBuildItem openDCUiBuildItem, - OpenDCUiRuntimeConfig runtimeConfig, - OpenDCUiConfig buildConfig, - ShutdownContextBuildItem shutdownContext) { - - WebJarResultsBuildItem.WebJarResult result = webJarResultsBuildItem.byArtifactKey(OPENDC_UI_WEBJAR_ARTIFACT_KEY); - if (result == null) { - return; - } - - String basePath = httpRootPathBuildItem.resolvePath(buildConfig.path); - String finalDestination = result.getFinalDestination(); - - /* Construct dynamic routes */ - for (var redirect : openDCUiBuildItem.getRedirects()) { - String destination = basePath.equals("/") ? redirect.getDestination() : basePath + redirect.getDestination(); - - routes.produce(httpRootPathBuildItem.routeBuilder() - .route(basePath + redirect.getPath()) - .handler(recorder.redirectHandler(destination, redirect.getStatusCode(), runtimeConfig)) - .build()); - } - - for (var page : openDCUiBuildItem.getPages()) { - routes.produce(httpRootPathBuildItem.routeBuilder() - .route(basePath + page.getPath()) - .handler(recorder.pageHandler(finalDestination, page.getName(), runtimeConfig)) - .build()); - } - - /* Construct static routes */ - Handler staticHandler = recorder.staticHandler( - finalDestination, - basePath, - result.getWebRootConfigurations(), - runtimeConfig, - shutdownContext - ); - - routes.produce(httpRootPathBuildItem.routeBuilder() - .route(buildConfig.path) - .displayOnNotFoundPage("OpenDC UI") - .routeConfigKey("quarkus.opendc-ui.path") - .handler(staticHandler) - .build()); - - routes.produce(httpRootPathBuildItem.routeBuilder() - .route(buildConfig.path + "*") - .handler(staticHandler) - .build()); - } - - /** - * A {@link WebJarResourcesFilter} that instantiates the variables in the web jar resource. - */ - private static class InsertVariablesResourcesFilter implements WebJarResourcesFilter { - - private static final String HTML = ".html"; - private static final String CSS = ".css"; - private static final String JS = ".js"; - - private final OpenDCUiConfig config; - private final HttpRootPathBuildItem httpRootPathBuildItem; - - - public InsertVariablesResourcesFilter(OpenDCUiConfig config, HttpRootPathBuildItem httpRootPathBuildItem) { - this.config = config; - this.httpRootPathBuildItem = httpRootPathBuildItem; - } - - @Override - public FilterResult apply(String fileName, InputStream stream) throws IOException { - if (stream == null) { - return new FilterResult(null, false); - } - - // Allow replacement of variables in HTML, CSS, and JavaScript files - if (fileName.endsWith(HTML) || fileName.endsWith(CSS) || fileName.endsWith(JS)) { - byte[] oldContentBytes = stream.readAllBytes(); - String oldContents = new String(oldContentBytes); - String contents = substituteVariables(oldContents, this::substitute); - - boolean changed = contents.length() != oldContents.length() || !contents.equals(oldContents); - if (changed) { - return new FilterResult(new ByteArrayInputStream(contents.getBytes()), true); - } else { - return new FilterResult(new ByteArrayInputStream(oldContentBytes), false); - } - } - - return new FilterResult(stream, false); - } - - private String substitute(String var) { - switch (var) { - case "NEXT_BASE_PATH": - String basePath = httpRootPathBuildItem.resolvePath(config.path); - return basePath.equals("/") ? "" : basePath; // Base path must not end with trailing slash - case "NEXT_PUBLIC_API_BASE_URL": - return config.apiBaseUrl; - case "NEXT_PUBLIC_SENTRY_DSN": - return config.sentryDsn.orElse(""); - case "NEXT_PUBLIC_AUTH0_DOMAIN": - return config.auth.domain.orElse(""); - case "NEXT_PUBLIC_AUTH0_CLIENT_ID": - return config.auth.clientId.orElse(""); - case "NEXT_PUBLIC_AUTH0_AUDIENCE": - return config.auth.audience.orElse(""); - default: - return null; - } - } - - /** - * Pattern to match variables in the OpenDC web UI sources specified using the following format: "%%VAR_NAME%%". - *

- * Be aware that to properly handle Next.js base path, we need to also match a possible forward slash in front - * of the variable. - */ - private static final Pattern PATTERN = Pattern.compile("/?%%(\\w+)%%"); - - /** - * Helper method to substitute variables in the OpenDC web UI. - */ - private static String substituteVariables(String contents, Function substitute) { - Matcher m = PATTERN.matcher(contents); - StringBuilder sb = new StringBuilder(); - - while (m.find()) { - String group = m.group(1); - String val = substitute.apply(group); - m.appendReplacement(sb, val != null ? val : group); - } - - m.appendTail(sb); - return sb.toString(); - } - } - - /** - * A {@link BooleanSupplier} to determine if the OpenDC web UI extension should be included. - */ - private static class IsIncluded implements BooleanSupplier { - OpenDCUiConfig config; - - @Override - public boolean getAsBoolean() { - return config.include; - } - } - - private static ResolvedDependency getAppArtifact(CurateOutcomeBuildItem curateOutcomeBuildItem, GACT artifactKey) { - for (ResolvedDependency dep : curateOutcomeBuildItem.getApplicationModel().getDependencies()) { - if (dep.getKey().equals(artifactKey)) { - return dep; - } - } - throw new RuntimeException("Could not find artifact " + artifactKey + " among the application dependencies"); - } -} diff --git a/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiRoutingBuildItem.java b/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiRoutingBuildItem.java deleted file mode 100644 index 7e0f9408..00000000 --- a/opendc-web/opendc-web-ui-quarkus/deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiRoutingBuildItem.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.ui.deployment; - -import io.quarkus.builder.item.SimpleBuildItem; - -import java.util.List; - -/** - * Build item containing the routes for the OpenDC web UI. - */ -public final class OpenDCUiRoutingBuildItem extends SimpleBuildItem { - - private final boolean custom404; - private final List pages; - private final List redirects; - - /** - * Construct a {@link OpenDCUiRoutingBuildItem} instance. - * - * @param routes The routes defined by Next.js. - * @param redirects The redirects that have been defined by Next.js. - * @param custom404 A flag to indicate that custom 404 pages are enabled. - */ - public OpenDCUiRoutingBuildItem(List routes, List redirects, boolean custom404) { - this.custom404 = custom404; - this.pages = routes; - this.redirects = redirects; - } - - public List getPages() { - return pages; - } - - public List getRedirects() { - return redirects; - } - - public boolean hasCustom404() { - return this.custom404; - } - - /** - * A redirect defined by the Next.js routes manifest. - */ - public static final class Redirect { - - private final String path; - private final String destination; - private final int statusCode; - - /** - * Construct a {@link Redirect} route. - * - * @param path The path that should result in a redirect. - * @param destination The destination of the redirect. - * @param statusCode The status code of the redirect. - */ - public Redirect(String path, String destination, int statusCode) { - this.statusCode = statusCode; - this.path = path; - this.destination = destination; - } - - public String getPath() { - return path; - } - - public String getDestination() { - return destination; - } - - public int getStatusCode() { - return statusCode; - } - } - - /** - * A page defined by the Next.js routes manifest. - */ - public static final class Page { - - private final String path; - private final String name; - - public Page(String path, String page) { - this.path = path; - this.name = page; - } - - public String getPath() { - return path; - } - - public String getName() { - return name; - } - } -} diff --git a/opendc-web/opendc-web-ui-quarkus/runtime/build.gradle.kts b/opendc-web/opendc-web-ui-quarkus/runtime/build.gradle.kts deleted file mode 100644 index f4131f0b..00000000 --- a/opendc-web/opendc-web-ui-quarkus/runtime/build.gradle.kts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -description = "Quarkus extension for serving OpenDC web interface" - -plugins { - `java-library-conventions` - id("io.quarkus.extension") -} - -dependencies { - implementation(enforcedPlatform(libs.quarkus.bom)) - - implementation(libs.quarkus.core.runtime) - implementation(libs.quarkus.vertx.http.runtime) - implementation(libs.quarkus.arc.runtime) -} diff --git a/opendc-web/opendc-web-ui-quarkus/runtime/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRecorder.java b/opendc-web/opendc-web-ui-quarkus/runtime/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRecorder.java deleted file mode 100644 index 026a9039..00000000 --- a/opendc-web/opendc-web-ui-quarkus/runtime/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRecorder.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.ui.runtime; - -import io.quarkus.runtime.ShutdownContext; -import io.quarkus.runtime.annotations.Recorder; -import io.quarkus.vertx.http.runtime.devmode.FileSystemStaticHandler; -import io.quarkus.vertx.http.runtime.webjar.WebJarNotFoundHandler; -import io.quarkus.vertx.http.runtime.webjar.WebJarStaticHandler; -import io.vertx.core.Handler; -import io.vertx.ext.web.RoutingContext; - -import java.util.List; -import java.util.stream.Collectors; - -/** - * Helper class for serving the OpenDC web interface. - */ -@Recorder -public class OpenDCUiRecorder { - /** - * Construct a {@link Handler} for serving a page of the OpenDC web interface. - */ - public Handler pageHandler( - String finalDestination, - String page, - OpenDCUiRuntimeConfig runtimeConfig - ) { - if (runtimeConfig.enable) { - String pageDirectory = finalDestination + "/pages"; - return (event) -> { - event.response() - .setStatusCode(200) - .sendFile(pageDirectory + page + ".html"); - }; - } - - return new WebJarNotFoundHandler(); - } - - /** - * Construct a {@link Handler} for handling redirects in the OpenDC web interface. - */ - public Handler redirectHandler( - String destination, - int statusCode, - OpenDCUiRuntimeConfig runtimeConfig - ) { - if (runtimeConfig.enable) { - return (event) -> { - String query = event.request().query(); - String fullDestination = query != null ? destination + "?" + query : destination; - - event.response() - .setStatusCode(statusCode) - .putHeader("Location", fullDestination) - .end(); - }; - } - - return new WebJarNotFoundHandler(); - } - - /** - * Construct a {@link Handler} for serving the static files of the OpenDC web interface. - */ - public Handler staticHandler( - String finalDestination, - String path, - List webRootConfigurations, - OpenDCUiRuntimeConfig runtimeConfig, - ShutdownContext shutdownContext - ) { - if (runtimeConfig.enable) { - var augmentedWebRootConfigurations = webRootConfigurations - .stream() - .map(c -> new FileSystemStaticHandler.StaticWebRootConfiguration(c.getFileSystem(), c.getWebRoot().isEmpty() ? "static" : c.getWebRoot() + "/static")) - .collect(Collectors.toList()); - - WebJarStaticHandler handler = new WebJarStaticHandler(finalDestination + "/static", path, augmentedWebRootConfigurations); - shutdownContext.addShutdownTask(new ShutdownContext.CloseRunnable(handler)); - return handler; - } - - return new WebJarNotFoundHandler(); - } -} diff --git a/opendc-web/opendc-web-ui-quarkus/runtime/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRuntimeConfig.java b/opendc-web/opendc-web-ui-quarkus/runtime/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRuntimeConfig.java deleted file mode 100644 index 8ae3b6a2..00000000 --- a/opendc-web/opendc-web-ui-quarkus/runtime/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRuntimeConfig.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.ui.runtime; - -import io.quarkus.runtime.annotations.ConfigItem; -import io.quarkus.runtime.annotations.ConfigPhase; -import io.quarkus.runtime.annotations.ConfigRoot; - -/** - * Configuration for the OpenDC web UI. - */ -@ConfigRoot(phase = ConfigPhase.RUN_TIME, name = "opendc-ui") -public class OpenDCUiRuntimeConfig { - /** - * Flag to indicate whether the web interface should be served by the OpenDC API server. - */ - @ConfigItem(defaultValue = "true") - public boolean enable; -} diff --git a/opendc-web/opendc-web-ui-quarkus/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/opendc-web/opendc-web-ui-quarkus/runtime/src/main/resources/META-INF/quarkus-extension.yaml deleted file mode 100644 index 581a1779..00000000 --- a/opendc-web/opendc-web-ui-quarkus/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -name: "OpenDC Web UI" -metadata: - status: "preview" - unlisted: true diff --git a/opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRecorder.java b/opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRecorder.java new file mode 100644 index 00000000..026a9039 --- /dev/null +++ b/opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRecorder.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.ui.runtime; + +import io.quarkus.runtime.ShutdownContext; +import io.quarkus.runtime.annotations.Recorder; +import io.quarkus.vertx.http.runtime.devmode.FileSystemStaticHandler; +import io.quarkus.vertx.http.runtime.webjar.WebJarNotFoundHandler; +import io.quarkus.vertx.http.runtime.webjar.WebJarStaticHandler; +import io.vertx.core.Handler; +import io.vertx.ext.web.RoutingContext; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Helper class for serving the OpenDC web interface. + */ +@Recorder +public class OpenDCUiRecorder { + /** + * Construct a {@link Handler} for serving a page of the OpenDC web interface. + */ + public Handler pageHandler( + String finalDestination, + String page, + OpenDCUiRuntimeConfig runtimeConfig + ) { + if (runtimeConfig.enable) { + String pageDirectory = finalDestination + "/pages"; + return (event) -> { + event.response() + .setStatusCode(200) + .sendFile(pageDirectory + page + ".html"); + }; + } + + return new WebJarNotFoundHandler(); + } + + /** + * Construct a {@link Handler} for handling redirects in the OpenDC web interface. + */ + public Handler redirectHandler( + String destination, + int statusCode, + OpenDCUiRuntimeConfig runtimeConfig + ) { + if (runtimeConfig.enable) { + return (event) -> { + String query = event.request().query(); + String fullDestination = query != null ? destination + "?" + query : destination; + + event.response() + .setStatusCode(statusCode) + .putHeader("Location", fullDestination) + .end(); + }; + } + + return new WebJarNotFoundHandler(); + } + + /** + * Construct a {@link Handler} for serving the static files of the OpenDC web interface. + */ + public Handler staticHandler( + String finalDestination, + String path, + List webRootConfigurations, + OpenDCUiRuntimeConfig runtimeConfig, + ShutdownContext shutdownContext + ) { + if (runtimeConfig.enable) { + var augmentedWebRootConfigurations = webRootConfigurations + .stream() + .map(c -> new FileSystemStaticHandler.StaticWebRootConfiguration(c.getFileSystem(), c.getWebRoot().isEmpty() ? "static" : c.getWebRoot() + "/static")) + .collect(Collectors.toList()); + + WebJarStaticHandler handler = new WebJarStaticHandler(finalDestination + "/static", path, augmentedWebRootConfigurations); + shutdownContext.addShutdownTask(new ShutdownContext.CloseRunnable(handler)); + return handler; + } + + return new WebJarNotFoundHandler(); + } +} diff --git a/opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRuntimeConfig.java b/opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRuntimeConfig.java new file mode 100644 index 00000000..8ae3b6a2 --- /dev/null +++ b/opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRuntimeConfig.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.ui.runtime; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * Configuration for the OpenDC web UI. + */ +@ConfigRoot(phase = ConfigPhase.RUN_TIME, name = "opendc-ui") +public class OpenDCUiRuntimeConfig { + /** + * Flag to indicate whether the web interface should be served by the OpenDC API server. + */ + @ConfigItem(defaultValue = "true") + public boolean enable; +} diff --git a/opendc-web/opendc-web-ui-quarkus/src/main/resources/META-INF/quarkus-extension.yaml b/opendc-web/opendc-web-ui-quarkus/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 00000000..581a1779 --- /dev/null +++ b/opendc-web/opendc-web-ui-quarkus/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,5 @@ +--- +name: "OpenDC Web UI" +metadata: + status: "preview" + unlisted: true diff --git a/settings.gradle.kts b/settings.gradle.kts index d3f3dc9d..f4ae69e4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -39,8 +39,8 @@ include(":opendc-web:opendc-web-proto") include(":opendc-web:opendc-web-api") include(":opendc-web:opendc-web-client") include(":opendc-web:opendc-web-ui") -include(":opendc-web:opendc-web-ui-quarkus:deployment") -include(":opendc-web:opendc-web-ui-quarkus:runtime") +include(":opendc-web:opendc-web-ui-quarkus") +include(":opendc-web:opendc-web-ui-quarkus-deployment") include(":opendc-web:opendc-web-runner:opendc-web-runner") include(":opendc-simulator:opendc-simulator-core") include(":opendc-simulator:opendc-simulator-flow") -- cgit v1.2.3 From 1489eefa18a904976f5328294c299df4285bacb4 Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 18 May 2022 14:01:38 +0200 Subject: feat(web/runner): Add Quarkus extension for OpenDC runner This change adds a Quarkus extension that hosts the OpenDC web runner for a (potentially local) OpenDC API instance. This functionality enables a simplified developer experience by allowing users to spawn the complete OpenDC stack with a single command. --- opendc-web/opendc-web-api/build.gradle.kts | 2 + .../src/main/resources/application-prod.properties | 3 +- .../src/main/resources/application-test.properties | 3 +- .../build.gradle.kts | 35 ++++++++ .../runner/deployment/OpenDCRunnerBuildItem.java | 42 +++++++++ .../web/runner/deployment/OpenDCRunnerConfig.java | 38 +++++++++ .../runner/deployment/OpenDCRunnerProcessor.java | 99 ++++++++++++++++++++++ .../opendc-web-runner-quarkus/build.gradle.kts | 47 ++++++++++ .../web/runner/runtime/OpenDCRunnerRecorder.java | 84 ++++++++++++++++++ .../runner/runtime/OpenDCRunnerRuntimeConfig.java | 77 +++++++++++++++++ .../main/resources/META-INF/quarkus-extension.yaml | 5 ++ settings.gradle.kts | 4 +- 12 files changed, 436 insertions(+), 3 deletions(-) create mode 100644 opendc-web/opendc-web-runner-quarkus-deployment/build.gradle.kts create mode 100644 opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerBuildItem.java create mode 100644 opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerConfig.java create mode 100644 opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerProcessor.java create mode 100644 opendc-web/opendc-web-runner-quarkus/build.gradle.kts create mode 100644 opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRecorder.java create mode 100644 opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRuntimeConfig.java create mode 100644 opendc-web/opendc-web-runner-quarkus/src/main/resources/META-INF/quarkus-extension.yaml diff --git a/opendc-web/opendc-web-api/build.gradle.kts b/opendc-web/opendc-web-api/build.gradle.kts index fe493f1d..a9392229 100644 --- a/opendc-web/opendc-web-api/build.gradle.kts +++ b/opendc-web/opendc-web-api/build.gradle.kts @@ -32,7 +32,9 @@ dependencies { implementation(projects.opendcWeb.opendcWebProto) compileOnly(projects.opendcWeb.opendcWebUiQuarkusDeployment) /* Temporary fix for Quarkus/Gradle issues */ + compileOnly(projects.opendcWeb.opendcWebRunnerQuarkusDeployment) implementation(projects.opendcWeb.opendcWebUiQuarkus) + implementation(projects.opendcWeb.opendcWebRunnerQuarkus) implementation(libs.quarkus.kotlin) implementation(libs.quarkus.resteasy.core) diff --git a/opendc-web/opendc-web-api/src/main/resources/application-prod.properties b/opendc-web/opendc-web-api/src/main/resources/application-prod.properties index 8f523d76..cebcdaab 100644 --- a/opendc-web/opendc-web-api/src/main/resources/application-prod.properties +++ b/opendc-web/opendc-web-api/src/main/resources/application-prod.properties @@ -28,5 +28,6 @@ quarkus.datasource.jdbc.url=${OPENDC_DB_URL} quarkus.hibernate-orm.dialect=org.hibernate.dialect.PostgreSQL95Dialect quarkus.hibernate-orm.database.generation=validate -# Disable OpenDC web UI +# Disable OpenDC web UI and runner quarkus.opendc-ui.include=false +quarkus.opendc-runner.include=false diff --git a/opendc-web/opendc-web-api/src/main/resources/application-test.properties b/opendc-web/opendc-web-api/src/main/resources/application-test.properties index ea07de6e..10197119 100644 --- a/opendc-web/opendc-web-api/src/main/resources/application-test.properties +++ b/opendc-web/opendc-web-api/src/main/resources/application-test.properties @@ -35,5 +35,6 @@ quarkus.smallrye-openapi.enable=false quarkus.swagger-ui.enable=false quarkus.smallrye-openapi.oidc-open-id-connect-url= -# Disable OpenDC web UI +# Disable OpenDC web UI and runner quarkus.opendc-ui.include=false +quarkus.opendc-runner.include=false diff --git a/opendc-web/opendc-web-runner-quarkus-deployment/build.gradle.kts b/opendc-web/opendc-web-runner-quarkus-deployment/build.gradle.kts new file mode 100644 index 00000000..24f667cf --- /dev/null +++ b/opendc-web/opendc-web-runner-quarkus-deployment/build.gradle.kts @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Quarkus extension for the OpenDC experiment runner" + +/* Build configuration */ +plugins { + `java-library-conventions` +} + +dependencies { + implementation(projects.opendcWeb.opendcWebRunnerQuarkus) + + implementation(platform(libs.quarkus.bom)) + implementation(libs.quarkus.core.deployment) +} diff --git a/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerBuildItem.java b/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerBuildItem.java new file mode 100644 index 00000000..3be15ee3 --- /dev/null +++ b/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerBuildItem.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.runner.deployment; + +import io.quarkus.builder.item.SimpleBuildItem; +import io.quarkus.runtime.RuntimeValue; +import org.opendc.web.runner.OpenDCRunner; + +/** + * A {@link SimpleBuildItem} that produces an {@link OpenDCRunner} instance. + */ +public final class OpenDCRunnerBuildItem extends SimpleBuildItem { + private final RuntimeValue runner; + + public OpenDCRunnerBuildItem(RuntimeValue runner) { + this.runner = runner; + } + + public RuntimeValue getRunner() { + return runner; + } +} diff --git a/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerConfig.java b/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerConfig.java new file mode 100644 index 00000000..ccbc5e19 --- /dev/null +++ b/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerConfig.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.runner.deployment; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * Build-time configuration for the OpenDC web runner extension. + */ +@ConfigRoot(name = "opendc-runner") +public class OpenDCRunnerConfig { + /** + * A flag to include the OpenDC web runner extension into the build. + */ + @ConfigItem(defaultValue = "true") + boolean include; +} diff --git a/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerProcessor.java b/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerProcessor.java new file mode 100644 index 00000000..b9a334ac --- /dev/null +++ b/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerProcessor.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.runner.deployment; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.*; +import io.quarkus.runtime.RuntimeValue; +import org.opendc.web.runner.OpenDCRunner; +import org.opendc.web.runner.runtime.OpenDCRunnerRecorder; +import org.opendc.web.runner.runtime.OpenDCRunnerRuntimeConfig; + +import java.util.function.BooleanSupplier; + +import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; + +/** + * Build processor for the OpenDC web runner Quarkus extension. + */ +public class OpenDCRunnerProcessor { + + private static final String FEATURE = "opendc-runner"; + + /** + * Provide the {@link FeatureBuildItem} for this Quarkus extension. + */ + @BuildStep(onlyIf = IsIncluded.class) + public FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } + + /** + * Build step to index the necessary dependencies for the OpenDC runner. + */ + @BuildStep + void addDependencies(BuildProducer indexDependency) { + indexDependency.produce(new IndexDependencyBuildItem("org.opendc.trace", "opendc-trace-opendc")); + indexDependency.produce(new IndexDependencyBuildItem("org.opendc.trace", "opendc-trace-bitbrains")); + } + + /** + * Build step to create the runner service. + */ + @BuildStep(onlyIf = IsIncluded.class) + @Record(RUNTIME_INIT) + ServiceStartBuildItem createRunnerService(OpenDCRunnerRecorder recorder, + OpenDCRunnerRuntimeConfig config, + BuildProducer runnerBuildItem) { + RuntimeValue runner = recorder.createRunner(config); + runnerBuildItem.produce(new OpenDCRunnerBuildItem(runner)); + return new ServiceStartBuildItem("OpenDCRunnerService"); + } + + /** + * Build step to start the runner service. + */ + @BuildStep(onlyIf = IsIncluded.class) + @Record(RUNTIME_INIT) + void startRunnerService(ApplicationStartBuildItem start, + OpenDCRunnerBuildItem runnerBuildItem, + OpenDCRunnerRecorder recorder, + OpenDCRunnerRuntimeConfig config, + ShutdownContextBuildItem shutdownContextBuildItem) { + recorder.startRunner(runnerBuildItem.getRunner(), config,shutdownContextBuildItem); + } + + /** + * A {@link BooleanSupplier} to determine if the OpenDC web runner extension should be included. + */ + private static class IsIncluded implements BooleanSupplier { + OpenDCRunnerConfig config; + + @Override + public boolean getAsBoolean() { + return config.include; + } + } +} diff --git a/opendc-web/opendc-web-runner-quarkus/build.gradle.kts b/opendc-web/opendc-web-runner-quarkus/build.gradle.kts new file mode 100644 index 00000000..a92e03f2 --- /dev/null +++ b/opendc-web/opendc-web-runner-quarkus/build.gradle.kts @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +description = "Quarkus extension for the OpenDC experiment runner" + +plugins { + `java-library-conventions` + id("io.quarkus.extension") +} + +quarkusExtension { + deploymentModule = "opendc-web-runner-quarkus-deployment" +} + +dependencies { + modules { + module("javax.annotation:javax.annotation-api") { + replacedBy("jakarta.annotation:jakarta.annotation-api", "javax has been replaced by Jakarta") + } + } + + api(projects.opendcWeb.opendcWebRunner) + + implementation(platform(libs.quarkus.bom)) + implementation(libs.quarkus.core.runtime) +} + +evaluationDependsOn(projects.opendcWeb.opendcWebRunnerQuarkusDeployment.dependencyProject.path) diff --git a/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRecorder.java b/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRecorder.java new file mode 100644 index 00000000..b243dbc3 --- /dev/null +++ b/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRecorder.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.runner.runtime; + +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.ShutdownContext; +import io.quarkus.runtime.annotations.Recorder; +import org.jboss.logging.Logger; +import org.opendc.web.client.runner.OpenDCRunnerClient; +import org.opendc.web.runner.OpenDCRunner; + +import java.io.File; +import java.net.URI; + +/** + * Helper class for starting the OpenDC web runner. + */ +@Recorder +public class OpenDCRunnerRecorder { + private static final Logger LOGGER = Logger.getLogger(OpenDCRunnerRecorder.class.getName()); + + /** + * Helper method to create an {@link OpenDCRunner} instance. + */ + public RuntimeValue createRunner(OpenDCRunnerRuntimeConfig config) { + URI apiUrl = URI.create(config.apiUrl); + OpenDCRunnerClient client = new OpenDCRunnerClient(apiUrl, null); + OpenDCRunner runner = new OpenDCRunner( + client, + new File(config.tracePath), + config.parallelism, + config.jobTimeout, + config.pollInterval, + config.heartbeatInterval + ); + + return new RuntimeValue<>(runner); + } + + /** + * Helper method to start the OpenDC runner service. + */ + public void startRunner(RuntimeValue runner, + OpenDCRunnerRuntimeConfig config, + ShutdownContext shutdownContext) { + if (config.enable) { + LOGGER.info("Starting OpenDC Runner in background (polling " + config.apiUrl + ")"); + + Thread thread = new Thread(() -> { + try { + // Wait for some time to allow Vert.x to bind to the local port + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + runner.getValue().run(); + }); + thread.start(); + + shutdownContext.addShutdownTask(thread::interrupt); + } + } +} diff --git a/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRuntimeConfig.java b/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRuntimeConfig.java new file mode 100644 index 00000000..e1dbf0a8 --- /dev/null +++ b/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRuntimeConfig.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.runner.runtime; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +import java.time.Duration; + +/** + * Configuration for the OpenDC web runner. + */ +@ConfigRoot(phase = ConfigPhase.RUN_TIME, name = "opendc-runner") +public class OpenDCRunnerRuntimeConfig { + /** + * Flag to indicate whether the runner should be enabled. + */ + @ConfigItem(defaultValue = "true") + public boolean enable; + + /** + * The URI to the (local) API to communicate with. + */ + @ConfigItem(defaultValue = "http://${quarkus.http.host}:${quarkus.http.port}${quarkus.resteasy.path:}") + public String apiUrl; + + /** + * The path where the workload traces are located. + */ + @ConfigItem(defaultValue = "traces") + public String tracePath; + + /** + * The number of concurrent simulations + */ + @ConfigItem(defaultValue = "1") + public int parallelism; + + /** + * The maximum duration of a job. + */ + @ConfigItem(defaultValue = "10m") + public Duration jobTimeout; + + /** + * The interval between successive polls to the API. + */ + @ConfigItem(defaultValue = "30s") + public Duration pollInterval; + + /** + * The interval between successive heartbeats to the API. + */ + @ConfigItem(defaultValue = "1m") + public Duration heartbeatInterval; +} diff --git a/opendc-web/opendc-web-runner-quarkus/src/main/resources/META-INF/quarkus-extension.yaml b/opendc-web/opendc-web-runner-quarkus/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 00000000..b93b467a --- /dev/null +++ b/opendc-web/opendc-web-runner-quarkus/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,5 @@ +--- +name: "OpenDC Web Runner" +metadata: + status: "preview" + unlisted: true diff --git a/settings.gradle.kts b/settings.gradle.kts index f4ae69e4..88c3ffb6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -41,7 +41,9 @@ include(":opendc-web:opendc-web-client") include(":opendc-web:opendc-web-ui") include(":opendc-web:opendc-web-ui-quarkus") include(":opendc-web:opendc-web-ui-quarkus-deployment") -include(":opendc-web:opendc-web-runner:opendc-web-runner") +include(":opendc-web:opendc-web-runner") +include(":opendc-web:opendc-web-runner-quarkus") +include(":opendc-web:opendc-web-runner-quarkus-deployment") include(":opendc-simulator:opendc-simulator-core") include(":opendc-simulator:opendc-simulator-flow") include(":opendc-simulator:opendc-simulator-power") -- cgit v1.2.3 From fb88cf197d9fe814e9692e7dafdc0d31b940acbc Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Wed, 18 May 2022 16:30:41 +0200 Subject: feat(web/api): Add initial server distribution This change adds a distribution that contains the OpenDC web server implementation based on Quarkus. This distribution should be used by advanced users that whish to deploy a custom OpenDC instance. --- opendc-web/opendc-web-api/build.gradle.kts | 28 ++++++++++++++++++++++ .../opendc-web-api/config/application.properties | 1 + 2 files changed, 29 insertions(+) create mode 100644 opendc-web/opendc-web-api/config/application.properties diff --git a/opendc-web/opendc-web-api/build.gradle.kts b/opendc-web/opendc-web-api/build.gradle.kts index a9392229..89b8273c 100644 --- a/opendc-web/opendc-web-api/build.gradle.kts +++ b/opendc-web/opendc-web-api/build.gradle.kts @@ -25,6 +25,7 @@ description = "REST API for the OpenDC website" /* Build configuration */ plugins { `quarkus-conventions` + distribution } dependencies { @@ -58,3 +59,30 @@ dependencies { testImplementation(libs.quarkus.test.security) testImplementation(libs.quarkus.jdbc.h2) } + +val createStartScripts by tasks.creating(CreateStartScripts::class) { + applicationName = "opendc-server" + mainClass.set("io.quarkus.bootstrap.runner.QuarkusEntryPoint") + classpath = files("lib/quarkus-run.jar") + outputDir = project.buildDir.resolve("scripts") +} + +distributions { + create("server") { + distributionBaseName.set("opendc-server") + + contents { + from("../../LICENSE.txt") + from("config") { + into("config") + } + + from(createStartScripts) { + into("bin") + } + from(tasks.quarkusBuild) { + into("lib") + } + } + } +} diff --git a/opendc-web/opendc-web-api/config/application.properties b/opendc-web/opendc-web-api/config/application.properties new file mode 100644 index 00000000..30eaaef9 --- /dev/null +++ b/opendc-web/opendc-web-api/config/application.properties @@ -0,0 +1 @@ +# Custom server properties -- cgit v1.2.3