elastic_beans 0.13.0.alpha3 → 0.13.0.alpha4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f706a4e17c133611b418f5ac6b7f57a7859d7ef2
4
- data.tar.gz: dd94a36a7bf6cf844972158a84dc30ecb57330f0
3
+ metadata.gz: a69e6d19ef9b23f9db1ccddb65d7f49644e72233
4
+ data.tar.gz: 1b2b65654a6abcc05e7033946f13464d5f51a85b
5
5
  SHA512:
6
- metadata.gz: 8d0efdaff74f60b3c64bac5950fa57aa2faa35f679e8c82dc6436539e66b748f902aa6ad1382d07a9d605f8d1c7d1ff409124edf6ac0c14a2fa7e59792d608be
7
- data.tar.gz: 32b43316bb1848aa77638d6777c3e80daa547ee10ab10b5221edb963bb27d6afaf9dabdd10773047c003611c6e7d28f46c3d60b96a8eef30f6f1f19d6a405629
6
+ metadata.gz: ced50156c632ae34c5219d7d8f4384cf7ccf1243611dd23e06c57eb552707907305d6bbd03a9bfda390126c220a96ff35a7b363649d72ea94bebc41da91dd5a7
7
+ data.tar.gz: 87f4ec5eb18640de5d43327b24a29a0539c4d22fbffb08c3fb8968054de09d0d2bff152f38467722ea625e70b68b932a362694846ca00c4872cc2c016ff07bef
data/README.md CHANGED
@@ -24,8 +24,9 @@ As the SDK documentation suggests, using environment variables is recommended.
24
24
 
25
25
  # Pre-configure the application before creating environments
26
26
  beans configure -n myapp-networking -a myapp \
27
- -p INTERNAL_PUBLIC_KEY -s SSL_CERTIFICATE_ARN \
28
- -k KEYPAIR [-i IMAGE_ID] [-t INSTANCE_TYPE]
27
+ -p INTERNAL_PUBLIC_KEY -s SSL_CERTIFICATE_ARN -k KEYPAIR \
28
+ [--solution-stack '64bit Amazon Linux 2017.03 v2.4.2 running Ruby 2.3 (Puma)'] \
29
+ [-o 'OPTION_SETTING_NAMESPACE/OPTION_NAME=VALUE'...]
29
30
  beans setenv -a myapp \
30
31
  DATABASE_URL=mysql2://db.example.com:3306/myapp \
31
32
  SECRET_KEY_BASE=abc123
@@ -65,8 +66,9 @@ As the SDK documentation suggests, using environment variables is recommended.
65
66
 
66
67
  # Update all existing environments and configuration
67
68
  beans configure -n myapp-networking -a myapp \
68
- [-p INTERNAL_PUBLIC_KEY] [-s SSL_CERTIFICATE_ARN] \
69
- [-k KEYPAIR] [-i IMAGE_ID] [-t INSTANCE_TYPE]
69
+ [-p INTERNAL_PUBLIC_KEY] [-s SSL_CERTIFICATE_ARN] [-k KEYPAIR] \
70
+ [--solution-stack '64bit Amazon Linux 2017.03 v2.4.2 running Ruby 2.3 (Puma)'] \
71
+ [-o 'OPTION_SETTING_NAMESPACE/OPTION_NAME=VALUE'...]
70
72
 
71
73
  ### API
72
74
 
@@ -29,7 +29,7 @@ module ElasticBeans
29
29
  live_environments = response.environments.select { |environment| environment.status !~ /Terminat/ }
30
30
  environment = live_environments.max_by(&:date_updated)
31
31
  if environment
32
- ElasticBeans::ApplicationVersion.new(environment.version_label)
32
+ ElasticBeans::ApplicationVersion.new(environment.version_label, application: self, elastic_beanstalk: elastic_beanstalk)
33
33
  end
34
34
  rescue ::Aws::ElasticBeanstalk::Errors::Throttling
35
35
  sleep 5
@@ -226,11 +226,22 @@ module ElasticBeans
226
226
 
227
227
  # Returns an ElasticBeans::ApplicationVersion for each version of the Elastic Beanstalk application.
228
228
  def versions
229
- response = elastic_beanstalk.describe_application_versions(application_name: name)
230
- response.application_versions.map { |version| ElasticBeans::ApplicationVersion.new(version.version_label) }
231
- rescue ::Aws::ElasticBeanstalk::Errors::Throttling
232
- sleep 5
233
- retry
229
+ application_versions = []
230
+ next_token = nil
231
+ loop do
232
+ begin
233
+ response = elastic_beanstalk.describe_application_versions(application_name: name, next_token: next_token)
234
+ next_token = response.next_token
235
+ application_versions += response.application_versions.map { |version|
236
+ ElasticBeans::ApplicationVersion.new_from_existing(version, application: self, elastic_beanstalk: elastic_beanstalk)
237
+ }
238
+ break unless next_token
239
+ rescue ::Aws::ElasticBeanstalk::Errors::Throttling
240
+ sleep 5
241
+ retry
242
+ end
243
+ end
244
+ application_versions
234
245
  end
235
246
 
236
247
  # Returns the +Worker{QUEUE}QueueUrl+ from the application CloudFormation stack.
@@ -7,11 +7,23 @@ require "elastic_beans/error"
7
7
 
8
8
  module ElasticBeans
9
9
  class ApplicationVersion
10
- attr_reader :version_label
10
+ attr_reader :version_label, :application
11
+ attr_writer :version_description
11
12
 
12
13
  # Create a representation of an existing application version in Elastic Beanstalk.
13
- def initialize(version_label)
14
+ # Details will be fetched from the Elastic Beanstalk API.
15
+ def initialize(version_label, application:, elastic_beanstalk:)
14
16
  @version_label = version_label
