slnky 0.8.3 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-gemset +1 -0
  3. data/.ruby-version +1 -0
  4. data/bin/slnky +1 -0
  5. data/lib/slnky/cli/command.rb +78 -0
  6. data/lib/slnky/cli/generate.rb +3 -1
  7. data/lib/slnky/cli/service.rb +30 -0
  8. data/lib/slnky/cli.rb +4 -1
  9. data/lib/slnky/command/request.rb +7 -0
  10. data/lib/slnky/command/response.rb +43 -0
  11. data/lib/slnky/command.rb +100 -0
  12. data/lib/slnky/config.rb +69 -0
  13. data/lib/slnky/data.rb +14 -3
  14. data/lib/slnky/generator.rb +44 -18
  15. data/lib/slnky/log.rb +117 -0
  16. data/lib/slnky/service/subscriber.rb +57 -0
  17. data/lib/slnky/service/timer.rb +45 -0
  18. data/lib/slnky/service.rb +58 -96
  19. data/lib/slnky/system.rb +19 -0
  20. data/lib/slnky/template/service/.ruby-version +1 -0
  21. data/lib/slnky/template/service/config/deploy.rb.erb +3 -6
  22. data/lib/slnky/template/service/lib/slnky/NAME/client.rb.erb +11 -0
  23. data/lib/slnky/template/service/lib/slnky/NAME/command.rb.erb +15 -0
  24. data/lib/slnky/template/service/lib/slnky/NAME/mock.rb.erb +11 -0
  25. data/lib/slnky/template/service/lib/slnky/{service/NAME.rb.erb → NAME/service.rb.erb} +7 -4
  26. data/lib/slnky/template/service/lib/slnky/NAME.rb.erb +6 -0
  27. data/lib/slnky/template/service/service-slnky-NAME.erb +2 -2
  28. data/lib/slnky/template/service/spec/slnky/NAME/command_spec.rb.erb +17 -0
  29. data/lib/slnky/template/service/spec/slnky/NAME/service_spec.rb.erb +14 -0
  30. data/lib/slnky/template/service/spec/spec_helper.rb.erb +15 -4
  31. data/lib/slnky/template/service/test/commands/echo.json.erb +11 -0
  32. data/lib/slnky/template/service/test/config.yaml.erb +1 -1
  33. data/lib/slnky/transport.rb +98 -0
  34. data/lib/slnky/version.rb +1 -1
  35. data/lib/slnky.rb +15 -14
  36. data/slnky.gemspec +2 -0
  37. metadata +52 -8
  38. data/lib/slnky/service/exchanges.rb +0 -33
  39. data/lib/slnky/service/periodics.rb +0 -19
  40. data/lib/slnky/service/queues.rb +0 -27
  41. data/lib/slnky/service/subscriptions.rb +0 -27
  42. data/lib/slnky/template/service/spec/slnky/service/NAME_spec.rb.erb +0 -10
@@ -0,0 +1,57 @@
1
+ module Slnky
2
+ module Service
3
+ class << self
4
+ def subscriber
5
+ Slnky::Service::Subscriber.instance
6
+ end
7
+ end
8
+
9
+ class Subscriber
10
+ class << self
11
+ def instance
12
+ @instance ||= self.new
13
+ end
14
+ end
15
+
16
+ def initialize
17
+ @subscriptions = []
18
+ end
19
+
20
+ def handle(name, data)
21
+
22
+ end
23
+
24
+ def add(name, method)
25
+ @subscriptions << Slnky::Service::Subscription.new(name, method)
26
+ end
27
+
28
+ def list
29
+ @subscriptions
30
+ end
31
+
32
+ def each
33
+ @subscriptions.each do |sub|
34
+ yield sub.name, sub.method
35
+ end
36
+ end
37
+
38
+ def for(name)
39
+ @subscriptions.each do |sub|
40
+ if sub.name == name || File.fnmatch(sub.name, name)
41
+ yield sub.name, sub.method if block_given?
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ class Subscription
48
+ attr_reader :name
49
+ attr_reader :method
50
+
51
+ def initialize(name, method)
52
+ @name = name
53
+ @method = method
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,45 @@
1
+ module Slnky
2
+ module Service
3
+ class << self
4
+ def timers
5
+ Slnky::Service::Timer.instance
6
+ end
7
+ end
8
+
9
+ class Timer
10
+ class << self
11
+ def instance
12
+ @instance ||= self.new
13
+ end
14
+ end
15
+
16
+ def initialize
17
+ @timers = []
18
+ end
19
+
20
+ def add(seconds, method)
21
+ @timers << Slnky::Service::Periodic.new(seconds, method)
22
+ end
23
+
24
+ def list
25
+ @timers
26
+ end
27
+
28
+ def each
29
+ @timers.each do |t|
30
+ yield t.seconds, t.method
31
+ end
32
+ end
33
+ end
34
+
35
+ class Periodic
36
+ attr_reader :seconds
37
+ attr_reader :method
38
+
39
+ def initialize(seconds, method)
40
+ @seconds = seconds
41
+ @method = method
42
+ end
43
+ end
44
+ end
45
+ end
data/lib/slnky/service.rb CHANGED
@@ -1,82 +1,70 @@
1
- require 'amqp'
2
- require 'open-uri'
3
- require 'json'
4
- require 'socket'
5
-
6
1
  require 'slnky/version'
7
2
  require 'slnky/message'
8
- require 'slnky/service/subscriptions'
9
- require 'slnky/service/periodics'
10
- require 'slnky/service/queues'
11
- require 'slnky/service/exchanges'
3
+ require 'slnky/service/subscriber'
4
+ require 'slnky/service/timer'
12
5
 
13
6
  module Slnky
14
7
  module Service
15
8
  class Base
16
9
  attr_reader :config
17
-
18
- def initialize(url, options={})
19
- @server = url
20
- @name = self.class.name.split('::').last.downcase
21
- @environment = options.delete(:env) || options.delete(:environment) || 'development'
22
- @config = load_config(options)
23
-
24
- @subscriptions = self.class.subscriptions || Slnky::Service::Subscriptions.new
25
- @periodics = self.class.periodics || Slnky::Service::Periodics.new
26
- @hostname = Socket.gethostname
27
- @ipaddress = Socket.ip_address_list.find { |ai| ai.ipv4? && !ai.ipv4_loopback? }.ip_address
10
+ attr_reader :subscriber
11
+ attr_reader :timers
12
+ attr_reader :name
13
+
14
+ def initialize
15
+ config.service = name
16
+ @environment = config.environment
17
+ @server_down = false
28
18
  end
29
19
 
30
20
  def start
31
- AMQP.start("amqp://#{config.rabbit.host}:#{config.rabbit.port}") do |connection|
32
- @channel = AMQP::Channel.new(connection)
33
- @channel.on_error do |ch, channel_close|
34
- raise "Channel-level exception: #{channel_close.reply_text}"
35
- end
36
-
37
- @exchanges = Slnky::Service::Exchanges.new(@channel)
38
- @exchanges.create('events')
39
- @exchanges.create('logs')
21
+ transport.start!(self) do |_|
22
+ log.info "running"
23
+ run
40
24
 
41
- @queues = Slnky::Service::Queues.new(@channel)
42
- @queues.create(@name, @exchanges['events'])
25
+ subscriber.add "slnky.#{name}.command", :handle_command
26
+ subscriber.add "slnky.help.command", :handle_command
27
+ subscriber.add "slnky.service.restart", :handle_restart
28
+ timers.add 5.seconds, :handle_heartbeat unless config.development?
43
29
 
44
- stopper = Proc.new do
45
- puts "#{Time.now}: stopping"
46
- connection.close { EventMachine.stop }
30
+ subscriber.each do |name, method|
31
+ log.info "subscribed to: #{name} -> #{self.class.name}.#{method}"
47
32
  end
33
+ end
34
+ end
48
35
 
49
- Signal.trap("INT", stopper)
50
- Signal.trap("TERM", stopper)
36
+ def handle_command(event, data)
37
+ if command
38
+ command.handle(event, data)
39
+ else
40
+ log.error "no comamnd support for #{name}"
41
+ end
42
+ end
51
43
 
52
- log :info, "running"
44
+ def handle_restart(name, data)
45
+ # if we get this event, just stop. upstart will start us again.
46
+ log.warn "received restart event"
47
+ transport.stop!('Restarted')
48
+ end
53
49
 
