@@ -41,12 +41,15 @@ func LoadPluginFunc(pluginDir, pluginID string) (func(map[string]interface{}) (i
4141 }
4242
4343 // Dynamic import based on plugin directory
44- // The plugin must have a Plugin() function that returns a map with an "execute" key
44+ // Prefer executing the real source plugin via a generated wrapper program.
45+ // This avoids the old behavior where many plugins silently fell back to stubs
46+ // or incorrectly required a precompiled .so file.
4547 return func (params map [string ]interface {}) (interface {}, error ) {
46- // Import the plugin using direct code execution
47- //pluginName := filepath.Base(pluginDir)
48+ if result , err := executeSourcePlugin (pluginDir , pluginID , params ); err == nil {
49+ return result , nil
50+ }
4851
49- // Handle specific plugins based on their IDs
52+ // Fall back to legacy helper implementations only when direct execution fails.
5053 switch pluginID {
5154 case "subnet_calculator" :
5255 // Use the ExecuteAdapter function from the subnet_calculator package
@@ -132,50 +135,160 @@ func executeCommand(command string) (string, error) {
132135 return output , err
133136}
134137
135- // Specific implementations for each plugin
136- // These functions would typically be replaced by properly loading the plugin modules
137- // but for now, we'll implement them with direct imports or simple placeholder functionality
138+ func executeSourcePlugin (pluginDir , pluginID string , params map [string ]interface {}) (interface {}, error ) {
139+ var err error
140+ pluginDir , err = filepath .Abs (pluginDir )
141+ if err != nil {
142+ return nil , fmt .Errorf ("resolve absolute plugin dir for %s: %w" , pluginID , err )
143+ }
138144
139- func executeSubnetCalculator (params map [string ]interface {}) (interface {}, error ) {
140- // Try to use a pre-compiled dynamic plugin (.so file)
141- // NOTE: Do NOT check the registry here - this function can be called from
142- // LoadPluginFunc which itself gets registered, causing infinite recursion
143- pluginDir := filepath .Join ("app" , "plugins" , "plugins" , "subnet_calculator" )
144- pluginPath := filepath .Join (pluginDir , "subnet_calculator.so" )
145+ pluginGoPath := filepath .Join (pluginDir , "plugin.go" )
146+ content , err := os .ReadFile (pluginGoPath )
147+ if err != nil {
148+ return nil , fmt .Errorf ("read plugin source for %s: %w" , pluginID , err )
149+ }
150+
151+ paramsJSON , err := json .Marshal (params )
152+ if err != nil {
153+ return nil , fmt .Errorf ("marshal parameters for %s: %w" , pluginID , err )
154+ }
145155
146- // Check if pre-compiled plugin exists
147- if _ , err := os .Stat (pluginPath ); os .IsNotExist (err ) {
148- return nil , fmt .Errorf ("subnet_calculator plugin not available: no pre-compiled .so file" )
156+ if strings .Contains (string (content ), "package main" ) {
157+ cmd := exec .Command ("go" , "run" , "plugin.go" , "--execute=" + string (paramsJSON ))
158+ cmd .Dir = pluginDir
159+ output , err := cmd .CombinedOutput ()
160+ if err != nil {
161+ return nil , fmt .Errorf ("execute main plugin %s: %w: %s" , pluginID , err , strings .TrimSpace (string (output )))
162+ }
163+ return decodePluginOutput (output )
149164 }
150165
151- // Try to load the pre-compiled plugin
152- p , err := plugin .Open (pluginPath )
166+ pluginModulePath , moduleRoot , err := resolvePluginModule (pluginDir )
153167 if err != nil {
154- return nil , fmt . Errorf ( "failed to load subnet_calculator plugin: %v" , err )
168+ return nil , err
155169 }
156170
157- // Look up the Plugin symbol
158- pluginSymbol , err := p .Lookup ("Plugin" )
171+ wrapperDir , err := os .MkdirTemp (moduleRoot , ".nettool-plugin-wrapper-*" )
159172 if err != nil {
160- return nil , fmt .Errorf ("subnet_calculator plugin does not export Plugin symbol : %v " , err )
173+ return nil , fmt .Errorf ("create temp wrapper dir : %w " , err )
161174 }
175+ defer os .RemoveAll (wrapperDir )
162176
163- // Call the Plugin function
164- pluginFunc := reflect .ValueOf (pluginSymbol ).Call (nil )[0 ].Interface ()
177+ importPath := pluginModulePath
178+ if pluginModulePath == "" {
179+ return nil , fmt .Errorf ("empty module path for plugin %s" , pluginID )
180+ }
181+ if pluginModulePath == "github.com/NetScout-Go/NetTool" {
182+ importPath = fmt .Sprintf ("%s/app/plugins/plugins/%s" , pluginModulePath , pluginID )
183+ }
184+ wrapperSource := fmt .Sprintf (`package main
185+ import (
186+ "encoding/json"
187+ "fmt"
188+ "os"
189+ pluginpkg %q
190+ )
191+ func main() {
192+ var params map[string]interface{}
193+ if err := json.Unmarshal([]byte(os.Args[1]), ¶ms); err != nil {
194+ fmt.Fprintf(os.Stderr, "parse params: %%v", err)
195+ os.Exit(1)
196+ }
197+ result, err := pluginpkg.Execute(params)
198+ if err != nil {
199+ fmt.Fprintf(os.Stderr, "execute plugin: %%v", err)
200+ os.Exit(1)
201+ }
202+ if err := json.NewEncoder(os.Stdout).Encode(result); err != nil {
203+ fmt.Fprintf(os.Stderr, "encode result: %%v", err)
204+ os.Exit(1)
205+ }
206+ }
207+ ` , importPath )
165208
166- // Extract the execute function
167- pluginMap , ok := pluginFunc .(map [string ]interface {})
168- if ! ok {
169- return nil , fmt .Errorf ("subnet_calculator Plugin() did not return a map" )
209+ mainPath := filepath .Join (wrapperDir , "main.go" )
210+ if err := os .WriteFile (mainPath , []byte (wrapperSource ), 0600 ); err != nil {
211+ return nil , fmt .Errorf ("write wrapper source: %w" , err )
170212 }
171213
172- execFunc , ok := pluginMap ["execute" ].(func (map [string ]interface {}) (interface {}, error ))
173- if ! ok {
174- return nil , fmt .Errorf ("subnet_calculator does not provide a valid execute function" )
214+ cmd := exec .Command ("go" , "run" , mainPath , string (paramsJSON ))
215+ cmd .Dir = moduleRoot
216+ output , err := cmd .CombinedOutput ()
217+ if err != nil {
218+ return nil , fmt .Errorf ("execute library plugin %s: %w: %s" , pluginID , err , strings .TrimSpace (string (output )))
175219 }
176220
177- // Call the execute function with the provided parameters
178- return execFunc (params )
221+ return decodePluginOutput (output )
222+ }
223+
224+ func decodePluginOutput (output []byte ) (interface {}, error ) {
225+ trimmed := bytes .TrimSpace (output )
226+ var result interface {}
227+ if err := json .Unmarshal (trimmed , & result ); err != nil {
228+ return map [string ]interface {}{"result" : string (trimmed )}, nil
229+ }
230+ return result , nil
231+ }
232+
233+ func findRepoRoot (start string ) (string , error ) {
234+ dir := start
235+ for {
236+ if _ , err := os .Stat (filepath .Join (dir , "go.mod" )); err == nil {
237+ return dir , nil
238+ }
239+ parent := filepath .Dir (dir )
240+ if parent == dir {
241+ return "" , fmt .Errorf ("could not locate repository root from %s" , start )
242+ }
243+ dir = parent
244+ }
245+ }
246+
247+ func resolvePluginModule (pluginDir string ) (modulePath string , moduleRoot string , err error ) {
248+ pluginGoMod := filepath .Join (pluginDir , "go.mod" )
249+ if _ , statErr := os .Stat (pluginGoMod ); statErr == nil {
250+ modulePath , err = readModulePath (pluginGoMod )
251+ if err != nil {
252+ return "" , "" , err
253+ }
254+ return modulePath , pluginDir , nil
255+ }
256+
257+ repoRoot , err := findRepoRoot (pluginDir )
258+ if err != nil {
259+ return "" , "" , err
260+ }
261+ modulePath , err = readModulePath (filepath .Join (repoRoot , "go.mod" ))
262+ if err != nil {
263+ return "" , "" , err
264+ }
265+ return modulePath , repoRoot , nil
266+ }
267+
268+ func readModulePath (goModPath string ) (string , error ) {
269+ data , err := os .ReadFile (goModPath )
270+ if err != nil {
271+ return "" , fmt .Errorf ("read go.mod: %w" , err )
272+ }
273+ for _ , line := range strings .Split (string (data ), "\n " ) {
274+ line = strings .TrimSpace (line )
275+ if strings .HasPrefix (line , "module " ) {
276+ return strings .TrimSpace (strings .TrimPrefix (line , "module " )), nil
277+ }
278+ }
279+ return "" , fmt .Errorf ("module path not found in %s" , goModPath )
280+ }
281+
282+ // Specific implementations for each plugin
283+ // These functions would typically be replaced by properly loading the plugin modules
284+ // but for now, we'll implement them with direct imports or simple placeholder functionality
285+
286+ func executeSubnetCalculator (params map [string ]interface {}) (interface {}, error ) {
287+ pluginDir := filepath .Join ("app" , "plugins" , "plugins" , "subnet_calculator" )
288+ if result , err := executeSourcePlugin (pluginDir , "subnet_calculator" , params ); err == nil {
289+ return result , nil
290+ }
291+ return nil , fmt .Errorf ("subnet_calculator plugin not available: no pre-compiled .so file" )
179292}
180293
181294func executeNetworkLatencyHeatmap (params map [string ]interface {}) (interface {}, error ) {
0 commit comments