omnibus 5.6.1 → 5.6.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +5 -5
  2. data/.expeditor/config.yml +31 -0
  3. data/.expeditor/update_version.sh +12 -0
  4. data/.github/CODEOWNERS +3 -0
  5. data/.github/ISSUE_TEMPLATE.md +0 -2
  6. data/.github/PULL_REQUEST_TEMPLATE.md +0 -1
  7. data/.gitignore +1 -0
  8. data/.travis.yml +2 -2
  9. data/CHANGELOG.md +29 -3
  10. data/Gemfile +1 -1
  11. data/README.md +2 -2
  12. data/VERSION +1 -0
  13. data/appveyor.yml +3 -2
  14. data/docs/Building on Debian.md +6 -4
  15. data/docs/Building on OSX.md +2 -2
  16. data/docs/Building on RHEL.md +3 -3
  17. data/docs/Building on Windows.md +1 -1
  18. data/lib/omnibus.rb +1 -0
  19. data/lib/omnibus/build_version.rb +13 -4
  20. data/lib/omnibus/builder.rb +10 -5
  21. data/lib/omnibus/config.rb +40 -8
  22. data/lib/omnibus/fetcher.rb +2 -0
  23. data/lib/omnibus/fetchers/file_fetcher.rb +131 -0
  24. data/lib/omnibus/packagers/deb.rb +81 -0
  25. data/lib/omnibus/packagers/ips.rb +22 -7
  26. data/lib/omnibus/publishers/artifactory_publisher.rb +2 -6
  27. data/lib/omnibus/publishers/s3_publisher.rb +7 -5
  28. data/lib/omnibus/s3_cache.rb +1 -0
  29. data/lib/omnibus/s3_helpers.rb +2 -0
  30. data/lib/omnibus/sugarable.rb +1 -0
  31. data/lib/omnibus/version.rb +1 -1
  32. data/lib/omnibus/whitelist.rb +1 -0
  33. data/omnibus.gemspec +3 -3
  34. data/resources/msi/CustomActionFastMsi.CA.dll +0 -0
  35. data/resources/msi/source.wxs.erb +7 -0
  36. data/spec/functional/fetchers/file_fetcher_spec.rb +92 -0
  37. data/spec/spec_helper.rb +1 -1
  38. data/spec/unit/build_version_spec.rb +11 -1
  39. data/spec/unit/compressor_spec.rb +2 -2
  40. data/spec/unit/config_spec.rb +13 -3
  41. data/spec/unit/fetchers/file_fetcher_spec.rb +81 -0
  42. data/spec/unit/fetchers/net_fetcher_spec.rb +14 -3
  43. data/spec/unit/health_check_spec.rb +3 -3
  44. data/spec/unit/metadata_spec.rb +8 -8
  45. data/spec/unit/packager_spec.rb +10 -10
  46. data/spec/unit/packagers/bff_spec.rb +1 -1
  47. data/spec/unit/packagers/deb_spec.rb +40 -0
  48. data/spec/unit/packagers/ips_spec.rb +31 -5
  49. data/spec/unit/packagers/rpm_spec.rb +5 -5
  50. data/spec/unit/project_spec.rb +7 -7
  51. data/spec/unit/publishers/artifactory_publisher_spec.rb +16 -0
  52. data/spec/unit/publishers/s3_publisher_spec.rb +17 -0
  53. data/spec/unit/software_spec.rb +10 -8
  54. metadata +16 -7
@@ -72,7 +72,7 @@ RSpec.configure do |config|
72
72
  FileUtils.mkdir_p(tmp_path)
73
73
 
74
74
  # Don't run Ohai - tests can still override this
75
- stub_ohai(platform: "ubuntu", version: "12.04")
75
+ stub_ohai(platform: "ubuntu", version: "16.04")
76
76
 
77
77
  # Default to real HTTP requests
78
78
  WebMock.allow_net_connect!
