jfoundry 0.1.0.pre
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/LICENSE +746 -0
- data/Rakefile +10 -0
- data/lib/cc_api_stub/applications.rb +53 -0
- data/lib/cc_api_stub/domains.rb +32 -0
- data/lib/cc_api_stub/frameworks.rb +22 -0
- data/lib/cc_api_stub/helper.rb +139 -0
- data/lib/cc_api_stub/login.rb +21 -0
- data/lib/cc_api_stub/organization_users.rb +21 -0
- data/lib/cc_api_stub/organizations.rb +70 -0
- data/lib/cc_api_stub/routes.rb +26 -0
- data/lib/cc_api_stub/runtimes.rb +22 -0
- data/lib/cc_api_stub/service_bindings.rb +22 -0
- data/lib/cc_api_stub/service_instances.rb +22 -0
- data/lib/cc_api_stub/services.rb +21 -0
- data/lib/cc_api_stub/spaces.rb +49 -0
- data/lib/cc_api_stub/users.rb +85 -0
- data/lib/cc_api_stub.rb +16 -0
- data/lib/jfoundry/auth_token.rb +63 -0
- data/lib/jfoundry/baseclient.rb +177 -0
- data/lib/jfoundry/chatty_hash.rb +46 -0
- data/lib/jfoundry/client.rb +39 -0
- data/lib/jfoundry/concerns/proxy_options.rb +17 -0
- data/lib/jfoundry/errors.rb +163 -0
- data/lib/jfoundry/rest_client.rb +331 -0
- data/lib/jfoundry/signature/version.rb +27 -0
- data/lib/jfoundry/signer.rb +13 -0
- data/lib/jfoundry/test_support.rb +3 -0
- data/lib/jfoundry/timer.rb +13 -0
- data/lib/jfoundry/trace_helpers.rb +64 -0
- data/lib/jfoundry/upload_helpers.rb +222 -0
- data/lib/jfoundry/v2/app.rb +357 -0
- data/lib/jfoundry/v2/app_event.rb +13 -0
- data/lib/jfoundry/v2/base.rb +92 -0
- data/lib/jfoundry/v2/client.rb +78 -0
- data/lib/jfoundry/v2/domain.rb +20 -0
- data/lib/jfoundry/v2/managed_service_instance.rb +13 -0
- data/lib/jfoundry/v2/model.rb +209 -0
- data/lib/jfoundry/v2/model_magic/attribute.rb +49 -0
- data/lib/jfoundry/v2/model_magic/client_extensions.rb +170 -0
- data/lib/jfoundry/v2/model_magic/has_summary.rb +49 -0
- data/lib/jfoundry/v2/model_magic/queryable_by.rb +39 -0
- data/lib/jfoundry/v2/model_magic/to_many.rb +138 -0
- data/lib/jfoundry/v2/model_magic/to_one.rb +81 -0
- data/lib/jfoundry/v2/model_magic.rb +93 -0
- data/lib/jfoundry/v2/organization.rb +22 -0
- data/lib/jfoundry/v2/quota_definition.rb +12 -0
- data/lib/jfoundry/v2/route.rb +25 -0
- data/lib/jfoundry/v2/service.rb +20 -0
- data/lib/jfoundry/v2/service_auth_token.rb +10 -0
- data/lib/jfoundry/v2/service_binding.rb +10 -0
- data/lib/jfoundry/v2/service_broker.rb +11 -0
- data/lib/jfoundry/v2/service_instance.rb +13 -0
- data/lib/jfoundry/v2/service_plan.rb +13 -0
- data/lib/jfoundry/v2/space.rb +18 -0
- data/lib/jfoundry/v2/stack.rb +10 -0
- data/lib/jfoundry/v2/user.rb +104 -0
- data/lib/jfoundry/v2/user_provided_service_instance.rb +7 -0
- data/lib/jfoundry/validator.rb +41 -0
- data/lib/jfoundry/version.rb +4 -0
- data/lib/jfoundry/zip.rb +56 -0
- data/lib/jfoundry.rb +5 -0
- data/lib/tasks/gem_release.rake +42 -0
- data/vendor/errors/README.md +3 -0
- data/vendor/errors/v1.yml +189 -0
- data/vendor/errors/v2.yml +470 -0
- metadata +269 -0
@@ -0,0 +1,331 @@
|
|
1
|
+
require "jfoundry/trace_helpers"
|
2
|
+
require "net/https"
|
3
|
+
require "net/http/post/multipart"
|
4
|
+
require "multi_json"
|
5
|
+
require "fileutils"
|
6
|
+
require "jfoundry/timer"
|
7
|
+
require "jfoundry/signature/version"
|
8
|
+
|
9
|
+
module JFoundry
|
10
|
+
class RestClient
|
11
|
+
class HTTPFactory
|
12
|
+
def self.create(uri, proxy_options = [])
|
13
|
+
http = Net::HTTP.new(uri.host, uri.port, *proxy_options)
|
14
|
+
|
15
|
+
if uri.is_a?(URI::HTTPS)
|
16
|
+
http.use_ssl = true
|
17
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
18
|
+
end
|
19
|
+
|
20
|
+
return http
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
include JFoundry::TraceHelpers
|
25
|
+
include JFoundry::ProxyOptions
|
26
|
+
include JFoundry::Timer
|
27
|
+
include JFoundry::Signature::Version
|
28
|
+
|
29
|
+
LOG_LENGTH = 10
|
30
|
+
|
31
|
+
HTTP_METHODS = {
|
32
|
+
"GET" => Net::HTTP::Get,
|
33
|
+
"PUT" => Net::HTTP::Put,
|
34
|
+
"POST" => Net::HTTP::Post,
|
35
|
+
"DELETE" => Net::HTTP::Delete,
|
36
|
+
"HEAD" => Net::HTTP::Head,
|
37
|
+
}
|
38
|
+
|
39
|
+
DEFAULT_OPTIONS = {
|
40
|
+
:follow_redirects => true
|
41
|
+
}
|
42
|
+
|
43
|
+
attr_reader :target
|
44
|
+
|
45
|
+
attr_accessor :trace, :backtrace, :log,
|
46
|
+
:request_id, :access_key, :secret_key, :version, :http_proxy, :https_proxy
|
47
|
+
|
48
|
+
def initialize(target, access_key, secret_key, version)
|
49
|
+
@target = target
|
50
|
+
#@token = token
|
51
|
+
@access_key = access_key
|
52
|
+
@secret_key = secret_key
|
53
|
+
@version = version
|
54
|
+
@trace = false
|
55
|
+
@backtrace = false
|
56
|
+
@log = false
|
57
|
+
end
|
58
|
+
|
59
|
+
def target=(target)
|
60
|
+
return if target == @target
|
61
|
+
|
62
|
+
@target = target
|
63
|
+
#@token = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def request(method, path, options = {})
|
67
|
+
request_uri(method, construct_url(path), DEFAULT_OPTIONS.merge(options))
|
68
|
+
end
|
69
|
+
|
70
|
+
def generate_headers(uri, payload, options)
|
71
|
+
headers = {}
|
72
|
+
|
73
|
+
if payload.is_a?(String)
|
74
|
+
headers["Content-Length"] = payload.size
|
75
|
+
elsif !payload
|
76
|
+
headers["Content-Length"] = 0
|
77
|
+
end
|
78
|
+
|
79
|
+
headers["X-Request-Id"] = @request_id if @request_id
|
80
|
+
#headers["Authorization"] = @token.auth_header if @token
|
81
|
+
headers['Version'] = @version
|
82
|
+
headers['Date'] = get_time
|
83
|
+
headers['ACCESS-KEY'] = @access_key
|
84
|
+
headers['Path'] = URI.parse(uri.to_s).path
|
85
|
+
|
86
|
+
if accept_type = mimetype(options[:accept])
|
87
|
+
headers["Accept"] = accept_type
|
88
|
+
end
|
89
|
+
|
90
|
+
if content_type = mimetype(options[:content])
|
91
|
+
headers["Content-Type"] = content_type
|
92
|
+
end
|
93
|
+
|
94
|
+
headers.merge!(options[:headers]) if options[:headers]
|
95
|
+
headers
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def request_uri(method, uri, options = {})
|
101
|
+
uri = URI.parse(uri)
|
102
|
+
|
103
|
+
unless uri.is_a?(URI::HTTP)
|
104
|
+
raise InvalidTarget.new(@target)
|
105
|
+
end
|
106
|
+
|
107
|
+
# keep original options in case there's a redirect to follow
|
108
|
+
original_options = options.dup
|
109
|
+
payload = options[:payload]
|
110
|
+
|
111
|
+
if options[:params]
|
112
|
+
encoded_params = encode_params(options[:params])
|
113
|
+
if encoded_params.respond_to?(:empty?) ? !encoded_params.empty? : encoded_params
|
114
|
+
if uri.query
|
115
|
+
uri.query += "&" + encoded_params
|
116
|
+
else
|
117
|
+
uri.query = encoded_params
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
unless payload.is_a?(String)
|
123
|
+
case options[:content]
|
124
|
+
when :json
|
125
|
+
payload = MultiJson.dump(payload)
|
126
|
+
when :form
|
127
|
+
payload = encode_params(payload)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
method_class = get_method_class(method)
|
132
|
+
if payload.is_a?(Hash)
|
133
|
+
multipart = method_class.const_get(:Multipart)
|
134
|
+
request = multipart.new(uri.request_uri, payload)
|
135
|
+
else
|
136
|
+
request = method_class.new(uri.request_uri)
|
137
|
+
request.body = payload if payload
|
138
|
+
end
|
139
|
+
|
140
|
+
headers = generate_headers(uri, payload, options)
|
141
|
+
|
142
|
+
request_hash = {
|
143
|
+
:url => uri.to_s,
|
144
|
+
:method => method,
|
145
|
+
:headers => headers,
|
146
|
+
:body => payload
|
147
|
+
}
|
148
|
+
|
149
|
+
print_request(request_hash) if @trace
|
150
|
+
|
151
|
+
add_headers(request, headers)
|
152
|
+
|
153
|
+
signature = generate_signature(@secret_key, method, request.to_hash())
|
154
|
+
request['signature'] = signature
|
155
|
+
|
156
|
+
http = HTTPFactory.create(uri, proxy_options_for(uri))
|
157
|
+
|
158
|
+
# TODO remove this when staging returns streaming responses
|
159
|
+
http.read_timeout = 300
|
160
|
+
|
161
|
+
before = Time.now
|
162
|
+
http.start do
|
163
|
+
response = http.request(request)
|
164
|
+
time = Time.now - before
|
165
|
+
|
166
|
+
response_hash = {
|
167
|
+
:headers => sane_headers(response),
|
168
|
+
:status => response.code,
|
169
|
+
:body => response.body
|
170
|
+
}
|
171
|
+
|
172
|
+
print_response(response_hash) if @trace
|
173
|
+
print_backtrace(caller) if @trace
|
174
|
+
|
175
|
+
log_request(time, request, response)
|
176
|
+
|
177
|
+
if response.is_a?(Net::HTTPRedirection) && options[:follow_redirects]
|
178
|
+
request_uri("GET", response["location"], original_options)
|
179
|
+
else
|
180
|
+
return request_hash, response_hash
|
181
|
+
end
|
182
|
+
end
|
183
|
+
rescue ::Timeout::Error => e
|
184
|
+
raise Timeout.new(method, uri, e)
|
185
|
+
rescue SocketError, Errno::ECONNREFUSED => e
|
186
|
+
raise TargetRefused, e.message
|
187
|
+
rescue URI::InvalidURIError
|
188
|
+
raise InvalidTarget.new(@target)
|
189
|
+
end
|
190
|
+
|
191
|
+
def construct_url(path)
|
192
|
+
uri = URI.parse(path)
|
193
|
+
return path if uri.scheme
|
194
|
+
|
195
|
+
path = "/#{path}" unless path[0] == ?\/
|
196
|
+
target + path
|
197
|
+
end
|
198
|
+
|
199
|
+
def get_method_class(method_string)
|
200
|
+
HTTP_METHODS[method_string.upcase]
|
201
|
+
end
|
202
|
+
|
203
|
+
def add_headers(request, headers)
|
204
|
+
headers.each { |key, value| request[key] = value }
|
205
|
+
end
|
206
|
+
|
207
|
+
def mimetype(content)
|
208
|
+
case content
|
209
|
+
when String
|
210
|
+
content
|
211
|
+
when :json
|
212
|
+
"application/json"
|
213
|
+
when :form
|
214
|
+
"application/x-www-form-urlencoded"
|
215
|
+
when nil
|
216
|
+
nil
|
217
|
+
# return request headers (not really Accept)
|
218
|
+
else
|
219
|
+
raise JFoundry::Error, "Unknown mimetype '#{content.inspect}'"
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def encode_params(hash, escape = true)
|
224
|
+
hash.keys.map do |k|
|
225
|
+
v = hash[k]
|
226
|
+
v = MultiJson.dump(v) if v.is_a?(Hash)
|
227
|
+
v = URI.escape(v.to_s, /[^#{URI::PATTERN::UNRESERVED}]/) if escape
|
228
|
+
"#{k}=#{v}"
|
229
|
+
end.join("&")
|
230
|
+
end
|
231
|
+
|
232
|
+
def log_data(time, request, response)
|
233
|
+
{ :time => time,
|
234
|
+
:request => {
|
235
|
+
:method => request.method,
|
236
|
+
:url => request.path,
|
237
|
+
:headers => sane_headers(request)
|
238
|
+
},
|
239
|
+
:response => {
|
240
|
+
:code => response.code,
|
241
|
+
:headers => sane_headers(response)
|
242
|
+
}
|
243
|
+
}
|
244
|
+
end
|
245
|
+
|
246
|
+
def log_line(io, data)
|
247
|
+
io.printf(
|
248
|
+
"[%s] %0.3fs %6s -> %d %s\n",
|
249
|
+
Time.now.strftime("%F %T"),
|
250
|
+
data[:time],
|
251
|
+
data[:request][:method].to_s.upcase,
|
252
|
+
data[:response][:code],
|
253
|
+
data[:request][:url])
|
254
|
+
end
|
255
|
+
|
256
|
+
def log_request(time, request, response)
|
257
|
+
return unless @log
|
258
|
+
|
259
|
+
data = log_data(time, request, response)
|
260
|
+
|
261
|
+
case @log
|
262
|
+
when IO
|
263
|
+
log_line(@log, data)
|
264
|
+
return
|
265
|
+
when String
|
266
|
+
if File.exists?(@log)
|
267
|
+
log = File.readlines(@log).last(LOG_LENGTH - 1)
|
268
|
+
elsif !File.exists?(File.dirname(@log))
|
269
|
+
FileUtils.mkdir_p(File.dirname(@log))
|
270
|
+
end
|
271
|
+
|
272
|
+
File.open(@log, "w") do |io|
|
273
|
+
log.each { |l| io.print l } if log
|
274
|
+
log_line(io, data)
|
275
|
+
end
|
276
|
+
|
277
|
+
return
|
278
|
+
end
|
279
|
+
|
280
|
+
if @log.respond_to?(:call)
|
281
|
+
@log.call(data)
|
282
|
+
return
|
283
|
+
end
|
284
|
+
|
285
|
+
if @log.respond_to?(:<<)
|
286
|
+
@log << data
|
287
|
+
return
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def print_request(request)
|
292
|
+
$stderr.puts ">>>"
|
293
|
+
$stderr.puts request_trace(request)
|
294
|
+
end
|
295
|
+
|
296
|
+
def print_response(response)
|
297
|
+
$stderr.puts response_trace(response)
|
298
|
+
$stderr.puts "<<<"
|
299
|
+
end
|
300
|
+
|
301
|
+
def print_backtrace(locs)
|
302
|
+
return unless @backtrace
|
303
|
+
|
304
|
+
interesting_locs = locs.drop_while { |loc|
|
305
|
+
loc =~ /\/(jfoundry\/|restclient\/|net\/http)/
|
306
|
+
}
|
307
|
+
|
308
|
+
$stderr.puts "--- backtrace:"
|
309
|
+
|
310
|
+
$stderr.puts "... (boring)" unless locs == interesting_locs
|
311
|
+
|
312
|
+
trimmed_locs = interesting_locs[0..5]
|
313
|
+
|
314
|
+
trimmed_locs.each do |loc|
|
315
|
+
$stderr.puts "=== #{loc}"
|
316
|
+
end
|
317
|
+
|
318
|
+
$stderr.puts "... (trimmed)" unless trimmed_locs == interesting_locs
|
319
|
+
end
|
320
|
+
|
321
|
+
def sane_headers(obj)
|
322
|
+
hds = {}
|
323
|
+
|
324
|
+
obj.each_header do |k, v|
|
325
|
+
hds[k] = v
|
326
|
+
end
|
327
|
+
|
328
|
+
hds
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
#encoding=utf-8
|
3
|
+
|
4
|
+
require 'jfoundry/signer'
|
5
|
+
|
6
|
+
module JFoundry
|
7
|
+
module Signature
|
8
|
+
module Version
|
9
|
+
|
10
|
+
include JFoundry::Signer
|
11
|
+
|
12
|
+
def generate_signature secret_key, method, req
|
13
|
+
return sign(secret_key, string_to_sign(method, req))
|
14
|
+
end
|
15
|
+
|
16
|
+
def string_to_sign method, req
|
17
|
+
str = [
|
18
|
+
method.downcase,
|
19
|
+
req['content-md5'],
|
20
|
+
req['content-type'],
|
21
|
+
req['date'],
|
22
|
+
req['path']
|
23
|
+
].join("\n")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
#encoding=utf-8
|
3
|
+
|
4
|
+
require 'base64'
|
5
|
+
require 'openssl'
|
6
|
+
|
7
|
+
module JFoundry
|
8
|
+
module Signer
|
9
|
+
def sign secret_key, string_to_sign, digest = 'sha1'
|
10
|
+
return Base64.encode64(OpenSSL::HMAC.digest(digest, secret_key, string_to_sign)).strip
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "net/https"
|
2
|
+
require "multi_json"
|
3
|
+
|
4
|
+
module JFoundry
|
5
|
+
module TraceHelpers
|
6
|
+
PROTECTED_ATTRIBUTES = ['Authorization', 'credentials']
|
7
|
+
|
8
|
+
def request_trace(request)
|
9
|
+
return nil unless request
|
10
|
+
info = ["REQUEST: #{request[:method]} #{request[:url]}"]
|
11
|
+
info << "REQUEST_HEADERS:"
|
12
|
+
info << header_trace(request[:headers])
|
13
|
+
info << "REQUEST_BODY: #{request[:body]}" if request[:body]
|
14
|
+
info.join("\n")
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def response_trace(response)
|
19
|
+
return nil unless response
|
20
|
+
info = ["RESPONSE: [#{response[:status]}]"]
|
21
|
+
info << "RESPONSE_HEADERS:"
|
22
|
+
info << header_trace(response[:headers])
|
23
|
+
info << "RESPONSE_BODY:"
|
24
|
+
begin
|
25
|
+
parsed_body = MultiJson.load(response[:body])
|
26
|
+
filter_protected_attributes(parsed_body)
|
27
|
+
info << MultiJson.dump(parsed_body, :pretty => true)
|
28
|
+
rescue
|
29
|
+
info << "#{response[:body]}"
|
30
|
+
end
|
31
|
+
info.join("\n")
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def header_trace(headers)
|
37
|
+
headers.sort.map do |key, value|
|
38
|
+
unless PROTECTED_ATTRIBUTES.include?(key)
|
39
|
+
" #{key} : #{value}"
|
40
|
+
else
|
41
|
+
" #{key} : [PRIVATE DATA HIDDEN]"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def filter_protected_attributes(hash_or_array)
|
47
|
+
if hash_or_array.is_a? Array
|
48
|
+
hash_or_array.each do |value|
|
49
|
+
filter_protected_attributes(value)
|
50
|
+
end
|
51
|
+
else
|
52
|
+
hash_or_array.each do |key, value|
|
53
|
+
if PROTECTED_ATTRIBUTES.include? key
|
54
|
+
hash_or_array[key] = "[PRIVATE DATA HIDDEN]"
|
55
|
+
else
|
56
|
+
if value.is_a?(Hash) || value.is_a?(Array)
|
57
|
+
filter_protected_attributes(value)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
require "tmpdir"
|
2
|
+
require "fileutils"
|
3
|
+
require "pathname"
|
4
|
+
require "digest/sha1"
|
5
|
+
|
6
|
+
require "jfoundry/zip"
|
7
|
+
|
8
|
+
module JFoundry
|
9
|
+
module UploadHelpers
|
10
|
+
# Default paths to exclude from upload payload.
|
11
|
+
UPLOAD_EXCLUDE = %w{.git _darcs .svn}
|
12
|
+
|
13
|
+
# Minimum size for an application payload to bother checking resources.
|
14
|
+
RESOURCE_CHECK_LIMIT = 64 * 1024
|
15
|
+
|
16
|
+
# Upload application's code to target. Do this after #create! and before
|
17
|
+
# #start!
|
18
|
+
#
|
19
|
+
# [path]
|
20
|
+
# A path pointing to either a directory, or a .jar, .war, or .zip
|
21
|
+
# file.
|
22
|
+
#
|
23
|
+
# If a .jfignore file is detected under the given path, it will be used
|
24
|
+
# to exclude paths from the payload, similar to a .gitignore.
|
25
|
+
#
|
26
|
+
# [check_resources]
|
27
|
+
# If set to `false`, the entire payload will be uploaded
|
28
|
+
# without checking the resource cache.
|
29
|
+
#
|
30
|
+
# Only do this if you know what you're doing.
|
31
|
+
def upload(path, check_resources = true)
|
32
|
+
unless File.exist? path
|
33
|
+
raise JFoundry::Error, "Invalid application path '#{path}'"
|
34
|
+
end
|
35
|
+
|
36
|
+
zipfile = "#{Dir.tmpdir}/#{@guid}.zip"
|
37
|
+
tmpdir = "#{Dir.tmpdir}/.jf_#{@guid}_files"
|
38
|
+
|
39
|
+
FileUtils.rm_f(zipfile)
|
40
|
+
FileUtils.rm_rf(tmpdir)
|
41
|
+
|
42
|
+
prepare_package(path, tmpdir)
|
43
|
+
|
44
|
+
resources = determine_resources(tmpdir) if check_resources
|
45
|
+
|
46
|
+
packed = JFoundry::Zip.pack(tmpdir, zipfile)
|
47
|
+
|
48
|
+
@client.base.upload_app(@guid, packed && zipfile, resources || [])
|
49
|
+
ensure
|
50
|
+
FileUtils.rm_f(zipfile) if zipfile
|
51
|
+
FileUtils.rm_rf(tmpdir) if tmpdir
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def prepare_package(path, to)
|
57
|
+
if path =~ /\.(jar|war|zip)$/
|
58
|
+
JFoundry::Zip.unpack(path, to)
|
59
|
+
elsif (war_file = Dir.glob("#{path}/*.war").first)
|
60
|
+
JFoundry::Zip.unpack(war_file, to)
|
61
|
+
else
|
62
|
+
FileUtils.mkdir(to)
|
63
|
+
|
64
|
+
exclude = UPLOAD_EXCLUDE
|
65
|
+
if File.exists?("#{path}/.jfignore")
|
66
|
+
exclude += File.read("#{path}/.jfignore").split(/\n+/)
|
67
|
+
end
|
68
|
+
|
69
|
+
files = files_to_consider(path, exclude)
|
70
|
+
|
71
|
+
check_unreachable_links(files, path)
|
72
|
+
|
73
|
+
copy_tree(files, path, to)
|
74
|
+
|
75
|
+
find_sockets(to).each do |s|
|
76
|
+
File.delete s
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def check_unreachable_links(files, path)
|
82
|
+
# only used for friendlier error message
|
83
|
+
pwd = Pathname.pwd
|
84
|
+
|
85
|
+
abspath = File.expand_path(path)
|
86
|
+
unreachable = []
|
87
|
+
files.each do |f|
|
88
|
+
file = Pathname.new(f)
|
89
|
+
if file.symlink? && !file.realpath.to_s.start_with?(abspath)
|
90
|
+
unreachable << file.relative_path_from(pwd)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
unless unreachable.empty?
|
95
|
+
root = Pathname.new(path).relative_path_from(pwd)
|
96
|
+
raise JFoundry::Error,
|
97
|
+
"Path contains links '#{unreachable}' that point outside '#{root}'"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def files_to_consider(path, exclusions)
|
102
|
+
entries = all_files(path)
|
103
|
+
|
104
|
+
exclusions.each do |exclusion|
|
105
|
+
ignore_pattern = exclusion.start_with?("/") ? File.join(path, exclusion) : File.join(path, "**", exclusion)
|
106
|
+
dir_glob = Dir.glob(ignore_pattern, File::FNM_DOTMATCH)
|
107
|
+
entries -= dir_glob
|
108
|
+
|
109
|
+
ignore_pattern = File.join(path, "**", exclusion, "**", "*")
|
110
|
+
dir_glob = Dir.glob(ignore_pattern, File::FNM_DOTMATCH)
|
111
|
+
entries -= dir_glob
|
112
|
+
end
|
113
|
+
|
114
|
+
entries
|
115
|
+
end
|
116
|
+
|
117
|
+
def glob_matches?(file, path, pattern)
|
118
|
+
name = file.sub("#{path}/", "/")
|
119
|
+
flags = File::FNM_DOTMATCH
|
120
|
+
|
121
|
+
# when pattern ends with /, match only directories
|
122
|
+
if pattern.end_with?("/") && File.directory?(file)
|
123
|
+
name = "#{name}/"
|
124
|
+
end
|
125
|
+
|
126
|
+
case pattern
|
127
|
+
# when pattern contains /, do a pathname match
|
128
|
+
when /\/./
|
129
|
+
flags |= File::FNM_PATHNAME
|
130
|
+
|
131
|
+
# otherwise, match any file path
|
132
|
+
else
|
133
|
+
pattern = "**/#{pattern}"
|
134
|
+
end
|
135
|
+
|
136
|
+
File.fnmatch(pattern, name, flags)
|
137
|
+
end
|
138
|
+
|
139
|
+
def find_sockets(path)
|
140
|
+
all_files(path).select { |f| File.socket? f }
|
141
|
+
end
|
142
|
+
|
143
|
+
def determine_resources(path)
|
144
|
+
fingerprints, total_size = make_fingerprints(path)
|
145
|
+
|
146
|
+
return if total_size <= RESOURCE_CHECK_LIMIT
|
147
|
+
|
148
|
+
resources = @client.base.resource_match(fingerprints)
|
149
|
+
|
150
|
+
resources.each do |resource|
|
151
|
+
FileUtils.rm_f resource[:fn]
|
152
|
+
resource[:fn].sub!("#{path}/", "")
|
153
|
+
end
|
154
|
+
|
155
|
+
prune_empty_directories(path)
|
156
|
+
|
157
|
+
resources
|
158
|
+
end
|
159
|
+
|
160
|
+
# OK, HERES THE PLAN...
|
161
|
+
#
|
162
|
+
# 1. Get all the directories in the entire file tree.
|
163
|
+
# 2. Sort them by the length of their absolute path.
|
164
|
+
# 3. Go through the list, longest paths first, and remove
|
165
|
+
# the directories that are empty.
|
166
|
+
#
|
167
|
+
# This ensures that directories containing empty directories
|
168
|
+
# are also pruned.
|
169
|
+
def prune_empty_directories(path)
|
170
|
+
all_files = all_files(path)
|
171
|
+
|
172
|
+
directories = all_files.select { |x| File.directory?(x) }
|
173
|
+
directories.sort! { |a, b| b.size <=> a.size }
|
174
|
+
|
175
|
+
directories.each do |directory|
|
176
|
+
entries = all_files(directory)
|
177
|
+
FileUtils.rmdir(directory) if entries.empty?
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def make_fingerprints(path)
|
182
|
+
fingerprints = []
|
183
|
+
total_size = 0
|
184
|
+
|
185
|
+
all_files(path).each do |filename|
|
186
|
+
next if File.directory?(filename)
|
187
|
+
|
188
|
+
size = File.size(filename)
|
189
|
+
|
190
|
+
total_size += size
|
191
|
+
|
192
|
+
fingerprints << {
|
193
|
+
:size => size,
|
194
|
+
:sha1 => Digest::SHA1.file(filename).hexdigest,
|
195
|
+
:fn => filename
|
196
|
+
}
|
197
|
+
end
|
198
|
+
|
199
|
+
[fingerprints, total_size]
|
200
|
+
end
|
201
|
+
|
202
|
+
def all_files(path)
|
203
|
+
Dir.glob("#{path}/**/*", File::FNM_DOTMATCH).reject do |fn|
|
204
|
+
fn =~ /\.$/
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def copy_tree(files, path, to)
|
209
|
+
files.each do |file|
|
210
|
+
dest = file.sub("#{path}/", "#{to}/")
|
211
|
+
|
212
|
+
if File.directory?(file)
|
213
|
+
FileUtils.mkdir_p(dest)
|
214
|
+
else
|
215
|
+
destdir = File.dirname(dest)
|
216
|
+
FileUtils.mkdir_p(destdir)
|
217
|
+
FileUtils.cp(file, dest)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|