Compare commits
13 commits
feature/wa
...
main
Author | SHA1 | Date | |
---|---|---|---|
634d4eb441 | |||
04644e99d9 | |||
8ded4bc17b | |||
19c20245ba | |||
d22bb0808e | |||
959f4dbf37 | |||
d53a1531e6 | |||
3a2207290f | |||
1f0c58b1c8 | |||
8f49d14549 | |||
3bf4797b6a | |||
cc52d19501 | |||
9386a24580 |
49 changed files with 836 additions and 4561 deletions
4
.buildr/.gitignore
vendored
Normal file
4
.buildr/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
bin/
|
||||
logs/
|
||||
out/
|
||||
state.sqlite
|
5
.buildr/config.hcl
Normal file
5
.buildr/config.hcl
Normal file
|
@ -0,0 +1,5 @@
|
|||
buildr {
|
||||
bin_dir = ".buildr/bin"
|
||||
out_dir = ".buildr/out"
|
||||
logs_dir = ".buildr/logs"
|
||||
}
|
54
.buildr/tasks.hcl
Normal file
54
.buildr/tasks.hcl
Normal file
|
@ -0,0 +1,54 @@
|
|||
task "script" "buf_generate" {
|
||||
inline = [
|
||||
"buf generate --debug"
|
||||
]
|
||||
|
||||
out_dir = repo.root
|
||||
|
||||
input_mapping = {
|
||||
"api" = "api",
|
||||
"buf.gen.yaml" = "buf.gen.yaml"
|
||||
"buf.work.yaml" = "buf.work.yaml"
|
||||
}
|
||||
|
||||
container {
|
||||
image = "docker.io/bufbuild/buf"
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
tools.vtprotobuf.id
|
||||
]
|
||||
}
|
||||
|
||||
task "script" "generate_examples_hello_world" {
|
||||
working_dir = "examples/hello_world_go"
|
||||
inline = [
|
||||
"go generate -x ./..."
|
||||
]
|
||||
|
||||
depends_on = [
|
||||
tasks.buf_generate.id,
|
||||
tools.mockery.id
|
||||
]
|
||||
}
|
||||
|
||||
task "script" "generate_sdk" {
|
||||
inline = [
|
||||
"go generate -x ./..."
|
||||
]
|
||||
|
||||
depends_on = [
|
||||
tasks.buf_generate.id,
|
||||
]
|
||||
}
|
||||
|
||||
task "script" "go_generate" {
|
||||
inline = [
|
||||
"/bin/true"
|
||||
]
|
||||
|
||||
depends_on = [
|
||||
tasks.generate_sdk.id,
|
||||
tasks.generate_examples_hello_world.id,
|
||||
]
|
||||
}
|
30
.buildr/tools.hcl
Normal file
30
.buildr/tools.hcl
Normal file
|
@ -0,0 +1,30 @@
|
|||
locals {
|
||||
tool_versions {
|
||||
mockery = gh_latest_release("vektra", "mockery")
|
||||
vtprotobuf = gh_latest_release("planetscale", "vtprotobuf")
|
||||
}
|
||||
}
|
||||
|
||||
tool "go_tool" "mockery" {
|
||||
binary_name = "mockery"
|
||||
repository = "github.com/vektra/mockery/v2"
|
||||
version = local.tool_versions.mockery
|
||||
build_args = [
|
||||
"-v",
|
||||
"-trimpath",
|
||||
"-a",
|
||||
"-installsuffix=cgo"
|
||||
]
|
||||
}
|
||||
|
||||
tool "go_tool" vtprotobuf {
|
||||
binary_name = "protoc-gen-go-vtproto"
|
||||
repository = "github.com/planetscale/vtprotobuf/cmd/protoc-gen-go-vtproto"
|
||||
version = local.tool_versions.vtprotobuf
|
||||
build_args = [
|
||||
"-v",
|
||||
"-trimpath",
|
||||
"-a",
|
||||
"-installsuffix=cgo"
|
||||
]
|
||||
}
|
27
.editorconfig
Normal file
27
.editorconfig
Normal file
|
@ -0,0 +1,27 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
indent_style = space
|
||||
insert_final_newline = false
|
||||
max_line_length = 120
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
ij_smart_tabs = true
|
||||
ij_go_GROUP_CURRENT_PROJECT_IMPORTS = true
|
||||
ij_go_group_stdlib_imports = true
|
||||
ij_go_import_sorting = goimports
|
||||
ij_go_local_group_mode = project
|
||||
ij_go_move_all_imports_in_one_declaration = true
|
||||
ij_go_move_all_stdlib_imports_in_one_group = true
|
||||
ij_go_remove_redundant_import_aliases = true
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
insert_final_newline = true
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -19,5 +19,6 @@
|
|||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work*
|
||||
|
||||
.idea/
|
37
api.go
37
api.go
|
@ -2,28 +2,21 @@ package sdk
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/mailru/easyjson"
|
||||
"golang.org/x/exp/slog"
|
||||
"io"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
commonv1 "code.icb4dc0.de/buildr/api/generated/common/v1"
|
||||
)
|
||||
|
||||
type Category string
|
||||
|
||||
func (t Category) String() string {
|
||||
return string(t)
|
||||
}
|
||||
|
||||
func (t Category) GroupName() string {
|
||||
return fmt.Sprintf("%ss", t)
|
||||
}
|
||||
type Category = commonv1.Category
|
||||
|
||||
const (
|
||||
CategoryTool Category = "tool"
|
||||
CategoryTask Category = "task"
|
||||
CategoryBuild Category = "build"
|
||||
CategoryPackage Category = "package"
|
||||
CategoryTool = commonv1.Category_CategoryTool
|
||||
CategoryTask = commonv1.Category_CategoryTask
|
||||
CategoryBuild = commonv1.Category_CategoryBuild
|
||||
CategoryPackage = commonv1.Category_CategoryPackage
|
||||
CategoryRelease = commonv1.Category_CategoryRelease
|
||||
)
|
||||
|
||||
type StateMetadata struct {
|
||||
|
@ -43,13 +36,23 @@ type ExecutionContext interface {
|
|||
SetState(ctx context.Context, key string, value []byte) error
|
||||
}
|
||||
|
||||
type TaskSpec[T Module] struct {
|
||||
Module T
|
||||
ModuleName string
|
||||
Container *commonv1.ContainerSpec
|
||||
OutputDir string
|
||||
}
|
||||
|
||||
type Module interface {
|
||||
easyjson.Unmarshaler
|
||||
Execute(ctx ExecutionContext) error
|
||||
Category() Category
|
||||
Type() string
|
||||
}
|
||||
|
||||
type Helper interface {
|
||||
Help() Help
|
||||
}
|
||||
|
||||
type BinaryNamer interface {
|
||||
BinaryName() string
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
# WASI host - module data exchange
|
||||
|
||||
The module SDK re-uses a subset of protobuf messages also used in the remote protocol except for the `TaskOutput` message because WASI handles `STDOUT`/`STDERR` already.
|
||||
|
||||
The protobuf messages are not used with gRPC but only as binary encoded messages shared via pointers in the WASI modules memory.
|
||||
|
||||
The following 'RPC' calls are supported:
|
||||
|
||||
- `log_msg` accepting a pointer and an offset to a `TaskLog` message - won't return anything
|
||||
- `get_state` accepting a pointer and an offset to a `GetStateRequest` message, returning a 64-bit integer (32-bit pointer, 32-bit size) to a `GetStateResponse` message
|
||||
- `set_state` accepting a pointer and an offset to a `SetState` message, returning a 64-bit integer (32-bit pointer, 32-bit size) to a `Result` message optionally containing error details
|
11
api/buf.yaml
11
api/buf.yaml
|
@ -1,11 +0,0 @@
|
|||
version: v1
|
||||
name: buf.build/buildr/buildr
|
||||
breaking:
|
||||
use:
|
||||
- FILE
|
||||
lint:
|
||||
use:
|
||||
- DEFAULT
|
||||
except:
|
||||
- PACKAGE_DIRECTORY_MATCH
|
||||
allow_comment_ignores: true
|
|
@ -1,72 +0,0 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package buildr.rpc.v1;
|
||||
|
||||
message Buildr {
|
||||
message Repo {
|
||||
string root = 1;
|
||||
}
|
||||
message GitHub {
|
||||
string api_token = 1;
|
||||
}
|
||||
|
||||
Repo repo = 1;
|
||||
GitHub github = 2;
|
||||
}
|
||||
|
||||
message ModuleReference {
|
||||
string module_category = 1;
|
||||
string module_type = 2;
|
||||
}
|
||||
|
||||
message TaskReference {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
ModuleReference module = 3;
|
||||
}
|
||||
|
||||
message StartTaskRequest {
|
||||
TaskReference reference = 1;
|
||||
Buildr buildr = 2;
|
||||
bytes raw_task = 3;
|
||||
}
|
||||
|
||||
message TaskResult {
|
||||
string error = 1;
|
||||
string modified_files_archive_path = 2;
|
||||
}
|
||||
|
||||
message TaskLog {
|
||||
message LogAttribute {
|
||||
string key = 1;
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
int64 time = 1;
|
||||
string message = 2;
|
||||
int32 level = 3;
|
||||
repeated LogAttribute attributes = 4;
|
||||
}
|
||||
|
||||
message SetState {
|
||||
bytes key = 1;
|
||||
bytes data = 2;
|
||||
}
|
||||
|
||||
message GetStateRequest {
|
||||
bytes key = 1;
|
||||
}
|
||||
|
||||
message GetStateResponse {
|
||||
bytes key = 1;
|
||||
bytes data = 2;
|
||||
}
|
||||
|
||||
message Result {
|
||||
bool success = 1;
|
||||
string error = 2;
|
||||
}
|
||||
|
||||
message PluginInventory {
|
||||
repeated ModuleReference modules = 1;
|
||||
}
|
17
buf.gen.yaml
17
buf.gen.yaml
|
@ -1,17 +0,0 @@
|
|||
version: v1
|
||||
managed:
|
||||
enabled: true
|
||||
go_package_prefix:
|
||||
default: code.icb4dc0.de/buildr/wasi-module-sdk-go/internal
|
||||
except:
|
||||
- buf.build/googleapis/googleapis
|
||||
plugins:
|
||||
- plugin: buf.build/protocolbuffers/go:v1.30.0
|
||||
out: ./protocol/generated/
|
||||
opt: paths=source_relative
|
||||
- plugin: go-vtproto
|
||||
out: ./protocol/generated/
|
||||
opt:
|
||||
- features=marshal+unmarshal+size+pool
|
||||
- paths=source_relative
|
||||
revision: 1
|
|
@ -1,3 +0,0 @@
|
|||
version: v1
|
||||
directories:
|
||||
- api/
|
4
buf.yaml
4
buf.yaml
|
@ -1,4 +0,0 @@
|
|||
version: v1
|
||||
name: buf.build/buildr/module-sdk-go
|
||||
deps:
|
||||
- buf.build/buildr/buildr
|
23
context.go
23
context.go
|
@ -2,9 +2,8 @@ package sdk
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"golang.org/x/exp/slog"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
)
|
||||
|
||||
|
@ -13,14 +12,12 @@ var _ ExecutionContext = (*wasiExecutionContext)(nil)
|
|||
func newWasiExecutionContext(
|
||||
ctx context.Context,
|
||||
logger *slog.Logger,
|
||||
modName string,
|
||||
mod Module,
|
||||
repoRoot, binDir, outDir string,
|
||||
) *wasiExecutionContext {
|
||||
return &wasiExecutionContext{
|
||||
Context: ctx,
|
||||
logger: logger,
|
||||
modName: modName,
|
||||
mod: mod,
|
||||
repoRoot: repoRoot,
|
||||
outDir: outDir,
|
||||
|
@ -33,7 +30,6 @@ type wasiExecutionContext struct {
|
|||
stateProxy StateProxy
|
||||
logger *slog.Logger
|
||||
mod Module
|
||||
modName string
|
||||
repoRoot string
|
||||
outDir string
|
||||
binDir string
|
||||
|
@ -64,22 +60,9 @@ func (w wasiExecutionContext) Logger() *slog.Logger {
|
|||
}
|
||||
|
||||
func (w wasiExecutionContext) GetState(_ context.Context, key string) ([]byte, StateMetadata, error) {
|
||||
return w.stateProxy.Get(w.keyBytes(w.mod.Category().String(), w.modName, key))
|
||||
return w.stateProxy.Get([]byte(key))
|
||||
}
|
||||
|
||||
func (w wasiExecutionContext) SetState(_ context.Context, key string, value []byte) error {
|
||||
return w.stateProxy.Set(w.keyBytes(w.mod.Category().String(), w.modName, key), value)
|
||||
}
|
||||
|
||||
func (w wasiExecutionContext) keyBytes(parts ...string) []byte {
|
||||
if len(parts) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
h := md5.New()
|
||||
for i := range parts {
|
||||
_, _ = h.Write([]byte(parts[i]))
|
||||
}
|
||||
|
||||
return h.Sum(nil)
|
||||
return w.stateProxy.Set([]byte(key), value)
|
||||
}
|
||||
|
|
153
entrypoint.go
153
entrypoint.go
|
@ -2,54 +2,147 @@ package sdk
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log/slog"
|
||||
|
||||
"github.com/mailru/easyjson"
|
||||
_ "github.com/tetratelabs/tinymem"
|
||||
|
||||
"code.icb4dc0.de/buildr/wasi-module-sdk-go/mem"
|
||||
rpcv1 "code.icb4dc0.de/buildr/wasi-module-sdk-go/protocol/generated/rpc/v1"
|
||||
commonv1 "code.icb4dc0.de/buildr/api/generated/common/v1"
|
||||
remotev1 "code.icb4dc0.de/buildr/api/generated/remote/v1"
|
||||
wasiv1 "code.icb4dc0.de/buildr/api/generated/wasi/v1"
|
||||
"code.icb4dc0.de/buildr/common/protocol"
|
||||
)
|
||||
|
||||
var defaultRegistry = NewTypeRegistry()
|
||||
var (
|
||||
defaultRegistry = NewTypeRegistry()
|
||||
startTaskWrapper = FuncExportWrapper[*remotev1.StartTaskRequest, *wasiv1.StartTaskResponse](StartTask)
|
||||
inventoryWrapper = FuncExportWrapper[*wasiv1.PluginInventoryRequest, *wasiv1.PluginInventoryResponse](GetInventory)
|
||||
helpForWrapper = FuncExportWrapper[*wasiv1.HelpRequest, *wasiv1.HelpResponse](HelpForModule)
|
||||
binaryNameWrapper = FuncExportWrapper[*wasiv1.BinaryNameRequest, *wasiv1.BinaryNameResponse](BinaryNameForModule)
|
||||
)
|
||||
|
||||
func Register(cat Category, moduleName string, factory Factory) {
|
||||
defaultRegistry.Add(cat, moduleName, factory)
|
||||
}
|
||||
|
||||
//export inventory
|
||||
func Inventory() uint64 {
|
||||
var inventory rpcv1.PluginInventory
|
||||
func GetInventory(*wasiv1.PluginInventoryRequest) (*wasiv1.PluginInventoryResponse, error) {
|
||||
var inventory wasiv1.PluginInventoryResponse
|
||||
|
||||
for _, t := range defaultRegistry.List() {
|
||||
inventory.Modules = append(inventory.Modules, &rpcv1.ModuleReference{
|
||||
ModuleCategory: t.Category.String(),
|
||||
ModuleType: t.Type,
|
||||
})
|
||||
}
|
||||
|
||||
data, err := inventory.MarshalVT()
|
||||
m := defaultRegistry.Get(t.Category, t.Type)
|
||||
spec, err := protocol.Marshal(m)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return mem.UnifyPtrSize(mem.DataToUnmanagedPtr(data))
|
||||
}
|
||||
|
||||
//export run
|
||||
func Run(specPtr, specSize uint32) {
|
||||
var startTask rpcv1.StartTaskRequest
|
||||
|
||||
if err := startTask.UnmarshalVT(mem.DataFromPtr(specPtr, specSize)); err != nil {
|
||||
data, err := spec.MarshalVT()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
executor := NewExecutor(startTask.Buildr.Repo.Root, "", "")
|
||||
reference := startTask.GetReference().GetModule()
|
||||
module := defaultRegistry.Get(Category(reference.GetModuleCategory()), reference.GetModuleType())
|
||||
inventory.Specs = append(inventory.Specs, &wasiv1.PluginInventoryResponse_InventorySpec{
|
||||
ModuleRef: &commonv1.ModuleReference{
|
||||
ModuleCategory: t.Category,
|
||||
ModuleType: t.Type,
|
||||
},
|
||||
EmptySpec: data,
|
||||
})
|
||||
}
|
||||
|
||||
if err := easyjson.Unmarshal(startTask.RawTask, module); err != nil {
|
||||
return &inventory, nil
|
||||
}
|
||||
|
||||
func StartTask(req *remotev1.StartTaskRequest) (*wasiv1.StartTaskResponse, error) {
|
||||
executor := NewExecutor(req.Buildr.Repo.Root, req.Buildr.OutDir, req.Buildr.BinDir)
|
||||
reference := req.GetReference().GetModule()
|
||||
module := defaultRegistry.Get(reference.GetModuleCategory(), reference.GetModuleType())
|
||||
|
||||
if err := protocol.Unmarshal(req.GetSpec(), module); err != nil {
|
||||
executor.logger.Error("Failed to unmarshal spec", slog.String("error", err.Error()))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var startTaskResponse wasiv1.StartTaskResponse
|
||||
|
||||
if err := executor.Run(context.Background(), module); err != nil {
|
||||
startTaskResponse.Error = err.Error()
|
||||
}
|
||||
|
||||
return &startTaskResponse, nil
|
||||
}
|
||||
|
||||
func HelpForModule(helpRequest *wasiv1.HelpRequest) (*wasiv1.HelpResponse, error) {
|
||||
module := defaultRegistry.Get(helpRequest.ModuleReference.ModuleCategory, helpRequest.ModuleReference.ModuleType)
|
||||
if module == nil {
|
||||
return nil, errors.New("unknown module")
|
||||
}
|
||||
|
||||
helper, ok := module.(Helper)
|
||||
if !ok {
|
||||
return new(wasiv1.HelpResponse), nil
|
||||
}
|
||||
|
||||
modHelp := helper.Help()
|
||||
helpResponse := &wasiv1.HelpResponse{
|
||||
Name: modHelp.Name,
|
||||
Description: modHelp.Description,
|
||||
Examples: make([]*wasiv1.TaskExample, 0, len(modHelp.Examples)),
|
||||
}
|
||||
|
||||
for _, e := range modHelp.Examples {
|
||||
modSpec, err := protocol.Marshal(e.Spec.Module)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
executor.Run(context.Background(), startTask.GetReference().GetName(), module)
|
||||
helpResponse.Examples = append(helpResponse.Examples, &wasiv1.TaskExample{
|
||||
Name: e.Name,
|
||||
Description: e.Description,
|
||||
TaskSpec: &commonv1.TaskSpec{
|
||||
ModuleName: e.Spec.ModuleName,
|
||||
Container: e.Spec.Container,
|
||||
OutputDir: e.Spec.OutputDir,
|
||||
ModuleSpec: modSpec,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return helpResponse, nil
|
||||
}
|
||||
|
||||
func BinaryNameForModule(req *wasiv1.BinaryNameRequest) (*wasiv1.BinaryNameResponse, error) {
|
||||
module := defaultRegistry.Get(req.ModuleReference.ModuleCategory, req.ModuleReference.ModuleType)
|
||||
if module == nil {
|
||||
return nil, errors.New("unknown module")
|
||||
}
|
||||
|
||||
namer, ok := module.(BinaryNamer)
|
||||
if !ok {
|
||||
return new(wasiv1.BinaryNameResponse), nil
|
||||
}
|
||||
|
||||
if err := protocol.Unmarshal(req.GetSpec(), module); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &wasiv1.BinaryNameResponse{
|
||||
Name: namer.BinaryName(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
//export /buildr.rpc.v1.WasiExecutorService/PluginInventory
|
||||
func Inventory(ptr, size uint32) uint64 {
|
||||
return inventoryWrapper.Call(ptr, size)
|
||||
}
|
||||
|
||||
//export /buildr.rpc.v1.WasiExecutorService/StartTask
|
||||
func Run(ptr, size uint32) uint64 {
|
||||
return startTaskWrapper.Call(ptr, size)
|
||||
}
|
||||
|
||||
//export /buildr.rpc.v1.WasiExecutorService/Help
|
||||
func HelpFor(ptr, size uint32) uint64 {
|
||||
return helpForWrapper.Call(ptr, size)
|
||||
}
|
||||
|
||||
//export /buildr.rpc.v1.WasiExecutorService/BinaryName
|
||||
func BinaryNameFor(ptr, size uint32) uint64 {
|
||||
return binaryNameWrapper.Call(ptr, size)
|
||||
}
|
||||
|
|
1
examples/hello_world_go/.gitignore
vendored
1
examples/hello_world_go/.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
*.wasm
|
||||
*_easyjson.go
|
||||
*.mock.go
|
12
examples/hello_world_go/.mockery.yaml
Normal file
12
examples/hello_world_go/.mockery.yaml
Normal file
|
@ -0,0 +1,12 @@
|
|||
inpackage: true
|
||||
with-expecter: true
|
||||
keeptree: false
|
||||
testonly: true
|
||||
|
||||
packages:
|
||||
code.icb4dc0.de/buildr/buildr/modules:
|
||||
interfaces:
|
||||
ExecutionContext:
|
||||
config:
|
||||
dir: mocks/modules
|
||||
filename: execution_context.mock.go
|
|
@ -3,14 +3,19 @@ module hello_world
|
|||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/mailru/easyjson v0.7.7
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
|
||||
code.icb4dc0.de/buildr/wasi-module-sdk-go v0.0.0-20230701111906-1f0c58b1c8a4
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.10.0 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/tetratelabs/tinymem v0.1.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.1.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
@ -1,19 +1,33 @@
|
|||
code.icb4dc0.de/buildr/wasi-module-sdk-go v0.0.0-20230701111906-1f0c58b1c8a4 h1:sDSOMWGtf/c+GGG+K6QR7sa7U+7PFJA5jzaQ2MgpE+8=
|
||||
code.icb4dc0.de/buildr/wasi-module-sdk-go v0.0.0-20230701111906-1f0c58b1c8a4/go.mod h1:4oTtECbg97YmFN2UHoHj4D59Cgq8/GACMjWeRpcyX4o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tetratelabs/tinymem v0.1.0 h1:Qza1JAg9lquPPJ/CIei5qQYx7t18KLie83O2WR6CM58=
|
||||
github.com/tetratelabs/tinymem v0.1.0/go.mod h1:WFFTZFhLod6lTL+UetFAopVbGaB+KFsVcIY+RUv7NeY=
|
||||
github.com/tetratelabs/wazero v1.1.0 h1:EByoAhC+QcYpwSZJSs/aV0uokxPwBgKxfiokSUwAknQ=
|
||||
github.com/tetratelabs/wazero v1.1.0/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 h1:EDuYyU/MkFXllv9QF9819VlI9a4tzGuCbhG0ExK9o1U=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
//go:generate tinygo build -o hello_world.wasm -scheduler=none -gc=leaking --no-debug -target=wasi ../../main.go
|
||||
|
||||
package integration_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"log/slog"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"code.icb4dc0.de/buildr/buildr/modules"
|
||||
mm "hello_world/mocks/modules"
|
||||
)
|
||||
|
||||
//go:embed hello_world.wasm
|
||||
var payload []byte
|
||||
|
||||
func TestModule(t *testing.T) {
|
||||
mod := plugin.Module{
|
||||
PluginCategory: modules.CategoryTask,
|
||||
PluginType: "hello_world",
|
||||
PluginPayload: plugin.MemoryPayload(payload),
|
||||
}.WithSpec(map[string]any{
|
||||
"Name": "Ted",
|
||||
})
|
||||
|
||||
testCtx := context.Background()
|
||||
mockCtx := mm.NewMockExecutionContext(t)
|
||||
|
||||
mockCtx.EXPECT().Value(mock.Anything).RunAndReturn(func(key any) any {
|
||||
return testCtx.Value(key)
|
||||
})
|
||||
|
||||
mockCtx.EXPECT().Done().RunAndReturn(func() <-chan struct{} {
|
||||
return testCtx.Done()
|
||||
})
|
||||
|
||||
mockCtx.EXPECT().BinariesDir().Return(t.TempDir())
|
||||
mockCtx.EXPECT().WorkingDir().Return(t.TempDir())
|
||||
mockCtx.EXPECT().OutDir().Return(t.TempDir())
|
||||
mockCtx.EXPECT().Name().Return("integration_test")
|
||||
mockCtx.EXPECT().StdOut().Return(os.Stdout)
|
||||
mockCtx.EXPECT().StdErr().Return(os.Stderr)
|
||||
mockCtx.EXPECT().Logger().Return(slog.Default()).Maybe()
|
||||
|
||||
if err := mod.Execute(mockCtx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package main
|
||||
|
||||
//go:generate tinygo build -o hello_world.wasm -scheduler=none -gc=leaking --no-debug -target=wasi main.go
|
||||
//go:generate mockery
|
||||
|
||||
import (
|
||||
sdk "code.icb4dc0.de/buildr/wasi-module-sdk-go"
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
package main_test
|
||||
|
||||
import (
|
||||
sdk "code.icb4dc0.de/buildr/wasi-module-sdk-go"
|
||||
"code.icb4dc0.de/buildr/wasi-module-sdk-go/integration"
|
||||
"context"
|
||||
_ "embed"
|
||||
"golang.org/x/exp/slog"
|
||||
"testing"
|
||||
)
|
||||
|
||||
//go:embed hello_world.wasm
|
||||
var payload []byte
|
||||
|
||||
func TestModule(t *testing.T) {
|
||||
h := integration.NewHost(
|
||||
slog.New(slog.NewTextHandler(integration.NewTestWriter(t))),
|
||||
integration.WithState(integration.StateKey(sdk.CategoryTask, "test", "hello"), []byte("world")),
|
||||
)
|
||||
|
||||
s := integration.TestSpec{
|
||||
ModuleCategory: sdk.CategoryTask,
|
||||
ModuleType: "hello_world",
|
||||
ModuleName: "test",
|
||||
RawTaskSpec: []byte(`{"Name": "Ted"}`),
|
||||
}
|
||||
|
||||
if err := h.Run(context.Background(), payload, s); err != nil {
|
||||
t.Errorf("Failed to run module: %v", err)
|
||||
}
|
||||
}
|
|
@ -1,13 +1,20 @@
|
|||
package module
|
||||
|
||||
//go:generate go run -mod=mod github.com/mailru/easyjson/easyjson -all hello_world.go
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
rpcv1 "code.icb4dc0.de/buildr/api/generated/rpc/v1"
|
||||
"code.icb4dc0.de/buildr/wasi-module-sdk-go/exec"
|
||||
|
||||
sdk "code.icb4dc0.de/buildr/wasi-module-sdk-go"
|
||||
"golang.org/x/exp/slog"
|
||||
)
|
||||
|
||||
var _ sdk.Module = (*HelloWorld)(nil)
|
||||
var (
|
||||
_ sdk.Module = (*HelloWorld)(nil)
|
||||
_ sdk.Helper = (*HelloWorld)(nil)
|
||||
)
|
||||
|
||||
type HelloWorld struct {
|
||||
Name string
|
||||
|
@ -16,18 +23,28 @@ type HelloWorld struct {
|
|||
func (h HelloWorld) Execute(ctx sdk.ExecutionContext) error {
|
||||
logger := ctx.Logger()
|
||||
|
||||
logger.Info("Executing hello world")
|
||||
logger.Info("Executing hello world", slog.String("name", h.Name))
|
||||
|
||||
val, _, err := ctx.GetState(ctx, "hello")
|
||||
if err != nil {
|
||||
if f, err := os.CreateTemp(ctx.OutDir(), "hello_world.*.txt"); err != nil {
|
||||
return err
|
||||
} else if err = f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ctx.SetState(ctx, "state", []byte(`{"hello":"world"}`)); err != nil {
|
||||
if foundPath, err := exec.LookPath("go"); err != nil {
|
||||
return err
|
||||
} else {
|
||||
logger.Info("found path for go", slog.String("path", foundPath))
|
||||
}
|
||||
|
||||
cmd := exec.NewCommand("/bin/bash", "-c", `set -ex; echo "Hello process execution!"`)
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("Got value from state", slog.String("value", string(val)))
|
||||
fmt.Println("Hello world")
|
||||
_, _ = fmt.Fprint(ctx.StdOut(), "Hello world via pipeline")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -39,3 +56,35 @@ func (HelloWorld) Category() sdk.Category {
|
|||
func (HelloWorld) Type() string {
|
||||
return "hello_world"
|
||||
}
|
||||
|
||||
func (h HelloWorld) Help() sdk.Help {
|
||||
return sdk.Help{
|
||||
Name: "Hello World",
|
||||
Description: `Example to illustrate how to use the Buildr plugin API.`,
|
||||
Examples: []sdk.Example{
|
||||
{
|
||||
Name: "Simple example",
|
||||
Description: `well, you know, hello world`,
|
||||
Spec: sdk.TaskSpec[sdk.Module]{
|
||||
ModuleName: "hello_world",
|
||||
Module: HelloWorld{
|
||||
Name: "Ted Tester",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Container example",
|
||||
Description: `well, you know, hello world, but in a container!`,
|
||||
Spec: sdk.TaskSpec[sdk.Module]{
|
||||
ModuleName: "hello_world",
|
||||
Module: HelloWorld{
|
||||
Name: "Paul Player",
|
||||
},
|
||||
Container: &rpcv1.ContainerSpec{
|
||||
Image: "busybox",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
87
exec/command.go
Normal file
87
exec/command.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
package exec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
wasiv1 "code.icb4dc0.de/buildr/api/generated/wasi/v1"
|
||||
"code.icb4dc0.de/buildr/wasi-module-sdk-go/mem"
|
||||
)
|
||||
|
||||
func NewCommand(name string, args ...string) *Command {
|
||||
return &Command{
|
||||
Name: name,
|
||||
Args: args,
|
||||
}
|
||||
}
|
||||
|
||||
type Command struct {
|
||||
Name string
|
||||
Args []string
|
||||
WorkingDir string
|
||||
Env map[string]string
|
||||
StdIn io.Reader
|
||||
}
|
||||
|
||||
func (c *Command) AddEnv(envToAdd map[string]string) {
|
||||
if envToAdd == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if c.Env == nil {
|
||||
c.Env = make(map[string]string)
|
||||
}
|
||||
|
||||
for k, v := range envToAdd {
|
||||
c.Env[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Command) Run() error {
|
||||
execReq := &wasiv1.ProcessStartRequest{
|
||||
Command: c.Name,
|
||||
Args: c.Args,
|
||||
WorkingDirectory: c.WorkingDir,
|
||||
Environment: c.Env,
|
||||
}
|
||||
|
||||
if c.StdIn != nil {
|
||||
if stdInData, err := io.ReadAll(c.StdIn); err != nil {
|
||||
return err
|
||||
} else {
|
||||
execReq.Stdin = stdInData
|
||||
}
|
||||
}
|
||||
|
||||
data, err := execReq.MarshalVT()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := _exec(mem.DataToManagedPtr(data))
|
||||
if result == 0 {
|
||||
return fmt.Errorf("failed to execute command: %s", c.Name)
|
||||
}
|
||||
|
||||
resultPtr := uint32(result >> 32)
|
||||
resultSize := uint32(result)
|
||||
|
||||
if resultSize == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
execResp := new(wasiv1.ProcessStartResponse)
|
||||
if err := execResp.UnmarshalVT(mem.PtrToData(resultPtr, resultSize)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if execResp.ExitCode != 0 {
|
||||
return &Error{
|
||||
ExitCode: int(execResp.ExitCode),
|
||||
Message: execResp.Error,
|
||||
StdErr: execResp.Stderr,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
15
exec/err.go
Normal file
15
exec/err.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package exec
|
||||
|
||||
import "fmt"
|
||||
|
||||
var _ error = (*Error)(nil)
|
||||
|
||||
type Error struct {
|
||||
ExitCode int
|
||||
Message string
|
||||
StdErr []byte
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf("command exited with code %d: %s - %s", e.ExitCode, e.Message, e.StdErr)
|
||||
}
|
7
exec/imports_stub.go
Normal file
7
exec/imports_stub.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
//go:build !wasi
|
||||
|
||||
package exec
|
||||
|
||||
func _exec(ptr, size uint32) (ptrSize uint64) { return 0 }
|
||||
|
||||
func _lookPath(ptr, size uint32) (ptrSize uint64) { return 0 }
|
11
exec/imports_wasi.go
Normal file
11
exec/imports_wasi.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
//go:build wasi
|
||||
|
||||
package exec
|
||||
|
||||
//go:wasm-module buildr
|
||||
//go:wasmimport buildr exec
|
||||
func _exec(ptr, size uint32) (ptrSize uint64)
|
||||
|
||||
//go:wasm-module buildr
|
||||
//go:wasmimport buildr lookPath
|
||||
func _lookPath(ptr, size uint32) (ptrSize uint64)
|
43
exec/lookup.go
Normal file
43
exec/lookup.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package exec
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
wasiv1 "code.icb4dc0.de/buildr/api/generated/wasi/v1"
|
||||
"code.icb4dc0.de/buildr/wasi-module-sdk-go/mem"
|
||||
)
|
||||
|
||||
func LookPath(file string) (string, error) {
|
||||
lookupPathReq := &wasiv1.LookupPathRequest{
|
||||
Command: file,
|
||||
}
|
||||
|
||||
data, err := lookupPathReq.MarshalVT()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
result := _lookPath(mem.DataToManagedPtr(data))
|
||||
if result == 0 {
|
||||
return "", fmt.Errorf("failed to lookup in path: %s", file)
|
||||
}
|
||||
|
||||
resultPtr := uint32(result >> 32)
|
||||
resultSize := uint32(result)
|
||||
|
||||
if resultSize == 0 {
|
||||
return "", fmt.Errorf("failed to lookup in path: %s", file)
|
||||
}
|
||||
|
||||
lookupPathResp := new(wasiv1.LookupPathResponse)
|
||||
if err := lookupPathResp.UnmarshalVT(mem.PtrToData(resultPtr, resultSize)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(lookupPathResp.Error) > 0 {
|
||||
return "", errors.New(lookupPathResp.Error)
|
||||
}
|
||||
|
||||
return lookupPathResp.Path, nil
|
||||
}
|
10
executor.go
10
executor.go
|
@ -2,7 +2,7 @@ package sdk
|
|||
|
||||
import (
|
||||
"context"
|
||||
"golang.org/x/exp/slog"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
func NewExecutor(repoRoot, outDir, binDir string) Executor {
|
||||
|
@ -21,9 +21,7 @@ type Executor struct {
|
|||
binDir string
|
||||
}
|
||||
|
||||
func (e Executor) Run(ctx context.Context, modName string, m Module) {
|
||||
execCtx := newWasiExecutionContext(ctx, e.logger, modName, m, e.repoRoot, e.binDir, e.outDir)
|
||||
if err := m.Execute(execCtx); err != nil {
|
||||
e.logger.Error("Failed to execute module", slog.String("err", err.Error()))
|
||||
}
|
||||
func (e Executor) Run(ctx context.Context, m Module) error {
|
||||
execCtx := newWasiExecutionContext(ctx, e.logger, m, e.repoRoot, e.binDir, e.outDir)
|
||||
return m.Execute(execCtx)
|
||||
}
|
||||
|
|
31
export.go
Normal file
31
export.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package sdk
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
bapi "code.icb4dc0.de/buildr/api"
|
||||
"code.icb4dc0.de/buildr/wasi-module-sdk-go/mem"
|
||||
)
|
||||
|
||||
type FuncExportWrapper[TRequest bapi.ProtoMessage, TResponse bapi.ProtoMessage] func(req TRequest) (TResponse, error)
|
||||
|
||||
func (w FuncExportWrapper[TRequest, TResponse]) Call(reqPtr, reqSize uint32) uint64 {
|
||||
var req TRequest
|
||||
if reflect.TypeOf(req).Kind() == reflect.Ptr {
|
||||
req = reflect.New(reflect.TypeOf(req).Elem()).Interface().(TRequest)
|
||||
}
|
||||
|
||||
if err := req.UnmarshalVT(mem.DataFromPtr(reqPtr, reqSize)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if resp, err := w(req); err != nil {
|
||||
panic(err)
|
||||
} else if data, err := resp.MarshalVT(); err != nil {
|
||||
panic(err)
|
||||
} else if len(data) == 0 {
|
||||
return 0
|
||||
} else {
|
||||
return mem.UnifyPtrSize(mem.DataToUnmanagedPtr(data))
|
||||
}
|
||||
}
|
10
go.mod
10
go.mod
|
@ -1,15 +1,13 @@
|
|||
module code.icb4dc0.de/buildr/wasi-module-sdk-go
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/mailru/easyjson v0.7.7
|
||||
github.com/tetratelabs/tinymem v0.1.0
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
|
||||
google.golang.org/protobuf v1.30.0
|
||||
code.icb4dc0.de/buildr/api v0.0.0-20230912160519-4b705a6732bc
|
||||
code.icb4dc0.de/buildr/common v0.0.0-20230912160755-da17cbfde028
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
)
|
||||
|
|
20
go.sum
20
go.sum
|
@ -1,16 +1,16 @@
|
|||
code.icb4dc0.de/buildr/api v0.0.0-20230905195458-e4d230e5c7fd h1:N4WkVoqphjFXfFwfKpLcEtS5lBCfd9NVytUutiszQ90=
|
||||
code.icb4dc0.de/buildr/api v0.0.0-20230905195458-e4d230e5c7fd/go.mod h1:eWSjeX25XbbNGKlxVoOf2a8V6xOCIaQh5W65T7nNcL8=
|
||||
code.icb4dc0.de/buildr/api v0.0.0-20230912160519-4b705a6732bc h1:NpOz5K0Oo/WFWBRsBKf92MTX9iW+sqaImwMuU33z6/s=
|
||||
code.icb4dc0.de/buildr/api v0.0.0-20230912160519-4b705a6732bc/go.mod h1:eWSjeX25XbbNGKlxVoOf2a8V6xOCIaQh5W65T7nNcL8=
|
||||
code.icb4dc0.de/buildr/common v0.0.0-20230905195627-a3d5bb4f1ee7 h1:sqBO2IFI0Gs+FiM4kkQjj7QkC0p+AhJKqhCQqCEwlM4=
|
||||
code.icb4dc0.de/buildr/common v0.0.0-20230905195627-a3d5bb4f1ee7/go.mod h1:biIRy/mBiVPAR/okWN5DFmB3Hnnxt2FP1Qswa4GBMeE=
|
||||
code.icb4dc0.de/buildr/common v0.0.0-20230912160755-da17cbfde028 h1:12xr1rT8faxqY7QqI7Z4nlKw1UdQZNfAtv4FybTPtwk=
|
||||
code.icb4dc0.de/buildr/common v0.0.0-20230912160755-da17cbfde028/go.mod h1:GMFttKFr14bCsjGHbq1tUCOuru7AqBkELvsk2Xhn0Bw=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/tetratelabs/tinymem v0.1.0 h1:Qza1JAg9lquPPJ/CIei5qQYx7t18KLie83O2WR6CM58=
|
||||
github.com/tetratelabs/tinymem v0.1.0/go.mod h1:WFFTZFhLod6lTL+UetFAopVbGaB+KFsVcIY+RUv7NeY=
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
|
|
13
help.go
Normal file
13
help.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package sdk
|
||||
|
||||
type Example struct {
|
||||
Spec TaskSpec[Module]
|
||||
Name string
|
||||
Description string
|
||||
}
|
||||
|
||||
type Help struct {
|
||||
Name string
|
||||
Description string
|
||||
Examples []Example
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
package sdk
|
||||
|
||||
func _log_msg(ptr, size uint32) {}
|
||||
func _log_msg(ptr, size uint32) (ptrSize uint64) { return 0 }
|
||||
|
||||
func _get_state(ptr, size uint32) (ptrSize uint64) { return 0 }
|
||||
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
package sdk
|
||||
|
||||
//go:wasm-module buildr
|
||||
//export log_msg
|
||||
func _log_msg(ptr, size uint32)
|
||||
//go:wasmimport buildr log_msg
|
||||
func _log_msg(ptr, size uint32) (ptrSize uint64)
|
||||
|
||||
//go:wasm-module buildr
|
||||
//export get_state
|
||||
//go:wasmimport buildr get_state
|
||||
func _get_state(ptr, size uint32) (ptrSize uint64)
|
||||
|
||||
//go:wasm-module buildr
|
||||
//export set_state
|
||||
//go:wasmimport buildr set_state
|
||||
func _set_state(ptr, size uint32) (ptrSize uint64)
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
# Integration module
|
||||
|
||||
The integration module is meant as a helper to blackbox test a WASI module.
|
||||
It's a shortcut to instantiate and execute a module in the context of buildr.
|
||||
|
||||
The workflow basically looks like this:
|
||||
|
||||
1. Compile module e.g. with tinygo to `*.wasm` file
|
||||
1. Create a `integration.Host` (with `integration.NewHost(...)` function)
|
||||
1. Specify the scenario (category, type, ...)
|
||||
1. Execute the module
|
||||
|
||||
A very basic example can be found in the [`hello_world_go` example](../examples/hello_world_go/main_test.go)
|
|
@ -1,16 +0,0 @@
|
|||
module code.icb4dc0.de/buildr/wasi-module-sdk-go/integration
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/tetratelabs/wazero v1.1.0
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/tetratelabs/tinymem v0.1.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
)
|
|
@ -1,19 +0,0 @@
|
|||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/tetratelabs/tinymem v0.1.0 h1:Qza1JAg9lquPPJ/CIei5qQYx7t18KLie83O2WR6CM58=
|
||||
github.com/tetratelabs/tinymem v0.1.0/go.mod h1:WFFTZFhLod6lTL+UetFAopVbGaB+KFsVcIY+RUv7NeY=
|
||||
github.com/tetratelabs/wazero v1.1.0 h1:EByoAhC+QcYpwSZJSs/aV0uokxPwBgKxfiokSUwAknQ=
|
||||
github.com/tetratelabs/wazero v1.1.0/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
|
||||
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
|
@ -1,23 +0,0 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var _ io.Writer = (*TestWriter)(nil)
|
||||
|
||||
func NewTestWriter(tb testing.TB) TestWriter {
|
||||
return TestWriter{
|
||||
TB: tb,
|
||||
}
|
||||
}
|
||||
|
||||
type TestWriter struct {
|
||||
TB testing.TB
|
||||
}
|
||||
|
||||
func (t TestWriter) Write(p []byte) (n int, err error) {
|
||||
t.TB.Log(string(p))
|
||||
return len(p), nil
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
type Message interface {
|
||||
MarshalVT() (dAtA []byte, err error)
|
||||
UnmarshalVT(dAtA []byte) error
|
||||
}
|
||||
|
||||
func newMemoryManager(mod api.Module) *memoryManager {
|
||||
return &memoryManager{
|
||||
allocate: mod.ExportedFunction("malloc"),
|
||||
deallocate: mod.ExportedFunction("free"),
|
||||
}
|
||||
}
|
||||
|
||||
type memoryManager struct {
|
||||
allocate api.Function
|
||||
deallocate api.Function
|
||||
danglingAllocations []uint64
|
||||
}
|
||||
|
||||
func (m *memoryManager) Deallocate(ctx context.Context, ptr uint64) error {
|
||||
_, err := m.deallocate.Call(ctx, ptr)
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *memoryManager) WriteMessage(ctx context.Context, mod api.Module, msg Message) (uint64, error) {
|
||||
data, err := msg.MarshalVT()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
ptr, err := m.Allocate(ctx, uint64(len(data)))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if !mod.Memory().Write(uint32(ptr), data) {
|
||||
return 0, errors.New("failed to write message to memory")
|
||||
}
|
||||
|
||||
return (ptr << uint64(32)) | uint64(len(data)), nil
|
||||
}
|
||||
|
||||
func (m *memoryManager) Allocate(ctx context.Context, size uint64) (ptr uint64, err error) {
|
||||
results, err := m.allocate.Call(ctx, size)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
m.danglingAllocations = append(m.danglingAllocations, results[0])
|
||||
|
||||
return results[0], nil
|
||||
}
|
||||
|
||||
func (m *memoryManager) WithMem(ctx context.Context, size uint64, delegate func(ptr uint64) error) error {
|
||||
results, err := m.allocate.Call(ctx, size)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer m.deallocate.Call(ctx, results[0])
|
||||
|
||||
return delegate(results[0])
|
||||
}
|
||||
|
||||
func (m *memoryManager) Close() (err error) {
|
||||
ctx := context.Background()
|
||||
|
||||
for i := range m.danglingAllocations {
|
||||
_, e := m.deallocate.Call(ctx, m.danglingAllocations[i])
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
|
@ -1,276 +0,0 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"code.icb4dc0.de/buildr/wasi-module-sdk-go/mem"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
|
||||
"golang.org/x/exp/slog"
|
||||
|
||||
sdk "code.icb4dc0.de/buildr/wasi-module-sdk-go"
|
||||
rpcv1 "code.icb4dc0.de/buildr/wasi-module-sdk-go/protocol/generated/rpc/v1"
|
||||
)
|
||||
|
||||
func StateKey(cat sdk.Category, modName, key string) string {
|
||||
h := md5.New()
|
||||
_, _ = h.Write([]byte(cat.String()))
|
||||
_, _ = h.Write([]byte(modName))
|
||||
_, _ = h.Write([]byte(key))
|
||||
|
||||
return string(h.Sum(nil))
|
||||
}
|
||||
|
||||
type TestSpec struct {
|
||||
ModuleCategory sdk.Category
|
||||
ModuleType string
|
||||
ModuleName string
|
||||
RawTaskSpec []byte
|
||||
}
|
||||
|
||||
type HostOption func(h *Host)
|
||||
|
||||
func WithState(key string, value []byte) HostOption {
|
||||
return func(h *Host) {
|
||||
h.state[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
func NewHost(logger *slog.Logger, opts ...HostOption) *Host {
|
||||
h := &Host{
|
||||
Logger: logger,
|
||||
state: make(map[string][]byte),
|
||||
}
|
||||
|
||||
for i := range opts {
|
||||
opts[i](h)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
type Host struct {
|
||||
memMgr *memoryManager
|
||||
state map[string][]byte
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
func (h *Host) Run(ctx context.Context, wasiPayload []byte, spec TestSpec) (err error) {
|
||||
runtimeConfig := wazero.NewRuntimeConfig().
|
||||
WithCloseOnContextDone(true)
|
||||
|
||||
r := wazero.NewRuntimeWithConfig(ctx, runtimeConfig)
|
||||
defer func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
err = errors.Join(err, r.Close(ctx))
|
||||
}()
|
||||
|
||||
_, err = r.NewHostModuleBuilder("buildr").
|
||||
NewFunctionBuilder().WithFunc(h.log).Export("log_msg").
|
||||
NewFunctionBuilder().WithFunc(h.getState).Export("get_state").
|
||||
NewFunctionBuilder().WithFunc(h.setState).Export("set_state").
|
||||
Instantiate(ctx)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
closer, err := wasi_snapshot_preview1.Instantiate(ctx, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = errors.Join(err, closer.Close(context.Background()))
|
||||
}()
|
||||
|
||||
config := wazero.NewFSConfig().
|
||||
WithDirMount(".", "/work")
|
||||
|
||||
moduleConfig := wazero.NewModuleConfig().
|
||||
WithStdout(os.Stdout).
|
||||
WithStderr(os.Stderr).
|
||||
WithFSConfig(config)
|
||||
|
||||
mod, err := r.InstantiateWithConfig(ctx, wasiPayload, moduleConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.memMgr = newMemoryManager(mod)
|
||||
|
||||
inv, err := h.getInventory(ctx, mod)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, ref := range inv {
|
||||
fmt.Printf("%s/%s/n", ref.Category, ref.Type)
|
||||
}
|
||||
|
||||
startTask := &rpcv1.StartTaskRequest{
|
||||
Reference: &rpcv1.TaskReference{
|
||||
Id: uuid.NewString(),
|
||||
Module: &rpcv1.ModuleReference{
|
||||
ModuleCategory: spec.ModuleCategory.String(),
|
||||
ModuleType: spec.ModuleType,
|
||||
},
|
||||
Name: spec.ModuleName,
|
||||
},
|
||||
Buildr: &rpcv1.Buildr{
|
||||
Repo: &rpcv1.Buildr_Repo{
|
||||
Root: "/work",
|
||||
},
|
||||
},
|
||||
RawTask: spec.RawTaskSpec,
|
||||
}
|
||||
|
||||
data, err := startTask.MarshalVT()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
run := mod.ExportedFunction("run")
|
||||
|
||||
defer func() {
|
||||
err = errors.Join(err, h.memMgr.Close())
|
||||
}()
|
||||
|
||||
err = h.memMgr.WithMem(ctx, uint64(len(data)), func(ptr uint64) error {
|
||||
if !mod.Memory().Write(uint32(ptr), data) {
|
||||
return errors.New("failed to write to memory")
|
||||
}
|
||||
|
||||
_, err = run.Call(ctx, ptr, uint64(len(data)))
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *Host) getInventory(ctx context.Context, mod api.Module) (refs []sdk.Reference, err error) {
|
||||
result, err := mod.ExportedFunction("inventory").Call(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ptr, size := mem.UnifiedPtrToSizePtr(result[0])
|
||||
if ptr == 0 {
|
||||
return nil, errors.New("failed to get inventory - 0 pointer")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = errors.Join(err, h.memMgr.Deallocate(ctx, uint64(ptr)))
|
||||
}()
|
||||
|
||||
data, ok := mod.Memory().Read(ptr, size)
|
||||
if !ok {
|
||||
return nil, errors.New("failed to get inventory")
|
||||
}
|
||||
|
||||
var inventory rpcv1.PluginInventory
|
||||
if err = inventory.UnmarshalVT(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
refs = make([]sdk.Reference, 0, len(inventory.Modules))
|
||||
for _, m := range inventory.Modules {
|
||||
refs = append(refs, sdk.Reference{
|
||||
Category: sdk.Category(m.GetModuleCategory()),
|
||||
Type: m.GetModuleType(),
|
||||
})
|
||||
}
|
||||
|
||||
return refs, nil
|
||||
}
|
||||
|
||||
func (h *Host) getState(ctx context.Context, m api.Module, offset, byteCount uint32) uint64 {
|
||||
if h.state == nil {
|
||||
h.state = make(map[string][]byte)
|
||||
}
|
||||
|
||||
buf, ok := m.Memory().Read(offset, byteCount)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
getStateReq := new(rpcv1.GetStateRequest)
|
||||
if err := getStateReq.UnmarshalVT(buf); err != nil {
|
||||
h.Logger.Error("failed to unmarshal getStateRequest", slog.String("err", err.Error()))
|
||||
return 0
|
||||
}
|
||||
|
||||
resp := new(rpcv1.GetStateResponse)
|
||||
resp.Data, _ = h.state[string(getStateReq.Key)]
|
||||
|
||||
if ptr, err := h.memMgr.WriteMessage(ctx, m, resp); err != nil {
|
||||
h.Logger.Error("Failed to write message", slog.String("err", err.Error()))
|
||||
return 0
|
||||
} else {
|
||||
return ptr
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Host) setState(ctx context.Context, m api.Module, offset, byteCount uint32) (result uint64) {
|
||||
if h.state == nil {
|
||||
h.state = make(map[string][]byte)
|
||||
}
|
||||
|
||||
buf, ok := m.Memory().Read(offset, byteCount)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
setState := new(rpcv1.SetState)
|
||||
if err := setState.UnmarshalVT(buf); err != nil {
|
||||
h.Logger.Error("failed to unmarshal SetState", slog.String("err", err.Error()))
|
||||
return 0
|
||||
}
|
||||
|
||||
var resp rpcv1.Result
|
||||
if len(setState.Key) < 1 {
|
||||
resp.Error = "key might not be empty"
|
||||
} else {
|
||||
h.state[string(setState.Key)] = setState.Data
|
||||
resp.Success = true
|
||||
}
|
||||
|
||||
if ptr, err := h.memMgr.WriteMessage(ctx, m, &resp); err != nil {
|
||||
h.Logger.Error("Failed to write message", slog.String("err", err.Error()))
|
||||
return 0
|
||||
} else {
|
||||
return ptr
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Host) log(ctx context.Context, m api.Module, offset, byteCount uint32) {
|
||||
buf, ok := m.Memory().Read(offset, byteCount)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
taskLog := new(rpcv1.TaskLog)
|
||||
if err := taskLog.UnmarshalVT(buf); err != nil {
|
||||
h.Logger.Warn("failed to unmarshal task log", slog.String("err", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
rec := slog.NewRecord(time.UnixMicro(taskLog.Time), slog.Level(taskLog.Level), taskLog.Message, 0)
|
||||
|
||||
for i := range taskLog.Attributes {
|
||||
attr := taskLog.Attributes[i]
|
||||
rec.AddAttrs(slog.String(attr.Key, attr.Value))
|
||||
}
|
||||
|
||||
_ = h.Logger.Handler().Handle(ctx, rec)
|
||||
}
|
51
json_state_encoder.go
Normal file
51
json_state_encoder.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package sdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type Encoder[T any] interface {
|
||||
Get(ctx context.Context, key string) (val T, ok bool, meta StateMetadata, err error)
|
||||
Set(ctx context.Context, key string, val T) error
|
||||
}
|
||||
|
||||
var _ Encoder[struct{}] = (*JSONStateEncoder[struct{}])(nil)
|
||||
|
||||
func NewJSONStateEncoder[T any](ctx ExecutionContext) *JSONStateEncoder[T] {
|
||||
return &JSONStateEncoder[T]{
|
||||
Context: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
type JSONStateEncoder[T any] struct {
|
||||
Context ExecutionContext
|
||||
}
|
||||
|
||||
func (j JSONStateEncoder[T]) Get(ctx context.Context, key string) (val T, ok bool, meta StateMetadata, err error) {
|
||||
var data []byte
|
||||
|
||||
data, meta, err = j.Context.GetState(ctx, key)
|
||||
if err != nil {
|
||||
return val, false, StateMetadata{}, err
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
return val, false, meta, nil
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &val); err != nil {
|
||||
return val, false, StateMetadata{}, err
|
||||
}
|
||||
|
||||
return val, true, meta, nil
|
||||
}
|
||||
|
||||
func (j JSONStateEncoder[T]) Set(ctx context.Context, key string, val T) error {
|
||||
data, err := json.Marshal(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return j.Context.SetState(ctx, key, data)
|
||||
}
|
35
logger.go
35
logger.go
|
@ -5,11 +5,10 @@ import "C"
|
|||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"golang.org/x/exp/slog"
|
||||
|
||||
remotev1 "code.icb4dc0.de/buildr/api/generated/remote/v1"
|
||||
"code.icb4dc0.de/buildr/wasi-module-sdk-go/mem"
|
||||
rpcv1 "code.icb4dc0.de/buildr/wasi-module-sdk-go/protocol/generated/rpc/v1"
|
||||
)
|
||||
|
||||
var _ slog.Handler = (*WASIHandler)(nil)
|
||||
|
@ -28,16 +27,16 @@ func (h WASIHandler) Enabled(_ context.Context, level slog.Level) bool {
|
|||
return h.Level <= level
|
||||
}
|
||||
|
||||
func (h WASIHandler) Handle(ctx context.Context, record slog.Record) error {
|
||||
taskLog := rpcv1.TaskLog{
|
||||
func (h WASIHandler) Handle(_ context.Context, record slog.Record) error {
|
||||
taskLog := remotev1.TaskLog{
|
||||
Time: record.Time.UnixMicro(),
|
||||
Message: record.Message,
|
||||
Level: int32(record.Level),
|
||||
Attributes: make([]*rpcv1.TaskLog_LogAttribute, 0, record.NumAttrs()),
|
||||
Attributes: make([]*remotev1.TaskLog_LogAttribute, 0, record.NumAttrs()),
|
||||
}
|
||||
|
||||
record.Attrs(func(attr slog.Attr) bool {
|
||||
taskLog.Attributes = append(taskLog.Attributes, &rpcv1.TaskLog_LogAttribute{
|
||||
taskLog.Attributes = append(taskLog.Attributes, &remotev1.TaskLog_LogAttribute{
|
||||
Key: attr.Key,
|
||||
Value: attr.Value.String(),
|
||||
})
|
||||
|
@ -45,13 +44,10 @@ func (h WASIHandler) Handle(ctx context.Context, record slog.Record) error {
|
|||
return true
|
||||
})
|
||||
|
||||
data, err := taskLog.MarshalVT()
|
||||
if err != nil {
|
||||
if _, err := LogMsg(&taskLog); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_log_msg(mem.DataToManagedPtr(data))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -78,3 +74,20 @@ func (h WASIHandler) WithGroup(name string) slog.Handler {
|
|||
|
||||
return newHandler
|
||||
}
|
||||
|
||||
func LogMsg(taskLog *remotev1.TaskLog) (*remotev1.Result, error) {
|
||||
data, err := taskLog.MarshalVT()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resultPtr := _log_msg(mem.DataToManagedPtr(data))
|
||||
|
||||
data = mem.PtrToData(mem.UnifiedPtrToSizePtr(resultPtr))
|
||||
resp := new(remotev1.Result)
|
||||
|
||||
if err := resp.UnmarshalVT(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
package mem
|
||||
|
||||
// #include <stdlib.h>
|
||||
import "C"
|
||||
import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
import "unsafe"
|
||||
var (
|
||||
alivePointers = map[uintptr][]byte{}
|
||||
pointersLock sync.Mutex
|
||||
)
|
||||
|
||||
func DataToManagedPtr(data []byte) (ptr uint32, size uint32) {
|
||||
uptr := unsafe.Pointer(unsafe.SliceData(data))
|
||||
|
@ -11,10 +16,16 @@ func DataToManagedPtr(data []byte) (ptr uint32, size uint32) {
|
|||
}
|
||||
|
||||
func DataToUnmanagedPtr(data []byte) (uint32, uint32) {
|
||||
size := C.ulong(len(data))
|
||||
ptr := unsafe.Pointer(C.malloc(size))
|
||||
copy(unsafe.Slice((*byte)(ptr), len(data)), data)
|
||||
return uint32(uintptr(ptr)), uint32(size)
|
||||
pointersLock.Lock()
|
||||
defer pointersLock.Unlock()
|
||||
|
||||
buf := make([]byte, len(data))
|
||||
copy(buf, data)
|
||||
ptr := &buf[0]
|
||||
unsafePtr := uintptr(unsafe.Pointer(ptr))
|
||||
alivePointers[unsafePtr] = buf
|
||||
|
||||
return uint32(unsafePtr), uint32(len(data))
|
||||
}
|
||||
|
||||
func DataFromPtr(ptr, size uint32) []byte {
|
||||
|
@ -33,3 +44,15 @@ func UnifyPtrSize(ptr, size uint32) uint64 {
|
|||
func UnifiedPtrToSizePtr(uint64ptr uint64) (ptr uint32, size uint32) {
|
||||
return uint32(uint64ptr >> 32), uint32(uint64ptr)
|
||||
}
|
||||
|
||||
//export allocate
|
||||
func Allocate(size uint32) uintptr {
|
||||
pointersLock.Lock()
|
||||
defer pointersLock.Unlock()
|
||||
|
||||
buf := make([]byte, size)
|
||||
|
||||
unsafePtr := uintptr(unsafe.Pointer(unsafe.SliceData(buf)))
|
||||
alivePointers[unsafePtr] = buf
|
||||
return unsafePtr
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
23
registry.go
23
registry.go
|
@ -1,8 +1,6 @@
|
|||
package sdk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
@ -23,28 +21,20 @@ func (f RegistrationFunc) RegisterAt(registry *TypeRegistry) {
|
|||
|
||||
func NewTypeRegistry() *TypeRegistry {
|
||||
return &TypeRegistry{
|
||||
registrations: make(map[string]Factory),
|
||||
registrations: make(map[Reference]Factory),
|
||||
}
|
||||
}
|
||||
|
||||
type TypeRegistry struct {
|
||||
lock sync.Mutex
|
||||
registrations map[string]Factory
|
||||
registrations map[Reference]Factory
|
||||
}
|
||||
|
||||
func (r *TypeRegistry) List() (refs []Reference) {
|
||||
refs = make([]Reference, 0, len(r.registrations))
|
||||
|
||||
for k := range r.registrations {
|
||||
split := strings.SplitN(k, "/", 2)
|
||||
if len(split) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
refs = append(refs, Reference{
|
||||
Category: Category(split[0]),
|
||||
Type: split[1],
|
||||
})
|
||||
refs = append(refs, k)
|
||||
}
|
||||
|
||||
return refs
|
||||
|
@ -66,6 +56,9 @@ func (r *TypeRegistry) Get(cat Category, moduleName string) Module {
|
|||
return f.Create()
|
||||
}
|
||||
|
||||
func specOf(cat Category, moduleName string) string {
|
||||
return fmt.Sprintf("%s/%s", cat.String(), moduleName)
|
||||
func specOf(cat Category, moduleName string) Reference {
|
||||
return Reference{
|
||||
Category: cat,
|
||||
Type: moduleName,
|
||||
}
|
||||
}
|
||||
|
|
18
renovate.json
Normal file
18
renovate.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
":dependencyDashboard",
|
||||
":ignoreModulesAndTests",
|
||||
":semanticPrefixFixDepsChoreOthers",
|
||||
":autodetectPinVersions",
|
||||
":prHourlyLimit2",
|
||||
":prConcurrentLimit10",
|
||||
"group:monorepos",
|
||||
"group:recommended",
|
||||
"workarounds:all"
|
||||
],
|
||||
"postUpdateOptions": [
|
||||
"gomodTidy1.17",
|
||||
"gomodUpdateImportPaths"
|
||||
]
|
||||
}
|
|
@ -3,66 +3,68 @@ package sdk
|
|||
import (
|
||||
"errors"
|
||||
|
||||
remotev1 "code.icb4dc0.de/buildr/api/generated/remote/v1"
|
||||
"code.icb4dc0.de/buildr/wasi-module-sdk-go/mem"
|
||||
rpcv1 "code.icb4dc0.de/buildr/wasi-module-sdk-go/protocol/generated/rpc/v1"
|
||||
)
|
||||
|
||||
type StateProxy struct {
|
||||
}
|
||||
type StateProxy struct{}
|
||||
|
||||
func (s StateProxy) Set(key, state []byte) error {
|
||||
setCmd := &rpcv1.SetState{
|
||||
setCmd := &remotev1.SetState{
|
||||
Key: key,
|
||||
Data: state,
|
||||
}
|
||||
data, err := setCmd.MarshalVT()
|
||||
if err != nil {
|
||||
|
||||
if resp, err := SetState(setCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := _set_state(mem.DataToManagedPtr(data))
|
||||
if result == 0 {
|
||||
return errors.New("unknown error occurred")
|
||||
}
|
||||
resultPtr := uint32(result >> 32)
|
||||
resultSize := uint32(result)
|
||||
|
||||
resultMsg := new(rpcv1.Result)
|
||||
if err := resultMsg.UnmarshalVT(mem.PtrToData(resultPtr, resultSize)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resultMsg.Success {
|
||||
if resultMsg.Error != "" {
|
||||
return errors.New(resultMsg.Error)
|
||||
}
|
||||
return errors.New("unknown error occurred")
|
||||
}
|
||||
|
||||
} else if resp.Error != "" {
|
||||
return errors.New(resp.Error)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s StateProxy) Get(key []byte) (state []byte, meta StateMetadata, err error) {
|
||||
getCmd := &rpcv1.GetStateRequest{
|
||||
getCmd := &remotev1.GetStateRequest{
|
||||
Key: key,
|
||||
}
|
||||
|
||||
data, err := getCmd.MarshalVT()
|
||||
if resp, err := GetState(getCmd); err != nil {
|
||||
return nil, StateMetadata{}, err
|
||||
} else {
|
||||
return resp.Data, StateMetadata{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetState(req *remotev1.GetStateRequest) (*remotev1.GetStateResponse, error) {
|
||||
data, err := req.MarshalVT()
|
||||
if err != nil {
|
||||
return nil, StateMetadata{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := _get_state(mem.DataToManagedPtr(data))
|
||||
if result == 0 {
|
||||
return nil, StateMetadata{}, errors.New("error occurred while processing request")
|
||||
}
|
||||
resultPtr := uint32(result >> 32)
|
||||
resultSize := uint32(result)
|
||||
resultPtr := _get_state(mem.DataToManagedPtr(data))
|
||||
data = mem.PtrToData(mem.UnifiedPtrToSizePtr(resultPtr))
|
||||
resp := new(remotev1.GetStateResponse)
|
||||
|
||||
getStateResult := new(rpcv1.GetStateResponse)
|
||||
if err := getStateResult.UnmarshalVT(mem.PtrToData(resultPtr, resultSize)); err != nil {
|
||||
return nil, StateMetadata{}, err
|
||||
if err := resp.UnmarshalVT(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
return getStateResult.Data, StateMetadata{}, nil
|
||||
func SetState(req *remotev1.SetState) (*remotev1.Result, error) {
|
||||
data, err := req.MarshalVT()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resultPtr := _set_state(mem.DataToManagedPtr(data))
|
||||
data = mem.PtrToData(mem.UnifiedPtrToSizePtr(resultPtr))
|
||||
|
||||
resp := new(remotev1.Result)
|
||||
|
||||
if err := resp.UnmarshalVT(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue