glass_octopus 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7b1a88b4eb29013db080c1e6a08c9b6f5af6fb0b8e844805074e1101554bbe7a
4
+ data.tar.gz: 24d003c5d814ccec622277a87385830fbc1825d327063d2e9a5f7714a58b84a4
5
+ SHA512:
6
+ metadata.gz: d317a58e73e7a313ddc4cca6b2e998a1554e214c5616990f1e4e582318dfad461d102053ffce34a47cf2d90653e23de1b2c2cbdc382fff4835dfbc4078339a80
7
+ data.tar.gz: de289957eff59996e7055f789c34867146ecf07617dadc80b13b4dc25a7f7a4afaedd7e4bbfe84b4ef61728d4c668401a2c3d114cf3b92a0f0f6f1823a1ca949
data/.env ADDED
@@ -0,0 +1,3 @@
1
+ KAFKA_0_8_PORT=9092
2
+ KAFKA_0_10_PORT=9093
3
+ ZOOKEEPER_EXTERNAL_PORT=2181
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1 @@
1
+ --hide-api private
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "poseidon", github: "bpot/poseidon"
6
+ gem "poseidon_cluster", github: "bsm/poseidon_cluster"
7
+ gem "ruby-kafka"
@@ -0,0 +1,7 @@
1
+ notification :terminal_notifier
2
+
3
+ guard :minitest, all_on_start: false do
4
+ watch(%r{^test/(.*)\/?(.*)_test\.rb$})
5
+ watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}#{m[2]}_test.rb" }
6
+ watch(%r{^test/test_helper\.rb$}) { 'test' }
7
+ end
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Secret Sauce Partners, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,93 @@
1
+ # Glass Octopus
2
+
3
+ A Kafka consumer framework. Like Rack but for Kafka.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'glass_octopus'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install glass_octopus
20
+
21
+ This gem requires Ruby 2.1 or higher.
22
+
23
+ ## Getting started
24
+
25
+ Pick your adapter:
26
+
27
+ * For Kafka 0.8.x use poseidon and poseidon-cluster
28
+
29
+ # in your Gemfile
30
+ gem "glass_octopus"
31
+ gem "poseidon", github: "bpot/poseidon"
32
+ gem "poseidon_cluster", github: "bsm/poseidon_cluster"
33
+
34
+ * For Kafka 0.9+ use ruby-kafka
35
+
36
+ # in your Gemfile
37
+ gem "glass_octopus"
38
+ gem "ruby-kafka"
39
+
40
+
41
+ ```ruby
42
+ # in app.rb
43
+ require "bundler/setup"
44
+ require "glass_octopus"
45
+
46
+ app = GlassOctopus.build do
47
+ use GlassOctopus::Middleware::CommonLogger
48
+
49
+ run Proc.new { |ctx|
50
+ puts "Got message: #{ctx.message.key} => #{ctx.message.value}"
51
+ }
52
+ end
53
+
54
+ GlassOctopus.run(app) do |config|
55
+ config.adapter :ruby_kafka do |kafka|
56
+ kafka.broker_list = %[localhost:9092]
57
+ kafka.topic = "mytopic"
58
+ kafka.group = "mygroup"
59
+ kafka.client = { logger: config.logger }
60
+ end
61
+ end
62
+ ```
63
+
64
+ Run it with `bundle exec ruby app.rb`
65
+
66
+ For more examples look into the [examples](examples) directory.
67
+
68
+ For the API documentation please see the [documentation site][rubydoc]
69
+
70
+ ## Development
71
+
72
+ Install docker and docker-compose to run Kafka and zookeeper for tests.
73
+
74
+ 1. Set the `ADVERTISED_HOST` environment variable
75
+ 2. Run `rake docker:up`
76
+ 3. Now you can run the tests.
77
+
78
+ Run all tests including integration tests:
79
+
80
+ $ rake test:all
81
+
82
+ Running tests without integration tests:
83
+
84
+ $ rake # or rake test
85
+
86
+ When you are done run `rake docker:down` to clean up docker containers.
87
+
88
+ ## License
89
+
90
+ The gem is available as open source under the terms of the
91
+ [MIT License](http://opensource.org/licenses/MIT).
92
+
93
+ [rubydoc]: http://www.rubydoc.info/github/sspinc/glass-octopus
@@ -0,0 +1,77 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ task :default => :test
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << "test"
8
+ t.pattern = "test/**/*_test.rb"
9
+ t.verbose = ENV.key?("VERBOSE")
10
+ t.warning = false
11
+ end
12
+
13
+ namespace :test do
14
+ desc "Run all tests including integration tests"
15
+ task :all do
16
+ ENV["TEST_KAFKA_INTEGRATION"] = "yes"
17
+ Rake::Task[:test].invoke
18
+ end
19
+ end
20
+
21
+ namespace :docker do
22
+ require "socket"
23
+
24
+ desc "Start docker containers"
25
+ task :up do
26
+ start
27
+ wait(9093)
28
+ docker_compose("run --rm kafka_0_10 kafka-topics.sh --zookeeper zookeeper --create --topic test_topic --replication-factor 1 --partitions 1")
29
+ wait(9092)
30
+ docker_compose("run --rm kafka_0_8 bash -c '$KAFKA_HOME/bin/kafka-topics.sh --zookeeper kafka_0_8 --create --topic test_topic --replication-factor 1 --partitions 1'")
31
+ end
32
+
33
+ desc "Stop and remove docker containers"
34
+ task :down do
35
+ docker_compose("down")
36
+ end
37
+
38
+ desc "Reset docker containers"
39
+ task :reset => [:down, :up]
40
+
41
+ def start
42
+ docker_compose("up -d")
43
+ end
44
+
45
+ def stop
46
+ docker_compose("down")
47
+ end
48
+
49
+ def docker_compose(args)
50
+ env = {
51
+ "ADVERTISED_HOST" => docker_machine_ip,
52
+ "KAFKA_0_8_EXTERNAL_PORT" => "9092",
53
+ "KAFKA_0_10_EXTERNAL_PORT" => "9093",
54
+ "ZOOKEEPER_EXTERNAL_PORT" => "2181",
55
+ }
56
+ system(env, "docker-compose #{args}")
57
+ end
58
+
59
+ def docker_machine_ip
60
+ return @docker_ip if defined? @docker_ip
61
+
62
+ if ENV.key?("ADVERTISED_HOST")
63
+ @docker_ip = ENV["ADVERTISED_HOST"]
64
+ else
65
+ active = %x{docker-machine active}.chomp
66
+ @docker_ip = %x{docker-machine ip #{active}}.chomp
67
+ end
68
+ end
69
+
70
+ def wait(port)
71
+ Socket.tcp(docker_machine_ip, port, connect_timeout: 5).close
72
+ rescue Errno::ECONNREFUSED
73
+ puts "waiting for #{docker_machine_ip}:#{port}"
74
+ sleep 1
75
+ retry
76
+ end
77
+ end
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'guard' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require "pathname"
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("guard", "guard")
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rake' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require "pathname"
10
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require "rubygems"
14
+ require "bundler/setup"
15
+
16
+ load Gem.bin_path("rake", "rake")
@@ -0,0 +1,25 @@
1
+ version: '3'
2
+ services:
3
+ kafka_0_8:
4
+ image: sspinc/kafka
5
+ environment:
6
+ - ADVERTISED_HOST=${ADVERTISED_HOST}
7
+ - ADVERTISED_PORT=${KAFKA_0_8_PORT}
8
+ ports:
9
+ - ${KAFKA_0_8_PORT}:${KAFKA_0_8_PORT}
10
+ - ${ZOOKEEPER_EXTERNAL_PORT}:2181
11
+ kafka_0_10:
12
+ image: ches/kafka:0.10.2.1
13
+ environment:
14
+ - KAFKA_ADVERTISED_HOST_NAME=${ADVERTISED_HOST}
15
+ - KAFKA_ADVERTISED_PORT=${KAFKA_0_10_PORT}
16
+ - KAFKA_PORT=${KAFKA_0_10_PORT}
17
+ - ZOOKEEPER_IP=zookeeper
18
+ ports:
19
+ - ${KAFKA_0_10_PORT}:${KAFKA_0_10_PORT}
20
+ links:
21
+ - zookeeper
22
+ zookeeper:
23
+ image: zookeeper:3.4
24
+ expose:
25
+ - "2181"
@@ -0,0 +1,35 @@
1
+ require "bundler/setup"
2
+ require "glass_octopus"
3
+
4
+ app = GlassOctopus.build do
5
+ use GlassOctopus::Middleware::CommonLogger
6
+
7
+ run Proc.new { |ctx|
8
+ puts "Got message: #{ctx.message.key} => #{ctx.message.value}"
9
+ }
10
+ end
11
+
12
+ def array_from_env(key, default:)
13
+ return default unless ENV.key?(key)
14
+ ENV.fetch(key).split(",").map(&:strip)
15
+ end
16
+
17
+ GlassOctopus.run(app) do |config|
18
+ config.logger = Logger.new("glass_octopus.log")
19
+
20
+ config.adapter :poseidon do |kafka_config|
21
+ kafka_config.broker_list = array_from_env("KAFKA_BROKER_LIST", default: %w[localhost:9092])
22
+ kafka_config.zookeeper_list = array_from_env("ZOOKEEPER_LIST", default: %w[localhost:2181])
23
+ kafka_config.topic = ENV.fetch("KAFKA_TOPIC", "mytopic")
24
+ kafka_config.group = ENV.fetch("KAFKA_GROUP", "mygroup")
25
+ kafka_config.logger = config.logger
26
+ end
27
+
28
+ config.executor = Concurrent::ThreadPoolExecutor.new(
29
+ max_threads: 25,
30
+ min_threads: 7
31
+ )
32
+
33
+ config.shutdown_timeout = 30
34
+ end
35
+
@@ -0,0 +1,24 @@
1
+ require "bundler/setup"
2
+ require "glass_octopus"
3
+
4
+ app = GlassOctopus.build do
5
+ use GlassOctopus::Middleware::CommonLogger
6
+
7
+ run Proc.new { |ctx|
8
+ puts "Got message: #{ctx.message.key} => #{ctx.message.value}"
9
+ }
10
+ end
11
+
12
+ def array_from_env(key, default:)
13
+ return default unless ENV.key?(key)
14
+ ENV.fetch(key).split(",").map(&:strip)
15
+ end
16
+
17
+ GlassOctopus.run(app) do |config|
18
+ config.adapter :poseidon do |kafka_config|
19
+ kafka_config.broker_list = array_from_env("KAFKA_BROKER_LIST", default: %w[localhost:9092])
20
+ kafka_config.zookeeper_list = array_from_env("ZOOKEEPER_LIST", default: %w[localhost:2181])
21
+ kafka_config.topic = ENV.fetch("KAFKA_TOPIC", "mytopic")
22
+ kafka_config.group = ENV.fetch("KAFKA_GROUP", "mygroup")
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ require "bundler/setup"
2
+ require "glass_octopus"
3
+
4
+ app = GlassOctopus.build do
5
+ use GlassOctopus::Middleware::CommonLogger
6
+
7
+ run Proc.new { |ctx|
8
+ puts "Got message: #{ctx.message.key} => #{ctx.message.value}"
9
+ }
10
+ end
11
+
12
+ def array_from_env(key, default:)
13
+ return default unless ENV.key?(key)
14
+ ENV.fetch(key).split(",").map(&:strip)
15
+ end
16
+
17
+ GlassOctopus.run(app) do |config|
18
+ config.adapter :ruby_kafka do |kafka|
19
+ kafka.broker_list = array_from_env("KAFKA_BROKER_LIST", default: %w[localhost:9092])
20
+ kafka.topic = ENV.fetch("KAFKA_TOPIC", "mytopic")
21
+ kafka.group = ENV.fetch("KAFKA_GROUP", "mygroup")
22
+ kafka.client = { logger: config.logger }
23
+ end
24
+ end
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'glass_octopus/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "glass_octopus"
8
+ spec.version = GlassOctopus::VERSION
9
+ spec.authors = ["Tamás Michelberger"]
10
+ spec.email = ["tomi@secretsaucepartners.com"]
11
+
12
+ spec.summary = %q{A Kafka consumer framework. Like Rack but for Kafka.}
13
+ spec.homepage = "https://github.com/sspinc/glass-octopus"
14
+ spec.license = "MIT"
15
+
16
+ spec.description = <<-EOF
17
+ GlassOctopus provides a minimal, modular and adaptable interface for developing
18
+ Kafka consumers in Ruby. In its philosophy it is very close to Rack.
19
+ EOF
20
+
21
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_runtime_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.1"
27
+
28
+ spec.add_development_dependency "rake", "~> 12.0"
29
+ spec.add_development_dependency "minitest", "~> 5.0"
30
+ spec.add_development_dependency "minitest-color", "~> 0"
31
+ spec.add_development_dependency "guard", "~> 2.14"
32
+ spec.add_development_dependency "guard-minitest", "~> 2.4"
33
+ spec.add_development_dependency "terminal-notifier-guard", "~> 1.7"
34
+ end
@@ -0,0 +1 @@
1
+ require "glass_octopus"
@@ -0,0 +1,50 @@
1
+ require "glass_octopus/version"
2
+ require "glass_octopus/middleware"
3
+ require "glass_octopus/runner"
4
+ require "glass_octopus/application"
5
+ require "glass_octopus/builder"
6
+
7
+ module GlassOctopus
8
+ # Run an application. The application can be anything that responds to
9
+ # +#call+. It is invoked with with a context that has the message and other
10
+ # goodies.
11
+ #
12
+ # @param app [#call] application to process messages
13
+ # @param runner [#run] a runner that takes care of running and shutting down
14
+ # the application. See {Runner} for more details.
15
+ # @yield [config] configure your application in this block, this is called
16
+ # before connecting to Kafka
17
+ # @yieldparam config [Configuration] the configuration object
18
+ # @raise [ArgumentError] when no block for configuration is passed
19
+ def self.run(app, runner: Runner, &block)
20
+ raise ArgumentError, "A block must be given to set up the #{name}." unless block_given?
21
+ go_app = Application.new(app, &block)
22
+ runner.run(go_app)
23
+ end
24
+
25
+ # Build a middleware stack.
26
+ # Basically a shortcut to +Builder.new { }.to_app+
27
+ #
28
+ # @example
29
+ #
30
+ # require "glass_octopus"
31
+ #
32
+ # app = GlassOctopus.build do
33
+ # use GlassOctopus::Middleware::CommonLogger
34
+ #
35
+ # run Proc.new { |context|
36
+ # puts "Hello, #{context.message.value}"
37
+ # }
38
+ # end
39
+ #
40
+ # GlassOctopus.run(app) do |config|
41
+ # # set config here
42
+ # end
43
+ #
44
+ # @see Builder
45
+ # @yield use the builder DSL to build your middleware stack
46
+ # @return [#call] an application that can be fed into the {.run}
47
+ def self.build(&block)
48
+ Builder.new(&block).to_app
49
+ end
50
+ end