rhc 1.14.7 → 1.15.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. data/autocomplete/rhc_bash +260 -124
  2. data/features/core_feature.rb +143 -0
  3. data/features/domains_feature.rb +49 -0
  4. data/features/members_feature.rb +131 -0
  5. data/lib/rhc/auth/token_store.rb +2 -0
  6. data/lib/rhc/command_runner.rb +7 -5
  7. data/lib/rhc/commands.rb +111 -68
  8. data/lib/rhc/commands/account.rb +3 -4
  9. data/lib/rhc/commands/alias.rb +10 -15
  10. data/lib/rhc/commands/app.rb +36 -64
  11. data/lib/rhc/commands/authorization.rb +1 -1
  12. data/lib/rhc/commands/base.rb +13 -8
  13. data/lib/rhc/commands/cartridge.rb +21 -29
  14. data/lib/rhc/commands/domain.rb +96 -47
  15. data/lib/rhc/commands/env.rb +11 -15
  16. data/lib/rhc/commands/git_clone.rb +2 -3
  17. data/lib/rhc/commands/member.rb +148 -0
  18. data/lib/rhc/commands/port_forward.rb +2 -3
  19. data/lib/rhc/commands/setup.rb +8 -8
  20. data/lib/rhc/commands/snapshot.rb +23 -20
  21. data/lib/rhc/commands/ssh.rb +12 -17
  22. data/lib/rhc/commands/tail.rb +2 -3
  23. data/lib/rhc/commands/threaddump.rb +2 -3
  24. data/lib/rhc/config.rb +1 -0
  25. data/lib/rhc/context_helper.rb +94 -19
  26. data/lib/rhc/core_ext.rb +6 -0
  27. data/lib/rhc/coverage_helper.rb +1 -1
  28. data/lib/rhc/exceptions.rb +24 -0
  29. data/lib/rhc/git_helpers.rb +5 -6
  30. data/lib/rhc/helpers.rb +23 -6
  31. data/lib/rhc/highline_extensions.rb +3 -2
  32. data/lib/rhc/output_helpers.rb +9 -6
  33. data/lib/rhc/rest.rb +1 -0
  34. data/lib/rhc/rest/application.rb +10 -4
  35. data/lib/rhc/rest/attributes.rb +4 -0
  36. data/lib/rhc/rest/base.rb +2 -1
  37. data/lib/rhc/rest/cartridge.rb +4 -0
  38. data/lib/rhc/rest/client.rb +41 -25
  39. data/lib/rhc/rest/domain.rb +29 -9
  40. data/lib/rhc/rest/membership.rb +105 -0
  41. data/lib/rhc/rest/mock.rb +45 -12
  42. data/lib/rhc/rest/user.rb +5 -1
  43. data/lib/rhc/ssh_helpers.rb +14 -0
  44. data/lib/rhc/usage_templates/command_syntax_help.erb +2 -0
  45. data/lib/rhc/wizard.rb +3 -3
  46. data/spec/coverage_helper.rb +31 -9
  47. data/spec/direct_execution_helper.rb +256 -0
  48. data/spec/rhc/command_spec.rb +86 -15
  49. data/spec/rhc/commands/account_spec.rb +2 -1
  50. data/spec/rhc/commands/alias_spec.rb +7 -0
  51. data/spec/rhc/commands/app_spec.rb +73 -90
  52. data/spec/rhc/commands/cartridge_spec.rb +61 -59
  53. data/spec/rhc/commands/domain_spec.rb +179 -43
  54. data/spec/rhc/commands/member_spec.rb +228 -0
  55. data/spec/rhc/commands/port_forward_spec.rb +7 -7
  56. data/spec/rhc/commands/snapshot_spec.rb +45 -24
  57. data/spec/rhc/commands/ssh_spec.rb +16 -3
  58. data/spec/rhc/commands/tail_spec.rb +1 -1
  59. data/spec/rhc/helpers_spec.rb +9 -0
  60. data/spec/rhc/rest_client_spec.rb +75 -7
  61. data/spec/rhc/rest_spec.rb +1 -1
  62. metadata +57 -107
  63. data/features/README.md +0 -198
  64. data/features/application.feature +0 -28
  65. data/features/cartridge.feature +0 -48
  66. data/features/client.feature +0 -11
  67. data/features/domain.feature +0 -30
  68. data/features/env.feature +0 -35
  69. data/features/geared_application.feature +0 -8
  70. data/features/lib/rhc_helper.rb +0 -21
  71. data/features/lib/rhc_helper/api.rb +0 -7
  72. data/features/lib/rhc_helper/app.rb +0 -137
  73. data/features/lib/rhc_helper/cartridge.rb +0 -97
  74. data/features/lib/rhc_helper/commandify.rb +0 -251
  75. data/features/lib/rhc_helper/domain.rb +0 -65
  76. data/features/lib/rhc_helper/env.rb +0 -29
  77. data/features/lib/rhc_helper/httpify.rb +0 -186
  78. data/features/lib/rhc_helper/loggable.rb +0 -37
  79. data/features/lib/rhc_helper/persistable.rb +0 -51
  80. data/features/lib/rhc_helper/runnable.rb +0 -55
  81. data/features/lib/rhc_helper/sshkey.rb +0 -33
  82. data/features/multiple_cartridge.feature +0 -19
  83. data/features/scaled_application.feature +0 -48
  84. data/features/sshkey.feature +0 -63
  85. data/features/step_definitions/application_steps.rb +0 -120
  86. data/features/step_definitions/cartridge_steps.rb +0 -95
  87. data/features/step_definitions/client_steps.rb +0 -41
  88. data/features/step_definitions/domain_steps.rb +0 -73
  89. data/features/step_definitions/env_steps.rb +0 -32
  90. data/features/step_definitions/sshkey_steps.rb +0 -56
  91. data/features/support/assumptions.rb +0 -49
  92. data/features/support/before_hooks.rb +0 -81
  93. data/features/support/env.rb +0 -208
  94. data/features/support/key1 +0 -27
  95. data/features/support/key1.pub +0 -1
  96. data/features/support/key2 +0 -27
  97. data/features/support/key2.pub +0 -1
  98. data/features/support/key3.pub +0 -1
  99. data/features/support/platform_support.rb +0 -29
  100. data/features/verify.feature +0 -18
  101. data/spec/rhc/context_spec.rb +0 -53
