bosh-stemcell 1.5.0.pre.1113

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.rspec +3 -0
  2. data/README.md +104 -0
  3. data/Vagrantfile +37 -0
  4. data/bosh-stemcell.gemspec +28 -0
  5. data/lib/bosh/stemcell.rb +4 -0
  6. data/lib/bosh/stemcell/archive.rb +59 -0
  7. data/lib/bosh/stemcell/archive_filename.rb +29 -0
  8. data/lib/bosh/stemcell/aws/ami.rb +58 -0
  9. data/lib/bosh/stemcell/aws/light_stemcell.rb +44 -0
  10. data/lib/bosh/stemcell/aws/region.rb +15 -0
  11. data/lib/bosh/stemcell/builder_command.rb +174 -0
  12. data/lib/bosh/stemcell/builder_options.rb +85 -0
  13. data/lib/bosh/stemcell/disk_image.rb +61 -0
  14. data/lib/bosh/stemcell/infrastructure.rb +49 -0
  15. data/lib/bosh/stemcell/operating_system.rb +31 -0
  16. data/lib/bosh/stemcell/stage_collection.rb +154 -0
  17. data/lib/bosh/stemcell/stage_runner.rb +53 -0
  18. data/lib/bosh/stemcell/version.rb +5 -0
  19. data/lib/monkeypatch/serverspec/backend/exec.rb +86 -0
  20. data/spec/assets/fake-stemcell-aws.tgz +0 -0
  21. data/spec/assets/fake-stemcell-vsphere.tgz +0 -0
  22. data/spec/assets/light-fake-stemcell-aws.tgz +0 -0
  23. data/spec/bosh/monkeypatch/serverspec/backend/exec_spec.rb +46 -0
  24. data/spec/bosh/stemcell/archive_filename_spec.rb +42 -0
  25. data/spec/bosh/stemcell/archive_spec.rb +98 -0
  26. data/spec/bosh/stemcell/aws/ami_spec.rb +30 -0
  27. data/spec/bosh/stemcell/aws/light_stemcell_spec.rb +94 -0
  28. data/spec/bosh/stemcell/aws/region_spec.rb +12 -0
  29. data/spec/bosh/stemcell/builder_command_spec.rb +241 -0
  30. data/spec/bosh/stemcell/builder_options_spec.rb +201 -0
  31. data/spec/bosh/stemcell/disk_image_spec.rb +163 -0
  32. data/spec/bosh/stemcell/infrastructure_spec.rb +66 -0
  33. data/spec/bosh/stemcell/operating_system_spec.rb +47 -0
  34. data/spec/bosh/stemcell/stage_collection_spec.rb +213 -0
  35. data/spec/bosh/stemcell/stage_runner_spec.rb +141 -0
  36. data/spec/bosh/stemcell/version_spec.rb +12 -0
  37. data/spec/bosh/stemcell_spec.rb +6 -0
  38. data/spec/spec_helper.rb +6 -0
  39. data/spec/stemcells/aws_spec.rb +9 -0
  40. data/spec/stemcells/centos_spec.rb +131 -0
  41. data/spec/stemcells/openstack_spec.rb +9 -0
  42. data/spec/stemcells/ubuntu_spec.rb +143 -0
  43. data/spec/stemcells/vsphere_spec.rb +9 -0
  44. data/spec/support/rspec_fire.rb +9 -0
  45. data/spec/support/serverspec.rb +4 -0
  46. data/spec/support/spec_assets.rb +11 -0
  47. data/spec/support/stemcell_image.rb +26 -0
  48. data/spec/support/stemcell_shared_examples.rb +43 -0
  49. data/spec/support/stub_env.rb +5 -0
  50. metadata +236 -0
