Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
mathieuancelin committed Nov 21, 2023
0 parents commit f291fc8
Show file tree
Hide file tree
Showing 23 changed files with 2,769 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.bsp
.DS_Store
.idea
.metals
.vscode
darwin-**
linux-**
target
80 changes: 80 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import Dependencies.munit

lazy val scala212 = "2.12.16"
lazy val scala213 = "2.13.11"
lazy val supportedScalaVersions = List(scala212, scala213)

ThisBuild / scalaVersion := scala212
ThisBuild / version := "1.0.0"
ThisBuild / organization := "io.otoroshi"
ThisBuild / organizationName := "wasm4s"

inThisBuild(
List(
description := "Library to run wasm vm in a scala app",
startYear := Some(2023),
organization := "io.otoroshi",
homepage := Some(url("https://github.com/MAIF/wasm4s")),
licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")),
scmInfo := Some(
ScmInfo(
url("https://github.com/MAIF/otoroshi"),
"scm:[email protected]:MAIF/otoroshi.git"
)
),
publishMavenStyle := true,
developers := List(
Developer(
"mathieuancelin",
"Mathieu Ancelin",
"[email protected]",
url("https://github.com/mathieuancelin")
)
)
)
)


lazy val playJsonVersion = "2.9.3"
lazy val playWsVersion = "2.8.19"
lazy val akkaVersion = "2.6.20"
lazy val akkaHttpVersion = "10.2.10"
lazy val metricsVersion = "4.2.12"
lazy val excludesJackson = Seq(
ExclusionRule(organization = "com.fasterxml.jackson.core"),
ExclusionRule(organization = "com.fasterxml.jackson.datatype"),
ExclusionRule(organization = "com.fasterxml.jackson.dataformat")
)

scalacOptions ++= Seq(
"-feature",
"-language:higherKinds",
"-language:implicitConversions",
"-language:existentials",
"-language:postfixOps"
)

lazy val root = (project in file("."))
.settings(
name := "wasm4s",
crossScalaVersions := supportedScalaVersions,
//githubOwner := "MAIF",
//githubRepository := "wasm4s",
//githubTokenSource := TokenSource.Environment("GITHUB_PACKAGES_TOKEN"),
libraryDependencies ++= Seq(
munit % Test,
"com.typesafe.play" %% "play-ws" % playWsVersion % "provided",
"com.typesafe.play" %% "play-json" % playJsonVersion % "provided",
"com.typesafe.akka" %% "akka-stream" % akkaVersion % "provided",
"com.typesafe.akka" %% "akka-http" % akkaHttpVersion % "provided",
"com.typesafe.play" %% "play-json-joda" % playJsonVersion % "provided",
"com.auth0" % "java-jwt" % "4.2.0" % "provided" excludeAll (excludesJackson: _*),
"commons-codec" % "commons-codec" % "1.16.0" % "provided",
"net.java.dev.jna" % "jna" % "5.13.0" % "provided",
"com.google.code.gson" % "gson" % "2.10" % "provided",
"io.dropwizard.metrics" % "metrics-json" % metricsVersion % "provided" excludeAll (excludesJackson: _*), // Apache 2.0
),
)

assembly / test := {}
assembly / assemblyJarName := s"wasm4s-bundle_${scalaVersion.value.split("\\.").init.mkString(".")}-${version.value}.jar"
6 changes: 6 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
rm ./target/scala-2.12/wasm4s_2.12-*.jar
rm ./target/scala-2.13/wasm4s_2.13-*.jar
rm ./target/scala-2.12/wasm4s-bundle_2.12-*.jar
rm ./target/scala-2.13/wasm4s-bundle_2.13-*.jar
sbt '+package'
sbt '+assembly'
Binary file added lib/extism-v0.4.0.jar
Binary file not shown.
5 changes: 5 additions & 0 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import sbt._

object Dependencies {
lazy val munit = "org.scalameta" %% "munit" % "0.7.29"
}
1 change: 1 addition & 0 deletions project/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version=1.7.2
4 changes: 4 additions & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.1.1")
addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.14")
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.1.1")
// addSbtPlugin("com.codecommit" % "sbt-github-packages" % "0.5.3")
109 changes: 109 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# wasm4s

this library provides a runtime to execute wasm function in a pooled vm paradigm

## how to use it

first you need to have a class that implements the `WasmConfiguration` trait. This type represent a wasm vm you want to run. Objects that implements
can be stored anywhere you want. This library provides a `BasicWasmConfiguration` implementation, but you can build your own.

then create an integration context class that will provide access to everything needed

for instance, here is the otoroshi integration :

