omnibus 5.0.0 → 5.1.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/.travis.yml +1 -0
  4. data/CHANGELOG.md +26 -0
  5. data/Gemfile +3 -0
  6. data/MAINTAINERS.md +1 -0
  7. data/appveyor.yml +1 -1
  8. data/bin/omnibus +5 -0
  9. data/lib/omnibus/builder.rb +165 -26
  10. data/lib/omnibus/digestable.rb +4 -2
  11. data/lib/omnibus/fetcher.rb +18 -5
  12. data/lib/omnibus/fetchers/git_fetcher.rb +38 -22
  13. data/lib/omnibus/fetchers/net_fetcher.rb +106 -37
  14. data/lib/omnibus/fetchers/path_fetcher.rb +13 -12
  15. data/lib/omnibus/file_syncer.rb +33 -14
  16. data/lib/omnibus/generator_files/README.md.erb +1 -1
  17. data/lib/omnibus/generator_files/package_scripts/postinst.erb +3 -3
  18. data/lib/omnibus/generator_files/package_scripts/postrm.erb +1 -1
  19. data/lib/omnibus/generator_files/package_scripts/preinst.erb +1 -1
  20. data/lib/omnibus/generator_files/package_scripts/prerm.erb +3 -3
  21. data/lib/omnibus/git_cache.rb +20 -7
  22. data/lib/omnibus/health_check.rb +144 -12
  23. data/lib/omnibus/packagers/bff.rb +57 -5
  24. data/lib/omnibus/packagers/deb.rb +2 -2
  25. data/lib/omnibus/packagers/pkg.rb +2 -2
  26. data/lib/omnibus/packagers/solaris.rb +18 -6
  27. data/lib/omnibus/project.rb +1 -1
  28. data/lib/omnibus/s3_cache.rb +8 -2
  29. data/lib/omnibus/software.rb +152 -18
  30. data/lib/omnibus/sugarable.rb +1 -5
  31. data/lib/omnibus/util.rb +1 -1
  32. data/lib/omnibus/version.rb +1 -1
  33. data/omnibus.gemspec +4 -1
  34. data/resources/bff/config.erb +7 -0
  35. data/resources/deb/md5sums.erb +1 -1
  36. data/spec/functional/builder_spec.rb +89 -2
  37. data/spec/functional/fetchers/git_fetcher_spec.rb +44 -37
  38. data/spec/functional/fetchers/net_fetcher_spec.rb +36 -5
  39. data/spec/functional/fetchers/path_fetcher_spec.rb +28 -28
  40. data/spec/unit/builder_spec.rb +143 -11
  41. data/spec/unit/fetchers/git_fetcher_spec.rb +23 -59
  42. data/spec/unit/fetchers/net_fetcher_spec.rb +151 -63
  43. data/spec/unit/fetchers/path_fetcher_spec.rb +4 -35
  44. data/spec/unit/git_cache_spec.rb +13 -14
  45. data/spec/unit/health_check_spec.rb +90 -0
  46. data/spec/unit/library_spec.rb +1 -1
  47. data/spec/unit/packagers/bff_spec.rb +126 -3
  48. data/spec/unit/packagers/deb_spec.rb +8 -3
  49. data/spec/unit/packagers/pkg_spec.rb +19 -19
  50. data/spec/unit/packagers/solaris_spec.rb +13 -1
  51. data/spec/unit/software_spec.rb +242 -38
  52. metadata +7 -6
  53. data/lib/omnibus/generator_files/package_scripts/makeselfinst.erb +0 -0
@@ -24,7 +24,10 @@ module Omnibus
24
24
  WIN_7Z_EXTENSIONS = %w(.7z .zip)
25
25
 
26
26
  # tar probably has compression scheme linked in, otherwise for tarballs
27
- TAR_EXTENSIONS = %w(.tar .tar.gz .tgz .bz2 .tar.xz .txz)
27
+ COMPRESSED_TAR_EXTENSIONS = %w(.tar.gz .tgz tar.bz2 .tar.xz .txz .tar.lzma)
28
+ TAR_EXTENSIONS = COMPRESSED_TAR_EXTENSIONS + ['.tar']
29
+
30
+ ALL_EXTENSIONS = WIN_7Z_EXTENSIONS + TAR_EXTENSIONS
28
31
 
29
32
  # Digest types used for verifying file checksums
30
33
  DIGESTS = [:sha512, :sha256, :sha1, :md5]
@@ -51,21 +54,21 @@ module Omnibus
51
54
  end
52
55
 
53
56
  #
54
- # Clean the project directory by removing the contents from disk.
57
+ # Clean the project directory if it exists and actually extract
58
+ # the downloaded file.
55
59
  #
56
60
  # @return [true, false]
57
61
  # true if the project directory was removed, false otherwise
58
62
  #
59
63
  def clean
60
- if File.exist?(project_dir)
64
+ needs_cleaning = File.exist?(project_dir)
65
+ if needs_cleaning
61
66
  log.info(log_key) { "Cleaning project directory `#{project_dir}'" }
62
67
  FileUtils.rm_rf(project_dir)
63
- extract
64
- true
65
- else
66
- extract
67
- false
68
68
  end
69
+ create_required_directories
70
+ deploy
71
+ needs_cleaning
69
72
  end
70
73
 
71
74
  #
@@ -81,13 +84,15 @@ module Omnibus
81
84
  create_required_directories
82
85
  download
83
86
  verify_checksum!
84
- extract
85
87
  end
86
88
 
87
89
  #
88
90
  # The version for this item in the cache. This is the digest of downloaded
89
91
  # file and the URL where it was downloaded from.
90
92
  #
93
+ # This method is called *before* clean but *after* fetch. Do not ever
94
+ # use the contents of the project_dir here.
95
+ #
91
96
  # @return [String]
92
97
  #
93
98
  def version_for_cache
@@ -182,8 +187,11 @@ module Omnibus
182
187
  }
183
188
 
184
189
  file = open(download_url, options)
185
- FileUtils.cp(file.path, downloaded_file)
190
+ # This is a temporary file. Close and flush it before attempting to copy
191
+ # it over.
186
192
  file.close
193
+ FileUtils.cp(file.path, downloaded_file)
194
+ file.unlink
187
195
  rescue SocketError,
188
196
  Errno::ECONNREFUSED,
189
197
  Errno::ECONNRESET,
@@ -191,7 +199,7 @@ module Omnibus
191
199
  Timeout::Error,
192
200
  OpenURI::HTTPError => e
193
201
  if fetcher_retries != 0
194
- log.debug(log_key) { "Retrying failed download (#{fetcher_retries})..." }
202
+ log.info(log_key) { "Retrying failed download due to #{e} (#{fetcher_retries} retries left)..." }
195
203
  fetcher_retries -= 1
196
204
  retry
197
205
  else
@@ -205,24 +213,91 @@ module Omnibus
205
213
  # ending file extension. In the rare event the file cannot be extracted, it
206
214
  # is copied over as a raw file.
207
215
  #
208
- def extract
209
- if command = extract_command
210
- log.info(log_key) { "Extracting `#{downloaded_file}' to `#{Config.source_dir}'" }
211
- shellout!(command)
216
+ def deploy
217
+ if downloaded_file.end_with?(*ALL_EXTENSIONS)
218
+ log.info(log_key) { "Extracting `#{safe_downloaded_file}' to `#{safe_project_dir}'" }
219
+ extract
212
220
  else
213
- log.info(log_key) { "`#{downloaded_file}' is not an archive - copying to `#{project_dir}'" }
221
+ log.info(log_key) { "`#{safe_downloaded_file}' is not an archive - copying to `#{safe_project_dir}'" }
214
222
 
215
- if File.directory?(project_dir)
223
+ if File.directory?(downloaded_file)
216
224
  # If the file itself was a directory, copy the whole thing over. This
217
225
  # seems unlikely, because I do not think it is a possible to download
218
226
  # a folder, but better safe than sorry.
219
- FileUtils.cp_r(downloaded_file, project_dir)
227
+ FileUtils.cp_r("#{downloaded_file}/.", project_dir)
220
228
  else
221
229
  # In the more likely case that we got a "regular" file, we want that
