rhc 1.2.7 → 1.3.8

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 (97) hide show
  1. data/bin/rhc +6 -8
  2. data/bin/rhc-chk +23 -10
  3. data/features/domain.feature +1 -1
  4. data/features/lib/rhc_helper.rb +3 -2
  5. data/features/lib/rhc_helper/api.rb +7 -0
  6. data/features/lib/rhc_helper/app.rb +8 -10
  7. data/features/lib/rhc_helper/domain.rb +2 -1
  8. data/features/lib/rhc_helper/runnable.rb +2 -24
  9. data/features/sshkey.feature +3 -3
  10. data/features/step_definitions/cartridge_steps.rb +6 -6
  11. data/features/step_definitions/client_steps.rb +0 -1
  12. data/features/step_definitions/sshkey_steps.rb +2 -2
  13. data/features/support/before_hooks.rb +0 -1
  14. data/features/support/env.rb +5 -3
  15. data/lib/rhc-common.rb +1 -1
  16. data/lib/rhc.rb +9 -8
  17. data/lib/rhc/auth.rb +3 -0
  18. data/lib/rhc/auth/basic.rb +54 -0
  19. data/lib/rhc/cartridge_helpers.rb +11 -5
  20. data/lib/rhc/cli.rb +4 -2
  21. data/lib/rhc/command_runner.rb +35 -30
  22. data/lib/rhc/commands.rb +127 -18
  23. data/lib/rhc/commands/account.rb +24 -0
  24. data/lib/rhc/commands/alias.rb +1 -1
  25. data/lib/rhc/commands/app.rb +210 -209
  26. data/lib/rhc/commands/apps.rb +22 -0
  27. data/lib/rhc/commands/base.rb +10 -77
  28. data/lib/rhc/commands/cartridge.rb +35 -35
  29. data/lib/rhc/commands/domain.rb +20 -13
  30. data/lib/rhc/commands/git_clone.rb +30 -0
  31. data/lib/rhc/commands/{port-forward.rb → port_forward.rb} +3 -3
  32. data/lib/rhc/commands/server.rb +28 -16
  33. data/lib/rhc/commands/setup.rb +18 -1
  34. data/lib/rhc/commands/snapshot.rb +4 -4
  35. data/lib/rhc/commands/sshkey.rb +4 -18
  36. data/lib/rhc/commands/tail.rb +32 -9
  37. data/lib/rhc/config.rb +168 -99
  38. data/lib/rhc/context_helper.rb +22 -9
  39. data/lib/rhc/core_ext.rb +41 -1
  40. data/lib/rhc/exceptions.rb +21 -5
  41. data/lib/rhc/git_helpers.rb +81 -0
  42. data/lib/rhc/help_formatter.rb +21 -1
  43. data/lib/rhc/helpers.rb +222 -87
  44. data/lib/rhc/output_helpers.rb +94 -110
  45. data/lib/rhc/rest.rb +15 -198
  46. data/lib/rhc/rest/api.rb +88 -0
  47. data/lib/rhc/rest/application.rb +29 -30
  48. data/lib/rhc/rest/attributes.rb +27 -0
  49. data/lib/rhc/rest/base.rb +29 -33
  50. data/lib/rhc/rest/cartridge.rb +42 -20
  51. data/lib/rhc/rest/client.rb +351 -89
  52. data/lib/rhc/rest/domain.rb +7 -13
  53. data/lib/rhc/rest/gear_group.rb +1 -1
  54. data/lib/rhc/rest/key.rb +7 -2
  55. data/lib/rhc/rest/mock.rb +609 -0
  56. data/lib/rhc/rest/user.rb +6 -2
  57. data/lib/rhc/{ssh_key_helpers.rb → ssh_helpers.rb} +58 -28
  58. data/lib/rhc/{targz.rb → tar_gz.rb} +0 -0
  59. data/lib/rhc/usage_templates/command_help.erb +4 -1
  60. data/lib/rhc/usage_templates/help.erb +24 -11
  61. data/lib/rhc/usage_templates/options_help.erb +14 -0
  62. data/lib/rhc/wizard.rb +283 -213
  63. data/spec/keys/example.pem +23 -0
  64. data/spec/keys/example_private.pem +27 -0
  65. data/spec/keys/server.pem +19 -0
  66. data/spec/rest_spec_helper.rb +3 -371
  67. data/spec/rhc/auth_spec.rb +226 -0
  68. data/spec/rhc/cli_spec.rb +41 -14
  69. data/spec/rhc/command_spec.rb +44 -15
  70. data/spec/rhc/commands/account_spec.rb +41 -0
  71. data/spec/rhc/commands/alias_spec.rb +16 -15
  72. data/spec/rhc/commands/app_spec.rb +115 -92
  73. data/spec/rhc/commands/apps_spec.rb +39 -0
  74. data/spec/rhc/commands/cartridge_spec.rb +134 -112
  75. data/spec/rhc/commands/domain_spec.rb +31 -86
  76. data/spec/rhc/commands/git_clone_spec.rb +56 -0
  77. data/spec/rhc/commands/{port-forward_spec.rb → port_forward_spec.rb} +27 -32
  78. data/spec/rhc/commands/server_spec.rb +28 -3
  79. data/spec/rhc/commands/setup_spec.rb +29 -11
  80. data/spec/rhc/commands/snapshot_spec.rb +4 -3
  81. data/spec/rhc/commands/sshkey_spec.rb +24 -56
  82. data/spec/rhc/commands/tail_spec.rb +26 -9
  83. data/spec/rhc/commands/threaddump_spec.rb +12 -11
  84. data/spec/rhc/config_spec.rb +211 -164
  85. data/spec/rhc/context_spec.rb +2 -0
  86. data/spec/rhc/helpers_spec.rb +242 -46
  87. data/spec/rhc/rest_application_spec.rb +42 -28
  88. data/spec/rhc/rest_client_spec.rb +110 -93
  89. data/spec/rhc/rest_spec.rb +220 -131
  90. data/spec/rhc/targz_spec.rb +1 -1
  91. data/spec/rhc/wizard_spec.rb +435 -624
  92. data/spec/spec.opts +1 -1
  93. data/spec/spec_helper.rb +140 -6
  94. data/spec/wizard_spec_helper.rb +326 -0
  95. metadata +163 -143
  96. data/lib/rhc/client.rb +0 -17
  97. data/lib/rhc/git_helper.rb +0 -59
