edploy 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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