This includes updates for resolving all warnings provided by Ruby for deprecations and/or removed methods. It also enables support for Ruby 2.7 in the specification constraint as all 2.7 related warnings are resolved with this changeset.
227 lines
6.8 KiB
Ruby
227 lines
6.8 KiB
Ruby
require 'fileutils'
|
|
require "tempfile"
|
|
|
|
require "json"
|
|
require "log4r"
|
|
|
|
require "vagrant/box_metadata"
|
|
require "vagrant/util/downloader"
|
|
require "vagrant/util/platform"
|
|
require "vagrant/util/safe_chdir"
|
|
require "vagrant/util/subprocess"
|
|
|
|
module Vagrant
|
|
# Represents a "box," which is a package Vagrant environment that is used
|
|
# as a base image when creating a new guest machine.
|
|
class Box
|
|
include Comparable
|
|
|
|
# Number of seconds to wait between checks for box updates
|
|
BOX_UPDATE_CHECK_INTERVAL = 3600
|
|
|
|
# The box name. This is the logical name used when adding the box.
|
|
#
|
|
# @return [String]
|
|
attr_reader :name
|
|
|
|
# This is the provider that this box is built for.
|
|
#
|
|
# @return [Symbol]
|
|
attr_reader :provider
|
|
|
|
# The version of this box.
|
|
#
|
|
# @return [String]
|
|
attr_reader :version
|
|
|
|
# This is the directory on disk where this box exists.
|
|
#
|
|
# @return [Pathname]
|
|
attr_reader :directory
|
|
|
|
# This is the metadata for the box. This is read from the "metadata.json"
|
|
# file that all boxes require.
|
|
#
|
|
# @return [Hash]
|
|
attr_reader :metadata
|
|
|
|
# This is the URL to the version info and other metadata for this
|
|
# box.
|
|
#
|
|
# @return [String]
|
|
attr_reader :metadata_url
|
|
|
|
# This is used to initialize a box.
|
|
#
|
|
# @param [String] name Logical name of the box.
|
|
# @param [Symbol] provider The provider that this box implements.
|
|
# @param [Pathname] directory The directory where this box exists on
|
|
# disk.
|
|
# @param [String] metadata_url Metadata URL for box
|
|
def initialize(name, provider, version, directory, metadata_url: nil)
|
|
@name = name
|
|
@version = version
|
|
@provider = provider
|
|
@directory = directory
|
|
@metadata_url = metadata_url
|
|
|
|
metadata_file = directory.join("metadata.json")
|
|
raise Errors::BoxMetadataFileNotFound, name: @name if !metadata_file.file?
|
|
|
|
begin
|
|
@metadata = JSON.parse(directory.join("metadata.json").read)
|
|
rescue JSON::ParserError
|
|
raise Errors::BoxMetadataCorrupted, name: @name
|
|
end
|
|
|
|
@logger = Log4r::Logger.new("vagrant::box")
|
|
end
|
|
|
|
# This deletes the box. This is NOT undoable.
|
|
def destroy!
|
|
# Delete the directory to delete the box.
|
|
FileUtils.rm_r(@directory)
|
|
|
|
# Just return true always
|
|
true
|
|
rescue Errno::ENOENT
|
|
# This means the directory didn't exist. Not a problem.
|
|
return true
|
|
end
|
|
|
|
# Checks if this box is in use according to the given machine
|
|
# index and returns the entries that appear to be using the box.
|
|
#
|
|
# The entries returned, if any, are not tested for validity
|
|
# with {MachineIndex::Entry#valid?}, so the caller should do that
|
|
# if the caller cares.
|
|
#
|
|
# @param [MachineIndex] index
|
|
# @return [Array<MachineIndex::Entry>]
|
|
def in_use?(index)
|
|
results = []
|
|
index.each do |entry|
|
|
box_data = entry.extra_data["box"]
|
|
next if !box_data
|
|
|
|
# If all the data matches, record it
|
|
if box_data["name"] == self.name &&
|
|
box_data["provider"] == self.provider.to_s &&
|
|
box_data["version"] == self.version.to_s
|
|
results << entry
|
|
end
|
|
end
|
|
|
|
return nil if results.empty?
|
|
results
|
|
end
|
|
|
|
# Loads the metadata URL and returns the latest metadata associated
|
|
# with this box.
|
|
#
|
|
# @param [Hash] download_options Options to pass to the downloader.
|
|
# @return [BoxMetadata]
|
|
def load_metadata(download_options={})
|
|
tf = Tempfile.new("vagrant-load-metadata")
|
|
tf.close
|
|
|
|
url = @metadata_url
|
|
if File.file?(url) || url !~ /^[a-z0-9]+:.*$/i
|
|
url = File.expand_path(url)
|
|
url = Util::Platform.cygwin_windows_path(url)
|
|
url = "file:#{url}"
|
|
end
|
|
|
|
opts = { headers: ["Accept: application/json"] }.merge(download_options)
|
|
Util::Downloader.new(url, tf.path, opts).download!
|
|
BoxMetadata.new(File.open(tf.path, "r"))
|
|
rescue Errors::DownloaderError => e
|
|
raise Errors::BoxMetadataDownloadError,
|
|
message: e.extra_data[:message]
|
|
ensure
|
|
tf.unlink if tf
|
|
end
|
|
|
|
# Checks if the box has an update and returns the metadata, version,
|
|
# and provider. If the box doesn't have an update that satisfies the
|
|
# constraints, it will return nil.
|
|
#
|
|
# This will potentially make a network call if it has to load the
|
|
# metadata from the network.
|
|
#
|
|
# @param [String] version Version constraints the update must
|
|
# satisfy. If nil, the version constrain defaults to being a
|
|
# larger version than this box.
|
|
# @return [Array]
|
|
def has_update?(version=nil, download_options: {})
|
|
if !@metadata_url
|
|
raise Errors::BoxUpdateNoMetadata, name: @name
|
|
end
|
|
|
|
if download_options.delete(:automatic_check) && !automatic_update_check_allowed?
|
|
@logger.info("Skipping box update check")
|
|
return
|
|
end
|
|
|
|
version += ", " if version
|
|
version ||= ""
|
|
version += "> #{@version}"
|
|
md = self.load_metadata(download_options)
|
|
newer = md.version(version, provider: @provider)
|
|
return nil if !newer
|
|
|
|
[md, newer, newer.provider(@provider)]
|
|
end
|
|
|
|
# Check if a box update check is allowed. Uses a file
|
|
# in the box data directory to track when the last auto
|
|
# update check was performed and returns true if the
|
|
# BOX_UPDATE_CHECK_INTERVAL has passed.
|
|
#
|
|
# @return [Boolean]
|
|
def automatic_update_check_allowed?
|
|
check_path = directory.join("box_update_check")
|
|
if check_path.exist?
|
|
last_check_span = Time.now.to_i - check_path.mtime.to_i
|
|
if last_check_span < BOX_UPDATE_CHECK_INTERVAL
|
|
@logger.info("box update check is under the interval threshold")
|
|
return false
|
|
end
|
|
end
|
|
FileUtils.touch(check_path)
|
|
true
|
|
end
|
|
|
|
# This repackages this box and outputs it to the given path.
|
|
#
|
|
# @param [Pathname] path The full path (filename included) of where
|
|
# to output this box.
|
|
# @return [Boolean] true if this succeeds.
|
|
def repackage(path)
|
|
@logger.debug("Repackaging box '#{@name}' to: #{path}")
|
|
|
|
Util::SafeChdir.safe_chdir(@directory) do
|
|
# Find all the files in our current directory and tar it up!
|
|
files = Dir.glob(File.join(".", "**", "*")).select { |f| File.file?(f) }
|
|
|
|
# Package!
|
|
Util::Subprocess.execute("bsdtar", "-czf", path.to_s, *files)
|
|
end
|
|
|
|
@logger.info("Repackaged box '#{@name}' successfully: #{path}")
|
|
|
|
true
|
|
end
|
|
|
|
# Implemented for comparison with other boxes. Comparison is
|
|
# implemented by comparing names and providers.
|
|
def <=>(other)
|
|
return super if !other.is_a?(self.class)
|
|
|
|
# Comparison is done by composing the name and provider
|
|
"#{@name}-#{@version}-#{@provider}" <=>
|
|
"#{other.name}-#{other.version}-#{other.provider}"
|
|
end
|
|
end
|
|
end
|