mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Prometheus: Type and flavor configuration (#56496)
* Adding two new fields to the data JSON in the prometheus datasource configuration: prometheusType, and prometheusVersion. * Version field will attempt to auto-detect via buildinfo API when prometheus Type is selected
This commit is contained in:
parent
8d0e24a622
commit
7ecbc98b3e
@ -59,6 +59,8 @@ datasources:
|
||||
jsonData:
|
||||
manageAlerts: true
|
||||
alertmanagerUid: gdev-alertmanager
|
||||
prometheusType: Prometheus #Cortex | Mimir | Prometheus | Thanos
|
||||
prometheusVersion: 2.40.0
|
||||
|
||||
- name: gdev-slow-prometheus
|
||||
type: prometheus
|
||||
|
@ -158,7 +158,7 @@ Since not all datasources have the same configuration settings we only have the
|
||||
| customQueryParameters | string | Prometheus | Query parameters to add, as a URL-encoded string. |
|
||||
| manageAlerts | boolean | Prometheus and Loki | Manage alerts via Alerting UI |
|
||||
| alertmanagerUid | string | Prometheus and Loki | UID of Alert Manager that manages Alert for this data source. |
|
||||
| esVersion | string | Elasticsearch | Elasticsearch version (E.g. `7.0.0`, `7.6.1`) |
|
||||
| esVersion | string | Elasticsearch | Elasticsearch version (e.g. `7.0.0`, `7.6.1`) |
|
||||
| timeField | string | Elasticsearch | Which field that should be used as timestamp |
|
||||
| interval | string | Elasticsearch | Index date time format. nil(No Pattern), 'Hourly', 'Daily', 'Weekly', 'Monthly' or 'Yearly' |
|
||||
| logMessageField | string | Elasticsearch | Which field should be used as the log message |
|
||||
@ -190,6 +190,8 @@ Since not all datasources have the same configuration settings we only have the
|
||||
| maxIdleConns | number | MySQL, PostgreSQL and MSSQL | Maximum number of connections in the idle connection pool (Grafana v5.4+) |
|
||||
| connMaxLifetime | number | MySQL, PostgreSQL and MSSQL | Maximum amount of time in seconds a connection may be reused (Grafana v5.4+) |
|
||||
| keepCookies | array | _HTTP\*_ | Cookies that needs to be passed along while communicating with datasources |
|
||||
| prometheusVersion | string | Prometheus | The version of the Prometheus datasource (e.g. `2.37.0`, `2.24.0`) |
|
||||
| prometheusType | string | Prometheus | The type of the Prometheus datasources (i.e. `Prometheus`, `Cortex`, `Thanos`, or `Mimir`) |
|
||||
|
||||
#### Secure Json Data
|
||||
|
||||
|
@ -21,25 +21,27 @@ Grafana includes built-in support for Prometheus. This topic explains options, v
|
||||
|
||||
To access Prometheus settings, hover your mouse over the **Configuration** (gear) icon, then click **Data Sources**, and then click the Prometheus data source.
|
||||
|
||||
| Name | Description |
|
||||
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `Name` | The data source name. This is how you refer to the data source in panels and queries. |
|
||||
| `Default` | Default data source that is pre-selected for new panels. |
|
||||
| `Url` | The URL of your Prometheus server, for example, `http://prometheus.example.org:9090`. |
|
||||
| `Access` | Only Server access mode is functional. If Server mode is already selected this option is hidden. Otherwise change to Server mode to prevent errors. |
|
||||
| `Basic Auth` | Enable basic authentication to the Prometheus data source. |
|
||||
| `User` | User name for basic authentication. |
|
||||
| `Password` | Password for basic authentication. |
|
||||
| `Scrape interval` | Set this to the typical scrape and evaluation interval configured in Prometheus. Defaults to 15s. |
|
||||
| `HTTP method` | Use either POST or GET HTTP method to query your data source. POST is the recommended and pre-selected method as it allows bigger queries. Change this to GET if you have a Prometheus version older than 2.1 or if POST requests are restricted in your network. |
|
||||
| `Disable metrics lookup` | Checking this option will disable the metrics chooser and metric/label support in the query field's autocomplete. This helps if you have performance issues with bigger Prometheus instances. |
|
||||
| `Custom Query Parameters` | Add custom parameters to the Prometheus query URL. For example `timeout`, `partial_response`, `dedup`, or `max_source_resolution`. Multiple parameters should be concatenated together with an '&'. |
|
||||
| **Exemplars configuration** | |
|
||||
| `Internal link` | Enable this option is you have an internal link. When you enable this option, you will see a data source selector. Select the backend tracing data store for your exemplar data. |
|
||||
| `Data source` | You will see this option only if you enable `Internal link` option. Select the backend tracing data store for your exemplar data. |
|
||||
| `URL` | You will see this option only if the `Internal link` option is disabled. Enter the full URL of the external link. You can interpolate the value from the field with `${__value.raw }` macro. |
|
||||
| `URL Label` | (Optional) add a custom display label to override the value of the `Label name` field. |
|
||||
| `Label name` | Add a name for the exemplar traceID property. |
|
||||
| Name | Description |
|
||||
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `Name` | The data source name. This is how you refer to the data source in panels and queries. |
|
||||
| `Default` | Default data source that is pre-selected for new panels. |
|
||||
| `Url` | The URL of your Prometheus server, for example, `http://prometheus.example.org:9090`. |
|
||||
| `Access` | Only Server access mode is functional. If Server mode is already selected this option is hidden. Otherwise change to Server mode to prevent errors. |
|
||||
| `Basic Auth` | Enable basic authentication to the Prometheus data source. |
|
||||
| `User` | User name for basic authentication. |
|
||||
| `Password` | Password for basic authentication. |
|
||||
| `Scrape interval` | Set this to the typical scrape and evaluation interval configured in Prometheus. Defaults to 15s. |
|
||||
| `HTTP method` | Use either POST or GET HTTP method to query your data source. POST is the recommended and pre-selected method as it allows bigger queries. Change this to GET if you have a Prometheus version older than 2.1 or if POST requests are restricted in your network. |
|
||||
| `Type` | The type of your Prometheus server, i.e `Prometheus`, `Cortex`, `Thanos` or `Mimir`. When this value is selected in the configuration UI, the Prometheus version field attempts to detect the version automatically using the Prometheus [buildinfo](https://semver.org/) API. Some Prometheus types do not support this API, and you will need to manually populate the version in those cases (e.g. Cortex). |
|
||||
| `Version` | The version of your Prometheus server, note that this field is not visible until the Prometheus type is selected. |
|
||||
| `Disable metrics lookup` | Checking this option will disable the metrics chooser and metric/label support in the query field's autocomplete. This helps if you have performance issues with bigger Prometheus instances. |
|
||||
| `Custom Query Parameters` | Add custom parameters to the Prometheus query URL. For example `timeout`, `partial_response`, `dedup`, or `max_source_resolution`. Multiple parameters should be concatenated together with an '&'. |
|
||||
| **Exemplars configuration** | |
|
||||
| `Internal link` | Enable this option is you have an internal link. When you enable this option, you will see a data source selector. Select the backend tracing data store for your exemplar data. |
|
||||
| `Data source` | You will see this option only if you enable `Internal link` option. Select the backend tracing data store for your exemplar data. |
|
||||
| `URL` | You will see this option only if the `Internal link` option is disabled. Enter the full URL of the external link. You can interpolate the value from the field with `${__value.raw }` macro. |
|
||||
| `URL Label` | (Optional) add a custom display label to override the value of the `Label name` field. |
|
||||
| `Label name` | Add a name for the exemplar traceID property. |
|
||||
|
||||
## Prometheus query editor
|
||||
|
||||
|
@ -10,7 +10,7 @@ const addDataSource = () => {
|
||||
e2e.components.DataSource.Prometheus.configPage.exemplarsAddButton().click();
|
||||
e2e.components.DataSource.Prometheus.configPage.internalLinkSwitch().check({ force: true });
|
||||
e2e.components.DataSource.DataSourceHttpSettings.urlInput().type('http://prom-url:9090');
|
||||
e2e.components.DataSourcePicker.inputV2().should('be.visible').click({ force: true });
|
||||
e2e.components.DataSourcePicker.inputV2().click({ force: true }).should('have.focus');
|
||||
|
||||
e2e().contains('gdev-tempo').scrollIntoView().should('be.visible').click();
|
||||
},
|
||||
|
@ -46,8 +46,8 @@ type FakeEvaluator_ConditionEval_Call struct {
|
||||
}
|
||||
|
||||
// ConditionEval is a helper method to define mock.On call
|
||||
// - ctx EvaluationContext
|
||||
// - condition models.Condition
|
||||
// - ctx EvaluationContext
|
||||
// - condition models.Condition
|
||||
func (_e *FakeEvaluator_Expecter) ConditionEval(ctx interface{}, condition interface{}) *FakeEvaluator_ConditionEval_Call {
|
||||
return &FakeEvaluator_ConditionEval_Call{Call: _e.mock.On("ConditionEval", ctx, condition)}
|
||||
}
|
||||
@ -93,8 +93,8 @@ type FakeEvaluator_QueriesAndExpressionsEval_Call struct {
|
||||
}
|
||||
|
||||
// QueriesAndExpressionsEval is a helper method to define mock.On call
|
||||
// - ctx EvaluationContext
|
||||
// - data []models.AlertQuery
|
||||
// - ctx EvaluationContext
|
||||
// - data []models.AlertQuery
|
||||
func (_e *FakeEvaluator_Expecter) QueriesAndExpressionsEval(ctx interface{}, data interface{}) *FakeEvaluator_QueriesAndExpressionsEval_Call {
|
||||
return &FakeEvaluator_QueriesAndExpressionsEval_Call{Call: _e.mock.On("QueriesAndExpressionsEval", ctx, data)}
|
||||
}
|
||||
@ -131,8 +131,8 @@ type FakeEvaluator_Validate_Call struct {
|
||||
}
|
||||
|
||||
// Validate is a helper method to define mock.On call
|
||||
// - ctx EvaluationContext
|
||||
// - condition models.Condition
|
||||
// - ctx EvaluationContext
|
||||
// - condition models.Condition
|
||||
func (_e *FakeEvaluator_Expecter) Validate(ctx interface{}, condition interface{}) *FakeEvaluator_Validate_Call {
|
||||
return &FakeEvaluator_Validate_Call{Call: _e.mock.On("Validate", ctx, condition)}
|
||||
}
|
||||
|
@ -6,7 +6,9 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
|
||||
@ -19,6 +21,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/tsdb/prometheus/buffered"
|
||||
"github.com/grafana/grafana/pkg/tsdb/prometheus/querydata"
|
||||
"github.com/grafana/grafana/pkg/tsdb/prometheus/resource"
|
||||
"github.com/patrickmn/go-cache"
|
||||
apiv1 "github.com/prometheus/client_golang/api/prometheus/v1"
|
||||
"github.com/yudai/gojsondiff"
|
||||
"github.com/yudai/gojsondiff/formatter"
|
||||
@ -32,9 +35,10 @@ type Service struct {
|
||||
}
|
||||
|
||||
type instance struct {
|
||||
buffered *buffered.Buffered
|
||||
queryData *querydata.QueryData
|
||||
resource *resource.Resource
|
||||
buffered *buffered.Buffered
|
||||
queryData *querydata.QueryData
|
||||
resource *resource.Resource
|
||||
versionCache *cache.Cache
|
||||
}
|
||||
|
||||
func ProvideService(httpClientProvider httpclient.Provider, cfg *setting.Cfg, features featuremgmt.FeatureToggles, tracer tracing.Tracer) *Service {
|
||||
@ -75,9 +79,10 @@ func newInstanceSettings(httpClientProvider httpclient.Provider, cfg *setting.Cf
|
||||
}
|
||||
|
||||
return instance{
|
||||
buffered: b,
|
||||
queryData: qd,
|
||||
resource: r,
|
||||
buffered: b,
|
||||
queryData: qd,
|
||||
resource: r,
|
||||
versionCache: cache.New(time.Minute*1, time.Minute*5),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@ -135,6 +140,20 @@ func (s *Service) CallResource(ctx context.Context, req *backend.CallResourceReq
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.EqualFold(req.Path, "version-detect") {
|
||||
versionObj, found := i.versionCache.Get("version")
|
||||
if found {
|
||||
return sender.Send(versionObj.(*backend.CallResourceResponse))
|
||||
}
|
||||
|
||||
vResp, err := i.resource.DetectVersion(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.versionCache.Set("version", vResp, cache.DefaultExpiration)
|
||||
return sender.Send(vResp)
|
||||
}
|
||||
|
||||
resp, err := i.resource.Execute(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -98,3 +98,23 @@ func (r *Resource) Execute(ctx context.Context, req *backend.CallResourceRequest
|
||||
|
||||
return callResponse, err
|
||||
}
|
||||
|
||||
func (r *Resource) DetectVersion(ctx context.Context, req *backend.CallResourceRequest) (*backend.CallResourceResponse, error) {
|
||||
newReq := &backend.CallResourceRequest{
|
||||
PluginContext: req.PluginContext,
|
||||
Path: "/api/v1/status/buildinfo",
|
||||
}
|
||||
|
||||
resp, err := r.Execute(ctx, newReq)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
callResponse := &backend.CallResourceResponse{
|
||||
Status: 200,
|
||||
Body: resp.Body,
|
||||
}
|
||||
|
||||
return callResponse, nil
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ describe('RuleEditor', () => {
|
||||
mocks.searchFolders.mockResolvedValue([]);
|
||||
|
||||
mocks.api.discoverFeatures.mockResolvedValue({
|
||||
application: PromApplication.Lotex,
|
||||
application: PromApplication.Cortex,
|
||||
features: {
|
||||
rulerApiEnabled: true,
|
||||
},
|
||||
@ -345,7 +345,7 @@ describe('RuleEditor', () => {
|
||||
mocks.searchFolders.mockResolvedValue([]);
|
||||
|
||||
mocks.api.discoverFeatures.mockResolvedValue({
|
||||
application: PromApplication.Lotex,
|
||||
application: PromApplication.Cortex,
|
||||
features: {
|
||||
rulerApiEnabled: true,
|
||||
},
|
||||
@ -598,7 +598,7 @@ describe('RuleEditor', () => {
|
||||
mocks.api.discoverFeatures.mockImplementation(async (dataSourceName) => {
|
||||
if (dataSourceName === 'loki with ruler' || dataSourceName === 'cortex with ruler') {
|
||||
return {
|
||||
application: PromApplication.Lotex,
|
||||
application: PromApplication.Cortex,
|
||||
features: {
|
||||
rulerApiEnabled: true,
|
||||
alertManagerConfigApi: false,
|
||||
@ -609,7 +609,7 @@ describe('RuleEditor', () => {
|
||||
}
|
||||
if (dataSourceName === 'loki with local rule store') {
|
||||
return {
|
||||
application: PromApplication.Lotex,
|
||||
application: PromApplication.Cortex,
|
||||
features: {
|
||||
rulerApiEnabled: false,
|
||||
alertManagerConfigApi: false,
|
||||
@ -620,7 +620,7 @@ describe('RuleEditor', () => {
|
||||
}
|
||||
if (dataSourceName === 'cortex without ruler api') {
|
||||
return {
|
||||
application: PromApplication.Lotex,
|
||||
application: PromApplication.Cortex,
|
||||
features: {
|
||||
rulerApiEnabled: false,
|
||||
alertManagerConfigApi: false,
|
||||
|
@ -234,7 +234,7 @@ describe('RuleList', () => {
|
||||
|
||||
setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom }));
|
||||
mocks.api.discoverFeatures.mockResolvedValue({
|
||||
application: PromApplication.Lotex,
|
||||
application: PromApplication.Cortex,
|
||||
features: {
|
||||
rulerApiEnabled: true,
|
||||
},
|
||||
@ -376,7 +376,7 @@ describe('RuleList', () => {
|
||||
setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom }));
|
||||
|
||||
mocks.api.discoverFeatures.mockResolvedValue({
|
||||
application: PromApplication.Lotex,
|
||||
application: PromApplication.Cortex,
|
||||
features: {
|
||||
rulerApiEnabled: true,
|
||||
},
|
||||
@ -524,7 +524,7 @@ describe('RuleList', () => {
|
||||
setDataSourceSrv(new MockDataSourceSrv(testDatasources));
|
||||
|
||||
mocks.api.discoverFeatures.mockResolvedValue({
|
||||
application: PromApplication.Lotex,
|
||||
application: PromApplication.Cortex,
|
||||
features: {
|
||||
rulerApiEnabled: true,
|
||||
},
|
||||
@ -710,7 +710,7 @@ describe('RuleList', () => {
|
||||
mocks.getAllDataSourcesMock.mockReturnValue([dataSources.prom]);
|
||||
setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom }));
|
||||
mocks.api.discoverFeatures.mockResolvedValue({
|
||||
application: PromApplication.Lotex,
|
||||
application: PromApplication.Cortex,
|
||||
features: {
|
||||
rulerApiEnabled: true,
|
||||
},
|
||||
@ -737,7 +737,7 @@ describe('RuleList', () => {
|
||||
mocks.getAllDataSourcesMock.mockReturnValue([dataSources.prom]);
|
||||
setDataSourceSrv(new MockDataSourceSrv({ prom: dataSources.prom }));
|
||||
mocks.api.discoverFeatures.mockResolvedValue({
|
||||
application: PromApplication.Lotex,
|
||||
application: PromApplication.Cortex,
|
||||
features: {
|
||||
rulerApiEnabled: true,
|
||||
},
|
||||
|
@ -93,7 +93,7 @@ describe('discoverDataSourceFeatures', () => {
|
||||
|
||||
const response = await discoverDataSourceFeatures({ url: '/datasource/proxy', name: 'Loki', type: 'loki' });
|
||||
|
||||
expect(response.application).toBe(PromApplication.Lotex);
|
||||
expect(response.application).toBe(PromApplication.Cortex);
|
||||
expect(response.features.rulerApiEnabled).toBe(true);
|
||||
|
||||
expect(mocks.fetchTestRulerRulesGroup).toHaveBeenCalledTimes(1);
|
||||
@ -126,7 +126,7 @@ describe('discoverDataSourceFeatures', () => {
|
||||
type: 'prometheus',
|
||||
});
|
||||
|
||||
expect(response.application).toBe(PromApplication.Lotex);
|
||||
expect(response.application).toBe(PromApplication.Cortex);
|
||||
expect(response.features.rulerApiEnabled).toBe(false);
|
||||
|
||||
expect(mocks.fetchTestRulerRulesGroup).toHaveBeenCalledTimes(1);
|
||||
@ -152,7 +152,7 @@ describe('discoverDataSourceFeatures', () => {
|
||||
type: 'prometheus',
|
||||
});
|
||||
|
||||
expect(response.application).toBe(PromApplication.Lotex);
|
||||
expect(response.application).toBe(PromApplication.Cortex);
|
||||
expect(response.features.rulerApiEnabled).toBe(true);
|
||||
|
||||
expect(mocks.fetchTestRulerRulesGroup).toHaveBeenCalledTimes(1);
|
||||
|
@ -78,7 +78,7 @@ export async function discoverDataSourceFeatures(dsSettings: {
|
||||
const rulerSupported = await hasRulerSupport(name);
|
||||
|
||||
return {
|
||||
application: PromApplication.Lotex,
|
||||
application: PromApplication.Cortex,
|
||||
features: {
|
||||
rulerApiEnabled: rulerSupported,
|
||||
},
|
||||
|
@ -266,7 +266,7 @@ export const fetchRulesSourceBuildInfoAction = createAsyncThunk(
|
||||
const rulerConfig: RulerDataSourceConfig | undefined = buildInfo.features.rulerApiEnabled
|
||||
? {
|
||||
dataSourceName: name,
|
||||
apiVersion: buildInfo.application === PromApplication.Lotex ? 'legacy' : 'config',
|
||||
apiVersion: buildInfo.application === PromApplication.Cortex ? 'legacy' : 'config',
|
||||
}
|
||||
: undefined;
|
||||
|
||||
|
@ -86,7 +86,7 @@ export type ViewProps = {
|
||||
onNameChange: (name: string) => AnyAction;
|
||||
onOptionsChange: (dataSource: DataSourceSettingsType) => AnyAction;
|
||||
onTest: () => void;
|
||||
onUpdate: (dataSource: DataSourceSettingsType) => Promise<void>;
|
||||
onUpdate: (dataSource: DataSourceSettingsType) => Promise<DataSourceSettingsType>;
|
||||
};
|
||||
|
||||
export function EditDataSourceView({
|
||||
|
@ -19,7 +19,7 @@ import { DataSourcePluginCategory, ThunkDispatch, ThunkResult } from 'app/types'
|
||||
import * as api from '../api';
|
||||
import { DATASOURCES_ROUTES } from '../constants';
|
||||
import { trackDataSourceCreated, trackDataSourceTested } from '../tracking';
|
||||
import { nameExits, findNewName } from '../utils';
|
||||
import { findNewName, nameExits } from '../utils';
|
||||
|
||||
import { buildCategories } from './buildCategories';
|
||||
import { buildNavModel } from './navModel';
|
||||
@ -231,8 +231,8 @@ export function loadDataSourcePlugins(): ThunkResult<void> {
|
||||
};
|
||||
}
|
||||
|
||||
export function updateDataSource(dataSource: DataSourceSettings): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
export function updateDataSource(dataSource: DataSourceSettings) {
|
||||
return async (dispatch: (dataSourceSettings: ThunkResult<Promise<DataSourceSettings>>) => DataSourceSettings) => {
|
||||
await api.updateDataSource(dataSource);
|
||||
await getDatasourceSrv().reload();
|
||||
return dispatch(loadDataSource(dataSource.uid));
|
||||
|
@ -0,0 +1,80 @@
|
||||
export const PromFlavorVersions: { [index: string]: Array<{ value?: string; label: string }> } = {
|
||||
Prometheus: [
|
||||
{ value: undefined, label: 'Please select' },
|
||||
{ value: '2.0.0', label: '< 2.14.x' },
|
||||
{ value: '2.14.0', label: '2.14.x' },
|
||||
{ value: '2.15.0', label: '2.15.x' },
|
||||
{ value: '2.16.0', label: '2.16.x' },
|
||||
{ value: '2.17.0', label: '2.17.x' },
|
||||
{ value: '2.18.0', label: '2.18.x' },
|
||||
{ value: '2.19.0', label: '2.19.x' },
|
||||
{ value: '2.20.0', label: '2.20.x' },
|
||||
{ value: '2.21.0', label: '2.21.x' },
|
||||
{ value: '2.22.0', label: '2.22.x' },
|
||||
{ value: '2.23.0', label: '2.23.x' },
|
||||
{ value: '2.24.0', label: '2.24.x' },
|
||||
{ value: '2.25.0', label: '2.25.x' },
|
||||
{ value: '2.26.0', label: '2.26.x' },
|
||||
{ value: '2.27.0', label: '2.27.x' },
|
||||
{ value: '2.28.0', label: '2.28.x' },
|
||||
{ value: '2.29.0', label: '2.29.x' },
|
||||
{ value: '2.30.0', label: '2.30.x' },
|
||||
{ value: '2.31.0', label: '2.31.x' },
|
||||
{ value: '2.32.0', label: '2.32.x' },
|
||||
{ value: '2.33.0', label: '2.33.x' },
|
||||
{ value: '2.34.0', label: '2.34.x' },
|
||||
{ value: '2.35.0', label: '2.35.x' },
|
||||
{ value: '2.36.0', label: '2.36.x' },
|
||||
{ value: '2.37.0', label: '2.37.x' },
|
||||
{ value: '2.38.0', label: '2.38.x' },
|
||||
{ value: '2.39.0', label: '2.39.x' },
|
||||
{ value: '2.40.0', label: '2.40.x' },
|
||||
|
||||
// This value will be returned for future versions of prometheus until we add new entries to this object
|
||||
{ value: '2.40.1', label: '> 2.40.x' },
|
||||
],
|
||||
Mimir: [
|
||||
{ value: undefined, label: 'Please select' },
|
||||
{ value: '2.0.0', label: '2.0.x' },
|
||||
{ value: '2.1.0', label: '2.1.x' },
|
||||
{ value: '2.2.0', label: '2.2.x' },
|
||||
{ value: '2.3.0', label: '2.3.x' },
|
||||
{ value: '2.4.0', label: '> 2.3.x' },
|
||||
],
|
||||
Thanos: [
|
||||
{ value: undefined, label: 'Please select' },
|
||||
{ value: '0.0.0', label: '< 0.16.x' },
|
||||
{ value: '0.16.0', label: '0.16.x' },
|
||||
{ value: '0.17.0', label: '0.17.x' },
|
||||
{ value: '0.18.0', label: '0.18.x' },
|
||||
{ value: '0.19.0', label: '0.19.x' },
|
||||
{ value: '0.20.0', label: '0.20.x' },
|
||||
{ value: '0.21.0', label: '0.21.x' },
|
||||
{ value: '0.22.0', label: '0.22.x' },
|
||||
{ value: '0.23.0', label: '0.23.x' },
|
||||
{ value: '0.24.0', label: '0.24.x' },
|
||||
{ value: '0.25.0', label: '0.25.x' },
|
||||
{ value: '0.26.0', label: '0.26.x' },
|
||||
{ value: '0.27.0', label: '0.27.x' },
|
||||
{ value: '0.28.0', label: '0.28.x' },
|
||||
{ value: '0.29.0', label: '> 0.28.x' },
|
||||
],
|
||||
Cortex: [
|
||||
{ value: undefined, label: 'Please select' },
|
||||
{ value: '0.0.0', label: '< 1.0.0' },
|
||||
{ value: '1.0.0', label: '1.0.0' },
|
||||
{ value: '1.1.0', label: '1.1.x' },
|
||||
{ value: '1.2.0', label: '1.2.x' },
|
||||
{ value: '1.3.0', label: '1.3.x' },
|
||||
{ value: '1.4.0', label: '1.4.x' },
|
||||
{ value: '1.5.0', label: '1.5.x' },
|
||||
{ value: '1.6.0', label: '1.6.x' },
|
||||
{ value: '1.7.0', label: '1.7.x' },
|
||||
{ value: '1.8.0', label: '1.8.x' },
|
||||
{ value: '1.9.0', label: '1.9.x' },
|
||||
{ value: '1.10.0', label: '1.10.x' },
|
||||
{ value: '1.11.0', label: '1.11.x' },
|
||||
{ value: '1.13.0', label: '1.13.x' },
|
||||
{ value: '1.14.0', label: '> 1.13.x' },
|
||||
],
|
||||
};
|
@ -1,9 +1,12 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React, { SyntheticEvent } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { EventsWithValidation } from '@grafana/ui';
|
||||
|
||||
import { configureStore } from '../../../../store/configureStore';
|
||||
|
||||
import { getValueFromEventItem, promSettingsValidationEvents, PromSettings } from './PromSettings';
|
||||
import { createDefaultConfigOptions } from './mocks';
|
||||
|
||||
@ -94,11 +97,12 @@ describe('PromSettings', () => {
|
||||
const options = defaultProps;
|
||||
options.url = '';
|
||||
options.jsonData.httpMethod = '';
|
||||
const store = configureStore();
|
||||
|
||||
render(
|
||||
<div>
|
||||
<Provider store={store}>
|
||||
<PromSettings onOptionsChange={() => {}} options={options} />
|
||||
</div>
|
||||
</Provider>
|
||||
);
|
||||
expect(screen.getByText('POST')).toBeInTheDocument();
|
||||
});
|
||||
@ -106,11 +110,12 @@ describe('PromSettings', () => {
|
||||
const options = defaultProps;
|
||||
options.url = 'test_url';
|
||||
options.jsonData.httpMethod = 'POST';
|
||||
const store = configureStore();
|
||||
|
||||
render(
|
||||
<div>
|
||||
<Provider store={store}>
|
||||
<PromSettings onOptionsChange={() => {}} options={options} />
|
||||
</div>
|
||||
</Provider>
|
||||
);
|
||||
expect(screen.getByText('POST')).toBeInTheDocument();
|
||||
});
|
||||
@ -118,11 +123,12 @@ describe('PromSettings', () => {
|
||||
const options = defaultProps;
|
||||
options.url = 'test_url';
|
||||
options.jsonData.httpMethod = 'GET';
|
||||
const store = configureStore();
|
||||
|
||||
render(
|
||||
<div>
|
||||
<Provider store={store}>
|
||||
<PromSettings onOptionsChange={() => {}} options={options} />
|
||||
</div>
|
||||
</Provider>
|
||||
);
|
||||
expect(screen.getByText('GET')).toBeInTheDocument();
|
||||
});
|
||||
|
@ -1,24 +1,31 @@
|
||||
import React, { SyntheticEvent } from 'react';
|
||||
import semver from 'semver/preload';
|
||||
|
||||
import {
|
||||
DataSourcePluginOptionsEditorProps,
|
||||
DataSourceSettings as DataSourceSettingsType,
|
||||
onUpdateDatasourceJsonDataOptionChecked,
|
||||
SelectableValue,
|
||||
updateDatasourcePluginJsonDataOption,
|
||||
} from '@grafana/data';
|
||||
import { getBackendSrv } from '@grafana/runtime/src';
|
||||
import {
|
||||
InlineField,
|
||||
InlineSwitch,
|
||||
EventsWithValidation,
|
||||
InlineField,
|
||||
InlineFormLabel,
|
||||
InlineSwitch,
|
||||
LegacyForms,
|
||||
regexValidation,
|
||||
Select,
|
||||
} from '@grafana/ui';
|
||||
|
||||
import { useUpdateDatasource } from '../../../../features/datasources/state';
|
||||
import { PromApplication, PromBuildInfoResponse } from '../../../../types/unified-alerting-dto';
|
||||
import { PromOptions } from '../types';
|
||||
|
||||
import { ExemplarsSettings } from './ExemplarsSettings';
|
||||
import { PromFlavorVersions } from './PromFlavorVersions';
|
||||
|
||||
const { Input, FormField } = LegacyForms;
|
||||
|
||||
const httpOptions = [
|
||||
@ -26,13 +33,110 @@ const httpOptions = [
|
||||
{ value: 'GET', label: 'GET' },
|
||||
];
|
||||
|
||||
type PrometheusSelectItemsType = Array<{ value: PromApplication; label: PromApplication }>;
|
||||
|
||||
const prometheusFlavorSelectItems: PrometheusSelectItemsType = [
|
||||
{ value: PromApplication.Prometheus, label: PromApplication.Prometheus },
|
||||
{ value: PromApplication.Cortex, label: PromApplication.Cortex },
|
||||
{ value: PromApplication.Mimir, label: PromApplication.Mimir },
|
||||
{ value: PromApplication.Thanos, label: PromApplication.Thanos },
|
||||
];
|
||||
|
||||
type Props = Pick<DataSourcePluginOptionsEditorProps<PromOptions>, 'options' | 'onOptionsChange'>;
|
||||
|
||||
/**
|
||||
* Returns the closest version to what the user provided that we have in our PromFlavorVersions for the currently selected flavor
|
||||
* Bugs: It will only reject versions that are a major release apart, so Mimir 2.x might get selected for Prometheus 2.8 if the user selects an incorrect flavor
|
||||
* Advantages: We don't need to maintain a list of every possible version for each release
|
||||
*
|
||||
* This function will return the closest version from PromFlavorVersions that is equal or lower to the version argument,
|
||||
* unless the versions are a major release apart.
|
||||
*/
|
||||
const getVersionString = (version: string, flavor?: string): string | undefined => {
|
||||
if (!flavor || !PromFlavorVersions[flavor]) {
|
||||
return;
|
||||
}
|
||||
const flavorVersionValues = PromFlavorVersions[flavor];
|
||||
|
||||
// As long as it's assured we're using versions which are sorted, we could just filter out the values greater than the target version, and then check the last element in the array
|
||||
const versionsLessThanOrEqual = flavorVersionValues
|
||||
?.filter((el) => !!el.value && semver.lte(el.value, version))
|
||||
.map((el) => el.value);
|
||||
|
||||
const closestVersion = versionsLessThanOrEqual[versionsLessThanOrEqual.length - 1];
|
||||
|
||||
if (closestVersion) {
|
||||
const differenceBetweenActualAndClosest = semver.diff(closestVersion, version);
|
||||
|
||||
// Only return versions if the target is close to the actual.
|
||||
if (['patch', 'prepatch', 'prerelease', null].includes(differenceBetweenActualAndClosest)) {
|
||||
return closestVersion;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
const unableToDeterminePrometheusVersion = (error?: Error): void => {
|
||||
console.warn('Error fetching version from buildinfo API, must manually select version!', error);
|
||||
};
|
||||
|
||||
/**
|
||||
* I don't like the daisy chain of network requests, and that we have to save on behalf of the user, but currently
|
||||
* the backend doesn't allow for the prometheus client url to be passed in from the frontend, so we currently need to save it
|
||||
* to the database before consumption.
|
||||
*
|
||||
* Since the prometheus version fields are below the url field, we can expect users to populate this field before
|
||||
* hitting save and test at the bottom of the page. For this case we need to save the current fields before calling the
|
||||
* resource to auto-detect the version.
|
||||
*
|
||||
* @param options
|
||||
* @param onOptionsChange
|
||||
* @param onUpdate
|
||||
*/
|
||||
const setPrometheusVersion = (
|
||||
options: DataSourceSettingsType<PromOptions>,
|
||||
onOptionsChange: (options: DataSourceSettingsType<PromOptions>) => void,
|
||||
onUpdate: (dataSource: DataSourceSettingsType<PromOptions>) => Promise<DataSourceSettingsType<PromOptions>>
|
||||
) => {
|
||||
// This will save the current state of the form, as the url is needed for this API call to function
|
||||
onUpdate(options)
|
||||
.then((updatedOptions) => {
|
||||
getBackendSrv()
|
||||
.get(`/api/datasources/${updatedOptions.id}/resources/version-detect`)
|
||||
.then((rawResponse: PromBuildInfoResponse) => {
|
||||
const rawVersionStringFromApi = rawResponse.data?.version ?? '';
|
||||
if (rawVersionStringFromApi && semver.valid(rawVersionStringFromApi)) {
|
||||
const parsedVersion = getVersionString(rawVersionStringFromApi, updatedOptions.jsonData.prometheusType);
|
||||
// If we got a successful response, let's update the backend with the version right away if it's new
|
||||
if (parsedVersion) {
|
||||
onUpdate({
|
||||
...updatedOptions,
|
||||
jsonData: {
|
||||
...updatedOptions.jsonData,
|
||||
prometheusVersion: parsedVersion,
|
||||
},
|
||||
}).then((updatedUpdatedOptions) => {
|
||||
onOptionsChange(updatedUpdatedOptions);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
unableToDeterminePrometheusVersion();
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
unableToDeterminePrometheusVersion(error);
|
||||
});
|
||||
};
|
||||
|
||||
export const PromSettings = (props: Props) => {
|
||||
const { options, onOptionsChange } = props;
|
||||
|
||||
// We are explicitly adding httpMethod so it is correctly displayed in dropdown. This way, it is more predictable for users.
|
||||
// This update call is typed as void, but it returns a response which we need
|
||||
const onUpdate = useUpdateDatasource();
|
||||
|
||||
// We are explicitly adding httpMethod so it is correctly displayed in dropdown. This way, it is more predictable for users.
|
||||
if (!options.jsonData.httpMethod) {
|
||||
options.jsonData.httpMethod = 'POST';
|
||||
}
|
||||
@ -40,6 +144,7 @@ export const PromSettings = (props: Props) => {
|
||||
return (
|
||||
<>
|
||||
<div className="gf-form-group">
|
||||
{/* Scrape interval */}
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<FormField
|
||||
@ -59,6 +164,7 @@ export const PromSettings = (props: Props) => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* Query Timeout */}
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<FormField
|
||||
@ -78,12 +184,13 @@ export const PromSettings = (props: Props) => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* HTTP Method */}
|
||||
<div className="gf-form">
|
||||
<InlineFormLabel
|
||||
width={13}
|
||||
tooltip="You can use either POST or GET HTTP method to query your Prometheus data source. POST is the recommended method as it allows bigger queries. Change this to GET if you have a Prometheus version older than 2.1 or if POST requests are restricted in your network."
|
||||
>
|
||||
HTTP Method
|
||||
HTTP method
|
||||
</InlineFormLabel>
|
||||
<Select
|
||||
aria-label="Select HTTP method"
|
||||
@ -94,6 +201,65 @@ export const PromSettings = (props: Props) => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="page-heading">Type and version</h3>
|
||||
<div className="gf-form-group">
|
||||
<div className="gf-form">
|
||||
<div className="gf-form">
|
||||
<FormField
|
||||
label="Prometheus type"
|
||||
labelWidth={13}
|
||||
inputEl={
|
||||
<Select
|
||||
aria-label="Prometheus type"
|
||||
options={prometheusFlavorSelectItems}
|
||||
value={prometheusFlavorSelectItems.find((o) => o.value === options.jsonData.prometheusType)}
|
||||
onChange={onChangeHandler(
|
||||
'prometheusType',
|
||||
{
|
||||
...options,
|
||||
jsonData: { ...options.jsonData, prometheusVersion: undefined },
|
||||
},
|
||||
(options) => {
|
||||
// Check buildinfo api and set default version if we can
|
||||
setPrometheusVersion(options, onOptionsChange, onUpdate);
|
||||
return onOptionsChange({
|
||||
...options,
|
||||
jsonData: { ...options.jsonData, prometheusVersion: undefined },
|
||||
});
|
||||
}
|
||||
)}
|
||||
width={20}
|
||||
/>
|
||||
}
|
||||
tooltip="Set this to the type of your prometheus database, e.g. Prometheus, Cortex, Mimir or Thanos. Changing this field will save your current settings, and attempt to detect the version."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
{options.jsonData.prometheusType && (
|
||||
<div className="gf-form">
|
||||
<FormField
|
||||
label={`${options.jsonData.prometheusType} version`}
|
||||
labelWidth={13}
|
||||
inputEl={
|
||||
<Select
|
||||
aria-label={`${options.jsonData.prometheusType} type`}
|
||||
options={PromFlavorVersions[options.jsonData.prometheusType]}
|
||||
value={PromFlavorVersions[options.jsonData.prometheusType]?.find(
|
||||
(o) => o.value === options.jsonData.prometheusVersion
|
||||
)}
|
||||
onChange={onChangeHandler('prometheusVersion', options, onOptionsChange)}
|
||||
width={20}
|
||||
/>
|
||||
}
|
||||
tooltip={`Use this to set the version of your ${options.jsonData.prometheusType} instance if it is not automatically configured.`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="page-heading">Misc</h3>
|
||||
<div className="gf-form-group">
|
||||
<div className="gf-form">
|
||||
@ -113,7 +279,7 @@ export const PromSettings = (props: Props) => {
|
||||
<FormField
|
||||
label="Custom query parameters"
|
||||
labelWidth={14}
|
||||
tooltip="Add Custom parameters to all Prometheus or Thanos queries."
|
||||
tooltip="Add custom parameters to all Prometheus or Thanos queries."
|
||||
inputEl={
|
||||
<Input
|
||||
className="width-25"
|
||||
|
@ -89,6 +89,8 @@ export class PrometheusDatasource
|
||||
exemplarTraceIdDestinations: ExemplarTraceIdDestination[] | undefined;
|
||||
lookupsDisabled: boolean;
|
||||
customQueryParameters: any;
|
||||
datasourceConfigurationPrometheusFlavor?: PromApplication;
|
||||
datasourceConfigurationPrometheusVersion?: string;
|
||||
exemplarsAvailable: boolean;
|
||||
subType: PromApplication;
|
||||
rulerEnabled: boolean;
|
||||
@ -121,6 +123,8 @@ export class PrometheusDatasource
|
||||
this.languageProvider = languageProvider ?? new PrometheusLanguageProvider(this);
|
||||
this.lookupsDisabled = instanceSettings.jsonData.disableMetricsLookup ?? false;
|
||||
this.customQueryParameters = new URLSearchParams(instanceSettings.jsonData.customQueryParameters);
|
||||
this.datasourceConfigurationPrometheusFlavor = instanceSettings.jsonData.prometheusType;
|
||||
this.datasourceConfigurationPrometheusVersion = instanceSettings.jsonData.prometheusVersion;
|
||||
this.variables = new PrometheusVariableSupport(this, this.templateSrv, this.timeSrv);
|
||||
this.exemplarsAvailable = true;
|
||||
|
||||
@ -849,11 +853,6 @@ export class PrometheusDatasource
|
||||
);
|
||||
}
|
||||
|
||||
async getSubtitle(): Promise<JSX.Element | null> {
|
||||
const buildInfo = await this.getBuildInfo();
|
||||
return buildInfo ? this.getBuildInfoMessage(buildInfo) : null;
|
||||
}
|
||||
|
||||
async getTagKeys(options?: any) {
|
||||
if (options?.series) {
|
||||
// Get tags for the provided series only
|
||||
@ -902,23 +901,28 @@ export class PrometheusDatasource
|
||||
);
|
||||
|
||||
const LOGOS = {
|
||||
[PromApplication.Lotex]: '/public/app/plugins/datasource/prometheus/img/cortex_logo.svg',
|
||||
[PromApplication.Cortex]: '/public/app/plugins/datasource/prometheus/img/cortex_logo.svg',
|
||||
[PromApplication.Mimir]: '/public/app/plugins/datasource/prometheus/img/mimir_logo.svg',
|
||||
[PromApplication.Prometheus]: '/public/app/plugins/datasource/prometheus/img/prometheus_logo.svg',
|
||||
[PromApplication.Thanos]: '/public/app/plugins/datasource/prometheus/img/thanos_logo.svg',
|
||||
};
|
||||
|
||||
const COLORS: Record<PromApplication, BadgeColor> = {
|
||||
[PromApplication.Lotex]: 'blue',
|
||||
[PromApplication.Cortex]: 'blue',
|
||||
[PromApplication.Mimir]: 'orange',
|
||||
[PromApplication.Prometheus]: 'red',
|
||||
[PromApplication.Thanos]: 'purple', // Purple hex taken from thanos.io
|
||||
};
|
||||
|
||||
const AppDisplayNames: Record<PromApplication, string> = {
|
||||
[PromApplication.Lotex]: 'Cortex',
|
||||
[PromApplication.Cortex]: 'Cortex',
|
||||
[PromApplication.Mimir]: 'Mimir',
|
||||
[PromApplication.Prometheus]: 'Prometheus',
|
||||
[PromApplication.Thanos]: 'Thanos',
|
||||
};
|
||||
|
||||
const application = this.datasourceConfigurationPrometheusFlavor ?? buildInfo.application;
|
||||
|
||||
// this will inform the user about what "subtype" the datasource is; Mimir, Cortex or vanilla Prometheus
|
||||
const applicationSubType = (
|
||||
<Badge
|
||||
@ -926,13 +930,13 @@ export class PrometheusDatasource
|
||||
<span>
|
||||
<img
|
||||
style={{ width: 14, height: 14, verticalAlign: 'text-bottom' }}
|
||||
src={LOGOS[buildInfo.application ?? PromApplication.Prometheus]}
|
||||
src={LOGOS[application ?? PromApplication.Prometheus]}
|
||||
alt=""
|
||||
/>{' '}
|
||||
{buildInfo.application ? AppDisplayNames[buildInfo.application] : 'Unknown'}
|
||||
{application ? AppDisplayNames[application] : 'Unknown'}
|
||||
</span>
|
||||
}
|
||||
color={COLORS[buildInfo.application ?? PromApplication.Prometheus]}
|
||||
color={COLORS[application ?? PromApplication.Prometheus]}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="2.41 2.16 355.42 355.17"><path fill="#fff" d="M9.49808 9.14822v341.59786h241.93485a99.66306 99.66306 0 0 0 99.657-99.663V9.14822zm266.263 215.32837h12.75033v12.75018h-12.75035zm-4.00963-54.89h20.71531v20.72131h-20.71533zm-3.186-54.10261h27.09335v27.11729h-27.09343zm-43.739 159.90321h12.7381v12.75018h-12.75017zm-3.98558-55.53932h20.72129V240.587h-20.72129zm-3.186-53.44731h27.12344v27.09337h-27.09339zm3.186-27.00322v-20.71528h20.72129v20.71527zm-97.82954 135.98981h12.75021v12.75018h-12.75021zm-3.98558-54.8901h20.72131v20.69121h-20.72124zm3.98558-34.1748v-12.75019h12.75021v12.75018zm-3.98558-67.64019h20.72131v20.71527h-20.72124zM72.10081 275.38715H84.875v12.75018H72.10081zm0-50.91056H84.875v12.75018H72.10081zm-3.97955-54.89h20.7153v20.72131h-20.7153zm-3.19206-54.10265h27.09939v27.11729H64.9292zM53.01459 52.67679h254.53469v50.91052H205.74622v203.6302h-50.90449v-203.6302H53.01459z"/></svg>
|
After Width: | Height: | Size: 958 B |
@ -1,5 +1,7 @@
|
||||
import { DataQuery, DataSourceJsonData, QueryResultMeta, ScopedVars } from '@grafana/data';
|
||||
|
||||
import { PromApplication } from '../../../types/unified-alerting-dto';
|
||||
|
||||
import { QueryEditorMode } from './querybuilder/shared/types';
|
||||
|
||||
export interface PromQuery extends DataQuery {
|
||||
@ -30,6 +32,8 @@ export interface PromOptions extends DataSourceJsonData {
|
||||
customQueryParameters?: string;
|
||||
disableMetricsLookup?: boolean;
|
||||
exemplarTraceIdDestinations?: ExemplarTraceIdDestination[];
|
||||
prometheusType?: PromApplication;
|
||||
prometheusVersion?: string;
|
||||
}
|
||||
|
||||
export enum PromQueryType {
|
||||
|
@ -54,10 +54,12 @@ export enum PromRuleType {
|
||||
Alerting = 'alerting',
|
||||
Recording = 'recording',
|
||||
}
|
||||
|
||||
export enum PromApplication {
|
||||
Lotex = 'Lotex',
|
||||
Cortex = 'Cortex',
|
||||
Mimir = 'Mimir',
|
||||
Prometheus = 'Prometheus',
|
||||
Thanos = 'Thanos',
|
||||
}
|
||||
|
||||
export interface PromBuildInfoResponse {
|
||||
|
Loading…
Reference in New Issue
Block a user