Prometheus: Use configured HTTP method for /series and /labels endpoints (#31401)

* Run post-friendly request with set method first

* Improve messaging, retry only when post and specific status code

* Add comments

* Fix backend

* Update public/app/plugins/datasource/prometheus/datasource.ts
This commit is contained in:
Ivana Huckova 2021-02-23 16:31:03 +01:00 committed by GitHub
parent 6d1076fca7
commit dce67db6ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 28 additions and 6 deletions

View File

@ -245,8 +245,8 @@ func (proxy *DataSourceProxy) validateRequest() error {
if proxy.ctx.Req.Request.Method == "PUT" {
return errors.New("puts not allowed on proxied Prometheus datasource")
}
if proxy.ctx.Req.Request.Method == "POST" && !(proxy.proxyPath == "api/v1/query" || proxy.proxyPath == "api/v1/query_range") {
return errors.New("posts not allowed on proxied Prometheus datasource except on /query and /query_range")
if proxy.ctx.Req.Request.Method == "POST" && !(proxy.proxyPath == "api/v1/query" || proxy.proxyPath == "api/v1/query_range" || proxy.proxyPath == "api/v1/series" || proxy.proxyPath == "api/v1/labels") {
return errors.New("posts not allowed on proxied Prometheus datasource except on /query, /query_range, /series and /labels")
}
}

View File

@ -105,8 +105,7 @@ describe('PrometheusDatasource', () => {
expect(fetchMock.mock.calls.length).toBe(1);
expect(fetchMock.mock.calls[0][0].method).toBe('GET');
});
it('should still perform a GET request with the DS HTTP method set to POST', () => {
it('should still perform a GET request with the DS HTTP method set to POST and not POST-friendly endpoint', () => {
const postSettings = _.cloneDeep(instanceSettings);
postSettings.jsonData.httpMethod = 'POST';
const promDs = new PrometheusDatasource(postSettings, templateSrvStub as any, timeSrvStub as any);
@ -114,6 +113,14 @@ describe('PrometheusDatasource', () => {
expect(fetchMock.mock.calls.length).toBe(1);
expect(fetchMock.mock.calls[0][0].method).toBe('GET');
});
it('should try to perform a POST request with the DS HTTP method set to POST and POST-friendly endpoint', () => {
const postSettings = _.cloneDeep(instanceSettings);
postSettings.jsonData.httpMethod = 'POST';
const promDs = new PrometheusDatasource(postSettings, templateSrvStub as any, timeSrvStub as any);
promDs.metadataRequest('api/v1/series');
expect(fetchMock.mock.calls.length).toBe(1);
expect(fetchMock.mock.calls[0][0].method).toBe('POST');
});
});
describe('When using customQueryParams', () => {

View File

@ -44,6 +44,7 @@ import { PrometheusVariableSupport } from './variables';
import PrometheusMetricFindQuery from './metric_find_query';
export const ANNOTATION_QUERY_STEP_DEFAULT = '60s';
const GET_AND_POST_MEDATADATA_ENDPOINTS = ['api/v1/query', 'api/v1/query_range', 'api/v1/series', 'api/v1/labels'];
export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions> {
type: string;
@ -136,7 +137,7 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
}
// Use this for tab completion features, wont publish response to other components
metadataRequest<T = any>(url: string) {
async metadataRequest<T = any>(url: string) {
const data: any = {};
for (const [key, value] of this.customQueryParameters) {
if (data[key] == null) {
@ -144,7 +145,21 @@ export class PrometheusDatasource extends DataSourceApi<PromQuery, PromOptions>
}
}
return this._request<T>(url, data, { method: 'GET', hideFromInspector: true }).toPromise(); // toPromise until we change getTagValues, getTagKeys to Observable
// If URL includes endpoint that supports POST and GET method, try to use configured method. This might fail as POST is supported only in v2.10+.
if (GET_AND_POST_MEDATADATA_ENDPOINTS.some((endpoint) => url.includes(endpoint))) {
try {
return await this._request<T>(url, data, { method: this.httpMethod, hideFromInspector: true }).toPromise();
} catch (err) {
// If status code of error is Method Not Allowed (405) and HTTP method is POST, retry with GET
if (this.httpMethod === 'POST' && err.status === 405) {
console.warn(`Couldn't use configured POST HTTP method for this request. Trying to use GET method instead.`);
} else {
throw err;
}
}
}
return await this._request<T>(url, data, { method: 'GET', hideFromInspector: true }).toPromise(); // toPromise until we change getTagValues, getTagKeys to Observable
}
interpolateQueryExpr(value: string | string[] = [], variable: any) {