stemcell 0.12.0 → 0.13.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.
@@ -54,16 +54,33 @@ encrypted_data_bag_secret_path='<%= opts['chef_data_bag_secret_path'].to_s %>'
54
54
  ##
55
55
 
56
56
  update() {
57
- echo updating apt repo
58
- apt-get update 1>&2
57
+ echo updating repository metadata
58
+
59
+ case $OS_PACKAGE_MANAGER in
60
+ apt)
61
+ apt-get update 1>&2
62
+ ;;
63
+ yum)
64
+ # yum install will check if metadata needs to be updated
65
+ # so it's just for consistency with apt-based distros
66
+ yum check-update 1>&2 || true
67
+ ;;
68
+ esac
59
69
  }
60
70
 
61
71
  install() {
62
- if ! (dpkg -l | awk '{print $2}' | grep -q ^$1$ ) ; then
63
- echo installing $1...
64
- export DEBIAN_FRONTEND=noninteractive
65
- apt-get install -y $1 1>&2
66
- fi
72
+ case $OS_PACKAGE_MANAGER in
73
+ apt)
74
+ if ! (dpkg -l | awk '{print $2}' | grep -q ^$1$ ) ; then
75
+ echo installing $1...
76
+ export DEBIAN_FRONTEND=noninteractive
77
+ apt-get install -y $1 1>&2
78
+ fi
79
+ ;;
80
+ yum)
81
+ rpm -q $1 2>&1 >/dev/null || yum install -y $1
82
+ ;;
83
+ esac
67
84
  }
68
85
 
69
86
  set_hostname() {
@@ -86,22 +103,34 @@ set_hostname() {
86
103
  echo "$local_ip $fqdn $hostname" >> /etc/hosts
87
104
  }
88
105
 
106
+ download_chef_package() {
107
+ echo "Downloading chef from $1"
108
+ wget "$1" -O "$2" --progress=dot:mega --tries=15 --retry-connrefused --timeout=5 --wait=10 --random-wait --continue --no-dns-cache
109
+ }
110
+
89
111
  install_chef() {
112
+ package_local="/tmp/chef_${chef_version}.${OS_PACKAGE_EXT}"
113
+ package_url="$(render_chef_url)"
114
+
90
115
  # check to see if chef is already installed
91
116
  if ! which chef-solo > /dev/null ; then
92
- platform=`lsb_release -s -i | tr '[:upper:]' '[:lower:]'`
93
- platform_version=`lsb_release -s -r`
94
- arch=`uname -m`
95
- package_local="/tmp/chef_${chef_version}.deb"
96
- package_url="<%= opts['chef_package_source'] %>"
97
- echo "Downloading chef from $package_url"
98
- wget $package_url -O $package_local --progress=dot:mega
99
- echo "Installing chef $chef_version"
100
- dpkg -i $package_local
101
- rm $package_local
117
+ case $OS_PACKAGE_MANAGER in
118
+ apt)
119
+ download_chef_package "$package_url" "$package_local"
120
+ echo "Installing chef $chef_version"
121
+ dpkg -i $package_local
122
+ ;;
123
+ yum)
124
+ download_chef_package "$package_url" "$package_local"
125
+ echo "Installing chef $chef_version"
126
+ rpm -ivh "$package_local"
127
+ ;;
128
+ esac
102
129
  else
103
130
  echo chef is already installed
104
131
  fi
132
+
133
+ rm -f $package_local
105
134
  }
106
135
 
107
136
  create_ohai_hint() {
@@ -154,7 +183,6 @@ cookbook_path ["#{repo_dir}/site-cookbooks", "#{repo_dir}/cookbooks"]
154
183
  role_path "#{repo_dir}/roles"
155
184
  data_bag_path "#{repo_dir}/data_bags"
156
185
  ohai.plugin_path << "#{repo_dir}/ohai_plugins"
157
-
158
186
  ssl_verify_mode :verify_peer
159
187
  log_level :info
160
188
  log_location STDOUT
@@ -199,7 +227,7 @@ git reset --hard origin/\${branch}
199
227
  git clean -fdx
200
228
 
201
229
  json="{\"role\": \"\${role}\", \"env\": \"\${env}\", \"branch\": \"\${branch}\"}"
202
- json_file=\`tempfile\`
230
+ json_file=\`mktemp\`
203
231
  echo \$json > \$json_file
204
232
  trap "{ rm -f '\$json_file' ; }" EXIT
205
233
 
@@ -237,12 +265,55 @@ run_chef() {
237
265
  set -e
238
266
  }
239
267
 
268
+ render_chef_url() {
269
+ base_url="<%= opts['chef_package_source'] %>"
270
+ echo $base_url | sed \
271
+ -e "s/%{platform}/$OS_PLATFORM/g" \
272
+ -e "s/%{platform_version}/$OS_PLATFORM_VERSION/g" \
273
+ -e "s/%{chef_version}/$chef_version/g" \
274
+ -e "s/%{package_manager}/$OS_PACKAGE_MANAGER/g" \
275
+ -e "s/%{package_arch}/$OS_PACKAGE_ARCH/g" \
276
+ -e "s/%{package_ext}/$OS_PACKAGE_EXT/g"
277
+ }
278
+
279
+ check_os_version() {
280
+ NAME=unsupported
281
+
282
+ if [ -f /etc/os-release ]; then
283
+ . /etc/os-release
284
+ fi
285
+
286
+ case "$NAME" in
287
+ "Ubuntu")
288
+ OS_VERSION="$NAME $VERSION_ID"
289
+ OS_PLATFORM=ubuntu
290
+ OS_PLATFORM_VERSION=$VERSION_ID
291
+ OS_PACKAGE_MANAGER=apt
292
+ OS_PACKAGE_ARCH=$(dpkg --print-architecture)
293
+ OS_PACKAGE_EXT=deb
294
+ ;;
295
+ "Amazon Linux")
296
+ OS_VERSION="$NAME $VERSION_ID"
297
+ OS_PLATFORM=amazon
298
+ OS_PLATFORM_VERSION=$VERSION_ID
299
+ OS_PACKAGE_MANAGER=yum
300
+ OS_PACKAGE_ARCH=$(uname -m)
301
+ OS_PACKAGE_EXT=rpm
302
+ ;;
303
+ *)
304
+ echo "Unsupported operating system"
305
+ exit 1
306
+ ;;
307
+ esac
308
+ }
240
309
 
241
310
  ##
242
311
  ## main
243
312
  ##
244
313
 
245
- echo "starting chef bootstrapping..."
314
+ check_os_version
315
+
316
+ echo "starting chef bootstrapping on ${OS_VERSION}..."
246
317
  update
247
318
  install curl
248
319
  install git
@@ -1,3 +1,3 @@
1
1
  module Stemcell
2
- VERSION = "0.12.0"
2
+ VERSION = "0.13.1"
3
3
  end
@@ -4,7 +4,9 @@
4
4
  },
5
5
  "backing_store": {
6
6
  "instance_store": {
7
- "image_id": "ami-d9d6a6b0"
7
+ "us-east-1": {
8
+ "image_id": "ami-d9d6a6b0"
9
+ }
8
10
  }
9
11
  }
10
12
  }
@@ -0,0 +1,13 @@
1
+ {
2
+ "defaults": {
3
+ "instance_type": "m1.small"
4
+ },
5
+ "backing_store": {
6
+ "instance_store": {
7
+ "image_id": "ami-d9d6a6b0"
8
+ }
9
+ },
10
+ "availability_zones": {
11
+ "us-east-1": ["us-east-1a"]
12
+ }
13
+ }
@@ -5,7 +5,9 @@
5
5
  },
6
6
  "backing_store": {
7
7
  "instance_store": {
8
- "image_id": "ami-d9d6a6b0"
8
+ "us-east-1": {
9
+ "image_id": "ami-d9d6a6b0"
10
+ }
9
11
  }
10
12
  },
11
13
  "availability_zones": {
@@ -1,7 +1,9 @@
1
1
  {
2
2
  "backing_store": {
3
3
  "instance_store": {
4
- "baz": "woo"
4
+ "us-east-1": {
5
+ "baz": "woo"
6
+ }
5
7
  }
6
8
  },
7
9
  "availability_zones": {
@@ -16,10 +16,14 @@
16
16
 
17
17
  "backing_store": {
18
18
  "hvm1": {
19
- "image_id": "ami-1"
19
+ "us-east-1": {
20
+ "image_id": "ami-1"
21
+ }
20
22
  },
21
23
  "pv1": {
22
- "image_id": "ami-2"
24
+ "us-east-1": {
25
+ "image_id": "ami-2"
26
+ }
23
27
  }
24
28
  },
25
29
 
@@ -4,7 +4,9 @@
4
4
  },
5
5
  "backing_store": {
6
6
  "instance_store": {
7
- "image_id": "ami-d9d6a6b0"
7
+ "us-east-1": {
8
+ "image_id": "ami-d9d6a6b0"
9
+ }
8
10
  }
9
11
  },
10
12
  "availability_zones": {
@@ -1,45 +1,56 @@
1
1
  require 'spec_helper'
2
+ require 'base64'
2
3
 
3
- class MockInstance
4
- def initialize(id)
5
- @id = id
6
- end
7
-
8
- def id
9
- @id
10
- end
11
-
12
- def status
13
- :running
14
- end
15
- end
16
-
17
- class MockSecurityGroup
18
- attr_reader :group_id, :name, :vpc_id
19
- def initialize(id, name, vpc_id)
20
- @group_id = id
21
- @name = name
22
- @vpc_id = vpc_id
4
+ describe Stemcell::Launcher do
5
+ before do
6
+ Aws.config[:stub_responses] = true
23
7
  end
24
- end
25
-
26
- class MockException < StandardError
27
- end
28
8
 
29
- describe Stemcell::Launcher do
30
9
  let(:launcher) {
31
10
  opts = {'region' => 'region'}
32
11
  launcher = Stemcell::Launcher.new(opts)
33
12
  launcher
34
13
  }
35
14
  let(:operation) { 'op' }
36
- let(:instances) { (1..4).map { |id| MockInstance.new(id) } }
15
+ let(:instances) do
16
+ ('1'..'4').map do |id|
17
+ Aws::EC2::Types::Instance.new(
18
+ instance_id: id,
19
+ private_ip_address: "10.10.10.#{id}",
20
+ state: Aws::EC2::Types::InstanceState.new(name: 'pending')
21
+ )
22
+ end
23
+ end
37
24
  let(:instance_ids) { instances.map(&:id) }
38
25
 
39
26
  describe '#launch' do
40
- let(:ec2) { instance_double(AWS::EC2) }
41
- let(:client) { double(AWS::EC2::Client) }
42
- let(:response) { instance_double(AWS::Core::Response) }
27
+ let(:ec2) do
28
+ ec2 = Aws::EC2::Client.new
29
+ ec2.stub_responses(
30
+ :describe_security_groups,
31
+ security_groups: [
32
+ {group_id: 'sg-1', group_name: 'sg_name1', vpc_id:'vpc-1'},
33
+ {group_id: 'sg-2', group_name: 'sg_name2', vpc_id:'vpc-1'},
34
+ ],
35
+ )
36
+ ec2.stub_responses(
37
+ :describe_instances,
38
+ reservations: [{
39
+ instances: ('1'..'4').map do |id|
40
+ {
41
+ instance_id: id,
42
+ private_ip_address: "10.10.10.#{id}",
43
+ public_ip_address: "24.10.10.#{id}",
44
+ state: {
45
+ name: 'running'
46
+ }
47
+ }
48
+ end
49
+ }]
50
+ )
51
+ ec2
52
+ end
53
+ let(:response) { instance_double(Seahorse::Client::Response) }
43
54
  let(:launcher) {
44
55
  opts = {'region' => 'region', 'vpc_id' => 'vpc-1'}
45
56
  launcher = Stemcell::Launcher.new(opts)
@@ -59,7 +70,9 @@ describe Stemcell::Launcher do
59
70
  'availability_zone' => 'us-east-1a',
60
71
  'count' => 2,
61
72
  'security_groups' => ['sg_name1', 'sg_name2'],
62
- 'wait' => false
73
+ 'user' => 'some_user',
74
+ 'wait' => true,
75
+ 'cpu_options' => 'core_count=1,threads_per_core=1'
63
76
  }
64
77
  }
65
78
 
@@ -67,132 +80,106 @@ describe Stemcell::Launcher do
67
80
  allow(launcher).to receive(:try_file).and_return('secret')
68
81
  allow(launcher).to receive(:render_template).and_return('template')
69
82
  allow(launcher).to receive(:ec2).and_return(ec2)
70
- allow(ec2).to receive(:client).and_return(client)
71
83
  allow(response).to receive(:error).and_return(nil)
72
84
  end
73
85
 
74
86
  it 'launches all of the instances' do
75
87
  expect(launcher).to receive(:get_vpc_security_group_ids).
76
88
  with('vpc-1', ['sg_name1', 'sg_name2']).and_call_original
77
- expect_any_instance_of(AWS::EC2::VPC).to receive(:security_groups).
78
- and_return([1,2].map { |i| MockSecurityGroup.new("sg-#{i}", "sg_name#{i}", 'vpc-1')})
89
+ expect(ec2).to receive(:describe_security_groups).and_call_original
79
90
  expect(launcher).to receive(:do_launch).with(a_hash_including(
80
91
  :image_id => 'ami-d9d6a6b0',
81
92
  :instance_type => 'c1.xlarge',
82
93
  :key_name => 'key',
83
- :count => 2,
84
- :security_group_ids => ['sg-1', 'sg-2'],
85
- :availability_zone => 'us-east-1a',
86
- :user_data => 'template'
94
+ :min_count => 2,
95
+ :max_count => 2,
96
+ :placement => { :availability_zone => 'us-east-1a' },
97
+ :network_interfaces => [{
98
+ :device_index => 0,
99
+ :groups => ['sg-1', 'sg-2' ]
100
+ }],
101
+ :tag_specifications => [
102
+ {
103
+ :resource_type => 'instance',
104
+ :tags => [
105
+ { :key => "Name", :value => "role-environment" },
106
+ { :key => "Group", :value => "role-environment" },
107
+ { :key => "created_by", :value => "some_user" },
108
+ { :key => "stemcell", :value => Stemcell::VERSION },
109
+ ]},
110
+ ],
111
+ :user_data => Base64.encode64('template'),
112
+ :cpu_options => 'core_count=1,threads_per_core=1'
87
113
  )).and_return(instances)
88
- expect(launcher).to receive(:set_tags).with(kind_of(Array), kind_of(Hash)).and_return(nil)
89
- # set_classic_link should not be set on vpc hosts.
90
- expect(launcher).not_to receive(:set_classic_link)
91
-
92
- launcher.send(:launch, launch_options)
93
- end
94
-
95
- it 'calls set_classic_link for non vpc instances' do
96
- launcher = Stemcell::Launcher.new({'region' => 'region', 'vpc_id' => false})
97
- expect(launcher).to receive(:set_classic_link)
98
- expect(launcher).to receive(:set_tags).with(kind_of(Array), kind_of(Hash)).and_return(nil)
99
- expect(launcher).to receive(:do_launch).and_return(instances)
100
- launcher.send(:launch, launch_options)
114
+ launched_instances = launcher.send(:launch, launch_options)
115
+ expect(launched_instances.map(&:public_ip_address)).to all(be_truthy)
101
116
  end
102
117
  end
103
118
 
104
- describe '#set_classic_link' do
105
- let(:ec2) { instance_double(AWS::EC2) }
106
- let(:client) { double(AWS::EC2::Client) }
107
- let(:response) { instance_double(AWS::Core::Response) }
108
- before do
109
- allow(launcher).to receive(:ec2).and_return(ec2)
110
- allow(ec2).to receive(:client).and_return(client)
111
- allow(response).to receive(:error).and_return(nil)
119
+ describe '#kill' do
120
+ let(:ec2) do
121
+ ec2 = Aws::EC2::Client.new
122
+ ec2.stub_responses(
123
+ :terminate_instances, -> (context) {
124
+ instance_ids = context.params[:instance_ids]
125
+ if instance_ids.include? 'i-3'
126
+ Aws::EC2::Errors::InvalidInstanceIDNotFound.new(nil, "The instance ID 'i-3' do not exist")
127
+ else
128
+ {} # success
129
+ end
130
+ })
131
+ ec2
112
132
  end
113
133
 
114
- let(:classic_link) {
115
- {
116
- 'vpc_id' => 'vpc-1',
117
- 'security_group_ids' => ['sg-1', 'sg-2'],
118
- 'security_groups' => ['sg_name']
119
- }
120
- }
121
-
122
- it 'invokes classic link on all of the instances' do
123
- expect(launcher).to receive(:get_vpc_security_group_ids).with('vpc-1', ['sg_name']).
124
- and_call_original
125
- expect_any_instance_of(AWS::EC2::VPC).to receive(:security_groups).
126
- and_return([MockSecurityGroup.new('sg-3', 'sg_name', 'vpc-1')])
127
- instances.each do |instance|
128
- expect(client).to receive(:attach_classic_link_vpc).ordered.with(a_hash_including(
129
- :instance_id => instance.id,
130
- :vpc_id => classic_link['vpc_id'],
131
- :groups => ['sg-1', 'sg-2', 'sg-3'],
132
- )).and_return(response)
133
- end
134
+ let(:instance_ids) { ('i-1'..'i-4').to_a }
134
135
 
135
- launcher.send(:set_classic_link, instances, classic_link)
136
- end
137
- end
138
-
139
- describe '#run_batch_operation' do
140
- it "raises no exception when no internal error occur" do
141
- errors = launcher.send(:run_batch_operation, instances) {}
142
- expect(errors.all?(&:nil?)).to be true
136
+ before do
137
+ allow(launcher).to receive(:ec2).and_return(ec2)
143
138
  end
144
139
 
145
- it "runs full batch even when there are two error" do
146
- errors = launcher.send(:run_batch_operation,
147
- instances) do |instance, error|
148
- raise "error-#{instance.id}" if instance.id % 2 == 0
140
+ context 'when ignore_not_found is true' do
141
+ it 'terminates valid instances even if an invalid instance id is provided' do
142
+ launcher.kill(instance_ids, ignore_not_found: true)
149
143
  end
150
- expect(errors.count(&:nil?)).to be_eql(2)
151
- expect(errors.reject(&:nil?).map { |e| e.message }).to \
152
- be_eql([2, 4].map { |id| "error-#{id}" })
153
- end
154
144
 
155
- it "retries after an intermittent error" do
156
- count = 0
157
- errors = launcher.send(:run_batch_operation,
158
- instances) do |instance|
159
- if instance.id == 3
160
- count += 1
161
- count < 3 ?
162
- AWS::EC2::Errors::InvalidInstanceID::NotFound.new("error-#{instance.id}"):
163
- nil
164
- end
145
+ it 'finishes without error even if no instance ids are valid' do
146
+ launcher.kill(['i-3'], ignore_not_found: true)
165
147
  end
166
- expect(errors.all?(&:nil?)).to be true
167
148
  end
168
149
 
169
- it "retries up to max_attempts option per instance" do
170
- max_attempts = 6
171
- opts = {'region' => 'region', 'max_attempts' => max_attempts}
172
- launcher = Stemcell::Launcher.new(opts)
173
- allow(launcher).to receive(:sleep).and_return(0)
174
- tags = double("Tags")
175
- instances = (1..2).map do |id|
176
- inst = MockInstance.new(id)
177
- allow(inst).to receive(:tags).and_return(tags)
178
- inst
150
+ context 'when ignore_not_found is false' do
151
+ it 'raises an error' do
152
+ expect { launcher.kill(instance_ids) }.to raise_error(Aws::EC2::Errors::InvalidInstanceIDNotFound)
179
153
  end
180
- expect(tags).to receive(:set).with({'a' => 'b'}).exactly(12).times.
181
- and_raise(AWS::EC2::Errors::InvalidInstanceID::NotFound.new("error"))
182
- expect do
183
- launcher.send(:set_tags, instances, {'a' => 'b'})
184
- end.to raise_error(Stemcell::IncompleteOperation)
185
154
  end
186
155
  end
187
156
 
188
157
  describe '#configure_aws_creds_and_region' do
189
- it 'AWS region is configured after launcher is instanciated' do
190
- expect(AWS.config.region).to be_eql('region')
158
+ it 'AWS region is configured after launcher is instantiated' do
159
+ expect(Aws.config[:region]).to be_eql('region')
191
160
  end
192
161
 
193
162
  it 'AWS region configuration changed' do
194
163
  mock_launcher = Stemcell::Launcher.new('region' => 'ap-northeast-1')
195
- expect(AWS.config.region).to be_eql('ap-northeast-1')
164
+ expect(Aws.config[:region]).to be_eql('ap-northeast-1')
165
+ end
166
+ end
167
+
168
+ describe '#ec2' do
169
+
170
+ it 'can return a client with regional endpoint' do
171
+ launcher = Stemcell::Launcher.new({'region' => 'us-east-1', 'ec2_endpoint' => nil})
172
+ client = launcher.send(:ec2)
173
+ expect(client.config[:endpoint].to_s).to be_eql('https://ec2.us-east-1.amazonaws.com')
174
+ end
175
+
176
+ it 'can return a client with custom endpoint' do
177
+ launcher = Stemcell::Launcher.new({
178
+ 'region' => 'region1',
179
+ 'ec2_endpoint' => 'https://endpoint1',
180
+ })
181
+ client = launcher.send(:ec2)
182
+ expect(client.config[:endpoint].to_s).to be_eql('https://endpoint1')
196
183
  end
197
184
  end
198
185
  end
@@ -32,7 +32,9 @@ describe Stemcell::MetadataSource::Configuration do
32
32
  it "sets backing_store_options" do
33
33
  expect(config.backing_store_options).to eql({
34
34
  'instance_store' => {
35
- 'image_id' => 'ami-d9d6a6b0'
35
+ 'us-east-1' => {
36
+ 'image_id' => 'ami-d9d6a6b0'
37
+ }
36
38
  }
37
39
  })
38
40
  end
@@ -86,10 +88,11 @@ describe Stemcell::MetadataSource::Configuration do
86
88
 
87
89
  describe '#options_for_backing_store' do
88
90
  let(:backing_store) { 'instance_store' }
91
+ let(:region) { 'us-east-1' }
89
92
 
90
93
  context "when the backing store definition exists" do
91
94
  it "returns the options" do
92
- expect(config.options_for_backing_store(backing_store)).to eql({
95
+ expect(config.options_for_backing_store(backing_store, region)).to eql({
93
96
  'image_id' => 'ami-d9d6a6b0'
94
97
  })
95
98
  end
@@ -97,13 +100,23 @@ describe Stemcell::MetadataSource::Configuration do
97
100
 
98
101
  context "when the backing store isn't defined" do
99
102
  let(:backing_store) { 'nyanstore' }
103
+ let(:region) { 'us-east-1' }
100
104
  it "raises" do
101
- expect { config.options_for_backing_store(backing_store) }.to raise_error(
105
+ expect { config.options_for_backing_store(backing_store, region) }.to raise_error(
102
106
  Stemcell::UnknownBackingStoreError
103
107
  )
104
108
  end
105
109
  end
106
110
 
111
+ context "when the legacy backing store definition exists" do
112
+ let(:config_filename) { 'stemcell-backing-store-legacy.json' }
113
+ it "returns the options" do
114
+ expect(config.options_for_backing_store(backing_store, region)).to eql({
115
+ 'image_id' => 'ami-d9d6a6b0'
116
+ })
117
+ end
118
+ end
119
+
107
120
  end
108
121
 
109
122
  describe '#random_az_in_region' do
@@ -161,7 +161,8 @@ describe Stemcell::MetadataSource do
161
161
  it "calls the config object to retrieve the backing store options" do
162
162
  backing_options.merge!('image_id' => 'ami-nyancat')
163
163
  override_options.merge!('backing_store' => 'ebs')
164
- expect(config).to receive(:options_for_backing_store).with('ebs') { backing_options }
164
+ override_options.merge!('region' => 'us-east-1')
165
+ expect(config).to receive(:options_for_backing_store).with('ebs', 'us-east-1') { backing_options }
165
166
  expect(expansion['image_id']).to eql 'ami-nyancat'
166
167
  end
167
168
 
data/stemcell.gemspec CHANGED
@@ -17,8 +17,11 @@ Gem::Specification.new do |s|
17
17
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
18
18
  s.require_paths = ["lib"]
19
19
 
20
- s.add_runtime_dependency 'aws-sdk-v1', '~> 1.63'
21
- s.add_runtime_dependency 'net-ssh', '~> 2.9'
20
+ # pins several aws sdk transitive dependencies to maintain compatibility with Ruby < 2.3
21
+ s.add_runtime_dependency 'aws-eventstream', '~> 1.1.1'
22
+ s.add_runtime_dependency 'aws-sdk-ec2', '~> 1'
23
+ s.add_runtime_dependency 'aws-sigv4', '~> 1.2.4'
24
+ s.add_runtime_dependency 'net-ssh', '~> 2.9'
22
25
  if RUBY_VERSION >= '2.0'
23
26
  s.add_runtime_dependency 'chef', '>= 11.4.0'
24
27
  else