elastic_beans 0.9.1 → 0.10.0.alpha1
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.
- checksums.yaml +4 -4
- data/Gemfile +0 -1
- data/README.md +4 -0
- data/elastic_beans.gemspec +1 -0
- data/lib/elastic_beans/cli.rb +35 -0
- data/lib/elastic_beans/command/create.rb +1 -3
- data/lib/elastic_beans/command/scale.rb +1 -1
- data/lib/elastic_beans/command/ssh.rb +185 -0
- data/lib/elastic_beans/command.rb +1 -0
- data/lib/elastic_beans/environment/worker.rb +2 -2
- data/lib/elastic_beans/environment.rb +27 -6
- data/lib/elastic_beans/version.rb +1 -1
- metadata +20 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 44f1c4f70c8a932819315f3d02623b75423ff299
|
|
4
|
+
data.tar.gz: 270d9d55029d816b5f45f91b13d2ff3e89e6b456
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c9808cfe16431ef5bd7ac8df11ac31e85331d36c0fefb89d24c6aa95bd91ca593762e09db378c3913a29bd0dd4d732cc4e0b831aaf39645684cbd123cc720acc
|
|
7
|
+
data.tar.gz: e223d9b76c7b922dd3271958458e86a100eaf45842c47012b33de0d4b647fc405f68d009f56daa7cb9d37365dec7230181b93631240fb0bbf0de8a26daad345c
|
data/Gemfile
CHANGED
data/README.md
CHANGED
|
@@ -53,6 +53,10 @@ As the SDK documentation suggests, using environment variables is recommended.
|
|
|
53
53
|
# Run one-off tasks
|
|
54
54
|
beans exec -a myapp rake db:migrate
|
|
55
55
|
|
|
56
|
+
# SSH to an instance for debugging, tunneling through a bastion instance to reach the private network
|
|
57
|
+
beans ssh -a myapp webserver [-n INDEX] [-i IDENTITY_FILE] [-u USERNAME] \
|
|
58
|
+
[-b BASTION_HOST] [--bastion-identity-file BASTION_IDENTITY_FILE] [--bastion-username BASTION_USERNAME]
|
|
59
|
+
|
|
56
60
|
# Update all existing environments and configuration
|
|
57
61
|
beans configure -n myapp-networking -a myapp \
|
|
58
62
|
[-b SECRET_KEY_BASE] [-d DATABASE_URL] [-k KEYPAIR] \
|
data/elastic_beans.gemspec
CHANGED
|
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
|
|
|
20
20
|
|
|
21
21
|
spec.add_dependency "aws-sdk", "~> 2.3"
|
|
22
22
|
spec.add_dependency "nesty", "~> 1.0"
|
|
23
|
+
spec.add_dependency "net-ssh-gateway"
|
|
23
24
|
spec.add_dependency "rails"
|
|
24
25
|
spec.add_dependency "ruby-progressbar", "~> 1.2"
|
|
25
26
|
spec.add_dependency "rubyzip", "~> 1.2"
|
data/lib/elastic_beans/cli.rb
CHANGED
|
@@ -200,6 +200,37 @@ class ElasticBeans::CLI < Thor
|
|
|
200
200
|
|
|
201
201
|
map ["delvar", "rmvar"] => "unsetenv"
|
|
202
202
|
|
|
203
|
+
desc ElasticBeans::Command::Ssh::USAGE, ElasticBeans::Command::Ssh::DESC
|
|
204
|
+
long_desc ElasticBeans::Command::Ssh::LONG_DESC
|
|
205
|
+
option :application, aliases: %w(-a), required: true, desc: APPLICATION_DESC
|
|
206
|
+
option :bastion_host, aliases: %w(-b), desc: "The hostname of the bastion server in the VPC"
|
|
207
|
+
option :bastion_identity_file, desc: "The SSH private key to use to connect to the bastion server"
|
|
208
|
+
option :bastion_username, desc: "The username to use to connect to the bastion server"
|
|
209
|
+
option :identity_file, aliases: %w(-i), desc: "The SSH private key associated with the keypair used when creating the environment"
|
|
210
|
+
option :index, aliases: %w(-n), desc: "The 0-based index of the instance to connect to"
|
|
211
|
+
option :queue, aliases: %w(-q), desc: "The name of the queue to identify the worker environment to connect to, e.g. `default`"
|
|
212
|
+
option :username, aliases: %w(-u), desc: "The username to log into the instance with, e.g. `ec2-user`"
|
|
213
|
+
def ssh(environment_type)
|
|
214
|
+
@verbose = options[:verbose]
|
|
215
|
+
ElasticBeans::Command::Ssh.new(
|
|
216
|
+
application: application(
|
|
217
|
+
name: options[:application],
|
|
218
|
+
),
|
|
219
|
+
bastion_host: options[:bastion_host],
|
|
220
|
+
bastion_identity_file: options[:bastion_identity_file],
|
|
221
|
+
bastion_username: options[:bastion_username],
|
|
222
|
+
identity_file: options[:identity_file],
|
|
223
|
+
index: options[:index],
|
|
224
|
+
queue: options[:queue],
|
|
225
|
+
username: options[:username],
|
|
226
|
+
ec2: ec2_client,
|
|
227
|
+
elastic_beanstalk: elastic_beanstalk_client,
|
|
228
|
+
ui: ui,
|
|
229
|
+
).run(environment_type)
|
|
230
|
+
rescue StandardError => e
|
|
231
|
+
error(e)
|
|
232
|
+
end
|
|
233
|
+
|
|
203
234
|
desc "talk", ""
|
|
204
235
|
def talk
|
|
205
236
|
ElasticBeans::Command::Talk.new(ui: ui).run
|
|
@@ -233,6 +264,10 @@ class ElasticBeans::CLI < Thor
|
|
|
233
264
|
::Aws::CloudFormation::Client.new
|
|
234
265
|
end
|
|
235
266
|
|
|
267
|
+
def ec2_client
|
|
268
|
+
::Aws::EC2::Client.new
|
|
269
|
+
end
|
|
270
|
+
|
|
236
271
|
def elastic_beanstalk_client
|
|
237
272
|
::Aws::ElasticBeanstalk::Client.new
|
|
238
273
|
end
|
|
@@ -34,9 +34,7 @@ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID an
|
|
|
34
34
|
)
|
|
35
35
|
@environment_type = environment_type
|
|
36
36
|
@dns = dns
|
|
37
|
-
|
|
38
|
-
@queue = queue || "default"
|
|
39
|
-
end
|
|
37
|
+
@queue = queue
|
|
40
38
|
@tags = tags
|
|
41
39
|
@application = application
|
|
42
40
|
@ui = ui
|
|
@@ -18,7 +18,7 @@ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID an
|
|
|
18
18
|
@application = application
|
|
19
19
|
@minimum = minimum
|
|
20
20
|
@maximum = maximum
|
|
21
|
-
@queue = queue
|
|
21
|
+
@queue = queue
|
|
22
22
|
@elastic_beanstalk = elastic_beanstalk
|
|
23
23
|
@ui = ui
|
|
24
24
|
end
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
require "net/ssh/gateway"
|
|
2
|
+
require "elastic_beans/error/environments_not_ready"
|
|
3
|
+
|
|
4
|
+
module ElasticBeans
|
|
5
|
+
module Command
|
|
6
|
+
# :nodoc: all
|
|
7
|
+
class Ssh
|
|
8
|
+
USAGE = "ssh ENVIRONMENT_TYPE"
|
|
9
|
+
DESC = "Connect to an instance for debugging, tunneling through a bastion server"
|
|
10
|
+
LONG_DESC = <<-LONG_DESC
|
|
11
|
+
Connect to an instance for debugging, tunneling through a bastion server.
|
|
12
|
+
Opens a login shell.
|
|
13
|
+
|
|
14
|
+
Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.
|
|
15
|
+
LONG_DESC
|
|
16
|
+
|
|
17
|
+
def initialize(
|
|
18
|
+
application:,
|
|
19
|
+
bastion_host:,
|
|
20
|
+
bastion_identity_file:,
|
|
21
|
+
bastion_username:,
|
|
22
|
+
identity_file:,
|
|
23
|
+
index:,
|
|
24
|
+
queue:,
|
|
25
|
+
username:,
|
|
26
|
+
ec2:,
|
|
27
|
+
elastic_beanstalk:,
|
|
28
|
+
ui:
|
|
29
|
+
)
|
|
30
|
+
@application = application
|
|
31
|
+
@bastion_host = bastion_host
|
|
32
|
+
@bastion_identity_file = bastion_identity_file
|
|
33
|
+
@bastion_username = bastion_username
|
|
34
|
+
@identity_file = identity_file
|
|
35
|
+
@index = index.to_i
|
|
36
|
+
@queue = queue
|
|
37
|
+
@username = username || "ec2-user"
|
|
38
|
+
@ec2 = ec2
|
|
39
|
+
@elastic_beanstalk = elastic_beanstalk
|
|
40
|
+
@ui = ui
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def run(environment_type)
|
|
44
|
+
environment = ElasticBeans::Environment.new_by_type(
|
|
45
|
+
environment_type,
|
|
46
|
+
queue: queue,
|
|
47
|
+
application: application,
|
|
48
|
+
elastic_beanstalk: elastic_beanstalk,
|
|
49
|
+
)
|
|
50
|
+
instance_id = environment.instance_ids[index]
|
|
51
|
+
ui.debug { "In environment '#{environment.name}' found instance '#{instance_id}' at index '#{index}' of #{environment.instance_ids.inspect}" }
|
|
52
|
+
if instance_id.nil?
|
|
53
|
+
raise NoInstanceError.new(environment: environment, index: index)
|
|
54
|
+
end
|
|
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
|
|
61
|
+
# It's possible a retry would just work, but I'd like to see this happen in reality before I assume that.
|
|
62
|
+
if instance_ip.nil?
|
|
63
|
+
raise TerminatedInstanceError.new(instance_id: instance_id, environment: environment)
|
|
64
|
+
end
|
|
65
|
+
|
|
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
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
attr_reader(
|
|
112
|
+
:application,
|
|
113
|
+
:bastion_host,
|
|
114
|
+
:bastion_identity_file,
|
|
115
|
+
:bastion_username,
|
|
116
|
+
:identity_file,
|
|
117
|
+
:index,
|
|
118
|
+
:queue,
|
|
119
|
+
:username,
|
|
120
|
+
:ec2,
|
|
121
|
+
:elastic_beanstalk,
|
|
122
|
+
:ui,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
class BastionAuthenticationError < ElasticBeans::Error
|
|
126
|
+
def initialize(cause:)
|
|
127
|
+
@cause = cause
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def message
|
|
131
|
+
<<-MESSAGE
|
|
132
|
+
#{@cause.message}
|
|
133
|
+
|
|
134
|
+
Please check the --bastion-identity-file or --bastion-username options and try again.
|
|
135
|
+
|
|
136
|
+
#{command_help "ssh"}
|
|
137
|
+
MESSAGE
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
class NoInstanceError < ElasticBeans::Error
|
|
142
|
+
def initialize(environment:, index:)
|
|
143
|
+
@environment = environment
|
|
144
|
+
@index = index
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def message
|
|
148
|
+
msg = "The '#{@environment.name}' environment has no instance at index #{@index}.\n"
|
|
149
|
+
if @index > 0
|
|
150
|
+
msg << "Try again with a lower index.\n"
|
|
151
|
+
else
|
|
152
|
+
msg << "Wait for instances to be created and try again.\n"
|
|
153
|
+
end
|
|
154
|
+
msg << <<-MESSAGE
|
|
155
|
+
|
|
156
|
+
#{command_help "ssh"}
|
|
157
|
+
MESSAGE
|
|
158
|
+
msg
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
class SshFailedError < ElasticBeans::Error
|
|
163
|
+
def message
|
|
164
|
+
""
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
class TerminatedInstanceError < ElasticBeans::Error
|
|
169
|
+
def initialize(instance_id:, environment:)
|
|
170
|
+
@instance_id = instance_id
|
|
171
|
+
@environment = environment
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def message
|
|
175
|
+
<<-MESSAGE
|
|
176
|
+
The instance '#{@instance_id}' in the environment '#{@environment.name}' has been terminated.
|
|
177
|
+
Please try again in a moment.
|
|
178
|
+
|
|
179
|
+
#{command_help "ssh"}
|
|
180
|
+
MESSAGE
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
@@ -8,6 +8,7 @@ require "elastic_beans/command/scale"
|
|
|
8
8
|
require "elastic_beans/command/get_env"
|
|
9
9
|
require "elastic_beans/command/set_env"
|
|
10
10
|
require "elastic_beans/command/unset_env"
|
|
11
|
+
require "elastic_beans/command/ssh"
|
|
11
12
|
require "elastic_beans/command/talk"
|
|
12
13
|
require "elastic_beans/command/version"
|
|
13
14
|
|
|
@@ -40,7 +40,8 @@ module ElasticBeans
|
|
|
40
40
|
when "webserver"
|
|
41
41
|
ElasticBeans::Environment::Webserver.new(name, application: application, **args)
|
|
42
42
|
when "worker"
|
|
43
|
-
|
|
43
|
+
queue_name = args[:queue] || "default"
|
|
44
|
+
name = "#{name}-#{queue_name}"
|
|
44
45
|
ElasticBeans::Environment::Worker.new(name, application: application, **args)
|
|
45
46
|
else
|
|
46
47
|
raise UnknownEnvironmentType.new(environment_type: type)
|
|
@@ -78,7 +79,6 @@ module ElasticBeans
|
|
|
78
79
|
ElasticBeans::Environment::Worker.new(
|
|
79
80
|
environment.environment_name,
|
|
80
81
|
application: application,
|
|
81
|
-
queue: "default",
|
|
82
82
|
**args,
|
|
83
83
|
)
|
|
84
84
|
else
|
|
@@ -211,6 +211,15 @@ module ElasticBeans
|
|
|
211
211
|
environment.cname
|
|
212
212
|
end
|
|
213
213
|
|
|
214
|
+
# Returns the instance IDs that are part of this environment.
|
|
215
|
+
#
|
|
216
|
+
# Raises an error if the environment does not exist.
|
|
217
|
+
def instance_ids
|
|
218
|
+
environment_resources.instances.map(&:id)
|
|
219
|
+
rescue ::Aws::ElasticBeanstalk::Errors::InvalidParameterValue
|
|
220
|
+
raise MissingEnvironmentError.new(environment: self, application: application)
|
|
221
|
+
end
|
|
222
|
+
|
|
214
223
|
# Returns the status of the environment in Elastic Beanstalk.
|
|
215
224
|
#
|
|
216
225
|
# Raises an error if the environment does not exist.
|
|
@@ -242,6 +251,15 @@ module ElasticBeans
|
|
|
242
251
|
retry
|
|
243
252
|
end
|
|
244
253
|
|
|
254
|
+
def environment_resources
|
|
255
|
+
elastic_beanstalk.describe_environment_resources(
|
|
256
|
+
environment_name: name,
|
|
257
|
+
).environment_resources
|
|
258
|
+
rescue ::Aws::ElasticBeanstalk::Errors::Throttling
|
|
259
|
+
sleep 5
|
|
260
|
+
retry
|
|
261
|
+
end
|
|
262
|
+
|
|
245
263
|
def template_name
|
|
246
264
|
TEMPLATE_NAME
|
|
247
265
|
end
|
|
@@ -324,10 +342,13 @@ Please re-run `#{command_as_string "configure"}`.
|
|
|
324
342
|
|
|
325
343
|
def message
|
|
326
344
|
require "elastic_beans/command/create"
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
345
|
+
msg = "Environment `#{@environment.name}' does not exist.\n"
|
|
346
|
+
msg << "Please run `#{command_as_string "create #{@environment.type}"}"
|
|
347
|
+
if @environment.respond_to?(:queue)
|
|
348
|
+
msg << " -q #{@environment.queue}"
|
|
349
|
+
end
|
|
350
|
+
msg << " -a #{@application.name}`.\n"
|
|
351
|
+
msg
|
|
331
352
|
end
|
|
332
353
|
end
|
|
333
354
|
|
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.
|
|
4
|
+
version: 0.10.0.alpha1
|
|
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-
|
|
11
|
+
date: 2017-04-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: aws-sdk
|
|
@@ -38,6 +38,20 @@ dependencies:
|
|
|
38
38
|
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '1.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: net-ssh-gateway
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
41
55
|
- !ruby/object:Gem::Dependency
|
|
42
56
|
name: rails
|
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -186,6 +200,7 @@ files:
|
|
|
186
200
|
- lib/elastic_beans/command/restart.rb
|
|
187
201
|
- lib/elastic_beans/command/scale.rb
|
|
188
202
|
- lib/elastic_beans/command/set_env.rb
|
|
203
|
+
- lib/elastic_beans/command/ssh.rb
|
|
189
204
|
- lib/elastic_beans/command/talk.rb
|
|
190
205
|
- lib/elastic_beans/command/unset_env.rb
|
|
191
206
|
- lib/elastic_beans/command/version.rb
|
|
@@ -232,12 +247,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
232
247
|
version: '0'
|
|
233
248
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
234
249
|
requirements:
|
|
235
|
-
- - "
|
|
250
|
+
- - ">"
|
|
236
251
|
- !ruby/object:Gem::Version
|
|
237
|
-
version:
|
|
252
|
+
version: 1.3.1
|
|
238
253
|
requirements: []
|
|
239
254
|
rubyforge_project:
|
|
240
|
-
rubygems_version: 2.
|
|
255
|
+
rubygems_version: 2.6.11
|
|
241
256
|
signing_key:
|
|
242
257
|
specification_version: 4
|
|
243
258
|
summary: Elastic Beanstalk environment orchestration for a Rails app
|