bosh_cli 1.0.3 → 1.5.0.pre.1113

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 (144) hide show
  1. data/bin/bosh +0 -9
  2. data/lib/cli.rb +69 -64
  3. data/lib/cli/backup_destination_path.rb +33 -0
  4. data/lib/cli/base_command.rb +57 -56
  5. data/lib/cli/blob_manager.rb +12 -12
  6. data/lib/cli/changeset_helper.rb +6 -7
  7. data/lib/cli/client/director.rb +724 -0
  8. data/lib/cli/command_handler.rb +6 -7
  9. data/lib/cli/commands/backup.rb +39 -0
  10. data/lib/cli/commands/biff.rb +42 -21
  11. data/lib/cli/commands/blob_management.rb +1 -1
  12. data/lib/cli/commands/cloudcheck.rb +11 -13
  13. data/lib/cli/commands/deployment.rb +53 -37
  14. data/lib/cli/commands/help.rb +3 -2
  15. data/lib/cli/commands/job_management.rb +67 -103
  16. data/lib/cli/commands/job_rename.rb +6 -8
  17. data/lib/cli/commands/log_management.rb +78 -55
  18. data/lib/cli/commands/maintenance.rb +36 -30
  19. data/lib/cli/commands/misc.rb +72 -51
  20. data/lib/cli/commands/package.rb +2 -2
  21. data/lib/cli/commands/property_management.rb +10 -12
  22. data/lib/cli/commands/release.rb +236 -133
  23. data/lib/cli/commands/snapshot.rb +93 -0
  24. data/lib/cli/commands/ssh.rb +216 -213
  25. data/lib/cli/commands/stemcell.rb +46 -34
  26. data/lib/cli/commands/task.rb +2 -2
  27. data/lib/cli/commands/user.rb +27 -3
  28. data/lib/cli/commands/vm.rb +28 -0
  29. data/lib/cli/commands/vms.rb +81 -23
  30. data/lib/cli/config.rb +6 -2
  31. data/lib/cli/core_ext.rb +31 -30
  32. data/lib/cli/deployment_helper.rb +134 -159
  33. data/lib/cli/deployment_manifest.rb +66 -0
  34. data/lib/cli/deployment_manifest_compiler.rb +0 -3
  35. data/lib/cli/event_log_renderer.rb +10 -10
  36. data/lib/cli/file_with_progress_bar.rb +52 -0
  37. data/lib/cli/job_builder.rb +1 -1
  38. data/lib/cli/job_command_args.rb +23 -0
  39. data/lib/cli/job_property_collection.rb +4 -7
  40. data/lib/cli/job_property_validator.rb +22 -12
  41. data/lib/cli/job_state.rb +54 -0
  42. data/lib/cli/line_wrap.rb +54 -0
  43. data/lib/cli/packaging_helper.rb +10 -10
  44. data/lib/cli/release.rb +18 -15
  45. data/lib/cli/release_builder.rb +9 -4
  46. data/lib/cli/release_compiler.rb +9 -9
  47. data/lib/cli/release_tarball.rb +3 -6
  48. data/lib/cli/resurrection.rb +31 -0
  49. data/lib/cli/runner.rb +56 -30
  50. data/lib/cli/stemcell.rb +25 -10
  51. data/lib/cli/task_log_renderer.rb +1 -1
  52. data/lib/cli/task_tracker.rb +10 -9
  53. data/lib/cli/validation.rb +3 -1
  54. data/lib/cli/version.rb +1 -1
  55. data/lib/cli/version_calc.rb +5 -18
  56. data/lib/cli/versions_index.rb +1 -1
  57. data/lib/cli/vm_state.rb +43 -0
  58. data/lib/cli/yaml_helper.rb +26 -35
  59. metadata +75 -208
  60. data/Rakefile +0 -56
  61. data/lib/cli/director.rb +0 -628
  62. data/spec/assets/biff/bad_gateway_config.yml +0 -28
  63. data/spec/assets/biff/good_simple_config.yml +0 -63
  64. data/spec/assets/biff/good_simple_golden_config.yml +0 -63
  65. data/spec/assets/biff/good_simple_template.erb +0 -69
  66. data/spec/assets/biff/ip_out_of_range.yml +0 -63
  67. data/spec/assets/biff/multiple_subnets_config.yml +0 -40
  68. data/spec/assets/biff/network_only_template.erb +0 -34
  69. data/spec/assets/biff/no_cc_config.yml +0 -27
  70. data/spec/assets/biff/no_range_config.yml +0 -27
  71. data/spec/assets/biff/no_subnet_config.yml +0 -16
  72. data/spec/assets/biff/ok_network_config.yml +0 -30
  73. data/spec/assets/biff/properties_template.erb +0 -6
  74. data/spec/assets/config/atmos/config/final.yml +0 -6
  75. data/spec/assets/config/atmos/config/private.yml +0 -4
  76. data/spec/assets/config/bad-providers/config/final.yml +0 -5
  77. data/spec/assets/config/bad-providers/config/private.yml +0 -4
  78. data/spec/assets/config/deprecation/config/final.yml +0 -5
  79. data/spec/assets/config/deprecation/config/private.yml +0 -2
  80. data/spec/assets/config/local/config/final.yml +0 -5
  81. data/spec/assets/config/local/config/private.yml +0 -1
  82. data/spec/assets/config/s3/config/final.yml +0 -5
  83. data/spec/assets/config/s3/config/private.yml +0 -5
  84. data/spec/assets/config/swift-hp/config/final.yml +0 -6
  85. data/spec/assets/config/swift-hp/config/private.yml +0 -7
  86. data/spec/assets/config/swift-rackspace/config/final.yml +0 -6
  87. data/spec/assets/config/swift-rackspace/config/private.yml +0 -6
  88. data/spec/assets/deployment.MF +0 -0
  89. data/spec/assets/plugins/bosh/cli/commands/echo.rb +0 -43
  90. data/spec/assets/plugins/bosh/cli/commands/ruby.rb +0 -24
  91. data/spec/assets/release/jobs/cacher.tgz +0 -0
  92. data/spec/assets/release/jobs/cacher/config/file1.conf +0 -0
  93. data/spec/assets/release/jobs/cacher/config/file2.conf +0 -0
  94. data/spec/assets/release/jobs/cacher/job.MF +0 -6
  95. data/spec/assets/release/jobs/cacher/monit +0 -1
  96. data/spec/assets/release/jobs/cleaner.tgz +0 -0
  97. data/spec/assets/release/jobs/cleaner/job.MF +0 -4
  98. data/spec/assets/release/jobs/cleaner/monit +0 -1
  99. data/spec/assets/release/jobs/sweeper.tgz +0 -0
  100. data/spec/assets/release/jobs/sweeper/config/test.conf +0 -1
  101. data/spec/assets/release/jobs/sweeper/job.MF +0 -5
  102. data/spec/assets/release/jobs/sweeper/monit +0 -1
  103. data/spec/assets/release/packages/mutator.tar.gz +0 -0
  104. data/spec/assets/release/packages/stuff.tgz +0 -0
  105. data/spec/assets/release/release.MF +0 -17
  106. data/spec/assets/release_invalid_checksum.tgz +0 -0
  107. data/spec/assets/release_invalid_jobs.tgz +0 -0
  108. data/spec/assets/release_no_name.tgz +0 -0
  109. data/spec/assets/release_no_version.tgz +0 -0
  110. data/spec/assets/stemcell/image +0 -1
  111. data/spec/assets/stemcell/stemcell.MF +0 -6
  112. data/spec/assets/stemcell_invalid_mf.tgz +0 -0
  113. data/spec/assets/stemcell_no_image.tgz +0 -0
  114. data/spec/assets/valid_release.tgz +0 -0
  115. data/spec/assets/valid_stemcell.tgz +0 -0
  116. data/spec/spec_helper.rb +0 -28
  117. data/spec/unit/base_command_spec.rb +0 -87
  118. data/spec/unit/biff_spec.rb +0 -172
  119. data/spec/unit/blob_manager_spec.rb +0 -288
  120. data/spec/unit/cache_spec.rb +0 -36
  121. data/spec/unit/cli_commands_spec.rb +0 -356
  122. data/spec/unit/config_spec.rb +0 -125
  123. data/spec/unit/core_ext_spec.rb +0 -81
  124. data/spec/unit/dependency_helper_spec.rb +0 -52
  125. data/spec/unit/deployment_manifest_compiler_spec.rb +0 -63
  126. data/spec/unit/deployment_manifest_spec.rb +0 -153
  127. data/spec/unit/director_spec.rb +0 -471
  128. data/spec/unit/director_task_spec.rb +0 -48
  129. data/spec/unit/event_log_renderer_spec.rb +0 -171
  130. data/spec/unit/hash_changeset_spec.rb +0 -73
  131. data/spec/unit/job_builder_spec.rb +0 -455
  132. data/spec/unit/job_property_collection_spec.rb +0 -111
  133. data/spec/unit/job_property_validator_spec.rb +0 -7
  134. data/spec/unit/job_rename_spec.rb +0 -200
  135. data/spec/unit/package_builder_spec.rb +0 -593
  136. data/spec/unit/release_builder_spec.rb +0 -120
  137. data/spec/unit/release_spec.rb +0 -173
  138. data/spec/unit/release_tarball_spec.rb +0 -29
  139. data/spec/unit/runner_spec.rb +0 -7
  140. data/spec/unit/ssh_spec.rb +0 -84
  141. data/spec/unit/stemcell_spec.rb +0 -17
  142. data/spec/unit/task_tracker_spec.rb +0 -131
  143. data/spec/unit/version_calc_spec.rb +0 -27
  144. data/spec/unit/versions_index_spec.rb +0 -144
