logstash-filter-jdbc_static 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2 -0
  3. data/CONTRIBUTORS +22 -0
  4. data/Gemfile +2 -0
  5. data/LICENSE +13 -0
  6. data/README.md +94 -0
  7. data/lib/logstash-filter-jdbc_static_jars.rb +5 -0
  8. data/lib/logstash/filters/jdbc/basic_database.rb +117 -0
  9. data/lib/logstash/filters/jdbc/column.rb +38 -0
  10. data/lib/logstash/filters/jdbc/db_object.rb +103 -0
  11. data/lib/logstash/filters/jdbc/loader.rb +114 -0
  12. data/lib/logstash/filters/jdbc/loader_schedule.rb +38 -0
  13. data/lib/logstash/filters/jdbc/lookup.rb +192 -0
  14. data/lib/logstash/filters/jdbc/lookup_processor.rb +91 -0
  15. data/lib/logstash/filters/jdbc/lookup_result.rb +39 -0
  16. data/lib/logstash/filters/jdbc/read_only_database.rb +57 -0
  17. data/lib/logstash/filters/jdbc/read_write_database.rb +86 -0
  18. data/lib/logstash/filters/jdbc/repeating_load_runner.rb +11 -0
  19. data/lib/logstash/filters/jdbc/single_load_runner.rb +43 -0
  20. data/lib/logstash/filters/jdbc/validatable.rb +49 -0
  21. data/lib/logstash/filters/jdbc_static.rb +216 -0
  22. data/logstash-filter-jdbc_static.gemspec +38 -0
  23. data/spec/filters/env_helper.rb +10 -0
  24. data/spec/filters/jdbc/column_spec.rb +70 -0
  25. data/spec/filters/jdbc/db_object_spec.rb +81 -0
  26. data/spec/filters/jdbc/loader_spec.rb +76 -0
  27. data/spec/filters/jdbc/lookup_processor_spec.rb +132 -0
  28. data/spec/filters/jdbc/lookup_spec.rb +129 -0
  29. data/spec/filters/jdbc/read_only_database_spec.rb +66 -0
  30. data/spec/filters/jdbc/read_write_database_spec.rb +89 -0
  31. data/spec/filters/jdbc/repeating_load_runner_spec.rb +24 -0
  32. data/spec/filters/jdbc/single_load_runner_spec.rb +16 -0
  33. data/spec/filters/jdbc_static_file_local_spec.rb +83 -0
  34. data/spec/filters/jdbc_static_spec.rb +70 -0
  35. data/spec/filters/remote_server_helper.rb +24 -0
  36. data/spec/filters/shared_helpers.rb +35 -0
  37. data/spec/helpers/WHY-THIS-JAR.txt +4 -0
  38. data/spec/helpers/derbyrun.jar +0 -0
  39. data/vendor/jar-dependencies/runtime-jars/derby-10.14.1.0.jar +0 -0
  40. data/vendor/jar-dependencies/runtime-jars/derbyclient-10.14.1.0.jar +0 -0
  41. metadata +224 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f5052dc6f6f29b7919706257f1f72df70243ef4c8ab3e8c9995fc5fff8603156