```scala
import io.otoroshi.wasm4s._

class OtoroshiWasmIntegrationContext(env: Env) extends WasmIntegrationContext {

implicit val ec = env.otoroshiExecutionContext
implicit val ev = env

val logger: Logger = Logger("otoroshi-wasm-integration")
val materializer: Materializer = env.otoroshiMaterializer
val executionContext: ExecutionContext = env.otoroshiExecutionContext
val wasmCacheTtl: Long = env.wasmCacheTtl
val wasmQueueBufferSize: Int = env.wasmQueueBufferSize
val wasmScriptCache: TrieMap[String, CacheableWasmScript] = new TrieMap[String, CacheableWasmScript]()
val wasmExecutor: ExecutionContext = ExecutionContext.fromExecutorService(
Executors.newWorkStealingPool(Math.max(32, (Runtime.getRuntime.availableProcessors * 4) + 1))
)

override def url(path: String): WSRequest = env.Ws.url(path)

override def mtlsUrl(path: String, tlsConfig: TlsConfig): WSRequest = {
val cfg = NgTlsConfig.format.reads(tlsConfig.json).get.legacy
env.MtlsWs.url(path, cfg)
}

override def wasmManagerSettings: Future[Option[WasmManagerSettings]] = env.datastores.globalConfigDataStore.latest().wasmManagerSettings.vfuture

override def wasmConfig(path: String): Option[WasmConfiguration] = env.proxyState.wasmPlugin(path).map(_.config)

override def wasmConfigs(): Seq[WasmConfiguration] = env.proxyState.allWasmPlugins().map(_.config)

override def hostFunctions(config: WasmConfiguration, pluginId: String): Array[WasmOtoroshiHostFunction[_ <: WasmOtoroshiHostUserData]] = {
HostFunctions.getFunctions(config.asInstanceOf[WasmConfig], pluginId, None)
}
}
```

you can create one yourself like :

```scala
val testWasmConfigs: InMemoryWasmConfigurationStore[WasmConfiguration] = InMemoryWasmConfigurationStore(
"basic" -> BasicWasmConfiguration.fromWasiSource(WasmSource(WasmSourceKind.File, "./src/test/resources/basic.wasm")),
"opa" -> BasicWasmConfiguration.fromOpaSource(WasmSource(WasmSourceKind.File, "./src/test/resources/opa.wasm")),
)

class FooWasmIntegrationContext(env: Env) extends WasmIntegrationContext {
val system = ActorSystem("foo-wasm")
val materializer: Materializer = Materializer(system)
val executionContext: ExecutionContext = system.dispatcher
val logger: Logger = Logger("foo-wasm")
val wasmCacheTtl: Long = 2000
val wasmQueueBufferSize: Int = 100
val wasmManagerSettings: Future[Option[WasmManagerSettings]] = Future.successful(None)
val wasmScriptCache: TrieMap[String, CacheableWasmScript] = new TrieMap[String, CacheableWasmScript]()
val wasmExecutor: ExecutionContext = ExecutionContext.fromExecutorService(
Executors.newWorkStealingPool(Math.max(32, (Runtime.getRuntime.availableProcessors * 4) + 1))
)
override def url(path: String): WSRequest = ??? // we do not provide http call right now ;)
override def mtlsUrl(path: String, tlsConfig: TlsConfig): WSRequest = ??? // we do not provide http call right now ;)
override def wasmConfig(path: String): Option[WasmConfiguration] = testWasmConfigs.wasmConfiguration(path)
override def wasmConfigs(): Seq[WasmConfiguration] = testWasmConfigs.wasmConfigurations()
override def hostFunctions(config: WasmConfiguration, pluginId: String): Array[WasmOtoroshiHostFunction[_ <: WasmOtoroshiHostUserData]] = Array.empty
}
```

then instanciate a wasm integration

```scala
val wasmIntegration = WasmIntegration(new FooWasmIntegrationContext(env))
```

now you have to trigger jobs that will cache wasm stuff and clean vm. you can either do it manually or let the integration do it.

```scala
wasmIntegration.start()
Runtime.getRuntime().addShutdownHook(() => {
wasmIntegration.stop()
})
```

now you can get a wasm vm through the wasm integration object and use it

```scala
wasmIntegration.withPooledVm(basicConfiguration) { vm =>
vm.callExtismFunction(
"execute",
Json.obj("message" -> "coucou").stringify
).map {
case Left(error) => println(s"error: ${error.prettify}")
case Right(out) => {
assertEquals(out, "{\"input\":{\"message\":\"coucou\"},\"message\":\"yo\"}")
println(s"output: ${out}")
}
}
}
```
Empty file added src/main/resources/.keepit
Empty file.
Loading

0 comments on commit f291fc8

Please sign in to comment.