factorylabs-fdlcap 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Factory Design Labs
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/README ADDED
File without changes
data/README.rdoc ADDED
@@ -0,0 +1,7 @@
1
+ = fdlcap
2
+
3
+ Description goes here.
4
+
5
+ == Copyright
6
+
7
+ Copyright (c) 2009 Gabe Varela. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,68 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "fdlcap"
8
+ gem.summary = %Q{a set of capistrano recipies we use regularly at Factory Design Labs}
9
+ gem.email = "interactive@factorylabs.com"
10
+ gem.homepage = "http://github.com/factorylabs/fdlcap"
11
+ gem.authors = ["Factory Design Labs"]
12
+ gem.add_dependency('engineyard-eycap', '>= 0.4.7')
13
+ gem.add_dependency('zilkey-auto_tagger', '>= 0.0.9')
14
+ gem.add_dependency('capistrano', '>= 2.5.5')
15
+ gem.add_dependency('capistrano-ext', '>= 1.2.1')
16
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
+ end
18
+
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
21
+ end
22
+
23
+ require 'rake/testtask'
24
+ Rake::TestTask.new(:test) do |test|
25
+ test.libs << 'lib' << 'test'
26
+ test.pattern = 'test/**/*_test.rb'
27
+ test.verbose = true
28
+ end
29
+
30
+ begin
31
+ require 'rcov/rcovtask'
32
+ Rcov::RcovTask.new do |test|
33
+ test.libs << 'test'
34
+ test.pattern = 'test/**/*_test.rb'
35
+ test.verbose = true
36
+ end
37
+ rescue LoadError
38
+ task :rcov do
39
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
40
+ end
41
+ end
42
+
43
+ begin
44
+ require 'cucumber/rake/task'
45
+ Cucumber::Rake::Task.new(:features)
46
+ rescue LoadError
47
+ task :features do
48
+ abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber"
49
+ end
50
+ end
51
+
52
+ task :default => :test
53
+
54
+ require 'rake/rdoctask'
55
+ Rake::RDocTask.new do |rdoc|
56
+ if File.exist?('VERSION.yml')
57
+ config = YAML.load(File.read('VERSION.yml'))
58
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
59
+ else
60
+ version = ""
61
+ end
62
+
63
+ rdoc.rdoc_dir = 'rdoc'
64
+ rdoc.title = "fdlcap #{version}"
65
+ rdoc.rdoc_files.include('README*')
66
+ rdoc.rdoc_files.include('lib/**/*.rb')
67
+ end
68
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/bin/fdlcap ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ path = ARGV[0]
4
+ File.mkdir_p File.join(path, 'deploy')
data/fdlcap.gemspec ADDED
@@ -0,0 +1,85 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{fdlcap}
5
+ s.version = "0.1.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Factory Design Labs"]
9
+ s.date = %q{2009-06-25}
10
+ s.default_executable = %q{fdlcap}
11
+ s.email = %q{interactive@factorylabs.com}
12
+ s.executables = ["fdlcap"]
13
+ s.extra_rdoc_files = [
14
+ "LICENSE",
15
+ "README",
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ "LICENSE",
20
+ "README",
21
+ "README.rdoc",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "bin/fdlcap",
25
+ "fdlcap.gemspec",
26
+ "features/fdlcap.feature",
27
+ "features/step_definitions/fdlcap_steps.rb",
28
+ "features/support/env.rb",
29
+ "lib/fdlcap.rb",
30
+ "lib/fdlcap/autotagger.rb",
31
+ "lib/fdlcap/craken.rb",
32
+ "lib/fdlcap/database.rb",
33
+ "lib/fdlcap/delayed_job.rb",
34
+ "lib/fdlcap/deploy.rb",
35
+ "lib/fdlcap/geminstaller.rb",
36
+ "lib/fdlcap/newrelic.rb",
37
+ "lib/fdlcap/nginx.rb",
38
+ "lib/fdlcap/performance.rb",
39
+ "lib/fdlcap/rake.rb",
40
+ "lib/fdlcap/recipes.rb",
41
+ "lib/fdlcap/rsync.rb",
42
+ "lib/fdlcap/sass.rb",
43
+ "lib/fdlcap/slice.rb",
44
+ "lib/fdlcap/ssh.rb",
45
+ "lib/fdlcap/symlinks.rb",
46
+ "lib/fdlcap/templates/nginx.auth.conf.erb",
47
+ "lib/fdlcap/templates/nginx.conf.erb",
48
+ "lib/fdlcap/templates/nginx.vhost.conf.erb",
49
+ "lib/fdlcap/thin.rb",
50
+ "lib/fdlcap/thinking_sphinx.rb",
51
+ "test/fdlcap_test.rb",
52
+ "test/test_helper.rb"
53
+ ]
54
+ s.homepage = %q{http://github.com/factorylabs/fdlcap}
55
+ s.rdoc_options = ["--charset=UTF-8"]
56
+ s.require_paths = ["lib"]
57
+ s.rubygems_version = %q{1.3.4}
58
+ s.summary = %q{a set of capistrano recipies we use regularly at Factory Design Labs}
59
+ s.test_files = [
60
+ "test/fdlcap_test.rb",
61
+ "test/test_helper.rb"
62
+ ]
63
+
64
+ if s.respond_to? :specification_version then
65
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
66
+ s.specification_version = 3
67
+
68
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
69
+ s.add_runtime_dependency(%q<engineyard-eycap>, [">= 0.4.7"])
70
+ s.add_runtime_dependency(%q<zilkey-auto_tagger>, [">= 0.0.9"])
71
+ s.add_runtime_dependency(%q<capistrano>, [">= 2.5.5"])
72
+ s.add_runtime_dependency(%q<capistrano-ext>, [">= 1.2.1"])
73
+ else
74
+ s.add_dependency(%q<engineyard-eycap>, [">= 0.4.7"])
75
+ s.add_dependency(%q<zilkey-auto_tagger>, [">= 0.0.9"])
76
+ s.add_dependency(%q<capistrano>, [">= 2.5.5"])
77
+ s.add_dependency(%q<capistrano-ext>, [">= 1.2.1"])
78
+ end
79
+ else
80
+ s.add_dependency(%q<engineyard-eycap>, [">= 0.4.7"])
81
+ s.add_dependency(%q<zilkey-auto_tagger>, [">= 0.0.9"])
82
+ s.add_dependency(%q<capistrano>, [">= 2.5.5"])
83
+ s.add_dependency(%q<capistrano-ext>, [">= 1.2.1"])
84
+ end
85
+ end
@@ -0,0 +1,9 @@
1
+ Feature: something something
2
+ In order to something something
3
+ A user something something
4
+ something something something
5
+
6
+ Scenario: something something
7
+ Given inspiration
8
+ When I create a sweet new gem
9
+ Then everyone should see how awesome I am
File without changes
@@ -0,0 +1,6 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
+ require 'fdlcap'
3
+
4
+ require 'test/unit/assertions'
5
+
6
+ World(Test::Unit::Assertions)
data/lib/fdlcap.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Fdlcap
2
+
3
+ end
@@ -0,0 +1,9 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+ # Run autotagger to get the right release
3
+ if exists?(:use_release_tagger)
4
+ before "deploy:update_code", "release_tagger:set_branch"
5
+ before "deploy:cleanup", "release_tagger:create_tag"
6
+ before "deploy:cleanup", "release_tagger:write_tag_to_shared"
7
+ before "deploy:cleanup", "release_tagger:print_latest_tags"
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+ # Run craken to get cron tasks installed
3
+ if exists?(:use_craken)
4
+ after "deploy:update", "craken:install"
5
+ end
6
+ end
@@ -0,0 +1,67 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+ namespace :database do
3
+ desc "Push db remotely"
4
+ task :push_db_remotely, :roles => :db do
5
+
6
+ default_character_set = Object.const_defined?(:DEFAULT_CHARACTER_SET) ? Object::DEFAULT_CHARACTER_SET : "utf8"
7
+
8
+ local_env = ENV['LOCAL_ENV'] || "development"
9
+
10
+ all_db_info = YAML.load(File.read("config/database.yml"))
11
+ local_db_info = all_db_info[local_env]
12
+ remote_db_info = all_db_info[rails_env.to_s]
13
+ raise "Missing database.yml entry for #{local_env}" unless local_db_info
14
+ raise "Missing database.yml entry for #{rails_env.to_s}" unless remote_db_info
15
+
16
+
17
+ puts %{
18
+
19
+ ! WARNING !: The remote database '#{remote_db_info["database"]}'
20
+ will be replaced with the contents of the local database '#{local_db_info["database"]}'.
21
+ A dump of the remote db will be placed in your remote home directory just prior
22
+ to it being replaced.
23
+
24
+ 1) current REMOTE_DB ===> backed up to dump file, in ~/
25
+ 2) LOCAL_DB ===> REMOTE_DB ...old REMOTE_DB contents are overwritten!!!
26
+
27
+ Even so, this is a very significant and potentially destructive operation. Please step
28
+ back and contemplate what you're about to do.
29
+
30
+ If you're really sure you want to continue, type "REPLACE #{remote_db_info["database"].upcase}":
31
+ }
32
+
33
+ if ($stdin.gets.strip != "REPLACE #{remote_db_info["database"].upcase}")
34
+ puts "No action taken, exiting"
35
+ exit(1)
36
+ else
37
+ puts "You confirmed that you want to continue, here we go"
38
+ end
39
+
40
+ dump_file_name = "#{local_db_info["database"]}.sql"
41
+ local_dump_file_gz_path = "/tmp/#{dump_file_name}.gz"
42
+
43
+ execute "time mysqldump -e -q --single-transaction --default_character_set=#{default_character_set} \
44
+ -u #{local_db_info["username"]} --password=#{local_db_info["password"]} \
45
+ --database #{local_db_info["database"]} | gzip > #{local_dump_file_gz_path}"
46
+
47
+ upload "#{local_dump_file_gz_path}", "#{dump_file_name}.gz", :via => :scp
48
+
49
+ execute "echo ^G^G^G^G^G"
50
+
51
+ run "gzip -df ~/#{dump_file_name}.gz"
52
+ run "perl -pi -e 's|#{local_db_info["database"]}|#{remote_db_info["database"]}|g' ~/#{dump_file_name}"
53
+
54
+ run "time mysqldump -e -q --single-transaction --default_character_set=#{default_character_set} \
55
+ -u #{remote_db_info["username"]} --password=#{remote_db_info["password"]} \
56
+ --database #{remote_db_info["database"]} | gzip > ~/#{remote_db_info["database"]}_#{Time.now.strftime("%Y-%m-%d_%H-%M-%S")}.sql.gz"
57
+
58
+ remote_host = remote_db_info["host"] || "localhost"
59
+
60
+ run "mysqladmin -u #{remote_db_info["username"]} --password=#{remote_db_info["password"]} -h #{remote_host} drop #{remote_db_info["database"]} -f"
61
+ run "mysqladmin -u #{remote_db_info["username"]} --password=#{remote_db_info["password"]} -h #{remote_host} create #{remote_db_info["database"]} --default_character_set=#{default_character_set}"
62
+ run "time mysql -u #{remote_db_info["username"]} --password=#{remote_db_info["password"]} -h #{remote_host} --database #{remote_db_info["database"]} --default_character_set=#{default_character_set} < ~/#{dump_file_name}"
63
+ run "rm ~/#{dump_file_name}"
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,16 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+ namespace :delayed_job do
3
+ desc "Start delayed_job"
4
+ task :start, :only => {:delayed_job => true} do
5
+ sudo "/usr/bin/monit start all -g dj_#{application}"
6
+ end
7
+ desc "Stop delayed_job"
8
+ task :stop, :only => {:delayed_job => true} do
9
+ sudo "/usr/bin/monit stop all -g dj_#{application}"
10
+ end
11
+ desc "Restart delayed_job"
12
+ task :restart, :only => {:delayed_job => true} do
13
+ sudo "/usr/bin/monit restart all -g dj_#{application}"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+
3
+ namespace :web do
4
+ task :disable, :roles => :web, :except => { :no_release => true } do
5
+ on_rollback { run "rm #{shared_path}/system/maintenance.html" }
6
+ run "cp #{current_path}/public/maintenance.html #{shared_path}/system/maintenance.html"
7
+ end
8
+ end
9
+
10
+ namespace :deploy do
11
+ desc "Pull files from a remote server"
12
+ task :download_file, :roles => :app, :except => { :no_release => true } do
13
+ ENV['FILES'].split(',').each do |file|
14
+ get "#{current_path}/#{file}", File.basename(file)
15
+ end
16
+ end
17
+ end
18
+
19
+ # Clean up old releases
20
+ if exists?(:perform_cleanup)
21
+ after "deploy", "deploy:cleanup"
22
+ after "deploy:migrations" , "deploy:cleanup"
23
+ after "deploy:long" , "deploy:cleanup"
24
+ end
25
+
26
+ end
@@ -0,0 +1,26 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+ namespace :geminstaller do
3
+ desc "Run geminstaller"
4
+ task :run, :only => { :geminstaller => true } do
5
+ sudo "/usr/bin/geminstaller -c #{release_path}/config/geminstaller.yml --geminstaller-output=all --rubygems-output=all"
6
+ end
7
+
8
+ desc "Install geminstaller"
9
+ task :install, :only => { :geminstaller => true } do
10
+ sudo "gem install geminstaller"
11
+ sudo "gem source -a http://gems.github.com"
12
+ end
13
+ end
14
+
15
+ #
16
+ # Configure Callbacks
17
+ #
18
+ # Run geminstaller to make sure gems are installed.
19
+ if exists?(:use_geminstaller)
20
+ after "deploy:setup", "geminstaller:install"
21
+ after "geminstaller:install", "geminstaller:run"
22
+ after "deploy:symlink", "geminstaller:run"
23
+ after "geminstaller:run", "deploy:migrate"
24
+ end
25
+
26
+ end
@@ -0,0 +1,6 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+ # Notify newrelic of the deploy
3
+ if exists?(:use_newrelic)
4
+ before "deploy:cleanup", "newrelic:notice_deployment"
5
+ end
6
+ end
@@ -0,0 +1,125 @@
1
+
2
+ #global vars
3
+ set(:can_configure_nginx, true)
4
+ set(:nginx_user, 'nginx')
5
+ set(:nginx_processes, 4)
6
+ set(:nginx_gzip_on, true)
7
+ set(:nginx_gzip_xml_on, false)
8
+
9
+ # app specific vars
10
+ set(:nginx_server_names, "_")
11
+ set(:nginx_far_future, false)
12
+ set(:nginx_default_app, true)
13
+
14
+ #http auth vars
15
+ set(:nginx_auth_ip_masks, ['192.168.0.0/254'])
16
+ set(:nginx_http_auth_app, false)
17
+ set(:nginx_auth_locations, [])
18
+ set(:nginx_http_auth_users, [])
19
+
20
+ namespace :nginx do
21
+
22
+ %w( start stop restart reload ).each do |cmd|
23
+ desc "#{cmd} your nginx servers"
24
+ task "#{cmd}".to_sym, :roles => :web do
25
+ default_run_options[:pty] = true
26
+ sudo "/etc/init.d/nginx #{cmd}"
27
+ end
28
+ end
29
+
30
+ desc "Setup Nginx vhost config"
31
+ task :vhost, :roles => :web do
32
+ result = render_erb_template(File.dirname(__FILE__) + "/templates/nginx.vhost.conf.erb")
33
+ put result, "/tmp/nginx.vhost.conf"
34
+ sudo "mkdir -p /etc/nginx/vhosts"
35
+ sudo "cp /tmp/nginx.vhost.conf /etc/nginx/vhosts/#{application}.conf"
36
+ inform "You must edit nginx.conf to include the vhost config file."
37
+ end
38
+
39
+ desc "Setup Nginx vhost auth config"
40
+ task :vhost_auth, :roles => :web do
41
+ result = render_erb_template(File.dirname(__FILE__) + "/templates/nginx.auth.conf.erb")
42
+ put result, "/tmp/nginx.auth.conf"
43
+ sudo "mkdir -p /etc/nginx/vhosts"
44
+ sudo "cp /tmp/nginx.vhost.conf /etc/nginx/vhosts/#{application}.auth.conf"
45
+ end
46
+
47
+ desc "Setup htpasswd file for nginx auth"
48
+ task :create_htpasswd, :roles => :web do
49
+ sudo "mkdir -p /etc/nginx/conf"
50
+ for user in nginx_http_auth_users
51
+ run "cd /etc/nginx/conf && htpasswd -b htpasswd #{user['name']} #{user['password']}"
52
+ # run <<-CMD
53
+ # cd /etc/nginx/conf;
54
+ # if [ ! -e /etc/nginx/conf/htpasswd ] ; then
55
+ # htpasswd -b -c htpasswd #{user['name']} #{user['password']};
56
+ # else
57
+ # htpasswd -b htpasswd #{user['name']} #{user['password']};
58
+ # fi
59
+ # CMD
60
+ end
61
+ end
62
+
63
+ desc "Setup Nginx.config"
64
+ task :conf, :roles => :web do
65
+ if can_configure_nginx
66
+
67
+ result = render_erb_template(File.dirname(__FILE__) + "/templates/nginx.conf.erb")
68
+ put result, "/tmp/nginx.conf"
69
+ sudo "cp /tmp/nginx.conf /etc/nginx/nginx.conf"
70
+
71
+ else
72
+ inform "Nginx configuration tasks have been disabled. Most likely you are deploying to engineyard which has it's own nginx conf setup."
73
+ end
74
+ end
75
+
76
+ desc "Setup Nginx vhost config and nginx.conf"
77
+ task :configure, :roles => :web do
78
+ if can_configure_nginx
79
+
80
+ conf
81
+ vhost
82
+ vhost_auth if nginx_auth_locations.length > 0 || nginx_http_auth_app
83
+ create_htpasswd if nginx_http_auth_users.length > 0
84
+
85
+ else
86
+ inform "Nginx configuration tasks have been disabled. Most likely you are deploying to engineyard which has it's own nginx conf setup."
87
+ end
88
+ end
89
+
90
+ end
91
+
92
+ class Capistrano::Configuration
93
+
94
+ ##
95
+ # Print an informative message with asterisks.
96
+
97
+ def inform(message)
98
+ puts "#{'*' * (message.length + 4)}"
99
+ puts "* #{message} *"
100
+ puts "#{'*' * (message.length + 4)}"
101
+ end
102
+
103
+ ##
104
+ # Read a file and evaluate it as an ERB template.
105
+ # Path is relative to this file's directory.
106
+
107
+ def render_erb_template(filename)
108
+ template = File.read(filename)
109
+ result = ERB.new(template).result(binding)
110
+ end
111
+
112
+ ##
113
+ # Run a command and return the result as a string.
114
+ #
115
+ # TODO May not work properly on multiple servers.
116
+
117
+ def run_and_return(cmd)
118
+ output = []
119
+ run cmd do |ch, st, data|
120
+ output << data
121
+ end
122
+ return output.to_s
123
+ end
124
+
125
+ end
@@ -0,0 +1,38 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+ namespace :autoperf do
3
+ desc "get nginx log for load test"
4
+ task :fetch_log, :roles => :app do
5
+ # Grab the last 1000 requests from production Nginx log, and extract the request path (ex: /index)
6
+ run "tail -n 1000 /var/log/nginx/#{application}.access.log | awk '{print $7}' > /tmp/requests.log"
7
+
8
+ # Replace newlines with null terminator (httperf format)
9
+ run 'tr "\n" "\0" < /tmp/requests.log > /tmp/requests_httperf.log'
10
+
11
+ download "/tmp/requests_httperf.log", "config/autoperf/requests_httperf.log", :via => :scp
12
+
13
+ run "rm /tmp/requests_httperf.log"
14
+ end
15
+
16
+ task :run_test, :roles => :app do
17
+ run "cd #{current_path} && script/autoperf -c config/autoperf/primary.conf" do |channel, stream, data|
18
+ puts data if stream == :out
19
+ if stream == :err
20
+ puts "[Error: #{channel[:host]}] #{data}"
21
+ break
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ namespace :autobench do
28
+
29
+ task :test_search, :roles => :app do
30
+ url = "/vehicles/search?search[zip]=06108&search[radius]=10+Miles&search[view_type]=block&search[per_page]=10&search[year_from]=From+Year&search[year_to]=To+Year&search[model]=Model&search[price]=Price&search[color]=Color&search[mileage]=Mileage&commit=Search"
31
+ run "/usr/local/bin/autobench --single_host --host1=audivehiclesearch.com --uri1=#{url} --file=/tmp/test_search.bench.txt --low_rate=1 --high_rate=20 --rate_step=2 --num_call=2 --num_conn=200"
32
+ download "/tmp/test_search.bench.txt", "autobench/test_search.bench.txt", :via => :scp
33
+ run "rm /tmp/test_search.bench.txt"
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -0,0 +1,19 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+ desc "Execute an arbitrary rake task on slices with a specified role (ROLES=x,y,z TASK=p)"
3
+ task :rake do
4
+ task = ENV['TASK']
5
+ run "cd #{current_path} && rake #{task} RAILS_ENV=#{rails_env}"
6
+ end
7
+
8
+ desc "Execute an arbitrary runner command on slices with a specified role (ROLES=x,y,z CMD=p)"
9
+ task :runner do
10
+ cmd = ENV['CMD']
11
+ run "cd #{current_path} && script/runner -e #{rails_env} '#{cmd}'"
12
+ end
13
+
14
+ desc "Execute an arbitrary UNIX command on slices with a specified role (ROLES=x,y,z CMD=p)"
15
+ task :command do
16
+ cmd = ENV['CMD']
17
+ run "cd #{current_path} && #{cmd}"
18
+ end
19
+ end
@@ -0,0 +1,35 @@
1
+ # Set up configuration defaults
2
+ Capistrano::Configuration.instance(:must_exist).load do
3
+ unless exists?(:stages)
4
+ set :stages, [ :staging, :production ]
5
+ end
6
+
7
+ unless exists?(:use_release_tagger) && exists?(:autotagger_stages)
8
+ set :autotagger_stages, [ :ci, :staging, :production ]
9
+ end
10
+
11
+ unless exists?(:default_stage)
12
+ set :default_stage, :staging
13
+ end
14
+ end
15
+
16
+ # Load fdlcap dependencies
17
+ require 'release_tagger'
18
+ require 'capistrano/ext/multistage'
19
+ require 'eycap/recipes'
20
+
21
+ # Load up custom recipes and callbacks
22
+ require 'fdlcap/recipes/autotagger'
23
+ require 'fdlcap/recipes/craken'
24
+ require 'fdlcap/recipes/database'
25
+ require 'fdlcap/recipes/delayed_job'
26
+ require 'fdlcap/recipes/deploy'
27
+ require 'fdlcap/recipes/geminstaller'
28
+ require 'fdlcap/recipes/newrelic'
29
+ require 'fdlcap/recipes/performance'
30
+ require 'fdlcap/recipes/rake'
31
+ require 'fdlcap/recipes/rsync'
32
+ require 'fdlcap/recipes/sass'
33
+ require 'fdlcap/recipes/ssh'
34
+ require 'fdlcap/recipes/slice'
35
+ require 'fdlcap/recipes/thinking_sphinx'
@@ -0,0 +1,40 @@
1
+ class Capistrano::Configuration
2
+ def execute(command, failure_message = "Command failed")
3
+ puts "Executing: #{command}"
4
+ system(command) || raise(failure_message)
5
+ end
6
+ end
7
+
8
+ Capistrano::Configuration.instance(:must_exist).load do
9
+ namespace :rsync do
10
+ desc <<-DESC
11
+ use rsync to sync assets locally or between servers
12
+ DESC
13
+ task :pull_shared , :roles => :app do
14
+ servers = find_servers :roles => :app, :except => { :no_release => true }
15
+ server = servers.first
16
+ if server
17
+ symlink_dirs.each do |share|
18
+ execute( "rsync -P -a -h -e 'ssh -p #{server.port || 22}' #{user}@#{server.host}:#{shared_path}/#{share}/* #{share}", "unable to run rsync files")
19
+ end
20
+ else
21
+ puts 'no server found'
22
+ end
23
+ end
24
+
25
+ desc <<-DESC
26
+ use rsync to sync assets locally or between servers
27
+ DESC
28
+ task :push_shared , :roles => :app do
29
+ servers = find_servers :roles => :app, :except => { :no_release => true }
30
+ server = servers.first
31
+ if server
32
+ symlink_dirs.each do |share|
33
+ execute( "rsync -P -a -h -e 'ssh -p #{server.port || 22}' #{share}/* #{user}@#{server.host}:#{shared_path}/#{share}/", "unable to run rsync files")
34
+ end
35
+ else
36
+ puts 'no server found'
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,13 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+ namespace :sass do
3
+ desc 'Updates the stylesheets generated by Sass'
4
+ task :update, :roles => :app do
5
+ invoke_command "cd #{latest_release}; RAILS_ENV=#{rails_env} rake sass:update"
6
+ end
7
+ end
8
+
9
+ # Generate all the stylesheets manually (from their Sass templates) before each restart.
10
+ if exists?(:use_sass)
11
+ before 'deploy:restart', 'sass:update'
12
+ end
13
+ end
@@ -0,0 +1,35 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+
3
+ namespace :slice do
4
+
5
+ desc "Copy the maintenance page from the public directory to the shared directory"
6
+ task :copy_maintenance_page, :roles => :app do
7
+ upload "public/maintenance.html","#{shared_path}/system/maintenance.html.custom", :via => :scp
8
+ end
9
+
10
+ desc "Tail the Rails import log for this environment"
11
+ task :tail_import_logs, :roles => :utility do
12
+ run "tail -f #{shared_path}/log/import-#{rails_env}.log" do |channel, stream, data|
13
+ puts # for an extra line break before the host name
14
+ puts "#{channel[:server]} -> #{data}"
15
+ break if stream == :err
16
+ end
17
+ end
18
+
19
+ desc "Tail the Rails log for this environment"
20
+ task :tail_logs, :roles => :utility do
21
+ run "tail -f #{shared_path}/log/#{rails_env}.log" do |channel, stream, data|
22
+ puts # for an extra line break before the host name
23
+ puts "#{channel[:server]} -> #{data}"
24
+ break if stream == :err
25
+ end
26
+ end
27
+
28
+ end
29
+
30
+ # Deploy the custom maintenance page
31
+ if exists?(:use_custom_maintenance_page)
32
+ before "deploy:web:disable", "slice:copy_maintenance_page"
33
+ end
34
+
35
+ end
data/lib/fdlcap/ssh.rb ADDED
@@ -0,0 +1,30 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+ task :ssh do
3
+ role = (ENV['ROLE'] || :app).to_sym
4
+ servers = find_servers :roles => role
5
+ server = servers.first
6
+ if server
7
+ `echo '#{password}' | /usr/bin/pbcopy`
8
+ exec "/usr/bin/ssh #{user}@#{server.host} -p #{server.port || 22} "
9
+ end
10
+ end
11
+
12
+ #namespace :ssh do
13
+ task :tunnel do
14
+ remote_port = ENV['REMOTE_PORT'] || 80
15
+ local_port = ENV['LOCAL_PORT'] || 2000
16
+ role = (ENV['ROLE'] || :app).to_sym
17
+
18
+ servers = find_servers :roles => role
19
+ server = servers.first
20
+ if server
21
+ puts "Opening a tunnel from port #{local_port} locally to port #{remote_port} on #{server.host}"
22
+ Net::SSH.start(server.host, user, :password => password, :port => server.port) do |ssh|
23
+ ssh.forward.local(local_port, "127.0.0.1", remote_port)
24
+ ssh.loop { true }
25
+ end
26
+ end
27
+ end
28
+ #end
29
+
30
+ end
@@ -0,0 +1,38 @@
1
+ after "deploy:update_code", "symlinks:create"
2
+
3
+ set(:symlink_dirs, [])
4
+ set(:symlink_absolute_dirs, [])
5
+
6
+ namespace :symlinks do
7
+
8
+ desc <<-DESC
9
+ fix symlinks to shared directory
10
+ DESC
11
+ task :fix, :roles => [:app, :web] do
12
+ # for folders stored under public
13
+ symlink_dirs.each do |share|
14
+ run "rm -rf #{current_path}/#{share}"
15
+ run "mkdir -p #{shared_path}/#{share}"
16
+ run "ln -nfs #{shared_path}/#{share} #{current_path}/#{share}"
17
+ end
18
+ end
19
+
20
+ desc <<-DESC
21
+ create symlinks to shared directory
22
+ DESC
23
+ task :create, :roles => [:app, :web] do
24
+ # for folders stored under public
25
+ symlink_dirs.each do |share|
26
+ run "rm -rf #{release_path}/#{share}"
27
+ run "mkdir -p #{shared_path}/#{share}"
28
+ run "ln -nfs #{shared_path}/#{share} #{release_path}/#{share}"
29
+ end
30
+
31
+ symlink_absolute_dirs.each do |share|
32
+ run "rm -rf #{share[:symlink]}"
33
+ run "mkdir -p #{share[:source]}"
34
+ run "ln -nfs #{share[:source]} #{share[:symlink]}"
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,9 @@
1
+ satisfy_any on;
2
+
3
+ <% for allow in nginx_auth_ip_masks -%>
4
+ <%= "allow #{allow};" %>
5
+ <% end -%>
6
+ deny all;
7
+
8
+ auth_basic "Restricted";
9
+ auth_basic_user_file /etc/nginx/conf/htpasswd;
@@ -0,0 +1,57 @@
1
+ # taken mostly from "The Rails Way" page 663
2
+
3
+ # user and group to run as
4
+ user <%= nginx_user %>;
5
+ # number of nginx workers
6
+ worker_processes <%= nginx_processes %>;
7
+ # pid of nginx master process
8
+ pid /var/run/nginx.pid;
9
+
10
+ error_log /var/log/nginx/default.error.log debug;
11
+ #error_log /var/log/nginx/error.log notice;
12
+ #error_log /var/log/nginx/error.log info;
13
+
14
+ # number of worker connections. 1024 is a good default
15
+ events {
16
+ worker_connections 1024;
17
+ use epoll; # linux only!
18
+ }
19
+
20
+
21
+ http {
22
+ # pull in mime-types. You can break out your config
23
+ # into as many include's as you want.
24
+ include /etc/nginx/mime.types;
25
+ # set a default type for the rare situation that nothing matches.
26
+ default_type application/octet-stream;
27
+ # configure log format
28
+ log_format main '$remote_addr - $remote_user [$time_local] $request '
29
+ '"$status" $body_bytes_sent "$http_referer" '
30
+ '"$http_user_agent" "$http_x_forwarded_for"';
31
+
32
+ # no sendfile on OS X
33
+ sendfile on;
34
+ tcp_nopush on;
35
+ tcp_nodelay on;
36
+
37
+ #keepalive_timeout 0;
38
+ keepalive_timeout 65;
39
+
40
+ <% if nginx_gzip_on %>
41
+ gzip on;
42
+ gzip_http_version 1.0;
43
+ gzip_comp_level 2;
44
+ gzip_proxied any;
45
+
46
+ <% if nginx_gzip_xml_on %>
47
+ # IE 6 doesn't pass compressed xml to flash. So if no flash xml consumption on site can add
48
+ # text/xml application/xml application/xml+rss
49
+ <% end %>
50
+ gzip_types text/plain text/html text/css application/x-javascript text/javascript;
51
+ <% end %>
52
+
53
+ access_log /var/log/nginx/nginx.default.access.log main;
54
+ error_log /var/log/nginx/nginx.default.error.log info;
55
+
56
+ include /etc/nginx/vhosts/*.conf;
57
+ }
@@ -0,0 +1,85 @@
1
+ # location of mongrel servers to proxy too
2
+ upstream mongrel_<%= application.gsub('.', '_') %> {
3
+ <% mongrel_port.upto(mongrel_port + mongrel_servers) do |port| %>
4
+ server 127.0.0.1:<%= port %>;
5
+ <% end %>
6
+ }
7
+
8
+ server {
9
+ # port to listen on. Can also be set to an IP:PORT
10
+ listen 80 <%= "default" if nginx_default_app %>;
11
+ # set max size for file uploads to 50mb
12
+ client_max_body_size 50M;
13
+ # sets the domain[s] that this vhost server requests for
14
+ # if two apps are on this box remove the ip and setup your hosts file
15
+ server_name <%= nginx_server_names %>;
16
+ # doc root
17
+ root <%= current_path %>/public;
18
+ # vhost specific logs
19
+ access_log <%= shared_path %>/log/<%= application %>.access.log main;
20
+ error_log <%= shared_path %>/log/<%= application %>.error.log notice;
21
+
22
+ # this rewrites all the requests to the maintenance.thml page if it exists in the doc root.
23
+ # this is for capistrano's disable web task
24
+ if (-f $document_root/system/maintenance.html) {
25
+ rewrite ^(.*)$ /system/maintenance.html last;
26
+ break;
27
+ }
28
+ # block access to paths containing .svn
29
+ location ~* ^.*\.svn.*$ {
30
+ internal;
31
+ }
32
+
33
+ location / {
34
+
35
+ <%= "include /etc/nginx/vhosts/#{application}.auth.conf" if nginx_http_auth_app %>
36
+
37
+ index index.html index.htm;
38
+ # forward the user's IP address to Rails
39
+ proxy_set_header X-Real-IP $remote_addr;
40
+ # needed for HTTPS must add an additional server block to configure it.
41
+ # see "The Rails Way" page 665 for more info
42
+ proxy_set_header X-FORWARD_PROTO https;
43
+ # Forward information about the client and host
44
+ # Otherwise our Rails app wouldn't have access to it
45
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
46
+ proxy_set_header Host $http_host;
47
+ proxy_redirect false;
48
+ proxy_max_temp_file_size 0;
49
+
50
+ # use this as a reference for a full production deployment
51
+ # do not use on a dev box. this adds far futures expires.
52
+ <%= "#" if nginx_far_future %> location ~ ^/(images|javascripts|stylesheets)/ {
53
+ <%= "#" if nginx_far_future %> expires 10y;
54
+ <%= "#" if nginx_far_future %> }
55
+
56
+ # if file exists break execution and serve up file for example files in images, javascripts, and stylesheets
57
+ if (-f $request_filename) {
58
+ break;
59
+ }
60
+ # Rails page caching, if file path plus index.html exists break execution and serve up file
61
+ if (-f $request_filename/index.html) {
62
+ rewrite (.*) $1/index.html break;
63
+ }
64
+ # Rails page caching, if file.html exists break execution and serve up file
65
+ if (-f $request_filename.html) {
66
+ rewrite (.*) $1.html break;
67
+ }
68
+ # if file does not exist forward to mongrel
69
+ if (!-f $request_filename) {
70
+ proxy_pass http://mongrel_<%= application.gsub('.', '_') %>;
71
+ break;
72
+ }
73
+ }
74
+ <% for location in nginx_auth_locations %>
75
+ location <%= location %> {
76
+ <%= "include /etc/nginx/vhosts/#{application}.auth.conf" %>
77
+ }
78
+ <% end %>
79
+ # must be an error so point to error page.
80
+ error_page 500 502 503 504 /500.html;
81
+ location = /500.html {
82
+ root <%= current_path %>/public;
83
+ }
84
+
85
+ }
@@ -0,0 +1,112 @@
1
+ set :thin_servers, 2
2
+ set :thin_port, 8000
3
+ set :thin_address, "127.0.0.1"
4
+ set :thin_environment, "production"
5
+ set :thin_conf, nil
6
+ set :thin_user, nil
7
+ set :thin_group, nil
8
+ set :thin_prefix, nil
9
+ set :thin_pid_file, nil
10
+ set :thin_log_file, nil
11
+ set :thin_config_script, nil
12
+
13
+ namespace :thin do
14
+ desc <<-DESC
15
+ Install Thin script on the app server. This uses the :use_sudo variable to determine whether to use sudo or not. By default, :use_sudo is
16
+ set to true.
17
+ DESC
18
+ task :install , :roles => :app do
19
+ send(run_method, "gem install thin")
20
+ send(run_method, "thin install")
21
+ end
22
+
23
+ desc <<-DESC
24
+ Configure thin processes on the app server. This uses the :use_sudo
25
+ variable to determine whether to use sudo or not. By default, :use_sudo is
26
+ set to true.
27
+ DESC
28
+ task :configure, :roles => :app do
29
+ set_conf
30
+
31
+ argv = []
32
+ argv << "thin"
33
+ argv << "-s #{thin_servers.to_s}"
34
+ argv << "-p #{thin_port.to_s}"
35
+ argv << "-e #{thin_environment}"
36
+ argv << "-a #{thin_address}"
37
+ argv << "-c #{current_path}"
38
+ argv << "-C #{thin_conf}"
39
+ argv << "-P #{thin_pid_file}" if thin_pid_file
40
+ argv << "-l #{thin_log_file}" if thin_log_file
41
+ argv << "--user #{thin_user}" if thin_user
42
+ argv << "--group #{thin_group}" if thin_group
43
+ argv << "--prefix #{thin_prefix}" if thin_prefix
44
+ argv << "config"
45
+ cmd = argv.join " "
46
+ send(run_method, cmd)
47
+ end
48
+
49
+ task :setup, :roles => :app do
50
+ thin.install
51
+ thin.configure
52
+ end
53
+
54
+ desc <<-DESC
55
+ Start Thin processes on the app server. This uses the :use_sudo variable to determine whether to use sudo or not. By default, :use_sudo is
56
+ set to true.
57
+ DESC
58
+ task :start , :roles => :app do
59
+ set_conf
60
+ cmd = "thin start -C #{thin_conf}"
61
+ send(run_method, cmd)
62
+ end
63
+
64
+ desc <<-DESC
65
+ Restart the Thin processes on the app server by starting and stopping the cluster. This uses the :use_sudo
66
+ variable to determine whether to use sudo or not. By default, :use_sudo is set to true.
67
+ DESC
68
+ task :restart , :roles => :app do
69
+ set_conf
70
+ cmd = "thin restart -C #{thin_conf}"
71
+ send(run_method, cmd)
72
+ end
73
+
74
+ desc <<-DESC
75
+ Stop the Thin processes on the app server. This uses the :use_sudo
76
+ variable to determine whether to use sudo or not. By default, :use_sudo is
77
+ set to true.
78
+ DESC
79
+ task :stop , :roles => :app do
80
+ set_conf
81
+ cmd = "thin stop -C #{thin_conf}"
82
+ send(run_method, cmd)
83
+ end
84
+
85
+
86
+ def set_conf
87
+ set :thin_conf, "/etc/thin/#{application}.yml" unless thin_conf
88
+ end
89
+ end
90
+
91
+ namespace :deploy do
92
+ desc <<-DESC
93
+ Restart the Thin processes on the app server by calling thin:restart.
94
+ DESC
95
+ task :restart, :roles => :app do
96
+ thin.restart
97
+ end
98
+
99
+ desc <<-DESC
100
+ Start the Thin processes on the app server by calling thin:start.
101
+ DESC
102
+ task :start, :roles => :app do
103
+ thin.start
104
+ end
105
+
106
+ desc <<-DESC
107
+ Stop the Thin processes on the app server by calling thin:stop.
108
+ DESC
109
+ task :stop, :roles => :app do
110
+ thin.stop
111
+ end
112
+ end
@@ -0,0 +1,9 @@
1
+ Capistrano::Configuration.instance(:must_exist).load do
2
+ # Make sphinx happy
3
+ if exists?(:use_thinking_sphinx)
4
+ after "deploy:update_code", "deploy:symlink_configs"
5
+ after "deploy:symlink_configs", "thinking_sphinx:symlink"
6
+ after "thinking_sphinx:symlink", "sphinx:configure"
7
+ after "deploy:update", "sphinx:reindex"
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ require 'test_helper'
2
+
3
+ class FdlcapTest < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'fdlcap'
8
+
9
+ class Test::Unit::TestCase
10
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: factorylabs-fdlcap
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Factory Design Labs
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-25 00:00:00 -07:00
13
+ default_executable: fdlcap
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: engineyard-eycap
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.4.7
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: zilkey-auto_tagger
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.0.9
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: capistrano
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 2.5.5
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: capistrano-ext
47
+ type: :runtime
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 1.2.1
54
+ version:
55
+ description:
56
+ email: interactive@factorylabs.com
57
+ executables:
58
+ - fdlcap
59
+ extensions: []
60
+
61
+ extra_rdoc_files:
62
+ - LICENSE
63
+ - README
64
+ - README.rdoc
65
+ files:
66
+ - LICENSE
67
+ - README
68
+ - README.rdoc
69
+ - Rakefile
70
+ - VERSION
71
+ - bin/fdlcap
72
+ - fdlcap.gemspec
73
+ - features/fdlcap.feature
74
+ - features/step_definitions/fdlcap_steps.rb
75
+ - features/support/env.rb
76
+ - lib/fdlcap.rb
77
+ - lib/fdlcap/autotagger.rb
78
+ - lib/fdlcap/craken.rb
79
+ - lib/fdlcap/database.rb
80
+ - lib/fdlcap/delayed_job.rb
81
+ - lib/fdlcap/deploy.rb
82
+ - lib/fdlcap/geminstaller.rb
83
+ - lib/fdlcap/newrelic.rb
84
+ - lib/fdlcap/nginx.rb
85
+ - lib/fdlcap/performance.rb
86
+ - lib/fdlcap/rake.rb
87
+ - lib/fdlcap/recipes.rb
88
+ - lib/fdlcap/rsync.rb
89
+ - lib/fdlcap/sass.rb
90
+ - lib/fdlcap/slice.rb
91
+ - lib/fdlcap/ssh.rb
92
+ - lib/fdlcap/symlinks.rb
93
+ - lib/fdlcap/templates/nginx.auth.conf.erb
94
+ - lib/fdlcap/templates/nginx.conf.erb
95
+ - lib/fdlcap/templates/nginx.vhost.conf.erb
96
+ - lib/fdlcap/thin.rb
97
+ - lib/fdlcap/thinking_sphinx.rb
98
+ - test/fdlcap_test.rb
99
+ - test/test_helper.rb
100
+ has_rdoc: false
101
+ homepage: http://github.com/factorylabs/fdlcap
102
+ post_install_message:
103
+ rdoc_options:
104
+ - --charset=UTF-8
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: "0"
112
+ version:
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: "0"
118
+ version:
119
+ requirements: []
120
+
121
+ rubyforge_project:
122
+ rubygems_version: 1.2.0
123
+ signing_key:
124
+ specification_version: 3
125
+ summary: a set of capistrano recipies we use regularly at Factory Design Labs
126
+ test_files:
127
+ - test/fdlcap_test.rb
128
+ - test/test_helper.rb