hako 2.6.2 → 2.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b17b6419ac68f7381855d71a4a8bb268ab1f453e148c55f655704a9d016de402
4
- data.tar.gz: 4263e33011c50dac954f497a8c10744270a7e0042c25ce0550efb752114b4231
3
+ metadata.gz: 5e976128af20ee669b05e2e9531dc84d3b0ad46dcba305fa732cfbf0e24215dc
4
+ data.tar.gz: 0cde6cd9e06468444bd63a55f869424b3daae0926d6b8cd9a049e5511ba8ef30
5
5
  SHA512:
6
- metadata.gz: 5c278d415a8d1bcf4c1e29346f8f298b5de39175cd5b02a7233230537881be87b7445ad4b329f1903ae9912d6f8408e7bca41d367773888323995f46977311ba
7
- data.tar.gz: d44cc04c09be159f7b179f82a5b17c93fb7cd88d2c562567532b64eec72429a0625b0445769362f52802d81a7a2527d609c3ace2d1f2c325078b6a97f1e317a5
6
+ metadata.gz: 27279574a6d795bbe859463056f7460b501e3d2a0c38f6fa147aeafb419970ec9a02efed532fd35497f040a5fcb758145d224e6611bcc68cc003e586dc101753
7
+ data.tar.gz: ebb6948d88f10ff6f8ad3e7dbc3f47ea674cfb45d35b391c6611de9cbdb87bd73ea8cca02112f2dc4e27b6798a9e0308bb437aaa5db2586e2d2e747e7363ad8e
@@ -1,3 +1,9 @@
1
+ # 2.7.0 (2019-03-15)
2
+ ## New features
3
+ - Support `entry_point` parameter
4
+ - Support ECS Service Discovery
5
+ - See [examples/hello-service-discovery.jsonnet](examples/hello-service-discovery.jsonnet)
6
+
1
7
  # 2.6.2 (2018-12-19)
2
8
  ## Bug fixes
3
9
  - Set `platform_version` correctly
@@ -0,0 +1,49 @@
1
+ local fileProvider = std.native('provide.file');
2
+ local provide(name) = fileProvider(std.toString({ path: 'hello.env' }), name);
3
+
4
+ {
5
+ scheduler: {
6
+ type: 'ecs',
7
+ region: 'ap-northeast-1',
8
+ cluster: 'eagletmt',
9
+ desired_count: 2,
10
+ role: 'ecsServiceRole',
11
+ service_discovery: [
12
+ {
13
+ container_name: 'app',
14
+ container_port: 80,
15
+ service: {
16
+ name: 'hello-service-discovery',
17
+ namespace_id: 'ns-XXXXXXXXXXXXXXXX',
18
+ dns_config: {
19
+ dns_records: [
20
+ {
21
+ type: 'SRV',
22
+ ttl: 60,
23
+ },
24
+ ],
25
+ },
26
+ health_check_custom_config: {
27
+ failure_threshold: 1,
28
+ },
29
+ },
30
+ },
31
+ ],
32
+ },
33
+ app: {
34
+ image: 'ryotarai/hello-sinatra',
35
+ memory: 128,
36
+ cpu: 256,
37
+ env: {
38
+ PORT: '3000',
39
+ MESSAGE: std.format('%s-san', provide('username')),
40
+ },
41
+ port_mappings: [
42
+ {
43
+ container_port: 3000,
44
+ host_port: 0,
45
+ protocol: 'tcp',
46
+ },
47
+ ],
48
+ },
49
+ }
@@ -30,6 +30,7 @@ Gem::Specification.new do |spec|
30
30
  spec.add_dependency 'aws-sdk-elasticloadbalancing'
31
31
  spec.add_dependency 'aws-sdk-elasticloadbalancingv2'
32
32
  spec.add_dependency 'aws-sdk-s3'
33
+ spec.add_dependency 'aws-sdk-servicediscovery'
33
34
  spec.add_dependency 'aws-sdk-sns'
34
35
  spec.add_dependency 'aws-sdk-ssm'
35
36
  spec.add_dependency 'jsonnet'
@@ -27,6 +27,7 @@ module Hako
27
27
  memory_reservation
28
28
  links
29
29
  essential
30
+ entry_point
30
31
  command
31
32
  user
32
33
  privileged
@@ -14,6 +14,7 @@ require 'hako/schedulers/ecs_definition_comparator'
14
14
  require 'hako/schedulers/ecs_elb'
15
15
  require 'hako/schedulers/ecs_elb_v2'
16
16
  require 'hako/schedulers/ecs_service_comparator'
17
+ require 'hako/schedulers/ecs_service_discovery'
17
18
  require 'hako/schedulers/ecs_volume_comparator'
18
19
 
19
20
  module Hako
@@ -87,6 +88,9 @@ module Hako
87
88
  }
88
89
  end
89
90
  end
91
+ if options['service_discovery']
92
+ @service_discovery = EcsServiceDiscovery.new(options.fetch('service_discovery'), @region, dry_run: @dry_run)
93
+ end
90
94
 
91
95
  @started_at = nil
92
96
  @container_instance_arn = nil
@@ -114,6 +118,9 @@ module Hako
114
118
  @autoscaling.apply(Aws::ECS::Types::Service.new(cluster_arn: @cluster, service_name: @app_id))
115
119
  end
116
120
  ecs_elb_client.modify_attributes
121
+ if @service_discovery
122
+ @service_discovery.apply
123
+ end
117
124
  else
118
125
  current_service = describe_service
119
126
  task_definition_changed, task_definition = register_task_definition(definitions)
@@ -130,12 +137,18 @@ module Hako
130
137
  @autoscaling.apply(current_service)
131
138
  end
132
139
  ecs_elb_client.modify_attributes
140
+ if @service_discovery
141
+ @service_discovery.apply
142
+ end
133
143
  else
134
144
  Hako.logger.info "Updated service: #{service.service_arn}"
135
145
  if @autoscaling
136
146
  @autoscaling.apply(service)
137
147
  end
138
148
  ecs_elb_client.modify_attributes
149
+ if @service_discovery
150
+ @service_discovery.apply
151
+ end
139
152
  unless wait_for_ready(service)
140
153
  if task_definition_changed
141
154
  Hako.logger.error("Rolling back to #{current_service.task_definition}")
@@ -295,6 +308,13 @@ module Hako
295
308
  else
296
309
  puts 'Autoscaling: No'
297
310
  end
311
+
312
+ if service.service_registries.empty?
313
+ puts 'Service Discovery: No'
314
+ else
315
+ puts 'Service Discovery:'
316
+ @service_discovery.status(service.service_registries)
317
+ end
298
318
  end
299
319
 
300
320
  # @return [nil]
@@ -313,6 +333,9 @@ module Hako
313
333
  ecs_client.delete_service(cluster: service.cluster_arn, service: service.service_arn)
314
334
  Hako.logger.info "#{service.service_arn} is deleted"
315
335
  end
336
+ unless service.service_registries.empty?
337
+ @service_discovery.remove(service.service_registries)
338
+ end
316
339
  else
317
340
  puts "Service #{@app_id} doesn't exist"
318
341
  end
@@ -611,6 +634,7 @@ module Hako
611
634
  secrets: container.secrets,
612
635
  docker_labels: container.docker_labels,
613
636
  mount_points: container.mount_points,
637
+ entry_point: container.entry_point,
614
638
  command: container.command,
615
639
  privileged: container.privileged,
616
640
  linux_parameters: container.linux_parameters,
@@ -831,6 +855,7 @@ module Hako
831
855
  params[:desired_count] = current_service.desired_count
832
856
  end
833
857
  warn_placement_policy_change(current_service)
858
+ warn_service_registries_change(current_service)
834
859
  if service_changed?(current_service, params)
835
860
  ecs_client.update_service(params).service
836
861
  else
@@ -863,6 +888,10 @@ module Hako
863
888
  ecs_elb_client.modify_attributes
864
889
  params[:load_balancers] = [ecs_elb_client.load_balancer_params_for_service]
865
890
  end
891
+ if @service_discovery
892
+ @service_discovery.apply
893
+ params[:service_registries] = @service_discovery.service_registries
894
+ end
866
895
  ecs_client.create_service(params).service
867
896
  end
868
897
 
@@ -1203,6 +1232,9 @@ module Hako
1203
1232
  (definition[:docker_security_options] || []).each do |docker_security_option|
1204
1233
  cmd << '--security-opt' << docker_security_option
1205
1234
  end
1235
+ if definition[:entry_point]
1236
+ cmd << '--entrypoint' << definition[:entry_point]
1237
+ end
1206
1238
 
1207
1239
  cmd << "\\\n "
1208
1240
  definition.fetch(:environment).each do |env|
@@ -1283,6 +1315,16 @@ module Hako
1283
1315
  end
1284
1316
  end
1285
1317
 
1318
+ # @param [Aws::ECS::Types::Service] service
1319
+ # @return [void]
1320
+ def warn_service_registries_change(service)
1321
+ actual_service_registries = service.service_registries.sort_by(&:registry_arn).map(&:to_h)
1322
+ expected_service_registries = @service_discovery&.service_registries&.sort_by { |s| s[:registry_arn] } || []
1323
+ if actual_service_registries != expected_service_registries
1324
+ Hako.logger.warn "Ignoring updated service_registries in the configuration, because AWS doesn't allow updating them for now."
1325
+ end
1326
+ end
1327
+
1286
1328
  # @param [Aws::ECS::Types::TaskDefinition] task_definition
1287
1329
  # @param [String] target_definition
1288
1330
  # @return [nil]
@@ -32,6 +32,7 @@ module Hako
32
32
  struct.member(:secrets, Schema::Nullable.new(Schema::UnorderedArray.new(secrets_schema)))
33
33
  struct.member(:docker_labels, Schema::Table.new(Schema::String.new, Schema::String.new))
34
34
  struct.member(:mount_points, Schema::UnorderedArray.new(mount_point_schema))
35
+ struct.member(:entry_point, Schema::Nullable.new(Schema::OrderedArray.new(Schema::String.new)))
35
36
  struct.member(:command, Schema::Nullable.new(Schema::OrderedArray.new(Schema::String.new)))
36
37
  struct.member(:volumes_from, Schema::UnorderedArray.new(volumes_from_schema))
37
38
  struct.member(:user, Schema::Nullable.new(Schema::String.new))
@@ -0,0 +1,257 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aws-sdk-servicediscovery'
4
+ require 'hako'
5
+ require 'hako/error'
6
+ require 'hako/schedulers/ecs_service_discovery_service_comparator'
7
+
8
+ module Hako
9
+ module Schedulers
10
+ class EcsServiceDiscovery
11
+ # @param [Array<Hash>] config
12
+ # @param [Boolean] dry_run
13
+ # @param [String] region
14
+ def initialize(config, region, dry_run:)
15
+ @region = region
16
+ @config = config
17
+ @dry_run = dry_run
18
+ end
19
+
20
+ # @return [void]
21
+ def apply
22
+ @config.map do |service_discovery|
23
+ service = service_discovery.fetch('service')
24
+ namespace_id = service.fetch('namespace_id')
25
+ namespace = get_namespace(namespace_id)
26
+ if !namespace
27
+ raise Error.new("Service discovery namespace #{namespace_id} not found")
28
+ elsif namespace.type != 'DNS_PRIVATE'
29
+ raise Error.new("ECS only supports registering a service into a private DNS namespace: #{namespace.name} (#{namespace_id})")
30
+ end
31
+
32
+ service_name = service.fetch('name')
33
+ current_service = find_service(namespace_id, service_name)
34
+ if !current_service
35
+ if @dry_run
36
+ Hako.logger.info("Created service discovery service #{service_name} (dry-run)")
37
+ else
38
+ current_service = create_service(service)
39
+ Hako.logger.info("Created service discovery service #{service_name} (#{current_service.id})")
40
+ end
41
+ else
42
+ if service_changed?(service, current_service)
43
+ if @dry_run
44
+ Hako.logger.info("Updated service discovery service #{service_name} (#{current_service.id}) (dry-run)")
45
+ else
46
+ update_service(current_service.id, service)
47
+ Hako.logger.info("Updated service discovery service #{service_name} (#{current_service.id})")
48
+ end
49
+ end
50
+ warn_disallowed_service_change(service, current_service)
51
+ end
52
+ end
53
+ end
54
+
55
+ # @return [void]
56
+ def status(service_registries)
57
+ service_registries.each do |service_registry|
58
+ service_id = service_registry.registry_arn.slice(%r{service/(.+)\z}, 1)
59
+ service = get_service(service_id)
60
+ next unless service
61
+
62
+ namespace = get_namespace(service.namespace_id)
63
+ instances = service_discovery_client.list_instances(service_id: service.id).flat_map(&:instances)
64
+ puts " #{service.name}.#{namespace.name} instance_count=#{instances.size}"
65
+ instances.each do |instance|
66
+ instance_attributes = instance.attributes.map { |k, v| "#{k}=#{v}" }.join(', ')
67
+ puts " #{instance.id} #{instance_attributes}"
68
+ end
69
+ end
70
+ end
71
+
72
+ # @return [void]
73
+ def remove(service_registries)
74
+ service_registries.each do |service_registry|
75
+ service_id = service_registry.registry_arn.slice(%r{service/(.+)\z}, 1)
76
+ service = get_service(service_id)
77
+ unless service
78
+ Hako.logger.info("Service discovery service #{service_name} (#{service_id}) doesn't exist")
79
+ next
80
+ end
81
+ if @dry_run
82
+ Hako.logger.info("Deleted service discovery service #{service.name} (#{service.id}) (dry-run)")
83
+ else
84
+ deleted = false
85
+ 10.times do |i|
86
+ sleep 10 unless i.zero?
87
+ begin
88
+ service_discovery_client.delete_service(id: service.id)
89
+ deleted = true
90
+ break
91
+ rescue Aws::ServiceDiscovery::Errors::ResourceInUse => e
92
+ Hako.logger.warn("#{e.class}: #{e.message}")
93
+ end
94
+ end
95
+ unless deleted
96
+ raise Error.new("Unable to delete service discovery service #{service.name} (#{service.id})")
97
+ end
98
+
99
+ Hako.logger.info("Deleted service discovery service #{service.name} (#{service.id})")
100
+ end
101
+ end
102
+ end
103
+
104
+ # @return [Hash]
105
+ def service_registries
106
+ @config.map do |service_discovery|
107
+ service = service_discovery.fetch('service')
108
+ namespace_id = service.fetch('namespace_id')
109
+ service_name = service.fetch('name')
110
+ current_service = find_service(namespace_id, service_name)
111
+ unless current_service
112
+ raise Error.new("Service discovery service #{service_name} not found")
113
+ end
114
+
115
+ {
116
+ container_name: service_discovery['container_name'],
117
+ container_port: service_discovery['container_port'],
118
+ port: service_discovery['port'],
119
+ registry_arn: current_service.arn,
120
+ }.reject { |_, v| v.nil? }
121
+ end
122
+ end
123
+
124
+ private
125
+
126
+ # @param [String] namespace_id
127
+ # @param [String] service_name
128
+ # @return [Aws::ServiceDiscovery::Types::ServiceSummary, nil]
129
+ def find_service(namespace_id, service_name)
130
+ params = {
131
+ filters: [
132
+ name: 'NAMESPACE_ID',
133
+ values: [namespace_id],
134
+ condition: 'EQ',
135
+ ],
136
+ }
137
+ services = service_discovery_client.list_services(params).flat_map(&:services)
138
+ services.find { |service| service.name == service_name }
139
+ end
140
+
141
+ # @return [Aws::ServiceDiscovery::Client]
142
+ def service_discovery_client
143
+ @service_discovery_client ||= Aws::ServiceDiscovery::Client.new(region: @region)
144
+ end
145
+
146
+ # @param [Hash] service
147
+ # @return [Aws::ServiceDiscovery::Types::Service]
148
+ def create_service(service)
149
+ service_discovery_client.create_service(create_service_params(service)).service
150
+ end
151
+
152
+ # @param [Hash] service
153
+ # @return [Hash]
154
+ def create_service_params(service)
155
+ dns_config = service.fetch('dns_config')
156
+ params = {
157
+ name: service.fetch('name'),
158
+ namespace_id: service['namespace_id'],
159
+ description: service['description'],
160
+ dns_config: {
161
+ namespace_id: dns_config['namespace_id'],
162
+ routing_policy: dns_config.fetch('routing_policy', 'MULTIVALUE'),
163
+ },
164
+ }
165
+ params[:dns_config][:dns_records] = dns_config.fetch('dns_records').map do |dns_record|
166
+ {
167
+ type: dns_record.fetch('type'),
168
+ ttl: dns_record.fetch('ttl'),
169
+ }
170
+ end
171
+ if (health_check_custom_config = service['health_check_custom_config'])
172
+ params[:health_check_custom_config] = {
173
+ failure_threshold: health_check_custom_config['failure_threshold'],
174
+ }
175
+ end
176
+ params
177
+ end
178
+
179
+ # @param [Hash] expected_service
180
+ # @param [Aws::ServiceDiscovery::Types::ServiceSummary] actual_service
181
+ # @return [Boolean]
182
+ def service_changed?(expected_service, actual_service)
183
+ EcsServiceDiscoveryServiceComparator.new(update_service_params(expected_service)).different?(actual_service)
184
+ end
185
+
186
+ # @param [String] service_id
187
+ # @param [Hash] service
188
+ def update_service(service_id, service)
189
+ operation_id = service_discovery_client.update_service(
190
+ id: service_id,
191
+ service: update_service_params(service),
192
+ ).operation_id
193
+ operation = wait_for_operation(operation_id)
194
+ if operation.status != 'SUCCESS'
195
+ raise Error.new("Unable to update service discovery service (#{operation.error_code}): #{operation.error_message}")
196
+ end
197
+ end
198
+
199
+ # @param [Hash] service
200
+ # @return [Hash]
201
+ def update_service_params(service)
202
+ dns_config = service.fetch('dns_config')
203
+ params = {
204
+ description: service['description'],
205
+ dns_config: {},
206
+ }
207
+ params[:dns_config][:dns_records] = dns_config.fetch('dns_records').map do |dns_record|
208
+ {
209
+ type: dns_record.fetch('type'),
210
+ ttl: dns_record.fetch('ttl'),
211
+ }
212
+ end
213
+ params
214
+ end
215
+
216
+ # @param [String] service_id
217
+ # @return [Aws::ServiceDiscovery::Types::GetOperationResponse]
218
+ def wait_for_operation(operation_id)
219
+ loop do
220
+ operation = service_discovery_client.get_operation(operation_id: operation_id).operation
221
+ return operation if %w[SUCCESS FAIL].include?(operation.status)
222
+
223
+ sleep 10
224
+ end
225
+ end
226
+
227
+ # @param [String] service_id
228
+ # @return [Aws::ServiceDiscovery::Types::Service, nil]
229
+ def get_service(service_id)
230
+ service_discovery_client.get_service(id: service_id).service
231
+ rescue Aws::ServiceDiscovery::Errors::ServiceNotFound
232
+ nil
233
+ end
234
+
235
+ # @param [String] namespace_id
236
+ # @return [Aws::ServiceDiscovery::Types::Namespace, nil]
237
+ def get_namespace(namespace_id)
238
+ service_discovery_client.get_namespace(id: namespace_id).namespace
239
+ rescue Aws::ServiceDiscovery::Errors::NamespaceNotFound
240
+ nil
241
+ end
242
+
243
+ # @param [Hash] expected_service
244
+ # @param [Aws::ServiceDiscovery::Types::ServiceSummary] actual_service
245
+ # @return [void]
246
+ def warn_disallowed_service_change(expected_service, actual_service)
247
+ expected_service = create_service_params(expected_service)
248
+ if expected_service.dig(:dns_config, :routing_policy) != actual_service.dns_config.routing_policy
249
+ Hako.logger.warn("Ignoring updated service_discovery.dns_config.routing_policy in the configuration, because AWS doesn't allow updating it for now.")
250
+ end
251
+ if expected_service[:health_check_custom_config] != actual_service.health_check_custom_config&.to_h
252
+ Hako.logger.warn("Ignoring updated service_discovery.health_check_custom_config in the configuration, because AWS doesn't allow updating it for now.")
253
+ end
254
+ end
255
+ end
256
+ end
257
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hako/schema'
4
+
5
+ module Hako
6
+ module Schedulers
7
+ class EcsServiceDiscoveryServiceComparator
8
+ # @param [Hash] expected_service
9
+ def initialize(expected_service)
10
+ @expected_service = expected_service
11
+ @schema = service_schema
12
+ end
13
+
14
+ # @param [Aws::ServiceDiscovery::Types::ServiceSummary] actual_service
15
+ # @return [Boolean]
16
+ def different?(actual_service)
17
+ !@schema.same?(actual_service.to_h, @expected_service)
18
+ end
19
+
20
+ private
21
+
22
+ def service_schema
23
+ Schema::Structure.new.tap do |struct|
24
+ struct.member(:description, Schema::Nullable.new(Schema::String.new))
25
+ struct.member(:dns_config, dns_config_schema)
26
+ end
27
+ end
28
+
29
+ def dns_config_schema
30
+ Schema::Structure.new.tap do |struct|
31
+ struct.member(:dns_records, Schema::UnorderedArray.new(dns_records_schema))
32
+ end
33
+ end
34
+
35
+ def dns_records_schema
36
+ Schema::Structure.new.tap do |struct|
37
+ struct.member(:ttl, Schema::Integer.new)
38
+ struct.member(:type, Schema::String.new)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Hako
4
- VERSION = '2.6.2'
4
+ VERSION = '2.7.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hako
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.2
4
+ version: 2.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kohei Suzuki
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-12-19 00:00:00.000000000 Z
11
+ date: 2019-03-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-applicationautoscaling
@@ -136,6 +136,20 @@ dependencies:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: aws-sdk-servicediscovery
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
139
153
  - !ruby/object:Gem::Dependency
140
154
  name: aws-sdk-sns
141
155
  requirement: !ruby/object:Gem::Requirement
@@ -312,6 +326,7 @@ files:
312
326
  - examples/hello-lb.jsonnet
313
327
  - examples/hello-nofront.jsonnet
314
328
  - examples/hello-privileged-app.jsonnet
329
+ - examples/hello-service-discovery.jsonnet
315
330
  - examples/hello.env
316
331
  - examples/hello.jsonnet
317
332
  - examples/put-ecs-container-status-to-s3/index.js
@@ -341,6 +356,8 @@ files:
341
356
  - lib/hako/schedulers/ecs_elb.rb
342
357
  - lib/hako/schedulers/ecs_elb_v2.rb
343
358
  - lib/hako/schedulers/ecs_service_comparator.rb
359
+ - lib/hako/schedulers/ecs_service_discovery.rb
360
+ - lib/hako/schedulers/ecs_service_discovery_service_comparator.rb
344
361
  - lib/hako/schedulers/ecs_volume_comparator.rb
345
362
  - lib/hako/schema.rb
346
363
  - lib/hako/schema/boolean.rb
@@ -380,7 +397,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
380
397
  version: '0'
381
398
  requirements: []
382
399
  rubyforge_project:
383
- rubygems_version: 2.7.6
400
+ rubygems_version: 2.7.6.2
384
401
  signing_key:
385
402
  specification_version: 4
386
403
  summary: Deploy Docker container