startapp 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/features/app_feature.rb +25 -0
  4. data/features/core_feature.rb +4 -2
  5. data/features/members_feature.rb +21 -1
  6. data/lib/rhc/commands/app.rb +64 -13
  7. data/lib/rhc/commands/apps.rb +7 -3
  8. data/lib/rhc/commands/create.rb +2 -1
  9. data/lib/rhc/commands/domain.rb +2 -1
  10. data/lib/rhc/commands/member.rb +348 -76
  11. data/lib/rhc/commands/scp.rb +15 -6
  12. data/lib/rhc/commands/snapshot.rb +5 -80
  13. data/lib/rhc/commands/team.rb +103 -0
  14. data/lib/rhc/context_helper.rb +57 -15
  15. data/lib/rhc/exceptions.rb +26 -2
  16. data/lib/rhc/git_helpers.rb +1 -1
  17. data/lib/rhc/helpers.rb +1 -1
  18. data/lib/rhc/output_helpers.rb +33 -8
  19. data/lib/rhc/rest/api.rb +1 -1
  20. data/lib/rhc/rest/cartridge.rb +6 -1
  21. data/lib/rhc/rest/client.rb +124 -5
  22. data/lib/rhc/rest/membership.rb +51 -6
  23. data/lib/rhc/rest/mock.rb +82 -8
  24. data/lib/rhc/rest/team.rb +34 -0
  25. data/lib/rhc/rest.rb +1 -0
  26. data/lib/rhc/ssh_helpers.rb +88 -0
  27. data/lib/rhc/usage_templates/command_help.erb +13 -3
  28. data/lib/rhc/usage_templates/command_syntax_help.erb +11 -0
  29. data/spec/direct_execution_helper.rb +1 -0
  30. data/spec/rhc/command_spec.rb +22 -3
  31. data/spec/rhc/commands/app_spec.rb +81 -3
  32. data/spec/rhc/commands/apps_spec.rb +41 -1
  33. data/spec/rhc/commands/domain_spec.rb +1 -1
  34. data/spec/rhc/commands/member_spec.rb +393 -22
  35. data/spec/rhc/commands/scp_spec.rb +14 -3
  36. data/spec/rhc/commands/snapshot_spec.rb +1 -2
  37. data/spec/rhc/commands/team_spec.rb +191 -0
  38. data/spec/rhc/helpers_spec.rb +2 -0
  39. data/spec/rhc/rest_client_spec.rb +23 -0
  40. data/spec/rhc/rest_spec.rb +5 -5
  41. data/spec/spec_helper.rb +36 -0
  42. metadata +8 -2
@@ -5,7 +5,7 @@ module RHC::Commands
5
5
  summary "Save the current state of your application locally"
6
6
  syntax "<action>"
7
7
  description <<-DESC
8
- Snapshots allow you to export the current state of your OpenShift application
8
+ Snapshots allow you to export the current state of your StartApp application
9
9
  into an archive on your local system, and then to restore it later.
10
10
 
11
11
  The snapshot archive contains the Git repository, dumps of any attached databases,
@@ -21,49 +21,19 @@ module RHC::Commands
21
21
  syntax "<application> [--filepath FILE] [--ssh path_to_ssh_executable]"
22
22
  takes_application :argument => true
23
23
  option ["-f", "--filepath FILE"], "Local path to save tarball (default: ./$APPNAME.tar.gz)"
24
- option ["--deployment"], "Snapshot as a deployable file which can be deployed with 'rhc deploy'"
24
+ option ["--deployment"], "Snapshot as a deployable file which can be deployed with 'app deploy'"
25
25
  option ["--ssh PATH"], "Full path to your SSH executable with additional options"
26
26
  alias_action :"app snapshot save", :root_command => true, :deprecated => true
27
27
  def save(app)
28
- ssh = check_ssh_executable! options.ssh
28
+
29
29
  rest_app = find_app
30
30
 
31
31
  raise RHC::DeploymentsNotSupportedException.new if options.deployment && !rest_app.supports?("DEPLOY")
32
32
 
33
- ssh_uri = URI.parse(rest_app.ssh_url)
34
33
  filename = options.filepath ? options.filepath : "#{rest_app.name}.tar.gz"
35
34
 
