rhc 0.92.11 → 0.93.18

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 (55) hide show
  1. data/README.md +1 -9
  2. data/Rakefile +1 -55
  3. data/bin/rhc +31 -1
  4. data/bin/rhc-app +62 -127
  5. data/bin/rhc-chk +6 -7
  6. data/bin/rhc-create-app +8 -8
  7. data/bin/rhc-create-domain +6 -86
  8. data/bin/rhc-ctl-app +3 -3
  9. data/bin/rhc-ctl-domain +4 -4
  10. data/bin/rhc-domain +16 -18
  11. data/bin/rhc-domain-info +3 -3
  12. data/bin/rhc-port-forward +127 -24
  13. data/bin/rhc-snapshot +7 -46
  14. data/bin/rhc-sshkey +13 -79
  15. data/bin/rhc-tail-files +16 -8
  16. data/lib/rhc-common.rb +406 -230
  17. data/lib/rhc-rest.rb +3 -3
  18. data/lib/rhc-rest/client.rb +1 -1
  19. data/lib/rhc-rest/domain.rb +1 -1
  20. data/lib/rhc.rb +20 -0
  21. data/lib/rhc/cli.rb +38 -0
  22. data/lib/rhc/client.rb +15 -0
  23. data/lib/rhc/commands.rb +32 -0
  24. data/lib/rhc/commands/base.rb +67 -0
  25. data/lib/rhc/commands/server.rb +24 -0
  26. data/lib/rhc/config.rb +141 -0
  27. data/lib/rhc/core_ext.rb +15 -0
  28. data/lib/rhc/helpers.rb +142 -0
  29. data/lib/rhc/json.rb +52 -0
  30. data/lib/rhc/targz.rb +46 -0
  31. data/lib/rhc/vendor/okjson.rb +600 -0
  32. data/lib/rhc/vendor/zliby.rb +628 -0
  33. data/lib/rhc/wizard.rb +579 -0
  34. data/spec/rhc/assets/foo.txt +1 -0
  35. data/spec/rhc/assets/targz_corrupted.tar.gz +1 -0
  36. data/spec/rhc/assets/targz_sample.tar.gz +0 -0
  37. data/spec/rhc/cli_spec.rb +24 -0
  38. data/spec/rhc/command_spec.rb +88 -0
  39. data/spec/rhc/commands/server_spec.rb +39 -0
  40. data/spec/rhc/helpers_spec.rb +171 -0
  41. data/spec/rhc/json_spec.rb +30 -0
  42. data/spec/rhc/targz_spec.rb +42 -0
  43. data/spec/rhc/wizard_spec.rb +426 -0
  44. data/spec/spec_helper.rb +192 -0
  45. data/test/functional/application_test.rb +71 -0
  46. data/test/functional/domain_test.rb +123 -0
  47. data/test/functional/test_credentials.rb +5 -0
  48. data/test/sample-usage.rb +122 -0
  49. data/test/support/server.rb +14 -0
  50. data/test/support/testcase.rb +3 -0
  51. data/test/test_helper.rb +4 -0
  52. data/test/unit/command_test.rb +19 -0
  53. metadata +181 -29
  54. data/ext/mkrf_conf.rb +0 -58
  55. data/lib/rhc +0 -115
