Skip to content

Commit

Permalink
feat: support Extism plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
nilslice committed Jan 12, 2024
1 parent 9c7db27 commit 26a05ef
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 21 deletions.
10 changes: 9 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,20 @@ jobs:
if [ "$WARNINGS" != 2 ]; then
exit 1
fi
- name: check output using plugin-sample-wasm
run: |
set +o pipefail
protolock status --plugins=plugin-samples/plugin-sample-wasm/status.wasm | grep "Extism plugin ran"
- name: check output using multiple plugins, with one error expected
run: |
set +o pipefail
protolock status --plugins=plugin-sample,plugin-sample-error | grep "some error"
WASM=plugin-samples/plugin-sample-wasm/status.wasm
protolock status --plugins=plugin-sample,plugin-sample-error,$WASM | grep "some error"
protolock status --plugins=plugin-sample-error,plugin-sample | grep "some error"
protolock status --plugins=plugin-sample,plugin-sample-error,$WASM | grep "Extism plugin ran"
- name: check output using multiple plugins with errors
run: |
set +o pipefail
Expand Down
71 changes: 53 additions & 18 deletions cmd/protolock/plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package main

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"os/exec"
"strings"
"sync"

extism "github.com/extism/go-sdk"
"github.com/nilslice/protolock"
"github.com/nilslice/protolock/extend"
)
Expand Down Expand Up @@ -68,28 +70,61 @@ func runPlugins(
// and updated Protolock structs from the `protolock status` call
go func(name string) {
defer wg.Done()
// output is populated either by the execution of an Extism plugin or a native binary
var output []byte
name = strings.TrimSpace(name)
path, err := exec.LookPath(name)
if err != nil {
if path == "" {
path = name
}
fmt.Println(logPrefix, name, "plugin exec error:", err)
return
}
path := name

// initialize the executable to be called from protolock using the
// absolute path and copy of the input data
plugin := &exec.Cmd{
Path: path,
Stdin: pluginInputData,
if debug {
fmt.Println(logPrefix, name, "running plugin")
}

// execute the plugin and capture the output
output, err := plugin.CombinedOutput()
if err != nil {
pluginErrsChan <- wrapPluginErr(name, path, err, output)
return
if strings.HasSuffix(name, ".wasm") {
// do extism call
manifest := extism.Manifest{
Wasm: []extism.Wasm{extism.WasmFile{Path: name}},
// TODO: consider enabling external configuration to add hosts and paths
// AllowedHosts: []string{},
// AllowedPaths: map[string]string{},
}

plugin, err := extism.NewPlugin(context.Background(), manifest, extism.PluginConfig{EnableWasi: true}, nil)
if err != nil {
fmt.Println(logPrefix, name, "failed to create extism plugin:", err)
return
}

var exitCode uint32
exitCode, output, err = plugin.Call("status", inputData.Bytes())
if err != nil {
fmt.Println(logPrefix, name, "plugin exec error: ", err, "code:", exitCode)
pluginErrsChan <- wrapPluginErr(name, path, err, output)
return
}

} else {
path, err = exec.LookPath(name)
if err != nil {
if path == "" {
path = name
}
fmt.Println(logPrefix, name, "plugin exec error:", err)
return
}

// initialize the executable to be called from protolock using the
// absolute path and copy of the input data
plugin := &exec.Cmd{
Path: path,
Stdin: pluginInputData,
}

// execute the plugin and capture the output
output, err = plugin.CombinedOutput()
if err != nil {
pluginErrsChan <- wrapPluginErr(name, path, err, output)
return
}
}

pluginData := &extend.Data{}
Expand Down
12 changes: 10 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
module github.com/nilslice/protolock

go 1.21.1

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/proto v1.9.1
github.com/stretchr/testify v1.8.4
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/extism/go-sdk v1.0.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.2.2
github.com/tetratelabs/wazero v1.3.0 // indirect
)
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ 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/emicklei/proto v1.9.1 h1:MUgjFo5xlMwYv72TnF5xmmdKZ04u+dVbv6wdARv16D8=
github.com/emicklei/proto v1.9.1/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
github.com/extism/go-sdk v1.0.0 h1://UAyiQGok1ihrlzpkfF6UTY5TwJs6hKJBXnQ0sui20=
github.com/extism/go-sdk v1.0.0/go.mod h1:xUfKSEQndAvHBc1Ohdre0e+UdnRzUpVfbA8QLcx4fbY=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
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/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tetratelabs/wazero v1.3.0 h1:nqw7zCldxE06B8zSZAY0ACrR9OH5QCcPwYmYlwtcwtE=
github.com/tetratelabs/wazero v1.3.0/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ=
9 changes: 9 additions & 0 deletions plugin-samples/plugin-sample-wasm/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module github.com/nilslice/protolock/plugin-samples/plugin-sample-wasm

go 1.21.1

require (
github.com/emicklei/proto v1.9.1 // indirect
github.com/extism/go-pdk v1.0.0 // indirect
github.com/nilslice/protolock v0.17.0 // indirect
)
9 changes: 9 additions & 0 deletions plugin-samples/plugin-sample-wasm/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/proto v1.9.1 h1:MUgjFo5xlMwYv72TnF5xmmdKZ04u+dVbv6wdARv16D8=
github.com/emicklei/proto v1.9.1/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
github.com/extism/go-pdk v1.0.0 h1:/VlFLDnpYfooMl+VW94VHrbdruDyKkpa47yYJ7YcCAE=
github.com/extism/go-pdk v1.0.0/go.mod h1:Gz+LIU/YCKnKXhgge8yo5Yu1F/lbv7KtKFkiCSzW/P4=
github.com/nilslice/protolock v0.17.0 h1:sYvcukABl62tZX77H6NuV+jtlwTIfQbn0ln0ixTqr4A=
github.com/nilslice/protolock v0.17.0/go.mod h1:DYFqop7QlHjmBCaJKfcVO1Mw5b8JejJZgMvmFng/N9Y=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
56 changes: 56 additions & 0 deletions plugin-samples/plugin-sample-wasm/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package main

import (
"encoding/json"

pdk "github.com/extism/go-pdk"
"github.com/nilslice/protolock"
"github.com/nilslice/protolock/extend"
)

// an Extism plugin uses a 'PDK' to communicate data input and output from its host system, in
// this case, the `protolock` command.

// see https://extism.org and https://github.com/extism/extism for more information.

// In order to satisfy the current usage, an Extism Protolock plugin must export a single function
// "status" with the following signature:

//export status
func status() int32 {
// rather than taking input from stdin, like native Protolock plugins, Extism plugins take data
// from their host, using the `pdk.Input()` function, returning bytes from protolock.
var data extend.Data
err := json.Unmarshal(pdk.Input(), &data.Current)
if err != nil {
pdk.SetError(err)
return 1
}

// with the `extend.Data` available, you would do some checks on the current and updated set of
// `proto.lock` representations. Here we are adding a warning to demonstrate that the plugin
// works with some known data output to verify.
warning := protolock.Warning{
Filepath: "fake.proto",
Message: "An Extism plugin ran and checked the status of the proto.lock files",
RuleName: "RuleNameXYZ",
}
data.PluginWarnings = append(data.PluginWarnings, warning)

b, err := json.Marshal(data)
if err != nil {
pdk.SetError(err)
return 1
}

// tather than writing data to stdout, like native Protolock plugins, Extism plugins provide
// data back to their host, using the `pdk.Output()` function, returning bytes to protolock.
pdk.Output(b)

// non-zero return code here will result in Extism detecting an error.
return 0
}

// this Go code is compiled to WebAssembly, and current compilers expect some entrypoint, even if
// this function isn't called.
func main() {}
Binary file added plugin-samples/plugin-sample-wasm/status.wasm
Binary file not shown.

0 comments on commit 26a05ef

Please sign in to comment.