222
- # file to live **inside** the project directory.
223
- FileUtils.mkdir_p(project_dir)
224
- FileUtils.cp(downloaded_file, "#{project_dir}/")
230
+ # file to live **inside** the project directory. project_dir should already
231
+ # exist due to create_required_directories
232
+ FileUtils.cp(downloaded_file, project_dir)
233
+ end
234
+ end
235
+ end
236
+
237
+ #
238
+ # Extracts the downloaded archive file into project_dir.
239
+ #
240
+ # On windows, this is a fuster cluck and we allow users to specify the
241
+ # preferred extractor to be used. The default is to use tar. User overrides
242
+ # can be set in source[:extract] as:
243
+ # :tar - use tar.exe and fail on errors (default strategy).
244
+ # :seven_zip - use 7zip for all tar/compressed tar files on windows.
245
+ # :lax_tar - use tar.exe on windows but ignore errors.
246
+ #
247
+ # Both 7z and bsdtar have issues on windows.
248
+ #
249
+ # 7z cannot extract and untar at the same time. You need to extract to a
250
+ # temporary location and then extract again into project_dir.
251
+ #
252
+ # 7z also doesn't handle symlinks well. A symlink to a non-existent
253
+ # location simply results in a text file with the target path written in
254
+ # it. It does this without throwing any errors.
255
+ #
256
+ # bsdtar will exit(1) if it is encounters symlinks on windows. So we can't
257
+ # use shellout! directly.
258
+ #
259
+ # bsdtar will also exit(1) and fail to overwrite files at the destination
260
+ # during extraction if a file already exists at the destination and is
261
+ # marked read-only. This used to be a problem when we weren't properly
262
+ # cleaning an existing project_dir. It should be less of a problem now...
263
+ # but who knows.
264
+ #
265
+ def extract
266
+ # Only used by tar
267
+ compression_switch = ''
268
+ compression_switch = 'z' if downloaded_file.end_with?('gz')
269
+ compression_switch = '--lzma -' if downloaded_file.end_with?('lzma')
270
+ compression_switch = 'j' if downloaded_file.end_with?('bz2')
271
+ compression_switch = 'J' if downloaded_file.end_with?('xz')
272
+
273
+ if Ohai['platform'] == 'windows'
274
+ if downloaded_file.end_with?(*TAR_EXTENSIONS) && source[:extract] != :seven_zip
275
+ returns = [0]
276
+ returns << 1 if source[:extract] == :lax_tar
277
+
278
+ shellout!("tar.exe #{compression_switch}xf #{safe_downloaded_file} -C#{safe_project_dir}", returns: returns)
279
+ elsif downloaded_file.end_with?(*COMPRESSED_TAR_EXTENSIONS)
280
+ Dir.mktmpdir do |temp_dir|
281
+ log.debug(log_key) { "Temporarily extracting `#{safe_downloaded_file}' to `#{temp_dir}'" }
282
+
283
+ shellout!("7z.exe x #{safe_downloaded_file} -o#{windows_safe_path(temp_dir)} -r -y")
284
+
285
+ fname = File.basename(downloaded_file, File.extname(downloaded_file))
286
+ fname << ".tar" if downloaded_file.end_with?('tgz', 'txz')
287
+ next_file = windows_safe_path(File.join(temp_dir, fname))
288
+
289
+ log.debug(log_key) { "Temporarily extracting `#{next_file}' to `#{safe_project_dir}'" }
290
+ shellout!("7z.exe x #{next_file} -o#{safe_project_dir} -r -y")
291
+ end
292
+ else
293
+ shellout!("7z.exe x #{safe_downloaded_file} -o#{safe_project_dir} -r -y")
225
294
  end
295
+ elsif downloaded_file.end_with?('.7z')
296
+ shellout!("7z x #{safe_downloaded_file} -o#{safe_project_dir} -r -y")
297
+ elsif downloaded_file.end_with?('.zip')
298
+ shellout!("unzip #{safe_downloaded_file} -d #{safe_project_dir}")
299
+ else
300
+ shellout!("#{tar} #{compression_switch}xf #{safe_downloaded_file} -C#{safe_project_dir}")
226
301
  end
227
302
  end
228
303
 
@@ -242,7 +317,7 @@ module Omnibus
242
317
  end
243
318
 