54
- run
50
+ def handle_heartbeat
51
+ return if @server_down
52
+ Slnky.heartbeat(name)
53
+ rescue => e
54
+ log.info "could not post heartbeat, server down? #{e.message}"
55
+ @server_down = true
56
+ end
55
57
 
56
- @subscriptions.each do |name, method|
57
- log :info, "subscribed to: #{name} -> #{self.class.name}.#{method}"
58
- end
58
+ def subscriber
59
+ @subscriber ||= Slnky::Service.subscriber
60
+ end
59
61
 
60
- @queues[@name].subscribe do |raw|
61
- message = parse(raw)
62
- event = message.name
63
- data = message.payload
64
- @subscriptions.for(event) do |name, method|
65
- self.send(method.to_sym, event, data)
66
- end
67
- if event == 'slnky.service.restart'
68
- # if we get this event, just stop. upstart will start us again.
69
- log :warn, "received restart event"
70
- stopper.call
71
- end
72
- end
62
+ def timers
63
+ @timers ||= Slnky::Service.timers
64
+ end
73
65
 
74
- @periodics.each do |seconds, method|
75
- EventMachine.add_periodic_timer(seconds) do
76
- self.send(method.to_sym)
77
- end
78
- end
79
- end
66
+ def name
67
+ @name ||= self.class.name.split('::')[1].downcase
80
68
  end
81
69
 
82
70
  protected
@@ -93,55 +81,29 @@ module Slnky
93
81
  Slnky::Message.parse(data)
94
82
  end
95
83
 
96
- def subscribe(name, method)
97
- raise "move this to class level, use methods instead of blocks"
98
- # @subscriptions.add(name, method)
84
+ def log
85
+ Slnky::Log.instance
99
86
  end
100
87
 
101
- def periodic(seconds, method)
102
- raise "move this to class level, use methods instead of blocks"
103
- # @periodics.add(seconds, method)
88
+ def config
89
+ Slnky.config
104
90
  end
105
91
 
106
- def log(level, message)
107
- data = {
108
- service: "#{@name}-#{$$}",
109
- level: level,
110
- hostname: @hostname,
111
- ipaddress: @ipaddress,
112
- message: "slnky.service.#{@name}: #{message}"
113
- }
114
- @exchanges['logs'].publish(msg(data)) if @exchanges && @exchanges['logs'] # only log to the exchange if it's created
115
- puts "%s [%6s] %s" % [Time.now, data[:level], data[:message]] if development? # log to the console if in development
92
+ def transport
93
+ @transport ||= Slnky::Transport.instance
116
94
  end
117
95
 
118
- def development?
119
- @environment == 'development'
120
- end
121
-
122
- def load_config(config)
123
- # if you specify config, it will not load from server
124
- # this is useful for testing, so you won't need to be running
125
- # a server locally or configure your development service to
126
- # talk to production server
127
- if !config || config.count == 0
128
- config = JSON.parse(open("#{@server}/configs/#{@name}") {|f| f.read })
129
- end
130
- DeepStruct.new(config)
96
+ def command
97
+ @command ||= "Slnky::#{name.capitalize}::Command".constantize.new rescue nil
131
98
  end
132
99
 
133
100
  class << self
134
- attr_reader :subscriptions
135
- attr_reader :periodics
136
-
137
101
  def subscribe(name, method)
138
- @subscriptions ||= Slnky::Service::Subscriptions.new
139
- @subscriptions.add(name, method)
102
+ Slnky::Service.subscriber.add(name, method)
140
103
  end
141
104
 
142
105
  def periodic(seconds, method)
143
- @periodics ||= Slnky::Service::Periodics.new
144
- @periodics.add(seconds, method)
106
+ Slnky::Service.timers.add(seconds, method)
145
107
  end
146
108
  alias_method :timer, :periodic
147
109
  end
