startapp 0.1.6

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.
Files changed (156) hide show
  1. checksums.yaml +7 -0
  2. data/COPYRIGHT +1 -0
  3. data/LICENSE +11 -0
  4. data/README.md +95 -0
  5. data/Rakefile +6 -0
  6. data/autocomplete/rhc_bash +1672 -0
  7. data/bin/app +37 -0
  8. data/conf/express.conf +8 -0
  9. data/features/assets/deploy.tar.gz +0 -0
  10. data/features/core_feature.rb +191 -0
  11. data/features/deployments_feature.rb +129 -0
  12. data/features/domains_feature.rb +58 -0
  13. data/features/keys_feature.rb +37 -0
  14. data/features/members_feature.rb +166 -0
  15. data/lib/rhc/auth/basic.rb +64 -0
  16. data/lib/rhc/auth/token.rb +102 -0
  17. data/lib/rhc/auth/token_store.rb +53 -0
  18. data/lib/rhc/auth.rb +5 -0
  19. data/lib/rhc/autocomplete.rb +66 -0
  20. data/lib/rhc/autocomplete_templates/bash.erb +39 -0
  21. data/lib/rhc/cartridge_helpers.rb +118 -0
  22. data/lib/rhc/cli.rb +40 -0
  23. data/lib/rhc/command_runner.rb +185 -0
  24. data/lib/rhc/commands/account.rb +25 -0
  25. data/lib/rhc/commands/alias.rb +124 -0
  26. data/lib/rhc/commands/app.rb +726 -0
  27. data/lib/rhc/commands/apps.rb +20 -0
  28. data/lib/rhc/commands/authorization.rb +115 -0
  29. data/lib/rhc/commands/base.rb +174 -0
  30. data/lib/rhc/commands/cartridge.rb +329 -0
  31. data/lib/rhc/commands/clone.rb +66 -0
  32. data/lib/rhc/commands/configure.rb +20 -0
  33. data/lib/rhc/commands/create.rb +100 -0
  34. data/lib/rhc/commands/delete.rb +19 -0
  35. data/lib/rhc/commands/deploy.rb +32 -0
  36. data/lib/rhc/commands/deployment.rb +82 -0
  37. data/lib/rhc/commands/domain.rb +172 -0
  38. data/lib/rhc/commands/env.rb +142 -0
  39. data/lib/rhc/commands/force_stop.rb +17 -0
  40. data/lib/rhc/commands/git_clone.rb +34 -0
  41. data/lib/rhc/commands/logout.rb +51 -0
  42. data/lib/rhc/commands/logs.rb +21 -0
  43. data/lib/rhc/commands/member.rb +148 -0
  44. data/lib/rhc/commands/port_forward.rb +197 -0
  45. data/lib/rhc/commands/reload.rb +17 -0
  46. data/lib/rhc/commands/restart.rb +17 -0
  47. data/lib/rhc/commands/scp.rb +54 -0
  48. data/lib/rhc/commands/server.rb +40 -0
  49. data/lib/rhc/commands/setup.rb +60 -0
  50. data/lib/rhc/commands/show.rb +43 -0
  51. data/lib/rhc/commands/snapshot.rb +137 -0
  52. data/lib/rhc/commands/ssh.rb +51 -0
  53. data/lib/rhc/commands/sshkey.rb +97 -0
  54. data/lib/rhc/commands/start.rb +17 -0
  55. data/lib/rhc/commands/stop.rb +17 -0
  56. data/lib/rhc/commands/tail.rb +47 -0
  57. data/lib/rhc/commands/threaddump.rb +14 -0
  58. data/lib/rhc/commands/tidy.rb +17 -0
  59. data/lib/rhc/commands.rb +396 -0
  60. data/lib/rhc/config.rb +321 -0
  61. data/lib/rhc/context_helper.rb +121 -0
  62. data/lib/rhc/core_ext.rb +202 -0
  63. data/lib/rhc/coverage_helper.rb +33 -0
  64. data/lib/rhc/deployment_helpers.rb +111 -0
  65. data/lib/rhc/exceptions.rb +256 -0
  66. data/lib/rhc/git_helpers.rb +106 -0
  67. data/lib/rhc/help_formatter.rb +55 -0
  68. data/lib/rhc/helpers.rb +481 -0
  69. data/lib/rhc/highline_extensions.rb +479 -0
  70. data/lib/rhc/json.rb +51 -0
  71. data/lib/rhc/output_helpers.rb +260 -0
  72. data/lib/rhc/rest/activation.rb +11 -0
  73. data/lib/rhc/rest/alias.rb +42 -0
  74. data/lib/rhc/rest/api.rb +87 -0
  75. data/lib/rhc/rest/application.rb +348 -0
  76. data/lib/rhc/rest/attributes.rb +36 -0
  77. data/lib/rhc/rest/authorization.rb +8 -0
  78. data/lib/rhc/rest/base.rb +79 -0
  79. data/lib/rhc/rest/cartridge.rb +162 -0
  80. data/lib/rhc/rest/client.rb +650 -0
  81. data/lib/rhc/rest/deployment.rb +18 -0
  82. data/lib/rhc/rest/domain.rb +98 -0
  83. data/lib/rhc/rest/environment_variable.rb +15 -0
  84. data/lib/rhc/rest/gear_group.rb +16 -0
  85. data/lib/rhc/rest/httpclient.rb +145 -0
  86. data/lib/rhc/rest/key.rb +44 -0
  87. data/lib/rhc/rest/membership.rb +105 -0
  88. data/lib/rhc/rest/mock.rb +1042 -0
  89. data/lib/rhc/rest/user.rb +32 -0
  90. data/lib/rhc/rest.rb +148 -0
  91. data/lib/rhc/scp_helpers.rb +27 -0
  92. data/lib/rhc/ssh_helpers.rb +380 -0
  93. data/lib/rhc/tar_gz.rb +51 -0
  94. data/lib/rhc/usage_templates/command_help.erb +51 -0
  95. data/lib/rhc/usage_templates/command_syntax_help.erb +11 -0
  96. data/lib/rhc/usage_templates/help.erb +61 -0
  97. data/lib/rhc/usage_templates/missing_help.erb +1 -0
  98. data/lib/rhc/usage_templates/options_help.erb +12 -0
  99. data/lib/rhc/vendor/okjson.rb +600 -0
  100. data/lib/rhc/vendor/parseconfig.rb +178 -0
  101. data/lib/rhc/vendor/sshkey.rb +253 -0
  102. data/lib/rhc/vendor/zliby.rb +628 -0
  103. data/lib/rhc/version.rb +5 -0
  104. data/lib/rhc/wizard.rb +637 -0
  105. data/lib/rhc.rb +34 -0
  106. data/spec/coverage_helper.rb +82 -0
  107. data/spec/direct_execution_helper.rb +339 -0
  108. data/spec/keys/example.pem +23 -0
  109. data/spec/keys/example_private.pem +27 -0
  110. data/spec/keys/server.pem +19 -0
  111. data/spec/rest_spec_helper.rb +31 -0
  112. data/spec/rhc/assets/cert.crt +22 -0
  113. data/spec/rhc/assets/cert_key_rsa +27 -0
  114. data/spec/rhc/assets/empty.txt +0 -0
  115. data/spec/rhc/assets/env_vars.txt +7 -0
  116. data/spec/rhc/assets/env_vars_2.txt +1 -0
  117. data/spec/rhc/assets/foo.txt +1 -0
  118. data/spec/rhc/assets/targz_corrupted.tar.gz +1 -0
  119. data/spec/rhc/assets/targz_sample.tar.gz +0 -0
  120. data/spec/rhc/auth_spec.rb +442 -0
  121. data/spec/rhc/cli_spec.rb +186 -0
  122. data/spec/rhc/command_spec.rb +435 -0
  123. data/spec/rhc/commands/account_spec.rb +42 -0
  124. data/spec/rhc/commands/alias_spec.rb +333 -0
  125. data/spec/rhc/commands/app_spec.rb +777 -0
  126. data/spec/rhc/commands/apps_spec.rb +39 -0
  127. data/spec/rhc/commands/authorization_spec.rb +157 -0
  128. data/spec/rhc/commands/cartridge_spec.rb +665 -0
  129. data/spec/rhc/commands/clone_spec.rb +41 -0
  130. data/spec/rhc/commands/deployment_spec.rb +327 -0
  131. data/spec/rhc/commands/domain_spec.rb +401 -0
  132. data/spec/rhc/commands/env_spec.rb +493 -0
  133. data/spec/rhc/commands/git_clone_spec.rb +102 -0
  134. data/spec/rhc/commands/logout_spec.rb +86 -0
  135. data/spec/rhc/commands/member_spec.rb +247 -0
  136. data/spec/rhc/commands/port_forward_spec.rb +217 -0
  137. data/spec/rhc/commands/scp_spec.rb +77 -0
  138. data/spec/rhc/commands/server_spec.rb +69 -0
  139. data/spec/rhc/commands/setup_spec.rb +118 -0
  140. data/spec/rhc/commands/snapshot_spec.rb +179 -0
  141. data/spec/rhc/commands/ssh_spec.rb +163 -0
  142. data/spec/rhc/commands/sshkey_spec.rb +188 -0
  143. data/spec/rhc/commands/tail_spec.rb +81 -0
  144. data/spec/rhc/commands/threaddump_spec.rb +84 -0
  145. data/spec/rhc/config_spec.rb +407 -0
  146. data/spec/rhc/helpers_spec.rb +531 -0
  147. data/spec/rhc/highline_extensions_spec.rb +314 -0
  148. data/spec/rhc/json_spec.rb +30 -0
  149. data/spec/rhc/rest_application_spec.rb +258 -0
  150. data/spec/rhc/rest_client_spec.rb +752 -0
  151. data/spec/rhc/rest_spec.rb +740 -0
  152. data/spec/rhc/targz_spec.rb +55 -0
  153. data/spec/rhc/wizard_spec.rb +756 -0
  154. data/spec/spec_helper.rb +575 -0
  155. data/spec/wizard_spec_helper.rb +330 -0
  156. metadata +469 -0
