slnky 0.8.3 → 0.9.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 (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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8df181eea71fd646f2a3d3d8457ff073498a9b94
4
- data.tar.gz: 8608344396d1c576532f397b2fbfc19935e4f5d8
3
+ metadata.gz: 8f463d4a733b0c60374a3a454f61cab9e87be031
4
+ data.tar.gz: c250fa9638bd70abc1fbc9d02bf74c56d56b847d
5
5
  SHA512:
6
- metadata.gz: 8a74844ae2514bf8ea23860e6fe4abcc0cbf6aa2522ba6cb0881eab4b62cfe793b3db7b644dc2c75f32d55fee64b3501520fdc2e0cb43ae060e4426116068db5
7
- data.tar.gz: f39d2207c1a50d1dfb57005ff6370dfe1e0dedeac26281a7ad719438e459f083e6db749a1ddf3e4d76f562cde17188869ab9036326f8be1cccb45fda601172f6
6
+ metadata.gz: dd296841c43e2b34a48cd92a93080efbf146d5ddd71ace7d0c8cba47952b1bb987f906173f1ced9d9a4d3a88026386067c71234e4ab58f5f0a12c5f5013c91e2
7
+ data.tar.gz: e06bcf6700821592792b287480aec22cb2320374938f8c48c4e4e5971d183cb6f979caa865b5c89105a186f941b11b116e71a9f66068aa1ced2680b2df0ccda2
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ slnky
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.2.2
data/bin/slnky CHANGED
@@ -4,4 +4,5 @@ require 'rubygems'
4
4
  require 'bundler/setup'
5
5
  require 'slnky/cli'
6
6
 
7
+ Slnky::Config.configure('cli')
7
8
  Slnky::CLI::Main.run
@@ -0,0 +1,78 @@
1
+ module Slnky
2
+ module CLI
3
+ class Command < Base
4
+ # option %w{-s --server}, '[SERVER]', 'set server url', environment_variable: 'SLNKY_SERVER'
5
+ option %w{-n --dry-run}, :flag, "just output the event, don't send"
6
+ option %w{-t --timeout}, '[TIMEOUT]', "time to wait for response in seconds", default: 10 do |t|
7
+ Integer(t)
8
+ end
9
+ parameter 'SERVICE', 'the name of the service'
10
+ parameter '[COMMAND]', 'the name of the command', default: 'help'
11
+ parameter '[ARGUMENTS] ...', <<-DESC.strip_heredoc, attribute_name: :args
12
+ arguments to the command
13
+ commands support options and command line arguments similarly to
14
+ standard option parsing.
15
+ DESC
16
+
17
+ def execute
18
+ @name = service
19
+ data = {
20
+ name: "slnky.#{service}.command",
21
+ command: service == 'help' ? nil : command,
22
+ args: args,
23
+ response: "command-#{$$}",
24
+ }
25
+ Slnky::Config.configure('cli')
26
+ msg = Slnky::Message.new(data)
27
+ puts JSON.pretty_generate(msg.to_h) if dry_run?
28
+ amqp(msg) unless dry_run?
29
+ end
30
+
31
+ def name
32
+ @name
33
+ end
34
+
35
+ def amqp(msg)
36
+ response = msg.response
37
+ tx = Slnky::Transport.instance
38
+
39
+ tx.start!(self) do |_|
40
+ tx.exchange('response', :direct)
41
+ queue = tx.queue(response, 'response', durable: false, auto_delete: true, routing_key: response)
42
+ queue.subscribe do |raw|
43
+ message = Slnky::Message.parse(raw)
44
+ level = message.level.to_sym
45
+ if level == :complete
46
+ tx.stop!
47
+ elsif level == :start
48
+ # start tracking responders?
49
+ else
50
+ out message.level, message.message, message.service
51
+ end
52
+ end
53
+
54
+ EventMachine.add_periodic_timer(timeout) do
55
+ out :error, "timed out after #{timeout} seconds"
56
+ tx.stop!('Timed out')
57
+ end
58
+
59
+ Slnky.notify(msg)
60
+ end
61
+ end
62
+
63
+ def out(level, message, service=:local)
64
+ # unless @remote[service]
65
+ # say "<%= color('response from service: #{data.service}', BOLD) %>"
66
+ # @first = true
67
+ # end
68
+ # color = level.to_s == 'info' ? 'GREEN' : 'RED'
69
+ # say "<%= color(\"#{service}\", GRAY) %> <%= color(\"#{message}\", #{color}) %>"
70
+ lines = message.split("\n")
71
+ lines.each do |line|
72
+ puts "#{service} [#{level}] #{line}"
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ Slnky::CLI::Main.subcommand('command', 'send command to the slnky server', Slnky::CLI::Command)
@@ -3,14 +3,16 @@ module Slnky
3
3
  class Generate < Base
4
4
  subcommand 'service', 'generate a service named NAME' do
5
5
  parameter 'NAME', 'the name of the service'
6
+ option %w{-f --force}, :flag, "force overwrite of files"
6
7
  def execute
7
- generator = Slnky::Generator::Service.new(name)
8
+ generator = Slnky::Generator::Service.new(name, force: force?)
8
9
  generator.generate
9
10
  end
10
11
  end
11
12
 
12
13
  subcommand 'command', 'generate a command named NAME' do
13
14
  parameter 'NAME', 'the name of the command'
15
+ option %w{-f --force}, :flag, "force overwrite of files"
14
16
  def execute
15
17
  raise 'not implemented'
16
18
  end
@@ -0,0 +1,30 @@
1
+ module Slnky
2
+ module CLI
3
+ class Service < Base
4
+ subcommand 'run', 'run service named NAME' do
5
+ parameter 'NAME', 'the name of the service'
6
+ # option %w{-f --force}, :flag, "force overwrite of files"
7
+ option %w{-e --environment}, '[ENV]', 'the environment to run under', default: 'development', environment_variable: 'SLNKY_ENV'
8
+ def execute
9
+ lib = File.expand_path("#{Dir.pwd}/lib", __FILE__)
10
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
11
+
12
+ require 'rubygems'
13
+ require 'bundler/setup'
14
+ require 'dotenv'
15
+ Dotenv.load
16
+
17
+ require 'slnky'
18
+ require "slnky/#{name}"
19
+
20
+ Slnky::Config.reset!
21
+ Slnky::Config.configure(name, 'environment' => environment)
22
+ Slnky::Chef::Service.new.start
23
+ rescue => e
24
+ puts "failed to run service #{name}: #{e.message} at #{e.backtrace.first}"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ Slnky::CLI::Main.subcommand 'service', 'manage service', Slnky::CLI::Service
data/lib/slnky/cli.rb CHANGED
@@ -1,7 +1,10 @@
1
- require 'slnky'
2
1
  require 'yaml'
3
2
  require 'clamp'
4
3
  require 'active_support/all'
4
+ require 'highline'
5
+
6
+ require 'slnky'
7
+ require 'slnky/generator'
5
8
 
6
9
  module Slnky
7
10
  module CLI
@@ -0,0 +1,7 @@
1
+ module Slnky
2
+ module Command
3
+ class Request < Slnky::Data
4
+
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,43 @@
1
+ module Slnky
2
+ module Command
3
+ class Response
4
+ def initialize(route, service)
5
+ @transport = Slnky::Transport.instance
6
+ @channel = @transport.channel
7
+ @exchange = @transport.exchanges['response']
8
+ @route = route
9
+ @service = Slnky::System.pid(service)
10
+ start!
11
+ end
12
+
13
+ [:info, :warn, :error].each do |l|
14
+ define_method(l) do |message|
15
+ pub l, message
16
+ end
17
+ end
18
+
19
+ def output(message)
20
+ info(message)
21
+ end
22
+
23
+ def start!
24
+ pub :start, "start"
25
+ end
26
+
27
+ def done!
28
+ pub :complete, "complete"
29
+ end
30
+
31
+ private
32
+
33
+ def msg(level, message)
34
+ Slnky::Message.new({level: level, message: message, service: @service})
35
+ end
36
+
37
+ def pub(level, message)
38
+ # puts "#{level} #{message}"
39
+ @exchange.publish(msg(level, message), routing_key: @route)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,100 @@
1
+ require 'slnky/command/request'
2
+ require 'slnky/command/response'
3
+
4
+ require 'docopt'
5
+
6
+ module Slnky
7
+ module Command
8
+ class Base
9
+ def initialize
10
+ @commands = self.class.commands
11
+ end
12
+
13
+ def handle(event, data)
14
+ begin
15
+ req = Slnky::Command::Request.new(data)
16
+ res = Slnky::Command::Response.new(data.response, name)
17
+ log.response = res
18
+ res.start!
19
+
20
+ if event == 'slnky.help.command'
21
+ handle_help(req, res)
22
+ else
23
+ handle_command(req, res)
24
+ end
25
+
26
+ rescue => e
27
+ log.error "failed to run command: #{name}: #{data.command}: #{e.message} at #{e.backtrace.first}"
28
+ ensure
29
+ res.done!
30
+ log.response = false
31
+ end
32
+ end
33
+
34
+ def handle_help(req, res, opts={})
35
+ @commands.each do |command|
36
+ log.info "#{name} #{command.name}: #{command.banner}"
37
+ end
38
+ end
39
+
40
+ def handle_command(req, res)
41
+ begin
42
+ processor = @commands.select { |c| c.name == req.command }.first
43
+ if processor
44
+ options = processor.process(req.args)
45
+ self.send("handle_#{processor.name}", req, res, options)
46
+ else
47
+ log.error "unknown command: #{req.command}"
48
+ end
49
+ rescue Docopt::Exit => e
50
+ log.info e.message
51
+ rescue => e
52
+ log.error "error in #{req.command}: #{e.message} at #{e.backtrace.first}"
53
+ end
54
+ end
55
+
56
+ def name
57
+ @name ||= self.class.name.split('::')[1].downcase
58
+ end
59
+
60
+ def log
61
+ @log ||= Slnky.log
62
+ end
63
+
64
+ class << self
65
+ attr_reader :commands
66
+
67
+ def command(name, banner, desc)
68
+ @commands ||= [Slnky::Command::Processor.new('help', 'print help', 'help [options]')]
69
+ @commands << Slnky::Command::Processor.new(name, banner, desc)
70
+ end
71
+ end
72
+ end
73
+
74
+ class Processor
75
+ attr_reader :name
76
+ attr_reader :banner
77
+ attr_reader :doc
78
+
79
+ def initialize(name, banner, doc)
80
+ @name = name.to_s
81
+ @banner = banner
82
+ @doc = doc =~ /^Usage/ ? doc : "Usage: #{doc}"
83
+ end
84
+
85
+ def usage
86
+ doc.lines.first.chomp
87
+ end
88
+
89
+ def process(args)
90
+ opts = Docopt::docopt(@doc, argv: args)
91
+ data = Slnky::Data.new
92
+ opts.each do |key, value|
93
+ k = key.gsub(/^--/, '').downcase
94
+ data.send("#{k}=", value)
95
+ end
96
+ data
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,69 @@
1
+ require 'open-uri'
2
+
3
+ module Slnky
4
+ class << self
5
+ def config
6
+ Slnky::Config.instance
7
+ end
8
+ end
9
+
10
+ class Config < Data
11
+ class << self
12
+ def configure(name, config={})
13
+ @name = name
14
+ @config ||= begin
15
+ config['service'] = name
16
+ config['environment'] ||= 'development'
17
+ file = ENV['SLINKY_CONFIG']||"~/.slnky/config.yaml"
18
+ config.merge!(config_file(file))
19
+ server = ENV['SLNKY_SERVER'] || config['url']
20
+ config.merge!(config_server(server))
21
+ self.new(config)
22
+ end
23
+ end
24
+
25
+ # def load_file(file)
26
+ # self.load(YAML.load_file(File.expand_path(file)))
27
+ # end
28
+
29
+ def instance
30
+ @config || configure('unknown')
31
+ end
32
+
33
+ def reset!
34
+ @config = nil
35
+ end
36
+
37
+ # def merge(config)
38
+ # @config.merge!(config)
39
+ # end
40
+
41
+ protected
42
+
43
+ def config_file(file)
44
+ path = File.expand_path(file)
45
+ return {} unless File.exists?(path)
46
+ d = YAML.load_file(path) rescue {}
47
+ d['slnky'] ? d['slnky'] : d
48
+ end
49
+
50
+ def config_server(server)
51
+ return {} unless server
52
+ server = "https://#{server}" unless server =~ /^http/
53
+ JSON.parse(open("#{server}/configs/#{@name}") { |f| f.read }) rescue {}
54
+ end
55
+ end
56
+
57
+ def development?
58
+ !self.environment || self.environment == 'development'
59
+ end
60
+
61
+ def production?
62
+ self.environment == 'production'
63
+ end
64
+
65
+ def test?
66
+ self.environment == 'test'
67
+ end
68
+ end
69
+ end
data/lib/slnky/data.rb CHANGED
@@ -3,14 +3,25 @@ require 'slnky/ext/deep_struct'
3
3
 
4
4
  module Slnky
5
5
  class Data < DeepStruct
6
- class << self
7
- def parse(str)
8
- new(JSON.parse(str))
6
+ def initialize(hash={})
7
+ if hash.is_a?(Slnky::Data)
8
+ hash = hash.to_h
9
9
  end
10
+ super(hash)
10
11
  end
11
12
 
12
13
  def to_s
13
14
  to_h.to_json
14
15
  end
16
+
17
+ def delete(name)
18
+ self.delete_field(name) || self.delete_field(name.to_s)
19
+ end
20
+
21
+ class << self
22
+ def parse(str)
23
+ new(JSON.parse(str))
24
+ end
25
+ end
15
26
  end
16
27
  end
@@ -5,41 +5,66 @@ require 'find'
5
5
  module Slnky
6
6
  module Generator
7
7
  class Base
8
+ require 'highline/import'
8
9
  attr_reader :name
9
10
  attr_reader :dir
10
11
 
11
- def initialize(name)
12
+ def initialize(name, options={})
13
+ options = {
14
+ force: false,
15
+ }.merge(options)
12
16
  @name = name
13
- @dir = File.expand_path("slnky-#{name}")
17
+ @service = "slnky-#{name}"
18
+ @dir = File.expand_path("./#{@service}")
14
19
  short = self.class.name.split('::').last.downcase
15
20
  @template = File.expand_path("../template/#{short}", __FILE__)
21
+ @force = options[:force]
16
22
  end
17
23
 
18
24
  def generate
19
- puts "generating service #{@name}:"
20
- puts " from: #{@template}"
21
- puts " to: #{@dir}"
25
+ say "<%= color('generating service #{@name}', BOLD) %>"
26
+ # puts " from: #{@template}"
27
+ # puts " to: #{@dir}"
22
28
  process_files
23
29
  # make service executable
24
30
  `chmod 755 #{@dir}/service-slnky-#{@name}`
25
31
  # git init
26
- puts "initializing git..."
27
- `cd #{@dir} && git init . || true`
28
- `cd #{@dir} && git add .`
32
+ if File.directory?("#{@dir}/.git")
33
+ puts "git already initialized"
34
+ else
35
+ puts "initializing git..."
36
+ `cd #{@dir} && git init . || true`
37
+ `cd #{@dir} && git add .`
38
+ end
29
39
  end
30
40
 
31
41
  protected
32
42
 
43
+ def askyn(message, options={})
44
+ options = {
45
+ choices: 'yn',
46
+ }.merge(options)
47
+ answer = ask("<%= color('#{message}', BOLD) %> [#{options[:choices]}]") do |q|
48
+ q.echo = false
49
+ q.character = true
50
+ q.validate = /\A[#{options[:choices]}]\Z/
51
+ end
52
+ answer == 'y'
53
+ end
54
+
33
55
  def process_files
34
56
  Find.find(@template).each do |path|
35
57
  next unless File.file?(path)
36
- file = path.gsub(/^#{@template}\//, '')
58
+ file = path.gsub(/^#{@template}\//, '').gsub('NAME', @name)
37
59
  ext = File.extname(path)
38
60
  mkdir(File.dirname("#{@dir}/#{file}"))
61
+ dest = "#{@dir}/#{file}".gsub(/\.erb$/, '')
62
+ say " <%= color('#{dest.gsub(/^#{File.expand_path('.')}\//, '')}', GREEN) %>"
39
63
  if ext == '.erb'
40
- tmpl(file)
64
+ dest = dest
65
+ tmpl(path, dest) if !File.exists?(dest) || @force || askyn(' overwrite file?')
41
66
  else
42
- file(file)
67
+ file(path, dest) if !File.exists?(dest) || @force || askyn(' overwrite file?')
43
68
  end
44
69
  end
45
70
  end
@@ -50,20 +75,21 @@ module Slnky
50
75
  FileUtils.mkdir_p(dir)
51
76
  end
52
77
 
53
- def file(file)
78
+ def file(path, dest)
54
79
  # puts "file: #{file}"
55
- FileUtils.cp("#{@template}/#{file}", "#{@dir}/#{file}")
80
+ FileUtils.cp(path, dest)
56
81
  end
57
82
 
58
- def tmpl(file)
83
+ def tmpl(path, dest)
59
84
  # puts "tmpl: #{file}"
60
- path = "#{@template}/#{file}"
61
85
  var = {
62
86
  name: @name,
63
- dir: @dir
87
+ dir: @dir,
88
+ cap: @name.capitalize,
89
+ service: @service
64
90
  }
65
- out = file.gsub(/\.erb$/, '').gsub('NAME', @name)
66
- dest = "#{@dir}/#{out}"
91
+ # out = file.gsub(/\.erb$/, '')
92
+ # dest = "#{@dir}/#{out}"
67
93
  # puts " #{dest}"
68
94
  template = Tilt.new(path)
69
95
  output = template.render(self, var)
data/lib/slnky/log.rb ADDED
@@ -0,0 +1,117 @@
1
+ module Slnky
2
+ class << self
3
+ def log
4
+ Slnky::Log.instance
5
+ end
6
+ end
7
+
8
+ class Log
9
+ class << self
10
+ def instance
11
+ @logger ||= self.new
12
+ end
13
+ end
14
+
15
+ attr_accessor :local
16
+ attr_accessor :service
17
+ attr_accessor :response
18
+
19
+ def initialize
20
+ @config = Slnky::Config.instance
21
+ @env = @config.environment
22
+ @response = false
23
+ case @config.environment
24
+ when 'production'
25
+ @local = false
26
+ @service = Slnky::Log::Service.new
27
+ when 'test'
28
+ @local = false
29
+ @service = false
30
+ else # development or unset
31
+ @local = Slnky::Log::Local.new
32
+ @service = false
33
+ end
34
+ end
35
+
36
+ [:debug, :info, :warn, :error].each do |l|
37
+ define_method(l) do |message|
38
+ log(l, message)
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def log(level, message)
45
+ @local.send(level, message) if @local
46
+ @service.send(level, message) if @service
47
+ @response.send(level, message) if @response
48
+ end
49
+
50
+ class Base
51
+ [:debug, :info, :warn, :error].each do |l|
52
+ define_method(l) do |message|
53
+ log(l, message)
54
+ end
55
+ end
56
+
57
+ protected
58
+
59
+ def log(level, message)
60
+ # override in subclass
61
+ end
62
+ end
63
+
64
+ class False < Base
65
+
66
+ end
67
+
68
+ class Local < Base
69
+ def initialize
70
+ super
71
+ @logger = Logger.new(STDOUT)
72
+ end
73
+
74
+ protected
75
+
76
+ def log(level, message)
77
+ @logger.send(level, message)
78
+ end
79
+ end
80
+
81
+ class Service < Base
82
+ # ex = @transport.exchanges['logs']
83
+ # ex.publish(msg(data)) if ex # only log to the exchange if it's created
84
+ # puts "%s [%6s] %s" % [Time.now, data[:level], data[:message]] if development? # log to the console if in development
85
+
86
+ def initialize
87
+ super
88
+ @service = Slnky::System.pid('unknown')
89
+ @hostname = Slnky::System.hostname
90
+ @ipaddress = Slnky::System.ipaddress
91
+ @transport = Slnky::Transport.instance
92
+ @exchange = nil
93
+ if @transport
94
+ @exchange = @transport.exchanges['logs']
95
+ end
96
+ end
97
+
98
+ protected
99
+
100
+ def log(level, message)
101
+ return unless @exchange
102
+ data = {
103
+ service: "#{@name}-#{$$}",
104
+ level: level,
105
+ hostname: @hostname,
106
+ ipaddress: @ipaddress,
107
+ message: message
108
+ }
109
+ @exchange.publish(msg(data))
110
+ end
111
+
112
+ def msg(data)
113
+ Slnky::Message.new(data)
114
+ end
115
+ end
116
+ end
117
+ end