receiver/splunkenterprisereceiver/client.go (136 lines of code) (raw):
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package splunkenterprisereceiver // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/splunkenterprisereceiver"
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"go.opentelemetry.io/collector/component"
)
// Indexer type "enum". Included in context sent from scraper functions
const (
typeIdx = "IDX"
typeSh = "SH"
typeCm = "CM"
)
var (
errCtxMissingEndpointType = errors.New("context was passed without the endpoint type included")
errEndpointTypeNotFound = errors.New("requested client is not configured and could not be found in splunkEntClient")
errNoClientFound = errors.New("no client corresponding to the endpoint type was found")
)
// Type wrapper for accessing context value
type endpointType string
// Wrapper around splunkClientMap to avoid awkward reference/dereference stuff that arises when using maps in golang
type splunkEntClient struct {
clients splunkClientMap
}
// The splunkEntClient is made up of a number of splunkClients defined for each configured endpoint
type splunkClientMap map[string]splunkClient
// The client does not carry the endpoint that is configured with it and golang does not support mixed
// type arrays so this struct contains the pair: the client configured for the endpoint and the endpoint
// itself
type splunkClient struct {
client *http.Client
endpoint *url.URL
}
func newSplunkEntClient(ctx context.Context, cfg *Config, h component.Host, s component.TelemetrySettings) (*splunkEntClient, error) {
var err error
var e *url.URL
var c *http.Client
clientMap := make(splunkClientMap)
// if the endpoint is defined, put it in the endpoints map for later use
// we already checked that url.Parse does not fail in cfg.Validate()
if cfg.IdxEndpoint.Endpoint != "" {
e, _ = url.Parse(cfg.IdxEndpoint.Endpoint)
c, err = cfg.IdxEndpoint.ToClient(ctx, h, s)
if err != nil {
return nil, err
}
clientMap[typeIdx] = splunkClient{
client: c,
endpoint: e,
}
}
if cfg.SHEndpoint.Endpoint != "" {
e, _ = url.Parse(cfg.SHEndpoint.Endpoint)
c, err = cfg.SHEndpoint.ToClient(ctx, h, s)
if err != nil {
return nil, err
}
clientMap[typeSh] = splunkClient{
client: c,
endpoint: e,
}
}
if cfg.CMEndpoint.Endpoint != "" {
e, _ = url.Parse(cfg.CMEndpoint.Endpoint)
c, err = cfg.CMEndpoint.ToClient(ctx, h, s)
if err != nil {
return nil, err
}
clientMap[typeCm] = splunkClient{
client: c,
endpoint: e,
}
}
return &splunkEntClient{clients: clientMap}, nil
}
// For running ad hoc searches only
func (c *splunkEntClient) createRequest(eptType string, sr *searchResponse) (req *http.Request, err error) {
ctx := context.WithValue(context.Background(), endpointType("type"), eptType)
// Running searches via Splunk's REST API is a two step process: First you submit the job to run
// this returns a jobid which is then used in the second part to retrieve the search results
if sr.Jobid == nil {
var u string
path := "/services/search/jobs/"
if e, ok := c.clients[eptType]; ok {
u, err = url.JoinPath(e.endpoint.String(), path)
if err != nil {
return nil, err
}
} else {
return nil, errNoClientFound
}
// reader for the response data
data := strings.NewReader(sr.search)
// return the build request, ready to be run by makeRequest
req, err = http.NewRequestWithContext(ctx, http.MethodPost, u, data)
if err != nil {
return nil, err
}
return req, nil
}
path := fmt.Sprintf("/services/search/jobs/%s/results", *sr.Jobid)
url, _ := url.JoinPath(c.clients[eptType].endpoint.String(), path)
req, err = http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, err
}
return req, nil
}
// forms an *http.Request for use with Splunk built-in API's (like introspection).
func (c *splunkEntClient) createAPIRequest(eptType string, apiEndpoint string) (req *http.Request, err error) {
var u string
ctx := context.WithValue(context.Background(), endpointType("type"), eptType)
if e, ok := c.clients[eptType]; ok {
u = e.endpoint.String() + apiEndpoint
} else {
return nil, errNoClientFound
}
req, err = http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
if err != nil {
return nil, err
}
return req, nil
}
// Perform a request.
func (c *splunkEntClient) makeRequest(req *http.Request) (*http.Response, error) {
// get endpoint type from the context
eptType := req.Context().Value(endpointType("type"))
if eptType == nil {
return nil, errCtxMissingEndpointType
}
var endpointType string
switch t := eptType.(type) {
case string:
endpointType = t
default:
endpointType = fmt.Sprintf("%v", eptType)
}
if sc, ok := c.clients[endpointType]; ok {
res, err := sc.client.Do(req)
if err != nil {
return nil, err
}
return res, nil
}
return nil, errEndpointTypeNotFound
}
// Check if the splunkEntClient contains a configured endpoint for the type of scraper
// Returns true if an entry exists, false if not.
func (c *splunkEntClient) isConfigured(v string) bool {
_, ok := c.clients[v]
return ok
}