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