@@ -0,0 +1,201 @@
1
+ require 'spec_helper'
2
+ require 'bosh/stemcell/builder_options'
3
+ require 'bosh/stemcell/infrastructure'
4
+ require 'bosh/stemcell/operating_system'
5
+
6
+ module Bosh::Stemcell
7
+ describe BuilderOptions do
8
+ subject(:stemcell_builder_options) { described_class.new(env, options) }
9
+ let(:env) { {} }
10
+ let(:options) do
11
+ {
12
+ tarball: 'fake/release.tgz',
13
+ stemcell_version: '007',
14
+ infrastructure: infrastructure,
15
+ operating_system: operating_system
16
+ }
17
+ end
18
+
19
+ let(:infrastructure) { Infrastructure.for('aws') }
20
+ let(:operating_system) { OperatingSystem.for('ubuntu') }
21
+ let(:expected_source_root) { File.expand_path('../../../../..', __FILE__) }
22
+ let(:archive_filename) { instance_double('Bosh::Stemcell::ArchiveFilename', to_s: 'FAKE_STEMCELL.tgz') }
23
+
24
+ before do
25
+ ArchiveFilename.stub(:new).
26
+ with('007', infrastructure, operating_system, 'bosh-stemcell', false).and_return(archive_filename)
27
+ end
28
+
29
+ describe '#initialize' do
30
+ context 'when :tarball is not set' do
31
+ before { options.delete(:tarball) }
32
+
33
+ it 'dies' do
34
+ expect { stemcell_builder_options }.to raise_error('key not found: :tarball')
35
+ end
36
+ end
37
+
38
+ context 'when :stemcell_version is not set' do
39
+ before { options.delete(:stemcell_version) }
40
+
41
+ it 'dies' do
42
+ expect { stemcell_builder_options }.to raise_error('key not found: :stemcell_version')
43
+ end
44
+ end
45
+
46
+ context 'when :infrastructure is not set' do
47
+ before { options.delete(:infrastructure) }
48
+
49
+ it 'dies' do
50
+ expect { stemcell_builder_options }.to raise_error('key not found: :infrastructure')
51
+ end
52
+ end
53
+
54
+ context 'when :operating_system is not set' do
55
+ before { options.delete(:operating_system) }
56
+
57
+ it 'dies' do
58
+ expect { stemcell_builder_options }.to raise_error('key not found: :operating_system')
59
+ end
60
+ end
61
+ end
62
+
63
+ describe '#default' do
64
+ let(:default_disk_size) { 2048 }
65
+ let(:rake_args) { {} }
66
+
67
+ it 'sets stemcell_tgz' do
68
+ result = stemcell_builder_options.default
69
+ expect(result['stemcell_tgz']).to eq(archive_filename.to_s)
70
+ end
71
+
72
+ it 'sets stemcell_image_name' do
73
+ result = stemcell_builder_options.default
74
+ expected_image_name = "#{infrastructure.name}-#{infrastructure.hypervisor}-#{operating_system.name}.raw"
75
+ expect(result['stemcell_image_name']).to eq(expected_image_name)
76
+ end
77
+
78
+ it 'sets stemcell_version' do
79
+ result = stemcell_builder_options.default
80
+ expect(result['stemcell_version']).to eq('007')
81
+ end
82
+
83
+ # rubocop:disable MethodLength
84
+ def self.it_sets_correct_environment_variables
85
+ describe 'setting enviroment variables' do
86
+ let(:env) do
87
+ {
88
+ 'UBUNTU_ISO' => 'fake_ubuntu_iso',
89
+ 'UBUNTU_MIRROR' => 'fake_ubuntu_mirror',
90
+ 'RUBY_BIN' => 'fake_ruby_bin',
91
+ }
92
+ end
93
+
94
+ it 'sets default values for options based in hash' do
95
+ expected_release_micro_manifest_path =
96
+ File.join(expected_source_root, "release/micro/#{infrastructure.name}.yml")
97
+
98
+ result = stemcell_builder_options.default
99
+
100
+ expect(result['stemcell_name']).to eq(
101
+ "bosh-#{infrastructure.name}-#{infrastructure.hypervisor}-#{operating_system.name}")
102
+ expect(result['stemcell_operating_system']).to eq(operating_system.name)
103
+ expect(result['stemcell_infrastructure']).to eq(infrastructure.name)
104
+ expect(result['stemcell_hypervisor']).to eq(infrastructure.hypervisor)
105
+ expect(result['bosh_protocol_version']).to eq('1')
106
+ expect(result['UBUNTU_ISO']).to eq('fake_ubuntu_iso')
107
+ expect(result['UBUNTU_MIRROR']).to eq('fake_ubuntu_mirror')
108
+ expect(result['ruby_bin']).to eq('fake_ruby_bin')
109
+ expect(result['bosh_release_src_dir']).to eq(File.join(expected_source_root, '/release/src/bosh'))
110
+ expect(result['bosh_agent_src_dir']).to eq(File.join(expected_source_root, 'bosh_agent'))
111
+ expect(result['image_create_disk_size']).to eq(default_disk_size)
112
+ expect(result['bosh_micro_enabled']).to eq('yes')
113
+ expect(result['bosh_micro_package_compiler_path']).to eq(
114
+ File.join(expected_source_root, 'bosh-release'))
115
+ expect(result['bosh_micro_manifest_yml_path']).to eq(expected_release_micro_manifest_path)
116
+ expect(result['bosh_micro_release_tgz_path']).to eq('fake/release.tgz')
117
+ end
118
+
119
+ context 'when RUBY_BIN is not set' do
120
+ before { env.delete('RUBY_BIN') }
121
+
122
+ before do
123
+ RbConfig::CONFIG.stub(:[]).with('bindir').and_return('/a/path/to/')
124
+ RbConfig::CONFIG.stub(:[]).with('ruby_install_name').and_return('ruby')
125
+ end
126
+
127
+ it 'uses the RbConfig values' do
128
+ result = stemcell_builder_options.default
129
+
130
+ expect(result['ruby_bin']).to eq('/a/path/to/ruby')
131
+ end
132
+ end
133
+
134
+ context 'when disk_size is not passed' do
135
+ it 'defaults to default disk size for infrastructure' do
136
+ result = stemcell_builder_options.default
137
+
138
+ expect(result['image_create_disk_size']).to eq(default_disk_size)
139
+ end
140
+ end
141
+
142
+ context 'when disk_size is passed' do
143
+ before { options.merge!(disk_size: 1234) }
144
+
145
+ it 'allows user to override default disk_size' do
146
+ result = stemcell_builder_options.default
147
+
148
+ expect(result['image_create_disk_size']).to eq(1234)
149
+ end
150
+ end
151
+ end
152
+ end
153
+ # rubocop:enable MethodLength
154
+
155
+ describe 'infrastructure variation' do
156
+ context 'when infrastruture is aws' do
157
+ let(:infrastructure) { Infrastructure.for('aws') }
158
+
159
+ it_sets_correct_environment_variables
160
+
161
+ it 'has no "image_vsphere_ovf_ovftool_path" key' do
162
+ expect(stemcell_builder_options.default).not_to have_key('image_vsphere_ovf_ovftool_path')
163
+ end
164
+ end
165
+
166
+ context 'when infrastruture is vsphere' do
167
+ let(:infrastructure) { Infrastructure.for('vsphere') }
168
+
169
+ it_sets_correct_environment_variables
170
+
171
+ it 'has an "image_vsphere_ovf_ovftool_path" key' do
172
+ result = stemcell_builder_options.default
173
+
174
+ expect(result['image_vsphere_ovf_ovftool_path']).to be_nil
175
+ end
176
+
177
+ context 'if you have OVFTOOL set in the environment' do
178
+ let(:env) { { 'OVFTOOL' => 'fake_ovf_tool_path' } }
179
+
180
+ it 'sets image_vsphere_ovf_ovftool_path' do
181
+ result = stemcell_builder_options.default
182
+
183
+ expect(result['image_vsphere_ovf_ovftool_path']).to eq('fake_ovf_tool_path')
184
+ end
185
+ end
186
+ end
187
+
188
+ context 'when infrastructure is openstack' do
189
+ let(:infrastructure) { Infrastructure.for('openstack') }
190
+ let(:default_disk_size) { 10240 }
191
+
192
+ it_sets_correct_environment_variables
193
+
194
+ it 'has no "image_vsphere_ovf_ovftool_path" key' do
195
+ expect(stemcell_builder_options.default).not_to have_key('image_vsphere_ovf_ovftool_path')
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,163 @@
1
+ require 'spec_helper'
2
+ require 'bosh/stemcell/disk_image'
3
+
4
+ module Bosh::Stemcell
5
+ describe DiskImage do
6
+ let(:shell) { instance_double('Bosh::Core::Shell', run: nil) }
7
+
8
+ let(:kpartx_map_output) { 'add map FAKE_LOOP1p1 (252:3): 0 3997984 linear /dev/loop1 63' }
9
+ let(:options) do
10
+ {
11
+ image_file_path: '/path/to/FAKE_IMAGE',
12
+ image_mount_point: '/fake/mnt'
13
+ }
14
+ end
15
+
16
+ subject(:disk_image) { DiskImage.new(options) }
17
+
18
+ before do
19
+ Bosh::Core::Shell.stub(:new).and_return(shell)
20
+ end
21
+
22
+ describe '#initialize' do
23
+ it 'requires an image_file_path' do
24
+ options.delete(:image_file_path)
25
+ expect { DiskImage.new(options) }.to raise_error /key not found: :image_file_path/
26
+ end
27
+
28
+ it 'requires an mount_point' do
29
+ options.delete(:image_mount_point)
30
+
31
+ dir_mock = class_double('Dir').as_stubbed_const
32
+ dir_mock.should_receive(:mktmpdir).and_return('/fake/tmpdir')
33
+
34
+ expect(DiskImage.new(options).image_mount_point).to eq('/fake/tmpdir')
35
+ end
36
+ end
37
+
38
+ describe '#mount' do
39
+ it 'maps the file to a loop device' do
40
+ losetup_commad = 'sudo losetup --show --find /path/to/FAKE_IMAGE'
41
+ shell.stub(:run).with(losetup_commad, output_command: false).and_return('/dev/loop0')
42
+ shell.should_receive(:run).with('sudo kpartx -av /dev/loop0',
43
+ output_command: false).and_return(kpartx_map_output)
44
+
45
+ disk_image.mount
46
+ end
47
+
48
+ it 'mounts the loop device' do
49
+ losetup_commad = 'sudo losetup --show --find /path/to/FAKE_IMAGE'
50
+ shell.stub(:run).with(losetup_commad, output_command: false).and_return('/dev/loop0')
51
+ shell.stub(:run).with('sudo kpartx -av /dev/loop0', output_command: false).and_return(kpartx_map_output)
52
+
53
+ shell.should_receive(:run).with('sudo mount /dev/mapper/FAKE_LOOP1p1 /fake/mnt', output_command: false)
54
+
55
+ disk_image.mount
56
+ end
57
+
58
+ context 'when the device does not exist' do
59
+ let(:mount_command) do
60
+ 'sudo mount /dev/mapper/FAKE_LOOP1p1 /fake/mnt'
61
+ end
62
+
63
+ let(:mount_error) do
64
+ "Failed: '#{mount_command}' from /fake/mnt/blah/blah/bosh-stemcell, with exit status 8192\n\n"
65
+ end
66
+
67
+ before do
68
+ disk_image.stub(:sleep)
69
+ end
70
+
71
+ it 'runs mount a second time after sleeping long enough for the device node to be created' do
72
+ losetup_commad = 'sudo losetup --show --find /path/to/FAKE_IMAGE'
73
+ shell.stub(:run).with(losetup_commad, output_command: false).and_return('/dev/loop0')
74
+ shell.stub(:run).with('sudo kpartx -av /dev/loop0', output_command: false).and_return(kpartx_map_output)
75
+ shell.should_receive(:run).with(mount_command, output_command: false).ordered.and_raise(mount_error)
76
+ disk_image.should_receive(:sleep).with(0.5)
77
+ shell.should_receive(:run).with(mount_command, output_command: false).ordered
78
+
79
+ disk_image.mount
80
+ end
81
+
82
+ context 'when the second mount command fails' do
83
+ it 'raises an error' do
84
+ losetup_commad = 'sudo losetup --show --find /path/to/FAKE_IMAGE'
85
+ shell.stub(:run).with(losetup_commad, output_command: false).and_return('/dev/loop0')
86
+ shell.stub(:run).with('sudo kpartx -av /dev/loop0', output_command: false).and_return(kpartx_map_output)
87
+ shell.should_receive(:run).with(mount_command, output_command: false).ordered.twice.and_raise(mount_error)
88
+
89
+ expect { disk_image.mount }.to raise_error(mount_error)
90
+ end
91
+ end
92
+ end
93
+
94
+ context 'when the mount command fails' do
95
+ it 'runs mount a second time' do
96
+ losetup_commad = 'sudo losetup --show --find /path/to/FAKE_IMAGE'
97
+ shell.stub(:run).with(losetup_commad, output_command: false).and_return('/dev/loop0')
98
+ shell.stub(:run).with('sudo kpartx -av /dev/loop0', output_command: false).and_return(kpartx_map_output)
99
+ shell.should_receive(:run).
100
+ with('sudo mount /dev/mapper/FAKE_LOOP1p1 /fake/mnt', output_command: false).ordered.
101
+ and_raise(RuntimeError, 'UNEXEPECTED')
102
+
103
+ expect { disk_image.mount }.to raise_error(RuntimeError, 'UNEXEPECTED')
104
+ end
105
+ end
106
+ end
107
+
108
+ describe '#unmount' do
109
+ before do
110
+ disk_image.stub(device: '/dev/loop0') # pretend we've mounted
111
+ end
112
+
113
+ it 'unmounts the loop device and then unmaps the file' do
114
+ shell.should_receive(:run).with('sudo umount /fake/mnt', output_command: false).ordered
115
+ shell.should_receive(:run).with('sudo kpartx -dv /dev/loop0', output_command: false).ordered
116
+ shell.should_receive(:run).with('sudo losetup -dv /dev/loop0', output_command: false).ordered
117
+
118
+ disk_image.unmount
119
+ end
120
+
121
+ it 'unmaps the file even if unmounting the device fails' do
122
+ shell.should_receive(:run).with('sudo umount /fake/mnt', output_command: false).and_raise
123
+ shell.should_receive(:run).with('sudo kpartx -dv /dev/loop0', output_command: false).ordered
124
+ shell.should_receive(:run).with('sudo losetup -dv /dev/loop0', output_command: false).ordered
125
+
126
+ expect { disk_image.unmount }.to raise_error
127
+ end
128
+ end
129
+
130
+ describe '#while_mounted' do
131
+ it 'mounts the disk, calls the provided block, and unmounts' do
132
+ fake_thing = double('FakeThing')
133
+ losetup_commad = 'sudo losetup --show --find /path/to/FAKE_IMAGE'
134
+ shell.stub(:run).with(losetup_commad, output_command: false).and_return('/dev/loop0')
135
+ shell.stub(:run).with('sudo kpartx -av /dev/loop0', output_command: false).and_return(kpartx_map_output)
136
+ shell.should_receive(:run).with('sudo mount /dev/mapper/FAKE_LOOP1p1 /fake/mnt', output_command: false)
137
+ fake_thing.should_receive(:fake_call).with(disk_image).ordered
138
+ shell.should_receive(:run).with('sudo umount /fake/mnt', output_command: false).ordered
139
+ shell.should_receive(:run).with('sudo kpartx -dv /dev/loop0', output_command: false).ordered
140
+ shell.should_receive(:run).with('sudo losetup -dv /dev/loop0', output_command: false).ordered
141
+
142
+ disk_image.while_mounted do |image|
143
+ fake_thing.fake_call(image)
144
+ end
145
+ end
146
+
147
+ context 'when the block raises and error' do
148
+ it 'mounts the disk, calls the provided block, and unmounts' do
149
+ losetup_commad = 'sudo losetup --show --find /path/to/FAKE_IMAGE'
150
+ shell.stub(:run).with(losetup_commad, output_command: false).and_return('/dev/loop0')
151
+ shell.stub(:run).with('sudo kpartx -av /dev/loop0', output_command: false).and_return(kpartx_map_output)
152
+ shell.should_receive(:run).with('sudo mount /dev/mapper/FAKE_LOOP1p1 /fake/mnt', output_command: false)
153
+
154
+ shell.should_receive(:run).with('sudo umount /fake/mnt', output_command: false).ordered
155
+ shell.should_receive(:run).with('sudo kpartx -dv /dev/loop0', output_command: false).ordered
156
+ shell.should_receive(:run).with('sudo losetup -dv /dev/loop0', output_command: false).ordered
157
+
158
+ expect { disk_image.while_mounted { |_| raise } }.to raise_error
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+ require 'bosh/stemcell/infrastructure'
3
+
4
+ module Bosh::Stemcell
5
+ describe Infrastructure do
6
+ describe '.for' do
7
+ it 'returns the correct infrastrcture' do
8
+ expect(Infrastructure.for('openstack')).to be_an(Infrastructure::OpenStack)
9
+ expect(Infrastructure.for('aws')).to be_an(Infrastructure::Aws)
10
+ expect(Infrastructure.for('vsphere')).to be_a(Infrastructure::Vsphere)
11
+ end
12
+
13
+ it 'raises for unknown instructures' do
14
+ expect {
15
+ Infrastructure.for('BAD_INFRASTRUCTURE')
16
+ }.to raise_error(ArgumentError, /invalid infrastructure: BAD_INFRASTRUCTURE/)
17
+ end
18
+ end
19
+ end
20
+
21
+ describe Infrastructure::Base do
22
+ it 'requires a name to be specified' do
23
+ expect {
24
+ Infrastructure::Base.new
25
+ }.to raise_error /key not found: :name/
26
+ end
27
+
28
+ it 'requires a hypervisor' do
29
+ expect {
30
+ Infrastructure::Base.new(name: 'foo', default_disk_size: 1024)
31
+ }.to raise_error /key not found: :hypervisor/
32
+ end
33
+
34
+ it 'requires a default_disk_size' do
35
+ expect {
36
+ Infrastructure::Base.new(name: 'foo', hypervisor: 'xen')
37
+ }.to raise_error /key not found: :default_disk_size/
38
+ end
39
+
40
+ it 'defaults to not supporting light stemcells' do
41
+ infrastructure = Infrastructure::Base.new(name: 'foo', hypervisor: 'bar', default_disk_size: 1024)
42
+ expect(infrastructure).not_to be_light
43
+ end
44
+ end
45
+
46
+ describe Infrastructure::Aws do
47
+ its(:name) { should eq('aws') }
48
+ its(:hypervisor) { should eq('xen') }
49
+ its(:default_disk_size) { should eq(2048) }
50
+ it { should be_light }
51
+ end
52
+
53
+ describe Infrastructure::OpenStack do
54
+ its(:name) { should eq('openstack') }
55
+ its(:hypervisor) { should eq('kvm') }
56
+ its(:default_disk_size) { should eq(10240) }
57
+ it { should_not be_light }
58
+ end
59
+
60
+ describe Infrastructure::Vsphere do
61
+ its(:name) { should eq('vsphere') }
62
+ its(:hypervisor) { should eq('esxi') }
63
+ its(:default_disk_size) { should eq(2048) }
64
+ it { should_not be_light }
65
+ end
66
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+ require 'bosh/stemcell/operating_system'
3
+
4
+ module Bosh::Stemcell
5
+ describe OperatingSystem do
6
+ describe '.for' do
7
+ it 'returns the correct infrastrcture' do
8
+ expect(OperatingSystem.for('centos')).to be_a(OperatingSystem::Centos)
9
+ expect(OperatingSystem.for('ubuntu')).to be_a(OperatingSystem::Ubuntu)
10
+ end
11
+
12
+ it 'raises for unknown operating system' do
13
+ expect {
14
+ OperatingSystem.for('BAD_OPERATING_SYSTEM')
15
+ }.to raise_error(ArgumentError, /invalid operating system: BAD_OPERATING_SYSTEM/)
16
+ end
17
+ end
18
+ end
19
+
20
+ describe OperatingSystem::Base do
21
+ describe '#initialize' do
22
+ it 'requires :name to be specified' do
23
+ expect {
24
+ OperatingSystem::Base.new
25
+ }.to raise_error /key not found: :name/
26
+ end
27
+ end
28
+
29
+ describe '#name' do
30
+ subject { OperatingSystem::Base.new(name: 'CLOUDY_PONY_OS') }
31
+
32
+ its(:name) { should eq('CLOUDY_PONY_OS') }
33
+ end
34
+ end
35
+
36
+ describe OperatingSystem::Centos do
37
+ subject { OperatingSystem::Centos.new }
38
+
39
+ its(:name) { should eq('centos') }
40
+ end
41
+
42
+ describe OperatingSystem::Ubuntu do
43
+ subject { OperatingSystem::Ubuntu.new }
44
+
45
+ its(:name) { should eq('ubuntu') }
46
+ end
47
+ end