Add architecture support for boxes

Introduce support for handling box architecture. Adds a new
`box_architecture` setting that defaults to `:auto` which will perform
automatic detection of the host system, but can be overridden with a
custom value. Can also be set to `nil` which will result in it fetching
the box flagged with the default architecture within the metadata.

Box collection has been modified to allow existing boxes already
downloaded and unpacked to still function as expected when architecture
information is not available.
This commit is contained in:
Chris Roberts 2023-07-28 10:40:02 -07:00
parent 5513173758
commit 51adb12547
28 changed files with 820 additions and 275 deletions

View File

@ -165,6 +165,7 @@ module Vagrant
env,
checksum: env[:box_checksum],
checksum_type: env[:box_checksum_type],
architecture: env[:architecture]
)
end
@ -179,6 +180,9 @@ module Vagrant
# a Atlas server URL.
def add_from_metadata(url, env, expanded)
original_url = env[:box_url]
architecture = env[:box_architecture]
display_architecture = architecture == :auto ?
Util::Platform.architecture : architecture
provider = env[:box_provider]
provider = Array(provider) if provider
version = env[:box_version]
@ -228,12 +232,17 @@ module Vagrant
end
metadata_version = metadata.version(
version || ">= 0", provider: provider)
version || ">= 0",
provider: provider,
architecture: architecture,
)
if !metadata_version
if provider && !metadata.version(">= 0", provider: provider)
if provider && !metadata.version(">= 0", provider: provider, architecture: architecture)
raise Errors::BoxAddNoMatchingProvider,
name: metadata.name,
requested: provider,
requested: [provider,
display_architecture ? "(#{display_architecture})" : nil
].compact.join(" "),
url: display_url
else
raise Errors::BoxAddNoMatchingVersion,
@ -249,16 +258,16 @@ module Vagrant
# If a provider was specified, make sure we get that specific
# version.
provider.each do |p|
metadata_provider = metadata_version.provider(p)
metadata_provider = metadata_version.provider(p, architecture)
break if metadata_provider
end
elsif metadata_version.providers.length == 1
elsif metadata_version.providers(architecture).length == 1
# If we have only one provider in the metadata, just use that
# provider.
metadata_provider = metadata_version.provider(
metadata_version.providers.first)
metadata_version.providers.first, architecture)
else
providers = metadata_version.providers.sort
providers = metadata_version.providers(architecture).sort
choice = 0
options = providers.map do |p|
@ -279,7 +288,7 @@ module Vagrant
end
metadata_provider = metadata_version.provider(
providers[choice-1])
providers[choice-1], architecture)
end
provider_url = metadata_provider.url
@ -302,6 +311,7 @@ module Vagrant
env,
checksum: metadata_provider.checksum,
checksum_type: metadata_provider.checksum_type,
architecture: architecture,
)
end
@ -317,16 +327,21 @@ module Vagrant
# @param [Hash] env
# @return [Box]
def box_add(urls, name, version, provider, md_url, env, **opts)
display_architecture = opts[:architecture] == :auto ?
Util::Platform.architecture : opts[:architecture]
env[:ui].output(I18n.t(
"vagrant.box_add_with_version",
name: name,
version: version,
providers: Array(provider).join(", ")))
providers: [
provider,
display_architecture ? "(#{display_architecture})" : nil
].compact.join(" ")))
# Verify the box we're adding doesn't already exist
if provider && !env[:box_force]
box = env[:box_collection].find(
name, provider, version)
name, provider, version, opts[:architecture])
if box
raise Errors::BoxAlreadyExists,
name: name,
@ -377,7 +392,9 @@ module Vagrant
box_url, name, version,
force: env[:box_force],
metadata_url: md_url,
providers: provider)
providers: provider,
architecture: env[:box_architecture]
)
ensure
# Make sure we delete the temporary file after we add it,
# unless we were interrupted, in which case we keep it around
@ -396,7 +413,10 @@ module Vagrant
"vagrant.box_added",
name: box.name,
version: box.version,
provider: box.provider))
provider: [
provider,
display_architecture ? "(#{display_architecture})" : nil
].compact.join(" ")))
# Store the added box in the env for future middleware
env[:box_added] = box

View File

@ -15,112 +15,162 @@ module Vagrant
def call(env)
box_name = env[:box_name]
box_provider = env[:box_provider]
box_provider = box_provider.to_sym if box_provider
box_architecture = env[:box_architecture] if env[:box_architecture]
box_provider = env[:box_provider].to_sym if env[:box_provider]
box_version = env[:box_version]
box_remove_all_versions = env[:box_remove_all_versions]
box_remove_all_providers = env[:box_remove_all_providers]
box_remove_all_architectures = env[:box_remove_all_architectures]
boxes = {}
env[:box_collection].all.each do |n, v, p|
boxes[n] ||= {}
boxes[n][p] ||= []
boxes[n][p] << v
box_info = Util::HashWithIndifferentAccess.new
env[:box_collection].all.each do |name, version, provider, architecture|
next if name != box_name
box_info[version] ||= Util::HashWithIndifferentAccess.new
box_info[version][provider] ||= []
box_info[version][provider] << architecture
end
all_box = boxes[box_name]
if !all_box
# If there's no box info, then the box doesn't exist here
if box_info.empty?
raise Errors::BoxRemoveNotFound, name: box_name
end
all_versions = nil
if !box_provider
if all_box.length == 1
# There is only one provider, just use that.
all_versions = all_box.values.first
box_provider = all_box.keys.first
else
raise Errors::BoxRemoveMultiProvider,
name: box_name,
providers: all_box.keys.map(&:to_s).sort.join(", ")
end
else
all_versions = all_box[box_provider]
if !all_versions
raise Errors::BoxRemoveProviderNotFound,
name: box_name,
provider: box_provider.to_s,
providers: all_box.keys.map(&:to_s).sort.join(", ")
end
end
if !box_version
if all_versions.length == 1
# There is only one version, just use that.
box_version = all_versions.first
elsif not box_remove_all_versions
# There are multiple versions, we can't choose.
# Filtering only matters if not removing all versions
if !box_remove_all_versions
# If no version was provided, and not removing all versions,
# only allow one version to proceed
if !box_version && box_info.size > 1
raise Errors::BoxRemoveMultiVersion,
name: box_name,
provider: box_provider.to_s,
versions: all_versions.sort.map { |k| " * #{k}" }.join("\n")
versions: box_info.keys.sort.map { |k| " * #{k}" }.join("\n")
end
# If a version was provided, make sure it exists
if box_version
if !box_info.keys.include?(box_version)
raise Errors::BoxRemoveVersionNotFound,
name: box_name,
version: box_version,
versions: box_info.keys.sort.map { |k| " * #{k}" }.join("\n")
else
box_info.delete_if { |k, _| k != box_version }
end
end
# Only a single version remains
box_version = box_info.keys.first
# Further filtering only matters if not removing all providers
if !box_remove_all_providers
# If no provider was given, check if there are more
# than a single provider for the version
if !box_provider && box_info.values.first.size > 1
raise Errors::BoxRemoveMultiProvider,
name: box_name,
version: box_version,
providers: box_info.values.first.keys.map(&:to_s).sort.join(", ")
end
# If a provider was given, check the version has it
if box_provider
if !box_info.values.first.key?(box_provider)
raise Errors::BoxRemoveProviderNotFound,
name: box_name,
version: box_version,
provider: box_provider.to_s,
providers: box_info.values.first.keys.map(&:to_s).sort.join(", ")
else
box_info.values.first.delete_if { |k, _| k.to_s != box_provider.to_s }
end
end
# Only a single provider remains
box_provider = box_info.values.first.keys.first
# Further filtering only matters if not removing all architectures
if !box_remove_all_architectures
# If no architecture was given, check if there are more
# than a single architecture for the provider in version
if !box_architecture && box_info.values.first.values.first.size > 1
raise Errors::BoxRemoveMultiArchitecture,
name: box_name,
version: box_version,
provider: box_provider.to_s,
architectures: box_info.values.first.values.first.sort.join(", ")
end
# If architecture was given, check the provider for the version has it
if box_architecture
if !box_info.values.first.values.first.include?(box_architecture)
raise Errors::BoxRemoveArchitectureNotFound,
name: box_name,
version: box_version,
provider: box_provider.to_s,
architecture: box_architecture,
architectures: box_info.values.first.values.first.sort.join(", ")
else
box_info.values.first.values.first.delete_if { |v| v != box_architecture }
end
end
end
end
elsif !all_versions.include?(box_version)
raise Errors::BoxRemoveVersionNotFound,
name: box_name,
provider: box_provider.to_s,
version: box_version,
versions: all_versions.sort.map { |k| " * #{k}" }.join("\n")
end
versions_to_remove = [box_version]
versions_to_remove = all_versions if box_remove_all_versions
box_info.each do |version, provider_info|
provider_info.each do |provider, architecture_info|
provider = provider.to_sym
architecture_info.each do |architecture|
box = env[:box_collection].find(
box_name, provider, version, architecture
)
versions_to_remove.sort.each do |version_to_remove|
box = env[:box_collection].find(
box_name, box_provider, box_version)
# Verify that this box is not in use by an active machine,
# otherwise warn the user.
users = box.in_use?(env[:machine_index]) || []
users = users.find_all { |u| u.valid?(env[:home_path]) }
if !users.empty?
# Build up the output to show the user.
users = users.map do |entry|
"#{entry.name} (ID: #{entry.id})"
end.join("\n")
# Verify that this box is not in use by an active machine,
# otherwise warn the user.
users = box.in_use?(env[:machine_index]) || []
users = users.find_all { |u| u.valid?(env[:home_path]) }
if !users.empty?
# Build up the output to show the user.
users = users.map do |entry|
"#{entry.name} (ID: #{entry.id})"
end.join("\n")
force_key = :force_confirm_box_remove
message = I18n.t(
"vagrant.commands.box.remove_in_use_query",
name: box.name,
architecture: box.architecture,
provider: box.provider,
version: box.version,
users: users) + " "
force_key = :force_confirm_box_remove
message = I18n.t(
"vagrant.commands.box.remove_in_use_query",
name: box.name,
provider: box.provider,
version: box.version,
users: users) + " "
# Ask the user if we should do this
stack = Builder.new.tap do |b|
b.use Confirm, message, force_key
end
# Ask the user if we should do this
stack = Builder.new.tap do |b|
b.use Confirm, message, force_key
end
# Keep used boxes, even if "force" is applied
keep_used_boxes = env[:keep_used_boxes]
# Keep used boxes, even if "force" is applied
keep_used_boxes = env[:keep_used_boxes]
result = env[:action_runner].run(stack, env)
if !result[:result] || keep_used_boxes
# They said "no", so continue with the next box
next
end
end
result = env[:action_runner].run(stack, env)
if !result[:result] || keep_used_boxes
# They said "no", so continue with the next box
next
env[:ui].info(I18n.t("vagrant.commands.box.removing",
name: box.name,
architecture: box.architecture,
provider: box.provider,
version: box.version))
box.destroy!
env[:box_collection].clean(box.name)
# Passes on the removed box to the rest of the middleware chain
env[:box_removed] = box
end
end
env[:ui].info(I18n.t("vagrant.commands.box.removing",
name: box.name,
provider: box.provider,
version: box.version))
box.destroy!
env[:box_collection].clean(box.name)
# Passes on the removed box to the rest of the middleware chain
env[:box_removed] = box
end
@app.call(env)

