factor 0.3.1 → 0.5.04

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ *.log
4
+ .bundle
5
+ .config
6
+ coverage
7
+ InstalledFiles
8
+ lib/bundler/man
9
+ pkg
10
+ rdoc
11
+ spec/reports
12
+ test/tmp
13
+ test/version_tmp
14
+ tmp
15
+
16
+ # YARD artifacts
17
+ .yardoc
18
+ _yardoc
19
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,32 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ factor (0.5.04)
5
+ colored (~> 1.2)
6
+ commander (~> 4.1.5)
7
+ configatron (~> 4.2.0)
8
+ faye-websocket (~> 0.7.2)
9
+ rest_client (~> 1.7.3)
10
+
11
+ GEM
12
+ remote: https://rubygems.org/
13
+ specs:
14
+ colored (1.2)
15
+ commander (4.1.6)
16
+ highline (~> 1.6.11)
17
+ configatron (4.2.0)
18
+ eventmachine (1.0.3)
19
+ faye-websocket (0.7.2)
20
+ eventmachine (>= 0.12.0)
21
+ websocket-driver (>= 0.3.1)
22
+ highline (1.6.20)
23
+ netrc (0.7.7)
24
+ rest_client (1.7.3)
25
+ netrc (~> 0.7.7)
26
+ websocket-driver (0.3.2)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ factor!
data/README.md ADDED
@@ -0,0 +1,10 @@
1
+ [![Code Climate](https://codeclimate.com/github/factor-io/factor.png)](https://codeclimate.com/github/factor-io/factor)
2
+ [![Test Coverage](https://codeclimate.com/github/factor-io/factor/coverage.png)](https://codeclimate.com/github/factor-io/factor)
3
+ [![Build Status](https://travis-ci.org/factor-io/factor.svg)](https://travis-ci.org/factor-io/factor)
4
+ Factor.io Server Runtime
5
+ ==========
6
+
7
+ gem install factor
8
+ git clone git@github.com/factor-io/example-workflows.git
9
+ cd example-workflows
10
+ factor s
data/bin/factor CHANGED
@@ -1,12 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
- require "rubygems"
2
+ # -*- mode: ruby -*-
3
3
 
4
- require "pathname"
5
- bin_file = Pathname.new(__FILE__).realpath
6
-
7
- # add self to libpath
8
- $:.unshift File.expand_path("../../lib", bin_file)
9
- require 'factor'
10
-
11
-
12
- Factor::CLI::FactorTask.start
4
+ require File.expand_path('../../lib/factor', __FILE__)
data/factor.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require 'factor/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "factor"
6
+ s.version = Factor::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Maciej Skierkowski"]
9
+ s.email = ["maciej@factor.io"]
10
+ s.homepage = "https://factor.io"
11
+ s.summary = %q{CLI to manager workflows on Factor.io}
12
+ s.description = %q{CLI to manager workflows on Factor.io}
13
+
14
+ s.files = %x{git ls-files}.split("\n")
15
+ s.test_files = %x{git ls-files -- {test,spec,features}/*}.split("\n")
16
+ s.executables = %x{git ls-files -- bin/*}.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_runtime_dependency 'commander', '~> 4.1.5'
20
+ s.add_runtime_dependency 'rest_client', '~> 1.7.3'
21
+ s.add_runtime_dependency 'faye-websocket', '~> 0.7.2'
22
+ s.add_runtime_dependency 'colored', '~> 1.2'
23
+ s.add_runtime_dependency 'configatron', '~> 4.2.0'
24
+ end
data/lib/cli.rb ADDED
@@ -0,0 +1,21 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'commander/import'
4
+
5
+ require 'commands/workflows'
6
+
7
+ program :name, 'Factor.io Server'
8
+ program :version, Factor::VERSION
9
+ program :description, 'Factor.io Server to run workflows'
10
+
11
+ command 'server' do |c|
12
+ c.syntax = 'factor server [options]'
13
+ c.description = 'Start the Factor.io Server in the current local directory'
14
+ c.option '--log FILE', String, 'Log file path. Default is stdout.'
15
+ c.option '--credentials FILE', String, 'credentials.yml file path.'
16
+ c.option '--connectors FILE', String, 'connectors.yml file path'
17
+ c.option '--path FILE', String, 'Path to workflows'
18
+ c.when_called Factor::Commands::Workflow, :server
19
+ end
20
+
21
+ alias_command 's', 'server'
@@ -0,0 +1,106 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'colored'
4
+ require 'configatron'
5
+ require 'yaml'
6
+ require 'fileutils'
7
+
8
+ module Factor
9
+ module Commands
10
+
11
+ # Base command with common methods used by all commands
12
+ class Command
13
+ DEFAULT_FILENAME = {
14
+ connectors: File.expand_path('./connectors.yml'),
15
+ credentials: File.expand_path('./credentials.yml')
16
+ }
17
+
18
+ attr_accessor :destination_stream
19
+
20
+ def info(options = {})
21
+ log_line :info, options
22
+ end
23
+
24
+ def error(options = {})
25
+ log_line :error, options
26
+ end
27
+
28
+ def warn(options = {})
29
+ log_line :warn, options
30
+ end
31
+
32
+ def success(options = {})
33
+ log_line :success, options
34
+ end
35
+
36
+ def debug(options = {})
37
+ end
38
+
39
+ def exception(message, exception)
40
+ error 'message' => message
41
+ error 'message' => " #{exception.message}"
42
+ exception.backtrace.each do |line|
43
+ error 'message' => " #{line}"
44
+ end
45
+ end
46
+
47
+ def load_config(options = {})
48
+ load_config_data :credentials, options
49
+ load_config_data :connectors, options
50
+ end
51
+
52
+ private
53
+
54
+ def load_config_data(config_type, options = {})
55
+ relative_path = options[config_type] || DEFAULT_FILENAME[config_type]
56
+ absolute_path = File.expand_path(relative_path)
57
+ info message: "Loading #{config_type.to_s} from #{absolute_path}"
58
+ data = YAML.load(File.read(absolute_path))
59
+ configatron[config_type].configure_from_hash(data)
60
+ rescue => ex
61
+ exception "Couldn't load #{config_type.to_s} from #{absolute_path}", ex
62
+ end
63
+
64
+ def log_line(section, options = {})
65
+ options = { message: options } if options.is_a?(String)
66
+ tag = tag(options)
67
+ message = options['message'] || options[:message]
68
+ section_text = format_section(section)
69
+ write "[ #{section_text} ] [#{time}]#{tag} #{message}" if message
70
+ end
71
+
72
+ def format_section(section)
73
+ formated_section = section.to_s.upcase.center(10)
74
+ case section
75
+ when :error then formated_section.red
76
+ when :info then formated_section.bold
77
+ when :warn then formated_section.yellow
78
+ when :success then formated_section.green
79
+ else formated_section
80
+ end
81
+ end
82
+
83
+ def tag(options)
84
+ tag = ''
85
+ if options['workflow_id'] && options['instance_id']
86
+ tag = "[#{options['workflow_id']}:#{options['instance_id']}]"
87
+ elsif options['workflow_id'] && !options['instance_id']
88
+ tag = "[#{options['workflow_id']}]"
89
+ elsif !options['workflow_id'] && options['instance_id']
90
+ tag = "[#{options['instance_id']}]"
91
+ end
92
+ tag
93
+ end
94
+
95
+ def time
96
+ Time.now.localtime.strftime('%m/%d/%y %T.%L')
97
+ end
98
+
99
+ def write(message)
100
+ stream = @destination_stream || $stdout
101
+ stream.puts(message)
102
+ stream.flush
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,115 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'configatron'
4
+
5
+ require 'commands/base'
6
+ require 'runtime'
7
+
8
+ module Factor
9
+ module Commands
10
+
11
+ # Workflow is a Command to start the factor runtime from the CLI
12
+ class Workflow < Factor::Commands::Command
13
+
14
+ def initialize
15
+ @workflows = {}
16
+ end
17
+
18
+ def server(args, options)
19
+ config_settings = {}
20
+ config_settings[:credentials] = options.credentials
21
+ config_settings[:connectors] = options.connectors
22
+ workflow_filename = File.expand_path(options.path || '.')
23
+ @destination_stream = File.new(options.log, 'w+') if options.log
24
+
25
+ load_config(config_settings)
26
+ load_all_workflows(workflow_filename)
27
+ block_until_interupt
28
+ log_message 'status' => 'info', 'message' => 'Good bye!'
29
+ end
30
+
31
+ private
32
+
33
+ def load_all_workflows(workflow_filename)
34
+ glob_ending = workflow_filename[-1] == '/' ? '' : '/'
35
+ glob = "#{workflow_filename}#{glob_ending}*.rb"
36
+ file_list = Dir.glob(glob)
37
+ if !file_list.all? { |file| File.file?(file) }
38
+ error "#{workflow_filename} is neither a file or directory"
39
+ elsif file_list.count == 0
40
+ error 'No workflows in this directory to run'
41
+ else
42
+ file_list.each { |filename| load_workflow(filename) }
43
+ end
44
+ end
45
+
46
+ def block_until_interupt
47
+ log_message 'status' => 'info', 'message' => 'Ctrl-c to exit'
48
+ begin
49
+ while true
50
+ sleep 1
51
+ end
52
+ rescue Interrupt
53
+ log_message 'status' => 'info', 'message' => 'Exiting app...'
54
+ ensure
55
+ @workflows.keys.each { |filename| unload_workflow(filename) }
56
+ end
57
+ end
58
+
59
+ def load_workflow(filename)
60
+ workflow_filename = File.expand_path(filename)
61
+ log_message(
62
+ 'status' => 'info',
63
+ 'message' => "Loading workflow from #{workflow_filename}")
64
+ begin
65
+ workflow_definition = File.read(workflow_filename)
66
+ rescue => ex
67
+ exception "Couldn't read file #{workflow_filename}", ex
68
+ return
69
+ end
70
+
71
+ log_message(
72
+ 'status' => 'info',
73
+ 'message' => 'Setting up workflow processor')
74
+ begin
75
+ connector_settings = configatron.connectors.to_hash
76
+ credential_settings = configatron.credentials.to_hash
77
+ runtime = Runtime.new(connector_settings, credential_settings)
78
+ runtime.logger = self.method(:log_message)
79
+ rescue => ex
80
+ message = "Couldn't setup workflow process for #{workflow_filename}"
81
+ exception message, ex
82
+ end
83
+
84
+ @workflows[workflow_filename] = fork do
85
+ begin
86
+ log_message(
87
+ 'status' => 'info',
88
+ 'message' => "Starting #{workflow_filename}")
89
+ runtime.load(workflow_definition)
90
+ rescue => ex
91
+ exception "Couldn't load #{workflow_filename}", ex
92
+ end
93
+ end
94
+ end
95
+
96
+ def unload_workflow(filename)
97
+ workflow_filename = File.expand_path(filename)
98
+ log_message(
99
+ 'status' => 'info',
100
+ 'message' => "Stopping #{workflow_filename}")
101
+ Process.kill('SIGINT', @workflows[workflow_filename])
102
+ end
103
+
104
+ def log_message(message_info)
105
+ case message_info['status']
106
+ when 'info' then info message_info
107
+ when 'success' then success message_info
108
+ when 'warn' then warn message_info
109
+ when 'debug' then debug message_info
110
+ else error message_info
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
data/lib/factor.rb CHANGED
@@ -1,7 +1,5 @@
1
- require 'rubygems'
1
+ # encoding: UTF-8
2
2
 
3
- # load all the libraries
4
- lib_files = "../**/*.rb"
5
- Dir[File.expand_path(lib_files,__FILE__)].each do |file|
6
- require file
7
- end
3
+ require 'factor/base'
4
+ require 'factor/version'
5
+ require 'cli'
@@ -0,0 +1,17 @@
1
+ # encoding: UTF-8
2
+
3
+ # Primary Factor.io module
4
+ module Factor
5
+
6
+ String.send :define_method, :classify do
7
+ self.split('_').collect! { |w| w.capitalize }.join
8
+ end
9
+
10
+ String.send :define_method, :underscore do
11
+ self.gsub(/::/, '/')
12
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
13
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
14
+ .tr('-', '_')
15
+ .downcase
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ # encoding: UTF-8
2
+
3
+ # Primary Factor.io module
4
+ module Factor
5
+ VERSION = '0.5.04'
6
+ end
data/lib/listener.rb ADDED
@@ -0,0 +1,48 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'rest_client'
4
+ require 'websocket_manager'
5
+
6
+ module Factor
7
+
8
+ # Class Listener for integrating with connector service
9
+ class Listener
10
+
11
+ def initialize(url)
12
+ @url = url
13
+ end
14
+
15
+ def definition
16
+ get("#{@url}/definition")
17
+ end
18
+
19
+ def listener(listener_id, &block)
20
+ ws = listen("#{@url}/listeners/#{listener_id}")
21
+ ws
22
+ end
23
+
24
+ def action(action_id, &block)
25
+ ws = listen("#{@url}/actions/#{action_id}")
26
+ ws
27
+ end
28
+
29
+ private
30
+
31
+ def post(uri_path, payload)
32
+ content = { 'payload' => MultiJson.dump(payload) }
33
+ JSON.parse(RestClient.post(uri_path, content))
34
+ end
35
+
36
+ def get(uri_path)
37
+ JSON.parse(RestClient.get(uri_path))
38
+ end
39
+
40
+ def delete(uri_path)
41
+ JSON.parse(RestClient.delete(uri_path))
42
+ end
43
+
44
+ def listen(uri_path)
45
+ WebSocketManager.new(uri_path)
46
+ end
47
+ end
48
+ end
data/lib/runtime.rb ADDED
@@ -0,0 +1,192 @@
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
+
10
+ require 'listener'
11
+ require 'commands/base'
12
+
13
+ module Factor
14
+ # Runtime class is the magic of the server
15
+ class Runtime
16
+ attr_accessor :logger, :name, :description, :id, :instance_id, :connectors, :credentials
17
+
18
+ def initialize(connectors, credentials)
19
+ @workflow_spec = {}
20
+ @sockets = []
21
+ @instance_id = SecureRandom.hex(3)
22
+ @reconnect = true
23
+
24
+ trap 'SIGINT' do
25
+ info "Exiting '#{@instance_id}'"
26
+ @reconnect = false
27
+ @sockets.each { |s| s.close }
28
+ exit
29
+ end
30
+
31
+ @connectors = {}
32
+ connectors.each do |connector_id, connector_url|
33
+ @connectors[connector_id] = Listener.new(connector_url)
34
+ end
35
+
36
+ @credentials = {}
37
+ credentials.each do |connector_id, credential_settings|
38
+ @credentials[connector_id] = credential_settings
39
+ end
40
+ end
41
+
42
+ def load(workflow_definition)
43
+ EM.run do
44
+ self.instance_eval(workflow_definition)
45
+ end
46
+ end
47
+
48
+ def workflow(key, value)
49
+ @workflow_spec[key] = value
50
+ @name = value if @key == 'name'
51
+ @id = value if @key == 'id'
52
+ @description = value if @key == 'description'
53
+ end
54
+
55
+ def listen(service_id, listener_id, params = {}, &block)
56
+
57
+ ws = @connectors[service_id.to_sym].listener(listener_id)
58
+
59
+ handle_on_open(service_id, listener_id, 'Listener', ws, params)
60
+
61
+ ws.on :close do |event|
62
+ error 'Listener disconnected'
63
+ if @reconnect
64
+ warn 'Reconnecting...'
65
+ sleep 3
66
+ ws.open
67
+ end
68
+ end
69
+
70
+ ws.on :message do |event|
71
+ listener_response = JSON.parse(event.data)
72
+ case listener_response['type']
73
+ when'start_workflow'
74
+ success "Workflow '#{service_id}::#{listener_id}' triggered"
75
+ error_handle_call(listener_response, &block)
76
+ when 'started'
77
+ success "Workflow '#{service_id}::#{listener_id}' started"
78
+ when 'fail'
79
+ error "Workflow '#{service_id}::#{listener_id}' failed to start"
80
+ when 'log'
81
+ listener_response['message'] = " #{listener_response['message']}"
82
+ log_message(listener_response)
83
+ else
84
+ error "Unknown listener response: #{listener_response}"
85
+ end
86
+ end
87
+
88
+ ws.on :retry do |event|
89
+ warn event[:message]
90
+ end
91
+
92
+ ws.on :error do |event|
93
+ err = 'Error during WebSocket handshake: Unexpected response code: 401'
94
+ if event.message == err
95
+ error "Sorry but you don't have access to this listener,
96
+ | either because your token is invalid or your plan doesn't
97
+ | support this listener"
98
+ else
99
+ error 'Failure in WebSocket connection to connector service'
100
+ end
101
+ end
102
+
103
+ ws.open
104
+
105
+ @sockets << ws
106
+ end
107
+
108
+ def run(service_id, action_id, params = {}, &block)
109
+ ws = @connectors[service_id.to_sym].action(action_id)
110
+
111
+ handle_on_open(service_id, action_id, 'Action', ws, params)
112
+
113
+ ws.on :error do |event|
114
+ error 'Connection dropped while calling action'
115
+ end
116
+
117
+ ws.on :message do |event|
118
+ action_response = JSON.parse(event.data)
119
+ case action_response['type']
120
+ when 'return'
121
+ ws.close
122
+ success "Action '#{service_id}::#{action_id}' responded"
123
+ error_handle_call(action_response, &block)
124
+ when 'fail'
125
+ ws.close
126
+ error " #{action_response['message']}"
127
+ error "Action '#{service_id}::#{action_id}' failed"
128
+ when 'log'
129
+ action_response['message'] = " #{action_response['message']}"
130
+ log_message(action_response)
131
+ else
132
+ error "Unknown action response: #{action_response}"
133
+ end
134
+ end
135
+
136
+ ws.open
137
+
138
+ @sockets << ws
139
+ end
140
+
141
+ private
142
+
143
+ def handle_on_open(service_id, action_id, dsl_type, ws, params)
144
+ ws.on :open do |event|
145
+ params.merge!(@credentials[service_id.to_sym] || {})
146
+ success "#{dsl_type.capitalize} '#{service_id}::#{action_id}' called"
147
+ ws.send(params.to_json)
148
+ end
149
+ end
150
+
151
+ def error_handle_call(listener_response, &block)
152
+ block.call(listener_response['payload']) if block
153
+ rescue => ex
154
+ error "Error in workflow definition: #{ex.message}"
155
+ ex.backtrace.each do |line|
156
+ error " #{line}"
157
+ end
158
+ end
159
+
160
+ def success(msg)
161
+ log_message('type' => 'log', 'status' => 'success', 'message' => msg)
162
+ end
163
+
164
+ def warn(msg)
165
+ log_message('type' => 'log', 'status' => 'warn', 'message' => msg)
166
+ end
167
+
168
+ def error(msg)
169
+ log_message('type' => 'log', 'status' => 'error', 'message' => msg)
170
+ end
171
+
172
+ def info(msg)
173
+ log_message('type' => 'log', 'status' => 'info', 'message' => msg)
174
+ end
175
+
176
+ def log_message(message_info)
177
+ message_info['instance_id'] = @instance_id
178
+ message_info['workflow_id'] = @id
179
+ @logger.call(message_info) if @logger
180
+ end
181
+
182
+ def define_method_in_class(class_ref, class_id, method_id, &block)
183
+ class_name = class_id.classify
184
+ method_name = method_id.underscore
185
+ class_ref.class.instance_eval do
186
+ define_method(method_name) do |params = {}, &passed_block|
187
+ block.call(class_name, method_name, params, &passed_block)
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,94 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'faye/websocket'
4
+ require 'uri'
5
+
6
+ # class for managing the web socket connections
7
+ class WebSocketManager
8
+ attr_accessor :keep_open, :events, :state
9
+
10
+ def initialize(uri, headers = {})
11
+ u = URI(uri)
12
+ u.scheme = 'wss' if u.scheme == 'https'
13
+ @uri = u.to_s
14
+ @settings = { ping: 10, retry: 5 }
15
+ @settings[:headers] = headers if headers && headers != {}
16
+ @state = :closed
17
+ @events = {}
18
+ end
19
+
20
+ def open
21
+ if closed?
22
+ @state = :opening
23
+ connect
24
+ end
25
+ @state
26
+ end
27
+
28
+ def close
29
+ if open?
30
+ @state = :closing
31
+ @ws.close
32
+ end
33
+ @state
34
+ end
35
+
36
+ def on(event, &block)
37
+ @events[event] = block
38
+ end
39
+
40
+ def open?
41
+ @state == :open
42
+ end
43
+
44
+ def opening?
45
+ @state == :opening
46
+ end
47
+
48
+ def closed?
49
+ @state == :closed
50
+ end
51
+
52
+ def closing?
53
+ @state == :closing
54
+ end
55
+
56
+ def send(msg)
57
+ @ws.send(msg)
58
+ end
59
+
60
+ private
61
+
62
+ def call_event(event, data)
63
+ @events[event].call(data) if @events[event]
64
+ end
65
+
66
+ def connect
67
+ EM.run do
68
+ begin
69
+ @ws = Faye::WebSocket::Client.new(@uri, nil, @settings)
70
+
71
+ @ws.on :close do |event|
72
+ @state = :closed
73
+ call_event :close, event
74
+ end
75
+
76
+ @ws.on :message do |msg|
77
+ call_event :message, msg
78
+ end
79
+
80
+ @ws.on :open do |event|
81
+ @state = :open
82
+ call_event :open, event
83
+ end
84
+
85
+ @ws.on :error do |event|
86
+ call_event :error, event
87
+ end
88
+ rescue => ex
89
+ call_event :fail, ex.message
90
+ end
91
+ end
92
+ end
93
+
94
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: factor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.5.04
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,99 +9,117 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-25 00:00:00.000000000 Z
12
+ date: 2014-07-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: thor
15
+ name: commander
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
- - - ! '>='
19
+ - - ~>
20
20
  - !ruby/object:Gem::Version
21
- version: 0.16.0
21
+ version: 4.1.5
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  none: false
26
26
  requirements:
27
- - - ! '>='
27
+ - - ~>
28
28
  - !ruby/object:Gem::Version
29
- version: 0.16.0
29
+ version: 4.1.5
30
30
  - !ruby/object:Gem::Dependency
31
- name: rest-client
31
+ name: rest_client
32
32
  requirement: !ruby/object:Gem::Requirement
33
33
  none: false
34
34
  requirements:
35
- - - ! '>='
35
+ - - ~>
36
36
  - !ruby/object:Gem::Version
37
- version: 1.6.7
37
+ version: 1.7.3
38
38
  type: :runtime
39
39
  prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
41
41
  none: false
42
42
  requirements:
43
- - - ! '>='
43
+ - - ~>
44
44
  - !ruby/object:Gem::Version
45
- version: 1.6.7
45
+ version: 1.7.3
46
46
  - !ruby/object:Gem::Dependency
47
- name: zip
47
+ name: faye-websocket
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  none: false
50
50
  requirements:
51
- - - ! '>='
51
+ - - ~>
52
52
  - !ruby/object:Gem::Version
53
- version: 2.0.2
53
+ version: 0.7.2
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  none: false
58
58
  requirements:
59
- - - ! '>='
59
+ - - ~>
60
60
  - !ruby/object:Gem::Version
61
- version: 2.0.2
61
+ version: 0.7.2
62
62
  - !ruby/object:Gem::Dependency
63
- name: json
63
+ name: colored
64
64
  requirement: !ruby/object:Gem::Requirement
65
65
  none: false
66
66
  requirements:
67
- - - ! '>='
67
+ - - ~>
68
68
  - !ruby/object:Gem::Version
69
- version: 1.7.6
69
+ version: '1.2'
70
70
  type: :runtime
71
71
  prerelease: false
72
72
  version_requirements: !ruby/object:Gem::Requirement
73
73
  none: false
74
74
  requirements:
75
- - - ! '>='
75
+ - - ~>
76
76
  - !ruby/object:Gem::Version
77
- version: 1.7.6
78
- description: Friendly command-line interface and library for Factor
79
- email: maciej@factor.io
77
+ version: '1.2'
78
+ - !ruby/object:Gem::Dependency
79
+ name: configatron
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 4.2.0
86
+ type: :runtime
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 4.2.0
94
+ description: CLI to manager workflows on Factor.io
95
+ email:
96
+ - maciej@factor.io
80
97
  executables:
81
98
  - factor
82
99
  extensions: []
83
100
  extra_rdoc_files: []
84
101
  files:
85
- - lib/channel/activity.rb
86
- - lib/channel/channel.rb
87
- - lib/channel/event.rb
88
- - lib/channel/listener.rb
89
- - lib/channel/trigger.rb
90
- - lib/cli/channel_task.rb
91
- - lib/cli/command.rb
92
- - lib/cli/credential_task.rb
93
- - lib/cli/factor_task.rb
94
- - lib/cli/workflow_task.rb
95
- - lib/client/client.rb
96
- - lib/factor.rb
102
+ - .gitignore
103
+ - Gemfile
104
+ - Gemfile.lock
105
+ - README.md
97
106
  - bin/factor
98
- homepage: http://rubygems.org/gems/factor
107
+ - factor.gemspec
108
+ - lib/cli.rb
109
+ - lib/commands/base.rb
110
+ - lib/commands/workflows.rb
111
+ - lib/factor.rb
112
+ - lib/factor/base.rb
113
+ - lib/factor/version.rb
114
+ - lib/listener.rb
115
+ - lib/runtime.rb
116
+ - lib/websocket_manager.rb
117
+ homepage: https://factor.io
99
118
  licenses: []
100
119
  post_install_message:
101
120
  rdoc_options: []
102
121
  require_paths:
103
122
  - lib
104
- - lib
105
123
  required_ruby_version: !ruby/object:Gem::Requirement
106
124
  none: false
107
125
  requirements:
@@ -119,5 +137,5 @@ rubyforge_project:
119
137
  rubygems_version: 1.8.25
120
138
  signing_key:
121
139
  specification_version: 3
122
- summary: Factor.io Library and CLI
140
+ summary: CLI to manager workflows on Factor.io
123
141
  test_files: []
@@ -1,10 +0,0 @@
1
- require 'rubygems'
2
-
3
- module Factor
4
- module Channel
5
- class Activity
6
- def do_work(params)
7
- end
8
- end
9
- end
10
- end
@@ -1,8 +0,0 @@
1
- require 'rubygems'
2
-
3
- module Factor
4
- module Channel
5
- class Channel
6
- end
7
- end
8
- end
data/lib/channel/event.rb DELETED
@@ -1,13 +0,0 @@
1
- require 'rubygems'
2
-
3
- module Factor
4
- module Channel
5
- class Event
6
- attr_accessor :params
7
-
8
- def initialize
9
- @params = Hash.new
10
- end
11
- end
12
- end
13
- end
@@ -1,20 +0,0 @@
1
- require 'rubygems'
2
-
3
- module Factor
4
- module Channel
5
- class Listener
6
- attr_accessor :workflow_id, :event
7
-
8
- def start(user_id,channel_name,listener_name,event,workflow_name,params,&code)
9
- end
10
-
11
- def stop()
12
- end
13
-
14
- def initialize(workflow_id,event)
15
- @workflow_id=workflow_id
16
- @event=event
17
- end
18
- end
19
- end
20
- end
@@ -1,16 +0,0 @@
1
- require 'rubygems'
2
-
3
- module Factor
4
- module Channel
5
- class Trigger
6
- def start(params)
7
-
8
- end
9
-
10
- def end
11
-
12
- end
13
- end
14
-
15
- end
16
- end
@@ -1,37 +0,0 @@
1
- require 'rubygems'
2
- require 'cli/command'
3
-
4
- module Factor
5
- module CLI
6
- class ChannelTask < Command
7
- desc "list", "list all the channels"
8
- #method_option :key, :alias=>"-k", :type=>:string, :desc=>"key reference"
9
- def list
10
- @client.get_channels().each do |channel|
11
- puts "#{channel['name']} (#{channel['slug']})"
12
- puts " Listeners:" if channel['listeners'].count>0
13
- channel['listeners'].each do |listener|
14
- puts " #{listener['name']}: #{listener['description']}"
15
- end
16
- puts " Actions:" if channel['actions'].count>0
17
- channel['actions'].each do |action|
18
- puts " #{action['name']}: #{action['description']}"
19
- end
20
- end
21
- end
22
-
23
- desc "create DIRECTORY DEFINITION", "add a key and value for the credential"
24
- method_option :organization, :alias=>"-o", :type=>:string, :desc=>"Organizoation to which this workflow belongs"
25
- def create(directory,definition_file)
26
- puts @client.create_channel(directory,definition_file,options[:organization])["notice"]
27
- end
28
-
29
- desc "delete NAME", "remove a workflow"
30
- def delete(name)
31
- puts @client.delete_channel(name)["notice"]
32
- end
33
-
34
-
35
- end
36
- end
37
- end
data/lib/cli/command.rb DELETED
@@ -1,36 +0,0 @@
1
- require 'rubygems'
2
- require 'thor'
3
- require 'thor/group'
4
- require 'yaml'
5
- require 'rest_client'
6
-
7
-
8
- module Factor
9
- module CLI
10
- class Command < Thor
11
-
12
-
13
-
14
- no_tasks do
15
-
16
- def initialize(*vals)
17
- @config_file_dir = File.expand_path("~/.factor")
18
- @client = Factor::Client::Client.new
19
- @client.login_token(get_config['token'])
20
- super(*vals)
21
- end
22
-
23
- def save_config(config)
24
- File.open(@config_file_dir,'w') do |file|
25
- YAML::dump(config,file)
26
- end
27
- @client.login_token(get_config['token'])
28
- end
29
-
30
- def get_config
31
- File.exists?(@config_file_dir) ? YAML::load_file(@config_file_dir) : {}
32
- end
33
- end
34
- end
35
- end
36
- end
@@ -1,39 +0,0 @@
1
- require 'rubygems'
2
- require 'cli/command'
3
-
4
- module Factor
5
- module CLI
6
- class CredentialTask < Command
7
-
8
-
9
- desc "create SERVICE NAME VALUE", "add a key and value for the credential"
10
- method_option :key, :type=>:string, :desc=>"File reference containing the symmetric key for encryption"
11
- method_option :organization, :type=>:string, :desc=>"Organizoation to which this credential belongs"
12
- def create(service,name,value)
13
- secret=nil
14
- if options[:key]
15
- secret=File.read(options[:key])
16
- end
17
- puts @client.create_credential(service,name,value,secret,options[:organization])
18
- end
19
-
20
- desc "list", "get all of the credential"
21
- def list()
22
- @client.get_credentials["value"].each do |service,values|
23
- # puts "#{credential['service']} : #{credential['name']} (#{credential['slug']}): #{credential['value']}"
24
- puts "#{service}:"
25
- values.each do |key,values|
26
- # puts "#{key['value']} (#{value['slug']}) : #{value}"
27
- puts " #{key} (#{values['slug']}): ***"
28
- end
29
- end
30
- end
31
-
32
- desc "delete SERVICE NAME", "remove a value from the credentials bag"
33
- def delete(service,name)
34
- puts @client.delete_credential(service,name)
35
- end
36
-
37
- end
38
- end
39
- end
@@ -1,26 +0,0 @@
1
- require 'rubygems'
2
- require 'cli/command'
3
- require 'cli/workflow_task'
4
- require 'cli/credential_task'
5
- require 'cli/channel_task'
6
-
7
- module Factor
8
- module CLI
9
- class FactorTask < Command
10
-
11
- desc "login EMAIL TOKEN", "login with token"
12
- # method_option :email, :alias=>"-e", :type=>:string, :desc=>"Email address for Factor account", :required=>true
13
- # method_option :token, :alias=>"-t", :type=>:string, :desc=>"Token value to set", :required=>true
14
- def login(email,token)
15
- config = get_config
16
- config['email']=email
17
- config['token']=token
18
- save_config(config)
19
- end
20
- end
21
- end
22
- end
23
-
24
- Factor::CLI::FactorTask.register(Factor::CLI::WorkflowTask,"workflow","workflow","start and list workflows")
25
- Factor::CLI::FactorTask.register(Factor::CLI::ChannelTask,"channel","channel","install,uninstall, list channels")
26
- Factor::CLI::FactorTask.register(Factor::CLI::CredentialTask,"credential","credential","manage remote credential store")
@@ -1,49 +0,0 @@
1
- require 'rubygems'
2
- require 'cli/command'
3
-
4
- module Factor
5
- module CLI
6
- class WorkflowTask < Command
7
-
8
- desc "start WORKFLOW","start listener for workflow"
9
- def start(workflow_name)
10
- puts @client.start_workflow(workflow_name)["notice"]
11
- end
12
-
13
- desc "stop WORKFLOW","stop listener for workflow"
14
- def stop(workflow_name)
15
- puts @client.stop_workflow(workflow_name)["notice"]
16
- end
17
-
18
- desc "call WORKFLOW","call workflow"
19
- method_option :parameters, :type=>:hash, :default=>{}, :required=>false
20
- def call(workflow_name)
21
- puts @client.call_workflow(workflow_name,options[:parameters])["notice"]
22
- end
23
-
24
-
25
- desc "list", "list all the workflows"
26
- def list
27
- @client.get_workflows().each do |workflow|
28
- puts "#{workflow['name']} (#{workflow['slug']})"
29
- end
30
- end
31
-
32
- desc "create NAME FILENAME", "add a key and value for the credential"
33
- #method_option :key, :alias=>"-k", :type=>:string, :desc=>"key reference"
34
- method_option :organization, :alias=>"-v", :type=>:string, :desc=>"Organizoation to which this workflow belongs"
35
- def create(name,filename)
36
- contents=File.open(File.expand_path(filename), "rb") {|f| f.read}
37
- puts @client.create_workflow(name,contents,options[:organization])["notice"]
38
- end
39
-
40
- desc "delete NAME", "remove a workflow"
41
- def delete(name)
42
- puts @client.delete_workflow(name)
43
- end
44
-
45
-
46
-
47
- end
48
- end
49
- end
data/lib/client/client.rb DELETED
@@ -1,151 +0,0 @@
1
- require 'rubygems'
2
- require 'rest_client'
3
- require 'zip'
4
- require 'zip/zipfilesystem'
5
- require 'zip/zip'
6
- require 'open-uri'
7
- require 'digest/sha2'
8
- require 'openssl'
9
- require 'base64'
10
- require 'json'
11
-
12
-
13
- module Factor
14
- module Client
15
- class Client
16
- attr_accessor :host
17
- FACTOR_HOST = ENV["FACTOR_HOST"] || "factor.io"
18
-
19
- def initialize(host=FACTOR_HOST)
20
- @host=host
21
- end
22
-
23
- def login_token(token)
24
- @token=token
25
- end
26
-
27
- def create_credential(service,name,value,secret=nil,organization=nil)
28
- # this is a PUT not POST because it is technically editing, not creating a new one
29
- credential = {:service=>service,:name=>name,:value=>value}
30
-
31
- if secret
32
- payload=value
33
- sha256= Digest::SHA2.new(256)
34
- encrypter = OpenSSL::Cipher.new("AES-256-CFB")
35
- encrypter.encrypt
36
- encrypter.key=Base64.encode64(sha256.digest(secret))
37
-
38
- encrypted = Base64.encode64(encrypter.update(value) + encrypter.final)
39
- credential[:value]=encrypted
40
- credential[:encrypted]=true
41
- end
42
- path = !!organization ? "organizations/#{organization}/credentials" : "credentials"
43
- rest_post(path,credential)
44
- end
45
-
46
- def get_credentials()
47
- rest_get("credentials")
48
- end
49
-
50
- def delete_credential(service,name)
51
- rest_delete("credentials",{:service=>service,:name=>name})
52
- end
53
-
54
-
55
-
56
-
57
- # untested
58
- def create_workflow(key,definition,organization=nil)
59
- path = !!organization ? "organizations/#{organization}/workflows" : "workflows"
60
- rest_post(path,{:workflow=>{:name=>key,:definition=>definition}})
61
- end
62
-
63
- def get_workflows()
64
- rest_get("workflows")
65
- end
66
-
67
- def delete_workflow(workflow_name)
68
- rest_delete("workflows/#{workflow_name}")
69
- end
70
-
71
- def start_workflow(workflow_name)
72
- rest_get("workflows/#{workflow_name}/state")
73
- end
74
-
75
- def stop_workflow(workflow_name)
76
- rest_delete("workflows/#{workflow_name}/state")
77
- end
78
-
79
- def call_workflow(workflow_name,params)
80
- rest_get("workflows/#{workflow_name}/call",params)
81
- end
82
-
83
-
84
-
85
- # channels
86
- def create_channel(path,definition_file,organization=nil)
87
- file=zip(File.expand_path(path))
88
- definition = File.read(definition_file)
89
-
90
- path = !!organization ? "organizations/#{organization}/channels" : "channels"
91
- rest_post(path,{:zip=>file,:channel=>{:definition=>definition}})
92
- end
93
-
94
- def get_channels()
95
- rest_get("channels")
96
- end
97
-
98
- def delete_channel(channel_name)
99
- rest_delete("channels/#{channel_name}")
100
- end
101
-
102
-
103
-
104
-
105
-
106
-
107
- private
108
-
109
- def zip(directory)
110
- path=directory.dup
111
- path.sub!(%r[/$],'')
112
- zip_path = File.join(path,"..",File.basename(path))+'.zip'
113
- FileUtils.rm zip_path, :force=>true
114
- Zip::ZipFile.open(zip_path, 'w') do |zip|
115
- Dir["#{path}/**/**"].reject{|f|f==zip_path}.each do |file|
116
- zip.add(file.sub(path+'/',''),file)
117
- end
118
- end
119
- File.open(zip_path,'r')
120
- end
121
-
122
- def rest_get(path,params={})
123
- params["auth_token"]=@token
124
-
125
- param_string=params.map{|key,value| "#{key}=#{value}"}.join("&")
126
- response=RestClient.get("http://#{@host}/#{path}.json?#{param_string}")
127
- JSON.parse(response)
128
- end
129
-
130
- def rest_put(path,params={})
131
- params["auth_token"]=@token
132
- response=RestClient.put("http://#{@host}/#{path}.json",params)
133
- JSON.parse(response)
134
- end
135
-
136
- def rest_post(path,params={})
137
- response=RestClient.post("http://#{@host}/#{path}.json?auth_token=#{@token}",params)
138
- JSON.parse(response)
139
- end
140
-
141
- def rest_delete(path,params={})
142
- params["auth_token"]=@token
143
-
144
- param_string=params.map{|key,value| "#{key}=#{value}"}.join("&")
145
- response=RestClient.delete("http://#{@host}/#{path}.json?#{param_string}")
146
- JSON.parse(response)
147
- end
148
-
149
- end
150
- end
151
- end