data/lib/rhc/wizard.rb ADDED
@@ -0,0 +1,579 @@
1
+ require 'rhc-common'
2
+ require 'rhc/helpers'
3
+ require 'highline/system_extensions'
4
+ require 'net/ssh'
5
+
6
+ # ruby 1.8 -> 1.9 magic
7
+ begin
8
+ require 'ftools'
9
+ rescue LoadError
10
+ require 'fileutils'
11
+ end
12
+
13
+ module RHC
14
+ class Wizard
15
+ include HighLine::SystemExtensions
16
+ include RHC::Helpers
17
+
18
+ @@stages = [:greeting_stage,
19
+ :login_stage,
20
+ :create_config_stage,
21
+ :config_ssh_key_stage,
22
+ :upload_ssh_key_stage,
23
+ :install_client_tools_stage,
24
+ :config_namespace_stage,
25
+ :show_app_info_stage,
26
+ :finalize_stage]
27
+
28
+ def initialize(config_path)
29
+ @config_path = config_path
30
+ end
31
+
32
+ # Public: Runs the setup wizard to make sure ~/.openshift and ~/.ssh are correct
33
+ #
34
+ # Examples
35
+ #
36
+ # wizard.run()
37
+ # # => true
38
+ #
39
+ # Returns nil on failure or true on success
40
+ def run
41
+ @@stages.each do |stage|
42
+ # FIXME: cleanup if we fail
43
+ if (self.send stage).nil?
44
+ return nil
45
+ end
46
+ end
47
+ true
48
+ end
49
+
50
+ private
51
+
52
+ def stages
53
+ @@stages
54
+ end
55
+
56
+ def greeting_stage
57
+ paragraph do
58
+ say "Starting Interactive Setup for OpenShift's command line interface"
59
+ end
60
+
61
+ paragraph do
62
+ say "It looks like you have not configured or used OpenShift " \
63
+ "client tools on this computer. " \
64
+ "We'll help you configure the client tools with a few quick questions. " \
65
+ "You can skip this in the future by copying your configuration files to other machines you use to manage your OpenShift account:"
66
+ end
67
+
68
+ paragraph do
69
+ say "#{@config_path}"
70
+ say "#{RHC::Config.home_dir}/.ssh/"
71
+ end
72
+
73
+ true
74
+ end
75
+
76
+ def login_stage
77
+ if @libra_server.nil?
78
+ @libra_server = get_var('libra_server')
79
+ # if not set, set to default
80
+ @libra_server = @libra_server ? @libra_server : "openshift.redhat.com"
81
+ end
82
+
83
+ # get_password adds an extra untracked newline so set :bottom to -1
84
+ section(:top => 1, :bottom => -1) do
85
+ @username = ask("To connect to #{@libra_server} enter your OpenShift login (email or Red Hat login id): ")
86
+ @password = RHC::get_password
87
+ end
88
+
89
+ # Confirm username / password works:
90
+ user_info = RHC::get_user_info(@libra_server, @username, @password, RHC::Config.default_proxy, true)
91
+
92
+ # instantiate a REST client that stages can use
93
+ # TODO: use only REST calls in the wizard
94
+ end_point = "https://#{@libra_server}/broker/rest/api"
95
+ @rest_client = Rhc::Rest::Client.new(end_point, @username, @password)
96
+
97
+ true
98
+ end
99
+
100
+ def create_config_stage
101
+ if !File.exists? @config_path
102
+ FileUtils.mkdir_p File.dirname(@config_path)
103
+ File.open(@config_path, 'w') do |file|
104
+ file.puts <<EOF
105
+ # Default user login
106
+ default_rhlogin='#{@username}'
107
+
108
+ # Server API
109
+ libra_server = '#{@libra_server}'
110
+ EOF
111
+
112
+ end
113
+
114
+ paragraph do
115
+ say "Created local config file: " + @config_path
116
+ say "The express.conf file contains user configuration, and can be transferred to different computers."
117
+ end
118
+
119
+ true
120
+ end
121
+
122
+ # Read in @config_path now that it exists (was skipped before because it did
123
+ # not exist
124
+ RHC::Config.set_local_config(@config_path)
125
+ end
126
+
127
+ def config_ssh_key_stage
128
+ @ssh_priv_key_file_path = "#{RHC::Config.home_dir}/.ssh/id_rsa"
129
+ @ssh_pub_key_file_path = "#{RHC::Config.home_dir}/.ssh/id_rsa.pub"
130
+ unless File.exists? @ssh_priv_key_file_path
131
+ paragraph do
132
+ say "No SSH keys were found. We will generate a pair of keys for you."
133
+ end
134
+ @ssh_pub_key_file_path = generate_ssh_key_ruby()
135
+ paragraph do
136
+ say " Created: #{@ssh_pub_key_file_path}\n\n"
137
+ end
138
+ end
139
+ true
140
+ end
141
+
142
+ def ssh_key_uploaded?
143
+ @ssh_keys = RHC::get_ssh_keys(@libra_server, @username, @password, RHC::Config.default_proxy)
144
+ additional_ssh_keys = @ssh_keys['keys']
145
+
146
+ local_fingerprint = nil
147
+ begin
148
+ local_fingerprint = Net::SSH::KeyFactory.load_public_key(@ssh_pub_key_file_path).fingerprint
149
+ rescue NoMethodError #older net/ssh (mac for example)
150
+ local_fingerprint = `ssh-keygen -lf #{@ssh_pub_key_file_path}`.split(' ')[1]
151
+ end
152
+
153
+ return true if @ssh_keys['fingerprint'] == local_fingerprint
154
+ additional_ssh_keys.each do |name, keyval|
155
+ return true if keyval['fingerprint'] == local_fingerprint
156
+ end
157
+
158
+ false
159
+ end
160
+
161
+ def upload_ssh_key
162
+ additional_ssh_keys = @ssh_keys['keys']
163
+ known_keys = []
164
+
165
+ paragraph do
166
+ say "You can enter a name for your key, or leave it blank to use the default name. " \
167
+ "Using the same name as an existing key will overwrite the old key."
168
+ end
169
+
170
+ section(:top => 1) { say 'Current Keys:' }
171
+ if @ssh_keys['fingerprint'].nil?
172
+ section(:bottom => 1) { say " None" }
173
+ else
174
+ known_keys << 'default'
175
+ section do
176
+ say " default - #{@ssh_keys['fingerprint']}"
177
+ end
178
+ end
179
+
180
+ if additional_ssh_keys && additional_ssh_keys.kind_of?(Hash)
181
+ section(:bottom => 1) do
182
+ additional_ssh_keys.each do |name, keyval|
183
+ say " #{name} - #{keyval['fingerprint']}"
184
+ known_keys.push(name)
185
+ end
186
+ end
187
+ end
188
+
189
+ if @ssh_keys['fingerprint'].nil?
190
+ key_name = "default"
191
+ paragraph do
192
+ say "Since you do not have any keys associated with your OpenShift account, " \
193
+ "your new key will be uploaded as the default key"
194
+ end
195
+ else
196
+ key_fingerprint = nil
197
+ key_valid = true
198
+ begin
199
+ key_fingerprint = Net::SSH::KeyFactory.load_public_key(@ssh_pub_key_file_path).fingerprint
200
+ rescue NoMethodError #older net/ssh (mac for example)
201
+ key_fingerprint = `ssh-keygen -lf #{@ssh_pub_key_file_path}`.split(' ')[1]
202
+ if $?.exitstatus != 0
203
+ key_valid = false
204
+ end
205
+ rescue Net::SSH::Exception
206
+ rescue NotImplementedError
207
+ key_valid = false
208
+ end
209
+
210
+ if ! key_valid
211
+ paragraph do
212
+ say "Your ssh public key at #{@ssh_pub_key_file_path} can not be read. " \
213
+ "The setup can not continue until you manually remove or fix both of your " \
214
+ "public and private keys id_rsa keys."
215
+ end
216
+ return false
217
+ end
218
+
219
+ pubkey_default_name = key_fingerprint[0, 12].gsub(/[^0-9a-zA-Z]/,'')
220
+ paragraph do
221
+ key_name = ask("Provide a name for this key: ") do |q|
222
+ q.default = pubkey_default_name
223
+ q.validate = lambda { |p| RHC::check_key(p) }
224
+ end
225
+ end
226
+ end
227
+
228
+ if known_keys.include?(key_name)
229
+ section do
230
+ say "Key already exists! Updating key #{key_name} .. "
231
+ add_or_update_key('update', key_name, @ssh_pub_key_file_path, @username, @password)
232
+ end
233
+ else
234
+ section do
235
+ say "Sending new key #{key_name} .. "
236
+ add_or_update_key('add', key_name, @ssh_pub_key_file_path, @username, @password)
237
+ end
238
+ end
239
+ true
240
+ end
241
+
242
+ def upload_ssh_key_stage
243
+ unless ssh_key_uploaded?
244
+ upload = false
245
+ section do
246
+ upload = agree "Your public ssh key must be uploaded to the OpenShift server. Would you like us to upload it for you? (yes/no) "
247
+ end
248
+
249
+ if upload
250
+ upload_ssh_key
251
+ else
252
+ paragraph do
253
+ say "You can upload your ssh key at a later time using the 'rhc sshkey' command"
254
+ end
255
+ end
256
+ end
257
+ true
258
+ end
259
+
260
+ ##
261
+ # Attempts install various tools if they aren't currently installed on the
262
+ # users system. If we can't automate the install, alert the user that they
263
+ # should manually install them
264
+ #
265
+ # On Unix we rely on PackageKit (which mostly just covers modern Linux flavors
266
+ # such as Fedora, Suse, Debian and Ubuntu). On Windows we will give instructions
267
+ # and links for the tools they should install
268
+ #
269
+ # Unix Tools:
270
+ # git
271
+ #
272
+ # Windows Tools:
273
+ # msysgit (Git for Windows)
274
+ # TortoiseGIT (Windows Explorer integration)
275
+ #
276
+ def install_client_tools_stage
277
+ if windows?
278
+ windows_install
279
+ else
280
+ # we use command line tools for dbus since the dbus gem is not cross
281
+ # platform and compiles itself on the host system when installed
282
+ paragraph do
283
+ say "We will now check to see if you have the necessary client tools installed."
284
+ end
285
+ if has_dbus_send?
286
+ package_kit_install
287
+ else
288
+ generic_unix_install_check
289
+ end
290
+ end
291
+ true
292
+ end
293
+
294
+ def config_namespace_stage
295
+ paragraph do
296
+ say "Checking for your namespace ... "
297
+ user_info = RHC::get_user_info(@libra_server, @username, @password, RHC::Config.default_proxy, true)
298
+ domains = user_info['user_info']['domains']
299
+ if domains.length == 0
300
+ say "not found"
301
+ ask_for_namespace
302
+ else
303
+ say "found namespace:"
304
+ domains.each { |d| say " #{d['namespace']}" }
305
+ end
306
+ end
307
+
308
+ true
309
+ end
310
+
311
+ def show_app_info_stage
312
+ section do
313
+ say "Checking for applications ... "
314
+ end
315
+ user_info = RHC::get_user_info(@libra_server, @username, @password, RHC::Config.default_proxy, true)
316
+ apps = user_info['app_info']
317
+ if !apps.nil? and !apps.empty?
318
+ section(:bottom => 1) do
319
+ say "found"
320
+ apps.each do |app_name, app_info|
321
+ app_url = nil
322
+ unless user_info['user_info']['domains'].empty?
323
+ app_url = "http://#{app_name}-#{user_info['user_info']['domains'][0]['namespace']}.#{user_info['user_info']['rhc_domain']}/"
324
+ end
325
+
326
+ if app_url.nil?
327
+ say " * #{app_name} - no public url (you need to add a namespace)"
328
+ else
329
+ say " * #{app_name} - #{app_url}"
330
+ end
331
+ end
332
+ end
333
+ else
334
+ section(:bottom => 1) { say "none found" }
335
+ paragraph do
336
+ say "Below is a list of the types of application " \
337
+ "you can create: "
338
+
339
+ application_types = RHC::get_cartridges_list @libra_server, RHC::Config.default_proxy
340
+ application_types.each do |cart|
341
+ say " * #{cart} - rhc app create -t #{cart} -a <app name>"
342
+ end
343
+ end
344
+ end
345
+
346
+ true
347
+ end
348
+
349
+ def finalize_stage
350
+ paragraph do
351
+ say "The OpenShift client tools have been configured on your computer. " \
352
+ "You can run this setup wizard at any time by using the command 'rhc setup' " \
353
+ "We will now execute your original " \
354
+ "command (rhc #{ARGV.join(" ")})"
355
+ end
356
+ true
357
+ end
358
+
359
+ def config_namespace(namespace)
360
+ # skip if string is empty
361
+ paragraph do
362
+ if namespace.nil? or namespace.chomp.length == 0
363
+ say "Skipping! You may create a namespace using 'rhc domain create'"
364
+ return true
365
+ end
366
+
367
+
368
+ begin
369
+ domain = @rest_client.add_domain(namespace)
370
+
371
+ say "Your domain name '#{domain.id}' has been successfully created"
372
+ rescue Rhc::Rest::ValidationException => e
373
+ say "#{e.to_s}"
374
+ return false
375
+ end
376
+ end
377
+ true
378
+ end
379
+
380
+ def ask_for_namespace
381
+ paragraph do
382
+ say "Your namespace is unique to your account and is the suffix of the " \
383
+ "public URLs we assign to your applications. You may configure your " \
384
+ "namespace here or leave it blank and use 'rhc domain create' to " \
385
+ "create a namespace later. You will not be able to create " \
386
+ "applications without first creating a namespace."
387
+ end
388
+
389
+ namespace = nil
390
+ paragraph do
391
+ namespace = ask "Please enter a namespace or leave this blank if you wish to skip this step:" do |q|
392
+ q.validate = lambda { |p| RHC::check_namespace p }
393
+ end
394
+ end
395
+
396
+ while !config_namespace namespace do
397
+ paragraph do
398
+ namespace = ask "Please enter a namespace or leave this blank if you wish to skip this step:" do |q|
399
+ q.validate = lambda { |p| RHC::check_namespace p }
400
+ end
401
+ end
402
+ end
403
+ end
404
+
405
+ def dbus_send_session_method(name, service, obj_path, iface, stringafied_params, wait_for_reply=true)
406
+ method = "#{iface}.#{name}"
407
+ print_reply = ""
408
+ print_reply = "--print-reply" if wait_for_reply
409
+ cmd = "dbus-send --session #{print_reply} --type=method_call \
410
+ --dest=#{service} #{obj_path} #{method} #{stringafied_params}"
411
+ output = `#{cmd} 2>&1`
412
+
413
+ throw output if output.start_with?('Error') and !$?.success?
414
+
415
+ # parse the output
416
+ results = []
417
+ output.each_with_index do |line, i|
418
+ if i != 0 # discard first line
419
+ param_type, value = line.chomp.split(" ", 2)
420
+
421
+ case param_type
422
+ when "boolean"
423
+ results << (value == 'true')
424
+ when "string"
425
+ results << value
426
+ else
427
+ puts "unknown type #{param_type} - treating as string"
428
+ results << value
429
+ end
430
+ end
431
+ end
432
+
433
+ if results.length == 0
434
+ return nil
435
+ elsif results.length == 1
436
+ return results[0]
437
+ else
438
+ return results
439
+ end
440
+ end
441
+
442
+ ##
443
+ # calls package kit methods using dbus_send
444
+ #
445
+ # name - method name
446
+ # iface - either 'Query' or 'Modify'
447
+ # stringafied_params - string of params in the format of dbus-send
448
+ # e.g. "int32:10 string:'hello world'"
449
+ #
450
+ def package_kit_method(name, iface, stringafied_params, wait_for_reply=true)
451
+ service = "org.freedesktop.PackageKit"
452
+ obj_path = "/org/freedesktop/PackageKit"
453
+ full_iface = "org.freedesktop.PackageKit.#{iface}"
454
+ dbus_send_session_method name, service, obj_path, full_iface, stringafied_params, wait_for_reply
455
+ end
456
+
457
+ def package_kit_install
458
+ section(:top => 1) do
459
+ say "Checking for git ... "
460
+ end
461
+
462
+ begin
463
+ git_installed = package_kit_method('IsInstalled', 'Query', 'string:git string:')
464
+ if git_installed
465
+ section(:bottom => 1) { say "found" }
466
+ else
467
+ section(:bottom => 1) { say "needs to be installed" }
468
+ install = false
469
+ section do
470
+ install = agree "Would you like to install git with the system installer? (yes/no) "
471
+ end
472
+ if install
473
+ package_kit_method('InstallPackageNames', 'Modify', 'uint32:0 array:string:"git" string:', false)
474
+ paragraph do
475
+ say "You may safely continue while the installer is running or " \
476
+ "you can wait until it has finished. Press any key to continue:"
477
+ get_character
478
+ end
479
+ end
480
+ end
481
+ rescue
482
+ generic_unix_install_check false
483
+ end
484
+ end
485
+
486
+ def generic_unix_install_check(show_action=true)
487
+ section(:top => 1) { say "Checking for git ... " } if show_action
488
+ if has_git?
489
+ section(:bottom => 1) { say "found" }
490
+ else
491
+ section(:bottom => 1) { say "needs to be installed" }
492
+ paragraph do
493
+ say "Automated installation of client tools is not supported for " \
494
+ "your platform. You will need to manually install git for full " \
495
+ "OpenShift functionality."
496
+ end
497
+ end
498
+ end
499
+
500
+ def windows_install
501
+ # Finding windows executables is hard since they can get installed
502
+ # in non standard directories. Punt on this for now and simply
503
+ # print out urls and some instructions
504
+ say <<EOF
505
+ In order to fully interact with OpenShift you will need to install and configure a git client if you have not already done so.
506
+
507
+ Documentation for installing other tools you will need for OpenShift can be found at https://#{@libra_server}/app/getting_started#install_client_tools
508
+
509
+ We recommend these free applications:
510
+
511
+ * Git for Windows - a basic git command line and GUI client https://github.com/msysgit/msysgit/wiki/InstallMSysGit
512
+ * TortoiseGit - git client that integrates into the file explorer http://code.google.com/p/tortoisegit/
513
+
514
+ EOF
515
+ end
516
+
517
+ def exe?(executable)
518
+ ENV['PATH'].split(File::PATH_SEPARATOR).any? do |directory|
519
+ File.executable?(File.join(directory, executable.to_s))
520
+ end
521
+ end
522
+
523
+ def has_git?
524
+ %x{ git --version }
525
+ $?.success?
526
+ end
527
+
528
+ def has_dbus_send?
529
+ bus = ENV['DBUS_SESSION_BUS_ADDRESS']
530
+ exe? 'dbus-send' and !bus.nil? and bus.length > 0
531
+ end
532
+ end
533
+
534
+ class RerunWizard < Wizard
535
+ def initialize(config_path)
536
+ super
537
+ end
538
+
539
+ def greeting_stage
540
+ paragraph do
541
+ say "Starting Interactive Setup for OpenShift's command line interface"
542
+ end
543
+
544
+ paragraph do
545
+ say "We'll help get you setup with just a couple of questions. " \
546
+ "You can skip this in the future by copying your config's around:"
547
+ end
548
+
549
+ paragraph do
550
+ say " #{@config_path}"
551
+ say " #{RHC::Config.home_dir}/.ssh/"
552
+ end
553
+
554
+ true
555
+ end
556
+
557
+ def create_config_stage
558
+ if File.exists? @config_path
559
+ backup = "#{@config_path}.bak"
560
+ paragraph do
561
+ say "Configuration file #{@config_path} already exists, " \
562
+ "backing up to #{backup}"
563
+ end
564
+ File.cp(@config_path, backup)
565
+ File.delete(@config_path)
566
+ end
567
+ super
568
+ true
569
+ end
570
+
571
+ def finalize_stage
572
+ paragraph do
573
+ say "Thank you for setting up your system. You can rerun this at any time " \
574
+ "by calling 'rhc setup'."
575
+ end
576
+ true
577
+ end
578
+ end
579
+ end