yad 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/LICENSE +20 -0
- data/README.rdoc +110 -0
- data/Rakefile +87 -0
- data/VERSION.yml +4 -0
- data/doc/faq.rdoc +9 -0
- data/features/step_definitions/yad_steps.rb +0 -0
- data/features/support/env.rb +9 -0
- data/features/yad.feature +9 -0
- data/lib/ext/rake.rb +66 -0
- data/lib/ext/rake/remote_task.rb +521 -0
- data/lib/ext/string.rb +6 -0
- data/lib/yad.rb +46 -0
- data/lib/yad/app/passenger.rb +28 -0
- data/lib/yad/core.rb +199 -0
- data/lib/yad/db/rails.rb +62 -0
- data/lib/yad/framework/rails.rb +64 -0
- data/lib/yad/maintenance/shared_system.rb +39 -0
- data/lib/yad/scm/git.rb +62 -0
- data/lib/yad/web/apache.rb +62 -0
- data/spec/ext/rake/remote_task_spec.rb +394 -0
- data/spec/spec_helper.rb +79 -0
- data/spec/yad/app/passenger_spec.rb +10 -0
- data/spec/yad/core_spec.rb +69 -0
- data/spec/yad/db/rails_spec.rb +15 -0
- data/spec/yad/framework/rails_spec.rb +25 -0
- data/spec/yad/maintenance/shared_system_spec.rb +15 -0
- data/spec/yad/scm/git_spec.rb +30 -0
- data/spec/yad/web/apache_spec.rb +25 -0
- data/yad.gemspec +87 -0
- metadata +132 -0
data/lib/ext/string.rb
ADDED
data/lib/yad.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'rake'
|
6
|
+
require 'open4'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
require "ext/rake/remote_task"
|
10
|
+
require "ext/rake"
|
11
|
+
require "ext/string"
|
12
|
+
|
13
|
+
require "yad/core"
|
14
|
+
Yad::Core.define_tasks
|
15
|
+
|
16
|
+
require "yad/web/apache"
|
17
|
+
|
18
|
+
require "yad/maintenance/shared_system"
|
19
|
+
|
20
|
+
require "yad/app/passenger"
|
21
|
+
|
22
|
+
require "yad/framework/rails"
|
23
|
+
|
24
|
+
require "yad/db/rails"
|
25
|
+
|
26
|
+
require "yad/scm/git"
|
27
|
+
|
28
|
+
module Yad
|
29
|
+
# :stopdoc:
|
30
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
31
|
+
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
32
|
+
|
33
|
+
VERSION = if File.exist?(File.join(PATH, 'VERSION.yml'))
|
34
|
+
config = YAML.load_file(File.join(PATH, 'VERSION.yml'))
|
35
|
+
"#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
36
|
+
else
|
37
|
+
"0.0.0"
|
38
|
+
end
|
39
|
+
# :startdoc:
|
40
|
+
|
41
|
+
# Returns the version string for the library.
|
42
|
+
def self.version
|
43
|
+
VERSION
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Yad
|
2
|
+
module App
|
3
|
+
class Passenger
|
4
|
+
def self.build_start_command(release_directory)
|
5
|
+
"touch #{release_directory}/tmp/restart.txt"
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.define_tasks
|
9
|
+
return if @tasks_already_defined
|
10
|
+
@tasks_already_defined = true
|
11
|
+
namespace :yad do
|
12
|
+
namespace :app do
|
13
|
+
|
14
|
+
desc "Starts the application server"
|
15
|
+
remote_task :start, :roles => :app do
|
16
|
+
cmd = Yad::App::Passenger.build_start_command(current_path)
|
17
|
+
run(cmd)
|
18
|
+
puts("Passenger app restarted on #{target_host}")
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end # class Passenger
|
26
|
+
|
27
|
+
end # module App
|
28
|
+
end # module Yad
|
data/lib/yad/core.rb
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
module Yad
|
2
|
+
class Core
|
3
|
+
def self.build_setup_command(deployment_directory, options = {})
|
4
|
+
default_options = { :umask => '02',
|
5
|
+
:shared_subdirectories => [] }
|
6
|
+
|
7
|
+
options = default_options.merge(options)
|
8
|
+
dirs = [File.join(deployment_directory, "releases"),
|
9
|
+
File.join(deployment_directory, "scm"),
|
10
|
+
File.join(deployment_directory, "shared"),
|
11
|
+
File.join(deployment_directory, "shared", "config")
|
12
|
+
]
|
13
|
+
options[:shared_subdirectories].each do |subdirectory|
|
14
|
+
dirs << File.join(deployment_directory, "shared", subdirectory) unless subdirectory == 'config'
|
15
|
+
end
|
16
|
+
"umask #{options[:umask]} && mkdir -p #{dirs.join(' ')}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.build_update_source_code_command(checkout_command, export_command, new_release_directory)
|
20
|
+
[checkout_command,
|
21
|
+
export_command,
|
22
|
+
"chmod -R g+w #{new_release_directory}"
|
23
|
+
].join(" && ")
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.build_remove_directory_command(directory_to_remove)
|
27
|
+
"rm -rf #{directory_to_remove}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.build_files_array(delimited_file_names)
|
31
|
+
return [] unless delimited_file_names
|
32
|
+
files = delimited_file_names.split(",").map { |f| f.strip }.flatten
|
33
|
+
files = files.reject { |f| File.directory?(f) || File.basename(f)[0] == ?. }
|
34
|
+
files
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.build_update_symlink_command(current_release_symlink, new_release_directory)
|
38
|
+
"rm -f #{current_release_symlink} && ln -s #{new_release_directory} #{current_release_symlink}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.build_revision_log_command(timestamp, inline_revision_identifier_command, new_release_directory, deployment_directory)
|
42
|
+
"echo #{timestamp} $USER #{inline_revision_identifier_command} #{File.basename(new_release_directory)} >> #{deployment_directory}/revisions.log"
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.build_cleanup_command(max_release_count, releases_directory, all_releases)
|
46
|
+
if all_releases.length <= max_release_count
|
47
|
+
"echo keeping all releases"
|
48
|
+
else
|
49
|
+
releases_to_remove = (all_releases - all_releases.last(max_release_count)).map { |release| File.join(releases_directory, release) }.join(" ")
|
50
|
+
"rm -rf #{releases_to_remove}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.build_rollback_command(current_release_symlink, previous_release_directory, latest_release_directory)
|
55
|
+
if previous_release_directory.nil? || previous_release_directory == latest_release_directory || latest_release_directory.nil?
|
56
|
+
"echo no previous release for rollback"
|
57
|
+
else
|
58
|
+
"rm -f #{current_release_symlink}; ln -s #{previous_release_directory} #{current_release_symlink} && rm -rf #{latest_release_directory}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.define_tasks
|
63
|
+
return if @tasks_already_defined
|
64
|
+
@tasks_already_defined = true
|
65
|
+
namespace :yad do
|
66
|
+
|
67
|
+
desc "Prepares one or more servers for deployment"
|
68
|
+
remote_task :setup do
|
69
|
+
Rake::Task['yad:core:setup_deployment'].invoke
|
70
|
+
Rake::Task.invoke_if_defined('yad:framework:setup', :framework)
|
71
|
+
end
|
72
|
+
|
73
|
+
desc "Copies files to the shared/config directory using FILES=a,b,c (e.g. rake vlad:upload_config_files FILES=config/database.yml,config/maintenance.html)"
|
74
|
+
remote_task :upload_config_files do
|
75
|
+
files = Yad::Core.build_files_array(ENV["FILES"])
|
76
|
+
if files.empty?
|
77
|
+
puts("Please specify at least one file to upload (via the FILES environment variable)")
|
78
|
+
else
|
79
|
+
files.each do |file|
|
80
|
+
rsync(file, "#{target_host}:#{File.join(shared_config_path, File.basename(file))}")
|
81
|
+
end
|
82
|
+
puts("uploaded #{files.inspect} to #{target_host}")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
desc "Creates the database for the application"
|
87
|
+
remote_task :create_db, :roles => :app do
|
88
|
+
Rake::Task.invoke_if_defined('yad:db:create', :db, "Please specify the database delegate via the :db variable")
|
89
|
+
end
|
90
|
+
|
91
|
+
desc "Updates the application servers to the latest version"
|
92
|
+
remote_task :update, :roles => :app do
|
93
|
+
Rake::Task.invoke_if_defined('yad:scm:update', :scm)
|
94
|
+
Rake::Task.invoke_if_defined('yad:framework:update', :framework)
|
95
|
+
Rake::Task['yad:core:update_symlink'].invoke
|
96
|
+
end
|
97
|
+
|
98
|
+
desc "Runs migrations for the database for the application"
|
99
|
+
remote_task :migrate_db, :roles => :app do
|
100
|
+
Rake::Task.invoke_if_defined('yad:db:migrate', :db, "Please specify the database delegate via the :db variable")
|
101
|
+
end
|
102
|
+
|
103
|
+
desc "Starts the application server"
|
104
|
+
remote_task :start_app, :roles => :app do
|
105
|
+
Rake::Task.invoke_if_defined('yad:app:start', :app, "Please specify the app delegate via the :app variable")
|
106
|
+
end
|
107
|
+
|
108
|
+
desc "Starts the web server"
|
109
|
+
remote_task :start_web, :roles => :web do
|
110
|
+
Rake::Task.invoke_if_defined('yad:web:start', :web, "Please specify the web delegate via the :web variable")
|
111
|
+
end
|
112
|
+
|
113
|
+
"Stops the web server"
|
114
|
+
remote_task :stop_web, :roles => :web do
|
115
|
+
Rake::Task.invoke_if_defined('yad:web:stop', :web, "Please specify the web delegate via the :web variable")
|
116
|
+
end
|
117
|
+
|
118
|
+
desc "Stops the application server"
|
119
|
+
remote_task :stop_app, :roles => :app do
|
120
|
+
Rake::Task.invoke_if_defined('yad:app:stop', :app, "Please specify the app delegate via the :app variable")
|
121
|
+
end
|
122
|
+
|
123
|
+
desc "Turns on the maintenance page for the application"
|
124
|
+
remote_task :turn_on_maintenance, :roles => :web do
|
125
|
+
Rake::Task.invoke_if_defined('yad:maintenance:turn_on', :maintenance, "Please specify the maintenance delegate via the :maintenance variable")
|
126
|
+
end
|
127
|
+
|
128
|
+
desc "Turns off the maintenance page for the application"
|
129
|
+
remote_task :turn_off_maintenance, :roles => :web do
|
130
|
+
Rake::Task.invoke_if_defined('yad:maintenance:turn_off', :maintenance, "Please specify the maintenance delegate via the :maintenance variable")
|
131
|
+
end
|
132
|
+
|
133
|
+
desc "Rolls back to the previous release, but DOES NOT restart the application"
|
134
|
+
remote_task :rollback do
|
135
|
+
cmd = Yad::Core.build_rollback_command(current_path, previous_release, latest_release)
|
136
|
+
if cmd.length == 0
|
137
|
+
puts("could not rollback the code because there is no previous release")
|
138
|
+
else
|
139
|
+
run(cmd)
|
140
|
+
puts("rolled back to #{File.basename(previous_release)} on #{target_host}")
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
desc "Cleans up old releases"
|
145
|
+
remote_task :cleanup do
|
146
|
+
cmd = Yad::Core.build_cleanup_command(keep_releases, releases_path, releases)
|
147
|
+
run(cmd)
|
148
|
+
puts("old releases cleaned up on #{target_host}")
|
149
|
+
end
|
150
|
+
|
151
|
+
remote_task :invoke do
|
152
|
+
# TODO:
|
153
|
+
end
|
154
|
+
|
155
|
+
namespace :core do
|
156
|
+
|
157
|
+
remote_task :setup_deployment, :roles => :app do
|
158
|
+
options = Rake::RemoteTask.get_options_hash(:umask, :shared_subdirectories)
|
159
|
+
cmd = Yad::Core.build_setup_command(deploy_to, options)
|
160
|
+
run(cmd)
|
161
|
+
puts("Yad set up on #{target_host}")
|
162
|
+
end
|
163
|
+
|
164
|
+
remote_task :update_symlink, :roles => :app do
|
165
|
+
begin
|
166
|
+
symlinked = false
|
167
|
+
cmd = Yad::Core.build_update_symlink_command(current_path, release_path)
|
168
|
+
run(cmd)
|
169
|
+
puts("'current' symlink updated on #{target_host}")
|
170
|
+
symlinked = true
|
171
|
+
scm_value = Rake::RemoteTask.fetch(:scm, false)
|
172
|
+
if scm_value
|
173
|
+
scm_class = eval("Yad::Scm::#{scm_value.to_s.classify}")
|
174
|
+
options = Rake::RemoteTask.get_options_hash(:revision)
|
175
|
+
inline_command = scm_class.build_inline_revision_identifier_command(scm_path, options)
|
176
|
+
else
|
177
|
+
inline_command = 'none'
|
178
|
+
end
|
179
|
+
cmd = Yad::Core.build_revision_log_command(Time.now.utc.strftime("%Y%m%d%H%M.%S"), inline_command, release_path, deploy_to)
|
180
|
+
run(cmd)
|
181
|
+
rescue => e
|
182
|
+
if releases.length > 1 && symlinked then
|
183
|
+
cmd = Yad::Core.build_update_symlink_command(current_path, previous_release)
|
184
|
+
run(cmd)
|
185
|
+
end
|
186
|
+
cmd = Yad::Core.build_remove_directory_command(release_path)
|
187
|
+
run(cmd)
|
188
|
+
raise e
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
end # class Core
|
198
|
+
|
199
|
+
end # module Yad
|
data/lib/yad/db/rails.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
module Yad
|
2
|
+
module Db
|
3
|
+
class Rails
|
4
|
+
def self.build_create_db_command(release_directory, options = {})
|
5
|
+
default_options = { :app_env => 'production',
|
6
|
+
:rake_cmd => 'rake'
|
7
|
+
}
|
8
|
+
options = default_options.merge(options)
|
9
|
+
"cd #{release_directory}; #{options[:rake_cmd]} RAILS_ENV=#{options[:app_env]} db:create"
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.build_migrate_db_command(release_directory, options = {})
|
13
|
+
default_options = { :app_env => 'production',
|
14
|
+
:rake_cmd => 'rake',
|
15
|
+
:migrate_args => '',
|
16
|
+
}
|
17
|
+
options = default_options.merge(options)
|
18
|
+
"cd #{release_directory}; #{options[:rake_cmd]} RAILS_ENV=#{options[:app_env]} db:migrate #{options[:migrate_args]}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.define_tasks
|
22
|
+
return if @tasks_already_defined
|
23
|
+
@tasks_already_defined = true
|
24
|
+
namespace :yad do
|
25
|
+
namespace :db do
|
26
|
+
|
27
|
+
desc "Creates the database for the application"
|
28
|
+
remote_task :create, :roles => :app do
|
29
|
+
break unless target_host == Rake::RemoteTask.hosts_for(:app).first
|
30
|
+
options = Rake::RemoteTask.get_options_hash(:app_env, :rake_cmd)
|
31
|
+
cmd = Yad::Db::Rails.build_create_db_command(current_path, options)
|
32
|
+
run(cmd)
|
33
|
+
puts("database created")
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "Runs migrations on the database"
|
37
|
+
remote_task :migrate, :roles => :app do
|
38
|
+
break unless target_host == Rake::RemoteTask.hosts_for(:app).first
|
39
|
+
options = Rake::RemoteTask.get_options_hash(:app_env, :rake_cmd, :migrate_args)
|
40
|
+
target = Rake::RemoteTask.fetch(:migrate_target, false)
|
41
|
+
if target
|
42
|
+
target_directory = case target.to_sym
|
43
|
+
when :current then current_path
|
44
|
+
when :latest then latest_release
|
45
|
+
else raise ArgumentError, "unknown migration target #{target.inspect}"
|
46
|
+
end
|
47
|
+
else
|
48
|
+
target_directory = latest_release
|
49
|
+
end
|
50
|
+
cmd = Yad::Db::Rails.build_migrate_db_command(target_directory, options)
|
51
|
+
run(cmd)
|
52
|
+
puts("database migrations completed")
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end # class Rails
|
60
|
+
|
61
|
+
end # module Db
|
62
|
+
end # module Yad
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Yad
|
2
|
+
module Framework
|
3
|
+
class Rails
|
4
|
+
def self.build_setup_command(shared_directory, options = {})
|
5
|
+
default_options = { :umask => '02' }
|
6
|
+
options = default_options.merge(options)
|
7
|
+
dirs = [File.join(shared_directory, "log"),
|
8
|
+
File.join(shared_directory, "pids"),
|
9
|
+
File.join(shared_directory, "system")
|
10
|
+
]
|
11
|
+
"umask #{options[:umask]} && mkdir -p #{dirs.join(' ')}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.build_update_command(new_release_directory, shared_directory, options = {})
|
15
|
+
default_options = { :app_env => 'production',
|
16
|
+
:framework_update_db_config_via => 'none' }
|
17
|
+
options = default_options.merge(options)
|
18
|
+
commands = ["rm -rf #{new_release_directory}/log #{new_release_directory}/public/system #{new_release_directory}/tmp/pids",
|
19
|
+
"mkdir -p #{new_release_directory}/tmp",
|
20
|
+
"ln -s #{shared_directory}/log #{new_release_directory}/log",
|
21
|
+
"ln -s #{shared_directory}/pids #{new_release_directory}/tmp/pids",
|
22
|
+
"ln -s #{shared_directory}/system #{new_release_directory}/public/system"
|
23
|
+
]
|
24
|
+
|
25
|
+
case options[:framework_update_db_config_via].to_sym
|
26
|
+
when :copy
|
27
|
+
commands << "cp -f #{new_release_directory}/config/database_#{options[:app_env]}.yml #{new_release_directory}/config/database.yml"
|
28
|
+
when :symlink
|
29
|
+
commands << "ln -nfs #{shared_directory}/config/database.yml #{new_release_directory}/config/database.yml"
|
30
|
+
end
|
31
|
+
|
32
|
+
commands.join(" && ")
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.define_tasks
|
36
|
+
return if @tasks_already_defined
|
37
|
+
@tasks_already_defined = true
|
38
|
+
namespace :yad do
|
39
|
+
namespace :framework do
|
40
|
+
|
41
|
+
desc "Performs additional setup needed for the framework"
|
42
|
+
remote_task :setup, :roles => :app do
|
43
|
+
options = Rake::RemoteTask.get_options_hash(:umask)
|
44
|
+
cmd = Yad::Framework::Rails.build_setup_command(shared_path, options)
|
45
|
+
run(cmd)
|
46
|
+
puts("Rails set up on #{target_host}")
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "Updates the framework configuration and working directories after a new release has been exported"
|
50
|
+
remote_task :update, :roles => :app do
|
51
|
+
options = Rake::RemoteTask.get_options_hash(:app_env, :framework_update_db_config_via)
|
52
|
+
cmd = Yad::Framework::Rails.build_update_command(release_path, shared_path, options)
|
53
|
+
run(cmd)
|
54
|
+
puts("Rails shared paths and files updated on #{target_host}")
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end # class Rails
|
62
|
+
|
63
|
+
end # module Framework
|
64
|
+
end # module Yad
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Yad
|
2
|
+
module Maintenance
|
3
|
+
class SharedSystem
|
4
|
+
def self.build_turn_on_command(shared_directory, options = {})
|
5
|
+
"cp -f #{shared_directory}/config/maintenance.html #{shared_directory}/system/"
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.build_turn_off_command(shared_directory, options = {})
|
9
|
+
"rm -f #{shared_directory}/system/maintenance.html"
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.define_tasks
|
13
|
+
return if @tasks_already_defined
|
14
|
+
@tasks_already_defined = true
|
15
|
+
namespace :yad do
|
16
|
+
namespace :maintenance do
|
17
|
+
|
18
|
+
desc "Turns on the maintenance page for the application"
|
19
|
+
remote_task :turn_on, :roles => :web do
|
20
|
+
cmd = Yad::Maintenance::SharedSystem.build_turn_on_command(shared_path)
|
21
|
+
run(cmd)
|
22
|
+
puts("maintenance page turned on for #{target_host}")
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "Turns off the maintenance page for the application"
|
26
|
+
remote_task :turn_off, :roles => :web do
|
27
|
+
cmd = Yad::Maintenance::SharedSystem.build_turn_off_command(shared_path)
|
28
|
+
run(cmd)
|
29
|
+
puts("maintenance page turned off for #{target_host}")
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end # class Rails
|
37
|
+
|
38
|
+
end # module Maintenance
|
39
|
+
end # module Yad
|