View File

@ -87,6 +87,7 @@ module Vagrant
env[:action_runner].run(Vagrant::Action.action_box_add, env.merge({
box_name: machine.config.vm.box,
box_url: machine.config.vm.box_url || machine.config.vm.box,
box_architecture: machine.config.vm.box_architecture,
box_server_url: machine.config.vm.box_server_url,
box_provider: box_formats,
box_version: machine.config.vm.box_version,

View File

@ -37,6 +37,11 @@ module Vagrant
# @return [Symbol]
attr_reader :provider
# This is the architecture that this box is build for.
#
# @return [String]
attr_reader :architecture
# The version of this box.
#
# @return [String]
@ -65,13 +70,15 @@ module Vagrant
# @param [Symbol] provider The provider that this box implements.
# @param [Pathname] directory The directory where this box exists on
# disk.
# @param [String] architecture Architecture the box was built for
# @param [String] metadata_url Metadata URL for box
# @param [Hook] hook A hook to apply to the box downloader, for example, for authentication
def initialize(name, provider, version, directory, metadata_url: nil, hook: nil)
def initialize(name, provider, version, directory, architecture: nil, metadata_url: nil, hook: nil)
@name = name
@version = version
@provider = provider
@directory = directory
@architecture = architecture
@metadata_url = metadata_url
@hook = hook
@ -130,6 +137,7 @@ module Vagrant
# If all the data matches, record it
if box_data["name"] == self.name &&
box_data["provider"] == self.provider.to_s &&
box_data["architecture"] == self.architecture &&
box_data["version"] == self.version.to_s
results << entry
end
@ -194,10 +202,10 @@ module Vagrant
version ||= ""
version += "> #{@version}"
md = self.load_metadata(download_options)
newer = md.version(version, provider: @provider)
newer = md.version(version, provider: @provider, architecture: @architecture)
return nil if !newer
[md, newer, newer.provider(@provider)]
[md, newer, newer.provider(@provider, @architecture)]
end
# Check if a box update check is allowed. Uses a file
@ -241,13 +249,13 @@ module Vagrant
end
# Implemented for comparison with other boxes. Comparison is
# implemented by comparing names and providers.
# implemented by comparing names, providers, and architectures.
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}"
"#{@name}-#{@version}-#{@provider}-#{@architecture}" <=>
"#{other.name}-#{other.version}-#{other.provider}-#{other.architecture}"
end
end
end

View File

@ -28,7 +28,7 @@ module Vagrant
# expects in order to easily manage and modify boxes. The folder structure
# is the following:
#
# COLLECTION_ROOT/BOX_NAME/PROVIDER/metadata.json
# COLLECTION_ROOT/BOX_NAME/PROVIDER/[ARCHITECTURE]/metadata.json
#
# Where:
#
@ -38,6 +38,8 @@ module Vagrant
# the user of Vagrant.
# * PROVIDER - The provider that the box was built for (VirtualBox,
# VMware, etc.).
# * ARCHITECTURE - Optional. The architecture that the box was built
# for (amd64, arm64, 386, etc.).
# * metadata.json - A simple JSON file that at the bare minimum
# contains a "provider" key that matches the provider for the
# box. This metadata JSON, however, can contain anything.
@ -80,14 +82,15 @@ module Vagrant
# @param [Boolean] force If true, any existing box with the same name
# and provider will be replaced.
def add(path, name, version, **opts)
architecture = opts[:architecture]
providers = opts[:providers]
providers = Array(providers) if providers
provider = nil
# A helper to check if a box exists. We store this in a variable
# since we call it multiple times.
check_box_exists = lambda do |box_formats|
box = find(name, box_formats, version)
check_box_exists = lambda do |box_formats, box_architecture|
box = find(name, box_formats, version, box_architecture)
next if !box
if !opts[:force]
@ -108,12 +111,12 @@ module Vagrant
with_collection_lock do
log_provider = providers ? providers.join(", ") : "any provider"
@logger.debug("Adding box: #{name} (#{log_provider}) from #{path}")
@logger.debug("Adding box: #{name} (#{log_provider} - #{architecture.inspect}) from #{path}")
# Verify the box doesn't exist early if we're given a provider. This
# can potentially speed things up considerably since we don't need
# to unpack any files.
check_box_exists.call(providers) if providers
check_box_exists.call(providers, architecture) if providers
# Create a temporary directory since we're not sure at this point if
# the box we're unpackaging already exists (if no provider was given)
@ -154,7 +157,7 @@ module Vagrant
end
else
# Verify the box doesn't already exist
check_box_exists.call([box_provider])
check_box_exists.call([box_provider], architecture)
end
# We weren't given a provider, so store this one.
@ -167,7 +170,16 @@ module Vagrant
@logger.debug("Box directory: #{box_dir}")
# This is the final directory we'll move it to
final_dir = box_dir.join(provider.to_s)
provider_dir = box_dir.join(provider.to_s)
final_dir = provider_dir
@logger.debug("Provider directory: #{provider_dir}")
# If architecture is set, unpack into architecture specific directory
if architecture
arch = architecture
arch = Util::Platform.architecture if architecture == :auto
final_dir = provider_dir.join(arch)
end
if final_dir.exist?
@logger.debug("Removing existing provider directory...")
final_dir.rmtree
@ -209,13 +221,13 @@ module Vagrant
end
# Return the box
find(name, provider, version)
find(name, provider, version, architecture)
end
# This returns an array of all the boxes on the system, given by
# their name and their provider.
#
# @return [Array] Array of `[name, version, provider]` of the boxes
# @return [Array] Array of `[name, version, provider, architecture]` of the boxes
# installed on this system.
def all
results = []
@ -252,7 +264,16 @@ module Vagrant
if provider.directory? && provider.join("metadata.json").file?
provider_name = provider.basename.to_s.to_sym
@logger.debug("Box: #{box_name} (#{provider_name}, #{version})")
results << [box_name, version, provider_name]
results << [box_name, version, provider_name, nil]
elsif provider.directory?
provider.children(true).each do |architecture|
provider_name = provider.basename.to_s.to_sym
if architecture.directory? && architecture.join("metadata.json").file?
architecture_name = architecture.basename.to_s.to_sym
@logger.debug("Box: #{box_name} (#{provider_name} (#{architecture_name}), #{version})")
results << [box_name, version, provider_name, architecture_name]
end
end
else
@logger.debug("Invalid box #{box_name}, ignoring: #{provider}")
end
@ -262,7 +283,7 @@ module Vagrant
end
# Sort the list to group like providers and properly ordered versions
results.sort_by! do |box_result|
[box_result[0], box_result[2], Gem::Version.new(box_result[1])]
[box_result[0], box_result[2], Gem::Version.new(box_result[1]), box_result[3]]
end
results
end
@ -274,8 +295,10 @@ module Vagrant
# @param [String] version Version constraints to adhere to. Example:
# "~> 1.0" or "= 1.0, ~> 1.1"
# @return [Box] The box found, or `nil` if not found.
def find(name, providers, version)
def find(name, providers, version, box_architecture=:auto)
providers = Array(providers)
architecture = box_architecture
architecture = Util::Platform.architecture if architecture == :auto
# Build up the requirements we have
requirements = version.to_s.split(",").map do |v|
@ -321,6 +344,19 @@ module Vagrant
providers.each do |provider|
provider_dir = versiondir.join(provider.to_s)
next if !provider_dir.directory?
# If architecture is defined then the box should be within
# a subdirectory. However, if box_architecture value is :auto
# and the box provider directory exists but the architecture
# directory does not, we will use the box provider directory. This
# allows Vagrant to work correctly with boxes which were added
# prior to the addition of architecture support
final_dir = provider_dir
if architecture
arch_dir = provider_dir.join(architecture.to_s)
next if !arch_dir.directory? && box_architecture != :auto
end
final_dir = arch_dir if arch_dir && arch_dir.directory?
@logger.info("Box found: #{name} (#{provider})")
metadata_url = nil
@ -334,8 +370,8 @@ module Vagrant
end
return Box.new(
name, provider, version_dir_map[v.to_s], provider_dir,
metadata_url: metadata_url, hook: @hook
name, provider, version_dir_map[v.to_s], final_dir,
architecture: architecture, metadata_url: metadata_url, hook: @hook
)
end
end

View File

@ -38,7 +38,7 @@ module Vagrant
@description = @raw["description"]
@version_map = (@raw["versions"] || []).map do |v|
begin
[Gem::Version.new(v["version"]), v]
[Gem::Version.new(v["version"]), Version.new(v)]
rescue ArgumentError
raise Errors::BoxMetadataMalformedVersion,
version: v["version"].to_s
@ -61,11 +61,27 @@ module Vagrant
providers = nil
providers = Array(opts[:provider]).map(&:to_sym) if opts[:provider]
# NOTE: The :auto value is not expanded here since no architecture
# value comparisons are being done within this method
architecture = opts.fetch(:architecture, :auto)
@version_map.keys.sort.reverse.each do |v|
next if !requirements.all? { |r| r.satisfied_by?(v) }
version = Version.new(@version_map[v])
next if (providers & version.providers).empty? if providers
version = @version_map[v]
valid_providers = version.providers
# If filtering by provider(s), apply filter
valid_providers &= providers if providers
# Skip if no valid providers are found
next if valid_providers.empty?
# Skip if no valid provider includes support
# the desired architecture
next if architecture && valid_providers.none? { |p|
version.provider(p, architecture)
}
return version
end
@ -79,20 +95,25 @@ module Vagrant
#
# @return[Array<String>]
def versions(**opts)
provider = nil
architecture = opts[:architecture]
provider = opts[:provider].to_sym if opts[:provider]
if provider
@version_map.select do |version, raw|
if raw["providers"]
raw["providers"].detect do |p|
p["name"].to_sym == provider
end
end
end.keys.sort.map(&:to_s)
else
@version_map.keys.sort.map(&:to_s)
# Return full version list if no filters provided
if provider.nil? && architecture.nil?
return @version_map.keys.sort.map(&:to_s)
end
# If a specific provider is not provided, filter
# only on architecture
if provider.nil?
return @version_map.select { |_, version|
!version.providers(architecture).empty?
}.keys.sort.map(&:to_s)
end
@version_map.select { |_, version|
version.provider(provider, architecture)
}.keys.sort.map(&:to_s)
end
# Represents a single version within the metadata.
@ -106,26 +127,81 @@ module Vagrant
return if !raw
@version = raw["version"]
@provider_map = (raw["providers"] || []).map do |p|
[p["name"].to_sym, p]
@providers = raw.fetch("providers", []).map do |data|
Provider.new(data)
end
@provider_map = Hash[@provider_map]
@provider_map = @providers.group_by(&:name)
@provider_map = Util::HashWithIndifferentAccess.new(@provider_map)
end
# Returns a [Provider] for the given name, or nil if it isn't
# supported by this version.
def provider(name)
p = @provider_map[name.to_sym]
return nil if !p
Provider.new(p)
def provider(name, architecture=nil)
name = name.to_sym
arch_name = architecture
arch_name = Util::Platform.architecture if arch_name == :auto
arch_name = arch_name.to_s if arch_name
# If the provider doesn't exist in the map, return immediately
return if !@provider_map.key?(name)
# If the arch_name value is set, filter based
# on architecture and return match if found. If
# no match is found and architecture wasn't automatically
# detected, return nil as an explicit match is
# being requested
if arch_name
match = @provider_map[name].detect do |p|
p.architecture == arch_name
end
return match if match || architecture != :auto
end
# If the passed architecture value was :auto and no explicit
# match for the architecture was found, check for a provider
# that is flagged as the default architecture, and has an
# architecture value of "unknown"
#
# NOTE: This preserves expected behavior with legacy boxes
if architecture == :auto
match = @provider_map[name].detect do |p|
p.architecture == "unknown" &&
p.default_architecture
end
return match if match
end
# If the architecture value is set to nil, then just return
# whatever is defined as the default architecture
if architecture.nil?
match = @provider_map[name].detect(&:default_architecture)
return match if match
end
# The metadata consumed may not include architecture information,
# in which case the match would just be the single provider
# defined within the provider map for the name
if @provider_map[name].size == 1 && !@provider_map[name].first.architecture_support?
return @provider_map[name].first
end
# Otherwise, there is no match
nil
end
# Returns the providers that are available for this version
# of the box.
#
# @return [Array<Symbol>]
def providers
@provider_map.keys.map(&:to_sym)
def providers(architecture=nil)
return @provider_map.keys.map(&:to_sym) if architecture.nil?
@provider_map.keys.find_all { |k|
provider(k, architecture)
}.map(&:to_sym)
end
end
@ -152,11 +228,27 @@ module Vagrant
# @return [String]
attr_accessor :checksum_type
# The architecture of the box
#
# @return [String]
attr_accessor :architecture
# Marked as the default architecture
#
# @return [Boolean, NilClass]
attr_accessor :default_architecture
def initialize(raw, **_)
@name = raw["name"]
@url = raw["url"]
@checksum = raw["checksum"]
@checksum_type = raw["checksum_type"]
@architecture = raw["architecture"]
@default_architecture = raw["default_architecture"]
end
def architecture_support?
!@default_architecture.nil?
end
end
end

View File

@ -199,6 +199,10 @@ module Vagrant
error_key(:box_not_found_with_provider)
end
class BoxNotFoundWithProviderArchitecture < VagrantError
error_key(:box_not_found_with_provider_architecture)
end
class BoxNotFoundWithProviderAndVersion < VagrantError
error_key(:box_not_found_with_provider_and_version)
end
@ -211,6 +215,10 @@ module Vagrant
error_key(:box_remove_not_found)
end
class BoxRemoveArchitectureNotFound < VagrantError
error_key(:box_remove_architecture_not_found)
end
class BoxRemoveProviderNotFound < VagrantError
error_key(:box_remove_provider_not_found)
end
@ -219,6 +227,10 @@ module Vagrant
error_key(:box_remove_version_not_found)
end
class BoxRemoveMultiArchitecture < VagrantError
error_key(:box_remove_multi_architecture)
end
class BoxRemoveMultiProvider < VagrantError
error_key(:box_remove_multi_provider)
end
@ -239,6 +251,10 @@ module Vagrant
error_key(:box_update_multi_provider)
end
class BoxUpdateMultiArchitecture < VagrantError
error_key(:box_update_multi_architecture)
end
class BoxUpdateNoMetadata < VagrantError
error_key(:box_update_no_metadata)
end

View File

@ -326,6 +326,7 @@ module Vagrant
entry.local_data_path = @env.local_data_path
entry.name = @name.to_s
entry.provider = @provider_name.to_s
entry.architecture = @architecture
entry.state = "preparing"
entry.vagrantfile_path = @env.root_path
entry.vagrantfile_name = @env.vagrantfile_name
@ -334,6 +335,7 @@ module Vagrant
entry.extra_data["box"] = {
"name" => @box.name,
"provider" => @box.provider.to_s,
"architecture" => @box.architecture,
"version" => @box.version.to_s,
}
end
@ -587,6 +589,7 @@ module Vagrant
entry.extra_data["box"] = {
"name" => @box.name,
"provider" => @box.provider.to_s,
"architecture" => @box.architecture,
"version" => @box.version.to_s,
}
end

View File

@ -30,6 +30,7 @@ module Vagrant
# "uuid": {
# "name": "foo",
# "provider": "vmware_fusion",
# "architecture": "amd64",
# "data_path": "/path/to/data/dir",
# "vagrantfile_path": "/path/to/Vagrantfile",
# "state": "running",
@ -381,6 +382,11 @@ module Vagrant
# @return [String]
attr_accessor :provider
# The name of the architecture.
#
# @return [String]
attr_accessor :architecture
# The last known state of this machine.
#
# @return [String]
@ -427,6 +433,7 @@ module Vagrant
@local_data_path = raw["local_data_path"]
@name = raw["name"]
@provider = raw["provider"]
@architecture = raw["architecture"]
@state = raw["state"]
@full_state = raw["full_state"]
@vagrantfile_name = raw["vagrantfile_name"]
@ -510,6 +517,7 @@ module Vagrant
"local_data_path" => @local_data_path.to_s,
"name" => @name,
"provider" => @provider,
"architecture" => @architecture,
"state" => @state,
"vagrantfile_name" => @vagrantfile_name,
"vagrantfile_path" => @vagrantfile_path.to_s,

View File

@ -46,7 +46,7 @@ module Vagrant
autoload :Numeric, 'vagrant/util/numeric'
autoload :Platform, 'vagrant/util/platform'
autoload :Powershell, 'vagrant/util/powershell'
autoload :Presence, 'vagrant/util/presence'
autoload :Presence, 'vagrant/util/presence'
autoload :Retryable, 'vagrant/util/retryable'
autoload :SafeChdir, 'vagrant/util/safe_chdir'
autoload :SafeEnv, 'vagrant/util/safe_env'

View File

@ -17,6 +17,29 @@ module Vagrant
class Platform
class << self
# Detect architecture of host system
#
# @return [String]
def architecture
if !defined?(@_host_architecture)
if ENV["VAGRANT_HOST_ARCHITECTURE"].to_s != ""
return @_host_architecture = ENV["VAGRANT_HOST_ARCHITECTURE"]
end
@_host_architecture = case RbConfig::CONFIG["target_cpu"]
when "x86_64"
"amd64"
when "i386"
"386"
when "arm64", "aarch64"
"arm64"
else
RbConfig::CONFIG["target_cpu"]
end
end
@_host_architecture
end
def logger
if !defined?(@_logger)
@_logger = Log4r::Logger.new("vagrant::util::platform")

View File

@ -202,7 +202,7 @@ module Vagrant
# Load the box Vagrantfile, if there is one
if !config.vm.box.to_s.empty? && boxes
box = boxes.find(config.vm.box, box_formats, config.vm.box_version)
box = boxes.find(config.vm.box, box_formats, config.vm.box_version, config.vm.box_architecture)
if box
box_vagrantfile = find_vagrantfile(box.directory)
if box_vagrantfile && !config.vm.ignore_box_vagrantfile

View File

@ -12,7 +12,9 @@ module VagrantPlugins
include DownloadMixins
def execute
options = {}
options = {
architecture: :auto,
}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant box add [options] <name, url, or path>"
@ -34,6 +36,10 @@ module VagrantPlugins
options[:location_trusted] = l
end
o.on("-a", "--architecture ARCH", String, "Architecture the box should satisfy") do |a|
options[:architecture] = a
end
o.on("--provider PROVIDER", String, "Provider the box should satisfy") do |p|
options[:provider] = p
end
@ -82,6 +88,7 @@ module VagrantPlugins
box_url: url,
box_name: options[:name],
box_provider: options[:provider],
box_architecture: options[:architecture],
box_version: options[:version],
box_checksum_type: options[:checksum_type],
box_checksum: options[:checksum],

View File

@ -44,26 +44,33 @@ module VagrantPlugins
longest_box = boxes.max_by { |x| x[0].length }
longest_box_length = longest_box[0].length
# Go through each box and output the information about it. We
# ignore the "v1" param for now since I'm not yet sure if its
# important for the user to know what boxes need to be upgraded
# and which don't, since we plan on doing that transparently.
boxes.each do |name, version, provider|
@env.ui.info("#{name.ljust(longest_box_length)} (#{provider}, #{version})")
# Group boxes by name and version and start iterating
boxes.group_by { |b| [b[0], b[1]] }.each do |box_info, box_data|
name, version = box_info
# Now group by provider so we can collect common architectures
box_data.group_by { |b| b[2] }.each do |provider, data|
architectures = data.map { |d| d.last }.compact.sort.uniq
meta_info = [provider, version]
if !architectures.empty?
meta_info << "(#{architectures.join(", ")})"
end
@env.ui.info("#{name.ljust(longest_box_length)} (#{meta_info.join(", ")})")
data.each do |arch_info|
@env.ui.machine("box-name", name)
@env.ui.machine("box-provider", provider)
@env.ui.machine("box-version", version)
@env.ui.machine("box-architecture", arch_info.last || "n/a")
info_file = @env.boxes.find(name, provider, version, arch_info.last).
directory.join("info.json")
if info_file.file?
info = JSON.parse(info_file.read)
info.each do |k, v|
@env.ui.machine("box-info", k, v)
@env.ui.machine("box-name", name)
@env.ui.machine("box-provider", provider)
@env.ui.machine("box-version", version)
info_file = @env.boxes.find(name, provider, version).
directory.join("info.json")
if info_file.file?
info = JSON.parse(info_file.read)
info.each do |k, v|
@env.ui.machine("box-info", k, v)
if extra_info
@env.ui.info(" - #{k}: #{v}", prefix: false)
if extra_info
@env.ui.info(" - #{k}: #{v}", prefix: false)
end
end
end
end
end

View File

@ -21,6 +21,9 @@ module VagrantPlugins
options[:force] = f
end
o.on("-a", "--architecture ARCH", String, "The specific architecture for the box to remove") do |a|
options[:architecture] = a
end
o.on("--provider PROVIDER", String,
"The specific provider type for the box to remove") do |p|
options[:provider] = p
@ -34,6 +37,14 @@ module VagrantPlugins
o.on("--all", "Remove all available versions of the box") do |a|
options[:all] = a
end
o.on("--all-providers", "Remove all providers within a version of the box") do |a|
options[:all_providers] = a
end
o.on("--all-architectures", "Remove all architectures within a provider a version of the box") do |a|
options[:all_architectures] = a
end
end
# Parse the options
@ -54,10 +65,13 @@ module VagrantPlugins
@env.action_runner.run(Vagrant::Action.action_box_remove, {
box_name: argv[0],
box_architecture: options[:architecture],
box_provider: options[:provider],
box_version: options[:version],
force_confirm_box_remove: options[:force],
box_remove_all_versions: options[:all],
box_remove_all_providers: options[:all_providers],
box_remove_all_architectures: options[:all_architectures]
})
# Success, exit status 0

View File

@ -32,6 +32,10 @@ module VagrantPlugins
options[:box] = b
end
o.on("--architecture ARCHITECTURE", String, "Update box with specific architecture") do |a|
options[:architecture] = a
end
o.on("--provider PROVIDER", String, "Update box with specific provider") do |p|
options[:provider] = p.to_sym
end
@ -47,7 +51,7 @@ module VagrantPlugins
return if !argv
if options[:box]
update_specific(options[:box], options[:provider], download_options, options[:force])
update_specific(options[:box], options[:provider], options[:architecture], download_options, options[:force])
else
update_vms(argv, options[:provider], download_options, options[:force])
end
@ -55,41 +59,62 @@ module VagrantPlugins
0
end
def update_specific(name, provider, download_options, force)
boxes = {}
@env.boxes.all.each do |n, v, p|
boxes[n] ||= {}
boxes[n][p] ||= []
boxes[n][p] << v
def update_specific(name, provider, architecture, download_options, force)
box_info = Vagrant::Util::HashWithIndifferentAccess.new
@env.boxes.all.each do |box_name, box_version, box_provider, box_architecture|
next if name != box_name
box_info[box_provider] ||= Vagrant::Util::HashWithIndifferentAccess.new
box_info[box_provider][box_version] ||= []
box_info[box_provider][box_version].push(box_architecture.to_s).uniq!
end
if !boxes[name]
if box_info.empty?
raise Vagrant::Errors::BoxNotFound, name: name.to_s
end
if !provider
if boxes[name].length > 1
if box_info.size > 1
raise Vagrant::Errors::BoxUpdateMultiProvider,
name: name.to_s,
providers: boxes[name].keys.map(&:to_s).sort.join(", ")
providers: box_info.keys.map(&:to_s).sort.join(", ")
end
provider = boxes[name].keys.first
elsif !boxes[name][provider]
provider = box_info.keys.first
elsif !box_info[provider]
raise Vagrant::Errors::BoxNotFoundWithProvider,
name: name.to_s,
provider: provider.to_s,
providers: boxes[name].keys.map(&:to_s).sort.join(", ")
providers: box_info.keys.map(&:to_s).sort.join(", ")
end
to_update = [
[name, provider, boxes[name][provider].sort_by{|n| Gem::Version.new(n)}.last],
]
version = box_info[provider].keys.sort_by{ |v| Gem::Version.new(v) }.last
architecture_list = box_info[provider][version]
to_update.each do |n, p, v|
box = @env.boxes.find(n, p, v)
box_update(box, "> #{v}", @env.ui, download_options, force)
if !architecture
if architecture_list.size > 1
raise Vagrant::Errors::BoxUpdateMultiArchitecture,
name: name.to_s,
provider: provider.to_s,
version: version.to_s,
architectures: architecture_list.sort.join(", ")
end
architecture = architecture_list.first
elsif !architecture_list.include?(architecture)
raise Vagrant::Errors::BoxNotFoundWithProviderArchitecture,
name: name.to_s,
provider: provider.to_s,
version: version.to_s,
architecture: architecture,
architectures: architecture_list.sort.join(", ")
end
# Architecture gets cast to a string when collecting information
# above. Convert it back to a nil if it's empty
architecture = nil if architecture.to_s.empty?
box = @env.boxes.find(name, provider, version, architecture)
box_update(box, "> #{version}", @env.ui, download_options, force)
end
def update_vms(argv, provider, download_options, force)
@ -146,6 +171,7 @@ module VagrantPlugins
ui.detail("Latest installed version: #{box.version}")
ui.detail("Version constraints: #{version}")
ui.detail("Provider: #{box.provider}")
ui.detail("Architecture: #{box.architecture.inspect}") if box.architecture
update = box.has_update?(version, download_options: download_options)
if !update
@ -165,6 +191,7 @@ module VagrantPlugins
box_url: box.metadata_url,
box_provider: update[2].name,
box_version: update[1].version,
box_architecture: update[2].architecture,
ui: ui,
box_force: force,
box_download_client_cert: download_options[:client_cert],

View File

@ -11,7 +11,12 @@ module VagrantPlugins
include Util
def execute
options = {quiet: true, versions: []}
options = {
architectures: [],
providers: [],
quiet: true,
versions: [],
}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant cloud box show [options] organization/box-name"
@ -21,8 +26,14 @@ module VagrantPlugins
o.separator "Options:"
o.separator ""
o.on("--architectures ARCH", String, "Filter results by architecture support (can be defined multiple times)") do |a|
options[:architectures].push(a).uniq!
end
o.on("--versions VERSION", String, "Display box information for a specific version (can be defined multiple times)") do |v|
options[:versions] << v
options[:versions].push(v).uniq!
end
o.on("--providers PROVIDER", String, "Filter results by provider support (can be defined multiple times)") do |pv|
options[:providers].push(pv).uniq!
end
o.on("--[no-]auth", "Authenticate with Vagrant Cloud if required before searching") do |l|
options[:quiet] = !l
@ -40,7 +51,7 @@ module VagrantPlugins
@client = client_login(@env, options.slice(:quiet))
org, box_name = argv.first.split('/', 2)
show_box(org, box_name, @client&.token, options.slice(:versions))
show_box(org, box_name, @client&.token, options.slice(:architectures, :providers, :versions))
end
# Display the requested box to the user
@ -57,26 +68,61 @@ module VagrantPlugins
access_token: access_token
)
with_box(account: account, org: org, box: box_name) do |box|
if box && !Array(options[:versions]).empty?
box = box.versions.find_all{ |v| options[:versions].include?(v.version) }
else
box = [box]
end
list = [box]
if !box.empty?
box.each do |b|
format_box_results(b, @env)
# If specific version(s) provided, filter out the version
list = list.first.versions.find_all { |v|
options[:versions].include?(v.version)
} if !Array(options[:versions]).empty?
# If specific provider(s) provided, filter out the provider(s)
list = list.find_all { |item|
if item.is_a?(VagrantCloud::Box)
item.versions.any? { |v|
v.providers.any? { |p|
options[:providers].include?(p.name)
}
}
else
item.providers.any? { |p|
options[:providers].include?(p.name)
}
end
} if !Array(options[:providers]).empty?
list = list.find_all { |item|
if item.is_a?(VagrantCloud::Box)
item.versions.any? { |v|
v.providers.any? { |p|
options[:architectures].include?(p.architecture)
}
}
else
item.providers.any? { |p|
options[:architectures].include?(p.architecture)
}
end
} if !Array(options[:architectures]).empty?
if !list.empty?
list.each do |b|
format_box_results(b, @env, options.slice(:providers, :architectures))
@env.ui.output("")
end
0
else
@env.ui.warn(I18n.t("cloud_command.box.show_filter_empty",
version: options[:version], org: org, box_name: box_name))
org: org,
box_name: box_name,
architectures: Array(options[:architectures]).empty? ? "N/A" : Array(options[:architectures]).join(", "),
providers: Array(options[:providers]).empty? ? "N/A" : Array(options[:providers]).join(", "),
versions: Array(options[:versions]).empty? ? "N/A" : Array(options[:versions]).join(", ")
))
1
end
end
rescue VagrantCloud::Error => e
@env.ui.error(I18n.t("cloud_command.errors.box.show_fail", org: org,box_name:box_name))
@env.ui.error(I18n.t("cloud_command.errors.box.show_fail", org: org, box_name:box_name))
@env.ui.error(e.message)
1
end