17
+ @application = application
18
+ @elastic_beanstalk = elastic_beanstalk
19
+ end
20
+
21
+ # Create a representation of an existing application version in Elastic Beanstalk.
22
+ # +version_description+ should be the +Aws::ElasticBeanstalk::Types::ApplicationVersionDescription+ from the AWS API.
23
+ def self.new_from_existing(version_description, application:, elastic_beanstalk:)
24
+ version = new(version_description.version_label, application: application, elastic_beanstalk: elastic_beanstalk)
25
+ version.version_description = version_description
26
+ version
15
27
  end
16
28
 
17
29
  class << self
@@ -24,7 +36,7 @@ module ElasticBeans
24
36
  def create(working_directory:, application:, elastic_beanstalk:, s3:)
25
37
  version_label = fetch_version_label(working_directory)
26
38
  if application.versions.any? { |version| version.version_label == version_label }
27
- return new(version_label)
39
+ return new(version_label, application: application, elastic_beanstalk: elastic_beanstalk)
28
40
  end
29
41
 
30
42
  archive_path = File.join(working_directory, ".elasticbeanstalk", "#{version_label}.zip")
@@ -36,7 +48,7 @@ module ElasticBeans
36
48
  elastic_beanstalk: elastic_beanstalk,
37
49
  s3: s3,
38
50
  )
39
- new(version_label)
51
+ new(version_label, application: application, elastic_beanstalk: elastic_beanstalk)
40
52
  end
41
53
 
42
54
  private
@@ -164,12 +176,40 @@ module ElasticBeans
164
176
  end
165
177
  end
166
178
 
179
+ def delete
180
+ elastic_beanstalk.delete_application_version(
181
+ application_name: application.name,
182
+ version_label: version_label,
183
+ delete_source_bundle: true,
184
+ )
185
+ rescue ::Aws::ElasticBeanstalk::Errors::Throttling
186
+ sleep 5
187
+ retry
188
+ end
189
+
190
+ def date_updated
191
+ version_description.date_updated
192
+ end
193
+
167
194
  def ==(other)
168
- version_label == other.version_label
195
+ version_label == other.version_label &&
196
+ application.name == other.application.name
169
197
  end
170
198
 
171
199
  private
172
200
 
201
+ attr_reader :elastic_beanstalk
202
+
203
+ def version_description
204
+ @version_description ||= elastic_beanstalk.describe_application_versions(
205
+ application_name: application.name,
206
+ version_labels: [version_label],
207
+ ).application_versions[0]
208
+ rescue ::Aws::ElasticBeanstalk::Errors::Throttling
209
+ sleep 5
210
+ retry
211
+ end
212
+
173
213
  # :nodoc: all
174
214
  # @!visibility private
175
215
  class FailedApplicationVersion < ElasticBeans::Error
@@ -55,8 +55,8 @@ module ElasticBeans
55
55
  end
56
56
 
57
57
  def message
58
- "CloudFormation stack `#{@stack_name}' not found." \
59
- " Make sure the stack exists and matches the outputs required."
58
+ "CloudFormation stack `#{@stack_name}' is in progress or does not exist." \
59
+ " Make sure the stack exists, is stable, and matches the outputs required."
60
60
  end
61
61
  end
62
62
 
@@ -16,21 +16,23 @@ class ElasticBeans::CLI < Thor
16
16
  long_desc ElasticBeans::Command::Configure::LONG_DESC
17
17
  option :application, aliases: %w(-a), required: true, desc: APPLICATION_DESC
18
18
  option :network, aliases: %w(-n), required: true, desc: "The name of the CloudFormation stack that contains networking settings"
19
- option :image_id, aliases: %w(-i), desc: "A custom AMI to use instead of the default Ruby Elastic Beanstalk AMI"
20
- option :instance_type, aliases: %w(-t), desc: "A default instance type to use for all environments instead of c4.large"
21
19
  option :internal, type: :boolean, desc: "Configure the webserver to only be available for internal VPC access"
22
20
  option :keypair, aliases: %w(-k), desc: "Required on first run. The EC2 keypair to use for Elastic Beanstalk instances"
21
+ option :option_settings, aliases: %w(-o), type: :array, default: [], desc: "Option settings to configure, format is NAMESPACE/OPTION_NAME=VALUE, e.g. aws:ec2:vpc/ELBScheme=internal"
22
+ option :option_settings_to_remove, aliases: %w(-r), type: :array, default: [], desc: "Option settings to remove, format is NAMESPACE/OPTION_NAME, e.g. aws:ec2:vpc/ELBScheme"
23
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"
24
24
  option :ssl_certificate_arn, aliases: %w(-s), desc: "The ARN of the SSL server certificate stored in IAM to attach to the ELB"
25
+ option :solution_stack, desc: "Solution stack name to deploy the application on. Defaults to the latest Ruby Puma solution stack."
25
26
  def configure
26
27
  @verbose = options[:verbose]
27
28
  ElasticBeans::Command::Configure.new(
28
- image_id: options[:image_id],
29
- instance_type: options[:instance_type],
30
29
  internal: options[:internal],
31
30
  keypair: options[:keypair],
32
31
  public_key: options[:public_key],
33
32
  ssl_certificate_arn: options[:ssl_certificate_arn],
33
+ solution_stack: options[:solution_stack],
34
+ option_settings: options[:option_settings],
35
+ option_settings_to_remove: options[:option_settings_to_remove],
34
36
  application: application(name: options[:application]),
35
37
  network: network(stack_name: options[:network]),
36
38
  elastic_beanstalk: elastic_beanstalk_client,
@@ -16,24 +16,26 @@ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID an
16
16
  LONG_DESC
17
17
 
18
18
  def initialize(
19
- image_id:,
20
- instance_type:,
21
19
  internal:,
22
20
  keypair:,
23
21
  public_key:,
24
22
  ssl_certificate_arn:,
23
+ solution_stack:,
24
+ option_settings:,
25
+ option_settings_to_remove:,
25
26
  application:,
26
27
  network:,
27
28
  elastic_beanstalk:,
28
29
  iam:,
29
30
  ui:
30
31
  )
31
- @image_id = image_id
32
- @instance_type = instance_type
33
32
  @internal = internal
34
33
  @keypair = keypair
35
34
  @public_key = public_key
36
35
  @ssl_certificate_arn = ssl_certificate_arn
36
+ @solution_stack = solution_stack
37
+ @option_setting_strings = option_settings
38
+ @option_strings_to_remove = option_settings_to_remove
37
39
  @application = application
38
40
  @network = network
39
41
  @elastic_beanstalk = elastic_beanstalk
@@ -71,9 +73,10 @@ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID an
71
73
  )
