elastic_beans 0.10.0.alpha6 → 0.10.0.alpha7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4aa4fc0dc734f2eeee83efeed1dd7b288f3f9904
4
- data.tar.gz: 703e6b50540326e18a48ad9abb79da92e940a8c3
3
+ metadata.gz: 2fec2d342af0cabded810ab2d37bf6f810b52a67
4
+ data.tar.gz: 6c2c698d6b72232de2461ec76f96084488a4e8f6
5
5
  SHA512:
6
- metadata.gz: f6cbb95eb184b7a85f247c45062dc3a5f2bebf2af8480bd32802f698bcc2fdcaa084fb8ecf12ef4f1b072cc313c76fcdaf610d3a6caf9b03816cc4cf54a0006e
7
- data.tar.gz: 48648ab0283f6130b6d54a611ae36034a818e3625510893384ee74bf77c189154137c6d1a48c1674be3b7e71bad8b03904b55171f52e95669e3306cfe73c4ec0
6
+ metadata.gz: 3b0b71d95a2282b86822c3675246e62604ff488f7ff17db594899d82593455b09782dadad20a5d3a01daab80992ffc5b58c52832d8ecc17dab69ebd5dee0a571
7
+ data.tar.gz: d9213516af9141f51372bbe55a57067dc28b2ab8f186a9b4c0e281ef3184427e80c7683c2c0e65541fd1fc28b8fbab93e86d0eae38d6a6001a3a2d3068cc3429
data/README.md CHANGED
@@ -51,12 +51,16 @@ As the SDK documentation suggests, using environment variables is recommended.
51
51
  beans deploy -a myapp
52
52
 
53
53
  # Run one-off tasks
54
- beans exec -a myapp rake db:migrate
54
+ beans exec rake db:migrate -a myapp
55
55
 
56
56
  # SSH to an instance for debugging, tunneling through a bastion instance to reach the private network
57
57
  beans ssh -a myapp webserver [-n INDEX] [-i IDENTITY_FILE] [-u USERNAME] \
58
58
  [-b BASTION_HOST] [--bastion-identity-file BASTION_IDENTITY_FILE] [--bastion-username BASTION_USERNAME]
59
59
 
60
+ # Run interactive commands via SSH
61
+ beans exec rails console -a myapp --interactive [-i IDENTITY_FILE] [-u USERNAME] \
62
+ [-b BASTION_HOST] [--bastion-identity-file BASTION_IDENTITY_FILE] [--bastion-username BASTION_USERNAME]
63
+
60
64
  # Update all existing environments and configuration
61
65
  beans configure -n myapp-networking -a myapp \
62
66
  [-b SECRET_KEY_BASE] [-d DATABASE_URL] [-k KEYPAIR] \
@@ -107,7 +107,7 @@ module ElasticBeans
107
107
  # Raises an error if the exec environment or queue cannot be found.
108
108
  def enqueue_command(command)
109
109
  if environments.none? { |environment| environment.is_a?(Environment::Exec) }
110
- raise MissingExecEnvironmentError
110
+ raise MissingExecEnvironmentError.new(application: self)
111
111
  end
112
112
 
113
113
  if command.to_s == command
@@ -269,11 +269,15 @@ module ElasticBeans
269
269
  # :nodoc: all
270
270
  # @!visibility private
271
271
  class MissingExecEnvironmentError < ElasticBeans::Error
272
+ def initialize(application:)
273
+ @application = application
274
+ end
275
+
272
276
  def message
273
277
  <<-MESSAGE
274
278
  A one-off command cannot be executed because the "exec" environment does not exist. Please create it:
275
279
 
276
- #{command_as_string "-a APPLICATION exec"}
280
+ #{command_as_string "create -a #{@application.name} exec"}
277
281
  MESSAGE
278
282
  end
279
283
  end
@@ -86,12 +86,25 @@ class ElasticBeans::CLI < Thor
86
86
  desc ElasticBeans::Command::Exec::USAGE, ElasticBeans::Command::Exec::DESC
