whiskey_disk 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ whiskey_disk.gemspec
2
+ pkg/
data/README ADDED
@@ -0,0 +1,51 @@
1
+ Whiskey Disk -- embarrassingly fast deployments.
2
+
3
+ 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.
4
+
5
+ Here are the features/constraints we're envisioning:
6
+
7
+ - 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.
8
+
9
+ - Setup should mostly just do a very fast remote git checkout in the right place. Deployment should very quickly update that checkout.
10
+
11
+ - 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.
12
+
13
+ - We should be able to use the same "setup == clone" + "deploy == reset" technique to manage the per-project/per-environment config files.
14
+
15
+ - Using rsync on the remote to those overlay config files on the deployed project would be a fast way to get them in place.
16
+
17
+ - Get rid of a bunch of annoying symlinks and symlink-hoops-to-jump-through.
18
+
19
+ - Get rid of a bunch of space (yeah yeah disk is cheap, but copying isn't) on the disk devoted to umpteen "releases".
20
+
21
+ - Obviously reduce deployment time by doing less, ssh-ing less, and taking less time to do whatever.
22
+
23
+ - should be rake based, and should provide a bare minimum of tasks -- like deploy:setup, deploy:now, and maybe a deploy:refresh_config_files.
24
+
25
+ - 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.
26
+
27
+ - Should work with projects that aren't remotely ruby.
28
+
29
+ - Should be loadable as a gem, meaning that it doesn't need to live in your project's space. (see also non-ruby projects)
30
+
31
+ - 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.
32
+
33
+ - 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.
34
+
35
+ - 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.
36
+
37
+ - dropping in a project Rakefile can add post-deploy / post-setup hooks transparently.
38
+
39
+ - actually have meaningful error messages, unlike anything that ever seems to happen with cap or vlad. :-/
40
+
41
+ - build this spec-first (whenever possible) so that there's a useful test suite.
42
+
43
+ - M$ windows hasn't been a priority for me for over a decade, not starting now.
44
+
45
+ ---
46
+
47
+ Resources:
48
+ - http://github.com/blog/470-deployment-script-spring-cleaning
49
+ - http://github.com/mislav/git-deploy
50
+ - http://toroid.org/ams/git-website-howto
51
+
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ desc 'Default: run unit tests.'
5
+ task :default => :test
6
+
7
+ desc 'Test RubyCloud'
8
+ Rake::TestTask.new(:test) do |t|
9
+ t.libs << 'lib'
10
+ t.pattern = 'spec/**/*_spec.rb'
11
+ t.verbose = true
12
+ end
13
+
14
+ begin
15
+ require 'jeweler'
16
+ Jeweler::Tasks.new do |gemspec|
17
+ gemspec.name = "whiskey_disk"
18
+ gemspec.summary = "embarrassingly fast deployments."
19
+ gemspec.description = "Opinionated gem for doing fast git-based server deployments."
20
+ gemspec.email = "rick@rickbradley.com"
21
+ gemspec.homepage = "http://github.com/flogic/whiskey_disk"
22
+ gemspec.authors = ["Rick Bradley"]
23
+ gemspec.add_dependency('vlad', '>= 1.3.2')
24
+ end
25
+ Jeweler::GemcutterTasks.new
26
+ rescue LoadError
27
+ puts "Jeweler not available. Install it with: sudo gem install jeweler -s http://gemcutter.org"
28
+ end
29
+
data/TODO.txt ADDED
@@ -0,0 +1,3 @@
1
+ - update the README
2
+ - push gem to gemcutter
3
+ - install hooks (look at maybe git-deploy for good examples)
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,6 @@
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"
@@ -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,12 @@
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
+ local:
9
+ repository: "git://github.com/clarkkent/suparsite.git"
10
+ config_repository: "git@github.com:clarkkent/suparconfig.git"
11
+ deploy_to: "/Users/clark/git/suparsite"
12
+ deploy_config_to: "/Users/clark/git/suparconfig"
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'lib', 'whiskey_disk'))
data/install.rb ADDED
@@ -0,0 +1,5 @@
1
+ def readme_contents
2
+ IO.read(File.expand_path(File.join(File.dirname(__FILE__), 'README')))
3
+ end
4
+
5
+ puts readme_contents
@@ -0,0 +1,36 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'whiskey_disk'))
2
+ require 'rake'
3
+ require 'vlad'
4
+
5
+ namespace :deploy do
6
+ desc "Perform initial setup for deployment"
7
+ task :setup do
8
+ WhiskeyDisk.ensure_main_parent_path_is_present if WhiskeyDisk.remote?
9
+ WhiskeyDisk.ensure_config_parent_path_is_present
10
+ WhiskeyDisk.checkout_main_repository if WhiskeyDisk.remote?
11
+ WhiskeyDisk.install_hooks if WhiskeyDisk.remote?
12
+ WhiskeyDisk.checkout_configuration_repository
13
+ WhiskeyDisk.refresh_configuration
14
+ WhiskeyDisk.run_post_setup_hooks
15
+ WhiskeyDisk.flush
16
+ end
17
+
18
+ desc "Deploy now."
19
+ task :now do
20
+ WhiskeyDisk.update_main_repository_checkout if WhiskeyDisk.remote?
21
+ WhiskeyDisk.update_configuration_repository_checkout
22
+ WhiskeyDisk.refresh_configuration
23
+ WhiskeyDisk.run_post_deploy_hooks
24
+ WhiskeyDisk.flush
25
+ end
26
+
27
+ task :post_setup do
28
+ env = WhiskeyDisk[:environment]
29
+ Rake::Task["deploy:#{env}:post_setup"].invoke if Rake::Task.task_defined? "deploy:#{env}:post_setup"
30
+ end
31
+
32
+ task :post_deploy do
33
+ env = WhiskeyDisk[:environment]
34
+ Rake::Task["deploy:#{env}:post_deploy"].invoke if Rake::Task.task_defined? "deploy:#{env}:post_deploy"
35
+ end
36
+ end
@@ -0,0 +1,77 @@
1
+ require 'yaml'
2
+
3
+ class WhiskeyDisk
4
+ class Config
5
+ class << self
6
+ def environment_name
7
+ (ENV['to'] && ENV['to'] != '') ? ENV['to'] : false
8
+ end
9
+
10
+ def contains_rakefile?(path)
11
+ File.exists?(File.expand_path(File.join(path, 'Rakefile')))
12
+ end
13
+
14
+ def base_path
15
+ while (!contains_rakefile?(Dir.pwd))
16
+ raise "Could not find Rakefile in the current directory tree!" if Dir.pwd == '/'
17
+ Dir.chdir('..')
18
+ end
19
+ Dir.pwd
20
+ end
21
+
22
+ def main_configuration_file
23
+ File.expand_path(File.join(base_path, 'config', 'deploy.yml'))
24
+ end
25
+
26
+ def main_configuration_data
27
+ raise "Main configuration file [#{main_configuration_file}] not found!" unless File.exists?(main_configuration_file)
28
+ File.read(main_configuration_file)
29
+ end
30
+
31
+ def environment_configuration_file
32
+ raise "Cannot determine current environment -- try rake ... to=staging, for example." unless environment_name
33
+ File.expand_path(File.join(base_path, 'config', "deploy-#{environment_name}.yml"))
34
+ end
35
+
36
+ def environment_configuration_data
37
+ File.exists?(environment_configuration_file) ? File.read(environment_configuration_file) : nil
38
+ rescue Exception => e
39
+ raise %Q{Could not read configuration file [#{environment_configuration_file}] for environment [#{environment_name}]: "#{e}"}
40
+ end
41
+
42
+ def project_name(config)
43
+ return '' unless config['repository'] and config['repository'] != ''
44
+ config['repository'].sub(%r{^.*/}, '').sub(%r{\.git$}, '')
45
+ end
46
+
47
+ def load_main_data
48
+ YAML.load(main_configuration_data)
49
+ rescue Exception => e
50
+ raise %Q{Error reading configuration file [#{main_configuration_file}]: "#{e}"}
51
+ end
52
+
53
+ def load_environment_data
54
+ begin
55
+ env = environment_configuration_data ? YAML.load(environment_configuration_data) : nil
56
+ rescue Exception => e
57
+ raise %Q{Error reading configuration file [#{environment_configuration_file}]: "#{e}"}
58
+ end
59
+ raise "Configuration file [#{environment_configuration_file}] does not define data for environment [#{environment_name}]" if env and !env[environment_name]
60
+ env || {}
61
+ end
62
+
63
+ def fetch
64
+ raise "Cannot determine current environment -- try rake ... to=staging, for example." unless environment_name
65
+ main, env = load_main_data, load_environment_data
66
+ raise "No configuration file defined data for environment [#{environment_name}]" unless main[environment_name] or env[environment_name]
67
+ config = (main[environment_name] || {}).merge(env[environment_name] || {}).merge({'environment' => environment_name})
68
+ { 'project' => project_name(config) }.merge(config)
69
+ end
70
+
71
+ def filenames
72
+ raise "Cannot determine current environment -- try rake ... to=staging, for example." unless environment_name
73
+ [ main_configuration_file, environment_configuration_file ]
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,122 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'tasks', 'deploy'))
2
+ require File.expand_path(File.join(File.dirname(__FILE__), 'whiskey_disk', 'config'))
3
+
4
+ class WhiskeyDisk
5
+ class << self
6
+ def reset
7
+ @configuration = nil
8
+ @buffer = nil
9
+ end
10
+
11
+ def buffer
12
+ @buffer ||= []
13
+ end
14
+
15
+ def configuration
16
+ @configuration ||= WhiskeyDisk::Config.fetch
17
+ end
18
+
19
+ def [](key)
20
+ configuration[key.to_s]
21
+ end
22
+
23
+ def enqueue(command)
24
+ buffer << command
25
+ end
26
+
27
+ def remote?
28
+ ! (self[:domain].nil? or self[:domain] == '')
29
+ end
30
+
31
+ def parent_path(path)
32
+ File.split(path).first
33
+ end
34
+
35
+ def tail_path(path)
36
+ File.split(path).last
37
+ end
38
+
39
+ def register_configuration
40
+ configuration.each_pair {|k,v| set k, v }
41
+ end
42
+
43
+ def needs(*keys)
44
+ keys.each do |key|
45
+ raise "No value for '#{key}' declared in configuration files [#{WhiskeyDisk::Config.filenames.join(", ")}]" unless self[key]
46
+ end
47
+ end
48
+
49
+ def bundle
50
+ return '' if buffer.empty?
51
+ buffer.collect {|c| "(#{c})" }.join(' && ')
52
+ end
53
+
54
+ def flush
55
+ if remote?
56
+ register_configuration
57
+ run(bundle)
58
+ else
59
+ system(bundle)
60
+ end
61
+ end
62
+
63
+ def ensure_main_parent_path_is_present
64
+ needs(:deploy_to)
65
+ enqueue "mkdir -p #{parent_path(self[:deploy_to])}"
66
+ end
67
+
68
+ def ensure_config_parent_path_is_present
69
+ needs(:deploy_config_to)
70
+ enqueue "mkdir -p #{parent_path(self[:deploy_config_to])}"
71
+ end
72
+
73
+ def checkout_main_repository
74
+ needs(:deploy_to, :repository)
75
+ enqueue "cd #{parent_path(self[:deploy_to])}"
76
+ enqueue "git clone #{self[:repository]} #{tail_path(self[:deploy_to])} || true"
77
+ end
78
+
79
+ def install_hooks
80
+ needs(:deploy_to)
81
+ # FIXME - TODO: MORE HERE
82
+ end
83
+
84
+ def checkout_configuration_repository
85
+ needs(:deploy_config_to, :config_repository)
86
+ enqueue "cd #{parent_path(self[:deploy_config_to])}"
87
+ enqueue "git clone #{self[:config_repository]} #{tail_path(self[:deploy_config_to])} || true"
88
+ end
89
+
90
+ def update_main_repository_checkout
91
+ needs(:deploy_to)
92
+ branch = (self[:branch] and self[:branch] != '') ? self[:branch] : 'master'
93
+ enqueue "cd #{self[:deploy_to]}"
94
+ enqueue "git fetch origin +refs/heads/#{branch}:refs/remotes/origin/#{branch}"
95
+ enqueue "git reset --hard origin/#{branch}"
96
+ end
97
+
98
+ def update_configuration_repository_checkout
99
+ needs(:deploy_config_to)
100
+ enqueue "cd #{self[:deploy_config_to]}"
101
+ enqueue "git fetch origin +refs/heads/master:refs/remotes/origin/master"
102
+ enqueue "git reset --hard origin/master"
103
+ end
104
+
105
+ def refresh_configuration
106
+ needs(:deploy_to, :deploy_config_to)
107
+ enqueue "rsync -av --progress #{self[:deploy_config_to]}/#{self[:project]}/#{self[:environment]}/ #{self[:deploy_to]}/"
108
+ end
109
+
110
+ def run_post_setup_hooks
111
+ needs(:deploy_to)
112
+ enqueue "cd #{self[:deploy_to]}"
113
+ enqueue "rake deploy:post_setup"
114
+ end
115
+
116
+ def run_post_deploy_hooks
117
+ needs(:deploy_to)
118
+ enqueue "cd #{self[:deploy_to]}"
119
+ enqueue "rake deploy:post_deploy"
120
+ end
121
+ end
122
+ end
data/spec/.bacon ADDED
File without changes
data/spec/init_spec.rb ADDED
@@ -0,0 +1,9 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper.rb'))
2
+ require 'rake'
3
+
4
+ describe 'when the init.rb plugin loader has been included' do
5
+ it 'should load the main library' do
6
+ require(File.expand_path(File.join(File.dirname(__FILE__), '..', 'init')))
7
+ $".should.include(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'whiskey_disk.rb')))
8
+ end
9
+ end
@@ -0,0 +1,43 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
2
+
3
+ def do_install
4
+ eval File.read(File.join(File.dirname(__FILE__), *%w[.. install.rb ]))
5
+ end
6
+
7
+ describe 'the plugin install.rb script' do
8
+ before do
9
+ self.stub!(:puts).and_return(true)
10
+ end
11
+
12
+ it 'displays the content of the plugin README file' do
13
+ self.stub!(:readme_contents).and_return('README CONTENTS')
14
+ self.should.receive(:puts).with('README CONTENTS')
15
+ do_install
16
+ end
17
+
18
+ describe 'readme_contents' do
19
+ it 'should work without arguments' do
20
+ do_install
21
+ lambda { readme_contents }.should.not.raise(ArgumentError)
22
+ end
23
+
24
+ it 'should accept no arguments' do
25
+ do_install
26
+ lambda { readme_contents(:foo) }.should.raise(ArgumentError)
27
+ end
28
+
29
+ it 'should read the plugin README file' do
30
+ do_install
31
+ File.stub!(:join).and_return('/path/to/README')
32
+ IO.should.receive(:read).with('/path/to/README')
33
+ readme_contents
34
+ end
35
+
36
+ it 'should return the contents of the plugin README file' do
37
+ do_install
38
+ File.stub!(:join).and_return('/path/to/README')
39
+ IO.stub!(:read).with('/path/to/README').and_return('README CONTENTS')
40
+ readme_contents.should == 'README CONTENTS'
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require 'bacon'
3
+ require 'facon'
4
+
5
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))