plunk 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2166c154fb85131ea652ccc4409725b29b4dc250
4
- data.tar.gz: 75503b3934ea91d1ba81ff934684ccd724af7a84
3
+ metadata.gz: f9783fb61da735628ac2266ff16fd4234d146bed
4
+ data.tar.gz: 8fa8d01bffed0cc5351afef868a2e22caa3ddf88
5
5
  SHA512:
6
- metadata.gz: bbeff1a1e3c8e36d0857662715b2b8c9392d6543ff7de11527f6748852feba47f449bb1e66c9a125918884c4d95bfa2d3cb952370c77cb87d9f8e1a3ae00f91f
7
- data.tar.gz: 1d281d2a627594100c9ed502a96e82e01192248585cba14a8de91174fb5c8381145dd8d8a192a9a7259b0653eed029629fc2642593011fee14c6c7bee4b3aa14
6
+ metadata.gz: aaf0493c53bfee8441676b7e72697fb72d47e32b5e6abd8e505e6ef89483a7976bc6ec3a5fb5b34b5959fff0a4d5074cb836b9acde565417737589c704c0fb54
7
+ data.tar.gz: 3767a5c241aa94a9c55faef62742f6e57862377cb2e662cc0c58cd7a952dd1ac5716f3ddea6a894adf73f806c0db5481c95e4f99c1804033a59d9f1875a7a2bf
data/Gemfile.lock CHANGED
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- plunk (0.3.1)
4
+ plunk (0.3.2)
5
5
  activesupport (~> 4.0, >= 4.0.0)
6
+ chronic (~> 0.10, >= 0.10.0)
6
7
  elasticsearch (~> 0.4, >= 0.4.3)
7
8
  json (~> 1.8, >= 1.8.0)
8
9
  parslet (~> 1.5, >= 1.5.0)
@@ -18,6 +19,7 @@ GEM
18
19
  tzinfo (~> 0.3.37)
19
20
  atomic (1.1.14)
20
21
  blankslate (2.1.2.4)
22
+ chronic (0.10.2)
21
23
  diff-lcs (1.2.5)
22
24
  elasticsearch (0.4.3)
23
25
  elasticsearch-api (= 0.4.3)
data/README.md CHANGED
@@ -5,43 +5,82 @@ Human-friendly query language for Elasticsearch
5
5
 
6
6
  ## About
7
7
 
8
- Plunk is a ruby gem to take a human-friendly search command and translate it
9
- to something Elasticsearch understands. Currently it only supports a few
10
- commands, but the framework is in place
8
+ Plunk is a ruby gem to take a human-friendly, one-line search command and
9
+ translate it to full-fledged JSON to send to Elasticsearch. Currently it only
10
+ supports a few commands, but the goal is to support a large subset of what
11
+ Elasticsearch offers.
11
12
 
12
13
  ## Installation
13
14
  ```
14
15
  gem install plunk
15
16
  ```
16
17
 