@@ -1,9 +1,9 @@
1
- require 'rhc/rest/base'
1
+ require 'ostruct'
2
2
 
3
3
  module RHC
4
4
  module Rest
5
5
  class User < Base
6
- attr_reader :login
6
+ define_attr :login, :plan_id, :max_gears, :consumed_gears
7
7
 
8
8
  def add_key(name, content, type)
9
9
  debug "Add key #{name} of type #{type} for user #{login}"
@@ -20,6 +20,10 @@ module RHC
20
20
  #TODO do a regex caomparison
21
21
  keys.detect { |key| key.name == name }
22
22
  end
23
+
24
+ def capabilities
25
+ @capabilities ||= OpenStruct.new attribute('capabilities')
26
+ end
23
27
  end
24
28
  end
25
29
  end
@@ -18,9 +18,45 @@
18
18
  require 'net/ssh'
19
19
  require 'rhc/vendor/sshkey'
20
20
 
21
-
22
21
  module RHC
23
- module SSHKeyHelpers
22
+ module SSHHelpers
23
+ # Public: Run ssh command on remote host
24
+ #
25
+ # host - The String of the remote hostname to ssh to.
26
+ # username - The String username of the remote user to ssh as.
27
+ # command - The String command to run on the remote host.
28
+ #
29
+ # Examples
30
+ #
31
+ # ssh_ruby('myapp-t.rhcloud.com',
32
+ # '109745632b514e9590aa802ec015b074',
33
+ # 'rhcsh tail -f $OPENSHIFT_LOG_DIR/*"')
34
+ # # => true
35
+ #
36
+ # Returns true on success
37
+ def ssh_ruby(host, username, command)
38
+ debug "Opening Net::SSH connection to #{host}, #{username}, #{command}"
39
+ Net::SSH.start(host, username) do |session|
40
+ #:nocov:
41
+ session.open_channel do |channel|
42
+ channel.request_pty do |ch, success|
43
+ say "pty could not be obtained" unless success
44
+ end
45
+
46
+ channel.on_data do |ch, data|
47
+ puts data
48
+ end
49
+ channel.exec command
50
+ end
51
+ session.loop
52
+ #:nocov:
53
+ end
54
+ rescue Errno::ECONNREFUSED => e
55
+ raise RHC::SSHConnectionRefused.new(host, username)
56
+ rescue SocketError => e
57
+ raise RHC::ConnectionFailed, "The connection to #{host} failed: #{e.message}"
58
+ end
59
+
24
60
  # Public: Generate an SSH key and store it in ~/.ssh/id_rsa
25
61
  #
26
62
  # type - The String type RSA or DSS.
@@ -29,7 +65,7 @@ module RHC
29
65
  #
30
66
  # Examples
31
67
  #
32
- # generate_ssh_key_ruby()
68
+ # generate_ssh_key_ruby
33
69
  # # => /home/user/.ssh/id_rsa.pub
34
70
  #
35
71
  # Returns nil on failure or public key location as a String on success
@@ -37,22 +73,25 @@ module RHC
37
73
  key = RHC::Vendor::SSHKey.generate(:type => type,
38
74
  :bits => bits,
39
75
  :comment => comment)
40
- ssh_dir = "#{RHC::Config.home_dir}/.ssh"
41
- if File.exists?("#{ssh_dir}/id_rsa")
42
- say "SSH key already exists: #{ssh_dir}/id_rsa. Reusing..."
76
+ ssh_dir = RHC::Config.ssh_dir
77
+ priv_key = RHC::Config.ssh_priv_key_file_path
78
+ pub_key = RHC::Config.ssh_pub_key_file_path
79
+
80
+ if File.exists?(priv_key)
81
+ say "SSH key already exists: #{priv_key}. Reusing..."
43
82
  return nil
44
83
  else
45
84
  unless File.exists?(ssh_dir)
46
85
  FileUtils.mkdir_p(ssh_dir)
47
86
  File.chmod(0700, ssh_dir)
48
87
  end
49
- File.open("#{ssh_dir}/id_rsa", 'w') {|f| f.write(key.private_key)}
50
- File.chmod(0600, "#{ssh_dir}/id_rsa")
51
- File.open("#{ssh_dir}/id_rsa.pub", 'w') {|f| f.write(key.ssh_public_key)}
88
+ File.open(priv_key, 'w') {|f| f.write(key.private_key)}
89
+ File.chmod(0600, priv_key)
90
+ File.open(pub_key, 'w') {|f| f.write(key.ssh_public_key)}
52
91
 
53
- ssh_add if exe?('ssh-add')
92
+ ssh_add
54
93
  end
55
- "#{ssh_dir}/id_rsa.pub"
94
+ pub_key
56
95
  end
57
96
 
58
97
  def exe?(executable)