@@ -112,7 +112,17 @@ module Omnibus
112
112
  expect(build_version.semver).to match(/11.0.0-alpha1\+#{today_string}[0-9]+.git.207.694b062/)
113
113
  end
114
114
 
115
- it "uses ENV['BUILD_ID'] to generate timestamp if set" do
115
+ it "uses ENV['BUILD_TIMESTAMP'] to generate timestamp if set" do
116
+ stub_env("BUILD_TIMESTAMP", "2012-12-25_16-41-40")
117
+ expect(build_version.semver).to eq("11.0.0-alpha1+20121225164140.git.207.694b062")
118
+ end
119
+
120
+ it "fails on invalid ENV['BUILD_TIMESTAMP'] values" do
121
+ stub_env("BUILD_TIMESTAMP", "AAAA")
122
+ expect { build_version.semver }.to raise_error(ArgumentError)
123
+ end
124
+
125
+ it "uses ENV['BUILD_ID'] to generate timestamp if set and BUILD_TIMESTAMP is not set" do
116
126
  stub_env("BUILD_ID", "2012-12-25_16-41-40")
117
127
  expect(build_version.semver).to eq("11.0.0-alpha1+20121225164140.git.207.694b062")
118
128
  end
@@ -4,7 +4,7 @@ module Omnibus
4
4
  describe Compressor do
5
5
  describe ".for_current_system" do
6
6
  context "on Mac OS X" do
7
- before { stub_ohai(platform: "mac_os_x", version: "10.9.2") }
7
+ before { stub_ohai(platform: "mac_os_x", version: "10.12") }
8
8
 
9
9
  context "when :dmg is activated" do
10
10
  it "prefers dmg" do
@@ -26,7 +26,7 @@ module Omnibus
26
26
  end
27
27
 
28
28
  context "on Ubuntu" do
29
- before { stub_ohai(platform: "ubuntu", version: "14.04") }
29
+ before { stub_ohai(platform: "ubuntu", version: "16.04") }
30
30
 
31
31
  context "when :tgz activated" do
32
32
  it "prefers tgz" do
@@ -50,7 +50,7 @@ module Omnibus
50
50
  describe "#workers" do
51
51
  context "when the Ohai data is not present" do
52
52
  before do
53
- stub_ohai(platform: "ubuntu", version: "12.04") do |data|
53
+ stub_ohai(platform: "ubuntu", version: "16.04") do |data|
54
54
  data["cpu"] = nil
55
55
  end
56
56
  end
@@ -62,7 +62,7 @@ module Omnibus
62
62
 
63
63
  context "when the Ohai data is present" do
64
64
  before do
65
- stub_ohai(platform: "ubuntu", version: "12.04") do |data|
65
+ stub_ohai(platform: "ubuntu", version: "16.04") do |data|
66
66
  data["cpu"] = { "total" => "5" }
67
67
  end
68
68
  end
@@ -74,7 +74,7 @@ module Omnibus
74
74
  end
75
75
 
76
76
  context "on Windows" do
77
- before { stub_ohai(platform: "windows", version: "2012") }
77
+ before { stub_ohai(platform: "windows", version: "2012R2") }
78
78
 
79
79
  include_examples "a configurable", :base_dir, "C:/omnibus-ruby"
80
80
  include_examples "a configurable", :cache_dir, "C:/omnibus-ruby/cache"
@@ -93,5 +93,15 @@ module Omnibus
93
93
  include_examples "a configurable", :build_dir, "/foo/bar/build"
94
94
  include_examples "a configurable", :package_dir, "/foo/bar/pkg"
95
95
  end
96
+
97
+ context "when cache_suffix is specified" do
98
+ before { described_class.cache_suffix("projecto") }
99
+
100
+ include_examples "a configurable", :cache_dir, "/var/cache/omnibus/projecto/cache"
101
+ include_examples "a configurable", :git_cache_dir, "/var/cache/omnibus/projecto/cache/git_cache"
102
+ include_examples "a configurable", :source_dir, "/var/cache/omnibus/projecto/src"
103
+ include_examples "a configurable", :build_dir, "/var/cache/omnibus/projecto/build"
104
+ include_examples "a configurable", :package_dir, "/var/cache/omnibus/projecto/pkg"
105
+ end
96
106
  end
97
107
  end
@@ -0,0 +1,81 @@
1
+ require "spec_helper"
2
+
3
+ module Omnibus
4
+ describe FileFetcher do
5
+ let(:source_file) { "/local/file" }
6
+ let(:target_file) { "/project/dir/file" }
7
+ let(:project_dir) { "/project/dir" }
8
+ let(:build_dir) { "/build/dir" }
9
+
10
+ let(:manifest_entry) do
11
+ double(ManifestEntry,
12
+ name: "software",
13
+ locked_version: nil,
14
+ described_version: nil,
15
+ locked_source: { file: source_file })
16
+ end
17
+
18
+ subject { described_class.new(manifest_entry, project_dir, build_dir) }
19
+
20
+ describe "#fetch_required?" do
21
+ context "when the SHAs match" do
22
+ before do
23
+ allow(subject).to receive(:target_shasum).and_return("abcd1234")
24
+ allow(subject).to receive(:destination_shasum).and_return("abcd1234")
25
+ end
26
+
27
+ it "returns false" do
28
+ expect(subject.fetch_required?).to be(false)
29
+ end
30
+ end
31
+
32
+ context "when the SHAs do not match" do
33
+ before do
34
+ allow(subject).to receive(:target_shasum).and_return("abcd1234")
35
+ allow(subject).to receive(:destination_shasum).and_return("efgh5678")
36
+ end
37
+
38
+ it "returns true" do
39
+ expect(subject.fetch_required?).to be_truthy
40
+ end
41
+ end
42
+ end
43
+
44
+ describe "#version_guid" do
45
+ it "returns the path" do
46
+ expect(subject.version_guid).to eq("file:#{source_file}")
47
+ end
48
+ end
49
+
50
+ describe "#clean" do
51
+ it "returns true" do
52
+ expect(subject.clean).to be_truthy
53
+ end
54
+ end
55
+
56
+ describe "#fetch" do
57
+ before do
58
+ allow(subject).to receive(:create_required_directories)
59
+ end
60
+
61
+ it "copies the new files over" do
62
+ expect(FileUtils).to receive(:cp).with(source_file, target_file)
63
+ subject.fetch
64
+ end
65
+ end
66
+
67
+ describe "#version_for_cache" do
68
+ let(:shasum) { "abcd1234" }
69
+
70
+ before do
71
+ allow(subject).to receive(:digest)
72
+ .with(source_file, :sha256)
73
+ .and_return(shasum)
74
+ end
75
+
76
+ it "returns the shasum of the source file" do
77
+ expect(subject.version_for_cache).to eq("file:#{source_file}|shasum:#{shasum}")
78
+ end
79
+ end
80
+ end
81
+ end
@@ -200,6 +200,17 @@ module Omnibus
200
200
  end
201
201
  end
202
202
 
203
+ context "custom endpoint with path style urls" do
204
+ before do
205
+ Config.s3_force_path_style(true)
206
+ Config.s3_endpoint("http://example.com")
207
+ end
208
+
209
+ it "returns the url using path style" do
210
+ expect(subject.send(:download_url)).to eq("http://example.com/mybucket/file-1.2.3-abcd1234")
211
+ end
212
+ end
213
+
203
214
  context "s3 transfer acceleration" do
204
215
  before { Config.s3_accelerate(true) }
205
216
 
@@ -369,7 +380,7 @@ module Omnibus
369
380
 
370
381
  before do
371
382
  Config.cache_dir("C:/")
372
- stub_ohai(platform: "windows", version: "2012")
383
+ stub_ohai(platform: "windows", version: "2012R2")
373
384
  allow(Dir).to receive(:mktmpdir).and_yield("C:/tmp_dir")
374
385
  end
375
386
 
@@ -446,7 +457,7 @@ module Omnibus
446
457
  context "on Linux" do
447
458
  before do
448
459
  Config.cache_dir("/")
449
- stub_ohai(platform: "ubuntu", version: "12.04")
460
+ stub_ohai(platform: "ubuntu", version: "16.04")
450
461
  stub_const("File::ALT_SEPARATOR", nil)
451
462
 
452
463
  allow(Omnibus).to receive(:which)
@@ -479,7 +490,7 @@ module Omnibus
479
490
  before do
480
491
  Config.cache_dir("/")
481
492
 
482
- stub_ohai(platform: "ubuntu", version: "12.04")
493
+ stub_ohai(platform: "ubuntu", version: "16.04")
483
494
  stub_const("File::ALT_SEPARATOR", nil)
484
495
 
485
496
  allow(Omnibus).to receive(:which)
@@ -30,11 +30,11 @@ module Omnibus
30
30
 
31
31
  context "on windows" do
32
32
  before do
33
- stub_ohai(platform: "windows", version: "2012")
33
+ stub_ohai(platform: "windows", version: "2012R2")
34
34
  end
35
35
 
36
36
  it "will perform dll base relocation checks" do
37
- stub_ohai(platform: "windows", version: "2012")
37
+ stub_ohai(platform: "windows", version: "2012R2")
38
38
  expect(subject.relocation_checkable?).to be true
39
39
  end
40
40
 
@@ -101,7 +101,7 @@ module Omnibus
101
101
  end
102
102
 
103
103
  context "on linux" do
104
- before { stub_ohai(platform: "ubuntu", version: "12.04") }
104
+ before { stub_ohai(platform: "ubuntu", version: "16.04") }
105
105
 
106
106
  let(:bad_healthcheck) do
107
107
  double("Mixlib::Shellout",
@@ -85,7 +85,7 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
85
85
  sha256: "abcd1234",
86
86
  sha512: "abcdef123456",
87
87
  platform: "ubuntu",
88
- platform_version: "12.04",
88
+ platform_version: "16.04",
89
89
  arch: "x86_64",
90
90
  name: "some-project",
91
91
  friendly_name: "Some Project",
@@ -130,7 +130,7 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
130
130
  let(:architecture) { "x86_64" }
131
131
 
132
132
  before do
133
- stub_ohai(platform: "ubuntu", version: "12.04") do |data|
133
+ stub_ohai(platform: "ubuntu", version: "16.04") do |data|
134
134
  data["kernel"]["machine"] = architecture
135
135
  end
136
136
  end
@@ -180,17 +180,17 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
180
180
 
181
181
  describe ".platform_shortname" do
182
182
  it "returns el on rhel" do
183
- stub_ohai(platform: "redhat", version: "6.4")
183
+ stub_ohai(platform: "redhat", version: "6.9")
184
184
  expect(described_class.platform_shortname).to eq("el")
185
185
  end
186
186
 
187
187
  it "returns sles on suse" do
188
- stub_ohai(platform: "suse", version: "12.0")
188
+ stub_ohai(platform: "suse", version: "12.2")
189
189
  expect(described_class.platform_shortname).to eq("sles")
190
190
  end
191
191
 
192
192
  it "returns .platform on all other systems" do
193
- stub_ohai(platform: "ubuntu", version: "12.04")
193
+ stub_ohai(platform: "ubuntu", version: "16.04")
194
194
  expect(described_class.platform_shortname).to eq("ubuntu")
195
195
  end
196
196
  end
@@ -199,7 +199,7 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
199
199
  shared_examples "a version manipulator" do |platform_shortname, version, expected|
200
200
  context "on #{platform_shortname}-#{version}" do
201
201
  it "returns the correct value" do
202
- stub_ohai(platform: "ubuntu", version: "12.04") do |data|
202
+ stub_ohai(platform: "ubuntu", version: "16.04") do |data|
203
203
  data["platform"] = platform_shortname
204
204
  data["platform_version"] = version
205
205
  end
@@ -247,7 +247,7 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
247
247
 
248
248
  context "given an unknown platform" do
249
249
  before do
250
- stub_ohai(platform: "ubuntu", version: "12.04") do |data|
250
+ stub_ohai(platform: "ubuntu", version: "16.04") do |data|
251
251
  data["platform"] = "bacon"
252
252
  data["platform_version"] = "1.crispy"
253
253
  end
@@ -261,7 +261,7 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
261
261
 
262
262
  context "given an unknown windows platform version" do
263
263
  before do
264
- stub_ohai(platform: "ubuntu", version: "12.04") do |data|
264
+ stub_ohai(platform: "ubuntu", version: "16.04") do |data|
265
265
  data["platform"] = "windows"
266
266
  data["platform_version"] = "1.2.3"
267
267
  end
@@ -4,14 +4,14 @@ module Omnibus
4
4
  describe Packager do
5
5
  describe ".for_current_system" do
6
6
  context "on Mac OS X" do
7
- before { stub_ohai(platform: "mac_os_x", version: "10.9.2") }
7
+ before { stub_ohai(platform: "mac_os_x", version: "10.12") }
8
8
  it "prefers PKG" do
9
9
  expect(described_class.for_current_system).to eq([Packager::PKG])
10
10
  end
11
11
  end
12
12
 
13
- context "on Windows 2012" do
14
- before { stub_ohai(platform: "windows", version: "2012") }
13
+ context "on Windows 2012 R2" do
14
+ before { stub_ohai(platform: "windows", version: "2012R2") }
15
15
  it "prefers MSI and APPX" do
16
16
  expect(described_class.for_current_system).to eq([Packager::MSI, Packager::APPX])
17
17
  end
@@ -38,29 +38,29 @@ module Omnibus
38
38
  end
39
39
  end
40
40
 
41
- context "on aix" do
41
+ context "on AIX" do
42
42
  before { stub_ohai(platform: "aix", version: "7.1") }
43
43
  it "prefers BFF" do
44
44
  expect(described_class.for_current_system).to eq([Packager::BFF])
45
45
  end
46
46
  end
47
47
 
48
- context "on fedora" do
49
- before { stub_ohai(platform: "fedora", version: "20") }
48
+ context "on Fedora" do
49
+ before { stub_ohai(platform: "fedora", version: "25") }
50
50
  it "prefers RPM" do
51
51
  expect(described_class.for_current_system).to eq([Packager::RPM])
52
52
  end
53
53
  end
54
54
 
55
- context "on debian" do
56
- before { stub_ohai(platform: "debian", version: "7.2") }
55
+ context "on Debian" do
56
+ before { stub_ohai(platform: "debian", version: "8.8") }
57
57
  it "prefers RPM" do
58
58
  expect(described_class.for_current_system).to eq([Packager::DEB])
59
59
  end
60
60
  end
61
61
 
62
- context "on suse" do
63
- before { stub_ohai(platform: "suse", version: "12.0") }
62
+ context "on SLES" do
63
+ before { stub_ohai(platform: "suse", version: "12.2") }
64
64
  it "prefers RPM" do
65
65
  expect(described_class.for_current_system).to eq([Packager::RPM])
66
66
  end
@@ -373,7 +373,7 @@ module Omnibus
373
373
 
374
374
  describe "#safe_architecture" do
375
375
  before do
376
- stub_ohai(platform: "ubuntu", version: "12.04") do |data|
376
+ stub_ohai(platform: "ubuntu", version: "16.04") do |data|
377
377
  data["kernel"]["machine"] = "i386"
378
378
  end
379
379
  end
@@ -337,6 +337,46 @@ module Omnibus
337
337
  end
338
338
  end
339
339
 
340
+ describe "#sign_deb_file", :not_supported_on_windows do
341
+ context "when DEB signing is not enabled" do
342
+ before do
343
+ subject.signing_passphrase(nil)
344
+ end
345
+
346
+ it "logs a message" do
347
+ output = capture_logging { subject.sign_deb_file }
348
+ expect(output).to include("Signing not enabled for .deb file")
349
+ end
350
+ end
351
+
352
+ context "when DEB signing is enabled" do
353
+ before do
354
+ allow(subject).to receive(:shellout!)
355
+ allow(subject).to receive(:package_name).and_return("safe")
356
+ subject.signing_passphrase("foobar")
357
+ end
358
+
359
+ it "logs a message" do
360
+ output = capture_logging { subject.sign_deb_file }
361
+ expect(output).to include("Signing enabled for .deb file")
362
+ end
363
+
364
+ it "finds gpg and ar commands" do
365
+ output = capture_logging { subject.sign_deb_file }
366
+ expect(output).not_to include("Signing not possible.")
367
+ end
368
+
369
+ it "runs correct commands" do
370
+ expect(subject).to receive(:shellout!)
371
+ .at_least(:once).with(/ar x/)
372
+ .at_least(:once).with(/cat debian-binary control\.tar/)
373
+ .at_least(:once).with(/fakeroot gpg/)
374
+ .at_least(:once).with(/fakeroot ar rc/)
375
+ subject.sign_deb_file
376
+ end
377
+ end
378
+ end
379
+
340
380
  describe "#package_size" do
341
381
  before do
342
382
  project.install_dir(staging_dir)
@@ -1,5 +1,4 @@
1
1
  require "spec_helper"
2
- require "pry"
3
2
 
4
3
  module Omnibus
5
4
  describe Packager::IPS do
@@ -171,18 +170,23 @@ module Omnibus
171
170
  expect(manifest_file_contents).to include("set name=variant.arch value=i386")
172
171
  end
173
172
 
174
- context "when symlinks.erb exists" do
173
+ context "when both symlinks.erb and project-symlinks.erb exists" do
175
174
  before do
176
175
  FileUtils.mkdir_p(resources_path)
177
176
  allow(subject).to receive(:resources_path).and_return(resources_path)
178
- File.open(File.join(resources_path, "symlinks.erb"), "w+") do |f|
177
+ File.open(File.join(resources_path, "project-symlinks.erb"), "w+") do |f|
179
178
  f.puts("link path=usr/bin/ohai target=<%= projectdir %>/bin/ohai")
180
179
  f.puts("link path=<%= projectdir %>/bin/gmake target=<%= projectdir %>/embedded/bin/make")
181
180
  end
181
+ File.open(File.join(resources_path, "symlinks.erb"), "w+") do |f|
182
+ f.puts("link path=usr/bin/knife target=<%= projectdir %>/bin/knife")
183
+ f.puts("link path=<%= projectdir %>/bin/berks target=<%= projectdir %>/embedded/bin/berks")
184
+ end
182
185
  end
183
186
 
184
- it "should append symlinks to metadata contents" do
187
+ it "should render project-symlinks.erb and append to metadata contents" do
185
188
  subject.write_pkg_metadata
189
+ expect(subject.symlinks_file).to eq("project-symlinks.erb")
186
190
  expect(File.exist?(manifest_file)).to be(true)
187
191
  manifest_file_contents = File.read(manifest_file)
188
192
  expect(manifest_file_contents).to include("link path=usr/bin/ohai target=/opt/project/bin/ohai")
@@ -190,12 +194,34 @@ module Omnibus
190
194
  end
191
195
  end
192
196
 
193
- context "when symlinks.erb does not exist" do
197
+ context "when only symlinks.erb exists" do
198
+ before do
199
+ FileUtils.mkdir_p(resources_path)
200
+ allow(subject).to receive(:resources_path).and_return(resources_path)
201
+ File.open(File.join(resources_path, "symlinks.erb"), "w+") do |f|
202
+ f.puts("link path=usr/bin/knife target=<%= projectdir %>/bin/knife")
203
+ f.puts("link path=<%= projectdir %>/bin/berks target=<%= projectdir %>/embedded/bin/berks")
204
+ end
205
+ end
206
+
207
+ it "should render symlinks.erb and append to metadata contents" do
208
+ subject.write_pkg_metadata
209
+ expect(subject.symlinks_file).to eq("symlinks.erb")
210
+ expect(File.exist?(manifest_file)).to be(true)
211
+ manifest_file_contents = File.read(manifest_file)
212
+ expect(manifest_file_contents).to include("link path=usr/bin/knife target=/opt/project/bin/knife")
213
+ expect(manifest_file_contents).to include("link path=/opt/project/bin/berks target=/opt/project/embedded/bin/berks")
214
+ end
215
+ end
216
+
217
+ context "when symlinks_file does not exist" do
194
218
  it "#write_pkg_metadata does not include symlinks" do
195
219
  subject.write_pkg_metadata
196
220
  manifest_file = File.join(staging_dir, "gen.manifestfile")
197
221
  manifest_file_contents = File.read(manifest_file)
222
+ expect(subject.symlinks_file).to be_nil
198
223
  expect(manifest_file_contents).not_to include("link path=usr/bin/ohai target=/opt/project/bin/ohai")
224
+ expect(manifest_file_contents).not_to include("link path=usr/bin/knife target=/opt/project/bin/knife")
199
225
  end
200
226
  end
201
227
  end