logstash-filter-jdbc_static 1.0.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.
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