History

feat(pluginhost): introduce browser-navigable plugin resources in Management API

- Added `resources` field in `management.register` for defining browser-accessible resources. - Updated examples and documentation to reflect resource-based paths under `/v0/resource/plugins/<pluginID>/...`. - Replaced legacy `GET` menu routes with resource-based implementations for consistent plugin behavior. - Enhanced request handling for resource paths, including proper response headers and streamlined test coverage.

Luis Pater committed 5 months agov7.1.59

Commit 44ea9ab1 parent 2aeb41c22 files · +341 −69Browse files

Showing the changes to internal/pluginhost (6 of 22 files). Show all

Unified · Split

6 files
  1. internal/pluginhost/host.go +7 −0
  2. internal/pluginhost/management.go +156 −4
  3. internal/pluginhost/management_test.go +76 −15
  4. internal/pluginhost/rpc_client.go +6 −1
  5. internal/pluginhost/rpc_schema.go +2 −1
  6. internal/pluginhost/snapshot.go +2 −6
internal/pluginhost/host.go+7−0
@@ -37,6 +37,7 @@ type Host struct { · 36 unmodified lines
37 commandLineFlags map[string]commandLineFlagRecord37 commandLineFlags map[string]commandLineFlagRecord
38 commandLineHits map[string]struct{}38 commandLineHits map[string]struct{}
39 managementRoutes map[string]managementRouteRecord39 managementRoutes map[string]managementRouteRecord
40 resourceRoutes map[string]resourceRouteRecord
40 streams *streamBridge41 streams *streamBridge
41 httpStreams *hostHTTPStreamBridge42 httpStreams *hostHTTPStreamBridge
42 callbackContexts *callbackContextRegistry43 callbackContexts *callbackContextRegistry
@@ -58,6 +59,7 @@ func New() *Host { · 15 unmodified lines
58 commandLineFlags: make(map[string]commandLineFlagRecord),59 commandLineFlags: make(map[string]commandLineFlagRecord),
59 commandLineHits: make(map[string]struct{}),60 commandLineHits: make(map[string]struct{}),
60 managementRoutes: make(map[string]managementRouteRecord),61 managementRoutes: make(map[string]managementRouteRecord),
62 resourceRoutes: make(map[string]resourceRouteRecord),
61 streams: newStreamBridge(),63 streams: newStreamBridge(),
62 httpStreams: newHostHTTPStreamBridge(),64 httpStreams: newHostHTTPStreamBridge(),
63 callbackContexts: newCallbackContextRegistry(),65 callbackContexts: newCallbackContextRegistry(),
@@ -93,6 +95,8 @@ func (h *Host) ApplyConfig(ctx context.Context, cfg *config.Config) { · 29 unmodified lines
93 h.runtimeConfig = cfg95 h.runtimeConfig = cfg
94 96
95 if !rc.Enabled {97 if !rc.Enabled {
98 h.managementRoutes = make(map[string]managementRouteRecord)
99 h.resourceRoutes = make(map[string]resourceRouteRecord)
96 h.snapshot.Store(emptySnapshot())100 h.snapshot.Store(emptySnapshot())
97 h.mu.Unlock()101 h.mu.Unlock()
98 h.refreshThinkingProviders(nil)102 h.refreshThinkingProviders(nil)
@@ -102,6 +106,8 @@ func (h *Host) ApplyConfig(ctx context.Context, cfg *config.Config) { · 3 unmodified lines
102 files, errSelect := selectPluginFiles(rc.Dir)106 files, errSelect := selectPluginFiles(rc.Dir)
103 if errSelect != nil {107 if errSelect != nil {
104 log.Warnf("pluginhost: failed to select plugin files: %v", errSelect)108 log.Warnf("pluginhost: failed to select plugin files: %v", errSelect)
109 h.managementRoutes = make(map[string]managementRouteRecord)
110 h.resourceRoutes = make(map[string]resourceRouteRecord)
105 h.snapshot.Store(emptySnapshot())111 h.snapshot.Store(emptySnapshot())
106 h.mu.Unlock()112 h.mu.Unlock()
107 h.refreshThinkingProviders(nil)113 h.refreshThinkingProviders(nil)
@@ -187,6 +193,7 @@ func (h *Host) ShutdownAll() { · 79 unmodified lines
187 h.commandLineFlags = make(map[string]commandLineFlagRecord)193 h.commandLineFlags = make(map[string]commandLineFlagRecord)
188 h.commandLineHits = make(map[string]struct{})194 h.commandLineHits = make(map[string]struct{})
189 h.managementRoutes = make(map[string]managementRouteRecord)195 h.managementRoutes = make(map[string]managementRouteRecord)
196 h.resourceRoutes = make(map[string]resourceRouteRecord)
190 h.snapshot.Store(emptySnapshot())197 h.snapshot.Store(emptySnapshot())
191 h.mu.Unlock()198 h.mu.Unlock()
192 199
internal/pluginhost/management.go+156−4
@@ -12,20 +12,30 @@ import ( · 11 unmodified lines
12 log "github.com/sirupsen/logrus"12 log "github.com/sirupsen/logrus"
13)13)
14 14
15const managementBasePath = "/v0/management"15const (
16 managementBasePath = "/v0/management"
17 resourcePluginBasePath = "/v0/resource/plugins"
18 legacyPluginRoutePrefix = "/plugins"
19)
16 20
17type managementRouteRecord struct {21type managementRouteRecord struct {
18 pluginID string22 pluginID string
19 route pluginapi.ManagementRoute23 route pluginapi.ManagementRoute
20}24}
21 25
22// RegisterManagementRoutes rebuilds the plugin-owned Management API route table.26type resourceRouteRecord struct {
27 pluginID string
28 route pluginapi.ResourceRoute
29}
30
31// RegisterManagementRoutes rebuilds the plugin-owned Management API and resource route tables.
23func (h *Host) RegisterManagementRoutes(ctx context.Context, reserved map[string]struct{}) {32func (h *Host) RegisterManagementRoutes(ctx context.Context, reserved map[string]struct{}) {
24 if h == nil {33 if h == nil {
25 return34 return
26 }35 }
27 36
28 nextRoutes := make(map[string]managementRouteRecord)37 nextRoutes := make(map[string]managementRouteRecord)
38 nextResources := make(map[string]resourceRouteRecord)
29 for _, record := range h.Snapshot().records {39 for _, record := range h.Snapshot().records {
30 plugin := record.plugin.Capabilities.ManagementAPI40 plugin := record.plugin.Capabilities.ManagementAPI
31 if plugin == nil || h.isPluginFused(record.id) {41 if plugin == nil || h.isPluginFused(record.id) {
@@ -36,12 +46,19 @@ func (h *Host) RegisterManagementRoutes(ctx context.Context, reserved map[string · 4 unmodified lines
36 log.Warnf("pluginhost: management registrar %s failed: %v", record.id, errRegister)46 log.Warnf("pluginhost: management registrar %s failed: %v", record.id, errRegister)
37 continue47 continue
38 }48 }
49
39 for _, item := range resp.Routes {50 for _, item := range resp.Routes {
40 method, path, okRoute := normalizeManagementRoute(item)51 method, path, okRoute := normalizeManagementRoute(item)
41 if !okRoute {52 if !okRoute {
42 log.Warnf("pluginhost: plugin %s declared invalid management route %s %s", record.id, item.Method, item.Path)53 log.Warnf("pluginhost: plugin %s declared invalid management route %s %s", record.id, item.Method, item.Path)
43 continue54 continue
44 }55 }
56 if routeDeclaresLegacyMenuResource(method, item) {
57 if !registerResourceRoute(nextResources, record.id, resourceRouteFromManagementRoute(item)) {
58 log.Warnf("pluginhost: plugin %s declared invalid resource route %s", record.id, item.Path)
59 }
60 continue
61 }
45 key := managementRouteKey(method, path)62 key := managementRouteKey(method, path)
46 if _, exists := reserved[key]; exists {63 if _, exists := reserved[key]; exists {
47 log.Warnf("pluginhost: plugin %s management route %s conflicts with an existing route and was skipped", record.id, key)64 log.Warnf("pluginhost: plugin %s management route %s conflicts with an existing route and was skipped", record.id, key)
@@ -58,10 +75,17 @@ func (h *Host) RegisterManagementRoutes(ctx context.Context, reserved map[string · 10 unmodified lines
58 route: item,75 route: item,
59 }76 }
60 }77 }
78
79 for _, item := range resp.Resources {
80 if !registerResourceRoute(nextResources, record.id, item) {
81 log.Warnf("pluginhost: plugin %s declared invalid resource route %s", record.id, item.Path)
82 }
83 }
61 }84 }
62 85
63 h.mu.Lock()86 h.mu.Lock()
64 h.managementRoutes = nextRoutes87 h.managementRoutes = nextRoutes
88 h.resourceRoutes = nextResources
65 h.mu.Unlock()89 h.mu.Unlock()
66}90}
67 91
@@ -77,8 +101,9 @@ func (h *Host) callManagementRegistrar(ctx context.Context, record capabilityRec · 9 unmodified lines
77 }101 }
78 }()102 }()
79 return plugin.RegisterManagement(ctx, pluginapi.ManagementRegistrationRequest{103 return plugin.RegisterManagement(ctx, pluginapi.ManagementRegistrationRequest{
80 Plugin: record.meta,104 Plugin: record.meta,
81 BasePath: managementBasePath,105 BasePath: managementBasePath,
106 ResourceBasePath: resourcePluginBasePath + "/" + record.id,
82 })107 })
83}108}
84 109
@@ -118,6 +143,75 @@ func normalizeManagementRoute(item pluginapi.ManagementRoute) (string, string, b · 33 unmodified lines
118 return method, fullPath, true143 return method, fullPath, true
119}144}
120 145
146func routeDeclaresLegacyMenuResource(method string, item pluginapi.ManagementRoute) bool {
147 return strings.EqualFold(strings.TrimSpace(method), http.MethodGet) && strings.TrimSpace(item.Menu) != ""
148}
149
150func resourceRouteFromManagementRoute(item pluginapi.ManagementRoute) pluginapi.ResourceRoute {
151 return pluginapi.ResourceRoute{
152 Path: item.Path,
153 Menu: item.Menu,
154 Description: item.Description,
155 Handler: item.Handler,
156 }
157}
158
159func registerResourceRoute(routes map[string]resourceRouteRecord, pluginID string, item pluginapi.ResourceRoute) bool {
160 path, okRoute := normalizeResourceRoute(pluginID, item)
161 if !okRoute {
162 return false
163 }
164 key := managementRouteKey(http.MethodGet, path)
165 if _, exists := routes[key]; exists {
166 log.Warnf("pluginhost: plugin %s resource route %s conflicts with a higher-priority plugin and was skipped", pluginID, key)
167 return true
168 }
169 item.Path = path
170 routes[key] = resourceRouteRecord{
171 pluginID: pluginID,
172 route: item,
173 }
174 return true
175}
176
177func normalizeResourceRoute(pluginID string, item pluginapi.ResourceRoute) (string, bool) {
178 if item.Handler == nil {
179 return "", false
180 }
181 pluginID = strings.TrimSpace(pluginID)
182 if pluginID == "" {
183 return "", false
184 }
185
186 path := strings.TrimSpace(item.Path)
187 if path == "" {
188 return "", false
189 }
190 if !strings.HasPrefix(path, "/") {
191 path = "/" + path
192 }
193
194 pluginBasePath := resourcePluginBasePath + "/" + pluginID
195 if strings.HasPrefix(path, pluginBasePath+"/") {
196 path = strings.TrimPrefix(path, pluginBasePath)
197 } else if strings.HasPrefix(path, legacyPluginRoutePrefix+"/"+pluginID+"/") {
198 path = strings.TrimPrefix(path, legacyPluginRoutePrefix+"/"+pluginID)
199 }
200 path = strings.TrimRight(path, "/")
201 if path == "" {
202 return "", false
203 }
204
205 fullPath := pluginBasePath + path
206 if !strings.HasPrefix(fullPath, pluginBasePath+"/") {
207 return "", false
208 }
209 if strings.ContainsAny(fullPath, " \t\r\n") || strings.Contains(fullPath, ":") || strings.Contains(fullPath, "*") || strings.Contains(fullPath, "..") {
210 return "", false
211 }
212 return fullPath, true
213}
214
121func managementRouteKey(method, path string) string {215func managementRouteKey(method, path string) string {
122 return strings.ToUpper(strings.TrimSpace(method)) + " " + strings.TrimSpace(path)216 return strings.ToUpper(strings.TrimSpace(method)) + " " + strings.TrimSpace(path)
123}217}
@@ -178,6 +272,50 @@ func (h *Host) ServeManagementHTTP(w http.ResponseWriter, r *http.Request) bool · 54 unmodified lines
178 return true272 return true
179}273}
180 274
275// ServeResourceHTTP dispatches an unauthenticated browser-navigable resource request to a plugin route.
276func (h *Host) ServeResourceHTTP(w http.ResponseWriter, r *http.Request) bool {
277 if h == nil || w == nil || r == nil || r.URL == nil {
278 return false
279 }
280 if !strings.EqualFold(r.Method, http.MethodGet) {
281 return false
282 }
283 key := managementRouteKey(http.MethodGet, r.URL.Path)
284 h.mu.Lock()
285 record, okRoute := h.resourceRoutes[key]
286 h.mu.Unlock()
287 if !okRoute || record.route.Handler == nil || h.isPluginFused(record.pluginID) {
288 return false
289 }
290
291 resp, errHandle := h.callResourceHandler(r.Context(), record, pluginapi.ManagementRequest{
292 Method: http.MethodGet,
293 Path: r.URL.Path,
294 Headers: cloneHeader(r.Header),
295 Query: cloneValues(r.URL.Query()),
296 })
297 if errHandle != nil {
298 log.Warnf("pluginhost: resource handler %s failed: %v", record.pluginID, errHandle)
299 http.Error(w, "plugin resource handler failed", http.StatusBadGateway)
300 return true
301 }
302
303 for keyHeader, values := range resp.Headers {
304 for _, value := range values {
305 w.Header().Add(keyHeader, value)
306 }
307 }
308 statusCode := resp.StatusCode
309 if statusCode == 0 {
310 statusCode = http.StatusOK
311 }
312 w.WriteHeader(statusCode)
313 if _, errWrite := w.Write(resp.Body); errWrite != nil {
314 log.Warnf("pluginhost: failed to write plugin resource response: %v", errWrite)
315 }
316 return true
317}
318
181func (h *Host) callManagementHandler(ctx context.Context, record managementRouteRecord, req pluginapi.ManagementRequest) (resp pluginapi.ManagementResponse, err error) {319func (h *Host) callManagementHandler(ctx context.Context, record managementRouteRecord, req pluginapi.ManagementRequest) (resp pluginapi.ManagementResponse, err error) {
182 if h == nil || record.route.Handler == nil || h.isPluginFused(record.pluginID) {320 if h == nil || record.route.Handler == nil || h.isPluginFused(record.pluginID) {
183 return pluginapi.ManagementResponse{}, nil321 return pluginapi.ManagementResponse{}, nil
@@ -191,3 +329,17 @@ func (h *Host) callManagementHandler(ctx context.Context, record managementRoute · 7 unmodified lines
191 }()329 }()
192 return record.route.Handler.HandleManagement(ctx, req)330 return record.route.Handler.HandleManagement(ctx, req)
193}331}
332
333func (h *Host) callResourceHandler(ctx context.Context, record resourceRouteRecord, req pluginapi.ManagementRequest) (resp pluginapi.ManagementResponse, err error) {
334 if h == nil || record.route.Handler == nil || h.isPluginFused(record.pluginID) {
335 return pluginapi.ManagementResponse{}, nil
336 }
337 defer func() {
338 if recovered := recover(); recovered != nil {
339 h.fusePlugin(record.pluginID, "ResourceHandler.HandleManagement", recovered)
340 resp = pluginapi.ManagementResponse{}
341 err = fmt.Errorf("resource handler panic: %v", recovered)
342 }
343 }()
344 return record.route.Handler.HandleManagement(ctx, req)
345}
internal/pluginhost/management_test.go+76−15
@@ -91,18 +91,77 @@ func TestManagementHandlerPanicFusesPlugin(t *testing.T) { · 90 unmodified lines
91 }91 }
92}92}
93 93
94func TestRegisteredPluginsIncludesGETManagementMenus(t *testing.T) {94func TestServeResourceHTTPDispatchesPluginResource(t *testing.T) {
95 plugin := &managementPluginDouble{95 host := newHostWithRecords(capabilityRecord{
96 routes: []pluginapi.ManagementRoute{96 id: "resource",
97 {97 plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
98 Method: http.MethodGet,98 ManagementAPI: &managementPluginDouble{resources: []pluginapi.ResourceRoute{{
99 Path: "/plugins/menu/status",99 Path: "/status",
100 Menu: "Status",100 Menu: "Status",
101 Description: "Shows plugin status.",101 Description: "Shows plugin status.",
102 Handler: managementHandlerFunc(func(_ context.Context, req pluginapi.ManagementRequest) (pluginapi.ManagementResponse, error) {
103 if req.Path != "/v0/resource/plugins/resource/status" {
104 t.Fatalf("resource request path = %q, want normalized resource path", req.Path)
105 }
106 return pluginapi.ManagementResponse{
107 Headers: http.Header{"Content-Type": []string{"text/html; charset=utf-8"}},
108 Body: []byte("<!doctype html><title>resource</title>"),
109 }, nil
110 }),
111 }}},
112 }},
113 })
114 host.RegisterManagementRoutes(context.Background(), nil)
115
116 req := httptest.NewRequest(http.MethodGet, "/v0/resource/plugins/resource/status", nil)
117 rec := httptest.NewRecorder()
118 if !host.ServeResourceHTTP(rec, req) {
119 t.Fatal("ServeResourceHTTP() = false, want true")
120 }
121 if rec.Code != http.StatusOK || rec.Body.String() != "<!doctype html><title>resource</title>" {
122 t.Fatalf("response = %d %q, want 200 html", rec.Code, rec.Body.String())
123 }
124 if got := rec.Header().Get("Content-Type"); got != "text/html; charset=utf-8" {
125 t.Fatalf("Content-Type = %q, want text/html; charset=utf-8", got)
126 }
127}
128
129func TestLegacyGETManagementMenuRegistersAsResource(t *testing.T) {
130 host := newHostWithRecords(capabilityRecord{
131 id: "legacy",
132 plugin: pluginapi.Plugin{Capabilities: pluginapi.Capabilities{
133 ManagementAPI: &managementPluginDouble{routes: []pluginapi.ManagementRoute{{
134 Method: http.MethodGet,
135 Path: "/plugins/legacy/status",
136 Menu: "Legacy Status",
137 Description: "Shows legacy plugin status.",
102 Handler: managementHandlerFunc(func(context.Context, pluginapi.ManagementRequest) (pluginapi.ManagementResponse, error) {138 Handler: managementHandlerFunc(func(context.Context, pluginapi.ManagementRequest) (pluginapi.ManagementResponse, error) {
103 return pluginapi.ManagementResponse{}, nil139 return pluginapi.ManagementResponse{Body: []byte("legacy")}, nil
104 }),140 }),
105 },141 }}},
142 }},
143 })
144 host.RegisterManagementRoutes(context.Background(), nil)
145
146 managementReq := httptest.NewRequest(http.MethodGet, "/v0/management/plugins/legacy/status", nil)
147 managementRec := httptest.NewRecorder()
148 if host.ServeManagementHTTP(managementRec, managementReq) {
149 t.Fatal("legacy menu route was served as Management API route")
150 }
151
152 resourceReq := httptest.NewRequest(http.MethodGet, "/v0/resource/plugins/legacy/status", nil)
153 resourceRec := httptest.NewRecorder()
154 if !host.ServeResourceHTTP(resourceRec, resourceReq) {
155 t.Fatal("legacy menu route was not served as resource route")
156 }
157 if resourceRec.Body.String() != "legacy" {
158 t.Fatalf("resource body = %q, want legacy", resourceRec.Body.String())
159 }
160}
161
162func TestRegisteredPluginsIncludesResourceMenus(t *testing.T) {
163 plugin := &managementPluginDouble{
164 routes: []pluginapi.ManagementRoute{
106 {165 {
107 Method: http.MethodGet,166 Method: http.MethodGet,
108 Path: "/plugins/menu/hidden",167 Path: "/plugins/menu/hidden",
@@ -110,11 +169,12 @@ func TestRegisteredPluginsIncludesGETManagementMenus(t *testing.T) { · 1 unmodified line
110 return pluginapi.ManagementResponse{}, nil169 return pluginapi.ManagementResponse{}, nil
111 }),170 }),
112 },171 },
172 },
173 resources: []pluginapi.ResourceRoute{
113 {174 {
114 Method: http.MethodPost,175 Path: "/status",
115 Path: "/plugins/menu/run",176 Menu: "Status",
116 Menu: "Run",177 Description: "Shows plugin status.",
117 Description: "Runs a plugin action.",
118 Handler: managementHandlerFunc(func(context.Context, pluginapi.ManagementRequest) (pluginapi.ManagementResponse, error) {178 Handler: managementHandlerFunc(func(context.Context, pluginapi.ManagementRequest) (pluginapi.ManagementResponse, error) {
119 return pluginapi.ManagementResponse{}, nil179 return pluginapi.ManagementResponse{}, nil
120 }),180 }),
@@ -136,17 +196,18 @@ func TestRegisteredPluginsIncludesGETManagementMenus(t *testing.T) { · 15 unmodified lines
136 t.Fatalf("RegisteredPlugins()[0].Menus = %#v, want one visible GET menu", plugins[0].Menus)196 t.Fatalf("RegisteredPlugins()[0].Menus = %#v, want one visible GET menu", plugins[0].Menus)
137 }197 }
138 menu := plugins[0].Menus[0]198 menu := plugins[0].Menus[0]
139 if menu.Path != "/v0/management/plugins/menu/status" || menu.Menu != "Status" || menu.Description != "Shows plugin status." {199 if menu.Path != "/v0/resource/plugins/menu/status" || menu.Menu != "Status" || menu.Description != "Shows plugin status." {
140 t.Fatalf("menu = %#v, want normalized status menu", menu)200 t.Fatalf("menu = %#v, want normalized status menu", menu)
141 }201 }
142}202}
143 203
144type managementPluginDouble struct {204type managementPluginDouble struct {
145 routes []pluginapi.ManagementRoute205 routes []pluginapi.ManagementRoute
206 resources []pluginapi.ResourceRoute
146}207}
147 208
148func (p *managementPluginDouble) RegisterManagement(context.Context, pluginapi.ManagementRegistrationRequest) (pluginapi.ManagementRegistrationResponse, error) {209func (p *managementPluginDouble) RegisterManagement(context.Context, pluginapi.ManagementRegistrationRequest) (pluginapi.ManagementRegistrationResponse, error) {
149 return pluginapi.ManagementRegistrationResponse{Routes: p.routes}, nil210 return pluginapi.ManagementRegistrationResponse{Routes: p.routes, Resources: p.resources}, nil
150}211}
151 212
152type managementHandlerFunc func(context.Context, pluginapi.ManagementRequest) (pluginapi.ManagementResponse, error)213type managementHandlerFunc func(context.Context, pluginapi.ManagementRequest) (pluginapi.ManagementResponse, error)
internal/pluginhost/rpc_client.go+6−1
@@ -498,7 +498,12 @@ func (a *rpcPluginAdapter) RegisterManagement(ctx context.Context, req pluginapi · 497 unmodified lines
498 route.Handler = a498 route.Handler = a
499 routes = append(routes, route)499 routes = append(routes, route)
500 }500 }
501 return pluginapi.ManagementRegistrationResponse{Routes: routes}, nil501 resources := make([]pluginapi.ResourceRoute, 0, len(resp.Resources))
502 for _, route := range resp.Resources {
503 route.Handler = a
504 resources = append(resources, route)
505 }
506 return pluginapi.ManagementRegistrationResponse{Routes: routes, Resources: resources}, nil
502}507}
503 508
504func (a *rpcPluginAdapter) HandleManagement(ctx context.Context, req pluginapi.ManagementRequest) (pluginapi.ManagementResponse, error) {509func (a *rpcPluginAdapter) HandleManagement(ctx context.Context, req pluginapi.ManagementRequest) (pluginapi.ManagementResponse, error) {
internal/pluginhost/rpc_schema.go+2−1
@@ -103,7 +103,8 @@ type rpcThinkingApplyRequest struct { · 102 unmodified lines
103}103}
104 104
105type rpcManagementRegistrationResponse struct {105type rpcManagementRegistrationResponse struct {
106 Routes []pluginapi.ManagementRoute `json:"routes,omitempty"`106 Routes []pluginapi.ManagementRoute `json:"routes,omitempty"`
107 Resources []pluginapi.ResourceRoute `json:"resources,omitempty"`
107}108}
108 109
109type rpcEmptyResponse struct{}110type rpcEmptyResponse struct{}
internal/pluginhost/snapshot.go+2−6
@@ -1,7 +1,6 @@
1package pluginhost1package pluginhost
2 2
3import (3import (
4 "net/http"
5 "sort"4 "sort"
6 "strings"5 "strings"
7 6
@@ -29,7 +28,7 @@ type RegisteredPluginInfo struct { · 21 unmodified lines
29 Menus []RegisteredPluginMenu28 Menus []RegisteredPluginMenu
30}29}
31 30
32// RegisteredPluginMenu describes a plugin-owned GET Management API menu entry.31// RegisteredPluginMenu describes a plugin-owned resource menu entry.
33type RegisteredPluginMenu struct {32type RegisteredPluginMenu struct {
34 Path string33 Path string
35 Menu string34 Menu string
@@ -67,10 +66,7 @@ func (h *Host) registeredPluginMenus() map[string][]RegisteredPluginMenu { · 31 unmodified lines
67 }66 }
68 h.mu.Lock()67 h.mu.Lock()
69 defer h.mu.Unlock()68 defer h.mu.Unlock()
70 for _, record := range h.managementRoutes {69 for _, record := range h.resourceRoutes {
71 if !strings.EqualFold(strings.TrimSpace(record.route.Method), http.MethodGet) {
72 continue
73 }
74 menu := strings.TrimSpace(record.route.Menu)70 menu := strings.TrimSpace(record.route.Menu)
75 if menu == "" {71 if menu == "" {
76 continue72 continue