elasticsearch

package module
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 11, 2026 License: MIT Imports: 9 Imported by: 0

README

elasticsearch

Thin, option-configured wrapper around the official github.com/elastic/go-elasticsearch/v8 (low-level esapi).

Covers document CRUD + search — Index/Get/Search/Delete — and adds ergonomic construction (functional options + sane defaults), a fail-fast Ping at construction, lightweight metrics + an event hook, and an escape hatch to *elasticsearch.Client. Bulk/Aggregation/Cat/Indices/Cluster are reached via Client() (not wrapped). There is no Close (elasticsearch.Client is stateless — an HTTP pool owned by http.Transport).

Like the minio/etcd/mongo wrappers, New and all ops take a context.Context: it bounds the construction Ping and each HTTP call (applied via esapi.X.WithContext). When the caller's context has no deadline, New applies a 10s fallback to the Ping so a context.Background() caller cannot block startup on a half-open endpoint.

Why

Elasticsearch is the default search/analytics backend (creative/event/log search) for ad-tech/finance. A thin wrapper with metrics + a fail-fast ping + consistent options removes per-service boilerplate. Uses the official client (the local projects use the semi-maintained olivere/elastic; this targets the maintained go-elasticsearch/v8).

Install

go get github.com/v8fg/kit4go/elasticsearch

Isolated Go module.

Quick start

ctx := context.Background()
c, err := elasticsearch.New(ctx, elasticsearch.WithAddresses("http://localhost:9200"))
if err != nil { log.Fatal(err) }

c.Index(ctx, "creatives", strings.NewReader(`{"name":"banner-1"}`),
    esapi.Index(nil).WithDocumentID("1"),
)

res, _ := c.Search(ctx,
    esapi.Search(nil).WithIndex("creatives"),
    esapi.Search(nil).WithBody(strings.NewReader(`{"query":{"match_all":{}}}`)),
)
defer res.Body.Close()

v8.19 options: in go-elasticsearch v8.19, the option helpers (WithDocumentID, WithIndex, WithBody, ...) are methods on the named func types — build them with esapi.Index(nil).WithDocumentID("1") (the method builds the option without invoking the nil receiver), or via the escape hatch c.Client().Index.WithDocumentID("1").

Operations

Method Notes
Index(ctx, index, body, opts...) create/replace a doc (body = JSON io.Reader); returns *esapi.Response
Get(ctx, index, id, opts...) fetch by id
Search(ctx, opts...) query (body via WithBody)
Delete(ctx, index, id, opts...) remove by id

Returns *esapi.Response (.StatusCode, .Body). Only a transport error (err != nil) is counted in Errors — HTTP-level outcomes (404 etc.) are in resp.StatusCode (inspect directly). Bulk/Indices/Cat/Cluster/Aggregations via Client().

Construction

New(ctx, opts...) builds the client and runs a Ping (200 = OK) to fail fast on an unreachable cluster — elasticsearch.NewClient is lazy (does not connect). ctx bounds the Ping; when it has no deadline, a 10s fallback is applied. There is no Close (stateless client). A non-2xx Ping returns an error wrapping the ErrPingFailed sentinel (errors.Is(err, elasticsearch.ErrPingFailed)).

Options

WithAddresses (required, or WithCloudID), WithCredentials (basic auth), WithCloudID (Elastic Cloud), WithCACert (PEM CA cert for TLS), WithTransport.

Metrics & events

c.SetOnEvent(func(e elasticsearch.Event) { /* e.Kind, e.Outcome */ })
m := c.Metrics() // Indexes, Searches, Gets, Deletes, Errors

Mock seam

v8.19 exposes Index/Search/Get/Delete/Ping as fields of named func types (esapi.Index, etc.) on *elasticsearch.Client. The wrapper holds these func fields directly (copied from the client) — tests overwrite them with their own funcs. No adapter or interface layer needed.

Testing

