awsborn 0.8.0 → 0.8.1

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.
data/README.mdown CHANGED
@@ -77,16 +77,16 @@ equally well be added as options to the specific servers.
77
77
 
78
78
  ### Defining a cluster
79
79
 
80
- The `cluster` method accepts three commands, `domain`, `load_balancer` and `server`. `domain` is
81
- optional and is just a way to avoid repetition in the `server` commands. `load_balancer` is also optional and take a name and a hash. `server`
82
- takes a name (which can be used as a key in the cluster, e.g. `log_servers[:log_a]`)
80
+ The `cluster` method accepts three commands, `domain`, `load_balancer` and `server`.
81
+ `domain` is optional and is just a way to avoid repetition in the `server` commands.
82
+ `load_balancer` is also optional and take a name and a hash. `server` takes a name
83
+ (which can be used as a key in the cluster, e.g. `log_servers[:log_a]`)
83
84
  and a hash:
84
85
 
85
86
  Mandatory keys for server:
86
87
 
87
- * `:zone` -- the availability zone for the server. One of `:us_east_1a`, `:us_east_1b`,
88
- `:us_east_1c`, `:us_east_1d`, `:us_west_1a`, `:us_west_1b`, `:eu_west_1a`, `:eu_west_1b`,
89
- `:ap-southeast-1a`, `:ap-southeast-1b`.
88
+ * `:zone` -- the availability zone for the server. Expressed as symbols, e.g.
89
+ `:us_east_1d`, `:us_west_1a`, `:eu_west_1c`, `:ap-southeast-1a`, `:ap-northeast-1b`.
90
90
  * `:disk` -- a hash of `device => volume-id`. Awsborn uses the disks to tell if a server
91
91
  is running or not (see *Launching a cluster*). `volume-id` can also be an array
92
92
  `[volume-id, :format]`, in which case `the_server.format_disk_on_device?(device)`
@@ -108,25 +108,39 @@ Keys that override server type settings:
108
108
 
109
109
  Mandatory keys for load_balancer:
110
110
 
111
- * `:region` -- the region covered by the load balancer. One of `:us_east_1`, `:us_west_1`, `:eu_west_1`, `:ap_southeast_1`
111
+ * `:region` -- the region covered by the load balancer. One of `:us_east_1`, `:us_west_1`,
112
+ `:eu_west_1`, `:ap_southeast_1`, `:ap_northeast_1`
112
113
 
113
114
  Optional keys for load_balancer:
114
115
 
115
- * `:only` -- A list of server names to which the load balancer is
116
- restricted
117
- * `:except` -- A list of servers that the load balancer should ignore
118
- * `:listerners` -- A list of listeners definitions for the load
119
- balancer. For instance: `{ :protocol => :http, :load_balancer_port => 80, :instance_port => 80 }`
120
- * `:sticky_cookies` -- A list of sticky cookies policies that the load balancer
121
- should take care of. There are three kinds of available policies: `:disabled`, `:app_generated` (the cookie is generated by the app) or `:lb_generated` (the cookie is generated by the load balancer).
116
+ * `:only` -- a list of server names to which the load balancer is
117
+ restricted.
118
+ * `:except` -- a list of servers that the load balancer should ignore.
119
+ * `:health_check` -- a hash containing full or partial health check
120
+ configuration. Health check can contain `:healthy_threshold`,
121
+ `:unhealthy_threshold`, `:target`, `:timeout` and `:interval`
122
+ * `:listeners` -- a list of listener definitions for the load balancer. For instance:
123
+ `{ :protocol => :http, :load_balancer_port => 80, :instance_port => 80 }`
124
+ * `:sticky_cookies` -- a list of ports/cookie policies that the load balancer
125
+ should take care of. There are three kinds of available policies:
126
+ `:disabled`,
127
+ `:app_generated` (the cookie is generated by the app) or
128
+ `:lb_generated` (the cookie is generated by the load balancer).
122
129
 
123
130
  Example:
124
131
 
125
132
  cluster "test" do
126
- domain 'yourdimain.net'
133
+ domain 'yourdomain.net'
127
134
  load_balancer 'test-balancer',
128
135
  :region => :eu_west_1,
