chap 0.0.1
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.
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/Guardfile +14 -0
- data/LICENSE.txt +22 -0
- data/README.md +121 -0
- data/Rakefile +7 -0
- data/bin/chap +6 -0
- data/chap.gemspec +32 -0
- data/lib/chap/cli.rb +26 -0
- data/lib/chap/config.rb +113 -0
- data/lib/chap/hook.rb +16 -0
- data/lib/chap/runner.rb +129 -0
- data/lib/chap/special_methods.rb +23 -0
- data/lib/chap/strategy/base.rb +20 -0
- data/lib/chap/strategy/checkout.rb +62 -0
- data/lib/chap/strategy/copy.rb +10 -0
- data/lib/chap/strategy/hardlink.rb +13 -0
- data/lib/chap/strategy/util/copy.rb +55 -0
- data/lib/chap/strategy.rb +6 -0
- data/lib/chap/task.rb +28 -0
- data/lib/chap/version.rb +3 -0
- data/lib/chap.rb +17 -0
- data/lib/mash.rb +225 -0
- data/lib/setup/chap.json +10 -0
- data/lib/setup/chap.yml +2 -0
- data/lib/setup/node.json +5 -0
- data/spec/fixtures/chapdemo/.gitignore +15 -0
- data/spec/fixtures/chapdemo/Gemfile +3 -0
- data/spec/fixtures/chapdemo/chap/deploy +4 -0
- data/spec/fixtures/chapdemo/chap/restart +10 -0
- data/spec/fixtures/chapdemo/config.ru +1 -0
- data/spec/fixtures/chapdemo/public/.gitkeep +0 -0
- data/spec/lib/chap_spec.rb +44 -0
- data/spec/spec_helper.rb +43 -0
- metadata +251 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3@chap
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec' do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/chap/(.+)\.rb$}) { |m| "spec/lib/chap_spec.rb" }
|
7
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
8
|
+
watch('spec/spec_helper.rb') { "spec" }
|
9
|
+
end
|
10
|
+
|
11
|
+
guard 'bundler' do
|
12
|
+
watch('Gemfile')
|
13
|
+
watch(/^.+\.gemspec/)
|
14
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Tung Nguyen
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
# Chap
|
2
|
+
|
3
|
+
chef + capistrano = chap: deploy your app with either chef or capistrano. This was written to solve the issue between having 2 deployment systems that are very similar but not exactly the same. With chap you can deploy to a single server by running one command:
|
4
|
+
|
5
|
+
<pre>
|
6
|
+
$ chap deploy
|
7
|
+
</pre>
|
8
|
+
|
9
|
+
The same command is called whether you're using chef or capistrano for deployment. The chap deploy command does the heavy lifting and manages the deploy instead of capistrano or chef.
|
10
|
+
|
11
|
+
## Requirements
|
12
|
+
|
13
|
+
<pre>
|
14
|
+
$ gem install chap
|
15
|
+
</pre>
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
The chap command is meant to be executed on the server which you want to deploy the code.
|
20
|
+
|
21
|
+
## Setup
|
22
|
+
|
23
|
+
Chap requires 3 configuration files: chap.yml, chap.json and node.json.
|
24
|
+
|
25
|
+
* chap.json: contains capistrano-like configuration settings.
|
26
|
+
* node.json: is intended to be the same file that chef solo uses and contains instance specific information, like node[:instance_role].
|
27
|
+
: chap.yml: The paths of the chap.json and node.json are configured in this file.
|
28
|
+
|
29
|
+
Here are examples of the starter setup files that you can generate via:
|
30
|
+
|
31
|
+
<pre>
|
32
|
+
$ chap setup -o /etc/chef
|
33
|
+
</pre>
|
34
|
+
|
35
|
+
<pre>
|
36
|
+
$ cat /etc/chef/chap.yml
|
37
|
+
chap: /etc/chef/chap.json
|
38
|
+
node: /etc/chef/node.json
|
39
|
+
$ cat /etc/chef/chap.json
|
40
|
+
{
|
41
|
+
"repo": "git@github.com:tongueroo/chapdemo.git",
|
42
|
+
"branch": "master",
|
43
|
+
"application": "chapdemo",
|
44
|
+
"deploy_to": "/data/chapdemo",
|
45
|
+
"strategy": "checkout",
|
46
|
+
"keep": 5,
|
47
|
+
"user": "deploy",
|
48
|
+
"group": "deploy"
|
49
|
+
}
|
50
|
+
$ cat /etc/chef/node.json
|
51
|
+
{
|
52
|
+
"environment": "staging",
|
53
|
+
"application": "chapdemo",
|
54
|
+
"instance_role": "app"
|
55
|
+
}
|
56
|
+
</pre>
|
57
|
+
|
58
|
+
### Deploy sequence
|
59
|
+
|
60
|
+
The deploy sequence is based on the sequence from the capistrano and chef deploy resource provider.
|
61
|
+
|
62
|
+
1. Download code to [deploy_to]/releases/[timestamp]
|
63
|
+
2. Run chap/deploy hook
|
64
|
+
3. Symlink [deploy_to]/releases/[timestamp] to [deploy_to]/current
|
65
|
+
4. Run chap/restart hook
|
66
|
+
5. Clean up old releases
|
67
|
+
|
68
|
+
On the server:
|
69
|
+
|
70
|
+
<pre>
|
71
|
+
$ chap deploy
|
72
|
+
</pre>
|
73
|
+
|
74
|
+
From capistrano, on local or deploy box:
|
75
|
+
|
76
|
+
<pre>
|
77
|
+
$ cap deploy # cap recipe calls "chap deploy"
|
78
|
+
</pre>
|
79
|
+
|
80
|
+
Chef Chap LWRP:
|
81
|
+
|
82
|
+
<pre>
|
83
|
+
# chef LWRP creates chap.yml and chap.json setup files and calls "chap deploy"
|
84
|
+
chap_deploy "chapdemo" do
|
85
|
+
repo "git@github.com:tongueroo/chapdemo.git"
|
86
|
+
revision "master"
|
87
|
+
end
|
88
|
+
</pre>
|
89
|
+
|
90
|
+
Chap loads up information from node.json because it needs the information for hooks, which tend to work differently for different server roles. For example, the chap/restart hook below will run "touch tmp/restart.txt" for an app role and will run "rvmsudo bluepill restart resque" for a resque role. Example:
|
91
|
+
|
92
|
+
<pre>
|
93
|
+
$ cat chap/restart
|
94
|
+
#!/usr/bin/env ruby
|
95
|
+
restart = case node[:instance_role]
|
96
|
+
when 'app'
|
97
|
+
"touch tmp/restart.txt"
|
98
|
+
when 'resque'
|
99
|
+
"rvmsudo bluepill restart resque"
|
100
|
+
end
|
101
|
+
run "cd #{current_path} && #{restart}"
|
102
|
+
</pre>
|
103
|
+
|
104
|
+
## Deploy Hooks
|
105
|
+
|
106
|
+
Define your deploy hooks in the chap folder of the project. There are 2 deploy hooks.
|
107
|
+
|
108
|
+
* chap/deploy - runs after the code has been deployed but not yet symlinked
|
109
|
+
* chap/restart - runs after the code has been symlinked and app needs to be restarted
|
110
|
+
|
111
|
+
Deploy hooks get evaluated within the context of a chap deploy run and have some special variables and methods:
|
112
|
+
|
113
|
+
Special variables:
|
114
|
+
|
115
|
+
* node - contains data from /etc/chef/node.json. Avaiable as mash.
|
116
|
+
* chap - contains data from /etc/chef/chap.json and some special variables added by chap. Avaiable as mash. Special variables: release_path, current_path, shared_path, cached_path. The special variables are also available directly as methods.
|
117
|
+
|
118
|
+
Special methods:
|
119
|
+
|
120
|
+
* run - output the command to be ran and runs command.
|
121
|
+
* log - log messages to [shared_path]/chap/chap.log.
|
data/Rakefile
ADDED
data/bin/chap
ADDED
data/chap.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'chap/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "chap"
|
8
|
+
gem.version = Chap::VERSION
|
9
|
+
gem.authors = ["Tung Nguyen"]
|
10
|
+
gem.email = ["tongueroo@gmail.com"]
|
11
|
+
gem.description = %q{chef + capistrano = chap: deploy your app with either chef or capistrano}
|
12
|
+
gem.summary = %q{chef + capistrano = chap: deploy your app with either chef or capistrano}
|
13
|
+
gem.homepage = "https://github.com/tongueroo/chap"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency "rake"
|
21
|
+
gem.add_dependency "json"
|
22
|
+
gem.add_dependency "thor"
|
23
|
+
gem.add_dependency "colorize"
|
24
|
+
gem.add_dependency "logger"
|
25
|
+
|
26
|
+
gem.add_development_dependency 'rspec'
|
27
|
+
gem.add_development_dependency 'guard'
|
28
|
+
gem.add_development_dependency 'guard-rspec'
|
29
|
+
gem.add_development_dependency 'guard-bundler'
|
30
|
+
gem.add_development_dependency 'rb-fsevent'
|
31
|
+
# gem.add_development_dependency 'fakefs'
|
32
|
+
end
|
data/lib/chap/cli.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module Chap
|
2
|
+
class CLI < Thor
|
3
|
+
desc "setup", "Sets up chap config files"
|
4
|
+
long_desc "Creates chap.json, chap.yml and node.json example files."
|
5
|
+
method_option :force, :aliases => '-f', :type => :boolean, :desc => "Overwrite existing files"
|
6
|
+
method_option :quiet, :aliases => '-q', :type => :boolean, :desc => "Quiet commands"
|
7
|
+
method_option :output, :aliases => '-o', :desc => "Folder which example files will be written to"
|
8
|
+
def setup
|
9
|
+
Chap::Task.setup(options)
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "deploy", "Deploy application"
|
13
|
+
long_desc <<-EOL
|
14
|
+
Example:
|
15
|
+
|
16
|
+
$ chap deploy
|
17
|
+
|
18
|
+
Deploys code using settings from chap.json and node.json. chap.json and node.json should be referenced in chap.yml.
|
19
|
+
EOL
|
20
|
+
method_option :quiet, :aliases => '-q', :type => :boolean, :desc => "Quiet commands"
|
21
|
+
method_option :config, :aliases => '-c', :default => '/etc/chef/chap.yml', :desc => "chap.yml config to use"
|
22
|
+
def deploy
|
23
|
+
Chap::Task.deploy(options)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/chap/config.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
module Chap
|
2
|
+
class Config
|
3
|
+
attr_reader :options, :node, :chap
|
4
|
+
def initialize(options={})
|
5
|
+
@options = options
|
6
|
+
# preload all config files so validatation happens before deploys
|
7
|
+
_ = yaml, chap, node
|
8
|
+
end
|
9
|
+
|
10
|
+
def yaml
|
11
|
+
path = options[:config]
|
12
|
+
if File.exist?(path)
|
13
|
+
@yaml ||= Mash.from_hash(YAML.load(IO.read(path)))
|
14
|
+
else
|
15
|
+
puts "ERROR: chap.yaml config does not exist at: #{path}"
|
16
|
+
exit 1
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def chap
|
21
|
+
return @chap if @chap
|
22
|
+
@chap = load_json(:chap)
|
23
|
+
@chap[:release_path] = release_path
|
24
|
+
@chap[:current_path] = current_path
|
25
|
+
@chap[:cached_path] = cached_path
|
26
|
+
@chap
|
27
|
+
end
|
28
|
+
|
29
|
+
def node
|
30
|
+
@node ||= load_json(:node)
|
31
|
+
end
|
32
|
+
|
33
|
+
# the chap.json and node.json is assumed to be in th same folder as
|
34
|
+
# chap.yml if a relative path is given
|
35
|
+
def load_json(key)
|
36
|
+
path = if yaml[key] =~ %r{^/} # root path given
|
37
|
+
yaml[key]
|
38
|
+
else # relative path
|
39
|
+
dirname = File.dirname(options[:config])
|
40
|
+
"#{dirname}/#{yaml[key]}"
|
41
|
+
end
|
42
|
+
if File.exist?(path)
|
43
|
+
Mash.from_hash(JSON.parse(IO.read(path)))
|
44
|
+
else
|
45
|
+
puts "ERROR: #{key}.json config does not exist at: #{path}"
|
46
|
+
exit 1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def timestamp
|
51
|
+
@timestamp ||= Time.now.strftime("%Y%m%d%H%M%S")
|
52
|
+
end
|
53
|
+
|
54
|
+
def deploy_to
|
55
|
+
chap[:deploy_to]
|
56
|
+
end
|
57
|
+
|
58
|
+
# special attributes added to chap
|
59
|
+
def release_path
|
60
|
+
return @release_path if @release_path
|
61
|
+
@release_path = "#{deploy_to}/releases/#{timestamp}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def current_path
|
65
|
+
return @current_path if @current_path
|
66
|
+
@current_path = "#{deploy_to}/current"
|
67
|
+
end
|
68
|
+
|
69
|
+
def shared_path
|
70
|
+
return @shared_path if @shared_path
|
71
|
+
@shared_path = "#{deploy_to}/shared"
|
72
|
+
end
|
73
|
+
|
74
|
+
def cached_path
|
75
|
+
return @cached_path if @cached_path
|
76
|
+
path = chap[:repo].split(':').last.sub('.git','')
|
77
|
+
@cached_path = "#{shared_path}/cache/#{strategy}/#{path}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def strategy
|
81
|
+
chap[:strategy] || 'checkout'
|
82
|
+
end
|
83
|
+
|
84
|
+
def log(msg)
|
85
|
+
puts msg unless options[:quiet]
|
86
|
+
logger.info(msg)
|
87
|
+
end
|
88
|
+
|
89
|
+
def chap_log_path
|
90
|
+
return @chap_log_path if @chap_log_path
|
91
|
+
dir = "#{shared_path}/chap"
|
92
|
+
FileUtils.mkdir_p(dir) unless File.exist?(dir)
|
93
|
+
@chap_log_path = "#{dir}/chap.log"
|
94
|
+
system("cat /dev/null > #{@chap_log_path}")
|
95
|
+
@chap_log_path
|
96
|
+
end
|
97
|
+
|
98
|
+
def logger
|
99
|
+
return @logger if @logger
|
100
|
+
@logger = Logger.new(chap_log_path)
|
101
|
+
# @logger.level = Logger::WARN
|
102
|
+
@logger
|
103
|
+
end
|
104
|
+
|
105
|
+
def run(cmd)
|
106
|
+
log "Running: #{cmd}"
|
107
|
+
cmd = "#{cmd} 2>&1" unless cmd.include?(" > ")
|
108
|
+
out = `#{cmd}`
|
109
|
+
log out
|
110
|
+
raise "DeployError" if $?.exitstatus > 0
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/lib/chap/hook.rb
ADDED
data/lib/chap/runner.rb
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
module Chap
|
2
|
+
class Runner
|
3
|
+
include SpecialMethods
|
4
|
+
|
5
|
+
attr_reader :options, :config
|
6
|
+
def initialize(options={})
|
7
|
+
@options = options
|
8
|
+
@config = Config.new(options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def deploy
|
12
|
+
setup
|
13
|
+
strategy.deploy
|
14
|
+
symlink_shared
|
15
|
+
hook(:deploy)
|
16
|
+
symlink_current
|
17
|
+
hook(:restart)
|
18
|
+
cleanup
|
19
|
+
end
|
20
|
+
|
21
|
+
def setup
|
22
|
+
user = config.chap[:user] || ENV['USER']
|
23
|
+
group = config.chap[:group]
|
24
|
+
begin
|
25
|
+
FileUtils.mkdir_p(deploy_to)
|
26
|
+
FileUtils.chown_R user, group, deploy_to
|
27
|
+
rescue Exception
|
28
|
+
# retry to create deploy_to folder with sudo
|
29
|
+
user_group = [user,group].compact.join(':')
|
30
|
+
raise unless system("sudo mkdir -p #{deploy_to}")
|
31
|
+
raise unless system("sudo chown -R #{user_group} #{deploy_to}")
|
32
|
+
end
|
33
|
+
dirs = ["#{deploy_to}/releases"]
|
34
|
+
dirs += shared_dirs
|
35
|
+
dirs.each do |dir|
|
36
|
+
FileUtils.mkdir_p(dir)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def strategy
|
41
|
+
strategy = config.strategy
|
42
|
+
klass = Strategy.const_get(camel_case(strategy))
|
43
|
+
@strategy ||= klass.new(:config => @config)
|
44
|
+
end
|
45
|
+
|
46
|
+
def camel_case(string)
|
47
|
+
return string if string !~ /_/ && string =~ /[A-Z]+.*/
|
48
|
+
string.split('_').map{|e| e.capitalize}.join
|
49
|
+
end
|
50
|
+
|
51
|
+
def symlink_shared
|
52
|
+
shared_dirs.each do |path|
|
53
|
+
src = path
|
54
|
+
relative_path = path.sub("#{shared_path}/",'')
|
55
|
+
dest = "#{release_path}/#{relative_path}"
|
56
|
+
# make sure the directory exist for symlink creation
|
57
|
+
dirname = File.dirname(dest)
|
58
|
+
FileUtils.mkdir_p(dirname) unless File.exist?(dirname)
|
59
|
+
if File.symlink?(dest)
|
60
|
+
File.delete(dest)
|
61
|
+
elsif File.directory?(dest)
|
62
|
+
FileUtils.rm_rf(dest)
|
63
|
+
end
|
64
|
+
FileUtils.ln_s(src,dest)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def symlink_current
|
69
|
+
FileUtils.rm(current_path) if File.exist?(current_path)
|
70
|
+
FileUtils.ln_s(release_path, current_path)
|
71
|
+
log "Current symlink updated".colorize(:green)
|
72
|
+
end
|
73
|
+
|
74
|
+
def cleanup
|
75
|
+
log "Cleaning up".colorize(:green)
|
76
|
+
remove_old_releases
|
77
|
+
logrotate
|
78
|
+
end
|
79
|
+
|
80
|
+
def remove_old_releases
|
81
|
+
dirname = File.dirname(release_path)
|
82
|
+
releases = Dir.glob("#{dirname}/*").sort.reverse
|
83
|
+
keep = config.chap[:keep] || 5
|
84
|
+
delete = releases - releases[0..keep-1]
|
85
|
+
delete.each do |old_release|
|
86
|
+
FileUtils.rm_rf(old_release)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def logrotate
|
91
|
+
logrotate_file(config.chap_log_path)
|
92
|
+
end
|
93
|
+
|
94
|
+
def logrotate_timestamp
|
95
|
+
@logrotate_timestamp ||= Time.now.strftime("%Y-%m-%d-%H-%M-%S")
|
96
|
+
end
|
97
|
+
|
98
|
+
def logrotate_file(log_path)
|
99
|
+
dirname = File.dirname(log_path)
|
100
|
+
basename = File.basename(log_path, '.log')
|
101
|
+
archive = "#{dirname}/#{basename}-#{logrotate_timestamp}.log"
|
102
|
+
FileUtils.cp(log_path, archive) if File.exist?(log_path)
|
103
|
+
logs = Dir.glob("#{dirname}/#{basename}-*.log").sort.reverse
|
104
|
+
delete = logs - logs[0..9] # keep last 10 chap logs
|
105
|
+
delete.each do |old_log|
|
106
|
+
FileUtils.rm_f(old_log)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def shared_dirs
|
111
|
+
dirs = config.chap[:shared_dirs] || [
|
112
|
+
"public/system",
|
113
|
+
"log",
|
114
|
+
"tmp/pids"
|
115
|
+
]
|
116
|
+
dirs.map! {|p| "#{shared_path}/#{p}"}
|
117
|
+
end
|
118
|
+
|
119
|
+
def hook(name)
|
120
|
+
log "Running hook: #{name}".colorize(:green)
|
121
|
+
path = "#{release_path}/chap/#{name}"
|
122
|
+
if File.exist?(path)
|
123
|
+
Hook.new(path, @config).evaluate
|
124
|
+
else
|
125
|
+
log "chap/#{name} hook does not exist".colorize(:red)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Chap
|
2
|
+
module SpecialMethods
|
3
|
+
SPECIAL_METHODS = %w/deploy_to release_path current_path shared_path cached_path node chap log run/
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.send(:extend, ClassMethods)
|
7
|
+
base.define_special_methods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
# delegate to the config class
|
12
|
+
def define_special_methods
|
13
|
+
SPECIAL_METHODS.each do |method|
|
14
|
+
class_eval <<-EOL
|
15
|
+
def #{method}(*args)
|
16
|
+
@config.#{method}(*args)
|
17
|
+
end
|
18
|
+
EOL
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Chap
|
2
|
+
module Strategy
|
3
|
+
class Base
|
4
|
+
include SpecialMethods
|
5
|
+
|
6
|
+
attr_reader :options, :config
|
7
|
+
def initialize(options={})
|
8
|
+
@options = options
|
9
|
+
@config = options[:config]
|
10
|
+
log "Deploying via #{self.class} strategy".colorize(:green)
|
11
|
+
end
|
12
|
+
|
13
|
+
# should download code to the release_path
|
14
|
+
def deploy
|
15
|
+
raise "Must implement deploy method"
|
16
|
+
end
|
17
|
+
|
18
|
+
end # of Base
|
19
|
+
end # of Strategy
|
20
|
+
end # of Chap
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Chap
|
2
|
+
module Strategy
|
3
|
+
class Checkout < Base
|
4
|
+
def deploy
|
5
|
+
update
|
6
|
+
copy
|
7
|
+
end
|
8
|
+
|
9
|
+
def update
|
10
|
+
log "Updating repo in #{cached_path}".colorize(:green)
|
11
|
+
cached_root = File.dirname(cached_path)
|
12
|
+
FileUtils.mkdir_p(cached_root) unless File.exist?(cached_root)
|
13
|
+
if File.exist?(cached_path)
|
14
|
+
sync
|
15
|
+
else
|
16
|
+
checkout
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def sync
|
21
|
+
command =<<-BASH
|
22
|
+
cd #{cached_path} && \
|
23
|
+
git fetch -q origin && \
|
24
|
+
git fetch --tags -q origin && \
|
25
|
+
git reset -q --hard #{revision} && \
|
26
|
+
git clean -q -d -x -f
|
27
|
+
BASH
|
28
|
+
run(command)
|
29
|
+
end
|
30
|
+
|
31
|
+
def checkout
|
32
|
+
command =<<BASH
|
33
|
+
git clone -q #{config.chap[:repo]} #{cached_path} && \
|
34
|
+
cd #{cached_path} && \
|
35
|
+
git checkout -q -b deploy #{revision}
|
36
|
+
BASH
|
37
|
+
run(command)
|
38
|
+
end
|
39
|
+
|
40
|
+
def copy
|
41
|
+
command = "cp -RPp #{cached_path} #{release_path} && #{mark}"
|
42
|
+
run command
|
43
|
+
log "Code copied to #{release_path}".colorize(:green)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def mark
|
49
|
+
"(echo #{revision} > #{release_path}/REVISION)"
|
50
|
+
end
|
51
|
+
|
52
|
+
def revision
|
53
|
+
return @revision if @revision
|
54
|
+
result = `git ls-remote #{config.chap[:repo]} #{config.chap[:branch]}`
|
55
|
+
@revision = result.split(/\s/).first
|
56
|
+
log "Fetched revision #{@revision}".colorize(:green)
|
57
|
+
@revision
|
58
|
+
end
|
59
|
+
|
60
|
+
end # of RemoteCache
|
61
|
+
end # of Strategy
|
62
|
+
end # of Chap
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Chap
|
2
|
+
module Strategy
|
3
|
+
class Hardlink < Checkout
|
4
|
+
def copy
|
5
|
+
copy = File.expand_path("../util/copy.rb", __FILE__)
|
6
|
+
command = "#{copy} #{cached_path} #{release_path} && #{mark}"
|
7
|
+
run command
|
8
|
+
log "Code copied to #{release_path}".colorize(:green)
|
9
|
+
end
|
10
|
+
|
11
|
+
end # of HardLink
|
12
|
+
end # of Strategy
|
13
|
+
end # of Chap
|