@@ -0,0 +1,650 @@
1
+ require 'rhc/json'
2
+ require 'rhc/helpers'
3
+ require 'uri'
4
+ require 'logger'
5
+ require 'httpclient'
6
+ require 'benchmark'
7
+ require 'set'
8
+
9
+ module RHC
10
+ module Rest
11
+
12
+ #
13
+ # These are methods that belong to the API object but are
14
+ # callable from the client for convenience.
15
+ #
16
+ module ApiMethods
17
+ def add_domain(id, payload={})
18
+ debug "Adding domain #{id} with options #{payload.inspect}"
19
+ @domains = nil
20
+ payload.delete_if{ |k,v| k.nil? or v.nil? }
21
+ api.rest_method "ADD_DOMAIN", {:id => id}.merge(payload)
22
+ end
23
+
24
+ def domains
25
+ debug "Getting all domains"
26
+ @domains ||= api.rest_method "LIST_DOMAINS"
27
+ end
28
+
29
+ def owned_domains
30
+ debug "Getting owned domains"
31
+ if link = api.link_href(:LIST_DOMAINS_BY_OWNER)
32
+ @owned_domains ||= api.rest_method 'LIST_DOMAINS_BY_OWNER', :owner => '@self'
33
+ else
34
+ domains
35
+ end
36
+ end
37
+
38
+ def applications(options={})
39
+ if link = api.link_href(:LIST_APPLICATIONS)
40
+ api.rest_method :LIST_APPLICATIONS, options
41
+ else
42
+ self.domains.map{ |d| d.applications(options) }.flatten
43
+ end
44
+ end
45
+
46
+ def cartridges
47
+ debug "Getting all cartridges"
48
+ @cartridges ||= api.rest_method("LIST_CARTRIDGES", nil, :lazy_auth => true)
49
+ end
50
+
51
+ def user
52
+ debug "Getting user info"
53
+ @user ||= api.rest_method "GET_USER"
54
+ end
55
+
56
+ #Find Domain by namesapce
57
+ def find_domain(id)
58
+ debug "Finding domain #{id}"
59
+ if link = api.link_href(:SHOW_DOMAIN, ':name' => id)
60
+ request(:url => link, :method => "GET")
61
+ else
62
+ domains.find{ |d| d.name.downcase == id.downcase }
63
+ end or raise DomainNotFoundException.new("Domain #{id} not found")
64
+ end
65
+
66
+ def find_application(domain, application, options={})
67
+ request(:url => link_show_application_by_domain_name(domain, application), :method => "GET", :payload => options)
68
+ end
69
+
70
+ def find_application_gear_groups(domain, application, options={})
71
+ request(:url => link_show_application_by_domain_name(domain, application, "gear_groups"), :method => "GET", :payload => options)
72
+ end
73
+
74
+ def find_application_aliases(domain, application, options={})
75
+ request(:url => link_show_application_by_domain_name(domain, application, "aliases"), :method => "GET", :payload => options)
76
+ end
77
+
78
+ def find_application_by_id(id, options={})
79
+ if api.supports? :show_application
80
+ request(:url => link_show_application_by_id(id), :method => "GET", :payload => options)
81
+ else
82
+ applications.find{ |a| a.id == id }
83
+ end or raise ApplicationNotFoundException.new("Application with id #{id} not found")
84
+ end
85
+
86
+ def find_application_by_id_gear_groups(id, options={})
87
+ if api.supports? :show_application
88
+ request(:url => link_show_application_by_id(id, 'gear_groups'), :method => "GET", :payload => options)
89
+ else
90
+ applications.find{ |a| return a.gear_groups if a.id == id }
91
+ end or raise ApplicationNotFoundException.new("Application with id #{id} not found")
92
+ end
93
+
94
+ def link_show_application_by_domain_name(domain, application, *args)
95
+ [
96
+ api.links['LIST_DOMAINS']['href'],
97
+ domain,
98
+ "applications",
99
+ application,
100
+ ].concat(args).map{ |s| URI.escape(s) }.join("/")
101
+ end
102
+
103
+ def link_show_application_by_id(id, *args)
104
+ api.link_href(:SHOW_APPLICATION, {':id' => id}, *args)
105
+ end
106
+
107
+ def link_show_domain_by_name(domain, *args)
108
+ api.link_href(:SHOW_DOMAIN, ':id' => domain)
109
+ end
110
+
111
+ #Find Cartridge by name or regex
112
+ def find_cartridges(name)
113
+ debug "Finding cartridge #{name}"
114
+ if name.is_a?(Hash)
115
+ regex = name[:regex]
116
+ type = name[:type]
117
+ name = name[:name]
118
+ end
119
+
120
+ filtered = Array.new
121
+ cartridges.each do |cart|
122
+ if regex
123
+ filtered.push(cart) if cart.name.match(regex) and (type.nil? or cart.type == type)
124
+ else
125
+ filtered.push(cart) if (name.nil? or cart.name == name) and (type.nil? or cart.type == type)
126
+ end
127
+ end
128
+ return filtered
129
+ end
130
+
131
+ #find Key by name
132
+ def find_key(name)
133
+ debug "Finding key #{name}"
134
+ user.find_key(name) or raise RHC::KeyNotFoundException.new("Key #{name} does not exist")
135
+ end
136
+
137
+ def sshkeys
138
+ debug "Finding all keys for #{user.login}"
139
+ user.keys
140
+ end
141
+
142
+ def add_key(name, key, content)
143
+ debug "Adding key #{key} for #{user.login}"
144
+ user.add_key name, key, content
145
+ end
146
+
147
+ def delete_key(name)
148
+ debug "Deleting key '#{name}'"
149
+ key = find_key(name)
150
+ key.destroy
151
+ end
152
+
153
+ def supports_sessions?
154
+ api.supports? 'ADD_AUTHORIZATION'
155
+ end
156
+
157
+ def authorizations
158
+ raise AuthorizationsNotSupported unless supports_sessions?
159
+ api.rest_method 'LIST_AUTHORIZATIONS'
160
+ end
161
+
162
+ #
163
+ # Returns nil if creating sessions is not supported, raises on error, otherwise
164
+ # returns an Authorization object.
165
+ #
166
+ def new_session(options={})
167
+ if supports_sessions?
168
+ api.rest_method('ADD_AUTHORIZATION', {
169
+ :scope => 'session',
170
+ :note => "APP/#{RHC::VERSION::STRING} (from #{Socket.gethostname rescue 'unknown'} on #{RUBY_PLATFORM})",
171
+ :reuse => true
172
+ }, options)
173
+ end
174
+ end
175
+
176
+ def add_authorization(options={})
177
+ raise AuthorizationsNotSupported unless supports_sessions?
178
+ api.rest_method('ADD_AUTHORIZATION', options, options)
179
+ end
180
+
181
+ def delete_authorizations
182
+ raise AuthorizationsNotSupported unless supports_sessions?
183
+ api.rest_method('LIST_AUTHORIZATIONS', nil, {:method => :delete})
184
+ end
185
+
186
+ def delete_authorization(token)
187
+ raise AuthorizationsNotSupported unless supports_sessions?
188
+ api.rest_method('SHOW_AUTHORIZATION', nil, {:method => :delete, :params => {':id' => token}})
189
+ end
190
+
191
+ def authorization_scope_list
192
+ raise AuthorizationsNotSupported unless supports_sessions?
193
+ link = api.links['ADD_AUTHORIZATION']
194
+ scope = link['optional_params'].find{ |h| h['name'] == 'scope' }
195
+ scope['description'].scan(/(?!\n)\*(.*?)\n(.*?)(?:\n|\Z)/m).inject([]) do |h, (a, b)|
196
+ h << [a.strip, b.strip] if a.present? && b.present?
197
+ h
198
+ end
199
+ end
200
+
201
+ def reset
202
+ (instance_variables - [
203
+ :@end_point, :@debug, :@preferred_api_versions, :@auth, :@options, :@headers,
204
+ :@last_options, :@httpclient, :@self_signed, :@current_api_version, :@api
205
+ ]).each{ |sym| instance_variable_set(sym, nil) }
206
+ self
207
+ end
208
+ end
209
+
210
+ class Client < Base
211
+ include ApiMethods
212
+
213
+ # Keep the list of supported API versions here
214
+ # The list may not necessarily be sorted; we will select the last
215
+ # matching one supported by the server.
216
+ # See #api_version_negotiated
217
+ CLIENT_API_VERSIONS = [1.1, 1.2, 1.3, 1.4, 1.5, 1.6]
218
+ MAX_RETRIES = 5
219
+
220
+ def initialize(*args)
221
+ options = args[0].is_a?(Hash) && args[0] || {}
222
+ @end_point, @debug, @preferred_api_versions =
223
+ if options.empty?
224
+ options[:user] = args.delete_at(1)
225
+ options[:password] = args.delete_at(1)
226
+ args
227
+ else
228
+ [
229
+ options.delete(:url) ||
230
+ (options[:server] && "https://#{options.delete(:server)}/broker/rest/api"),
231
+ options.delete(:debug),
232
+ options.delete(:preferred_api_versions)
233
+ ]
234
+ end
235
+
236
+ @preferred_api_versions ||= CLIENT_API_VERSIONS
237
+ @debug ||= false
238
+
239
+ @auth = options.delete(:auth)
240
+
241
+ self.headers.merge!(options.delete(:headers)) if options[:headers]
242
+ self.options.merge!(options)
243
+
244
+ debug "Connecting to #{@end_point}"
245
+ end
246
+
247
+ def url
248
+ @end_point
249
+ end
250
+
251
+ def api
252
+ @api ||= RHC::Rest::Api.new(self, @preferred_api_versions).tap do |api|
253
+ self.current_api_version = api.api_version_negotiated
254
+ end
255
+ end
256
+
257
+ def api_version_negotiated
258
+ api
259
+ current_api_version
260
+ end
261
+
262
+ def attempt(retries, &block)
263
+ (0..retries).each do |i|
264
+ yield i < (retries-1), i
265
+ end
266
+ raise "Too many retries, giving up."
267
+ end
268
+
269
+ def request(options, &block)
270
+ attempt(MAX_RETRIES) do |more, i|
271
+ begin
272
+ client, args = new_request(options.dup)
273
+ auth = options[:auth] || self.auth
274
+ response = nil
275
+
276
+ debug "Request #{args[0].to_s.upcase} #{args[1]}#{"?#{args[2].map{|a| a.join('=')}.join(' ')}" if args[2] && args[0] == 'GET'}"
277
+ time = Benchmark.realtime{ response = client.request(*(args << true)) }
278
+ debug " code %s %4i ms" % [response.status, (time*1000).to_i] if response
279
+
280
+ next if more && retry_proxy(response, i, args, client)
281
+ auth.retry_auth?(response, self) and next if more && auth
282
+ handle_error!(response, args[1], client) unless response.ok?
283
+
284
+ return (if block_given?
285
+ yield response
286
+ else
287
+ parse_response(response.content) unless response.nil? or response.code == 204
288
+ end)
289
+ rescue HTTPClient::BadResponseError => e
290
+ if e.res
291
+ debug "Response: #{e.res.status} #{e.res.headers.inspect}\n#{e.res.content}\n-------------" if debug?
292
+
293
+ next if more && retry_proxy(e.res, i, args, client)
294
+ auth.retry_auth?(e.res, self) and next if more && auth
295
+ handle_error!(e.res, args[1], client)
296
+ end
297
+ raise ConnectionException.new(
298
+ "An unexpected error occured when connecting to the server: #{e.message}")
299
+ rescue HTTPClient::TimeoutError => e
300
+ raise TimeoutException.new(
301
+ "Connection to server timed out. "\
302
+ "It is possible the operation finished without being able "\
303
+ "to report success. Use 'rhc domain show' or 'rhc app show' "\
304
+ "to see the status of your applications.", e)
305
+ rescue EOFError => e
306
+ raise ConnectionException.new(
307
+ "Connection to server got interrupted: #{e.message}")
308
+ rescue OpenSSL::SSL::SSLError => e
309
+ raise SelfSignedCertificate.new(
310
+ 'self signed certificate',
311
+ "The server is using a self-signed certificate, which means that a secure connection can't be established '#{args[1]}'.\n\n"\
312
+ "You may disable certificate checks with the -k (or --insecure) option. Using this option means that your data is potentially visible to third parties.") if self_signed?
313
+ raise case e.message
314
+ when /self signed certificate/
315
+ CertificateVerificationFailed.new(
316
+ e.message,
317
+ "The server is using a self-signed certificate, which means that a secure connection can't be established '#{args[1]}'.\n\n"\
318
+ "You may disable certificate checks with the -k (or --insecure) option. Using this option means that your data is potentially visible to third parties.")
319
+ when /certificate verify failed/
320
+ CertificateVerificationFailed.new(
321
+ e.message,
322
+ "The server's certificate could not be verified, which means that a secure connection can't be established to the server '#{args[1]}'.\n\n"\
323
+ "If your server is using a self-signed certificate, you may disable certificate checks with the -k (or --insecure) option. Using this option means that your data is potentially visible to third parties.")
324
+ when /unable to get local issuer certificate/
325
+ SSLConnectionFailed.new(
326
+ e.message,
327
+ "The server's certificate could not be verified, which means that a secure connection can't be established to the server '#{args[1]}'.\n\n"\
328
+ "You may need to specify your system CA certificate file with --ssl-ca-file=<path_to_file>. If your server is using a self-signed certificate, you may disable certificate checks with the -k (or --insecure) option. Using this option means that your data is potentially visible to third parties.")
329
+ when /^SSL_connect returned=1 errno=0 state=SSLv2\/v3 read server hello A/
330
+ SSLVersionRejected.new(
331
+ e.message,
332
+ "The server has rejected your connection attempt with an older SSL protocol. Pass --ssl-version=sslv3 on the command line to connect to this server.")
333
+ when /^SSL_CTX_set_cipher_list:: no cipher match/
334
+ SSLVersionRejected.new(
335
+ e.message,
336
+ "The server has rejected your connection attempt because it does not support the requested SSL protocol version.\n\n"\
337
+ "Check with the administrator for a valid SSL version to use and pass --ssl-version=<version> on the command line to connect to this server.")
338
+ else
339
+ SSLConnectionFailed.new(
340
+ e.message,
341
+ "A secure connection could not be established to the server (#{e.message}). You may disable secure connections to your server with the -k (or --insecure) option '#{args[1]}'.\n\n"\
342
+ "If your server is using a self-signed certificate, you may disable certificate checks with the -k (or --insecure) option. Using this option means that your data is potentially visible to third parties.")
343
+ end
344
+ rescue SocketError, Errno::ECONNREFUSED => e
345
+ raise ConnectionException.new(
346
+ "Unable to connect to the server (#{e.message})."\
347
+ "#{client.proxy.present? ? " Check that you have correctly specified your proxy server '#{client.proxy}' as well as your OpenShift server '#{args[1]}'." : " Check that you have correctly specified your OpenShift server '#{args[1]}'."}")
348
+ rescue Errno::ECONNRESET => e
349
+ raise ConnectionException.new(
350
+ "The server has closed the connection unexpectedly (#{e.message}). Your last operation may still be running on the server; please check before retrying your last request.")
351
+ rescue RHC::Rest::Exception
352
+ raise
353
+ rescue => e
354
+ debug_error(e)
355
+ raise ConnectionException, "An unexpected error occured: #{e.message}", e.backtrace
356
+ end
357
+ end
358
+ end
359
+
360
+ protected
361
+ include RHC::Helpers
362
+
363
+ attr_reader :auth
364
+ attr_accessor :current_api_version
365
+ def headers
366
+ @headers ||= {
367
+ :accept => :json
368
+ }
369
+ end
370
+
371
+ def user_agent
372
+ RHC::Helpers.user_agent
373
+ end
374
+
375
+ def options
376
+ @options ||= {
377
+ }
378
+ end
379
+
380
+ def httpclient_for(options, auth=nil)
381
+ user, password, token = options.delete(:user), options.delete(:password), options.delete(:token)
382
+
383
+ if !@httpclient || @last_options != options
384
+ @httpclient = RHC::Rest::HTTPClient.new(:agent_name => user_agent).tap do |http|
385
+ debug "Created new httpclient"
386
+ http.cookie_manager = nil
387
+ http.debug_dev = $stderr if ENV['HTTP_DEBUG']
388
+
389
+ options.select{ |sym, value| http.respond_to?("#{sym}=") }.each{ |sym, value| http.send("#{sym}=", value) }
390
+
391
+ ssl = http.ssl_config
392
+ options.select{ |sym, value| ssl.respond_to?("#{sym}=") }.each{ |sym, value| ssl.send("#{sym}=", value) }
393
+ ssl.add_trust_ca(options[:ca_file]) if options[:ca_file]
394
+ ssl.verify_callback = default_verify_callback
395
+
396
+ @last_options = options
397
+ end
398
+ end
399
+ if auth && auth.respond_to?(:to_httpclient)
400
+ auth.to_httpclient(@httpclient, options)
401
+ else
402
+ @httpclient.www_auth.basic_auth.set(@end_point, user, password) if user
403
+ @httpclient.www_auth.oauth2.set_token(@end_point, token) if token
404
+ end
405
+ @httpclient
406
+ end
407
+
408
+ def default_verify_callback
409
+ lambda do |is_ok, ctx|
410
+ @self_signed = false
411
+ unless is_ok
412
+ cert = ctx.current_cert
413
+ if cert && (cert.subject.cmp(cert.issuer) == 0)
414
+ @self_signed = true
415
+ debug "SSL Verification failed -- Using self signed cert"
416
+ else
417
+ debug "SSL Verification failed -- Preverify: #{is_ok}, Error: #{ctx.error_string} (#{ctx.error})"
418
+ end
419
+ return false
420
+ end
421
+ true
422
+ end
423
+ end
424
+ def self_signed?
425
+ @self_signed
426
+ end
427
+
428
+ def new_request(options)
429
+ options.reverse_merge!(self.options)
430
+
431
+ options[:connect_timeout] ||= options[:timeout] || 120
432
+ options[:receive_timeout] ||= options[:timeout] || 0
433
+ options[:send_timeout] ||= options[:timeout] || 0
434
+ options[:timeout] = nil
435
+
436
+ if auth = options[:auth] || self.auth
437
+ auth.to_request(options)
438
+ end
439
+
440
+ headers = (self.headers.to_a + (options.delete(:headers) || []).to_a).inject({}) do |h,(k,v)|
441
+ v = "application/#{v}" if k == :accept && v.is_a?(Symbol)
442
+ h[k.to_s.downcase.gsub(/_/, '-')] = v
443
+ h
444
+ end
445
+
446
+ modifiers = []
447
+ version = options.delete(:api_version) || current_api_version
448
+ modifiers << ";version=#{version}" if version
449
+
450
+ query = options.delete(:query) || {}
451
+ payload = options.delete(:payload)
452
+ if options[:method].to_s.upcase == 'GET'
453
+ query = payload
454
+ payload = nil
455
+ else
456
+ headers['content-type'] ||= begin
457
+ payload = payload.to_json unless payload.nil? || payload.is_a?(String)
458
+ "application/json#{modifiers.join}"
459
+ end
460
+ end
461
+ query = nil if query.blank?
462
+
463
+ if headers['accept'] && modifiers.present?
464
+ headers['accept'] << modifiers.join
465
+ end
466
+
467
+ # remove all unnecessary options
468
+ options.delete(:lazy_auth)
469
+ options.delete(:accept)
470
+
471
+ args = [options.delete(:method), options.delete(:url), query, payload, headers, true]
472
+ [httpclient_for(options, auth), args]
473
+ end
474
+
475
+ def retry_proxy(response, i, args, client)
476
+ if response.status == 502
477
+ debug "ERROR: Received bad gateway from server, will retry once if this is a GET"
478
+ return true if i == 0 && args[0] == :get
479
+ raise ConnectionException.new(
480
+ "An error occurred while communicating with the server. This problem may only be temporary."\
481
+ "#{client.proxy.present? ? " Check that you have correctly specified your proxy server '#{client.proxy}' as well as your OpenShift server '#{args[1]}'." : " Check that you have correctly specified your OpenShift server '#{args[1]}'."}")
482
+ end
483
+ end
484
+
485
+ def parse_response(response)
486
+ result = RHC::Json.decode(response)
487
+ type = result['type']
488
+ data = result['data'] || {}
489
+
490
+ parse_messages result, data
491
+
492
+ case type
493
+ when 'domains'
494
+ data.map{ |json| Domain.new(json, self) }
495
+ when 'domain'
496
+ Domain.new(data, self)
497
+ when 'authorization'
498
+ Authorization.new(data, self)
499
+ when 'authorizations'
500
+ data.map{ |json| Authorization.new(json, self) }
501
+ when 'applications'
502
+ data.map{ |json| Application.new(json, self) }
503
+ when 'application'
504
+ Application.new(data, self)
505
+ when 'cartridges'
506
+ data.map{ |json| Cartridge.new(json, self) }
507
+ when 'cartridge'
508
+ Cartridge.new(data, self)
509
+ when 'user'
510
+ User.new(data, self)
511
+ when 'keys'
512
+ data.map{ |json| Key.new(json, self) }
513
+ when 'key'
514
+ Key.new(data, self)
515
+ when 'gear_groups'
516
+ data.map{ |json| GearGroup.new(json, self) }
517
+ when 'aliases'
518
+ data.map{ |json| Alias.new(json, self) }
519
+ when 'environment-variables'
520
+ data.map{ |json| EnvironmentVariable.new(json, self) }
521
+ when 'deployments'
522
+ data.map{ |json| Deployment.new(json, self) }
523
+ else
524
+ data
525
+ end
526
+ end
527
+
528
+ def parse_messages(result, data)
529
+ raw = (result || {})['messages'] || []
530
+ raw.delete_if do |m|
531
+ m.delete_if{ |k,v| k.nil? || v.blank? } if m.is_a? Hash
532
+ m.blank?
533
+ end
534
+ warnings, messages, raw = Array(raw).inject([[],[],[]]) do |a, m|
535
+ severity, field, text = m.values_at('severity', 'field', 'text')
536
+ text = (text || "").gsub(/\A\n+/m, "").rstrip
537
+ case severity
538
+ when 'warning'
539
+ a[0] << text
540
+ when 'debug'
541
+ a[2] << m
542
+ a[1] << text if debug?
543
+ when 'info'
544
+ a[2] << m
545
+ a[1] << text if debug? || field == 'result'
546
+ else
547
+ a[2] << m
548
+ a[1] << text
549
+ end
550
+ a
551
+ end
552
+
553
+ if data.is_a?(Array)
554
+ data.each do |d|
555
+ d['messages'] = messages
556
+ d['warnings'] = warnings
557
+ end
558
+ elsif data.is_a?(Hash)
559
+ data['messages'] = messages
560
+ data['warnings'] = warnings
561
+ end
562
+
563
+ warnings.each do |warning|
564
+ unless (@warning_map ||= Set.new).include?(warning)
565
+ @warning_map << warning
566
+ warn warning
567
+ end
568
+ end if respond_to? :warn
569
+ raw
570
+ end
571
+
572
+ def raise_generic_error(url, client)
573
+ raise ServerErrorException.new(generic_error_message(url, client), 129)
574
+ end
575
+ def generic_error_message(url, client)
576
+ "The server did not respond correctly. This may be an issue "\
577
+ "with the server configuration or with your connection to the "\
578
+ "server (such as a Web proxy or firewall)."\
579
+ "#{client.proxy.present? ? " Please verify that your proxy server is working correctly (#{client.proxy}) and that you can access the OpenShift server #{url}" : " Please verify that you can access the OpenShift server #{url}"}"
580
+ end
581
+
582
+ def handle_error!(response, url, client)
583
+ messages = []
584
+ parse_error = nil
585
+ begin
586
+ result = RHC::Json.decode(response.content)
587
+ messages = parse_messages(result, {})
588
+ rescue => e
589
+ debug "Response did not include a message from server: #{e.message}"
590
+ end
591
+ case response.status
592
+ when 400
593
+ raise_generic_error(url, client) if messages.empty?
594
+ message, keys = messages_to_fields(messages)
595
+ raise ValidationException.new(message || "The operation could not be completed.", keys)
596
+ when 401
597
+ raise UnAuthorizedException, "Not authenticated"
598
+ when 403
599
+ raise RequestDeniedException, messages_to_error(messages) || "You are not authorized to perform this operation."
600
+ when 404
601
+ if messages.length == 1
602
+ case messages.first['exit_code']
603
+ when 127
604
+ raise DomainNotFoundException, messages_to_error(messages) || generic_error_message(url, client)
605
+ when 101
606
+ raise ApplicationNotFoundException, messages_to_error(messages) || generic_error_message(url, client)
607
+ end
608
+ end
609
+ raise ResourceNotFoundException, messages_to_error(messages) || generic_error_message(url, client)
610
+ when 409
611
+ raise_generic_error(url, client) if messages.empty?
612
+ message, keys = messages_to_fields(messages)
613
+ raise ValidationException.new(message || "The operation could not be completed.", keys)
614
+ when 422
615
+ raise_generic_error(url, client) if messages.empty?
616
+ message, keys = messages_to_fields(messages)
617
+ raise ValidationException.new(message || "The operation was not valid.", keys)
618
+ when 400
619
+ raise ClientErrorException, messages_to_error(messages) || "The server did not accept the requested operation."
620
+ when 500
621
+ raise ServerErrorException, messages_to_error(messages) || generic_error_message(url, client)
622
+ when 503
623
+ raise ServiceUnavailableException, messages_to_error(messages) || generic_error_message(url, client)
624
+ else
625
+ raise ServerErrorException, messages_to_error(messages) || "Server returned an unexpected error code: #{response.status}"
626
+ end
627
+ raise_generic_error
628
+ end
629
+
630
+ private
631
+ def messages_to_error(messages)
632
+ errors, remaining = messages.partition{ |m| (m['severity'] || "").upcase == 'ERROR' }
633
+ if errors.present?
634
+ if errors.length == 1
635
+ errors.first['text']
636
+ else
637
+ "The server reported multiple errors:\n* #{errors.map{ |m| m['text'] || "An unknown server error occurred.#{ " (exit code: #{m['exit_code']}" if m['exit_code']}}" }.join("\n* ")}"
638
+ end
639
+ elsif remaining.present?
640
+ "The operation did not complete successfully, but the server returned additional information:\n* #{remaining.map{ |m| m['text'] || 'No message'}.join("\n* ")}"
641
+ end
642
+ end
643
+
644
+ def messages_to_fields(messages)
645
+ keys = messages.group_by{ |m| m['field'] }.keys.compact.sort.map(&:to_sym) rescue []
646
+ [messages_to_error(messages), keys]
647
+ end
648
+ end
649
+ end
650
+ end
@@ -0,0 +1,18 @@
1
+ require 'rhc/rest/activation'
2
+
3
+ module RHC
4
+ module Rest
5
+ class Deployment < Base
6
+ define_attr :id, :ref, :sha1, :artifact_url, :hot_deploy, :created_at, :force_clean_build, :activations
7
+
8
+ def activations
9
+ @activations ||=
10
+ attributes['activations'].map{|activation| Activation.new({:created_at => RHC::Helpers.datetime_rfc3339(activation)}, client)}.sort
11
+ end
12
+
13
+ def <=>(other)
14
+ other.created_at <=> created_at
15
+ end
16
+ end
17
+ end
18
+ end