simonmenke-shuttle 0.1.07

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.
Files changed (48) hide show
  1. data/app_generators/engine/engine_generator.rb +39 -0
  2. data/app_generators/engine/templates/config/routes.rb +2 -0
  3. data/app_generators/engine/templates/init.rb +1 -0
  4. data/app_generators/engine/templates/lib/engine.rb +1 -0
  5. data/app_generators/engine/templates/rails/init.rb +1 -0
  6. data/bin/shuttle +20 -0
  7. data/lib/rubygems_plugin.rb +1 -0
  8. data/lib/shuttle/actor/actions.rb +76 -0
  9. data/lib/shuttle/actor.rb +23 -0
  10. data/lib/shuttle/actors/apache_actor.rb +56 -0
  11. data/lib/shuttle/actors/base_actor.rb +276 -0
  12. data/lib/shuttle/actors/mysql_actor.rb +20 -0
  13. data/lib/shuttle/actors/passenger_actor.rb +19 -0
  14. data/lib/shuttle/actors/plesk_actor.rb +210 -0
  15. data/lib/shuttle/actors/sqlite3_actor.rb +44 -0
  16. data/lib/shuttle/app_runner.rb +118 -0
  17. data/lib/shuttle/apps/dev.rb +27 -0
  18. data/lib/shuttle/apps/engines.rb +33 -0
  19. data/lib/shuttle/apps/jobs.rb +35 -0
  20. data/lib/shuttle/apps/satellite.rb +32 -0
  21. data/lib/shuttle/apps/server.rb +70 -0
  22. data/lib/shuttle/client/auth_token.rb +98 -0
  23. data/lib/shuttle/client.rb +48 -0
  24. data/lib/shuttle/exception_handler.rb +53 -0
  25. data/lib/shuttle/extentions/rubygems_plugin.rb +27 -0
  26. data/lib/shuttle/extentions/thor_extentions.rb +32 -0
  27. data/lib/shuttle/job_queue.rb +199 -0
  28. data/lib/shuttle/satellite/actions.rb +35 -0
  29. data/lib/shuttle/satellite/dependency_loader.rb +79 -0
  30. data/lib/shuttle/satellite/persistence.rb +50 -0
  31. data/lib/shuttle/satellite.rb +49 -0
  32. data/lib/shuttle/server/daemon.rb +85 -0
  33. data/lib/shuttle/server/proxy.rb +25 -0
  34. data/lib/shuttle/server/security.rb +113 -0
  35. data/lib/shuttle/server.rb +113 -0
  36. data/lib/shuttle/system/config.rb +36 -0
  37. data/lib/shuttle/system/helper.rb +21 -0
  38. data/lib/shuttle/system/options.rb +79 -0
  39. data/lib/shuttle/system/process_user.rb +75 -0
  40. data/lib/shuttle/system/satellites.rb +44 -0
  41. data/lib/shuttle/system/shell.rb +80 -0
  42. data/lib/shuttle/system.rb +159 -0
  43. data/lib/shuttle/systems/centos_plesk_system.rb +28 -0
  44. data/lib/shuttle/systems/macports_system.rb +14 -0
  45. data/lib/shuttle.rb +82 -0
  46. data/spec/actor/actions_spec.rb +13 -0
  47. data/spec/spec_helper.rb +1 -0
  48. metadata +129 -0
