Empact-ec2onrails 0.9.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. data/CHANGELOG +159 -0
  2. data/LICENSE +339 -0
  3. data/README.rdoc +232 -0
  4. data/Rakefile +34 -0
  5. data/examples/Capfile +3 -0
  6. data/examples/deploy.rb +85 -0
  7. data/examples/s3.yml +9 -0
  8. data/lib/ec2onrails.rb +20 -0
  9. data/lib/ec2onrails/capistrano_utils.rb +33 -0
  10. data/lib/ec2onrails/recipes.rb +460 -0
  11. data/lib/ec2onrails/version.rb +31 -0
  12. data/test/autobench.conf +60 -0
  13. data/test/spec/lib/s3_helper_spec.rb +134 -0
  14. data/test/spec/lib/s3_old.yml +3 -0
  15. data/test/spec/test_files/test1 +0 -0
  16. data/test/spec/test_files/test2 +0 -0
  17. data/test/test_app/Capfile +3 -0
  18. data/test/test_app/README +182 -0
  19. data/test/test_app/Rakefile +10 -0
  20. data/test/test_app/app/controllers/application.rb +7 -0
  21. data/test/test_app/app/controllers/db_fast_controller.rb +6 -0
  22. data/test/test_app/app/controllers/fast_controller.rb +5 -0
  23. data/test/test_app/app/controllers/slow_controller.rb +6 -0
  24. data/test/test_app/app/controllers/very_slow_controller.rb +6 -0
  25. data/test/test_app/app/helpers/application_helper.rb +3 -0
  26. data/test/test_app/app/helpers/db_fast_helper.rb +2 -0
  27. data/test/test_app/app/helpers/fast_helper.rb +2 -0
  28. data/test/test_app/app/helpers/slow_helper.rb +2 -0
  29. data/test/test_app/app/helpers/very_slow_helper.rb +2 -0
  30. data/test/test_app/config/boot.rb +109 -0
  31. data/test/test_app/config/database.yml +36 -0
  32. data/test/test_app/config/deploy.rb +21 -0
  33. data/test/test_app/config/environment.rb +60 -0
  34. data/test/test_app/config/environments/development.rb +21 -0
  35. data/test/test_app/config/environments/production.rb +18 -0
  36. data/test/test_app/config/environments/test.rb +19 -0
  37. data/test/test_app/config/routes.rb +27 -0
  38. data/test/test_app/db/schema.rb +7 -0
  39. data/test/test_app/doc/README_FOR_APP +2 -0
  40. data/test/test_app/public/404.html +30 -0
  41. data/test/test_app/public/500.html +30 -0
  42. data/test/test_app/public/dispatch.cgi +10 -0
  43. data/test/test_app/public/dispatch.fcgi +24 -0
  44. data/test/test_app/public/dispatch.rb +10 -0
  45. data/test/test_app/public/favicon.ico +0 -0
  46. data/test/test_app/public/images/rails.png +0 -0
  47. data/test/test_app/public/javascripts/application.js +2 -0
  48. data/test/test_app/public/javascripts/controls.js +963 -0
  49. data/test/test_app/public/javascripts/dragdrop.js +972 -0
  50. data/test/test_app/public/javascripts/effects.js +1120 -0
  51. data/test/test_app/public/javascripts/prototype.js +4225 -0
  52. data/test/test_app/public/robots.txt +1 -0
  53. data/test/test_app/script/about +3 -0
  54. data/test/test_app/script/breakpointer +3 -0
  55. data/test/test_app/script/console +3 -0
  56. data/test/test_app/script/destroy +3 -0
  57. data/test/test_app/script/generate +3 -0
  58. data/test/test_app/script/performance/benchmarker +3 -0
  59. data/test/test_app/script/performance/profiler +3 -0
  60. data/test/test_app/script/performance/request +3 -0
  61. data/test/test_app/script/plugin +3 -0
  62. data/test/test_app/script/process/inspector +3 -0
  63. data/test/test_app/script/process/reaper +3 -0
  64. data/test/test_app/script/process/spawner +3 -0
  65. data/test/test_app/script/runner +3 -0
  66. data/test/test_app/script/server +3 -0
  67. data/test/test_app/test/functional/db_fast_controller_test.rb +18 -0
  68. data/test/test_app/test/functional/fast_controller_test.rb +18 -0
  69. data/test/test_app/test/functional/slow_controller_test.rb +18 -0
  70. data/test/test_app/test/functional/very_slow_controller_test.rb +18 -0
  71. data/test/test_app/test/test_helper.rb +28 -0
  72. data/test/test_ec2onrails.rb +11 -0
  73. data/test/test_helper.rb +2 -0
  74. metadata +156 -0
