factor 0.6.3 → 0.6.4
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.
- 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
|