factor 0.3.1 → 0.5.04

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