edploy 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +4 -0
  3. data/MIT-LICENSE +92 -0
  4. data/README.rdoc.fast_remote_cache +48 -0
  5. data/Rakefile +1 -0
  6. data/TODO.TXT +17 -0
  7. data/bin/dploy +15 -0
  8. data/bin/dploygen +39 -0
  9. data/bin/dployify +29 -0
  10. data/edploy.gemspec +21 -0
  11. data/lib/capistrano/ext/mailer.rb +90 -0
  12. data/lib/capistrano/ext/multistage.rb +59 -0
  13. data/lib/capistrano/ext/output_catcher.rb +97 -0
  14. data/lib/capistrano/ext/output_hooks.rb +54 -0
  15. data/lib/capistrano/recipes/deploy/strategy/fast_remote_cache.rb +44 -0
  16. data/lib/capistrano/recipes/deploy/strategy/utilities/copy.rb +53 -0
  17. data/lib/edploy/environment_capistrano.rb +19 -0
  18. data/lib/edploy/recipes/apache.rb +8 -0
  19. data/lib/edploy/recipes/bundle_install.rb +15 -0
  20. data/lib/edploy/recipes/fast_remote_cache.rb +43 -0
  21. data/lib/edploy/recipes/fast_remote_cache_extensions.rb +11 -0
  22. data/lib/edploy/recipes/hooks.rb +24 -0
  23. data/lib/edploy/recipes/production.rb +56 -0
  24. data/lib/edploy/recipes/rails.rb +5 -0
  25. data/lib/edploy/recipes/setup_project.rb +26 -0
  26. data/lib/edploy/recipes/smart_deploy.rb +47 -0
  27. data/lib/edploy/recipes/templates.rb +60 -0
  28. data/lib/edploy/recipes/web_disable_enable.rb +32 -0
  29. data/lib/edploy.rb +17 -0
  30. data/lib/edployify/configuration.rb +140 -0
  31. data/lib/edployify/preferences.rb +176 -0
  32. data/lib/edployify/project.rb +69 -0
  33. data/lib/edployify/template.rb +84 -0
  34. data/lib/edployify.rb +6 -0
  35. data/lib/edployscripts/archive.rb +27 -0
  36. data/lib/edployscripts/environment_scripts.rb +47 -0
  37. data/lib/edployscripts/file_handling.rb +28 -0
  38. data/lib/edployscripts/git.rb +27 -0
  39. data/lib/edployscripts.rb +6 -0
  40. data/templates/config/edploy/copy/extract/.gitkeep +0 -0
  41. data/templates/config/edploy/copy/init_scripts/.gitkeep +0 -0
  42. data/templates/config/edploy/copy/project_files/.gitkeep +0 -0
  43. data/templates/config/edploy/scripts/after_deploy_setup/.gitkeep +0 -0
  44. data/templates/config/edploy/scripts/after_deploy_symlink/passenger/clear_file_cache.erb +7 -0
  45. data/templates/config/edploy/scripts/after_deploy_update_code/make_symlinks.erb +74 -0
  46. data/templates/config/edploy/scripts/edploygen/01_bundle_install.erb +5 -0
  47. data/templates/config/edploy/scripts/edploygen/01_prepare_servers.post.erb +12 -0
  48. data/templates/config/edploy/scripts/edploygen/02_whenever_configs.erb +36 -0
  49. data/templates/config/edploy/templates/passenger/activate.rb.erb +2 -0
  50. data/templates/config/edploy/templates/passenger/after_config.rb.erb +2 -0
  51. data/templates/config/edploy/templates/passenger/apache_vhost.erb.erb +107 -0
  52. data/templates/config/edploy/templates/passenger/config.rb.erb +3 -0
  53. data/templates/config/edploy/templates/passenger/deactivate.rb.erb +1 -0
  54. data/templates/config/edploy/whenever/generic.rb.erb +12 -0
  55. metadata +113 -0
@@ -0,0 +1,53 @@
1
+ # ---------------------------------------------------------------------------
2
+ # A simple copy script for doing hard links and symbolic links instead of
3
+ # explicit copies. Some OS's will already have a utility to do this, but
4
+ # some won't; this file suffices in either case.
5
+ #
6
+ # Usage: ruby copy.rb <source> <target> <exclude> ...
7
+ #
8
+ # The <source> directory is recursively descended, and hard links to all of
9
+ # the files are created in corresponding locations under <target>. Symbolic
10
+ # links in <source> map to symbolic links in <target> that point to the same
11
+ # destination.
12
+ #
13
+ # All arguments after <target> are taken to be exclude patterns. Any file
14
+ # or directory in <source> that matches any of those patterns will be
15
+ # skipped, and will thus not be present in <target>.
16
+ # ---------------------------------------------------------------------------
17
+ # This file is distributed under the terms of the MIT license by 37signals,
18
+ # LLC, and is copyright (c) 2008 by the same. See the LICENSE file distributed
19
+ # with this file for the complete text of the license.
20
+ # ---------------------------------------------------------------------------
21
+ require 'fileutils'
22
+
23
+ from = ARGV.shift or abort "need source directory"
24
+ to = ARGV.shift or abort "need target directory"
25
+
26
+ exclude = ARGV
27
+
28
+ from = File.expand_path(from)
29
+ to = File.expand_path(to)
30
+
31
+ Dir.chdir(from) do
32
+ FileUtils.mkdir_p(to)
33
+ queue = Dir.glob("*", File::FNM_DOTMATCH)
34
+ while queue.any?
35
+ item = queue.shift
36
+ name = File.basename(item)
37
+
38
+ next if name == "." || name == ".."
39
+ next if exclude.any? { |pattern| File.fnmatch(pattern, item) }
40
+
41
+ source = File.join(from, item)
42
+ target = File.join(to, item)
43
+
44
+ if File.symlink?(item)
45
+ FileUtils.ln_s(File.readlink(source), target)
46
+ elsif File.directory?(item)
47
+ queue += Dir.glob("#{item}/*", File::FNM_DOTMATCH)
48
+ FileUtils.mkdir_p(target, :mode => File.stat(item).mode)
49
+ else
50
+ FileUtils.ln(source, target)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,19 @@
1
+ if ENV['BUNDLE_GEMFILE'].nil? || ENV['BUNDLE_BIN_PATH'].nil?
2
+ abort "You should run this with bundle exec!"
3
+ elsif !File.exists?(File.join(Dir.getwd, 'Capfile'))
4
+ abort "You should run this from the rails root!"
5
+ end
6
+
7
+ set :rails_root, Dir.getwd
8
+
9
+ require 'Syck'
10
+
11
+ Syck.load_file(File.join(rails_root, 'config', 'edploy', 'edploy.yml')).each do |key, value|
12
+ if key.to_sym == :ssh_options
13
+ value.each do |ssh_option, value|
14
+ ssh_options[ssh_option.to_sym] = value
15
+ end
16
+ else
17
+ set key.to_sym, value
18
+ end
19
+ end
@@ -0,0 +1,8 @@
1
+ namespace :edploy do
2
+ namespace :apache do
3
+ desc "phased restart of apache on production"
4
+ task :restart, :roles => :passenger do
5
+ run "apache2ctl configtest && sudo /etc/init.d/apache2 restart"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,15 @@
1
+ set :bundle_path, "vendor/bundle"
2
+ set :bundle_without, "development test"
3
+
4
+ namespace :edploy do
5
+ after 'deploy:update_code', 'edploy:bundle_install'
6
+ task :bundle_install do
7
+ commands = []
8
+ [ bundle_path, ".bundle" ].uniq.each do |link_path|
9
+ commands << "mkdir -p #{shared_path}/#{link_path}"
10
+ commands << "ln -nfs #{shared_path}/#{link_path} #{latest_release}/#{link_path}"
11
+ end
12
+ commands << "cd #{latest_release} ; bundle install --path #{shared_path}/#{bundle_path} --local --without #{bundle_without} || bundle install --path #{shared_path}/#{bundle_path} --without #{bundle_without}"
13
+ run commands.join(' ; ')
14
+ end
15
+ end
@@ -0,0 +1,43 @@
1
+ # ---------------------------------------------------------------------------
2
+ # This is a recipe definition file for Capistrano. The tasks are documented
3
+ # below.
4
+ # ---------------------------------------------------------------------------
5
+ # This file is distributed under the terms of the MIT license by 37signals,
6
+ # LLC, and is copyright (c) 2008 by the same. See the LICENSE file distributed
7
+ # with this file for the complete text of the license.
8
+ # ---------------------------------------------------------------------------
9
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
10
+
11
+ namespace :fast_remote_cache do
12
+
13
+ desc <<-DESC
14
+ Perform any setup required by fast_remote_cache. This is called
15
+ automatically after deploy:setup, but may be invoked manually to configure
16
+ a new machine. It is also necessary to invoke when you are switching to the
17
+ fast_remote_cache strategy for the first time.
18
+ DESC
19
+ task :setup, :except => { :no_release => true } do
20
+ if deploy_via == :fast_remote_cache
21
+ strategy.setup!
22
+ else
23
+ logger.important "you're including the fast_remote_cache strategy, but not using it!"
24
+ end
25
+ end
26
+
27
+ desc <<-DESC
28
+ Updates the remote cache. This is handy for either priming a new box so
29
+ the cache is all set for the first deploy, or for preparing for a large
30
+ deploy by making sure the cache is updated before the deploy goes through.
31
+ Either way, this will happen automatically as part of a deploy; this task
32
+ is purely convenience for giving admins more control over the deployment.
33
+ DESC
34
+ task :prepare, :except => { :no_release => true } do
35
+ if deploy_via == :fast_remote_cache
36
+ strategy.prepare!
37
+ else
38
+ logger.important "#{current_task.fully_qualified_name} only works with the fast_remote_cache strategy"
39
+ end
40
+ end
41
+ end
42
+
43
+ after "deploy:setup", "fast_remote_cache:setup"
@@ -0,0 +1,11 @@
1
+ namespace :edploy do
2
+ before 'fast_remote_cache:prepare', 'edploy:gc'
3
+ task :gc do
4
+ run "cd #{shared_path}/cached-copy ; git gc"
5
+ end
6
+
7
+ after 'fast_remote_cache:prepare', 'edploy:pre_bundle'
8
+ task :pre_bundle do
9
+ run "cd #{shared_path}/cached-copy ; bundle install --path #{shared_path}/vendor/bundle --local --without development test || bundle install --path #{shared_path}/vendor/bundle --without development test"
10
+ end
11
+ end
@@ -0,0 +1,24 @@
1
+ namespace :edploy do
2
+
3
+ %w(deploy:update_code deploy:symlink deploy:setup).each do |task_name|
4
+ [ :before, :after ].each do |milestone|
5
+
6
+ subdir = "#{milestone}_#{task_name.gsub(/:/, '_')}"
7
+ Dir[File.join(rails_root, 'config', 'edploy', 'scripts', subdir, "**", "*")].sort.each do |full_entry|
8
+ next if File.directory?(full_entry)
9
+
10
+ entry = full_entry[File.join(rails_root, 'config', 'edploy', 'scripts', subdir, "/").length..-1]
11
+ roles = (entry.include?('/') ? File.dirname(entry).split('_').map(&:to_sym) : nil)
12
+ options = {}
13
+ options[:roles] = roles if roles
14
+ script_name = File.basename(entry).to_sym
15
+
16
+ self.send(milestone, task_name, "edploy:#{script_name}")
17
+ task script_name, options do
18
+ run "cd #{latest_release} ; RAILS_ENV=#{rails_env} PROJECT_PATH=#{latest_release} DEPLOY_USER=#{user} DEPLOY_GROUP=#{group} bundle exec config/edploy/scripts/#{subdir}/#{entry}"
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,56 @@
1
+ after 'production', 'setup_production'
2
+
3
+ task :setup_production do
4
+ set :branch, "production"
5
+ set :rails_env, 'production'
6
+
7
+ EVEN_PASSENGER_HOSTS = find_servers(:roles => :passenger).each_with_index.map { |s, i| i % 2 == 1 ? s : nil }.compact
8
+ ODD_PASSENGER_HOSTS = find_servers(:roles => :passenger).each_with_index.map { |s, i| i % 2 == 0 ? s : nil }.compact
9
+ EVEN_DB_HOSTS = find_servers(:roles => :db).each_with_index.map { |s, i| i % 2 == 1 ? s : nil }.compact
10
+ ODD_DB_HOSTS = find_servers(:roles => :db).each_with_index.map { |s, i| i % 2 == 0 ? s : nil }.compact
11
+
12
+ def filter_hosts(hostfilter)
13
+ old_hostfilter = ENV['HOSTFILTER']
14
+ ENV['HOSTFILTER'] = hostfilter.to_s
15
+ yield
16
+ if old_hostfilter.nil?
17
+ ENV.delete('HOSTFILTER')
18
+ else
19
+ ENV['HOSTFILTER'] = old_hostfilter
20
+ end
21
+ end
22
+
23
+ namespace :deploy do
24
+ desc "phased deploy of app on production"
25
+ task :zero do
26
+ [ ODD_PASSENGER_HOSTS + ODD_DB_HOSTS, EVEN_PASSENGER_HOSTS + EVEN_DB_HOSTS ].each do |hostlist|
27
+ filter_hosts(hostlist.join(',')) do
28
+ top.deploy.zero_downtime
29
+ end
30
+ end
31
+ end
32
+
33
+ task :zero_downtime do
34
+ top.deploy.update
35
+ top.deploy.restart
36
+ end
37
+
38
+ end
39
+
40
+ namespace :edploy do
41
+ namespace :apache do
42
+ desc "phased restart of apache on production"
43
+ task :restart, :roles => :passenger do
44
+ [ ODD_PASSENGER_HOSTS, EVEN_PASSENGER_HOSTS ].each do |hostlist|
45
+ filter_hosts(hostlist.join(',')) do
46
+ top.deploy.apache.actual_restart
47
+ end
48
+ end
49
+ end
50
+
51
+ task :actual_restart, :roles => :passenger do
52
+ run "apache2ctl configtest && sudo /etc/init.d/apache2 restart"
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,5 @@
1
+ namespace :deploy do
2
+ task :restart do
3
+ run "touch #{current_path}/tmp/restart.txt"
4
+ end
5
+ end
@@ -0,0 +1,26 @@
1
+ require File.expand_path('../../../edployscripts/archive', __FILE__)
2
+ require File.expand_path('../../../edployscripts/file_handling', __FILE__)
3
+
4
+ namespace :edploy do
5
+
6
+ after 'deploy:setup', 'edploy:setup_project'
7
+ task :setup_project do
8
+ extract_path = File.expand_path('../../config/copy/extract', __FILE__)
9
+ init_scripts_path = File.expand_path('../../config/copy/init_scripts', __FILE__)
10
+
11
+ Edploy::FileHandling.iterate_dir_items(extract_path) do |dirname, basename, relative_entry, fqpn|
12
+ Edploy::FileHandling.upload_file(fqpn, "#{shared_path}/tmp") do |tmp_file|
13
+ target_dir = dirname.gsub(/HOME/, '~')
14
+ Edploy::Archive.extract tmp_file, target_dir, :remove => true
15
+ end
16
+ end
17
+
18
+ Edploy::FileHandling.iterate_dir_items(init_scripts_path) do |dirname, basename, relative_entry, fqpn|
19
+ Edploy::FileHandling.upload_file(fqpn, "#{shared_path}/tmp") do |tmp_file|
20
+ script_name = "#{application}_#{stage}_#{basename}"
21
+ system("sudo sh -c 'test -f /etc/init.d/#{script_name} || { cp #{tmp_file} /etc/init.d/#{script_name} ; chmod 755 /etc/init.d/#{script_name} ; update-rc.d #{script_name} defaults ; }'")
22
+ end
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,47 @@
1
+ namespace :deploy do
2
+
3
+ desc <<-DESC
4
+ Deploys your project. Does smart deploy if this is activated.
5
+ DESC
6
+ task :default do
7
+ if exists?(:smart_deploy) && smart_deploy
8
+ top.deploy.smart
9
+ else
10
+ top.deploy.code
11
+ end
12
+ end
13
+
14
+ desc <<-DESC
15
+ Deploys your project. This calls both `update' and `restart'. Note that \
16
+ this will generally only work for applications that have already been deployed \
17
+ once. For a "cold" deploy, you'll want to take a look at the `deploy:cold' \
18
+ task, which handles the cold start specifically.
19
+ DESC
20
+ task :code do
21
+ update
22
+ restart
23
+ end
24
+
25
+ desc <<-DESC
26
+ Deploys your project. Automagically figures out if deploy:setup, deploy:cold, \
27
+ deploy:migrations or deploy:default is needed.
28
+ DESC
29
+ task :smart do
30
+ project_is_setup = (capture("find #{deploy_to} -mindepth 1 -maxdepth 1 -type d \\( -name 'releases' -o -name 'shared' \\) 2>/dev/null | wc -l").to_i == 2)
31
+ top.deploy.setup unless project_is_setup
32
+ at_least_one_release_exists = capture("find #{releases_path} -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l").to_i > 0
33
+ unless at_least_one_release_exists
34
+ top.deploy.cold
35
+ else
36
+ remote_migrations = capture("find #{latest_release}/db/migrate -type f -name '*.rb' -exec basename {} .rb \\; 2>/dev/null || true").split("\n")
37
+ local_migrations = `find db/migrate -type f -name '*.rb' -exec basename {} .rb \\; 2>/dev/null || true`.split("\n")
38
+ pending_migrations = !(local_migrations - remote_migrations).empty?
39
+ if pending_migrations
40
+ top.deploy.migrations
41
+ else
42
+ top.deploy.code
43
+ end
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,60 @@
1
+ require 'erb'
2
+ require 'fileutils'
3
+ def render_template(local_template)
4
+ template_name = File.basename(local_template)
5
+ file_name = File.basename(template_name, '.erb')
6
+ app = File.basename(File.dirname(local_template))
7
+ path = File.join(deploy_to, app, file_name)
8
+
9
+ template = ERB.new(IO.read(local_template))
10
+ rendered_template = template.result(binding)
11
+
12
+ tmp_path = File.join('/tmp/', "#{File.basename(path)}-$CAPISTRANO:HOST$")
13
+ put(rendered_template, tmp_path, :mode => 0644)
14
+ send run_method, <<-CMD
15
+ sh -c "install -m#{sprintf("%3o",0644)} #{tmp_path} #{path} &&
16
+ rm -f #{tmp_path}"
17
+ CMD
18
+ end
19
+
20
+ TEMPLATES = {}
21
+ Dir[File.expand_path("../../config/templates/*", __FILE__)].each do |app|
22
+ TEMPLATES[File.basename(app).to_sym] = {
23
+ :templates => Dir[File.join(app, '*.erb')],
24
+ :config => File.join(app, 'config.rb'),
25
+ :after_config => File.join(app, 'after_config.rb'),
26
+ :activate => File.join(app, 'activate.rb'),
27
+ :deactivate => File.join(app, 'deactivate.rb'),
28
+ }
29
+ end
30
+
31
+ TEMPLATES.each do |application, data|
32
+ eval File.read(data[:config]) if File.exists?(data[:config])
33
+
34
+ namespace :edploy do
35
+ namespace application do
36
+ after 'deploy:setup', "edploy:#{application}:config"
37
+ desc "Push #{application} config files to server"
38
+ task :config, :roles => application do
39
+ data[:templates].each do |template|
40
+ render_template(template)
41
+ end
42
+ eval File.read(data[:after_config]) if File.exists?(data[:after_config])
43
+ end
44
+
45
+ if File.exists?(data[:activate])
46
+ desc "Activate #{application}"
47
+ task :activate, :roles => application do
48
+ eval File.read(data[:activate])
49
+ end
50
+ end
51
+
52
+ if File.exists?(data[:deactivate])
53
+ desc "Deactivate #{application}"
54
+ task :deactivate, :roles => application do
55
+ eval File.read(data[:deactivate])
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,32 @@
1
+ namespace :edploy do
2
+ namespace :web do
3
+ namespace :disable do
4
+ before 'deploy', 'edploy:web:disable:maintenance'
5
+ before 'deploy:migrations', 'edploy:web:disable:maintenance'
6
+ task :maintenance, :roles => :passenger do
7
+ on_rollback { run "rm -f #{shared_path}/system/maintenance.html" }
8
+ run "cp #{current_path}/config/edploy/copy/project_files/#{stage}/$(hostname)/public/maintenance.html #{shared_path}/system/ 2>/dev/null || cp #{current_path}/config/edploy/copy/project_files/#{stage}/public/maintenance.html #{shared_path}/system/ 2>/dev/null || cp #{current_path}/public/maintenance.html #{shared_path}/system/ 2>/dev/null"
9
+ end
10
+
11
+ before 'deploy:zero_downtime', 'edploy:web:disable:zero_downtime'
12
+ task :zero_downtime, :roles => :passenger do
13
+ run "mv /var/www/check.txt /var/www/check2.txt 2>/dev/null || true"
14
+ # sudo "/sbin/iptables -D INPUT -p tcp -s 95.170.77.132/26 -m state --state NEW --dport 443 -j ACCEPT 2>/dev/null || true"
15
+ end
16
+ end
17
+
18
+ namespace :enable do
19
+ after 'deploy', 'edploy:web:enable:maintenance'
20
+ after 'deploy:migrations', 'edploy:web:enable:maintenance'
21
+ task :maintenance, :roles => :passenger do
22
+ run "rm -f #{shared_path}/system/maintenance.html"
23
+ end
24
+
25
+ after 'deploy:zero_downtime', 'edploy:web:enable:zero_downtime'
26
+ task :zero_downtime, :roles => :passenger do
27
+ run "mv /var/www/check2.txt /var/www/check.txt 2>/dev/null || true"
28
+ # sudo "/sbin/iptables -A INPUT -p tcp -s 95.170.77.132/26 -m state --state NEW --dport 443 -j ACCEPT"
29
+ end
30
+ end
31
+ end
32
+ end
data/lib/edploy.rb ADDED
@@ -0,0 +1,17 @@
1
+ # Copyright (c) 2009-2012 by Ewout Vonk. All rights reserved.
2
+
3
+ # prevent loading when called by Bundler, only load when called by capistrano
4
+ if caller.any? { |callstack_line| callstack_line =~ /^Capfile:/ }
5
+ unless Capistrano::Configuration.respond_to?(:instance)
6
+ abort "edploy requires Capistrano 2"
7
+ end
8
+
9
+ Capistrano::Configuration.instance(:must_exist).load do
10
+ load File.expand_path('../edploy/environment_capistrano.rb', __FILE__)
11
+ load File.expand_path('../capistrano/ext/multistage.rb', __FILE__)
12
+ # load File.expand_path('../capistrano/ext/mailer.rb', __FILE__)
13
+ # load File.expand_path('../capistrano/ext/output_catcher.rb', __FILE__)
14
+ # load File.expand_path('../capistrano/ext/output_hooks.rb', __FILE__)
15
+ Dir[File.expand_path('../edploy/recipes/*.rb', __FILE__)].each { |plugin| load(plugin) }
16
+ end
17
+ end
@@ -0,0 +1,140 @@
1
+ require 'highline'
2
+ require 'yaml'
3
+ require 'fileutils'
4
+ # work around problem where HighLine detects an eof on $stdin and raises an err
5
+ HighLine.track_eof = false
6
+
7
+ module Edployify
8
+
9
+ class Configuration
10
+
11
+ class << self
12
+
13
+ def configure
14
+ instance.configure
15
+ end
16
+
17
+ def get_binding
18
+ instance.get_binding
19
+ end
20
+
21
+ private
22
+
23
+ def instance
24
+ @@instance ||= self.new
25
+ end
26
+
27
+ end
28
+
29
+ def get_binding
30
+ binding
31
+ end
32
+
33
+ CONFIGURATION_KEYS = {
34
+ :application => { :default => Proc.new { |c| File.basename(::RAILS_ROOT).gsub(/[^A-Za-z0-9_]/, '_').gsub(/_+/, '_') }, :type => Symbol },
35
+ :domain => { :default => Proc.new { |c| "#{c[:application]}.com" } },
36
+ :scm => { :default => :git, :type => Symbol },
37
+ :git_shallow_clone => { :default => true, :boolean => true },
38
+ :repository => { :default => Proc.new { |c| "git@HOSTNAME:#{c[:application]}.git" } },
39
+ :git_enable_submodules => { :default => false, :boolean => true },
40
+ :deploy_via => { :default => :fast_remote_cache, :type => Symbol },
41
+ :deploy_to => { :default => Proc.new { |c| "/apps/#{c[:application]}" } },
42
+ :user => { :default => 'deploy' },
43
+ :group => { :default => 'deploy' },
44
+ :normalize_asset_timestamps => { :default => false, :boolean => true },
45
+ :paranoid => { :ssh_option => true, :default => false, :boolean => true },
46
+ :forward_agent => { :ssh_option => true, :default => true, :boolean => true },
47
+ :notify_email => { :default => Proc.new { |c| "support@#{c[:application]}.com" } }
48
+ }
49
+
50
+ def configure
51
+ CONFIGURATION_KEYS.each do |variable, options|
52
+ if self.respond_to?(:"ask_#{variable}")
53
+ self.send(:"ask_#{variable}", variable, options)
54
+ elsif options[:ssh_option]
55
+ ask_ssh_option(variable, options)
56
+ else
57
+ ask_value(variable, options)
58
+ end
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def ask_repository(variable, options = {})
65
+ default = base[variable]
66
+ unless default
67
+ preset = Edployify::Preferences.request_preset(:use)
68
+ project = highline.ask("Git project name (without .git): ") { |q| q.default = config[:application] if config[:application] }
69
+ default = preset.sub(/\{PROJECT\}/, project)
70
+ options[:default] = default
71
+ end
72
+ ask_value(variable, options)
73
+ end
74
+
75
+ def ask_ssh_option(variable, options = {})
76
+ ask_value(variable, options.merge(:base => config[:ssh_options]))
77
+ end
78
+
79
+ def ask_value(variable, options = {})
80
+ base = options[:base] || config
81
+ default = base[variable] || options[:default]
82
+ if default.is_a?(Proc)
83
+ default = default.call(config)
84
+ end
85
+
86
+ variable_prompt_name = variable.to_s.gsub(/(^|_)[A-Za-z0-9]/, &:upcase).gsub(/_/, ' ')
87
+ question_prompt = "#{variable_prompt_name} " # add trailing space so user input happens on the same line
88
+
89
+ value = if options[:boolean]
90
+ highline.agree(question_prompt) do |q|
91
+ q.default = (default ? "y" : "n") unless default.nil?
92
+ q.readline = true
93
+ end
94
+ else
95
+ arguments = [question_prompt]
96
+ arguments << options[:type] if options[:type]
97
+ highline.ask(*arguments) do |q|
98
+ q.default = default if default
99
+ end
100
+ end
101
+
102
+ base[variable] = value
103
+ write_file
104
+ end
105
+
106
+ ##
107
+
108
+ def config
109
+ unless @config
110
+ edploy_yml = File.join(::RAILS_ROOT, 'config', 'edploy', 'edploy.yml')
111
+ FileUtils.touch edploy_yml
112
+ $edploy_yml = File.open(edploy_yml, "r+")
113
+ at_exit do
114
+ $edploy_yml.close unless $edploy_yml.closed?
115
+ end
116
+ config_contents = $edploy_yml.read
117
+ @config = config_contents.length == 0 ? {} : YAML::load(config_contents)
118
+ $edploy_yml.rewind
119
+ @config[:ssh_options] = {}
120
+ end
121
+ @config
122
+ end
123
+
124
+ def write_file
125
+ $edploy_yml.write YAML::dump(config)
126
+ $edploy_yml.truncate $edploy_yml.pos
127
+ $edploy_yml.rewind
128
+ end
129
+
130
+ def highline
131
+ @highline ||= HighLine.new
132
+ end
133
+
134
+ def method_missing(method, *args, &block)
135
+ config[method.to_sym] || config[method.to_s] || super
136
+ end
137
+
138
+ end
139
+
140
+ end