caplets 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2 -0
- data/MIT-LICENSE +20 -0
- data/MODULES.md +148 -0
- data/README.md +105 -0
- data/lib/caplets.rb +8 -0
- data/lib/caplets/basic.rb +11 -0
- data/lib/caplets/bundle.rb +19 -0
- data/lib/caplets/cache.rb +11 -0
- data/lib/caplets/db.rb +61 -0
- data/lib/caplets/deploy.rb +148 -0
- data/lib/caplets/git-tag.rb +16 -0
- data/lib/caplets/logs.rb +3 -0
- data/lib/caplets/memcached.rb +30 -0
- data/lib/caplets/mongrel.rb +46 -0
- data/lib/caplets/networkfs.rb +30 -0
- data/lib/caplets/passenger.rb +19 -0
- data/lib/caplets/thinking-sphinx.rb +72 -0
- data/lib/caplets/unicorn.rb +83 -0
- data/lib/caplets/unicorn_rails.rb +17 -0
- data/lib/caplets/utils.rb +16 -0
- data/lib/caplets/version.rb +10 -0
- data/lib/caplets/web.rb +13 -0
- data/lib/caplets/whenever.rb +32 -0
- data/lib/caplets/yaml.rb +17 -0
- metadata +98 -0
data/CHANGELOG
ADDED
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,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'
|
data/lib/caplets/logs.rb
ADDED
@@ -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
|
data/lib/caplets/web.rb
ADDED
@@ -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"
|
data/lib/caplets/yaml.rb
ADDED
@@ -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
|
+
|