129
136
  :only => [:cluster_test_1, :cluster_test_2],
137
+ :health_check => {
138
+ :target => 'HTTP:80/index.html',
139
+ :timeout => 6,
140
+ :interval => 31,
141
+ :unhealthy_threshold => 3,
142
+ :healthy_threshold => 9
143
+ },
130
144
  :listeners => [
131
145
  { :protocol => :http, :load_balancer_port => 80, :instance_port => 80},
132
146
  { :protocol => :tcp, :load_balancer_port => 443, :instance_port => 443}
@@ -311,6 +325,14 @@ Other rake tasks include:
311
325
 
312
326
  ## Changes
313
327
 
328
+ ### New in 0.8.1
329
+
330
+ * Specify health_check for load balancers.
331
+
332
+ ### New in 0.8.0
333
+
334
+ * Server cluster can have load balancers.
335
+
314
336
  ### New in 0.7.0
315
337
 
316
338
  * Allow several security groups per server.
data/Rakefile CHANGED
@@ -9,7 +9,7 @@ begin
9
9
  gem.summary = %Q{Awsborn defines servers as instances with a certain disk volume, which makes it easy to restart missing servers.}
10
10
  gem.email = "david@icehouse.se"
11
11
  gem.homepage = "http://github.com/icehouse/awsborn"
12
- gem.authors = ["David Vrensk"]
12
+ gem.authors = ["David Vrensk", "Jean-Louis Giordano"]
13
13
  gem.add_dependency "right_aws", ">= 2.1.0"
14
14
  gem.add_dependency "json_pure", ">= 1.2.3"
15
15
  gem.add_dependency "rake"
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.8.0
1
+ 0.8.1
data/awsborn.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{awsborn}
8
- s.version = "0.8.0"
8
+ s.version = "0.8.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["David Vrensk"]
12
- s.date = %q{2011-08-11}
11
+ s.authors = ["David Vrensk", "Jean-Louis Giordano"]
12
+ s.date = %q{2011-08-16}
13
13
  s.description = %q{Awsborn lets you define and launch a server cluster on Amazon EC2.}
14
14
  s.email = %q{david@icehouse.se}
15
15
  s.extra_rdoc_files = [
@@ -3,8 +3,8 @@ module Awsborn
3
3
 
4
4
  AVAILABILITY_ZONES = %w[
5
5
  us-east-1a us-east-1b us-east-1c us-east-1d
6
- us-west-1a us-west-1b
7
- eu-west-1a eu-west-1b
6
+ us-west-1a us-west-1b us-west-1c
7
+ eu-west-1a eu-west-1b eu-west-1c
8
8
  ap-southeast-1a ap-southeast-1b
9
9
  ap-northeast-1a ap-northeast-1b
10
10
  ]
@@ -15,14 +15,14 @@ module Awsborn
15
15
  SYMBOL_CONSTANT_MAP = (AVAILABILITY_ZONES + INSTANCE_TYPES).inject({}) { |memo,str| memo[str.tr('-.','_').to_sym] = str; memo }
16
16
 
17
17
  def endpoint_for_zone_and_service (zone, service)
18
- region = zone_to_awz_region(zone)
18
+ region = zone_to_aws_region(zone)
19
19
  case service
20
20
  when :ec2 then "https://#{region}.ec2.amazonaws.com"
21
21
  when :elb then "https://#{region}.elasticloadbalancing.amazonaws.com"
22
22
  end
23
23
  end
24
24
 
25
- def zone_to_awz_region (zone)
25
+ def zone_to_aws_region (zone)
26
26
  region = zone.to_s.sub(/[a-z]$/,'').tr('_','-')
27
27
  raise UnknownConstantError, "Unknown region: #{region} for zone: #{zone}" unless REGIONS.include? region
28
28
  region
@@ -50,7 +50,7 @@ module Awsborn
50
50
  type.to_s.tr('.','_').to_sym
51
51
  end
52
52
 
53
- def awz_constant (symbol)
53
+ def aws_constant (symbol)
54
54
  SYMBOL_CONSTANT_MAP[symbol] || raise(UnknownConstantError, "Unknown constant: #{symbol}")
55
55
  end
56
56
  end
data/lib/awsborn/elb.rb CHANGED
@@ -23,7 +23,7 @@ module Awsborn
23
23
  end
24
24
 
25
25
  def initialize (zone)
26
- @region = zone_to_awz_region(zone)
26
+ @region = zone_to_aws_region(zone)
27
27
  @endpoint = endpoint_for_zone_and_service(@region, :elb)
28
28
  end
29
29
 
@@ -112,6 +112,15 @@ module Awsborn
112
112
  connection.disable_availability_zones_for_load_balancer(balancer_name, *zones)
113
113
  end
114
114
 
115
+ def configure_health_check (balancer_name, health_check)
116
+ logger.debug "Setting health check on load balancer #{balancer_name}"
117
+ connection.configure_health_check(balancer_name, health_check)
118
+ end
119
+
120
+ def health_status (balancer_name)
121
+ connection.describe_instance_health(balancer_name, *instances(balancer_name))
122
+ end
123
+
115
124
  protected
116
125
 
117
126
  def existing_app_cookie_policies (balancer_name)
@@ -2,17 +2,24 @@ module Awsborn
2
2
  class LoadBalancer
3
3
 
4
4
  include Awsborn::AwsConstants
5
- attr_accessor :name, :only, :except, :region, :listeners, :sticky_cookies
5
+ attr_accessor :name, :only, :except, :region, :listeners, :sticky_cookies, :health_check_config
6
6
 
7
7
  DEFAULT_LISTENERS = [ { :protocol => :http, :load_balancer_port => 80, :instance_port => 80} ]
8
+ DEFAULT_HEALTH_CONFIG = {
9
+ :healthy_threshold => 10,
10
+ :unhealthy_threshold => 2,
11
+ :target => "TCP:80",
12
+ :timeout => 5,
13
+ :interval => 30
14
+ }
8
15
  def initialize (name, options={})
9
16
  @name = name
10
17
  @only = options[:only] || []
11
18
  @except = options[:except] || []
12
- @region = zone_to_awz_region(options[:region])
19
+ @region = zone_to_aws_region(options[:region])
13
20
  @listeners = options[:listeners] || DEFAULT_LISTENERS
14
21
  @sticky_cookies = options[:sticky_cookies] || []
15
- launch unless running?
22
+ @health_check_config = DEFAULT_HEALTH_CONFIG.merge(options[:health_check] || {})
16
23
  end
17
24
 
18
25
  def elb
@@ -57,6 +64,9 @@ module Awsborn
57
64
  elb.create_load_balancer(@name)
58
65
  end
59
66
 
67
+ def health_status
68
+ elb.health_status(@name)
69
+ end
60
70
 
61
71
  def update_listeners
62
72
  elb.set_load_balancer_listeners(@name, @listeners)
@@ -80,7 +90,13 @@ module Awsborn
80
90
  end
81
91
  end
82
92
 
93
+ def update_health_config
94
+ elb.configure_health_check(@name, @health_check_config)
95
+ end
96
+
83
97
  def update_with (new_servers)
98
+ launch unless running?
99
+
84
100
  servers_to_be_balanced = new_servers
85
101
  servers_to_be_balanced =
86
102
  servers_to_be_balanced.select{|s| @only.include?(s.name)} unless @only.empty?
@@ -92,6 +108,7 @@ module Awsborn
92
108
 
93
109
  update_listeners
94
110
  update_sticky_cookies
111
+ update_health_config
95
112
 
96
113
  self.description
97
114
  end
@@ -15,19 +15,19 @@ describe Awsborn::AwsConstants do
15
15
  end
16
16
  end
17
17
 
18
- describe "zone_to_awz_region" do
18
+ describe "zone_to_aws_region" do
19
19
  it "accepts a zone symbol and returns its region" do
20
- zone_to_awz_region(:eu_west_1a).should == 'eu-west-1'
20
+ zone_to_aws_region(:eu_west_1a).should == 'eu-west-1'
21
21
  end
22
22
  it "accepts an aws zone symbol and returns its region" do
23
- zone_to_awz_region('eu-west-1a').should == 'eu-west-1'
23
+ zone_to_aws_region('eu-west-1a').should == 'eu-west-1'
24
24
  end
25
25
  it "raise an error if no region found" do
26
- expect{zone_to_awz_region('santa-northpole-2b')}.to raise_error(Awsborn::UnknownConstantError)
26
+ expect{zone_to_aws_region('santa-northpole-2b')}.to raise_error(Awsborn::UnknownConstantError)
27
27
  end
28
28
  it "returns a region even when a region is given" do
29
- zone_to_awz_region('eu-west-1').should == 'eu-west-1'
30
- zone_to_awz_region(:eu_west_1).should == 'eu-west-1'
29
+ zone_to_aws_region('eu-west-1').should == 'eu-west-1'
30
+ zone_to_aws_region(:eu_west_1).should == 'eu-west-1'
31
31
  end
32
32
  end
33
33
 
@@ -67,15 +67,15 @@ describe Awsborn::AwsConstants do
67
67
  end
68
68
  end
69
69
 
70
- describe "awz_constant" do
70
+ describe "aws_constant" do
71
71
  it "should look up an availability zone" do
72
- awz_constant(:eu_west_1a).should == "eu-west-1a"
72
+ aws_constant(:eu_west_1a).should == "eu-west-1a"
73
73
  end
74
74
  it "should look up an instance type" do
75
- awz_constant(:m1_large).should == "m1.large"
75
+ aws_constant(:m1_large).should == "m1.large"
76
76
  end
77
77
  it "should raise an error if the symbol is unknown" do
78
- expect{awz_constant(:unknown_constant)}.to raise_error(Awsborn::UnknownConstantError)
78
+ expect{aws_constant(:unknown_constant)}.to raise_error(Awsborn::UnknownConstantError)
79
79
  end
80
80
  end
81
81
 
data/spec/elb_spec.rb CHANGED
@@ -251,6 +251,21 @@ describe Awsborn::Elb do
251
251
  @elb.disable_zones('some-name', ['eu-west-1a', 'eu-west-1b'])
252
252
  end
253
253
  end
254
+
255
+ describe "configure_health_check" do
256
+ it "forwards to ElbInterface" do
257
+ @mock_interface.should_receive(:configure_health_check).with('some-name', 'health-check-config')
258
+ @elb.configure_health_check('some-name', 'health-check-config')
259
+ end
260
+ end
261
+
262
+ describe "health_status" do
263
+ it "returns the health status of each instance of the load balancer" do
264
+ @mock_interface.should_receive(:describe_load_balancers).with('some-name').and_return([{:instances => 'instances'}])
265
+ @mock_interface.should_receive(:describe_instance_health).with('some-name', 'instances').and_return("health_status")
266
+ @elb.health_status('some-name')
267
+ end
268
+ end
254
269
  end
255
270
  end
256
271
 
@@ -5,9 +5,23 @@ describe Awsborn::LoadBalancer do
5
5
  @mocked_elb = mock(:elb,
6
6
  :running? => false,
7
7
  :remove_all_cookie_policies => true,
8
- :create_load_balancer => {})
8
+ :create_load_balancer => {},
9
+ :instances => [],
10
+ :zones => [],
11
+ :register_instances => true,
12
+ :enable_zones => true,
13
+ :set_load_balancer_listeners => true,
14
+ :describe_load_balancer => "description",
15
+ :configure_health_check => true)
9
16
  @listener_fixture = [ { :protocol => :tcp, :load_balancer_port => 123, :instance_port => 123} ]
10
17
  @cookies_fixture = [ { :ports => [123], :policy => :disabled } ]
18
+ @health_check_fixture = {
19
+ :healthy_threshold => 9,
20
+ :unhealthy_threshold => 3,
21
+ :target => "TCP:433",
22
+ :timeout => 6,
23
+ :interval => 31
24
+ }
11
25
  Awsborn::Elb.stub!(:new).and_return(@mocked_elb)
12
26
  end
13
27
  describe "initialize" do
@@ -15,22 +29,6 @@ describe Awsborn::LoadBalancer do
15
29
  expect { Awsborn::LoadBalancer.new('some-name') }.to raise_error
16
30
  expect { Awsborn::LoadBalancer.new('some-name', :region => :blabla) }.to raise_error
17
31
  end
18
- it "launches the load balancer if not running" do
19
- @mocked_elb.should_receive(:running?).with('some-name').and_return(false)
20
- @mocked_elb.should_receive(:create_load_balancer).with('some-name').and_return(nil)
21
- @balancer = Awsborn::LoadBalancer.new(
22
- 'some-name',
23
- :region => :eu_west_1
24
- )
25
- end
26
- it "does not launch the load balancer if running" do
27
- @mocked_elb.should_receive(:running?).with('some-name').and_return(true)
28
- @mocked_elb.should_not_receive(:create_load_balancer)
29
- @balancer = Awsborn::LoadBalancer.new(
30
- 'some-name',
31
- :region => :eu_west_1
32
- )
33
- end
34
32
  describe "sets all attributes properly" do
35
33
  subject do
36
34
  @balancer = Awsborn::LoadBalancer.new(
@@ -39,7 +37,8 @@ describe Awsborn::LoadBalancer do
39
37
  :only => [:server1, :server2],
40
38
  :except => [:server2],
41
39
  :listeners => @listener_fixture,
42
- :sticky_cookies => @cookies_fixture
40
+ :sticky_cookies => @cookies_fixture,
41
+ :health_check => @health_check_fixture
43
42
  )
44
43
  end
45
44
  its(:name) { should == 'some-name' }
@@ -48,6 +47,7 @@ describe Awsborn::LoadBalancer do
48
47
  its(:except) { should == [:server2] }
49
48
  its(:listeners) { should == @listener_fixture }
50
49
  its(:sticky_cookies) { should == @cookies_fixture }
50
+ its(:health_check_config) { should == @health_check_fixture }
51
51
  end
52
52
  describe "sets proper default values" do
53
53
  subject do
@@ -60,6 +60,19 @@ describe Awsborn::LoadBalancer do
60
60
  its(:except) { should == [] }
61
61
  its(:listeners) { should == Awsborn::LoadBalancer::DEFAULT_LISTENERS }
62
62
  its(:sticky_cookies) { should == [] }
63
+ its(:health_check_config) { should == Awsborn::LoadBalancer::DEFAULT_HEALTH_CONFIG }
64
+ end
65
+ it "accepts partial health config" do
66
+ balancer = Awsborn::LoadBalancer.new(
67
+ 'some-name',
68
+ :region => :eu_west_1,
69
+ :health_check => { :target => 'HTTP:80/test.html' }
70
+ )
71
+ balancer.health_check_config[:target].should == 'HTTP:80/test.html'
72
+ [:healthy_threshold, :unhealthy_threshold, :timeout, :interval].each do |other_attribute|
73
+ balancer.health_check_config[other_attribute].should ==
74
+ Awsborn::LoadBalancer::DEFAULT_HEALTH_CONFIG[other_attribute]
75
+ end
63
76
  end
64
77
  end
65
78
 
@@ -180,6 +193,27 @@ describe Awsborn::LoadBalancer do
180
193
  end
181
194
  end
182
195
 
196
+ describe "health_status" do
197
+ it "delegates to elb" do
198
+ @mocked_elb.should_receive(:health_status).with('some-name').and_return('health_status')
199
+ @balancer = Awsborn::LoadBalancer.new(
200
+ 'some-name',
201
+ :region => :eu_west_1
202
+ ).health_status.should == 'health_status'
203
+ end
204
+ end
205
+
206
+ describe "update_health_config" do
207
+ it "delegates to elb" do
208
+ @mocked_elb.should_receive(:configure_health_check).with('some-name', @health_check_fixture)
209
+ @balancer = Awsborn::LoadBalancer.new(
210
+ 'some-name',
211
+ :region => :eu_west_1,
212
+ :health_check => @health_check_fixture
213
+ ).update_health_config
214
+ end
215
+ end
216
+
183
217
  describe "update_listeners" do
184
218
  it "delegates to elb" do
185
219
  @mocked_elb.should_receive('set_load_balancer_listeners').with('some-name', @listener_fixture)
@@ -254,11 +288,22 @@ describe Awsborn::LoadBalancer do
254
288
  mock(:server1, :name => :server2, :instance_id => 'i-00000002', :zone => :eu_west_1b)
255
289
  ]
256
290
  end
257
- it "sets new instances, sets new zones, updates listeners and updates sticky cookies" do
291
+ it "launches the load balancer if not running" do
292
+ @mocked_elb.should_receive(:running?).with('some-name').and_return(false)
293
+ @mocked_elb.should_receive(:create_load_balancer).with('some-name').and_return(nil)
294
+ @balancer.update_with(@new_servers)
295
+ end
296
+ it "does not launch the load balancer if running" do
297
+ @mocked_elb.should_receive(:running?).with('some-name').and_return(true)
298
+ @mocked_elb.should_not_receive(:create_load_balancer)
299
+ @balancer.update_with(@new_servers)
300
+ end
301
+ it "sets instances and new zones and updates listeners, sticky cookies and health config" do
258
302
  @balancer.should_receive(:instances=).with(['i-00000001', 'i-00000002'])
259
303
  @balancer.should_receive(:zones=).with(['eu-west-1a', 'eu-west-1b'])
260
304
  @balancer.should_receive(:update_listeners)
261
305
  @balancer.should_receive(:update_sticky_cookies)
306
+ @balancer.should_receive(:update_health_config)
262
307
  @balancer.should_receive(:description).and_return('description')
263
308
  @balancer.update_with(@new_servers).should == 'description'
264
309
  end
@@ -269,6 +314,7 @@ describe Awsborn::LoadBalancer do
269
314
  @balancer.should_receive(:zones=).with(['eu-west-1a'])
270
315
  @balancer.should_receive(:update_listeners)
271
316
  @balancer.should_receive(:update_sticky_cookies)
317
+ @balancer.should_receive(:update_health_config)
272
318
  @balancer.should_receive(:description).and_return('description')
273
319
  @balancer.update_with(@new_servers).should == 'description'
274
320
  end
@@ -279,6 +325,7 @@ describe Awsborn::LoadBalancer do
279
325
  @balancer.should_receive(:zones=).with(['eu-west-1b'])
280
326
  @balancer.should_receive(:update_listeners)
281
327
  @balancer.should_receive(:update_sticky_cookies)
328
+ @balancer.should_receive(:update_health_config)
282
329
  @balancer.should_receive(:description).and_return('description')
283
330
  @balancer.update_with(@new_servers).should == 'description'
284
331
  end
data/spec/server_spec.rb CHANGED
@@ -17,15 +17,15 @@ describe Awsborn::Server do
17
17
  @server = SampleServer.new :sample, :zone => :eu_west_1a, :disk => {:sdf => "vol-aaaaaaaa"}
18
18
  end
19
19
 
20
- describe "awz_constant" do
20
+ describe "aws_constant" do
21
21
  it "should look up an availability zone" do
22
- @server.awz_constant(:eu_west_1a).should == "eu-west-1a"
22
+ @server.aws_constant(:eu_west_1a).should == "eu-west-1a"
23
23
  end
24
24
  it "should look up an instance type" do
25
- @server.awz_constant(:m1_large).should == "m1.large"
25
+ @server.aws_constant(:m1_large).should == "m1.large"
26
26
  end
27
27
  it "should raise an error if the symbol is unknown" do
28
- expect{@server.awz_constant(:unknown_constant)}.to raise_error(Awsborn::UnknownConstantError)
28
+ expect{@server.aws_constant(:unknown_constant)}.to raise_error(Awsborn::UnknownConstantError)
29
29
  end
30
30
  end
31
31
 
metadata CHANGED
@@ -1,21 +1,22 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: awsborn
3
3
  version: !ruby/object:Gem::Version
4
- hash: 63
4
+ hash: 61
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 8
9
- - 0
10
- version: 0.8.0
9
+ - 1
10
+ version: 0.8.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - David Vrensk
14
+ - Jean-Louis Giordano
14
15
  autorequire:
15
16
  bindir: bin
16
17
  cert_chain: []
17
18
 
18
- date: 2011-08-11 00:00:00 +02:00
19
+ date: 2011-08-16 00:00:00 +02:00
19
20
  default_executable:
20
21
  dependencies:
21
22
  - !ruby/object:Gem::Dependency