@@ -0,0 +1,93 @@
1
+ # Copyright (c) 2009-2013 VMware, Inc.
2
+
3
+ module Bosh::Cli::Command
4
+ class Snapshot < Base
5
+ usage 'snapshots'
6
+ desc 'List all snapshots'
7
+ def list(job = nil, index = nil)
8
+ auth_required
9
+
10
+ deployment_name = prepare_deployment_manifest['name']
11
+ say("Deployment `#{deployment_name.make_green}'")
12
+
13
+ snapshots = director.list_snapshots(deployment_name, job, index)
14
+
15
+ sorted = snapshots.sort do |a, b|
16
+ s = a['job'].to_s <=> b['job'].to_s
17
+ s = a['index'].to_i <=> b['index'].to_i if s == 0
18
+ s = a['created_at'].to_s <=> b['created_at'].to_s if s == 0
19
+ s
20
+ end
21
+
22
+ snapshots_table = table do |t|
23
+ t.headings = ['Job/index', 'Snapshot CID', 'Created at', 'Clean']
24
+
25
+ sorted.each do |snapshot|
26
+ job = "#{snapshot['job'] || 'unknown'}/#{snapshot['index'] || 'unknown'}"
27
+ t << [job, snapshot['snapshot_cid'], snapshot['created_at'], snapshot['clean']]
28
+ end
29
+ end
30
+
31
+ nl
32
+ say(snapshots_table)
33
+ nl
34
+ say('Snapshots total: %d' % snapshots.size)
35
+ end
36
+
37
+ usage 'take snapshot'
38
+ desc 'Takes a snapshot'
39
+ def take(job = nil, index = nil)
40
+ auth_required
41
+
42
+ deployment_name = prepare_deployment_manifest['name']
43
+ say("Deployment `#{deployment_name.make_green}'")
44
+
45
+ unless job && index
46
+ unless confirmed?("Are you sure you want to take a snapshot of all deployment `#{deployment_name}'?")
47
+ say('Canceled taking snapshot'.make_green)
48
+ return
49
+ end
50
+ end
51
+
52
+ status, task_id = director.take_snapshot(deployment_name, job, index)
53
+
54
+ task_report(status, task_id, 'Snapshot taken')
55
+ end
56
+
57
+ usage 'delete snapshot'
58
+ desc 'Deletes a snapshot'
59
+ def delete(snapshot_cid)
60
+ auth_required
61
+
62
+ deployment_name = prepare_deployment_manifest['name']
63
+ say("Deployment `#{deployment_name.make_green}'")
64
+
65
+ unless confirmed?("Are you sure you want to delete snapshot `#{snapshot_cid}'?")
66
+ say('Canceled deleting snapshot'.make_green)
67
+ return
68
+ end
69
+
70
+ status, task_id = director.delete_snapshot(deployment_name, snapshot_cid)
71
+
72
+ task_report(status, task_id, "Deleted Snapshot `#{snapshot_cid}'")
73
+ end
74
+
75
+ usage 'delete snapshots'
76
+ desc 'Deletes all snapshots of a deployment'
77
+ def delete_all
78
+ auth_required
79
+
80
+ deployment_name = prepare_deployment_manifest['name']
81
+ say("Deployment `#{deployment_name.make_green}'")
82
+
83
+ unless confirmed?("Are you sure you want to delete all snapshots of deployment `#{deployment_name}'?")
84
+ say('Canceled deleting snapshots'.make_green)
85
+ return
86
+ end
87
+
88
+ status, task_id = director.delete_all_snapshots(deployment_name)
89
+
90
+ task_report(status, task_id, "Deleted all snapshots of deployment `#{deployment_name}'")
91
+ end
92
+ end
93
+ end
@@ -1,264 +1,267 @@
1
- # Copyright (c) 2009-2012 VMware, Inc.
2
-
3
- module Bosh::Cli::Command
4
- class Ssh < Base
5
- include Bosh::Cli::DeploymentHelper
6
-
7
- SSH_USER_PREFIX = "bosh_"
8
- SSH_DSA_PUB = File.expand_path("~/.ssh/id_dsa.pub")
9
- SSH_RSA_PUB = File.expand_path("~/.ssh/id_rsa.pub")
10
-
11
- # bosh ssh
12
- usage "ssh"
13
- desc "Execute command or start an interactive session"
14
- option "--public_key FILE", "Public key"
15
- option "--gateway_host HOST", "Gateway host"
16
- option "--gateway_user USER", "Gateway user"
17
- option "--default_password PASSWORD",
18
- "Use default ssh password (NOT RECOMMENDED)"
19
- def shell(*args)
20
- job, index, command = parse_args(args)
21
-
22
- if command.empty?
23
- if index.nil?
24
- err("Can't run interactive shell on more than one instance")
1
+ require 'cli/job_command_args'
2
+
3
+ module Bosh::Cli
4
+ module Command
5
+ class Ssh < Base
6
+ SSH_USER_PREFIX = 'bosh_'
7
+ SSH_DSA_PUB = File.expand_path('~/.ssh/id_dsa.pub')
8
+ SSH_RSA_PUB = File.expand_path('~/.ssh/id_rsa.pub')
9
+
10
+ # bosh ssh
11
+ usage 'ssh'
12
+ desc 'Execute command or start an interactive session'
13
+ option '--public_key FILE', 'Public key'
14
+ option '--gateway_host HOST', 'Gateway host'
15
+ option '--gateway_user USER', 'Gateway user'
16
+ option '--gateway_identity_file FILE', 'Gateway identity file'
17
+ option '--default_password PASSWORD',
18
+ 'Use default ssh password (NOT RECOMMENDED)'
19
+
20
+ def shell(*args)
21
+ if args.size > 0
22
+ job, index, command = JobCommandArgs.new(args).to_a
23
+ else
24
+ command = ''
25
+ job, index = prompt_for_job_and_index
25
26
  end
