capistrano_sentinel 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +133 -0
- data/LICENSE.txt +20 -0
- data/README.md +38 -0
- data/Rakefile +43 -0
- data/capistrano_sentinel.gemspec +25 -0
- data/lib/capistrano_sentinel.rb +36 -0
- data/lib/capistrano_sentinel/classes/gem_finder.rb +53 -0
- data/lib/capistrano_sentinel/classes/input_stream.rb +34 -0
- data/lib/capistrano_sentinel/classes/output_stream.rb +33 -0
- data/lib/capistrano_sentinel/classes/request_hooks.rb +85 -0
- data/lib/capistrano_sentinel/classes/request_worker.rb +128 -0
- data/lib/capistrano_sentinel/classes/websocket/errors.rb +13 -0
- data/lib/capistrano_sentinel/classes/websocket_client.rb +345 -0
- data/lib/capistrano_sentinel/helpers/actor.rb +52 -0
- data/lib/capistrano_sentinel/helpers/application_helper.rb +47 -0
- data/lib/capistrano_sentinel/helpers/logging.rb +79 -0
- data/lib/capistrano_sentinel/initializers/active_support/blank.rb +143 -0
- data/lib/capistrano_sentinel/initializers/active_support/hash_keys.rb +172 -0
- data/lib/capistrano_sentinel/patches/bundler.rb +24 -0
- data/lib/capistrano_sentinel/patches/capistrano2.rb +34 -0
- data/lib/capistrano_sentinel/patches/rake.rb +10 -0
- data/lib/capistrano_sentinel/version.rb +27 -0
- data/spec/spec_helper.rb +7 -0
- metadata +83 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative './application_helper'
|
2
|
+
module CapistranoSentinel
|
3
|
+
module AsyncActor
|
4
|
+
include CapistranoSentinel::ApplicationHelper
|
5
|
+
|
6
|
+
Context = Struct.new(:method, :args, :block)
|
7
|
+
|
8
|
+
Async = Struct.new(:instance, :mailbox) do
|
9
|
+
extend Forwardable
|
10
|
+
def_delegator :instance, :respond_to?
|
11
|
+
|
12
|
+
private :instance
|
13
|
+
private :mailbox
|
14
|
+
|
15
|
+
def initialize(instance, mailbox)
|
16
|
+
super(instance, mailbox)
|
17
|
+
run!
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(method, *args, &block)
|
21
|
+
mailbox << CapistranoSentinel::AsyncActor::Context.new(method, args, block)
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def run!
|
29
|
+
Thread.new do
|
30
|
+
loop do
|
31
|
+
break if mailbox.empty?
|
32
|
+
begin
|
33
|
+
mailbox.synchronize do
|
34
|
+
ctx = mailbox.pop
|
35
|
+
instance.public_send(ctx.method, *ctx.args, &ctx.block)
|
36
|
+
end
|
37
|
+
rescue => e
|
38
|
+
log_to_file("crashed with #{e.inspect} #{e.backtrace}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def async_queue
|
45
|
+
@async_queue ||= Queue.new.extend(MonitorMixin)
|
46
|
+
end
|
47
|
+
|
48
|
+
def async
|
49
|
+
@async ||= CapistranoSentinel::AsyncActor::Async.new(self, async_queue)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative './logging'
|
2
|
+
module CapistranoSentinel
|
3
|
+
# class that holds the options that are configurable for this gem
|
4
|
+
module ApplicationHelper
|
5
|
+
include CapistranoSentinel::Logging
|
6
|
+
module_function
|
7
|
+
|
8
|
+
# Method that is used to parse a string as JSON , if it fails will return nil
|
9
|
+
# @see JSON#parse
|
10
|
+
# @param [string] res The string that will be parsed as JSON
|
11
|
+
# @return [Hash, nil] Returns Hash object if the json parse succeeds or nil otherwise
|
12
|
+
def parse_json(res)
|
13
|
+
return if res.blank?
|
14
|
+
JSON.parse(res)
|
15
|
+
rescue JSON::ParserError
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def show_warning(message)
|
20
|
+
warn message
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def msg_for_stdin?(message)
|
25
|
+
message['action'] == 'stdin'
|
26
|
+
end
|
27
|
+
|
28
|
+
def message_is_for_stdout?(message)
|
29
|
+
message.present? && message.is_a?(Hash) && message['action'].present? && message['job_id'].present? && message['action'] == 'stdout'
|
30
|
+
end
|
31
|
+
|
32
|
+
def message_is_about_a_task?(message)
|
33
|
+
message.present? && message.is_a?(Hash) && message['action'].present? && message['job_id'].present? && message['task'].present? && message['action'] == 'invoke'
|
34
|
+
end
|
35
|
+
|
36
|
+
def message_from_bundler?(message)
|
37
|
+
message.present? && message.is_a?(Hash) && message['action'].present? && message['job_id'].present? && message['task'].present? && message['action'] == 'bundle_install'
|
38
|
+
end
|
39
|
+
|
40
|
+
def get_question_details(data)
|
41
|
+
matches = /(.*)\?*\s*\:*\s*(\([^)]*\))*/m.match(data).captures
|
42
|
+
[matches[0], matches[1]]
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module CapistranoSentinel
|
2
|
+
# class that holds the options that are configurable for this gem
|
3
|
+
module Logging
|
4
|
+
module_function
|
5
|
+
|
6
|
+
def logger
|
7
|
+
@logger ||= ::Logger.new('/home/raul/workspace/ecommerce/ecommerce-livechat/log/multi_cap.log')
|
8
|
+
end
|
9
|
+
|
10
|
+
def error_filtered?(error)
|
11
|
+
[SystemExit].find { |class_name| error.is_a?(class_name) }.present?
|
12
|
+
end
|
13
|
+
|
14
|
+
def log_error(error, options = {})
|
15
|
+
message = format_error(error)
|
16
|
+
log_output_error(error, options.fetch(:output, nil), message)
|
17
|
+
log_to_file(message, options.merge(log_method: 'fatal'))
|
18
|
+
end
|
19
|
+
|
20
|
+
def log_output_error(error, output, message)
|
21
|
+
return if message.blank? || error_filtered?(error)
|
22
|
+
puts message if output.present?
|
23
|
+
terminal_actor.errors.push(message) if terminal_errors?
|
24
|
+
end
|
25
|
+
|
26
|
+
def format_error(exception)
|
27
|
+
message = "\n#{exception.class} (#{exception.respond_to?(:message) ? exception.message : exception.inspect}):\n"
|
28
|
+
message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
|
29
|
+
message << ' ' << exception.backtrace.join("\n ") if exception.respond_to?(:backtrace)
|
30
|
+
message
|
31
|
+
end
|
32
|
+
|
33
|
+
def log_to_file(message, options = {})
|
34
|
+
worker_log = options.fetch(:job_id, '').present? ? find_worker_log(options[:job_id]) : logger
|
35
|
+
print_to_log_file(worker_log, options.merge(message: message)) if worker_log.present?
|
36
|
+
end
|
37
|
+
|
38
|
+
def print_to_log_file(worker_log, options = {})
|
39
|
+
worker_log.send(options.fetch(:log_method, 'debug'), "#{options.fetch(:message, '')}\n")
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_worker_log(job_id)
|
43
|
+
return if job_id.blank?
|
44
|
+
FileUtils.mkdir_p(log_directory) unless File.directory?(log_directory)
|
45
|
+
filename = File.join(log_directory, "worker_#{job_id}.log")
|
46
|
+
setup_filename_logger(filename)
|
47
|
+
end
|
48
|
+
|
49
|
+
def setup_filename_logger(filename)
|
50
|
+
worker_log = ::Logger.new(filename)
|
51
|
+
worker_log.level = ::Logger::Severity::DEBUG
|
52
|
+
setup_logger_formatter(worker_log)
|
53
|
+
worker_log
|
54
|
+
end
|
55
|
+
|
56
|
+
def setup_logger_formatter(logger)
|
57
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
58
|
+
date_format = datetime.strftime('%Y-%m-%d %H:%M:%S')
|
59
|
+
"[#{date_format}] #{severity} (#{progname}): #{msg}\n"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def execute_with_rescue(output = nil)
|
64
|
+
yield if block_given?
|
65
|
+
rescue Interrupt
|
66
|
+
rescue_interrupt
|
67
|
+
rescue => error
|
68
|
+
rescue_error(error, output)
|
69
|
+
end
|
70
|
+
|
71
|
+
def rescue_error(error, output = nil)
|
72
|
+
log_error(error, output: output)
|
73
|
+
exit(1)
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
class Object
|
2
|
+
# An object is blank if it's false, empty, or a whitespace string.
|
3
|
+
# For example, +false+, '', ' ', +nil+, [], and {} are all blank.
|
4
|
+
#
|
5
|
+
# This simplifies
|
6
|
+
#
|
7
|
+
# !address || address.empty?
|
8
|
+
#
|
9
|
+
# to
|
10
|
+
#
|
11
|
+
# address.blank?
|
12
|
+
#
|
13
|
+
# @return [true, false]
|
14
|
+
def blank?
|
15
|
+
respond_to?(:empty?) ? !!empty? : !self
|
16
|
+
end
|
17
|
+
|
18
|
+
# An object is present if it's not blank.
|
19
|
+
#
|
20
|
+
# @return [true, false]
|
21
|
+
def present?
|
22
|
+
!blank?
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the receiver if it's present otherwise returns +nil+.
|
26
|
+
# <tt>object.presence</tt> is equivalent to
|
27
|
+
#
|
28
|
+
# object.present? ? object : nil
|
29
|
+
#
|
30
|
+
# For example, something like
|
31
|
+
#
|
32
|
+
# state = params[:state] if params[:state].present?
|
33
|
+
# country = params[:country] if params[:country].present?
|
34
|
+
# region = state || country || 'US'
|
35
|
+
#
|
36
|
+
# becomes
|
37
|
+
#
|
38
|
+
# region = params[:state].presence || params[:country].presence || 'US'
|
39
|
+
#
|
40
|
+
# @return [Object]
|
41
|
+
def presence
|
42
|
+
self if present?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class NilClass
|
47
|
+
# +nil+ is blank:
|
48
|
+
#
|
49
|
+
# nil.blank? # => true
|
50
|
+
#
|
51
|
+
# @return [true]
|
52
|
+
def blank?
|
53
|
+
true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class FalseClass
|
58
|
+
# +false+ is blank:
|
59
|
+
#
|
60
|
+
# false.blank? # => true
|
61
|
+
#
|
62
|
+
# @return [true]
|
63
|
+
def blank?
|
64
|
+
true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class TrueClass
|
69
|
+
# +true+ is not blank:
|
70
|
+
#
|
71
|
+
# true.blank? # => false
|
72
|
+
#
|
73
|
+
# @return [false]
|
74
|
+
def blank?
|
75
|
+
false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Array
|
80
|
+
# An array is blank if it's empty:
|
81
|
+
#
|
82
|
+
# [].blank? # => true
|
83
|
+
# [1,2,3].blank? # => false
|
84
|
+
#
|
85
|
+
# @return [true, false]
|
86
|
+
alias_method :blank?, :empty?
|
87
|
+
end
|
88
|
+
|
89
|
+
class Hash
|
90
|
+
# A hash is blank if it's empty:
|
91
|
+
#
|
92
|
+
# {}.blank? # => true
|
93
|
+
# { key: 'value' }.blank? # => false
|
94
|
+
#
|
95
|
+
# @return [true, false]
|
96
|
+
alias_method :blank?, :empty?
|
97
|
+
end
|
98
|
+
|
99
|
+
class String
|
100
|
+
BLANK_RE2 = /\A[[:space:]]*\z/
|
101
|
+
|
102
|
+
# A string is blank if it's empty or contains whitespaces only:
|
103
|
+
#
|
104
|
+
# ''.blank? # => true
|
105
|
+
# ' '.blank? # => true
|
106
|
+
# "\t\n\r".blank? # => true
|
107
|
+
# ' blah '.blank? # => false
|
108
|
+
#
|
109
|
+
# Unicode whitespace is supported:
|
110
|
+
#
|
111
|
+
# "\u00a0".blank? # => true
|
112
|
+
#
|
113
|
+
# @return [true, false]
|
114
|
+
def blank?
|
115
|
+
# The regexp that matches blank strings is expensive. For the case of empty
|
116
|
+
# strings we can speed up this method (~3.5x) with an empty? call. The
|
117
|
+
# penalty for the rest of strings is marginal.
|
118
|
+
empty? || BLANK_RE2 === self
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class Numeric #:nodoc:
|
123
|
+
# No number is blank:
|
124
|
+
#
|
125
|
+
# 1.blank? # => false
|
126
|
+
# 0.blank? # => false
|
127
|
+
#
|
128
|
+
# @return [false]
|
129
|
+
def blank?
|
130
|
+
false
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class Time #:nodoc:
|
135
|
+
# No Time is blank:
|
136
|
+
#
|
137
|
+
# Time.now.blank? # => false
|
138
|
+
#
|
139
|
+
# @return [false]
|
140
|
+
def blank?
|
141
|
+
false
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
class Hash
|
2
|
+
|
3
|
+
# Returns a new hash with all keys converted using the +block+ operation.
|
4
|
+
#
|
5
|
+
# hash = { name: 'Rob', age: '28' }
|
6
|
+
#
|
7
|
+
# hash.transform_keys { |key| key.to_s.upcase } # => {"NAME"=>"Rob", "AGE"=>"28"}
|
8
|
+
#
|
9
|
+
# If you do not provide a +block+, it will return an Enumerator
|
10
|
+
# for chaining with other methods:
|
11
|
+
#
|
12
|
+
# hash.transform_keys.with_index { |k, i| [k, i].join } # => {"name0"=>"Rob", "age1"=>"28"}
|
13
|
+
def transform_keys
|
14
|
+
return enum_for(:transform_keys) { size } unless block_given?
|
15
|
+
result = {}
|
16
|
+
each_key do |key|
|
17
|
+
result[yield(key)] = self[key]
|
18
|
+
end
|
19
|
+
result
|
20
|
+
end
|
21
|
+
|
22
|
+
# Destructively converts all keys using the +block+ operations.
|
23
|
+
# Same as +transform_keys+ but modifies +self+.
|
24
|
+
def transform_keys!
|
25
|
+
return enum_for(:transform_keys!) { size } unless block_given?
|
26
|
+
keys.each do |key|
|
27
|
+
self[yield(key)] = delete(key)
|
28
|
+
end
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns a new hash with all keys converted to strings.
|
33
|
+
#
|
34
|
+
# hash = { name: 'Rob', age: '28' }
|
35
|
+
#
|
36
|
+
# hash.stringify_keys
|
37
|
+
# # => {"name"=>"Rob", "age"=>"28"}
|
38
|
+
def stringify_keys
|
39
|
+
transform_keys(&:to_s)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Destructively converts all keys to strings. Same as
|
43
|
+
# +stringify_keys+, but modifies +self+.
|
44
|
+
def stringify_keys!
|
45
|
+
transform_keys!(&:to_s)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns a new hash with all keys converted to symbols, as long as
|
49
|
+
# they respond to +to_sym+.
|
50
|
+
#
|
51
|
+
# hash = { 'name' => 'Rob', 'age' => '28' }
|
52
|
+
#
|
53
|
+
# hash.symbolize_keys
|
54
|
+
# # => {:name=>"Rob", :age=>"28"}
|
55
|
+
def symbolize_keys
|
56
|
+
transform_keys{ |key| key.to_sym rescue key }
|
57
|
+
end
|
58
|
+
alias_method :to_options, :symbolize_keys
|
59
|
+
|
60
|
+
# Destructively converts all keys to symbols, as long as they respond
|
61
|
+
# to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
|
62
|
+
def symbolize_keys!
|
63
|
+
transform_keys!{ |key| key.to_sym rescue key }
|
64
|
+
end
|
65
|
+
alias_method :to_options!, :symbolize_keys!
|
66
|
+
|
67
|
+
# Validates all keys in a hash match <tt>*valid_keys</tt>, raising
|
68
|
+
# +ArgumentError+ on a mismatch.
|
69
|
+
#
|
70
|
+
# Note that keys are treated differently than HashWithIndifferentAccess,
|
71
|
+
# meaning that string and symbol keys will not match.
|
72
|
+
#
|
73
|
+
# { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: :years. Valid keys are: :name, :age"
|
74
|
+
# { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: :name. Valid keys are: 'name', 'age'"
|
75
|
+
# { name: 'Rob', age: '28' }.assert_valid_keys(:name, :age) # => passes, raises nothing
|
76
|
+
def assert_valid_keys(*valid_keys)
|
77
|
+
valid_keys.flatten!
|
78
|
+
each_key do |k|
|
79
|
+
unless valid_keys.include?(k)
|
80
|
+
raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{valid_keys.map(&:inspect).join(', ')}")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns a new hash with all keys converted by the block operation.
|
86
|
+
# This includes the keys from the root hash and from all
|
87
|
+
# nested hashes and arrays.
|
88
|
+
#
|
89
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
90
|
+
#
|
91
|
+
# hash.deep_transform_keys{ |key| key.to_s.upcase }
|
92
|
+
# # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}}
|
93
|
+
def deep_transform_keys(&block)
|
94
|
+
_deep_transform_keys_in_object(self, &block)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Destructively converts all keys by using the block operation.
|
98
|
+
# This includes the keys from the root hash and from all
|
99
|
+
# nested hashes and arrays.
|
100
|
+
def deep_transform_keys!(&block)
|
101
|
+
_deep_transform_keys_in_object!(self, &block)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Returns a new hash with all keys converted to strings.
|
105
|
+
# This includes the keys from the root hash and from all
|
106
|
+
# nested hashes and arrays.
|
107
|
+
#
|
108
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
109
|
+
#
|
110
|
+
# hash.deep_stringify_keys
|
111
|
+
# # => {"person"=>{"name"=>"Rob", "age"=>"28"}}
|
112
|
+
def deep_stringify_keys
|
113
|
+
deep_transform_keys(&:to_s)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Destructively converts all keys to strings.
|
117
|
+
# This includes the keys from the root hash and from all
|
118
|
+
# nested hashes and arrays.
|
119
|
+
def deep_stringify_keys!
|
120
|
+
deep_transform_keys!(&:to_s)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Returns a new hash with all keys converted to symbols, as long as
|
124
|
+
# they respond to +to_sym+. This includes the keys from the root hash
|
125
|
+
# and from all nested hashes and arrays.
|
126
|
+
#
|
127
|
+
# hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
|
128
|
+
#
|
129
|
+
# hash.deep_symbolize_keys
|
130
|
+
# # => {:person=>{:name=>"Rob", :age=>"28"}}
|
131
|
+
def deep_symbolize_keys
|
132
|
+
deep_transform_keys{ |key| key.to_sym rescue key }
|
133
|
+
end
|
134
|
+
|
135
|
+
# Destructively converts all keys to symbols, as long as they respond
|
136
|
+
# to +to_sym+. This includes the keys from the root hash and from all
|
137
|
+
# nested hashes and arrays.
|
138
|
+
def deep_symbolize_keys!
|
139
|
+
deep_transform_keys!{ |key| key.to_sym rescue key }
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
# support methods for deep transforming nested hashes and arrays
|
144
|
+
def _deep_transform_keys_in_object(object, &block)
|
145
|
+
case object
|
146
|
+
when Hash
|
147
|
+
object.each_with_object({}) do |(key, value), result|
|
148
|
+
result[yield(key)] = _deep_transform_keys_in_object(value, &block)
|
149
|
+
end
|
150
|
+
when Array
|
151
|
+
object.map {|e| _deep_transform_keys_in_object(e, &block) }
|
152
|
+
else
|
153
|
+
object
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def _deep_transform_keys_in_object!(object, &block)
|
158
|
+
case object
|
159
|
+
when Hash
|
160
|
+
object.keys.each do |key|
|
161
|
+
value = object.delete(key)
|
162
|
+
object[yield(key)] = _deep_transform_keys_in_object!(value, &block)
|
163
|
+
end
|
164
|
+
object
|
165
|
+
when Array
|
166
|
+
object.map! {|e| _deep_transform_keys_in_object!(e, &block)}
|
167
|
+
else
|
168
|
+
object
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|