factor 0.0.3 → 0.0.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.
data/bin/factor CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ require "rubygems"
3
+ require "factor"
2
4
 
3
- require 'factor'
4
-
5
- Factor::Agent.hi(ARGV[0])
5
+ Factor::CLI::FactorTask.start
@@ -0,0 +1,10 @@
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
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+
3
+ module Factor
4
+ module Channel
5
+ class Channel
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,13 @@
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
@@ -0,0 +1,16 @@
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
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+ require 'cli/command'
3
+
4
+ module Factor
5
+ module CLI
6
+ class ChannelTask < Command
7
+
8
+ desc "install NAME", "install a new channel"
9
+ def install(name)
10
+ end
11
+
12
+ desc "uninstall NAME", "uninstall the channel"
13
+ def uninstall(name)
14
+ end
15
+
16
+ desc "list", "list all my installed channels"
17
+ def list
18
+ end
19
+
20
+ desc "all", "list all channels available for installation"
21
+ def all
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
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
+ CONFIG_FILE_DIR = File.expand_path("~/.factor")
12
+ no_tasks do
13
+ def save_config(config)
14
+ File.open(CONFIG_FILE_DIR,'w') do |file|
15
+ YAML::dump(config,file)
16
+ end
17
+ end
18
+
19
+ def get_config
20
+ File.exists?(CONFIG_FILE_DIR) ? YAML::load_file(CONFIG_FILE_DIR) : {}
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ require 'rubygems'
2
+ require 'cli/command'
3
+
4
+ module Factor
5
+ module CLI
6
+ class CredentialTask < Command
7
+
8
+ desc "list", "list all the credentials"
9
+ def list
10
+ end
11
+
12
+ desc "add KEY VALUE", "add a key and value for the credential"
13
+ def add
14
+ end
15
+
16
+ desc "remove KEY", "remove a value from the credentials bag"
17
+ def remove
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'cli/command'
3
+ require 'cli/server_task'
4
+ require 'cli/workflow_task'
5
+ require 'cli/credential_task'
6
+
7
+ module Factor
8
+ module CLI
9
+ class FactorTask < Command
10
+
11
+ desc "register","register a new account"
12
+ method_option :email, :alias=>"-e", :type=>:string, :desc=>"Email address you would like to use for new account"
13
+ method_option :password, :alias=>"-p", :type=>:string, :desc=>"Password you would like to use for new account"
14
+ def register
15
+ email = options[:email]==nil ? ask("Email address:") : options[:email]
16
+ password = options[:password]==nil ? ask("Password:") : options[:password]
17
+
18
+ # register with email/password
19
+
20
+ end
21
+
22
+ desc "login", "login to the account"
23
+ method_option :email, :alias=>"-u", :type=>:string, :desc=>"Account email address"
24
+ method_option :password, :alias=>"-p", :type=>:string, :desc=>"Account password"
25
+ def login
26
+ email = options[:email]==nil ? ask("Email address:") : options[:email]
27
+ password = options[:password]==nil ? ask("Password:") : options[:password]
28
+
29
+ #login with email/password
30
+ end
31
+
32
+ desc "token", "login with token"
33
+ method_option :token, :alias=>"-t", :type=>:string, :desc=>"Token value to set", :required=>true
34
+ def token
35
+ config = get_config
36
+ config[:token]=options[:token]
37
+ save_config(config)
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ Factor::CLI::FactorTask.register(Factor::CLI::ServerTask, "server","server","start and list servers")
44
+ Factor::CLI::FactorTask.register(Factor::CLI::WorkflowTask,"workflow","workflow","start and list workflows")
45
+ Factor::CLI::FactorTask.register(Factor::CLI::ChannelTask,"channel","channel","install,uninstall, list channels")
@@ -0,0 +1,47 @@
1
+ require 'rubygems'
2
+ require 'cli/command'
3
+
4
+ module Factor
5
+ module CLI
6
+ class ServerTask < Command
7
+ desc "start", "start the server"
8
+ def start
9
+ engine = Factor::Engine.new
10
+
11
+ client = Factor::Client.new
12
+ client.login_token(get_config[:token])
13
+
14
+ puts "loading channels"
15
+ engine = client.load_channels(engine)
16
+ puts "loading channels complete"
17
+
18
+ puts "loading workflows"
19
+ engine = client.load_workflows(engine)
20
+ puts "loading workflows complete"
21
+
22
+ puts "loading credentials"
23
+ engine = client.load_credentials(engine)
24
+ puts "loading credentials complete"
25
+
26
+ puts "starting the server..."
27
+ engine.start
28
+
29
+ end
30
+
31
+ desc "list", "list all the running servers"
32
+ def list
33
+ puts "listing all servers"
34
+ end
35
+
36
+ desc "logs", "listen to incoming logs"
37
+ def logs
38
+ engine = Factor::Engine.new
39
+ puts "Listening..."
40
+ engine.logs do |message|
41
+ puts "[#{message.route}] #{message.body}"
42
+ end
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'cli/command'
3
+
4
+ module Factor
5
+ module CLI
6
+ class WorkflowTask < Command
7
+
8
+ desc "call WORKFLOW","start a workflow"
9
+ method_option :parameters, :type=>:hash, :default=>{}, :required=>false
10
+ def call(workflow_name)
11
+ puts "starting workflow #{workflow_name} with options #{options.parameters.to_s}"
12
+
13
+ engine = Factor::Engine.new
14
+ id = engine.launch(workflow_name,options.parameters)
15
+
16
+ puts "workflow executed with id #{id}"
17
+ end
18
+
19
+ desc "list", "list all available workflows"
20
+ def list
21
+
22
+
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,118 @@
1
+ require 'rubygems'
2
+ require 'rest_client'
3
+ require 'zip'
4
+ require 'zip/zipfilesystem'
5
+ require 'open-uri'
6
+
7
+ module Factor
8
+ module Client
9
+ class Client
10
+ HOST = "http://localhost:3000"
11
+
12
+ def register(email,password)
13
+ end
14
+
15
+ def login(email, password)
16
+ end
17
+
18
+ def login_token(token)
19
+ @token=token
20
+ end
21
+
22
+ def load_workflows(engine)
23
+ workflows = rest_get("workflows")
24
+ workflows.each do |workflow|
25
+ workflow_definition = JSON.parse(workflow['definition'])
26
+ workflow=Factor::Workflow.new(workflow_definition)
27
+ engine.load_workflow(workflow)
28
+ end
29
+ engine
30
+ end
31
+
32
+ def load_credentials(engine)
33
+
34
+ credentials_definition = rest_get("credentials")
35
+ credentials = JSON.parse(credentials_definition["bag"])
36
+ engine.load_credentials(credentials)
37
+
38
+ engine
39
+ end
40
+
41
+
42
+ def load_channels(engine)
43
+
44
+ # get list of channels
45
+ channels = rest_get("channels")
46
+
47
+ #load each channel
48
+ channels.each do |channel|
49
+
50
+ # URI of the zip file containing the module
51
+ uri = URI.parse(channel['zip_url'])
52
+
53
+ # temp file to store the zip for download
54
+ temp_file = Tempfile.new([uri.to_s.gsub(/\W+/,'-'), '.zip'], :encoding => 'ascii-8bit')
55
+
56
+ # temp directory where zip will be decompressed
57
+ unzip_dir=temp_file.path[0..-5]
58
+
59
+ # download
60
+ open(uri.to_s,"rb") do |bin|
61
+ temp_file.print bin.read
62
+ end
63
+ temp_file.close
64
+
65
+ # unzip download zip into unzipped directory
66
+ unzip(temp_file.path,unzip_dir)
67
+
68
+ Dir.glob("#{unzip_dir}/#{channel['name'].downcase}/*.rb").each do |file|
69
+ engine.load_channel(file)
70
+ end
71
+
72
+ end
73
+
74
+
75
+ engine
76
+ end
77
+
78
+
79
+ private
80
+
81
+ def fix_github_zip_archive(file)
82
+ Zip::ZipFile.open(file.path) do |zip|
83
+ basedir = zip.entries.first.name
84
+ zip.remove zip.entries.first
85
+ zip.each do |entry|
86
+ target = entry.name.sub(/^#{basedir}/,'')
87
+ if entry.directory?
88
+ zip.dir.mkdir(target)
89
+ else
90
+ zip.file.open(entry.name) do |istream|
91
+ zip.file.open(target, 'w') do |ostream|
92
+ ostream.write istream.read
93
+ end
94
+ end
95
+ end
96
+ zip.remove entry
97
+ end
98
+ end
99
+ end
100
+
101
+ def unzip(zip, unzip_dir, remove_after = false)
102
+ Zip::ZipFile.open(zip) do |zip_file|
103
+ zip_file.each do |f|
104
+ f_path=File.join(unzip_dir, f.name)
105
+ FileUtils.mkdir_p(File.dirname(f_path))
106
+ zip_file.extract(f, f_path) unless File.exist?(f_path)
107
+ end
108
+ end
109
+ FileUtils.rm(zip) if remove_after
110
+ end
111
+
112
+ def rest_get(path)
113
+ JSON.parse(RestClient.get "#{HOST}/#{path}.json?auth_token=#{@token}")
114
+ end
115
+
116
+ end
117
+ end
118
+ end
data/lib/factor.rb CHANGED
@@ -1,9 +1,7 @@
1
- $LOAD_PATH.unshift File.dirname(__FILE__)
2
1
  require 'rubygems'
3
- require 'SecureRandom'
4
- require "amqp"
5
- require 'json'
6
- require 'activity.rb'
7
- require 'worker.rb'
8
- require 'engine.rb'
9
- require 'message.rb'
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
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+
3
+ module Factor
4
+ module Runtime
5
+
6
+ class Attributes < Hash
7
+
8
+ private
9
+ def map_values(map,values)
10
+ params=Hash.new
11
+ map.each do |key,reference|
12
+ params[key]=values[reference]
13
+ end
14
+ params
15
+ end
16
+
17
+ def format_map(parent,map)
18
+ newmap=Hash.new
19
+ map.each do |key,value|
20
+ newmap["#{parent}::#{key}"]=value
21
+ end
22
+ newmap
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,107 @@
1
+ require 'rubygems'
2
+ require 'factor'
3
+ require 'mustache'
4
+
5
+
6
+ module Factor
7
+ module Runtime
8
+ class Engine
9
+ attr_accessor :channel_modules, :workflows, :message_bus
10
+
11
+ # Engine needs modules that contain the code, workflows to run, and message bus for communication
12
+ def initialize
13
+ @channel_modules=Hash.new
14
+ @workflows = Hash.new
15
+ @message_bus ||= MessageBus.new
16
+ @credentials = Hash.new
17
+ end
18
+
19
+ # load the channel by referencing the .rb file
20
+ # the filename is lowercase with "_" for spaces
21
+ # and the module inside must be camal cased to match
22
+ # once loaded it is in the channel_modules Hash
23
+ def load_channel filename
24
+ file=File.new filename
25
+ require file
26
+ channel_module_name = File.basename(file).gsub('.rb','').split('_').map{|ea| ea.capitalize}.join('')
27
+ channel_module= self.class.const_get(channel_module_name)
28
+
29
+ @channel_modules[channel_module.definition["module"]]=channel_module
30
+ end
31
+
32
+ def load_credentials credentials
33
+ @credentials["credentials"] = credentials
34
+ end
35
+
36
+ # adds the workflow to the workflows list
37
+ # the object must be a Workflow type
38
+ def load_workflow workflow
39
+ @workflows[workflow.name] = workflow
40
+ end
41
+
42
+ def logs routing_key="#", &code
43
+ @message_bus.start do
44
+ @message_bus.listen routing_key do |message|
45
+ code.call message
46
+ end
47
+ end
48
+ end
49
+
50
+ def launch workflow, params
51
+ instance_id=SecureRandom.hex
52
+ @message_bus.start do
53
+ message = Message.new
54
+ message.position << "start"
55
+ message.workflow=workflow
56
+ message.add_values params
57
+ message.workflow_instance_id= instance_id
58
+ @message_bus.send_and_close message
59
+ end
60
+ instance_id
61
+ end
62
+
63
+ # start your engines. vroom vrooooom!
64
+ def start
65
+ @message_bus.start do
66
+ @message_bus.listen do |message|
67
+ if @workflows.include? message.workflow
68
+ workflow = @workflows[message.workflow]
69
+ activity = workflow.get_activity(message.position)
70
+ if !activity.nil?
71
+ # puts "[activity] #{activity.to_s}"
72
+ action = activity["action"]
73
+ channel = activity["channel"]
74
+ method = activity["method"]
75
+
76
+ values = message.body.merge(@credentials)
77
+ puts "[values] #{values}"
78
+ # this maps the input values passed in with the templated defined in the workflow
79
+ params = Hash.new
80
+ activity["params"].each do |key,template|
81
+ params[key]=Mustache.render(template,values)
82
+ end
83
+ puts "[calling] #{channel}::#{method} (#{params.to_s})"
84
+ event = call_channel_method(channel,method,params)
85
+
86
+ response_message = message.respond(event.params,event.class.name.split("::").last)
87
+
88
+
89
+ @message_bus.send response_message
90
+
91
+ end
92
+ else
93
+ # workflow doesn't exist
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+
100
+ def call_channel_method(channel_name,class_name,params)
101
+ channel_module = @channel_modules[channel_name]
102
+ command = channel_module.const_get(class_name)
103
+ command.new.do_work(params)
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,65 @@
1
+ require 'rubygems'
2
+ require 'SecureRandom'
3
+ require 'json'
4
+
5
+ module Factor
6
+ module Runtime
7
+ class Message
8
+ attr_accessor :body, :workflow, :workflow_instance_id, :activity_instance_id, :last_activity_instance_id, :position
9
+ def initialize
10
+ @activity_instance_id=SecureRandom.hex
11
+ @workflow = workflow
12
+ @body = Hash.new
13
+ @position = Array.new
14
+ end
15
+
16
+ def respond(params, event)
17
+ m = Message.new
18
+ m.body = @body
19
+ m.workflow_instance_id=@workflow_instance_id
20
+ m.last_activity_instance_id = @activity_instance_id
21
+ m.workflow = @workflow
22
+ m.position = @position
23
+ m.position << "on"
24
+ m.position << event
25
+ m.add_values(params)
26
+ m
27
+ end
28
+
29
+
30
+ def add_values(values)
31
+ current=@body
32
+ position.each do |key|
33
+ #puts "[add value] #{key} (#{key.class.name})"
34
+ current[key]={} if !current.include?(key)
35
+ current=current[key]
36
+ end
37
+ values.each do |key,value|
38
+ current[key]=value
39
+ end
40
+
41
+ end
42
+
43
+ def route
44
+ "#{workflow}.#{position.join('.')}"
45
+ end
46
+
47
+ def payload
48
+ {"body"=>@body, "workflow_instance_id"=>@workflow_instance_id, "activity_instance_id"=>@activity_instance_id, "last_activity_instance_id"=>@last_activity_instance_id}.to_json
49
+ end
50
+
51
+ def from_queue routing_key, payload
52
+ routing_array=routing_key.split('.')
53
+ @workflow=routing_array.first #first
54
+ @position=routing_array.drop(1) # everything after first
55
+
56
+ message=JSON.parse(payload)
57
+ @body=message["body"]
58
+ @workflow_instance_id=message["workflow_instance_id"]
59
+ @activity_instance_id=message["activity_instance_id"]
60
+ @last_activity_instance_id=message["last_activity_instance_id"]
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,57 @@
1
+ require 'rubygems'
2
+ require 'SecureRandom'
3
+
4
+ module Factor
5
+ module Runtime
6
+ class MessageBus
7
+ attr_accessor :host, :connection, :channel, :exchange, :queue
8
+
9
+ def initialize(host="queue.factor.io")
10
+ @host = host
11
+ end
12
+
13
+ # Creates the connection and creates a topic exchange
14
+ # An exchange references a place to send messages to
15
+ # the exchange routes it to the queues based on the route_key
16
+ def start(topic="workflow",&code)
17
+ EventMachine.run do
18
+ @connection = AMQP.connect(:host=>@host)
19
+ @channel = AMQP::Channel.new(connection)
20
+ @exchange = @channel.topic(topic,:auto_delete=>true) # new topic exchange
21
+ code.call
22
+ end
23
+ end
24
+
25
+ # creates a new queue to listen to the topic exchange
26
+ def listen(routing_key="#",&code)
27
+ queue_name=SecureRandom.hex
28
+ @queue = @channel.queue(queue_name)
29
+ @queue.bind(@exchange, :routing_key=>routing_key) # bind queue to the Exchange
30
+
31
+ @queue.subscribe do |headers,payload|
32
+ message = Message.new
33
+ message.from_queue headers.routing_key, payload
34
+ code.call(message)
35
+ end
36
+ end
37
+
38
+ def send(message)
39
+ @exchange.publish(message.payload,:routing_key => message.route)
40
+ end
41
+
42
+ def send_and_close(message)
43
+ send(message)
44
+
45
+ EM.add_timer(1, Proc.new { close})
46
+
47
+ end
48
+
49
+ def close
50
+ @connection.close{ EventMachine.stop }
51
+
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+
3
+ module Factor
4
+ module Runtime
5
+ class Workflow
6
+ attr_accessor :definition
7
+ attr_reader :name
8
+
9
+ def initialize(definition)
10
+ @definition=definition
11
+ @name = @definition["name"]
12
+ end
13
+
14
+ def get_activity(position)
15
+ last_position=@definition
16
+ position.each do |section|
17
+ return nil if last_position[section].nil?
18
+ last_position=last_position[section]
19
+ end
20
+ last_position
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+
3
+ module Factor
4
+ module Runtime
5
+ class WorkflowInstance
6
+ attr_accessor :id, :attributes, :start_message, :workflow
7
+
8
+ def initialize(workflow,attributes)
9
+ @id=SecureRandom.hex
10
+ @attributes = attributes
11
+ @workflow = workflow
12
+ @start_message = Message.new(@attributes)
13
+ @start_message.workflow_instance_id=@id
14
+ @start_message.position=["start"]
15
+ end
16
+
17
+ def get_activity(position)
18
+ last_position=@workflow
19
+ position.each do |section|
20
+ return nil if last_position[section].nil?
21
+ last_position=last_position[section]
22
+ end
23
+ last_position
24
+ end
25
+
26
+ end
27
+ end
28
+ 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.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,16 +9,33 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-15 00:00:00.000000000 Z
12
+ date: 2012-12-09 00:00:00.000000000 Z
13
13
  dependencies: []
14
- description: Library to create Channels for Factor
14
+ description: Friendly command-line interface and library for Factor
15
15
  email: maciej@skierkowski.com
16
16
  executables:
17
17
  - factor
18
18
  extensions: []
19
19
  extra_rdoc_files: []
20
20
  files:
21
+ - lib/channel/activity.rb
22
+ - lib/channel/channel.rb
23
+ - lib/channel/event.rb
24
+ - lib/channel/trigger.rb
25
+ - lib/cli/channel_task.rb
26
+ - lib/cli/command.rb
27
+ - lib/cli/credential_task.rb
28
+ - lib/cli/factor_task.rb
29
+ - lib/cli/server_task.rb
30
+ - lib/cli/workflow_task.rb
31
+ - lib/client/client.rb
21
32
  - lib/factor.rb
33
+ - lib/runtime/attributes.rb
34
+ - lib/runtime/engine.rb
35
+ - lib/runtime/message.rb
36
+ - lib/runtime/message_bus.rb
37
+ - lib/runtime/workflow.rb
38
+ - lib/runtime/workflow_instance.rb
22
39
  - bin/factor
23
40
  homepage: http://rubygems.org/gems/factor
24
41
  licenses: []
@@ -26,6 +43,7 @@ post_install_message:
26
43
  rdoc_options: []
27
44
  require_paths:
28
45
  - lib
46
+ - lib
29
47
  required_ruby_version: !ruby/object:Gem::Requirement
30
48
  none: false
31
49
  requirements:
@@ -43,5 +61,5 @@ rubyforge_project:
43
61
  rubygems_version: 1.8.24
44
62
  signing_key:
45
63
  specification_version: 3
46
- summary: Factor.io Library
64
+ summary: Factor.io Library and CLI
47
65
  test_files: []