- 追加された行はこの色です。
- 削除された行はこの色です。
#author("2022-01-04T06:31:38+00:00","","")
#mynavi(Grafana);
#setlinebreak(on);
* 概要 [#p3e34a19]
#html(<div class="pl10">)
ここではバックエンドデータソースプラグインの開発手順を記載する。
Grafanaのバックエンドデータソースプラグインの開発手順を記載する。
#html(</div>)
* 目次 [#s4a88cd0]
#contents
- 関連
-- [[Grafana]]
-- [[Grafanaプラグイン開発]]
- 参考
-- https://grafana.com/tutorials/build-a-data-source-backend-plugin/
* 開発環境の構築 [#f9652aa4]
#html(<div class="pl10">)
https://grafana.com/tutorials/build-a-data-source-backend-plugin/ を参考に開発環境を構築する。
ビルドには以下が必要との事 (2021/10 現在)
- Grafana 7.0
- Go 1.14+
- Mage
- NodeJS
- yarn
セットアップ
#mycode2(){{
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
}}
#html(</div>)
* プラグインの雛形の作成 [#t99c9af9]
#html(<div class="pl10">)
#myterm(){{
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/
}}
ビルド確認
#myterm2(){{
cd my-backend-datasource-test
go get -u github.com/grafana/grafana-plugin-sdk-go
go mod tidy
mage -v
}}
#html(</div>)
* データソースプラグインの作成 [#qda4d0a9]
#html(<div class="pl10">)
バックエンドのデータソースプラグインは設定画面とバックエンド処理の2つで成り立っており、フロント部分は TypeScrypt 、バックエンド部分は Go で作成する必要がある。
その為、ビルドは別々に行う事になるが、ビルド結果は両方とも dist 配下に出力される為、プラグインのデプロイは dist フォルダを grafana のプラグインディレクトリに配置するだけでOK。
* データソース 及び クエリの設定画面 [#u62a127d]
#html(<div class="pl10">)
各設定画面は、データソース自体の設定画面と各パネルで使用するクエリ編集画面用の2つある。
** データソース設定画面( ConfigEditor.tsx ) の作成 [#b8d61e88]
#html(<div class="pl10">)
中身は react コンポーネントだが、各入力フィールドは Grafana の UIコンポーネントを利用して作成する。
通常の必要なフィールドは FormField、よりセキュアな情報を扱うフィールドは SecretFormField コンポーネントを利用して作成する。
以下、雛形生成された ConfigEditor.tsx より抜粋。
#mycode2(){{
:
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 メソッドを実装する必要がある。
※セキュア項目は onReset 時のメソッドも必要。
以下、雛形生成された ConfigEditor.tsx より抜粋。
#mycode2(){{
:
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 に追加する。
デフォルトの状態だと、通常の設定項目は MyDataSourceOptions、セキュア項目は MySecureJsonData に定義されている為、ここに追加する。
types.ts
#mycode2(){{
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;
}
}}
#html(</div>)
** クエリの設定画面 ( QueryEditor.tsx ) の作成 [#u7fe10ec]
#html(<div class="pl10">)
クエリ画面側も設定画面と同じく react コンポーネントとして作成する。
※項目自体を追加する場合は、設定画面と同じく types.ts の編集も必要。
以下、雛形生成された QueryEditor.tsx をそのまま記載。
#mycode2(){{
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>
);
}
}
}}
#html(</div>)
ビルド等は yarn を使用して行う事ができる。( package.json を参照 )
依存モジュールのインストール
#myterm2(){{
yarn install
}}
ビルド
#myterm2(){{
yarn build
}}
#html(</div>)
* バックエンド処理の作成 [#fb4dac0b]
#html(<div class="pl10">)
バックエンド部分は pkg/plugin/plugin.go に記述する。
クエリに対する処理は query メソッドに記述する。
#mycode2(){{
:
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 メソッドに記述する。
雛形生成直後の状態だとランダムでエラーになるように記述されている為、ヘルスチェックを行わない場合でも必ず書き換えが必要。
#mycode2(){{
:
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
}
:
}}
#html(</div>)
#html(</div>)
* Grafanaへの配備 [#cce9f58f]
#html(<div class="pl10">)
grafana の plugins ディレクトリに dist フォルダ事コピーする。
Grafana8 からは署名が必須になっているが、開発途中などで、直ちに署名できない場合は、
環境変数 GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS に該当のプラグイン名を列挙する事でプラグインのロードが可能。
https://grafana.com/docs/grafana/latest/developers/plugins/sign-a-plugin/
https://grafana.com/docs/grafana/latest/administration/configuration/#allow_loading_unsigned_plugins
#html(</div>)
* サンプル作成 [#pecca4c9]
#html(<div class="pl10">)
サンプルとして他サーバで動作する WebAPI をデータソースとする、バックエンドデータソースを作成してみる。
#TODO
#html(</div>)