View File

@ -168,6 +168,11 @@ EOH
def with_error_handling(&block)
yield
rescue VagrantCloud::Error::ClientError => e
@logger.debug("vagrantcloud request error:")
@logger.debug(e.message)
@logger.debug(e.backtrace.join("\n"))
raise Errors::Unexpected, error: e.message
rescue Excon::Error::Unauthorized
@logger.debug("Unauthorized!")
raise Errors::Unauthorized

View File

@ -39,13 +39,25 @@ en:
Box Description: %{description}
box_short_desc: |-
Box Short Description: %{short_description}
checksum_type: |-
Checksum Type: %{checksum_type}
checksum_value: |-
Checksum Value: %{checksum_value}
architecture: |-
Box Architecture: %{architecture}
default_architecture: |-
Default Architecture: true
version_desc: |-
Version Description: %{version_description}
continue: |-
Do you wish to continue? [y/N]
box:
show_filter_empty: |-
No version matched %{version} for %{org}/%{box_name}
No matches found for %{org}/%{box_name}
Filters applied:
Architectures: %{architectures}
Providers: %{providers}
Versions: %{versions}
create_success: |-
Created box %{org}/%{box_name}
delete_success: |-
@ -68,12 +80,19 @@ en:
Uploading box file for '%{org}/%{box_name}' (v%{version}) for provider: '%{provider}'
upload_success: |-
Uploaded provider %{provider} on %{org}/%{box_name} for version %{version}
delete_multiple_architectures: |-
Multiple architectures detected for %{provider} on %{org}/%{box_name}:
delete_architectures_prompt: |-
Please enter the architecture name to delete:
delete_warn: |-
This will completely remove provider %{provider} on version %{version} from %{box} on Vagrant Cloud. This cannot be undone.
This will completely remove provider %{provider} with architecture %{architecture}
on version %{version} from %{box} on Vagrant Cloud. This cannot be undone.
create_success: |-
Created provider %{provider} on %{org}/%{box_name} for version %{version}
Created provider %{provider} with %{architecture} architecture on %{org}/%{box_name} for version %{version}
delete_success: |-
Deleted provider %{provider} on %{org}/%{box_name} for version %{version}
Deleted provider %{provider} with %{architecture} architecture on %{org}/%{box_name}
for version %{version}
update_success: |-
Updated provider %{provider} on %{org}/%{box_name} for version %{version}
not_found: |-
@ -122,13 +141,13 @@ en:
Failed to locate account information
provider:
create_fail: |-
Failed to create provider %{provider} on box %{org}/%{box_name} for version %{version}
Failed to create '%{architecture}' variant of %{provider} provider for version %{version} of the %{org}/%{box_name} box
update_fail: |-
Failed to update provider %{provider} on box %{org}/%{box_name} for version %{version}
Failed to update '%{architecture}' variant of %{provider} provider for version %{version} of the %{org}/%{box_name} box
delete_fail: |-
Failed to delete provider %{provider} on box %{org}/%{box_name} for version %{version}
Failed to delete '%{architecture}' variant of %{provider} provider for version %{version} of the %{org}/%{box_name} box
upload_fail: |-
Failed to upload provider %{provider} on box %{org}/%{box_name} for version %{version}
Failed to upload '%{architecture}' variant of %{provider} provider for version %{version} of the %{org}/%{box_name} box
version:
create_fail: |-
Failed to create version %{version} on box %{org}/%{box_name}

