Skip to content

Commit 425cbd7

Browse files
authored
Support running nested plugins via standalone when debugging (#1255)
* support running nested plugins via standalone * remove test * simplify error message * add more tests * update to be more specific where we're generally looking * update tests * apply PR feedback * fix linter issues
1 parent de025bc commit 425cbd7

File tree

5 files changed

+239
-57
lines changed

5 files changed

+239
-57
lines changed

internal/standalone/standalone.go

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"flag"
77
"fmt"
8+
"io/fs"
89
"log"
910
"net"
1011
"os"
@@ -81,14 +82,24 @@ func RunDummyPluginLocator(address string) {
8182
}
8283

8384
func serverSettings(pluginID string) (ServerSettings, error) {
84-
curProcPath, err := currentProcPath()
85+
cwd, err := currentWd()
8586
if err != nil {
8687
return ServerSettings{}, err
8788
}
8889

89-
dir := filepath.Dir(curProcPath) // Default to current directory
90-
if pluginDir, found := findPluginRootDir(curProcPath); found {
91-
dir = pluginDir
90+
// if we are in the pkg directory, go up one level
91+
if filepath.Base(cwd) == "pkg" {
92+
cwd = filepath.Dir(cwd)
93+
}
94+
95+
// if dist directory exists, use that as the base directory
96+
if finfo, err := os.Stat(filepath.Join(cwd, "dist")); err == nil && finfo.IsDir() {
97+
cwd = filepath.Join(cwd, "dist")
98+
}
99+
100+
dir, found := findPluginDir(cwd, pluginID)
101+
if !found {
102+
return ServerSettings{}, fmt.Errorf("plugin directory not found for plugin ID: %s", pluginID)
92103
}
93104

94105
address, err := createStandaloneAddress(pluginID)
@@ -101,6 +112,10 @@ func serverSettings(pluginID string) (ServerSettings, error) {
101112
}, nil
102113
}
103114

115+
// currentWd returns the current working directory.
116+
// This is a variable so that it can be mocked in tests.
117+
var currentWd = os.Getwd
118+
104119
// clientSettings will attempt to find a standalone server's address and PID.
105120
func clientSettings(pluginID string) (ClientSettings, error) {
106121
procPath, err := currentProcPath()
@@ -139,29 +154,39 @@ func currentProcPath() (string, error) {
139154
return curProcPath, nil
140155
}
141156

142-
// findPluginRootDir will attempt to find a plugin directory based on the executing process's file-system path.
143-
func findPluginRootDir(curProcPath string) (string, bool) {
144-
cwd, _ := os.Getwd()
145-
if filepath.Base(cwd) == "pkg" {
146-
cwd = filepath.Join(cwd, "..")
147-
}
157+
// findPluginDir will attempt to find a plugin directory based on the provided plugin ID.
158+
// This function will traverse the filesystem from the provided directory until it finds a directory that contains
159+
// a plugin.json file containing an id matching the provided plugin ID.
160+
func findPluginDir(dir, pluginID string) (string, bool) {
161+
var pluginDir string
162+
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
163+
if err != nil {
164+
return err
165+
}
148166

149-
check := []string{
150-
filepath.Join(curProcPath, "plugin.json"),
151-
filepath.Join(curProcPath, "dist", "plugin.json"),
152-
filepath.Join(filepath.Dir(curProcPath), "plugin.json"),
153-
filepath.Join(filepath.Dir(curProcPath), "dist", "plugin.json"),
154-
filepath.Join(cwd, "dist", "plugin.json"),
155-
filepath.Join(cwd, "plugin.json"),
156-
}
167+
if d.Name() != "plugin.json" || d.IsDir() {
168+
return nil
169+
}
170+
171+
id, err := internal.GetStringValueFromJSON(path, "id")
172+
if err != nil {
173+
return nil
174+
}
157175

158-
for _, path := range check {
159-
if _, err := os.Stat(path); err == nil {
160-
return filepath.Dir(path), true
176+
if pluginID == id {
177+
pluginDir = filepath.Dir(path)
178+
return fs.SkipAll
161179
}
180+
181+
return nil
182+
})
183+
184+
if err != nil {
185+
log.Printf("findPluginDir error: %s", err.Error())
186+
return "", false
162187
}
163188

164-
return "", false
189+
return pluginDir, pluginDir != ""
165190
}
166191

167192
func getStandaloneAddress(pluginID string, dir string) (string, error) {

internal/standalone/standalone_test.go

Lines changed: 183 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package standalone
22

33
import (
4+
"errors"
45
"os"
56
"path/filepath"
67
"testing"
@@ -21,51 +22,21 @@ func TestServerModeEnabled(t *testing.T) {
2122
})
2223

2324
t.Run("Enabled by flag", func(t *testing.T) {
25+
testDataDir, err := filepath.Abs("testdata")
26+
require.NoError(t, err)
27+
2428
before := standaloneEnabled
2529
t.Cleanup(func() {
2630
standaloneEnabled = before
2731
})
2832
truthy := true
2933
standaloneEnabled = &truthy
3034

31-
curProcPath, err := os.Executable()
32-
require.NoError(t, err)
33-
3435
settings, enabled := ServerModeEnabled(pluginID)
3536
require.True(t, enabled)
3637
require.NotEmpty(t, settings.Address)
37-
require.Equal(t, filepath.Dir(curProcPath), settings.Dir)
38+
require.Equal(t, filepath.Join(testDataDir, "plugin"), settings.Dir)
3839
})
39-
40-
t.Run("Nearby dist folder will be used as server directory",
41-
func(t *testing.T) {
42-
curProcPath, err := os.Executable()
43-
require.NoError(t, err)
44-
45-
procDir := filepath.Dir(curProcPath)
46-
distDir := filepath.Join(procDir, "dist")
47-
48-
err = os.MkdirAll(distDir, 0755)
49-
require.NoError(t, err)
50-
_, err = os.Create(filepath.Join(distDir, "plugin.json"))
51-
require.NoError(t, err)
52-
t.Cleanup(func() {
53-
err = os.RemoveAll(distDir)
54-
require.NoError(t, err)
55-
})
56-
57-
before := standaloneEnabled
58-
t.Cleanup(func() {
59-
standaloneEnabled = before
60-
})
61-
truthy := true
62-
standaloneEnabled = &truthy
63-
64-
settings, enabled := ServerModeEnabled(pluginID)
65-
require.True(t, enabled)
66-
require.NotEmpty(t, settings.Address)
67-
require.Equal(t, distDir, settings.Dir)
68-
})
6940
}
7041

7142
func TestClientModeEnabled(t *testing.T) {
@@ -166,3 +137,181 @@ func TestClientModeEnabled(t *testing.T) {
166137
require.Zero(t, settings.TargetPID)
167138
})
168139
}
140+
141+
func Test_findPluginDir(t *testing.T) {
142+
tests := []struct {
143+
name string
144+
dir string
145+
pluginID string
146+
want string
147+
exists bool
148+
}{
149+
{
150+
name: "existing plugin found from root directory",
151+
pluginID: pluginID,
152+
dir: "testdata",
153+
want: filepath.Join("testdata", "plugin"),
154+
exists: true,
155+
},
156+
{
157+
name: "existing plugin found from plugin directory",
158+
pluginID: pluginID,
159+
dir: filepath.Join("testdata", "plugin"),
160+
want: filepath.Join("testdata", "plugin"),
161+
exists: true,
162+
},
163+
{
164+
name: "existing plugin found from nested plugin directory",
165+
pluginID: "grafana-nested-datasource",
166+
dir: filepath.Join("testdata", "plugin"),
167+
want: filepath.Join("testdata", "plugin", "datasource"),
168+
exists: true,
169+
},
170+
{
171+
name: "existing plugin found from dist directory",
172+
pluginID: "grafana-foobar-datasource",
173+
dir: filepath.Join("testdata", "dist"),
174+
want: filepath.Join("testdata", "dist"),
175+
exists: true,
176+
},
177+
{
178+
name: "non matching plugin id",
179+
pluginID: pluginID,
180+
dir: filepath.Join("testdata", "GoLand"),
181+
want: "",
182+
},
183+
{
184+
name: "non-existing plugin",
185+
pluginID: "non-existing-plugin",
186+
dir: "testdata",
187+
want: "",
188+
},
189+
{
190+
name: "empty plugin ID",
191+
pluginID: "",
192+
dir: "testdata",
193+
want: "",
194+
},
195+
}
196+
197+
for _, tt := range tests {
198+
t.Run(tt.name, func(t *testing.T) {
199+
got, found := findPluginDir(tt.dir, tt.pluginID)
200+
require.Equal(t, tt.exists, found)
201+
require.Equal(t, tt.want, got)
202+
})
203+
}
204+
}
205+
206+
func TestServerSettings(t *testing.T) {
207+
originalWd := currentWd
208+
defer func() { currentWd = originalWd }()
209+
210+
t.Run("Returns settings for valid plugin", func(t *testing.T) {
211+
testDataDir, err := filepath.Abs(filepath.Join("testdata", "plugin"))
212+
require.NoError(t, err)
213+
214+
currentWd = func() (string, error) {
215+
return testDataDir, nil
216+
}
217+
218+
settings, err := serverSettings(pluginID)
219+
require.NoError(t, err)
220+
require.NotEmpty(t, settings.Address)
221+
require.Equal(t, testDataDir, settings.Dir)
222+
})
223+
224+
t.Run("Returns settings for nested plugin", func(t *testing.T) {
225+
testDataDir, err := filepath.Abs(filepath.Join("testdata", "plugin"))
226+
require.NoError(t, err)
227+
228+
currentWd = func() (string, error) {
229+
return testDataDir, nil
230+
}
231+
232+
settings, err := serverSettings("grafana-nested-datasource")
233+
require.NoError(t, err)
234+
require.NotEmpty(t, settings.Address)
235+
require.Equal(t, filepath.Join(testDataDir, "datasource"), settings.Dir)
236+
})
237+
238+
t.Run("Returns error for invalid plugin ID", func(t *testing.T) {
239+
testDataDir, err := filepath.Abs("testdata")
240+
require.NoError(t, err)
241+
242+
currentWd = func() (string, error) {
243+
return testDataDir, nil
244+
}
245+
246+
settings, err := serverSettings("")
247+
require.Error(t, err)
248+
require.Contains(t, err.Error(), "plugin directory not found")
249+
require.Empty(t, settings)
250+
})
251+
252+
t.Run("Handles pkg directory by moving up one level", func(t *testing.T) {
253+
tmpDir, err := os.MkdirTemp("", "plugin-test")
254+
require.NoError(t, err)
255+
defer func() {
256+
err = os.RemoveAll(tmpDir)
257+
t.Log("Error removing temp directory:", err)
258+
}()
259+
260+
pkgDir := filepath.Join(tmpDir, "pkg")
261+
err = os.Mkdir(pkgDir, 0755)
262+
require.NoError(t, err)
263+
264+
distDir := filepath.Join(tmpDir, "dist")
265+
err = os.Mkdir(distDir, 0755)
266+
require.NoError(t, err)
267+
268+
err = os.WriteFile(filepath.Join(distDir, "plugin.json"), []byte(`{"id": "`+pluginID+`"}`), 0600)
269+
require.NoError(t, err)
270+
271+
currentWd = func() (string, error) {
272+
return pkgDir, nil
273+
}
274+
275+
settings, err := serverSettings(pluginID)
276+
require.NoError(t, err)
277+
require.Equal(t, filepath.Join(tmpDir, "dist"), settings.Dir)
278+
require.NotEmpty(t, settings.Address)
279+
})
280+
281+
t.Run("Uses dist directory when available", func(t *testing.T) {
282+
tmpDir, err := os.MkdirTemp("", "plugin-test")
283+
require.NoError(t, err)
284+
defer func() {
285+
err = os.RemoveAll(tmpDir)
286+
t.Log("Error removing temp directory:", err)
287+
}()
288+
289+
distDir := filepath.Join(tmpDir, "dist")
290+
err = os.Mkdir(distDir, 0755)
291+
require.NoError(t, err)
292+
293+
err = os.WriteFile(filepath.Join(distDir, "plugin.json"), []byte(`{"id": "`+pluginID+`"}`), 0600)
294+
require.NoError(t, err)
295+
296+
currentWd = func() (string, error) {
297+
return tmpDir, nil
298+
}
299+
300+
settings, err := serverSettings(pluginID)
301+
require.NoError(t, err)
302+
require.Equal(t, filepath.Join(tmpDir, "dist"), settings.Dir)
303+
require.NotEmpty(t, settings.Address)
304+
})
305+
306+
t.Run("Returns error when working directory cannot be determined", func(t *testing.T) {
307+
wdErr := errors.New("mock working directory error")
308+
currentWd = func() (string, error) {
309+
return "", wdErr
310+
}
311+
312+
settings, err := serverSettings(pluginID)
313+
require.Error(t, err)
314+
require.ErrorIs(t, err, wdErr)
315+
require.Empty(t, settings)
316+
})
317+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"id": "grafana-foobar-datasource"
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"id": "grafana-nested-datasource"
3+
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
{}
1+
{
2+
"id": "grafana-test-datasource"
3+
}

0 commit comments

Comments
 (0)