@@ -60,15 +99,6 @@ module RHC
60
99
  File.executable?(File.join(directory, executable.to_s))
61
100
  end
62
101
  end
63
-
64
- # Public: Format SSH key's core attributes (name, type, fingerprint)
65
- # in a given ERB template
66
- #
67
- # key - an object to be formatted
68
- # template - ERB template
69
- def format(key, template)
70
- template.result(binding)
71
- end
72
102
 
73
103
 
74
104
  # For Net::SSH versions (< 2.0.11) that does not have
@@ -88,15 +118,13 @@ module RHC
88
118
  rescue NoMethodError, NotImplementedError => e
89
119
  ssh_keygen_fallback key
90
120
  return nil
91
- # :nocov: no reason to cover this case
92
121
  rescue OpenSSL::PKey::PKeyError, Net::SSH::Exception => e
93
122
  error e.message
94
123
  return nil
95
- # :nocov:
96
124
  end
97
-
125
+
98
126
  def fingerprint_for_default_key
99
- fingerprint_for_local_key RHC::Config::ssh_pub_key_file_path
127
+ fingerprint_for_local_key RHC::Config.ssh_pub_key_file_path
100
128
  end
101
129
 
102
130
  # for an SSH public key specified by 'key', return a triple
@@ -114,15 +142,17 @@ module RHC
114
142
  end
115
143
 
116
144
  def ssh_key_triple_for_default_key
117
- ssh_key_triple_for RHC::Config::ssh_pub_key_file_path
145
+ ssh_key_triple_for RHC::Config.ssh_pub_key_file_path
118
146
  end
119
147
 
120
148
  private
121
149
 
122
150
  def ssh_add
123
- #:nocov:
124
- `ssh-add 2&>1`
125
- #:nocov:
151
+ if exe?('ssh-add')
152
+ #:nocov:
153
+ `ssh-add 2&>1`
154
+ #:nocov:
155
+ end
126
156
  end
127
157
  end
128
158
  end
File without changes
@@ -11,9 +11,12 @@ Options for <%= @command.name %>
11
11
  <% end -%>
12
12
 
13
13
  Global Options
14
- <% for option in @global_options -%><% next if option[:hide] -%>
14
+ <% for option in @global_options -%><% next if option[:hide] || @command.options.any?{ |o| (o[:switches] & option[:switches]).present? } -%>
15
15
  <%= "%-25s %s\n" % [option[:switches].join(', '), option[:description]] -%>
16
16
  <% end -%>
17
+
18
+ See 'rhc help options' for a full list of command-line options.
19
+
17
20
  <% else -%>
18
21
 
19
22
  List of Actions
@@ -1,19 +1,32 @@
1
- Usage: <%= Array(program :name).first %> <resource> [<action>] [--help] [<args>]
1
+ Usage: rhc [--help] [--version] [--debug] <command> [<args>]
2
+
2
3
  <%= Array(program :description).first %>
3
4
 
4
- List of Commands and Resources
5
- <% for name, command in @commands.sort -%>
6
- <%- unless alias? name or name == "help" or name.include?(" ") or command.summary.nil? -%> <%="%-18s %s\n" % [command.name, command.summary || command.description] %><%- end -%>
7
- <%- end -%>
5
+ <%
6
+ remaining = Hash[@commands.dup.select{ |name, command| not alias?(name) and not command.summary.blank? }]
7
+ basic = remaining.slice!('setup', 'app create', 'apps', 'cartridge list', 'cartridge add', 'server')
8
+ begin -%>
9
+ Getting started:
10
+ <% for name, command in basic -%>
11
+ <%="%-18s %s\n" % [command.name, command.summary || command.description] -%>
12
+ <% end %>
13
+ <% end unless basic.empty?
8
14
 
9
- Global Options
10
- <%- unless @options.nil? or @options.empty? -%>
11
- <% for option in @options -%><% next if option[:hide] -%>
12
- <%= "%-25s %s\n" % [option[:switches].join(', '), option[:description]] -%>
13
- <% end -%>
15
+ debugging = remaining.slice!('app restart', 'app show', 'tail', 'port-forward', 'threaddump', 'snapshot', 'git-clone')
16
+ begin -%>
17
+ Working with apps:
18
+ <% for name, command in debugging -%>
19
+ <%="%-18s %s\n" % [command.name, command.summary || command.description] -%>
20
+ <% end
21
+ end unless debugging.empty?
22
+ %>
23
+ Management commands:
24
+ <% for name, command in remaining.sort -%>
25
+ <%- unless alias? name or name.include?(' ') or command.summary.nil? -%> <%="%-18s %s\n" % [command.name, command.summary || command.description] %><%- end -%>
14
26
  <%- end -%>
15
27
 
16
- See '<%= Array(program :name).first %> help <resource>' for more specifics on each resource or command.
28
+ See '<%= Array(program :name).first %> help <command>' for more information on a specific command. See 'rhc help options' for a list of global command-line options and information about the config file.
29
+
17
30
  <% if program :help -%>
18
31
  <% for title, body in program(:help) %>
19
32
  <%= $terminal.color title.to_s.upcase, :bold %>:
@@ -0,0 +1,14 @@
1
+ The following options can be passed to any command:
2
+
3
+ <% for option in options -%>
4
+ <%= "%-25s %s\n" % [option[:switches].join(', '), option[:description]] -%>
5
+ <% end -%>
6
+
7
+ The following configuration options are stored in the <%= RHC::Helpers.system_path('.openshift/express.conf') %> file in your home directory. Passing any of these options to 'rhc setup' will save that value to the file:
8
+
9
+ <%= "%-25s %s\n" % ["Option", "In Config"] -%>
10
+
11
+ <% for key, value in RHC::Config::OPTIONS -%>
12
+ <% next if value.nil? -%>
13
+ <%= "%-25s %s\n" % ["--#{key.to_s.gsub('_','-')}", value[0] || key] -%>
14
+ <% end -%>
@@ -1,16 +1,15 @@
1
- require 'rhc-common'
2
1
  require 'rhc/helpers'