36
- snapshot_cmd = options.deployment ? 'gear archive-deployment' : 'snapshot'
37
- ssh_cmd = "#{ssh} #{ssh_uri.user}@#{ssh_uri.host} '#{snapshot_cmd}' > #{filename}"
38
- debug ssh_cmd
39
-
40
- say "Pulling down a snapshot to #{filename}..."
35
+ save_snapshot(rest_app, filename, options.deployment, options.ssh)
41
36
 
42
- begin
43
- if !RHC::Helpers.windows?
44
- status, output = exec(ssh_cmd)
45
- if status != 0
46
- debug output
47
- raise RHC::SnapshotSaveException.new "Error in trying to save snapshot. You can try to save manually by running:\n#{ssh_cmd}"
48
- end
49
- else
50
- Net::SSH.start(ssh_uri.host, ssh_uri.user) do |ssh|
51
- File.open(filename, 'wb') do |file|
52
- ssh.exec! "snapshot" do |channel, stream, data|
53
- if stream == :stdout
54
- file.write(data)
55
- else
56
- debug data
57
- end
58
- end
59
- end
60
- end
61
- end
62
- rescue Timeout::Error, Errno::EADDRNOTAVAIL, Errno::EADDRINUSE, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Net::SSH::AuthenticationFailed => e
63
- debug e.backtrace
64
- raise RHC::SnapshotSaveException.new "Error in trying to save snapshot. You can try to save manually by running:\n#{ssh_cmd}"
65
- end
66
- results { say "Success" }
67
37
  0
68
38
  end
69
39
 
@@ -74,59 +44,14 @@ module RHC::Commands
74
44
  option ["--ssh PATH"], "Full path to your SSH executable with additional options"
75
45
  alias_action :"app snapshot restore", :root_command => true, :deprecated => true
76
46
  def restore(app)
77
- ssh = check_ssh_executable! options.ssh
78
47
  rest_app = find_app
79
48
  filename = options.filepath ? options.filepath : "#{rest_app.name}.tar.gz"
80
49
 
81
50
  if File.exists? filename
82
-
83
- include_git = RHC::Helpers.windows? ? true : RHC::TarGz.contains(filename, './*/git')
84
- ssh_uri = URI.parse(rest_app.ssh_url)
85
-
86
- ssh_cmd = "cat '#{filename}' | #{ssh} #{ssh_uri.user}@#{ssh_uri.host} 'restore#{include_git ? ' INCLUDE_GIT' : ''}'"
87
-
88
- say "Restoring from snapshot #{filename}..."
89
- debug ssh_cmd
90
-
91
- begin
92
- if !RHC::Helpers.windows?
93
- status, output = exec(ssh_cmd)
94
- if status != 0
95
- debug output
96
- raise RHC::SnapshotRestoreException.new "Error in trying to restore snapshot. You can try to restore manually by running:\n#{ssh_cmd}"
97
- end
98
- else
99
- ssh = Net::SSH.start(ssh_uri.host, ssh_uri.user)
100
- ssh.open_channel do |channel|
101
- channel.exec("restore#{include_git ? ' INCLUDE_GIT' : ''}") do |ch, success|
102
- channel.on_data do |ch, data|
103
- say data
104
- end
105
- channel.on_extended_data do |ch, type, data|
106
- say data
107
- end
108
- channel.on_close do |ch|
109
- say "Terminating..."
110
- end
111
- File.open(filename, 'rb') do |file|
112
- file.chunk(1024) do |chunk|
113
- channel.send_data chunk
114
- end
115
- end
116
- channel.eof!
117
- end
118
- end
119
- ssh.loop
120
- end
121
- rescue Timeout::Error, Errno::EADDRNOTAVAIL, Errno::EADDRINUSE, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, Net::SSH::AuthenticationFailed => e
122
- debug e.backtrace
123
- raise RHC::SnapshotRestoreException.new "Error in trying to restore snapshot. You can try to restore manually by running:\n#{ssh_cmd}"
124
- end
125
-
51
+ restore_snapshot(rest_app, filename, options.ssh)
126
52
  else
127
53
  raise RHC::SnapshotRestoreException.new "Archive not found: #{filename}"
128
54
  end
129
- results { say "Success" }
130
55
  0
131
56
  end
132
57
 
@@ -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 'app create-team'.
17
+
18
+ To add members to an existing team, use the 'app add-member' command.
19
+
20
+ To list members of an existing team, use the 'app 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 'app 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,32 +91,40 @@ 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
 
