-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Docs: Add/update resource handler topics for datasource and app plugi…
…ns (#1081)
- Loading branch information
Showing
3 changed files
with
199 additions
and
116 deletions.
There are no files selected for viewing
59 changes: 59 additions & 0 deletions
59
docusaurus/docs/how-to-guides/app-plugins/add-resource-handler.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
--- | ||
id: add-resource-handler | ||
title: Add resource handler for app plugins | ||
description: Learn how to add a resource handler for app plugins. | ||
keywords: | ||
- grafana | ||
- plugins | ||
- plugin | ||
- app | ||
- resource | ||
- resource handler | ||
--- | ||
|
||
import ImplementResourceHandler from '@shared/implement-resource-handler.md'; | ||
|
||
# Add resource handler for app plugins | ||
|
||
You can add a resource handler to your app backend to extend the Grafana HTTP API with your own app-specific routes. This guide explains why you may want to add [resource](../../key-concepts/backend-plugins/#resources) handlers and some common ways for doing so. | ||
|
||
## Uses of resource handlers | ||
|
||
An app often integrates with a HTTP service of some kind, e.g. a 3rd party service, to retrieve and send data. For example, this service might have specific authentication and authorization needs or a response format not suitable to return to Grafana and the plugin frontend. | ||
|
||
In addition, you might want to [secure your resources](implement-rbac-in-app-plugins.md#secure-backend-resources) so that only users with a certain permission can access certain routes. | ||
|
||
Resource handlers are also useful for building control panels that allow the user to write back to the app. For example, you could add a resource handler to update the state of an IoT device. | ||
|
||
<ImplementResourceHandler /> | ||
|
||
## Accessing app resources | ||
|
||
Once implemented you can access the resources using the Grafana HTTP API and from the frontend. | ||
|
||
### Using the Grafana HTTP API | ||
|
||
You can access the resources through the Grafana HTTP API by using the endpoint, `http://<GRAFANA_HOSTNAME>:<PORT>/api/plugins/<PLUGIN_ID>/resources{/<RESOURCE>}`. The `PLUGIN_ID` is the plugin identifier that uniquely identifies your app and the `RESOURCE` depends on how the resource handler is implemented and what resources (routes) are supported. | ||
|
||
With the above example you can access the following resources: | ||
|
||
- HTTP GET `http://<GRAFANA_HOSTNAME>:<PORT>/api/plugins/<PLUGIN_ID>/resources/namespaces` | ||
- HTTP GET `http://<GRAFANA_HOSTNAME>:<PORT>/api/plugins/<PLUGIN_ID>/resources/projects` | ||
|
||
### From the frontend | ||
|
||
You can access your resources in a compontent using the `get` function of the `backendSrv` runtime service to send a HTTP GET request to `http://<GRAFANA_HOSTNAME>:<PORT>/api/plugins/<PLUGIN_ID>/resources/namespaces` | ||
|
||
```typescript | ||
import { getBackendSrv } from '@grafana/runtime'; | ||
|
||
const namespaces = await getBackendSrv().get(`/api/plugins/<PLUGIN_ID>/resources/namespaces`); | ||
``` | ||
|
||
## Additional examples | ||
|
||
Some other examples of using resource handlers and the [`httpadapter`](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter) package: | ||
|
||
- The [app-with-backend](https://github.com/grafana/grafana-plugin-examples/tree/main/examples/app-with-backend) example: | ||
- [create resource handler](https://github.com/grafana/grafana-plugin-examples/blob/main/examples/app-with-backend/pkg/plugin/app.go) and [register routes](https://github.com/grafana/grafana-plugin-examples/blob/main/examples/app-with-backend/pkg/plugin/resources.go) in the backend. | ||
- use [backendSrv](https://github.com/grafana/grafana-plugin-examples/blob/main/examples/app-with-backend/src/pages/PageOne/PageOne.tsx) to call resources. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
## Implement the resource handler interface | ||
|
||
To add a resource handler to your backend plugin, you need to implement the `backend.CallResourceHandler` interface. | ||
|
||
There are two ways you can implement this in your plugin, [using the `httpadapter` package](#using-the-httpadapter-package) or [manually implementing it](#manually-implementing-backendcallresourcehandler) in your plugin. | ||
|
||
### Using the `httpadapter` package | ||
|
||
The [`httpadapter`](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter) package provided by the [Grafana Plugin SDK for Go](../../key-concepts/backend-plugins/grafana-plugin-sdk-for-go) is the recommended way for handling resources. This package provides support for handling resource calls using using the [`http.Handler`](https://pkg.go.dev/net/http#Handler) interface and allows responding to HTTP requests in a more Go-agnostic way and makes it easier to support multiple routes and methods (GET, POST etc). | ||
|
||
Using [`http.Handler`](https://pkg.go.dev/net/http#Handler) allows you to also use Go’s built-in router functionality called [`ServeMux`](https://pkg.go.dev/net/http#ServeMux) or your preferred HTTP router library (for example, [`gorilla/mux`](https://github.com/gorilla/mux)). | ||
|
||
:::note | ||
|
||
Go 1.22 [includes routing enhancement](https://go.dev/blog/routing-enhancements) that adds support for method matching and wildcards using the [`ServeMux`](https://pkg.go.dev/net/http#ServeMux). | ||
|
||
::: | ||
|
||
In the following example we demonstrate using the `httpadapter` package, `ServeMux` and `http.Handler` to add support for retrieving namespaces (`/namespaces`), projects (`/projects`) and updating the state of some device (`/device`) : | ||
|
||
```go | ||
package myplugin | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
|
||
"github.com/grafana/grafana-plugin-sdk-go/backend" | ||
"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter" | ||
) | ||
|
||
type MyPlugin struct { | ||
resourceHandler backend.CallResourceHandler | ||
} | ||
|
||
func New() *MyPlugin { | ||
p := &MyPlugin{} | ||
mux := http.NewServeMux() | ||
mux.HandleFunc("/namespaces", p.handleNamespaces) | ||
mux.HandleFunc("/projects", p.handleProjects) | ||
p.resourceHandler := httpadapter.New(mux) | ||
return p | ||
} | ||
|
||
func (p *MyPlugin) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { | ||
return p.resourceHandler.CallResource(ctx, req) | ||
} | ||
|
||
func (p *MyPlugin) handleNamespaces(rw http.ResponseWriter, req *http.Request) { | ||
rw.Header().Add("Content-Type", "application/json") | ||
_, err := rw.Write([]byte(`{ "namespaces": ["ns-1", "ns-2"] }`)) | ||
if err != nil { | ||
return | ||
} | ||
rw.WriteHeader(http.StatusOK) | ||
} | ||
|
||
func (p *MyPlugin) handleProjects(rw http.ResponseWriter, req *http.Request) { | ||
rw.Header().Add("Content-Type", "application/json") | ||
_, err := rw.Write([]byte(`{ "projects": ["project-1", "project-2"] }`)) | ||
if err != nil { | ||
return | ||
} | ||
rw.WriteHeader(http.StatusOK) | ||
} | ||
``` | ||
|
||
#### Accessing the backend plugin context | ||
|
||
You can use the [backend.PluginConfigFromContext](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go/backend#PluginConfigFromContext) function to access [backend.PluginContext](https://pkg.go.dev/github.com/grafana/grafana-plugin-sdk-go/backend#PluginContext). This holds contextual information about a plugin request, such as the user performing the request: | ||
|
||
```go | ||
func (p *MyPlugin) handleSomeRoute(rw http.ResponseWriter, req *http.Request) { | ||
pCtx := backend.PluginConfigFromContext(req.Context()) | ||
bytes, err := json.Marshal(pCtx.User) | ||
if err != nil { | ||
return | ||
} | ||
|
||
rw.Header().Add("Content-Type", "application/json") | ||
_, err := rw.Write(bytes) | ||
if err != nil { | ||
return | ||
} | ||
rw.WriteHeader(http.StatusOK) | ||
} | ||
``` | ||
|
||
### Manually implementing `backend.CallResourceHandler` | ||
|
||
Manually implementing the `backend.CallResourceHandler` interface might be enough for the basic needs. To support a couple of different routes retrieving data you can use a switch with the `req.Path`: | ||
|
||
```go | ||
func (p *MyPlugin) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { | ||
switch req.Path { | ||
case "namespaces": | ||
return sender.Send(&backend.CallResourceResponse{ | ||
Status: http.StatusOK, | ||
Body: []byte(`{ "namespaces": ["ns-1", "ns-2"] }`), | ||
}) | ||
case "projects": | ||
return sender.Send(&backend.CallResourceResponse{ | ||
Status: http.StatusOK, | ||
Body: []byte(`{ "projects": ["project-1", "project-2"] }`), | ||
}) | ||
default: | ||
return sender.Send(&backend.CallResourceResponse{ | ||
Status: http.StatusNotFound, | ||
}) | ||
} | ||
} | ||
``` |