199 lines
5.8 KiB
Ruby
199 lines
5.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
|
|
|
|
# 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.
|
|
def initialize(name, provider, version, directory, **opts)
|
|
@name = name
|
|
@version = version
|
|
@provider = provider
|
|
@directory = directory
|
|
@metadata_url = opts[: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
|
|
|
|
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
|
|
|
|
# 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
|