69
110
  def find_app(opts={})
70
- if id = options.application_id
111
+ if id = options.application_id.presence
71
112
  if opts.delete(:with_gear_groups)
72
113
  return rest_client.find_application_by_id_gear_groups(id, opts)
73
114
  else
74
115
  return rest_client.find_application_by_id(id, opts)
75
116
  end
76
117
  end
118
+ option = (opts && opts[:app]) || options.app
77
119
  domain, app =
78
- if options.app
79
- if options.app =~ /\//
80
- options.app.split(/\//)
120
+ if option
121
+ if option =~ /\//
122
+ option.split(/\//)
81
123
  else
82
- [options.namespace || namespace_context, options.app]
124
+ [options.namespace || namespace_context, option]
83
125
  end
84
126
  end
85
- if app && domain
127
+ if app.present? && domain.present?
86
128
  if opts.delete(:with_gear_groups)
87
129
  rest_client.find_application_gear_groups(domain, app, opts)
88
130
  else
@@ -67,6 +67,24 @@ module RHC
67
67
  end
68
68
  end
69
69
 
70
+ class TeamsNotSupportedException < Exception
71
+ def initialize(message="Server does not support teams")
72
+ super message, 161
73
+ end
74
+ end
75
+
76
+ class TeamNotFoundException < Exception
77
+ def initialize(message="Team not found")
78
+ super message, 162
79
+ end
80
+ end
81
+
82
+ class MemberNotFoundException < Exception
83
+ def initialize(message="Member not found")
84
+ super message, 163
85
+ end
86
+ end
87
+
70
88
  class GitPermissionDenied < GitException; end
71
89
  class GitDirectoryExists < GitException; end
72
90
 
@@ -138,6 +156,12 @@ module RHC
138
156
  end
139
157
  end
140
158
 
159
+ class AppCloneNotSupportedException < Exception
160
+ def initialize(message="The server does not support cloning apps")
161
+ super message, 134
162
+ end
163
+ end
164
+
141
165
  class MissingScalingValueException < Exception
142
166
  def initialize(message="Must provide either a min or max value for scaling")
143
167
  super message
@@ -190,13 +214,13 @@ module RHC
190
214
  end
191
215
 
192
216
  class ChangeMembersOnResourceNotSupported < Exception
193
- 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.")
194
218
  super message, 1
195
219
  end
196
220
  end
197
221
 
198
222
  class MembersNotSupported < Exception
199
- 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.")
200
224
  super message, 1
201
225
  end
202
226
  end
@@ -60,7 +60,7 @@ module RHC
60
60
  # :nocov: These all call external binaries so test them in cucumber
61
61
  def git_config_get(key)
62
62
  return nil unless has_git?
63
-
63
+
64
64
  config_get_cmd = "#{git_cmd} config --get #{key}"
65
65
  value = %x[#{config_get_cmd}].strip
66
66
  debug "Git config '#{config_get_cmd}' returned '#{value}'"
data/lib/rhc/helpers.rb CHANGED
@@ -437,7 +437,7 @@ module RHC
437
437
  def run_with_tee(cmd)
438
438
  status, stdout, stderr = nil
439
439
 
440
- if windows?
440
+ if windows? || jruby?
441
441
  #:nocov: TODO: Test block
442
442
  system(cmd)
443
443
  status = $?.exitstatus
@@ -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
@@ -26,7 +44,7 @@ module RHC
26
44
  #---------------------------
27
45
  # Application information
28
46
  #---------------------------
29
- def display_app(app, cartridges=nil, properties=nil)
47
+ def display_app(app, cartridges=nil, properties=nil, verbose=false)
30
48
  paragraph do
31
49
  header [app.name, "@ #{app.app_url}", "(uuid: #{app.uuid})"] do
32
50
  section(:bottom => 1) do
@@ -43,7 +61,7 @@ module RHC
43
61
  :aliases]),
44
62
  :delete => true
45
63
  end
46
- cartridges.each{ |c| section(:bottom => 1){ display_cart(c) } } if cartridges
64
+ cartridges.each{ |c| section(:bottom => 1){ display_cart(c, verbose ? :verbose : []) } } if cartridges
47
65
  end
48
66
  end
49
67
  end
@@ -69,6 +87,8 @@ module RHC
69
87
  format_scaling_info(cart.scaling)
70
88
  elsif cart.shares_gears?
71
89
  "Located with #{cart.collocated_with.join(", ")}"
90
+ elsif cart.external? && cart.current_scale == 0
91
+ "none (external service)"
72
92
  else
73
93
  "%d %s" % [format_value(:current_scale, cart.current_scale), format_value(:gear_profile, cart.gear_profile)]
74
94
  end
@@ -84,13 +104,18 @@ module RHC
84
104
  #---------------------------
85
105
 
86
106
  def display_cart(cart, *properties)
107
+ verbose = properties.delete(:verbose)
87
108
  say format_table \
88
109
  format_cart_header(cart),
89
- get_properties(cart, *properties).
90
- concat([[:downloaded_cartridge_url, cart.url]]).
91
- concat([[cart.scalable? ? :scaling : :gears, format_cart_gears(cart)]]).
92
- concat(cart.properties.map{ |p| ["#{table_heading(p['name'])}:", p['value']] }.sort{ |a,b| a[0] <=> b[0] }).
93
- concat(cart.environment_variables.present? ? [[:environment_variables, cart.environment_variables.map{|item| "#{item[:name]}=#{item[:value]}" }.sort.join(', ')]] : []),
110
+ get_properties(cart, *properties).
111
+ concat(verbose && cart.custom? ? [[:description, cart.description.strip]] : []).
112
+ concat([[:downloaded_cartridge_url, cart.url]]).
113
+ concat(verbose && cart.custom? ? [[:version, cart.version]] : []).
114
+ concat(verbose && cart.custom? && cart.license.strip.downcase != 'unknown' ? [[:license, cart.license]] : []).
115
+ concat(cart.custom? ? [[:website, cart.website]] : []).
116
+ concat([[cart.scalable? ? :scaling : :gears, format_cart_gears(cart)]]).
117
+ concat(cart.properties.map{ |p| ["#{table_heading(p['name'])}:", p['value']] }.sort{ |a,b| a[0] <=> b[0] }).
118
+ concat(cart.environment_variables.present? ? [[:environment_variables, cart.environment_variables.map{|item| "#{item[:name]}=#{item[:value]}" }.sort.join(', ')]] : []),
94
119
  :delete => true
95
120
 
96
121
  say format_usage_message(cart) if cart.usage_rate?
@@ -248,7 +273,7 @@ module RHC
248
273
  when :activations
249
274
  value.collect{|item| date(item.created_at.to_s)}.join("\n")
250
275
  when :auto_deploy
251
- value ? 'auto (on git push)' : "manual (use 'rhc deploy')"
276
+ value ? 'auto (on git push)' : "manual (use 'app deploy')"
252
277
  else
253
278
  case value
254
279
  when Array then value.empty? ? '<none>' : value.join(', ')
data/lib/rhc/rest/api.rb CHANGED
@@ -67,7 +67,7 @@ module RHC
67
67
  if !api_version_match?
68
68
  warn "WARNING: API version mismatch. This client supports #{client_api_versions.join(', ')} but
69
69
  server at #{URI.parse(client.url).host} supports #{@server_api_versions.join(', ')}."
70
- warn "The client version may be outdated; please consider updating 'rhc'. We will continue, but you may encounter problems."
70
+ warn "The client version may be outdated; please consider updating 'app'. We will continue, but you may encounter problems."
71
71
  end
72
72
  end
73
73
 
@@ -5,7 +5,8 @@ module RHC
5
5
 
6
6
  define_attr :type, :name, :display_name, :properties, :gear_profile, :status_messages, :scales_to, :scales_from, :scales_with,
7
7
  :current_scale, :supported_scales_to, :supported_scales_from, :tags, :description, :collocated_with, :base_gear_storage,
8
- :additional_gear_storage, :url, :environment_variables, :gear_size, :automatic_updates
8
+ :additional_gear_storage, :url, :environment_variables, :gear_size, :automatic_updates,
9
+ :version, :license, :website, :description
9
10
 
10
11
  def scalable?
11
12
  supported_scales_to != supported_scales_from
@@ -31,6 +32,10 @@ module RHC
31
32
  v
32
33
  end
33
34
 
35
+ def external?
36
+ tags.include?('external')
37
+ end
38
+
34
39
  def shares_gears?
35
40
  Array(collocated_with).present?
36
41
  end