72
74
  base_config.upsert(
73
75
  network: network,
74
- image_id: image_id,
75
- instance_type: instance_type,
76
76
  keypair: keypair,
77
+ solution_stack: solution_stack,
78
+ option_settings: option_settings,
79
+ options_to_remove: options_to_remove,
77
80
  iam: iam,
78
81
  )
79
82
  progressbar.increment
@@ -85,13 +88,14 @@ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID an
85
88
  )
86
89
  webserver_config.upsert(
87
90
  network: network,
88
- image_id: image_id,
89
- instance_type: instance_type,
90
91
  internal: internal,
91
92
  keypair: keypair,
93
+ option_settings: option_settings,
94
+ options_to_remove: options_to_remove,
92
95
  iam: iam,
93
96
  public_key: public_key,
94
97
  ssl_certificate_arn: ssl_certificate_arn,
98
+ solution_stack: solution_stack,
95
99
  )
96
100
  webserver_environment = webserver_config.environment
97
101
  if webserver_environment
@@ -109,9 +113,10 @@ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID an
109
113
  )
110
114
  exec_config.upsert(
111
115
  network: network,
112
- image_id: image_id,
113
- instance_type: instance_type,
114
116
  keypair: keypair,
117
+ solution_stack: solution_stack,
118
+ option_settings: option_settings,
119
+ options_to_remove: options_to_remove,
115
120
  iam: iam,
116
121
  )
117
122
  exec_environment = exec_config.environment
@@ -130,9 +135,10 @@ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID an
130
135
  )
131
136
  scheduler_config.upsert(
132
137
  network: network,
133
- image_id: image_id,
134
- instance_type: instance_type,
135
138
  keypair: keypair,
139
+ solution_stack: solution_stack,
140
+ option_settings: option_settings,
141
+ options_to_remove: options_to_remove,
136
142
  iam: iam,
137
143
  )
138
144
  scheduler_environment = scheduler_config.environment
@@ -153,9 +159,10 @@ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID an
153
159
  )
154
160
  worker_config.upsert(
155
161
  network: network,
156
- image_id: image_id,
157
- instance_type: instance_type,
158
162
  keypair: keypair,
163
+ solution_stack: solution_stack,
164
+ option_settings: option_settings,
165
+ options_to_remove: options_to_remove,
159
166
  iam: iam,
160
167
  )
161
168
  worker_environment = worker_config.environment
@@ -175,17 +182,76 @@ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID an
175
182
 
176
183
  attr_reader(
177
184
  :application,
178
- :image_id,
179
- :instance_type,
180
185
  :internal,
181
186
  :keypair,
182
187
  :network,
183
188
  :public_key,
184
189
  :ssl_certificate_arn,
190
+ :solution_stack,
185
191
  :elastic_beanstalk,
186
192
  :iam,
187
193
  :ui,
188
194
  )
195
+
196
+ # Coerces +@option_setting_strings+ into object format.
197
+ def option_settings
198
+ @option_settings ||= @option_setting_strings.map { |option_setting_string|
199
+ setting, value = option_setting_string.split('=', 2)
200
+ if !setting || setting.empty? || !value || value.empty?
201
+ raise InvalidOptionSettingFormatError
202
+ end
203
+ namespace, option_name = setting.split('/', 2)
204
+ if !namespace || namespace.empty? || !option_name || option_name.empty?
205
+ raise InvalidOptionSettingFormatError
206
+ end
207
+ {namespace: namespace, option_name: option_name, value: value}
208
+ }
209
+ end
210
+
211
+ # Coerces +@option_strings_to_remove+ into object format.
212
+ def options_to_remove
213
+ @options_to_remove ||= @option_strings_to_remove.map { |option_setting_string|
214
+ namespace, option_name = option_setting_string.split('/', 2)
215
+ if !namespace || namespace.empty? || !option_name || option_name.empty? || option_name.include?('=')
216
+ raise InvalidOptionSettingFormatError
217
+ end
218
+ {namespace: namespace, option_name: option_name}
219
+ }
220
+ end
221
+
222
+ # :nodoc: all
223
+ # @!visibility private
224
+ class InvalidOptionSettingFormatError < ElasticBeans::Error
225
+ def message
226
+ require "elastic_beans/cli"
227
+ require "elastic_beans/cli/string_shell"
228
+ <<-MESSAGE
229
+ Invalid format for --option-settings.
230
+ Option settings should in the format 'NAMESPACE/OPTION_NAME=VALUE', e.g. 'aws:autoscaling:asg/Cooldown=180'.
231
+ If you'd like to *remove* option settings, use --option-settings-to-remove 'NAMESPACE/OPTION_NAME'.
232
+ Please re-run `#{command_as_string "configure"}` with updated syntax.
233
+
234
+ #{command_help "configure"}
235
+ MESSAGE
236
+ end
237
+ end
238
+
239
+ # :nodoc: all
240
+ # @!visibility private
241
+ class InvalidOptionSettingsToRemoveFormatError < ElasticBeans::Error
242
+ def message
243
+ require "elastic_beans/cli"
244
+ require "elastic_beans/cli/string_shell"
245
+ <<-MESSAGE
246
+ Invalid format for --option-settings-to-remove.
247
+ Option settings to remove should in the format 'NAMESPACE/OPTION_NAME', e.g. 'aws:autoscaling:asg/Cooldown'.
248
+ If you'd like to *set* option settings, use --option-settings 'NAMESPACE/OPTION_NAME=VALUE'.
249
+ Please re-run `#{command_as_string "configure"}` with updated syntax.
250
+
251
+ #{command_help "configure"}
252
+ MESSAGE
253
+ end
254
+ end
189
255
  end