244
319
  #
245
- # Verify the downloaded file has the correct checksum.#
320
+ # Verify the downloaded file has the correct checksum.
246
321
  #
247
322
  # @raise [ChecksumMismatch]
248
323
  # if the checksum does not match
@@ -258,26 +333,20 @@ module Omnibus
258
333
  end
259
334
  end
260
335
 
336
+ def safe_project_dir
337
+ windows_safe_path(project_dir)
338
+ end
339
+
340
+ def safe_downloaded_file
341
+ windows_safe_path(downloaded_file)
342
+ end
343
+
261
344
  #
262
345
  # The command to use for extracting this piece of software.
263
346
  #
264
- # @return [String, nil]
347
+ # @return [[String]]
265
348
  #
266
349
  def extract_command
267
- if Ohai['platform'] == 'windows' && downloaded_file.end_with?(*WIN_7Z_EXTENSIONS)
268
- "7z.exe x #{windows_safe_path(downloaded_file)} -o#{Config.source_dir} -r -y"
269
- elsif Ohai['platform'] != 'windows' && downloaded_file.end_with?('.7z')
270
- "7z x #{windows_safe_path(downloaded_file)} -o#{Config.source_dir} -r -y"
271
- elsif Ohai['platform'] != 'windows' && downloaded_file.end_with?('.zip')
272
- "unzip #{windows_safe_path(downloaded_file)} -d #{Config.source_dir}"
273
- elsif downloaded_file.end_with?(*TAR_EXTENSIONS)
274
- compression_switch = 'z' if downloaded_file.end_with?('gz')
275
- compression_switch = 'j' if downloaded_file.end_with?('bz2')
276
- compression_switch = 'J' if downloaded_file.end_with?('xz')
277
- compression_switch = '' if downloaded_file.end_with?('tar')
278
-
279
- "#{tar} #{compression_switch}xf #{windows_safe_path(downloaded_file)} -C#{Config.source_dir}"
280
- end
281
350
  end
282
351
 
283
352
  #
@@ -42,17 +42,14 @@ module Omnibus
42
42
  # Clean the given path by removing the project directory.
43
43
  #
44
44
  # @return [true, false]
45
- # true if the directory was cleaned, false otherwise
45
+ # true if the directory was cleaned, false otherwise.
46
+ # Since we do not currently use the cache to sync files and
47
+ # always fetch from source, there is no need to clean anything.
48
+ # The fetch step (which needs to be called before clean) would
49
+ # have already removed anything extraneous.
46
50
  #
47
51
  def clean
48
- if File.exist?(project_dir)
49
- log.info(log_key) { "Cleaning project directory `#{project_dir}'" }
50
- FileUtils.rm_rf(project_dir)
51
- fetch
52
- true
53
- else
54
- false
55
- end
52
+ return true
56
53
  end
57
54
 
58
55
  #
@@ -74,10 +71,14 @@ module Omnibus
74
71
  # The version for this item in the cache. The is the shasum of the directory
75
72
  # on disk.
76
73
  #
74
+ # This method is called *before* clean but *after* fetch. Since fetch
75
+ # automatically cleans, target vs. destination sha doesn't matter. Change this
76
+ # if that assumption changes.
77
+ #
77
78
  # @return [String]
78
79
  #
79
80
  def version_for_cache
80
- "path:#{source_path}|shasum:#{target_shasum}"
81
+ "path:#{source_path}|shasum:#{destination_shasum}"
81
82
  end
82
83
 
83
84
  #
@@ -117,7 +118,7 @@ module Omnibus
117
118
  # @return [String]
118
119
  #
119
120
  def target_shasum
120
- @target_shasum ||= digest_directory(project_dir, :sha256)
121
+ @target_shasum ||= digest_directory(project_dir, :sha256, source_options)
121
122
  end
122
123
 
123
124
  #
@@ -126,7 +127,7 @@ module Omnibus
126
127
  # @return [String]
127
128
  #
128
129
  def destination_shasum
129
- @destination_shasum ||= digest_directory(source_path, :sha256)
130
+ @destination_shasum ||= digest_directory(source_path, :sha256, source_options)
130
131
  end
131
132
  end
132
133
  end