3
- require 'rhc/ssh_key_helpers'
2
+ require 'rhc/ssh_helpers'
3
+ require 'rhc/git_helpers'
4
4
  require 'highline/system_extensions'
5
- require 'net/ssh'
6
5
  require 'fileutils'
7
6
  require 'socket'
8
7
 
9
8
  module RHC
10
9
  class Wizard
11
10
  include HighLine::SystemExtensions
12
- include RHC::Helpers
13
- include RHC::SSHKeyHelpers
11
+
12
+ DEFAULT_MAX_LENGTH = 16
14
13
 
15
14
  STAGES = [:greeting_stage,
16
15
  :login_stage,
@@ -18,6 +17,7 @@ module RHC
18
17
  :config_ssh_key_stage,
19
18
  :upload_ssh_key_stage,
20
19
  :install_client_tools_stage,
20
+ :setup_test_stage,
21
21
  :config_namespace_stage,
22
22
  :show_app_info_stage,
23
23
  :finalize_stage]
@@ -25,15 +25,15 @@ module RHC
25
25
  STAGES
26
26
  end
27
27
 
28
- def initialize(config, opts=nil)
28
+ attr_reader :rest_client
29
+
30
+ #
31
+ # Running the setup wizard may change the contents of opts and config if
32
+ # the create_config_stage completes successfully.
33
+ #
34
+ def initialize(config=RHC::Config.new, opts=Commander::Command::Options.new)
29
35
  @config = config
30
- @config_path = config.config_path
31
- if @libra_server.nil?
32
- @libra_server = config['libra_server']
33
- # if not set, set to default
34
- @libra_server = @libra_server ? @libra_server : "openshift.redhat.com"
35
- end
36
- @config.config_user opts.rhlogin if opts && opts.rhlogin
36
+ @options = opts
37
37
  @debug = opts.debug if opts
38
38
  end
39
39
 
@@ -47,16 +47,53 @@ module RHC
47
47
  # Returns nil on failure or true on success
48
48
  def run
49
49
  stages.each do |stage|
50
- # FIXME: cleanup if we fail
51
50
  debug "Running #{stage}"
52
- if (self.send stage).nil?
51
+ if self.send(stage).nil?
53
52
  return nil
54
53
  end
55
54
  end
56
55
  true
57
56
  end
58
57
 
58
+ protected
59
+ include RHC::Helpers
60
+ include RHC::SSHHelpers
61
+ include RHC::GitHelpers
62
+ attr_reader :config, :options
63
+ attr_accessor :auth, :user
64
+
65
+ def openshift_server
66
+ options.server || config['libra_server'] || "openshift.redhat.com"
67
+ end
68
+
69
+ def new_client_for_options
70
+ client_from_options({
71
+ :auth => auth,
72
+ })
73
+ end
74
+
75
+ def auth
76
+ @auth ||= RHC::Auth::Basic.new(options)
77
+ end
78
+
79
+ def username
80
+ auth.send(:username)
81
+ end
82
+ def password
83
+ auth.send(:password)
84
+ end
85
+
59
86
  private
87
+
88
+ # cache SSH keys from the REST client
89
+ def ssh_keys
90
+ @ssh_keys ||= rest_client.sshkeys
91
+ end
92
+
93
+ # clear SSH key cache
94
+ def clear_ssh_keys_cache
95
+ @ssh_keys = nil
96
+ end
60
97
 
61
98
  def greeting_stage
62
99
  info "OpenShift Client Tools (RHC) Setup Wizard"
@@ -69,67 +106,67 @@ module RHC
69
106
  end
70
107
 
71
108
  def login_stage
72
- # get_password adds an extra untracked newline so set :bottom to -1
73
- section(:top => 1, :bottom => -1) do
74
- if @config.has_opts? && @config.opts_login
75
- @username = @config.opts_login
76
- say "Using #{@username}"
77
- else
78
- @username = ask("Login to #{@libra_server}: ") do |q|
79
- q.default = RHC::Config.default_rhlogin
109
+ say "Using #{options.rhlogin} to login to #{openshift_server}" if options.rhlogin
110
+
111
+ self.rest_client = new_client_for_options
112
+
113
+ begin
114
+ rest_client.api
115
+ rescue RHC::Rest::CertificateVerificationFailed => e
116
+ debug "Certificate validation failed: #{e.reason}"
117
+ unless options.insecure
118
+ if RHC::Rest::SelfSignedCertificate === e
119
+ warn "The server's certificate is self-signed, which means that a secure connection can't be established to '#{openshift_server}'."
120
+ else
121
+ warn "The server's certificate could not be verified, which means that a secure connection can't be established to '#{openshift_server}'."
122
+ end
123
+ if openshift_online_server?
124
+ paragraph{ warn "This may mean that a server between you and OpenShift is capable of accessing information sent from this client. If you wish to continue without checking the certificate, please pass the -k (or --insecure) option to this command." }
125
+ return
126
+ else
127
+ paragraph{ warn "You may bypass this check, but any data you send to the server could be intercepted by others." }
128
+ return unless agree "Connect without checking the certificate? (yes|no): "
129
+ options.insecure = true
130
+ self.rest_client = new_client_for_options
131
+ retry
80
132
  end