190
256
  end
191
257
  end
@@ -9,9 +9,14 @@ module ElasticBeans
9
9
  DESC = "Deploy the HEAD git commit to all environments in Elastic Beanstalk"
10
10
  LONG_DESC = <<-LONG_DESC
11
11
  Deploy the HEAD git commit of the working directory to all environments in Elastic Beanstalk.
12
+ Cleans up old application versions in parallel to avoid reaching the 1,000 application version limit.
12
13
 
13
14
  Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.
14
15
  LONG_DESC
16
+ # Maximum age of application versions that are left behind and not cleaned up. 1 week.
17
+ APPLICATION_VERSION_MAX_AGE = 604_800
18
+ # Minimum number of application versions to leave around.
19
+ APPLICATION_VERSION_MIN_COUNT = 5
15
20
 
16
21
  def initialize(application:, elastic_beanstalk:, s3:, ui:)
17
22
  @application = application
@@ -38,15 +43,19 @@ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID an
38
43
  s3: s3,
39
44
  ).version_label
40
45
  end
46
+ cleanup_thread = Thread.new do
47
+ remove_old_versions
48
+ end
41
49
 
42
50
  loop do
43
51
  sleep 0.5
44
52
  progressbar.increment
45
- if !version_thread.alive?
53
+ if !version_thread.alive? && !cleanup_thread.alive?
46
54
  break
47
55
  end
48
56
  end
49
57
  version_thread.join
58
+ cleanup_thread.join
50
59
 
51
60
  Signal.trap("INT") do
52
61
  puts "\nInterrupting beans"
@@ -85,6 +94,21 @@ Requires AWS credentials to be set in the environment, i.e. AWS_ACCESS_KEY_ID an
85
94
  private
86
95
 
87
96
  attr_reader :application, :elastic_beanstalk, :s3, :ui
97
+
98
+ # Removes application versions that are old and just taking up space.
99
+ # Elastic Beanstalk has a limit of 1000 application versions, so we should keep this tidy.
100
+ def remove_old_versions
101
+ cutoff = Time.now
102
+ outdated_versions, leftover_versions = application.versions.partition { |version|
103
+ version.date_updated + APPLICATION_VERSION_MAX_AGE < cutoff
104
+ }
105
+ if leftover_versions.size < APPLICATION_VERSION_MIN_COUNT
106
+ outdated_versions.pop(APPLICATION_VERSION_MIN_COUNT - leftover_versions.size)
107
+ end
108
+ outdated_versions.each do |version|
109
+ version.delete
110
+ end
111
+ end
88
112
  end
89
113
  end
90
114
  end
@@ -10,6 +10,13 @@ module ElasticBeans
10
10
  super(name: "base", **args)
11
11
  end
12
12
 
13
+ # Returns the specifed +solution_stack+ from #upsert.
14
+ # Finds the previously-configured solution stack when one was not specified.
15
+ # Finds the latest available Ruby Puma solution stack (matching +SOLUTION_STACK_PATTERN+) when one was never specified.
16
+ def solution_stack_name
17
+ @solution_stack_name ||= (configured_solution_stack || super)
18
+ end
19
+
13
20
  protected
14
21
 
15
22
  # Constructs the common configuration for all environments.
@@ -18,20 +25,21 @@ module ElasticBeans
18
25
  network: nil,
19
26
  keypair: nil,
20
27
  iam: nil,
21
- image_id: nil,
22
- instance_type: nil,
23
28
  min_size: nil,
24
29
  max_size: nil,
30
+ solution_stack: nil,
31
+ option_settings: [],
25
32
  **_
26
33
  )
34
+ @solution_stack_name = solution_stack
27
35
  template = configuration_settings_description("base")
28
36
 
29
- instance_profile_setting = template_option_setting(template: template, namespace: "aws:autoscaling:launchconfiguration", option_name: "IamInstanceProfile", override: instance_profile(iam))
37
+ instance_profile_setting = template_option_setting(template: template, namespace: "aws:autoscaling:launchconfiguration", option_name: "IamInstanceProfile", override: instance_profile(iam), new_settings: option_settings)
30
38
  if instance_profile_setting[:value].nil?
31
39
  raise MissingInstanceProfileError
32
40
  end
33
41
 
34
- keypair_setting = template_option_setting(template: template, namespace: "aws:autoscaling:launchconfiguration", option_name: "EC2KeyName", override: keypair)
42
+ keypair_setting = template_option_setting(template: template, namespace: "aws:autoscaling:launchconfiguration", option_name: "EC2KeyName", override: keypair, new_settings: option_settings)
35
43
  if keypair_setting[:value].nil?
36
44
  raise MissingOptionsError.new(
37
45
  keypair: keypair_setting[:value],
@@ -40,35 +48,57 @@ module ElasticBeans
40
48
 
41
49
  config_path = "#{application.bucket_name}/#{application.env_vars.s3_key}"
42
50
  settings = [
43
- template_option_setting(template: template, namespace: "aws:elasticbeanstalk:command", option_name: "BatchSize", default: "1"),
44
- template_option_setting(template: template, namespace: "aws:elasticbeanstalk:command", option_name: "BatchSizeType", default: "Fixed"),
45
- template_option_setting(template: template, namespace: "aws:elasticbeanstalk:command", option_name: "DeploymentPolicy", default: "Rolling"),
46
- template_option_setting(template: template, namespace: "aws:elasticbeanstalk:application:environment", option_name: "DISABLE_SQS_CONSUMER", default: "true"),
47
- template_option_setting(template: template, namespace: "aws:elasticbeanstalk:application:environment", option_name: "ELASTIC_BEANS_ENV_VARS", default: config_path),
48
- template_option_setting(template: template, namespace: "aws:elasticbeanstalk:environment", option_name: "ServiceRole", default: "aws-elasticbeanstalk-service-role"),
49
- template_option_setting(template: template, namespace: "aws:elasticbeanstalk:healthreporting:system", option_name: "SystemType", default: "enhanced"),
50
- template_option_setting(template: template, namespace: "aws:ec2:vpc", option_name: "AssociatePublicIpAddress", default: "false"),
51
- template_option_setting(template: template, namespace: "aws:autoscaling:asg", option_name: "MinSize", default: "1", override: min_size),
52
- template_option_setting(template: template, namespace: "aws:autoscaling:asg", option_name: "MaxSize", default: "4", override: max_size),
53
- template_option_setting(template: template, namespace: "aws:autoscaling:launchconfiguration", option_name: "InstanceType", default: "c4.large", override: instance_type),
54
- template_option_setting(template: template, namespace: "aws:autoscaling:launchconfiguration", option_name: "SSHSourceRestriction", default: "tcp, 22, 22, 0.0.0.0/32"),
55
- template_option_setting(template: template, namespace: "aws:autoscaling:updatepolicy:rollingupdate", option_name: "RollingUpdateType", default: "Health"),
56
- template_option_setting(template: template, namespace: "aws:autoscaling:updatepolicy:rollingupdate", option_name: "RollingUpdateEnabled", default: "true"),
57
- template_option_setting(template: template, namespace: "aws:autoscaling:launchconfiguration", option_name: "SecurityGroups", override: security_groups(network)),
58
- template_option_setting(template: template, namespace: "aws:ec2:vpc", option_name: "ELBSubnets", override: elb_subnets(network)),
59
- template_option_setting(template: template, namespace: "aws:ec2:vpc", option_name: "Subnets", override: subnets(network)),
60
- template_option_setting(template: template, namespace: "aws:ec2:vpc", option_name: "VPCId", override: vpc_id(network)),
51
+ template_option_setting(template: template, namespace: "aws:elasticbeanstalk:command", option_name: "BatchSize", default: "1", new_settings: option_settings),
52
+ template_option_setting(template: template, namespace: "aws:elasticbeanstalk:command", option_name: "BatchSizeType", default: "Fixed", new_settings: option_settings),
53
+ template_option_setting(template: template, namespace: "aws:elasticbeanstalk:command", option_name: "DeploymentPolicy", default: "Rolling", new_settings: option_settings),
54
+ template_option_setting(template: template, namespace: "aws:elasticbeanstalk:application:environment", option_name: "DISABLE_SQS_CONSUMER", default: "true", new_settings: option_settings),
55
+ template_option_setting(template: template, namespace: "aws:elasticbeanstalk:application:environment", option_name: "ELASTIC_BEANS_ENV_VARS", default: config_path, new_settings: option_settings),
56
+ template_option_setting(template: template, namespace: "aws:elasticbeanstalk:environment", option_name: "ServiceRole", default: "aws-elasticbeanstalk-service-role", new_settings: option_settings),
57
+ template_option_setting(template: template, namespace: "aws:elasticbeanstalk:healthreporting:system", option_name: "SystemType", default: "enhanced", new_settings: option_settings),
58
+ template_option_setting(template: template, namespace: "aws:ec2:vpc", option_name: "AssociatePublicIpAddress", default: "false", new_settings: option_settings),
59
+ template_option_setting(template: template, namespace: "aws:autoscaling:asg", option_name: "MinSize", default: "1", override: min_size, new_settings: option_settings),
60
+ template_option_setting(template: template, namespace: "aws:autoscaling:asg", option_name: "MaxSize", default: "4", override: max_size, new_settings: option_settings),
61
+ template_option_setting(template: template, namespace: "aws:autoscaling:launchconfiguration", option_name: "SSHSourceRestriction", default: "tcp, 22, 22, 0.0.0.0/32", new_settings: option_settings),
62
+ template_option_setting(template: template, namespace: "aws:autoscaling:updatepolicy:rollingupdate", option_name: "RollingUpdateType", default: "Health", new_settings: option_settings),
63
+ template_option_setting(template: template, namespace: "aws:autoscaling:updatepolicy:rollingupdate", option_name: "RollingUpdateEnabled", default: "true", new_settings: option_settings),
64
+ template_option_setting(template: template, namespace: "aws:autoscaling:launchconfiguration", option_name: "SecurityGroups", override: security_groups(network), new_settings: option_settings),
65
+ template_option_setting(template: template, namespace: "aws:ec2:vpc", option_name: "ELBSubnets", override: elb_subnets(network), new_settings: option_settings),
66
+ template_option_setting(template: template, namespace: "aws:ec2:vpc", option_name: "Subnets", override: subnets(network), new_settings: option_settings),
67
+ template_option_setting(template: template, namespace: "aws:ec2:vpc", option_name: "VPCId", override: vpc_id(network), new_settings: option_settings),
61
68
  instance_profile_setting,
62
69
  keypair_setting,
63
70
  ]
64
- if image_id
65
- settings << template_option_setting(template: template, namespace: "aws:autoscaling:launchconfiguration", option_name: "ImageId", override: image_id)
71
+ if solution_stack
72
+ settings << template_option_setting(template: template, namespace: "aws:elasticbeanstalk:customoption", option_name: "SolutionStack", override: solution_stack, new_settings: option_settings)
66
73
  end
67
- settings
74
+ super + settings
75
+ end
76
+
77
+ def source_configuration
78
+ nil
68
79
  end
69
80
 
70
81
  private
71
82
 
83
+ # Finds the previously-configured solution stack when one was not specified.
84
+ # The solution stack name is stored in a custom option named "SolutionStack",
85
+ # because updating the template's solution stack is not supported by Elastic Beanstalk.
86
+ # See http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/configuration-options-custom.html for more information on custom options.
87
+ # If the custom option is not set because the application was configured by an older version of beans, returns the template's configured solution stack.
88
+ def configured_solution_stack
89
+ template = configuration_settings_description("base")
90
+ if template
91
+ setting = template.option_settings.find { |setting|
92
+ setting.namespace == "aws:elasticbeanstalk:customoption" && setting.option_name == "SolutionStack"
93
+ }
94
+ if setting
95
+ return setting.value
96
+ else
97
+ return template.solution_stack_name
98
+ end
99
+ end
100
+ end
101
+
72
102
  def elb_subnets(network)
73
103
  if network
74
104
  network.elb_subnets.join(",")
@@ -12,14 +12,18 @@ module ElasticBeans
12
12
  protected
13
13
 
14
14
  # Constructs the configuration for the exec environment.
15
- def build_option_settings(**_)
15
+ def build_option_settings(option_settings: [], **_)
16
16
  super + [
17
- template_option_setting(namespace: "aws:elasticbeanstalk:application", option_name: "Application Healthcheck URL", default: "HTTP:80/"),
18
- template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "DISABLE_SQS_CONSUMER", override: "false"),
19
- template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "ELASTIC_BEANS_EXEC_QUEUE_URL", override: application.exec_queue_url),
20
- template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "RAILS_SKIP_MIGRATIONS", default: "true"),
17
+ template_option_setting(namespace: "aws:elasticbeanstalk:application", option_name: "Application Healthcheck URL", default: "HTTP:80/", new_settings: option_settings),
18
+ template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "DISABLE_SQS_CONSUMER", override: "false", new_settings: option_settings),
19
+ template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "ELASTIC_BEANS_EXEC_QUEUE_URL", override: application.exec_queue_url, new_settings: option_settings),
20
+ template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "RAILS_SKIP_MIGRATIONS", default: "true", new_settings: option_settings),
21
21
  ]
