Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
adamwg committed Oct 19, 2017
0 parents commit c512621
Show file tree
Hide file tree
Showing 373 changed files with 59,454 additions and 0 deletions.
21 changes: 21 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"


[[constraint]]
branch = "master"
name = "github.com/ianschenck/envflag"

[[constraint]]
name = "gopkg.in/olivere/elastic.v3"
version = "3.0.71"
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2017 Adam Wolfe Gordon <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# elastiquery - A CLI tool for querying ElasticSearch

elastiquery is a CLI tool that can query ElasticSearch servers and print results
as JSON. The resulting JSON can then be manipulated with your favorite CLI
tools, e.g. `jq`.

## Compatibility

Currently only ElasticSearch version 2.x is supported, however elastiquery is
built in such a way that adding support for additional versions is simple.

## Usage

```
Usage of elastiquery:
-es-url string
ElasticSearch server URL
-index string
ElasticSearch index
-limit int
Number of results to return
-offset int
Number of results to skip
-or
Require only one of the given queries to match, rather than all of them
-prefixes string
Semicolon-separated ElasticSearch term queries in the form field=term
-raw string
Raw ElasticSearch JSON query
-reverse
Sort in reverse order
-sort-by string
Field name to sort results by
-terms string
Semicolon-separated ElasticSearch term queries in the form field=term
-timeout duration
Timeout for ElasticSearch queries (default 30s)
```

For convenience, the ElasticSearch URL and index can also be specified via the
environment variables `ES_URL` and `ES_INDEX`.

## Installation

`go get github.com/adamwg/elastiquery/cmd/elastiquery`

## Examples

Find all records whose `app` field is `myapp` and whose `message` field starts
with `error`:

```console
$ elastiquery -terms 'app=myapp' -prefixes 'message=error'
```

Find all records whose 'app' field is 'myapp' or 'yourapp':

```console
$ elastiquery -or -terms 'app=myapp;app=yourapp'
```
135 changes: 135 additions & 0 deletions cmd/elastiquery/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package main

import (
"context"
"encoding/json"
"flag"
"log"
"os"
"strings"
"time"

"github.com/adamwg/elastiquery"
"github.com/adamwg/elastiquery/v2"
"github.com/ianschenck/envflag"
)

