yad 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/.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
|