81
133
  end
82
-
83
- @password = RHC::Config.password
84
- @password = RHC::get_password if @password.nil?
85
134
  end
86
135
 
87
- # instantiate a REST client that stages can use
88
- end_point = "https://#{@libra_server}/broker/rest/api"
89
- @rest_client = RHC::Rest::Client.new(end_point, @username, @password, @debug)
90
-
91
- # confirm that the REST client can connect
92
- return false unless @rest_client.user
93
-
136
+ self.user = rest_client.user
94
137
  true
95
138
  end
96
139
 
97
140
  def create_config_stage
98
- if !File.exists? @config_path
99
- FileUtils.mkdir_p File.dirname(@config_path)
100
- File.open(@config_path, 'w') do |file|
101
- file.puts <<EOF
102
- # Default user login
103
- default_rhlogin='#{@username}'
104
-
105
- # Server API
106
- libra_server = '#{@libra_server}'
107
- EOF
108
-
141
+ paragraph do
142
+ if File.exists? config.path
143
+ backup = "#{@config.path}.bak"
144
+ FileUtils.cp(config.path, backup)
145
+ FileUtils.rm(config.path)
109
146
  end
110
147
 
111
- paragraph do
112
- say "Created local config file: " + @config_path
113
- say "The #{File.basename(@config_path)} file contains user configuration, and can be transferred to different computers."
114
- end
148
+ say "Saving configuration to #{system_path(config.path)} ... "
115
149
 
116
- true
150
+ changed = Commander::Command::Options.new(options)
151
+ changed.rhlogin = username
152
+ changed.password = password
153
+
154
+ FileUtils.mkdir_p File.dirname(config.path)
155
+ config.save!(changed)
156
+ options.__replace__(changed)
157
+
158
+ success "done"
117
159
  end
118
160
 
119
- # Read in @config_path now that it exists (was skipped before because it did
120
- # not exist
121
- RHC::Config.set_local_config(@config_path)
161
+ true
122
162
  end
123
163
 
124
164
  def config_ssh_key_stage
125
- if RHC::Config.should_run_ssh_wizard?
126
- paragraph do
127
- say "No SSH keys were found. We will generate a pair of keys for you."
128
- end
129
- ssh_pub_key_file_path = generate_ssh_key_ruby()
130
- paragraph do
131
- say " Created: #{ssh_pub_key_file_path}\n\n"
132
- end
165
+ if config.should_run_ssh_wizard?
166
+ paragraph{ say "No SSH keys were found. We will generate a pair of keys for you." }
167
+
168
+ ssh_pub_key_file_path = generate_ssh_key_ruby
169
+ paragraph{ say " Created: #{ssh_pub_key_file_path}" }
133
170
  end
134
171
  true
135
172
  end
@@ -137,60 +174,54 @@ EOF
137
174
  # return true if the account has the public key defined by
138
175
  # RHC::Config::ssh_pub_key_file_path
139
176
  def ssh_key_uploaded?
140
- @ssh_keys ||= @rest_client.sshkeys
141
- @ssh_keys.any? { |k| k.fingerprint == fingerprint_for_default_key }
177
+ ssh_keys.any? { |k| k.fingerprint == fingerprint_for_default_key }
142
178
  end
143
179
 
144
180
  def existing_keys_info
145
- return unless @ssh_keys
146
- # TODO: This ERB format is shared with RHC::Commands::Sshkey; should be refactored
147
- @ssh_keys.inject("Current Keys: \n") do |result, key|
148
- erb = ::RHC::Helpers.ssh_key_display_format
149
- result += format(key, erb)
150
- end
181
+ return unless ssh_keys
182
+ indent{ ssh_keys.each{ |key| paragraph{ display_key(key) } } }
151
183
  end
152
184
 
153
185
  def get_preferred_key_name
154
- paragraph do
155
- say "You can enter a name for your key, or leave it blank to use the default name. " \
156
- "Using the same name as an existing key will overwrite the old key."
157
- end
158
186
  key_name = 'default'
159
187
 
160
- if @ssh_keys.empty?
188
+ if ssh_keys.empty?
161
189
  paragraph do
162
- say <<-DEFAULT_KEY_UPLOAD_MSG
163
- Since you do not have any keys associated with your OpenShift account,
164
- your new key will be uploaded as the 'default' key
165
- DEFAULT_KEY_UPLOAD_MSG
190
+ info "Since you do not have any keys associated with your OpenShift account, "\
191
+ "your new key will be uploaded as the 'default' key."
166
192
  end
167
193
  else
168
- section(:top => 1) { say existing_keys_info }
194
+ paragraph do
195
+ say "You can enter a name for your key, or leave it blank to use the default name. " \
196
+ "Using the same name as an existing key will overwrite the old key."
197
+ end
198
+
199
+ paragraph { existing_keys_info }
169
200
 
170
201
  key_fingerprint = fingerprint_for_default_key
171
202
  unless key_fingerprint
172
203
  paragraph do
173
- say <<-CONFIG_KEY_INVALID
174
- Your ssh public key at #{RHC::Config.ssh_pub_key_file_path} is invalid or unreadable.
175
- The setup can not continue until you manually remove or fix both of your
176
- public and private keys id_rsa keys.
177
- CONFIG_KEY_INVALID
204
+ say "Your ssh public key at #{system_path(RHC::Config.ssh_pub_key_file_path)} is invalid or unreadable. "\
205
+ "Setup can not continue until you manually remove or fix your "\
206
+ "public and private keys id_rsa keys."
178
207
  end
179
208
  return nil
180
209
  end
