Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve plugin loading, and include sample plugin with docker container #39

Merged
merged 3 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# build hmruntime binary
FROM golang:alpine as runtime-builder
WORKDIR /src
COPY hmruntime/ ./
RUN go build .

# build example plugin
FROM node:20-alpine as plugin-builder
WORKDIR /src
COPY plugins/as/ ./
WORKDIR /src/hmplugin1
RUN npm install
RUN npm run build:release

# build runtime image
FROM ubuntu:20.04
LABEL maintainer="Hypermode <hello@hypermode.com>"
COPY --from=runtime-builder /src/hmruntime /usr/bin/hmruntime
COPY --from=plugin-builder /src/hmplugin1/build/release.wasm /plugins/hmplugin1.wasm

ENTRYPOINT ["hmruntime", "--plugins=/plugins"]
13 changes: 0 additions & 13 deletions hmruntime/Dockerfile

This file was deleted.

7 changes: 5 additions & 2 deletions hmruntime/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ The following must be installed on your development workstation or build server:

To build the Hypermode runtime server: `go build`

To build the docker image: `docker build -t hmruntime .`
To build the docker image, from the root directory: `docker build -t hypermode/runtime .`

## Running

- To run the compiled program, invoke the `hmruntime` binary.
- To run from code (while developing), use `go run .` instead.
- To run using docker containers, use `docker run -p 8686:8686 -v <PLUGINS_PATH>:/plugins hmruntime:latest hmruntime --dgraph=http://host.docker.internal:8080`.
- To run using docker containers, first decide where plugins will be loaded from.
- To just use the default `hmplugin1` sample plugin, use `docker run -p 8686:8686 hypermode/runtime --dgraph=http://host.docker.internal:8080`.
- Or, mount a plugins directory on the host, use `docker run -p 8686:8686 -v <PLUGINS_PATH>:/plugins hypermode/runtime --dgraph=http://host.docker.internal:8080`.
- For example `-v ./plugins/as:/plugins`

## Notes

Expand Down
75 changes: 59 additions & 16 deletions hmruntime/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@ var compiledModules = make(map[string]wazero.CompiledModule)
var functionsMap = make(map[string]functionInfo)

var dgraphUrl *string
var pluginsPath *string

func main() {
ctx := context.Background()

// Parse command-line flags
var port = flag.Int("port", 8686, "The HTTP port to listen on.")
dgraphUrl = flag.String("dgraph", "http://localhost:8080", "The Dgraph url to connect to.")
pluginsPath = flag.String("plugins", "../plugins/as", "The path to the plugins directory.")
flag.Parse()

// Initialize the WebAssembly runtime
Expand Down Expand Up @@ -71,22 +73,45 @@ func main() {
}

func loadPlugins(ctx context.Context) error {
entries, err := os.ReadDir(*pluginsPath)
if err != nil {
return fmt.Errorf("failed to read plugins directory: %v", err)
}

for _, entry := range entries {

// Determine if the entry represents a plugin.
var pluginName string
entryName := entry.Name()
if entry.IsDir() {
pluginName = entryName
path := fmt.Sprintf("%s/%s/build/debug.wasm", *pluginsPath, pluginName)
if _, err := os.Stat(path); err != nil {
continue
}
} else if strings.HasSuffix(entryName, ".wasm") {
pluginName = strings.TrimSuffix(entryName, ".wasm")
} else {
continue
}

// Load the plugin
err := loadPlugin(ctx, pluginName)
if err != nil {
log.Printf("Failed to load plugin '%s': %v\n", pluginName, err)
}
}

// For now, we have just one hardcoded plugin.
const pluginName = "hmplugin1"
return nil
}

// TODO: This will need work:
// - Plugins should probably be loaded from a repository, not from disk.
// - We'll need to figure out how to handle plugin updates.
// - We'll need to figure out hot/warm/cold plugin loading.
func loadPlugin(ctx context.Context, pluginName string) error {

err := loadPluginModule(ctx, pluginName)
if err != nil {
return err
}

// Temporarily, watch for changes to the plugin so we can reload it.
// TODO: Remove this when we have a better way to handle plugin updates.
err = watchForPluginChanges(ctx, pluginName)
if err != nil {
return err
Expand Down Expand Up @@ -169,8 +194,11 @@ func loadPluginModule(ctx context.Context, name string) error {
fmt.Printf("Loading plugin '%s'\n", name)
}

// TODO: Load the plugin from some repository instead of disk.
path := getPathForPlugin(name)
path, err := getPathForPlugin(name)
if err != nil {
return fmt.Errorf("failed to get path for plugin: %v", err)
}

plugin, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to load the plugin: %v", err)
Expand Down Expand Up @@ -228,10 +256,21 @@ func getModuleInstance(ctx context.Context, pluginName string) (wasm.Module, buf
return mod, buf, nil
}

func getPathForPlugin(name string) string {
// TODO: Decide whether to load the plugin in debug or release mode.
// For now use debug, so we get better stack traces on errors.
return "../plugins/as/" + name + "/build/debug.wasm"
func getPathForPlugin(name string) (string, error) {

// Normally the plugin will be directly in the plugins directory, by filename.
path := *pluginsPath + "/" + name + ".wasm"
if _, err := os.Stat(path); err == nil {
return path, nil
}

// For local development, the plugin will be in a subdirectory and we'll use the debug.wasm file.
path = *pluginsPath + "/" + name + "/build/debug.wasm"
if _, err := os.Stat(path); err == nil {
return path, nil
}

return "", fmt.Errorf("compiled wasm file not found for plugin '%s'", name)
}

func watchForPluginChanges(ctx context.Context, name string) error {
Expand Down Expand Up @@ -269,8 +308,12 @@ func watchForPluginChanges(ctx context.Context, name string) error {
}
}()

path := getPathForPlugin(name)
err := w.Add(path)
path, err := getPathForPlugin(name)
if err != nil {
return fmt.Errorf("failed to get path for plugin: %v", err)
}

err = w.Add(path)
if err != nil {
return fmt.Errorf("failed to watch plugin file: %v", err)
}
Expand Down
Loading