data/lib/rhc/helpers.rb CHANGED
@@ -141,6 +141,17 @@ module RHC
141
141
  #:nocov:
142
142
  end
143
143
 
144
+ ROLES = {'view' => 'viewer', 'edit' => 'editor', 'admin' => 'administrator'}
145
+ OptionParser.accept(Role = Class.new) do |s|
146
+ s.downcase!
147
+ (ROLES.has_key?(s) && s) or
148
+ raise OptionParser::InvalidOption.new(nil, "The provided role '#{s}' is not valid. Supported values: #{ROLES.keys.join(', ')}")
149
+ end
150
+
151
+ def role_name(s)
152
+ ROLES[s.downcase]
153
+ end
154
+
144
155
  def openshift_server
145
156
  to_host((options.server rescue nil) || ENV['LIBRA_SERVER'] || "openshift.redhat.com")
146
157
  end
@@ -191,7 +202,7 @@ module RHC
191
202
  :url => openshift_rest_endpoint.to_s,
192
203
  :debug => options.debug,
193
204
  :timeout => options.timeout,
194
- :warn => self.class.instance_method(:warn).bind(self),
205
+ :warn => BOUND_WARNING,
195
206
  }.merge!(ssl_options).merge!(opts))
196
207
  end
197
208
 
@@ -211,6 +222,7 @@ module RHC
211
222
  raise OptionParser::InvalidOption.new(nil, "The certificate '#{file}' cannot be loaded: #{e.message} (#{e.class})")
212
223
  end
213
224
 
225
+
214
226
  #
215
227
  # Output helpers
216
228
  #
@@ -229,6 +241,11 @@ module RHC
229
241
  $terminal.debug?
230
242
  end
231
243
 