@@ -40,6 +40,31 @@ module Omnibus
40
40
  end
41
41
  end
42
42
 
43
+ #
44
+ # Glob for all files under a given path/pattern, removing Ruby's
45
+ # dumb idea to include +'.'+ and +'..'+ as entries.
46
+ #
47
+ # @param [String] source
48
+ # the path or glob pattern to get all files from
49
+ #
50
+ # @option options [String, Array<String>] :exclude
51
+ # a file, folder, or globbing pattern of files to ignore when syncing
52
+ #
53
+ # @return [Array<String>]
54
+ # the list of all files
55
+ #
56
+ def all_files_under(source, options = {})
57
+ excludes = Array(options[:exclude]).map do |exclude|
58
+ [exclude, "#{exclude}/*"]
59
+ end.flatten
60
+
61
+ source_files = glob(File.join(source, '**/*'))
62
+ source_files = source_files.reject do |source_file|
63
+ basename = relative_path_for(source_file, source)
64
+ excludes.any? { |exclude| File.fnmatch?(exclude, basename, File::FNM_DOTMATCH) }
65
+ end
66
+ end
67
+
43
68
  #
44
69
  # Copy the files from +source+ to +destination+, while removing any files
45
70
  # in +destination+ that are not present in +source+.
@@ -69,16 +94,7 @@ module Omnibus
69
94
  "the `copy' method instead."
70
95
  end
71
96
 
72
- # Reject any files that match the excludes pattern
73
- excludes = Array(options[:exclude]).map do |exclude|
74
- [exclude, "#{exclude}/*"]
75
- end.flatten
76
-
77
- source_files = glob(File.join(source, '**/*'))
78
- source_files = source_files.reject do |source_file|
79
- basename = relative_path_for(source_file, source)
80
- excludes.any? { |exclude| File.fnmatch?(exclude, basename, File::FNM_DOTMATCH) }
81
- end
97
+ source_files = all_files_under(source, options)
82
98
 
83
99
  # Ensure the destination directory exists
84
100
  FileUtils.mkdir_p(destination) unless File.directory?(destination)
@@ -106,9 +122,13 @@ module Omnibus
106
122
  # duplicate them, provided their source is in place already
107
123
  if hardlink? source_stat
108
124
  if existing = hardlink_sources[[source_stat.dev, source_stat.ino]]
109
- FileUtils.ln(existing, "#{destination}/#{relative_path}")
125
+ FileUtils.ln(existing, "#{destination}/#{relative_path}", force: true)
110
126
  else
111
- FileUtils.cp(source_file, "#{destination}/#{relative_path}")
127
+ begin
128
+ FileUtils.cp(source_file, "#{destination}/#{relative_path}")
129
+ rescue Errno::EACCES
130
+ FileUtils.cp_r(source_file, "#{destination}/#{relative_path}", remove_destination: true)
131
+ end
112
132
  hardlink_sources.store([source_stat.dev, source_stat.ino], "#{destination}/#{relative_path}")
113
133
  end
114
134
  else
@@ -120,8 +140,7 @@ module Omnibus
120
140
  begin
121
141
  FileUtils.cp(source_file, "#{destination}/#{relative_path}")
122
142
  rescue Errno::EACCES
123
- FileUtils.cp_r(source_file, "#{destination}/#{relative_path}",
124
- :remove_destination => true)
143
+ FileUtils.cp_r(source_file, "#{destination}/#{relative_path}", remove_destination: true)
125
144
  end
126
145
  end
127
146
  else
@@ -5,7 +5,7 @@ This project creates full-stack platform-specific packages for
5
5
 
6
6
  Installation
7
7
  ------------
8
- You must have a sane Ruby 1.9+ environment with Bundler installed. Ensure all
8
+ You must have a sane Ruby 2.0.0+ environment with Bundler installed. Ensure all
9
9
  the required gems are installed:
10
10
 
