diff --git a/CHANGELOG.md b/CHANGELOG.md index 9733f43..67d87ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ * SECURITY: bump golang.org/x/net to 0.33.0. See https://github.com/advisories/GHSA-w32m-9786-jp63 +* FEATURE: enable to set headers for every request to the datasource. It helps to use custom headers in the Grafana to define AccountID and ProjectID if it is needed. See [this issue](https://github.com/VictoriaMetrics/victorialogs-datasource/issues/85). + ## v0.13.1 * FEATURE: update plugin dependencies to satisfy Grafana marketplace requirements. diff --git a/pkg/plugin/datasource.go b/pkg/plugin/datasource.go index 06f23ef..5c1d20a 100644 --- a/pkg/plugin/datasource.go +++ b/pkg/plugin/datasource.go @@ -25,7 +25,11 @@ var ( ) const ( - health = "/health" + health = "/health" + settingsHTTPMethod = "httpMethod" + settingsCustomQueryParameters = "customQueryParameters" + httpHeaderName = "httpHeaderName" + httpHeaderValue = "httpHeaderValue" ) // NewDatasource creates a new datasource instance. @@ -47,6 +51,10 @@ func NewDatasource(ctx context.Context, settings backend.DataSourceInstanceSetti }, nil } +// Settings contains the raw DataSourceConfig as JSON as stored by Grafana server. +// It repeats the properties in this object and includes custom properties. +type Settings map[string]string + // Datasource is an example datasource which can respond to data queries, reports // its health and has streaming skills. type Datasource struct { @@ -173,33 +181,45 @@ func (d *Datasource) getQueryFromRaw(data json.RawMessage) (*Query, error) { // datasourceQuery process the query to the datasource and returns the result. func (d *Datasource) datasourceQuery(ctx context.Context, q *Query, isStream bool) (io.ReadCloser, error) { - var settings struct { - HTTPMethod string `json:"httpMethod"` - QueryParams string `json:"customQueryParameters"` - } + settings := make(Settings) if err := json.Unmarshal(d.settings.JSONData, &settings); err != nil { return nil, fmt.Errorf("failed to parse datasource settings: %w", err) } - if settings.HTTPMethod == "" { - settings.HTTPMethod = http.MethodPost + + httpMethod := settings[settingsHTTPMethod] + if httpMethod == "" { + httpMethod = http.MethodPost } - reqURL, err := q.getQueryURL(d.settings.URL, settings.QueryParams) + customQueryParameters := settings[settingsCustomQueryParameters] + + reqURL, err := q.getQueryURL(d.settings.URL, customQueryParameters) if err != nil { return nil, fmt.Errorf("failed to create request URL: %w", err) } if isStream { - reqURL, err = q.queryTailURL(d.settings.URL, settings.QueryParams) + reqURL, err = q.queryTailURL(d.settings.URL, customQueryParameters) if err != nil { return nil, fmt.Errorf("failed to create request URL: %w", err) } } - req, err := http.NewRequestWithContext(ctx, settings.HTTPMethod, reqURL, nil) + req, err := http.NewRequestWithContext(ctx, httpMethod, reqURL, nil) if err != nil { return nil, fmt.Errorf("failed to create new request with context: %w", err) } + + for k, v := range settings { + if strings.HasPrefix(k, httpHeaderName) { + headerName := v + headerValueName := strings.Replace(k, httpHeaderName, httpHeaderValue, 1) + if headerValue, ok := d.settings.DecryptedSecureJSONData[headerValueName]; ok { + req.Header.Add(headerName, headerValue) + } + } + } + resp, err := d.httpClient.Do(req) if err != nil { if !isTrivialError(err) { @@ -209,7 +229,7 @@ func (d *Datasource) datasourceQuery(ctx context.Context, q *Query, isStream boo // Something in the middle between client and datasource might be closing // the connection. So we do a one more attempt in hope request will succeed. - req, err = http.NewRequestWithContext(ctx, settings.HTTPMethod, reqURL, nil) + req, err = http.NewRequestWithContext(ctx, httpMethod, reqURL, nil) if err != nil { return nil, fmt.Errorf("failed to create new request with context: %w", err) }