go test -short -race -cover ./...          # unit (mock func fields), ~98% coverage
# integration (optional, needs a live cluster):
docker run -d -p 9200:9200 -e discovery.type=single-node -e xpack.security.enabled=false \
  docker.elastic.co/elasticsearch/elasticsearch:8.19.0
ELASTICSEARCH_ADDR=http://127.0.0.1:9200 go test -run Integration -v ./elasticsearch/

Documentation

Overview

Package elasticsearch is a thin, option-configured wrapper around the official github.com/elastic/go-elasticsearch/v8 (low-level esapi).

It covers document CRUD + search — Index/Get/Search/Delete — and provides ergonomic construction (functional options + sane defaults), a fail-fast Ping at construction, lightweight metrics + an event hook, and an escape hatch to the underlying *elasticsearch.Client. Bulk/Aggregation/Cat/Indices/Cluster APIs are reached via Client() (not wrapped — keeps the surface thin). There is no Close: elasticsearch.Client is stateless (an HTTP connection pool owned by http.Transport; release a custom Transport set via WithTransport yourself).

Like the minio/etcd/mongo wrappers, New and all ops take a context.Context: it bounds the construction Ping and each HTTP call. When the caller's context carries no deadline, New applies a 10s fallback to the Ping so a context.Background() caller cannot block startup on a half-open endpoint.

go-elasticsearch v8.19 exposes Index/Search/Get/Delete/Ping as FIELDS of named func types (not methods). The wrapper holds these func fields directly (copied from the client; tests assign their own) — so no adapter or interface layer is needed.

Index

Examples

Constants

View Source
const (
	KindIndex  = "index"
	KindSearch = "search"
	KindGet    = "get"
	KindDelete = "delete"
)

Event kinds.

View Source
const (
	OutcomeSuccess = "success"
	OutcomeError   = "error"
)

Event outcomes.

Variables

View Source
var ErrNoAddresses = errors.New("elasticsearch: at least one address required (WithAddresses)")

ErrNoAddresses is returned by New when no address was configured.

View Source
var ErrPingFailed = errors.New("elasticsearch: ping returned non-success status")

ErrPingFailed is returned by New when the construction Ping does not yield a 2xx status (the cluster is reachable but unhealthy/unauthorized, or returned an unexpected status). Callers may errors.Is against it to distinguish a connectivity/health failure from a config/transport error.

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client wraps an elasticsearch client. Safe for concurrent use.

func New

func New(ctx context.Context, opts ...Option) (*Client, error)

New constructs the client and verifies connectivity with a Ping. Returns an owning Client.

The context bounds the construction-time Ping. If it carries no deadline, a 10s fallback is applied so a caller passing context.Background() against a half-open endpoint cannot block startup indefinitely.

Example

