divergence 0.0.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/README.md +49 -3
- data/bin/divergence +12 -0
- data/divergence.gemspec +0 -1
- data/generators/files/config.rb +9 -2
- data/lib/divergence.rb +7 -15
- data/lib/divergence/application.rb +27 -0
- data/lib/divergence/cache_manager.rb +55 -0
- data/lib/divergence/config.rb +36 -18
- data/lib/divergence/git_manager.rb +54 -82
- data/lib/divergence/helpers.rb +18 -10
- data/lib/divergence/request_parser.rb +1 -2
- data/lib/divergence/respond.rb +22 -13
- data/lib/divergence/version.rb +1 -1
- data/lib/divergence/webhook.rb +13 -9
- data/test/config.rb +5 -3
- data/test/config_test.rb +4 -0
- data/test/git_test.rb +8 -21
- data/test/test_helper.rb +10 -7
- data/test/webhook_test.rb +2 -3
- metadata +4 -21
- data/LICENSE.txt +0 -22
- data/test/app_root/test.txt +0 -1
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -24,14 +24,48 @@ All configuration happens in `config/config.rb`. You must set the git repository
|
|
24
24
|
|
25
25
|
You will probably want divergence to take over port 80 on your testing server, so you may have to update the forwarding host/port. Note, this is the address where your actual web application can be reached.
|
26
26
|
|
27
|
+
A sample config could look like this:
|
28
|
+
|
29
|
+
``` ruby
|
30
|
+
Divergence::Application.configure do |config|
|
31
|
+
config.git_path = "/path/to/git_root"
|
32
|
+
config.app_path = "/path/to/app_root"
|
33
|
+
config.cache_path = "/path/to/cache_root"
|
34
|
+
|
35
|
+
config.forward_host = 'localhost'
|
36
|
+
config.forward_port = 80
|
37
|
+
|
38
|
+
config.callbacks :after_swap do
|
39
|
+
restart_passenger
|
40
|
+
end
|
41
|
+
|
42
|
+
config.callbacks :after_cache, :after_webhook do
|
43
|
+
bundle_install :path => "vendor/bundle"
|
44
|
+
end
|
45
|
+
|
46
|
+
config.callbacks :on_branch_discover do |subdomain|
|
47
|
+
case subdomain
|
48
|
+
when "release-1"
|
49
|
+
"test_branch"
|
50
|
+
when "release-2"
|
51
|
+
"other_branch"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
```
|
56
|
+
|
27
57
|
### Callbacks
|
28
58
|
|
29
59
|
Divergence lets you hook into various callbacks throughout the entire process. These are defined in `config/callbacks.rb`. Most callbacks automatically change the current working directory for you in order to make modifications as simple as possible.
|
30
60
|
|
31
61
|
The available callbacks are:
|
32
62
|
|
33
|
-
*
|
63
|
+
* before_cache
|
34
64
|
* Active dir: git repository
|
65
|
+
* after_cache
|
66
|
+
* Active dir: cached folder path
|
67
|
+
* before_swap
|
68
|
+
* Active dir: cached folder path
|
35
69
|
* after_swap
|
36
70
|
* Active dir: application
|
37
71
|
* before_pull
|
@@ -47,11 +81,22 @@ The available callbacks are:
|
|
47
81
|
* Active dir: git repository
|
48
82
|
* Executes if the subdomain has a dash in the name. The subdomain name is passed to the callback in the options hash.
|
49
83
|
* If the callback returns nil, Divergence will try to auto-detect the branch name, otherwise it will use whatever you return.
|
84
|
+
* before_webook
|
85
|
+
* Active dir: git repository
|
86
|
+
* Runs before a branch is updated via webhooks.
|
87
|
+
* after_webhook
|
88
|
+
* Active dir: cached folder path
|
89
|
+
* Runs after a webhook update completes
|
50
90
|
|
51
91
|
There are also some built-in helper methods that are available inside callbacks. They are:
|
52
92
|
|
53
93
|
* bundle_install
|
94
|
+
* Recommended - after_cache, after_webhook
|
95
|
+
* Options:
|
96
|
+
* :deployment => boolean
|
97
|
+
* :path => string
|
54
98
|
* restart_passenger
|
99
|
+
* Recommended - after_swap
|
55
100
|
|
56
101
|
### Github Service Hook
|
57
102
|
|
@@ -91,8 +136,9 @@ Divergence currently does not support HTTPS on its own; however, you can still u
|
|
91
136
|
|
92
137
|
## TODO
|
93
138
|
|
94
|
-
* Handle
|
95
|
-
*
|
139
|
+
* Handle simultaneous users better
|
140
|
+
* Built-in HTTPS support
|
141
|
+
* Helpers for more frameworks
|
96
142
|
|
97
143
|
## Contributing
|
98
144
|
|
data/bin/divergence
CHANGED
@@ -16,6 +16,7 @@ module CLI
|
|
16
16
|
def create_directories
|
17
17
|
empty_directory "config"
|
18
18
|
empty_directory "log"
|
19
|
+
empty_directory "tmp"
|
19
20
|
end
|
20
21
|
|
21
22
|
def copy_templates
|
@@ -36,8 +37,19 @@ module CLI
|
|
36
37
|
cmd = 'rackup'
|
37
38
|
cmd << " -p #{options[:port]}" if options[:port]
|
38
39
|
cmd << " -D" unless options[:dev]
|
40
|
+
cmd << " -P tmp/rack.pid"
|
39
41
|
|
40
42
|
exec cmd
|
43
|
+
|
44
|
+
unless options[:dev]
|
45
|
+
puts "Divergence is now running"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "stop", "Stop divergence"
|
50
|
+
def stop
|
51
|
+
`kill -9 $(cat tmp/rack.pid) && rm tmp/rack.pid`
|
52
|
+
puts "Divergence halted"
|
41
53
|
end
|
42
54
|
end
|
43
55
|
end
|
data/divergence.gemspec
CHANGED
data/generators/files/config.rb
CHANGED
@@ -1,8 +1,15 @@
|
|
1
1
|
require File.expand_path('../callbacks', __FILE__)
|
2
2
|
|
3
3
|
Divergence::Application.configure do |config|
|
4
|
-
config.git_path = nil
|
5
|
-
config.app_path = nil
|
4
|
+
config.git_path = nil # Change this to the git repository path
|
5
|
+
config.app_path = nil # and this to your application's path.
|
6
|
+
config.cache_path = nil # This should be an empty directory
|
7
|
+
|
8
|
+
# The number of branches to cache for quick switching. If you're
|
9
|
+
# switching around between many branches frequently, you might
|
10
|
+
# want to raise this. Keep in mind that each cached branch will
|
11
|
+
# have it's own Passenger instance, so don't get too carried away.
|
12
|
+
# config.cache_num = 5
|
6
13
|
|
7
14
|
# Where should we proxy this request to? Normally you can leave
|
8
15
|
# the host as 'localhost', but if you are using virtual hosts in
|
data/lib/divergence.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
require "rack/proxy"
|
2
|
-
require "git"
|
3
2
|
require "json"
|
4
3
|
require "logger"
|
4
|
+
require "fileutils"
|
5
5
|
|
6
6
|
require "rack_ssl_hack"
|
7
7
|
require "divergence/version"
|
8
8
|
require "divergence/config"
|
9
|
+
require "divergence/application"
|
9
10
|
require "divergence/git_manager"
|
11
|
+
require "divergence/cache_manager"
|
10
12
|
require "divergence/helpers"
|
11
13
|
require "divergence/request_parser"
|
12
14
|
require "divergence/respond"
|
@@ -30,25 +32,15 @@ module Divergence
|
|
30
32
|
end
|
31
33
|
|
32
34
|
def initialize
|
33
|
-
|
35
|
+
config.ok?
|
34
36
|
|
35
|
-
@
|
37
|
+
@git = GitManager.new(config.git_path)
|
38
|
+
@cache = CacheManager.new(config.cache_path, config.cache_num)
|
39
|
+
@active_branch = ""
|
36
40
|
end
|
37
41
|
|
38
42
|
def config
|
39
43
|
@@config
|
40
44
|
end
|
41
|
-
|
42
|
-
private
|
43
|
-
|
44
|
-
def file_checks
|
45
|
-
unless File.exists?(config.app_path)
|
46
|
-
raise "Configured path not found: #{config.app_path}"
|
47
|
-
end
|
48
|
-
|
49
|
-
unless File.exists?(config.git_path)
|
50
|
-
raise "Configured git path not found: #{config.git_path}"
|
51
|
-
end
|
52
|
-
end
|
53
45
|
end
|
54
46
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Divergence
|
2
|
+
# Responsible for managing the cache folders and swapping
|
3
|
+
# the codebases around.
|
4
|
+
class Application < Rack::Proxy
|
5
|
+
# Prepares the filesystem for loading up a branch
|
6
|
+
def prepare(branch, opts = {})
|
7
|
+
return nil if branch == @active_branch
|
8
|
+
|
9
|
+
unless @cache.is_cached?(branch)
|
10
|
+
@cache.add branch, @git.switch(branch)
|
11
|
+
end
|
12
|
+
|
13
|
+
@cache.path(branch)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Links the application directory to the given path,
|
17
|
+
# which is always a cache directory in our case.
|
18
|
+
def link!(path)
|
19
|
+
Application.log.info "Link: #{path} -> #{config.app_path}"
|
20
|
+
|
21
|
+
config.callback :before_swap, path
|
22
|
+
FileUtils.rm config.app_path if File.exists?(config.app_path)
|
23
|
+
FileUtils.ln_s path, config.app_path, :force => true
|
24
|
+
config.callback :after_swap, config.app_path
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Divergence
|
2
|
+
class CacheManager
|
3
|
+
def initialize(cache_path, cache_num)
|
4
|
+
@cache_path = cache_path
|
5
|
+
@cache_num = cache_num
|
6
|
+
|
7
|
+
Dir.chdir @cache_path do
|
8
|
+
@cached_branches = Dir['*/'].map {|dir| dir.gsub('/', '')}
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def is_cached?(branch)
|
13
|
+
@cached_branches.include?(branch)
|
14
|
+
end
|
15
|
+
|
16
|
+
def add(branch, src_path)
|
17
|
+
Application.log.info "Caching: #{branch} from #{src_path}"
|
18
|
+
|
19
|
+
prune_cache!
|
20
|
+
|
21
|
+
Application.config.callback :before_cache, src_path, :branch => branch
|
22
|
+
|
23
|
+
FileUtils.mkdir_p path(branch)
|
24
|
+
sync branch, src_path
|
25
|
+
@cached_branches.push branch
|
26
|
+
|
27
|
+
Application.config.callback :after_cache, path(branch), :branch => branch
|
28
|
+
end
|
29
|
+
|
30
|
+
def sync(branch, src_path)
|
31
|
+
`rsync -a --delete #{src_path}/* #{path(branch)}`
|
32
|
+
end
|
33
|
+
|
34
|
+
def path(branch)
|
35
|
+
"#{@cache_path}/#{branch}"
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# Delete the oldest cached branches to make room for new
|
41
|
+
def prune_cache!
|
42
|
+
Dir.chdir @cache_path do
|
43
|
+
branches = Dir.glob('*/')
|
44
|
+
return if branches.nil? or branches.length <= @cache_num
|
45
|
+
|
46
|
+
branches
|
47
|
+
.sort_by {|f| File.mtime(f)}[@cache_num..-1]
|
48
|
+
.each do|dir|
|
49
|
+
FileUtils.rm_rf(dir)
|
50
|
+
@cached_branches.delete(dir.gsub('/', ''))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/divergence/config.rb
CHANGED
@@ -2,12 +2,17 @@ module Divergence
|
|
2
2
|
class Configuration
|
3
3
|
include Enumerable
|
4
4
|
|
5
|
-
attr_accessor :app_path, :git_path
|
5
|
+
attr_accessor :app_path, :git_path, :cache_path
|
6
|
+
attr_accessor :cache_num
|
6
7
|
attr_accessor :forward_host, :forward_port
|
7
8
|
|
8
9
|
def initialize
|
9
10
|
@git_path = nil
|
10
11
|
@app_path = nil
|
12
|
+
@cache_path = nil
|
13
|
+
|
14
|
+
@cache_num = 5
|
15
|
+
|
11
16
|
@forward_host = 'localhost'
|
12
17
|
@forward_port = 80
|
13
18
|
|
@@ -15,33 +20,46 @@ module Divergence
|
|
15
20
|
@helpers = Divergence::Helpers.new(self)
|
16
21
|
end
|
17
22
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
def ok?
|
24
|
+
[:git_path, :app_path, :cache_path].each do |path|
|
25
|
+
if instance_variable_get("@#{path}").nil?
|
26
|
+
raise "Configure #{path} before running"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
unless File.exists?(@git_path)
|
31
|
+
raise "Configured git path not found: #{@git_path}"
|
32
|
+
end
|
24
33
|
|
25
|
-
|
26
|
-
|
34
|
+
unless File.exists?(@cache_path)
|
35
|
+
FileUtils.mkdir_p @cache_path
|
36
|
+
end
|
27
37
|
end
|
28
38
|
|
29
39
|
# Lets a user define a callback for a specific event
|
30
|
-
def callbacks(
|
31
|
-
|
32
|
-
@callback_store
|
33
|
-
|
40
|
+
def callbacks(*names, &block)
|
41
|
+
names.each do |name|
|
42
|
+
unless @callback_store.has_key?(name)
|
43
|
+
@callback_store[name] = []
|
44
|
+
end
|
34
45
|
|
35
|
-
|
46
|
+
@callback_store[name].push block
|
47
|
+
end
|
36
48
|
end
|
37
49
|
|
38
|
-
def callback(name, args = {})
|
50
|
+
def callback(name, run_path=nil, args = {})
|
39
51
|
return unless @callback_store.has_key?(name)
|
40
52
|
|
41
|
-
|
53
|
+
if run_path.nil? or !File.exists?(run_path)
|
54
|
+
run_path = Dir.pwd
|
55
|
+
end
|
56
|
+
|
57
|
+
Application.log.debug "Execute callback: #{name.to_s} in #{run_path}"
|
42
58
|
|
43
|
-
|
44
|
-
@
|
59
|
+
Dir.chdir run_path do
|
60
|
+
@callback_store[name].each do |cb|
|
61
|
+
@helpers.execute cb, args
|
62
|
+
end
|
45
63
|
end
|
46
64
|
end
|
47
65
|
|
@@ -1,81 +1,47 @@
|
|
1
1
|
module Divergence
|
2
|
+
# Manages the configured Git repository
|
2
3
|
class GitManager
|
3
4
|
attr_reader :current_branch
|
4
5
|
|
5
|
-
def initialize(
|
6
|
-
@
|
7
|
-
@app_path = config.app_path
|
8
|
-
@git_path = config.git_path
|
9
|
-
|
6
|
+
def initialize(git_path)
|
7
|
+
@git_path = git_path
|
10
8
|
@log = Logger.new('./log/git.log')
|
11
|
-
@
|
12
|
-
|
13
|
-
@current_branch = @git.branch
|
14
|
-
@new_branch = false
|
9
|
+
@current_branch = current_branch
|
15
10
|
end
|
16
11
|
|
17
|
-
def
|
18
|
-
return if is_current?(branch) and !force
|
12
|
+
def switch(branch, force=false)
|
13
|
+
return @git_path if is_current?(branch) and !force
|
14
|
+
|
19
15
|
pull branch
|
20
|
-
|
21
|
-
|
22
|
-
# Performs the swap between the git directory and the working
|
23
|
-
# app directory. We want to copy the files without copying
|
24
|
-
# the .git directory, but this is a temporary dumb solution.
|
25
|
-
#
|
26
|
-
# Future idea: try the capistrano route and simply symlink
|
27
|
-
# to the git directory instead of copying files.
|
28
|
-
#
|
29
|
-
# TODO: make this more ruby-like.
|
30
|
-
def swap!
|
31
|
-
return unless @new_branch
|
32
|
-
|
33
|
-
Dir.chdir @config.git_path do
|
34
|
-
@config.callback :before_swap
|
35
|
-
end
|
36
|
-
|
37
|
-
Application.log.info "Swap: #{@git_path} -> #{@app_path}"
|
38
|
-
`rsync -a --delete --exclude=.git #{@git_path}/* #{@app_path}`
|
39
|
-
@new_branch = false
|
40
|
-
|
41
|
-
Dir.chdir @config.app_path do
|
42
|
-
@config.callback :after_swap
|
43
|
-
end
|
16
|
+
return @git_path
|
44
17
|
end
|
45
18
|
|
46
19
|
# Since underscores are technically not allowed in URLs,
|
47
20
|
# but they are allowed in Git branch names, we have to do
|
48
21
|
# some magic to possibly convert dashes to underscores
|
49
22
|
# so we can load the right branch.
|
50
|
-
#
|
51
|
-
# Another possible thing to explore is converting all
|
52
|
-
# dashes in the URL to a regex search against all branches
|
53
|
-
# in this repository to avoid the current brute-force
|
54
|
-
# solution we're using.
|
55
23
|
def discover(branch)
|
56
24
|
return branch if is_branch?(branch)
|
57
25
|
|
58
|
-
|
59
|
-
resp = @config.callback :on_branch_discover, branch
|
26
|
+
resp = Application.config.callback :on_branch_discover, @git_path, branch
|
60
27
|
|
61
|
-
|
62
|
-
|
63
|
-
end
|
64
|
-
|
65
|
-
local_search = "^" + branch.gsub(/-/, ".") + "$"
|
66
|
-
remote_search = "^remotes/origin/(" + branch.gsub(/-/, ".") + ")$"
|
67
|
-
local_r = Regexp.new(local_search, Regexp::IGNORECASE)
|
68
|
-
remote_r = Regexp.new(remote_search, Regexp::IGNORECASE)
|
69
|
-
|
70
|
-
`git branch -a`.split("\n").each do |b|
|
71
|
-
b = b.gsub('*', '').strip
|
72
|
-
|
73
|
-
return b if local_r.match(b)
|
74
|
-
if remote_r.match(b)
|
75
|
-
return remote_r.match(b)[1]
|
76
|
-
end
|
77
|
-
end
|
28
|
+
unless resp.nil?
|
29
|
+
return resp
|
78
30
|
end
|
31
|
+
|
32
|
+
local_search = "^" + branch.gsub(/-/, ".") + "$"
|
33
|
+
remote_search = "^remotes/origin/(" + branch.gsub(/-/, ".") + ")$"
|
34
|
+
local_r = Regexp.new(local_search, Regexp::IGNORECASE)
|
35
|
+
remote_r = Regexp.new(remote_search, Regexp::IGNORECASE)
|
36
|
+
|
37
|
+
git('branch -a').split("\n").each do |b|
|
38
|
+
b = b.gsub('*', '').strip
|
39
|
+
|
40
|
+
return b if local_r.match(b)
|
41
|
+
if remote_r.match(b)
|
42
|
+
return remote_r.match(b)[1]
|
43
|
+
end
|
44
|
+
end
|
79
45
|
|
80
46
|
raise "Unable to automatically detect branch. Given = #{branch}"
|
81
47
|
end
|
@@ -86,6 +52,14 @@ module Divergence
|
|
86
52
|
|
87
53
|
private
|
88
54
|
|
55
|
+
def current_branch
|
56
|
+
git('branch -a').split("\n").each do |b|
|
57
|
+
if b[0, 2] == '* '
|
58
|
+
return b.gsub('* ', '').strip
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
89
63
|
def is_branch?(branch)
|
90
64
|
Dir.chdir @git_path do
|
91
65
|
# This is fast, but only works on locally checked out branches
|
@@ -100,26 +74,11 @@ module Divergence
|
|
100
74
|
|
101
75
|
def pull(branch)
|
102
76
|
if checkout(branch)
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
#@git.pull 'origin', branch
|
108
|
-
@git.chdir do
|
109
|
-
# For some reason, I'm having issues with the pull
|
110
|
-
# that's built into the library. Doing this manually
|
111
|
-
# for now.
|
112
|
-
@log.info "git pull origin #{branch} 2>&1"
|
113
|
-
`git pull origin #{branch} 2>&1`
|
114
|
-
end
|
115
|
-
|
116
|
-
Dir.chdir @config.git_path do
|
117
|
-
@config.callback :after_pull
|
118
|
-
end
|
77
|
+
Application.config.callback :before_pull, @git_path
|
78
|
+
git "pull origin #{branch}"
|
79
|
+
Application.config.callback :after_pull, @git_path
|
119
80
|
else
|
120
|
-
|
121
|
-
@config.callback :on_pull_error
|
122
|
-
end
|
81
|
+
Application.config.callback :on_pull_error, @git_path
|
123
82
|
|
124
83
|
return false
|
125
84
|
end
|
@@ -130,21 +89,34 @@ module Divergence
|
|
130
89
|
reset
|
131
90
|
|
132
91
|
begin
|
133
|
-
|
92
|
+
git "checkout -f #{branch}"
|
134
93
|
@current_branch = branch
|
135
|
-
@new_branch = true
|
136
94
|
rescue
|
137
95
|
return false
|
138
96
|
end
|
139
97
|
end
|
140
98
|
|
141
99
|
def reset
|
142
|
-
|
100
|
+
git 'reset --hard'
|
143
101
|
end
|
144
102
|
|
145
103
|
# Fetch all remote branch information
|
146
104
|
def fetch
|
147
|
-
|
105
|
+
git :fetch
|
106
|
+
end
|
107
|
+
|
108
|
+
def git(cmd)
|
109
|
+
Dir.chdir @git_path do
|
110
|
+
@log.info "git #{cmd.to_s}"
|
111
|
+
out = `git #{cmd.to_s} 2>&1`
|
112
|
+
|
113
|
+
if $?.exitstatus != 0
|
114
|
+
Application.log.error "git #{cmd.to_s} failed"
|
115
|
+
Application.log.error out
|
116
|
+
end
|
117
|
+
|
118
|
+
return out
|
119
|
+
end
|
148
120
|
end
|
149
121
|
end
|
150
122
|
end
|
data/lib/divergence/helpers.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'fileutils'
|
2
|
-
|
3
1
|
module Divergence
|
4
2
|
class Helpers
|
5
3
|
def initialize(config)
|
@@ -12,22 +10,32 @@ module Divergence
|
|
12
10
|
|
13
11
|
private
|
14
12
|
|
15
|
-
def bundle_install
|
13
|
+
def bundle_install(opts={})
|
16
14
|
Application.log.debug "bundle install"
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
|
16
|
+
begin
|
17
|
+
cmd = 'bundle install'
|
18
|
+
cmd << ' --deployment' if opts[:deployment]
|
19
|
+
cmd << " --path #{opts[:path]}" if opts[:path]
|
20
|
+
cmd << ' --without development test'
|
21
|
+
result = `#{cmd}`
|
22
|
+
Application.log.debug result
|
23
|
+
rescue
|
24
|
+
Application.log.error "bundle install failed!"
|
25
|
+
Application.log.error e.message
|
20
26
|
end
|
21
27
|
end
|
22
28
|
|
23
29
|
def restart_passenger
|
24
30
|
Application.log.debug "Restarting passenger..."
|
25
31
|
|
26
|
-
|
27
|
-
|
28
|
-
FileUtils.
|
29
|
-
rescue
|
32
|
+
begin
|
33
|
+
unless File.exists? 'tmp'
|
34
|
+
FileUtils.mkdir_p 'tmp'
|
30
35
|
end
|
36
|
+
|
37
|
+
FileUtils.touch 'tmp/restart.txt'
|
38
|
+
rescue
|
31
39
|
end
|
32
40
|
end
|
33
41
|
end
|
data/lib/divergence/respond.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
module Divergence
|
2
2
|
class Application < Rack::Proxy
|
3
|
-
# The main entry point for the application. This is
|
3
|
+
# The main entry point for the application. This is called
|
4
4
|
# by Rack.
|
5
5
|
def call(env)
|
6
|
-
@req = RequestParser.new(env
|
6
|
+
@req = RequestParser.new(env)
|
7
7
|
|
8
8
|
# First, lets find out what subdomain/git branch
|
9
9
|
# we're dealing with (if any).
|
@@ -15,18 +15,27 @@ module Divergence
|
|
15
15
|
# Handle webhooks from Github for updating the current
|
16
16
|
# branch if necessary.
|
17
17
|
if @req.is_webhook?
|
18
|
-
return
|
18
|
+
return handle_webhook
|
19
19
|
end
|
20
20
|
|
21
|
-
#
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
return error!
|
26
|
-
end
|
21
|
+
# Lets get down to business.
|
22
|
+
begin
|
23
|
+
# Get the proper branch name using a touch of magic
|
24
|
+
branch = @git.discover(@req.subdomain)
|
27
25
|
|
28
|
-
|
29
|
-
|
26
|
+
# Prepare the branch and cache if needed
|
27
|
+
path = prepare(branch)
|
28
|
+
|
29
|
+
# If we're requesting a different branch than the
|
30
|
+
# one currently loaded, we'll need to link it to
|
31
|
+
# the application directory.
|
32
|
+
link!(path) unless path.nil?
|
33
|
+
|
34
|
+
@active_branch = branch
|
35
|
+
rescue Exception => e
|
36
|
+
Application.log.error e.message
|
37
|
+
return error!(branch)
|
38
|
+
end
|
30
39
|
|
31
40
|
# We're finished, pass the request through.
|
32
41
|
proxy(env)
|
@@ -56,8 +65,8 @@ module Divergence
|
|
56
65
|
env["HTTP_HOST"] = "#{config.forward_host}:#{config.forward_port}"
|
57
66
|
end
|
58
67
|
|
59
|
-
def error!
|
60
|
-
Application.log.error "Branch #{
|
68
|
+
def error!(branch)
|
69
|
+
Application.log.error "Branch #{branch} does not exist"
|
61
70
|
Application.log.error @req.raw
|
62
71
|
|
63
72
|
public_path = File.expand_path('../../../public', __FILE__)
|
data/lib/divergence/version.rb
CHANGED
data/lib/divergence/webhook.rb
CHANGED
@@ -1,30 +1,34 @@
|
|
1
1
|
module Divergence
|
2
|
-
class
|
3
|
-
def
|
4
|
-
hook = JSON.parse(req['payload'])
|
2
|
+
class Application
|
3
|
+
def handle_webhook
|
4
|
+
hook = JSON.parse(@req['payload'])
|
5
5
|
branch = hook["ref"].split("/").last.strip
|
6
6
|
|
7
7
|
Application.log.info "Webhook: received for #{branch} branch"
|
8
8
|
|
9
9
|
# If the webhook is for the currently active branch,
|
10
10
|
# then we perform a pull and a swap.
|
11
|
-
if
|
11
|
+
if @cache.is_cached?(branch)
|
12
12
|
Application.log.info "Webhook: updating #{branch}"
|
13
13
|
|
14
|
-
git.
|
15
|
-
|
16
|
-
|
14
|
+
git_path = @git.switch branch, :force => true
|
15
|
+
|
16
|
+
config.callback :before_webhook, git_path, :branch => branch
|
17
|
+
@cache.sync branch, git_path
|
18
|
+
config.callback :after_webhook, @cache.path(branch), :branch => branch
|
19
|
+
|
17
20
|
ok
|
18
21
|
else
|
22
|
+
Application.log.info "Webhook: ignoring #{branch}"
|
19
23
|
ignore
|
20
24
|
end
|
21
25
|
end
|
22
26
|
|
23
|
-
def
|
27
|
+
def ok
|
24
28
|
[200, {"Content-Type" => "text/html"}, ["OK"]]
|
25
29
|
end
|
26
30
|
|
27
|
-
def
|
31
|
+
def ignore
|
28
32
|
[200, {"Content-Type" => "text/html"}, ["IGNORE"]]
|
29
33
|
end
|
30
34
|
end
|
data/test/config.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
Divergence::Application.configure do |config|
|
2
|
-
config.git_path = "test/git_root"
|
3
|
-
config.app_path = "test/app_root"
|
2
|
+
config.git_path = "./test/git_root"
|
3
|
+
config.app_path = "./test/app_root"
|
4
|
+
config.cache_path = "./test/cache_root"
|
5
|
+
|
4
6
|
config.forward_host = 'localhost'
|
5
7
|
config.forward_port = 80
|
6
8
|
|
7
|
-
config.callbacks :after_swap do
|
9
|
+
config.callbacks :after_swap, :after_webhook do
|
8
10
|
# Run anything after the swap finishes
|
9
11
|
end
|
10
12
|
end
|
data/test/config_test.rb
CHANGED
@@ -4,15 +4,19 @@ class ConfigureTest < Test::Unit::TestCase
|
|
4
4
|
def test_config
|
5
5
|
git_path = File.expand_path('../git_root', __FILE__)
|
6
6
|
app_path = File.expand_path('../app_root', __FILE__)
|
7
|
+
cache_path = File.expand_path('../cache_root', __FILE__)
|
7
8
|
|
8
9
|
Divergence::Application.configure do |config|
|
9
10
|
config.git_path = git_path
|
10
11
|
config.app_path = app_path
|
12
|
+
config.cache_path = cache_path
|
13
|
+
|
11
14
|
config.forward_host = 'localhost'
|
12
15
|
config.forward_port = 80
|
13
16
|
end
|
14
17
|
|
15
18
|
assert app.config.app_path, app_path
|
16
19
|
assert app.config.git_path, git_path
|
20
|
+
assert app.config.cache_path, cache_path
|
17
21
|
end
|
18
22
|
end
|
data/test/git_test.rb
CHANGED
@@ -2,38 +2,30 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
class GitTest < Test::Unit::TestCase
|
4
4
|
def test_ignore
|
5
|
-
force_branch :master
|
6
5
|
mock_get 'master.example.com'
|
7
6
|
|
8
|
-
|
9
|
-
assert_equal "master", git.current_branch
|
7
|
+
assert_equal "master", active_branch
|
10
8
|
|
11
9
|
# and again...
|
12
10
|
mock_get 'master.example.com'
|
13
|
-
assert_equal 'master',
|
11
|
+
assert_equal 'master', active_branch
|
14
12
|
end
|
15
13
|
|
16
14
|
def test_switch_branch
|
17
|
-
force_branch :master
|
18
15
|
mock_get 'branch1.example.com'
|
19
|
-
|
20
|
-
git = Git.open('test/git_root')
|
21
|
-
assert_equal 'branch1', git.current_branch
|
16
|
+
assert_equal 'branch1', active_branch
|
22
17
|
end
|
23
18
|
|
24
19
|
def test_branch_discover
|
25
|
-
force_branch :master
|
26
20
|
mock_get 'branch-1.example.com'
|
27
|
-
|
28
|
-
git = Git.open('test/git_root')
|
29
|
-
assert_equal 'branch_1', git.current_branch
|
21
|
+
assert_equal 'branch_1', active_branch
|
30
22
|
|
31
23
|
mock_get 'branch-with-complex-name-1.example.com'
|
32
|
-
assert_equal 'branch_with_complex_name-1',
|
24
|
+
assert_equal 'branch_with_complex_name-1', active_branch
|
33
25
|
end
|
34
26
|
|
35
27
|
def test_dirty_switch
|
36
|
-
|
28
|
+
mock_get "master.example.com"
|
37
29
|
|
38
30
|
File.open 'test/git_root/test.txt', 'a' do |f|
|
39
31
|
f.write 'modifying this'
|
@@ -41,19 +33,14 @@ class GitTest < Test::Unit::TestCase
|
|
41
33
|
|
42
34
|
mock_get 'branch1.example.com'
|
43
35
|
|
44
|
-
|
45
|
-
assert_equal 'branch1', git.current_branch
|
36
|
+
assert_equal 'branch1', active_branch
|
46
37
|
end
|
47
38
|
|
48
39
|
def test_swap
|
49
|
-
force_branch :master
|
50
40
|
mock_get "branch1.example.com"
|
51
41
|
|
52
42
|
assert File.exists? 'test/app_root/test.txt'
|
53
|
-
file = File.open 'test/app_root/test.txt'
|
54
|
-
contents = file.read.strip
|
55
|
-
file.close
|
56
43
|
|
57
|
-
assert_equal "branch1",
|
44
|
+
assert_equal "branch1", active_branch
|
58
45
|
end
|
59
46
|
end
|
data/test/test_helper.rb
CHANGED
@@ -3,6 +3,9 @@ require "rack"
|
|
3
3
|
require "rack/test"
|
4
4
|
|
5
5
|
require "./lib/divergence"
|
6
|
+
require "./test/config"
|
7
|
+
|
8
|
+
#require 'debugger'; debugger
|
6
9
|
|
7
10
|
Test::Unit::TestCase.class_eval do
|
8
11
|
include Rack::Test::Methods
|
@@ -17,10 +20,11 @@ class Test::Unit::TestCase
|
|
17
20
|
@d
|
18
21
|
end
|
19
22
|
|
20
|
-
def
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
def active_branch
|
24
|
+
file = File.open 'test/app_root/test.txt'
|
25
|
+
contents = file.read.strip
|
26
|
+
file.close
|
27
|
+
contents
|
24
28
|
end
|
25
29
|
|
26
30
|
# We have to rewrite the host constant in rack-test
|
@@ -34,8 +38,7 @@ class Test::Unit::TestCase
|
|
34
38
|
|
35
39
|
def set_mock_request(addr, opts={})
|
36
40
|
req = Rack::MockRequest.env_for "http://#{addr}", opts
|
37
|
-
|
38
|
-
app.req = Divergence::RequestParser.new(req, git)
|
41
|
+
app.req = Divergence::RequestParser.new(req)
|
39
42
|
end
|
40
43
|
|
41
44
|
def mock_get(addr, params={})
|
@@ -63,7 +66,7 @@ end
|
|
63
66
|
|
64
67
|
module Divergence
|
65
68
|
class Application < Rack::Proxy
|
66
|
-
attr_accessor :req
|
69
|
+
attr_accessor :req, :active_branch
|
67
70
|
|
68
71
|
def perform_request(env)
|
69
72
|
[200, {"Content-Type" => "text/html"}, ["Ohai"]]
|
data/test/webhook_test.rb
CHANGED
@@ -2,7 +2,7 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
class WebhookTest < Test::Unit::TestCase
|
4
4
|
def test_detect
|
5
|
-
|
5
|
+
mock_get "master.example.com"
|
6
6
|
status, _, body = mock_webhook :master
|
7
7
|
|
8
8
|
assert_equal 200, status
|
@@ -10,8 +10,7 @@ class WebhookTest < Test::Unit::TestCase
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def test_ignore
|
13
|
-
|
14
|
-
status, _, body = mock_webhook :branch1
|
13
|
+
status, _, body = mock_webhook 'webhook-ignore'
|
15
14
|
|
16
15
|
assert_equal 200, status
|
17
16
|
assert_equal ["IGNORE"], body
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: divergence
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-10-
|
12
|
+
date: 2012-10-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rack
|
@@ -59,22 +59,6 @@ dependencies:
|
|
59
59
|
- - ! '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
|
-
- !ruby/object:Gem::Dependency
|
63
|
-
name: git
|
64
|
-
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
|
-
requirements:
|
67
|
-
- - ! '>='
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: '0'
|
70
|
-
type: :runtime
|
71
|
-
prerelease: false
|
72
|
-
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
|
-
requirements:
|
75
|
-
- - ! '>='
|
76
|
-
- !ruby/object:Gem::Version
|
77
|
-
version: '0'
|
78
62
|
- !ruby/object:Gem::Dependency
|
79
63
|
name: json
|
80
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -120,7 +104,6 @@ files:
|
|
120
104
|
- .gitignore
|
121
105
|
- Gemfile
|
122
106
|
- LICENSE
|
123
|
-
- LICENSE.txt
|
124
107
|
- README.md
|
125
108
|
- Rakefile
|
126
109
|
- bin/divergence
|
@@ -129,6 +112,8 @@ files:
|
|
129
112
|
- generators/files/config.rb
|
130
113
|
- generators/files/config.ru
|
131
114
|
- lib/divergence.rb
|
115
|
+
- lib/divergence/application.rb
|
116
|
+
- lib/divergence/cache_manager.rb
|
132
117
|
- lib/divergence/config.rb
|
133
118
|
- lib/divergence/git_manager.rb
|
134
119
|
- lib/divergence/helpers.rb
|
@@ -139,7 +124,6 @@ files:
|
|
139
124
|
- lib/rack_ssl_hack.rb
|
140
125
|
- log/.gitkeep
|
141
126
|
- public/404.html
|
142
|
-
- test/app_root/test.txt
|
143
127
|
- test/config.rb
|
144
128
|
- test/config_test.rb
|
145
129
|
- test/git_test.rb
|
@@ -171,7 +155,6 @@ signing_key:
|
|
171
155
|
specification_version: 3
|
172
156
|
summary: Map virtual host subdomains to git branches for testing
|
173
157
|
test_files:
|
174
|
-
- test/app_root/test.txt
|
175
158
|
- test/config.rb
|
176
159
|
- test/config_test.rb
|
177
160
|
- test/git_test.rb
|
data/LICENSE.txt
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
Copyright (c) 2012 Ryan LeFevre
|
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/test/app_root/test.txt
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
master
|