26
- setup_interactive_shell(job, index)
27
- else
28
- say("Executing `#{command.join(" ")}' on #{job}/#{index}")
29
- perform_operation(:exec, job, index, command)
30
- end
31
- end
32
27
 
33
- # bosh scp
34
- usage "scp"
35
- desc "upload/download the source file to the given job. " +
36
- "Note: for download /path/to/destination is a directory"
37
- option "--download", "Download file"
38
- option "--upload", "Upload file"
39
- option "--public_key FILE", "Public key"
40
- option "--gateway_host HOST", "Gateway host"
41
- option "--gateway_user USER", "Gateway user"
42
- def scp(*args)
43
- job, index, args = parse_args(args)
44
- upload = options[:upload]
45
- download = options[:download]
46
- if (upload && download) || (upload.nil? && download.nil?)
47
- err("Please specify either --upload or --download")
48
- end
28
+ job_must_exist_in_deployment(job)
29
+ index = valid_index_for(job, index, integer_index: true)
49
30
 
50
- if args.size != 2
51
- err("Please enter valid source and destination paths")
31
+ if command.empty?
32
+ setup_interactive_shell(job, index)
33
+ else
34
+ say("Executing `#{command.join(' ')}' on #{job}/#{index}")
35
+ perform_operation(:exec, job, index, command)
36
+ end
52
37
  end