22
22
  end
23
+
24
+ def source_configuration
25
+ SOURCE_CONFIGURATION
26
+ end
23
27
  end
24
28
  end
25
29
  end
@@ -11,13 +11,17 @@ module ElasticBeans
11
11
 
12
12
  # Constructs the configuration for the scheduler environment.
13
13
  # No special options here!
14
- def build_option_settings(**_)
14
+ def build_option_settings(option_settings: [], **_)
15
15
  super + [
16
- template_option_setting(namespace: "aws:elasticbeanstalk:application", option_name: "Application Healthcheck URL", default: "HTTP:80/"),
17
- template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "DISABLE_SQS_CONSUMER", override: "false"),
18
- template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "RAILS_SKIP_MIGRATIONS", default: "true"),
16
+ template_option_setting(namespace: "aws:elasticbeanstalk:application", option_name: "Application Healthcheck URL", default: "HTTP:80/", new_settings: option_settings),
17
+ template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "DISABLE_SQS_CONSUMER", override: "false", new_settings: option_settings),
18
+ template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "RAILS_SKIP_MIGRATIONS", default: "true", new_settings: option_settings),
19
19
  ]
20
20
  end
21
+
22
+ def source_configuration
23
+ SOURCE_CONFIGURATION
24
+ end
21
25
  end
22
26
  end
23
27
  end
@@ -13,35 +13,35 @@ module ElasticBeans
13
13
 
14
14
  # Constructs the configuration for the webserver environment.
15
15
  # All arguments are required on first run.
16
- def build_option_settings(network: nil, public_key: nil, ssl_certificate_arn: nil, internal: nil, **_)
17
- public_key_policy_names_setting = template_option_setting(namespace: "aws:elb:policies:backendencryption", option_name: "PublicKeyPolicyNames", default: "backendkey")
18
- public_key_setting = template_option_setting(namespace: "aws:elb:policies:#{public_key_policy_names_setting[:value]}", option_name: "PublicKey", override: public_key)
19
- ssl_certificate_setting = template_option_setting(namespace: "aws:elb:listener:443", option_name: "SSLCertificateId", override: ssl_certificate_arn)
16
+ def build_option_settings(network: nil, public_key: nil, ssl_certificate_arn: nil, internal: nil, option_settings: [], **_)
17
+ public_key_policy_names_setting = template_option_setting(namespace: "aws:elb:policies:backendencryption", option_name: "PublicKeyPolicyNames", default: "backendkey", new_settings: option_settings)
18
+ public_key_setting = template_option_setting(namespace: "aws:elb:policies:#{public_key_policy_names_setting[:value]}", option_name: "PublicKey", override: public_key, new_settings: option_settings)
19
+ ssl_certificate_setting = template_option_setting(namespace: "aws:elb:listener:443", option_name: "SSLCertificateId", override: ssl_certificate_arn, new_settings: option_settings)
20
20
  if public_key_setting[:value].nil? || ssl_certificate_setting[:value].nil?
21
21
  raise NoEncryptionSettingsError
22
22
  end
23
23
 
24
- option_settings = [
25
- template_option_setting(namespace: "aws:elasticbeanstalk:application", option_name: "Application Healthcheck URL", default: "HTTPS:443/", allow_blank: false),
26
- template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "RAILS_SKIP_ASSET_COMPILATION", default: "false"),
27
- template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "RAILS_SKIP_MIGRATIONS", default: "false"),
28
- template_option_setting(namespace: "aws:elb:listener:443", option_name: "InstancePort", default: "443"),
29
- template_option_setting(namespace: "aws:elb:listener:443", option_name: "InstanceProtocol", default: "HTTPS"),
30
- template_option_setting(namespace: "aws:elb:listener:443", option_name: "ListenerProtocol", default: "HTTPS"),
31
- template_option_setting(namespace: "aws:elb:loadbalancer", option_name: "ManagedSecurityGroup", override: managed_security_group(network)),
32
- template_option_setting(namespace: "aws:elb:loadbalancer", option_name: "SecurityGroups", override: elb_security_groups(network)),
33
- template_option_setting(namespace: "aws:elb:policies", option_name: "ConnectionDrainingEnabled", default: "true"),
34
- template_option_setting(namespace: "aws:elb:policies:backendencryption", option_name: "InstancePorts", default: "443"),
24
+ settings = [
25
+ template_option_setting(namespace: "aws:elasticbeanstalk:application", option_name: "Application Healthcheck URL", default: "HTTPS:443/", allow_blank: false, new_settings: option_settings),
26
+ template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "RAILS_SKIP_ASSET_COMPILATION", default: "false", new_settings: option_settings),
27
+ template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "RAILS_SKIP_MIGRATIONS", default: "false", new_settings: option_settings),
28
+ template_option_setting(namespace: "aws:elb:listener:443", option_name: "InstancePort", default: "443", new_settings: option_settings),
29
+ template_option_setting(namespace: "aws:elb:listener:443", option_name: "InstanceProtocol", default: "HTTPS", new_settings: option_settings),
30
+ template_option_setting(namespace: "aws:elb:listener:443", option_name: "ListenerProtocol", default: "HTTPS", new_settings: option_settings),
31
+ template_option_setting(namespace: "aws:elb:loadbalancer", option_name: "ManagedSecurityGroup", override: managed_security_group(network), new_settings: option_settings),
32
+ template_option_setting(namespace: "aws:elb:loadbalancer", option_name: "SecurityGroups", override: elb_security_groups(network), new_settings: option_settings),
33
+ template_option_setting(namespace: "aws:elb:policies", option_name: "ConnectionDrainingEnabled", default: "true", new_settings: option_settings),
34
+ template_option_setting(namespace: "aws:elb:policies:backendencryption", option_name: "InstancePorts", default: "443", new_settings: option_settings),
35
35
  public_key_policy_names_setting,
