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
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