11
11
  ```shell
@@ -1,12 +1,12 @@
1
- #!/bin/bash
1
+ #!/bin/sh
2
2
  #
3
3
  # Perform necessary <%= config[:name] %> setup steps
4
4
  # after package is installed.
5
5
  #
6
6
 
7
- PROGNAME=$(basename $0)
7
+ PROGNAME=`basename $0`
8
8
 
9
- function error_exit
9
+ error_exit()
10
10
  {
11
11
  echo "${PROGNAME}: ${1:-"Unknown Error"}" 1>&2
12
12
  exit 1
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/bin/sh
2
2
  #
3
3
  # Perform necessary <%= config[:name] %> removal steps
4
4
  # after package is uninstalled.
@@ -1,4 +1,4 @@
1
- #!/bin/bash
1
+ #!/bin/sh
2
2
  #
3
3
  # Perform necessary <%= config[:name] %> setup steps
4
4
  # before package is installed.
@@ -1,12 +1,12 @@
1
- #!/bin/bash
1
+ #!/bin/sh
2
2
  #
3
3
  # Perform necessary <%= config[:name] %> setup steps
4
4
  # prior to installing package.
5
5
  #
6
6
 
7
- PROGNAME=$(basename $0)
7
+ PROGNAME=`basename $0`
8
8
 
9
- function error_exit
9
+ error_exit()
10
10
  {
11
11
  echo "${PROGNAME}: ${1:-"Unknown Error"}" 1>&2
12
12
  exit 1
@@ -64,7 +64,7 @@ module Omnibus
64
64
  false
65
65
  else
66
66
  create_directory(File.dirname(cache_path))
67
- shellout!("git --git-dir=#{cache_path} init -q")
67
+ git_cmd('init -q')
68
68
  true
69
69
  end
70
70
  end
@@ -113,15 +113,15 @@ module Omnibus
113
113
  create_cache_path
114
114
  remove_git_dirs
115
115
 
116
- shellout!(%Q(git --git-dir=#{cache_path} --work-tree=#{install_dir} add -A -f))
116
+ git_cmd('add -A -f')
117
117
 
118
118
  begin
119
- shellout!(%Q(git --git-dir=#{cache_path} --work-tree=#{install_dir} commit -q -m "Backup of #{tag}"))
119
+ git_cmd(%Q(commit -q -m "Backup of #{tag}"))
120
120
  rescue CommandFailed => e
121
121
  raise unless e.message.include?('nothing to commit')
122
122
  end
123
123
 
124
- shellout!(%Q(git --git-dir=#{cache_path} --work-tree=#{install_dir} tag -f "#{tag}"))
124
+ git_cmd(%Q(tag -f "#{tag}"))
125
125
  end
126
126
 
127
127
  def restore
@@ -129,16 +129,16 @@ module Omnibus
129
129
 
130
130
  create_cache_path
131
131
 
132
- cmd = shellout(%Q(git --git-dir=#{cache_path} --work-tree=#{install_dir} tag -l "#{tag}"))
133
-
134
132
  restore_me = false
133
+ cmd = git_cmd(%Q(tag -l "#{tag}"))
134
+
135
135
  cmd.stdout.each_line do |line|
136
136
  restore_me = true if tag == line.chomp
137
137
  end
138
138
 
139
139
  if restore_me
140
140
  log.internal(log_key) { "Detected tag `#{tag}' can be restored, restoring" }
141
- shellout!(%Q(git --git-dir=#{cache_path} --work-tree=#{install_dir} checkout -f "#{tag}"))
141
+ git_cmd(%Q(checkout -f "#{tag}"))
142
142
  true
143
143
  else
144
144
  log.internal(log_key) { "Could not find tag `#{tag}', skipping restore" }
@@ -169,6 +169,19 @@ module Omnibus
169
169
 
170
170
  private
171
171
 
172
+ #
173
+ # Shell out and invoke a git command in the context of the git cache.
174
+ #
175
+ # We explicitly disable autocrlf because we want bit-for-bit storage and
176
+ # recovery of build output. Hashes calculated on output files will be
177
+ # invalid if we muck around with files after they have been produced.
178
+ #
179
+ # @return [Mixlib::Shellout] the underlying command object.
180
+ #
181
+ def git_cmd(command)
182
+ shellout!("git -c core.autocrlf=false --git-dir=#{cache_path} --work-tree=#{install_dir} #{command}")
183
+ end
184
+
172
185
  #
173
186
  #
174
187
  # The installation directory for this software's project. Drive letters are