@@ -0,0 +1,19 @@
1
+ require 'socket'
2
+
3
+ module Slnky
4
+ class System
5
+ class << self
6
+ def pid(service)
7
+ "#{ipaddress}/#{service}-#{$$}"
8
+ end
9
+
10
+ def hostname
11
+ @hostname ||= Socket.gethostname
12
+ end
13
+
14
+ def ipaddress
15
+ @ipaddress ||= Socket.ip_address_list.find { |ai| ai.ipv4? && !ai.ipv4_loopback? }.ip_address
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1 @@
1
+ 2.2.2
@@ -1,17 +1,14 @@
1
1
  # config valid only for current version of Capistrano
2
2
  lock '3.4.0'
3
3
 
4
- set :application, '<%= dir %>'
5
- set :repo_url, 'git@github.com:something/<%= dir %>.git'
6
-
7
- rubyversion = File.read('.ruby-version').chomp
8
- rubygemset = File.read('.ruby-gemset').chomp
4
+ set :application, '<%= service %>'
5
+ set :repo_url, 'git@github.com:something/<%= service %>.git'
9
6
 
10
7
  set :deploy_to, "#{ENV['DEPLOY_DIR']}/#{fetch(:application)}#{fetch(:stage) == 'staging' ? '-stg' : ''}"
11
8
 
12
9
  set :keep_releases, 5
13
10
 
14
- set :rvm_ruby_version, "#{rubyversion}@#{rubygemset}" # Defaults to: 'default'
11
+ set :rvm_ruby_version, File.read('.ruby-version').chomp # Defaults to: 'default'
15
12
 
16
13
  # Default branch is :master
17
14
  # ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp
@@ -0,0 +1,11 @@
1
+ module Slnky
2
+ module <%= cap %>
3
+ class Client
4
+ def initialize(config)
5
+
6
+ end
7
+
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module Slnky
2
+ module <%= cap %>
3
+ class Command < Slnky::Command::Base
4
+ attr_writer :client
5
+ def client
6
+ @client ||= Slnky::<%= cap %>::Client.new(config)
7
+ end
8
+
9
+ def echo(request, response)
10
+ args = request.arguments||[]
11
+ response.output = args.join(" ")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module Slnky
2
+ module <%= cap %>
3
+ class Mock
4
+ def initialize(config)
5
+
6
+ end
7
+
8
+
9
+ end
10
+ end
11
+ end
@@ -1,8 +1,11 @@
1
- require 'slnky'
2
-
3
1
  module Slnky
4
- module Service
5
- class <%= name.capitalize %> < Base
2
+ module <%= cap %>
3
+ class Service < Slnky::Service::Base
4
+ attr_writer :client
5
+ def client
6
+ @client ||= Slnky::<%= cap %>::Client.new(config)
7
+ end
8
+
6
9
  subscribe 'slnky.service.test', :handler
7
10
  # you can also subscribe to heirarchies, this gets
8
11
  # all events under something.happened
@@ -0,0 +1,6 @@
1
+ require 'slnky'
2
+
3
+ require 'slnky/<%= name %>/client'
4
+ require 'slnky/<%= name %>/mock'
5
+ require 'slnky/<%= name %>/service'
6
+ require 'slnky/<%= name %>/command'
@@ -7,7 +7,7 @@ require 'rubygems'
7
7
  require 'bundler/setup'
8
8
  require 'dotenv'
9
9
  require 'daemons'
10
- require 'slnky/service/<%= name %>'
10
+ require 'slnky/<%= name %>'
11
11
  Dotenv.load
12
12
 
13
13
  env = ARGV[0]
@@ -15,4 +15,4 @@ env = ARGV[0]
15
15
  # Become a daemon
16
16
  # Daemons.daemonize if env == 'production'
17
17
 
18
- Slnky::Service::<%= name.capitalize %>.new(ENV['SLNKY_URL'], env: env).start
18
+ Slnky::<%= cap %>::Service.new(ENV['SLNKY_URL'], environment: env).start
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe Slnky::<%= cap %>::Command do
4
+ subject do
5
+ s = described_class.new("http://localhost:3000", test_config)
6
+ s.client = Slnky::<%= cap %>::Mock.new(test_config)
7
+ s
8
+ end
9
+ let(:echo) { command('echo').payload }
10
+ let(:response) { Slnky::Command::Response.new() }
11
+
12
+ it 'handles echo' do
13
+ resp = response
14
+ expect(subject.echo(echo, resp)).not_to raise_error
15
+ expect(resp.output).to eq("test echo")
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe Slnky::<%= cap %>::Service do
4
+ subject do
5
+ s = described_class.new("http://localhost:3000", test_config)
6
+ s.client = Slnky::<%= cap %>::Mock.new(test_config)
7
+ s
8
+ end
9
+ let(:test_event) { event_load('test')}
10
+
11
+ it 'handles event' do
12
+ expect(subject.handler(test_event.name, test_event.payload)).to eq(true)
13
+ end
14
+ end
@@ -1,6 +1,6 @@
1
1
  $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
