glass_octopus 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.
- checksums.yaml +7 -0
- data/.env +3 -0
- data/.gitignore +9 -0
- data/.yardopts +1 -0
- data/Gemfile +7 -0
- data/Guardfile +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +93 -0
- data/Rakefile +77 -0
- data/bin/guard +16 -0
- data/bin/rake +16 -0
- data/docker-compose.yml +25 -0
- data/example/advanced.rb +35 -0
- data/example/basic.rb +24 -0
- data/example/ruby_kafka.rb +24 -0
- data/glass_octopus.gemspec +34 -0
- data/lib/glass-octopus.rb +1 -0
- data/lib/glass_octopus.rb +50 -0
- data/lib/glass_octopus/application.rb +37 -0
- data/lib/glass_octopus/bounded_executor.rb +53 -0
- data/lib/glass_octopus/builder.rb +115 -0
- data/lib/glass_octopus/configuration.rb +62 -0
- data/lib/glass_octopus/connection/options_invalid.rb +10 -0
- data/lib/glass_octopus/connection/poseidon_adapter.rb +113 -0
- data/lib/glass_octopus/connection/ruby_kafka_adapter.rb +116 -0
- data/lib/glass_octopus/consumer.rb +39 -0
- data/lib/glass_octopus/context.rb +30 -0
- data/lib/glass_octopus/message.rb +4 -0
- data/lib/glass_octopus/middleware.rb +10 -0
- data/lib/glass_octopus/middleware/active_record.rb +21 -0
- data/lib/glass_octopus/middleware/common_logger.rb +31 -0
- data/lib/glass_octopus/middleware/json_parser.rb +42 -0
- data/lib/glass_octopus/middleware/mongoid.rb +19 -0
- data/lib/glass_octopus/middleware/new_relic.rb +32 -0
- data/lib/glass_octopus/middleware/sentry.rb +33 -0
- data/lib/glass_octopus/runner.rb +64 -0
- data/lib/glass_octopus/unit_of_work.rb +24 -0
- data/lib/glass_octopus/version.rb +3 -0
- metadata +187 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
require "glass_octopus/unit_of_work"
|
2
|
+
|
3
|
+
module GlassOctopus
|
4
|
+
# @api private
|
5
|
+
class Consumer
|
6
|
+
attr_reader :connection, :processor, :executor, :logger
|
7
|
+
|
8
|
+
def initialize(connection, processor, executor, logger)
|
9
|
+
@connection = connection
|
10
|
+
@processor = processor
|
11
|
+
@executor = executor
|
12
|
+
@logger = logger
|
13
|
+
end
|
14
|
+
|
15
|
+
def run
|
16
|
+
connection.fetch_message do |message|
|
17
|
+
work = UnitOfWork.new(message, processor, logger)
|
18
|
+
submit(work)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def shutdown(timeout=10)
|
23
|
+
connection.close
|
24
|
+
executor.shutdown
|
25
|
+
logger.info("Waiting for workers to terminate...")
|
26
|
+
executor.wait_for_termination(timeout)
|
27
|
+
end
|
28
|
+
|
29
|
+
def submit(work)
|
30
|
+
if executor.post(work) { |work| work.perform }
|
31
|
+
logger.debug { "Accepted message: #{work.message.to_h}" }
|
32
|
+
else
|
33
|
+
logger.warn { "Rejected message: #{work.message.to_h}" }
|
34
|
+
end
|
35
|
+
rescue Concurrent::RejectedExecutionError
|
36
|
+
logger.warn { "Rejected message: #{work.message.to_h}" }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module GlassOctopus
|
4
|
+
# Message context. Wraps a Kafka message and adds some convenience methods.
|
5
|
+
#
|
6
|
+
# @!attribute [rw] logger
|
7
|
+
# A logger object. Defaults to the application logger.
|
8
|
+
# @!attribute [r] message
|
9
|
+
# A message read from Kafka.
|
10
|
+
# @return [Message]
|
11
|
+
class Context
|
12
|
+
extend Forwardable
|
13
|
+
attr_reader :message
|
14
|
+
attr_accessor :logger
|
15
|
+
|
16
|
+
# @!method [](key)
|
17
|
+
# Retrieves the +value+ object corresponding to the +key+ object.
|
18
|
+
# @param key key to retrieve
|
19
|
+
# @!method []=(key, value)
|
20
|
+
# Associates +value+ with +key+.
|
21
|
+
def_delegators :@data, :[], :[]=
|
22
|
+
|
23
|
+
# @api private
|
24
|
+
def initialize(message, logger)
|
25
|
+
@data = {}
|
26
|
+
@message = message
|
27
|
+
@logger = logger
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module GlassOctopus
|
2
|
+
module Middleware
|
3
|
+
autoload :ActiveRecord, "glass_octopus/middleware/active_record"
|
4
|
+
autoload :CommonLogger, "glass_octopus/middleware/common_logger"
|
5
|
+
autoload :JsonParser, "glass_octopus/middleware/json_parser"
|
6
|
+
autoload :Mongoid, "glass_octopus/middleware/mongoid"
|
7
|
+
autoload :NewRelic, "glass_octopus/middleware/new_relic"
|
8
|
+
autoload :Sentry, "glass_octopus/middleware/sentry"
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
begin
|
2
|
+
require "active_record"
|
3
|
+
rescue LoadError
|
4
|
+
raise "Can't find 'activerecord' gem. Please add it to your Gemfile or install it."
|
5
|
+
end
|
6
|
+
|
7
|
+
module GlassOctopus
|
8
|
+
module Middleware
|
9
|
+
class ActiveRecord
|
10
|
+
def initialize(app)
|
11
|
+
@app = app
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(ctx)
|
15
|
+
@app.call(ctx)
|
16
|
+
ensure
|
17
|
+
::ActiveRecord::Base.clear_active_connections!
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "benchmark"
|
2
|
+
|
3
|
+
module GlassOctopus
|
4
|
+
module Middleware
|
5
|
+
class CommonLogger
|
6
|
+
FORMAT = "Processed message. topic=%s, partition=%d, key=%s, runtime=%fms".freeze
|
7
|
+
|
8
|
+
def initialize(app, logger=nil, log_level=:info)
|
9
|
+
@app = app
|
10
|
+
@logger = logger
|
11
|
+
@log_level = log_level
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(ctx)
|
15
|
+
log(ctx) { @app.call(ctx) }
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def log(ctx)
|
21
|
+
logger = @logger || ctx.logger
|
22
|
+
|
23
|
+
runtime = Benchmark.realtime { yield }
|
24
|
+
runtime *= 1000 # Convert to milliseconds
|
25
|
+
|
26
|
+
logger.send(@log_level, format(FORMAT,
|
27
|
+
ctx.message.topic, ctx.message.partition, ctx.message.key, runtime))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "json"
|
2
|
+
require "delegate"
|
3
|
+
|
4
|
+
module GlassOctopus
|
5
|
+
module Middleware
|
6
|
+
class JsonParser
|
7
|
+
def initialize(app, options={})
|
8
|
+
@app = app
|
9
|
+
@klass = options.delete(:class)
|
10
|
+
@encoding = options.delete(:encoding)
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(ctx)
|
15
|
+
message = parse(ensure_encoding(ctx.message.value))
|
16
|
+
ctx = ContextWithJsonParsedMessage.new(ctx, message)
|
17
|
+
@app.call(ctx)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def parse(str)
|
23
|
+
hash = JSON.parse(str, { :create_additions => false }.merge(@options))
|
24
|
+
@klass ? @klass.new(hash) : hash
|
25
|
+
end
|
26
|
+
|
27
|
+
def ensure_encoding(value)
|
28
|
+
return value unless @encoding
|
29
|
+
value.encode(@encoding, invalid: :replace, undef: :replace, replace: '')
|
30
|
+
end
|
31
|
+
|
32
|
+
class ContextWithJsonParsedMessage < SimpleDelegator
|
33
|
+
attr_reader :params
|
34
|
+
|
35
|
+
def initialize(wrapped_ctx, params)
|
36
|
+
super(wrapped_ctx)
|
37
|
+
@params = params
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
begin
|
2
|
+
require "mongoid"
|
3
|
+
rescue LoadError
|
4
|
+
raise "Can't find 'mongoid' gem. Please add it to your Gemfile or install it."
|
5
|
+
end
|
6
|
+
|
7
|
+
module GlassOctopus
|
8
|
+
module Middleware
|
9
|
+
class Mongoid
|
10
|
+
def initialize(app)
|
11
|
+
@app = app
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(ctx)
|
15
|
+
Mongoid.unit_of_work { @app.call(ctx) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
begin
|
2
|
+
require 'new_relic/agent'
|
3
|
+
rescue LoadError
|
4
|
+
raise "Can't find 'newrelic_rpm' gem. Please add it to your Gemfile or install it."
|
5
|
+
end
|
6
|
+
|
7
|
+
module GlassOctopus
|
8
|
+
module Middleware
|
9
|
+
class NewRelic
|
10
|
+
include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
|
11
|
+
|
12
|
+
DEFAULT_OPTIONS = {
|
13
|
+
:name => "call",
|
14
|
+
:category => "OtherTransaction/GlassOctopus",
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
def initialize(app, klass, options={})
|
18
|
+
@app = app
|
19
|
+
@options = DEFAULT_OPTIONS.merge(class_name: klass.name).merge(options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(ctx)
|
23
|
+
perform_action_with_newrelic_trace(@options) do
|
24
|
+
@app.call(ctx)
|
25
|
+
end
|
26
|
+
rescue Exception => ex
|
27
|
+
::NewRelic::Agent.notice_error(ex, :custom_params => { :message => ctx.message.to_h })
|
28
|
+
raise
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
begin
|
2
|
+
require "raven"
|
3
|
+
rescue LoadError
|
4
|
+
raise "Can't find 'sentry-raven' gem. Please add it to your Gemfile or install it."
|
5
|
+
end
|
6
|
+
|
7
|
+
module GlassOctopus
|
8
|
+
module Middleware
|
9
|
+
class Sentry
|
10
|
+
def initialize(app)
|
11
|
+
@app = app
|
12
|
+
end
|
13
|
+
|
14
|
+
# Based on Raven::Rack integration
|
15
|
+
def call(ctx)
|
16
|
+
# clear context at the beginning of the processing to ensure a clean slate
|
17
|
+
Raven::Context.clear!
|
18
|
+
started_at = Time.now
|
19
|
+
|
20
|
+
begin
|
21
|
+
@app.call(ctx)
|
22
|
+
rescue Raven::Error
|
23
|
+
raise # Don't capture Raven errors
|
24
|
+
rescue Exception => ex
|
25
|
+
Raven.logger.debug("Collecting %p: %s" % [ ex.class, ex.message ])
|
26
|
+
Raven.capture_exception(ex, :extra => { :message => ctx.message.to_h },
|
27
|
+
:time_spent => Time.now - started_at)
|
28
|
+
raise
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "singleton"
|
2
|
+
|
3
|
+
module GlassOctopus
|
4
|
+
# A very simple runner that takes an app and handles graceful shutdown for
|
5
|
+
# SIGINT and SIGTERM.
|
6
|
+
#
|
7
|
+
# The {#run} method alters the state of the process globally and irreversibly
|
8
|
+
# by registering signal handlers. The Runner class is a singleton and can only
|
9
|
+
# be started once.
|
10
|
+
#
|
11
|
+
# Runner runs the application in the main thread. When a signal hits the
|
12
|
+
# process the control is transferred to the signal handler which will raise an
|
13
|
+
# Interrupt exception which kicks off the graceful shutdown. During shutdown
|
14
|
+
# no more messages are read because everything happens in the main thread.
|
15
|
+
#
|
16
|
+
# Runner does not provide any meaningful error handling. Errors are logged and
|
17
|
+
# then the process exits with status code 1.
|
18
|
+
class Runner
|
19
|
+
include Singleton
|
20
|
+
|
21
|
+
# Shortcut to {#run}.
|
22
|
+
# @return [void]
|
23
|
+
def self.run(app)
|
24
|
+
instance.run(app)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Starts the application and blocks until the process gets a SIGTERM or
|
28
|
+
# SIGINT signal.
|
29
|
+
#
|
30
|
+
# @param app the application to run
|
31
|
+
# @return [void]
|
32
|
+
def run(app)
|
33
|
+
return if running?
|
34
|
+
running!
|
35
|
+
|
36
|
+
# To support JRuby Ctrl+C as MRI does.
|
37
|
+
# See: https://github.com/jruby/jruby/issues/1639
|
38
|
+
trap(:INT) { Thread.main.raise Interrupt }
|
39
|
+
trap(:TERM) { Thread.main.raise Interrupt }
|
40
|
+
|
41
|
+
app.run
|
42
|
+
rescue Interrupt
|
43
|
+
app.logger.info("Shutting down...")
|
44
|
+
app.shutdown
|
45
|
+
app.logger.info("Bye.")
|
46
|
+
rescue => ex
|
47
|
+
app.logger.fatal("#{ex.class} - #{ex.message}:")
|
48
|
+
app.logger.fatal(ex.backtrace.join("\n")) if ex.backtrace
|
49
|
+
exit(1)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Determines whether the application is running or not.
|
53
|
+
# @return [Boolean]
|
54
|
+
def running?
|
55
|
+
@running
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def running!
|
61
|
+
@running = true
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "glass_octopus/context"
|
2
|
+
|
3
|
+
module GlassOctopus
|
4
|
+
# Unit of work. Builds a context for a message and runs it through the
|
5
|
+
# middleware stack. It catches and logs all application level exceptions.
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class UnitOfWork
|
9
|
+
attr_reader :message, :processor, :logger
|
10
|
+
|
11
|
+
def initialize(message, processor, logger)
|
12
|
+
@message = message
|
13
|
+
@processor = processor
|
14
|
+
@logger = logger
|
15
|
+
end
|
16
|
+
|
17
|
+
def perform
|
18
|
+
processor.call(Context.new(message, logger))
|
19
|
+
rescue => ex
|
20
|
+
logger.logger.error("#{ex.class} - #{ex.message}:")
|
21
|
+
logger.logger.error(ex.backtrace.join("\n")) if ex.backtrace
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: glass_octopus
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tamás Michelberger
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-11-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: concurrent-ruby
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.0.1
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.0'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.0.1
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rake
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '12.0'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '12.0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: minitest
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '5.0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '5.0'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: minitest-color
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: guard
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '2.14'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '2.14'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: guard-minitest
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '2.4'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '2.4'
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: terminal-notifier-guard
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '1.7'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - "~>"
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '1.7'
|
117
|
+
description: |
|
118
|
+
GlassOctopus provides a minimal, modular and adaptable interface for developing
|
119
|
+
Kafka consumers in Ruby. In its philosophy it is very close to Rack.
|
120
|
+
email:
|
121
|
+
- tomi@secretsaucepartners.com
|
122
|
+
executables: []
|
123
|
+
extensions: []
|
124
|
+
extra_rdoc_files: []
|
125
|
+
files:
|
126
|
+
- ".env"
|
127
|
+
- ".gitignore"
|
128
|
+
- ".yardopts"
|
129
|
+
- Gemfile
|
130
|
+
- Guardfile
|
131
|
+
- LICENSE.txt
|
132
|
+
- README.md
|
133
|
+
- Rakefile
|
134
|
+
- bin/guard
|
135
|
+
- bin/rake
|
136
|
+
- docker-compose.yml
|
137
|
+
- example/advanced.rb
|
138
|
+
- example/basic.rb
|
139
|
+
- example/ruby_kafka.rb
|
140
|
+
- glass_octopus.gemspec
|
141
|
+
- lib/glass-octopus.rb
|
142
|
+
- lib/glass_octopus.rb
|
143
|
+
- lib/glass_octopus/application.rb
|
144
|
+
- lib/glass_octopus/bounded_executor.rb
|
145
|
+
- lib/glass_octopus/builder.rb
|
146
|
+
- lib/glass_octopus/configuration.rb
|
147
|
+
- lib/glass_octopus/connection/options_invalid.rb
|
148
|
+
- lib/glass_octopus/connection/poseidon_adapter.rb
|
149
|
+
- lib/glass_octopus/connection/ruby_kafka_adapter.rb
|
150
|
+
- lib/glass_octopus/consumer.rb
|
151
|
+
- lib/glass_octopus/context.rb
|
152
|
+
- lib/glass_octopus/message.rb
|
153
|
+
- lib/glass_octopus/middleware.rb
|
154
|
+
- lib/glass_octopus/middleware/active_record.rb
|
155
|
+
- lib/glass_octopus/middleware/common_logger.rb
|
156
|
+
- lib/glass_octopus/middleware/json_parser.rb
|
157
|
+
- lib/glass_octopus/middleware/mongoid.rb
|
158
|
+
- lib/glass_octopus/middleware/new_relic.rb
|
159
|
+
- lib/glass_octopus/middleware/sentry.rb
|
160
|
+
- lib/glass_octopus/runner.rb
|
161
|
+
- lib/glass_octopus/unit_of_work.rb
|
162
|
+
- lib/glass_octopus/version.rb
|
163
|
+
homepage: https://github.com/sspinc/glass-octopus
|
164
|
+
licenses:
|
165
|
+
- MIT
|
166
|
+
metadata: {}
|
167
|
+
post_install_message:
|
168
|
+
rdoc_options: []
|
169
|
+
require_paths:
|
170
|
+
- lib
|
171
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
172
|
+
requirements:
|
173
|
+
- - ">="
|
174
|
+
- !ruby/object:Gem::Version
|
175
|
+
version: '0'
|
176
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
requirements: []
|
182
|
+
rubyforge_project:
|
183
|
+
rubygems_version: 2.7.2
|
184
|
+
signing_key:
|
185
|
+
specification_version: 4
|
186
|
+
summary: A Kafka consumer framework. Like Rack but for Kafka.
|
187
|
+
test_files: []
|