View File

@ -11,7 +11,9 @@ module VagrantPlugins
include Util
def execute
options = {}
options = {
architecture: Vagrant::Util::Platform.architecture,
}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant cloud provider create [options] organization/box-name provider-name version [url]"
@ -21,12 +23,18 @@ module VagrantPlugins
o.separator "Options:"
o.separator ""
o.on("-a", "--architecture ARCH", String, "Architecture of guest box (defaults to current host architecture)") do |a|
options[:architecture] = a
end
o.on("-c", "--checksum CHECKSUM_VALUE", String, "Checksum of the box for this provider. --checksum-type option is required.") do |c|
options[:checksum] = c
end
o.on("-C", "--checksum-type TYPE", String, "Type of checksum used (md5, sha1, sha256, sha384, sha512). --checksum option is required.") do |c|
options[:checksum_type] = c
end
o.on("--[no-]default-architecture", "Mark as default architecture for specific provider") do |d|
options[:default_architecture] = d
end
end
# Parse the options
@ -56,8 +64,10 @@ module VagrantPlugins
# @param [String] url Provider asset URL
# @param [String] access_token User Vagrant Cloud access token
# @param [Hash] options
# @option options [String] :architecture Architecture of guest box
# @option options [String] :checksum Checksum of the box asset
# @option options [String] :checksum_type Type of the checksum
# @option options [Boolean] :default_architecture Default architecture for named provider
# @return [Integer]
def create_provider(org, box, version, provider, url, access_token, options={})
if !url
@ -68,21 +78,23 @@ module VagrantPlugins
access_token: access_token
)
with_version(account: account, org: org, box: box, version: version) do |version|
provider = version.add_provider(provider)
provider = version.add_provider(provider, options[:architecture])
provider.checksum = options[:checksum] if options.key?(:checksum)
provider.checksum_type = options[:checksum_type] if options.key?(:checksum_type)
provider.architecture = options[:architecture] if options.key?(:architecture)
provider.default_architecture = options[:default_architecture] if options.key?(:default_architecture)
provider.url = url if url
provider.save
@env.ui.success(I18n.t("cloud_command.provider.create_success",
provider: provider.name, org: org, box_name: box, version: version.version))
architecture: options[:architecture], provider: provider.name, org: org, box_name: box, version: version.version))
format_box_results(provider, @env)
0
end
rescue VagrantCloud::Error => e
@env.ui.error(I18n.t("cloud_command.errors.provider.create_fail",
provider: provider, org: org, box_name: box, version: version))
architecture: options[:architecture], provider: provider.name, org: org, box_name: box, version: version))
@env.ui.error(e.message)
1
end

View File

@ -14,7 +14,7 @@ module VagrantPlugins
options = {}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant cloud provider delete [options] organization/box-name provider-name version"
o.banner = "Usage: vagrant cloud provider delete [options] organization/box-name provider-name version [architecture]"
o.separator ""
o.separator "Deletes a provider entry on Vagrant Cloud"
o.separator ""
@ -28,7 +28,7 @@ module VagrantPlugins
# Parse the options
argv = parse_options(opts)
return if !argv
if argv.count != 3
if argv.count < 3 || argv.count > 4
raise Vagrant::Errors::CLIInvalidUsage,
help: opts.help.chomp
end
@ -36,18 +36,47 @@ module VagrantPlugins
org, box_name = argv.first.split('/', 2)
provider_name = argv[1]
version = argv[2]
architecture = argv[3]
@client = client_login(@env)
account = VagrantCloud::Account.new(
custom_server: api_server_url,
access_token: @client.token
)
if architecture.nil?
architecture = select_provider_architecture(account, org, box_name, version, provider_name)
end
@env.ui.warn(I18n.t("cloud_command.provider.delete_warn",
provider: provider_name, version:version, box: argv.first))
architecture: architecture, provider: provider_name, version: version, box: argv.first))
if !options[:force]
cont = @env.ui.ask(I18n.t("cloud_command.continue"))
return 1 if cont.strip.downcase != "y"
end
@client = client_login(@env)
delete_provider(org, box_name, version, provider_name, architecture, account, options)
end
delete_provider(org, box_name, version, provider_name, @client.token, options)
def select_provider_architecture(account, org, box, version, provider)
with_version(account: account, org: org, box: box, version: version) do |box_version|
list = box_version.providers.map(&:architecture)
return list.first if list.size == 1
@env.ui.info(I18n.t("cloud_command.provider.delete_multiple_architectures",
org: org, box_name: box, provider: provider))
list.each do |provider_name|
@env.ui.info(" * #{provider_name}")
end
selected = nil
while selected.nil?
user_input = @env.ui.ask(I18n.t("cloud_command.provider.delete_architectures_prompt") + " ")
selected = user_input if list.include?(user_input)
end
return selected
end
end
# Delete a provider for the box version
@ -56,23 +85,20 @@ module VagrantPlugins
# @param [String] box Box name
# @param [String] version Box version
# @param [String] provider Provider name
# @param [String] access_token User Vagrant Cloud access token
# @param [String] architecture Architecture of guest
# @param [VagrantCloud::Account] account VagrantCloud account
# @param [Hash] options Currently unused
# @return [Integer]
def delete_provider(org, box, version, provider, access_token, options={})
account = VagrantCloud::Account.new(
custom_server: api_server_url,
access_token: access_token
)
with_provider(account: account, org: org, box: box, version: version, provider: provider) do |p|
def delete_provider(org, box, version, provider, architecture, account, options={})
with_provider(account: account, org: org, box: box, version: version, provider: provider, architecture: architecture) do |p|
p.delete
@env.ui.error(I18n.t("cloud_command.provider.delete_success",
provider: provider, org: org, box_name: box, version: version))
architecture: architecture, provider: provider, org: org, box_name: box, version: version))
0
end
rescue VagrantCloud::Error => e
@env.ui.error(I18n.t("cloud_command.errors.provider.delete_fail",
provider: provider, org: org, box_name: box, version: version))
architecture: architecture, provider: provider, org: org, box_name: box, version: version))
@env.ui.error(e)
1
end

View File

@ -14,25 +14,31 @@ module VagrantPlugins
options = {}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant cloud provider update [options] organization/box-name provider-name version [url]"
o.banner = "Usage: vagrant cloud provider update [options] organization/box-name provider-name version architecture [url]"
o.separator ""
o.separator "Updates a provider entry on Vagrant Cloud"
o.separator ""
o.separator "Options:"
o.separator ""
o.on("-a", "--architecture ARCH", String, "Update architecture value of guest box") do |a|
options[:architecture] = a
end
o.on("-c", "--checksum CHECKSUM_VALUE", String, "Checksum of the box for this provider. --checksum-type option is required.") do |c|
options[:checksum] = c
end
o.on("-C", "--checksum-type TYPE", String, "Type of checksum used (md5, sha1, sha256, sha384, sha512). --checksum option is required.") do |c|
options[:checksum_type] = c
end
o.on("--[no-]default-architecture", "Mark as default architecture for specific provider") do |d|
options[:default_architecture] = d
end
end
# Parse the options
argv = parse_options(opts)
return if !argv
if argv.count < 3 || argv.count > 4
if argv.count < 4 || argv.count > 5
raise Vagrant::Errors::CLIInvalidUsage,
help: opts.help.chomp
end
@ -42,9 +48,10 @@ module VagrantPlugins
org, box_name = argv.first.split('/', 2)
provider_name = argv[1]
version = argv[2]
url = argv[3]
architecture = argv[3]
url = argv[4]
update_provider(org, box_name, version, provider_name, url, @client.token, options)
update_provider(org, box_name, version, provider_name, architecture, url, @client.token, options)
end
# Update a provider for the box version
@ -53,12 +60,13 @@ module VagrantPlugins
# @param [String] box Box name
# @param [String] version Box version
# @param [String] provider Provider name
# @param [String] architecture Architecture of guest
# @param [String] access_token User Vagrant Cloud access token
# @param [Hash] options
# @option options [String] :checksum Checksum of the box asset
# @option options [String] :checksum_type Type of the checksum
# @return [Integer]
def update_provider(org, box, version, provider, url, access_token, options)
def update_provider(org, box, version, provider, architecture, url, access_token, options)
if !url
@env.ui.warn(I18n.t("cloud_command.upload.no_url"))
end
@ -67,21 +75,23 @@ module VagrantPlugins
access_token: access_token
)
with_provider(account: account, org: org, box: box, version: version, provider: provider) do |p|
with_provider(account: account, org: org, box: box, version: version, provider: provider, architecture: architecture) do |p|
p.checksum = options[:checksum] if options.key?(:checksum)
p.checksum_type = options[:checksum_type] if options.key?(:checksum_type)
p.architecture = options[:architecture] if options.key?(:architecture)
p.default_architecture = options[:default_architecture] if options.key?(:default_architecture)
p.url = url if !url.nil?
p.save
@env.ui.success(I18n.t("cloud_command.provider.update_success",
provider: provider, org: org, box_name: box, version: version))
architecture: architecture, provider: provider, org: org, box_name: box, version: version))
format_box_results(p, @env)
0
end
rescue VagrantCloud::Error => e
@env.ui.error(I18n.t("cloud_command.errors.provider.update_fail",
provider: provider, org: org, box_name: box, version: version))
architecture: architecture, provider: provider, org: org, box_name: box, version: version))
@env.ui.error(e.message)
1
end

View File

@ -11,7 +11,10 @@ module VagrantPlugins
include Util
def execute
options = {direct_upload: true}
options = {
architecture: Vagrant::Util::Platform.architecture,
direct_upload: true,
}
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant cloud publish [options] organization/box-name version provider-name [provider-file]"
@ -21,8 +24,8 @@ module VagrantPlugins
o.separator "Options:"
o.separator ""
o.on("--box-version VERSION", String, "Version of box to create") do |v|
options[:box_version] = v
o.on("-a", "--architecture ARCH", String, "Architecture of guest box (defaults to current host architecture)") do |a|
options[:architecture] = a
end
o.on("--url URL", String, "Remote URL to download this provider (cannot be used with provider-file)") do |u|
options[:url] = u
@ -54,6 +57,9 @@ module VagrantPlugins
o.on("--[no-]direct-upload", "Upload asset directly to backend storage") do |d|
options[:direct_upload] = d
end
o.on("--[no-]default-architecture", "Mark as default architecture for specific provider") do |d|
options[:default_architecture] = d
end
end
# Parse the options
@ -63,7 +69,7 @@ module VagrantPlugins
if argv.length < 3 || # missing required arguments
argv.length > 4 || # too many arguments
(argv.length < 4 && !options.key?(:url)) || # file argument required if url is not provided
(argv.length > 3 && options.key?(:url)) # cannot provider url and file argument
(argv.length > 3 && options.key?(:url)) # cannot provide url and file argument
raise Vagrant::Errors::CLIInvalidUsage,
help: opts.help.chomp
end
@ -78,7 +84,8 @@ module VagrantPlugins
@client = client_login(@env)
params = options.slice(:private, :release, :url, :short_description,
:description, :version_description, :checksum, :checksum_type)
:description, :version_description, :checksum, :checksum_type,
:architecture, :default_architecture)
# Display output to user describing action to be taken
display_preamble(org, box_name, version, provider_name, params)
@ -91,12 +98,17 @@ module VagrantPlugins
# Load up all the models we'll need to publish the asset
box = load_box(org, box_name, @client.token)
box_v = load_box_version(box, version)
box_p = load_version_provider(box_v, provider_name)
box_p = load_version_provider(box_v, provider_name, params[:architecture])
# Update all the data
set_box_info(box, params.slice(:private, :short_description, :description))
set_version_info(box_v, params.slice(:version_description))
set_provider_info(box_p, params.slice(:checksum, :checksum_type, :url))
set_provider_info(box_p, params.slice(
:architecture,
:checksum,
:checksum_type,
:default_architecture,
:url))
# Save any updated state
@env.ui.warn(I18n.t("cloud_command.publish.box_save"))
@ -114,7 +126,7 @@ module VagrantPlugins
# And we're done!
@env.ui.success(I18n.t("cloud_command.publish.complete", org: org, box_name: box_name))
format_box_results(box, @env)
format_box_results(box_p, @env)
0
rescue VagrantCloud::Error => err
@env.ui.error(I18n.t("cloud_command.errors.publish.fail", org: org, box_name: box_name))
@ -188,14 +200,18 @@ module VagrantPlugins
#
# @param [VagrantCloud::Box::Provider] provider Vagrant Cloud box version provider
# @param [Hash] options
# @option options [String] architecture Guest architecture of box
# @option options [String] :url Remote URL for self hosted
# @option options [String] :checksum_type Type of checksum value provided
# @option options [String] :checksum Checksum of the box asset
# @option options [Boolean] :default_architecture Default architecture for named provider
# @return [VagrantCloud::Box::Provider]
def set_provider_info(provider, options={})
provider.url = options[:url] if options.key?(:url)
provider.checksum_type = options[:checksum_type] if options.key?(:checksum_type)
provider.checksum = options[:checksum] if options.key?(:checksum)
provider.architecture = options[:architecture] if options.key?(:architecture)
provider.default_architecture = options[:default_architecture] if options.key?(:default_architecture)
provider
end
@ -204,10 +220,13 @@ module VagrantPlugins
# @param [VagrantCloud::Box::Version] version The version of the Vagrant Cloud box
# @param [String] provider_name Name of the provider
# @return [VagrantCloud::Box::Provider]
def load_version_provider(version, provider_name)
provider = version.providers.detect { |pv| pv.name == provider_name }
def load_version_provider(version, provider_name, architecture)
provider = version.providers.detect { |pv|
pv.name == provider_name &&
pv.architecture == architecture
}
return provider if provider
version.add_provider(provider_name)
version.add_provider(provider_name, architecture)
end
# Load the requested box version
@ -244,9 +263,11 @@ module VagrantPlugins
# @param [String] version Version of the box to publish
# @param [String] provider_name Name of the provider being published
# @param [Hash] options
# @option options [String] :architecture Name of architecture of provider being published#
# @option options [Boolean] :private Box is private
# @option options [Boolean] :release Box should be released
# @option options [String] :url Remote URL for self-hosted boxes
# @option options [Boolean] :default_architecture Architecture is default for provider name
# @option options [String] :description Description of the box
# @option options [String] :short_description Short description of the box
# @option options [String] :version_description Description of the box version
@ -257,6 +278,9 @@ module VagrantPlugins
box_name: box_name, version: version, provider_name: provider_name))
@env.ui.info(I18n.t("cloud_command.publish.confirm.private")) if options[:private]
@env.ui.info(I18n.t("cloud_command.publish.confirm.release")) if options[:release]
@env.ui.info(I18n.t("cloud_command.publish.confirm.architecture",
architecture: options[:architecture]))
@env.ui.info(I18n.t("cloud_command.publish.confirm.default_architecture")) if options[:default_architecture]
@env.ui.info(I18n.t("cloud_command.publish.confirm.box_url",
url: options[:url])) if options[:url]
@env.ui.info(I18n.t("cloud_command.publish.confirm.box_description",

View File

@ -21,6 +21,9 @@ module VagrantPlugins
o.separator "Options:"
o.separator ""
o.on("-a", "--architecture ARCH", "Filter search results to a single architecture. Defaults to all.") do |a|
options[:architecture] = a
end
o.on("-j", "--json", "Formats results in JSON") do |j|
options[:json] = j
end
@ -78,7 +81,7 @@ module VagrantPlugins
custom_server: api_server_url,
access_token: access_token
)
params = {query: query}.merge(options.slice(:provider, :sort, :order, :limit, :page))
params = {query: query}.merge(options.slice(:architecture, :provider, :sort, :order, :limit, :page))
result = account.searcher.search(**params)
if result.boxes.empty?

View File

@ -102,7 +102,8 @@ module VagrantPlugins
name: b.tag,
version: b.current_version.version,
downloads: format_downloads(b.downloads.to_s),
providers: b.current_version.providers.map(&:name).join(", ")
providers: b.current_version.providers.map(&:name).uniq.join(", "),
architectures: b.current_version.providers.map(&:architecture).join(", ")
}
end
@ -126,19 +127,26 @@ module VagrantPlugins
# @param [VagrantCloud::Box, VagrantCloud::Box::Version] box Box or box version to display
# @param [Vagrant::Environment] env Current Vagrant environment
# @return [nil]
def format_box_results(box, env)
def format_box_results(box, env, options={})
if box.is_a?(VagrantCloud::Box)
info = box_info(box)
elsif box.is_a?(VagrantCloud::Box::Provider)
info = version_info(box.version)
else
info = box_info(box, options)
elsif box.is_a?(VagrantCloud::Box::Version)
info = version_info(box)
else
info = provider_info(box)
end
width = info.keys.map(&:size).max
info.each do |k, v|
whitespace = width - k.size
env.ui.info "#{k}: #{"".ljust(whitespace)} #{v}"
v.to_s.split("\n").each_with_index do |line, idx|
whitespace = width - k.size + line.to_s.size
if idx == 0
env.ui.info "#{k}: #{line.rjust(whitespace)}"
else
whitespace += k.size + 2
env.ui.info line.rjust(whitespace)
end
end
end
nil
end
@ -193,9 +201,12 @@ module VagrantPlugins
# @yieldparam [VagrantCloud::Box::Provider] provider Requested Vagrant Cloud box version provider
# @yieldreturn [Integer]
# @return [Integer]
def with_provider(account:, org:, box:, version:, provider:)
def with_provider(account:, org:, box:, version:, provider:, architecture:)
with_version(account: account, org: org, box: box, version: version) do |v|
p = v.providers.detect { |p| p.name == provider }
p = v.providers.detect { |p|
p.name == provider &&
p.architecture == architecture
}
if !p
@env.ui.error(I18n.t("cloud_command.provider.not_found",
org: org, box_name: box, version: version, provider_name: provider))
@ -211,19 +222,42 @@ module VagrantPlugins
#
# @param [VagrantCloud::Box] box Box for extracting information
# @return [Hash<String,String>]
def box_info(box)
def box_info(box, options={})
current_version = box.current_version
if current_version
current_version = nil if !Array(options[:providers]).empty? &&
current_version.providers.none? { |p| options[:providers].include?(p.name) }
current_version = nil if !Array(options[:architectures]).empty? &&
current_version.providers.none? { |p| options[:architectures].include?(p.architecture) }
end
versions = box.versions
# Apply provider filter if defined
versions = versions.find_all { |v|
v.providers.any? { |p|
options[:providers].include?(p.name)
}
} if !Array(options[:providers]).empty?
# Apply architecture filter if defined
versions = versions.find_all { |v|
v.providers.any? { |p|
options[:architectures].include?(p.architecture)
}
} if !Array(options[:architectures]).empty?
raise "no matches" if current_version.nil? && versions.empty?
Hash.new.tap do |i|
i["Box"] = box.tag
i["Description"] = box.description
i["Private"] = box.private ? "yes" : "no"
i["Created"] = box.created_at
i["Updated"] = box.updated_at
if !box.current_version.nil?
if !current_version.nil?
i["Current Version"] = box.current_version.version
else
i["Current Version"] = "N/A"
end
i["Versions"] = box.versions.slice(0, 5).map(&:version).join(", ")
i["Versions"] = versions.slice(0, 5).map(&:version).join(", ")
if box.versions.size > 5
i["Versions"] += " ..."
end
@ -236,17 +270,35 @@ module VagrantPlugins
# @param [VagrantCloud::Box::Version] version Box version for extracting information
# @return [Hash<String,String>]
def version_info(version)
provider_arches = version.providers.group_by(&:name).map { |provider_name, info|
"#{provider_name} (#{info.map(&:architecture).sort.join(", ")})"
}.sort.join("\n")
Hash.new.tap do |i|
i["Box"] = version.box.tag
i["Version"] = version.version
i["Description"] = version.description
i["Status"] = version.status
i["Providers"] = version.providers.map(&:name).sort.join(", ")
i["Providers"] = provider_arches
i["Created"] = version.created_at
i["Updated"] = version.updated_at
end
end
# Extract provider information for display
#
# @param [VagrantCloud::Box::Provider] provider Box provider for extracting information
# @return [Hash<String,String>]
def provider_info(provider)
{
"Box" => provider.version.box.tag,
"Private" => provider.version.box.private ? "yes" : "no",
"Version" => provider.version.version,
"Provider" => provider.name,
"Architecture" => provider.architecture,
"Default Architecture" => provider.default_architecture ? "yes" : "no",
}
end
# Print table results from search request
#
# @param [Vagrant::Environment] env Current Vagrant environment

View File

@ -32,6 +32,7 @@ module VagrantPlugins
attr_accessor :base_address
attr_accessor :boot_timeout
attr_accessor :box
attr_accessor :box_architecture
attr_accessor :ignore_box_vagrantfile
attr_accessor :box_check_update
attr_accessor :box_url
@ -69,6 +70,7 @@ module VagrantPlugins
@base_address = UNSET_VALUE
@boot_timeout = UNSET_VALUE
@box = UNSET_VALUE
@box_architecture = UNSET_VALUE
@ignore_box_vagrantfile = UNSET_VALUE
@box_check_update = UNSET_VALUE
@box_download_ca_cert = UNSET_VALUE
@ -509,6 +511,11 @@ module VagrantPlugins
@base_address = nil if @base_address == UNSET_VALUE
@boot_timeout = 300 if @boot_timeout == UNSET_VALUE
@box = nil if @box == UNSET_VALUE
@box_architecture = :auto if @box_architecture == UNSET_VALUE
# If box architecture value was set, force to string
if @box_architecture && @box_architecture != :auto
@box_architecture = @box_architecture.to_s
end
@ignore_box_vagrantfile = false if @ignore_box_vagrantfile == UNSET_VALUE
if @box_check_update == UNSET_VALUE

View File

@ -659,6 +659,13 @@ en:
the box are shown below:
%{providers}
box_not_found_with_provider_architecture: |-
The box '%{name}' for the provider '%{provider}' isn't installed
for the architecture '%{architecture}'. Please double-check and
try again. The installed architectures for the box are shown
below:
%{architectures}
box_not_found_with_provider_and_version: |-
The box '%{name}' (v%{version}) with provider '%{provider}'
could not be found. Please double check and try again. You
@ -669,35 +676,51 @@ en:
Provider expected: %{expected}
Provider of box: %{actual}
box_remove_multi_provider: |-
You requested to remove the box '%{name}'. This box has
multiple providers. You must explicitly select a single
provider to remove with `--provider`.
You requested to remove the box '%{name}' version '%{version}'. This
box has multiple providers. You must explicitly select a single provider to
remove with `--provider` or specify the `--all-providers` flag to remove
all providers.
Available providers: %{providers}
box_remove_multi_version: |-
You requested to remove the box '%{name}' with provider
'%{provider}'. This box has multiple versions. You must
explicitly specify which version you want to remove with
the `--box-version` flag or specify the `--all` flag to remove all
versions. The available versions for this box are:
You requested to remove the box '%{name}'. This box has multiple
versions. You must explicitly specify which version you want to
remove with the `--box-version` flag or specify the `--all` flag
to remove all versions. The available versions for this box are:
%{versions}
box_remove_not_found: |-
The box you requested to be removed could not be found. No
boxes named '%{name}' could be found.
box_remove_provider_not_found: |-
You requested to remove the box '%{name}' with provider
'%{provider}'. The box '%{name}' exists but not with
the provider specified. Please double-check and try again.
You requested to remove the box '%{name}' version '%{version}'
with provider '%{provider}'. The box '%{name}' exists but not
with the provider specified. Please double-check and try again.
The providers for this are: %{providers}
box_remove_version_not_found: |-
You requested to remove the box '%{name}' version '%{version}' with
provider '%{provider}', but that specific version of the box is
not installed. Please double-check and try again. The available versions
for this box are:
You requested to remove the box '%{name}' version '%{version}',
but that specific version of the box is not installed. Please
double-check and try again. The available versions for this box
are:
%{versions}
box_remove_multi_architecture: |-
You requested to remove the box '%{name}' version '%{version}'
with provider '%{provider}'. This box has multiple architectures.
You must explicitly select a single architecture to remove with
`--architecture` or specify the `--all-architectures` flag to
remove all architectures. The available architectures for this
box are:
%{architectures}
box_remove_architecture_not_found: |-
You requested to remove the box '%{name}' version '%{version}'
with provider '%{provider}' and architecture '%{architecture}' but
that specific architecture is not installed. Please double-check
and try again. The available architectures are:
%{architectures}
box_server_not_set: |-
A URL to a Vagrant Cloud server is not set, so boxes cannot be added with a
shorthand ("mitchellh/precise64") format. You may also be seeing this
@ -710,9 +733,15 @@ en:
box_update_multi_provider: |-
You requested to update the box '%{name}'. This box has
multiple providers. You must explicitly select a single
provider to remove with `--provider`.
provider to update with `--provider`.
Available providers: %{providers}
box_update_multi_architecture: |-
You requested to update the box '%{name}' (v%{version}) with provider
'%{provider}'. This box has multiple architectures. You must explicitly
select a single architecture to update with `--architecture`.
Available architectures: %{architectures}
box_update_no_box: |-
Box '%{name}' not installed, can't check for updates.
box_update_no_metadata: |-
@ -2188,10 +2217,10 @@ en:
box:
no_installed_boxes: "There are no installed boxes! Use `vagrant box add` to add some."
remove_in_use_query: |-
Box '%{name}' (v%{version}) with provider '%{provider}' appears
to still be in use by at least one Vagrant environment. Removing
the box could corrupt the environment. We recommend destroying
these environments first:
Box '%{name}' (v%{version}) with provider '%{provider}' and
architectures '%{architecture}' appears to still be in use by
at least one Vagrant environment. Removing the box could corrupt
the environment. We recommend destroying these environments first:
%{users}

View File

@ -34,7 +34,7 @@ Gem::Specification.new do |s|
s.add_dependency "rexml", "~> 3.2"
s.add_dependency "rgl", "~> 0.5.10"
s.add_dependency "rubyzip", "~> 2.3.2"
s.add_dependency "vagrant_cloud", "~> 3.0.5"
s.add_dependency "vagrant_cloud", "> 3.0.5", "< 3.1"
s.add_dependency "wdm", "~> 0.1.1"
s.add_dependency "winrm", ">= 2.3.6", "< 3.0"
s.add_dependency "winrm-elevated", ">= 1.2.3", "< 2.0"