Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] feat: backend-plugin conversion #77

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Magefile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//+build mage

package main

import (
// mage:import
build "github.com/grafana/grafana-plugin-sdk-go/build"
)

// Default configures the default target.
var Default = build.BuildAll
5 changes: 5 additions & 0 deletions conf/custom.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[plugins]
allow_loading_unsigned_plugins = instana-datasource

[log]
level=debug
Binary file added dist/instana-grafana-datasource_darwin_amd64
Binary file not shown.
Binary file added dist/instana-grafana-datasource_linux_amd64
Binary file not shown.
Binary file added dist/instana-grafana-datasource_linux_arm64
Binary file not shown.
Binary file added dist/instana-grafana-datasource_windows_amd64.exe
Binary file not shown.
7,043 changes: 7,041 additions & 2 deletions dist/module.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/module.js.map

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions dist/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
"name": "Instana",
"id": "instana-datasource",
"metrics": true,
"backend": true,
"annotations": false,
"alerting": false,
"logs": false,
"executable": "instana-grafana-datasource",
"info": {
"description": "Grafana datasource plugin for Instana: Automatic Infrastructure and Application Monitoring",
"author": {
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ services:
- 3000:3000
volumes:
- ./dist:/var/lib/grafana/plugins/instana
- ./conf/custom.ini:/etc/grafana/grafana.ini
- ./provisioning/datasources:/etc/grafana/provisioning/datasources
environment:
INSTANA_UI_BACKEND_URL: ${INSTANA_UI_BACKEND_URL}
Expand Down
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module github.com/instana/instana-grafana-datasource

go 1.14

require (
github.com/avast/retry-go v2.6.1+incompatible
github.com/grafana/grafana-plugin-sdk-go v0.77.0
github.com/magefile/mage v1.10.0 // indirect
)
190 changes: 190 additions & 0 deletions go.sum

Large diffs are not rendered by default.

66 changes: 66 additions & 0 deletions pkg/datasource/checkHealth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package datasource

import (
"context"
"encoding/json"
"fmt"
"strings"

"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/instana/instana-grafana-datasource/pkg/datasource/settings"
)

type instanaHealth struct {
health string
messages []string
}

// CheckHealth handles health checks sent from Grafana to the plugin.
func (instana *InstanaDataSource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
log.DefaultLogger.Info("CheckHealth", "request", req)
instance, err := instana.im.Get(req.PluginContext)
if err != nil {
return &backend.CheckHealthResult{
Status: backend.HealthStatusError,
Message: fmt.Sprintf("Issue reading Instana Settings: %s.", err),
}, nil
}
settings, valid := instance.(settings.InstanaSettings)
if !valid {
return &backend.CheckHealthResult{
Status: backend.HealthStatusError,
Message: fmt.Sprintf("Issue reading Instana Settings. Unknown Error."),
}, nil
}

log.DefaultLogger.Info("CheckHealth", "url", settings.Options.URL, "hasToken", settings.Options.APIToken != "")
res, err := instana.GetRequest(settings, "/api/instana/health")
if err != nil {
return &backend.CheckHealthResult{
Status: backend.HealthStatusError,
Message: fmt.Sprintf("Issue connecting to Instana: %s.", err),
}, nil
}

health := &instanaHealth{}
err = json.Unmarshal(*res, health)
if err != nil {
return &backend.CheckHealthResult{
Status: backend.HealthStatusError,
Message: fmt.Sprintf("Invalid response from Instana: %s.", err),
}, nil
}

if health.health == "RED" {
return &backend.CheckHealthResult{
Status: backend.HealthStatusError,
Message: fmt.Sprintf("Instana reporting not healthy. Health: %s; Messages: [%s].", health.health, strings.Join(health.messages, ", ")),
}, nil
}

return &backend.CheckHealthResult{
Status: backend.HealthStatusOk,
Message: "Successfully connected to the Instana API.",
}, nil
}
24 changes: 24 additions & 0 deletions pkg/datasource/datasource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package datasource

import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
"github.com/instana/instana-grafana-datasource/pkg/datasource/settings"
)

// InstanaDataSource is the grafana backend datasource for Instana
type InstanaDataSource struct {
im instancemgmt.InstanceManager
}

// NewInstanceCallback returns instana settings for a given backend settings
func NewInstanceCallback(setting backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
return settings.From(&setting)
}

// New returns a new InstanaDataSource
func New(im instancemgmt.InstanceManager) *InstanaDataSource {
return &InstanaDataSource{
im: im,
}
}
7 changes: 7 additions & 0 deletions pkg/datasource/instanaQuery.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package datasource

import "github.com/grafana/grafana-plugin-sdk-go/backend"

type InstanaQuery struct {
*backend.DataQuery
}
35 changes: 35 additions & 0 deletions pkg/datasource/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package datasource

import (
"context"

"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
"github.com/grafana/grafana-plugin-sdk-go/data"
)

// QueryData queries Instana with the given query
func (instana *InstanaDataSource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
log.DefaultLogger.Info("QueryData", "request", req)

// create response struct
response := backend.NewQueryDataResponse()

// loop over queries and execute them individually.
for _, q := range req.Queries {
res := instana.query(ctx, q)

// save the response in a hashmap
// based on with RefID as identifier
response.Responses[q.RefID] = res
}

return response, nil
}

func (instana *InstanaDataSource) query(ctx context.Context, query backend.DataQuery) backend.DataResponse {
return backend.DataResponse{
Frames: make(data.Frames, 0),
Error: nil,
}
}
93 changes: 93 additions & 0 deletions pkg/datasource/request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package datasource

import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"strings"

retry "github.com/avast/retry-go"
"github.com/instana/instana-grafana-datasource/pkg/datasource/settings"
)

