overlay 1.1.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.rdoc +47 -7
- data/app/controllers/overlay/github_controller.rb +4 -7
- data/lib/overlay/configuration.rb +103 -9
- data/lib/overlay/engine.rb +1 -1
- data/lib/overlay/github.rb +183 -63
- data/lib/overlay/subscriber.rb +1 -0
- data/lib/overlay/version.rb +1 -1
- data/spec/configuration_spec.rb +145 -0
- data/spec/controllers/overlay/github_controller_spec.rb +10 -4
- data/spec/dummy/log/test.log +1527 -252
- data/spec/github_spec.rb +223 -65
- data/spec/spec_helper.rb +3 -0
- metadata +33 -7
- data/app/assets/stylesheets/overlay/application.css +0 -13
- data/app/helpers/overlay/application_helper.rb +0 -4
- data/app/views/layouts/overlay/application.html.erb +0 -14
- data/spec/dummy/log/development.log +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e7cf4853cb51c3c998a8b0d89691dd7fe769b71
|
4
|
+
data.tar.gz: a3a51bcfb29b2d56ebd16b25547c609476506afd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5dae53b5bd7073acba446d4a605341b473139bef5e763582946bf9173251474e29ce523cd65bb9e6e6cc074c6a58b0e966c8bc093ee44d4892b8717172ca43e6
|
7
|
+
data.tar.gz: 4e7f06ee20f890721b59bd548b1e1ffee3d9b75da166206518314ac52563157d94750878e1b1f8e0f043f7202129f711da8a86591c6a1dcb3e68a63cf5a6e0de
|
data/README.rdoc
CHANGED
@@ -1,15 +1,55 @@
|
|
1
|
-
|
1
|
+
Overlay
|
2
|
+
====================
|
2
3
|
|
3
|
-
|
4
|
+
Rails engine that allows for overlaying external templates onto an existing Rails application. Overlayed directories are prepended to the view path to allow overwriting of deployed templates.
|
4
5
|
|
5
|
-
|
6
|
+
Features
|
7
|
+
====================
|
6
8
|
|
7
|
-
|
9
|
+
GithubRepo Features
|
10
|
+
---------------------
|
8
11
|
|
9
|
-
|
12
|
+
* Overlay separate directories in a single repo to specific places in your Rails application.
|
13
|
+
* Update files in realtime utilizing self registering post commit webhooks on github.
|
14
|
+
* Run code on file update via the GithubRepo #after_process_hook block.
|
15
|
+
* Utilize an OverlayPublisher application and a redis server to centralize hook management and publish changes to a fleet of servers.
|
10
16
|
|
11
|
-
|
17
|
+
Installation
|
18
|
+
====================
|
12
19
|
|
13
|
-
|
20
|
+
Add the gem to your Gemfile:
|
21
|
+
|
22
|
+
gem 'overlay'
|
23
|
+
|
24
|
+
Configuration
|
25
|
+
====================
|
26
|
+
|
27
|
+
Add an initializer to your Rails `config/initializers` directory. This file should configure your repositories and launch the initial overlay. Here is a sample initializer:
|
28
|
+
|
29
|
+
require 'overlay'
|
30
|
+
|
31
|
+
Overlay.configure do |config|
|
32
|
+
config.relative_root_url = Rails.application.config.relative_url_root
|
33
|
+
|
34
|
+
github_repo = Overlay::GithubRepo.new(
|
35
|
+
'repo_org',
|
36
|
+
'repo_name',
|
37
|
+
'repo_user_user:repo_password',
|
38
|
+
'source_root_directory',
|
39
|
+
'source_destination_path'
|
40
|
+
)
|
41
|
+
config.repositories << repo_config
|
42
|
+
end
|
43
|
+
|
44
|
+
# Overlay files after rails is initialized
|
45
|
+
#
|
46
|
+
Rails.application.config.after_initialize do
|
47
|
+
Overlay::Github.instance.process_overlays
|
48
|
+
end
|
49
|
+
|
50
|
+
Usage
|
51
|
+
====================
|
52
|
+
|
53
|
+
Once Overlay is configured, on startup, a process will be forked to run the initial pull-down of files from the repository. Overlay will update specific files on change in the repo through use of Github webhooks.
|
14
54
|
|
15
55
|
This project rocks and uses MIT-LICENSE.
|
@@ -1,18 +1,15 @@
|
|
1
|
-
require_dependency "overlay/application_controller"
|
2
|
-
|
3
1
|
module Overlay
|
4
|
-
class GithubController < ApplicationController
|
2
|
+
class GithubController < Overlay::ApplicationController
|
5
3
|
def update
|
6
4
|
render nothing: true
|
7
5
|
|
8
6
|
Overlay.configuration.repositories.each do |repo_config|
|
9
7
|
next unless repo_config.class == GithubRepo
|
10
|
-
branch = repo_config
|
8
|
+
branch = repo_config.branch
|
11
9
|
|
12
10
|
if (params[:repository] && params[:ref])
|
13
|
-
if (params[:repository][:name] == repo_config
|
14
|
-
|
15
|
-
Overlay::Github.overlay_repo repo_config
|
11
|
+
if (params[:repository][:name] == repo_config.repo) && (params[:ref] == "refs/heads/#{branch}")
|
12
|
+
Overlay::Github.instance.process_hook(params, repo_config)
|
16
13
|
end
|
17
14
|
end
|
18
15
|
end
|
@@ -1,16 +1,15 @@
|
|
1
|
+
|
1
2
|
module Overlay
|
2
3
|
VALID_OPTIONS_KEYS = [
|
3
|
-
:site,
|
4
|
-
:endpoint,
|
5
|
-
:repo,
|
6
|
-
:user,
|
7
|
-
:auth,
|
8
4
|
:repositories,
|
9
5
|
:host_name,
|
10
6
|
:host_port,
|
11
7
|
:relative_root_url
|
12
8
|
].freeze
|
13
9
|
|
10
|
+
# Exceptions
|
11
|
+
class RequiredParameterError < StandardError; end
|
12
|
+
|
14
13
|
class << self
|
15
14
|
attr_accessor :configuration
|
16
15
|
end
|
@@ -28,11 +27,106 @@ module Overlay
|
|
28
27
|
end
|
29
28
|
|
30
29
|
def reset
|
31
|
-
|
32
|
-
@endpoint = 'https://api.github.com'
|
33
|
-
@repositories = Set.new
|
30
|
+
@repositories = Set.new
|
34
31
|
end
|
35
32
|
end
|
36
33
|
|
37
|
-
|
34
|
+
# Configure a github repository. Required parameters:
|
35
|
+
# :org,
|
36
|
+
# :repo,
|
37
|
+
# :auth,
|
38
|
+
# :root_source_path,
|
39
|
+
# :root_dest_path
|
40
|
+
|
41
|
+
# Optional parameters:
|
42
|
+
# :branch,
|
43
|
+
# :use_publisher
|
44
|
+
# :registration_address
|
45
|
+
# :redis_server
|
46
|
+
# :redis_port
|
47
|
+
# :endpoint,
|
48
|
+
# :site,
|
49
|
+
class GithubRepo
|
50
|
+
attr_accessor :root_source_path, :root_dest_path, :branch
|
51
|
+
attr_accessor :use_publisher, :redis_server, :redis_port, :registration_server
|
52
|
+
attr_reader :repo, :org, :auth, :endpoint, :site
|
53
|
+
|
54
|
+
# Internal repo api hook
|
55
|
+
attr_accessor :github_api
|
56
|
+
|
57
|
+
REQUIRED_PARAMS = [:org, :repo, :auth, :root_source_path]
|
58
|
+
REQUIRED_PUBLISHER_PARAMS = [:redis_server, :redis_port, :registration_server]
|
59
|
+
|
60
|
+
def initialize(org, repo, auth, root_source_path, root_dest_path)
|
61
|
+
@org = org
|
62
|
+
@repo = repo
|
63
|
+
@auth = auth
|
64
|
+
@root_source_path = root_source_path
|
65
|
+
@root_dest_path = root_dest_path
|
66
|
+
@branch = 'master'
|
67
|
+
@use_publisher = false
|
68
|
+
|
69
|
+
# Quick sanity check
|
70
|
+
validate
|
71
|
+
|
72
|
+
# Create a hook to the Github API
|
73
|
+
initialize_api
|
74
|
+
end
|
75
|
+
|
76
|
+
def endpoint=(endpoint_addr)
|
77
|
+
@endpoint = endpoint_addr
|
78
|
+
|
79
|
+
# re-initialize api
|
80
|
+
initialize_api
|
81
|
+
end
|
82
|
+
|
83
|
+
def site=(site_addr)
|
84
|
+
@site = site_addr
|
85
|
+
|
86
|
+
# re-initialize api
|
87
|
+
initialize_api
|
88
|
+
end
|
89
|
+
|
90
|
+
# Make sure that this configuration has all required parameters
|
91
|
+
def validate
|
92
|
+
REQUIRED_PARAMS.each do |param|
|
93
|
+
raise RequiredParameterError, "Overlay GithubRepo missing required paramater: #{param}" if (send(param).nil? || send(param).empty?)
|
94
|
+
end
|
95
|
+
|
96
|
+
# If we are using a publisher, check required publisher params
|
97
|
+
if @use_publisher
|
98
|
+
REQUIRED_PUBLISHER_PARAMS.each do |param|
|
99
|
+
raise RequiredParameterError, "Overlay GithubRepo missing required paramater: #{param}" if (send(param).nil?)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Add code to be called after processing a hook
|
105
|
+
def after_process_hook(&hook)
|
106
|
+
raise ArgumentError, "No block given" unless block_given?
|
107
|
+
@post_hook = hook
|
108
|
+
end
|
109
|
+
|
110
|
+
# Run post_hook code
|
111
|
+
def post_hook
|
112
|
+
@post_hook.call unless @post_hook.nil?
|
113
|
+
end
|
114
|
+
|
115
|
+
# Retrieve API hook to repo
|
116
|
+
def initialize_api
|
117
|
+
::Github.reset!
|
118
|
+
::Github.configure do |github_config|
|
119
|
+
github_config.endpoint = @endpoint if @endpoint
|
120
|
+
github_config.site = @site if @site
|
121
|
+
github_config.basic_auth = @auth
|
122
|
+
github_config.repo = @repo
|
123
|
+
github_config.org = @org
|
124
|
+
github_config.adapter = :net_http
|
125
|
+
github_config.ssl = {:verify => false}
|
126
|
+
end
|
127
|
+
|
128
|
+
@github_api = ::Github::Repos.new
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
38
132
|
end
|
data/lib/overlay/engine.rb
CHANGED
data/lib/overlay/github.rb
CHANGED
@@ -1,40 +1,104 @@
|
|
1
1
|
require 'github_api'
|
2
2
|
require 'fileutils'
|
3
3
|
require 'socket'
|
4
|
-
|
4
|
+
require 'singleton'
|
5
|
+
require 'redis'
|
6
|
+
|
7
|
+
# The github class is responsible for managing overlaying
|
8
|
+
# directories in a Github repo on the current application.
|
9
|
+
# Call to action is based on either a webhook call to the github controller
|
10
|
+
# or a publish event to a redis key.
|
11
|
+
#
|
5
12
|
module Overlay
|
6
13
|
class Github
|
14
|
+
include Singleton
|
7
15
|
include Overlay::Engine.routes.url_helpers
|
16
|
+
|
17
|
+
attr_accessor :subscribed_configs
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@subscribed_configs = []
|
21
|
+
end
|
22
|
+
|
8
23
|
# Cycle through all configured repositories and overlay
|
9
|
-
#
|
10
|
-
|
24
|
+
# This function should be called only at initialization time as it causes a
|
25
|
+
# full overlay to be run
|
26
|
+
def process_overlays
|
11
27
|
# This can be called in an application initializer which will
|
12
28
|
# load anytime the environment is loaded. Make sure we are prepared to run
|
13
29
|
# this.
|
14
30
|
#
|
15
31
|
return unless (config.host_port || ENV['SERVER_HOST_PORT'] || defined? Rails::Server)
|
16
32
|
|
17
|
-
# Configure github api
|
18
|
-
configure
|
19
|
-
|
20
33
|
Overlay.configuration.repositories.each do |repo_config|
|
21
34
|
next unless repo_config.class == GithubRepo
|
22
35
|
|
23
|
-
#
|
24
|
-
|
25
|
-
|
36
|
+
# Register this server's endpoint as a webhook or subscribe
|
37
|
+
# to a redis pub/sub
|
38
|
+
if repo_config.use_publisher
|
39
|
+
publisher_subscribe(repo_config)
|
40
|
+
else
|
41
|
+
register_web_hook(repo_config)
|
42
|
+
end
|
26
43
|
|
27
|
-
|
44
|
+
# Now that all the set-up is done, for a process
|
45
|
+
# to overlay the repo.
|
46
|
+
fork_it(:overlay_repo, repo_config)
|
47
|
+
end
|
48
|
+
end
|
28
49
|
|
29
|
-
|
50
|
+
# Take a hook hash and process each changelist.
|
51
|
+
# For every file updated, clone it back to us.
|
52
|
+
def process_hook hook, repo_config
|
53
|
+
# Grab the commit array
|
54
|
+
commits = hook['commits']
|
55
|
+
|
56
|
+
# We don't care if there aren't commits
|
57
|
+
return if commits.nil?
|
58
|
+
|
59
|
+
commits.each do |commit|
|
60
|
+
# There will be three entries in each commit with file paths: added, removed, and modified.
|
61
|
+
added_files = commit['added']
|
62
|
+
added_files.each do |file|
|
63
|
+
# Do we care?
|
64
|
+
if my_file?(file, repo_config)
|
65
|
+
Rails.logger.info "Overlay found added file in hook: #{file}"
|
66
|
+
|
67
|
+
# Make sure that the directory is in place
|
68
|
+
FileUtils.mkdir_p(destination_path(File.dirname(file), repo_config))
|
69
|
+
clone_file(file, repo_config)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
modified_files = commit['added']
|
74
|
+
modified_files.each do |file|
|
75
|
+
# Do we care?
|
76
|
+
if my_file?(file, repo_config)
|
77
|
+
Rails.logger.info "Overlay found modified file in hook: #{file}"
|
78
|
+
clone_file(file, repo_config)
|
79
|
+
end
|
80
|
+
end
|
30
81
|
|
31
|
-
|
82
|
+
removed_files = commit['added']
|
83
|
+
removed_files.each do |file|
|
84
|
+
# Do we care?
|
85
|
+
if my_file?(file, repo_config)
|
86
|
+
Rails.logger.info "Overlay found deleted file in hook: #{file}"
|
87
|
+
File.delete(destination_path(file, repo_config))
|
88
|
+
end
|
89
|
+
end
|
32
90
|
end
|
91
|
+
|
92
|
+
# Call post hook code
|
93
|
+
repo_config.post_hook
|
33
94
|
end
|
34
95
|
|
96
|
+
private
|
97
|
+
|
35
98
|
# Register our listener on the repo
|
36
99
|
#
|
37
|
-
def
|
100
|
+
def register_web_hook(repo_config)
|
101
|
+
Rails.logger.info "Overlay register webhook for repo: org => #{repo_config.org}, repo => #{repo_config.repo}"
|
38
102
|
# Make sure our routes are loaded
|
39
103
|
Rails.application.reload_routes!
|
40
104
|
|
@@ -44,46 +108,78 @@ module Overlay
|
|
44
108
|
path = Overlay::Engine.routes.url_for({:controller=>"overlay/github", :action=>"update", :only_path => true})
|
45
109
|
uri = ActionDispatch::Http::URL::url_for({:host => host, :port => port, :path => "#{config.relative_root_url}#{path}"})
|
46
110
|
|
111
|
+
github_api = repo_config.github_api
|
112
|
+
|
47
113
|
# Retrieve current web hooks
|
48
|
-
current_hooks =
|
114
|
+
current_hooks = github_api.hooks.list(repo_config.org, repo_config.repo).response.body
|
49
115
|
if current_hooks.find {|hook| hook.config.url == uri}.nil?
|
50
116
|
# register hook
|
51
|
-
|
117
|
+
github_api.hooks.create(repo_config.org, repo_config.repo, name: 'web', active: true, config: {:url => uri, :content_type => 'json'})
|
52
118
|
end
|
53
119
|
end
|
54
120
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
121
|
+
# Retrieve Subscribe to a OverlayPublisher redis key
|
122
|
+
# Fork a process that subscribes to the redis key and processes updates.
|
123
|
+
def publisher_subscribe repo_config
|
124
|
+
return unless @subscribed_configs.find_index(repo_config).nil?
|
125
|
+
|
126
|
+
# Validate our settings
|
127
|
+
repo_config.validate
|
128
|
+
|
129
|
+
# Register this repo with the manager
|
130
|
+
uri = URI.parse(repo_config.registration_server)
|
131
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
132
|
+
request = Net::HTTP::Post.new("/register")
|
133
|
+
request.add_field('Content-Type', 'application/json')
|
134
|
+
request.body = {
|
135
|
+
'organization' => repo_config.org,
|
136
|
+
'repo' => repo_config.repo,
|
137
|
+
'auth' => repo_config.auth,
|
138
|
+
'endpoint' => repo_config.endpoint,
|
139
|
+
'site' => repo_config.site
|
140
|
+
}.to_json
|
141
|
+
|
142
|
+
response = http.request(request)
|
143
|
+
|
144
|
+
# Retrieve publish key
|
145
|
+
publish_key = JSON.parse(response.read_body)['publish_key']
|
146
|
+
|
147
|
+
Rails.logger.info "Overlay subscribing to redis channel: #{publish_key}"
|
148
|
+
|
149
|
+
# Subscribe to redis channel
|
150
|
+
fork_it(:subscribe_to_channel, publish_key, repo_config)
|
151
|
+
|
152
|
+
@subscribed_configs << repo_config
|
153
|
+
end
|
154
|
+
|
155
|
+
# Overlay all the files specifiec by the repo_config. This
|
156
|
+
# process can be long_running so we fork. We should only be running
|
157
|
+
# this method in initialization of the application.
|
158
|
+
def overlay_repo repo_config
|
159
|
+
Rails.logger.info "Overlay started processing repo with config #{repo_config.inspect}"
|
160
|
+
|
161
|
+
# Get our root entries
|
162
|
+
root = repo_config.root_source_path || '/'
|
163
|
+
|
164
|
+
# If we have a root defined, jump right into it
|
165
|
+
if root != '/'
|
166
|
+
overlay_directory(root, repo_config)
|
167
|
+
else
|
168
|
+
root_entries = repo_config.github_api.contents.get(repo_config.org, repo_config.repo, root, ref: repo_config.branch).response.body
|
169
|
+
|
170
|
+
# We aren't pulling anything out of root. Cycle through directories and overlay
|
171
|
+
root_entries.each do |entry|
|
172
|
+
if entry.type == 'dir'
|
173
|
+
overlay_directory(entry.path, repo_config)
|
75
174
|
end
|
76
175
|
end
|
77
|
-
Rails.logger.info "Finished processing repo with config #{repo_config.inspect}"
|
78
176
|
end
|
177
|
+
Rails.logger.info "Overlay finished processing repo with config #{repo_config.inspect}"
|
79
178
|
end
|
80
179
|
|
81
|
-
def
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
FileUtils.mkdir_p "#{root_path}/#{dynamic_path}"
|
86
|
-
directory_entries = github_repo.contents.get(repo_config[:user], repo_config[:repo], path, ref: repo_config[:branch]).response.body
|
180
|
+
def overlay_directory path, repo_config
|
181
|
+
FileUtils.mkdir_p destination_path(path, repo_config)
|
182
|
+
directory_entries = repo_config.github_api.contents.get(repo_config.org, repo_config.repo, path, ref: repo_config.branch).response.body
|
87
183
|
|
88
184
|
directory_entries.each do |entry|
|
89
185
|
if entry.type == 'dir'
|
@@ -94,36 +190,60 @@ module Overlay
|
|
94
190
|
end
|
95
191
|
end
|
96
192
|
|
97
|
-
def
|
98
|
-
|
99
|
-
|
193
|
+
def clone_file path, repo_config
|
194
|
+
file = repo_config.github_api.contents.get(repo_config.org, repo_config.repo, path, ref: repo_config.branch).response.body.content
|
195
|
+
File.open(destination_path(path, repo_config), "wb") { |f| f.write(Base64.decode64(file)) }
|
196
|
+
Rails.logger.info "Overlay cloned file: #{path}"
|
197
|
+
end
|
198
|
+
|
199
|
+
# Fork a new process and subscribe to a redis channel
|
200
|
+
def subscribe_to_channel key, repo_config
|
201
|
+
redis = Redis.new(:timeout => 30, :host => repo_config.redis_server, :port => repo_config.redis_port)
|
202
|
+
|
203
|
+
# This key is going to receive a publish event
|
204
|
+
# for any changes to the target repo. We need to verify
|
205
|
+
# that the payload references our branch and our watch direstory
|
206
|
+
redis.subscribe(key) do |on|
|
207
|
+
on.message do |channel, msg|
|
208
|
+
Rails.logger.info "Overlay received publish event for channel #{publish_key} with payload: #{msg}"
|
209
|
+
hook = JSON.parse(msg)
|
210
|
+
|
211
|
+
# Make sure this is the branch we are watching
|
212
|
+
if (hook['ref'] == "refs/heads/#{repo_config.branch}")
|
213
|
+
Rails.logger.info "Overlay enqueueing Github hook processing job for repo: #{repo_config.repo} and branch: #{repo_config.branch}"
|
214
|
+
process_hook(hook, repo_config)
|
215
|
+
Rails.logger.info "Overlay done processing job for repo: #{repo_config.repo} and branch: #{repo_config.branch}"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
100
220
|
|
101
|
-
|
102
|
-
|
221
|
+
def my_file? file_path, repo_config
|
222
|
+
return true if repo_config.root_dest_path.empty?
|
223
|
+
file_path.starts_with?(repo_config.root_source_path)
|
103
224
|
end
|
104
225
|
|
105
|
-
|
226
|
+
def root_path repo_config
|
227
|
+
repo_config.root_dest_path.empty? ? "#{Rails.application.root}" : "#{Rails.application.root}/#{repo_config.root_dest_path}"
|
228
|
+
end
|
106
229
|
|
107
|
-
def
|
108
|
-
|
230
|
+
def destination_path file_path, repo_config
|
231
|
+
raise "The file #{file_path} isn't handled by this repo with root path: #{repo_config.root_source_path}" unless my_file?(file_path, repo_config)
|
232
|
+
dynamic_path = file_path.partition(repo_config.root_source_path).last
|
233
|
+
return "#{root_path(repo_config)}#{dynamic_path}"
|
109
234
|
end
|
110
235
|
|
111
|
-
def
|
112
|
-
|
236
|
+
def config
|
237
|
+
@overlay_config ||= Overlay.configuration
|
113
238
|
end
|
114
239
|
|
115
|
-
#
|
116
|
-
def
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
::Github.configure do |github_config|
|
121
|
-
github_config.endpoint = config.endpoint if config.endpoint
|
122
|
-
github_config.site = config.site if config.site
|
123
|
-
github_config.basic_auth = config.auth
|
124
|
-
github_config.adapter = :net_http
|
125
|
-
github_config.ssl = {:verify => false}
|
240
|
+
# Process the passed in function symbol and args in a fork
|
241
|
+
def fork_it method, *args
|
242
|
+
pid = Process.fork do
|
243
|
+
send(method, *args)
|
244
|
+
Process.exit
|
126
245
|
end
|
246
|
+
Process.detach(pid)
|
127
247
|
end
|
128
248
|
end
|
129
249
|
end
|