lib/omnibus/fetchers/net_fetcher.rb in omnibus-5.0.0 vs lib/omnibus/fetchers/net_fetcher.rb in omnibus-5.1.0
- old
+ new
@@ -22,12 +22,15 @@
class NetFetcher < Fetcher
# Use 7-zip to extract 7z/zip for Windows
WIN_7Z_EXTENSIONS = %w(.7z .zip)
# tar probably has compression scheme linked in, otherwise for tarballs
- TAR_EXTENSIONS = %w(.tar .tar.gz .tgz .bz2 .tar.xz .txz)
+ COMPRESSED_TAR_EXTENSIONS = %w(.tar.gz .tgz tar.bz2 .tar.xz .txz .tar.lzma)
+ TAR_EXTENSIONS = COMPRESSED_TAR_EXTENSIONS + ['.tar']
+ ALL_EXTENSIONS = WIN_7Z_EXTENSIONS + TAR_EXTENSIONS
+
# Digest types used for verifying file checksums
DIGESTS = [:sha512, :sha256, :sha1, :md5]
#
# A fetch is required if the downloaded_file (such as a tarball) does not
@@ -49,25 +52,25 @@
def version_guid
"#{digest_type}:#{checksum}"
end
#
- # Clean the project directory by removing the contents from disk.
+ # Clean the project directory if it exists and actually extract
+ # the downloaded file.
#
# @return [true, false]
# true if the project directory was removed, false otherwise
#
def clean
- if File.exist?(project_dir)
+ needs_cleaning = File.exist?(project_dir)
+ if needs_cleaning
log.info(log_key) { "Cleaning project directory `#{project_dir}'" }
FileUtils.rm_rf(project_dir)
- extract
- true
- else
- extract
- false
end
+ create_required_directories
+ deploy
+ needs_cleaning
end
#
# Fetch the given software definition. This method **always** fetches the
# file, even if it already exists on disk! You should use {#fetch_required?}
@@ -79,17 +82,19 @@
log.info(log_key) { "Downloading from `#{download_url}'" }
create_required_directories
download
verify_checksum!
- extract
end
#
# The version for this item in the cache. This is the digest of downloaded
# file and the URL where it was downloaded from.
#
+ # This method is called *before* clean but *after* fetch. Do not ever
+ # use the contents of the project_dir here.
+ #
# @return [String]
#
def version_for_cache
"download_url:#{source[:url]}|#{digest_type}:#{checksum}"
end
@@ -180,20 +185,23 @@
downloaded_amount = [step, reported_total].min
progress_bar.progress = downloaded_amount
}
file = open(download_url, options)
- FileUtils.cp(file.path, downloaded_file)
+ # This is a temporary file. Close and flush it before attempting to copy
+ # it over.
file.close
+ FileUtils.cp(file.path, downloaded_file)
+ file.unlink
rescue SocketError,
Errno::ECONNREFUSED,
Errno::ECONNRESET,
Errno::ENETUNREACH,
Timeout::Error,
OpenURI::HTTPError => e
if fetcher_retries != 0
- log.debug(log_key) { "Retrying failed download (#{fetcher_retries})..." }
+ log.info(log_key) { "Retrying failed download due to #{e} (#{fetcher_retries} retries left)..." }
fetcher_retries -= 1
retry
else
log.error(log_key) { "Download failed - #{e.class}!" }
raise
@@ -203,32 +211,99 @@
#
# Extract the downloaded file, using the magical logic based off of the
# ending file extension. In the rare event the file cannot be extracted, it
# is copied over as a raw file.
#
- def extract
- if command = extract_command
- log.info(log_key) { "Extracting `#{downloaded_file}' to `#{Config.source_dir}'" }
- shellout!(command)
+ def deploy
+ if downloaded_file.end_with?(*ALL_EXTENSIONS)
+ log.info(log_key) { "Extracting `#{safe_downloaded_file}' to `#{safe_project_dir}'" }
+ extract
else
- log.info(log_key) { "`#{downloaded_file}' is not an archive - copying to `#{project_dir}'" }
+ log.info(log_key) { "`#{safe_downloaded_file}' is not an archive - copying to `#{safe_project_dir}'" }
- if File.directory?(project_dir)
+ if File.directory?(downloaded_file)
# If the file itself was a directory, copy the whole thing over. This
# seems unlikely, because I do not think it is a possible to download
# a folder, but better safe than sorry.
- FileUtils.cp_r(downloaded_file, project_dir)
+ FileUtils.cp_r("#{downloaded_file}/.", project_dir)
else
# In the more likely case that we got a "regular" file, we want that
- # file to live **inside** the project directory.
- FileUtils.mkdir_p(project_dir)
- FileUtils.cp(downloaded_file, "#{project_dir}/")
+ # file to live **inside** the project directory. project_dir should already
+ # exist due to create_required_directories
+ FileUtils.cp(downloaded_file, project_dir)
end
end
end
#
+ # Extracts the downloaded archive file into project_dir.
+ #
+ # On windows, this is a fuster cluck and we allow users to specify the
+ # preferred extractor to be used. The default is to use tar. User overrides
+ # can be set in source[:extract] as:
+ # :tar - use tar.exe and fail on errors (default strategy).
+ # :seven_zip - use 7zip for all tar/compressed tar files on windows.
+ # :lax_tar - use tar.exe on windows but ignore errors.
+ #
+ # Both 7z and bsdtar have issues on windows.
+ #
+ # 7z cannot extract and untar at the same time. You need to extract to a
+ # temporary location and then extract again into project_dir.
+ #
+ # 7z also doesn't handle symlinks well. A symlink to a non-existent
+ # location simply results in a text file with the target path written in
+ # it. It does this without throwing any errors.
+ #
+ # bsdtar will exit(1) if it is encounters symlinks on windows. So we can't
+ # use shellout! directly.
+ #
+ # bsdtar will also exit(1) and fail to overwrite files at the destination
+ # during extraction if a file already exists at the destination and is
+ # marked read-only. This used to be a problem when we weren't properly
+ # cleaning an existing project_dir. It should be less of a problem now...
+ # but who knows.
+ #
+ def extract
+ # Only used by tar
+ compression_switch = ''
+ compression_switch = 'z' if downloaded_file.end_with?('gz')
+ compression_switch = '--lzma -' if downloaded_file.end_with?('lzma')
+ compression_switch = 'j' if downloaded_file.end_with?('bz2')
+ compression_switch = 'J' if downloaded_file.end_with?('xz')
+
+ if Ohai['platform'] == 'windows'
+ if downloaded_file.end_with?(*TAR_EXTENSIONS) && source[:extract] != :seven_zip
+ returns = [0]
+ returns << 1 if source[:extract] == :lax_tar
+
+ shellout!("tar.exe #{compression_switch}xf #{safe_downloaded_file} -C#{safe_project_dir}", returns: returns)
+ elsif downloaded_file.end_with?(*COMPRESSED_TAR_EXTENSIONS)
+ Dir.mktmpdir do |temp_dir|
+ log.debug(log_key) { "Temporarily extracting `#{safe_downloaded_file}' to `#{temp_dir}'" }
+
+ shellout!("7z.exe x #{safe_downloaded_file} -o#{windows_safe_path(temp_dir)} -r -y")
+
+ fname = File.basename(downloaded_file, File.extname(downloaded_file))
+ fname << ".tar" if downloaded_file.end_with?('tgz', 'txz')
+ next_file = windows_safe_path(File.join(temp_dir, fname))
+
+ log.debug(log_key) { "Temporarily extracting `#{next_file}' to `#{safe_project_dir}'" }
+ shellout!("7z.exe x #{next_file} -o#{safe_project_dir} -r -y")
+ end
+ else
+ shellout!("7z.exe x #{safe_downloaded_file} -o#{safe_project_dir} -r -y")
+ end
+ elsif downloaded_file.end_with?('.7z')
+ shellout!("7z x #{safe_downloaded_file} -o#{safe_project_dir} -r -y")
+ elsif downloaded_file.end_with?('.zip')
+ shellout!("unzip #{safe_downloaded_file} -d #{safe_project_dir}")
+ else
+ shellout!("#{tar} #{compression_switch}xf #{safe_downloaded_file} -C#{safe_project_dir}")
+ end
+ end
+
+ #
# The digest type defined in the software definition
#
# @raise [ChecksumMissing]
# if the checksum does not exist
#
@@ -240,11 +315,11 @@
end
raise ChecksumMissing.new(self)
end
#
- # Verify the downloaded file has the correct checksum.#
+ # Verify the downloaded file has the correct checksum.
#
# @raise [ChecksumMismatch]
# if the checksum does not match
#
def verify_checksum!
@@ -256,29 +331,23 @@
if expected != actual
raise ChecksumMismatch.new(self, expected, actual)
end
end
+ def safe_project_dir
+ windows_safe_path(project_dir)
+ end
+
+ def safe_downloaded_file
+ windows_safe_path(downloaded_file)
+ end
+
#
# The command to use for extracting this piece of software.
#
- # @return [String, nil]
+ # @return [[String]]
#
def extract_command
- if Ohai['platform'] == 'windows' && downloaded_file.end_with?(*WIN_7Z_EXTENSIONS)
- "7z.exe x #{windows_safe_path(downloaded_file)} -o#{Config.source_dir} -r -y"
- elsif Ohai['platform'] != 'windows' && downloaded_file.end_with?('.7z')
- "7z x #{windows_safe_path(downloaded_file)} -o#{Config.source_dir} -r -y"
- elsif Ohai['platform'] != 'windows' && downloaded_file.end_with?('.zip')
- "unzip #{windows_safe_path(downloaded_file)} -d #{Config.source_dir}"
- elsif downloaded_file.end_with?(*TAR_EXTENSIONS)
- compression_switch = 'z' if downloaded_file.end_with?('gz')
- compression_switch = 'j' if downloaded_file.end_with?('bz2')
- compression_switch = 'J' if downloaded_file.end_with?('xz')
- compression_switch = '' if downloaded_file.end_with?('tar')
-
- "#{tar} #{compression_switch}xf #{windows_safe_path(downloaded_file)} -C#{Config.source_dir}"
- end
end
#
# Primitively determine whether we should use gtar or tar to untar a file.
# If gtar is present, we will use gtar (AIX). Otherwise, we fallback to tar.