87
87
  long_desc ElasticBeans::Command::Exec::LONG_DESC
88
88
  option :application, aliases: %w(-a), required: true, desc: APPLICATION_DESC
89
+ option :interactive, aliases: %w(-t), type: :boolean, desc: "Run this command interactively, via SSH. Set SSH options as necessary."
90
+ option :bastion_host, aliases: %w(-b), desc: "The hostname of the bastion server in the VPC"
91
+ option :bastion_identity_file, desc: "The SSH private key to use to connect to the bastion server"
92
+ option :bastion_username, desc: "The username to use to connect to the bastion server"
93
+ option :identity_file, aliases: %w(-i), desc: "The SSH private key associated with the keypair used when creating the environment"
94
+ option :username, aliases: %w(-u), desc: "The username to log into the instance with, e.g. `ec2-user`"
89
95
  def exec(*command_parts)
90
96
  @verbose = options[:verbose]
91
97
  ElasticBeans::Command::Exec.new(
92
98
  application: application(
93
99
  name: options[:application],
94
100
  ),
101
+ bastion_host: options[:bastion_host],
102
+ bastion_identity_file: options[:bastion_identity_file],
103
+ bastion_username: options[:bastion_username],
104
+ identity_file: options[:identity_file],
105
+ interactive: options[:interactive],
106
+ username: options[:username],
107
+ ec2: ec2_client,
95
108
  ui: ui,
96
109
  ).run(*command_parts)
97
110
  rescue StandardError => e
@@ -215,8 +228,8 @@ class ElasticBeans::CLI < Thor
215
228
 
216
229
  map ["delvar", "rmvar"] => "unsetenv"
217
230
 
218
- desc ElasticBeans::Command::Ssh::USAGE, ElasticBeans::Command::Ssh::DESC
219
- long_desc ElasticBeans::Command::Ssh::LONG_DESC
231
+ desc ElasticBeans::Command::SSH::USAGE, ElasticBeans::Command::SSH::DESC
232
+ long_desc ElasticBeans::Command::SSH::LONG_DESC
220
233
  option :application, aliases: %w(-a), required: true, desc: APPLICATION_DESC
221
234
  option :bastion_host, aliases: %w(-b), desc: "The hostname of the bastion server in the VPC"
222
235
  option :bastion_identity_file, desc: "The SSH private key to use to connect to the bastion server"
@@ -227,7 +240,7 @@ class ElasticBeans::CLI < Thor
227
240
  option :username, aliases: %w(-u), desc: "The username to log into the instance with, e.g. `ec2-user`"
228
241
  def ssh(environment_type)
229
242
  @verbose = options[:verbose]
