elastic_beans 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -0
- data/README.md +13 -8
- data/Rakefile +9 -1
- data/circle.yml +4 -0
- data/elastic_beans.gemspec +1 -0
- data/lib/elastic_beans.rb +1 -0
- data/lib/elastic_beans/application.rb +64 -9
- data/lib/elastic_beans/application_version.rb +4 -0
- data/lib/elastic_beans/cli.rb +18 -4
- data/lib/elastic_beans/command.rb +1 -0
- data/lib/elastic_beans/command/configure.rb +0 -4
- data/lib/elastic_beans/command/exec.rb +6 -6
- data/lib/elastic_beans/command/ps.rb +64 -0
- data/lib/elastic_beans/configuration_template/exec.rb +3 -33
- data/lib/elastic_beans/exec.rb +6 -0
- data/lib/elastic_beans/exec/command.rb +58 -0
- data/lib/elastic_beans/exec/ebextension.yml +4 -0
- data/lib/elastic_beans/exec/init.rb +10 -4
- data/lib/elastic_beans/exec/logrotate +8 -0
- data/lib/elastic_beans/exec/sqs_consumer.rb +101 -41
- data/lib/elastic_beans/rack/exec.rb +4 -4
- data/lib/elastic_beans/ui.rb +30 -0
- data/lib/elastic_beans/version.rb +1 -1
- metadata +21 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '058ffb4aa22e205e19470449420643bc3e55298f'
|
4
|
+
data.tar.gz: 5dcd342a6962c0ee82dd672c2ee07a32fc7b1562
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 40f88906d991227878c85386c3fd79fba38e14bbddddfbb26c9101e3c4b1e01d7996f9dafef8cb1b0e3035f59548a664f41c4de94f46d0010a7bd9b653c517d8
|
7
|
+
data.tar.gz: 3aeebfe162e590b106c6e253af178aef7c447ef4d005179ed197e34c4357a5a350bcc864a1e8eed51ef9c1d9e425a6617fe6a06df4a540e2b7aea8470f79829c
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -26,7 +26,7 @@ As the SDK documentation suggests, using environment variables is recommended.
|
|
26
26
|
beans configure -n myapp-networking -a myapp \
|
27
27
|
-b SECRET_KEY_BASE -d DATABASE_URL -k KEYPAIR \
|
28
28
|
-p INTERNAL_PUBLIC_KEY -s SSL_CERTIFICATE_ARN \
|
29
|
-
[-i IMAGE_ID] [-t INSTANCE_TYPE]
|
29
|
+
[-i IMAGE_ID] [-t INSTANCE_TYPE]
|
30
30
|
|
31
31
|
# Create a webserver environment with a pretty DNS name at myapp.TLD (managed by Route53)
|
32
32
|
beans create -a myapp [-d myapp.TLD] [--tags=Environment:production Team:Unicorn] webserver
|
@@ -50,14 +50,14 @@ As the SDK documentation suggests, using environment variables is recommended.
|
|
50
50
|
# Then deploy that version to each running environment.
|
51
51
|
beans deploy -a myapp
|
52
52
|
|
53
|
-
# Run one-off tasks
|
53
|
+
# Run one-off tasks
|
54
54
|
beans exec -a myapp rake db:migrate
|
55
55
|
|
56
56
|
# Update all existing environments and configuration
|
57
57
|
beans configure -n myapp-networking -a myapp \
|
58
58
|
[-b SECRET_KEY_BASE] [-d DATABASE_URL] [-k KEYPAIR] \
|
59
59
|
[-p INTERNAL_PUBLIC_KEY] [-s SSL_CERTIFICATE_ARN] \
|
60
|
-
[-i IMAGE_ID] [-t INSTANCE_TYPE]
|
60
|
+
[-i IMAGE_ID] [-t INSTANCE_TYPE]
|
61
61
|
|
62
62
|
### API
|
63
63
|
|
@@ -88,8 +88,8 @@ In `config/initializers/elastic_beans.rb`, add the middleware into your stack, b
|
|
88
88
|
cloudformation: Aws::CloudFormation::Client.new,
|
89
89
|
elastic_beanstalk: Aws::ElasticBeanstalk::Client.new,
|
90
90
|
s3: Aws::S3::Client.new,
|
91
|
+
sqs: Aws::SQS::Client.new,
|
91
92
|
),
|
92
|
-
sqs: Aws::SQS::Client.new,
|
93
93
|
logger: Rails.logger,
|
94
94
|
)
|
95
95
|
else
|
@@ -100,8 +100,8 @@ In `config/initializers/elastic_beans.rb`, add the middleware into your stack, b
|
|
100
100
|
cloudformation: Aws::CloudFormation::Client.new,
|
101
101
|
elastic_beanstalk: Aws::ElasticBeanstalk::Client.new,
|
102
102
|
s3: Aws::S3::Client.new,
|
103
|
+
sqs: Aws::SQS::Client.new,
|
103
104
|
),
|
104
|
-
sqs: Aws::SQS::Client.new,
|
105
105
|
logger: Rails.logger,
|
106
106
|
)
|
107
107
|
end
|
@@ -141,7 +141,6 @@ Before loading your application, the configuration is fetched and loaded.
|
|
141
141
|
Elastic Beans supports the execution of one-off and periodic commands in an isolated environment.
|
142
142
|
This way they can be computationally expensive without affecting application performance.
|
143
143
|
Additionally, they are not limited to the visibility timeout of an application background job queue.
|
144
|
-
Logs are persisted to an HTTPS endpoint upon command completion.
|
145
144
|
|
146
145
|
### Persistent configuration
|
147
146
|
|
@@ -209,9 +208,13 @@ Its details will be discovered from the following outputs:
|
|
209
208
|
* `ExecQueueUrl`
|
210
209
|
* `Worker[Name]QueueUrl`
|
211
210
|
|
212
|
-
|
211
|
+
A separate worker environment will be configured for each queue `[Name]` that appears.
|
213
212
|
A default worker queue, i.e. `WorkerDefaultQueueUrl`, must exist.
|
214
213
|
|
214
|
+
The `ExecQueueUrl` is used by `beans exec` to enqueue one-off commands.
|
215
|
+
It is also by `beans ps` to inspect scheduled commands that have not yet run.
|
216
|
+
Make sure that its redrive policy allows such inspection before considering a message failed.
|
217
|
+
|
215
218
|
### Code
|
216
219
|
|
217
220
|
Your application must use the [active-elastic-job gem](https://github.com/tawan/active-elastic-job) for background job processing.
|
@@ -288,7 +291,9 @@ Or install it yourself as:
|
|
288
291
|
|
289
292
|
## Development
|
290
293
|
|
291
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
294
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run all the tests, including feature tests. This will take about 1.5–2 hours. To run only the unit tests, run `rake spec:unit`.
|
295
|
+
|
296
|
+
You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
292
297
|
|
293
298
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
294
299
|
|
data/Rakefile
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rspec/core/rake_task"
|
3
3
|
|
4
|
-
RSpec::Core::RakeTask.new(:spec)
|
4
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
5
|
+
t.rspec_opts = ENV['RSPEC_OPTS']
|
6
|
+
end
|
5
7
|
|
6
8
|
task :default => :spec
|
9
|
+
|
10
|
+
namespace :spec do
|
11
|
+
RSpec::Core::RakeTask.new(:unit) do |t|
|
12
|
+
t.rspec_opts = "--tag ~feature #{ENV['RSPEC_OPTS']}"
|
13
|
+
end
|
14
|
+
end
|
data/circle.yml
CHANGED
data/elastic_beans.gemspec
CHANGED
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
|
|
24
24
|
spec.add_dependency "ruby-progressbar", "~> 1.2"
|
25
25
|
spec.add_dependency "rubyzip", "~> 1.2"
|
26
26
|
spec.add_dependency "thor", "~> 0.19.0"
|
27
|
+
spec.add_dependency "tty-table", "~> 0.7.0"
|
27
28
|
|
28
29
|
spec.add_development_dependency "bundler", "~> 1.12"
|
29
30
|
spec.add_development_dependency "rake", "~> 10.0"
|
data/lib/elastic_beans.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "json"
|
2
|
+
require "timeout"
|
2
3
|
require "aws-sdk"
|
3
4
|
require "elastic_beans/aws/cloudformation_stack"
|
4
5
|
require "elastic_beans/error"
|
@@ -10,10 +11,11 @@ module ElasticBeans
|
|
10
11
|
class Application
|
11
12
|
attr_reader :name
|
12
13
|
|
13
|
-
def initialize(name:, cloudformation:, elastic_beanstalk:, s3:)
|
14
|
+
def initialize(name:, cloudformation:, elastic_beanstalk:, s3:, sqs: nil)
|
14
15
|
@name = name
|
15
16
|
@elastic_beanstalk = elastic_beanstalk
|
16
17
|
@s3 = s3
|
18
|
+
@sqs = sqs
|
17
19
|
@stack = ElasticBeans::Aws::CloudformationStack.new(name, cloudformation: cloudformation)
|
18
20
|
end
|
19
21
|
|
@@ -96,25 +98,76 @@ module ElasticBeans
|
|
96
98
|
@bucket_name = bucket.name
|
97
99
|
end
|
98
100
|
|
99
|
-
# Enqueues a one-off
|
101
|
+
# Enqueues a one-off Exec::Command to be run on the application's +exec+ environment.
|
100
102
|
# Does not wait for action to be taken, but returns immediately after enqueuing the command.
|
101
103
|
#
|
102
|
-
# Raises an error if the exec environment cannot be found.
|
103
|
-
def enqueue_command(command
|
104
|
+
# Raises an error if the exec environment or queue cannot be found.
|
105
|
+
def enqueue_command(command)
|
104
106
|
if environments.none? { |environment| environment.is_a?(Environment::Exec) }
|
105
107
|
raise MissingExecEnvironmentError
|
106
108
|
end
|
107
109
|
|
110
|
+
if command.to_s == command
|
111
|
+
command = Exec::Command.new(command_string: command)
|
112
|
+
end
|
113
|
+
command.metadata[:bucket] = bucket_name
|
114
|
+
command.metadata[:key] = "#{command_key_prefix}#{command.id}.json"
|
115
|
+
|
108
116
|
sqs.send_message(
|
109
117
|
queue_url: exec_queue_url,
|
110
|
-
message_body:
|
118
|
+
message_body: command.to_json,
|
111
119
|
)
|
112
120
|
end
|
113
121
|
|
122
|
+
# Fetches commands enqueued in the +exec_queue_url+.
|
123
|
+
# Polls the queue for 5 seconds.
|
124
|
+
#
|
125
|
+
# If a large number of commands is enqueued they may not all be found.
|
126
|
+
#
|
127
|
+
# Raises an error if the exec queue cannot be found.
|
128
|
+
def enqueued_commands
|
129
|
+
messages = []
|
130
|
+
Timeout.timeout(5) do
|
131
|
+
loop do
|
132
|
+
messages += sqs.receive_message(
|
133
|
+
queue_url: exec_queue_url,
|
134
|
+
max_number_of_messages: 10,
|
135
|
+
visibility_timeout: 5,
|
136
|
+
wait_time_seconds: 5,
|
137
|
+
).messages
|
138
|
+
end
|
139
|
+
end
|
140
|
+
rescue Timeout::Error
|
141
|
+
messages.map { |msg| Exec::Command.from_json(msg.body) }
|
142
|
+
end
|
143
|
+
|
114
144
|
def exec_queue_url
|
115
145
|
stack.stack_output("ExecQueueUrl")
|
116
146
|
end
|
117
147
|
|
148
|
+
# Fetches commands running in the Environment::Exec environment.
|
149
|
+
# Commands are deserialized from metadata files in a well-known location in S3.
|
150
|
+
# The instances update the metadata when executing a command, and remove the metadata when they are done.
|
151
|
+
#
|
152
|
+
# Raises an error if the exec environment cannot be found.
|
153
|
+
def running_commands
|
154
|
+
# Ignoring truncation for simplicity; >100 commands will just have to suffer.
|
155
|
+
objects = s3.list_objects_v2(
|
156
|
+
bucket: bucket_name,
|
157
|
+
prefix: command_key_prefix,
|
158
|
+
max_keys: 100,
|
159
|
+
).contents
|
160
|
+
objects.each_with_object([]) { |object, commands|
|
161
|
+
begin
|
162
|
+
response = s3.get_object(bucket: bucket_name, key: object.key)
|
163
|
+
commands << ElasticBeans::Exec::Command.from_json(response.body.read)
|
164
|
+
# skip finished or invalid commands
|
165
|
+
rescue ::Aws::S3::Errors::NoSuchKey
|
166
|
+
rescue JSON::ParserError
|
167
|
+
end
|
168
|
+
}
|
169
|
+
end
|
170
|
+
|
118
171
|
# Returns an ElasticBeans::ApplicationVersion for each version of the Elastic Beanstalk application.
|
119
172
|
def versions
|
120
173
|
response = elastic_beanstalk.describe_application_versions(application_name: name)
|
@@ -146,10 +199,8 @@ module ElasticBeans
|
|
146
199
|
|
147
200
|
attr_reader :elastic_beanstalk, :s3, :stack
|
148
201
|
|
149
|
-
def
|
150
|
-
{
|
151
|
-
command: command,
|
152
|
-
}.to_json
|
202
|
+
def command_key_prefix
|
203
|
+
@command_key_prefix ||= "#{name}/exec/command/"
|
153
204
|
end
|
154
205
|
|
155
206
|
def exists?
|
@@ -159,6 +210,10 @@ module ElasticBeans
|
|
159
210
|
retry
|
160
211
|
end
|
161
212
|
|
213
|
+
def sqs
|
214
|
+
@sqs || raise("ElasticBeans::Application: Missing SQS client")
|
215
|
+
end
|
216
|
+
|
162
217
|
# :nodoc: all
|
163
218
|
# @!visibility private
|
164
219
|
class MissingApplicationError < ElasticBeans::Error
|
@@ -129,6 +129,10 @@ module ElasticBeans
|
|
129
129
|
".elastic_beans/exec/init.rb",
|
130
130
|
File.expand_path('../exec/init.rb', __FILE__),
|
131
131
|
)
|
132
|
+
zip_file.add(
|
133
|
+
".elastic_beans/exec/logrotate",
|
134
|
+
File.expand_path('../exec/logrotate', __FILE__),
|
135
|
+
)
|
132
136
|
zip_file.add(
|
133
137
|
".elastic_beans/exec/run_command.sh",
|
134
138
|
File.expand_path('../exec/run_command.sh', __FILE__),
|
data/lib/elastic_beans/cli.rb
CHANGED
@@ -20,7 +20,6 @@ class ElasticBeans::CLI < Thor
|
|
20
20
|
option :image_id, aliases: %w(-i), desc: "A custom AMI to use instead of the default Ruby Elastic Beanstalk AMI"
|
21
21
|
option :instance_type, aliases: %w(-t), desc: "A default instance type to use for all environments instead of c4.large"
|
22
22
|
option :keypair, aliases: %w(-k), desc: "Required on first run. The EC2 keypair to use for Elastic Beanstalk instances"
|
23
|
-
option :logging_endpoint, aliases: %w(-l), desc: "An HTTP endpoint that can receive logs from one-off commands"
|
24
23
|
option :public_key, aliases: %w(-p), desc: "For end-to-end encryption. The public key of the SSL certificate the ELB will verify to communicate with your Rails app"
|
25
24
|
option :secret_key_base, aliases: %w(-b), desc: "The SECRET_KEY_BASE for the Rails application"
|
26
25
|
option :ssl_certificate_arn, aliases: %w(-s), desc: "The ARN of the SSL server certificate stored in IAM to attach to the ELB"
|
@@ -31,7 +30,6 @@ class ElasticBeans::CLI < Thor
|
|
31
30
|
image_id: options[:image_id],
|
32
31
|
instance_type: options[:instance_type],
|
33
32
|
keypair: options[:keypair],
|
34
|
-
logging_endpoint: options[:logging_endpoint],
|
35
33
|
public_key: options[:public_key],
|
36
34
|
secret_key_base: options[:secret_key_base],
|
37
35
|
ssl_certificate_arn: options[:ssl_certificate_arn],
|
@@ -94,13 +92,27 @@ class ElasticBeans::CLI < Thor
|
|
94
92
|
application: application(
|
95
93
|
name: options[:application],
|
96
94
|
),
|
97
|
-
sqs: sqs_client,
|
98
95
|
ui: ui,
|
99
96
|
).run(*command_parts)
|
100
97
|
rescue StandardError => e
|
101
98
|
error(e)
|
102
99
|
end
|
103
100
|
|
101
|
+
desc ElasticBeans::Command::Ps::USAGE, ElasticBeans::Command::Ps::DESC
|
102
|
+
long_desc ElasticBeans::Command::Ps::LONG_DESC
|
103
|
+
option :application, aliases: %w(-a), required: true, desc: APPLICATION_DESC
|
104
|
+
def ps
|
105
|
+
@verbose = options[:verbose]
|
106
|
+
ElasticBeans::Command::Ps.new(
|
107
|
+
application: application(
|
108
|
+
name: options[:application],
|
109
|
+
),
|
110
|
+
ui: ui,
|
111
|
+
).run
|
112
|
+
rescue StandardError => e
|
113
|
+
error(e)
|
114
|
+
end
|
115
|
+
|
104
116
|
desc ElasticBeans::Command::Scale::USAGE, ElasticBeans::Command::Scale::DESC
|
105
117
|
long_desc ElasticBeans::Command::Scale::LONG_DESC
|
106
118
|
option :application, aliases: %w(-a), required: true, desc: APPLICATION_DESC
|
@@ -187,13 +199,15 @@ class ElasticBeans::CLI < Thor
|
|
187
199
|
name:,
|
188
200
|
cloudformation: cloudformation_client,
|
189
201
|
elastic_beanstalk: elastic_beanstalk_client,
|
190
|
-
s3: s3_client
|
202
|
+
s3: s3_client,
|
203
|
+
sqs: sqs_client
|
191
204
|
)
|
192
205
|
@application ||= ElasticBeans::Application.new(
|
193
206
|
name: name,
|
194
207
|
cloudformation: cloudformation,
|
195
208
|
elastic_beanstalk: elastic_beanstalk,
|
196
209
|
s3: s3,
|
210
|
+
sqs: sqs,
|
197
211
|
)
|
198
212
|
end
|
199
213
|
|
@@ -2,6 +2,7 @@ require "elastic_beans/command/configure"
|
|
2
2
|
require "elastic_beans/command/create"
|
3
3
|
require "elastic_beans/command/deploy"
|
4
4
|
require "elastic_beans/command/exec"
|
5
|
+
require "elastic_beans/command/ps"
|
5
6
|
require "elastic_beans/command/scale"
|
6
7
|
require "elastic_beans/command/get_env"
|
7
8
|
require "elastic_beans/command/set_env"
|
@@ -20,7 +20,6 @@ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID an
|
|
20
20
|
image_id:,
|
21
21
|
instance_type:,
|
22
22
|
keypair:,
|
23
|
-
logging_endpoint:,
|
24
23
|
public_key:,
|
25
24
|
secret_key_base:,
|
26
25
|
ssl_certificate_arn:,
|
@@ -34,7 +33,6 @@ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID an
|
|
34
33
|
@image_id = image_id
|
35
34
|
@instance_type = instance_type
|
36
35
|
@keypair = keypair
|
37
|
-
@logging_endpoint = logging_endpoint
|
38
36
|
@public_key = public_key
|
39
37
|
@secret_key_base = secret_key_base
|
40
38
|
@ssl_certificate_arn = ssl_certificate_arn
|
@@ -111,7 +109,6 @@ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID an
|
|
111
109
|
instance_type: instance_type,
|
112
110
|
keypair: keypair,
|
113
111
|
iam: iam,
|
114
|
-
logging_endpoint: logging_endpoint,
|
115
112
|
)
|
116
113
|
exec_environment = exec_config.environment
|
117
114
|
if exec_environment
|
@@ -182,7 +179,6 @@ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID an
|
|
182
179
|
:image_id,
|
183
180
|
:instance_type,
|
184
181
|
:keypair,
|
185
|
-
:logging_endpoint,
|
186
182
|
:network,
|
187
183
|
:public_key,
|
188
184
|
:secret_key_base,
|
@@ -10,28 +10,28 @@ module ElasticBeans
|
|
10
10
|
Run an arbitrary command in the context of your application.
|
11
11
|
The command is run in an "exec" environment, separate from your webserver or worker environments.
|
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
15
|
Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.
|
16
16
|
LONG_DESC
|
17
17
|
|
18
|
-
def initialize(application:,
|
18
|
+
def initialize(application:, ui:)
|
19
19
|
@application = application
|
20
|
-
@sqs = sqs
|
21
20
|
@ui = ui
|
22
21
|
end
|
23
22
|
|
24
23
|
def run(*command_parts)
|
25
24
|
ui.info("Running `#{command_parts.join(" ")}' on #{application.name}...")
|
26
|
-
application.enqueue_command(command(command_parts)
|
25
|
+
application.enqueue_command(command(command_parts))
|
27
26
|
end
|
28
27
|
|
29
28
|
private
|
30
29
|
|
31
|
-
attr_reader :application, :
|
30
|
+
attr_reader :application, :ui
|
32
31
|
|
33
32
|
def command(command_parts)
|
34
|
-
command_parts.map { |word| Shellwords.escape(word) }.join(" ")
|
33
|
+
command_string = command_parts.map { |word| Shellwords.escape(word) }.join(" ")
|
34
|
+
::ElasticBeans::Exec::Command.new(command_string: command_string)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module ElasticBeans
|
2
|
+
module Command
|
3
|
+
# :nodoc: all
|
4
|
+
class Ps
|
5
|
+
USAGE = "ps"
|
6
|
+
DESC = "List scheduled and running one-off commands run using `beans exec`"
|
7
|
+
LONG_DESC = <<-LONG_DESC
|
8
|
+
List scheduled and running one-off commands run using `beans exec`.
|
9
|
+
See `beans help exec` for more information about running one-off commands.
|
10
|
+
|
11
|
+
Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.
|
12
|
+
LONG_DESC
|
13
|
+
|
14
|
+
COLUMNS = {
|
15
|
+
"INSTANCE" => ->(command) { command.instance_id || SCHEDULED_INSTANCE },
|
16
|
+
"RUN TIME" => ->(command) {
|
17
|
+
if command.start_time
|
18
|
+
seconds = Time.now - command.start_time
|
19
|
+
hours, seconds = seconds / 3600, seconds % 3600
|
20
|
+
minutes, seconds = seconds / 60, seconds % 60
|
21
|
+
sprintf("%02d:%02d:%02d", hours, minutes, seconds)
|
22
|
+
else
|
23
|
+
SCHEDULED_RUN_TIME
|
24
|
+
end
|
25
|
+
},
|
26
|
+
"COMMAND" => ->(command) { command.command_string },
|
27
|
+
}
|
28
|
+
SCHEDULED_INSTANCE = "scheduled"
|
29
|
+
SCHEDULED_RUN_TIME = "00:00:00"
|
30
|
+
|
31
|
+
def initialize(application:, ui:)
|
32
|
+
@application = application
|
33
|
+
@ui = ui
|
34
|
+
end
|
35
|
+
|
36
|
+
def run
|
37
|
+
enqueued_commands = []
|
38
|
+
running_commands = []
|
39
|
+
ui.debug { "Fetching enqueued and running commands from #{application.name}..." }
|
40
|
+
threads = [
|
41
|
+
Thread.new do
|
42
|
+
enqueued_commands = application.enqueued_commands
|
43
|
+
end,
|
44
|
+
Thread.new do
|
45
|
+
running_commands = application.running_commands
|
46
|
+
end,
|
47
|
+
]
|
48
|
+
threads.each(&:join)
|
49
|
+
|
50
|
+
running_commands.sort_by!(&:start_time)
|
51
|
+
commands = running_commands + enqueued_commands
|
52
|
+
if commands.any?
|
53
|
+
ui.table(level: :info, columns: COLUMNS, rows: running_commands + enqueued_commands)
|
54
|
+
else
|
55
|
+
ui.info("No commands enqueued or running")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
attr_reader :application, :ui
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -1,10 +1,9 @@
|
|
1
|
-
require "uri"
|
2
1
|
require "elastic_beans/error"
|
3
2
|
|
4
3
|
module ElasticBeans
|
5
4
|
class ConfigurationTemplate
|
6
5
|
# The "exec" configuration template stored in the Elastic Beanstalk application.
|
7
|
-
# Settings for the exec environment are stored here, such as the exec queue URL
|
6
|
+
# Settings for the exec environment are stored here, such as the exec queue URL.
|
8
7
|
class Exec < ElasticBeans::ConfigurationTemplate::Base
|
9
8
|
def initialize(**args)
|
10
9
|
super(name: "exec", **args)
|
@@ -13,42 +12,13 @@ module ElasticBeans
|
|
13
12
|
protected
|
14
13
|
|
15
14
|
# Constructs the configuration for the exec environment.
|
16
|
-
|
17
|
-
|
18
|
-
if logging_endpoint
|
19
|
-
begin
|
20
|
-
if URI(logging_endpoint).scheme != "https"
|
21
|
-
raise InvalidLoggingEndpointError.new(logging_endpoint: logging_endpoint)
|
22
|
-
end
|
23
|
-
rescue URI::InvalidURIError
|
24
|
-
raise InvalidLoggingEndpointError.new(logging_endpoint: logging_endpoint)
|
25
|
-
end
|
26
|
-
|
27
|
-
logging_endpoint_setting = template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "ELASTIC_BEANS_EXEC_LOGGING_ENDPOINT", override: logging_endpoint)
|
28
|
-
end
|
29
|
-
|
30
|
-
settings = [
|
15
|
+
def build_option_settings(**_)
|
16
|
+
super + [
|
31
17
|
template_option_setting(namespace: "aws:elasticbeanstalk:application", option_name: "Application Healthcheck URL", default: "HTTP:80/"),
|
32
18
|
template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "DISABLE_SQS_CONSUMER", override: "false"),
|
33
19
|
template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "ELASTIC_BEANS_EXEC_QUEUE_URL", override: application.exec_queue_url),
|
34
20
|
template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "RAILS_SKIP_MIGRATIONS", default: "true"),
|
35
21
|
]
|
36
|
-
if logging_endpoint
|
37
|
-
settings << logging_endpoint_setting
|
38
|
-
end
|
39
|
-
super + settings
|
40
|
-
end
|
41
|
-
|
42
|
-
# :nodoc: all
|
43
|
-
# @!visibility private
|
44
|
-
class InvalidLoggingEndpointError < ElasticBeans::Error
|
45
|
-
def initialize(logging_endpoint:)
|
46
|
-
@logging_endpoint = logging_endpoint
|
47
|
-
end
|
48
|
-
|
49
|
-
def message
|
50
|
-
"Logging endpoint `#{@logging_endpoint}' must be a valid HTTPS URL."
|
51
|
-
end
|
52
22
|
end
|
53
23
|
end
|
54
24
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "json"
|
2
|
+
require "time"
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
module ElasticBeans
|
6
|
+
module Exec
|
7
|
+
# A command enqueued or running on an exec instance in this Application, from +beans exec+.
|
8
|
+
class Command
|
9
|
+
# A unique ID to identify this command later on.
|
10
|
+
attr_reader :id
|
11
|
+
|
12
|
+
# The command string that is executed; the string passed to +beans exec+
|
13
|
+
attr_reader :command_string
|
14
|
+
|
15
|
+
# The instance ID this command is being executed on.
|
16
|
+
attr_reader :instance_id
|
17
|
+
|
18
|
+
# The time this command started execution.
|
19
|
+
attr_reader :start_time
|
20
|
+
|
21
|
+
# Command metadata, such as where to store execution data in S3.
|
22
|
+
attr_reader :metadata
|
23
|
+
|
24
|
+
def initialize(command_string:, id: nil, instance_id: nil, start_time: nil, metadata: nil)
|
25
|
+
@id = id || SecureRandom.uuid
|
26
|
+
@command_string = command_string
|
27
|
+
@instance_id = instance_id
|
28
|
+
@start_time = start_time
|
29
|
+
@metadata = metadata || {}
|
30
|
+
end
|
31
|
+
|
32
|
+
def ==(other)
|
33
|
+
id == other.id
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_json
|
37
|
+
attributes = {}
|
38
|
+
attributes[:id] = id
|
39
|
+
attributes[:command] = command_string
|
40
|
+
attributes[:instance_id] = instance_id if instance_id
|
41
|
+
attributes[:start_time] = start_time.iso8601 if start_time
|
42
|
+
attributes[:metadata] = metadata
|
43
|
+
attributes.to_json
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.from_json(json)
|
47
|
+
attributes = JSON.parse(json)
|
48
|
+
new(
|
49
|
+
id: attributes["id"],
|
50
|
+
command_string: attributes["command"],
|
51
|
+
instance_id: attributes["instance_id"],
|
52
|
+
start_time: attributes["start_time"] ? Time.iso8601(attributes["start_time"]) : nil,
|
53
|
+
metadata: attributes["metadata"],
|
54
|
+
)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -3,8 +3,12 @@ container_commands:
|
|
3
3
|
command: "mkdir -vp /opt/elastic_beans && cp -vR /var/app/ondeck/.elastic_beans/exec /opt/elastic_beans/"
|
4
4
|
01_permissions:
|
5
5
|
command: "chmod 755 /opt/elastic_beans/exec/run_command.sh"
|
6
|
+
09_logrotate:
|
7
|
+
command: "cp -v /opt/elastic_beans/exec/logrotate /etc/logrotate.d/elastic_beans_exec"
|
8
|
+
test: "/opt/elasticbeanstalk/bin/get-config meta -k sqsdconfig --output YAML | grep -q '^environment_name: .*-exec$'"
|
6
9
|
09_upstart:
|
7
10
|
command: "cp -v /opt/elastic_beans/exec/elastic_beans_exec.conf /etc/init/"
|
11
|
+
test: "/opt/elasticbeanstalk/bin/get-config meta -k sqsdconfig --output YAML | grep -q '^environment_name: .*-exec$'"
|
8
12
|
10_start_elastic_beans_exec:
|
9
13
|
command: stop elastic_beans_exec; start elastic_beans_exec
|
10
14
|
test: "/opt/elasticbeanstalk/bin/get-config meta -k sqsdconfig --output YAML | grep -q '^environment_name: .*-exec$'"
|
@@ -6,10 +6,11 @@ require "uri"
|
|
6
6
|
# :nodoc: all
|
7
7
|
# @!visibility private
|
8
8
|
class Init
|
9
|
-
|
9
|
+
LOGDIR = "/var/log/elastic_beans/exec"
|
10
10
|
PIDFILE = "/var/run/elastic_beans_exec.pid"
|
11
11
|
AZ_PATTERN = /\A(?<region>.+-\d+)\w+\z/
|
12
12
|
AZ_URI = URI("http://169.254.169.254/latest/meta-data/placement/availability-zone")
|
13
|
+
INSTANCE_ID_URI = URI("http://169.254.169.254/latest/meta-data/instance-id")
|
13
14
|
|
14
15
|
def self.run(command)
|
15
16
|
init = new
|
@@ -20,17 +21,18 @@ class Init
|
|
20
21
|
end
|
21
22
|
|
22
23
|
def start
|
23
|
-
logging_endpoint = ENV['ELASTIC_BEANS_EXEC_LOGGING_ENDPOINT']
|
24
24
|
queue_url = ENV['ELASTIC_BEANS_EXEC_QUEUE_URL']
|
25
25
|
if queue_url.nil?
|
26
26
|
raise "ELASTIC_BEANS_EXEC_QUEUE_URL not set; please re-run `beans configure`."
|
27
27
|
end
|
28
|
+
s3_client = ::Aws::S3::Client.new(region: region)
|
28
29
|
sqs_client = ::Aws::SQS::Client.new(region: region)
|
29
30
|
require File.expand_path("../sqs_consumer", __FILE__)
|
30
31
|
consumer = SQSConsumer.new(
|
31
|
-
|
32
|
-
|
32
|
+
instance_id: instance_id,
|
33
|
+
logdir: LOGDIR,
|
33
34
|
queue_url: queue_url,
|
35
|
+
s3: s3_client,
|
34
36
|
sqs: sqs_client,
|
35
37
|
)
|
36
38
|
consumer.run
|
@@ -38,6 +40,10 @@ class Init
|
|
38
40
|
|
39
41
|
private
|
40
42
|
|
43
|
+
def instance_id
|
44
|
+
@instance_id ||= Net::HTTP.get(INSTANCE_ID_URI)
|
45
|
+
end
|
46
|
+
|
41
47
|
def region
|
42
48
|
return @region if @region
|
43
49
|
az = Net::HTTP.get(AZ_URI)
|
@@ -1,77 +1,137 @@
|
|
1
|
+
require "fileutils"
|
1
2
|
require "json"
|
2
3
|
require "net/http"
|
3
|
-
require "
|
4
|
+
require "time"
|
4
5
|
|
5
6
|
# :nodoc: all
|
6
7
|
# @!visibility private
|
7
8
|
class SQSConsumer
|
8
|
-
def initialize(
|
9
|
-
@
|
9
|
+
def initialize(instance_id:, logdir:, queue_url:, s3:, sqs:)
|
10
|
+
@instance_id = instance_id
|
11
|
+
@logdir = logdir
|
12
|
+
@command_logfile = File.join(logdir, "command.log")
|
13
|
+
@consumer_logfile = File.join(logdir, "sqs_consumer.log")
|
10
14
|
@queue_url = queue_url
|
15
|
+
@s3 = s3
|
11
16
|
@sqs = sqs
|
12
|
-
@logging_uri = URI(logging_endpoint) if logging_endpoint
|
13
17
|
@command_script = File.expand_path('../run_command.sh', __FILE__)
|
14
18
|
end
|
15
19
|
|
16
20
|
def run
|
17
21
|
trap("TERM", &method(:stop))
|
22
|
+
FileUtils.mkdir_p(logdir)
|
23
|
+
log("Starting message receive loop")
|
18
24
|
loop do
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
begin
|
26
|
+
sleep 1
|
27
|
+
if (message = receive_message)
|
28
|
+
command = command_from_message(message)
|
29
|
+
log_command("Executing command ID=#{command['id']} `#{command['command']}' on host #{`hostname`.chomp} pid #{command_pid}...")
|
30
|
+
start_time = Time.now
|
31
|
+
@command_pid = Process.spawn(
|
32
|
+
"#{command_script} #{command['command']}",
|
33
|
+
out: [command_logfile, "a"],
|
34
|
+
err: [command_logfile, "a"],
|
35
|
+
)
|
36
|
+
delete_message(message)
|
37
|
+
update_metadata(command: command, start_time: start_time)
|
38
|
+
_, status = Process.wait2(command_pid)
|
39
|
+
log_command("Command ID=#{command['id']} `#{command['command']}' exited on host #{`hostname`.chomp}: #{status}")
|
27
40
|
|
28
|
-
|
29
|
-
|
30
|
-
queue_url: queue_url,
|
31
|
-
receipt_handle: message.receipt_handle,
|
32
|
-
)
|
33
|
-
upload_log("Executing command `#{command['command']}' on host #{`hostname`.chomp} pid #{command_pid}...")
|
34
|
-
_, status = Process.wait2(command_pid)
|
35
|
-
File.open(logfile, "a") do |file|
|
36
|
-
file.puts "Command `#{command['command']}' exited on host #{`hostname`.chomp}: #{status}"
|
41
|
+
@command_pid = nil
|
42
|
+
remove_metadata(command: command)
|
37
43
|
end
|
38
|
-
|
39
|
-
|
40
|
-
|
44
|
+
rescue StandardError => e
|
45
|
+
log("[loop] #{e.class.name}: #{e.message}\n#{e.backtrace.join("\n")}")
|
46
|
+
next
|
41
47
|
end
|
42
48
|
end
|
43
49
|
end
|
44
50
|
|
45
51
|
private
|
46
52
|
|
47
|
-
attr_reader :
|
53
|
+
attr_reader :consumer_logfile, :command_logfile, :command_script, :instance_id, :logdir, :queue_url, :s3, :sqs
|
54
|
+
# Transient attributes
|
55
|
+
attr_reader :command_pid
|
48
56
|
|
49
57
|
def stop(signal = nil)
|
58
|
+
log("Terminating message receiving process")
|
50
59
|
signal ||= "TERM"
|
51
60
|
Process.kill(signal, command_pid) if command_pid
|
52
61
|
exit
|
53
62
|
rescue Errno::ESRCH
|
54
63
|
end
|
55
64
|
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
65
|
+
def command_from_message(message)
|
66
|
+
JSON.parse(message.body)
|
67
|
+
rescue JSON::ParserError => e
|
68
|
+
log("[command_from_message] #{e.class.name}: #{e.message}\n#{e.backtrace.join("\n")}")
|
69
|
+
raise e
|
70
|
+
end
|
71
|
+
|
72
|
+
def log(message)
|
73
|
+
File.open(consumer_logfile, "a") do |file|
|
74
|
+
file.puts("[#{Time.now.iso8601}] [elastic_beans/exec] #{message}")
|
64
75
|
end
|
76
|
+
rescue StandardError => e
|
77
|
+
$stdout.puts message
|
78
|
+
$stderr.puts "#{e.class.name}: #{e.message}"
|
79
|
+
$stderr.puts e.backtrace.join("\n")
|
65
80
|
end
|
66
81
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
70
|
-
request = Net::HTTP::Post.new(logging_uri, "Transfer-Encoding" => "chunked")
|
71
|
-
request.body_stream = File.open(logfile, "rb")
|
72
|
-
request.content_type = "text/plain"
|
73
|
-
http.request(request)
|
74
|
-
end
|
82
|
+
def log_command(message)
|
83
|
+
File.open(command_logfile, "a") do |file|
|
84
|
+
file.puts("[#{Time.now.iso8601}] [elastic_beans/exec] #{message}")
|
75
85
|
end
|
86
|
+
rescue StandardError => e
|
87
|
+
$stdout.puts message
|
88
|
+
$stderr.puts "#{e.class.name}: #{e.message}"
|
89
|
+
$stderr.puts e.backtrace.join("\n")
|
90
|
+
end
|
91
|
+
|
92
|
+
def receive_message
|
93
|
+
sqs.receive_message(
|
94
|
+
queue_url: queue_url,
|
95
|
+
max_number_of_messages: 1,
|
96
|
+
).messages[0]
|
97
|
+
rescue StandardError => e
|
98
|
+
log("[receive_message] #{e.class.name}: #{e.message}\n#{e.backtrace.join("\n")}")
|
76
99
|
end
|
100
|
+
|
101
|
+
def delete_message(message)
|
102
|
+
sqs.delete_message(
|
103
|
+
queue_url: queue_url,
|
104
|
+
receipt_handle: message.receipt_handle,
|
105
|
+
)
|
106
|
+
rescue StandardError => e
|
107
|
+
log("[delete_message] #{e.class.name}: #{e.message}\n#{e.backtrace.join("\n")}")
|
108
|
+
end
|
109
|
+
|
110
|
+
def update_metadata(command:, start_time:)
|
111
|
+
metadata = command['metadata']
|
112
|
+
if metadata && metadata['bucket'] && metadata['key']
|
113
|
+
s3.put_object(
|
114
|
+
bucket: metadata['bucket'],
|
115
|
+
key: metadata['key'],
|
116
|
+
body: {
|
117
|
+
instance_id: instance_id,
|
118
|
+
start_time: start_time.iso8601,
|
119
|
+
}.merge(command).to_json,
|
120
|
+
)
|
121
|
+
end
|
122
|
+
rescue StandardError => e
|
123
|
+
log("[update_metadata] #{e.class.name}: #{e.message}\n#{e.backtrace.join("\n")}")
|
124
|
+
end
|
125
|
+
|
126
|
+
def remove_metadata(command:)
|
127
|
+
metadata = command['metadata']
|
128
|
+
if metadata && metadata['bucket'] && metadata['key']
|
129
|
+
s3.delete_object(
|
130
|
+
bucket: metadata['bucket'],
|
131
|
+
key: metadata['key'],
|
132
|
+
)
|
77
133
|
end
|
134
|
+
rescue StandardError => e
|
135
|
+
log("[remove_metadata] #{e.class.name}: #{e.message}\n#{e.backtrace.join("\n")}")
|
136
|
+
end
|
137
|
+
end
|
@@ -17,7 +17,6 @@ module ElasticBeans
|
|
17
17
|
def initialize(app, args={})
|
18
18
|
@app = app
|
19
19
|
@application = args.fetch(:application)
|
20
|
-
@sqs = args.fetch(:sqs)
|
21
20
|
@logger = args[:logger] || Logger.new("/dev/null")
|
22
21
|
end
|
23
22
|
|
@@ -27,7 +26,7 @@ module ElasticBeans
|
|
27
26
|
command = command_from_request(request)
|
28
27
|
logger.info("[elastic_beans] Executing command: `#{command}'")
|
29
28
|
begin
|
30
|
-
application.enqueue_command(command
|
29
|
+
application.enqueue_command(command)
|
31
30
|
rescue => e
|
32
31
|
logger.info("[elastic_beans] Enqueue failed: #{e.class.name}: #{e.message}\n#{e.backtrace.join("; ")}")
|
33
32
|
return FAILURE
|
@@ -42,7 +41,7 @@ module ElasticBeans
|
|
42
41
|
|
43
42
|
private
|
44
43
|
|
45
|
-
attr_reader :app, :application, :logger
|
44
|
+
attr_reader :app, :application, :logger
|
46
45
|
|
47
46
|
def aws_sqsd?(request)
|
48
47
|
current_user_agent = request.headers['User-Agent'.freeze]
|
@@ -52,7 +51,8 @@ module ElasticBeans
|
|
52
51
|
end
|
53
52
|
|
54
53
|
def command_from_request(request)
|
55
|
-
URI.unescape(request.path.sub(%r{\A/exec/}, ''))
|
54
|
+
command_string = URI.unescape(request.path.sub(%r{\A/exec/}, ''))
|
55
|
+
::ElasticBeans::Exec::Command.new(command_string: command_string)
|
56
56
|
end
|
57
57
|
|
58
58
|
def cron_request?(request)
|
data/lib/elastic_beans/ui.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'tty-table'
|
2
|
+
|
1
3
|
module ElasticBeans
|
2
4
|
# :nodoc: all
|
3
5
|
# @!visibility private
|
@@ -24,6 +26,34 @@ module ElasticBeans
|
|
24
26
|
stdout.puts message
|
25
27
|
end
|
26
28
|
|
29
|
+
def table(level:, columns:, rows:)
|
30
|
+
raise "ElasticBeans::UI#table: No such level '#{level}'" unless %w(debug error info).include?(level.to_s)
|
31
|
+
raise "ElasticBeans::UI#table: Missing 'columns' argument" if columns.empty?
|
32
|
+
|
33
|
+
msg = Proc.new do
|
34
|
+
# Produce an array for each row, containing strings for each column
|
35
|
+
if rows.any?
|
36
|
+
table_rows = rows.map { |item|
|
37
|
+
columns.map { |_, item_proc|
|
38
|
+
item_proc.call(item)
|
39
|
+
}
|
40
|
+
}
|
41
|
+
else
|
42
|
+
table_rows = [columns.map { "" }]
|
43
|
+
end
|
44
|
+
TTY::Table.new(
|
45
|
+
header: columns.keys,
|
46
|
+
rows: table_rows,
|
47
|
+
).render(:basic)
|
48
|
+
end
|
49
|
+
|
50
|
+
if level.to_s == "debug"
|
51
|
+
debug(&msg)
|
52
|
+
else
|
53
|
+
send(level, msg.call)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
27
57
|
private
|
28
58
|
|
29
59
|
def verbose?
|
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.8.0
|
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-02-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: 0.19.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: tty-table
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.7.0
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.7.0
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
112
|
name: bundler
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -168,6 +182,7 @@ files:
|
|
168
182
|
- lib/elastic_beans/command/deploy.rb
|
169
183
|
- lib/elastic_beans/command/exec.rb
|
170
184
|
- lib/elastic_beans/command/get_env.rb
|
185
|
+
- lib/elastic_beans/command/ps.rb
|
171
186
|
- lib/elastic_beans/command/scale.rb
|
172
187
|
- lib/elastic_beans/command/set_env.rb
|
173
188
|
- lib/elastic_beans/command/talk.rb
|
@@ -189,9 +204,12 @@ files:
|
|
189
204
|
- lib/elastic_beans/environment/worker.rb
|
190
205
|
- lib/elastic_beans/error.rb
|
191
206
|
- lib/elastic_beans/error/environments_not_ready.rb
|
207
|
+
- lib/elastic_beans/exec.rb
|
208
|
+
- lib/elastic_beans/exec/command.rb
|
192
209
|
- lib/elastic_beans/exec/ebextension.yml
|
193
210
|
- lib/elastic_beans/exec/elastic_beans_exec.conf
|
194
211
|
- lib/elastic_beans/exec/init.rb
|
212
|
+
- lib/elastic_beans/exec/logrotate
|
195
213
|
- lib/elastic_beans/exec/run_command.sh
|
196
214
|
- lib/elastic_beans/exec/sqs_consumer.rb
|
197
215
|
- lib/elastic_beans/network.rb
|
@@ -218,7 +236,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
218
236
|
version: '0'
|
219
237
|
requirements: []
|
220
238
|
rubyforge_project:
|
221
|
-
rubygems_version: 2.5.
|
239
|
+
rubygems_version: 2.5.2
|
222
240
|
signing_key:
|
223
241
|
specification_version: 4
|
224
242
|
summary: Elastic Beanstalk environment orchestration for a Rails app
|