cloulu 0.0.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. data/bin/{cloulu → cl} +0 -0
  2. data/lib/cc_api_stub/applications.rb +53 -0
  3. data/lib/cc_api_stub/domains.rb +16 -0
  4. data/lib/cc_api_stub/frameworks.rb +22 -0
  5. data/lib/cc_api_stub/helper.rb +131 -0
  6. data/lib/cc_api_stub/login.rb +21 -0
  7. data/lib/cc_api_stub/organization_users.rb +21 -0
  8. data/lib/cc_api_stub/organizations.rb +70 -0
  9. data/lib/cc_api_stub/routes.rb +26 -0
  10. data/lib/cc_api_stub/runtimes.rb +22 -0
  11. data/lib/cc_api_stub/service_bindings.rb +22 -0
  12. data/lib/cc_api_stub/service_instances.rb +22 -0
  13. data/lib/cc_api_stub/services.rb +25 -0
  14. data/lib/cc_api_stub/spaces.rb +49 -0
  15. data/lib/cc_api_stub/users.rb +84 -0
  16. data/lib/cc_api_stub.rb +17 -0
  17. data/lib/cfoundry/auth_token.rb +63 -0
  18. data/lib/cfoundry/baseclient.rb +201 -0
  19. data/lib/cfoundry/chatty_hash.rb +46 -0
  20. data/lib/cfoundry/client.rb +46 -0
  21. data/lib/cfoundry/concerns/login_helpers.rb +13 -0
  22. data/lib/cfoundry/errors.rb +160 -0
  23. data/lib/cfoundry/rest_client.rb +299 -0
  24. data/lib/cfoundry/test_support.rb +3 -0
  25. data/lib/cfoundry/trace_helpers.rb +40 -0
  26. data/lib/cfoundry/uaaclient.rb +112 -0
  27. data/lib/cfoundry/upload_helpers.rb +187 -0
  28. data/lib/cfoundry/v1/app.rb +363 -0
  29. data/lib/cfoundry/v1/base.rb +72 -0
  30. data/lib/cfoundry/v1/client.rb +193 -0
  31. data/lib/cfoundry/v1/framework.rb +21 -0
  32. data/lib/cfoundry/v1/model.rb +178 -0
  33. data/lib/cfoundry/v1/model_magic.rb +129 -0
  34. data/lib/cfoundry/v1/runtime.rb +24 -0
  35. data/lib/cfoundry/v1/service.rb +39 -0
  36. data/lib/cfoundry/v1/service_instance.rb +32 -0
  37. data/lib/cfoundry/v1/service_plan.rb +19 -0
  38. data/lib/cfoundry/v1/user.rb +22 -0
  39. data/lib/cfoundry/v2/app.rb +392 -0
  40. data/lib/cfoundry/v2/base.rb +83 -0
  41. data/lib/cfoundry/v2/client.rb +138 -0
  42. data/lib/cfoundry/v2/domain.rb +11 -0
  43. data/lib/cfoundry/v2/framework.rb +14 -0
  44. data/lib/cfoundry/v2/model.rb +148 -0
  45. data/lib/cfoundry/v2/model_magic.rb +449 -0
  46. data/lib/cfoundry/v2/organization.rb +16 -0
  47. data/lib/cfoundry/v2/route.rb +15 -0
  48. data/lib/cfoundry/v2/runtime.rb +12 -0
  49. data/lib/cfoundry/v2/service.rb +19 -0
  50. data/lib/cfoundry/v2/service_auth_token.rb +9 -0
  51. data/lib/cfoundry/v2/service_binding.rb +10 -0
  52. data/lib/cfoundry/v2/service_instance.rb +14 -0
  53. data/lib/cfoundry/v2/service_plan.rb +12 -0
  54. data/lib/cfoundry/v2/space.rb +18 -0
  55. data/lib/cfoundry/v2/user.rb +64 -0
  56. data/lib/cfoundry/validator.rb +39 -0
  57. data/lib/cfoundry/version.rb +4 -0
  58. data/lib/cfoundry/zip.rb +56 -0
  59. data/lib/cfoundry.rb +2 -0
  60. data/lib/manifests-vmc-plugin/errors.rb +21 -0
  61. data/lib/manifests-vmc-plugin/loader/builder.rb +34 -0
  62. data/lib/manifests-vmc-plugin/loader/normalizer.rb +149 -0
  63. data/lib/manifests-vmc-plugin/loader/resolver.rb +79 -0
  64. data/lib/manifests-vmc-plugin/loader.rb +31 -0
  65. data/lib/manifests-vmc-plugin/plugin.rb +145 -0
  66. data/lib/manifests-vmc-plugin/version.rb +3 -0
  67. data/lib/manifests-vmc-plugin.rb +313 -0
  68. data/lib/mothership/base.rb +99 -0
  69. data/lib/mothership/callbacks.rb +85 -0
  70. data/lib/mothership/command.rb +146 -0
  71. data/lib/mothership/errors.rb +38 -0
  72. data/lib/mothership/help/commands.rb +53 -0
  73. data/lib/mothership/help/printer.rb +170 -0
  74. data/lib/mothership/help.rb +64 -0
  75. data/lib/mothership/inputs.rb +189 -0
  76. data/lib/mothership/parser.rb +182 -0
  77. data/lib/mothership/version.rb +3 -0
  78. data/lib/mothership.rb +64 -0
  79. data/lib/tunnel-vmc-plugin/plugin.rb +178 -0
  80. data/lib/tunnel-vmc-plugin/tunnel.rb +308 -0
  81. data/lib/tunnel-vmc-plugin/version.rb +3 -0
  82. data/lib/uaa/http.rb +168 -0
  83. data/lib/uaa/misc.rb +121 -0
  84. data/lib/uaa/scim.rb +292 -0
  85. data/lib/uaa/token_coder.rb +196 -0
  86. data/lib/uaa/token_issuer.rb +255 -0
  87. data/lib/uaa/util.rb +235 -0
  88. data/lib/uaa/version.rb +19 -0
  89. data/lib/uaa.rb +18 -0
  90. data/lib/vmc/cli/app/app.rb +45 -0
  91. data/lib/vmc/cli/app/apps.rb +99 -0
  92. data/lib/vmc/cli/app/base.rb +90 -0
  93. data/lib/vmc/cli/app/crashes.rb +42 -0
  94. data/lib/vmc/cli/app/delete.rb +95 -0
  95. data/lib/vmc/cli/app/deprecated.rb +11 -0
  96. data/lib/vmc/cli/app/env.rb +78 -0
  97. data/lib/vmc/cli/app/files.rb +137 -0
  98. data/lib/vmc/cli/app/health.rb +26 -0
  99. data/lib/vmc/cli/app/instances.rb +53 -0
  100. data/lib/vmc/cli/app/logs.rb +76 -0
  101. data/lib/vmc/cli/app/push/create.rb +165 -0
  102. data/lib/vmc/cli/app/push/interactions.rb +94 -0
  103. data/lib/vmc/cli/app/push/sync.rb +64 -0
  104. data/lib/vmc/cli/app/push.rb +109 -0
  105. data/lib/vmc/cli/app/rename.rb +35 -0
  106. data/lib/vmc/cli/app/restart.rb +20 -0
  107. data/lib/vmc/cli/app/scale.rb +71 -0
  108. data/lib/vmc/cli/app/start.rb +143 -0
  109. data/lib/vmc/cli/app/stats.rb +67 -0
  110. data/lib/vmc/cli/app/stop.rb +27 -0
  111. data/lib/vmc/cli/help.rb +11 -0
  112. data/lib/vmc/cli/interactive.rb +105 -0
  113. data/lib/vmc/cli/route/base.rb +12 -0
  114. data/lib/vmc/cli/route/map.rb +82 -0
  115. data/lib/vmc/cli/route/routes.rb +25 -0
  116. data/lib/vmc/cli/route/unmap.rb +94 -0
  117. data/lib/vmc/cli/service/base.rb +8 -0
  118. data/lib/vmc/cli/service/bind.rb +44 -0
  119. data/lib/vmc/cli/service/create.rb +126 -0
  120. data/lib/vmc/cli/service/delete.rb +86 -0
  121. data/lib/vmc/cli/service/rename.rb +35 -0
  122. data/lib/vmc/cli/service/service.rb +42 -0
  123. data/lib/vmc/cli/service/services.rb +114 -0
  124. data/lib/vmc/cli/service/unbind.rb +38 -0
  125. data/lib/vmc/cli/start/base.rb +94 -0
  126. data/lib/vmc/cli/start/colors.rb +13 -0
  127. data/lib/vmc/cli/start/info.rb +126 -0
  128. data/lib/vmc/cli/start/login.rb +97 -0
  129. data/lib/vmc/cli/start/logout.rb +17 -0
  130. data/lib/vmc/cli/start/target.rb +60 -0
  131. data/lib/vmc/cli/start/target_interactions.rb +37 -0
  132. data/lib/vmc/cli/start/targets.rb +16 -0
  133. data/lib/vmc/cli/user/base.rb +29 -0
  134. data/lib/vmc/cli/user/create.rb +39 -0
  135. data/lib/vmc/cli/user/delete.rb +27 -0
  136. data/lib/vmc/cli/user/passwd.rb +50 -0
  137. data/lib/vmc/cli/user/register.rb +42 -0
  138. data/lib/vmc/cli/user/users.rb +32 -0
  139. data/lib/vmc/cli/v2_check_cli.rb +16 -0
  140. data/lib/vmc/cli.rb +474 -0
  141. data/lib/vmc/constants.rb +13 -0
  142. data/lib/vmc/detect.rb +129 -0
  143. data/lib/vmc/errors.rb +19 -0
  144. data/lib/vmc/plugin.rb +56 -0
  145. data/lib/vmc/spacing.rb +89 -0
  146. data/lib/vmc/spec_helper.rb +1 -0
  147. data/lib/vmc/test_support.rb +6 -0
  148. data/lib/vmc/version.rb +3 -0
  149. data/lib/vmc.rb +8 -0
  150. data/vendor/errors/v1.yml +189 -0
  151. data/vendor/errors/v2.yml +360 -0
  152. metadata +303 -190
@@ -0,0 +1,299 @@
1
+ require "cfoundry/trace_helpers"
2
+ require "net/https"
3
+ require "net/http/post/multipart"
4
+ require "multi_json"
5
+ require "fileutils"
6
+
7
+ module CFoundry
8
+ class RestClient
9
+ include CFoundry::TraceHelpers
10
+
11
+ LOG_LENGTH = 10
12
+
13
+ HTTP_METHODS = {
14
+ "GET" => Net::HTTP::Get,
15
+ "PUT" => Net::HTTP::Put,
16
+ "POST" => Net::HTTP::Post,
17
+ "DELETE" => Net::HTTP::Delete,
18
+ "HEAD" => Net::HTTP::Head,
19
+ }
20
+
21
+ DEFAULT_OPTIONS = {
22
+ :follow_redirects => true
23
+ }
24
+
25
+ attr_reader :target
26
+
27
+ attr_accessor :trace, :backtrace, :log, :request_id, :token, :target, :proxy
28
+
29
+ def initialize(target, token = nil)
30
+ @target = target
31
+ @token = token
32
+ @trace = false
33
+ @backtrace = false
34
+ @log = false
35
+ end
36
+
37
+ def request(method, path, options = {})
38
+ request_uri(method, construct_url(path), DEFAULT_OPTIONS.merge(options))
39
+ end
40
+
41
+ def generate_headers(payload, options)
42
+ headers = {}
43
+
44
+ if payload.is_a?(String)
45
+ headers["Content-Length"] = payload.size
46
+ elsif !payload
47
+ headers["Content-Length"] = 0
48
+ end
49
+
50
+ headers["X-Request-Id"] = @request_id if @request_id
51
+ headers["Authorization"] = @token.auth_header if @token
52
+ headers["Proxy-User"] = @proxy if @proxy
53
+
54
+ if accept_type = mimetype(options[:accept])
55
+ headers["Accept"] = accept_type
56
+ end
57
+
58
+ if content_type = mimetype(options[:content])
59
+ headers["Content-Type"] = content_type
60
+ end
61
+
62
+ headers.merge!(options[:headers]) if options[:headers]
63
+ headers
64
+ end
65
+
66
+ private
67
+
68
+ def request_uri(method, uri, options = {})
69
+ uri = URI.parse(uri)
70
+
71
+ unless uri.is_a?(URI::HTTP)
72
+ raise InvalidTarget.new(@target)
73
+ end
74
+
75
+ # keep original options in case there's a redirect to follow
76
+ original_options = options.dup
77
+ payload = options[:payload]
78
+
79
+ if params = options[:params]
80
+ if uri.query
81
+ uri.query += "&" + encode_params(params)
82
+ else
83
+ uri.query = encode_params(params)
84
+ end
85
+ end
86
+
87
+ unless payload.is_a?(String)
88
+ case options[:content]
89
+ when :json
90
+ payload = MultiJson.dump(payload)
91
+ when :form
92
+ payload = encode_params(payload)
93
+ end
94
+ end
95
+
96
+ method_class = get_method_class(method)
97
+ if payload.is_a?(Hash)
98
+ multipart = method_class.const_get(:Multipart)
99
+ request = multipart.new(uri.request_uri, payload)
100
+ else
101
+ request = method_class.new(uri.request_uri)
102
+ request.body = payload if payload
103
+ end
104
+
105
+ headers = generate_headers(payload, options)
106
+
107
+ request_hash = {
108
+ :url => uri.to_s,
109
+ :method => method,
110
+ :headers => headers,
111
+ :body => payload
112
+ }
113
+
114
+ print_request(request_hash) if @trace
115
+
116
+ add_headers(request, headers)
117
+
118
+ # TODO: test http proxies
119
+ http = Net::HTTP.new(uri.host, uri.port)
120
+
121
+ # TODO remove this when staging returns streaming responses
122
+ http.read_timeout = 300
123
+
124
+ if uri.is_a?(URI::HTTPS)
125
+ http.use_ssl = true
126
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
127
+ end
128
+
129
+ before = Time.now
130
+ http.start do
131
+ response = http.request(request)
132
+ time = Time.now - before
133
+
134
+ response_hash = {
135
+ :headers => sane_headers(response),
136
+ :status => response.code,
137
+ :body => response.body
138
+ }
139
+
140
+ print_response(response_hash) if @trace
141
+ print_backtrace(caller) if @trace
142
+
143
+ log_request(time, request, response)
144
+
145
+ if response.is_a?(Net::HTTPRedirection) && options[:follow_redirects]
146
+ request_uri("GET", response["location"], original_options)
147
+ else
148
+ return request_hash, response_hash
149
+ end
150
+ end
151
+ rescue ::Timeout::Error => e
152
+ raise Timeout.new(method, uri, e)
153
+ rescue SocketError, Errno::ECONNREFUSED => e
154
+ raise TargetRefused, e.message
155
+ rescue URI::InvalidURIError
156
+ raise InvalidTarget.new(@target)
157
+ end
158
+
159
+ def construct_url(path)
160
+ uri = URI.parse(path)
161
+ return path if uri.scheme
162
+
163
+ path = "/#{path}" unless path[0] == ?\/
164
+ target + path
165
+ end
166
+
167
+ def get_method_class(method_string)
168
+ HTTP_METHODS[method_string.upcase]
169
+ end
170
+
171
+ def add_headers(request, headers)
172
+ headers.each { |key, value| request[key] = value }
173
+ end
174
+
175
+ def mimetype(content)
176
+ case content
177
+ when String
178
+ content
179
+ when :json
180
+ "application/json"
181
+ when :form
182
+ "application/x-www-form-urlencoded"
183
+ when nil
184
+ nil
185
+ # return request headers (not really Accept)
186
+ else
187
+ raise CFoundry::Error, "Unknown mimetype '#{content.inspect}'"
188
+ end
189
+ end
190
+
191
+ def encode_params(hash, escape = true)
192
+ hash.keys.map do |k|
193
+ v = hash[k]
194
+ v = MultiJson.dump(v) if v.is_a?(Hash)
195
+ v = URI.escape(v.to_s, /[^#{URI::PATTERN::UNRESERVED}]/) if escape
196
+ "#{k}=#{v}"
197
+ end.join("&")
198
+ end
199
+
200
+ def log_data(time, request, response)
201
+ { :time => time,
202
+ :request => {
203
+ :method => request.method,
204
+ :url => request.path,
205
+ :headers => sane_headers(request)
206
+ },
207
+ :response => {
208
+ :code => response.code,
209
+ :headers => sane_headers(response)
210
+ }
211
+ }
212
+ end
213
+
214
+ def log_line(io, data)
215
+ io.printf(
216
+ "[%s] %0.3fs %6s -> %d %s\n",
217
+ Time.now.strftime("%F %T"),
218
+ data[:time],
219
+ data[:request][:method].to_s.upcase,
220
+ data[:response][:code],
221
+ data[:request][:url])
222
+ end
223
+
224
+ def log_request(time, request, response)
225
+ return unless @log
226
+
227
+ data = log_data(time, request, response)
228
+
229
+ case @log
230
+ when IO
231
+ log_line(@log, data)
232
+ return
233
+ when String
234
+ if File.exists?(@log)
235
+ log = File.readlines(@log).last(LOG_LENGTH - 1)
236
+ elsif !File.exists?(File.dirname(@log))
237
+ FileUtils.mkdir_p(File.dirname(@log))
238
+ end
239
+
240
+ File.open(@log, "w") do |io|
241
+ log.each { |l| io.print l } if log
242
+ log_line(io, data)
243
+ end
244
+
245
+ return
246
+ end
247
+
248
+ if @log.respond_to?(:call)
249
+ @log.call(data)
250
+ return
251
+ end
252
+
253
+ if @log.respond_to?(:<<)
254
+ @log << data
255
+ return
256
+ end
257
+ end
258
+
259
+ def print_request(request)
260
+ $stderr.puts ">>>"
261
+ $stderr.puts request_trace(request)
262
+ end
263
+
264
+ def print_response(response)
265
+ $stderr.puts response_trace(response)
266
+ $stderr.puts "<<<"
267
+ end
268
+
269
+ def print_backtrace(locs)
270
+ return unless @backtrace
271
+
272
+ interesting_locs = locs.drop_while { |loc|
273
+ loc =~ /\/(cfoundry\/|restclient\/|net\/http)/
274
+ }
275
+
276
+ $stderr.puts "--- backtrace:"
277
+
278
+ $stderr.puts "... (boring)" unless locs == interesting_locs
279
+
280
+ trimmed_locs = interesting_locs[0..5]
281
+
282
+ trimmed_locs.each do |loc|
283
+ $stderr.puts "=== #{loc}"
284
+ end
285
+
286
+ $stderr.puts "... (trimmed)" unless trimmed_locs == interesting_locs
287
+ end
288
+
289
+ def sane_headers(obj)
290
+ hds = {}
291
+
292
+ obj.each_header do |k, v|
293
+ hds[k] = v
294
+ end
295
+
296
+ hds
297
+ end
298
+ end
299
+ end
@@ -0,0 +1,3 @@
1
+ Dir[File.expand_path('../../../spec/{support,fakes}/**/*.rb', __FILE__)].each do |file|
2
+ require file
3
+ end
@@ -0,0 +1,40 @@
1
+ require "net/https"
2
+ require "multi_json"
3
+
4
+ module CFoundry
5
+ module TraceHelpers
6
+
7
+ def request_trace(request)
8
+ return nil unless request
9
+ info = ["REQUEST: #{request[:method]} #{request[:url]}"]
10
+ info << "REQUEST_HEADERS:"
11
+ info << header_trace(request[:headers])
12
+ info << "REQUEST_BODY: #{request[:body]}" if request[:body]
13
+ info.join("\n")
14
+ end
15
+
16
+
17
+ def response_trace(response)
18
+ return nil unless response
19
+ info = ["RESPONSE: [#{response[:status]}]"]
20
+ info << "RESPONSE_HEADERS:"
21
+ info << header_trace(response[:headers])
22
+ info << "RESPONSE_BODY:"
23
+ begin
24
+ parsed_body = MultiJson.load(response[:body])
25
+ info << MultiJson.dump(parsed_body, :pretty => true)
26
+ rescue
27
+ info << "#{response[:body]}"
28
+ end
29
+ info.join("\n")
30
+ end
31
+
32
+ private
33
+
34
+ def header_trace(headers)
35
+ headers.sort.map do |key, value|
36
+ " #{key} : #{value}"
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,112 @@
1
+ require "cfoundry/baseclient"
2
+ require 'uaa'
3
+
4
+ module CFoundry
5
+ class UAAClient
6
+ attr_accessor :target, :client_id, :token, :trace
7
+
8
+ def initialize(target = "https://uaa.cloudfoundry.com", client_id = "vmc")
9
+ @target = target
10
+ @client_id = client_id
11
+ CF::UAA::Misc.symbolize_keys = true
12
+ end
13
+
14
+ def prompts
15
+ wrap_uaa_errors do
16
+ CF::UAA::Misc.server(target)[:prompts]
17
+ end
18
+ end
19
+
20
+ def authorize(username, password)
21
+ wrap_uaa_errors do
22
+ begin
23
+ token_issuer.owner_password_grant(username, password)
24
+ rescue CF::UAA::BadResponse => e
25
+ status_code = e.message[/\d+/] || 400
26
+ raise CFoundry::Denied.new("Authorization failed", status_code)
27
+ rescue CF::UAA::TargetError
28
+ token_issuer.implicit_grant_with_creds(:username => username, :password => password)
29
+ end
30
+ end
31
+ end
32
+
33
+ def users
34
+ wrap_uaa_errors do
35
+ scim.query(:user)
36
+ end
37
+ end
38
+
39
+ def change_password(guid, new, old)
40
+ wrap_uaa_errors do
41
+ scim.change_password(guid, new, old)
42
+ end
43
+ end
44
+
45
+ def password_score(password)
46
+ wrap_uaa_errors do
47
+ response = CF::UAA::Misc.password_strength(target, password)
48
+
49
+ required_score = response[:requiredScore] || 0
50
+ case (response[:score] || 0)
51
+ when 10 then
52
+ :strong
53
+ when required_score..9 then
54
+ :good
55
+ else
56
+ :weak
57
+ end
58
+ end
59
+ end
60
+
61
+ def add_user(email, password)
62
+ wrap_uaa_errors do
63
+ scim.add(
64
+ :user,
65
+ {:userName => email,
66
+ :emails => [{:value => email}],
67
+ :password => password,
68
+ :name => {:givenName => email, :familyName => email}
69
+ }
70
+ )
71
+ end
72
+ end
73
+
74
+ def try_to_refresh_token!
75
+ wrap_uaa_errors do
76
+ begin
77
+ token_info = token_issuer.refresh_token_grant(token.refresh_token)
78
+ self.token = AuthToken.from_uaa_token_info(token_info)
79
+ rescue CF::UAA::TargetError
80
+ self.token
81
+ end
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def token_issuer
88
+ @token_issuer ||= CF::UAA::TokenIssuer.new(target, client_id, nil, :symbolize_keys => true)
89
+ @token_issuer.logger.level = @trace ? Logger::Severity::TRACE : 1
90
+ @token_issuer
91
+ end
92
+
93
+ def scim
94
+ auth_header = token && token.auth_header
95
+ scim = CF::UAA::Scim.new(target, auth_header)
96
+ scim.logger.level = @trace ? Logger::Severity::TRACE : 1
97
+ scim
98
+ end
99
+
100
+ def wrap_uaa_errors
101
+ yield
102
+ rescue CF::UAA::BadResponse
103
+ raise CFoundry::BadResponse
104
+ rescue CF::UAA::NotFound
105
+ raise CFoundry::NotFound
106
+ rescue CF::UAA::InvalidToken
107
+ raise CFoundry::Denied
108
+ rescue CF::UAA::TargetError => e
109
+ raise CFoundry::UAAError.new(e.info[:error_description], e.info[:error])
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,187 @@
1
+ require "tmpdir"
2
+ require "fileutils"
3
+ require "pathname"
4
+ require "digest/sha1"
5
+
6
+ require "cfoundry/zip"
7
+
8
+ module CFoundry
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
+ RESOURCE_CHECK_LIMIT = 1024 * 1024 * 1024
16
+
17
+ # Upload application's code to target. Do this after #create! and before
18
+ # #start!
19
+ #
20
+ # [path]
21
+ # A path pointing to either a directory, or a .jar, .war, or .zip
22
+ # file.
23
+ #
24
+ # If a .vmcignore file is detected under the given path, it will be used
25
+ # to exclude paths from the payload, similar to a .gitignore.
26
+ #
27
+ # [check_resources]
28
+ # If set to `false`, the entire payload will be uploaded
29
+ # without checking the resource cache.
30
+ #
31
+ # Only do this if you know what you're doing.
32
+ def upload(path, check_resources = true)
33
+ unless File.exist? path
34
+ raise CFoundry::Error, "Invalid application path '#{path}'"
35
+ end
36
+
37
+ zipfile = "#{Dir.tmpdir}/#{@guid}.zip"
38
+ tmpdir = "#{Dir.tmpdir}/.vmc_#{@guid}_files"
39
+
40
+ FileUtils.rm_f(zipfile)
41
+ FileUtils.rm_rf(tmpdir)
42
+
43
+ prepare_package(path, tmpdir)
44
+
45
+ resources = determine_resources(tmpdir) if check_resources
46
+
47
+ packed = CFoundry::Zip.pack(tmpdir, zipfile)
48
+
49
+ @client.base.upload_app(@guid, packed && zipfile, resources || [])
50
+ ensure
51
+ FileUtils.rm_f(zipfile) if zipfile
52
+ FileUtils.rm_rf(tmpdir) if tmpdir
53
+ end
54
+
55
+ private
56
+
57
+ def prepare_package(path, to)
58
+ if path =~ /\.(jar|war|zip)$/
59
+ CFoundry::Zip.unpack(path, to)
60
+ elsif war_file = Dir.glob("#{path}/*.war").first
61
+ CFoundry::Zip.unpack(war_file, to)
62
+ else
63
+ check_unreachable_links(path)
64
+
65
+ FileUtils.mkdir(to)
66
+
67
+ files = Dir.glob("#{path}/{*,.[^\.]*}")
68
+
69
+ exclude = UPLOAD_EXCLUDE
70
+ if File.exists?("#{path}/.vmcignore")
71
+ exclude += File.read("#{path}/.vmcignore").split(/\n+/)
72
+ end
73
+
74
+ # prevent initial copying if we can, remove sub-files later
75
+ files.reject! do |f|
76
+ exclude.any? do |e|
77
+ File.fnmatch(f.sub(path + "/", ""), e)
78
+ end
79
+ end
80
+
81
+ FileUtils.cp_r(files, to)
82
+
83
+ find_sockets(to).each do |s|
84
+ File.delete s
85
+ end
86
+
87
+ # remove ignored globs more thoroughly
88
+ #
89
+ # note that the above file list only includes toplevel
90
+ # files/directories for cp_r, so this is where sub-files/etc. are
91
+ # removed
92
+ exclude.each do |e|
93
+ Dir.glob("#{to}/#{e}").each do |f|
94
+ FileUtils.rm_rf(f)
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ def check_unreachable_links(path)
101
+ files = Dir.glob("#{path}/**/*", File::FNM_DOTMATCH)
102
+
103
+ # only used for friendlier error message
104
+ pwd = Pathname.pwd
105
+
106
+ abspath = File.expand_path(path)
107
+ unreachable = []
108
+ files.each do |f|
109
+ file = Pathname.new(f)
110
+ if file.symlink? && !file.realpath.to_s.start_with?(abspath)
111
+ unreachable << file.relative_path_from(pwd)
112
+ end
113
+ end
114
+
115
+ unless unreachable.empty?
116
+ root = Pathname.new(path).relative_path_from(pwd)
117
+ raise CFoundry::Error,
118
+ "Path contains links '#{unreachable}' that point outside '#{root}'"
119
+ end
120
+ end
121
+
122
+ def find_sockets(path)
123
+ files = Dir.glob("#{path}/**/*", File::FNM_DOTMATCH)
124
+ files && files.select { |f| File.socket? f }
125
+ end
126
+
127
+ def determine_resources(path)
128
+ fingerprints, total_size = make_fingerprints(path)
129
+
130
+ return if total_size <= RESOURCE_CHECK_LIMIT
131
+
132
+ resources = @client.base.resource_match(fingerprints)
133
+
134
+ resources.each do |resource|
135
+ FileUtils.rm_f resource[:fn]
136
+ resource[:fn].sub!("#{path}/", "")
137
+ end
138
+
139
+ prune_empty_directories(path)
140
+
141
+ resources
142
+ end
143
+
144
+ # OK, HERES THE PLAN...
145
+ #
146
+ # 1. Get all the directories in the entire file tree.
147
+ # 2. Sort them by the length of their absolute path.
148
+ # 3. Go through the list, longest paths first, and remove
149
+ # the directories that are empty.
150
+ #
151
+ # This ensures that directories containing empty directories
152
+ # are also pruned.
153
+ def prune_empty_directories(path)
154
+ all_files = Dir["#{path}/**/{*,.*}"]
155
+ all_files.reject! { |fn| fn =~ /\/\.+$/ }
156
+
157
+ directories = all_files.select { |x| File.directory?(x) }
158
+ directories.sort! { |a, b| b.size <=> a.size }
159
+
160
+ directories.each do |directory|
161
+ entries = Dir.entries(directory) - %w{. ..}
162
+ FileUtils.rmdir(directory) if entries.empty?
163
+ end
164
+ end
165
+
166
+ def make_fingerprints(path)
167
+ fingerprints = []
168
+ total_size = 0
169
+
170
+ Dir.glob("#{path}/**/*", File::FNM_DOTMATCH) do |filename|
171
+ next if File.directory?(filename)
172
+
173
+ size = File.size(filename)
174
+
175
+ total_size += size
176
+
177
+ fingerprints << {
178
+ :size => size,
179
+ :sha1 => Digest::SHA1.file(filename).hexdigest,
180
+ :fn => filename
181
+ }
182
+ end
183
+
184
+ [fingerprints, total_size]
185
+ end
186
+ end
187
+ end