ExampleNew shows connect + Index/Search. It is a compile-checked illustration (an Example without an // Output: comment is compiled but not executed); wire your own address to run it against a live cluster.

v8.19 options are methods on the named func types: esapi.Index(nil).WithXxx(v) builds an option func(*IndexRequest) without invoking the (nil) receiver.

package main

import (
	"context"
	"log"
	"strings"

	"github.com/elastic/go-elasticsearch/v8/esapi"

	"github.com/v8fg/kit4go/elasticsearch"
)

func main() {
	ctx := context.Background()
	c, err := elasticsearch.New(ctx, elasticsearch.WithAddresses("http://localhost:9200"))
	if err != nil {
		log.Fatal(err)
	}

	if _, err := c.Index(ctx, "creatives", strings.NewReader(`{"name":"banner-1"}`),
		esapi.Index(nil).WithDocumentID("1"),
	); err != nil {
		log.Fatal(err)
	}

	res, err := c.Search(ctx,
		esapi.Search(nil).WithIndex("creatives"),
		esapi.Search(nil).WithBody(strings.NewReader(`{"query":{"match_all":{}}}`)),
	)
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()
}

func Wrap

func Wrap(raw *elasticsearch.Client, opts ...Option) *Client

Wrap adopts an existing *elasticsearch.Client.

func (*Client) Client

func (c *Client) Client() *elasticsearch.Client

Client returns the underlying *elasticsearch.Client for anything the wrapper does not expose (Bulk, Indices, Cat, Cluster, Aggregations). Returns nil when built from a mock.

func (*Client) Delete

func (c *Client) Delete(ctx context.Context, index, id string, opts ...func(*esapi.DeleteRequest)) (*esapi.Response, error)

Delete removes a document by id.

The caller MUST close resp.Body (failure leaks the connection).

func (*Client) Get

func (c *Client) Get(ctx context.Context, index, id string, opts ...func(*esapi.GetRequest)) (*esapi.Response, error)

Get fetches a document by id.

The caller MUST close resp.Body (failure leaks the connection).

func (*Client) Index

func (c *Client) Index(ctx context.Context, index string, body io.Reader, opts ...func(*esapi.IndexRequest)) (*esapi.Response, error)

Index creates/replaces a document. body is the JSON document. Forward options (WithDocumentID, WithRefresh, ...) untouched; ctx is applied via WithContext (caller options win — appended last, so an explicit WithContext overrides).

The caller MUST close resp.Body (failure leaks the connection).

func (*Client) Metrics

func (c *Client) Metrics() Metrics

Metrics returns a snapshot of the counters.

func (*Client) Options

func (c *Client) Options() Options

Options returns the resolved options the client was built with.

The struct may carry credentials: do not log it verbatim.

func (*Client) Search

func (c *Client) Search(ctx context.Context, opts ...func(*esapi.SearchRequest)) (*esapi.Response, error)

Search runs a query. Pass WithBody(body) + WithIndex("idx") etc. in opts.

The caller MUST close resp.Body (failure leaks the connection).

func (*Client) SetOnEvent

func (c *Client) SetOnEvent(fn func(Event))

SetOnEvent installs a hook fired after each operation (nil disables it). The hook runs on the calling goroutine; keep it cheap and must not panic (a panic propagates to the caller — the wrapper does not recover). When nil, the cost is a single atomic-pointer load per operation (effectively zero overhead).

type Event

type Event struct {
	Kind    string // KindIndex, KindSearch, KindGet, KindDelete
	Outcome string // OutcomeSuccess or OutcomeError
}

Event is fired after each operation when an OnEvent hook is installed.

type Metrics

type Metrics struct {
	Indexes  uint64 // Index calls
	Searches uint64 // Search calls
	Gets     uint64 // Get calls
	Deletes  uint64 // Delete calls
	Errors   uint64 // any operation returning a transport error (err != nil)
}

Metrics is a point-in-time snapshot of Client counters (all atomic loads).

type Option

type Option func(*Options)

Option configures Options.

func WithAddresses

func WithAddresses(addrs ...string) Option

WithAddresses sets the cluster URLs (e.g. "http://localhost:9200"). Required unless CloudID is set. Copies the slice.

func WithCACert

func WithCACert(cert []byte) Option

WithCACert sets a PEM-encoded CA cert for TLS verification.

func WithCloudID

func WithCloudID(id string) Option

WithCloudID connects to Elastic Cloud (mutually exclusive with Addresses).

func WithCredentials

func WithCredentials(user, password string) Option

WithCredentials sets basic-auth username/password.

func WithTransport

func WithTransport(t http.RoundTripper) Option

WithTransport sets a custom http.RoundTripper.

type Options

type Options struct {
	Addresses []string
	Username  string
	Password  string
	CloudID   string // Elastic Cloud (mutually exclusive with Addresses)
	CACert    []byte // PEM-encoded CA cert for TLS verification
	Transport http.RoundTripper
}

Options configures the elasticsearch client. Only Addresses (or CloudID) is required.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL