cloulu 0.0.0 → 0.1.1
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/bin/{cloulu → cl} +0 -0
- data/lib/cc_api_stub/applications.rb +53 -0
- data/lib/cc_api_stub/domains.rb +16 -0
- data/lib/cc_api_stub/frameworks.rb +22 -0
- data/lib/cc_api_stub/helper.rb +131 -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 +25 -0
- data/lib/cc_api_stub/spaces.rb +49 -0
- data/lib/cc_api_stub/users.rb +84 -0
- data/lib/cc_api_stub.rb +17 -0
- data/lib/cfoundry/auth_token.rb +63 -0
- data/lib/cfoundry/baseclient.rb +201 -0
- data/lib/cfoundry/chatty_hash.rb +46 -0
- data/lib/cfoundry/client.rb +46 -0
- data/lib/cfoundry/concerns/login_helpers.rb +13 -0
- data/lib/cfoundry/errors.rb +160 -0
- data/lib/cfoundry/rest_client.rb +299 -0
- data/lib/cfoundry/test_support.rb +3 -0
- data/lib/cfoundry/trace_helpers.rb +40 -0
- data/lib/cfoundry/uaaclient.rb +112 -0
- data/lib/cfoundry/upload_helpers.rb +187 -0
- data/lib/cfoundry/v1/app.rb +363 -0
- data/lib/cfoundry/v1/base.rb +72 -0
- data/lib/cfoundry/v1/client.rb +193 -0
- data/lib/cfoundry/v1/framework.rb +21 -0
- data/lib/cfoundry/v1/model.rb +178 -0
- data/lib/cfoundry/v1/model_magic.rb +129 -0
- data/lib/cfoundry/v1/runtime.rb +24 -0
- data/lib/cfoundry/v1/service.rb +39 -0
- data/lib/cfoundry/v1/service_instance.rb +32 -0
- data/lib/cfoundry/v1/service_plan.rb +19 -0
- data/lib/cfoundry/v1/user.rb +22 -0
- data/lib/cfoundry/v2/app.rb +392 -0
- data/lib/cfoundry/v2/base.rb +83 -0
- data/lib/cfoundry/v2/client.rb +138 -0
- data/lib/cfoundry/v2/domain.rb +11 -0
- data/lib/cfoundry/v2/framework.rb +14 -0
- data/lib/cfoundry/v2/model.rb +148 -0
- data/lib/cfoundry/v2/model_magic.rb +449 -0
- data/lib/cfoundry/v2/organization.rb +16 -0
- data/lib/cfoundry/v2/route.rb +15 -0
- data/lib/cfoundry/v2/runtime.rb +12 -0
- data/lib/cfoundry/v2/service.rb +19 -0
- data/lib/cfoundry/v2/service_auth_token.rb +9 -0
- data/lib/cfoundry/v2/service_binding.rb +10 -0
- data/lib/cfoundry/v2/service_instance.rb +14 -0
- data/lib/cfoundry/v2/service_plan.rb +12 -0
- data/lib/cfoundry/v2/space.rb +18 -0
- data/lib/cfoundry/v2/user.rb +64 -0
- data/lib/cfoundry/validator.rb +39 -0
- data/lib/cfoundry/version.rb +4 -0
- data/lib/cfoundry/zip.rb +56 -0
- data/lib/cfoundry.rb +2 -0
- data/lib/manifests-vmc-plugin/errors.rb +21 -0
- data/lib/manifests-vmc-plugin/loader/builder.rb +34 -0
- data/lib/manifests-vmc-plugin/loader/normalizer.rb +149 -0
- data/lib/manifests-vmc-plugin/loader/resolver.rb +79 -0
- data/lib/manifests-vmc-plugin/loader.rb +31 -0
- data/lib/manifests-vmc-plugin/plugin.rb +145 -0
- data/lib/manifests-vmc-plugin/version.rb +3 -0
- data/lib/manifests-vmc-plugin.rb +313 -0
- data/lib/mothership/base.rb +99 -0
- data/lib/mothership/callbacks.rb +85 -0
- data/lib/mothership/command.rb +146 -0
- data/lib/mothership/errors.rb +38 -0
- data/lib/mothership/help/commands.rb +53 -0
- data/lib/mothership/help/printer.rb +170 -0
- data/lib/mothership/help.rb +64 -0
- data/lib/mothership/inputs.rb +189 -0
- data/lib/mothership/parser.rb +182 -0
- data/lib/mothership/version.rb +3 -0
- data/lib/mothership.rb +64 -0
- data/lib/tunnel-vmc-plugin/plugin.rb +178 -0
- data/lib/tunnel-vmc-plugin/tunnel.rb +308 -0
- data/lib/tunnel-vmc-plugin/version.rb +3 -0
- data/lib/uaa/http.rb +168 -0
- data/lib/uaa/misc.rb +121 -0
- data/lib/uaa/scim.rb +292 -0
- data/lib/uaa/token_coder.rb +196 -0
- data/lib/uaa/token_issuer.rb +255 -0
- data/lib/uaa/util.rb +235 -0
- data/lib/uaa/version.rb +19 -0
- data/lib/uaa.rb +18 -0
- data/lib/vmc/cli/app/app.rb +45 -0
- data/lib/vmc/cli/app/apps.rb +99 -0
- data/lib/vmc/cli/app/base.rb +90 -0
- data/lib/vmc/cli/app/crashes.rb +42 -0
- data/lib/vmc/cli/app/delete.rb +95 -0
- data/lib/vmc/cli/app/deprecated.rb +11 -0
- data/lib/vmc/cli/app/env.rb +78 -0
- data/lib/vmc/cli/app/files.rb +137 -0
- data/lib/vmc/cli/app/health.rb +26 -0
- data/lib/vmc/cli/app/instances.rb +53 -0
- data/lib/vmc/cli/app/logs.rb +76 -0
- data/lib/vmc/cli/app/push/create.rb +165 -0
- data/lib/vmc/cli/app/push/interactions.rb +94 -0
- data/lib/vmc/cli/app/push/sync.rb +64 -0
- data/lib/vmc/cli/app/push.rb +109 -0
- data/lib/vmc/cli/app/rename.rb +35 -0
- data/lib/vmc/cli/app/restart.rb +20 -0
- data/lib/vmc/cli/app/scale.rb +71 -0
- data/lib/vmc/cli/app/start.rb +143 -0
- data/lib/vmc/cli/app/stats.rb +67 -0
- data/lib/vmc/cli/app/stop.rb +27 -0
- data/lib/vmc/cli/help.rb +11 -0
- data/lib/vmc/cli/interactive.rb +105 -0
- data/lib/vmc/cli/route/base.rb +12 -0
- data/lib/vmc/cli/route/map.rb +82 -0
- data/lib/vmc/cli/route/routes.rb +25 -0
- data/lib/vmc/cli/route/unmap.rb +94 -0
- data/lib/vmc/cli/service/base.rb +8 -0
- data/lib/vmc/cli/service/bind.rb +44 -0
- data/lib/vmc/cli/service/create.rb +126 -0
- data/lib/vmc/cli/service/delete.rb +86 -0
- data/lib/vmc/cli/service/rename.rb +35 -0
- data/lib/vmc/cli/service/service.rb +42 -0
- data/lib/vmc/cli/service/services.rb +114 -0
- data/lib/vmc/cli/service/unbind.rb +38 -0
- data/lib/vmc/cli/start/base.rb +94 -0
- data/lib/vmc/cli/start/colors.rb +13 -0
- data/lib/vmc/cli/start/info.rb +126 -0
- data/lib/vmc/cli/start/login.rb +97 -0
- data/lib/vmc/cli/start/logout.rb +17 -0
- data/lib/vmc/cli/start/target.rb +60 -0
- data/lib/vmc/cli/start/target_interactions.rb +37 -0
- data/lib/vmc/cli/start/targets.rb +16 -0
- data/lib/vmc/cli/user/base.rb +29 -0
- data/lib/vmc/cli/user/create.rb +39 -0
- data/lib/vmc/cli/user/delete.rb +27 -0
- data/lib/vmc/cli/user/passwd.rb +50 -0
- data/lib/vmc/cli/user/register.rb +42 -0
- data/lib/vmc/cli/user/users.rb +32 -0
- data/lib/vmc/cli/v2_check_cli.rb +16 -0
- data/lib/vmc/cli.rb +474 -0
- data/lib/vmc/constants.rb +13 -0
- data/lib/vmc/detect.rb +129 -0
- data/lib/vmc/errors.rb +19 -0
- data/lib/vmc/plugin.rb +56 -0
- data/lib/vmc/spacing.rb +89 -0
- data/lib/vmc/spec_helper.rb +1 -0
- data/lib/vmc/test_support.rb +6 -0
- data/lib/vmc/version.rb +3 -0
- data/lib/vmc.rb +8 -0
- data/vendor/errors/v1.yml +189 -0
- data/vendor/errors/v2.yml +360 -0
- 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,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
|