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.
- checksums.yaml +7 -0
- data/COPYRIGHT +1 -0
- data/LICENSE +11 -0
- data/README.md +95 -0
- data/Rakefile +6 -0
- data/autocomplete/rhc_bash +1672 -0
- data/bin/app +37 -0
- data/conf/express.conf +8 -0
- data/features/assets/deploy.tar.gz +0 -0
- data/features/core_feature.rb +191 -0
- data/features/deployments_feature.rb +129 -0
- data/features/domains_feature.rb +58 -0
- data/features/keys_feature.rb +37 -0
- data/features/members_feature.rb +166 -0
- data/lib/rhc/auth/basic.rb +64 -0
- data/lib/rhc/auth/token.rb +102 -0
- data/lib/rhc/auth/token_store.rb +53 -0
- data/lib/rhc/auth.rb +5 -0
- data/lib/rhc/autocomplete.rb +66 -0
- data/lib/rhc/autocomplete_templates/bash.erb +39 -0
- data/lib/rhc/cartridge_helpers.rb +118 -0
- data/lib/rhc/cli.rb +40 -0
- data/lib/rhc/command_runner.rb +185 -0
- data/lib/rhc/commands/account.rb +25 -0
- data/lib/rhc/commands/alias.rb +124 -0
- data/lib/rhc/commands/app.rb +726 -0
- data/lib/rhc/commands/apps.rb +20 -0
- data/lib/rhc/commands/authorization.rb +115 -0
- data/lib/rhc/commands/base.rb +174 -0
- data/lib/rhc/commands/cartridge.rb +329 -0
- data/lib/rhc/commands/clone.rb +66 -0
- data/lib/rhc/commands/configure.rb +20 -0
- data/lib/rhc/commands/create.rb +100 -0
- data/lib/rhc/commands/delete.rb +19 -0
- data/lib/rhc/commands/deploy.rb +32 -0
- data/lib/rhc/commands/deployment.rb +82 -0
- data/lib/rhc/commands/domain.rb +172 -0
- data/lib/rhc/commands/env.rb +142 -0
- data/lib/rhc/commands/force_stop.rb +17 -0
- data/lib/rhc/commands/git_clone.rb +34 -0
- data/lib/rhc/commands/logout.rb +51 -0
- data/lib/rhc/commands/logs.rb +21 -0
- data/lib/rhc/commands/member.rb +148 -0
- data/lib/rhc/commands/port_forward.rb +197 -0
- data/lib/rhc/commands/reload.rb +17 -0
- data/lib/rhc/commands/restart.rb +17 -0
- data/lib/rhc/commands/scp.rb +54 -0
- data/lib/rhc/commands/server.rb +40 -0
- data/lib/rhc/commands/setup.rb +60 -0
- data/lib/rhc/commands/show.rb +43 -0
- data/lib/rhc/commands/snapshot.rb +137 -0
- data/lib/rhc/commands/ssh.rb +51 -0
- data/lib/rhc/commands/sshkey.rb +97 -0
- data/lib/rhc/commands/start.rb +17 -0
- data/lib/rhc/commands/stop.rb +17 -0
- data/lib/rhc/commands/tail.rb +47 -0
- data/lib/rhc/commands/threaddump.rb +14 -0
- data/lib/rhc/commands/tidy.rb +17 -0
- data/lib/rhc/commands.rb +396 -0
- data/lib/rhc/config.rb +321 -0
- data/lib/rhc/context_helper.rb +121 -0
- data/lib/rhc/core_ext.rb +202 -0
- data/lib/rhc/coverage_helper.rb +33 -0
- data/lib/rhc/deployment_helpers.rb +111 -0
- data/lib/rhc/exceptions.rb +256 -0
- data/lib/rhc/git_helpers.rb +106 -0
- data/lib/rhc/help_formatter.rb +55 -0
- data/lib/rhc/helpers.rb +481 -0
- data/lib/rhc/highline_extensions.rb +479 -0
- data/lib/rhc/json.rb +51 -0
- data/lib/rhc/output_helpers.rb +260 -0
- data/lib/rhc/rest/activation.rb +11 -0
- data/lib/rhc/rest/alias.rb +42 -0
- data/lib/rhc/rest/api.rb +87 -0
- data/lib/rhc/rest/application.rb +348 -0
- data/lib/rhc/rest/attributes.rb +36 -0
- data/lib/rhc/rest/authorization.rb +8 -0
- data/lib/rhc/rest/base.rb +79 -0
- data/lib/rhc/rest/cartridge.rb +162 -0
- data/lib/rhc/rest/client.rb +650 -0
- data/lib/rhc/rest/deployment.rb +18 -0
- data/lib/rhc/rest/domain.rb +98 -0
- data/lib/rhc/rest/environment_variable.rb +15 -0
- data/lib/rhc/rest/gear_group.rb +16 -0
- data/lib/rhc/rest/httpclient.rb +145 -0
- data/lib/rhc/rest/key.rb +44 -0
- data/lib/rhc/rest/membership.rb +105 -0
- data/lib/rhc/rest/mock.rb +1042 -0
- data/lib/rhc/rest/user.rb +32 -0
- data/lib/rhc/rest.rb +148 -0
- data/lib/rhc/scp_helpers.rb +27 -0
- data/lib/rhc/ssh_helpers.rb +380 -0
- data/lib/rhc/tar_gz.rb +51 -0
- data/lib/rhc/usage_templates/command_help.erb +51 -0
- data/lib/rhc/usage_templates/command_syntax_help.erb +11 -0
- data/lib/rhc/usage_templates/help.erb +61 -0
- data/lib/rhc/usage_templates/missing_help.erb +1 -0
- data/lib/rhc/usage_templates/options_help.erb +12 -0
- data/lib/rhc/vendor/okjson.rb +600 -0
- data/lib/rhc/vendor/parseconfig.rb +178 -0
- data/lib/rhc/vendor/sshkey.rb +253 -0
- data/lib/rhc/vendor/zliby.rb +628 -0
- data/lib/rhc/version.rb +5 -0
- data/lib/rhc/wizard.rb +637 -0
- data/lib/rhc.rb +34 -0
- data/spec/coverage_helper.rb +82 -0
- data/spec/direct_execution_helper.rb +339 -0
- data/spec/keys/example.pem +23 -0
- data/spec/keys/example_private.pem +27 -0
- data/spec/keys/server.pem +19 -0
- data/spec/rest_spec_helper.rb +31 -0
- data/spec/rhc/assets/cert.crt +22 -0
- data/spec/rhc/assets/cert_key_rsa +27 -0
- data/spec/rhc/assets/empty.txt +0 -0
- data/spec/rhc/assets/env_vars.txt +7 -0
- data/spec/rhc/assets/env_vars_2.txt +1 -0
- data/spec/rhc/assets/foo.txt +1 -0
- data/spec/rhc/assets/targz_corrupted.tar.gz +1 -0
- data/spec/rhc/assets/targz_sample.tar.gz +0 -0
- data/spec/rhc/auth_spec.rb +442 -0
- data/spec/rhc/cli_spec.rb +186 -0
- data/spec/rhc/command_spec.rb +435 -0
- data/spec/rhc/commands/account_spec.rb +42 -0
- data/spec/rhc/commands/alias_spec.rb +333 -0
- data/spec/rhc/commands/app_spec.rb +777 -0
- data/spec/rhc/commands/apps_spec.rb +39 -0
- data/spec/rhc/commands/authorization_spec.rb +157 -0
- data/spec/rhc/commands/cartridge_spec.rb +665 -0
- data/spec/rhc/commands/clone_spec.rb +41 -0
- data/spec/rhc/commands/deployment_spec.rb +327 -0
- data/spec/rhc/commands/domain_spec.rb +401 -0
- data/spec/rhc/commands/env_spec.rb +493 -0
- data/spec/rhc/commands/git_clone_spec.rb +102 -0
- data/spec/rhc/commands/logout_spec.rb +86 -0
- data/spec/rhc/commands/member_spec.rb +247 -0
- data/spec/rhc/commands/port_forward_spec.rb +217 -0
- data/spec/rhc/commands/scp_spec.rb +77 -0
- data/spec/rhc/commands/server_spec.rb +69 -0
- data/spec/rhc/commands/setup_spec.rb +118 -0
- data/spec/rhc/commands/snapshot_spec.rb +179 -0
- data/spec/rhc/commands/ssh_spec.rb +163 -0
- data/spec/rhc/commands/sshkey_spec.rb +188 -0
- data/spec/rhc/commands/tail_spec.rb +81 -0
- data/spec/rhc/commands/threaddump_spec.rb +84 -0
- data/spec/rhc/config_spec.rb +407 -0
- data/spec/rhc/helpers_spec.rb +531 -0
- data/spec/rhc/highline_extensions_spec.rb +314 -0
- data/spec/rhc/json_spec.rb +30 -0
- data/spec/rhc/rest_application_spec.rb +258 -0
- data/spec/rhc/rest_client_spec.rb +752 -0
- data/spec/rhc/rest_spec.rb +740 -0
- data/spec/rhc/targz_spec.rb +55 -0
- data/spec/rhc/wizard_spec.rb +756 -0
- data/spec/spec_helper.rb +575 -0
- data/spec/wizard_spec_helper.rb +330 -0
- 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
|