func main() {
// Global options can be set by either envvar or flag, with the flag taking
// precedence.
var esURL string
envflag.StringVar(&esURL, "ES_URL", "", "ElasticSearch server URL")
flag.StringVar(&esURL, "es-url", "", "ElasticSearch server URL")
var esIndex string
envflag.StringVar(&esIndex, "ES_INDEX", "", "ElasticSearch index")
flag.StringVar(&esIndex, "index", "", "ElasticSearch index")

timeout := flag.Duration("timeout", 30*time.Second, "Timeout for ElasticSearch queries")
offset := flag.Int("offset", 0, "Number of results to skip")
limit := flag.Int("limit", 0, "Number of results to return")
sortField := flag.String("sort-by", "", "Field name to sort results by")
sortReverse := flag.Bool("reverse", false, "Sort in reverse order")
or := flag.Bool("or", false, "Require only one of the given queries to match, rather than all of them")

rawQuery := flag.String("raw", "", "Raw ElasticSearch JSON query")
termQueries := flag.String("terms", "", "Semicolon-separated ElasticSearch term queries in the form field=term")
prefixQueries := flag.String("prefixes", "", "Semicolon-separated ElasticSearch term queries in the form field=term")

envflag.Parse()
flag.Parse()

if esURL == "" {
log.Fatal("ElasticSearch server address must be provided via flag or environment")
}
if esIndex == "" {
log.Fatal("ElasticSearch index must be provided via flag or environment")
}

esVersion, err := elastiquery.GetServerVersion(esURL)
if err != nil {
log.Fatalf("Could not determine ES server version: %v", err)
}
log.Printf("ES server version is %s", esVersion)

var client elastiquery.ESClient
if strings.HasPrefix(esVersion, "2.") {
client, err = v2.NewClient(esURL)
}
if err != nil {
log.Fatalf("Error creating ElasticSearch client: %v", err)
}
if client == nil {
log.Fatalf("Could not create client for ES version %s", esVersion)
}

var queries []elastiquery.ESQuery
if *rawQuery != "" {
queries = append(queries, client.RawQuery(*rawQuery))
}
if *termQueries != "" {
qs := strings.Split(*termQueries, ";")
for _, q := range qs {
parts := strings.SplitN(q, "=", 2)
if len(parts) != 2 {
log.Fatalf("Invalid term query %q", q)
}
queries = append(queries, client.TermQuery(parts[0], parts[1]))
}
}
if *prefixQueries != "" {
qs := strings.Split(*prefixQueries, ";")
for _, q := range qs {
parts := strings.SplitN(q, "=", 2)
if len(parts) != 2 {
log.Fatalf("Invalid prefix query %q", q)
}
queries = append(queries, client.PrefixQuery(parts[0], parts[1]))
}
}

if len(queries) == 0 {
log.Fatal("No queries specified")
}

opts := parseQueryOpts(*offset, *limit, *sortField, *sortReverse)
ctx, cancel := context.WithTimeout(context.Background(), *timeout)
defer cancel()
var result elastiquery.ESResult
if len(queries) == 1 {
result, err = queries[0].Do(ctx, esIndex, opts...)
} else if *or {
result, err = client.OrQuery(queries...).Do(ctx, esIndex, opts...)
} else {
result, err = client.AndQuery(queries...).Do(ctx, esIndex, opts...)
}

if err != nil {
log.Fatalf("Query failed: %v", err)
}
if result.TotalHits() == 0 {
log.Fatalf("Query returned no results")
}

enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
enc.Encode(result.RawHits())
}

func parseQueryOpts(offset, limit int, sortField string, sortReverse bool) []elastiquery.QueryOpt {
var ret []elastiquery.QueryOpt

if offset != 0 {
ret = append(ret, elastiquery.WithOffset(offset))
}
if limit != 0 {
ret = append(ret, elastiquery.WithLimit(limit))
}
if sortField != "" {
ret = append(ret, elastiquery.WithSortField(sortField))
}
if sortReverse {
ret = append(ret, elastiquery.WithReverseSort())
}

return ret
}
61 changes: 61 additions & 0 deletions esclient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package elastiquery

import (
"context"
"encoding/json"
)

// ESClient is a version-independent ElasticSearch client.
type ESClient interface {
RawQuery(string) ESQuery
PrefixQuery(string, string) ESQuery
TermQuery(string, string) ESQuery
AndQuery(...ESQuery) ESQuery
OrQuery(...ESQuery) ESQuery
}

// ESQuery is a version-independent ElasticSearch query.
type ESQuery interface {
Do(ctx context.Context, index string, opts ...QueryOpt) (ESResult, error)
}

// ESResult is a version-independent ElasticSearch query result.
type ESResult interface {
TotalHits() int64
RawHits() []*json.RawMessage
}

type QueryOpt func(*QueryParams)

func WithOffset(offset int) QueryOpt {
return func(qp *QueryParams) {
qp.Offset = offset
}
}

func WithLimit(limit int) QueryOpt {
return func(qp *QueryParams) {
qp.Limit = limit
}
}

func WithSortField(field string) QueryOpt {
return func(qp *QueryParams) {
qp.SortField = field
}
}

func WithReverseSort() QueryOpt {
return func(qp *QueryParams) {
qp.SortReverse = true
}
}

// QueryParams are common parameters for queries. It should not normally be used
// directly - use the `With...` functions to modify the defaults instead.
type QueryParams struct {
Offset int
Limit int
SortField string
SortReverse bool
}
Loading

0 comments on commit c512621

Please sign in to comment.