data/README.rdoc ADDED
@@ -0,0 +1,232 @@
1
+ = EC2 on Rails
2
+
3
+
4
+ == Deploy a Ruby on Rails app on EC2 in five minutes
5
+
6
+ EC2 on Rails is an Ubuntu Linux server image for
7
+ "Amazon's EC2 hosting service":http://www.amazon.com/b/ref=sc_fe_l_2/102-6342260-7987311?ie=UTF8&node=201590011&no=3435361
8
+ that's ready to run a standard Ruby on Rails application with little or no customization.
9
+ It's a Ruby on Rails "virtual appliance":http://en.wikipedia.org/wiki/Virtual_appliance.
10
+
11
+ If you have an EC2 account and can start EC2 instances you're five minutes away from deploying
12
+ your Rails app.
13
+
14
+ EC2 on Rails is "opinionated software":http://gettingreal.37signals.com/ch04_Make_Opinionated_Software.php:
15
+ the opinion is that for many rails apps the server setup can be generalized
16
+ and shared the same way as the web application framework itself. For many people (Twitter, this isn't for you)
17
+ the server image can be treated the same way as other shared libraries. And if the day comes when your needs are
18
+ unique enough that EC2 on Rails can't be configured to work for you then you can bundle your own image from it
19
+ or fork the build source and customize it.
20
+
21
+ But until then, why spend your time configuring servers?
22
+
23
+ Features of the EC2 image:
24
+
25
+ * Ready to deploy a Rails app with little or no configuration of the server required
26
+ * Automatic backup of MySQL database to S3 (full backup nightly + incremental backup using binary logs every 5 minutes)
27
+ * Capistrano tasks to customize the server image, archive and restore the database to/from S3, and more (available as a rubygem)
28
+ * Mongrel_cluster behind Apache 2.2, configured according to
29
+ "Coda Hale's excellent guide":http://blog.codahale.com/2006/06/19/time-for-a-grown-up-server-rails-mongrel-apache-capistrano-and-you/
30
+ * Ruby on Rails 2.1.0, 2.0.2 and 1.2.6
31
+ * Ruby 1.8.6
32
+ * MySQL 5
33
+ * "memcached":http://www.danga.com/memcached/
34
+ * "monit":http://www.tildeslash.com/monit/ configured to monitor apache, mongrel, mysql, memcached, drive space and system load
35
+ * Ubuntu 8.04 LTS "Hardy" base image built using "Eric Hammond's EC2 Ubuntu script":http://alestic.com/
36
+ * SSL support
37
+ * Amazon AMI tools installed
38
+ * MySQL, Apache, and syslog configured to use /mnt for data and logging so you don't fill up EC2's small root filesystem
39
+ * Automatically archives Rails and Apache logs to S3 nightly.
40
+ * 32-bit and 64-bit images available (supports all instance types, small to extra large).
41
+ * Created using a build file, full source is "available":http://rubyforge.org/scm/?group_id=4552 (the EC2 on Rails script is run from "Eric Hammond's EC2 Ubuntu script":http://alestic.com/)
42
+ * Can be used as a clustered Rails app running on multiple instances
43
+ * Automatically runs hourly, daily, weekly and monthly scripts if they exist in Rails application's script directory
44
+ * Local "Postfix":http://www.postfix.org/ SMTP mail server (only available from within the instance, not listening on external network interfaces)
45
+
46
+
47
+ == Using the image
48
+
49
+ This documentation will be improved soon, for now hopefully this covers the basics.
50
+
51
+ The current AMI id's are:
52
+ * ami-c9bc58a0 (32-bit)
53
+ * ami-cbbc58a2 (64-bit)
54
+
55
+ _I will keep these images for as long as possible, they will not be deleted for at least a few years._
56
+
57
+
58
+ === 1. Install the gem
59
+
60
+ <pre>sudo gem install ec2onrails</pre>
61
+
62
+ === 2. Add the config files to your Rails app
63
+
64
+ Put "Capfile":http://ec2onrails.rubyforge.org/svn/trunk/documentation/examples/Capfile
65
+ in the root of your rails folder, and put
66
+ "deploy.rb":http://ec2onrails.rubyforge.org/svn/trunk/documentation/examples/deploy.rb
67
+ and
68
+ "s3.yml":http://ec2onrails.rubyforge.org/svn/trunk/documentation/examples/s3.yml
69
+ in the config folder.
70
+
71
+ _Be sure to customize those files and read the comments._
72
+
73
+ Also, use the hostname "db_primary" in your database.yml file. After running "cap ec2onrails:server:set_roles" it will resolve
74
+ to the instance defined in your Capistrano "db" role.
75
+
76
+ === 4. Start up one or more instances of the image.
77
+
78
+ There is nothing EC2 on Rails-specific here yet (though soon there will be a Capistrano task to do this for you),
79
+ if you've started EC2 instances before you can skip this section. Otherwise, I'm not going to lie, this part is complicated
80
+ and will take a lot more than 5 minutes the first time.
81
+
82
+ Read the
83
+ "running an instance section":http://docs.amazonwebservices.com/AWSEC2/2007-08-29/GettingStartedGuide/running-an-instance.html
84
+ in Amazon's getting started guide.
85
+
86
+ For the AMI id's of the current images do <code>cap ec2onrails:ami_ids</code> from within the app that you
87
+ configured in the previous step (they're also listed earlier on this page).
88
+
89
+ _NOTE: Only use the images that match the current version of the gem._
90
+
91
+ Please see the "change log":http://ec2onrails.rubyforge.org/svn/trunk/gem/History.txt for release notes, and
92
+ see the "list of open issues":http://rubyforge.org/tracker/?atid=17558&group_id=4552&func=browse.
93
+
94
+ As is "standard for public AMI's":http://docs.amazonwebservices.com/AWSEC2/2007-08-29/DeveloperGuide/public-ami-guidelines.html,
95
+ password-based logins are disabled. You log in with your own
96
+ "public/private keypair":http://docs.amazonwebservices.com/AWSEC2/2007-08-29/GettingStartedGuide/running-an-instance.html.
97
+
98
+ Most basic things can be configured automatically by the Capistrano tasks, but if you want to
99
+ you can login by ssh as a user named "admin" (has sudo ability) or as "app" (the user
100
+ that the app runs as, does not have sudo ability). The Capistrano tasks automatically
101
+ use the app user to deploy the app, and the admin user for server admin tasks
102
+ that require sudo.
103
+
104
+ IMPORTANT: Double-check "your firewall settings":http://docs.amazonwebservices.com/AWSEC2/2007-08-29/GettingStartedGuide/running-an-instance.html.
105
+ Be sure that you haven't allowed public access to any ports other than TCP 22 and TCP 80
106
+ (and possibly TCP 443 if you're going to enable HTTPS).
107
+ If you're using multiple instances, be sure to allow them network access to each other.
108
+
109
+
110
+ === 5. Copy your public key from the server to keep Capistrano happy
111
+
112
+ This is a workaround for a quirk in Capistrano. Technically all you should need to connect to the server is the private
113
+ key file, the public key is on the server. But for some reason
114
+ "Capistrano requires that you have both the public key and the private key files together on the client":http://groups.google.com/group/capistrano/browse_thread/thread/1102208ff925d18.
115
+
116
+ There is a Capistrano task that tries to fix this for you. From within the root of your rails app do:
117
+
118
+ <pre>cap ec2onrails:get_public_key_from_server</pre>
119
+
120
+ Note, this will only work if you have an external ssh command in the path, it won't work for most Windows users.
121
+
122
+
123
+ === 6. Deploy the app with Capistrano
124
+
125
+ Now that the gem is installed, your deploy.rb is configured and you can start and stop EC2 instances,
126
+ this is the only thing you'll need to do from now on.
127
+
128
+ <pre>
129
+ cap ec2onrails:setup
130
+ cap deploy:cold
131
+ </pre>
132
+
133
+ Yes, it's that easy! The setup task will set the server's timezone, install any
134
+ gems and Ubuntu packages that you specified in the config file, and
135
+ create your database.
136
+
137
+ That's it, your app is now running on EC2!!
138
+
139
+
140
+ == Capistrano tasks
141
+
142
+ "Capistrano":http://capify.org is the most commonly used Rails deployment tool. It comes with many standard "tasks",
143
+ and the EC2 on Rails gem includes Capistrano tasks specifically for configuring the server instance.
144
+
145
+ Capistrano is run from the command-line using the "cap" command, with a task name given as an argument.
146
+
147
+ === Commonly-used tasks
148
+
149
+ You'll mostly need just the following Capistrano tasks:
150
+
151
+ * <code>cap ec2onrails:ami_ids</code>
152
+ Shows the AMI id's of the images that match the current version of the gem.
153
+
154
+ * <code>cap ec2onrails:setup</code>
155
+ This task configures a newly-launched instance. This is the first thing you should do after
156
+ starting a new instance. It can be run more than once without ill effect. After running
157
+ "cap ec2onrails:setup" the next thing to do is run "cap deploy:cold"
158
+
159
+ * <code>cap ec2onrails:server:set_roles</code>
160
+ Customizes each instance for it's role(s) (as defined in your Capistrano deploy.rb file).
161
+ Run this after starting or stopping instances.
162
+ For now this just makes sure that only the appropriate services (Apache, Mongrel, and/or MySQL)
163
+ are running. Eventually this will customize settings for the running services also. Note that
164
+ an instance can have more than one role. If there's only one instance it will have all roles.
165
+
166
+ Note that due to the way that Capistrano works all tasks are run
167
+ against all hosts that are currently defined in the deploy.rb file.
168
+ So if you start a new instance then add it to your deploy.rb you will need to run
169
+ "cap ec2onrails:setup" again which will be run on all existing instances.
170
+
171
+
172
+ === Database management tasks
173
+
174
+ * <code>cap ec2onrails:db:archive</code>
175
+ Archive the MySQL database to the bucket specified in your deploy.rb. This is for archiving a snapshot of your
176
+ database into any S3 bucket. For example, you might want to do this before deploying.
177
+
178
+ * <code>cap ec2onrails:db:restore</code>
179
+ Restore the MySQL database from the bucket specified in your deploy.rb
180
+ For example, I use this to restore the current production data (from my actual production backup
181
+ bucket) onto a staging server that has the current production version of my
182
+ app. I then deploy the new version which tests migrations exactly as they'll
183
+ run on the production server.
184
+
185
+ To get a full list of the Capistrano tasks at any time type <code>cap -T</code> from with your rails app root.
186
+
187
+ == Building the image
188
+
189
+ Building the image is not required, most people will simply use the prebuilt public
190
+ image, but there is also a build script that builds the image. It's meant to be called by
191
+ "Eric Hammond's EC2 Ubuntu script":http://alestic.com/.
192
+
193
+
194
+ == Mailing lists
195
+
196
+ There are two Google groups, one for
197
+ "announcements":http://groups.google.com/group/ec2-on-rails-announce
198
+ (usually just new release announcements) and one for
199
+ "discussion":http://groups.google.com/group/ec2-on-rails-discuss.
200
+
201
+
202
+ == Comments
203
+
204
+ Comments are welcome. Send an email to "Paul Dowman":http://pauldowman.com/contact/
205
+ or to the "Google group":http://groups.google.com/group/ec2-on-rails-discuss.
206
+ If you find bugs please file them
207
+ "here":http://rubyforge.org/tracker/?atid=17558&group_id=4552&func=browse
208
+ or send me an "email":http://pauldowman.com/contact/.
209
+
210
+
211
+ == Change log
212
+
213
+ See the "change log":http://ec2onrails.rubyforge.org/svn/trunk/gem/History.txt.
214
+
215
+
216
+ == How to submit patches
217
+
218
+ Pleae read the "8 steps for fixing other people's code":http://drnicwilliams.com/2007/06/01/8-steps-for-fixing-other-peoples-code/.
219
+ The source code can be checked out anonymously using:
220
+ <pre>
221
+ svn checkout http://ec2onrails.rubyforge.org/svn/trunk ec2onrails
222
+ </pre>
223
+
224
+ Patches can be submitted to the "RubyForge Tracker":http://rubyforge.org/tracker/?atid=17560&group_id=4552&func=browse
225
+ or "emailed directly to me":http://pauldowman.com/contact/ .
226
+
227
+ == License
228
+
229
+ This code is free to use under the terms of the GPL v2.
230
+
231
+ If you find EC2 on Rails useful please "recommend Paul Dowman":http://www.workingwithrails.com/person/10131-paul-dowman
232
+ at Working With Rails.
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'spec/rake/spectask'
6
+
7
+ # read the contents of the gemspec, eval it, and assign it to 'spec'
8
+ # this lets us maintain all gemspec info in one place. Nice and DRY.
9
+ spec = eval(IO.read("ec2onrails.gemspec"))
10
+
11
+ Rake::GemPackageTask.new(spec) do |pkg|
12
+ pkg.gem_spec = spec
13
+ end
14
+
15
+ task :install => [:package] do
16
+ sh %{sudo gem install pkg/#{GEM}-#{VERSION}}
17
+ end
18
+
19
+ Rake::TestTask.new do |t|
20
+ t.libs << "test"
21
+ t.test_files = FileList['test/test*.rb']
22
+ t.verbose = true
23
+ end
24
+
25
+ Spec::Rake::SpecTask.new('spec') do |t|
26
+ t.spec_files = FileList['test/spec/lib/*.rb']
27
+ end
28
+
29
+ Rake::RDocTask.new do |rd|
30
+ rd.main = "README.rdoc"
31
+ rd.rdoc_files.include("README.rdoc", "lib/**/*.rb")
32
+ rd.rdoc_dir = 'doc'
33
+ rd.options = spec.rdoc_options
34
+ end
data/examples/Capfile ADDED
@@ -0,0 +1,3 @@
1
+ load 'deploy' if respond_to?(:namespace) # cap2 differentiator
2
+ load 'config/deploy'
3
+ require 'ec2onrails/recipes'
@@ -0,0 +1,85 @@
1
+ # This is a sample Capistrano config file for EC2 on Rails.
2
+ # It should be edited and customized.
3
+
4
+ set :application, "yourapp"
5
+
6
+ set :repository, "http://svn.foo.com/svn/#{application}/trunk"
7
+
8
+ # NOTE: for some reason Capistrano requires you to have both the public and
9
+ # the private key in the same folder, the public key should have the
10
+ # extension ".pub".
11
+ ssh_options[:keys] = ["#{ENV['HOME']}/.ssh/your-ec2-key"]
12
+
13
+ # Your EC2 instances. Use the ec2-xxx....amazonaws.com hostname, not
14
+ # any other name (in case you have your own DNS alias) or it won't
15
+ # be able to resolve to the internal IP address.
16
+ role :web, "ec2-12-xx-xx-xx.z-1.compute-1.amazonaws.com"
17
+ role :app, "ec2-34-xx-xx-xx.z-1.compute-1.amazonaws.com"
18
+ role :db, "ec2-56-xx-xx-xx.z-1.compute-1.amazonaws.com", :primary => true
19
+ role :memcache, "ec2-12-xx-xx-xx.z-1.compute-1.amazonaws.com"
20
+
21
+ # Whatever you set here will be taken set as the default RAILS_ENV value
22
+ # on the server. Your app and your hourly/daily/weekly/monthly scripts
23
+ # will run with RAILS_ENV set to this value.
24
+ set :rails_env, "production"
25
+
26
+ # EC2 on Rails config.
27
+ # NOTE: Some of these should be omitted if not needed.
28
+ set :ec2onrails_config, {
29
+ # S3 bucket and "subdir" used by the ec2onrails:db:restore task
30
+ :restore_from_bucket => "your-bucket",
31
+ :restore_from_bucket_subdir => "database",
32
+
33
+ # S3 bucket and "subdir" used by the ec2onrails:db:archive task
34
+ # This does not affect the automatic backup of your MySQL db to S3, it's
35
+ # just for manually archiving a db snapshot to a different bucket if
36
+ # desired.
37
+ :archive_to_bucket => "your-other-bucket",
38
+ :archive_to_bucket_subdir => "db-archive/#{Time.new.strftime('%Y-%m-%d--%H-%M-%S')}",
39
+
40
+ # Set a root password for MySQL. Run "cap ec2onrails:db:set_root_password"
41
+ # to enable this. This is optional, and after doing this the
42
+ # ec2onrails:db:drop task won't work, but be aware that MySQL accepts
43
+ # connections on the public network interface (you should block the MySQL
44
+ # port with the firewall anyway).
45
+ # If you don't care about setting the mysql root password then remove this.
46
+ :mysql_root_password => "your-mysql-root-password",
47
+
48
+ # Any extra Ubuntu packages to install if desired
49
+ # If you don't want to install extra packages then remove this.
50
+ :packages => ["logwatch", "imagemagick"],
51
+
52
+ # Any extra RubyGems to install if desired: can be "gemname" or if a
53
+ # particular version is desired "gemname -v 1.0.1"
54
+ # If you don't want to install extra rubygems then remove this
55
+ :rubygems => ["rmagick", "rfacebook -v 0.9.7"],
56
+
57
+ # Set the server timezone. run "cap -e ec2onrails:server:set_timezone" for
58
+ # details
59
+ :timezone => "Canada/Eastern",
60
+
61
+ # Files to deploy to the server (they'll be owned by root). It's intended
62
+ # mainly for customized config files for new packages installed via the
63
+ # ec2onrails:server:install_packages task. Subdirectories and files inside
64
+ # here will be placed in the same structure relative to the root of the
65
+ # server's filesystem.
66
+ # If you don't need to deploy customized config files to the server then
67
+ # remove this.
68
+ :server_config_files_root => "../server_config",
69
+
70
+ # If config files are deployed, some services might need to be restarted.
71
+ # If you don't need to deploy customized config files to the server then
72
+ # remove this.
73
+ :services_to_restart => %w(apache2 postfix sysklogd),
74
+
75
+ # Set an email address to forward admin mail messages to. If you don't
76
+ # want to receive mail from the server (e.g. monit alert messages) then
77
+ # remove this.
78
+ :admin_mail_forward_address => "you@yourdomain.com",
79
+
80
+ # Set this if you want SSL to be enabled on the web server. The SSL cert
81
+ # and key files need to exist on the server, The cert file should be in
82
+ # /etc/ssl/certs/default.pem and the key file should be in
83
+ # /etc/ssl/private/default.key (see :server_config_files_root).
84
+ :enable_ssl => true
85
+ }
data/examples/s3.yml ADDED
@@ -0,0 +1,9 @@
1
+ staging:
2
+ aws_access_key: ABC123
3
+ aws_secret_access_key: abc123abc123abc123abc123
4
+ bucket_base_name: yourbucket
5
+
6
+ production:
7
+ aws_access_key: DEF456
8
+ aws_secret_access_key: def456def456def456def456
9
+ bucket_base_name: yourbucket
data/lib/ec2onrails.rb ADDED
@@ -0,0 +1,20 @@
1
+ # This file is part of EC2 on Rails.
2
+ # http://rubyforge.org/projects/ec2onrails/
3
+ #
4
+ # Copyright 2007 Paul Dowman, http://pauldowman.com/
5
+ #
6
+ # EC2 on Rails is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # EC2 on Rails is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+
20
+ $:.unshift File.dirname(__FILE__)
@@ -0,0 +1,33 @@
1
+ module Ec2onrails
2
+ module CapistranoUtils
3
+ def run_local(command)
4
+ result = system command
5
+ raise("error: #{$?}") unless result
6
+ end
7
+
8
+ def run_init_script(script, arg)
9
+ # since init scripts might have the execute bit unset by the set_roles script we need to check
10
+ sudo "sh -c 'if [ -x /etc/init.d/#{script} ] ; then /etc/init.d/#{script} #{arg}; fi'"
11
+ end
12
+
13
+ def make_admin_role_for(role)
14
+ newrole = "#{role.to_s}_admin".to_sym
15
+ roles[role].each do |srv_def|
16
+ options = srv_def.options.dup
17
+ options[:user] = "admin"
18
+ options[:port] = srv_def.port
19
+ options[:no_release] = true
20
+ role newrole, srv_def.host, options
21
+ end
22
+ end
23
+
24
+ # return hostnames for the role named role_sym that has the specified options
25
+ def hostnames_for_role(role_sym, options = {})
26
+ role = roles[role_sym]
27
+ unless role
28
+ return []
29
+ end
30
+ role.select{|s| s.options == options}.collect{|s| s.host}
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,460 @@
1
+ # This file is part of EC2 on Rails.
2
+ # http://rubyforge.org/projects/ec2onrails/
3
+ #
4
+ # Copyright 2007 Paul Dowman, http://pauldowman.com/
5
+ #
6
+ # EC2 on Rails is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # EC2 on Rails is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ require 'fileutils'
20
+ include FileUtils
21
+ require 'tmpdir'
22
+ require 'pp'
23
+ require 'zlib'
24
+ require 'archive/tar/minitar'
25
+ include Archive::Tar
26
+
27
+ require 'ec2onrails/version'
28
+ require 'ec2onrails/capistrano_utils'
29
+ include Ec2onrails::CapistranoUtils
30
+
31
+ Capistrano::Configuration.instance.load do
32
+
33
+ unless ec2onrails_config
34
+ raise "ec2onrails_config variable not set. (It should be a hash.)"
35
+ end
36
+
37
+ cfg = ec2onrails_config
38
+
39
+ set :ec2onrails_version, Ec2onrails::VERSION::STRING
40
+ set :image_id_32_bit, Ec2onrails::VERSION::AMI_ID_32_BIT
41
+ set :image_id_64_bit, Ec2onrails::VERSION::AMI_ID_64_BIT
42
+ set :deploy_to, "/mnt/app"
43
+ set :use_sudo, false
44
+ set :user, "app"
45
+
46
+ # make an "admin" role for each role, and create arrays containing
47
+ # the names of admin roles and non-admin roles for convenience
48
+ set :all_admin_role_names, []
49
+ set :all_non_admin_role_names, []
50
+ roles.keys.clone.each do |name|
51
+ make_admin_role_for(name)
52
+ all_non_admin_role_names << name
53
+ all_admin_role_names << "#{name.to_s}_admin".to_sym
54
+ end
55
+
56
+ after "deploy:symlink", "ec2onrails:server:set_roles"
57
+ after "deploy:cold", "ec2onrails:db:init_backup"
58
+
59
+ # override default start/stop/restart tasks
60
+ namespace :deploy do
61
+ desc <<-DESC
62
+ Overrides the default Capistrano deploy:restart, uses \
63
+ /etc/init.d/mongrel
64
+ DESC
65
+ task :start, :roles => :app_admin do
66
+ run_init_script("mongrel", "start")
67
+ run "sleep 30" # give the service 30 seconds to start before attempting to monitor it
68
+ sudo "monit -g app monitor all"
69
+ end
70
+
71
+ desc <<-DESC
72
+ Overrides the default Capistrano deploy:restart, uses \
73
+ /etc/init.d/mongrel
74
+ DESC
75
+ task :stop, :roles => :app_admin do
76
+ sudo "monit -g app unmonitor all"
77
+ run_init_script("mongrel", "stop")
78
+ end
79
+
80
+ desc <<-DESC
81
+ Overrides the default Capistrano deploy:restart, uses \
82
+ /etc/init.d/mongrel
83
+ DESC
84
+ task :restart, :roles => :app_admin do
85
+ deploy.stop
86
+ deploy.start
87
+ end
88
+ end
89
+
90
+ namespace :ec2onrails do
91
+ desc <<-DESC
92
+ Show the AMI id's of the current images for this version of \
93
+ EC2 on Rails.
94
+ DESC
95
+ task :ami_ids do
96
+ puts "32-bit server image for EC2 on Rails #{ec2onrails_version}: #{image_id_32_bit}"
97
+ puts "64-bit server image for EC2 on Rails #{ec2onrails_version}: #{image_id_64_bit}"
98
+ end
99
+
100
+ desc <<-DESC
101
+ Copies the public key from the server using the external "ssh"
102
+ command because Net::SSH, which is used by Capistrano, needs it.
103
+ This will only work if you have an ssh command in the path.
104
+ If Capistrano can successfully connect to your EC2 instance you
105
+ don't need to do this. It will copy from the first server in the
106
+ :app role, this can be overridden by specifying the HOST
107
+ environment variable
108
+ DESC
109
+ task :get_public_key_from_server do
110
+ host = find_servers_for_task(current_task).first.host
111
+ privkey = ssh_options[:keys][0]
112
+ pubkey = "#{privkey}.pub"
113
+ msg = <<-MSG
114
+ Your first key in ssh_options[:keys] is #{privkey}, presumably that's
115
+ your EC2 private key. The public key will be copied from the server
116
+ named '#{host}' and saved locally as #{pubkey}. Continue? [y/n]
117
+ MSG
118
+ choice = nil
119
+ while choice != "y" && choice != "n"
120
+ choice = Capistrano::CLI.ui.ask(msg).downcase
121
+ msg = "Please enter 'y' or 'n'."
122
+ end
123
+ if choice == "y"
124
+ run_local "scp -i '#{privkey}' app@#{host}:.ssh/authorized_keys #{pubkey}"
125
+ end
126
+ end
127
+
128
+ desc <<-DESC
129
+ Prepare a newly-started instance for a cold deploy.
130
+ DESC
131
+ task :setup, :roles => all_admin_role_names do
132
+ server.set_admin_mail_forward_address
133
+ server.set_timezone
134
+ server.install_packages
135
+ server.install_gems
136
+ server.deploy_files
137
+ server.enable_ssl if cfg[:enable_ssl]
138
+ server.set_rails_env
139
+ server.restart_services
140
+ deploy.setup
141
+ db.create
142
+ end
143
+
144
+ desc <<-DESC
145
+ Deploy and restore database from S3
146
+ DESC
147
+ task :restore_db_and_deploy do
148
+ db.recreate
149
+ deploy.update_code
150
+ deploy.symlink
151
+ db.restore
152
+ deploy.migrations
153
+ end
154
+
155
+ namespace :ec2 do
156
+ desc <<-DESC
157
+ DESC
158
+ task :configure_firewall do
159
+ # TODO
160
+ end
161
+ end
162
+
163
+ namespace :db do
164
+ desc <<-DESC
165
+ [internal] Load configuration info for the database from
166
+ config/database.yml, and start mysql (it must be running
167
+ in order to interact with it).
168
+ DESC
169
+ task :load_config do
170
+ unless hostnames_for_role(:db, :primary => true).empty?
171
+ db_config = YAML::load(ERB.new(File.read("config/database.yml")).result)[rails_env.to_s]
172
+ cfg[:db_name] = db_config['database']
173
+ cfg[:db_user] = db_config['username'] || db_config['user']
174
+ cfg[:db_password] = db_config['password']
175
+ cfg[:db_host] = db_config['host']
176
+ cfg[:db_socket] = db_config['socket']
177
+
178
+ if (cfg[:db_host].nil? || cfg[:db_host].empty?) && (cfg[:db_socket].nil? || cfg[:db_socket].empty?)
179
+ raise "ERROR: missing database config. Make sure database.yml contains a '#{rails_env}' section with either 'host: hostname' or 'socket: /var/run/mysqld/mysqld.sock'."
180
+ end
181
+
182
+ [cfg[:db_name], cfg[:db_user], cfg[:db_password]].each do |s|
183
+ if s.nil? || s.empty?
184
+ raise "ERROR: missing database config. Make sure database.yml contains a '#{rails_env}' section with a database name, user, and password."
185
+ elsif s.match(/['"]/)
186
+ raise "ERROR: database config string '#{s}' contains quotes."
187
+ end
188
+ end
189
+ end
190
+ end
191
+
192
+ desc <<-DESC
193
+ Create the MySQL database. Assumes there is no MySQL root \
194
+ password. To create a MySQL root password create a task that's run \
195
+ after this task using an after hook.
196
+ DESC
197
+ task :create, :roles => :db do
198
+ on_rollback { drop }
199
+ load_config
200
+ start
201
+
202
+ # For some reason the default db on Hardy contains users with '' as the name.
203
+ # This causes authentication problems when connecting from localhost
204
+ run %{mysql -u root -D mysql -e "delete from user where User = ''; flush privileges;"}
205
+
206
+ run %{mysql -u root -e "create database if not exists #{cfg[:db_name]};"}
207
+ run %{mysql -u root -e "grant all on #{cfg[:db_name]}.* to '#{cfg[:db_user]}'@'%' identified by '#{cfg[:db_password]}';"}
208
+ run %{mysql -u root -e "grant reload on *.* to '#{cfg[:db_user]}'@'%' identified by '#{cfg[:db_password]}';"}
209
+ run %{mysql -u root -e "grant super on *.* to '#{cfg[:db_user]}'@'%' identified by '#{cfg[:db_password]}';"}
210
+ end
211
+
212
+ desc <<-DESC
213
+ [internal] Make sure the MySQL server has been started, just in case the db role
214
+ hasn't been set, e.g. when called from ec2onrails:setup.
215
+ (But don't enable monitoring on it.)
216
+ DESC
217
+ task :start, :roles => :db_admin do
218
+ sudo "chmod a+x /etc/init.d/mysql"
219
+ # The mysql init script can fail on the first startup if mysql takes too long
220
+ # to create the logfiles, so try again
221
+ sudo "sh -c '/etc/init.d/mysql start || (sleep 10 && /etc/init.d/mysql start)'"
222
+ end
223
+
224
+ desc <<-DESC
225
+ Drop the MySQL database. Assumes there is no MySQL root \
226
+ password. If there is a MySQL root password, create a task that removes \
227
+ it and run that task before this one using a before hook.
228
+ DESC
229
+ task :drop, :roles => :db do
230
+ load_config
231
+ run %{mysql -u root -e "drop database if exists #{cfg[:db_name]};"}
232
+ end
233
+
234
+ desc <<-DESC
235
+ db:drop and db:create.
236
+ DESC
237
+ task :recreate, :roles => :db do
238
+ drop
239
+ create
240
+ end
241
+
242
+ desc <<-DESC
243
+ Set a root password for MySQL, using the variable mysql_root_password \
244
+ if it is set. If this is done db:drop won't work.
245
+ DESC
246
+ task :set_root_password, :roles => :db do
247
+ if cfg[:mysql_root_password]
248
+ run %{mysql -u root -e "UPDATE mysql.user SET Password=PASSWORD('#{cfg[:mysql_root_password]}') WHERE User='root'; FLUSH PRIVILEGES;"}
249
+ end
250
+ end
251
+
252
+ desc <<-DESC
253
+ Dump the MySQL database to the S3 bucket specified by \
254
+ ec2onrails_config[:archive_to_bucket]. The filename will be \
255
+ "database-archive/<timestamp>/dump.sql.gz".
256
+ DESC
257
+ task :archive, :roles => :db do
258
+ run "/usr/local/ec2onrails/bin/backup_app_db.rb --bucket #{cfg[:archive_to_bucket]} --dir #{cfg[:archive_to_bucket_subdir]}"
259
+ end
260
+
261
+ desc <<-DESC
262
+ Restore the MySQL database from the S3 bucket specified by \
263
+ ec2onrails_config[:restore_from_bucket]. The archive filename is \
264
+ expected to be the default, "mysqldump.sql.gz".
265
+ DESC
266
+ task :restore, :roles => :db do
267
+ run "/usr/local/ec2onrails/bin/restore_app_db.rb --bucket #{cfg[:restore_from_bucket]} --dir #{cfg[:restore_from_bucket_subdir]}"
268
+ end
269
+
270
+ desc <<-DESC
271
+ [internal] Initialize the default backup folder on S3 (i.e. do a full
272
+ backup of the newly-created db so the automatic incremental backups
273
+ make sense).
274
+ DESC
275
+ task :init_backup, :roles => :db do
276
+ run "/usr/local/ec2onrails/bin/backup_app_db.rb --reset"
277
+ end
278
+ end
279
+
280
+ namespace :server do
281
+ desc <<-DESC
282
+ Tell the servers what roles they are in. This configures them with \
283
+ the appropriate settings for each role, and starts and/or stops the \
284
+ relevant services.
285
+ DESC
286
+ task :set_roles, :roles => all_admin_role_names do
287
+ # TODO generate this based on the roles that actually exist so arbitrary new ones can be added
288
+ roles = {
289
+ :web => hostnames_for_role(:web),
290
+ :app => hostnames_for_role(:app),
291
+ :db_primary => hostnames_for_role(:db, :primary => true),
292
+ :memcache => hostnames_for_role(:memcache)
293
+ }
294
+ roles_yml = YAML::dump(roles)
295
+ put roles_yml, "/tmp/roles.yml"
296
+ sudo "cp /tmp/roles.yml /etc/ec2onrails"
297
+ sudo "/usr/local/ec2onrails/bin/set_roles.rb"
298
+ end
299
+
300
+ desc <<-DESC
301
+ Change the default value of RAILS_ENV on the server. Technically
302
+ this changes the server's mongrel config to use a different value
303
+ for "environment". The value is specified in :rails_env.
304
+ Be sure to do deploy:restart after this.
305
+ DESC
306
+ task :set_rails_env, :roles => all_admin_role_names do
307
+ rails_env = fetch(:rails_env, "production")
308
+ sudo "/usr/local/ec2onrails/bin/set_rails_env #{rails_env}"
309
+ end
310
+
311
+ desc <<-DESC
312
+ Upgrade to the newest versions of all Ubuntu packages.
313
+ DESC
314
+ task :upgrade_packages, :roles => all_admin_role_names do
315
+ sudo "aptitude -q update"
316
+ run "export DEBIAN_FRONTEND=noninteractive; sudo aptitude -q -y safe-upgrade"
317
+ end
318
+
319
+ desc <<-DESC
320
+ Upgrade to the newest versions of all rubygems.
321
+ DESC
322
+ task :upgrade_gems, :roles => all_admin_role_names do
323
+ sudo "gem update --system --no-rdoc --no-ri"
324
+ sudo "gem update --no-rdoc --no-ri" do |ch, str, data|
325
+ ch[:data] ||= ""
326
+ ch[:data] << data
327
+ if data =~ />\s*$/
328
+ puts data
329
+ choice = Capistrano::CLI.ui.ask("The gem command is asking for a number:")
330
+ ch.send_data("#{choice}\n")
331
+ else
332
+ puts data
333
+ end
334
+ end
335
+
336
+ end
337
+
338
+ desc <<-DESC
339
+ Install extra Ubuntu packages. Set ec2onrails_config[:packages], it \
340
+ should be an array of strings.
341
+ NOTE: the package installation will be non-interactive, if the packages \
342
+ require configuration either log in as 'admin' and run \
343
+ 'dpkg-reconfigure packagename' or replace the package's config files \
344
+ using the 'ec2onrails:server:deploy_files' task.
345
+ DESC
346
+ task :install_packages, :roles => all_admin_role_names do
347
+ if cfg[:packages] && cfg[:packages].any?
348
+ run "export DEBIAN_FRONTEND=noninteractive; sudo aptitude -q -y install #{cfg[:packages].join(' ')}"
349
+ end
350
+ end
351
+
352
+ desc <<-DESC
353
+ Install extra rubygems. Set ec2onrails_config[:rubygems], it should \
354
+ be with an array of strings.
355
+ DESC
356
+ task :install_gems, :roles => all_admin_role_names do
357
+ if cfg[:rubygems]
358
+ cfg[:rubygems].each do |gem|
359
+ sudo "gem install #{gem} --no-rdoc --no-ri" do |ch, str, data|
360
+ ch[:data] ||= ""
361
+ ch[:data] << data
362
+ if data =~ />\s*$/
363
+ puts data
364
+ choice = Capistrano::CLI.ui.ask("The gem command is asking for a number:")
365
+ ch.send_data("#{choice}\n")
366
+ else
367
+ puts data
368
+ end
369
+ end
370
+ end
371
+ end
372
+ end
373
+
374
+ desc <<-DESC
375
+ A convenience task to upgrade existing packages and gems and install \
376
+ specified new ones.
377
+ DESC
378
+ task :upgrade_and_install_all, :roles => all_admin_role_names do
379
+ upgrade_packages
380
+ upgrade_gems
381
+ install_packages
382
+ install_gems
383
+ end
384
+
385
+ desc <<-DESC
386
+ Set the timezone using the value of the variable named timezone. \
387
+ Valid options for timezone can be determined by the contents of \
388
+ /usr/share/zoneinfo, which can be seen here: \
389
+ http://packages.ubuntu.com/cgi-bin/search_contents.pl?searchmode=filelist&word=tzdata&version=gutsy&arch=all&page=1&number=all \
390
+ Remove 'usr/share/zoneinfo/' from the filename, and use the last \
391
+ directory and file as the value. For example 'Africa/Abidjan' or \
392
+ 'posix/GMT' or 'Canada/Eastern'.
393
+ DESC
394
+ task :set_timezone, :roles => all_admin_role_names do
395
+ if cfg[:timezone]
396
+ sudo "bash -c 'echo #{cfg[:timezone]} > /etc/timezone'"
397
+ sudo "cp /usr/share/zoneinfo/#{cfg[:timezone]} /etc/localtime"
398
+ end
399
+ end
400
+
401
+ desc <<-DESC
402
+ Deploy a set of config files to the server, the files will be owned by \
403
+ root. This doesn't delete any files from the server. This is intended
404
+ mainly for customized config files for new packages installed via the \
405
+ ec2onrails:server:install_packages task. Subdirectories and files \
406
+ inside here will be placed within the same directory structure \
407
+ relative to the root of the server's filesystem.
408
+ DESC
409
+ task :deploy_files, :roles => all_admin_role_names do
410
+ if cfg[:server_config_files_root]
411
+ begin
412
+ filename = "config_files.tar"
413
+ local_file = "#{Dir.tmpdir}/#{filename}"
414
+ remote_file = "/tmp/#{filename}"
415
+ FileUtils.cd(cfg[:server_config_files_root]) do
416
+ File.open(local_file, 'wb') { |tar| Minitar.pack(".", tar) }
417
+ end
418
+ put File.read(local_file), remote_file
419
+ sudo "tar xvf #{remote_file} -o -C /"
420
+ ensure
421
+ rm_rf local_file
422
+ run "rm -f #{remote_file}"
423
+ end
424
+ end
425
+ end
426
+
427
+ desc <<-DESC
428
+ Restart a set of services. Set ec2onrails_config[:services_to_restart] \
429
+ to an array of strings. It's assumed that each service has a script \
430
+ in /etc/init.d
431
+ DESC
432
+ task :restart_services, :roles => all_admin_role_names do
433
+ if cfg[:services_to_restart] && cfg[:services_to_restart].any?
434
+ cfg[:services_to_restart].each do |service|
435
+ run_init_script(service, "restart")
436
+ end
437
+ end
438
+ end
439
+
440
+ desc <<-DESC
441
+ Set the email address that mail to the admin user forwards to.
442
+ DESC
443
+ task :set_admin_mail_forward_address, :roles => all_admin_role_names do
444
+ put cfg[:admin_mail_forward_address], "/home/admin/.forward" if cfg[:admin_mail_forward_address]
445
+ end
446
+
447
+ desc <<-DESC
448
+ Enable ssl for the web server. The SSL cert file should be in
449
+ /etc/ssl/certs/default.pem and the SSL key file should be in
450
+ /etc/ssl/private/default.key (use the deploy_files task).
451
+ DESC
452
+ task :enable_ssl, :roles => :web_admin do
453
+ sudo "a2enmod ssl"
454
+ sudo "a2ensite default-ssl"
455
+ run_init_script("apache2", "restart")
456
+ end
457
+ end
458
+
459
+ end
460
+ end