// RequestOptions represents the request options to use for a request
type RequestOptions struct {
Method string
Path string
MaxRetries uint
}

// GetRequest does a GET against an Instana endpoint
func (instana *InstanaDataSource) GetRequest(settings settings.InstanaSettings, path string) (*json.RawMessage, error) {
options := RequestOptions{
Method: "GET",
Path: path,
}

return instana.DoRequest(settings, options)
}

// DoRequest performs a generic request against an Instana endpoint
func (instana *InstanaDataSource) DoRequest(settings settings.InstanaSettings, request RequestOptions) (*json.RawMessage, error) {
opts := settings.Options
url := fmt.Sprintf("%s%s", settings.Options.URL, request.Path)
req, err := http.NewRequest(request.Method, url, nil)
if err != nil {
return nil, err
}

if !opts.UseProxy {
req.Header.Set("Authorization", fmt.Sprintf("apiToken %s", opts.APIToken))
}

var body []byte
client := settings.Client
err = retry.Do(func() error {
response, err := client.Do(req)
if err != nil {
return fmt.Errorf("Connection issue while attempted to connect to the Instana API: %s", err)
}

if response.StatusCode == 429 {
return retry.Unrecoverable(errors.New("API limit is reached"))
}

if response.StatusCode == 401 {
return retry.Unrecoverable(errors.New("Unauthorized Request. Please verify the API Token is correct"))
}

if response.StatusCode >= 400 && response.StatusCode < 500 {
return fmt.Errorf("Error (%v) connecting to the Instana API: %s", response.StatusCode, response.Status)
}

if response.StatusCode >= 500 {
return retry.Unrecoverable(fmt.Errorf("Error (%v) connecting to the Instana API: %s", response.StatusCode, response.Status))
}

body, err = ioutil.ReadAll(response.Body)
if err != nil {
return err
}

return nil
}, retry.Attempts(request.MaxRetries+1))

if err != nil {
retryErrs, _ := err.(retry.Error)
errs := []string{}
for _, retryErr := range retryErrs.WrappedErrors() {
errs = append(errs, retryErr.Error())
}
return nil, fmt.Errorf("%s", strings.Join(errs, "\n "))
}

res := &json.RawMessage{}
err = json.Unmarshal(body, res)
if err != nil {
return nil, err
}

return res, nil
}
13 changes: 13 additions & 0 deletions pkg/datasource/resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package datasource

import (
"context"

"github.com/grafana/grafana-plugin-sdk-go/backend"
)

// CallResource implements resource handler to allow frontend to query backend details
func (instana *InstanaDataSource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {

return nil
}
42 changes: 42 additions & 0 deletions pkg/datasource/settings/settings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package settings

import (
"encoding/json"
"fmt"
"net/http"

backend "github.com/grafana/grafana-plugin-sdk-go/backend"
)

// InstanaSettings extends DataSourceInstanceSettings with the
// Instana Specific Settings
type InstanaSettings struct {
*backend.DataSourceInstanceSettings
Options *InstanaOptions
Client *http.Client
}

// InstanaOptions is the parse Instana specific options for talking to the API
type InstanaOptions struct {
URL string `json:"url"`
APIToken string `json:"apiToken"`
UseProxy bool `json:"useProxy"`
ShowOffline bool `json:"showOffline"`
AllowSLO bool `json:"allowSlo"`
QueryIntervalLimitInfra int `json:"queryinterval_limit_infra"`
QueryIntervalLimitAppMetrics int `json:"queryinterval_limit_app_metrics"`
QueryIntervalLimitAppCalls int `json:"queryinterval_limit_app_calls"`
QueryIntervalLimitWebsiteMetrics int `json:"queryinterval_limit_website_metrics"`
}

// From converts a generic settings struct into InstanaSettings
func From(settings *backend.DataSourceInstanceSettings) (InstanaSettings, error) {
options := InstanaOptions{}
err := json.Unmarshal(settings.JSONData, &options)
if err != nil {
return InstanaSettings{}, fmt.Errorf("Failed to parse Instana settings: %s", err)
}

instanaSettings := InstanaSettings{settings, &options, http.DefaultClient}
return instanaSettings, nil
}
21 changes: 21 additions & 0 deletions pkg/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package main

import (
"os"

"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
)

func main() {
// Start listening to requests send from Grafana. This call is blocking so
// it wont finish until Grafana shutsdown the process or the plugin choose
// to exit close down by itself
err := datasource.Serve(newDatasource())

// Log any error if we could start the plugin.
if err != nil {
log.DefaultLogger.Error(err.Error())
os.Exit(1)
}
}
22 changes: 22 additions & 0 deletions pkg/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package main

import (
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"

ds "github.com/instana/instana-grafana-datasource/pkg/datasource"
)

// newDatasource returns datasource.ServeOpts.
func newDatasource() datasource.ServeOpts {
// creates a instance manager for your plugin. The function passed
// into `NewInstanceManger` is called when the instance is created
// for the first time or when a datasource configuration changed.
im := datasource.NewInstanceManager(ds.NewInstanceCallback)
ds := ds.New(im)

return datasource.ServeOpts{
QueryDataHandler: ds,
CallResourceHandler: ds,
CheckHealthHandler: ds,
}
}
Loading