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 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
@@ -0,0 +1,6 @@
1
+ .DS_Store
2
+ *.swp
3
+ *.gem
4
+ Gemfile.lock
5
+ coverage/
6
+ .rbenv-version
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
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
@@ -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
@@ -0,0 +1,4 @@
1
+ # This file contains the version of the gem.
2
+ module Sumo
3
+ VERSION = '0.0.1'
4
+ end
@@ -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
@@ -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
@@ -0,0 +1,8 @@
1
+ require 'vcr'
2
+
3
+ VCR.configure do |c|
4
+ c.allow_http_connections_when_no_cassette = false
5
+ c.hook_into :excon
6
+ c.cassette_library_dir = File.join(File.dirname(__FILE__), '..', 'vcr')
7
+ c.configure_rspec_metadata!
8
+ end
@@ -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
@@ -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: