collector/logs/transforms/plugin/yaegi.go (48 lines of code) (raw):
package plugin
import (
"fmt"
"path/filepath"
"github.com/Azure/adx-mon/collector/logs/types"
"github.com/traefik/yaegi/interp"
"github.com/traefik/yaegi/stdlib"
)
/* Plugins are defined with a Go package that exports a New() function that returns a types.Transformer.
The structure of a plugin is like the following, simulating a full GoPath with a single project within.
The plugin system does not support Go modules, so all dependencies must be vendored and the plugin path
must be the full package path.
.
└── src
└── github.com
└── Azure
└── testplugin
├── go.mod
├── pkg
│ ├── transforms
│ │ ├── addemoji.go
│ │ └── addemoji_test.go
│ └── mapping
│ └── emoji_mapping.go
└── vendor
└── github.com
└── Azure
└── adx-mon
└── collector
└── logs
└── types
└── processors.go
└── logs.go
*/
func FromConfigMap(config map[string]interface{}) (types.Transformer, error) {
goPath, ok := config["GoPath"].(string)
if !ok {
return nil, fmt.Errorf("GoPath is required")
}
importName, ok := config["ImportName"].(string)
if !ok {
return nil, fmt.Errorf("ImportName is required")
}
transformConfig := TransformConfig{
GoPath: goPath,
ImportName: importName,
}
return NewTransform(transformConfig)
}
type TransformConfig struct {
// Path on disk that contains the plugin.
// This expects a directory with a src directory at the base.
GoPath string
// Import package of the plugin that contains the New() function.
// e.g. github.com/Azure/emojitransforms/pkg/transforms
ImportName string
}
func NewTransform(config TransformConfig) (types.Transformer, error) {
baseName := filepath.Base(config.ImportName)
i := interp.New(interp.Options{GoPath: config.GoPath})
// Go stdlib symbols
i.Use(stdlib.Symbols)
// Collector plugin symbols
i.Use(Symbols)
_, err := i.Eval(fmt.Sprintf(`import "%s"`, config.ImportName))
if err != nil {
return nil, fmt.Errorf("failed to import %s: %w", config.ImportName, err)
}
newFuncInterface, err := i.Eval(fmt.Sprintf("%s.New", baseName))
if err != nil {
return nil, fmt.Errorf("failed to get New function from %s: %w", baseName, err)
}
// Consider changing this interface to accept a configuration object that can contain metrics, etc.
results := newFuncInterface.Call(nil)
var ok bool
transformer, ok := results[0].Interface().(types.Transformer)
if !ok {
return nil, fmt.Errorf("failed to cast %s.New() result to *logs.Transformer", baseName)
}
return transformer, nil
}