4
+ data.tar.gz: 4ea4e75413cecd1bf5dbb44ac954810ab7369001448f9d23eaefe29631b0fd00
5
+ SHA512:
6
+ metadata.gz: '086d5fd949f32b807b78562e36ecefd1952464775e81be53bd81cd4a11f612df65043defdc6368107368f21d196ce19de307cd4a2bd05ef6110ab46c131a857c'
7
+ data.tar.gz: 0b27db6ac28a7a9b831fe3a5e7369b3a4dbf69ca220fd5e33c968b372d7d7e69a6054d7668919ad06070ec02b9b902cdba3d09da6f8c58eb5eddd6c8e1f7ff51
@@ -0,0 +1,2 @@
1
+ ## 1.0.0
2
+ - Initial commit
@@ -0,0 +1,22 @@
1
+ The following is a list of people who have contributed ideas, code, bug
2
+ reports, or in general have helped logstash along its way.
3
+
4
+ Contributors:
5
+ * Christian Paredes (cparedes)
6
+ * Colin Surprenant (colinsurprenant)
7
+ * Jay Luker (lbjay)
8
+ * John E. Vincent (lusis)
9
+ * Jordan Sissel (jordansissel)
10
+ * João Duarte (jsvd)
11
+ * Laust Rud Jacobsen (rud)
12
+ * Nikolay Bryskin (nikicat)
13
+ * Pete Fritchman (fetep)
14
+ * Philippe Weber (wiibaa)
15
+ * Pier-Hugues Pellerin (ph)
16
+ * Suyog Rao (suyograo)
17
+ * Guy Boertje (guyboertje)
18
+
19
+ Note: If you've sent us patches, bug reports, or otherwise contributed to
20
+ Logstash, and you aren't on the list above and want to be, please let us know
21
+ and we'll make sure you're here. Contributions from folks like you are what make
22
+ open source awesome.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2012–2018 Elasticsearch <http://www.elastic.co>
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.
@@ -0,0 +1,94 @@
1
+ # logstash-filter-jdbc_static
2
+ Lookup plugin which overwrites a loader query's result set into a local in-memory db and then lookup queries add records to a target field using properties from the event.
3
+
4
+ # Logstash Plugin
5
+
6
+ [![Travis Build Status](https://travis-ci.org/logstash-plugins/logstash-filter-jdbc_static.svg)](https://travis-ci.org/logstash-plugins/logstash-filter-jdbc_static)
7
+
8
+ This is a plugin for [Logstash](https://github.com/elastic/logstash).
9
+
10
+ It is fully free and fully open source. The license is Apache 2.0, meaning you are pretty much free to use it however you want in whatever way.
11
+
12
+ ## Need Help?
13
+
14
+ Need help? Try #logstash on freenode IRC or the https://discuss.elastic.co/c/logstash discussion forum.
15
+
16
+ ## Developing
17
+
18
+ ### 1. Plugin Development and Testing
19
+
20
+ #### Code
21
+ - To get started, you'll need JRuby with the Bundler gem installed.
22
+
23
+ - Create a new plugin or clone and existing from the GitHub [logstash-plugins](https://github.com/logstash-plugins) organization. We also provide [example plugins](https://github.com/logstash-plugins?query=example).
24
+
25
+ - Install dependencies
26
+ ```sh
27
+ bundle install
28
+ ```
29
+
30
+ #### Test
31
+
32
+ - Update your dependencies
33
+
34
+ ```sh
35
+ bundle install
36
+ ```
37
+
38
+ - Run tests
39
+
40
+ ```sh
41
+ bundle exec rspec
42
+ ```
43
+
44
+ ### 2. Running your unpublished Plugin in Logstash
45
+
46
+ #### 2.1 Run in a local Logstash clone
47
+
48
+ - Edit Logstash `Gemfile` and add the local plugin path, for example:
49
+ ```ruby
50
+ gem "logstash-filter-awesome", :path => "/your/local/logstash-filter-awesome"
51
+ ```
52
+ - Install plugin
53
+ ```sh
54
+ # Logstash 2.3 and higher
55
+ bin/logstash-plugin install --no-verify
56
+
57
+ # Prior to Logstash 2.3
58
+ bin/plugin install --no-verify
59
+
60
+ ```
61
+ - Run Logstash with your plugin
62
+ ```sh
63
+ bin/logstash -e 'filter {awesome {}}'
64
+ ```
65
+ At this point any modifications to the plugin code will be applied to this local Logstash setup. After modifying the plugin, simply rerun Logstash.
66
+
67
+ #### 2.2 Run in an installed Logstash
68
+
69
+ You can use the same **2.1** method to run your plugin in an installed Logstash by editing its `Gemfile` and pointing the `:path` to your local plugin development directory or you can build the gem and install it using:
70
+
71
+ - Build your plugin gem
72
+ ```sh
73
+ gem build logstash-filter-awesome.gemspec
74
+ ```
75
+ - Install the plugin from the Logstash home
76
+ ```sh
77
+ # Logstash 2.3 and higher
78
+ bin/logstash-plugin install --no-verify
79
+
80
+ # Prior to Logstash 2.3
81
+ bin/plugin install --no-verify
82
+
83
+ ```
84
+ - Start Logstash and proceed to test the plugin
85
+
86
+ ## Contributing
87
+
88
+ All contributions are welcome: ideas, patches, documentation, bug reports, complaints, and even something you drew up on a napkin.
89
+
90
+ Programming is not a required skill. Whatever you've seen about open source and maintainers or community members saying "send patches or die" - you will not see that here.
91
+
92
+ It is more important to the community that you are able to contribute.
93
+
94
+ For more information about contributing, see the [CONTRIBUTING](https://github.com/elastic/logstash/blob/master/CONTRIBUTING.md) file.
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+ require 'logstash/environment'
3
+
4
+ root_dir = File.expand_path(File.join(File.dirname(__FILE__), ".."))
5
+ LogStash::Environment.load_runtime_jars! File.join(root_dir, "vendor")
@@ -0,0 +1,117 @@
1
+ require "sequel"
2
+ require "sequel/adapters/jdbc"
3
+ require "java"
4
+ require "logstash/util/loggable"
5
+
6
+ module LogStash module Filters module Jdbc
7
+ EMBEDDED_DERBY_DRIVER_CLASS = "org.apache.derby.jdbc.EmbeddedDriver".freeze
8
+ MEMORY_DERBY_LOCAL_CONNECTION_STRING = "jdbc:derby:memory:____;create=true".freeze
9
+ CONNECTION_ERRORS = [::Sequel::DatabaseConnectionError, ::Sequel::DatabaseDisconnectError, ::Sequel::PoolTimeout]
10
+
11
+ class LookupJdbcException < ::StandardError; end
12
+ class LoaderJdbcException < ::StandardError; end
13
+ class ConnectionJdbcException < ::StandardError; end
14
+
15
+ class BasicDatabase
16
+ include LogStash::Util::Loggable
17
+
18
+ def self.wrap_error(new_error_class, exception, message = nil)
19
+ error = new_error_class.new(message || exception.message)
20
+ error.set_backtrace(exception.backtrace)
21
+ error
22
+ end
23
+
24
+ def self.create(
25
+ connection_string = MEMORY_DERBY_LOCAL_CONNECTION_STRING,
26
+ driver_class = EMBEDDED_DERBY_DRIVER_CLASS,
27
+ driver_library = nil,
28
+ user = nil,
29
+ password = nil)
30
+ instance = new
31
+ instance.post_create(connection_string, driver_class, driver_library, user, password)
32
+ instance
33
+ end
34
+
35
+ def self.random_name(length = 10)
36
+ SecureRandom.hex(length)
37
+ end
38
+
39
+ attr_reader :unique_db_name
40
+
41
+ def initialize()
42
+ @options_hash = {}
43
+ post_initialize
44
+ end
45
+
46
+ def connect(err_message)
47
+ begin
48
+ @db = ::Sequel.connect(@connection_string, @options_hash)
49
+ rescue *CONNECTION_ERRORS => err
50
+ # we do not raise an error when there is a connection error, we hope that the connection works next time
51
+ logger.error(err_message, :exception => err.message, :backtrace => err.backtrace.take(8))
52
+ end
53
+ end
54
+
55
+ def disconnect(err_message)
56
+ return if @db.nil?
57
+ begin
58
+ @db.disconnect
59
+ rescue *CONNECTION_ERRORS => err
60
+ # we do not raise an error when there is a connection error, we hope that the connection works next time
61
+ logger.error(err_message, :exception => err.message, :backtrace => err.backtrace.take(8))
62
+ ensure
63
+ @db = nil
64
+ end
65
+ end
66
+
67
+ def connected?
68
+ !@db.nil?
69
+ end
70
+
71
+ def empty_record_set
72
+ []
73
+ end
74
+
75
+ def post_create(connection_string, driver_class, driver_library, user, password)
76
+ raise NotImplementedError.new("#{self.class.name} is abstract, you must subclass it and implement #post_create()")
77
+ end
78
+
79
+ private
80
+
81
+ def verify_connection(connection_string, driver_class, driver_library, user, password)
82
+ begin
83
+ require driver_library if driver_library
84
+ rescue LoadError => e
85
+ msg = "The driver library cannot be loaded. The system error was: '#{e.message}'."
86
+ raise wrap_error(ConnectionJdbcException, e, msg)
87
+ end
88
+ begin
89
+ db = nil
90
+ ::Sequel::JDBC.load_driver(driver_class)
91
+ @connection_string = connection_string
92
+ if user
93
+ @options_hash[:user] = user
94
+ end
95
+ if password
96
+ @options_hash[:password] = password.value
97
+ end
98
+ # test the connection as early as possible
99
+ db = ::Sequel.connect(@connection_string, {:test => true}.merge(@options_hash))
100
+ rescue ::Sequel::AdapterNotFound => anf
101
+ raise wrap_error(ConnectionJdbcException, anf)
102
+ rescue ::Sequel::DatabaseConnectionError => dce
103
+ raise wrap_error(ConnectionJdbcException, dce)
104
+ ensure
105
+ db.disconnect unless db.nil?
106
+ end
107
+ end
108
+
109
+ def post_initialize()
110
+ @unique_db_name = SecureRandom.hex(12)
111
+ end
112
+
113
+ def wrap_error(new_error_class, exception, message = nil)
114
+ self.class.wrap_error(new_error_class, exception, message)
115
+ end
116
+ end
117
+ end end end
@@ -0,0 +1,38 @@
1
+ require_relative "validatable"
2
+
3
+ module LogStash module Filters module Jdbc
4
+ class Column < Validatable
5
+ attr_reader :name, :datatype
6
+
7
+ private
8
+
9
+ def post_initialize
10
+ if valid?
11
+ @name = @name.to_sym
12
+ @datatype = @datatype.to_sym
13
+ end
14
+ end
15
+
16
+ def to_array
17
+ [@name.to_s, @datatype.to_s]
18
+ end
19
+
20
+ def parse_options
21
+ unless @options.is_a?(Array)
22
+ @option_errors << "The column options must be an array"
23
+ end
24
+
25
+ @name, @datatype = @options
26
+
27
+ unless @name && @name.is_a?(String)
28
+ @option_errors << "The first column option is the name and must be a string"
29
+ end
30
+
31
+ unless @datatype && @datatype.is_a?(String)
32
+ @option_errors << "The second column option is the datatype and must be a string"
33
+ end
34
+
35
+ @valid = @option_errors.empty?
36
+ end
37
+ end
38
+ end end end
@@ -0,0 +1,103 @@
1
+ require_relative "validatable"
2
+ require_relative "column"
3
+
4
+ module LogStash module Filters module Jdbc
5
+
6
+ TEMP_TABLE_PREFIX = "temp_".freeze
7
+
8
+ class DbObject < Validatable
9
+ # {name => "servers", index_columns => ["ip"], columns => [["ip", "text"], ["name", "text"], ["location", "text"]]},
10
+
11
+ attr_reader :name, :columns, :preserve_existing, :index_columns
12
+
13
+ def build(db)
14
+ return unless valid?
15
+ schema_gen = db.create_table_generator()
16
+ @columns.each {|col| schema_gen.column(col.name, col.datatype)}
17
+ schema_gen.index(@index_columns)
18
+ options = {:generator => schema_gen}
19
+ if @preserve_existing
20
+ db.create_table?(@name, options)
21
+ else
22
+ db.create_table(@name, options)
23
+ end
24
+ end
25
+
26
+ def <=>(other)
27
+ @name <=> other.name
28
+ end
29
+
30
+ def as_temp_table_opts
31
+ {"name" => "#{TEMP_TABLE_PREFIX}#{@name}", "preserve_existing" => @preserve_existing, "index_columns" => @index_columns.map(&:to_s), "columns" => @columns.map(&:to_array)}
32
+ end
33
+
34
+ def to_s
35
+ inspect
36
+ end
37
+
38
+ def inspect
39
+ "<LogStash::Filters::Jdbc::DbObject name: #{@name}, columns: #{@columns.inspect}>"
40
+ end
41
+
42
+ private
43
+
44
+ def post_initialize
45
+ if valid?
46
+ @name = @name.to_sym
47
+ end
48
+ end
49
+
50
+ def parse_options
51
+ if !@options.is_a?(Hash)
52
+ @option_errors << "DbObject options must be a Hash"
53
+ @valid = false
54
+ return
55
+ end
56
+
57
+ @name = @options["name"]
58
+ unless @name && @name.is_a?(String)
59
+ @option_errors << "DbObject options must include a 'name' string"
60
+ @name = "unnamed"
61
+ end
62
+
63
+ @preserve_existing = @options.fetch("preserve_existing", false)
64
+ @preserve_existing = true if @preserve_existing == "true"
65
+
66
+ @columns_options = @options["columns"]
67
+ @columns = []
68
+ temp_column_names = []
69
+ if @columns_options && @columns_options.is_a?(Array)
70
+ sizes = @columns_options.map{|option| option.size}.uniq
71
+ if sizes == [2]
72
+ @columns_options.each do |option|
73
+ column = Column.new(option)
74
+ if column.valid?
75
+ @columns << column
76
+ temp_column_names << column.name
77
+ else
78
+ @option_errors << column.formatted_errors
79
+ end
80
+ end
81
+ else
82
+ @option_errors << "The columns array for '#{@name}' is not uniform, it should contain arrays of two strings only"
83
+ end
84
+ else
85
+ @option_errors << "DbObject options for '#{@name}' must include a 'columns' array"
86
+ end
87
+
88
+ @index_column_options = @options["index_columns"]
89
+ @index_columns = []
90
+ if @index_column_options && @index_column_options.is_a?(Array)
91
+ @index_column_options.each do |option|
92
+ if option.is_a?(String) && temp_column_names.member?(option.to_sym)
93
+ @index_columns << option.to_sym
94
+ else
95
+ @option_errors << "The index_columns element: '#{option}' must be a column defined in the columns array"
96
+ end
97
+ end
98
+ end
99
+
100
+ @valid = @option_errors.empty?
101
+ end
102
+ end
103
+ end end end
@@ -0,0 +1,114 @@
1
+ # encoding: utf-8
2
+ require_relative "validatable"
3
+ require_relative "db_object"
4
+ require_relative "read_only_database"
5
+ require "logstash/util/loggable"
6
+
7
+ module LogStash module Filters module Jdbc
8
+ class Loader < Validatable
9
+ include LogStash::Util::Loggable
10
+
11
+ CONNECTION_ERROR_MSG = "Remote DB connection error when executing loader Jdbc query"
12
+
13
+ attr_reader :id, :table, :temp_table, :query, :max_rows
14
+ attr_reader :connection_string, :driver_library, :driver_class
15
+ attr_reader :user, :password
16
+
17
+ def build_remote_db
18
+ @remote = ReadOnlyDatabase.create(connection_string, driver_class, driver_library, user, password)
19
+ end
20
+
21
+ def fetch
22
+ @remote.connect(CONNECTION_ERROR_MSG)
23
+ row_count = @remote.count(query)
24
+ if row_count.zero?
25
+ logger.warn? && logger.warn("Query returned no results", :lookup_id => @id, :query => query)
26
+ return @remote.empty_record_set
27
+ end
28
+ if row_count > max_rows
29
+ logger.warn? && logger.warn("Query returned more than max_rows results", :lookup_id => @id, :query => query, :count => row_count, :max_rows => max_rows)
30
+ return @remote.empty_record_set
31
+ end
32
+ @remote.query(query)
33
+ ensure
34
+ @remote.disconnect(CONNECTION_ERROR_MSG)
35
+ end
36
+
37
+ def close
38
+ @remote.disconnect(CONNECTION_ERROR_MSG)
39
+ end
40
+
41
+ private
42
+
43
+ def pre_initialize(options)
44
+ @table = options["local_table"]
45
+ end
46
+
47
+ def post_initialize
48
+ if valid?
49
+ @temp_table = "#{TEMP_TABLE_PREFIX}#{@table}".to_sym
50
+ @table = @table.to_sym
51
+ end
52
+ end
53
+
54
+ def parse_options
55
+ unless @table && @table.is_a?(String)
56
+ @option_errors << "The options must include a 'local_table' string"
57
+ end
58
+
59
+ @id = @options.fetch("id", @table)
60
+
61
+ @query = @options["query"]
62
+ unless @query && @query.is_a?(String)
63
+ @option_errors << "The options for '#{@table}' must include a 'query' string"
64
+ end
65
+
66
+ @max_rows = @options["max_rows"]
67
+ if @max_rows
68
+ if !@max_rows.respond_to?(:to_i)
69
+ @option_errors << "The 'max_rows' option for '#{@table}' must be an integer"
70
+ else
71
+ @max_rows = @max_rows.to_i
72
+ end
73
+ else
74
+ @max_rows = 1_000_000
75
+ end
76
+
77
+ @driver_library = @options["jdbc_driver_library"]
78
+ if @driver_library
79
+ if !@driver_library.is_a?(String)
80
+ @option_errors << "The 'jdbc_driver_library' option for '#{@table}' must be a string"
81
+ end
82
+ if !::File.exists?(@driver_library)
83
+ @option_errors << "The 'jdbc_driver_library' option for '#{@table}' must be a file that can be opened: #{driver_library}"
84
+ end
85
+ end
86
+
87
+ @driver_class = @options["jdbc_driver_class"]
88
+ if @driver_class && !@driver_class.is_a?(String)
89
+ @option_errors << "The 'jdbc_driver_class' option for '#{@table}' must be a string"
90
+ end
91
+
92
+ @connection_string = @options["jdbc_connection_string"]
93
+ if @connection_string && !@connection_string.is_a?(String)
94
+ @option_errors << "The 'jdbc_connection_string' option for '#{@table}' must be a string"
95
+ end
96
+
97
+ @user = @options["jdbc_user"]
98
+ if @user && !@user.is_a?(String)
99
+ @option_errors << "The 'jdbc_user' option for '#{@table}' must be a string"
100
+ end
101
+
102
+ @password = @options["jdbc_password"]
103
+ case @password
104
+ when String
105
+ @password = LogStash::Util::Password.new(@password)
106
+ when LogStash::Util::Password, nil
107
+ # this is OK
108
+ else
109
+ @option_errors << "The 'jdbc_password' option for '#{@table}' must be a string"
110
+ end
111
+ @valid = @option_errors.empty?
112
+ end
113
+ end
114
+ end end end