gabrielg-vlad 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/vlad.rb ADDED
@@ -0,0 +1,91 @@
1
+ require 'rubygems'
2
+ require 'thread'
3
+ require 'rake_remote_task'
4
+
5
+ $TESTING ||= false
6
+
7
+ ##
8
+ # Vlad the Deployer - Pragmatic application deployment automation, without mercy.
9
+ #
10
+ # Please read doco/getting_started.txt or http://rubyhitsquad.com/
11
+ #
12
+ # === Basic scenario:
13
+ #
14
+ # 1. rake vlad:setup (first time only)
15
+ # 2. rake vlad:update
16
+ # 3. rake vlad:migrate (optional)
17
+ # 4. rake vlad:start
18
+
19
+ module Vlad
20
+
21
+ ##
22
+ # This is the version of Vlad you are running.
23
+ VERSION = '1.2.0'
24
+
25
+ ##
26
+ # Base error class for all Vlad errors.
27
+ class Error < RuntimeError; end
28
+
29
+ ##
30
+ # Raised when you have incorrectly configured Vlad.
31
+ class ConfigurationError < Error; end
32
+
33
+ ##
34
+ # Raised when a remote command fails.
35
+ class CommandFailedError < Error; end
36
+
37
+ ##
38
+ # Raised when an environment variable hasn't been set.
39
+ class FetchError < Error; end
40
+
41
+ ##
42
+ # Loads tasks file +tasks_file+ and various recipe styles as a hash
43
+ # of category/style pairs. Recipes default to:
44
+ #
45
+ # :app => :mongrel
46
+ # :config => 'config/deploy.rb'
47
+ # :core => :core
48
+ # :scm => :subversion
49
+ # :web => :apache
50
+ #
51
+ # You can override individual values and/or set to nil to
52
+ # deactivate. :config will get loaded last to ensure that user
53
+ # variables override default values.
54
+ #
55
+ # And by all means, feel free to skip this entirely if it doesn't
56
+ # fit for you. All it does is a fancy-pants require. Require
57
+ # whatever files you need as you see fit straight from your
58
+ # Rakefile. YAY for simple and clean!
59
+ def self.load options = {}
60
+ options = {:config => options} if String === options
61
+ order = [:app, :config, :core, :scm, :web]
62
+ order += options.keys - order
63
+
64
+ recipes = {
65
+ :app => :mongrel,
66
+ :config => 'config/deploy.rb',
67
+ :core => :core,
68
+ :scm => :subversion,
69
+ :web => :apache,
70
+ }.merge(options)
71
+
72
+ order.each do |flavor|
73
+ recipe = recipes[flavor]
74
+ next if recipe.nil? or flavor == :config
75
+ require "vlad/#{recipe}"
76
+ end
77
+
78
+ Kernel.load recipes[:config]
79
+ Kernel.load "config/deploy_#{ENV['to']}.rb" if ENV['to']
80
+ end
81
+ end
82
+
83
+ class String #:nodoc:
84
+ def cleanup
85
+ if ENV['FULL'] then
86
+ gsub(/\s+/, ' ').strip
87
+ else
88
+ self[/\A.*?\./]
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,37 @@
1
+ require 'vlad'
2
+
3
+ namespace :vlad do
4
+ ##
5
+ # Apache web server
6
+
7
+ set :web_command, "apachectl"
8
+
9
+ desc "(Re)Start the web servers"
10
+
11
+ remote_task :start_web, :roles => :web do
12
+ run "#{web_command} restart"
13
+ end
14
+
15
+ desc "Stop the web servers"
16
+
17
+ remote_task :stop_web, :roles => :web do
18
+ run "#{web_command} stop"
19
+ end
20
+
21
+ ##
22
+ # Everything HTTP.
23
+
24
+ desc "(Re)Start the web and app servers"
25
+
26
+ remote_task :start do
27
+ Rake::Task['vlad:start_app'].invoke
28
+ Rake::Task['vlad:start_web'].invoke
29
+ end
30
+
31
+ desc "Stop the web and app servers"
32
+
33
+ remote_task :stop do
34
+ Rake::Task['vlad:stop_app'].invoke
35
+ Rake::Task['vlad:stop_web'].invoke
36
+ end
37
+ end
data/lib/vlad/core.rb ADDED
@@ -0,0 +1,181 @@
1
+ require 'vlad'
2
+
3
+ ##
4
+ # used by update, out here so we can ensure all threads have the same value
5
+ def now
6
+ @now ||= Time.now.utc.strftime("%Y%m%d%H%M.%S")
7
+ end
8
+
9
+ namespace :vlad do
10
+ desc "Show the vlad setup. This is all the default variables for vlad
11
+ tasks.".cleanup
12
+
13
+ task :debug do
14
+ require 'yaml'
15
+
16
+ # force them into values
17
+ Rake::RemoteTask.env.keys.each do |key|
18
+ next if key =~ /_release|releases|sudo_password/
19
+ Rake::RemoteTask.fetch key
20
+ end
21
+
22
+ puts "# Environment:"
23
+ puts
24
+ y Rake::RemoteTask.env
25
+ puts "# Roles:"
26
+ y Rake::RemoteTask.roles
27
+ end
28
+
29
+ desc "Setup your servers. Before you can use any of the deployment
30
+ tasks with your project, you will need to make sure all of your
31
+ servers have been prepared with 'rake vlad:setup'. It is safe to
32
+ run this task on servers that have already been set up; it will
33
+ not destroy any deployed revisions or data.".cleanup
34
+
35
+ task :setup do
36
+ Rake::Task['vlad:setup_app'].invoke
37
+ end
38
+
39
+ desc "Prepares application servers for deployment.".cleanup
40
+
41
+ remote_task :setup_app, :roles => :app do
42
+ dirs = [deploy_to, releases_path, scm_path, shared_path]
43
+ dirs += %w(system log pids).map { |d| File.join(shared_path, d) }
44
+ run "umask #{umask} && mkdir -p #{dirs.join(' ')}"
45
+ end
46
+
47
+ desc "Updates your application server to the latest revision. Syncs
48
+ a copy of the repository, exports it as the latest release, fixes
49
+ up your symlinks, symlinks the latest revision to current and logs
50
+ the update.".cleanup
51
+
52
+ remote_task :update, :roles => :app do
53
+ symlink = false
54
+ begin
55
+ run [ "cd #{scm_path}",
56
+ "#{source.checkout revision, '.'}",
57
+ "#{source.export ".", release_path}",
58
+ "chmod -R g+w #{latest_release}",
59
+ "rm -rf #{latest_release}/log #{latest_release}/public/system #{latest_release}/tmp/pids",
60
+ "mkdir -p #{latest_release}/db #{latest_release}/tmp"
61
+ ].join(" && ")
62
+ Rake::Task['vlad:update_symlinks'].invoke
63
+
64
+ symlink = true
65
+ run "rm -f #{current_path} && ln -s #{latest_release} #{current_path}"
66
+
67
+ run "echo #{now} $USER #{revision} #{File.basename release_path} >> #{deploy_to}/revisions.log"
68
+ rescue => e
69
+ run "rm -f #{current_path} && ln -s #{previous_release} #{current_path}" if
70
+ symlink
71
+ run "rm -rf #{release_path}"
72
+ raise e
73
+ end
74
+ end
75
+
76
+ desc "Updates the symlinks for shared paths".cleanup
77
+
78
+ remote_task :update_symlinks, :roles => :app do
79
+ run [ "ln -s #{shared_path}/log #{latest_release}/log",
80
+ "ln -s #{shared_path}/system #{latest_release}/public/system",
81
+ "ln -s #{shared_path}/pids #{latest_release}/tmp/pids" ].join(" && ")
82
+ end
83
+
84
+ desc "Run the migrate rake task for the the app. By default this is run in
85
+ the latest app directory. You can run migrations for the current app
86
+ directory by setting :migrate_target to :current. Additional environment
87
+ variables can be passed to rake via the migrate_env variable.".cleanup
88
+
89
+ # No application files are on the DB machine, also migrations should only be
90
+ # run once.
91
+ remote_task :migrate, :roles => :app do
92
+ break unless target_host == Rake::RemoteTask.hosts_for(:app).first
93
+
94
+ directory = case migrate_target.to_sym
95
+ when :current then current_path
96
+ when :latest then current_release
97
+ else raise ArgumentError, "unknown migration target #{migrate_target.inspect}"
98
+ end
99
+
100
+ run "cd #{current_path}; #{rake_cmd} RAILS_ENV=#{rails_env} db:migrate #{migrate_args}"
101
+ end
102
+
103
+ desc "Invoke a single command on every remote server. This is useful for
104
+ performing one-off commands that may not require a full task to be written
105
+ for them. Simply specify the command to execute via the COMMAND
106
+ environment variable. To execute the command only on certain roles,
107
+ specify the ROLES environment variable as a comma-delimited list of role
108
+ names.
109
+
110
+ $ rake vlad:invoke COMMAND='uptime'".cleanup
111
+
112
+ remote_task :invoke do
113
+ command = ENV["COMMAND"]
114
+ abort "Please specify a command to execute on the remote servers (via the COMMAND environment variable)" unless command
115
+ puts run(command)
116
+ end
117
+
118
+ desc "Copy arbitrary files to the currently deployed version using
119
+ FILES=a,b,c. This is useful for updating files piecemeal when you
120
+ need to quickly deploy only a single file.
121
+
122
+ To use this task, specify the files and directories you want to copy as a
123
+ comma-delimited list in the FILES environment variable. All directories
124
+ will be processed recursively, with all files being pushed to the
125
+ deployment servers. Any file or directory starting with a '.' character
126
+ will be ignored.
127
+
128
+ $ rake vlad:upload FILES=templates,controller.rb".cleanup
129
+
130
+ remote_task :upload do
131
+ file_list = (ENV["FILES"] || "").split(",")
132
+
133
+ files = file_list.map do |f|
134
+ f = f.strip
135
+ File.directory?(f) ? Dir["#{f}/**/*"] : f
136
+ end.flatten
137
+
138
+ files = files.reject { |f| File.directory?(f) || File.basename(f)[0] == ?. }
139
+
140
+ abort "Please specify at least one file to update (via the FILES environment variable)" if files.empty?
141
+
142
+ files.each do |file|
143
+ rsync file, File.join(current_path, file)
144
+ end
145
+ end
146
+
147
+ desc "Rolls back to a previous version and restarts. This is handy if you
148
+ ever discover that you've deployed a lemon; 'rake vlad:rollback' and
149
+ you're right back where you were, on the previously deployed
150
+ version.".cleanup
151
+
152
+ remote_task :rollback do
153
+ if releases.length < 2 then
154
+ abort "could not rollback the code because there is no prior release"
155
+ else
156
+ run "rm #{current_path}; ln -s #{previous_release} #{current_path} && rm -rf #{current_release}"
157
+ end
158
+
159
+ Rake::Task['vlad:start'].invoke
160
+ end
161
+
162
+ desc "Clean up old releases. By default, the last 5 releases are kept on
163
+ each server (though you can change this with the keep_releases variable).
164
+ All other deployed revisions are removed from the servers.".cleanup
165
+
166
+ remote_task :cleanup do
167
+ max = keep_releases
168
+ if releases.length <= max then
169
+ puts "no old releases to clean up #{releases.length} <= #{max}"
170
+ else
171
+ puts "keeping #{max} of #{releases.length} deployed releases"
172
+
173
+ directories = (releases - releases.last(max)).map { |release|
174
+ File.join(releases_path, release)
175
+ }.join(" ")
176
+
177
+ run "rm -rf #{directories}"
178
+ end
179
+ end
180
+
181
+ end # namespace vlad
data/lib/vlad/git.rb ADDED
@@ -0,0 +1,54 @@
1
+ class Vlad::Git
2
+
3
+ set :source, Vlad::Git.new
4
+ set :git_cmd, "git"
5
+
6
+ ##
7
+ # Sets up a repository on the remote server to fetch changes to and
8
+ # to deploy new releases from
9
+
10
+ def setup
11
+ ["cd #{scm_path}",
12
+ "#{git_cmd} clone #{repository} repo"].join(" && ")
13
+ end
14
+
15
+ ##
16
+ # Returns the command that will check out +revision+ from the
17
+ # repository. +revision+ can be any SHA1 or equivalent
18
+ # (e.g. branch, tag, etc...)
19
+
20
+ def checkout(revision, whatever)
21
+ revision = 'HEAD' if revision =~ /head/i
22
+
23
+ ["cd #{scm_path}/repo",
24
+ "#{git_cmd} fetch",
25
+ "#{git_cmd} fetch --tags",
26
+ "#{git_cmd} checkout -f #{revision}",
27
+ "#{git_cmd} submodule init",
28
+ "#{git_cmd} submodule update"
29
+ ].join(" && ")
30
+ end
31
+
32
+ ##
33
+ # Returns the command that will export +revision+ from the repository into
34
+ # the directory +destination+.
35
+
36
+ def export(revision, destination)
37
+ revision = 'HEAD' if revision == "."
38
+
39
+ ["mkdir -p #{destination}",
40
+ "cd #{scm_path}/repo && find . | grep -v '/.git' | cpio -p --make-directories #{destination}"
41
+ ].join(" && ")
42
+ end
43
+
44
+ def self.setup_rake_tasks
45
+ desc "Sets up a Git clone to deploy from on the remote host"
46
+ remote_task 'vlad:setup:git', :roles => :app do
47
+ run source.setup
48
+ end
49
+
50
+ task('vlad:setup') { Rake::Task['vlad:setup:git'].invoke }
51
+ end
52
+
53
+ setup_rake_tasks
54
+ end
@@ -0,0 +1,85 @@
1
+ require 'vlad'
2
+
3
+ namespace :vlad do
4
+
5
+ set :lighttpd_port, 65536
6
+ set :web_command, "lighttpd"
7
+ set :lighttpd_user, "nobody"
8
+ set :lighttpd_group, "nobody"
9
+ set(:lighttpd_init) { "#{shared_path}/lighttpd.sh" }
10
+ set(:lighttpd_conf) { "#{shared_path}/lighttpd.conf" }
11
+
12
+ desc "Prepares application servers for deployment. Lighttpd
13
+ configuration is set via the lighttpd_* variables.".cleanup
14
+
15
+ remote_task :setup_lighttpd, :roles => :app do
16
+ require 'tempfile'
17
+
18
+ put lighttpd_conf, 'vlad.lighttpd_config' do
19
+ conf = <<-"EOF"
20
+ server.modules = ( "mod_rewrite",
21
+ "mod_access",
22
+ "mod_fastcgi",
23
+ "mod_compress",
24
+ "mod_accesslog" )
25
+
26
+ server.document-root = "#{current_path}/public"
27
+ server.errorlog = "#{shared_path}/log/lighttpd.error.log"
28
+ accesslog.filename = "#{shared_path}/log/lighttpd.access.log"
29
+ server.pid-file = "#{shared_path}/pids/lighttpd.pid"
30
+ server.port = #{lighttpd_port}
31
+ server.username = "#{lighttpd_user}"
32
+ server.groupname = "#{lighttpd_group}"
33
+ server.error-handler-404 = "/dispatch.fcgi"
34
+ server.indexfiles = ( "index.html", "index.rb" )
35
+ url.access-deny = ( "~", ".inc" )
36
+ compress.cache-dir = "#{shared_path}/tmp/cache/compress"
37
+ compress.filetype = ("text/html","text/plain","text/javascript","text/css")
38
+ server.tag = "lighttpd | TextDriven"
39
+
40
+ fastcgi.server = (
41
+ ".fcgi" => (
42
+ "localhost" => (
43
+ "min-procs" => 1,
44
+ "max-procs" => 1,
45
+ "socket" => "#{shared_path}/pids/rubyholic.socket",
46
+ "bin-path" => "#{current_path}/public/dispatch.fcgi",
47
+ "bin-environment" => ( "RAILS_ENV" => "production" ) ) ) )
48
+ EOF
49
+ end
50
+
51
+ run "mkdir -p \"#{shared_path}/tmp/cache/compress\""
52
+ end
53
+
54
+ desc "(Re)Start the web servers"
55
+
56
+ remote_task :start_web, :roles => :web do
57
+ cmd = %w(lighttpd ruby).map {|app| "(killall #{app} || true)"}.join(" && ")
58
+ cmd += " && #{web_command} -f #{lighttpd_conf} </dev/null >/dev/null 2>&1"
59
+ run cmd
60
+ end
61
+
62
+ desc "Stop the web servers"
63
+ remote_task :stop_web, :roles => :web do
64
+ cmd = %w(lighttpd ruby).map {|app| "(killall #{app} || true)"}.join(" && ")
65
+
66
+ run cmd
67
+ end
68
+
69
+ ##
70
+ # Everything HTTP.
71
+
72
+ desc "(Re)Start the web and app servers"
73
+
74
+ remote_task :start do
75
+ Rake::Task['vlad:start_app'].invoke
76
+ Rake::Task['vlad:start_web'].invoke
77
+ end
78
+
79
+ desc "Stop the web and app servers"
80
+
81
+ remote_task :stop do
82
+ Rake::Task['vlad:stop_app'].invoke
83
+ Rake::Task['vlad:stop_web'].invoke
84
+ end
85
+ end