Skip to content
Open
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ require (
github.com/shopspring/decimal v1.4.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/yuin/gopher-lua v1.1.1 // indirect
go.starlark.net v0.0.0-20250701195324-d457b4515e0e // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
go.starlark.net v0.0.0-20250701195324-d457b4515e0e h1:/WX+ZvcgVJxdIxVR9J3u45ds+Bl4IWPIHRSSICp0t3Q=
go.starlark.net v0.0.0-20250701195324-d457b4515e0e/go.mod h1:YKMCv9b1WrfWmeqdV5MAuEHWsu5iC+fe6kYl2sQjdI8=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
Expand Down
185 changes: 185 additions & 0 deletions pkg/mappers/startlark.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package mappers

import (
"fmt"
"log"

"github.com/reugn/go-streams/flow"
"go.starlark.net/starlark"
"go.starlark.net/starlarkstruct"
"go.starlark.net/syntax"
)

func NewStarlark(rule string) flow.MapFunction[map[string]interface{}, map[string]interface{}] {
thread := &starlark.Thread{Name: "starlark_thread"}

return func(item map[string]interface{}) map[string]interface{} {
// Use ExecFileOptions with the correct signature
fileOpts := &syntax.FileOptions{}

// Create predeclared values if needed
predeclared := starlark.StringDict{}

globals, err := starlark.ExecFileOptions(fileOpts, thread, rule, nil, predeclared)
if err != nil {
log.Fatalf("Failed to execute Starlark script: %v", err)
}

// Get the enrich function from the globals
enrich, ok := globals["enrich"]
if !ok {
log.Fatalf("Function 'enrich' not found in Starlark script")
}

dict, err := toStringDict(item)
if err != nil {
log.Fatal(err)
}
st := starlarkstruct.FromStringDict(starlarkstruct.Default, dict)

v, err := starlark.Call(thread, enrich, starlark.Tuple{st}, nil)
if err != nil {
log.Fatalf("Evaluate returned error: %v", err)
}

// Convert the Starlark result to a Go map
result, err := starlarkValueToGo(v)
if err != nil {
log.Fatalf("Failed to convert Starlark result: %v", err)
}

// Ensure we have a map[string]interface{}
resultMap, ok := result.(map[string]interface{})
if !ok {
log.Fatalf("Expected map[string]interface{}, got %T", result)
}

return resultMap
}
}

// Recursively converts map[string]interface{} to starlark.StringDict
func toStringDict(m map[string]interface{}) (starlark.StringDict, error) {
dict := starlark.StringDict{}
for k, v := range m {
val, err := goToStarlarkValue(v)
if err != nil {
return nil, fmt.Errorf("key %q: %w", k, err)
}
dict[k] = val
}
return dict, nil
}

// Recursively converts Go values to starlark.Value
func goToStarlarkValue(v interface{}) (starlark.Value, error) {
// Handle nil values
if v == nil {
return starlark.None, nil
}

switch v := v.(type) {
case string:
return starlark.String(v), nil
case int:
return starlark.MakeInt(v), nil
case int64:
return starlark.MakeInt64(v), nil
case float64:
return starlark.Float(v), nil
case bool:
return starlark.Bool(v), nil
case map[string]interface{}:
d, err := toStringDict(v)
if err != nil {
return nil, err
}
return starlarkstruct.FromStringDict(starlarkstruct.Default, d), nil
case []interface{}:
elems := make([]starlark.Value, len(v))
for i, elem := range v {
sv, err := goToStarlarkValue(elem)
if err != nil {
return nil, fmt.Errorf("in slice at index %d: %w", i, err)
}
elems[i] = sv
}
return starlark.NewList(elems), nil
default:
return nil, fmt.Errorf("unsupported type %T", v)
}
}

// starlarkValueToGo converts a Starlark value to a Go value
func starlarkValueToGo(v starlark.Value) (interface{}, error) {
switch val := v.(type) {
case starlark.String:
return string(val), nil
case starlark.Int:
i, ok := val.Int64()
if !ok {
return nil, fmt.Errorf("int value out of range: %v", val)
}
return i, nil
case starlark.Float:
return float64(val), nil
case starlark.Bool:
return bool(val), nil
case *starlark.List:
result := make([]interface{}, val.Len())
iter := val.Iterate()
defer iter.Done()
var x starlark.Value
i := 0
for iter.Next(&x) {
item, err := starlarkValueToGo(x)
if err != nil {
return nil, err
}
result[i] = item
i++
}
return result, nil
case *starlark.Dict:
result := make(map[string]interface{})
for _, item := range val.Items() {
key, ok := item[0].(starlark.String)
if !ok {
return nil, fmt.Errorf("dict key is not a string: %s", item[0].Type())
}
goVal, err := starlarkValueToGo(item[1])
if err != nil {
return nil, err
}
result[string(key)] = goVal
}
return result, nil
case *starlarkstruct.Struct:
return starlarkStructToMap(val)
default:
return nil, fmt.Errorf("unsupported starlark type: %s", v.Type())
}
}

// starlarkStructToMap converts a Starlark struct to a Go map
func starlarkStructToMap(s *starlarkstruct.Struct) (map[string]interface{}, error) {
result := make(map[string]interface{})

// Get all attributes from the struct
attrs := s.AttrNames()
for _, name := range attrs {
val, err := s.Attr(name)
if err != nil {
return nil, fmt.Errorf("error getting attribute %s: %v", name, err)
}

goVal, err := starlarkValueToGo(val)
if err != nil {
return nil, err
}

result[name] = goVal
}

return result, nil
}
16 changes: 16 additions & 0 deletions pkg/mappers/startlark_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package mappers

import (
"fmt"
"testing"

"github.com/metraction/pharos/pkg/model"
)

func TestStarlark(t *testing.T) {

item := model.NewTestScanResult(model.NewTestScanTask(t, "test-1", "test-image-1"), "test-engine-1")
result := NewStarlark("../../testdata/enrichers/fibonachi.star")(ToMap(item))

fmt.Println(result)
}
11 changes: 11 additions & 0 deletions testdata/enrichers/fibonachi.star
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
def enrich(data):
# Extract n from input data or use attribute access
n = len(data.Image.ImageSpec)

# Calculate Fibonacci sequence
seq = list(range(n))
for i in seq[2:]:
seq[i] = seq[i-2] + seq[i-1]

# Return the result as a map of maps
return {"fibonacci_sequence": seq, "count": n}
Loading