logstash-output-riak 0.1.0

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,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ODhlM2FlY2Q0MjhhNmVlOTRiNTliNmM1ODVmOGJiODA2MTlmNjI5Yw==
5
+ data.tar.gz: !binary |-
6
+ MTc4ODdmNDAyMDcxNDY2ZDZhZTZlMjE1NjM3YjQxNjgxZjc3YTE0OA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ OTE3YTY3ZWFkMjczNWFlYWMxZmJkNTdlMzZlNzhiMDJlYzVmMzdjMTZiYzYx
10
+ MDU1YjU5MjZhYjJhYTI5ZDJlODllNzk1MjIzMTM0Y2UyZmNhYmRiN2I2NDMy
11
+ NTZjZjY3OTdmZGE0ZDcyOWZhZjUyYjE5NzY5MTcwZmIxOWQzYzA=
12
+ data.tar.gz: !binary |-
13
+ ODA3NDQ3MDk1ODE3NTgyMjk1NTdmZDEwNzg2ZjM2ZWFmZjgyMzJjYTE2NTM0
14
+ OWI1YzEzNTEyMjMyZmRiYzYxYzk3ZTM2YWQzYjRmYTA2N2E1YjI1NTUzNTQ4
15
+ YzU5MWE2NTRlNWJhMDFmNjY0MDIzOTM4ZTdmZmJlMGMxMmRkZGE=
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ Gemfile.lock
3
+ .bundle
4
+ vendor
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+ gem 'rake'
3
+ gem 'gem_publisher'
4
+ gem 'archive-tar-minitar'
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2012-2014 Elasticsearch <http://www.elasticsearch.org>
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ @files=[]
2
+
3
+ task :default do
4
+ system("rake -T")
5
+ end
6
+
@@ -0,0 +1,153 @@
1
+ # encoding: utf-8
2
+ require "logstash/outputs/base"
3
+ require "logstash/namespace"
4
+
5
+
6
+ # Riak is a distributed k/v store from Basho.
7
+ # It's based on the Dynamo model.
8
+
9
+ class LogStash::Outputs::Riak < LogStash::Outputs::Base
10
+ config_name "riak"
11
+ milestone 1
12
+
13
+ # The nodes of your Riak cluster
14
+ # This can be a single host or
15
+ # a Logstash hash of node/port pairs
16
+ # e.g
17
+ # ["node1", "8098", "node2", "8098"]
18
+ config :nodes, :validate => :hash, :default => {"localhost" => "8098"}
19
+
20
+ # The protocol to use
21
+ # HTTP or ProtoBuf
22
+ # Applies to ALL backends listed above
23
+ # No mix and match
24
+ config :proto, :validate => ["http", "pb"], :default => "http"
25
+
26
+ # The bucket name to write events to
27
+ # Expansion is supported here as values are
28
+ # passed through event.sprintf
29
+ # Multiple buckets can be specified here
30
+ # but any bucket-specific settings defined
31
+ # apply to ALL the buckets.
32
+ config :bucket, :validate => :array, :default => ["logstash-%{+YYYY.MM.dd}"]
33
+
34
+ # The event key name
35
+ # variables are valid here.
36
+ #
37
+ # Choose this carefully. Best to let riak decide....
38
+ config :key_name, :validate => :string
39
+
40
+ # Bucket properties (NYI)
41
+ # Logstash hash of properties for the bucket
42
+ # i.e.
43
+ # `bucket_props => ["r", "one", "w", "one", "dw", "one"]`
44
+ # or
45
+ # `bucket_props => ["n_val", "3"]`
46
+ # Note that the Logstash config language cannot support
47
+ # hash or array values
48
+ # Properties will be passed as-is
49
+ config :bucket_props, :validate => :hash
50
+
51
+ # Indices
52
+ # Array of fields to add 2i on
53
+ # e.g.
54
+ # `indices => ["source_host", "type"]
55
+ # Off by default as not everyone runs eleveldb
56
+ config :indices, :validate => :array
57
+
58
+ # Search
59
+ # Enable search on the bucket defined above
60
+ config :enable_search, :validate => :boolean, :default => false
61
+
62
+ # SSL
63
+ # Enable SSL
64
+ config :enable_ssl, :validate => :boolean, :default => false
65
+
66
+ # SSL Options
67
+ # Options for SSL connections
68
+ # Only applied if SSL is enabled
69
+ # Logstash hash that maps to the riak-client options
70
+ # here: https://github.com/basho/riak-ruby-client/wiki/Connecting-to-Riak
71
+ # You'll likely want something like this:
72
+ # `ssl_opts => ["pem", "/etc/riak.pem", "ca_path", "/usr/share/certificates"]
73
+ # Per the riak client docs, the above sample options
74
+ # will turn on SSL `VERIFY_PEER`
75
+ config :ssl_opts, :validate => :hash
76
+
77
+ # Metadata (NYI)
78
+ # Allow the user to set custom metadata on the object
79
+ # Should consider converting logstash data to metadata as well
80
+ #
81
+
82
+ public
83
+ def register
84
+ require 'riak'
85
+ riak_opts = {}
86
+ cluster_nodes = Array.new
87
+ @logger.debug("Setting protocol", :protocol => @proto)
88
+ proto_type = "#{@proto}_port".to_sym
89
+ @nodes.each do |node,port|
90
+ @logger.debug("Adding node", :node => node, :port => port)
91
+ cluster_nodes << {:host => node, proto_type => port}
92
+ end
93
+ @logger.debug("Cluster nodes", :nodes => cluster_nodes)
94
+ if @enable_ssl
95
+ @logger.debug("SSL requested")
96
+ if @ssl_opts
97
+ @logger.debug("SSL options provided", @ssl_opts)
98
+ riak_opts.merge!(@ssl_opts.inject({}) {|h,(k,v)| h[k.to_sym] = v; h})
99
+ else
100
+ riak_opts.merge!({:ssl => true})
101
+ end
102
+ @logger.debug("Riak options:", :riak_opts => riak_opts)
103
+ end
104
+ riak_opts.merge!({:nodes => cluster_nodes})
105
+ @logger.debug("Riak options:", :riak_opts => riak_opts)
106
+ @client = Riak::Client.new(riak_opts)
107
+ end # def register
108
+
109
+ public
110
+ def receive(event)
111
+ return unless output?(event)
112
+
113
+ @bucket.each do |b|
114
+ # setup our bucket(s)
115
+ bukkit = @client.bucket(event.sprintf(b))
116
+ # Disable bucket props for now
117
+ # Need to detect params passed that should be converted to int
118
+ # otherwise setting props fails =(
119
+ # Logstash syntax only supports strings and bools
120
+ # likely fix is to either hack in is_numeric?
121
+ # or whitelist certain params and call to_i
122
+ ##@logger.debug("Setting bucket props", :props => @bucket_props)
123
+ ##bukkit.props = @bucket_props if @bucket_props
124
+ ##@logger.debug("Bucket", :bukkit => bukkit.inspect)
125
+
126
+ if @enable_search
127
+ @logger.debug("Enable search requested", :bucket => bukkit.inspect)
128
+ # Check if search is enabled
129
+ @logger.debug("Checking bucket status", :search_enabled => bukkit.is_indexed?)
130
+ bukkit.enable_index! unless bukkit.is_indexed?
131
+ @logger.debug("Rechecking bucket status", :search_enabled => bukkit.is_indexed?)
132
+ end
133
+ @key_name.nil? ? evt_key=nil : evt_key=event.sprintf(@key_name)
134
+ evt = Riak::RObject.new(bukkit, evt_key)
135
+ @logger.debug("RObject", :robject => evt.to_s)
136
+ begin
137
+ evt.content_type = "application/json"
138
+ evt.data = event
139
+ if @indices
140
+ @indices.each do |k|
141
+ idx_name = "#{k.gsub('@','')}_bin"
142
+ @logger.debug("Riak index name", :idx => idx_name)
143
+ @logger.info("Indexes", :indexes => evt.indexes.to_s)
144
+ evt.indexes[idx_name] << event.sprintf("%{#{k}}")
145
+ end
146
+ end
147
+ evt.store
148
+ rescue Exception => e
149
+ @logger.warn("Exception storing", :message => e.message)
150
+ end
151
+ end
152
+ end # def receive
153
+ end # class LogStash::Outputs::Riak
@@ -0,0 +1,28 @@
1
+ Gem::Specification.new do |s|
2
+
3
+ s.name = 'logstash-output-riak'
4
+ s.version = '0.1.0'
5
+ s.licenses = ['Apache License (2.0)']
6
+ s.summary = "Send events to Riak"
7
+ s.description = "Riak is a distributed k/v store from Basho"
8
+ s.authors = ["Elasticsearch"]
9
+ s.email = 'richard.pijnenburg@elasticsearch.com'
10
+ s.homepage = "http://logstash.net/"
11
+ s.require_paths = ["lib"]
12
+
13
+ # Files
14
+ s.files = `git ls-files`.split($\)+::Dir.glob('vendor/*')
15
+
16
+ # Tests
17
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
18
+
19
+ # Special flag to let us know this is actually a logstash plugin
20
+ s.metadata = { "logstash_plugin" => "true", "group" => "output" }
21
+
22
+ # Gem dependencies
23
+ s.add_runtime_dependency 'logstash', '>= 1.4.0', '< 2.0.0'
24
+
25
+ s.add_runtime_dependency 'riak-client', ['1.0.3']
26
+
27
+ end
28
+
@@ -0,0 +1,9 @@
1
+ require "gem_publisher"
2
+
3
+ desc "Publish gem to RubyGems.org"
4
+ task :publish_gem do |t|
5
+ gem_file = Dir.glob(File.expand_path('../*.gemspec',File.dirname(__FILE__))).first
6
+ gem = GemPublisher.publish_if_updated(gem_file, :rubygems)
7
+ puts "Published #{gem}" if gem
8
+ end
9
+
@@ -0,0 +1,169 @@
1
+ require "net/http"
2
+ require "uri"
3
+ require "digest/sha1"
4
+
5
+ def vendor(*args)
6
+ return File.join("vendor", *args)
7
+ end
8
+
9
+ directory "vendor/" => ["vendor"] do |task, args|
10
+ mkdir task.name
11
+ end
12
+
13
+ def fetch(url, sha1, output)
14
+
15
+ puts "Downloading #{url}"
16
+ actual_sha1 = download(url, output)
17
+
18
+ if actual_sha1 != sha1
19
+ fail "SHA1 does not match (expected '#{sha1}' but got '#{actual_sha1}')"
20
+ end
21
+ end # def fetch
22
+
23
+ def file_fetch(url, sha1)
24
+ filename = File.basename( URI(url).path )
25
+ output = "vendor/#{filename}"
26
+ task output => [ "vendor/" ] do
27
+ begin
28
+ actual_sha1 = file_sha1(output)
29
+ if actual_sha1 != sha1
30
+ fetch(url, sha1, output)
31
+ end
32
+ rescue Errno::ENOENT
33
+ fetch(url, sha1, output)
34
+ end
35
+ end.invoke
36
+
37
+ return output
38
+ end
39
+
40
+ def file_sha1(path)
41
+ digest = Digest::SHA1.new
42
+ fd = File.new(path, "r")
43
+ while true
44
+ begin
45
+ digest << fd.sysread(16384)
46
+ rescue EOFError
47
+ break
48
+ end
49
+ end
50
+ return digest.hexdigest
51
+ ensure
52
+ fd.close if fd
53
+ end
54
+
55
+ def download(url, output)
56
+ uri = URI(url)
57
+ digest = Digest::SHA1.new
58
+ tmp = "#{output}.tmp"
59
+ Net::HTTP.start(uri.host, uri.port, :use_ssl => (uri.scheme == "https")) do |http|
60
+ request = Net::HTTP::Get.new(uri.path)
61
+ http.request(request) do |response|
62
+ fail "HTTP fetch failed for #{url}. #{response}" if [200, 301].include?(response.code)
63
+ size = (response["content-length"].to_i || -1).to_f
64
+ count = 0
65
+ File.open(tmp, "w") do |fd|
66
+ response.read_body do |chunk|
67
+ fd.write(chunk)
68
+ digest << chunk
69
+ if size > 0 && $stdout.tty?
70
+ count += chunk.bytesize
71
+ $stdout.write(sprintf("\r%0.2f%%", count/size * 100))
72
+ end
73
+ end
74
+ end
75
+ $stdout.write("\r \r") if $stdout.tty?
76
+ end
77
+ end
78
+
79
+ File.rename(tmp, output)
80
+
81
+ return digest.hexdigest
82
+ rescue SocketError => e
83
+ puts "Failure while downloading #{url}: #{e}"
84
+ raise
85
+ ensure
86
+ File.unlink(tmp) if File.exist?(tmp)
87
+ end # def download
88
+
89
+ def untar(tarball, &block)
90
+ require "archive/tar/minitar"
91
+ tgz = Zlib::GzipReader.new(File.open(tarball))
92
+ # Pull out typesdb
93
+ tar = Archive::Tar::Minitar::Input.open(tgz)
94
+ tar.each do |entry|
95
+ path = block.call(entry)
96
+ next if path.nil?
97
+ parent = File.dirname(path)
98
+
99
+ mkdir_p parent unless File.directory?(parent)
100
+
101
+ # Skip this file if the output file is the same size
102
+ if entry.directory?
103
+ mkdir path unless File.directory?(path)
104
+ else
105
+ entry_mode = entry.instance_eval { @mode } & 0777
106
+ if File.exists?(path)
107
+ stat = File.stat(path)
108
+ # TODO(sissel): Submit a patch to archive-tar-minitar upstream to
109
+ # expose headers in the entry.
110
+ entry_size = entry.instance_eval { @size }
111
+ # If file sizes are same, skip writing.
112
+ next if stat.size == entry_size && (stat.mode & 0777) == entry_mode
113
+ end
114
+ puts "Extracting #{entry.full_name} from #{tarball} #{entry_mode.to_s(8)}"
115
+ File.open(path, "w") do |fd|
116
+ # eof? check lets us skip empty files. Necessary because the API provided by
117
+ # Archive::Tar::Minitar::Reader::EntryStream only mostly acts like an
118
+ # IO object. Something about empty files in this EntryStream causes
119
+ # IO.copy_stream to throw "can't convert nil into String" on JRuby
120
+ # TODO(sissel): File a bug about this.
121
+ while !entry.eof?
122
+ chunk = entry.read(16384)
123
+ fd.write(chunk)
124
+ end
125
+ #IO.copy_stream(entry, fd)
126
+ end
127
+ File.chmod(entry_mode, path)
128
+ end
129
+ end
130
+ tar.close
131
+ File.unlink(tarball) if File.file?(tarball)
132
+ end # def untar
133
+
134
+ def ungz(file)
135
+
136
+ outpath = file.gsub('.gz', '')
137
+ tgz = Zlib::GzipReader.new(File.open(file))
138
+ begin
139
+ File.open(outpath, "w") do |out|
140
+ IO::copy_stream(tgz, out)
141
+ end
142
+ File.unlink(file)
143
+ rescue
144
+ File.unlink(outpath) if File.file?(outpath)
145
+ raise
146
+ end
147
+ tgz.close
148
+ end
149
+
150
+ desc "Process any vendor files required for this plugin"
151
+ task "vendor" do |task, args|
152
+
153
+ @files.each do |file|
154
+ download = file_fetch(file['url'], file['sha1'])
155
+ if download =~ /.tar.gz/
156
+ prefix = download.gsub('.tar.gz', '').gsub('vendor/', '')
157
+ untar(download) do |entry|
158
+ if !file['files'].nil?
159
+ next unless file['files'].include?(entry.full_name.gsub(prefix, ''))
160
+ out = entry.full_name.split("/").last
161
+ end
162
+ File.join('vendor', out)
163
+ end
164
+ elsif download =~ /.gz/
165
+ ungz(download)
166
+ end
167
+ end
168
+
169
+ end
@@ -0,0 +1 @@
1
+ require 'spec_helper'
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logstash-output-riak
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Elasticsearch
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: logstash
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.4.0
20
+ - - <
21
+ - !ruby/object:Gem::Version
22
+ version: 2.0.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.4.0
30
+ - - <
31
+ - !ruby/object:Gem::Version
32
+ version: 2.0.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: riak-client
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - '='
38
+ - !ruby/object:Gem::Version
39
+ version: 1.0.3
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - '='
45
+ - !ruby/object:Gem::Version
46
+ version: 1.0.3
47
+ description: Riak is a distributed k/v store from Basho
48
+ email: richard.pijnenburg@elasticsearch.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - Gemfile
55
+ - LICENSE
56
+ - Rakefile
57
+ - lib/logstash/outputs/riak.rb
58
+ - logstash-output-riak.gemspec
59
+ - rakelib/publish.rake
60
+ - rakelib/vendor.rake
61
+ - spec/outputs/riak_spec.rb
62
+ homepage: http://logstash.net/
63
+ licenses:
64
+ - Apache License (2.0)
65
+ metadata:
66
+ logstash_plugin: 'true'
67
+ group: output
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubyforge_project:
84
+ rubygems_version: 2.4.1
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: Send events to Riak
88
+ test_files:
89
+ - spec/outputs/riak_spec.rb