elastic_beans 0.7.0 → 0.8.0
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 +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
|