-
Notifications
You must be signed in to change notification settings - Fork 32
/
plugin.go
174 lines (146 loc) · 4.63 KB
/
plugin.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package hrp
import (
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"github.com/httprunner/funplugin"
"github.com/httprunner/funplugin/fungo"
"github.com/pkg/errors"
"github.com/rs/zerolog/log"
"github.com/test-instructor/yangfan/parsing/hrp/internal/code"
"github.com/test-instructor/yangfan/parsing/hrp/internal/env"
"github.com/test-instructor/yangfan/parsing/hrp/internal/myexec"
"github.com/test-instructor/yangfan/parsing/hrp/internal/sdk"
)
const (
PluginGoBuiltFile = "debugtalk.so" // built from go official plugin
PluginHashicorpGoBuiltFile = "debugtalk.bin" // built from hashicorp go plugin
PluginGoSourceFile = "debugtalk.go" // golang function plugin source file
PluginGoSourceGenFile = "debugtalk_gen.go" // generated for hashicorp go plugin
PluginPySourceFile = "debugtalk.py" // python function plugin source file
PluginPySourceGenFile = ".debugtalk_gen.py" // generated for hashicorp python plugin
)
const projectInfoFile = "proj.json" // used for ensuring root project
var (
pluginMap sync.Map // used for reusing plugin instance
pluginMutex sync.RWMutex
)
func initPlugin(path, venv string, logOn bool) (plugin funplugin.IPlugin, err error) {
// plugin file not found
if path == "" {
return nil, nil
}
pluginPath, err := locatePlugin(path)
if err != nil {
log.Warn().Str("path", path).Msg("locate plugin failed")
return nil, nil
}
pluginMutex.Lock()
defer pluginMutex.Unlock()
// reuse plugin instance if it already initialized
if p, ok := pluginMap.Load(pluginPath); ok {
return p.(funplugin.IPlugin), nil
}
pluginOptions := []funplugin.Option{funplugin.WithLogOn(logOn)}
if strings.HasSuffix(pluginPath, ".py") {
// register funppy plugin
genPyPluginPath := filepath.Join(filepath.Dir(pluginPath), PluginPySourceGenFile)
err = BuildPlugin(pluginPath, genPyPluginPath)
if err != nil {
log.Error().Err(err).Str("path", pluginPath).Msg("build plugin failed")
return nil, err
}
pluginPath = genPyPluginPath
packages := []string{
fmt.Sprintf("funppy==%s", fungo.Version),
}
python3, err := myexec.EnsurePython3Venv(venv, packages...)
if err != nil {
log.Error().Err(err).
Interface("packages", packages).
Msg("python3 venv is not ready")
return nil, err
}
pluginOptions = append(pluginOptions, funplugin.WithPython3(python3))
}
// found plugin file
plugin, err = funplugin.Init(pluginPath, pluginOptions...)
if err != nil {
log.Error().Err(err).Msgf("init plugin failed: %s", pluginPath)
err = errors.Wrap(code.InitPluginFailed, err.Error())
return
}
// add plugin instance to plugin map
pluginMap.Store(pluginPath, plugin)
// report event for initializing plugin
event := sdk.EventTracking{
Category: "InitPlugin",
Action: fmt.Sprintf("Init %s plugin", plugin.Type()),
Value: 0, // success
}
if err != nil {
event.Value = 1 // failed
}
go sdk.SendEvent(event)
return
}
func locatePlugin(path string) (pluginPath string, err error) {
// priority: hashicorp plugin (debugtalk.bin > debugtalk.py) > go plugin (debugtalk.so)
pluginPath, err = locateFile(path, PluginHashicorpGoBuiltFile)
if err == nil {
return
}
pluginPath, err = locateFile(path, PluginPySourceFile)
if err == nil {
return
}
pluginPath, err = locateFile(path, PluginGoBuiltFile)
if err == nil {
return
}
return "", fmt.Errorf("plugin file not found")
}
// locateFile searches destFile upward recursively until system root dir
func locateFile(startPath string, destFile string) (string, error) {
stat, err := os.Stat(startPath)
if os.IsNotExist(err) {
return "", err
}
var startDir string
if stat.IsDir() {
startDir = startPath
} else {
startDir = filepath.Dir(startPath)
}
startDir, _ = filepath.Abs(startDir)
// convention over configuration
pluginPath := filepath.Join(startDir, destFile)
if _, err := os.Stat(pluginPath); err == nil {
return pluginPath, nil
}
// system root dir
parentDir, _ := filepath.Abs(filepath.Dir(startDir))
if parentDir == startDir {
return "", fmt.Errorf("searched to system root dir, plugin file not found")
}
return locateFile(parentDir, destFile)
}
func GetProjectRootDirPath(path string) (rootDir string, err error) {
pluginPath, err := locatePlugin(path)
if err == nil {
rootDir = filepath.Dir(pluginPath)
return
}
// fix: no debugtalk file in project but having proj.json created by startpeoject
projPath, err := locateFile(path, projectInfoFile)
if err == nil {
rootDir = filepath.Dir(projPath)
return
}
// failed to locate project root dir
// maybe project plugin debugtalk.xx and proj.json are not exist
// use current dir instead
return env.RootDir, nil
}