factor 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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: []