18
+ Plunk uses [Parslet](https://github.com/kschiess/parslet) to first parse your
19
+ query, and then [Elasticsearch's official ruby library](https://github.com/elasticsearch/elasticsearch-ruby)
20
+ to send it to Elasticsearch.
21
+
17
22
  ## Usage
18
23
  ```ruby
19
24
  require 'plunk'
20
25
 
21
- # configure is required
22
- # elasticsearch_options accepts the same params as Elasticsearch::Client
26
+ #
27
+ # Configuration is required before using Plunk
28
+ #
29
+ # Elasticsearch_options accepts the same params as Elasticsearch::Client
30
+ # from the elasticsearch-ruby library
23
31
  Plunk.configure do |config|
24
32
  config.elasticsearch_options = { host: 'localhost' }
25
33
  end
26
34
 
27
- # all documents of type syslog from the last week
28
- Plunk.search 'last 1w _type = syslog'
35
+ # Restrict timeframe to last 1 week and match documents with _type=syslog
36
+ # s = seconds
37
+ # m = minutes
38
+ # h = hours
39
+ # d = days
40
+ # w = weeks
41
+ # All times in Plunk are converted to UTC
42
+ Plunk.search 'last 1w AND _type = syslog'
43
+
44
+ # The ```window``` command can also be used to filter by time
45
+ Plunk.search 'window -2d to -1d'
46
+
47
+ # Plunk tries to parse the date with Chronic, so this works too. Note the
48
+ # double quotes around the time string. This is needed if it contains a space.
49
+ Plunk.search 'window "last monday" to "last thursday"'
50
+
51
+ # Of course, absolute dates are supported as well. Date format is American style
52
+ # e.g. MM/DD/YY
53
+ Plunk.search 'window 3/14/12 to 3/15/12'
54
+
55
+ # Use double quotes to wrap space-containing strings
56
+ Plunk.search 'http.header = "UserAgent: Mozilla/5.0"'
57
+
58
+ # Commands are joined using parenthesized booleans
59
+ Plunk.search '(last 1h AND severity = 5) OR (last 1w AND severity = 3)'
29
60
 
30
- # nested search. first runs the '_type=access_logs' search, extracts the values
31
- # for fields user.name, user.nickname, and user.email, then ORs these together
32
- # as the query for the outer search.
33
- Plunk.search 'user = `_type=access_logs | user.name, user.nickname, user.email`'
61
+ # "AND" is aliased to "and" and "&". Similarly, "OR" is aliased to "or" and "|".
62
+ # The following queries are identical to one above
63
+ Plunk.search '(last 1h and severity = 5) or (last 1w and severity = 3)'
64
+ Plunk.search '(last 1h & severity = 5) | (last 1w & severity = 3)'
34
65
 
35
- # booleans are supported
36
- Plunk.search 'foo.field = (bar OR baz)'
66
+ # Use the NOT keyword to negate the following command or boolean chain
67
+ Plunk.search 'NOT message = Error'
68
+
69
+ # Like AND and OR, "NOT" is aliased to "not" and "~"
70
+ Plunk.search 'not message = Error'
71
+ Plunk.search '~ message = Error'
72
+
73
+ # Regexp is supported as well
74
+ Plunk.search 'http.headers = /.*User-Agent: Mozilla.*/ OR http.headers = /.*application\/json.*/'
37
75
  ```
38
76
 
39
77
 
40
78
  ## Translation
41
79
 
42
- Plunk takes your command and translates
80
+ Under the hood, Plunk takes your query and translates it to
81
+ Elasticsearch-compatible JSON. For example,
43
82
 
44
- ```last 24h _type=syslog```
83
+ ```last 24h & _type=syslog```
45
84
 
46
85
  gets translated to:
47
86
 
@@ -49,11 +88,6 @@ gets translated to:
49
88
  {
50
89
  "query": {
51
90
  "filtered": {
52
- "query": {
53
- "query_string": {
54
- "query": "_type:syslog"
55
- }
56
- },
57
91
  "filter": {
58
92
  "and": [
59
93
  {
@@ -63,6 +97,13 @@ gets translated to:
63
97
  "lte": "2013-08-24T05:43:13.770Z"
64
98
  }
65
99
  }
100
+ },
101
+ {
102
+ "query": {
103
+ "query_string": {
104
+ "query": "_type:syslog"
105
+ }
106
+ }
66
107
  }
67
108
  ]
68
109
  }
@@ -70,3 +111,6 @@ gets translated to:
70
111
  }
71
112
  }
72
113
  ```
114
+
115
+ In general, commands are combined into a single filter using Elasticsearch's,
116
+ ```and```, ```or```, and ```not``` filters.
data/lib/plunk/helper.rb CHANGED
@@ -30,17 +30,34 @@ module Plunk
30
30
  }
31
31
  end
32
32
 
33
+ def self.limit_builder(limit)
34
+ {
35
+ limit: {
36
+ value: limit
37
+ }
38
+ }
39
+ end
40
+
33
41
  def self.range_builder(range_min, range_max)
34
42
  {
35
43
  range: {
36
44
  Plunk.timestamp_field => {
37
45
  gte: range_min,
38
- gte: range_max
46
+ lte: range_max
39
47
  }
40
48
  }
41
49
  }
42
50
  end
43
51
 
52
+ def self.regexp_builder(field, regexp, flags=nil)
53
+ {
54
+ regexp: {
55
+ field => regexp,
56
+ flags: flags || 'ALL'
57
+ }
58
+ }
59
+ end
60
+
44
61
  def self.time_query_to_timestamp(int_quantity, quantifier)
45
62
  case quantifier
46
63
  when 's'
data/lib/plunk/parser.rb CHANGED
@@ -13,7 +13,9 @@ module Plunk
13
13
  rule(:space?) { space.maybe }
14
14
 
15
15
  # Numbers
16
- rule(:integer) { str('-').maybe >> digit.repeat(1) >> space? }
16
+ rule(:positive_integer) { digit.repeat(1) >> space? }
17
+ rule(:negative_integer) { str('-') >> positive_integer }
18
+ rule(:integer) { negative_integer | positive_integer }
17
19
  rule(:float) {
18
20
  str('-').maybe >> digit.repeat(1) >> str('.') >> digit.repeat(1) >> space?
19
21
  }
@@ -62,6 +64,16 @@ module Plunk
62
64
  rule(:rhs) {
63
65
  regexp | query_value
64
66
  }
67
+ rule(:chronic_time) {
68
+ string | match('[^\s]').repeat
69
+ }
70
+ rule(:relative_time) {
71
+ integer.as(:quantity) >>
72
+ match('s|m|h|d|w').as(:quantifier)
73
+ }
74
+ rule(:absolute_time) {
75
+ datetime.as(:datetime) | chronic_time.as(:chronic_time)
76
+ }
65
77
 
66
78
  # Field = Value
67
79
  rule(:field_value) {
@@ -72,20 +84,31 @@ module Plunk
72
84
 
73
85
  # Value-only
74
86
  rule(:value_only) {
75
- rhs.as(:value)
87
+ query_value.as(:value)
88
+ }
89
+
90
+ # Window
91
+ rule(:window) {
92
+ str('window') >>
93
+ space >>
94
+ (relative_time | absolute_time).as(:window_start) >>
95
+ space >> str('to') >> space >>
96
+ (relative_time | absolute_time).as(:window_end)
97
+ }
98
+
99
+ # Limit
100
+ rule(:limit) {
101
+ str('limit') >> space >> positive_integer.as(:limit)
76
102
  }
77
103
 
78
104
  # Regexp
79
105
  rule(:regexp) {
80
- str('/') >> (str('\/') | match('[^/]')).repeat >> str('/')
106
+ str('/') >> (str('\/') | match('[^/]')).repeat.as(:regexp) >> str('/')
81
107
  }
82
108
 
83
109
  # Last
84
110
  rule(:last) {
85
- str('last') >>
86
- space >>
87
- integer.as(:quantity) >>
88
- match('s|m|h|d|w').as(:quantifier)
111
+ (str('last') >> space >> relative_time).as(:last)
89
112
  }
90
113
 
91
114
  # Subsearch
@@ -101,7 +124,9 @@ module Plunk
101
124
  # NOTE: order matters!
102
125
  rule(:command) {
103
126
  (
127
+ window |
104
128
  last |
129
+ limit |
105
130
  field_value |
106
131
  value_only
107
132
  ).as(:command) >> space?
@@ -1,41 +1,84 @@
1
1
  require 'parslet'
2
+ require 'chronic'
2
3
 
3
4
  module Plunk
4
5
  class Transformer < Parslet::Transform
6
+ # Base
7
+ rule(command: subtree(:command)) do
8
+ command
9
+ end
10
+
5
11
  # Field = Value
6
- rule(command: {
12
+ rule(
7
13
  field: simple(:field),
8
14
  value: simple(:value)
9
- }) do
15
+ ) do
10
16
  Helper.query_builder(
11
17
  String(field) + ":" + String(value)
12
18
  )
13
19
  end
14
20
 
21
+ # Limit
22
+ rule(limit: simple(:limit)) do
23
+ Helper.limit_builder(Integer(limit))
24
+ end
25
+
26
+ # Regexp
27
+ rule(
28
+ field: simple(:field),
29
+ value: {
30
+ regexp: simple(:regexp)
31
+ }
32
+ ) do
33
+ Helper.regexp_builder(String(field), String(regexp))
34
+ end
35
+
15
36
  # Value-only
16
- rule(command: { value: simple(:value) }) do
37
+ rule(value: simple(:value)) do
17
38
  Helper.query_builder(String(value))
18
39
  end
19
40
 
20
- rule(command: {
41
+ # Last
42
+ rule(last: subtree(:last)) do
43
+ start_time = last
44
+ end_time = Helper.timestamp_format(Time.now)
45
+
46
+ Helper.range_builder(start_time, end_time)
47
+ end
48
+
49
+ # Window
50
+ rule(
51
+ window_start: subtree(:window_start),
52
+ window_end: subtree(:window_end)
53
+ ) do
54
+ Helper.range_builder(window_start, window_end)
55
+ end
56
+
57
+ # Time parts
58
+ rule(datetime: simple(:datetime)) do
59
+ String(datetime)
60
+ end
61
+ rule(
21
62
  quantity: simple(:quantity),
22
63
  quantifier: simple(:quantifier)
23
- }) do
24
- start_timestamp = Helper.time_query_to_timestamp(
25
- Integer(quantity),
64
+ ) do
65
+ timestamp = Helper.time_query_to_timestamp(
66
+ Integer(quantity).abs, # last -1h same as last 1h
26
67
  String(quantifier)
27
68
  )
28
69
 
29
- start_time = Helper.timestamp_format start_timestamp
30
- end_time = Helper.timestamp_format(Time.now)
31
-
32
- Helper.range_builder(start_time, end_time)
70
+ Helper.timestamp_format timestamp
71
+ end
72
+ rule(chronic_time: simple(:chronic_time)) do
73
+ Helper.timestamp_format Chronic.parse(chronic_time)
33
74
  end
34
75
 
35
- rule(:negate => subtree(:not)) do
76
+ # Negate
77
+ rule(negate: subtree(:not)) do
36
78
  { not: negate }
37
79
  end
38
80
 
81
+ # Command joining
39
82
  rule(:or => {
40
83
  left: subtree(:left),
41
84
  right: subtree(:right)
data/plunk.gemspec CHANGED
@@ -1,10 +1,11 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "plunk"
3
- s.version = "0.3.2"
3
+ s.version = "0.3.3"
4
4
  s.add_runtime_dependency "json", "~> 1.8", ">= 1.8.0"
5
5
  s.add_runtime_dependency "parslet", "~> 1.5", ">= 1.5.0"
6
6
  s.add_runtime_dependency "elasticsearch", "~> 0.4", ">= 0.4.3"
7
7
  s.add_runtime_dependency "activesupport", "~> 4.0", ">= 4.0.0"
8
+ s.add_runtime_dependency "chronic", "~> 0.10", ">= 0.10.0"
8
9
  s.add_development_dependency "rspec", "~> 2.0", ">= 2.14.1"
9
10
  s.add_development_dependency "timecop", "~> 0.7", ">= 0.7.1"
10
11
  s.summary = "Elasticsearch query language"
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'the limit command' do
4
+ it 'should parse limit 1' do
5
+ result = Plunk.search 'limit 1'
6
+ expected = Plunk::Helper.filter_builder(
7
+ Plunk::Helper.limit_builder(1)
8
+ )
9
+ expect(result).to eq(expected)
10
+ end
11
+ end
data/spec/regexp_spec.rb CHANGED
@@ -5,7 +5,10 @@ describe 'regexp searches' do
5
5
  it 'should parse foo=/blah foo/' do
6
6
  result = Plunk.search 'foo=/blah foo/'
7
7
  expected = Plunk::Helper.filter_builder(
8
- Plunk::Helper.query_builder('foo:/blah foo/')
8
+ Plunk::Helper.regexp_builder(
9
+ 'foo',
10
+ 'blah foo'
11
+ )
9
12
  )
10
13
  expect(result).to eq(expected)
11
14
  end
@@ -13,7 +16,10 @@ describe 'regexp searches' do
13
16
  it 'should parse foo=/blah\/ foo/' do
14
17
  result = Plunk.search 'foo=/blah\/ foo/'
15
18
  expected = Plunk::Helper.filter_builder(
16
- Plunk::Helper.query_builder('foo:/blah\/ foo/')
19
+ Plunk::Helper.regexp_builder(
20
+ 'foo',
21
+ 'blah\/ foo'
22
+ )
17
23
  )
18
24
  expect(result).to eq(expected)
19
25
  end
@@ -21,15 +27,21 @@ describe 'regexp searches' do
21
27
  it 'should parse foo=/blah\. foo/' do
22
28
  result = Plunk.search 'foo=/blah\. foo/'
23
29
  expected = Plunk::Helper.filter_builder(
24
- Plunk::Helper.query_builder('foo:/blah\. foo/')
30
+ Plunk::Helper.regexp_builder(
31
+ 'foo',
32
+ 'blah\. foo'
33
+ )
25
34
  )
26
35
  expect(result).to eq(expected)
27
36
  end
28
37
 
29
- it 'should parse /.*User\-Agent\: Microsoft\-WebDAV.*/' do
30
- result = Plunk.search '/.*User\-Agent\: Microsoft\-WebDAV.*/'
38
+ it 'should parse http.headers=/.*User\-Agent\: Microsoft\-WebDAV.*/' do
39
+ result = Plunk.search 'http.headers=/.*User\-Agent\: Microsoft\-WebDAV.*/'
31
40
  expected = Plunk::Helper.filter_builder(
32
- Plunk::Helper.query_builder('/.*User\-Agent\: Microsoft\-WebDAV.*/')
41
+ Plunk::Helper.regexp_builder(
42
+ 'http.headers',
43
+ '.*User\-Agent\: Microsoft\-WebDAV.*'
44
+ )
33
45
  )
34
46
  expect(result).to eq(expected)
35
47
  end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ require 'shared/time_stubs'
3
+ require 'shared/plunk_stubs'
4
+ require 'chronic'
5
+
6
+ describe 'the window command' do
7
+ include_context 'time stubs'
8
+ include_context 'plunk stubs'
9
+
10
+ it 'should parse window 3/11/13 to 3/12/13' do
11
+ result = Plunk.search 'window 3/11/13 to 3/12/13'
12
+ expected = Plunk::Helper.filter_builder(
13
+ Plunk::Helper.range_builder(
14
+ Plunk::Helper.timestamp_format(Chronic.parse('3/11/13')),
15
+ Plunk::Helper.timestamp_format(Chronic.parse('3/12/13'))
16
+ )
17
+ )
18
+ expect(result).to eq(expected)
19
+ end
20
+
21
+ it 'should parse window "last monday" to "last thursday"' do
22
+ result = Plunk.search 'window "last monday" to "last thursday"'
23
+ expected = Plunk::Helper.filter_builder(
24
+ Plunk::Helper.range_builder(
25
+ Plunk::Helper.timestamp_format(Chronic.parse('last monday')),
26
+ Plunk::Helper.timestamp_format(Chronic.parse('last thursday'))
27
+ )
28
+ )
29
+ expect(result).to eq(expected)
30
+ end
31
+
32
+ it 'should parse window -60m to -30m' do
33
+ result = Plunk.search 'window -60m to -30m'
34
+ expected = Plunk::Helper.filter_builder(
35
+ Plunk::Helper.range_builder(
36
+ Plunk::Helper.timestamp_format(@time - 60.minutes),
37
+ Plunk::Helper.timestamp_format(@time - 30.minutes)
38
+ )
39
+ )
40
+ expect(result).to eq(expected)
41
+ end
42
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plunk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ram Mehta
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2014-03-20 00:00:00.000000000 Z
13
+ date: 2014-03-21 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: json
@@ -92,6 +92,26 @@ dependencies:
92
92
  - - '>='
93
93
  - !ruby/object:Gem::Version
94
94
  version: 4.0.0
95
+ - !ruby/object:Gem::Dependency
96
+ name: chronic
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: '0.10'
102
+ - - '>='
103
+ - !ruby/object:Gem::Version
104
+ version: 0.10.0
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ~>
110
+ - !ruby/object:Gem::Version
111
+ version: '0.10'
112
+ - - '>='
113
+ - !ruby/object:Gem::Version
114
+ version: 0.10.0
95
115
  - !ruby/object:Gem::Dependency
96
116
  name: rspec
97
117
  requirement: !ruby/object:Gem::Requirement
@@ -161,12 +181,14 @@ files:
161
181
  - spec/chained_search_spec.rb
162
182
  - spec/field_value_spec.rb
163
183
  - spec/last_spec.rb
184
+ - spec/limit_spec.rb
164
185
  - spec/nested_search_spec.rb
165
186
  - spec/regexp_spec.rb
166
187
  - spec/shared/dummy_client.rb
167
188
  - spec/shared/plunk_stubs.rb
168
189
  - spec/shared/time_stubs.rb
169
190
  - spec/spec_helper.rb
191
+ - spec/window_spec.rb
170
192
  homepage: https://github.com/elbii/plunk
171
193
  licenses:
172
194
  - MIT