244
+ def exec(cmd)
245
+ output = Kernel.send(:`, cmd)
246
+ [$?.exitstatus, output]
247
+ end
248
+
232
249
  def disable_deprecated?
233
250
  ENV['DISABLE_DEPRECATED'] == '1'
234
251
  end
@@ -237,10 +254,6 @@ module RHC
237
254
  deprecated("This command is deprecated. Please use '#{correct}' instead.", short)
238
255
  end
239
256
 
240
- def deprecated_option(deprecated, other)
241
- deprecated("The option '#{deprecated}' is deprecated. Please use '#{other}' instead")
242
- end
243
-
244
257
  def deprecated(msg,short = false)
245
258
  raise DeprecatedError.new(msg % ['an error','a warning',0]) if disable_deprecated?
246
259
  warn "Warning: #{msg}\n" % ['a warning','an error',1]
@@ -317,13 +330,16 @@ module RHC
317
330
  headings.merge!({
318
331
  :creation_time => "Created",
319
332
  :expires_in_seconds => "Expires In",
320
- :uuid => "UUID",
333
+ :uuid => "ID",
334
+ :id => 'ID',
321
335
  :current_scale => "Current",
322
336
  :scales_from => "Minimum",
323
337
  :scales_to => "Maximum",
324
338
  :gear_sizes => "Allowed Gear Sizes",
325
339
  :consumed_gears => "Gears Used",
326
340
  :max_gears => "Gears Allowed",
341
+ :max_domains => "Domains Allowed",
342
+ :compact_members => "Members",
327
343
  :gear_info => "Gears",
328
344
  :plan_id => "Plan",
329
345
  :url => "URL",
@@ -453,5 +469,6 @@ module RHC
453
469
  env_vars
454
470
  end
455
471
 
472
+ BOUND_WARNING = self.method(:warn).to_proc
456
473
  end
457
474
  end
@@ -85,7 +85,7 @@ class HighLineExtension < HighLine
85
85
  end
86
86
 
87
87
  @output.print HighLine::CLEAR if color
88
- @output.flush
88
+ @output.flush
89
89
  end
90
90
 
91
91
  msg
@@ -211,7 +211,8 @@ class HighLineExtension < HighLine
211
211
 
212
212
  read, write = IO.pipe
213
213
 
214
- unless Kernel.fork # Child process
214
+ unless Kernel.fork
215
+ # Child process
215
216
  STDOUT.reopen(write)
216
217
  STDERR.reopen(write) if STDERR.tty?
217
218
  read.close
@@ -1,16 +1,18 @@
1
1
  module RHC
2
2
  module OutputHelpers
3
3
 
4
- def display_domain(domain, applications=nil)
4
+ def display_domain(domain, applications=nil, ids=false)
5
5
  paragraph do
6
- header ["Domain #{domain.id}", ("(owned by #{domain.owner})" if domain.owner.present?)] do
6
+ header ["Domain #{domain.name}", ("(owned by #{domain.owner})" if domain.owner.present?)] do
7
7
  section(:bottom => 1) do
8
8
  say format_table \
9
9
  nil,
10
10
  get_properties(
11
11
  domain,
12
12
  :creation_time,
13
- :allowed_gear_sizes
13
+ (:id if ids),
14
+ (:allowed_gear_sizes unless domain.allowed_gear_sizes.nil?),
15
+ :compact_members
14
16
  ),
15
17
  :delete => true
16
18
  end
@@ -156,7 +158,7 @@ module RHC
156
158
  values = values.to_a if values.is_a? Hash
157
159
  values.delete_if do |arr|
158
160
  arr[0] = "#{table_heading(arr.first)}:" if arr[0].is_a? Symbol
159
- opts[:delete] and arr.last.blank?
161
+ opts[:delete] and arr.last.nil? || arr.last == ""
160
162
  end
161
163
 
162
164
  table(values, :heading => heading, :indent => heading ? ' ' : nil, :color => opts[:color])
@@ -171,6 +173,7 @@ module RHC
171
173
  def get_properties(object,*properties)
172
174
  properties.map do |prop|
173
175
  # Either send the property to the object or yield it
176
+ next if prop.nil?
174
177
  value = begin
175
178
  block_given? ? yield(prop) : object.send(prop)
176
179
  rescue ::Exception => e
@@ -178,7 +181,7 @@ module RHC
178
181
  "<error>"
179
182
  end
180
183
  [prop, format_value(prop,value)]
181
- end
184
+ end.compact
182
185
  end
183
186
 
184
187
  # Format some special values
@@ -206,7 +209,7 @@ module RHC
206
209
  distance_of_time_in_words(value)
207
210
  else
208
211
  case value
209
- when Array then value.join(', ')
212
+ when Array then value.empty? ? '<none>' : value.join(', ')
210
213
  else value
211
214
  end
212
215
  end
data/lib/rhc/rest.rb CHANGED
@@ -16,6 +16,7 @@ module RHC
16
16
  autoload :EnvironmentVariable, 'rhc/rest/environment_variable'
17
17
  autoload :GearGroup, 'rhc/rest/gear_group'
18
18
  autoload :Key, 'rhc/rest/key'
19
+ autoload :Membership, 'rhc/rest/membership'
19
20
  autoload :User, 'rhc/rest/user'
20
21
 
21
22
  class Exception < RuntimeError
@@ -3,6 +3,8 @@ require 'uri'
3
3
  module RHC
4
4
  module Rest
5
5
  class Application < Base
6
+ include Membership
7
+
6
8
  define_attr :domain_id, :name, :creation_time, :uuid,
7
9
  :git_url, :app_url, :gear_profile, :framework,
8
10
  :scalable, :health_check_path, :embedded, :gear_count,
@@ -14,6 +16,14 @@ module RHC
14
16
  scalable
15
17
  end
16
18
 
19
+ def id
20
+ attributes['id'] || uuid
21
+ end
22
+
23
+ def domain
24
+ domain_id
25
+ end
26
+
17
27
  def scalable_carts
18
28
  return [] unless scalable?
19
29
  carts = cartridges.select(&:scalable?)
@@ -58,10 +68,6 @@ module RHC
58
68
  end
59
69
  end
60
70
 
61
- def domain
62
- domain_id
63
- end
64
-
65
71
  def gear_info
66
72
  { :gear_count => gear_count, :gear_profile => gear_profile } unless gear_count.nil?
67
73
  end
@@ -29,4 +29,8 @@ module RHC::Rest::AttributesClass
29
29
  end
30
30
  end
31
31
  end
32
+
33
+ def model_name
34
+ name.split("::").last
35
+ end
32
36
  end
data/lib/rhc/rest/base.rb CHANGED
@@ -51,9 +51,10 @@ module RHC
51
51
  end
52
52
  end
53
53
 
54
- def link_href(sym, params=nil, &block)
54
+ def link_href(sym, params=nil, resource=nil, &block)
55
55
  if (l = link(sym)) && (h = l['href'])
56
56
  h = h.gsub(/:\w+/){ |s| params[s].nil? ? s : CGI.escape(params[s]) } if params
57
+ h = "#{h}/#{resource}" if resource
57
58
  return h
58
59
  end
59
60
  yield if block_given?
@@ -34,6 +34,10 @@ module RHC
34
34
  Array(attribute(:tags))
35
35
  end
36
36
 
37
+ def gear_storage
38
+ (base_gear_storage + additional_gear_storage) * 1024 * 1024 * 1024
39
+ end
40
+
37
41
  def additional_gear_storage
38
42
  attribute(:additional_gear_storage).to_i rescue 0
39
43
  end
@@ -14,10 +14,11 @@ module RHC
14
14
  # callable from the client for convenience.
15
15
  #
16
16
  module ApiMethods
17
- def add_domain(id)
18
- debug "Adding domain #{id}"
17
+ def add_domain(id, payload={})
18
+ debug "Adding domain #{id} with options #{payload.inspect}"
19
19
  @domains = nil
20
- api.rest_method "ADD_DOMAIN", :id => id
20
+ payload.delete_if{ |k,v| k.nil? or v.nil? }
21
+ api.rest_method "ADD_DOMAIN", {:id => id}.merge(payload)
21
22
  end
22
23
 
23
24
  def domains
@@ -56,37 +57,38 @@ module RHC
56
57
  def find_domain(id)
57
58
  debug "Finding domain #{id}"
58
59
  if link = api.link_href(:SHOW_DOMAIN, ':name' => id)
59
- request({
60
- :url => link,
61
- :method => "GET",
62
- })
60
+ request(:url => link, :method => "GET")
63
61
  else
64
- domains.find{ |d| d.id.downcase == id.downcase }
62
+ domains.find{ |d| d.name.downcase == id.downcase }
65
63
  end or raise DomainNotFoundException.new("Domain #{id} not found")
66
64
  end
67
65
 
68
66
  def find_application(domain, application, options={})
69
- response = request({
70
- :url => link_show_application_by_domain_name(domain, application),
71
- :method => "GET",
72
- :payload => options
73
- })
67
+ request(:url => link_show_application_by_domain_name(domain, application), :method => "GET", :payload => options)
74
68
  end
75
69
 
76
70
  def find_application_gear_groups(domain, application, options={})
77
- response = request({
78
- :url => link_show_application_by_domain_name(domain, application, "gear_groups"),
79
- :method => "GET",
80
- :payload => options
81
- })
71
+ request(:url => link_show_application_by_domain_name(domain, application, "gear_groups"), :method => "GET", :payload => options)
82
72
  end
83
73
 
84
74
  def find_application_aliases(domain, application, options={})
85
- response = request({
86
- :url => link_show_application_by_domain_name(domain, application, "aliases"),
87
- :method => "GET",
88
- :payload => options
89
- })
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")
90
92
  end
91
93
 
92
94
  def link_show_application_by_domain_name(domain, application, *args)
@@ -98,6 +100,10 @@ module RHC
98
100
  ].concat(args).map{ |s| URI.escape(s) }.join("/")
99
101
  end
100
102
 
103
+ def link_show_application_by_id(id, *args)
104
+ api.link_href(:SHOW_APPLICATION, {':id' => id}, *args)
105
+ end
106
+
101
107
  def link_show_domain_by_name(domain, *args)
102
108
  api.link_href(:SHOW_DOMAIN, ':id' => domain)
103
109
  end
@@ -191,6 +197,14 @@ module RHC
191
197
  h
192
198
  end
193
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
194
208
  end
195
209
 
196
210
  class Client < Base
@@ -368,13 +382,14 @@ module RHC
368
382
 
369
383
  if !@httpclient || @last_options != options
370
384
  @httpclient = RHC::Rest::HTTPClient.new(:agent_name => user_agent).tap do |http|
385
+ debug "Created new httpclient"
371
386
  http.cookie_manager = nil
372
387
  http.debug_dev = $stderr if ENV['HTTP_DEBUG']
373
388
 
374
- options.select{ |sym, value| http.respond_to?("#{sym}=") }.map{ |sym, value| http.send("#{sym}=", value) }
389
+ options.select{ |sym, value| http.respond_to?("#{sym}=") }.each{ |sym, value| http.send("#{sym}=", value) }
375
390
 
376
391
  ssl = http.ssl_config
377
- options.select{ |sym, value| ssl.respond_to?("#{sym}=") }.map{ |sym, value| ssl.send("#{sym}=", value) }
392
+ options.select{ |sym, value| ssl.respond_to?("#{sym}=") }.each{ |sym, value| ssl.send("#{sym}=", value) }
378
393
  ssl.add_trust_ca(options[:ca_file]) if options[:ca_file]
379
394
  ssl.verify_callback = default_verify_callback
380
395
 
@@ -451,6 +466,7 @@ module RHC
451
466
 
452
467
  # remove all unnecessary options
453
468
  options.delete(:lazy_auth)
469
+ options.delete(:accept)
454
470
 
455
471
  args = [options.delete(:method), options.delete(:url), query, payload, headers, true]
456
472
  [httpclient_for(options, auth), args]
@@ -1,15 +1,30 @@
1
1
  module RHC
2
2
  module Rest
3
3
  class Domain < Base
4
- define_attr :id, :allowed_gear_sizes, :creation_time
4
+ include Membership
5
5
 
6
- def allowed_gear_sizes
7
- Array(attribute(:allowed_gear_sizes))
8
- end
6
+ define_attr :id, # Domain name for API version < 1.6, domain unique id otherwise
7
+ :name, # Available from API version 1.6 onwards
8
+ :allowed_gear_sizes, # Available from API version 1.3 onwards on compatible servers
9
+ :creation_time # Available from API version 1.3 onwards on compatible servers
9
10
 
10
- def owner
11
- if o = Array(attribute(:members)).find{ |m| m['owner'] == true }
12
- o['name']
11
+ def id
12
+ id_and_name.first
13
+ end
14
+ def name
15
+ id_and_name.last
16
+ end
17
+ def id_and_name
18
+ id = @id || attributes['id']
19
+ name = @name || attributes['name']
20
+ if name.present?
21
+ if id == name
22
+ [nil, name]
23
+ else
24
+ [id, name]
25
+ end
26
+ else
27
+ [nil, id]
13
28
  end
14
29
  end
15
30
 
@@ -57,12 +72,17 @@ module RHC
57
72
  rest_method "LIST_APPLICATIONS", options
58
73
  end
59
74
 
60
- def update(new_id)
75
+ def rename(new_id)
61
76
  debug "Updating domain #{id} to #{new_id}"
62
77
  # 5 minute timeout as this may take time if there are a lot of apps
63
78
  rest_method "UPDATE", {:id => new_id}, {:timeout => 0}
64
79
  end
65
- alias :save :update
80
+ alias :update :rename
81
+
82
+ def configure(payload, options={})
83
+ self.attributes = rest_method("UPDATE", payload, options).attributes
84
+ self
85
+ end
66
86
 
67
87
  def destroy(force=false)
68
88
  debug "Deleting domain #{id}"
@@ -0,0 +1,105 @@
1
+ module RHC::Rest
2
+ module Membership
3
+ class Member < Base
4
+ define_attr :name, :login, :id, :type, :from, :role, :owner, :explicit_role
5
+ def owner?
6
+ !!owner
7
+ end
8
+ def admin?
9
+ role == 'admin'
10
+ end
11
+ def editor?
12
+ role == 'edit'
13
+ end
14
+ def viewer?
15
+ role == 'view'
16
+ end
17
+ def name
18
+ attributes['name'] || login
19
+ end
20
+ def to_s
21
+ if name == login
22
+ "#{login} (#{role})"
23
+ elsif login
24
+ "#{name} <#{login}> (#{role})"
25
+ else
26
+ "#{name} (#{role})"
27
+ end
28
+ end
29
+ def role_weight
30
+ case role
31
+ when 'admin' then 0
32
+ when 'edit' then 1
33
+ when 'view' then 2
34
+ else 3
35
+ end
36
+ end
37
+ end
38
+
39
+ def self.included(other)
40
+ end
41
+
42
+ def supports_members?
43
+ supports? 'LIST_MEMBERS'
44
+ end
45
+
46
+ def supports_update_members?
47
+ supports? 'UPDATE_MEMBERS'
48
+ end
49
+
50
+ def members
51
+ @members ||=
52
+ if (members = attributes['members']).nil?
53
+ debug "Getting all members for #{id}"
54
+ raise RHC::MembersNotSupported unless supports_members?
55
+ rest_method 'LIST_MEMBERS'
56
+ else
57
+ members.map{ |m| Member.new(m, client) }
58
+ end
59
+ end
60
+
61
+ def compact_members
62
+ arr = members.reject(&:owner?) rescue []
63
+ if arr.length > 5
64
+ arr.sort_by!(&:name)
65
+ admin, arr = arr.partition(&:admin?)
66
+ edit, arr = arr.partition(&:editor?)
67
+ view, arr = arr.partition(&:viewer?)
68
+ admin << "Admins" if admin.present?
69
+ edit << "Editors" if edit.present?
70
+ view << "Viewers" if view.present?
71
+ arr.map!(&:to_s)
72
+ admin.concat(edit).concat(view).concat(arr)
73
+ elsif arr.present?
74
+ arr.sort_by{ |m| [m.role_weight, m.name] }.join(', ')
75
+ end
76
+ end
77
+
78
+ def update_members(members, options={})
79
+ raise "Members must be an array" unless members.is_a?(Array)
80
+ raise RHC::MembersNotSupported unless supports_members?
81
+ raise RHC::ChangeMembersOnResourceNotSupported unless supports_update_members?
82
+ @members = (attributes['members'] = rest_method('UPDATE_MEMBERS', {:members => members}, options))
83
+ end
84
+
85
+ def delete_members(options={})
86
+ raise RHC::MembersNotSupported unless supports_members?
87
+ rest_method "LIST_MEMBERS", nil, {:method => :delete}.merge(options)
88
+ ensure
89
+ @members = attributes['members'] = nil
90
+ end
91
+
92
+ def leave(options={})
93
+ raise RHC::MembersNotSupported unless supports? 'LEAVE'
94
+ rest_method "LEAVE", nil, options
95
+ ensure
96
+ @members = attributes['members'] = nil
97
+ end
98
+
99
+ def owner
100
+ if o = Array(attribute(:members)).find{ |m| m['owner'] == true }
101
+ o['name'] || o['login']
102
+ end
103
+ end
104
+ end
105
+ end