sumo-search 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/Gemfile +3 -0
- data/README.md +44 -0
- data/Rakefile +11 -0
- data/bin/sumo +68 -0
- data/lib/sumo.rb +28 -0
- data/lib/sumo/config.rb +55 -0
- data/lib/sumo/error.rb +18 -0
- data/lib/sumo/formatter.rb +21 -0
- data/lib/sumo/query_builder.rb +61 -0
- data/lib/sumo/version.rb +4 -0
- data/spec/fixtures/sumo_creds +1 -0
- data/spec/lib/sumo/config_spec.rb +130 -0
- data/spec/lib/sumo/formatter_spec.rb +51 -0
- data/spec/lib/sumo/query_builder_spec.rb +128 -0
- data/spec/lib/sumo_spec.rb +35 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/vcr.rb +8 -0
- data/spec/vcr/Sumo/_search/when_the_credentials_can_be_found/and_the_query_is_valid/parses_the_response.yml +49 -0
- data/spec/vcr/Sumo/_search/when_the_credentials_can_be_found/but_the_query_is_invalid/raises_an_error.yml +43 -0
- data/spec/vcr/Sumo_QueryBuilder/integration/when_the_request_is_invalid/raises_an_error.yml +43 -0
- data/spec/vcr/Sumo_QueryBuilder/integration/when_the_request_is_valid/compiles_and_sends_the_query_to_the_server.yml +49 -0
- data/sumo-search.gemspec +26 -0
- metadata +192 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a8e3759e39a03e8a3fceef78e0bf775275c35901
|
4
|
+
data.tar.gz: 193be08aa43cc6aebe1281606d6e24034a10900b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f513e2bdb1b53414aba48a4838de8320713d7425a209dde25535e9803b6a97b0ca3ca6ed8e90136da45707e24c696d42debc9f66b8bf3e6801e656a6ee48c397
|
7
|
+
data.tar.gz: f883c5490d9810001a7159fb3d9467dd7e7da03bf4ee9ef67e2184e1f86a42b02a496aad2efbd9765d8d355972373ae1e1092571072df30ab24ad269595cb109
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# sumo-search
|
2
|
+
|
3
|
+
This library can be used to query Sumo Logic's search API.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
From the command line:
|
8
|
+
|
9
|
+
```shell
|
10
|
+
$ sudo gem install sumo-search
|
11
|
+
```
|
12
|
+
|
13
|
+
Installing this gem will install a script called `sumo`.
|
14
|
+
Note that this gem requires a Ruby version of at least 2.0.0.
|
15
|
+
|
16
|
+
## Configuration
|
17
|
+
|
18
|
+
To use this script, you must supply your Sumo Logic credentials.
|
19
|
+
The script can read credentials from one of two places; either an environment variable or a configuration file.
|
20
|
+
The environment variable is `SUMO_CREDS`, and the configuration file is `~/.sumo_creds`.
|
21
|
+
Whichever you choose, the credentials must be in the following format:
|
22
|
+
|
23
|
+
```
|
24
|
+
username@website:password
|
25
|
+
```
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
```shell
|
30
|
+
$ sumo [*options/flags*]
|
31
|
+
```
|
32
|
+
|
33
|
+
This script wraps the [Sumo Search API](https://github.com/SumoLogic/sumo-api-doc/wiki/search-api).
|
34
|
+
The API only has one endpoint, `/api/v1/logs/search` which searches your logs.
|
35
|
+
|
36
|
+
| Command Line Option | Corresponding Query Parameter | Description |
|
37
|
+
|------------------------|-------------------------------|--------------------------------------|
|
38
|
+
| -q, --query QUERY | q | The query to search (required) |
|
39
|
+
| -f, --from START | from | Begin searching at START (ISO8601) |
|
40
|
+
| -t, --to END | to | Finish searching at END (ISO8601) |
|
41
|
+
| -z, --time-zone ZONE | tz | The timezone of the search |
|
42
|
+
| -c, --config-file FILE | - | Use the specified config file |
|
43
|
+
| -e, --extract-key KEY | - | Extract the given key from your logs |
|
44
|
+
| -h, --help | - | Display the help message |
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'sumo'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
require 'cane/rake_task'
|
7
|
+
|
8
|
+
task :default => [:spec, :quality]
|
9
|
+
|
10
|
+
RSpec::Core::RakeTask.new(:spec) { |t| t.pattern = 'spec/**/*_spec.rb' }
|
11
|
+
Cane::RakeTask.new(:quality) { |t| t.canefile = '.cane' }
|
data/bin/sumo
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'sumo'
|
4
|
+
require 'time'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
include Sumo
|
8
|
+
|
9
|
+
options = {
|
10
|
+
:query_options => {},
|
11
|
+
:config_file => DEFAULT_CONFIG_FILE,
|
12
|
+
:key => nil
|
13
|
+
}
|
14
|
+
|
15
|
+
OptionParser.new do |opts|
|
16
|
+
opts.banner = 'Usage: sumo [flags/options]'
|
17
|
+
|
18
|
+
opts.separator ''
|
19
|
+
opts.separator 'Specific options:'
|
20
|
+
|
21
|
+
opts.on('-q', '--query QUERY', 'Search for QUERY (required)') do |q|
|
22
|
+
options[:query] = q
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on('-f', '--from START', 'Begin at the given ISO8601 time') do |f|
|
26
|
+
options[:query_options]['from'] = f
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on('-t', '--to END', 'Finish at the given ISO8601 time') do |t|
|
30
|
+
options[:query_options]['to'] = t
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on('-z', '--time-zone ZONE', 'Use the given time zone') do |tz|
|
34
|
+
options[:query_options]['tz'] = tz
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on('-c', '--config-file FILE',
|
38
|
+
"Use the given config file instead of #{DEFAULT_CONFIG_FILE}") do |f|
|
39
|
+
options[:config_file] = config_file
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on('-e', '--extract-key KEY', 'Extract the specified key') do |k|
|
43
|
+
options[:key] = k
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on('-h', '--help', 'Print this message') do
|
47
|
+
puts opts
|
48
|
+
exit 0
|
49
|
+
end
|
50
|
+
end.parse!
|
51
|
+
|
52
|
+
if __FILE__ == $0
|
53
|
+
if options[:query].nil?
|
54
|
+
puts 'Please specify a query'
|
55
|
+
exit 1
|
56
|
+
else
|
57
|
+
begin
|
58
|
+
puts search(options[:query],
|
59
|
+
options: options[:query_options],
|
60
|
+
key: options[:key],
|
61
|
+
config_file: options[:config_file])
|
62
|
+
exit 0
|
63
|
+
rescue StandardError => ex
|
64
|
+
puts ex.message
|
65
|
+
exit 1
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/sumo.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'excon'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
# This module acts as a namespace, and contains a `.search` method which ties
|
6
|
+
# together functionality of its child modules.
|
7
|
+
module Sumo
|
8
|
+
autoload :Config, 'sumo/config'
|
9
|
+
autoload :Error, 'sumo/error'
|
10
|
+
autoload :Formatter, 'sumo/formatter'
|
11
|
+
autoload :QueryBuilder, 'sumo/query_builder'
|
12
|
+
|
13
|
+
# Unless otherwise specified in the initializer, the configuration file
|
14
|
+
# defaults to '~/.sumo_creds'.
|
15
|
+
DEFAULT_CONFIG_FILE = File.expand_path('~/.sumo_creds').freeze
|
16
|
+
|
17
|
+
# Search the given query. If any options are supplied, those are sent to the
|
18
|
+
# QueryBuilder. If the key is supplied, that key will be attempted to be
|
19
|
+
# extracted from the JSON response. If a config_file is supplied, that will
|
20
|
+
# be read instead of the default.
|
21
|
+
def search(query, options: {}, key: nil, config_file: DEFAULT_CONFIG_FILE)
|
22
|
+
creds = Config.new(config_file).load_config!
|
23
|
+
response = QueryBuilder.new(creds, options.merge('q' => query)).execute
|
24
|
+
formatted = Formatter.format_json(response)
|
25
|
+
key.nil? ? formatted : Formatter.extract_key(key, formatted)
|
26
|
+
end
|
27
|
+
module_function :search
|
28
|
+
end
|
data/lib/sumo/config.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# This class contains the logic to find the user's credentials in either an
|
2
|
+
# environment variable or a configuration file. If both exist and a
|
3
|
+
# configuration file has not been specified, the environment variable is
|
4
|
+
# preferred. If both exist and a config file has been specified, the config
|
5
|
+
# file is preferred.
|
6
|
+
#
|
7
|
+
# The environment varibale is called 'SUMO_CREDS'; the default configuration
|
8
|
+
# file is '~/.sumo_creds'.
|
9
|
+
class Sumo::Config
|
10
|
+
include Sumo::Error
|
11
|
+
|
12
|
+
attr_reader :config_file
|
13
|
+
|
14
|
+
# Set and freeze the @config_file instance variable.
|
15
|
+
def initialize(config_file = Sumo::DEFAULT_CONFIG_FILE)
|
16
|
+
if config_file.is_a?(String)
|
17
|
+
@config_file = File.expand_path(config_file).freeze
|
18
|
+
else
|
19
|
+
raise TypeError, "Expected a String, got: #{config_file}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Test if an alternate file has been specified.
|
24
|
+
def file_specified?
|
25
|
+
config_file != Sumo::DEFAULT_CONFIG_FILE
|
26
|
+
end
|
27
|
+
|
28
|
+
# Memoize the credentials from the environment.
|
29
|
+
def env_creds
|
30
|
+
@env_creds ||= ENV['SUMO_CREDS']
|
31
|
+
end
|
32
|
+
|
33
|
+
# Memoize the credentials from the configuration file.
|
34
|
+
def file_creds
|
35
|
+
@file_creds ||= File.read(config_file).chomp if File.exists?(config_file)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Load the credentials.
|
39
|
+
def load_config
|
40
|
+
if file_specified?
|
41
|
+
file_creds || env_creds
|
42
|
+
else
|
43
|
+
env_creds || file_creds
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Load the credentials, raising an error if none are specified.
|
48
|
+
def load_config!
|
49
|
+
if (creds = load_config).nil?
|
50
|
+
raise NoCredsFound, "No credentials were found, set ENV['SUMO_CREDS']."
|
51
|
+
else
|
52
|
+
creds
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/sumo/error.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# This module acts as a namespace for all errors thrown in the gem.
|
2
|
+
module Sumo::Error
|
3
|
+
# This error is never raised, but instead used to catch all other errors that
|
4
|
+
# may be raised.
|
5
|
+
class BaseError < StandardError; end
|
6
|
+
|
7
|
+
# Raised when an unexpected type is passed to a method.
|
8
|
+
class TypeError < BaseError; end
|
9
|
+
|
10
|
+
# This error is raised when no credentials can be found.
|
11
|
+
class NoCredsFound < BaseError; end
|
12
|
+
|
13
|
+
# This error is raised when there is an error on a request.
|
14
|
+
class RequestError < BaseError; end
|
15
|
+
|
16
|
+
# This error is raised when a parsing error occurs.
|
17
|
+
class ParseError < BaseError; end
|
18
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# This module contains formatting functions to help make text more readable.
|
2
|
+
module Sumo::Formatter
|
3
|
+
include Sumo::Error
|
4
|
+
|
5
|
+
# Given a sumo response (string)
|
6
|
+
def format_json(response)
|
7
|
+
JSON.parse(response)
|
8
|
+
.sort_by { |hash| hash['_messagetime'] }
|
9
|
+
.map { |hash| hash['_raw'] }
|
10
|
+
rescue
|
11
|
+
raise ParseError, 'Could not parse the response.'
|
12
|
+
end
|
13
|
+
|
14
|
+
def extract_key(key, logs)
|
15
|
+
logs.map { |log| JSON.parse(log)[key] }
|
16
|
+
rescue
|
17
|
+
raise ParseError, "Error extracting the #{key} from the hash."
|
18
|
+
end
|
19
|
+
|
20
|
+
module_function :format_json, :extract_key
|
21
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# This class can be used to build and execute queries. For example, the
|
2
|
+
# following code builds a query for 'error' in the last 15 minutes.
|
3
|
+
#
|
4
|
+
# q = Sumo::QueryBuilder.new('user@example.com:pass')
|
5
|
+
# .query('error')
|
6
|
+
# .from('-15m')
|
7
|
+
# .to('now')
|
8
|
+
#
|
9
|
+
# Note that this class is immutable.
|
10
|
+
class Sumo::QueryBuilder
|
11
|
+
include Sumo::Error
|
12
|
+
|
13
|
+
attr_reader :creds, :opts
|
14
|
+
|
15
|
+
# Create a new QueryBuilder with the given credentials and query.
|
16
|
+
def initialize(creds, opts = {})
|
17
|
+
if creds.is_a?(String) && opts.is_a?(Hash)
|
18
|
+
@creds = creds.freeze
|
19
|
+
@opts = opts.freeze
|
20
|
+
else
|
21
|
+
raise TypeError, "Invalid initialization parameters to QueryBuilder."
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Metaprogram the #query, #to, #from, #time_zone, and #format methods. Each
|
26
|
+
# of these methods accepts one argument, and returns a new 'QueryBuilder' that
|
27
|
+
# has the specified query parameter key set.
|
28
|
+
def self.builder(name, key)
|
29
|
+
klass = self
|
30
|
+
define_method(name) { |arg| klass.new(creds, opts.merge(key => arg)) }
|
31
|
+
end
|
32
|
+
private_class_method :builder
|
33
|
+
|
34
|
+
builder :to, 'to'
|
35
|
+
builder :from, 'from'
|
36
|
+
builder :query, 'q'
|
37
|
+
builder :format, 'format'
|
38
|
+
builder :time_zone, 'tz'
|
39
|
+
|
40
|
+
# Send the API request to Sumo Logic.
|
41
|
+
def execute
|
42
|
+
encoded = Base64.encode64(creds)
|
43
|
+
resp = connection.get(:path => '/api/v1/logs/search',
|
44
|
+
:query => opts,
|
45
|
+
:headers => { 'Authorization' => "Basic #{encoded}" })
|
46
|
+
if resp.status >= 400
|
47
|
+
if resp.body.nil? || resp.body.empty?
|
48
|
+
raise RequestError, "An error occurred sending your request."
|
49
|
+
else
|
50
|
+
raise RequestError, JSON.parse(resp.body)['message']
|
51
|
+
end
|
52
|
+
end
|
53
|
+
resp.body
|
54
|
+
end
|
55
|
+
|
56
|
+
# A memoized HTTP connection.
|
57
|
+
def connection
|
58
|
+
@connection ||= Excon.new('https://api.sumologic.com')
|
59
|
+
end
|
60
|
+
private :connection
|
61
|
+
end
|
data/lib/sumo/version.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
fake_user@fake_site:fake_pass
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Sumo::Config do
|
4
|
+
describe '.new' do
|
5
|
+
subject { Sumo::Config }
|
6
|
+
|
7
|
+
context 'when no argument is given' do
|
8
|
+
it 'sets @config_file to its default value' do
|
9
|
+
subject.new.config_file.should == Sumo::DEFAULT_CONFIG_FILE
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when an argument is given' do
|
14
|
+
context 'when it is not a String' do
|
15
|
+
it 'raises an error' do
|
16
|
+
expect { subject.new(nil) }.to raise_error(Sumo::Error::TypeError)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'when it is a String' do
|
21
|
+
let(:file) { '/dev/null' }
|
22
|
+
|
23
|
+
it 'sets @config_file to the argument' do
|
24
|
+
subject.new(file).config_file.should == file
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#file_specified?' do
|
31
|
+
context 'when @config_file is the default' do
|
32
|
+
it 'returns false' do
|
33
|
+
subject.file_specified?.should be_false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when @config_file is not the default' do
|
38
|
+
subject { Sumo::Config.new('my-config-file') }
|
39
|
+
|
40
|
+
it 'returns true' do
|
41
|
+
subject.file_specified?.should be_true
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#env_creds' do
|
47
|
+
before { ENV['SUMO_CREDS'] = creds }
|
48
|
+
|
49
|
+
context 'when the environment variable is not set' do
|
50
|
+
let(:creds) { nil }
|
51
|
+
|
52
|
+
it 'returns nil' do
|
53
|
+
subject.env_creds.should be_nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'when the environment variable is set' do
|
58
|
+
let(:creds) { 'alpha' }
|
59
|
+
|
60
|
+
it 'returns that variable' do
|
61
|
+
subject.env_creds.should == creds
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#file_creds' do
|
67
|
+
subject { Sumo::Config.new(file) }
|
68
|
+
|
69
|
+
context 'when the @config_file does not exist' do
|
70
|
+
let(:file) { 'not a file' }
|
71
|
+
|
72
|
+
it 'returns nil' do
|
73
|
+
subject.file_creds.should be_nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'when @config_file does exist' do
|
78
|
+
let(:file) { 'spec/fixtures/sumo_creds' }
|
79
|
+
|
80
|
+
it 'returns its contents' do
|
81
|
+
subject.file_creds.should == 'fake_user@fake_site:fake_pass'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe '#load_config' do
|
87
|
+
before do
|
88
|
+
subject.stub(:file_specified?).and_return(specified)
|
89
|
+
subject.stub(:file_creds).and_return('alpha')
|
90
|
+
subject.stub(:env_creds).and_return('beta')
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'when an alternate file is specifed' do
|
94
|
+
let(:specified) { true }
|
95
|
+
|
96
|
+
it 'attempts to read the file first' do
|
97
|
+
subject.load_config.should == 'alpha'
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
context 'when an alternate file is not specified' do
|
102
|
+
let(:specified) { false }
|
103
|
+
|
104
|
+
it 'attempts to read the environment variable first' do
|
105
|
+
subject.load_config.should == 'beta'
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe 'load_config!' do
|
111
|
+
before { subject.stub(:load_config).and_return(config) }
|
112
|
+
|
113
|
+
context 'when #load_config returns nil' do
|
114
|
+
let(:config) { nil }
|
115
|
+
|
116
|
+
it 'raises an error' do
|
117
|
+
expect { subject.load_config! }
|
118
|
+
.to raise_error(Sumo::Error::NoCredsFound)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context 'when #load_config returns a value' do
|
123
|
+
let(:config) { 'configuration' }
|
124
|
+
|
125
|
+
it 'returns that value' do
|
126
|
+
subject.load_config!.should == config
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Sumo::Formatter do
|
4
|
+
subject { Sumo::Formatter }
|
5
|
+
|
6
|
+
describe '.format_json' do
|
7
|
+
context 'when the input cannot be parsed' do
|
8
|
+
it 'raises an error' do
|
9
|
+
expect { subject.format_json('cannot parse this') }
|
10
|
+
.to raise_error(Sumo::Error::ParseError)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'when the input can be parsed' do
|
15
|
+
let(:input) {
|
16
|
+
[
|
17
|
+
{ '_messagetime' => 2, '_raw' => 'world' },
|
18
|
+
{ '_messagetime' => 1, '_raw' => 'hello' }
|
19
|
+
].to_json
|
20
|
+
}
|
21
|
+
|
22
|
+
it 'sorts the input by the _messagetime, returning a String' do
|
23
|
+
subject.format_json(input).should == %w(hello world)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '.extract_key' do
|
29
|
+
context 'when at least one element of the Array cannot be parsed' do
|
30
|
+
let(:input) { [ { :a => 1 }.to_json, 'qwerty'] }
|
31
|
+
|
32
|
+
it 'raises an error' do
|
33
|
+
expect { subject.extract_key('anything', input) }
|
34
|
+
.to raise_error(Sumo::Error::ParseError)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when the input is an Array of parseable values' do
|
39
|
+
let(:input) {
|
40
|
+
[
|
41
|
+
{ 'message' => 'rats', 'time' => 2 }.to_json,
|
42
|
+
{ 'message' => 'cats', 'time' => 1 }.to_json
|
43
|
+
]
|
44
|
+
}
|
45
|
+
|
46
|
+
it 'returns an Array with the specified key extracted' do
|
47
|
+
subject.extract_key('message', input).should == %w(rats cats)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Sumo::QueryBuilder do
|
4
|
+
subject { Sumo::QueryBuilder.new('fake-creds') }
|
5
|
+
|
6
|
+
describe '.new' do
|
7
|
+
context 'when the correct types of arguments are supplied' do
|
8
|
+
subject { Sumo::QueryBuilder.new('auth', 'q' => 'error') }
|
9
|
+
|
10
|
+
it 'creates a new QueryBuilder' do
|
11
|
+
subject.creds.should == 'auth'
|
12
|
+
subject.opts.should == { 'q' => 'error' }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'when invalid arguments are supplied' do
|
17
|
+
it 'raises an error' do
|
18
|
+
expect { Sumo::QueryBuilder.new(:symbol, 1) }
|
19
|
+
.to raise_error(Sumo::Error::TypeError)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#query' do
|
25
|
+
it 'adds a query to the request' do
|
26
|
+
subject.query('alpha').opts.should == { 'q' => 'alpha' }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#to' do
|
31
|
+
it 'adds an end time to the request' do
|
32
|
+
subject.to('now').opts.should == { 'to' => 'now' }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#from' do
|
37
|
+
it 'adds an end time to the request' do
|
38
|
+
subject.from('-15m').opts.should == { 'from' => '-15m' }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#time_zone' do
|
43
|
+
it 'adds a time zone to the request' do
|
44
|
+
subject.time_zone('UTC').opts.should == { 'tz' => 'UTC' }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#format' do
|
49
|
+
it 'adds a format to the request' do
|
50
|
+
subject.format('JSON').opts.should == { 'format' => 'JSON' }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#execute' do
|
55
|
+
let(:creds) { 'user@example.com:pass' }
|
56
|
+
let(:encoded) { Base64.encode64(creds) }
|
57
|
+
let(:conn) { double(:connection) }
|
58
|
+
let(:query) {
|
59
|
+
{
|
60
|
+
'q' => 'production',
|
61
|
+
'from' => '-30m',
|
62
|
+
'to' => '-15m',
|
63
|
+
'tz' => 'UTC',
|
64
|
+
'format' => 'json'
|
65
|
+
}
|
66
|
+
}
|
67
|
+
subject {
|
68
|
+
Sumo::QueryBuilder.new(creds).query('production')
|
69
|
+
.from('-30m')
|
70
|
+
.to('-15m')
|
71
|
+
.time_zone('UTC')
|
72
|
+
.format('json')
|
73
|
+
}
|
74
|
+
|
75
|
+
before do
|
76
|
+
subject.stub(:connection).and_return(conn)
|
77
|
+
conn.stub(:get)
|
78
|
+
.with(:path => '/api/v1/logs/search',
|
79
|
+
:query => query,
|
80
|
+
:headers => { 'Authorization' => "Basic #{encoded}" })
|
81
|
+
.and_return(resp)
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'when the server responds' do
|
85
|
+
let(:resp) { double(:response, :body => '{}', :status => 200) }
|
86
|
+
|
87
|
+
it 'parses the response' do
|
88
|
+
subject.execute.should == '{}'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'when the server does not respond' do
|
93
|
+
let(:resp) { double(:response, :body => nil, :status => 504) }
|
94
|
+
|
95
|
+
it 'raises an error' do
|
96
|
+
expect { subject.execute }.to raise_error(Sumo::Error::RequestError)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'integration', :vcr do
|
102
|
+
# WARNING: If you are going to change this VCR, modify the credentials so
|
103
|
+
# that they are valid, record the VCR, remove the credentials from it, and
|
104
|
+
# change the below variable back to its original form.
|
105
|
+
let(:creds) { 'aladdin@swipely.com:open sesame' }
|
106
|
+
|
107
|
+
context 'when the request is valid' do
|
108
|
+
let(:result) { JSON.parse(subject.execute) }
|
109
|
+
subject { Sumo::QueryBuilder.new(creds).query('nginx') }
|
110
|
+
|
111
|
+
it 'compiles and sends the query to the server' do
|
112
|
+
result.length.should_not be_zero
|
113
|
+
result.should be_all { |hash|
|
114
|
+
%w(_sourcecategory _raw _sourcehost _receipttime _sourcename
|
115
|
+
_messagetime).all? { |key| hash.has_key?(key) }
|
116
|
+
}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'when the request is invalid' do
|
121
|
+
subject { Sumo::QueryBuilder.new(creds).query('nginx').to('nonsense') }
|
122
|
+
|
123
|
+
it 'raises an error' do
|
124
|
+
expect { subject.execute }.to raise_error(Sumo::Error::RequestError)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Sumo do
|
4
|
+
subject { Sumo }
|
5
|
+
|
6
|
+
it { should be_a Module }
|
7
|
+
|
8
|
+
describe '.search' do
|
9
|
+
context 'when the credentials cannot be found' do
|
10
|
+
before { ENV['SUMO_CREDS'] = nil }
|
11
|
+
|
12
|
+
it 'raises an error' do
|
13
|
+
expect { subject.search('anything') }
|
14
|
+
.to raise_error(Sumo::Error::NoCredsFound)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'when the credentials can be found' do
|
19
|
+
before { ENV['SUMO_CREDS'] = 'aladdin@swipely.com:open sesame' }
|
20
|
+
|
21
|
+
context 'but the query is invalid' do
|
22
|
+
it 'raises an error', :vcr do
|
23
|
+
expect { subject.search('Rails', options: { 'from' => 'never' }) }
|
24
|
+
.to raise_error(Sumo::Error::RequestError)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'and the query is valid' do
|
29
|
+
it 'parses the response', :vcr do
|
30
|
+
subject.search('rails').each { |str| str.should_not match(/_raw/) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
require 'simplecov'
|
5
|
+
require 'sumo'
|
6
|
+
|
7
|
+
SimpleCov.start
|
8
|
+
|
9
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
config.mock_with :rspec
|
13
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
14
|
+
config.color_enabled = true
|
15
|
+
config.formatter = :documentation
|
16
|
+
config.tty = true
|
17
|
+
end
|
data/spec/support/vcr.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: get
|
5
|
+
uri: https://api.sumologic.com/api/v1/logs/search?q=rails
|
6
|
+
body:
|
7
|
+
encoding: US-ASCII
|
8
|
+
string: ''
|
9
|
+
headers:
|
10
|
+
User-Agent:
|
11
|
+
- excon/0.31.0
|
12
|
+
Authorization:
|
13
|
+
- |
|
14
|
+
Basic YWxhZGRpbkBzd2lwZWx5LmNvbTpvcGVuIHNlc2FtZQ==
|
15
|
+
response:
|
16
|
+
status:
|
17
|
+
code: 200
|
18
|
+
message:
|
19
|
+
headers:
|
20
|
+
Cache-control:
|
21
|
+
- no-cache="set-cookie"
|
22
|
+
Content-Type:
|
23
|
+
- application/json; charset=ISO-8859-1
|
24
|
+
Date:
|
25
|
+
- Tue, 07 Jan 2014 18:32:15 GMT
|
26
|
+
Expires:
|
27
|
+
- Thu, 01-Jan-1970 00:00:00 GMT
|
28
|
+
Set-Cookie:
|
29
|
+
- JSESSIONID=p8dhtx823tvbbxs578ipt3mn;Path=/api, AWSELB=D5C1176F0665104977B708B0B48E6FFEC09E311CD1473A91FD800B5F0A2443FAC23088ACED0D807762BB3B769CEAADA14ED5C9BC9C86BA1749DC556A5C1B04944D462E0D92;PATH=/
|
30
|
+
Content-Length:
|
31
|
+
- '1052'
|
32
|
+
Connection:
|
33
|
+
- Close
|
34
|
+
body:
|
35
|
+
encoding: UTF-8
|
36
|
+
string: |-
|
37
|
+
[
|
38
|
+
{
|
39
|
+
"_sourcecategory" : "all",
|
40
|
+
"_raw" : "{\"@source\":\"syslog://\",\"@tags\":[\"railslog\"],\"@fields\":{\"facility\":\"local0\",\"severity\":[\"notice\",\"D\"],\"program\":\"swipely-ledger-delayed-1\",\"processid\":\"-\",\"sslsubject\":[\"/C=US/ST=Rhode Island/O=Swipely/OU=Engineering/CN=s-ledger\",\"/C=US/ST=Rhode Island/O=Swipely/OU=Engineering/CN=s-ledger\"],\"host\":[\"s-ledger\"],\"timestamp\":[\"140107-18:26:50.605\"],\"thread_id\":[\"8_15364200\"],\"msg\":[\"** [Honeybadger] Environment Info: [Ruby: 1.9.3] [Rails: 3.2.11] [Env: staging]\"]},\"@timestamp\":\"2014-01-07T18:26:54.159449+00:00\",\"@source_host\":\"ip-10-244-158-144\",\"@message\":\"Jan 7 18:26:54 swipely-ledger-delayed-1:D 140107-18:26:50.605 8_15364200: ** [Honeybadger] Environment Info: [Ruby: 1.9.3] [Rails: 3.2.11] [Env: staging]\",\"@type\":\"syslog\"}",
|
41
|
+
"_sourcehost" : "logs.example.com",
|
42
|
+
"_receipttime" : 1389119226829,
|
43
|
+
"_sourcename" : "/media/ephemeral0/logs/swipely/s-ledger-2014-01-07.log",
|
44
|
+
"_messagetime" : 1389119214159
|
45
|
+
}
|
46
|
+
]
|
47
|
+
http_version:
|
48
|
+
recorded_at: Tue, 07 Jan 2014 18:32:19 GMT
|
49
|
+
recorded_with: VCR 2.8.0
|
@@ -0,0 +1,43 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: get
|
5
|
+
uri: https://api.sumologic.com/api/v1/logs/search?from=never&q=Rails
|
6
|
+
body:
|
7
|
+
encoding: US-ASCII
|
8
|
+
string: ''
|
9
|
+
headers:
|
10
|
+
User-Agent:
|
11
|
+
- excon/0.31.0
|
12
|
+
Authorization:
|
13
|
+
- |
|
14
|
+
Basic YWxhZGRpbkBzd2lwZWx5LmNvbTpvcGVuIHNlc2FtZQ==
|
15
|
+
response:
|
16
|
+
status:
|
17
|
+
code: 400
|
18
|
+
message:
|
19
|
+
headers:
|
20
|
+
Cache-control:
|
21
|
+
- no-cache="set-cookie"
|
22
|
+
Content-Type:
|
23
|
+
- application/json; charset=ISO-8859-1
|
24
|
+
Date:
|
25
|
+
- Tue, 07 Jan 2014 18:30:35 GMT
|
26
|
+
Set-Cookie:
|
27
|
+
- AWSELB=D5C1176F0665104977B708B0B48E6FFEC09E311CD1473A91FD800B5F0A2443FAC23088ACED0D807762BB3B769CEAADA14ED5C9BC9C86BA1749DC556A5C1B04944D462E0D92;PATH=/
|
28
|
+
Content-Length:
|
29
|
+
- '154'
|
30
|
+
Connection:
|
31
|
+
- Close
|
32
|
+
body:
|
33
|
+
encoding: UTF-8
|
34
|
+
string: |-
|
35
|
+
{
|
36
|
+
"status" : 400,
|
37
|
+
"id" : "9IJJK-HJQ9G-H09X3",
|
38
|
+
"code" : "search.invalid.timestamp.from",
|
39
|
+
"message" : "The 'from' field contains an invalid time."
|
40
|
+
}
|
41
|
+
http_version:
|
42
|
+
recorded_at: Tue, 07 Jan 2014 18:30:39 GMT
|
43
|
+
recorded_with: VCR 2.8.0
|
@@ -0,0 +1,43 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: get
|
5
|
+
uri: https://api.sumologic.com/api/v1/logs/search?q=nginx&to=nonsense
|
6
|
+
body:
|
7
|
+
encoding: US-ASCII
|
8
|
+
string: ''
|
9
|
+
headers:
|
10
|
+
User-Agent:
|
11
|
+
- excon/0.31.0
|
12
|
+
Authorization:
|
13
|
+
- |
|
14
|
+
Basic YWxhZGRpbkBzd2lwZWx5LmNvbTpvcGVuIHNlc2FtZQ==
|
15
|
+
response:
|
16
|
+
status:
|
17
|
+
code: 400
|
18
|
+
message:
|
19
|
+
headers:
|
20
|
+
Cache-control:
|
21
|
+
- no-cache="set-cookie"
|
22
|
+
Content-Type:
|
23
|
+
- application/json; charset=ISO-8859-1
|
24
|
+
Date:
|
25
|
+
- Tue, 07 Jan 2014 15:52:16 GMT
|
26
|
+
Set-Cookie:
|
27
|
+
- AWSELB=D5C1176F0665104977B708B0B48E6FFEC09E311CD10EE26CC6F86FEAE76E4BBB3D728D261CADA16D6256329826144A742E70346FDF2C8592D1FAF5BF2ABFF69B2FDD5A38FA;PATH=/
|
28
|
+
Content-Length:
|
29
|
+
- '150'
|
30
|
+
Connection:
|
31
|
+
- Close
|
32
|
+
body:
|
33
|
+
encoding: UTF-8
|
34
|
+
string: |-
|
35
|
+
{
|
36
|
+
"status" : 400,
|
37
|
+
"id" : "ELTN2-7BBZF-ZCZQ8",
|
38
|
+
"code" : "search.invalid.timestamp.to",
|
39
|
+
"message" : "The 'to' field contains an invalid time."
|
40
|
+
}
|
41
|
+
http_version:
|
42
|
+
recorded_at: Tue, 07 Jan 2014 15:52:18 GMT
|
43
|
+
recorded_with: VCR 2.8.0
|
@@ -0,0 +1,49 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: get
|
5
|
+
uri: https://api.sumologic.com/api/v1/logs/search?q=nginx
|
6
|
+
body:
|
7
|
+
encoding: US-ASCII
|
8
|
+
string: ''
|
9
|
+
headers:
|
10
|
+
User-Agent:
|
11
|
+
- excon/0.31.0
|
12
|
+
Authorization:
|
13
|
+
- |
|
14
|
+
Basic YWxhZGRpbkBzd2lwZWx5LmNvbTpvcGVuIHNlc2FtZQ==
|
15
|
+
response:
|
16
|
+
status:
|
17
|
+
code: 200
|
18
|
+
message:
|
19
|
+
headers:
|
20
|
+
Cache-control:
|
21
|
+
- no-cache="set-cookie"
|
22
|
+
Content-Type:
|
23
|
+
- application/json; charset=ISO-8859-1
|
24
|
+
Date:
|
25
|
+
- Tue, 07 Jan 2014 15:49:55 GMT
|
26
|
+
Expires:
|
27
|
+
- Thu, 01-Jan-1970 00:00:00 GMT
|
28
|
+
Set-Cookie:
|
29
|
+
- JSESSIONID=1dybd1qub96gu13g5a9qh9dyqc;Path=/api, AWSELB=D5C1176F0665104977B708B0B48E6FFEC09E311CD1473A91FD800B5F0A2443FAC23088ACEDDBBB8C99D44C2403D82B59D74368F2FCC1FFB75ECAF9255D4A40FE87F45B4364;PATH=/
|
30
|
+
transfer-encoding:
|
31
|
+
- ''
|
32
|
+
Connection:
|
33
|
+
- Close
|
34
|
+
body:
|
35
|
+
encoding: UTF-8
|
36
|
+
string: |-
|
37
|
+
[
|
38
|
+
{
|
39
|
+
"_sourcecategory" : "all",
|
40
|
+
"_raw" : "nonsense result",
|
41
|
+
"_sourcehost" : "ls01.logs.swipely.com",
|
42
|
+
"_receipttime" : 1389109434302,
|
43
|
+
"_sourcename" : "/media/ephemeral0/logs/swipely/p-ledger-lb-2014-01-07.log",
|
44
|
+
"_messagetime" : 1389109430911
|
45
|
+
}
|
46
|
+
]
|
47
|
+
http_version:
|
48
|
+
recorded_at: Tue, 07 Jan 2014 15:49:59 GMT
|
49
|
+
recorded_with: VCR 2.8.0
|
data/sumo-search.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/sumo/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Swipely, Inc."]
|
6
|
+
gem.email = %w{tomhulihan@swipely.com}
|
7
|
+
gem.description = %q{A CLI for querying the Sumo Logic search API}
|
8
|
+
gem.summary = %q{A CLI for querying the Sumo Logic search API}
|
9
|
+
gem.homepage = 'https://github.com/swipely/sumo-search'
|
10
|
+
gem.license = 'MIT'
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = 'sumo-search'
|
15
|
+
gem.require_paths = %w{lib}
|
16
|
+
gem.version = Sumo::VERSION
|
17
|
+
gem.required_ruby_version = '>= 2.0.0'
|
18
|
+
gem.add_dependency 'excon', '>= 0.28'
|
19
|
+
gem.add_dependency 'json'
|
20
|
+
gem.add_development_dependency 'rake'
|
21
|
+
gem.add_development_dependency 'rspec'
|
22
|
+
gem.add_development_dependency 'cane'
|
23
|
+
gem.add_development_dependency 'pry'
|
24
|
+
gem.add_development_dependency 'vcr', '>= 2.7.0'
|
25
|
+
gem.add_development_dependency 'simplecov'
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,192 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sumo-search
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Swipely, Inc.
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-01-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: excon
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.28'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.28'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: cane
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: vcr
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 2.7.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 2.7.0
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: simplecov
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: A CLI for querying the Sumo Logic search API
|
126
|
+
email:
|
127
|
+
- tomhulihan@swipely.com
|
128
|
+
executables:
|
129
|
+
- sumo
|
130
|
+
extensions: []
|
131
|
+
extra_rdoc_files: []
|
132
|
+
files:
|
133
|
+
- .gitignore
|
134
|
+
- Gemfile
|
135
|
+
- README.md
|
136
|
+
- Rakefile
|
137
|
+
- bin/sumo
|
138
|
+
- lib/sumo.rb
|
139
|
+
- lib/sumo/config.rb
|
140
|
+
- lib/sumo/error.rb
|
141
|
+
- lib/sumo/formatter.rb
|
142
|
+
- lib/sumo/query_builder.rb
|
143
|
+
- lib/sumo/version.rb
|
144
|
+
- spec/fixtures/sumo_creds
|
145
|
+
- spec/lib/sumo/config_spec.rb
|
146
|
+
- spec/lib/sumo/formatter_spec.rb
|
147
|
+
- spec/lib/sumo/query_builder_spec.rb
|
148
|
+
- spec/lib/sumo_spec.rb
|
149
|
+
- spec/spec_helper.rb
|
150
|
+
- spec/support/vcr.rb
|
151
|
+
- spec/vcr/Sumo/_search/when_the_credentials_can_be_found/and_the_query_is_valid/parses_the_response.yml
|
152
|
+
- spec/vcr/Sumo/_search/when_the_credentials_can_be_found/but_the_query_is_invalid/raises_an_error.yml
|
153
|
+
- spec/vcr/Sumo_QueryBuilder/integration/when_the_request_is_invalid/raises_an_error.yml
|
154
|
+
- spec/vcr/Sumo_QueryBuilder/integration/when_the_request_is_valid/compiles_and_sends_the_query_to_the_server.yml
|
155
|
+
- sumo-search.gemspec
|
156
|
+
homepage: https://github.com/swipely/sumo-search
|
157
|
+
licenses:
|
158
|
+
- MIT
|
159
|
+
metadata: {}
|
160
|
+
post_install_message:
|
161
|
+
rdoc_options: []
|
162
|
+
require_paths:
|
163
|
+
- lib
|
164
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - '>='
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: 2.0.0
|
169
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
requirements: []
|
175
|
+
rubyforge_project:
|
176
|
+
rubygems_version: 2.0.7
|
177
|
+
signing_key:
|
178
|
+
specification_version: 4
|
179
|
+
summary: A CLI for querying the Sumo Logic search API
|
180
|
+
test_files:
|
181
|
+
- spec/fixtures/sumo_creds
|
182
|
+
- spec/lib/sumo/config_spec.rb
|
183
|
+
- spec/lib/sumo/formatter_spec.rb
|
184
|
+
- spec/lib/sumo/query_builder_spec.rb
|
185
|
+
- spec/lib/sumo_spec.rb
|
186
|
+
- spec/spec_helper.rb
|
187
|
+
- spec/support/vcr.rb
|
188
|
+
- spec/vcr/Sumo/_search/when_the_credentials_can_be_found/and_the_query_is_valid/parses_the_response.yml
|
189
|
+
- spec/vcr/Sumo/_search/when_the_credentials_can_be_found/but_the_query_is_invalid/raises_an_error.yml
|
190
|
+
- spec/vcr/Sumo_QueryBuilder/integration/when_the_request_is_invalid/raises_an_error.yml
|
191
|
+
- spec/vcr/Sumo_QueryBuilder/integration/when_the_request_is_valid/compiles_and_sends_the_query_to_the_server.yml
|
192
|
+
has_rdoc:
|