rhc 1.23.7 → 1.24.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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