logstash-input-unix 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
+ MTQ1YjFkNjU1MzI3OGI3YmYxNDQxNjExNTllNWUzYzZhOWM2MzIwMw==
5
+ data.tar.gz: !binary |-
6
+ OTUwODdmNzQ4YWI3MzYyNDBlMzkxM2YwN2Q4NjExODU2MDg3MTRkMQ==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ Zjc4MWEyYTNjZGJjNDgxOWE2NzBiYWEyMzljZGRjMDNkZTViNjhkYmNjYjcy
10
+ MDcxYzVmMDNlZmNhZTJkOTY4NWE4NzU5YmNiMTcxNTU2Zjg0OTdhYTZiZjI0
11
+ ZjAxMzQ2MjYzODA2NjlmMDNjMjUyYjUzOTAxY2YxMzFjZWI2Nzg=
12
+ data.tar.gz: !binary |-
13
+ M2RhZWU2NGMyM2RhNTZmNWM4MWU4OTUwZTc5NzkwOGVlYjkwODNiNzEyNGE0
14
+ YjZlOTIzZDY5ZmM5OGE2ZGU5ZmRmYWVmY2VkOTliNTE0ZjdkZjIwN2ZlNDI1
15
+ YWQ0MWI4ZDlhYjFiY2VhOWMwNjBjMWYxYTM0Nzk0NWFkODMyOTU=
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,163 @@
1
+ # encoding: utf-8
2
+ require "logstash/inputs/base"
3
+ require "logstash/namespace"
4
+ require "socket"
5
+
6
+ # Read events over a UNIX socket.
7
+ #
8
+ # Like stdin and file inputs, each event is assumed to be one line of text.
9
+ #
10
+ # Can either accept connections from clients or connect to a server,
11
+ # depending on `mode`.
12
+ class LogStash::Inputs::Unix < LogStash::Inputs::Base
13
+ class Interrupted < StandardError; end
14
+ config_name "unix"
15
+ milestone 2
16
+
17
+ default :codec, "line"
18
+
19
+ # When mode is `server`, the path to listen on.
20
+ # When mode is `client`, the path to connect to.
21
+ config :path, :validate => :string, :required => true
22
+
23
+ # Remove socket file in case of EADDRINUSE failure
24
+ config :force_unlink, :validate => :boolean, :default => false
25
+
26
+ # The 'read' timeout in seconds. If a particular connection is idle for
27
+ # more than this timeout period, we will assume it is dead and close it.
28
+ #
29
+ # If you never want to timeout, use -1.
30
+ config :data_timeout, :validate => :number, :default => -1
31
+
32
+ # Mode to operate in. `server` listens for client connections,
33
+ # `client` connects to a server.
34
+ config :mode, :validate => ["server", "client"], :default => "server"
35
+
36
+ def initialize(*args)
37
+ super(*args)
38
+ end # def initialize
39
+
40
+ public
41
+ def register
42
+ require "socket"
43
+ require "timeout"
44
+
45
+ if server?
46
+ @logger.info("Starting unix input listener", :address => "#{@path}", :force_unlink => "#{@force_unlink}")
47
+ begin
48
+ @server_socket = UNIXServer.new(@path)
49
+ rescue Errno::EADDRINUSE, IOError
50
+ if @force_unlink
51
+ File.unlink(@path)
52
+ begin
53
+ @server_socket = UNIXServer.new(@path)
54
+ return
55
+ rescue Errno::EADDRINUSE, IOError
56
+ @logger.error("!!!Could not start UNIX server: Address in use",
57
+ :path => @path)
58
+ raise
59
+ end
60
+ end
61
+ @logger.error("Could not start UNIX server: Address in use",
62
+ :path => @path)
63
+ raise
64
+ end
65
+ end
66
+ end # def register
67
+
68
+ private
69
+ def handle_socket(socket, output_queue)
70
+ begin
71
+ hostname = Socket.gethostname
72
+ loop do
73
+ buf = nil
74
+ # NOTE(petef): the timeout only hits after the line is read
75
+ # or socket dies
76
+ # TODO(sissel): Why do we have a timeout here? What's the point?
77
+ if @data_timeout == -1
78
+ buf = socket.readpartial(16384)
79
+ else
80
+ Timeout::timeout(@data_timeout) do
81
+ buf = socket.readpartial(16384)
82
+ end
83
+ end
84
+ @codec.decode(buf) do |event|
85
+ decorate(event)
86
+ event["host"] = hostname
87
+ event["path"] = @path
88
+ output_queue << event
89
+ end
90
+ end # loop do
91
+ rescue => e
92
+ @logger.debug("Closing connection", :path => @path,
93
+ :exception => e, :backtrace => e.backtrace)
94
+ rescue Timeout::Error
95
+ @logger.debug("Closing connection after read timeout",
96
+ :path => @path)
97
+ end # begin
98
+
99
+ ensure
100
+ begin
101
+ socket.close
102
+ rescue IOError
103
+ #pass
104
+ end # begin
105
+ end
106
+
107
+ private
108
+ def server?
109
+ @mode == "server"
110
+ end # def server?
111
+
112
+ public
113
+ def run(output_queue)
114
+ if server?
115
+ @thread = Thread.current
116
+ @client_threads = []
117
+ loop do
118
+ # Start a new thread for each connection.
119
+ begin
120
+ @client_threads << Thread.start(@server_socket.accept) do |s|
121
+ # TODO(sissel): put this block in its own method.
122
+
123
+ @logger.debug("Accepted connection",
124
+ :server => "#{@path}")
125
+ begin
126
+ handle_socket(s, output_queue)
127
+ rescue Interrupted
128
+ s.close rescue nil
129
+ end
130
+ end # Thread.start
131
+ rescue IOError, Interrupted
132
+ if @interrupted
133
+ # Intended shutdown, get out of the loop
134
+ @server_socket.close
135
+ @client_threads.each do |thread|
136
+ thread.raise(IOError.new)
137
+ end
138
+ break
139
+ else
140
+ # Else it was a genuine IOError caused by something else, so propagate it up..
141
+ raise
142
+ end
143
+ end
144
+ end # loop
145
+ else
146
+ loop do
147
+ client_socket = UNIXSocket.new(@path)
148
+ client_socket.instance_eval { class << self; include ::LogStash::Util::SocketPeer end }
149
+ @logger.debug("Opened connection", :client => @path)
150
+ handle_socket(client_socket, output_queue)
151
+ end # loop
152
+ end
153
+ end # def run
154
+
155
+ public
156
+ def teardown
157
+ if server?
158
+ File.unlink(@path)
159
+ @interrupted = true
160
+ @thread.raise(Interrupted.new)
161
+ end
162
+ end # def teardown
163
+ end # class LogStash::Inputs::Unix
@@ -0,0 +1,27 @@
1
+ Gem::Specification.new do |s|
2
+
3
+ s.name = 'logstash-input-unix'
4
+ s.version = '0.1.0'
5
+ s.licenses = ['Apache License (2.0)']
6
+ s.summary = "Read events over a UNIX socket."
7
+ s.description = "Read events over a UNIX socket."
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" => "input" }
21
+
22
+ # Gem dependencies
23
+ s.add_runtime_dependency 'logstash', '>= 1.4.0', '< 2.0.0'
24
+
25
+ s.add_runtime_dependency 'logstash-codec-line'
26
+ end
27
+
@@ -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-input-unix
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-05 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: logstash-codec-line
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ! '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ description: Read events over a UNIX socket.
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/inputs/unix.rb
58
+ - logstash-input-unix.gemspec
59
+ - rakelib/publish.rake
60
+ - rakelib/vendor.rake
61
+ - spec/inputs/unix_spec.rb
62
+ homepage: http://logstash.net/
63
+ licenses:
64
+ - Apache License (2.0)
65
+ metadata:
66
+ logstash_plugin: 'true'
67
+ group: input
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: Read events over a UNIX socket.
88
+ test_files:
89
+ - spec/inputs/unix_spec.rb