rhc 1.23.7 → 1.24.4

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.
@@ -0,0 +1,103 @@
1
+ require 'rhc/commands/base'
2
+
3
+ module RHC::Commands
4
+ class Team < Base
5
+ summary "Create or delete a team"
6
+ syntax "<action>"
7
+ description <<-DESC
8
+ People who typically share the same role can be added to a team. The team can
9
+ then be added as a member of a domain, and all of the people in the team will
10
+ inherit the team's role on the domain.
11
+
12
+ If a person is a member of multiple teams which are members of a domain, or
13
+ is also added as a domain member individually, their effective role is the
14
+ higher of their individual role or their teams' roles on the domain.
15
+
16
+ To create a team, run 'rhc create-team'.
17
+
18
+ To add members to an existing team, use the 'rhc add-member' command.
19
+
20
+ To list members of an existing team, use the 'rhc members' command.
21
+ DESC
22
+ default_action :help
23
+
24
+ summary "Create a new team"
25
+ syntax "<team_name>"
26
+ description <<-DESC
27
+ People who typically share the same role can be added to a team. The team can
28
+ then be added as a member of a domain, and all of the people in the team will
29
+ inherit the team's role on the domain.
30
+
31
+ If a person is a member of multiple teams which are members of a domain, or
32
+ is also added as a domain member individually, their effective role is the
33
+ higher of their individual role or their teams' roles on the domain.
34
+ DESC
35
+ argument :team_name, "New team name (min 2 chars, max 250 chars)", ["-t", "--team-name NAME"]
36
+ def create(name)
37
+ say "Creating team '#{name}' ... "
38
+ rest_client.add_team(name)
39
+ success "done"
40
+
41
+ info "You may now add team members using the 'rhc add-member' command"
42
+
43
+ 0
44
+ end
45
+
46
+ summary "Display a team and its members"
47
+ syntax "<team_name>"
48
+ takes_team :argument => true
49
+ def show(_)
50
+ team = find_team
51
+
52
+ display_team(team, true)
53
+
54
+ 0
55
+ end
56
+
57
+ summary "Display all teams you are a member of"
58
+ option ['--mine'], "Display only teams you own"
59
+ alias_action :teams, :root_command => true
60
+ def list
61
+ teams = rest_client.send(options.mine ? :owned_teams : :teams, {:include => "members"})
62
+
63
+ teams.each do |t|
64
+ display_team(t, true)
65
+ end
66
+
67
+ if options.mine
68
+ success "You have #{pluralize(teams.length, 'team')}."
69
+ else
70
+ success "You are a member of #{pluralize(teams.length, 'team')}."
71
+ end
72
+
73
+ 0
74
+ end
75
+
76
+ summary "Delete a team"
77
+ syntax "<team_name>"
78
+ takes_team :argument => true
79
+ def delete(_)
80
+ team = find_team(:owned => true)
81
+
82
+ say "Deleting team '#{team.name}' ... "
83
+ team.destroy
84
+ success "deleted"
85
+
86
+ 0
87
+ end
88
+
89
+ summary "Leave a team (remove your membership)"
90
+ syntax "<team_name> [-t TEAM_NAME] [--team-id TEAM_ID]"
91
+ takes_team :argument => true
92
+ def leave(_)
93
+ team = find_team
94
+
95
+ say "Leaving team ... "
96
+ result = team.leave
97
+ success "done"
98
+ result.messages.each{ |s| paragraph{ say s } }
99
+
100
+ 0
101
+ end
102
+ end
103
+ end
@@ -10,21 +10,43 @@ module RHC
10
10
 
11
11
  def self.included(other)
12
12
  other.module_eval do
13
+ def self.takes_team(opts={})
14
+ if opts[:argument]
15
+ argument :team_name, "Name of a team", ["-t", "--team-name NAME"], :allow_nil => true, :covered_by => :team_id
16
+ else
17
+ #:nocov:
18
+ option ["-t", "--team-name NAME"], "Name of a team", :covered_by => :team_id
19
+ #:nocov:
20
+ end
21
+ option ["--team-id ID"], "ID of a team", :covered_by => :team_name
22
+ end
23
+
13
24
  def self.takes_domain(opts={})