210
+
181
211
  hostname = Socket.gethostname.gsub(/\..*\z/,'')
182
- username = @username ? @username.gsub(/@.*/, '') : ''
183
- pubkey_base_name = "#{username}#{hostname}".gsub(/[^A-Za-z0-9]/,'').slice(0,16)
184
- pubkey_default_name = find_unique_key_name(
185
- :keys => @ssh_keys,
212
+ userkey = username ? username.gsub(/@.*/, '') : ''
213
+ pubkey_base_name = "#{userkey}#{hostname}".gsub(/[^A-Za-z0-9]/,'').slice(0,16)
214
+ default_name = find_unique_key_name(
215
+ :keys => ssh_keys,
186
216
  :base => pubkey_base_name,
187
- :max_length => RHC::DEFAULT_MAX_LENGTH
217
+ :max_length => DEFAULT_MAX_LENGTH
188
218
  )
189
-
219
+
190
220
  paragraph do
191
- key_name = ask("Provide a name for this key: ") do |q|
192
- q.default = pubkey_default_name
193
- q.validate = lambda { |p| RHC::check_key(p) }
221
+ key_name = ask("Provide a name for this key: ") do |q|
222
+ q.default = default_name
223
+ q.validate = /^[0-9a-zA-Z]*$/
224
+ q.responses[:not_valid] = 'Your key name must be letters and numbers only.'
194
225
  end
195
226
  end
196
227
  end
@@ -201,12 +232,12 @@ public and private keys id_rsa keys.
201
232
  # given the base name and the maximum length,
202
233
  # find a name that does not clash with what is in opts[:keys]
203
234
  def find_unique_key_name(opts)
204
- keys = opts[:keys] || @ssh_keys
235
+ keys = opts[:keys] || ssh_keys
205
236
  base = opts[:base] || 'default'
206
- max = opts[:max_length] || RHC::DEFAULT_MAX_LENGTH # in rhc-common.rb
237
+ max = opts[:max_length] || DEFAULT_MAX_LENGTH
207
238
  key_name_suffix = 1
208
239
  candidate = base
209
- while @ssh_keys.detect { |k| k.name == candidate }
240
+ while ssh_keys.detect { |k| k.name == candidate }
210
241
  candidate = base.slice(0, max - key_name_suffix.to_s.length) +
211
242
  key_name_suffix.to_s
212
243
  key_name_suffix += 1
@@ -219,35 +250,42 @@ public and private keys id_rsa keys.
219
250
  return false unless key_name
220
251
 
221
252
  type, content, comment = ssh_key_triple_for_default_key
222
- say "type: %s\ncontent: %s\nfingerprint: %s" % [type, content, fingerprint_for_default_key]
253
+ indent do
254
+ say table([['Type:', type], ['Fingerprint:', fingerprint_for_default_key]])
255
+ end
223
256
 
224
- if !@ssh_keys.empty? && @ssh_keys.any? { |k| k.name == key_name }
225
- say "Key with the name #{key_name} already exists. Updating... "
226
- key = @rest_client.find_key(key_name)
227
- key.update(type, content)
228
- else
229
- say "Uploading key '#{key_name}' from #{RHC::Config::ssh_pub_key_file_path}"
230
- @rest_client.add_key key_name, content, type
257
+ paragraph do
258
+ if !ssh_keys.empty? && ssh_keys.any? { |k| k.name == key_name }
259
+ clear_ssh_keys_cache
260
+ say "Key with the name #{key_name} already exists. Updating ... "
261
+ key = rest_client.find_key(key_name)
262
+ key.update(type, content)
263
+ else
264
+ clear_ssh_keys_cache
265
+ say "Uploading key '#{key_name}' from #{system_path(RHC::Config::ssh_pub_key_file_path)} ... "
266
+ rest_client.add_key key_name, content, type
267
+ end
268
+ success "done"
231
269
  end
232
-
270
+
233
271
  true
234
272
  end
235
273
 
236
274
  def upload_ssh_key_stage
237
275
  return true if ssh_key_uploaded?
238
276
 
239
- upload = false
240
- section do
241
- upload = agree "Your public ssh key must be uploaded to the OpenShift server. Would you like us to upload it for you? (yes/no) "
277
+ upload = paragraph do
278
+ agree "Your public SSH key must be uploaded to the OpenShift server to access code. Upload now? (yes|no) "
242
279
  end
243
280
 
244
281
  if upload
245
282
  upload_ssh_key
246
283
  else
247
284
  paragraph do
248
- say "You can upload your ssh key at a later time using the 'rhc sshkey' command"
285
+ info "You can upload your SSH key at a later time using the 'rhc sshkey' command"
249
286
  end
250
287
  end
288
+
251
289
  true
252
290
  end
253
291
 
@@ -266,9 +304,6 @@ public and private keys id_rsa keys.
266
304
  if windows?
267
305
  windows_install
268
306
  else
269
- paragraph do
270
- say "We will now check to see if you have the necessary client tools installed."
271
- end
272
307
  generic_unix_install_check
273
308
  end
274
309
  true
@@ -276,14 +311,22 @@ public and private keys id_rsa keys.
276
311
 
277
312
  def config_namespace_stage
278
313
  paragraph do
279
- say "Checking for your namespace ... "
280
- domains = @rest_client.domains
314
+ say "Checking your namespace ... "
315
+ domains = rest_client.domains
281
316
  if domains.length == 0
282
- say "not found"
317
+ warn "none"
318
+
319
+ paragraph do
320
+ say "Your namespace is unique to your account and is the suffix of the " \
321
+ "public URLs we assign to your applications. You may configure your " \
322
+ "namespace here or leave it blank and use 'rhc domain create' to " \
323
+ "create a namespace later. You will not be able to create " \
324
+ "applications without first creating a namespace."
325
+ end
326
+
283
327
  ask_for_namespace
284
328
  else
285
- say "found namespace:"
286
- domains.each { |d| say " #{d.id}" }
329
+ success domains.map(&:id).join(', ')
287
330
  end
288
331
  end
289
332
 
@@ -293,39 +336,99 @@ public and private keys id_rsa keys.
293
336
  def show_app_info_stage
294
337
  section do
295
338
  say "Checking for applications ... "
296
- end
297
-
298
- apps = @rest_client.domains.inject([]) do |list, domain|
299
- list += domain.applications
300
- end
301
-
302
- if !apps.nil? and !apps.empty?
303
- section(:bottom => 1) do
304
- say "found"
305
- apps.each do |app|
306
- if app.app_url.nil? && app.u
307
- say " * #{app.name} - no public url (you need to add a namespace)"
308
- else
309
- say " * #{app.name} - #{app.app_url}"
339
+
340
+ if !applications.nil? and !applications.empty?
341
+ success "found #{applications.length}"
342
+
343
+ paragraph do
344
+ indent do
345
+ say table(applications.map do |app|
346
+ [app.name, app.app_url]
347
+ end)
310
348
  end
311
349
  end
350
+ else
351
+ info "none"
352
+
353
+ paragraph{ say "Run 'rhc app create' to create your first application." }
354
+ paragraph do
355
+ application_types = rest_client.find_cartridges :type => "standalone"
356
+ say table(application_types.sort {|a,b| a.display_name <=> b.display_name }.map do |cart|
357
+ [' ', cart.display_name, "rhc app create <app name> #{cart.name}"]
358
+ end).join("\n")
359
+ end
312
360
  end
313
- else
314
- section(:bottom => 1) { say "none found" }
315
361
  paragraph do
316
- say "Run 'rhc app create' to create your first application.\n\n"
317
- say "Below is a list of the types of application you can create: \n"
318
-
319
- application_types = @rest_client.find_cartridges :type => "standalone"
320
- application_types.sort {|a,b| a.name <=> b.name }.each do |cart|
321
- say " * #{cart.name} - rhc app create <app name> #{cart.name}"
362
+ indent do
363
+ say "You are using #{color(self.user.consumed_gears.to_s, :green)} of #{color(self.user.max_gears.to_s, :green)} total gears" if user.max_gears.is_a? Fixnum
364
+ say "The following gear sizes are available to you: #{self.user.capabilities.gear_sizes.join(', ')}" if user.capabilities.gear_sizes.present?
322
365
  end
323
366
  end
324
367
  end
368
+ true
369
+ end
370
+
371
+ # Perform basic tests to ensure that setup is sane
372
+ # search for private methods starting with "test_" and execute them
373
+ # in alphabetical order
374
+ # NOTE: The order itself is not important--the tests should be independent.
375
+ # However, the hash order is unpredictable in Ruby 1.8, and is preserved in
376
+ # 1.9. There are two similar tests that can fail under the same conditions,
377
+ # and this makes the spec results inconsistent between 1.8 and 1.9.
378
+ # Thus, we force an order with #sort to ensure spec passage on both.
379
+ def setup_test_stage
380
+ say "Checking common problems "
381
+ tests = private_methods.select {|m| m.to_s.start_with? 'test_'}
382
+ tests.sort.each do |test|
383
+ send(test)
384
+ end.tap do |pass|
385
+ success(' done') if pass
386
+ end
325
387
 
326
388
  true
327
389
  end
328
390
 
391
+ # cached list of applications needed for test stage
392
+ def applications
393
+ @applications ||= rest_client.domains.map(&:applications).flatten
394
+ end
395
+
396
+ ###
397
+ # tests for specific user errors
398
+
399
+ def test_private_key_mode
400
+ pub_key_file = RHC::Config.ssh_pub_key_file_path
401
+ private_key_file = RHC::Config.ssh_priv_key_file_path
402
+ if File.exist?(private_key_file)
403
+ report_result (File.exist?(private_key_file) and File.stat(private_key_file).mode.to_s(8) =~ /[4-7]00$/),
404
+ "#{private_key_file} should not be accessible by anyone but the current user"
405
+ else
406
+ #:nocov:
407
+ true
408
+ #:nocov:
409
+ end
410
+ end
411
+
412
+ # test connectivity an app
413
+ def test_ssh_connectivity
414
+ unless ssh_key_uploaded?
415
+ return true
416
+ end
417
+
418
+ applications.take(1).each do |app|
419
+ begin
420
+ ssh = Net::SSH.start(app.host, app.uuid, :timeout => 60)
421
+ return true
422
+ rescue => e
423
+ report_result(ssh, "An SSH connection could not be established to #{app.host}. Your SSH configuration may not be correct, or the application may not be responding. #{e.message} (#{e.class})", false)
424
+ return false
425
+ ensure
426
+ ssh.close if ssh
427
+ end
428
+ end
429
+ end
430
+
431
+ ###
329
432
  def finalize_stage
330
433
  paragraph do
331
434
  say "The OpenShift client tools have been configured on your computer. " \
@@ -338,59 +441,51 @@ public and private keys id_rsa keys.
338
441
 
339
442
  def config_namespace(namespace)
340
443
  # skip if string is empty
341
- paragraph do
342
- if namespace.nil? or namespace.chomp.length == 0
343
- say "Skipping! You may create a namespace using 'rhc domain create'"
344
- return true
345
- end
444
+ if namespace.nil? or namespace.chomp.length == 0
445
+ paragraph{ info "You may create a namespace later through 'rhc domain create'" }
446
+ return true
447
+ end
346
448
 
347
- begin
348
- domain = @rest_client.add_domain(namespace)
449
+ begin
450
+ domain = rest_client.add_domain(namespace)
349
451
 
350
- say "Your domain name '#{domain.id}' has been successfully created"
351
- rescue RHC::Rest::ValidationException => e
352
- say e.message
353
- return false
354
- end
452
+ success "Your domain name '#{domain.id}' has been successfully created"
453
+ rescue RHC::Rest::ValidationException => e
454
+ error e.message || "Unknown error during namespace creation."
455
+ return false
355
456
  end
356
457
  true
357
458
  end
358
459
 
359
460
  def ask_for_namespace
360
- paragraph do
361
- say "Your namespace is unique to your account and is the suffix of the " \
362
- "public URLs we assign to your applications. You may configure your " \
363
- "namespace here or leave it blank and use 'rhc domain create' to " \
364
- "create a namespace later. You will not be able to create " \
365
- "applications without first creating a namespace."
366
- end
367
-
368
461
  # Ask for a namespace at least once, configure the namespace if a valid,
369
462
  # non-blank string is provided.
370
- namespace = nil
371
- first_pass = true
372
- while first_pass or !config_namespace namespace do
373
- first_pass = false
374
- paragraph do
375
- namespace = ask "Please enter a namespace or leave this blank if you wish to skip this step:" do |q|
376
- q.validate = lambda{ |p| RHC::check_namespace p }
377
- q.responses[:not_valid] = 'The namespace value must contain only letters and/or numbers (A-Za-z0-9):'
463
+ namespace = nil
464
+ paragraph do
465
+ begin
466
+ namespace = ask "Please enter a namespace (letters and numbers only) |<none>|: " do |q|
467
+ #q.validate = lambda{ |p| RHC::check_namespace p }
468
+ #q.responses[:not_valid] = 'The namespace value must contain only letters and/or numbers (A-Za-z0-9):'
378
469
  q.responses[:ask_on_error] = ''
379
470
  end
380
- end
471
+ end while !config_namespace(namespace)
381
472
  end
382
473
  end
383
474
 
384
- def generic_unix_install_check(show_action=true)
385
- section(:top => 1) { say "Checking for git ... " } if show_action
386
- if has_git?
387
- section(:bottom => 1) { say "found" }
388
- else
389
- section(:bottom => 1) { say "needs to be installed" }
390
- paragraph do
391
- say "Automated installation of client tools is not supported for " \
392
- "your platform. You will need to manually install git for full " \
393
- "OpenShift functionality."
475
+ def generic_unix_install_check
476
+ paragraph do
477
+ say "Checking for git ... "
478
+
479
+ if has_git?
480
+ success("found #{git_version}") rescue success('found')
481
+ else
482
+ warn "needs to be installed"
483
+
484
+ paragraph do
485
+ say "Automated installation of client tools is not supported for " \
486
+ "your platform. You will need to manually install git for full " \
487
+ "OpenShift functionality."
488
+ end
394
489
  end
395
490
  end
396
491
  end
@@ -399,10 +494,11 @@ public and private keys id_rsa keys.
399
494
  # Finding windows executables is hard since they can get installed
400
495
  # in non standard directories. Punt on this for now and simply
401
496
  # print out urls and some instructions
402
- say <<EOF
497
+ warn <<EOF
498
+
403
499
  In order to fully interact with OpenShift you will need to install and configure a git client if you have not already done so.
404
500
 
405
- Documentation for installing other tools you will need for OpenShift can be found at https://#{@libra_server}/app/getting_started#install_client_tools
501
+ Documentation for installing other tools you will need for OpenShift can be found at https://openshift.redhat.com/community/developers/install-the-client-tools
406
502
 
407
503
  We recommend these free applications:
408
504
 
@@ -412,45 +508,19 @@ We recommend these free applications:
412
508
  EOF
413
509
  end
414
510
 
415
- def git_version_exec
416
- `git --version 2>&1`
417
- end
418
-
419
- def has_git?
420
- git_version_exec
421
- $?.success?
422
- rescue
423
- false
424
- end
425
-
426
511
  def debug?
427
512
  @debug
428
513
  end
514
+
515
+ protected
516
+ attr_writer :rest_client
429
517
  end
430
518
 
431
519
  class RerunWizard < Wizard
432
- def initialize(config, login=nil)
433
- super(config, login)
434
- end
435
-
436
- def create_config_stage
437
- if File.exists? @config_path
438
- backup = "#{@config_path}.bak"
439
- paragraph do
440
- say "Configuration file #{@config_path} already exists, " \
441
- "backing up to #{backup}"
442
- end
443
- FileUtils.cp(@config_path, backup)
444
- FileUtils.rm(@config_path)
445
- end
446
- super
447
- true
448
- end
449
520
 
450
521
  def finalize_stage
451
- paragraph do
452
- say "Thank you for setting up your system. You can rerun this at any time " \
453
- "by calling 'rhc setup'."
522
+ section :top => 1 do
523
+ success "Your client tools are now configured."
454
524
  end
455
525
  true
456
526
  end
@@ -463,9 +533,9 @@ EOF
463
533
  STAGES
464
534
  end
465
535
 
466
- def initialize(rest_client)
467
- @rest_client = rest_client
468
- super RHC::Config
536
+ def initialize(rest_client, config, options)
537
+ self.rest_client = rest_client
538
+ super config, options
469
539
  end
470
540
  end
471
541
  end