36
36
  public_key_setting,
37
37
  ssl_certificate_setting,
38
38
  ]
39
39
  if internal
40
- internal_setting = template_option_setting(namespace: "aws:ec2:vpc", option_name: "ELBScheme", override: "internal")
41
- option_settings << internal_setting
40
+ internal_setting = template_option_setting(namespace: "aws:ec2:vpc", option_name: "ELBScheme", override: "internal", new_settings: option_settings)
41
+ settings << internal_setting
42
42
  end
43
43
 
44
- super + option_settings
44
+ super + settings
45
45
  end
46
46
 
47
47
  # Removes the "internal" ELB scheme if explicitly disabled with --no-internal.
@@ -64,6 +64,10 @@ module ElasticBeans
64
64
  network.elb_security_groups[0] if network
65
65
  end
66
66
 
67
+ def source_configuration
68
+ SOURCE_CONFIGURATION
69
+ end
70
+
67
71
  # :nodoc: all
68
72
  # @!visibility private
69
73
  class NoEncryptionSettingsError < ElasticBeans::Error
@@ -24,17 +24,21 @@ module ElasticBeans
24
24
  # Constructs the configuration for the worker environments.
25
25
  # No special arguments, the +queue+ name is stored from the initializer and is used to look up the appropriate
26
26
  # URL.
27
- def build_option_settings(**_)
27
+ def build_option_settings(option_settings: [], **_)
28
28
  super + [
29
- template_option_setting(namespace: "aws:elasticbeanstalk:application", option_name: "Application Healthcheck URL", default: "HTTP:80/"),
30
- template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "DISABLE_SQS_CONSUMER", override: "false"),
31
- template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "RAILS_SKIP_MIGRATIONS", default: "true"),
32
- template_option_setting(namespace: "aws:elasticbeanstalk:sqsd", option_name: "InactivityTimeout", default: "1800"),
33
- template_option_setting(namespace: "aws:elasticbeanstalk:sqsd", option_name: "MaxRetries", default: "10"),
34
- template_option_setting(namespace: "aws:elasticbeanstalk:sqsd", option_name: "VisibilityTimeout", default: "1800"),
35
- template_option_setting(namespace: "aws:elasticbeanstalk:sqsd", option_name: "WorkerQueueURL", override: application.worker_queue_url(queue)),
29
+ template_option_setting(namespace: "aws:elasticbeanstalk:application", option_name: "Application Healthcheck URL", default: "HTTP:80/", new_settings: option_settings),
30
+ template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "DISABLE_SQS_CONSUMER", override: "false", new_settings: option_settings),
31
+ template_option_setting(namespace: "aws:elasticbeanstalk:application:environment", option_name: "RAILS_SKIP_MIGRATIONS", default: "true", new_settings: option_settings),
32
+ template_option_setting(namespace: "aws:elasticbeanstalk:sqsd", option_name: "InactivityTimeout", default: "1800", new_settings: option_settings),
33
+ template_option_setting(namespace: "aws:elasticbeanstalk:sqsd", option_name: "MaxRetries", default: "10", new_settings: option_settings),
34
+ template_option_setting(namespace: "aws:elasticbeanstalk:sqsd", option_name: "VisibilityTimeout", default: "1800", new_settings: option_settings),
35
+ template_option_setting(namespace: "aws:elasticbeanstalk:sqsd", option_name: "WorkerQueueURL", override: application.worker_queue_url(queue), new_settings: option_settings),
36
36
  ]
37
37
  end
38
+
39
+ def source_configuration
40
+ SOURCE_CONFIGURATION
41
+ end
38
42
  end
39
43
  end
40
44
  end
@@ -10,8 +10,11 @@ module ElasticBeans
10
10
  # This is an abstract class; use the provided factories in this class or use a subclass (contained within this
11
11
  # namespace) directly.
12
12
  class ConfigurationTemplate
13
- # The solution stack used for a new application. Should not be hardcoded, but here we are.
14
- SOLUTION_STACK_NAME = "64bit Amazon Linux 2016.09 v2.2.0 running Ruby 2.3 (Puma)"
13
+ # Matches the latest available Ruby Puma solution stack when one was not specified.
14
+ SOLUTION_STACK_PATTERN = /\A64bit Amazon Linux (?<date>\d+(\.\d+)*) v(?<version>\d+(\.\d+)*) running Ruby (?<ruby_version>\d+(\.\d+)*) \(Puma\)\z/
15
+ # The source configuration for new configuration templates.
16
+ # Set to ElasticBeans::ConfigurationTemplate::Base to include all custom configuration that has already been performed.
17
+ SOURCE_CONFIGURATION = {template_name: "base"}
15
18
  # :category: Internal
16
19
  WORKER_TEMPLATE_NAME_PATTERN = /\Aworker-(?<queue>\w+)\z/