53
- say("Executing file operations on job #{job}")
54
- perform_operation(upload ? :upload : :download, job, index, args)
55
- end
56
38
 
57
- usage "cleanup ssh"
58
- desc "Cleanup SSH artifacts"
59
- def cleanup(*args)
60
- job, index, args = parse_args(args)
61
- if args.size > 0
62
- err("SSH cleanup doesn't accept any extra args")
63
- end
39
+ # bosh scp
40
+ usage 'scp'
41
+ desc 'upload/download the source file to the given job. ' +
42
+ 'Note: for download /path/to/destination is a directory'
43
+ option '--download', 'Download file'
44
+ option '--upload', 'Upload file'
45
+ option '--public_key FILE', 'Public key'
46
+ option '--gateway_host HOST', 'Gateway host'
47
+ option '--gateway_user USER', 'Gateway user'
48
+ option '--gateway_identity_file FILE', 'Gateway identity file'
49
+
50
+ def scp(*args)
51
+ job, index, args = JobCommandArgs.new(args).to_a
52
+ upload = options[:upload]
53
+ download = options[:download]
54
+ if (upload && download) || (upload.nil? && download.nil?)
55
+ err('Please specify either --upload or --download')
56
+ end
64
57
 
65
- manifest_name = prepare_deployment_manifest["name"]
58
+ job_must_exist_in_deployment(job)
66
59
 
67
- say("Cleaning up ssh artifacts from #{job}/#{index}")
68
- director.cleanup_ssh(manifest_name, job, "^#{SSH_USER_PREFIX}", [index])
69
- end
60
+ if args.size != 2
61
+ err('Please enter valid source and destination paths')
62
+ end
63
+ say("Executing file operations on job #{job}")
64
+ perform_operation(upload ? :upload : :download, job, index, args)
65
+ end
70
66
 
71
- private
67
+ usage 'cleanup ssh'
68
+ desc 'Cleanup SSH artifacts'
72
69
 
73
- def get_salt_charset
74
- charset = []
75
- charset.concat(("a".."z").to_a)
76
- charset.concat(("A".."Z").to_a)
77
- charset.concat(("0".."9").to_a)
78
- charset << "."
79
- charset << "/"
80
- charset
81
- end
70
+ def cleanup(*args)
71
+ job, index, args = JobCommandArgs.new(args).to_a
72
+ if args.size > 0
73
+ err("SSH cleanup doesn't accept any extra args")
74
+ end
82
75
 
83
- def encrypt_password(plain_text)
84
- return unless plain_text
85
- @salt_charset ||= get_salt_charset
86
- salt = ""
87
- 8.times do
88
- salt << @salt_charset[rand(@salt_charset.size)]
89
- end
90
- plain_text.crypt(salt)
91
- end
76
+ job_must_exist_in_deployment(job)
77
+
78
+ manifest_name = prepare_deployment_manifest['name']
92
79
 
93
- # @param [String] job
94
- # @param [Integer] index
95
- # @param [optional,String] password
96
- def setup_ssh(job, index, password = nil)
97
- public_key = get_public_key
98
- user = SSH_USER_PREFIX + rand(36**9).to_s(36)
99
- deployment_name = prepare_deployment_manifest["name"]
100
-
101
- say("Target deployment is `#{deployment_name}'")
102
- status, task_id = director.setup_ssh(
103
- deployment_name, job, index, user,
104
- public_key, encrypt_password(password))
105
-
106
- unless status == :done
107
- err("Failed to set up SSH: see task #{task_id} log for details")
80
+ say("Cleaning up ssh artifacts from #{job}/#{index}")
81
+ director.cleanup_ssh(manifest_name, job, "^#{SSH_USER_PREFIX}", [index])
108
82
  end
109
83
 
110
- sessions = JSON.parse(director.get_task_result_log(task_id))
84
+ private
111
85
 
112
- unless sessions && sessions.kind_of?(Array) && sessions.size > 0
113
- err("Error setting up ssh, check task #{task_id} log for more details")
86
+ def get_salt_charset
87
+ charset = []
88
+ charset.concat(('a'..'z').to_a)
89
+ charset.concat(('A'..'Z').to_a)
90
+ charset.concat(('0'..'9').to_a)
91
+ charset << '.'
92
+ charset << '/'
93
+ charset
114
94
  end
115
95
 
116
- sessions.each do |session|
117
- unless session.kind_of?(Hash)
118
- err("Unexpected SSH session info: #{session.inspect}. " +
119
- "Please check task #{task_id} log for more details")
96
+ def encrypt_password(plain_text)
97
+ return unless plain_text
98
+ @salt_charset ||= get_salt_charset
99
+ salt = ''
100
+ 8.times do
101
+ salt << @salt_charset[rand(@salt_charset.size)]
120
102
  end
103
+ plain_text.crypt(salt)
121
104
  end
122
105
 
123
- if options[:gateway_host]
124
- require "net/ssh/gateway"
125
- gw_host = options[:gateway_host]
126
- gw_user = options[:gateway_user] || ENV["USER"]
127
- gateway = Net::SSH::Gateway.new(gw_host, gw_user)
128
- else
129
- gateway = nil
130
- end
106
+ # @param [String] job
107
+ # @param [Integer] index
108
+ # @param [optional,String] password
109
+ def setup_ssh(job, index, password = nil)
110
+ user = random_ssh_username
111
+ deployment_name = prepare_deployment_manifest['name']
131
112
 
132
- begin
133
- yield sessions, user, gateway
134
- ensure
113
+ say("Target deployment is `#{deployment_name}'")
135
114
  nl
136
- say("Cleaning up ssh artifacts")
137
- indices = sessions.map { |session| session["index"] }
138
- director.cleanup_ssh(deployment_name, job, "^#{user}$", indices)
139
- gateway.shutdown! if gateway
140
- end
141
- end
142
-
143
- # @param [String] job Job name
144
- # @param [Integer] index Job index
145
- def setup_interactive_shell(job, index)
146
- password = options[:default_password]
115
+ say('Setting up ssh artifacts')
116
+ status, task_id = director.setup_ssh(
117
+ deployment_name, job, index, user,
118
+ public_key, encrypt_password(password))
147
119
 
148
- if password.nil?
149
- password = ask(
150
- "Enter password (use it to " +
151
- "sudo on remote host): ") { |q| q.echo = "*" }
152
-
153
- err("Please provide ssh password") if password.blank?
154
- end
120
+ unless status == :done
121
+ err("Failed to set up SSH: see task #{task_id} log for details")
122
+ end
155
123
 
156
- setup_ssh(job, index, password) do |sessions, user, gateway|
157
- session = sessions.first
124
+ sessions = JSON.parse(director.get_task_result_log(task_id))
158
125
 
159
- unless session["status"] == "success" && session["ip"]
160
- err("Failed to set up SSH on #{job}/#{index}: #{session.inspect}")
126
+ unless sessions && sessions.kind_of?(Array) && sessions.size > 0
127
+ err("Error setting up ssh, check task #{task_id} log for more details")
161
128
  end
162
129
 
163
- say("Starting interactive shell on job #{job}/#{index}")
164
-
165
- if gateway
166
- port = gateway.open(session["ip"], 22)
167
- ssh_session = fork do
168
- exec("ssh #{user}@localhost -p #{port}")
130
+ sessions.each do |session|
131
+ unless session.kind_of?(Hash)
132
+ err("Unexpected SSH session info: #{session.inspect}. " +
133
+ "Please check task #{task_id} log for more details")
169
134
  end
170
- Process.waitpid(ssh_session)
171
- gateway.close(port)
172
- else
173
- ssh_session = fork do
174
- exec("ssh #{user}@#{session["ip"]}")
135
+ end
136
+
137
+ begin
138
+ if options[:gateway_host]
139
+ require 'net/ssh/gateway'
140
+ gw_host = options[:gateway_host]
141
+ gw_user = options[:gateway_user] || ENV['USER']
142
+ gw_options = {}
143
+ gw_options[:keys] = [options[:gateway_identity_file]] if options[:gateway_identity_file]
144
+ begin
145
+ gateway = Net::SSH::Gateway.new(gw_host, gw_user, gw_options)
146
+ rescue Net::SSH::AuthenticationFailed
147
+ err("Authentication failed with gateway #{gw_host} and user #{gw_user}.")
148
+ end
149
+ else
150
+ gateway = nil
175
151
  end
176
- Process.waitpid(ssh_session)
152
+
153
+ yield sessions, user, gateway
154
+ ensure
155
+ nl
156
+ say('Cleaning up ssh artifacts')
157
+ indices = sessions.map { |session| session['index'] }
158
+ director.cleanup_ssh(deployment_name, job, "^#{user}$", indices)
159
+ gateway.shutdown! if gateway
177
160
  end
178
161
  end
179
- end
180
162
 
181
- def perform_operation(operation, job, index, args)
182
- setup_ssh(job, index, nil) do |sessions, user, gateway|
183
- sessions.each do |session|
184
- unless session["status"] == "success" && session["ip"]
163
+ def random_ssh_username
164
+ SSH_USER_PREFIX + rand(36**9).to_s(36)
165
+ end
166
+
167
+ # @param [String] job Job name
168
+ # @param [Integer] index Job index
169
+ def setup_interactive_shell(job, index)
170
+ deployment_required
171
+ password = options[:default_password]
172
+
173
+ if password.nil?
174
+ password = ask(
175
+ 'Enter password (use it to ' +
176
+ 'sudo on remote host): ') { |q| q.echo = '*' }
177
+
178
+ err('Please provide ssh password') if password.blank?
179
+ end
180
+
181
+ setup_ssh(job, index, password) do |sessions, user, gateway|
182
+ session = sessions.first
183
+
184
+ unless session['status'] == 'success' && session['ip']
185
185
  err("Failed to set up SSH on #{job}/#{index}: #{session.inspect}")
186
186
  end
187
187
 
188
- with_ssh(user, session["ip"], gateway) do |ssh|
189
- case operation
190
- when :exec
191
- nl
192
- say("#{job}/#{session["index"]}")
193
- say(ssh.exec!(args.join(" ")))
194
- when :upload
195
- ssh.scp.upload!(args[0], args[1])
196
- when :download
197
- file = File.basename(args[0])
198
- path = "#{args[1]}/#{file}.#{job}.#{session["index"]}"
199
- ssh.scp.download!(args[0], path)
200
- say("Downloaded file to #{path}".green)
201
- else
202
- err("Unknown operation #{operation}")
203
- end
188
+ say("Starting interactive shell on job #{job}/#{index}")
189
+
190
+ if gateway
191
+ port = gateway.open(session['ip'], 22)
192
+ ssh_session = Process.spawn('ssh', "#{user}@localhost", '-p', port.to_s)
193
+ Process.waitpid(ssh_session)
194
+ gateway.close(port)
195
+ else
196
+ ssh_session = Process.spawn('ssh', "#{user}@#{session['ip']}")
197
+ Process.waitpid(ssh_session)
204
198
  end
205
199
  end
206
200
  end
207
- end
208
201
 
209
- # @param [Array] args
210
- # @return [Array] job, index, command
211
- def parse_args(args)
212
- job = args.shift
213
- job, index = job.split("/", 2)
202
+ def perform_operation(operation, job, index, args)
203
+ setup_ssh(job, index, nil) do |sessions, user, gateway|
204
+ sessions.each do |session|
205
+ unless session['status'] == 'success' && session['ip']
206
+ err("Failed to set up SSH on #{job}/#{index}: #{session.inspect}")
207
+ end
214
208
 
215
- if index
216
- if index =~ /^\d+$/
217
- index = index.to_i
218
- else
219
- err("Invalid job index, integer number expected")
209
+ with_ssh(user, session['ip'], gateway) do |ssh|
210
+ case operation
211
+ when :exec
212
+ nl
213
+ say("#{job}/#{session['index']}")
214
+ say(ssh.exec!(args.join(' ')))
215
+ when :upload
216
+ ssh.scp.upload!(args[0], args[1])
217
+ when :download
218
+ file = File.basename(args[0])
219
+ path = "#{args[1]}/#{file}.#{job}.#{session['index']}"
220
+ ssh.scp.download!(args[0], path)
221
+ say("Downloaded file to #{path}".make_green)
222
+ else
223
+ err("Unknown operation #{operation}")
224
+ end
225
+ end
226
+ end
220
227
  end
221
- elsif args[0] =~ /^\d+$/
222
- index = args.shift.to_i
223
228
  end
224
229
 
225
- [job, index, args]
226
- end
230
+ # @return [String] Public key
231
+ def public_key
232
+ public_key_path = options[:public_key]
227
233
 
228
- # @return [String] Public key
229
- def get_public_key
230
- public_key_path = options[:public_key]
231
-
232
- if public_key_path
233
- unless File.file?(public_key_path)
234
- err("Can't find file `#{public_key_path}'")
235
- end
236
- return File.read(public_key_path)
237
- else
238
- %x[ssh-add -L 1>/dev/null 2>&1]
239
- if $?.exitstatus == 0
240
- return %x[ssh-add -L].split("\n").first
234
+ if public_key_path
235
+ unless File.file?(public_key_path)
236
+ err("Can't find file `#{public_key_path}'")
237
+ end
238
+ return File.read(public_key_path)
241
239
  else
242
- [SSH_DSA_PUB, SSH_RSA_PUB].each do |key_file|
243
- return File.read(key_file) if File.file?(key_file)
240
+ %x[ssh-add -L 1>/dev/null 2>&1]
241
+ if $?.exitstatus == 0
242
+ return %x[ssh-add -L].split("\n").first
243
+ else
244
+ [SSH_DSA_PUB, SSH_RSA_PUB].each do |key_file|
245
+ return File.read(key_file) if File.file?(key_file)
246
+ end
244
247
  end
245
248
  end
246
- end
247
249
 
248
- err("Please specify a public key file")
249
- end
250
+ err('Please specify a public key file')
251
+ end
250
252
 
251
- # @param [String] user
252
- # @param [String] ip
253
- # @param [optional, Net::SSH::Gateway] gateway
254
- # @yield [Net::SSH]
255
- def with_ssh(user, ip, gateway = nil)
256
- if gateway
257
- gateway.ssh(ip, user) { |ssh| yield ssh }
258
- else
259
- require "net/ssh"
260
- require "net/scp"
261
- Net::SSH.start(ip, user) { |ssh| yield ssh }
253
+ # @param [String] user
254
+ # @param [String] ip
255
+ # @param [optional, Net::SSH::Gateway] gateway
256
+ # @yield [Net::SSH]
257
+ def with_ssh(user, ip, gateway = nil)
258
+ require 'net/scp'
259
+ if gateway
260
+ gateway.ssh(ip, user) { |ssh| yield ssh }
261
+ else
262
+ require 'net/ssh'
263
+ Net::SSH.start(ip, user) { |ssh| yield ssh }
264
+ end
262
265
  end
263
266
  end
264
267
  end