2
  require 'slnky'
3
- require 'slnky/service/<%= name %>'
3
+ require 'slnky/<%= name %>'
4
4
  require 'yaml'
5
5
  require 'erb'
6
6
  require 'tilt'
@@ -8,12 +8,21 @@ require 'tilt'
8
8
  require 'dotenv'
9
9
  @dotenv = Dotenv.load
10
10
 
11
- def event_load(name)
11
+ def event(name)
12
12
  @events ||= {}
13
13
  @events[name] ||= begin
14
14
  file = File.expand_path("../../test/events/#{name}.json", __FILE__)
15
15
  raise "file #{file} not found" unless File.exists?(file)
16
- Slnky::Message.new(JSON.parse(File.read(file)))
16
+ Slnky::Data.new(JSON.parse(File.read(file)))
17
+ end
18
+ end
19
+
20
+ def command(name)
21
+ @commands ||= {}
22
+ @commands[name] ||= begin
23
+ file = File.expand_path("../../test/commands/#{name}.json", __FILE__)
24
+ raise "file #{file} not found" unless File.exists?(file)
25
+ Slnky::Command::Request.new(JSON.parse(File.read(file)))
17
26
  end
18
27
  end
19
28
 
@@ -22,6 +31,8 @@ def test_config
22
31
  file = File.expand_path("../../test/config.yaml", __FILE__)
23
32
  template = Tilt::ERBTemplate.new(file)
24
33
  output = template.render(self, @dotenv)
25
- YAML.load(output)
34
+ cfg = Slnky::Data.new(YAML.load(output))
35
+ cfg.environment = 'test'
36
+ cfg
26
37
  end
27
38
  end
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "slnky.<%= name %>.command",
3
+ "payload": {
4
+ "command": "echo",
5
+ "args": [
6
+ "test",
7
+ "echo"
8
+ ],
9
+ "responder": "STDOUT"
10
+ }
11
+ }
@@ -1,5 +1,5 @@
1
1
  ---
2
- # config.yaml for Slnky::Service::<%= name.capitalize %>
2
+ # config.yaml for Slnky::<%= cap %>::Service
3
3
  # this file is processed through ERB, you can inject
4
4
  # values into the config from the environment, by specifying them
5
5
  # in the .env file