@@ -0,0 +1,48 @@
1
+ require 'drb'
2
+ require 'drb/ssl'
3
+ require 'uri'
4
+
5
+ module Shuttle
6
+ class Client
7
+
8
+ autoload :AuthToken, File.dirname(__FILE__)+'/client/auth_token'
9
+
10
+ # return a DRb uri for the given Shuttle uri.
11
+ def self.parse_uri(uri)
12
+ uri = URI.parse(uri)
13
+ use_ssl = (uri.scheme == 'ssl+shuttle')
14
+ uri.scheme = 'druby'
15
+ return use_ssl, uri.to_s
16
+ end
17
+
18
+ # return an potentialy initialize the client to the given token.
19
+ def self.current(token=nil)
20
+ @client = connect(token) unless @client
21
+ @client
22
+ end
23
+
24
+ # connect to the server referenced by the given token.
25
+ def self.connect(token=nil)
26
+ token ||= 'core.token'
27
+
28
+ [Shuttle::DEFAULT_ROOT_SYSTEM_DIR,
29
+ Shuttle::DEFAULT_USER_SYSTEM_DIR,
30
+ File.join(Shuttle::DEFAULT_USER_SYSTEM_DIR, 'tokens'),
31
+ '.'].each do |path|
32
+ path = File.expand_path(File.join(path, token))
33
+ if File.file? path
34
+ token = path
35
+ break
36
+ end
37
+ end
38
+
39
+ unless File.file? token
40
+ raise "Unable to read the token at: #{token}"
41
+ end
42
+
43
+ token = Shuttle::Client::AuthToken.load_file(token) if String === token
44
+ token.connect if token
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,53 @@
1
+ autoload :Logger, 'logger'
2
+
3
+ module Shuttle
4
+ module ExceptionHandler
5
+
6
+ def self.setup(out=STDOUT, err=STDERR)
7
+ if String === out
8
+ @out = Logger.new(out, 'daily')
9
+ else
10
+ @out = Logger.new(out)
11
+ end
12
+ if String === err
13
+ @err = Logger.new(err, 'daily')
14
+ else
15
+ @err = Logger.new(err)
16
+ end
17
+ @out.level = Logger::DEBUG
18
+ @err.level = Logger::DEBUG
19
+ end
20
+
21
+ def self.err
22
+ @err
23
+ end
24
+
25
+ def self.out
26
+ @out
27
+ end
28
+
29
+ def logger
30
+ Shuttle::ExceptionHandler
31
+ end
32
+
33
+ def log(*args, &block)
34
+ logger.out.info(*args, &block)
35
+ end
36
+
37
+ def report
38
+ yield
39
+ rescue Exception => e
40
+ if StandardError === e
41
+ logger.err.error(e)
42
+ else
43
+ logger.err.fatal(e)
44
+ end
45
+ raise e
46
+ end
47
+
48
+ end
49
+ end
50
+
51
+ def FileUtils.fu_output_message(msg)
52
+ Shuttle::ExceptionHandler.out.info(msg)
53
+ end
@@ -0,0 +1,27 @@
1
+ require 'rubygems/specification'
2
+
3
+ module Gem # :nodoc:
4
+ class Specification
5
+ attribute :engine_dependencies, {}
6
+
7
+ def add_engine_dependency(name, options={})
8
+ name = name.to_s
9
+ add_runtime_dependency(name, *[options[:version]].compact)
10
+ @engine_dependencies ||= {}
11
+ @engine_dependencies[name] = options
12
+ end
13
+
14
+ alias_method :ruby_code_without_engines, :ruby_code # :nodoc:
15
+ def ruby_code(obj) # :nodoc:
16
+ return obj.inspect if Hash === obj
17
+ return ruby_code_without_engines(obj)
18
+ end
19
+
20
+ alias_method :to_ruby_without_engines, :to_ruby # :nodoc:
21
+ def to_ruby # :nodoc:
22
+ code = to_ruby_without_engines
23
+ code.gsub! /s\.engine_dependencies\s+[=]([^\n]+)/, 's.instance_variable_set(:@engine_dependencies,\1)'
24
+ code
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,32 @@
1
+ require 'thor'
2
+
3
+ class Thor
4
+ class << self
5
+ attr_accessor :real_namespace # :nodoc:
6
+ end
7
+ def self.namespace=(value)
8
+ Thor.real_namespace = value
9
+ end
10
+ def self.namespace
11
+ Thor.real_namespace
12
+ end
13
+ self.namespace = nil
14
+ end
15
+
16
+ module Thor::Util # :nodoc:
17
+ class << self
18
+ alias_method :old_constant_to_thor_path, :constant_to_thor_path
19
+ alias_method :old_constant_from_thor_path, :constant_from_thor_path
20
+ end
21
+
22
+ def self.constant_to_thor_path(*args) # :nodoc:
23
+ path = old_constant_to_thor_path(*args)
24
+ path.sub! /^#{Thor.namespace}:/, '' if Thor.namespace
25
+ path
26
+ end
27
+
28
+ def self.constant_from_thor_path(path) # :nodoc:
29
+ path = "#{Thor.namespace}:"+path if Thor.namespace
30
+ old_constant_from_thor_path(path)
31
+ end
32
+ end
@@ -0,0 +1,199 @@
1
+ require 'thread'
2
+
3
+ module Shuttle
4
+ class JobQueue
5
+ include DRbUndumped
6
+
7
+ # create a new job queue
8
+ def initialize
9
+ @immediated_jobs = Array.new
10
+ @canceled_jobs = Array.new
11
+ @job_queue = Array.new
12
+ @jobs = Hash.new
13
+ @mutex = Mutex.new
14
+ @next_id = 1
15
+
16
+ @worker = Thread.new(self) do |job_queue|
17
+ while job_queue.running? or job_queue.peek
18
+
19
+ if job = job_queue.peek
20
+ job.run(job_queue)
21
+ job_queue.delete(job.id)
22
+ else
23
+ sleep(1)
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+
30
+ # enqueue a new job with the given +name+, +options+ and +proc+
31
+ def enqueue(name, options={}, &proc)
32
+ @mutex.synchronize do
33
+ job = Job.new(@next_id, name, options, &proc)
34
+ @next_id += 1
35
+ @jobs[job.id] = job
36
+ @job_queue.push job.id
37
+ return job.id
38
+ end
39
+ end
40
+
41
+ # dequeue the next job of the queue.
42
+ def dequeue
43
+ @mutex.synchronize do
44
+ id = @job_queue.shift
45
+ return @jobs.delete(id) if id
46
+ end
47
+ end
48
+
49
+ # delete the job associated with the given +id+.
50
+ def delete(id)
51
+ @mutex.synchronize do
52
+ id = @job_queue.delete(id)
53
+ return @jobs.delete(id) if id
54
+ end
55
+ end
56
+
57
+ # peek at the next job in the queue
58
+ def peek
59
+ job = nil
60
+ @mutex.synchronize do
61
+ id = @job_queue.first
62
+ job = @jobs[id] if id
63
+ end
64
+ job
65
+ end
66
+
67
+ # get the size of the job queue
68
+ def size
69
+ @mutex.synchronize do
70
+ @job_queue.size
71
+ end
72
+ end
73
+
74
+ # cancel the job associated with the given +id+.
75
+ def cancel(id)
76
+ @mutex.synchronize do
77
+ id = @job_queue.delete(id)
78
+ if id
79
+ @jobs.delete(id)
80
+ @canceled_jobs.push(id)
81
+ end
82
+ end
83
+ end
84
+
85
+ # is the job associated with the given +id+ canceled.
86
+ def canceled?(id)
87
+ @mutex.synchronize do
88
+ return !@canceled_jobs.delete(id).nil?
89
+ end
90
+ end
91
+
92
+ # run the job associated with the given +id+ immediately.
93
+ def immediate(id)
94
+ @mutex.synchronize do
95
+ @immediated_jobs.push(id) if @jobs[id]
96
+ end
97
+ end
98
+
99
+ # should the job associated with the given +id+ be run immediately.
100
+ def immediated?(id)
101
+ @mutex.synchronize do
102
+ return !@immediated_jobs.delete(id).nil?
103
+ end
104
+ end
105
+
106
+ # join the worker thread
107
+ def join!
108
+ @worker.join
109
+ end
110
+
111
+ # wait until the queue is empty then stop the worker
112
+ def stop!
113
+ @mutex.synchronize do
114
+ @stopped = true
115
+ end
116
+ join!
117
+ end
118
+
119
+ # is the queue stopping or stopped?
120
+ def stopped?
121
+ @mutex.synchronize do
122
+ return !!@stopped
123
+ end
124
+ end
125
+
126
+ # is the queue running
127
+ def running?
128
+ !stopped?
129
+ end
130
+
131
+ # iterate through all the jobs on the queue
132
+ def each
133
+ @mutex.synchronize do
134
+ @job_queue.each do |id|
135
+ job = @jobs[id]
136
+ canceled = @canceled_jobs.include?(id)
137
+ immediated = @immediated_jobs.include?(id)
138
+
139
+ yield(job, canceled, immediated)
140
+ end
141
+ end
142
+ end
143
+
144
+ class Job
145
+ include DRbUndumped
146
+
147
+ attr_accessor :id, :name, :options, :proc
148
+
149
+ def initialize(id, name, options={}, &proc)
150
+ @id = id.to_i
151
+ @mutex = Mutex.new
152
+ @name, @options, @proc = name, options, proc
153
+ @run_at = Time.now + (options.delete(:delay) || 30)
154
+ end
155
+
156
+ def delay
157
+ @mutex.synchronize do
158
+ delay = @run_at - Time.now
159
+ delay = 0 if delay < 0
160
+ return delay
161
+ end
162
+ end
163
+
164
+ def running?
165
+ @mutex.synchronize do
166
+ return @running
167
+ end
168
+ end
169
+
170
+ def waiting?
171
+ @mutex.synchronize do
172
+ return @waiting
173
+ end
174
+ end
175
+
176
+ def run(job_queue)
177
+ Shuttle.report do
178
+ @waiting = true
179
+ immediated = canceled = false
180
+ Shuttle.log "waiting #{@run_at - Time.now}s."
181
+ until immediated or canceled or @run_at <= Time.now
182
+ sleep(1)
183
+ canceled = job_queue.canceled?(self.id)
184
+ immediated = job_queue.immediated?(self.id)
185
+ end
186
+
187
+ unless canceled
188
+ @waiting = false
189
+ @running = true
190
+ Shuttle.log("[queue]> #{@name}")
191
+ @proc.call(@options)
192
+ end
193
+ end
194
+ end
195
+
196
+ end
197
+
198
+ end
199
+ end
@@ -0,0 +1,35 @@
1
+
2
+ module Shuttle
3
+ class Satellite
4
+ module Actions
5
+
6
+ def add_engine(name, options={})
7
+ unless @engines.key? name
8
+ @engines[name] = options
9
+ true
10
+ else
11
+ false
12
+ end
13
+ end
14
+
15
+ def update_engine(name, options={})
16
+ if @engines.key? name
17
+ @engines[name] = options
18
+ true
19
+ else
20
+ false
21
+ end
22
+ end
23
+
24
+ def remove_engine(name)
25
+ if @engines.key? name
26
+ @engines.delete(name)
27
+ true
28
+ else
29
+ false
30
+ end
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,79 @@
1
+
2
+ autoload :TSort, 'tsort'
3
+
4
+ module Shuttle
5
+ class Satellite
6
+ class DependencyLoader
7
+
8
+ attr_reader :names, :specs, :engines
9
+
10
+ def self.load_for(engines)
11
+ dependency_loader = self.new(engines)
12
+ dependency_loader.add_dependecies!
13
+ dependency_loader.order_by_dependecies!
14
+
15
+ return dependency_loader
16
+ end
17
+
18
+ def initialize(engines)
19
+ specs = engines.collect do |k,r|
20
+ s = Gem.source_index.find_name(k, Gem::Requirement.new(r[:version] || ">= 0.0.0"))
21
+ s.last
22
+ end.compact
23
+
24
+ @names = specs.collect { |spec| spec.name }
25
+ @specs = specs.inject({}) { |h, spec| h[spec.name] = spec ; h }
26
+ @engines = engines
27
+ end
28
+
29
+ def add_dependecies!
30
+ @specs.values.each do |spec|
31
+ add_dependecies_for spec
32
+ end
33
+ end
34
+
35
+ def order_by_dependecies!
36
+ @names = tsort
37
+ end
38
+
39
+ def each
40
+ @names.each do |name|
41
+ yield(@specs[name])
42
+ end
43
+ end
44
+
45
+ def reverse_each
46
+ @names.reverse.each do |name|
47
+ yield(@specs[name])
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ include TSort
54
+
55
+ def add_dependecies_for(spec)
56
+ engine_dependencies = spec.engine_dependencies || {}
57
+ engine_dependencies.each do |name, options|
58
+ gems = Gem.source_index.find_name(name, [options[:version]].compact)
59
+ next unless gem = gems.last
60
+ next if @names.include?(name) and gem.version <= @specs[gem.name].version
61
+ @specs[gem.name] = gem
62
+ @names.push(gem.name)
63
+ add_dependecies_for(gem)
64
+ end
65
+ @engines = @engines.merge(engine_dependencies)
66
+ end
67
+
68
+ def tsort_each_node(&block)
69
+ @names.each(&block)
70
+ end
71
+
72
+ def tsort_each_child(node, &block)
73
+ engine_dependencies = @specs[node].engine_dependencies || {}
74
+ engine_dependencies.keys.each(&block)
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,50 @@
1
+ require 'yaml'
2
+
3
+ module Shuttle
4
+ class Satellite
5
+ module Persistence
6
+
7
+ def self.included(base)
8
+ base.extend Shuttle::Satellite::Persistence::ClassMethods
9
+ end
10
+
11
+ module ClassMethods
12
+
13
+ def load(data)
14
+ Shuttle::Satellite.new(YAML.load(data))
15
+ end
16
+
17
+ def load_file(path)
18
+ return nil unless File.exist?(path)
19
+ Shuttle::Satellite.new(YAML.load_file(path))
20
+ end
21
+
22
+ end
23
+
24
+ def dump(io=nil)
25
+ data = {}
26
+
27
+ private_vars = %w( basedomain subdomain )
28
+ instance_variables.each do |ivar_name|
29
+ ivar_name = ivar_name.to_s
30
+ ivar_name =~ /^@(.+)$/
31
+ name = $1
32
+ unless private_vars.include? name
33
+ data[name] = instance_variable_get(ivar_name.to_sym)
34
+ end
35
+ end
36
+
37
+ if io
38
+ io.write YAML.dump(data)
39
+ else
40
+ YAML.dump(data)
41
+ end
42
+ end
43
+
44
+ def dump_file(path)
45
+ File.open(path, 'w+') { |f| dump(f) }
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,49 @@
1
+
2
+ module Shuttle
3
+ class Satellite
4
+
5
+ autoload :Actions, File.dirname(__FILE__)+'/satellite/actions'
6
+ autoload :Persistence, File.dirname(__FILE__)+'/satellite/persistence'
7
+ autoload :DependencyLoader, File.dirname(__FILE__)+'/satellite/dependency_loader'
8
+
9
+ include Shuttle::Satellite::Actions
10
+ include Shuttle::Satellite::Persistence
11
+
12
+ attr_reader :domain, :engines
13
+
14
+ def initialize(domain)
15
+ if Hash === domain
16
+ domain.each do |name, value|
17
+ instance_variable_set("@#{name}".to_sym, value)
18
+ end
19
+ else
20
+ @domain = domain
21
+ @engines = {}
22
+ end
23
+ @domain.gsub! /^www\./, ''
24
+ end
25
+
26
+ def basedomain
27
+ unless @basedomain
28
+ parts = self.domain.split('.')
29
+ parts = parts[-2..-1]
30
+ @basedomain = parts.join('.')
31
+ end
32
+ @basedomain
33
+ end
34
+
35
+ def subdomain
36
+ unless @subdomain
37
+ parts = self.domain.split('.')
38
+ parts = parts[0..-3]
39
+ @subdomain = parts.join('.')
40
+ end
41
+ @subdomain unless @subdomain == ''
42
+ end
43
+
44
+ def subdomain?
45
+ !self.subdomain.nil?
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,85 @@
1
+ require 'uri'
2
+
3
+ module Shuttle
4
+ class Server
5
+
6
+ module Daemon # :nodoc:
7
+
8
+ def self.included(base)
9
+ base.extend Shuttle::Server::Daemon::ClassMethods
10
+ end
11
+
12
+ # all a daemon needs to run
13
+ module ClassMethods
14
+
15
+ # construct a Shuttle uri from a DRb uri
16
+ def construct_uri(uri)
17
+ uri = URI.parse(uri)
18
+ uri.scheme = ( Shuttle.system.use_ssl? ? 'ssl+shuttle' : 'shuttle')
19
+ uri.to_s
20
+ end
21
+
22
+ # stop teh server
23
+ def stop
24
+ Shuttle.client.stop_server
25
+ end
26
+
27
+ # start the server
28
+ def start
29
+ start_with_failsafe
30
+ end
31
+
32
+ # start the failsafe runner.
33
+ def start_with_failsafe
34
+ stop_server = false
35
+ wait_before_start = 0
36
+ retries = 0
37
+ until stop_server
38
+ if wait_before_start > 0
39
+ sleep(wait_before_start)
40
+ wait_before_start = 0
41
+ end
42
+
43
+ pid = Process.fork { self.run_server }
44
+ Process.waitpid(pid, 0)
45
+ case $?.exitstatus
46
+ when Shuttle::STOP_STATUS
47
+ stop_server = true
48
+ when Shuttle::RESTART_STATUS
49
+ wait_before_start = 2
50
+ when Shuttle::RELOAD_STATUS
51
+ stop_server = true
52
+ Shuttle.system.run(%{sleep 2 ; #{Shuttle::BIN_PATH} #{ORIGINAL_ARGV.join(' ')}})
53
+ else
54
+ retries += 1
55
+ stop_server = true if retries >= 3
56
+ end
57
+ end
58
+
59
+ path = Shuttle.system.path('Server.pid')
60
+ File.unlink(path) if File.file?(path)
61
+ end
62
+
63
+ # start the actual DRb server
64
+ def run_server
65
+ Dir.chdir(Shuttle.system.root)
66
+
67
+ Shuttle.log "Server started"
68
+ DRb.start_service "druby://localhost:5000", self.proxy, self.options_for_server
69
+ Shuttle.log "listening at #{self.construct_uri("druby://#{`hostname`.strip}:5000")}"
70
+ make_client_cert_public!
71
+
72
+ at_exit do
73
+ unless $task_child
74
+ Shuttle.system.queue.stop!
75
+ Shuttle.log "Server stopped"
76
+ end
77
+ end
78
+
79
+ DRb.thread.join
80
+ end
81
+
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,25 @@
1
+
2
+ module Shuttle
3
+ class Server
4
+ # the proxy object hides all the server internals from the clients.
5
+ class Proxy
6
+
7
+ def initialize(server)
8
+ @server = server
9
+ end
10
+
11
+ def self.allow(*methods)
12
+ methods.each do |method|
13
+ module_eval %{ def #{method}(*args,&block) ; @server.#{method}(*args,&block) ; end }
14
+ end
15
+ end
16
+
17
+ allow :stop_server, :restart_server, :reload_server, :server_version, :update_server, :install_satellite, :uninstall_satellite, :install_engine, :update_engine, :uninstall_engine, :satellites, :queued_jobs, :cancel_job, :immediate_job
18
+
19
+ class << self
20
+ undef_method :allow
21
+ end
22
+
23
+ end
24
+ end
25
+ end