kaykay-vlad 1.2.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+
62
+ recipes = {
63
+ :app => :mongrel,
64
+ :config => 'config/deploy.rb',
65
+ :core => :core,
66
+ :scm => :subversion,
67
+ :web => :apache,
68
+ }.merge(options)
69
+
70
+ # be sure core comes first so base tasks aren't clobbered
71
+ if core = recipes.delete(:core)
72
+ require "vlad/#{core}"
73
+ end
74
+ recipes.each do |flavor, recipe|
75
+ next if recipe.nil? or flavor == :config
76
+ require "vlad/#{recipe}"
77
+ end
78
+
79
+ Kernel.load recipes[:config]
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
@@ -0,0 +1,175 @@
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 02 && 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
+ "ln -s #{shared_path}/log #{latest_release}/log",
62
+ "ln -s #{shared_path}/system #{latest_release}/public/system",
63
+ "ln -s #{shared_path}/pids #{latest_release}/tmp/pids",
64
+ ].join(" && ")
65
+
66
+ symlink = true
67
+ run "rm -f #{current_path} && ln -s #{latest_release} #{current_path}"
68
+
69
+ run "echo #{now} $USER #{revision} #{File.basename release_path} >> #{deploy_to}/revisions.log"
70
+ rescue => e
71
+ run "rm -f #{current_path} && ln -s #{previous_release} #{current_path}" if
72
+ symlink
73
+ run "rm -rf #{release_path}"
74
+ raise e
75
+ end
76
+ end
77
+
78
+ desc "Run the migrate rake task for the the app. By default this is run in
79
+ the latest app directory. You can run migrations for the current app
80
+ directory by setting :migrate_target to :current. Additional environment
81
+ variables can be passed to rake via the migrate_env variable.".cleanup
82
+
83
+ # No application files are on the DB machine, also migrations should only be
84
+ # run once.
85
+ remote_task :migrate, :roles => :app do
86
+ break unless target_host == Rake::RemoteTask.hosts_for(:app).first
87
+
88
+ directory = case migrate_target.to_sym
89
+ when :current then current_path
90
+ when :latest then current_release
91
+ else raise ArgumentError, "unknown migration target #{migrate_target.inspect}"
92
+ end
93
+
94
+ run "cd #{current_path}; #{rake_cmd} RAILS_ENV=#{rails_env} db:migrate #{migrate_args}"
95
+ end
96
+
97
+ desc "Invoke a single command on every remote server. This is useful for
98
+ performing one-off commands that may not require a full task to be written
99
+ for them. Simply specify the command to execute via the COMMAND
100
+ environment variable. To execute the command only on certain roles,
101
+ specify the ROLES environment variable as a comma-delimited list of role
102
+ names.
103
+
104
+ $ rake vlad:invoke COMMAND='uptime'".cleanup
105
+
106
+ remote_task :invoke do
107
+ command = ENV["COMMAND"]
108
+ abort "Please specify a command to execute on the remote servers (via the COMMAND environment variable)" unless command
109
+ puts run(command)
110
+ end
111
+
112
+ desc "Copy arbitrary files to the currently deployed version using
113
+ FILES=a,b,c. This is useful for updating files piecemeal when you
114
+ need to quickly deploy only a single file.
115
+
116
+ To use this task, specify the files and directories you want to copy as a
117
+ comma-delimited list in the FILES environment variable. All directories
118
+ will be processed recursively, with all files being pushed to the
119
+ deployment servers. Any file or directory starting with a '.' character
120
+ will be ignored.
121
+
122
+ $ rake vlad:upload FILES=templates,controller.rb".cleanup
123
+
124
+ remote_task :upload do
125
+ file_list = (ENV["FILES"] || "").split(",")
126
+
127
+ files = file_list.map do |f|
128
+ f = f.strip
129
+ File.directory?(f) ? Dir["#{f}/**/*"] : f
130
+ end.flatten
131
+
132
+ files = files.reject { |f| File.directory?(f) || File.basename(f)[0] == ?. }
133
+
134
+ abort "Please specify at least one file to update (via the FILES environment variable)" if files.empty?
135
+
136
+ files.each do |file|
137
+ rsync file, File.join(current_path, file)
138
+ end
139
+ end
140
+
141
+ desc "Rolls back to a previous version and restarts. This is handy if you
142
+ ever discover that you've deployed a lemon; 'rake vlad:rollback' and
143
+ you're right back where you were, on the previously deployed
144
+ version.".cleanup
145
+
146
+ remote_task :rollback do
147
+ if releases.length < 2 then
148
+ abort "could not rollback the code because there is no prior release"
149
+ else
150
+ run "rm #{current_path}; ln -s #{previous_release} #{current_path} && rm -rf #{current_release}"
151
+ end
152
+
153
+ Rake::Task['vlad:start'].invoke
154
+ end
155
+
156
+ desc "Clean up old releases. By default, the last 5 releases are kept on
157
+ each server (though you can change this with the keep_releases variable).
158
+ All other deployed revisions are removed from the servers.".cleanup
159
+
160
+ remote_task :cleanup do
161
+ max = keep_releases
162
+ if releases.length <= max then
163
+ puts "no old releases to clean up #{releases.length} <= #{max}"
164
+ else
165
+ puts "keeping #{max} of #{releases.length} deployed releases"
166
+
167
+ directories = (releases - releases.last(max)).map { |release|
168
+ File.join(releases_path, release)
169
+ }.join(" ")
170
+
171
+ run "rm -rf #{directories}"
172
+ end
173
+ end
174
+
175
+ end # namespace vlad
@@ -0,0 +1,43 @@
1
+ class Vlad::Git
2
+
3
+ set :source, Vlad::Git.new
4
+ set :git_cmd, "git"
5
+
6
+ ##
7
+ # Returns the command that will check out +revision+ from the
8
+ # repository into directory +destination+. +revision+ can be any
9
+ # SHA1 or equivalent (e.g. branch, tag, etc...)
10
+
11
+ def checkout(revision, destination)
12
+ destination = 'repo' if destination == '.'
13
+ revision = 'HEAD' if revision =~ /head/i
14
+
15
+ [ "rm -rf #{destination}",
16
+ "#{git_cmd} clone #{repository} #{destination}",
17
+ "cd #{destination}",
18
+ "#{git_cmd} checkout -f -b deployed-#{revision} #{revision}"
19
+ ].join(" && ")
20
+ end
21
+
22
+ ##
23
+ # Returns the command that will export +revision+ from the repository into
24
+ # the directory +destination+.
25
+
26
+ def export(revision, destination)
27
+ revision = 'HEAD' if revision == "."
28
+
29
+ [ "mkdir -p #{destination}",
30
+ "#{git_cmd} archive --format=tar #{revision} | (cd #{destination} && tar xf -)"
31
+ ].join(" && ")
32
+ end
33
+
34
+ ##
35
+ # Returns a command that maps human-friendly revision identifier +revision+
36
+ # into a git SHA1.
37
+
38
+ def revision(revision)
39
+ revision = 'HEAD' if revision =~ /head/i
40
+
41
+ "`#{git_cmd} rev-parse #{revision}`"
42
+ end
43
+ 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
@@ -0,0 +1,34 @@
1
+ class Vlad::Mercurial
2
+
3
+ set :source, Vlad::Mercurial.new
4
+
5
+ ##
6
+ # Returns the command that will check out +revision+ from the repository
7
+ # into directory +destination+
8
+
9
+ def checkout(revision, destination)
10
+ revision = 'tip' if revision =~ /^head$/i
11
+ "hg pull -r #{revision} -R #{destination} #{repository}"
12
+ end
13
+
14
+ ##
15
+ # Returns the command that will export +revision+ from the repository into
16
+ # the directory +destination+.
17
+
18
+ def export(revision_or_source, destination)
19
+ revision_or_source = 'tip' if revision_or_source =~ /^head$/i
20
+ if revision_or_source =~ /^(\d+|tip)$/i then
21
+ "hg archive -r #{revision_or_source} -R #{repository} #{destination}"
22
+ else
23
+ "hg archive -R #{revision_or_source} #{destination}"
24
+ end
25
+ end
26
+
27
+ ##
28
+ # Returns a command that maps human-friendly revision identifier +revision+
29
+ # into a subversion revision specification.
30
+
31
+ def revision(revision)
32
+ "`hg identify -R #{repository} | cut -f1 -d\\ `"
33
+ end
34
+ end