caplets 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,2 @@
1
+ == 1.0.0
2
+ * Initial public release
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Dean Strelau, Mint Digital
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/MODULES.md ADDED
@@ -0,0 +1,148 @@
1
+ Caplets Modules
2
+ ===============
3
+
4
+ caplets/deploy
5
+ --------------
6
+
7
+ #### Variables
8
+
9
+ `:bin_cmd` - Command used to execute binaries within the context of the app.
10
+ For instance, `caplets/bundle` sets this to `gem exec`. (default: `nil`)
11
+
12
+ `:environment` - The environment for this deployment, used as RAILS_ENV among
13
+ other things. (default: `production`)
14
+
15
+ `:required_children` - Directories that need to exist in the project root.
16
+ These will be created during `deploy:setup`. This is the caplets replacement
17
+ of `:shared_children`. (default: `%w[config log public tmp/pids]`)
18
+
19
+ `:server_processes` - Number of backend processes (ie, mongrels or unicorn
20
+ workers) to run. (default: `2`)
21
+
22
+ `:server_port` - Port number on which to bind backend processes.
23
+ (default: `8000`)
24
+
25
+ `:user` - UNIX user as which to login and run deploys. (default: `deploy`)
26
+
27
+ ### caplets/bundle
28
+
29
+ Bundler 0.9+ support. By default, run after every code update, this `bundle
30
+ install`s gems into the project (not into system gems to avoid using `sudo`).
31
+
32
+ #### Variables
33
+
34
+ `:bundle_exclude` - Bundler groups to exclude from installation. Passed to
35
+ bundler's `--without` switch. (default: `%w[development test]`)
36
+
37
+ `:bundle_roles` - The server roles on which to bundle. (default: `[:app]`)
38
+
39
+ `:bundle_to` - Subdirectory of the application in which to put installed gems.
40
+ Passed as an argument to `bundle install`. (default: `vendor/bundled_gems`)
41
+
42
+ #### Tasks
43
+
44
+ `deploy:bundle:install` - Runs a `bundle install` to install needed gems from
45
+ the application's Gemfile.
46
+
47
+ #### Hooks
48
+
49
+ `deploy:bundle:install` after `deploy:update_code`
50
+
51
+ ### caplets/db
52
+
53
+ Tasks to support using ActiveRecord within your applications. This module
54
+ adds functionality to write out your `database.yml` file and provides a
55
+ `deploy:migrations` task.
56
+
57
+ Differently from capistrano defaults, caplets does not expect your code to be
58
+ deployed to your DB server. This means you don't even have to list it in your
59
+ deploy file. Instead, it expects to run your migrations from your `:primary
60
+ :app` server.
61
+
62
+ #### Variables
63
+
64
+ `:db_host` - default: `localhost`
65
+ `:db_adapter` - default: `mysql`
66
+ `:db_database` - default: `<application>_<environment>`
67
+ `:db_username` - default: `<user>`
68
+ `:db_password` - default: `<prompt for value>`
69
+ `:db_encoding` - default: `utf8'
70
+
71
+ These variables are used to set the corresponding values in your
72
+ `database.yml` file. These values will be scoped appropriately under a key
73
+ for your current `:environment`. If you'd rather, you can specify the entire
74
+ `database.yml` yourself using `:db_confg`
75
+
76
+ `:db_extra` - A hash of values merged into your :db_config, outside of any
77
+ environment key. Useful for adding access to slaves or other DBs.
78
+ (default: {})
79
+
80
+ #### Tasks
81
+
82
+ `deploy:db:config` - Write a generated `database.yml` to your project's config
83
+ directory.
84
+
85
+ `deploy:migrate` - Run `rake db:migrate` on your primary app server.
86
+
87
+ `deploy:migrations` - Do a deploy with migrations, including doing a
88
+ `web:disable` first and restarting (not reloading) the backends
89
+
90
+ #### Hooks
91
+
92
+ `deploy:db:config` after `deploy:setup`
93
+
94
+ ### caplets/git-tag
95
+
96
+ Automatically tag a revision on deploy.
97
+
98
+ #### Tasks
99
+
100
+ `git:tag_current_release` - Tag the `:current_revision` with a tag like
101
+ `deploy-<environment>-<timestamp>` and push it to the origin.
102
+
103
+ #### Hooks
104
+
105
+ `git:tag_current_release` after `deploy:migrations`
106
+ `git:tag_current_release` after `deploy:quick`
107
+ `git:tag_current_release` after `deploy:rebuild`
108
+
109
+ ### caplets/memcached
110
+
111
+ Support for keeping track of memcached nodes and writing a `memcached.yml`
112
+ config file based on the `:memcached` role.
113
+
114
+ ### Variables
115
+
116
+ `:memcached_servers` - An array of 'host:port' strings of memcached servers.
117
+ If any of your servers have the `:memcached` role, this is constructed
118
+ dynamically based on those servers' `:private_ip`s. Otherwise:
119
+ (default: `%w[localhost:11211]`)
120
+
121
+ ### Tasks
122
+
123
+ `deploy:memcached:config` - Write a generated `memcached.yml` to your
124
+ projet's config directory.
125
+
126
+ ### Hooks
127
+
128
+ `deploy:memcached:config` after `deploy:setup`
129
+
130
+ ### caplets/mongrel
131
+
132
+ ### caplets/networkfs
133
+
134
+ ## caplets/passenger
135
+
136
+ ## caplets/thinking-sphinx
137
+
138
+ ## caplets/unicorn
139
+
140
+ ## caplets/unicorn_rails
141
+
142
+ ## caplets/utils
143
+
144
+ ## caplets/web
145
+
146
+ ## caplets/whenever
147
+
148
+ ## caplets/yaml
data/README.md ADDED
@@ -0,0 +1,105 @@
1
+ Caplets
2
+ =======
3
+
4
+ Capistrano is old and busted, right? Time to move on? WRONG! Caplets makes
5
+ capistrano the new hotness once again. It bring capistrano into the future
6
+ with all your favorite goodies: git, bundler, unicorn, and more.
7
+
8
+
9
+ WARNING: Caplets has evolved out of Mint Digital's real-world deployments of
10
+ many large Rails/Rack applications. Although we have tried to keep it as
11
+ generic and customizable as possible, it is still very opinionated. That is
12
+ to say, it is largely tailored to how we like to deploy applications. If
13
+ caplets is missing something important to you, [submit an issue][issues] or
14
+ [fork away][fork].
15
+
16
+ [issues]: http://github.com/mintdigital/caplets/issues
17
+ [fork]: http://github.com/mintdigital/caplets/fork
18
+
19
+ Quickstart
20
+ ----------
21
+
22
+
23
+ $ gem install caplets
24
+
25
+ # config/deploy.rb
26
+ require 'caplets'
27
+ load 'caplets/memcached'
28
+ load 'caplets/whenever'
29
+ # etc...
30
+
31
+ Roles
32
+ -----
33
+
34
+ Caplets depends heavily on capistrano's concept of server roles to apply tasks
35
+ only to the servers that require them. Most often, your sever definitions will
36
+ look like this:
37
+
38
+ server 'app1.mintdigital.com',
39
+ :app, :memcached, :sphinx, :assets,
40
+ :primary => true
41
+
42
+ Modules
43
+ -------
44
+
45
+ Caplets is a series of `load`able capistrano extensions. This given you
46
+ maximum flexibility in your deployments without having to remember to
47
+ set/unset loads of capistrano variables. Only the tasks you need get run.
48
+
49
+ See the [MODULES][] file for descriptions of all the available modules, along
50
+ with what tasks they add and what variables they use.
51
+
52
+ [MODULES]: http://github.com/mintdigital/caplets/blob/master/MODULES.md
53
+
54
+ caplets/deploy
55
+ --------------
56
+
57
+ The one module you will _always_ get, even if you just require
58
+ `caplets/basic` is `caplets/deploy`. This file is the heart of caplets; it
59
+ sets up the essentials of what caplets considers a modern deployment.
60
+
61
+ The biggest change to the standard capistrano setup is that we use git to
62
+ manage releases. Let me say that again as it's important:
63
+
64
+ ** Instead of using subdirectories and symlinks to manage releases, caplets
65
+ uses git.**
66
+
67
+ That means:
68
+
69
+ - You will not have `releases` or `shared` directories.
70
+ - Your code will be directly inside your `:deploy_to` directory.
71
+ - `:current_path`, `:current_release`, `:release_path`, `:latest_release`,
72
+ and `:shared_path` will all be the same.
73
+ - Rollbacks still work, through the power of `git reset`
74
+ - "shared" files are really just files written to your project root but not
75
+ under git version control -- no symlinks needed.
76
+
77
+ Here are a few other changes that `caplets/deploy` makes:
78
+
79
+ - `:use_sudo` is false by default
80
+ - `:rails_env` is replaced with `:environment`, which caplets expects you to
81
+ set in your deploy file.
82
+ - `:user` and `:group` default to `deploy`
83
+ - `:shared_children` is not used. Use `:required_children` instead.
84
+ - The standard `deploy` task is disabled. Use `deploy:quick` to do an
85
+ `deploy:update` + `deploy:reload`. Load `caplets/db` for caplets'
86
+ replacement `deploy:migrations` task.
87
+ - Capistrano's built-in `deploy:web:enable` and `deploy:web:disable` are
88
+ disabled. Load `caplets/web` for caplets' replacements.
89
+
90
+ require 'caplets'
91
+ -----------------
92
+
93
+ Requiring `caplets` gets you the most common modules in one go. If you don't
94
+ want them all, you can always require `caplets/basic` instead.
95
+
96
+ # config/deploy.rb
97
+ require 'caplets'
98
+
99
+ # Equivalent to...
100
+ require 'caplets/basic'
101
+ load 'caplets/bundle'
102
+ load 'caplets/db'
103
+ load 'caplets/logs'
104
+ load 'caplets/web'
105
+ load 'caplets/yaml'
data/lib/caplets.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'caplets/basic'
2
+
3
+ config = Capistrano::Configuration.instance(:raise_on_error)
4
+ config.load 'caplets/bundle'
5
+ config.load 'caplets/db'
6
+ config.load 'caplets/logs'
7
+ config.load 'caplets/web'
8
+ config.load 'caplets/yaml'
@@ -0,0 +1,11 @@
1
+ require 'caplets/version'
2
+ require 'caplets/utils'
3
+
4
+ config = Capistrano::Configuration.instance(:raise_on_error)
5
+
6
+ # Apparently $: isn't good enough for Capistrano.
7
+ config.instance_eval do
8
+ @load_paths.unshift File.expand_path('..',File.dirname(__FILE__))
9
+ end
10
+
11
+ config.load 'caplets/deploy'
@@ -0,0 +1,19 @@
1
+ set :bundle_roles, [:app]
2
+ set :bundle_exclude, %w[development test]
3
+ set :bundle_to, 'vendor/bundled_gems'
4
+ set :bin_cmd, 'bundle exec'
5
+
6
+ ## Tasks
7
+ namespace :deploy do
8
+ namespace :bundle do
9
+ desc "Install gems from Gemfile"
10
+ task :install, :roles => lambda { fetch(:bundle_roles) },
11
+ :except => {:no_release => true} do
12
+ without = fetch(:bundle_exclude).map{|g| "--without #{g}"}.join(' ')
13
+ run_current "bundle install #{fetch(:bundle_to)} #{without}"
14
+ end
15
+ end
16
+ end
17
+
18
+ ## Hooks
19
+ after 'deploy:update_code', 'deploy:bundle:install'
@@ -0,0 +1,11 @@
1
+ ## Defaults
2
+ _cset :cache_dir, 'cache'
3
+
4
+ ## Tasks
5
+ namespace :deploy do
6
+ namespace :cache do
7
+ task :clear, :roles => :app, :only => { :primary => true } do
8
+ run "find #{fetch(:current_path)}/public/#{fetch(:cache_dir)} -type f -delete"
9
+ end
10
+ end
11
+ end
data/lib/caplets/db.rb ADDED
@@ -0,0 +1,61 @@
1
+ # In deploy:migrations, we use web.enable and web.disable
2
+ # so we need to make sure this has been loaded.
3
+ load 'caplets/web'
4
+
5
+ ## Defaults
6
+ _cset :db_host, 'localhost'
7
+ _cset :db_adapter, 'mysql'
8
+ _cset(:db_database) { "#{fetch(:application)}_#{fetch(:environment)}" }
9
+ _cset(:db_username) { user }
10
+ _cset(:db_password) {
11
+ Capistrano::CLI.password_prompt(
12
+ "Please enter MySQL password for user #{fetch(:db_username)}: "
13
+ )
14
+ }
15
+ _cset :db_encoding, 'utf8'
16
+ _cset(:db_config) {
17
+ fetch(:db_extra).merge({
18
+ fetch(:environment).to_s => {
19
+ 'host' => fetch(:db_host).to_s,
20
+ 'adapter' => fetch(:db_adapter).to_s,
21
+ 'database' => fetch(:db_database).to_s,
22
+ 'username' => fetch(:db_username).to_s,
23
+ 'password' => fetch(:db_password).to_s,
24
+ 'encoding' => fetch(:db_encoding).to_s
25
+ }
26
+ })
27
+ }
28
+ _cset :db_extra, {}
29
+
30
+ ## Tasks
31
+ namespace :deploy do
32
+ namespace :db do
33
+ desc "write out database.yml"
34
+ task :config, :roles => :app do
35
+ put YAML.dump(fetch(:db_config)), "#{shared_path}/config/database.yml"
36
+ end
37
+ end
38
+
39
+ # migrate runs on the app server (no need for the code on the DB server)
40
+ task :migrate, :roles => :app, :only => { :primary => true } do
41
+ rake 'db:migrate'
42
+ end
43
+
44
+ desc <<-DESC
45
+ Do a deploy with migrations.
46
+
47
+ Use this when you need to completely disable the site and run migrations.
48
+ This will put up a maintenance page, run the migrations and completely
49
+ restart the app server processes.
50
+ DESC
51
+ task :migrations do
52
+ web.disable
53
+ update
54
+ migrate
55
+ restart
56
+ web.enable
57
+ end
58
+ end
59
+
60
+ ## Hooks
61
+ after 'deploy:setup', 'deploy:db:config'
@@ -0,0 +1,148 @@
1
+ # A lot of this taken from GitHub's deployment:
2
+ # http://github.com/blog/470-deployment-script-spring-cleaning
3
+
4
+ # Overwrite cap defaults
5
+ set :use_sudo, false
6
+ set :deploy_via, :git # not used
7
+ set :shared_children, %w[] # not relevant
8
+ set(:rails_env) { environment } ## some built-in tasks use :rails_env
9
+ default_run_options[:pty] = true
10
+
11
+ # Setup basics
12
+ _cset :user, 'deploy'
13
+ _cset(:group) { user }
14
+ _cset :environment, 'production'
15
+ _cset :required_children, %w[config log public tmp/pids]
16
+
17
+ # Server basics
18
+ _cset :server_processes, 2
19
+ _cset :server_port, 8000
20
+
21
+ # Use one directory for everything
22
+ set(:current_path) { fetch(:deploy_to) }
23
+ set(:latest_release) { fetch(:current_path) }
24
+ set(:release_path) { fetch(:current_path) }
25
+ set(:current_release) { fetch(:current_path) }
26
+ set(:shared_path) { fetch(:current_path) }
27
+
28
+ # Behold, the power of git
29
+ set(:current_revision) {
30
+ capture("cd #{current_path}; git rev-parse --short HEAD").strip
31
+ }
32
+ set(:latest_revision) {
33
+ capture("cd #{current_path}; git rev-parse --short HEAD").strip
34
+ }
35
+ set(:previous_revision) {
36
+ capture("cd #{current_path}; git rev-parse --short HEAD@{1}").strip
37
+ }
38
+
39
+ ###########
40
+ ## Tasks ##
41
+ ###########
42
+
43
+ namespace :deploy do
44
+ [:default, :cleanup, :symlink].each do |t|
45
+ task t do
46
+ abort "Task disabled by caplets."
47
+ end
48
+ end
49
+ [:enable, :disable].each do |t|
50
+ namespace :web do
51
+ task t do
52
+ abort "Task disabled by caplets. Load 'caplets/web' to re-enable."
53
+ end
54
+ end
55
+ end
56
+
57
+ desc "Setup a Git-based deploy"
58
+ task :setup, :except => {:no_release => true} do
59
+ run_multi(
60
+ "if [ -d #{fetch(:current_path)}/.git ]",
61
+ "then cd #{fetch(:current_path)}",
62
+ "#{try_sudo} git fetch",
63
+ "else #{try_sudo} git clone #{fetch(:repository)} #{fetch(:current_path)}",
64
+ "fi",
65
+ "mkdir -p " + fetch(:required_children,[]).
66
+ map {|dir| "#{fetch(:current_path)}/#{dir}" }.
67
+ join(' ')
68
+ )
69
+ end
70
+
71
+ desc <<-DESC
72
+ Do a zero-downtime deploy.
73
+
74
+ Use this when you have small code changes that you want to go out with
75
+ minimal impact. This does not put up the maintanance page nor run migrations,
76
+ and will tell your app servers to 'reload' in-place instead of restarting
77
+ completely if they support it.
78
+ DESC
79
+ task :quick do
80
+ update
81
+ reload
82
+ end
83
+
84
+ task :update do
85
+ update_code
86
+ end
87
+
88
+ desc "Update the deployed code from git"
89
+ task :update_code, :except => { :no_release => true } do
90
+ run_multi "cd #{fetch(:current_path)}",
91
+ "git fetch origin",
92
+ "git reset --hard #{fetch(:branch)}"
93
+ finalize_update
94
+ end
95
+
96
+ # Just timestamps, please
97
+ task :finalize_update, :except => { :no_release => true } do
98
+ if fetch(:normalize_asset_timestamps, false)
99
+ stamp = Time.now.utc.strftime("%Y%m%d%H%M.%S")
100
+ asset_paths = %w(images stylesheets javascripts).map {|p|
101
+ "#{latest_release}/public/#{p}"
102
+ }.join(" ")
103
+ run "find #{asset_paths} -exec touch -t #{stamp} {} ';'; true",
104
+ :env => { "TZ" => "UTC" }
105
+ end
106
+ end
107
+
108
+ %w[start stop restart reload].each do |taskname|
109
+ desc "#{taskname.capitalize} the application server(s)"
110
+ task taskname, :roles => :app, :except => {:no_release => true} do
111
+ deploy.send(fetch(:_server)).send(taskname)
112
+ end
113
+ end
114
+
115
+ namespace :rollback do
116
+ task :code do
117
+ abort "Task disabled by caplets"
118
+ end
119
+
120
+ desc <<-DESC
121
+ [internal] Rollback repo.
122
+
123
+ Moves the repo back one release by checking out HEAD@{1}
124
+ DESC
125
+ task :repo, :except => { :no_release => true } do
126
+ set :branch, "HEAD@{1}"
127
+ deploy.update_code
128
+ end
129
+
130
+ desc <<-DESC
131
+ [internal] Rewrite git reflog.
132
+ This makes HEAD@{1} continue to point at the next previous release.
133
+ DESC
134
+ task :cleanup, :except => { :no_release => true } do
135
+ run_multi "cd #{fetch(:current_path)}",
136
+ 'git reflog delete --rewrite HEAD@{1}',
137
+ 'git reflog delete --rewrite HEAD@{1}'
138
+ end
139
+
140
+ desc "Rolls back to the previously deployed version."
141
+ task :default do
142
+ rollback.repo
143
+ rollback.cleanup
144
+ deploy.reload
145
+ end
146
+ end
147
+
148
+ end
@@ -0,0 +1,16 @@
1
+ ## Tasks
2
+ namespace :git do
3
+ desc 'Tag and push tag for current release'
4
+ task :tag_current_release, :roles => :app, :only => {:primary => true} do
5
+ if ENV['NO_TAG'].nil?
6
+ tag = "deploy-#{environment}-#{Time.now.to_i}"
7
+ `git tag #{tag} #{fetch(:current_revision)}`
8
+ `git push origin #{tag}`
9
+ end
10
+ end
11
+ end
12
+
13
+ ## Hooks
14
+ after 'deploy:migrations', 'git:tag_current_release'
15
+ after 'deploy:quick', 'git:tag_current_release'
16
+ after 'deploy:rebuild', 'git:tag_current_release'
@@ -0,0 +1,3 @@
1
+ task :logs, :roles => :app do
2
+ stream "tail -n 0 -f #{shared_path}/log/*.log"
3
+ end
@@ -0,0 +1,30 @@
1
+ ## Defaults
2
+ _cset(:memcached_servers) do
3
+ if roles[:memcached].servers.any?
4
+ unless roles[:memcached].servers.all? {|s| s.options[:private_ip] }
5
+ abort "Set :private_ip for all :memcached servers or "+
6
+ "set :memcached_servers explicitly."
7
+ end
8
+ roles[:memcached].servers.map {|s| s.options[:private_ip] + ":11211" }
9
+ else
10
+ %w[localhost:11211]
11
+ end
12
+ end
13
+
14
+ ## Tasks
15
+ namespace :deploy do
16
+ namespace :memcached do
17
+ desc "Generate the memcached.yml in the shared directory"
18
+ task :config, :roles => :app do
19
+ config = {
20
+ environment => {
21
+ 'servers' => fetch(:memcached_servers)
22
+ }
23
+ }
24
+ put YAML.dump(config), "#{shared_path}/config/memcached.yml"
25
+ end
26
+ end
27
+ end
28
+
29
+ ## Hooks
30
+ after 'deploy:setup', 'deploy:memcached:config'
@@ -0,0 +1,46 @@
1
+ set :_server, :mongrel
2
+
3
+ namespace :deploy do
4
+ namespace :mongrel do
5
+ desc "Write the cluster config."
6
+ task :config, :roles => :app, :except => {:no_release => true} do
7
+ config = {
8
+ 'user' => user,
9
+ 'group' => group,
10
+ 'cwd' => current_path,
11
+ 'environment' => environment,
12
+ 'port' => server_port,
13
+ 'address' => '0.0.0.0',
14
+ 'pid_file' => "#{shared_path}/tmp/pids/mongrel.pid",
15
+ 'servers' => server_processes
16
+ }
17
+ put YAML.dump(config), "#{shared_path}/config/cluster.yml"
18
+ end
19
+
20
+ desc "Start the mongrel_cluster"
21
+ task :start, :roles => :app do
22
+ run "cd #{current_path} && " +
23
+ "mongrel_rails cluster::start -C #{shared_path}/config/cluster.yml --clean"
24
+ end
25
+
26
+ desc "Stop the mongrel_cluster"
27
+ task :stop, :roles => :app do
28
+ run "cd #{current_path} && " +
29
+ "mongrel_rails cluster::stop -C #{shared_path}/config/cluster.yml --clean"
30
+ end
31
+
32
+ desc "Restart the mongrel_cluster"
33
+ task :restart, :roles => :app do
34
+ run "cd #{current_path} && " +
35
+ "mongrel_rails cluster::restart -C #{shared_path}/config/cluster.yml --clean"
36
+ end
37
+
38
+ desc "Restart the mongrel_cluster, as mongrel does not support reloading"
39
+ task :reload, :roles => :app do
40
+ restart
41
+ end
42
+ end
43
+ end
44
+
45
+ ## Hooks
46
+ after 'deploy:setup', 'deploy:mongrel:config'
@@ -0,0 +1,30 @@
1
+ # loading this file will modify your deploy for use with a networked filesystem
2
+ # Mostly, this means you can define paths that will get symlinked to the FS
3
+
4
+ ## Defaults
5
+ _cset :networkfs_path, '/srv/share'
6
+ _cset :networkfs_resources, []
7
+
8
+ ## Tasks
9
+ namespace :deploy do
10
+ namespace :networkfs do
11
+ desc "Create the application's directory on the network FS"
12
+ task :setup, :roles => :app, :only => {:primary => true} do
13
+ try_sudo "mkdir -p #{fetch(:networkfs_path)}/#{fetch(:application)}"
14
+ end
15
+
16
+ desc "Symlink networkfs_resources to the network FS"
17
+ task :symlink, :roles => [:app, :web] do
18
+ cmds = fetch(:networkfs_resources, []).map do |resource|
19
+ "ln -nfs" +
20
+ " #{fetch(:networkfs_path)}/#{fetch(:application)}/#{resource}" +
21
+ " #{fetch(:release_path)}/#{resource}"
22
+ end
23
+ try_sudo cmds.join(" && ")
24
+ end
25
+ end
26
+ end
27
+
28
+ ## Hooks
29
+ after 'deploy:setup', 'deploy:networkfs:setup'
30
+ after 'deploy:setup', 'deploy:networkfs:symlink'
@@ -0,0 +1,19 @@
1
+ set :_server, :passenger
2
+
3
+ namespace :deploy do
4
+ namespace :passenger do
5
+ # These are no-ops for Passenger
6
+ task :start, :roles => :app, :except => {:no_release => true} do; end
7
+ task :stop, :roles => :app, :except => {:no_release => true} do; end
8
+
9
+ desc 'Restart the Passenger processes by touching tmp/restart.txt'
10
+ task :restart, :roles => :app, :except => {:no_release => true} do
11
+ run "touch #{current_path}/tmp/restart.txt"
12
+ end
13
+
14
+ desc 'Restart the Passenger process by touching tmp/restart.txt'
15
+ task :restart, :roles => :app, :except => {:no_release => true} do
16
+ run "touch #{current_path}/tmp/restart.txt"
17
+ end
18
+ end # namespace :passenger
19
+ end # namespace :deploy
@@ -0,0 +1,72 @@
1
+ # We require web:enable and web:disable for the rebuild task
2
+ load 'caplets/web'
3
+
4
+ ## Defaults
5
+ _cset(:sphinx_address) {
6
+ roles[:sphinx].servers.detect {|s| s.options[:primary] }.options[:private_ip] ||
7
+ '127.0.0.1'
8
+ }
9
+ _cset(:sphinx_max_matches) { 1000 }
10
+
11
+ ## Tasks
12
+ namespace :deploy do
13
+ desc <<-DESC
14
+ Do a deploy with migrations and a sphinx rebuild.
15
+
16
+ Use this when you need to completely rebuild the sphinx index on deploy,
17
+ for instance if you have changed the index definitions. This task puts up
18
+ the maintenance page, runs migrations, rebuilds the sphinx index and restarts
19
+ the app server processes.
20
+ DESC
21
+ task :rebuild do
22
+ web.disable
23
+ update
24
+ migrate
25
+ ts.rebuild
26
+ restart
27
+ web.enable
28
+ end
29
+
30
+ namespace :sphinx do
31
+ desc "Generate the sphinx.yml file in the shared directory"
32
+ task :config, :roles => [:app, :sphinx] do
33
+ config = {
34
+ fetch(:environment) => {
35
+ 'address' => fetch(:sphinx_address),
36
+ 'max_matches' => fetch(:sphinx_max_matches)
37
+ }
38
+ }
39
+ put YAML.dump(config), "#{shared_path}/config/sphinx.yml"
40
+ end
41
+ end
42
+
43
+ namespace :ts do
44
+ desc "Generate the Thinking Sphinx config file"
45
+ task :config, :roles => [:app, :sphinx] do
46
+ rake 'ts:config'
47
+ end
48
+
49
+ desc "Rebuild Thinking Sphinx config and indexes and restart Sphinx"
50
+ task :rebuild, :roles => :sphinx do
51
+ rake 'ts:rebuild'
52
+ end
53
+
54
+ desc "Run a Sphinx index via Thinking Sphinx"
55
+ task :index, :roles => :sphinx do
56
+ rake 'ts:in'
57
+ end
58
+
59
+ desc "Start Sphinx searchd via Thinking Sphinx"
60
+ task :start, :roles => :sphinx do
61
+ rake 'ts:start'
62
+ end
63
+
64
+ desc "Stop Sphinx searchd via Thinking Sphinx"
65
+ task :stop, :roles => :sphinx do
66
+ rake 'ts:stop'
67
+ end
68
+ end
69
+ end
70
+
71
+ ## Hooks
72
+ after 'deploy:setup', 'deploy:sphinx:config'
@@ -0,0 +1,83 @@
1
+ set :_server, :unicorn
2
+
3
+ namespace :deploy do
4
+ namespace :unicorn do
5
+
6
+ def start_cmd
7
+ "#{fetch(:_unicorn_cmd,'unicorn')} -D" +
8
+ " -c config/unicorn.rb" +
9
+ " -E #{fetch(:environment)}"
10
+ end
11
+
12
+ def stop_cmd(signal='QUIT', pid='unicorn.pid')
13
+ pid = "tmp/pids/#{pid}"
14
+ "if [ -f #{pid} ] ; then kill -#{signal} $(cat #{pid}) ; fi"
15
+ end
16
+
17
+ def wait_for_pid(pid, to_disappear=false)
18
+ "while [ #{'!' unless to_disappear} -f tmp/pids/#{pid} ] ; do sleep 1; done"
19
+ end
20
+
21
+ desc "Write the unicorn config to the shared directory."
22
+ task :config, :roles => :app, :except => {:no_release => true} do
23
+ config = File.read(__FILE__).split(/^__END__$/, 2)[1]
24
+ put ERB.new(config,nil,'-').result(binding.dup),
25
+ fetch(:current_path) + '/config/unicorn.rb'
26
+ end
27
+
28
+ desc "Start the unicorn master and workers."
29
+ task :start, :roles => :app, :except => {:no_release => true} do
30
+ run_current start_cmd
31
+ end
32
+
33
+ desc "Stop the unicorn master and workers."
34
+ task :stop, :roles => :app, :except => {:no_release => true} do
35
+ run_current stop_cmd
36
+ end
37
+
38
+ desc "Restart the unicorn master and workers."
39
+ task :restart, :roles => :app, :except => {:no_release => true} do
40
+ run_current stop_cmd,
41
+ wait_for_pid('unicorn.pid', :to_disappear),
42
+ start_cmd,
43
+ wait_for_pid('unicorn.pid'),
44
+ 'sleep 5' # app start-up
45
+ end
46
+
47
+ desc "Reload the unicorn master and workers with zero downtime."
48
+ task :reload, :roles => :app, :except => {:no_release => true} do
49
+ run_current stop_cmd('USR2'),
50
+ wait_for_pid('unicorn.pid.oldbin'),
51
+ wait_for_pid('unicorn.pid'),
52
+ 'sleep 5', # app start-up
53
+ stop_cmd('QUIT', 'unicorn.pid.oldbin')
54
+ end
55
+ end
56
+ end
57
+
58
+ ## Hooks
59
+ after 'deploy:setup', 'deploy:unicorn:config'
60
+
61
+ __END__
62
+ pid '<%= fetch(:current_path) %>/tmp/pids/unicorn.pid'
63
+
64
+ working_directory "<%= fetch(:current_path) %>"
65
+
66
+ worker_processes <%= fetch(:server_processes, 4) %>
67
+
68
+ # Pre-load for fast worker forks and COW-friendliness
69
+ preload_app <%= fetch(:preload_app, true) %>
70
+
71
+ timeout <%= fetch(:server_timeout, 60) %>
72
+
73
+ listen '0.0.0.0:<%= fetch(:server_port, 8000) %>'
74
+
75
+ stderr_path "<%= current_path %>/log/unicorn.log"
76
+ stdout_path "<%= current_path %>/log/unicorn.log"
77
+
78
+ # http://www.rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
79
+ GC.respond_to?(:copy_on_write_friendly=) and
80
+ GC.copy_on_write_friendly = true
81
+
82
+ <%= fetch(:_unicorn_ar_config,'') %>
83
+ <%= fetch(:unicorn_extra_config,'') %>
@@ -0,0 +1,17 @@
1
+ set :_server, :unicorn
2
+ set :_unicorn_cmd, 'unicorn_rails'
3
+ set :_unicorn_ar_config, <<-CONF
4
+ before_fork do |server, worker|
5
+ # master process doesn't need a connection
6
+ defined?(ActiveRecord::Base) and
7
+ ActiveRecord::Base.connection.disconnect!
8
+ end
9
+
10
+ after_fork do |server, worker|
11
+ # workers need to connect individually
12
+ defined?(ActiveRecord::Base) and
13
+ ActiveRecord::Base.establish_connection
14
+ end
15
+ CONF
16
+
17
+ load 'caplets/unicorn'
@@ -0,0 +1,16 @@
1
+ # This is just a collection of handy methods to use while writing tasks
2
+ module Caplets::Utils
3
+ def rake(cmd)
4
+ run_current "RAILS_ENV=#{fetch(:environment)} rake #{cmd}"
5
+ end
6
+
7
+ def run_current(*cmds)
8
+ run ["cd #{fetch(:current_path)}"].concat(cmds).join(' && ')
9
+ end
10
+
11
+ def run_multi(*cmds)
12
+ run cmds.join(' ; ')
13
+ end
14
+ end
15
+
16
+ Capistrano::Configuration.send :include, Caplets::Utils
@@ -0,0 +1,10 @@
1
+ module Caplets
2
+ module Version
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ TINY = 0
6
+ BUILD = nil
7
+ STRING = [MAJOR,MINOR,TINY,BUILD].compact.join('.')
8
+ end
9
+ VERSION = Version::STRING
10
+ end
@@ -0,0 +1,13 @@
1
+ namespace :deploy do
2
+ namespace :web do
3
+ desc 'Enable maintenance mode'
4
+ task :disable, :roles => :web do
5
+ try_sudo "touch #{fetch(:current_release)}/public/SHOW_MAINTENANCE_PAGE"
6
+ end
7
+
8
+ desc 'Disable maintenance mode'
9
+ task :enable, :roles => :web do
10
+ try_sudo "rm -f #{fetch(:current_release)}/public/SHOW_MAINTENANCE_PAGE"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,32 @@
1
+ # Uses the whenever gem to update the server's crontab, using a combination
2
+ # of role and environment to pick which crontab to use.
3
+ #
4
+ # Put per-server crontabs inside whenever/{environment}.{role}.rb
5
+ #
6
+ namespace :deploy do
7
+ namespace :whenever do
8
+ def update_cmd_for(role)
9
+ load_file = "config/whenever/#{fetch(:environment)}.#{role}.rb"
10
+ [ "cd #{fetch(:current_path)} &&",
11
+ "if [ -f #{load_file} ] ; then",
12
+ "#{fetch(:bin_cmd)} whenever",
13
+ "--update-crontab #{fetch(:application)}.#{fetch(:environment)}.#{role}",
14
+ "--load-file #{load_file}",
15
+ "--set environment=#{fetch(:environment)}",
16
+ "; fi"
17
+ ].join(' ')
18
+ end
19
+
20
+ desc "Update the crontab files by running 'whenever'"
21
+ task :update, :except => { :no_release => true } do
22
+ parallel do |session|
23
+ roles.keys.each do |role|
24
+ session.when "in?(#{role.inspect})", update_cmd_for(role)
25
+ end
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+
32
+ after "deploy:update", "deploy:whenever:update"
@@ -0,0 +1,17 @@
1
+ ## Defaults
2
+ _cset :config_files, {}
3
+
4
+ ## Tasks
5
+ namespace :deploy do
6
+ namespace :yaml do
7
+ desc "Write any defined custom YAML config files."
8
+ task :config, :except => { :no_release => true } do
9
+ fetch(:config_files,{}).each do |name, data|
10
+ put YAML.dump(data), "#{fetch(:shared_path)}/config/#{name}.yml"
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ ## Hooks
17
+ after 'deploy:setup', 'deploy:yaml:config'
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: caplets
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 0
9
+ version: 1.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Dean Strelau
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-06-08 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: capistrano
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 2
29
+ - 5
30
+ - 0
31
+ version: 2.5.0
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ description: " Caplets modernizes your capistrano deployments. At its most basic, it\n provides a fast, efficient git-based deployment without copying release\n trees or symlink tomfoolery. In addition, it includes modules for common\n tasks such as writing config files and crontabs, working with bundler,\n and using a networked filesystem.\n"
35
+ email: dean@mintdigital.com
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files: []
41
+
42
+ files:
43
+ - lib/caplets/basic.rb
44
+ - lib/caplets/bundle.rb
45
+ - lib/caplets/cache.rb
46
+ - lib/caplets/db.rb
47
+ - lib/caplets/deploy.rb
48
+ - lib/caplets/git-tag.rb
49
+ - lib/caplets/logs.rb
50
+ - lib/caplets/memcached.rb
51
+ - lib/caplets/mongrel.rb
52
+ - lib/caplets/networkfs.rb
53
+ - lib/caplets/passenger.rb
54
+ - lib/caplets/thinking-sphinx.rb
55
+ - lib/caplets/unicorn.rb
56
+ - lib/caplets/unicorn_rails.rb
57
+ - lib/caplets/utils.rb
58
+ - lib/caplets/version.rb
59
+ - lib/caplets/web.rb
60
+ - lib/caplets/whenever.rb
61
+ - lib/caplets/yaml.rb
62
+ - lib/caplets.rb
63
+ - CHANGELOG
64
+ - MIT-LICENSE
65
+ - README.md
66
+ - MODULES.md
67
+ has_rdoc: true
68
+ homepage: http://mintdigital.github.com/caplets
69
+ licenses: []
70
+
71
+ post_install_message:
72
+ rdoc_options: []
73
+
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ segments:
81
+ - 0
82
+ version: "0"
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ segments:
88
+ - 0
89
+ version: "0"
90
+ requirements: []
91
+
92
+ rubyforge_project:
93
+ rubygems_version: 1.3.6
94
+ signing_key:
95
+ specification_version: 3
96
+ summary: Capistrano super powers
97
+ test_files: []
98
+