factor 0.6.3 → 0.6.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/factor +1 -1
- data/lib/{factor.rb → commands.rb} +8 -8
- data/lib/commands/base.rb +6 -64
- data/lib/commands/{registry.rb → registry_command.rb} +1 -1
- data/lib/commands/{workflows.rb → workflow_command.rb} +25 -34
- data/lib/common/deep_struct.rb +48 -0
- data/lib/factor/version.rb +1 -1
- data/lib/logger/basic.rb +68 -0
- data/lib/logger/logger.rb +28 -0
- data/lib/runtime/exec_handler.rb +16 -0
- data/lib/runtime/service_address.rb +46 -0
- data/lib/runtime/service_caller.rb +105 -0
- data/lib/runtime/workflow.rb +170 -0
- data/lib/websocket_manager.rb +2 -1
- data/spec/spec_helper.rb +0 -7
- metadata +14 -17
- data/lib/listener.rb +0 -26
- data/lib/runtime.rb +0 -232
- data/spec/base_spec.rb +0 -102
- data/spec/listener_spec.rb +0 -9
- data/spec/registry_spec.rb +0 -0
- data/spec/workflow_spec.rb +0 -11
data/lib/listener.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
require 'websocket_manager'
|
4
|
-
|
5
|
-
module Factor
|
6
|
-
# Class Listener for integrating with connector service
|
7
|
-
class Listener
|
8
|
-
def initialize(url)
|
9
|
-
@url = url
|
10
|
-
end
|
11
|
-
|
12
|
-
def listener(listener_id)
|
13
|
-
listen("#{@url}/listeners/#{listener_id}")
|
14
|
-
end
|
15
|
-
|
16
|
-
def action(action_id)
|
17
|
-
listen("#{@url}/actions/#{action_id}")
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
def listen(uri_path)
|
23
|
-
WebSocketManager.new(uri_path)
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
data/lib/runtime.rb
DELETED
@@ -1,232 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
require 'json'
|
4
|
-
require 'securerandom'
|
5
|
-
require 'yaml'
|
6
|
-
require 'eventmachine'
|
7
|
-
require 'uri'
|
8
|
-
require 'faye/websocket'
|
9
|
-
require 'ostruct'
|
10
|
-
|
11
|
-
require 'listener'
|
12
|
-
require 'commands/base'
|
13
|
-
|
14
|
-
module Factor
|
15
|
-
# Runtime class is the magic of the server
|
16
|
-
class Runtime
|
17
|
-
attr_accessor :logger, :name, :description, :id, :instance_id, :connectors, :credentials
|
18
|
-
|
19
|
-
def initialize(connectors, credentials)
|
20
|
-
@workflow_spec = {}
|
21
|
-
@sockets = []
|
22
|
-
@instance_id = SecureRandom.hex(3)
|
23
|
-
@reconnect = true
|
24
|
-
|
25
|
-
trap 'SIGINT' do
|
26
|
-
info "Exiting '#{@instance_id}'"
|
27
|
-
@reconnect = false
|
28
|
-
@sockets.each { |s| s.close }
|
29
|
-
exit
|
30
|
-
end
|
31
|
-
|
32
|
-
@connectors = {}
|
33
|
-
flat_hash(connectors).each do |key, connector_url|
|
34
|
-
@connectors[key] = Listener.new(connector_url)
|
35
|
-
end
|
36
|
-
|
37
|
-
@credentials = {}
|
38
|
-
credentials.each do |connector_id, credential_settings|
|
39
|
-
@credentials[connector_id] = credential_settings
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def load(workflow_definition)
|
44
|
-
EM.run do
|
45
|
-
instance_eval(workflow_definition)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def listen(service_ref, params = {}, &block)
|
50
|
-
service_map = service_ref.split('::')
|
51
|
-
service_id = service_map.first
|
52
|
-
listener_id = service_map.last
|
53
|
-
service_key = service_map[0..-2].map{|k| k.to_sym}
|
54
|
-
|
55
|
-
ws = @connectors[service_key].listener(listener_id)
|
56
|
-
|
57
|
-
handle_on_open(service_ref, 'Listener', ws, params)
|
58
|
-
|
59
|
-
ws.on :close do
|
60
|
-
error 'Listener disconnected'
|
61
|
-
if @reconnect
|
62
|
-
warn 'Reconnecting...'
|
63
|
-
sleep 3
|
64
|
-
ws.open
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
ws.on :message do |event|
|
69
|
-
listener_response = JSON.parse(event.data)
|
70
|
-
case listener_response['type']
|
71
|
-
when'start_workflow'
|
72
|
-
success "Workflow '#{service_id}::#{listener_id}' triggered"
|
73
|
-
error_handle_call(listener_response, &block)
|
74
|
-
when 'return'
|
75
|
-
success "Workflow '#{service_ref}' started"
|
76
|
-
when 'fail'
|
77
|
-
error "Workflow '#{service_ref}' failed to start"
|
78
|
-
when 'log'
|
79
|
-
listener_response['message'] = " #{listener_response['message']}"
|
80
|
-
log_message(listener_response)
|
81
|
-
else
|
82
|
-
error "Unknown listener response: #{listener_response}"
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
ws.on :retry do |event|
|
87
|
-
warn event[:message]
|
88
|
-
end
|
89
|
-
|
90
|
-
ws.on :error do |event|
|
91
|
-
err = 'Error during WebSocket handshake: Unexpected response code: 401'
|
92
|
-
if event.message == err
|
93
|
-
error "Sorry but you don't have access to this listener,
|
94
|
-
| either because your token is invalid or your plan doesn't
|
95
|
-
| support this listener"
|
96
|
-
else
|
97
|
-
error 'Failure in WebSocket connection to connector service'
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
ws.open
|
102
|
-
|
103
|
-
@sockets << ws
|
104
|
-
end
|
105
|
-
|
106
|
-
def run(service_ref, params = {}, &block)
|
107
|
-
service_map = service_ref.split('::')
|
108
|
-
service_id = service_map.first
|
109
|
-
action_id = service_map.last
|
110
|
-
service_key = service_map[0..-2].map{|k| k.to_sym}
|
111
|
-
|
112
|
-
ws = @connectors[service_key].action(action_id)
|
113
|
-
|
114
|
-
handle_on_open(service_ref, 'Action', ws, params)
|
115
|
-
|
116
|
-
ws.on :error do
|
117
|
-
error 'Connection dropped while calling action'
|
118
|
-
end
|
119
|
-
|
120
|
-
ws.on :message do |event|
|
121
|
-
action_response = JSON.parse(event.data)
|
122
|
-
case action_response['type']
|
123
|
-
when 'return'
|
124
|
-
ws.close
|
125
|
-
success "Action '#{service_ref}' responded"
|
126
|
-
error_handle_call(action_response, &block)
|
127
|
-
when 'fail'
|
128
|
-
ws.close
|
129
|
-
error " #{action_response['message']}"
|
130
|
-
error "Action '#{service_ref}' failed"
|
131
|
-
when 'log'
|
132
|
-
action_response['message'] = " #{action_response['message']}"
|
133
|
-
log_message(action_response)
|
134
|
-
else
|
135
|
-
error "Unknown action response: #{action_response}"
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
ws.open
|
140
|
-
|
141
|
-
@sockets << ws
|
142
|
-
end
|
143
|
-
|
144
|
-
private
|
145
|
-
|
146
|
-
class DeepStruct < OpenStruct
|
147
|
-
def initialize(hash=nil)
|
148
|
-
@table = {}
|
149
|
-
@hash_table = {}
|
150
|
-
|
151
|
-
if hash
|
152
|
-
hash.each do |k,v|
|
153
|
-
@table[k.to_sym] = (v.is_a?(Hash) ? self.class.new(v) : v)
|
154
|
-
@hash_table[k.to_sym] = v
|
155
|
-
|
156
|
-
new_ostruct_member(k)
|
157
|
-
end
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
def to_h
|
162
|
-
@hash_table
|
163
|
-
end
|
164
|
-
|
165
|
-
def [](idx)
|
166
|
-
hash = marshal_dump
|
167
|
-
hash[idx.to_sym]
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
def simple_object_convert(item)
|
172
|
-
if item.is_a?(Hash)
|
173
|
-
DeepStruct.new(item)
|
174
|
-
elsif item.is_a?(Array)
|
175
|
-
item.map do |i|
|
176
|
-
simple_object_convert(i)
|
177
|
-
end
|
178
|
-
else
|
179
|
-
item
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
def flat_hash(h,f=[],g={})
|
184
|
-
return g.update({ f=>h }) unless h.is_a? Hash
|
185
|
-
h.each { |k,r| flat_hash(r,f+[k],g) }
|
186
|
-
g
|
187
|
-
end
|
188
|
-
|
189
|
-
def handle_on_open(service_ref, dsl_type, ws, params)
|
190
|
-
service_map = service_ref.split('::')
|
191
|
-
service_id = service_map.first
|
192
|
-
|
193
|
-
ws.on :open do
|
194
|
-
params.merge!(@credentials[service_id.to_sym] || {})
|
195
|
-
success "#{dsl_type.capitalize} '#{service_ref}' called"
|
196
|
-
ws.send(params.to_json)
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
def error_handle_call(listener_response, &block)
|
201
|
-
content = simple_object_convert(listener_response['payload'])
|
202
|
-
block.call(content) if block
|
203
|
-
rescue => ex
|
204
|
-
error "Error in workflow definition: #{ex.message}"
|
205
|
-
ex.backtrace.each do |line|
|
206
|
-
error " #{line}"
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
def success(msg)
|
211
|
-
log_message('type' => 'log', 'status' => 'success', 'message' => msg)
|
212
|
-
end
|
213
|
-
|
214
|
-
def warn(msg)
|
215
|
-
log_message('type' => 'log', 'status' => 'warn', 'message' => msg)
|
216
|
-
end
|
217
|
-
|
218
|
-
def error(msg)
|
219
|
-
log_message('type' => 'log', 'status' => 'error', 'message' => msg)
|
220
|
-
end
|
221
|
-
|
222
|
-
def info(msg)
|
223
|
-
log_message('type' => 'log', 'status' => 'info', 'message' => msg)
|
224
|
-
end
|
225
|
-
|
226
|
-
def log_message(message_info)
|
227
|
-
message_info['instance_id'] = @instance_id
|
228
|
-
message_info['workflow_id'] = @id
|
229
|
-
@logger.call(message_info) if @logger
|
230
|
-
end
|
231
|
-
end
|
232
|
-
end
|
data/spec/base_spec.rb
DELETED
@@ -1,102 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
require 'spec_helper'
|
4
|
-
require 'tempfile'
|
5
|
-
require 'yaml'
|
6
|
-
require 'commander'
|
7
|
-
|
8
|
-
describe Factor::Commands::Command do
|
9
|
-
before :each do
|
10
|
-
@command = Factor::Commands::Command.new
|
11
|
-
end
|
12
|
-
|
13
|
-
output_methods = %w(info warn error success)
|
14
|
-
|
15
|
-
output_methods.each do |method_name|
|
16
|
-
describe ".#{method_name}" do
|
17
|
-
it "logs #{method_name}" do
|
18
|
-
|
19
|
-
test_string = 'Hello World'
|
20
|
-
output = capture_stdout do
|
21
|
-
@command.method(method_name.to_sym).call message: test_string
|
22
|
-
end
|
23
|
-
|
24
|
-
expect(output).to include(test_string)
|
25
|
-
expect(output).to include(method_name.upcase)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
describe '.exception' do
|
31
|
-
it 'logs exception' do
|
32
|
-
|
33
|
-
test_string = 'Hello World'
|
34
|
-
exception_string = 'Something be busted'
|
35
|
-
output = capture_stdout do
|
36
|
-
begin
|
37
|
-
fail ArgumentError, exception_string
|
38
|
-
rescue => ex
|
39
|
-
@command.exception test_string, ex
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
expect(output).to include(test_string)
|
44
|
-
expect(output).to include(exception_string)
|
45
|
-
expect(output).to include('ERROR')
|
46
|
-
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
describe '.load_config' do
|
51
|
-
it 'can load credentials and connectors' do
|
52
|
-
credentials_file = Tempfile.new('credentials')
|
53
|
-
connectors_file = Tempfile.new('connectors')
|
54
|
-
|
55
|
-
credentials_content = {
|
56
|
-
'github' => {
|
57
|
-
'api_key' => 'fake_github_key'
|
58
|
-
},
|
59
|
-
'heroku' => {
|
60
|
-
'api_key' => 'fake_heroku_key'
|
61
|
-
}
|
62
|
-
}
|
63
|
-
|
64
|
-
connectors_content = {
|
65
|
-
'timer' => 'http://localhost:9294/v0.4/timer',
|
66
|
-
'web' => 'http://localhost:9294/v0.4/web',
|
67
|
-
'github' => 'http://localhost:9294/v0.4/github',
|
68
|
-
'heroku' => 'http://localhost:9294/v0.4/heroku'
|
69
|
-
}
|
70
|
-
|
71
|
-
credentials_file.write(YAML.dump(credentials_content))
|
72
|
-
connectors_file.write(YAML.dump(connectors_content))
|
73
|
-
|
74
|
-
credentials_file.rewind
|
75
|
-
connectors_file.rewind
|
76
|
-
|
77
|
-
options = Commander::Command::Options.new
|
78
|
-
options.credentials = credentials_file.path
|
79
|
-
options.connectors = connectors_file.path
|
80
|
-
|
81
|
-
config_settings = {
|
82
|
-
credentials: options.credentials,
|
83
|
-
connectors: options.connectors
|
84
|
-
}
|
85
|
-
|
86
|
-
output = capture_stdout do
|
87
|
-
@command.load_config config_settings
|
88
|
-
end
|
89
|
-
|
90
|
-
expect(configatron.credentials.github.api_key).to eq('fake_github_key')
|
91
|
-
expect(configatron.credentials.heroku.api_key).to eq('fake_heroku_key')
|
92
|
-
connectors_content.keys.each do |expected_connector_key|
|
93
|
-
actual_connector = configatron.connectors[expected_connector_key]
|
94
|
-
expected_connector = connectors_content[expected_connector_key]
|
95
|
-
expect(actual_connector).to eq(expected_connector)
|
96
|
-
end
|
97
|
-
|
98
|
-
credentials_file.close
|
99
|
-
connectors_file.close
|
100
|
-
end
|
101
|
-
end
|
102
|
-
end
|
data/spec/listener_spec.rb
DELETED
data/spec/registry_spec.rb
DELETED
File without changes
|