14
25
  if opts[:argument]
15
26
  argument :namespace, "Name of a domain", ["-n", "--namespace NAME"], :allow_nil => true, :default => :from_local_git
16
27
  else
28
+ #:nocov:
17
29
  option ["-n", "--namespace NAME"], "Name of a domain", :default => :from_local_git
30
+ #:nocov:
18
31
  end
19
32
  end
20
- # Does not take defaults to avoid conflicts
21
- def self.takes_application_or_domain(opts={})
22
- option ["-n", "--namespace NAME"], "Name of a domain"
23
- option ["-a", "--app NAME"], "Name of an application"
24
- if opts[:argument]
25
- argument :target, "The name of a domain, or an application name with domain (domain or domain/application)", ["-t", "--target NAME_OR_PATH"], :allow_nil => true, :covered_by => [:application_id, :namespace, :app]
33
+
34
+ def self.takes_membership_container(opts={})
35
+ if opts && opts[:argument]
36
+ if opts && opts[:writable]
37
+ #:nocov:
38
+ argument :namespace, "Name of a domain", ["-n", "--namespace NAME"], :allow_nil => true, :default => :from_local_git
39
+ #:nocov:
40
+ else
41
+ argument :target, "The name of a domain, or an application name with domain (domain or domain/application)", ["--target NAME_OR_PATH"], :allow_nil => true, :covered_by => [:application_id, :namespace, :app]
42
+ end
26
43
  end
44
+ option ["-n", "--namespace NAME"], "Name of a domain"
45
+ option ["-a", "--app NAME"], "Name of an application" unless opts && opts[:writable]
46
+ option ["-t", "--team-name NAME"], "Name of a team"
47
+ option ["--team-id ID"], "ID of a team"
27
48
  end
49
+
28
50
  def self.takes_application(opts={})
29
51
  if opts[:argument]
30
52
  argument :app, "Name of an application", ["-a", "--app NAME"], :allow_nil => true, :default => :from_local_git, :covered_by => :application_id
@@ -37,6 +59,18 @@ module RHC
37
59
  end
38
60
  end
39
61
 
62
+ def find_team(opts={})
63
+ if id = options.team_id.presence
64
+ return rest_client.find_team_by_id(id, opts)
65
+ end
66
+ team_name = (opts && opts[:team_name]) || options.team_name
67
+ if team_name.present?
68
+ rest_client.find_team(team_name, opts)
69
+ else
70
+ raise ArgumentError, "You must specify a team name with -t, or a team id with --team-id."
71
+ end
72
+ end
73
+
40
74
  def find_domain(opts={})
41
75
  domain = options.namespace || options.target || namespace_context
42
76
  if domain
@@ -46,7 +80,7 @@ module RHC
46
80
  end
47
81
  end
48
82
 
49
- def find_app_or_domain(opts={})
83
+ def find_membership_container(opts={})
50
84
  domain, app =
51
85
  if options.target.present?