17
20
 
@@ -102,6 +105,11 @@ module ElasticBeans
102
105
  @options_to_remove ||= []
103
106
  end
104
107
 
108
+ # Finds the latest available Ruby Puma solution stack (matching +SOLUTION_STACK_PATTERN+).
109
+ def solution_stack_name
110
+ @solution_stack_name ||= latest_solution_stack
111
+ end
112
+
105
113
  # Create or update the configuration template in Elastic Beanstalk.
106
114
  # Arguments are passed to #build_option_settings and #build_options_to_remove.
107
115
  # See the appropriate subclass for what the arguments should be.
@@ -119,7 +127,8 @@ module ElasticBeans
119
127
  elastic_beanstalk.create_configuration_template(
120
128
  application_name: application.name,
121
129
  template_name: name,
122
- solution_stack_name: SOLUTION_STACK_NAME,
130
+ solution_stack_name: solution_stack_name,
131
+ source_configuration: source_configuration,
123
132
  option_settings: option_settings,
124
133
  )
125
134
  end
@@ -135,12 +144,17 @@ module ElasticBeans
135
144
  # :category: Internal
136
145
  attr_reader :elastic_beanstalk
137
146
 
138
- def build_option_settings(**_)
139
- []
147
+ # Returns option settings that should be set on the template.
148
+ def build_option_settings(option_settings: [], **_)
149
+ option_settings
140
150
  end
141
151
 
142
- def build_options_to_remove(**_)
143
- []
152
+ # Returns option settings that should be removed from the template.
153
+ # Will not remove settings that beans has defaults for, even if they aren't being changed right now.
154
+ def build_options_to_remove(options_to_remove: [], **_)
155
+ options_to_remove.reject { |setting|
156
+ option_settings.any? { |new_setting| new_setting[:namespace] == setting[:namespace] && new_setting[:option_name] == setting[:option_name] }
157
+ }
144
158
  end
145
159
 
146
160
  def configuration_settings_description(template_name = name)
@@ -181,6 +195,36 @@ module ElasticBeans
181
195
  retry
182
196
  end
183
197
 
198
+ # Finds the latest available Ruby Puma solution stack (matching +SOLUTION_STACK_PATTERN+).
199
+ def latest_solution_stack
200
+ latest_stack = nil
201
+ latest_date = Gem::Version.new("0.0")
202
+ latest_version = Gem::Version.new("0.0")
203
+ latest_ruby_version = Gem::Version.new("0.0")
204
+ elastic_beanstalk.list_available_solution_stacks.solution_stacks.each do |stack|
205
+ match = SOLUTION_STACK_PATTERN.match(stack)
206
+ if match
207
+ stack_date = Gem::Version.new(match[:date])
208
+ stack_version = Gem::Version.new(match[:version])
209
+ stack_ruby_version = Gem::Version.new(match[:ruby_version])
210
+ if stack_date >= latest_date && stack_version >= latest_version && stack_ruby_version >= latest_ruby_version
211
+ latest_date = stack_date
212
+ latest_version = stack_version
213
+ latest_ruby_version = stack_ruby_version
214
+ latest_stack = stack
215
+ end
216
+ end
217
+ end
218
+ latest_stack
219
+ rescue ::Aws::ElasticBeanstalk::Errors::Throttling
220
+ sleep 5
221
+ retry
222
+ end
223
+
224
+ def source_configuration
225
+ SOURCE_CONFIGURATION
226
+ end
227
+
184
228
  def template_option_setting(
185
229
  template: configuration_settings_description,
186
230
  environment: environment_configuration_settings_description,
@@ -188,13 +232,21 @@ module ElasticBeans
188
232
  option_name:,
189
233
  default: nil,
190
234
  override: nil,
191
- allow_blank: true
235
+ allow_blank: true,
236
+ new_settings:
192
237
  )
193
238
  option_setting = {namespace: namespace, option_name: option_name, value: default}
194
239
  if override
195
240
  return option_setting.merge!(value: override)
196
241
  end
197
242
 
243
+ new_setting = new_settings.find { |setting|
244
+ setting[:namespace] == namespace && setting[:option_name] == option_name
245
+ }
246
+ if new_setting
247
+ return new_setting
248
+ end
249
+
198
250
  existing_settings = []
199
251
  if environment
200
252
  # Persist changes made directly to the environment from the AWS console UI
@@ -116,6 +116,7 @@ module ElasticBeans
116
116
  elastic_beanstalk.create_environment(
117
117
  application_name: application.name,
118
118
  environment_name: name,
119
+ solution_stack_name: configuration_template.solution_stack_name,
119
120
  tier: {name: tier_name, type: tier_type},
120
121
  template_name: template_name,
121
122
  version_label: version,
@@ -196,6 +197,7 @@ module ElasticBeans
196
197
  environment_name: name,
197
198
  option_settings: configuration_template.option_settings,
198
199
  options_to_remove: configuration_template.options_to_remove,
200
+ solution_stack_name: configuration_template.solution_stack_name,
199
201
  )
200
202
  wait_environment(wait_status: "Updating", wait_health_status: "Info") if blocking
201
203
  rescue ::Aws::ElasticBeanstalk::Errors::InvalidParameterValue
@@ -245,6 +247,10 @@ module ElasticBeans
245
247
  # :category: Internal
246
248
  attr_reader :elastic_beanstalk
247
249
 
250
+ def configuration_template
251
+ ElasticBeans::ConfigurationTemplate.new_from_existing(template_name, application: application, elastic_beanstalk: elastic_beanstalk)
252
+ end
253
+
248
254
  def environment_description
249
255
  elastic_beanstalk.describe_environments(
250
256
  environment_names: [name],
@@ -1,3 +1,3 @@
1
1
  module ElasticBeans
2
- VERSION = "0.13.0.alpha3"
2
+ VERSION = "0.13.0.alpha4"
3
3
  end
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.13.0.alpha3
4
+ version: 0.13.0.alpha4
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-07-21 00:00:00.000000000 Z
11
+ date: 2017-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk