ol-whisk_deploy 0.6.25

Sign up to get free protection for your applications and to get access to all the features.
Files changed (177) hide show
  1. data/CHANGELOG +292 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.integration_specs +24 -0
  4. data/README.markdown +832 -0
  5. data/Rakefile +105 -0
  6. data/VERSION +1 -0
  7. data/WHY.txt +45 -0
  8. data/bin/wd +61 -0
  9. data/bin/wd_role +42 -0
  10. data/examples/deploy-configs.yml +13 -0
  11. data/examples/deploy-local.yml +4 -0
  12. data/examples/deploy-multiple-remotes.yml +26 -0
  13. data/examples/deploy-staging.yml +8 -0
  14. data/examples/deploy.rake +11 -0
  15. data/examples/deploy.yml +16 -0
  16. data/init.rb +1 -0
  17. data/install.rb +5 -0
  18. data/lib/whiskey_disk.rb +327 -0
  19. data/lib/whiskey_disk/config.rb +127 -0
  20. data/lib/whiskey_disk/config/abstract_filter.rb +19 -0
  21. data/lib/whiskey_disk/config/filter.rb +48 -0
  22. data/lib/whiskey_disk/config/filters/add_environment_name_filter.rb +11 -0
  23. data/lib/whiskey_disk/config/filters/add_project_name_filter.rb +11 -0
  24. data/lib/whiskey_disk/config/filters/check_for_duplicate_domains_filter.rb +21 -0
  25. data/lib/whiskey_disk/config/filters/convert_role_strings_to_list_filter.rb +20 -0
  26. data/lib/whiskey_disk/config/filters/default_config_target_filter.rb +12 -0
  27. data/lib/whiskey_disk/config/filters/default_domain_filter.rb +12 -0
  28. data/lib/whiskey_disk/config/filters/drop_empty_domain_roles_filter.rb +32 -0
  29. data/lib/whiskey_disk/config/filters/environment_scope_filter.rb +20 -0
  30. data/lib/whiskey_disk/config/filters/hashify_domain_entries_filter.rb +29 -0
  31. data/lib/whiskey_disk/config/filters/localize_domains_filter.rb +24 -0
  32. data/lib/whiskey_disk/config/filters/modules/scope_helper.rb +11 -0
  33. data/lib/whiskey_disk/config/filters/normalize_ssh_options_filter.rb +29 -0
  34. data/lib/whiskey_disk/config/filters/project_scope_filter.rb +34 -0
  35. data/lib/whiskey_disk/config/filters/select_project_and_environment_filter.rb +12 -0
  36. data/lib/whiskey_disk/config/filters/stringify_hash_keys_filter.rb +25 -0
  37. data/lib/whiskey_disk/helpers.rb +50 -0
  38. data/lib/whiskey_disk/rake.rb +47 -0
  39. data/scenarios/git_repositories/config.git/HEAD +1 -0
  40. data/scenarios/git_repositories/config.git/config +5 -0
  41. data/scenarios/git_repositories/config.git/description +1 -0
  42. data/scenarios/git_repositories/config.git/git-daemon-export-ok +0 -0
  43. data/scenarios/git_repositories/config.git/hooks/applypatch-msg.sample +15 -0
  44. data/scenarios/git_repositories/config.git/hooks/commit-msg.sample +24 -0
  45. data/scenarios/git_repositories/config.git/hooks/post-commit.sample +8 -0
  46. data/scenarios/git_repositories/config.git/hooks/post-receive.sample +15 -0
  47. data/scenarios/git_repositories/config.git/hooks/post-update.sample +8 -0
  48. data/scenarios/git_repositories/config.git/hooks/pre-applypatch.sample +14 -0
  49. data/scenarios/git_repositories/config.git/hooks/pre-commit.sample +46 -0
  50. data/scenarios/git_repositories/config.git/hooks/pre-rebase.sample +169 -0
  51. data/scenarios/git_repositories/config.git/hooks/prepare-commit-msg.sample +36 -0
  52. data/scenarios/git_repositories/config.git/hooks/update.sample +128 -0
  53. data/scenarios/git_repositories/config.git/info/exclude +6 -0
  54. data/scenarios/git_repositories/config.git/objects/0d/b14dd6ddc54017c0a11960dcda82ed802cde69 +0 -0
  55. data/scenarios/git_repositories/config.git/objects/0e/e781f5ce80d64db32a74a7aae7b5248dafe112 +3 -0
  56. data/scenarios/git_repositories/config.git/objects/17/6bf54cf17d1d1c24556dc059c4144a5df230e8 +0 -0
  57. data/scenarios/git_repositories/config.git/objects/20/e9ff3feaa8ede30f707e5f1b4356e3c02bb7ec +0 -0
  58. data/scenarios/git_repositories/config.git/objects/45/117b1c775f0de415478dbf08ed9d667ab17d13 +0 -0
  59. data/scenarios/git_repositories/config.git/objects/51/3954c9aca090e6ce40359f0e9fde30ea78eb8c +0 -0
  60. data/scenarios/git_repositories/config.git/objects/66/947a7a11a6f5d3d561fe95de284ced3010819a +0 -0
  61. data/scenarios/git_repositories/config.git/objects/6b/bc79311bfac47d3ed724aa82a4814e0dda4c67 +0 -0
  62. data/scenarios/git_repositories/config.git/objects/71/eb5df52676e8e6efba471050b46978173af110 +1 -0
  63. data/scenarios/git_repositories/config.git/objects/84/17d2fe3e8fcc0825249c517b29b0f9ea8b8b31 +2 -0
  64. data/scenarios/git_repositories/config.git/objects/8b/384fcfcf7c0dee7c3c1d5636bee9e645d9cf38 +0 -0
  65. data/scenarios/git_repositories/config.git/objects/bb/59da633ba74296b0c2f9ff70784ac155ddb599 +0 -0
  66. data/scenarios/git_repositories/config.git/objects/cc/b86b26189afbf45d8eb9165812ab86dbdfca63 +0 -0
  67. data/scenarios/git_repositories/config.git/objects/d1/0bcd51fec41f854001e4d61f99d9e282a695d3 +0 -0
  68. data/scenarios/git_repositories/config.git/objects/d8/a8b0f5b1fd66844efb141d9544965ea0065f2d +0 -0
  69. data/scenarios/git_repositories/config.git/objects/e6/b02c66ad632e6b8535c4630cb8fe07732a72fc +0 -0
  70. data/scenarios/git_repositories/config.git/objects/e8/b8bfeeba735c0a1a873082554cb4d7256ac125 +0 -0
  71. data/scenarios/git_repositories/config.git/objects/f9/0181466a1a60b793ca9cc9abd584c18d4e3887 +0 -0
  72. data/scenarios/git_repositories/config.git/objects/f9/49d5d8a4f12c91471e34d4e277239c35ebd10d +0 -0
  73. data/scenarios/git_repositories/config.git/refs/heads/master +1 -0
  74. data/scenarios/git_repositories/project.git/HEAD +1 -0
  75. data/scenarios/git_repositories/project.git/config +5 -0
  76. data/scenarios/git_repositories/project.git/description +1 -0
  77. data/scenarios/git_repositories/project.git/git-daemon-export-ok +0 -0
  78. data/scenarios/git_repositories/project.git/hooks/applypatch-msg.sample +15 -0
  79. data/scenarios/git_repositories/project.git/hooks/commit-msg.sample +24 -0
  80. data/scenarios/git_repositories/project.git/hooks/post-commit.sample +8 -0
  81. data/scenarios/git_repositories/project.git/hooks/post-receive.sample +15 -0
  82. data/scenarios/git_repositories/project.git/hooks/post-update.sample +8 -0
  83. data/scenarios/git_repositories/project.git/hooks/pre-applypatch.sample +14 -0
  84. data/scenarios/git_repositories/project.git/hooks/pre-commit.sample +46 -0
  85. data/scenarios/git_repositories/project.git/hooks/pre-rebase.sample +169 -0
  86. data/scenarios/git_repositories/project.git/hooks/prepare-commit-msg.sample +36 -0
  87. data/scenarios/git_repositories/project.git/hooks/update.sample +128 -0
  88. data/scenarios/git_repositories/project.git/info/exclude +6 -0
  89. data/scenarios/git_repositories/project.git/objects/04/26e152e66c8cd42974279bdcae09be9839c172 +0 -0
  90. data/scenarios/git_repositories/project.git/objects/04/f4de85eaf72ef1631dc6d7424045c0a749b757 +1 -0
  91. data/scenarios/git_repositories/project.git/objects/06/13fe277280cbcdb2856e1eefc70bdaff011b20 +0 -0
  92. data/scenarios/git_repositories/project.git/objects/06/7aca89b86265eee211387434c3e50f37ccf009 +0 -0
  93. data/scenarios/git_repositories/project.git/objects/09/445dacc4822722612d60833c9948219ecdd8f5 +0 -0
  94. data/scenarios/git_repositories/project.git/objects/11/c4ec64326de35462f4e79d0f4229bf8e26e0c5 +0 -0
  95. data/scenarios/git_repositories/project.git/objects/20/1c7641c2e42b0b904e5c1f793489d8b858e4da +2 -0
  96. data/scenarios/git_repositories/project.git/objects/23/979639da60d2d31e9744468df1c1221b101e64 +0 -0
  97. data/scenarios/git_repositories/project.git/objects/27/a3fff2c4c45ab5513a405f694c0a042cb5d417 +1 -0
  98. data/scenarios/git_repositories/project.git/objects/2c/0c33cfba8e1af15df88522c0db2b10a6a94138 +2 -0
  99. data/scenarios/git_repositories/project.git/objects/38/b574660305ecb5fec6b2daa7ee1e0dbf1b6003 +0 -0
  100. data/scenarios/git_repositories/project.git/objects/4a/57abb5e4e426cfc9101b3af22ac83ccbd8e2ad +0 -0
  101. data/scenarios/git_repositories/project.git/objects/4c/77ebdd985e57afe7988480720c5dc77ec525c9 +0 -0
  102. data/scenarios/git_repositories/project.git/objects/51/c94da6f1b8aa9d2346088d3d362475b60c7f32 +0 -0
  103. data/scenarios/git_repositories/project.git/objects/5b/a96acf9cc9b87babe37c032676f53bf1ba9ae7 +2 -0
  104. data/scenarios/git_repositories/project.git/objects/5d/f555601d60f1c2a84d2364af0ad640612c3ba5 +0 -0
  105. data/scenarios/git_repositories/project.git/objects/71/03b5ac94940d596c2160a5cfcd55ca4ccac41f +0 -0
  106. data/scenarios/git_repositories/project.git/objects/73/3fc331098b03523f414f3509b9ae6e637c6866 +0 -0
  107. data/scenarios/git_repositories/project.git/objects/80/26076649ceccbe96a6292f2432652f08483035 +0 -0
  108. data/scenarios/git_repositories/project.git/objects/86/d1ef0976be4567de562224e1b51fbf9820c53a +1 -0
  109. data/scenarios/git_repositories/project.git/objects/87/a9d8b09b3401d21b23d90253332d6b28b47db2 +0 -0
  110. data/scenarios/git_repositories/project.git/objects/8b/030ba688255c917d189ae3f87d7c5ccd226bc2 +0 -0
  111. data/scenarios/git_repositories/project.git/objects/95/c9d5ad9b1c90e4c805516783105fc2037dedeb +2 -0
  112. data/scenarios/git_repositories/project.git/objects/95/d82d043af35a80eabfd56c0d705abfa3488787 +2 -0
  113. data/scenarios/git_repositories/project.git/objects/96/0bf34bb0b46d0aeb0be87f688f4ef06a4b35e1 +0 -0
  114. data/scenarios/git_repositories/project.git/objects/a3/860106dc1d148c7831cd45ae38829b4ed47702 +2 -0
  115. data/scenarios/git_repositories/project.git/objects/a8/506d6439b71784a72ac72d284b2ad53088f573 +0 -0
  116. data/scenarios/git_repositories/project.git/objects/ad/22ea6c7563777936ecfbe50d8e2cf8120fd525 +0 -0
  117. data/scenarios/git_repositories/project.git/objects/ae/3900de54aff557c61c81146d00f9d38e55a265 +1 -0
  118. data/scenarios/git_repositories/project.git/objects/bf/5e3740d52b80abb0378b3f85f93a53b1294521 +1 -0
  119. data/scenarios/git_repositories/project.git/objects/bf/b59811cdbc069418dee14b171e6e7e979784b7 +0 -0
  120. data/scenarios/git_repositories/project.git/objects/cc/5ac0afb24e727d5de344cc26a425f4fb7fd17d +3 -0
  121. data/scenarios/git_repositories/project.git/objects/d1/091aa2dd76885108461110c639e6b33a297fce +0 -0
  122. data/scenarios/git_repositories/project.git/objects/d8/913f6650eb2b7bf2a633732d8452008ca23dcb +0 -0
  123. data/scenarios/git_repositories/project.git/objects/db/d1b9667f1b26b13331ac0c321dced8be1aeab0 +3 -0
  124. data/scenarios/git_repositories/project.git/objects/e4/3b9107e9b1908ce415025e64eb83a493d329b7 +0 -0
  125. data/scenarios/git_repositories/project.git/objects/ef/2a88894d5421920b9dfe67a9a4d8043830e62e +0 -0
  126. data/scenarios/git_repositories/project.git/objects/f4/0123a1ff20c65d8dc15a38a83222647908e6f7 +0 -0
  127. data/scenarios/git_repositories/project.git/objects/f5/0af315b75ca0b12c720dec6d916b76b968c319 +0 -0
  128. data/scenarios/git_repositories/project.git/objects/f6/0215709b7b23f3738e9cbaf634b1c86bbd376a +0 -0
  129. data/scenarios/git_repositories/project.git/refs/heads/bad_rakefile +1 -0
  130. data/scenarios/git_repositories/project.git/refs/heads/hook_with_changed +1 -0
  131. data/scenarios/git_repositories/project.git/refs/heads/master +1 -0
  132. data/scenarios/git_repositories/project.git/refs/heads/no_rake_hooks +1 -0
  133. data/scenarios/git_repositories/project.git/refs/heads/post_rake_tasks +1 -0
  134. data/scenarios/invalid/deploy.yml +1 -0
  135. data/scenarios/local/deploy.yml.erb +17 -0
  136. data/scenarios/remote/deploy.yml +119 -0
  137. data/scenarios/setup/vagrant/.gitignore +3 -0
  138. data/scenarios/setup/vagrant/Vagrantfile +10 -0
  139. data/scenarios/setup/vagrant/manifests/integration.pp +32 -0
  140. data/scenarios/setup/vagrant/pids/.gitignore +1 -0
  141. data/spec/.bacon +0 -0
  142. data/spec/init_spec.rb +9 -0
  143. data/spec/install_spec.rb +43 -0
  144. data/spec/integration/branch_switching_spec.rb +41 -0
  145. data/spec/integration/deployment_failures_spec.rb +106 -0
  146. data/spec/integration/helper_spec.rb +90 -0
  147. data/spec/integration/invalid_configuration_spec.rb +39 -0
  148. data/spec/integration/local_deployments_spec.rb +230 -0
  149. data/spec/integration/post_rake_tasks_spec.rb +226 -0
  150. data/spec/integration/post_scripts_spec.rb +246 -0
  151. data/spec/integration/remote_deployments_spec.rb +166 -0
  152. data/spec/integration/staleness_checks_spec.rb +72 -0
  153. data/spec/spec_helper.rb +117 -0
  154. data/spec/wd_command_spec.rb +986 -0
  155. data/spec/wd_role_command_spec.rb +49 -0
  156. data/spec/whiskey_disk/config/filter_spec.rb +77 -0
  157. data/spec/whiskey_disk/config/filters/add_environment_name_filter_spec.rb +20 -0
  158. data/spec/whiskey_disk/config/filters/add_project_name_filter_spec.rb +19 -0
  159. data/spec/whiskey_disk/config/filters/check_for_duplicate_domains_filter_spec.rb +29 -0
  160. data/spec/whiskey_disk/config/filters/convert_role_strings_to_list_filter_spec.rb +48 -0
  161. data/spec/whiskey_disk/config/filters/default_config_target_filter_spec.rb +19 -0
  162. data/spec/whiskey_disk/config/filters/default_domain_filter_spec.rb +18 -0
  163. data/spec/whiskey_disk/config/filters/drop_empty_domain_roles_filter_spec.rb +60 -0
  164. data/spec/whiskey_disk/config/filters/environment_scope_filter_spec.rb +32 -0
  165. data/spec/whiskey_disk/config/filters/hashify_domain_entries_filter_spec.rb +41 -0
  166. data/spec/whiskey_disk/config/filters/localize_domains_filter_spec.rb +30 -0
  167. data/spec/whiskey_disk/config/filters/normalize_ssh_options_filter_spec.rb +56 -0
  168. data/spec/whiskey_disk/config/filters/project_scope_filter_spec.rb +75 -0
  169. data/spec/whiskey_disk/config/filters/select_project_and_environment_filter_spec.rb +30 -0
  170. data/spec/whiskey_disk/config/filters/stringify_hash_keys_filter_spec.rb +40 -0
  171. data/spec/whiskey_disk/config_spec.rb +754 -0
  172. data/spec/whiskey_disk/helpers_spec.rb +443 -0
  173. data/spec/whiskey_disk/rake_spec.rb +261 -0
  174. data/spec/whiskey_disk_spec.rb +1224 -0
  175. data/tasks/deploy.rake +2 -0
  176. data/whisk_deploy.gemspec +215 -0
  177. metadata +242 -0
@@ -0,0 +1,105 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ desc 'Default: run unit tests.'
5
+ task :default => :test
6
+
7
+ desc 'Test whiskey_disk'
8
+ task :test do
9
+ files = Dir['spec/**/*_spec.rb'].join(" ")
10
+ system("bacon #{files}")
11
+ end
12
+
13
+ namespace :integration do
14
+ def say(mesg)
15
+ STDERR.puts mesg
16
+ end
17
+
18
+ def vagrant_path
19
+ File.expand_path(File.join(File.dirname(__FILE__), 'scenarios', 'setup', 'vagrant'))
20
+ end
21
+
22
+ def root_path
23
+ File.expand_path(File.dirname(__FILE__))
24
+ end
25
+
26
+ def pidfile
27
+ File.join(vagrant_path, 'pids', 'git-daemon.pid')
28
+ end
29
+
30
+ def start_git_daemon
31
+ stop_git_daemon
32
+ say "Starting git daemon..."
33
+ run(root_path, "git daemon --base-path=#{root_path}/scenarios/git_repositories/ --reuseaddr --verbose --detach --pid-file=#{pidfile}")
34
+ end
35
+
36
+ def stop_git_daemon
37
+ return unless File.exists?(pidfile)
38
+ pid = File.read(pidfile).chomp
39
+ return if pid == ''
40
+ say "Stopping git daemon..."
41
+ run(root_path, "kill #{pid}")
42
+ end
43
+
44
+ def start_vm
45
+ say "Bringing up vagrant vm..."
46
+ run(vagrant_path, 'vagrant up')
47
+ copy_ssh_config
48
+ end
49
+
50
+ def stop_vm
51
+ say "Shutting down vagrant vm..."
52
+ run(vagrant_path, 'vagrant halt')
53
+ end
54
+
55
+ def copy_ssh_config
56
+ say "Capturing vagrant ssh_config data..."
57
+ run(vagrant_path, "vagrant ssh_config > #{vagrant_path}/ssh_config")
58
+ end
59
+
60
+ def run(path, cmd)
61
+ Dir.chdir(path)
62
+ say "running: #{cmd} [cwd: #{Dir.pwd}]"
63
+ system(cmd)
64
+ end
65
+
66
+ desc 'Start a vagrant VM and git-daemon server to support running integration specs'
67
+ task :up do
68
+ start_vm
69
+ start_git_daemon
70
+ end
71
+
72
+ desc 'Shut down integration vagrant VM and git-daemon server'
73
+ task :down do
74
+ stop_git_daemon
75
+ stop_vm
76
+ end
77
+
78
+ desc 'Completely remove the vagrant VM files used by the integration spec suite'
79
+ task :destroy do
80
+ stop_git_daemon
81
+ stop_vm
82
+ run(vagrant_path, 'vagrant destroy')
83
+ end
84
+ end
85
+
86
+ begin
87
+ require 'jeweler'
88
+ Jeweler::Tasks.new do |gemspec|
89
+ gemspec.name = "whisk_deploy"
90
+ gemspec.summary = "embarrassingly fast deployments."
91
+ gemspec.description = "Opinionated gem for doing fast git-based server deployments."
92
+ gemspec.email = "jesse@techno-geeks.org"
93
+ gemspec.homepage = "http://github.com/jesseadams/whiskey"
94
+ gemspec.authors = ["Rick Bradley", "Jesse R. Adams"]
95
+ gemspec.add_dependency('rake')
96
+
97
+ # I've decided that the integration spec shizzle shouldn't go into the gem
98
+ rejected = %w(scenarios spec/integration)
99
+ gemspec.files.reject {|f| rejected.include?(f) }
100
+ gemspec.test_files.reject {|f| rejected.include?(f) }
101
+ end
102
+ Jeweler::GemcutterTasks.new
103
+ rescue LoadError
104
+ # if you get here, you need Jeweler installed to do packaging and gem installation, yo.
105
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.6.25
data/WHY.txt ADDED
@@ -0,0 +1,45 @@
1
+
2
+ Why?
3
+ ----
4
+
5
+ The idea here is inspired by github's cleaned up deployment scripts, and mislav's git-deploy project. Only, we gave up on capistrano a long time ago, and after a few years of doing deployments on a few dozen projects, we realized that we really have very little variation on how we do things. That is, we can afford to have a very opinionated tool to do our deployments.
6
+
7
+ Here are the features/constraints we're envisioning:
8
+
9
+ - We need some sort of very basic "run this on a remote server" functionality. We've been using vlad for years now and this would suffice: it does what we're looking for and is much much smaller than capistrano. If we don't load the included recipes it's basically a fancy ruby ssh wrapper.
10
+
11
+ - Setup should mostly just do a very fast remote git checkout in the right place. Deployment should very quickly update that checkout.
12
+
13
+ - We have been considering a move towards tracking configuration data across our projects as a separate concern. So, if we can have a private repo that stores per-project and per-environment (here I mean staging vs. production, etc.) configuration files, and have our deployments overlay those files quickly, that would be ideal. I'm talking about hoptoad configs, database.yml files, AWS cert files, GeoKit API keys, etc., etc., etc.
14
+
15
+ - We should be able to use the same "setup == clone" + "deploy == reset" technique to manage the per-project/per-environment config files.
16
+
17
+ - Using rsync on the remote to those overlay config files on the deployed project would be a fast way to get them in place.
18
+
19
+ - Get rid of a bunch of annoying symlinks and symlink-hoops-to-jump-through.
20
+
21
+ - Get rid of a bunch of space (yeah yeah disk is cheap, but copying isn't) on the disk devoted to umpteen "releases".
22
+
23
+ - Obviously reduce deployment time by doing less, ssh-ing less, and taking less time to do whatever.
24
+
25
+ - should be rake based, and should provide a bare minimum of tasks -- like deploy:setup, deploy:now, and maybe a deploy:refresh_config_files.
26
+
27
+ - While a very basic task or few would run after setup or after deployment (e.g., rake db:migrate if migrations were changed, or touch tmp/restart.txt if the web server needs a restart; see git-deploy for more examples), we should be able to declare optional rake tasks (e.g., "deploy:staging:post_deploy") and have them run on this project if they are declared.
28
+
29
+ - Should work with projects that aren't remotely ruby.
30
+
31
+ - Should be loadable as a gem, meaning that it doesn't need to live in your project's space. (see also non-ruby projects)
32
+
33
+ - Should be able to use a non-ruby config, preferably yaml, for information for all environments. That could be stored in <project>/config/deploy.yml and saved with the project. Even if this is just shoved into vlad 'set' commands, it's still an improvement: we don't need ruby in the config file because we're opinionated.
34
+
35
+ - should be able to override settings for an environment locally by declaring a <project>/config/deploy-<environment>.yml. Ideal for testing out deployments to different servers (or deploying locally). This also makes it possible to .gitignore your local settings, so everyone can have their config repos in different places.
36
+
37
+ - should make it easier to do local development (e.g., on a laptop) by being able to overlay config files using the same rake tasks as used for remote deployments, just not running the functionality remotely.
38
+
39
+ - dropping in a project Rakefile can add post-deploy / post-setup hooks transparently.
40
+
41
+ - actually have meaningful error messages, unlike anything that ever seems to happen with cap or vlad. :-/
42
+
43
+ - build this spec-first (whenever possible) so that there's a useful test suite.
44
+
45
+ - M$ windows hasn't been a priority for me for over a decade, not starting now.
data/bin/wd ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'whiskey_disk/rake'
5
+
6
+ $0 = "#{$0} setup|deploy" # jesus, this is a hack.
7
+
8
+ options = {}
9
+ op = OptionParser.new do |opts|
10
+ opts.on('-t=TARGET', '--to=TARGET', "deployment target") do |target|
11
+ options[:target] = target
12
+ end
13
+
14
+ opts.on('-p=TARGET', '--path=TARGET', "configuration path") do |path|
15
+ options[:path] = path
16
+ end
17
+
18
+ opts.on('-o=DOMAIN', '--only=DOMAIN', "limit deployment to this domain") do |domain|
19
+ options[:only] = domain
20
+ end
21
+
22
+ opts.on('-c', '--check', "do a staleness check before deploying") do |path|
23
+ options[:check] = 'true'
24
+ end
25
+
26
+ opts.on('-d', '--debug', "turn on debug mode (ssh -v and rake --trace)") do
27
+ options[:debug] = 'true'
28
+ end
29
+
30
+ opts.on('--version', 'show current version') do
31
+ puts File.read(File.expand_path(File.join(File.dirname(__FILE__), '..', 'VERSION')))
32
+ exit 0
33
+ end
34
+
35
+ opts.on_tail('-h', '--help', 'show this message') do
36
+ abort opts.to_s
37
+ end
38
+ end
39
+
40
+ begin
41
+ rest = op.parse(ARGV)
42
+ rescue
43
+ abort op.to_s
44
+ end
45
+
46
+ abort op.to_s unless options[:target]
47
+ abort op.to_s unless rest and rest.size == 1
48
+ command = rest.first
49
+ abort op.to_s unless ['deploy', 'setup'].include?(command)
50
+
51
+ ENV['to'] = options[:target]
52
+ ENV['path'] = options[:path]
53
+ ENV['only'] = options[:only]
54
+ ENV['check'] = options[:check]
55
+ ENV['debug'] = options[:debug]
56
+
57
+ if command == 'deploy'
58
+ Rake::Task['deploy:now'].invoke
59
+ else
60
+ Rake::Task['deploy:setup'].invoke
61
+ end
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+ require 'whiskey_disk/helpers'
3
+
4
+ # simple command-line script to detect whether this deployment target
5
+ # is in a specified whiskey_disk role. sets exit status appropriately
6
+ #
7
+ # useful for conditionalizing shell scripting based on roles.
8
+
9
+ role = ARGV.shift
10
+ exit(1) unless role?(role)
11
+
12
+ __END__
13
+
14
+ ( this example session presumes you `gem install rockhands` first... )
15
+
16
+ $ export WD_ROLES='app:db'
17
+ $ wd_role web && rock || shocker
18
+ .-.
19
+ .-.U|
20
+ |U| | .-.
21
+ | | |_|U|
22
+ | | | | |
23
+ /| ` |
24
+ | | |
25
+ | |
26
+ \ /
27
+ | |
28
+ | |
29
+
30
+ $ wd_role app && rock || shocker
31
+ .-.
32
+ |U|
33
+ | | .-.
34
+ | |-._|U|
35
+ | | | | |
36
+ /| ` |
37
+ | | |
38
+ | |
39
+ /
40
+ | |
41
+ | |
42
+
@@ -0,0 +1,13 @@
1
+ production:
2
+ domain: "ogc@hoenir.websages.com"
3
+ deploy_to: "/tmp/test-deployment"
4
+ deploy_config_to: "/tmp/test-config"
5
+ repository: "git@git.ogtastic.com:whiskey_disk.git"
6
+ config_repository: "git@git.ogtastic.com:ogc-config.git"
7
+ staging:
8
+ domain: "ogc@hoenir.websages.com"
9
+ deploy_to: "/tmp/test-deployment"
10
+ deploy_config_to: "/tmp/test-config"
11
+ repository: "git@git.ogtastic.com:whiskey_disk.git"
12
+ config_repository: "git@git.ogtastic.com:ogc-config.git"
13
+ config_target: "local"
@@ -0,0 +1,4 @@
1
+ local:
2
+ deploy_to: "/Users/rick/deploy/whiskey_disk/local"
3
+ repository: "/Users/rick/git/whiskey_disk"
4
+ branch: "develop"
@@ -0,0 +1,26 @@
1
+ multi:
2
+ domain:
3
+ - "ogc@hoenir.websages.com"
4
+ - "ogc@nerthus.websages.com"
5
+ deploy_to: "/tmp/test-deployment/"
6
+ repository: "git@git.ogtastic.com:whiskey_disk.git"
7
+ branch: "develop"
8
+ roles:
9
+ domain:
10
+ - name: "ogc@hoenir.websages.com"
11
+ roles:
12
+ - web
13
+ - app
14
+ - name: "ogc@nerthus.websages.com"
15
+ roles: db
16
+ deploy_to: "/tmp/test-deployment/"
17
+ repository: "git@git.ogtastic.com:whiskey_disk.git"
18
+ branch: "feature/support-domain-roles"
19
+ post_deploy_script: "/tmp/role-checker.sh"
20
+ badmulti:
21
+ domain:
22
+ - "ogc@hoenir.websages.com"
23
+ - "ogc@bogus.example.com"
24
+ deploy_to: "/tmp/test-deployment/"
25
+ repository: "git@git.ogtastic.com:whiskey_disk.git"
26
+ branch: "develop"
@@ -0,0 +1,8 @@
1
+ staging:
2
+ domain: "user@www.example.com"
3
+ deploy_to: "/var/www/suparsite.com/"
4
+ repository: "git://github.com/clarkkent/suparsite.git"
5
+ config_repository: "git@github.com:clarkkent/suparconfig.git"
6
+ deploy_config_to: "/var/cache/git/suparconfig"
7
+ rake_env:
8
+ RAILS_ENV: 'development'
@@ -0,0 +1,11 @@
1
+ namespace :deploy do
2
+ namespace :staging do
3
+ task :post_setup do
4
+ puts "This is my local post_setup hook."
5
+ end
6
+
7
+ task :post_deploy do
8
+ puts "This is my local post_deploy hook."
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ staging:
2
+ domain: "user@www.example.com"
3
+ deploy_to: "/var/www/suparsite.com/"
4
+ repository: "git://github.com/clarkkent/suparsite.git"
5
+ config_repository: "git@github.com:clarkkent/suparconfig.git"
6
+ deploy_config_to: "/var/cache/git/suparconfig"
7
+ branch: "production"
8
+ rake_env:
9
+ RAILS_ENV: "production"
10
+ local:
11
+ repository: "git://github.com/clarkkent/suparsite.git"
12
+ config_repository: "git@github.com:clarkkent/suparconfig.git"
13
+ deploy_to: "/Users/clark/git/suparsite"
14
+ deploy_config_to: "/Users/clark/git/suparconfig"
15
+ rake_env:
16
+ RAILS_ENV: "production"
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'lib', 'whiskey_disk'))
@@ -0,0 +1,5 @@
1
+ def readme_contents
2
+ IO.read(File.expand_path(File.join(File.dirname(__FILE__), 'README.markdown')))
3
+ end
4
+
5
+ puts readme_contents
@@ -0,0 +1,327 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'whiskey_disk', 'config'))
2
+
3
+ class WhiskeyDisk
4
+ attr_writer :configuration, :config
5
+ attr_reader :results
6
+
7
+ def initialize(options = {})
8
+ @staleness_checks = true if options[:staleness_checks]
9
+ end
10
+
11
+ def buffer
12
+ @buffer ||= []
13
+ end
14
+
15
+ def config
16
+ @config ||= WhiskeyDisk::Config.new
17
+ end
18
+
19
+ def configuration
20
+ @configuration ||= config.fetch
21
+ end
22
+
23
+ def debugging?
24
+ config.debug?
25
+ end
26
+
27
+ def setting(key)
28
+ configuration[key.to_s]
29
+ end
30
+
31
+ def check_staleness?
32
+ config.check_staleness?
33
+ end
34
+
35
+ def staleness_checks_enabled?
36
+ !!@staleness_checks
37
+ end
38
+
39
+ def enqueue(command)
40
+ buffer << command
41
+ end
42
+
43
+ def remote?(domain)
44
+ return false unless domain
45
+ return false if domain == 'local'
46
+ limit = config.domain_limit
47
+ return false if limit and domain_limit_match?(domain, limit)
48
+
49
+ true
50
+ end
51
+
52
+ def has_config_repo?
53
+ ! (setting(:config_repository).nil? or setting(:config_repository) == '')
54
+ end
55
+
56
+ def project_name_specified?
57
+ setting(:project) != 'unnamed_project'
58
+ end
59
+
60
+ def branch
61
+ (setting(:branch) and setting(:branch) != '') ? setting(:branch) : 'master'
62
+ end
63
+
64
+ def config_branch
65
+ (setting(:config_branch) and setting(:config_branch) != '') ? setting(:config_branch) : 'master'
66
+ end
67
+
68
+ def env_vars
69
+ return '' unless setting(:rake_env)
70
+ setting(:rake_env).keys.inject('') do |buffer,k|
71
+ buffer += "#{k}='#{setting(:rake_env)[k]}' "
72
+ buffer
73
+ end
74
+ end
75
+
76
+ def parent_path(path)
77
+ File.split(path).first
78
+ end
79
+
80
+ def tail_path(path)
81
+ File.split(path).last
82
+ end
83
+
84
+ def needs(*keys)
85
+ keys.each do |key|
86
+ raise "No value for '#{key}' declared in configuration files [#{config.configuration_file}]" unless setting(key)
87
+ end
88
+ end
89
+
90
+ def apply_staleness_check(commands)
91
+ needs(:deploy_to, :repository)
92
+
93
+ check = "cd #{setting(:deploy_to)}; " +
94
+ "ml=\`git log -1 --pretty=format:%H\`; " +
95
+ "mr=\`git ls-remote #{setting(:repository)} refs/heads/#{branch}\`; "
96
+
97
+ if setting(:deploy_config_to)
98
+ check += "cd #{setting(:deploy_config_to)}; " +
99
+ "cl=\`git log -1 --pretty=format:%H\`; " +
100
+ "cr=\`git ls-remote #{setting(:config_repository)} refs/heads/#{config_branch}\`; "
101
+ end
102
+
103
+ check += "if [[ $ml != ${mr%%\t*} ]] " +
104
+ (setting(:deploy_config_to) ? "|| [[ $cl != ${cr%%\t*} ]]" : '') +
105
+ "; then #{commands}; else echo \"No changes to deploy.\"; fi"
106
+ end
107
+
108
+ def join_commands
109
+ buffer.collect {|c| "{ #{c} ; }"}.join(' && ')
110
+ end
111
+
112
+ def bundle
113
+ return '' if buffer.empty?
114
+ (staleness_checks_enabled? and check_staleness?) ? apply_staleness_check(join_commands) : join_commands
115
+ end
116
+
117
+ def domain_limit_match?(domain, limit)
118
+ domain.sub(%r{^.*@}, '') == limit
119
+ end
120
+
121
+ def domain_of_interest?(domain)
122
+ return true unless limit = config.domain_limit
123
+ domain_limit_match?(domain, limit)
124
+ end
125
+
126
+ def encode_roles(roles)
127
+ return '' unless roles and !roles.empty?
128
+ "export WD_ROLES='#{roles.join(':')}'; "
129
+ end
130
+
131
+ def build_command(domain, cmd)
132
+ "#{'set -x; ' if debugging?}" + encode_roles(domain['roles']) + cmd
133
+ end
134
+
135
+ def rake_command
136
+ (setting(:rake_command) and setting(:rake_command) != '') ? setting(:rake_command) : 'rake'
137
+ end
138
+
139
+ def run(domain, cmd)
140
+ ssh(domain, cmd)
141
+ end
142
+
143
+ def ssh(domain, cmd)
144
+ args = []
145
+ args << domain['name']
146
+ args << '-v' if debugging?
147
+ args += domain['ssh_options'] if domain['ssh_options']
148
+ args << build_command(domain, cmd)
149
+
150
+ puts "Running: ssh #{args.join(' ')}" if debugging?
151
+ system('ssh', *args)
152
+ end
153
+
154
+ def shell(domain, cmd)
155
+ puts "Running command locally: [#{cmd}]" if debugging?
156
+ system('bash', '-c', build_command(domain, cmd))
157
+ end
158
+
159
+ def flush
160
+ needs(:domain)
161
+ setting(:domain).each do |domain|
162
+ next unless domain_of_interest?(domain['name'])
163
+ puts "Deploying #{domain['name']}..."
164
+ status = remote?(domain['name']) ? run(domain, bundle) : shell(domain, bundle)
165
+ record_result(domain['name'], status)
166
+ end
167
+ end
168
+
169
+ def record_result(domain, status)
170
+ @results ||= []
171
+ @results << { 'domain' => domain, 'status' => status }
172
+ end
173
+
174
+ def summarize_results(results)
175
+ successes = failures = 0
176
+ results.each do |result|
177
+ puts "#{result['domain']} => #{result['status'] ? 'succeeded' : 'failed'}."
178
+ if result['status']
179
+ successes += 1
180
+ else
181
+ failures += 1
182
+ end
183
+ end
184
+ [successes + failures, successes, failures]
185
+ end
186
+
187
+ def summarize
188
+ puts "\nResults:"
189
+ if results and not results.empty?
190
+ total, successes, failures = summarize_results(results)
191
+ puts "Total: #{total} deployment#{total == 1 ? '' : 's'}, " +
192
+ "#{successes} success#{successes == 1 ? '' : 'es'}, " +
193
+ "#{failures} failure#{failures == 1 ? '' : 's'}."
194
+ else
195
+ puts "No deployments to report."
196
+ end
197
+ end
198
+
199
+ def success?
200
+ return true if !results or results.empty?
201
+ results.all? {|result| result['status'] }
202
+ end
203
+
204
+ def if_file_present(path, cmd)
205
+ "if [ -e #{path} ]; then #{cmd}; fi"
206
+ end
207
+
208
+ def if_task_defined(task, cmd)
209
+ %Q(rakep=`#{env_vars} #{rake_command} -P` && if [[ `echo "${rakep}" | grep #{task}` != "" ]]; then #{cmd}; fi )
210
+ end
211
+
212
+ def safe_branch_checkout(path, my_branch)
213
+ %Q(cd #{path} && git checkout -b #{my_branch} origin/#{my_branch} || git checkout #{my_branch} origin/#{my_branch} || git checkout #{my_branch})
214
+ end
215
+
216
+ def clone_repository(repo, path, my_branch)
217
+ enqueue "cd #{parent_path(path)}"
218
+ enqueue("if [ -e #{path} ]; then echo 'Repository already cloned to [#{path}]. Skipping.'; " +
219
+ "else git clone #{repo} #{tail_path(path)} && #{safe_branch_checkout(path, my_branch)}; fi")
220
+ end
221
+
222
+ def refresh_checkout(path, repo_branch)
223
+ enqueue "cd #{path}"
224
+ enqueue "git fetch origin +refs/heads/#{repo_branch}:refs/remotes/origin/#{repo_branch} #{'&>/dev/null' unless debugging?}"
225
+ enqueue "git checkout --force #{repo_branch} #{'&>/dev/null' unless debugging?}"
226
+ enqueue "git reset --hard origin/#{repo_branch} #{'&>/dev/null' unless debugging?}"
227
+ end
228
+
229
+ def run_rake_task(path, task_name)
230
+ enqueue "echo Running rake #{task_name}..."
231
+ enqueue "cd #{path}"
232
+ enqueue(if_file_present("#{setting(:deploy_to)}/Rakefile",
233
+ if_task_defined(task_name, "#{env_vars} #{rake_command} #{'--trace' if debugging?} #{task_name} to=#{setting(:environment)}")))
234
+ end
235
+
236
+ def build_path(path)
237
+ return path if path =~ %r{^/}
238
+ File.join(setting(:deploy_to), path)
239
+ end
240
+
241
+ def run_script(script)
242
+ return unless script
243
+ enqueue(%Q<cd #{setting(:deploy_to)}; echo "Running post script..."; #{env_vars} bash #{'-x' if debugging?} #{build_path(script)}>)
244
+ end
245
+
246
+ def ensure_main_parent_path_is_present
247
+ needs(:deploy_to)
248
+ enqueue "mkdir -p #{parent_path(setting(:deploy_to))}"
249
+ end
250
+
251
+ def ensure_config_parent_path_is_present
252
+ needs(:deploy_config_to)
253
+ enqueue "mkdir -p #{parent_path(setting(:deploy_config_to))}"
254
+ end
255
+
256
+ def checkout_main_repository
257
+ needs(:deploy_to, :repository)
258
+ clone_repository(setting(:repository), setting(:deploy_to), branch)
259
+ end
260
+
261
+ def checkout_configuration_repository
262
+ needs(:deploy_config_to, :config_repository)
263
+ clone_repository(setting(:config_repository), setting(:deploy_config_to), config_branch)
264
+ end
265
+
266
+ def snapshot_git_revision
267
+ needs(:deploy_to)
268
+ enqueue "cd #{setting(:deploy_to)}"
269
+ enqueue %Q{ml=\`git log -1 --pretty=format:%H\`}
270
+ end
271
+
272
+ def initialize_git_changes
273
+ needs(:deploy_to)
274
+ enqueue "rm -f #{setting(:deploy_to)}/.whiskey_disk_git_changes"
275
+ snapshot_git_revision
276
+ end
277
+
278
+ def initialize_rsync_changes
279
+ needs(:deploy_to)
280
+ enqueue "rm -f #{setting(:deploy_to)}/.whiskey_disk_rsync_changes"
281
+ end
282
+
283
+ def initialize_all_changes
284
+ needs(:deploy_to)
285
+ initialize_git_changes
286
+ initialize_rsync_changes
287
+ end
288
+
289
+ def capture_git_changes
290
+ needs(:deploy_to)
291
+ enqueue "git diff --name-only ${ml}..HEAD > #{setting(:deploy_to)}/.whiskey_disk_git_changes"
292
+ end
293
+
294
+ def update_main_repository_checkout
295
+ needs(:deploy_to)
296
+ initialize_git_changes
297
+ refresh_checkout(setting(:deploy_to), branch)
298
+ capture_git_changes
299
+ end
300
+
301
+ def update_configuration_repository_checkout
302
+ needs(:deploy_config_to)
303
+ initialize_rsync_changes
304
+ refresh_checkout(setting(:deploy_config_to), config_branch)
305
+ end
306
+
307
+ def refresh_configuration
308
+ needs(:deploy_to, :deploy_config_to)
309
+ raise "Must specify project name when using a configuration repository." unless project_name_specified?
310
+ enqueue "echo Rsyncing configuration..."
311
+ enqueue("rsync -a#{'v --progress' if debugging?} " + '--log-format="%t [%p] %i %n" ' +
312
+ "#{setting(:deploy_config_to)}/#{setting(:project)}/#{setting(:config_target)}/ #{setting(:deploy_to)}/ " +
313
+ "> #{setting(:deploy_to)}/.whiskey_disk_rsync_changes")
314
+ end
315
+
316
+ def run_post_setup_hooks
317
+ needs(:deploy_to)
318
+ run_script(setting(:post_setup_script))
319
+ run_rake_task(setting(:deploy_to), "deploy:post_setup")
320
+ end
321
+
322
+ def run_post_deploy_hooks
323
+ needs(:deploy_to)
324
+ run_script(setting(:post_deploy_script))
325
+ run_rake_task(setting(:deploy_to), "deploy:post_deploy")
326
+ end
327
+ end