@@ -0,0 +1,98 @@
1
+ require 'amqp'
2
+
3
+ module Slnky
4
+ module Transport
5
+ class << self
6
+ def instance
7
+ @instance ||= begin
8
+ Slnky::Transport::Rabbit.new
9
+ end
10
+ end
11
+ end
12
+
13
+ class Rabbit
14
+ attr_reader :channel
15
+ attr_reader :exchanges
16
+ attr_reader :queues
17
+ attr_reader :stopper
18
+
19
+ def initialize
20
+ @config = Slnky.config
21
+ @host = @config.rabbit.host
22
+ @port = @config.rabbit.port
23
+ @url = "amqp://#{@host}:#{@port}"
24
+ @channel = nil
25
+ @exchanges = {}
26
+ @queues = {}
27
+ end
28
+
29
+ def start!(service, &block)
30
+ AMQP.start(@url) do |connection|
31
+ @connection = connection
32
+ @channel = AMQP::Channel.new(@connection)
33
+ @channel.on_error do |ch, channel_close|
34
+ raise "Channel-level exception: #{channel_close.reply_text}"
35
+ end
36
+
37
+ Signal.trap("INT", proc { self.stop!('Interrupted') })
38
+ Signal.trap("TERM", proc { self.stop!('Terminated') })
39
+
40
+ exchange('events', :fanout)
41
+ exchange('logs', :fanout)
42
+ exchange('response', :direct)
43
+ queue(service.name, 'events')
44
+
45
+ yield self if block_given?
46
+
47
+ if service.is_a?(Slnky::Service::Base)
48
+ queues.each do |name, queue|
49
+ queue.subscribe do |raw|
50
+ event = Slnky::Message.parse(raw)
51
+ service.subscriber.for(event.name) do |name, method|
52
+ service.send(method.to_sym, event.name, event.payload)
53
+ end
54
+ end
55
+ end
56
+
57
+ service.timers.each do |seconds, method|
58
+ EventMachine.add_periodic_timer(seconds) do
59
+ service.send(method.to_sym)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ def stop!(msg=nil)
67
+ return unless @connection
68
+ puts "#{Time.now}: stopping (#{msg})" if msg
69
+ @connection.close { EventMachine.stop { exit } }
70
+ end
71
+
72
+ def exchange(desc, type)
73
+ raise 'attempting to create exchange without channel' unless @channel
74
+ name = "slnky.#{desc}"
75
+ @exchanges[desc] ||=
76
+ case type
77
+ when :fanout
78
+ @channel.fanout(name)
79
+ when :direct
80
+ @channel.direct(name)
81
+ else
82
+ raise "unknown exchange type: #{ex.type}"
83
+ end
84
+ end
85
+
86
+ def queue(desc, exchange, options={})
87
+ raise 'attempting to create queue without channel' unless @channel
88
+ name = "service.#{desc}.#{exchange}"
89
+ options = {
90
+ durable: true
91
+ }.merge(options)
92
+ routing = options.delete(:routing_key)
93
+ bindoptions = routing ? {routing_key: routing} : {}
94
+ @queues[desc] ||= @channel.queue(name, options).bind(@exchanges[exchange], bindoptions)
95
+ end
96
+ end
97
+ end
98
+ end
data/lib/slnky/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Slnky
2
- VERSION = "0.8.3"
2
+ VERSION = "0.9.0"
3
3
  end
data/lib/slnky.rb CHANGED
@@ -1,11 +1,17 @@
1
+ require 'rest_client'
2
+ require 'active_support/all'
3
+ require 'open-uri'
4
+ require 'json'
5
+
1
6
  require 'slnky/version'
2
7
  require 'slnky/data'
8
+ require 'slnky/config'
9
+ require 'slnky/log'
10
+ require 'slnky/system'
3
11
  require 'slnky/message'
4
12
  require 'slnky/service'
5
- require 'slnky/generator'
6
-
7
- require 'rest_client'
8
- require 'active_support/all'
13
+ require 'slnky/transport'
14
+ require 'slnky/command'
9
15
 
10
16
  module Slnky
11
17
  class << self
@@ -13,18 +19,13 @@ module Slnky
13
19
  Slnky::VERSION
14
20
  end
15
21
 
16
- def config
17
- load_config unless @config
18
- @config
19
- end
20
-
21
- def load_config(file='~/.slnky/config.yaml')
22
- path = File.expand_path(file)
23
- @config = Slnky::Data.new(YAML.load_file(path))
22
+ def heartbeat(name)
23
+ server = ENV['SLNKY_URL'] || Slnky.config.url
24
+ RestClient.post "#{server}/hooks/heartbeat", {name: name}, content_type: :json, accept: :json
24
25
  end
25
26
 
26
- def notify(msg, server=nil)
27
- server ||= config.slnky.url
27
+ def notify(msg)
28
+ server = self.config.url
28
29
  params = {name: msg.name, event: msg.to_h}
29
30
  RestClient.post "#{server}/hooks/notify", params.to_json, content_type: :json, accept: :json
30
31
  end
data/slnky.gemspec CHANGED
@@ -38,4 +38,6 @@ Gem::Specification.new do |spec|
38
38
  spec.add_dependency 'tilt', '~> 2.0.2'
39
39
  spec.add_dependency 'eventmachine', '~> 1.0.9.1'
40
40
  spec.add_dependency 'rest-client', '~> 1.8.0'
41
+ spec.add_dependency 'docopt', '~> 0.5.0'
42
+ spec.add_dependency 'highline', '~> 1.7.8'
41
43
  end