230
- ElasticBeans::Command::Ssh.new(
243
+ ElasticBeans::Command::SSH.new(
231
244
  application: application(
232
245
  name: options[:application],
233
246
  ),
@@ -12,28 +12,186 @@ The command is run in an "exec" environment, separate from your webserver or wor
12
12
  You must create the exec environment prior to this command being run: `beans create exec -a APPLICATION`.
13
13
  Output from the command is appended to /var/log/elastic_beans/exec/command.log.
14
14
 
15
+ If interactive, runs the command via SSH, tunneling through a bastion server if specified.
16
+
15
17
  Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.
16
18
  LONG_DESC
17
19
 
18
- def initialize(application:, ui:)
20
+ # :category: internal
21
+ COMMAND_LOGFILE = "/var/log/elastic_beans/exec/command.log"
22
+ # :category: internal
23
+ COMMAND_SCRIPT = "/opt/elastic_beans/exec/run_command.sh"
24
+ # :category: internal
25
+ TAKEN_WAIT_PERIOD = 5
26
+ # :category: internal
27
+ TAKEN_WAIT_TIMEOUT = 120
28
+
29
+ def initialize(
30
+ application:,
31
+ bastion_host:,
32
+ bastion_identity_file:,
33
+ bastion_username:,
34
+ identity_file:,
35
+ interactive:,
36
+ username:,
37
+ ec2:,
38
+ ui:
39
+ )
19
40
  @application = application
41
+ @bastion_host = bastion_host
42
+ @bastion_identity_file = bastion_identity_file
43
+ @bastion_username = bastion_username
44
+ @identity_file = identity_file
45
+ @interactive = interactive
46
+ @username = username || "ec2-user"
47
+ @ec2 = ec2
20
48
  @ui = ui
21
49
  end
22
50
 
23
51
  def run(*command_parts)
24
- command = command(command_parts)
52
+ command = command_from_parts(command_parts)
25
53
  ui.info("Running `#{command.command_string}' on #{application.name}... (ID=#{command.id})")
26
- application.enqueue_command(command)
54
+
55
+ if interactive?
56
+ ui.info("Finding an exec instance...")
57
+ freeze_command = ::ElasticBeans::Exec::Command.freeze_instance
58
+
59
+ begin
60
+ ui.debug { "Freezing exec instance... (ID=#{freeze_command.id})" }
61
+ application.enqueue_command(freeze_command)
62
+ freeze_command = wait_until_command_taken(freeze_command)
63
+ ui.debug { "Frozen exec instance: '#{freeze_command.instance_id}'" }
64
+
65
+ begin
66
+ instance_ip = ec2.describe_instances(instance_ids: [freeze_command.instance_id]).reservations[0].instances[0].private_ip_address
67
+ rescue ::Aws::EC2::Errors::InvalidInstanceIDMalformed
68
+ raise TerminatedInstanceError.new(instance_id: freeze_command.instance_id)
69
+ end
70
+ # It's possible a retry would just work, but I'd like to see this happen in reality before I assume that.
71
+ if instance_ip.nil?
72
+ raise TerminatedInstanceError.new(instance_id: freeze_command.instance_id)
73
+ end
74
+
75
+ ui.info("Connecting to #{username}@#{instance_ip}...")
76
+ ElasticBeans::SSH.new(
77
+ hostname: instance_ip,
78
+ username: username,
79
+ identity_file: identity_file,
80
+ bastion_host: bastion_host,
81
+ bastion_username: bastion_username,
82
+ bastion_identity_file: bastion_identity_file,
83
+ ssh_options: %w(-t),
84
+ command: [
85
+ # Do not lose command exit status
86
+ *%w(set -o pipefail &&),
87
+ # Log command start just like SQSConsumer
88
+ "sudo", "echo", %("Executing command ID=#{command.id} \\`#{command.command_string}' on host `hostname` pid $$..."),
89
+ ">>", COMMAND_LOGFILE, "&&",
90
+ # Load application environment
91
+ "sudo", COMMAND_SCRIPT, *command_parts,
92
+ # Log command execution
93
+ "|", "tee", "-a", COMMAND_LOGFILE, ";",
94
+ # Save command exit status for final exit
95
+ *%w(status=$? ;),
96
+ # Log command end just like SQSConsumer
97
+ "sudo", "echo", %("Command ID=#{command.id} \\`#{command.command_string}' exited on host `hostname`: pid $$ exit $status"),
98
+ ">>", COMMAND_LOGFILE, "&&",
99
+ # Propagate command exit status
100
+ *%w(exit $status),
101
+ ],
102
+ logger: ui,
103
+ ).connect
104
+ rescue ElasticBeans::SSH::BastionAuthenticationError => e
105
+ raise BastionAuthenticationError.new(cause: e)
106
+ ensure
107
+ application.kill_command(freeze_command)
108
+ end
109
+ else
110
+ application.enqueue_command(command)
111
+ end
27
112
  end
28
113
 
29
114
  private
30
115
 
31
- attr_reader :application, :ui
116
+ attr_reader(
117
+ :application,
118
+ :bastion_host,
119
+ :bastion_identity_file,
120
+ :bastion_username,
121
+ :identity_file,
122
+ :username,
123
+ :ec2,
124
+ :ui,
125
+ )
32
126
 
33
- def command(command_parts)
127
+ def command_from_parts(command_parts)
34
128
  command_string = command_parts.map { |word| Shellwords.escape(word) }.join(" ")
35
129
  ::ElasticBeans::Exec::Command.new(command_string: command_string)
36
130
  end
131
+
132
+ def interactive?
133
+ @interactive
134
+ end
135
+
136
+ def wait_until_command_taken(command)
137
+ taken_command = nil
138
+ Timeout.timeout(TAKEN_WAIT_TIMEOUT) do
139
+ until taken_command
140
+ sleep(TAKEN_WAIT_PERIOD)
141
+ taken_command = application.enqueued_commands.find { |cmd| cmd == command && !cmd.instance_id.nil? }
142
+ end
143
+ end
144
+ taken_command
145
+ rescue Timeout::Error
146
+ raise ExecEnvironmentBusyError.new(timeout: TAKEN_WAIT_TIMEOUT)
147
+ end
148
+
149
+ class BastionAuthenticationError < ElasticBeans::Error
150
+ def initialize(cause:)
151
+ @cause = cause
152
+ end
153
+
154
+ def backtrace
155
+ @cause.backtrace
156
+ end
157
+
158
+ def message
159
+ <<-MESSAGE
160
+ #{@cause.message}
161
+
162
+ Please check the bastion options and try again.
163
+
164
+ #{command_help "exec"}
165
+ MESSAGE
166
+ end
167
+ end
168
+
169
+ class ExecEnvironmentBusyError < ElasticBeans::Error
170
+ def initialize(timeout:)
171
+ @timeout = timeout
172
+ end
173
+
174
+ def message
175
+ <<-MESSAGE
176
+ The exec environment is too busy to reserve an instance for this interactive session (we waited #{@timeout} seconds).
177
+ Please scale it up and try again.
178
+ MESSAGE
179
+ end
180
+ end
181
+
182
+ class TerminatedInstanceError < ElasticBeans::Error
183
+ def initialize(instance_id:)
184
+ @instance_id = instance_id
185
+ end
186
+
187
+ def message
188
+ <<-MESSAGE
189
+ The exec instance '#{@instance_id}' was terminated.
190
+ Please check the exec environment status, wait for the environment to stabilize, and try again.
191
+ MESSAGE
192
+ msg
193
+ end
194
+ end
37
195
  end
38
196
  end
39
197
  end
@@ -37,6 +37,7 @@ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID an
37
37
  def run
38
38
  ui.debug { "Fetching enqueued commands from #{application.name}..." }
39
39
  enqueued_commands = application.enqueued_commands
40
+ enqueued_commands.reject!(&:freeze_instance?)
40
41
 
41
42
  enqueued_commands, running_commands = enqueued_commands.partition { |cmd| cmd.start_time.nil? }
42
43
  running_commands.sort_by!(&:start_time)
@@ -4,7 +4,7 @@ require "elastic_beans/error/environments_not_ready"
4
4
  module ElasticBeans
5
5
  module Command
6
6
  # :nodoc: all
7
- class Ssh
7
+ class SSH
8
8
  USAGE = "ssh ENVIRONMENT_TYPE"
9
9
  DESC = "Connect to an instance for debugging, tunneling through a bastion server"
10
10
  LONG_DESC = <<-LONG_DESC
@@ -53,57 +53,26 @@ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID an
53
53
  raise NoInstanceError.new(environment: environment, index: index)
54
54
  end
55
55
 
56
- begin
57
- instance_ip = ec2.describe_instances(instance_ids: [instance_id]).reservations[0].instances[0].private_ip_address
58
- rescue ::Aws::EC2::Errors::InvalidInstanceIDMalformed
59
- raise NoInstanceError.new(environment: environment, index: index)
60
- end
56
+ instance_ip = ec2.describe_instances(instance_ids: [instance_id]).reservations[0].instances[0].private_ip_address
61
57
  # It's possible a retry would just work, but I'd like to see this happen in reality before I assume that.
62
58
  if instance_ip.nil?
63
59
  raise TerminatedInstanceError.new(instance_id: instance_id, environment: environment)
64
60
  end
65
61
 
66
- ssh_args = ["ssh"]
67
-
68
- if identity_file
69
- ssh_args << "-i"
70
- ssh_args << identity_file
71
- end
72
-
73
- gateway = nil
74
- if bastion_host
75
- ssh_args << "#{username}@localhost"
76
- ssh_args += ["-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no"]
77
- begin
78
- bastion_options = {auth_methods: ["publickey"]}
79
- if bastion_identity_file
80
- bastion_options[:key_data] = File.read(bastion_identity_file)
81
- end
82
-
83
- ui.info("Connecting to '#{bastion_username}@#{bastion_host}'...")
84
- gateway = Net::SSH::Gateway.new(
85
- bastion_host,
86
- bastion_username,
87
- bastion_options
88
- )
89
- port = gateway.open(instance_ip, 22)
90
- ui.debug { "Opened gateway to '#{instance_ip}' on port '#{port}'" }
91
- ssh_args += ["-p", port.to_s]
92
- rescue Net::SSH::AuthenticationFailed => e
93
- raise BastionAuthenticationError.new(cause: e)
94
- end
95
- else
96
- ssh_args << "#{username}@#{instance_ip}"
97
- end
98
-
99
- ui.info("Connecting to '#{username}@#{instance_ip}'...")
100
- pid = Process.spawn(*ssh_args)
101
- _, status = Process.waitpid2(pid)
102
- unless status.success?
103
- raise SshFailedError
104
- end
105
- ensure
106
- gateway.close(port) if gateway
62
+ ui.info("Connecting to #{username}@#{instance_ip}...")
63
+ ElasticBeans::SSH.new(
64
+ hostname: instance_ip,
65
+ username: username,
66
+ identity_file: identity_file,
67
+ bastion_host: bastion_host,
68
+ bastion_username: bastion_username,
69
+ bastion_identity_file: bastion_identity_file,
70
+ logger: ui,
71
+ ).connect
72
+ rescue ::Aws::EC2::Errors::InvalidInstanceIDMalformed
73
+ raise NoInstanceError.new(environment: environment, index: index)
74
+ rescue ElasticBeans::SSH::BastionAuthenticationError => e
75
+ raise BastionAuthenticationError.new(cause: e)
107
76
  end
108
77
 
109
78
  private
@@ -127,11 +96,15 @@ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID an
127
96
  @cause = cause
128
97
  end
129
98
 
99
+ def backtrace
100
+ @cause.backtrace
101
+ end
102
+
130
103
  def message
131
104
  <<-MESSAGE
132
105
  #{@cause.message}
133
106
 
134
- Please check the --bastion-identity-file or --bastion-username options and try again.
107
+ Please check the bastion options and try again.
135
108
 
136
109
  #{command_help "ssh"}
137
110
  MESSAGE
@@ -159,12 +132,6 @@ Please check the --bastion-identity-file or --bastion-username options and try a
159
132
  end
160
133
  end
161
134
 
162
- class SshFailedError < ElasticBeans::Error
163
- def message
164
- ""
165
- end
166
- end
167
-
168
135
  class TerminatedInstanceError < ElasticBeans::Error
169
136
  def initialize(instance_id:, environment:)
170
137
  @instance_id = instance_id
@@ -175,8 +142,6 @@ Please check the --bastion-identity-file or --bastion-username options and try a
175
142
  <<-MESSAGE
176
143
  The instance '#{@instance_id}' in the environment '#{@environment.name}' has been terminated.
177
144
  Please try again in a moment.
178
-
179
- #{command_help "ssh"}
180
145
  MESSAGE
181
146
  end
182
147
  end
@@ -21,6 +21,10 @@ module ElasticBeans
21
21
  TIER_TYPE = "Standard"
22
22
  # :category: Internal
23
23
  WORKER_TEMPLATE_NAME_PATTERN = /worker-(?<queue>\w+)/
24
+ # :category: Internal
25
+ WAIT_TIMEOUT = 3600
26
+ # :category: Internal
27
+ DEGRADED_TIMEOUT = 90
24
28
 
25
29
  attr_reader :name
26
30
 
@@ -279,7 +283,7 @@ module ElasticBeans
279
283
  status = wait_status[0]
280
284
  health = "Grey"
281
285
  health_status = wait_health_status[0]
282
- Timeout.timeout(3600) do
286
+ Timeout.timeout(WAIT_TIMEOUT) do
283
287
  while wait_status.include?(status) || wait_health_status.include?(health_status)
284
288
  sleep 5
285
289
  environment = environment_description
@@ -289,8 +293,8 @@ module ElasticBeans
289
293
  end
290
294
  end
291
295
 
292
- # Wait a little bit longer. Sometimes apps are Ready but still Degraded for a few seconds.
293
- Timeout.timeout(30) do
296
+ # Wait a little bit longer. Sometimes apps are Ready but still Degraded for a few moments.
297
+ Timeout.timeout(DEGRADED_TIMEOUT) do
294
298
  while status == "Ready" && health != "Green"
295
299
  sleep 5
296
300
  environment = environment_description
@@ -1,8 +1,12 @@
1
1
  container_commands:
2
2
  00_copy_files:
3
- command: "mkdir -vp /opt/elastic_beans && cp -vR /var/app/ondeck/.elastic_beans/exec /opt/elastic_beans/"
3
+ command: "mkdir -vp /opt/elastic_beans && cp -vR .elastic_beans/exec /opt/elastic_beans/"
4
4
  01_permissions:
5
5
  command: "chmod 755 /opt/elastic_beans/exec/run_command.sh"
6
+ 05_log_files:
7
+ command: |
8
+ mkdir -vp /var/log/elastic_beans/exec
9
+ touch /var/log/elastic_beans/exec/command.log
6
10
  09_logrotate:
7
11
  command: "cp -v /opt/elastic_beans/exec/logrotate /etc/logrotate.d/elastic_beans_exec"
8
12
  test: "/opt/elasticbeanstalk/bin/get-config meta -k sqsdconfig --output YAML | grep -q '^environment_name: .*-exec$'"
@@ -1,5 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
+ set -e
4
+
3
5
  # execute in the context of the application
4
6
  EB_APP_DIR="`/opt/elasticbeanstalk/bin/get-config container -k app_deploy_dir`"
5
7
  cd "$EB_APP_DIR"
@@ -1,4 +1,4 @@
1
1
  container_commands:
2
2
  remove_scheduler_file:
3
- command: "rm -f /var/app/ondeck/cron.yaml"
3
+ command: "rm -f cron.yaml"
4
4
  test: "/opt/elasticbeanstalk/bin/get-config meta -k sqsdconfig --output YAML | grep '^environment_name: ' | grep -qv '^environment_name: .*-scheduler$'"
@@ -0,0 +1,110 @@
1
+ require 'logger'
2
+
3
+ module ElasticBeans
4
+ # Opens an SSH connection, tunneling through a bastion host if specified.
5
+ class SSH
6
+ def initialize(
7
+ hostname:,
8
+ username:,
9
+ bastion_host: nil,
10
+ bastion_username: nil,
11
+ bastion_identity_file: nil,
12
+ identity_file: nil,
13
+ ssh_options: [],
14
+ command: [],
15
+ logger: Logger.new('/dev/null')
16
+ )
17
+ @hostname = hostname
18
+ @username = username
19
+ @bastion_host = bastion_host
20
+ @bastion_username = bastion_username
21
+ @bastion_identity_file = bastion_identity_file
22
+ @identity_file = identity_file
23
+ @ssh_options = ssh_options
24
+ @command = command
25
+ @logger = logger
26
+ end
27
+
28
+ # Opens an SSH connection, tunneling through a bastion host if specified.
29
+ # Spawns a child process using the ssh command-line utility and waits for it to complete.
30
+ #
31
+ # Raises an error if SSH exits with a non-zero exit status.
32
+ def connect
33
+ ssh_args = ["ssh", *ssh_options]
34
+
35
+ if identity_file
36
+ ssh_args << "-i"
37
+ ssh_args << identity_file
38
+ end
39
+
40
+ gateway = nil
41
+ if bastion_host
42
+ ssh_args << "#{username}@localhost"
43
+ ssh_args += ["-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no"]
44
+ begin
45
+ bastion_options = {auth_methods: ["publickey"]}
46
+ if bastion_identity_file
47
+ bastion_options[:key_data] = File.read(bastion_identity_file)
48
+ end
49
+
50
+ logger.info("Connecting to '#{bastion_username}@#{bastion_host}'...")
51
+ gateway = Net::SSH::Gateway.new(
52
+ bastion_host,
53
+ bastion_username,
54
+ bastion_options,
55
+ )
56
+ port = gateway.open(hostname, 22)
57
+ logger.debug { "Opened gateway to '#{hostname}' on port '#{port}'" }
58
+ ssh_args += ["-p", port.to_s]
59
+ rescue Net::SSH::Exception => e
60
+ raise BastionAuthenticationError.new(cause: e)
61
+ end
62
+ else
63
+ ssh_args << "#{username}@#{hostname}"
64
+ end
65
+
66
+ logger.debug { "Connecting: `#{ssh_args.join(' ')}'..." }
67
+ pid = Process.spawn(*ssh_args, *command)
68
+ _, status = Process.waitpid2(pid)
69
+ unless status.success?
70
+ raise SSHFailedError
71
+ end
72
+ ensure
73
+ gateway.close(port) if gateway
74
+ end
75
+
76
+ private
77
+
78
+ attr_reader(
79
+ :bastion_host,
80
+ :bastion_identity_file,
81
+ :bastion_username,
82
+ :command,
83
+ :identity_file,
84
+ :hostname,
85
+ :ssh_options,
86
+ :username,
87
+ :logger,
88
+ )
89
+
90
+ class BastionAuthenticationError < ElasticBeans::Error
91
+ def initialize(cause:)
92
+ @cause = cause
93
+ end
94
+
95
+ def backtrace
96
+ @cause.backtrace
97
+ end
98
+
99
+ def message
100
+ @cause.message
101
+ end
102
+ end
103
+
104
+ class SSHFailedError < ElasticBeans::Error
105
+ def message
106
+ ""
107
+ end
108
+ end
109
+ end
110
+ end
@@ -1,3 +1,3 @@
1
1
  module ElasticBeans
2
- VERSION = "0.10.0.alpha6"
2
+ VERSION = "0.10.0.alpha7"
3
3
  end
data/lib/elastic_beans.rb CHANGED
@@ -8,4 +8,5 @@ require "elastic_beans/env_vars"
8
8
  require "elastic_beans/environment"
9
9
  require "elastic_beans/exec"
10
10
  require "elastic_beans/network"
11
+ require "elastic_beans/ssh"
11
12
  require "elastic_beans/version"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elastic_beans
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0.alpha6
4
+ version: 0.10.0.alpha7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Stegman
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-04-26 00:00:00.000000000 Z
11
+ date: 2017-04-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk
@@ -233,6 +233,7 @@ files:
233
233
  - lib/elastic_beans/network.rb
234
234
  - lib/elastic_beans/rack/exec.rb
235
235
  - lib/elastic_beans/scheduler/ebextension.yml
236
+ - lib/elastic_beans/ssh.rb
236
237
  - lib/elastic_beans/ui.rb
237
238
  - lib/elastic_beans/version.rb
238
239
  homepage: https://github.com/onemedical/elastic_beans