simonmenke-shuttle 0.1.07

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