概要 †Grafanaのバックエンドデータソースプラグインの開発手順を記載する。 目次 †
開発環境の構築 †https://grafana.com/tutorials/build-a-data-source-backend-plugin/ を参考に開発環境を構築する。 ビルドには以下が必要との事 (2021/10 現在)
セットアップ cd /tmp echo "## apt update ##" apt update echo "## apt install (initial) ##" apt install -y wget curl git curl nodejs npm echo "## go install ##" curl -L -o go1.16.9.linux-amd64.tar.gz https://golang.org/dl/go1.16.9.linux-amd64.tar.gz rm -rf /usr/local/go && tar -C /usr/local -xzf go1.16.9.linux-amd64.tar.gz echo "export PATH=\$PATH:/usr/local/go/bin" >> ~/.bashrc echo "## npm install n ##" npm install -g n echo "## n install 16.11.0 ##" n install 16.11.0 echo "## apt remove nodejs npm ##" apt remove -y nodejs npm echo "## apt remove yarn ##" apt remove -y cmdtest yarn echo "## re install yarn ##" curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list apt update && apt install -y yarn echo "## install mage ##" mkdir -p ~/go/bin git clone https://github.com/magefile/mage cd mage GOPATH=~/go go run bootstrap.go echo "export GOPATH=\$HOME/go" >>~/.bashrc echo "export PATH=\$PATH:\$GOPATH/bin" >>~/.bashrc source ~/.bashrc echo "## npm install @grafana/toolkit ##" npm install -g @grafana/toolkit プラグインの雛形の作成 †npx @grafana/toolkit plugin:create my-backend-datasource-test Need to install the following packages: Ok to proceed? (y) y ? Select plugin type Backend Datasource Plugin ✔ Fetching plugin template... ? Plugin name my-backend-datasource-test ? Organization (used as part of plugin ID) sample ? Description ? Keywords (separated by comma) ? Author myname ? Your URL (i.e. organisation url) Your plugin details --- Name: my-backend-datasource-test ID: sample-my-backend-datasource-test Description: Keywords: [] Author: myname Organisation: sample Website: ? Is that ok? Yes ✔ Saving package.json and plugin.json files ✔ Cleaning Congrats! You have just created Grafana Backend Datasource Plugin. Learn more about Grafana Plugins at https://grafana.com/docs/grafana/latest/plugins/developing/development/ ビルド確認 cd my-backend-datasource-test go get -u github.com/grafana/grafana-plugin-sdk-go go mod tidy mage -v データソースプラグインの作成 †バックエンドのデータソースプラグインは設定画面とバックエンド処理の2つで成り立っており、フロント部分は TypeScrypt 、バックエンド部分は Go で作成する必要がある。 データソース 及び クエリの設定画面 †各設定画面は、データソース自体の設定画面と各パネルで使用するクエリ編集画面用の2つある。 データソース設定画面( ConfigEditor.tsx ) の作成 †中身は react コンポーネントだが、各入力フィールドは Grafana の UIコンポーネントを利用して作成する。 以下、雛形生成された ConfigEditor.tsx より抜粋。 : return ( <div className="gf-form-group"> <div className="gf-form"> <FormField label="Path" labelWidth={6} inputWidth={20} onChange={this.onPathChange} value={jsonData.path || ''} placeholder="json field returned to frontend" /> </div> <div className="gf-form-inline"> <div className="gf-form"> <SecretFormField isConfigured={(secureJsonFields && secureJsonFields.apiKey) as boolean} value={secureJsonData.apiKey || ''} label="API Key" placeholder="secure json field (backend only)" labelWidth={6} inputWidth={20} onReset={this.onResetAPIKey} onChange={this.onAPIKeyChange} /> </div> </div> </div> ); } } 値変更時の処理は、項目毎に onChange メソッドを実装する必要がある。 以下、雛形生成された ConfigEditor.tsx より抜粋。 : export class ConfigEditor extends PureComponent<Props, State> { onPathChange = (event: ChangeEvent<HTMLInputElement>) => { const { onOptionsChange, options } = this.props; const jsonData = { ...options.jsonData, path: event.target.value, }; onOptionsChange({ ...options, jsonData }); }; // Secure field (only sent to the backend) onAPIKeyChange = (event: ChangeEvent<HTMLInputElement>) => { const { onOptionsChange, options } = this.props; onOptionsChange({ ...options, secureJsonData: { apiKey: event.target.value, }, }); }; onResetAPIKey = () => { const { onOptionsChange, options } = this.props; onOptionsChange({ ...options, secureJsonFields: { ...options.secureJsonFields, apiKey: false, }, secureJsonData: { ...options.secureJsonData, apiKey: '', }, }); }; : 項目を追加する場合は、 types.ts に追加する。 types.ts import { DataQuery, DataSourceJsonData } from '@grafana/data'; export interface MyQuery extends DataQuery { // ここはクエリ画面の項目定義 queryText?: string; constant: number; withStreaming: boolean; } export const defaultQuery: Partial<MyQuery> = { // ここはクエリ画面の項目のデフォルト値 constant: 6.5, withStreaming: false, }; /** * These are options configured for each DataSource instance. */ export interface MyDataSourceOptions extends DataSourceJsonData { // ここに通常の設定項目 path?: string; } /** * Value that is used in the backend, but never sent over HTTP to the frontend */ export interface MySecureJsonData { // ここにセキュアな設定項目 apiKey?: string; } クエリの設定画面 ( QueryEditor.tsx ) の作成 †クエリ画面側も設定画面と同じく react コンポーネントとして作成する。 以下、雛形生成された QueryEditor.tsx をそのまま記載。 import { LegacyForms } from '@grafana/ui'; import { QueryEditorProps } from '@grafana/data'; import { DataSource } from './datasource'; import { defaultQuery, MyDataSourceOptions, MyQuery } from './types'; const { FormField, Switch } = LegacyForms; type Props = QueryEditorProps<DataSource, MyQuery, MyDataSourceOptions>; export class QueryEditor extends PureComponent<Props> { onQueryTextChange = (event: ChangeEvent<HTMLInputElement>) => { const { onChange, query } = this.props; onChange({ ...query, queryText: event.target.value }); }; onConstantChange = (event: ChangeEvent<HTMLInputElement>) => { const { onChange, query, onRunQuery } = this.props; onChange({ ...query, constant: parseFloat(event.target.value) }); // executes the query onRunQuery(); }; onWithStreamingChange = (event: SyntheticEvent<HTMLInputElement>) => { const { onChange, query, onRunQuery } = this.props; onChange({ ...query, withStreaming: event.currentTarget.checked }); // executes the query onRunQuery(); }; render() { const query = defaults(this.props.query, defaultQuery); const { queryText, constant, withStreaming } = query; return ( <div className="gf-form"> <FormField width={4} value={constant} onChange={this.onConstantChange} label="Constant" type="number" step="0.1" /> <FormField labelWidth={8} value={queryText || ''} onChange={this.onQueryTextChange} label="Query Text" tooltip="Not used yet" /> <Switch checked={withStreaming || false} label="Enable streaming (v8+)" onChange={this.onWithStreamingChange} /> </div> ); } } ビルド等は yarn を使用して行う事ができる。( package.json を参照 ) 依存モジュールのインストール yarn install ビルド yarn build バックエンド処理の作成 †バックエンド部分は pkg/plugin/plugin.go に記述する。 クエリに対する処理は query メソッドに記述する。 : func (d *SampleDatasource) query(_ context.Context, pCtx backend.PluginContext, query backend.DataQuery) backend.DataResponse { response := backend.DataResponse{} // Unmarshal the JSON into our queryModel. var qm queryModel response.Error = json.Unmarshal(query.JSON, &qm) if response.Error != nil { return response } log.DefaultLogger.Info("### pCtx ###", "pCtx", pCtx) log.DefaultLogger.Info("### query ###", "query", query) // create data frame response. frame := data.NewFrame("response") // add fields. frame.Fields = append(frame.Fields, data.NewField("time", nil, []time.Time{query.TimeRange.From, query.TimeRange.To}), data.NewField("values", nil, []int64{10, 20}), ) // add the frames to the response. response.Frames = append(response.Frames, frame) return response } : 設定画面でデータソース作成時行うヘルスチェックの内容は CheckHealth メソッドに記述する。 : func (d *SampleDatasource) CheckHealth(_ context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { log.DefaultLogger.Info("CheckHealth called", "request", req) var status = backend.HealthStatusOk var message = "Data source is working" // ランダムでエラーになるように記述されている為、コメントアウトしておく //if rand.Int()%2 == 0 { // status = backend.HealthStatusError // message = "randomized error" //} return &backend.CheckHealthResult{ Status: status, Message: message, }, nil } : Grafanaへの配備 †grafana の plugins ディレクトリに dist フォルダ事コピーする。 https://grafana.com/docs/grafana/latest/developers/plugins/sign-a-plugin/ サンプル作成 †サンプルとして他サーバで動作する WebAPI をデータソースとする、バックエンドデータソースを作成してみる。 TODO:
|