52
86
  options.target.split(/\//)
@@ -57,12 +91,19 @@ module RHC
57
91
  [options.namespace || namespace_context, options.app]
58
92
  end
59
93
  end
60
- if app && domain
94
+
95
+ if options.team_id.present?
96
+ rest_client.find_team_by_id(options.team_id)
97
+ elsif options.team_name.present?
98
+ rest_client.find_team(options.team_name)
99
+ elsif app && domain
61
100
  rest_client.find_application(domain, app)
62
101
  elsif domain
63
102
  rest_client.find_domain(domain)
103
+ elsif opts && opts[:writable]
104
+ raise ArgumentError, "You must specify a domain with -n, or a team with -t."
64
105
  else
65
- raise ArgumentError, "You must specify a domain with -n, or an application with -a."
106
+ raise ArgumentError, "You must specify a domain with -n, an application with -a, or a team with -t."
66
107
  end
67
108
  end
68
109
 
@@ -214,13 +214,13 @@ module RHC
214
214
  end
215
215
 
216
216
  class ChangeMembersOnResourceNotSupported < Exception
217
- def initialize(message="You can only add or remove members on a domain.")
217
+ def initialize(message="You can only add or remove members on a domain or team.")
218
218
  super message, 1
219
219
  end
220
220
  end
221
221
 
222
222
  class MembersNotSupported < Exception
223
- def initialize(message="The server does not support adding or removing members.")
223
+ def initialize(message="The server does not support adding or removing members on this resource.")
224
224
  super message, 1
225
225
  end
226
226
  end
@@ -1,6 +1,24 @@
1
1
  module RHC
2
2
  module OutputHelpers
3
3
 
4
+ def display_team(team, ids=false)
5
+ paragraph do
6
+ header ["Team #{team.name}", ("(owned by #{team.owner})" if team.owner.present?)] do
7
+ section(:bottom => 1) do
8
+ say format_table \
9
+ nil,
10
+ get_properties(
11
+ team,
12
+ (:id if ids),
13
+ (:global if team.global?),
14
+ :compact_members
15
+ ),
16
+ :delete => true
17
+ end
18
+ end
19
+ end
20
+ end
21
+
4
22
  def display_domain(domain, applications=nil, ids=false)
5
23
  paragraph do
6
24
  header ["Domain #{domain.name}", ("(owned by #{domain.owner})" if domain.owner.present?)] do
@@ -144,7 +162,16 @@ module RHC
144
162
  end
145
163
 
146
164
  def format_usage_message(cart)
147
- "This gear costs an additional $#{cart.usage_rate} per gear after the first 3 gears."
165
+ cart.usage_rates.map do |rate, plans|
166
+ plans = plans.map(&:capitalize) if plans
167
+ if plans && plans.length > 1
168
+ "This cartridge costs an additional $#{rate} per gear after the first 3 gears on the #{plans[0...-1].join(', ')} and #{plans[-1]} plans."
169
+ elsif plans && plans.length == 1
170
+ "This cartridge costs an additional $#{rate} per gear after the first 3 gears on the #{plans.first} plan."
171
+ else
172
+ "This cartridge costs an additional $#{rate} per gear after the first 3 gears."
173
+ end
174
+ end
148
175
  end
149
176
 
150
177
  def default_display_env_var(env_var_name, env_var_value=nil)
@@ -67,18 +67,22 @@ module RHC
67
67
  end
68
68
 
69
69
  def usage_rate?
70
- rate = usage_rate
71
- rate && rate > 0.0
72
- end
73
-
74
- def usage_rate
75
- rate = attribute(:usage_rate_usd)
76
-
77
- if attribute(:usage_rates)
78
- rate ||= attribute(:usage_rates).inject(0) { |total, rate| total + rate['usd'].to_f }
70
+ rates = usage_rates
71
+ !(rates.nil? || rates.empty?)
72
+ end
73
+
74
+ def usage_rates
75
+ rate = attribute(:usage_rate_usd).to_f rescue 0.0
76
+ if rate > 0
77
+ {rate => []}
78
+ elsif attribute(:usage_rates).present?
79
+ attribute(:usage_rates).inject({}) do |plans_by_rate, rate|
80
+ if (usd = rate['usd'].to_f rescue 0.0) > 0
81
+ (plans_by_rate[usd] ||= []) << rate['plan_id']
82
+ end
83
+ plans_by_rate
84
+ end
79
85
  end
80
-
81
- rate.to_f rescue 0.0
82
86
  end
83
87
 
84
88
  def scaling
@@ -64,19 +64,30 @@ module RHC
64
64
  @user ||= api.rest_method "GET_USER"
65
65
  end
66
66
 
67
- def teams
68
- debug "Getting all teams"
67
+ def add_team(name, payload={})
68
+ debug "Adding team #{name} with options #{payload.inspect}"
69
+ @teams = nil
70
+ payload.delete_if{ |k,v| k.nil? or v.nil? }
71
+ if api.supports? 'ADD_TEAM'
72
+ api.rest_method "ADD_TEAM", {:name => name}.merge(payload)
73
+ else
74
+ raise RHC::TeamsNotSupportedException
75
+ end
76
+ end
77
+
78
+ def teams(opts={})
79
+ debug "Getting teams you are a member of"
69
80
  if link = api.link_href(:LIST_TEAMS)
70
- @teams ||= api.rest_method "LIST_TEAMS"
81
+ @teams ||= api.rest_method("LIST_TEAMS", opts)
71
82
  else
72
83
  raise RHC::TeamsNotSupportedException
73
84
  end
74
85
  end
75
86
 
76
- def owned_teams
87
+ def owned_teams(opts={})
77
88
  debug "Getting owned teams"
78
89
  if link = api.link_href(:LIST_TEAMS_BY_OWNER)
79
- @owned_teams ||= api.rest_method "LIST_TEAMS_BY_OWNER", :owner => '@self'
90
+ @owned_teams ||= api.rest_method("LIST_TEAMS_BY_OWNER", opts.merge({:owner => '@self'}))
80
91
  else
81
92
  raise RHC::TeamsNotSupportedException
82
93
  end
@@ -96,6 +107,35 @@ module RHC
96
107
  owned_teams.select{|team| team.name.downcase =~ /#{Regexp.escape(search)}/i}
97
108
  end
98
109
 
110
+ def find_team(name, options={})
111
+ precheck_team_id(name)
112
+
113
+ matching_teams = if options[:global]
114
+ search_teams(name, true).select { |t| t.name == name }
115
+ elsif options[:owned]
116
+ owned_teams.select { |t| t.name == name }
117
+ else
118
+ teams.select{ |t| t.name == name }
119
+ end
120
+
121
+ if matching_teams.blank?
122
+ raise TeamNotFoundException.new("Team with name #{name} not found")
123
+ elsif matching_teams.length > 1
124
+ raise TeamNotFoundException.new("Multiple teams with name #{name} found. Use --team-id to select the team by id.")
125
+ else
126
+ matching_teams.first
127
+ end
128
+ end
129
+
130
+ def find_team_by_id(id, options={})
131
+ precheck_team_id(id)
132
+ if api.supports? :show_team
133
+ request(:url => link_show_team_by_id(id), :method => "GET", :payload => options)
134
+ else
135
+ teams.find{ |t| t.id == id }
136
+ end or raise TeamNotFoundException.new("Team with id #{id} not found")
137
+ end
138
+
99
139
  #Find Domain by namespace
100
140
  def find_domain(id)
101
141
  debug "Finding domain #{id}"
@@ -153,6 +193,11 @@ module RHC
153
193
  raise ApplicationNotFoundException.new("Application #{application} not found") if ['.','..'].include?(application)
154
194
  end
155
195
 
196
+ def precheck_team_id(team)
197
+ raise TeamNotFoundException.new("Team not specified") if team.blank?
198
+ raise TeamNotFoundException.new("Team #{team} not found") if ['.','..'].include?(team)
199
+ end
200
+
156
201
  def link_show_application_by_domain_name(domain, application, *args)
157
202
  [
158
203
  api.links['LIST_DOMAINS']['href'],
@@ -170,6 +215,10 @@ module RHC
170
215
  api.link_href(:SHOW_DOMAIN, ':id' => domain)
171
216
  end
172
217
 
218
+ def link_show_team_by_id(id, *args)
219
+ api.link_href(:SHOW_TEAM, {':id' => id}, *args)
220
+ end
221
+
173
222
  #Find Cartridge by name or regex
174
223
  def find_cartridges(name)
175
224
  debug "Finding cartridge #{name}"
@@ -276,7 +325,7 @@ module RHC
276
325
  # The list may not necessarily be sorted; we will select the last
277
326
  # matching one supported by the server.
278
327
  # See #api_version_negotiated
279
- CLIENT_API_VERSIONS = [1.1, 1.2, 1.3, 1.4, 1.5, 1.6]
328
+ CLIENT_API_VERSIONS = [1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7]
280
329
  MAX_RETRIES = 5
281
330
 
282
331
  def initialize(*args)
@@ -582,6 +631,8 @@ module RHC
582
631
  data.map{ |json| EnvironmentVariable.new(json, self) }
583
632
  when 'deployments'
584
633
  data.map{ |json| Deployment.new(json, self) }
634
+ when 'team'
635
+ Team.new(data, self)
585
636
  when 'teams'
586
637
  data.map{ |json| Team.new(json, self) }
587
638
  when 'member'
@@ -88,6 +88,10 @@ module RHC::Rest
88
88
  supports? 'UPDATE_MEMBERS'
89
89
  end
90
90
 
91
+ def default_member_role
92
+ 'edit'
93
+ end
94
+
91
95
  def members
92
96
  @members ||=
93
97
  if (members = attributes['members']).nil?
@@ -131,7 +135,7 @@ module RHC::Rest
131
135
  end
132
136
 
133
137
  def leave(options={})
134
- raise RHC::MembersNotSupported unless supports? 'LEAVE'
138
+ raise RHC::MembersNotSupported.new("The server does not support leaving this resource.") unless supports? 'LEAVE'
135
139
  rest_method "LEAVE", nil, options
136
140
  ensure
137
141
  @members = attributes['members'] = nil
data/lib/rhc/rest/mock.rb CHANGED
@@ -89,7 +89,7 @@ module RHC::Rest::Mock
89
89
  to_return({
90
90
  :body => {
91
91
  :data => mock_response_links(authorizations ? mock_api_with_authorizations : mock_real_client_links),
92
- :supported_api_versions => [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6],
92
+ :supported_api_versions => [1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7],
93
93
  }.to_json
94
94
  })
95
95
  end
@@ -421,10 +421,12 @@ module RHC::Rest::Mock
421
421
  end
422
422
 
423
423
  def mock_client_links
424
- [['GET_USER', 'user/', 'get' ],
424
+ mock_teams_links.concat([
425
+ ['GET_USER', 'user/', 'get' ],
425
426
  ['ADD_DOMAIN', 'domains/add', 'post'],
426
427
  ['LIST_DOMAINS', 'domains/', 'get' ],
427
- ['LIST_CARTRIDGES', 'cartridges/', 'get' ]]
428
+ ['LIST_CARTRIDGES', 'cartridges/', 'get' ]
429
+ ])
428
430
  end
429
431
 
430
432
  def mock_real_client_links
@@ -450,6 +452,15 @@ module RHC::Rest::Mock
450
452
  ])
451
453
  end
452
454
 
455
+ def mock_team_links(team_id='test_team')
456
+ [['GET', "team/#{team_id}", 'get' ],
457
+ ['ADD_MEMBER', "team/#{team_id}/members/", 'post', {'optional_params' => [{'name' => 'id'}, {'name' => 'login'}], 'required_params' => [{'name' => 'role'}]} ],
458
+ ['LIST_MEMBERS', "team/#{team_id}/update", 'get' ],
459
+ ['UPDATE_MEMBERS', "team/#{team_id}/delete", 'patch', {'optional_params' => [{'name' => 'id'}, {'name' => 'login'}, {'name' => 'members'}] } ],
460
+ ['LEAVE', "team/#{team_id}/delete", 'delete' ],
461
+ ['DELETE', "team/#{team_id}/delete", 'delete' ]]
462
+ end
463
+
453
464
  def mock_domain_links(domain_id='test_domain')
454
465
  [['ADD_APPLICATION', "domains/#{domain_id}/apps/add", 'post', {'optional_params' => [{'name' => 'environment_variables'}, {'name' => 'cartridges[][name]'}, {'name' => 'cartridges[][url]'}]} ],
455
466
  ['LIST_APPLICATIONS', "domains/#{domain_id}/apps/", 'get' ],
@@ -540,6 +551,7 @@ module RHC::Rest::Mock
540
551
  end
541
552
  end
542
553
  @domains = []
554
+ @teams = []
543
555
  @user = MockRestUser.new(self, config.username)
544
556
  @api = MockRestApi.new(self, config)
545
557
  @version = version
@@ -557,13 +569,17 @@ module RHC::Rest::Mock
557
569
  @domains
558
570
  end
559
571
 
572
+ def teams(opts={})
573
+ @teams
574
+ end
575
+
560
576
  def api_version_negotiated
561
577
  @version
562
578
  end
563
579
 
564
580
  def cartridges
565
581
  premium_embedded = MockRestCartridge.new(self, "premium_cart", "embedded")
566
- premium_embedded.usage_rate = 0.05
582
+ premium_embedded.usage_rates = {0.05 => []}
567
583
 
568
584
  [MockRestCartridge.new(self, "mock_cart-1", "embedded"), # code should sort this to be after standalone
569
585
  MockRestCartridge.new(self, "mock_standalone_cart-1", "standalone"),
@@ -579,6 +595,15 @@ module RHC::Rest::Mock
579
595
  ]
580
596
  end
581
597
 
598
+ def add_team(name, extra=false)
599
+ t = MockRestTeam.new(self, name)
600
+ if extra
601
+ t.attributes['members'] = [{'owner' => true, 'name' => 'a_user_name'}]
602
+ end
603
+ @teams << t
604
+ t
605
+ end
606
+
582
607
  def add_domain(id, extra=false)
583
608
  d = MockRestDomain.new(self, id)
584
609
  if extra
@@ -662,6 +687,35 @@ module RHC::Rest::Mock
662
687
  end
663
688
  end
664
689
 
690
+ class MockRestTeam < RHC::Rest::Team
691
+ include Helpers
692
+ def initialize(client, name, id="123")
693
+ super({}, client)
694
+ @id = id
695
+ @name = name
696
+ @members = []
697
+ self.attributes = {:links => mock_response_links(mock_team_links(id))}
698
+ end
699
+
700
+ def destroy
701
+ raise RHC::OperationNotSupportedException.new("The server does not support deleting this resource.") unless supports? 'DELETE'
702
+ client.teams.delete_if { |t| t.name == @name }
703
+ end
704
+
705
+ def init_members
706
+ @members ||= []
707
+ attributes['members'] ||= []
708
+ self
709
+ end
710
+
711
+ def add_member(member)
712
+ (@members ||= []) << member
713
+ (attributes['members'] ||= []) << member.attributes
714
+ self
715
+ end
716
+
717
+ end
718
+
665
719
  class MockRestDomain < RHC::Rest::Domain
666
720
  include Helpers
667
721
  def initialize(client, id)
@@ -792,7 +846,7 @@ module RHC::Rest::Mock
792
846
  self.attributes = {:links => mock_response_links(mock_app_links('mock_domain_0', 'mock_app_0')), :messages => []}
793
847
  self.gear_count = 5
794
848
  types = Array(type)
795
- cart = add_cartridge(types.first, false) if types.first
849
+ cart = add_cartridge(types.first, true) if types.first
796
850
  if scale
797
851
  cart.supported_scales_to = (cart.scales_to = -1)
798
852
  cart.supported_scales_from = (cart.scales_from = 2)
@@ -800,6 +854,7 @@ module RHC::Rest::Mock
800
854
  cart.scales_with = "haproxy-1.4"
801
855
  prox = add_cartridge('haproxy-1.4')
802
856
  prox.collocated_with = [types.first]
857
+ prox.tags = ['web_proxy']
803
858
  end
804
859
  types.drop(1).each{ |c| add_cartridge(c, false) }
805
860
  @framework = types.first
@@ -959,7 +1014,7 @@ module RHC::Rest::Mock
959
1014
  class MockRestCartridge < RHC::Rest::Cartridge
960
1015
  include Helpers
961
1016
 
962
- attr_accessor :usage_rate
1017
+ attr_accessor :usage_rates
963
1018
 
964
1019
  def initialize(client, name, type, app=nil, tags=[], properties=[{'type' => 'cart_data', 'name' => 'connection_url', 'value' => "http://fake.url" }], description=nil)
965
1020
  super({}, client)
@@ -975,7 +1030,7 @@ module RHC::Rest::Mock
975
1030
  @current_scale = 1
976
1031
  @gear_profile = 'small'
977
1032
  @additional_gear_storage = 5
978
- @usage_rate = 0.0
1033
+ @usage_rates = {}
979
1034
  end
980
1035
 
981
1036
  def destroy