omnibus 5.0.0 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
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